# HG changeset patch # User David Demelier # Date 1512556964 -3600 # Node ID 35832b7f4f9d18ebb3ba1e1461c0cdfeff18dccc # Parent 029667d16d1242b98203722fae40384e22e60c4d Irccd: move files to daemon folder, closes #731 diff -r 029667d16d12 -r 35832b7f4f9d irccd/main.cpp --- a/irccd/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/irccd/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -33,17 +33,18 @@ #include #include -#include -#include -#include #include #include -#include -#include -#include #include #include -#include + +#include +#include +#include +#include +#include +#include +#include #if defined(HAVE_JS) # include diff -r 029667d16d12 -r 35832b7f4f9d libirccd-js/irccd/js/plugin_jsapi.cpp --- a/libirccd-js/irccd/js/plugin_jsapi.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd-js/irccd/js/plugin_jsapi.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -16,8 +16,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -#include +#include +#include #include "irccd_jsapi.hpp" #include "js_plugin.hpp" diff -r 029667d16d12 -r 35832b7f4f9d libirccd-js/irccd/js/server_jsapi.cpp --- a/libirccd-js/irccd/js/server_jsapi.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd-js/irccd/js/server_jsapi.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -20,8 +20,8 @@ #include #include -#include -#include +#include +#include #include "duktape_vector.hpp" #include "irccd_jsapi.hpp" diff -r 029667d16d12 -r 35832b7f4f9d libirccd-js/irccd/js/timer_jsapi.cpp --- a/libirccd-js/irccd/js/timer_jsapi.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd-js/irccd/js/timer_jsapi.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -18,9 +18,10 @@ #include -#include #include +#include + #include "irccd_jsapi.hpp" #include "js_plugin.hpp" #include "plugin_jsapi.hpp" diff -r 029667d16d12 -r 35832b7f4f9d libirccd-test/irccd/command_test.hpp --- a/libirccd-test/irccd/command_test.hpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd-test/irccd/command_test.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -23,10 +23,10 @@ #include -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d libirccd-test/irccd/js_test.hpp --- a/libirccd-test/irccd/js_test.hpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd-test/irccd/js_test.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -28,7 +28,7 @@ #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d libirccd-test/irccd/plugin_test.cpp --- a/libirccd-test/irccd/plugin_test.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd-test/irccd/plugin_test.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,9 @@ #include #include -#include -#include + +#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d libirccd/CMakeLists.txt --- a/libirccd/CMakeLists.txt Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccd/CMakeLists.txt Wed Dec 06 11:42:44 2017 +0100 @@ -22,46 +22,46 @@ set( HEADERS - ${libirccd_SOURCE_DIR}/irccd/basic_transport_client.hpp - ${libirccd_SOURCE_DIR}/irccd/basic_transport_server.hpp - ${libirccd_SOURCE_DIR}/irccd/command.hpp - ${libirccd_SOURCE_DIR}/irccd/command_service.hpp - ${libirccd_SOURCE_DIR}/irccd/config.hpp - ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.hpp - ${libirccd_SOURCE_DIR}/irccd/ip_transport_server.hpp - ${libirccd_SOURCE_DIR}/irccd/irccd.hpp - ${libirccd_SOURCE_DIR}/irccd/irc.hpp - ${libirccd_SOURCE_DIR}/irccd/local_transport_server.hpp - ${libirccd_SOURCE_DIR}/irccd/plugin.hpp - ${libirccd_SOURCE_DIR}/irccd/plugin_service.hpp - ${libirccd_SOURCE_DIR}/irccd/rule.hpp - ${libirccd_SOURCE_DIR}/irccd/rule_service.hpp - ${libirccd_SOURCE_DIR}/irccd/server.hpp - ${libirccd_SOURCE_DIR}/irccd/server_service.hpp - ${libirccd_SOURCE_DIR}/irccd/transport_client.hpp - ${libirccd_SOURCE_DIR}/irccd/transport_server.hpp - ${libirccd_SOURCE_DIR}/irccd/transport_service.hpp - $<$:${libirccd_SOURCE_DIR}/irccd/tls_transport_server.hpp> + ${libirccd_SOURCE_DIR}/irccd/daemon/basic_transport_client.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/basic_transport_server.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/command.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/command_service.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/config.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/ip_transport_server.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/irc.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/local_transport_server.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/rule.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/server.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.hpp + ${libirccd_SOURCE_DIR}/irccd/daemon/transport_service.hpp + $<$:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.hpp> ) set( SOURCES - ${libirccd_SOURCE_DIR}/irccd/command.cpp - ${libirccd_SOURCE_DIR}/irccd/command_service.cpp - ${libirccd_SOURCE_DIR}/irccd/config.cpp - ${libirccd_SOURCE_DIR}/irccd/dynlib_plugin.cpp - ${libirccd_SOURCE_DIR}/irccd/irccd.cpp - ${libirccd_SOURCE_DIR}/irccd/irc.cpp - ${libirccd_SOURCE_DIR}/irccd/plugin.cpp - ${libirccd_SOURCE_DIR}/irccd/plugin_service.cpp - ${libirccd_SOURCE_DIR}/irccd/rule.cpp - ${libirccd_SOURCE_DIR}/irccd/rule_service.cpp - ${libirccd_SOURCE_DIR}/irccd/server.cpp - ${libirccd_SOURCE_DIR}/irccd/server_service.cpp - ${libirccd_SOURCE_DIR}/irccd/transport_client.cpp - ${libirccd_SOURCE_DIR}/irccd/transport_server.cpp - ${libirccd_SOURCE_DIR}/irccd/transport_service.cpp - $<$:${libirccd_SOURCE_DIR}/irccd/tls_transport_server.cpp> + ${libirccd_SOURCE_DIR}/irccd/daemon/command.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/command_service.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/config.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/dynlib_plugin.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/irccd.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/irc.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/plugin.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/plugin_service.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/rule.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/rule_service.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/server.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/server_service.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/transport_client.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/transport_server.cpp + ${libirccd_SOURCE_DIR}/irccd/daemon/transport_service.cpp + $<$:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.cpp> ) irccd_define_library( @@ -76,7 +76,7 @@ $<$:resolv> libcommon PUBLIC_INCLUDES - $ + $ $ $ ) diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/basic_transport_client.hpp --- a/libirccd/irccd/basic_transport_client.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,102 +0,0 @@ -/* - * basic_transport_client.hpp -- simple socket transport client - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_BASIC_TRANSPORT_CLIENT_HPP -#define IRCCD_BASIC_TRANSPORT_CLIENT_HPP - -#include - -#include "transport_client.hpp" - -namespace irccd { - -/** - * \brief Transport client for sockets. - * - * This class implements do_recv/do_send for Boost.Asio based socket streams. - */ -template -class basic_transport_client : public transport_client { -private: - network_stream stream_; - -public: - /** - * Construct the client. - * - * \param parent the parent - * \param args the argument to pass to the network_stream - */ - template - inline basic_transport_client(transport_server& parent, Args&&... args) - : transport_client(parent) - , stream_(std::forward(args)...) - { - } - - /** - * Get the underlying stream. - * - * \return the stream - */ - inline const network_stream& stream() const noexcept - { - return stream_; - } - - /** - * Overloaded function. - * - * \return the stream - */ - inline network_stream& stream() noexcept - { - return stream_; - } - - /** - * \copydoc transport_client::do_recv - */ - void do_recv(network_recv_handler handler) override - { - assert(handler); - - auto self = shared_from_this(); - - stream_.recv([this, self, handler] (auto msg, auto code) { - handler(std::move(msg), std::move(code)); - }); - } - - /** - * \copydoc transport_client::do_send - */ - void do_send(nlohmann::json json, network_send_handler handler) override - { - auto self = shared_from_this(); - - stream_.send(std::move(json), [this, self, handler] (auto code) { - if (handler) - handler(std::move(code)); - }); - } -}; - -} // !irccd - -#endif // !IRCCD_BASIC_TRANSPORT_CLIENT_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/basic_transport_server.hpp --- a/libirccd/irccd/basic_transport_server.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,97 +0,0 @@ -/* - * basic_transport_server.hpp -- simple socket transport servers - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_BASIC_TRANSPORT_SERVER_HPP -#define IRCCD_BASIC_TRANSPORT_SERVER_HPP - -/** - * \file basic_transport_server.hpp - * \brief Simple socket transport servers. - */ - -#include "basic_transport_client.hpp" -#include "transport_server.hpp" - -namespace irccd { - -/** - * \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 -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 -basic_transport_server::basic_transport_server(acceptor_t acceptor) - : acceptor_(std::move(acceptor)) -{ - assert(acceptor_.is_open()); -} - -template -void basic_transport_server::do_accept(accept_t handler) -{ - auto client = std::make_shared>(*this, acceptor_.get_io_service()); - - acceptor_.async_accept(client->stream().socket(), [this, client, handler] (auto code) { - if (code) - handler(std::move(code), nullptr); - else - handler(std::move(code), std::move(client)); - }); -} - -} // !irccd - -#endif // !IRCCD_BASIC_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/command.cpp --- a/libirccd/irccd/command.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,729 +0,0 @@ -/* - * command.cpp -- implementation of plugin-config command - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "command.hpp" -#include "irccd.hpp" -#include "json_util.hpp" -#include "rule_service.hpp" -#include "plugin_service.hpp" -#include "server_service.hpp" -#include "transport_client.hpp" -#include "string_util.hpp" -#include "util.hpp" - -using namespace std::string_literals; - -namespace irccd { - -namespace { - -void exec_set(transport_client& client, plugin& plugin, const nlohmann::json& args) -{ - assert(args.count("value") > 0); - - auto var = args.find("variable"); - auto value = args.find("value"); - - if (var == args.end() || !var->is_string()) - throw irccd_error(irccd_error::error::incomplete_message); - if (value == args.end() || !value->is_string()) - throw irccd_error(irccd_error::error::incomplete_message); - - auto config = plugin.config(); - - config[*var] = *value; - plugin.set_config(config); - client.success("plugin-config"); -} - -void exec_get(transport_client& client, plugin& plugin, const nlohmann::json& args) -{ - auto variables = nlohmann::json::object(); - auto var = args.find("variable"); - - if (var != args.end() && var->is_string()) - variables[var->get()] = plugin.config()[*var]; - else - for (const auto& pair : plugin.config()) - variables[pair.first] = pair.second; - - /* - * Don't put all variables into the response, put them into a sub property - * 'variables' instead. - * - * It's easier for the client to iterate over all. - */ - client.send({ - { "command", "plugin-config" }, - { "variables", variables } - }); -} - -nlohmann::json to_json(const rule& rule) -{ - auto join = [] (const auto& set) { - auto array = nlohmann::json::array(); - - for (const auto& entry : set) - array.push_back(entry); - - return array; - }; - auto str = [] (auto action) { - switch (action) { - case rule::action_type::accept: - return "accept"; - default: - return "drop"; - } - }; - - return { - { "servers", join(rule.servers()) }, - { "channels", join(rule.channels()) }, - { "plugins", join(rule.plugins()) }, - { "events", join(rule.events()) }, - { "action", str(rule.action()) } - }; -} - -rule from_json(const nlohmann::json& json) -{ - auto toset = [] (auto object, auto name) { - rule::set result; - - for (const auto& s : object[name]) - if (s.is_string()) - result.insert(s.template get()); - - return result; - }; - auto toaction = [] (auto object, auto name) { - auto v = object[name]; - - if (!v.is_string()) - throw rule_error(rule_error::invalid_action); - - auto s = v.template get(); - if (s == "accept") - return rule::action_type::accept; - if (s == "drop") - return rule::action_type::drop; - - throw rule_error(rule_error::invalid_action); - }; - - return { - toset(json, "servers"), - toset(json, "channels"), - toset(json, "origins"), - toset(json, "plugins"), - toset(json, "events"), - toaction(json, "action") - }; -} - -unsigned get_rule_index(const nlohmann::json& json, const std::string& key = "index") -{ - auto index = json.find(key); - - if (index == json.end() || !index->is_number_integer() || index->get() < 0) - throw rule_error(rule_error::invalid_index); - - return index->get(); -} - -std::shared_ptr get_server(irccd& daemon, const nlohmann::json& args) -{ - auto id = json_util::get_string(args, "server"); - - if (!string_util::is_identifier(id)) - throw server_error(server_error::invalid_identifier, ""); - - auto server = daemon.servers().get(id); - - if (!server) - throw server_error(server_error::not_found, id); - - return server; -} - -} // !namespace - -plugin_config_command::plugin_config_command() - : command("plugin-config") -{ -} - -void plugin_config_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin")); - - if (args.count("value") > 0) - exec_set(client, *plugin, args); - else - exec_get(client, *plugin, args); -} - -plugin_info_command::plugin_info_command() - : command("plugin-info") -{ -} - -void plugin_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin")); - - client.send({ - { "command", "plugin-info" }, - { "author", plugin->author() }, - { "license", plugin->license() }, - { "summary", plugin->summary() }, - { "version", plugin->version() } - }); -} - -plugin_list_command::plugin_list_command() - : command("plugin-list") -{ -} - -void plugin_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&) -{ - auto list = nlohmann::json::array(); - - for (const auto& plugin : irccd.plugins().list()) - list += plugin->name(); - - client.send({ - { "command", "plugin-list" }, - { "list", list } - }); -} - -plugin_load_command::plugin_load_command() - : command("plugin-load") -{ -} - -void plugin_load_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - irccd.plugins().load(json_util::require_identifier(args, "plugin")); - client.success("plugin-load"); -} - -plugin_reload_command::plugin_reload_command() - : command("plugin-reload") -{ -} - -void plugin_reload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - irccd.plugins().reload(json_util::require_identifier(args, "plugin")); - client.success("plugin-reload"); -} - -plugin_unload_command::plugin_unload_command() - : command("plugin-unload") -{ -} - -void plugin_unload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - irccd.plugins().unload(json_util::require_identifier(args, "plugin")); - client.success("plugin-unload"); -} - -server_connect_command::server_connect_command() - : command("server-connect") -{ -} - -void server_connect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = server_service::from_json(irccd.service(), args); - - if (irccd.servers().has(server->name())) - throw server_error(server_error::error::already_exists, server->name()); - - irccd.servers().add(std::move(server)); - client.success("server-connect"); -} - -server_disconnect_command::server_disconnect_command() - : command("server-disconnect") -{ -} - -void server_disconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto it = args.find("server"); - - if (it == args.end()) - irccd.servers().clear(); - else { - if (!it->is_string()) - throw server_error(server_error::invalid_identifier, ""); - - auto name = it->get(); - auto s = irccd.servers().get(name); - - if (!s) - throw server_error(server_error::not_found, name); - - irccd.servers().remove(name); - } - - client.success("server-disconnect"); -} - -server_info_command::server_info_command() - : command("server-info") -{ -} - -void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto response = nlohmann::json::object(); - auto server = get_server(irccd, args); - - // 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()}); - response.push_back({"nickname", server->nickname()}); - response.push_back({"username", server->username()}); - response.push_back({"realname", server->realname()}); - response.push_back({"channels", server->channels()}); - - // Optional stuff. - if (server->flags() & server::ipv6) - response.push_back({"ipv6", true}); - if (server->flags() & server::ssl) - response.push_back({"ssl", true}); - if (server->flags() & server::ssl_verify) - response.push_back({"sslVerify", true}); - - client.send(response); -} - -server_invite_command::server_invite_command() - : command("server-invite") -{ -} - -void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto target = json_util::get_string(args, "target"); - auto channel = json_util::get_string(args, "channel"); - - if (target.empty()) - throw server_error(server_error::invalid_nickname, server->name()); - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->invite(target, channel); - client.success("server-invite"); -} - -server_join_command::server_join_command() - : command("server-join") -{ -} - -void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "channel"); - auto password = json_util::get_string(args, "password"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->join(channel, password); - client.success("server-join"); -} - -server_kick_command::server_kick_command() - : command("server-kick") -{ -} - -void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto target = json_util::get_string(args, "target"); - auto channel = json_util::get_string(args, "channel"); - auto reason = json_util::get_string(args, "reason"); - - if (target.empty()) - throw server_error(server_error::invalid_nickname, server->name()); - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->kick(target, channel, reason); - client.success("server-kick"); -} - -server_list_command::server_list_command() - : command("server-list") -{ -} - -void server_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&) -{ - auto json = nlohmann::json::object(); - auto list = nlohmann::json::array(); - - for (const auto& server : irccd.servers().servers()) - list.push_back(server->name()); - - client.send({ - { "command", "server-list" }, - { "list", std::move(list) } - }); -} - -server_me_command::server_me_command() - : command("server-me") -{ -} - -void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "target"); - auto message = json_util::get_string(args, "message"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->me(channel, message); - client.success("server-me"); -} - -server_message_command::server_message_command() - : command("server-message") -{ -} - -void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "target"); - auto message = json_util::get_string(args, "message"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->message(channel, message); - client.success("server-message"); -} - -server_mode_command::server_mode_command() - : command("server-mode") -{ -} - -void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "channel"); - auto mode = json_util::get_string(args, "mode"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - if (mode.empty()) - throw server_error(server_error::invalid_mode, server->name()); - - auto limit = json_util::get_string(args, "limit"); - auto user = json_util::get_string(args, "user"); - auto mask = json_util::get_string(args, "mask"); - - server->mode(channel, mode, limit, user, mask); - client.success("server-mode"); -} - -server_nick_command::server_nick_command() - : command("server-nick") -{ -} - -void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto nick = json_util::get_string(args, "nickname"); - - if (nick.empty()) - throw server_error(server_error::invalid_nickname, server->name()); - - server->set_nickname(nick); - client.success("server-nick"); -} - -server_notice_command::server_notice_command() - : command("server-notice") -{ -} - -void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "target"); - auto message = json_util::get_string(args, "message"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->notice(channel, message); - client.success("server-notice"); -} - -server_part_command::server_part_command() - : command("server-part") -{ -} - -void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "channel"); - auto reason = json_util::get_string(args, "reason"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->part(channel, reason); - client.success("server-part"); -} - -server_reconnect_command::server_reconnect_command() - : command("server-reconnect") -{ -} - -void server_reconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = args.find("server"); - - if (server == args.end()) { - for (auto& server : irccd.servers().servers()) - server->reconnect(); - } else { - if (!server->is_string() || !string_util::is_identifier(server->get())) - throw server_error(server_error::invalid_identifier, ""); - - auto name = server->get(); - auto s = irccd.servers().get(name); - - if (!s) - throw server_error(server_error::not_found, name); - - s->reconnect(); - } - - client.success("server-reconnect"); -} - -server_topic_command::server_topic_command() - : command("server-topic") -{ -} - -void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto server = get_server(irccd, args); - auto channel = json_util::get_string(args, "channel"); - auto topic = json_util::get_string(args, "topic"); - - if (channel.empty()) - throw server_error(server_error::invalid_channel, server->name()); - - server->topic(channel, topic); - client.success("server-topic"); -} - -rule_edit_command::rule_edit_command() - : command("rule-edit") -{ -} - -void rule_edit_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - static const auto updateset = [] (auto& set, auto args, const auto& key) { - for (const auto& v : args["remove-"s + key]) { - if (v.is_string()) - set.erase(v.template get()); - } - for (const auto& v : args["add-"s + key]) { - if (v.is_string()) - set.insert(v.template get()); - } - }; - - // Create a copy to avoid incomplete edition in case of errors. - auto index = get_rule_index(args); - auto rule = irccd.rules().require(index); - - updateset(rule.channels(), args, "channels"); - updateset(rule.events(), args, "events"); - updateset(rule.plugins(), args, "plugins"); - updateset(rule.servers(), args, "servers"); - - auto action = args.find("action"); - - if (action != args.end()) { - if (!action->is_string()) - throw rule_error(rule_error::error::invalid_action); - - if (action->get() == "accept") - rule.set_action(rule::action_type::accept); - else if (action->get() == "drop") - rule.set_action(rule::action_type::drop); - else - throw rule_error(rule_error::invalid_action); - } - - // All done, sync the rule. - irccd.rules().require(index) = rule; - client.success("rule-edit"); -} - -rule_list_command::rule_list_command() - : command("rule-list") -{ -} - -void rule_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&) -{ - auto array = nlohmann::json::array(); - - for (const auto& rule : irccd.rules().list()) - array.push_back(to_json(rule)); - - client.send({ - { "command", "rule-list" }, - { "list", std::move(array) } - }); -} - -rule_info_command::rule_info_command() - : command("rule-info") -{ -} - -void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto json = to_json(irccd.rules().require(get_rule_index(args))); - - json.push_back({"command", "rule-info"}); - client.send(std::move(json)); -} - -rule_remove_command::rule_remove_command() - : command("rule-remove") -{ -} - -void rule_remove_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto index = get_rule_index(args); - - if (index >= irccd.rules().length()) - throw rule_error(rule_error::invalid_index); - - irccd.rules().remove(index); - client.success("rule-remove"); -} - -rule_move_command::rule_move_command() - : command("rule-move") -{ -} - -void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto from = get_rule_index(args, "from"); - auto to = get_rule_index(args, "to"); - - /* - * Examples of moves - * -------------------------------------------------------------- - * - * Before: [0] [1] [2] - * - * from = 0 - * to = 2 - * - * After: [1] [2] [0] - * - * -------------------------------------------------------------- - * - * Before: [0] [1] [2] - * - * from = 2 - * to = 0 - * - * After: [2] [0] [1] - * - * -------------------------------------------------------------- - * - * Before: [0] [1] [2] - * - * from = 0 - * to = 123 - * - * After: [1] [2] [0] - */ - - // Ignore dumb input. - if (from == to) { - client.success("rule-move"); - return; - } - - if (from >= irccd.rules().length()) - throw rule_error(rule_error::error::invalid_index); - - auto save = irccd.rules().list()[from]; - - irccd.rules().remove(from); - irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to); - client.success("rule-move"); -} - -rule_add_command::rule_add_command() - : command("rule-add") -{ -} - -void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) -{ - auto index = json_util::get_uint(args, "index", irccd.rules().length()); - auto rule = from_json(args); - - if (index > irccd.rules().length()) - throw rule_error(rule_error::error::invalid_index); - - irccd.rules().insert(rule, index); - client.success("rule-add"); -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/command.hpp --- a/libirccd/irccd/command.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,656 +0,0 @@ -/* - * command.hpp -- remote command - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_COMMAND_HPP -#define IRCCD_COMMAND_HPP - -/** - * \file command.hpp - * \brief Remote commands. - */ - -#include -#include -#include - -#include "json.hpp" -#include "sysconfig.hpp" - -namespace irccd { - -class irccd; -class irccdctl; -class transport_client; - -/** - * \brief Server side remote command - */ -class command { -private: - std::string name_; - -public: - /** - * Construct a command. - * - * \pre !name.empty() - * \param name the command name - */ - inline command(std::string name) noexcept - : name_(std::move(name)) - { - assert(!name_.empty()); - } - - /** - * Default destructor virtual. - */ - virtual ~command() = default; - - /** - * Return the command name, must not have spaces. - * - * \return the command name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Execute the command. - * - * If the command throw an exception, the error is sent to the client so be - * careful about sensitive information. - * - * The implementation should use client.success() or client.error() to send - * some data. - * - * \param irccd the irccd instance - * \param client the client - * \param args the client arguments - */ - virtual void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) = 0; -}; - -/** - * \brief Implementation of plugin-config transport command. - * - * Replies: - * - * - plugin_error::not_found - */ -class plugin_config_command : public command { -public: - /** - * Constructor. - */ - plugin_config_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of plugin-info transport command. - * - * Replies: - * - * - plugin_error::not_found - */ -class plugin_info_command : public command { -public: - /** - * Constructor. - */ - plugin_info_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of plugin-list transport command. - */ -class plugin_list_command : public command { -public: - /** - * Constructor. - */ - plugin_list_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& request) override; -}; - -/** - * \brief Implementation of plugin-load transport command. - * - * Replies: - * - * - plugin_error::already_exists - * - plugin_error::not_found - * - plugin_error::exec_error - */ -class plugin_load_command : public command { -public: - /** - * Constructor. - */ - plugin_load_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of plugin-reload transport command. - * - * Replies: - * - * - plugin_error::not_found - * - plugin_error::exec_error - */ -class plugin_reload_command : public command { -public: - /** - * Constructor. - */ - plugin_reload_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of plugin-unload transport command. - * - * Replies: - * - * - plugin_error::not_found - * - plugin_error::exec_error - */ -class plugin_unload_command : public command { -public: - /** - * Constructor. - */ - plugin_unload_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-connect transport command. - * - * Replies: - * - * - server_error::already_exists, - * - server_error::invalid_hostname, - * - server_error::invalid_identifier, - * - server_error::invalid_port_number, - * - server_error::ssl_disabled. - */ -class server_connect_command : public command { -public: - /** - * Constructor. - */ - server_connect_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-disconnect transport command. - * - * Replies: - * - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_disconnect_command : public command { -public: - /** - * Constructor. - */ - server_disconnect_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-info transport command. - * - * Replies: - * - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_info_command : public command { -public: - /** - * Constructor. - */ - server_info_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-invite transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::invalid_nickname, - * - server_error::not_found. - */ -class server_invite_command : public command { -public: - /** - * Constructor. - */ - server_invite_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-join transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_join_command : public command { -public: - /** - * Constructor. - */ - server_join_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-kick transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::invalid_nickname, - * - server_error::not_found. - */ -class server_kick_command : public command { -public: - /** - * Constructor. - */ - server_kick_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-list transport command. - */ -class server_list_command : public command { -public: - /** - * Constructor. - */ - server_list_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-me transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_me_command : public command { -public: - /** - * Constructor. - */ - server_me_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-message transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_message_command : public command { -public: - /** - * Constructor. - */ - server_message_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-mode transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::invalid_mode, - * - server_error::not_found. - */ -class server_mode_command : public command { -public: - /** - * Constructor. - */ - server_mode_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-nick transport command. - * - * Replies: - * - * - server_error::invalid_identifier, - * - server_error::invalid_nickname, - * - server_error::not_found. - */ -class server_nick_command : public command { -public: - /** - * Constructor. - */ - server_nick_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-notice transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_notice_command : public command { -public: - /** - * Constructor. - */ - server_notice_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-part transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_part_command : public command { -public: - /** - * Constructor. - */ - server_part_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-reconnect transport command. - * - * Replies: - * - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_reconnect_command : public command { -public: - /** - * Constructor. - */ - server_reconnect_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of server-topic transport command. - * - * Replies: - * - * - server_error::invalid_channel, - * - server_error::invalid_identifier, - * - server_error::not_found. - */ -class server_topic_command : public command { -public: - /** - * Constructor. - */ - server_topic_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of rule-edit transport command. - * - * Replies: - * - * - rule_error::invalid_index - * - rule_error::invalid_action - */ -class rule_edit_command : public command { -public: - /** - * Constructor. - */ - rule_edit_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of rule-list transport command. - */ -class rule_list_command : public command { -public: - /** - * Constructor. - */ - rule_list_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of rule-info transport command. - * - * Replies: - * - * - rule_error::invalid_index - */ -class rule_info_command : public command { -public: - /** - * Constructor. - */ - rule_info_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of rule-remove transport command. - * - * Replies: - * - * - rule_error::invalid_index - */ -class rule_remove_command : public command { -public: - /** - * Constructor. - */ - rule_remove_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of rule-move transport command. - * - * Replies: - * - * - rule_error::invalid_index - */ -class rule_move_command : public command { -public: - /** - * Constructor. - */ - rule_move_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -/** - * \brief Implementation of rule-add transport command. - * - * Replies: - * - * - rule_error::invalid_action - */ -class rule_add_command : public command { -public: - /** - * Constructor. - */ - rule_add_command(); - - /** - * \copydoc command::exec - */ - void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; -}; - -} // !irccd - -#endif // !IRCCD_COMMAND_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/command_service.cpp --- a/libirccd/irccd/command_service.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -/* - * command_service.cpp -- command service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "command_service.hpp" - -namespace irccd { - -bool command_service::contains(const std::string& name) const noexcept -{ - return find(name) != nullptr; -} - -std::shared_ptr command_service::find(const std::string& name) const noexcept -{ - auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { - return cmd->name() == name; - }); - - return it == commands_.end() ? nullptr : *it; -} - -void command_service::add(std::shared_ptr command) -{ - auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { - return cmd->name() == command->name(); - }); - - if (it != commands_.end()) - *it = std::move(command); - else - commands_.push_back(std::move(command)); -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/command_service.hpp --- a/libirccd/irccd/command_service.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,80 +0,0 @@ -/* - * command_service.hpp -- command service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_COMMAND_SERVICE_HPP -#define IRCCD_COMMAND_SERVICE_HPP - -/** - * \file command_service.hpp - * \brief Command service. - */ - -#include -#include - -#include "command.hpp" - -namespace irccd { - -/** - * \brief Store remote commands. - * \ingroup services - */ -class command_service { -private: - std::vector> commands_; - -public: - /** - * Get all commands. - * - * \return the list of commands. - */ - inline const std::vector>& commands() const noexcept - { - return commands_; - } - - /** - * Tells if a command exists. - * - * \param name the command name - * \return true if the command exists - */ - bool contains(const std::string& name) const noexcept; - - /** - * Find a command by name. - * - * \param name the command name - * \return the command or empty one if not found - */ - std::shared_ptr find(const std::string& name) const noexcept; - - /** - * Add a command or replace existing one. - * - * \pre command != nullptr - * \param command the command name - */ - void add(std::shared_ptr command); -}; - -} // !irccd - -#endif // !IRCCD_COMMAND_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/config.cpp --- a/libirccd/irccd/config.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -/* - * config.cpp -- irccd configuration loader - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 - -#include - -#include - -#include "config.hpp" - -namespace irccd { - -config config::find(const std::string& name) -{ - for (const auto& path : sys::config_filenames(name)) { - boost::system::error_code ec; - - if (boost::filesystem::exists(path, ec) && !ec) - return config(path); - } - - throw std::runtime_error("no configuration file found"); -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/config.hpp --- a/libirccd/irccd/config.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/* - * config.hpp -- irccd configuration loader - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_CONFIG_HPP -#define IRCCD_CONFIG_HPP - -/** - * \file config.hpp - * \brief Read .ini configuration file for irccd - */ - -#include "ini.hpp" - -namespace irccd { - -/** - * \brief Read .ini configuration file for irccd - */ -class config { -private: - std::string path_; - ini::document document_; - -public: - /** - * Search the configuration file into the standard defined paths. - * - * \param name the file name - * \return the config - * \throw std::exception on errors or if no config could be found - */ - static config find(const std::string& name); - - /** - * Load the configuration from the specified path. - * - * \param path the path - */ - inline config(std::string path = "") - : path_(std::move(path)) - { - if (!path_.empty()) - document_ = ini::read_file(path_); - } - - /** - * Get the underlying document. - * - * \return the document - */ - inline const ini::document& doc() const noexcept - { - return document_; - } - - /** - * Get the path to the configuration file. - * - * \return the path - */ - inline const std::string& path() const noexcept - { - return path_; - } - - /** - * Convenience function to access a section. - * - * \param section the section name - * \return the section or empty one - */ - inline ini::section section(const std::string& section) const noexcept - { - return document_.get(section); - } - - /** - * Convenience function to access an ini value. - * - * \param section the section name - * \param option the option name - * \return the value or empty string - */ - inline std::string value(const std::string& section, const std::string& option) const noexcept - { - return document_.get(section).get(option).value(); - } -}; - -} // !irccd - -#endif // !IRCCD_CONFIG_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/basic_transport_client.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/basic_transport_client.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,102 @@ +/* + * basic_transport_client.hpp -- simple socket transport client + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_BASIC_TRANSPORT_CLIENT_HPP +#define IRCCD_BASIC_TRANSPORT_CLIENT_HPP + +#include + +#include "transport_client.hpp" + +namespace irccd { + +/** + * \brief Transport client for sockets. + * + * This class implements do_recv/do_send for Boost.Asio based socket streams. + */ +template +class basic_transport_client : public transport_client { +private: + network_stream stream_; + +public: + /** + * Construct the client. + * + * \param parent the parent + * \param args the argument to pass to the network_stream + */ + template + inline basic_transport_client(transport_server& parent, Args&&... args) + : transport_client(parent) + , stream_(std::forward(args)...) + { + } + + /** + * Get the underlying stream. + * + * \return the stream + */ + inline const network_stream& stream() const noexcept + { + return stream_; + } + + /** + * Overloaded function. + * + * \return the stream + */ + inline network_stream& stream() noexcept + { + return stream_; + } + + /** + * \copydoc transport_client::do_recv + */ + void do_recv(network_recv_handler handler) override + { + assert(handler); + + auto self = shared_from_this(); + + stream_.recv([this, self, handler] (auto msg, auto code) { + handler(std::move(msg), std::move(code)); + }); + } + + /** + * \copydoc transport_client::do_send + */ + void do_send(nlohmann::json json, network_send_handler handler) override + { + auto self = shared_from_this(); + + stream_.send(std::move(json), [this, self, handler] (auto code) { + if (handler) + handler(std::move(code)); + }); + } +}; + +} // !irccd + +#endif // !IRCCD_BASIC_TRANSPORT_CLIENT_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/basic_transport_server.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/basic_transport_server.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,97 @@ +/* + * basic_transport_server.hpp -- simple socket transport servers + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_BASIC_TRANSPORT_SERVER_HPP +#define IRCCD_BASIC_TRANSPORT_SERVER_HPP + +/** + * \file basic_transport_server.hpp + * \brief Simple socket transport servers. + */ + +#include "basic_transport_client.hpp" +#include "transport_server.hpp" + +namespace irccd { + +/** + * \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 +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 +basic_transport_server::basic_transport_server(acceptor_t acceptor) + : acceptor_(std::move(acceptor)) +{ + assert(acceptor_.is_open()); +} + +template +void basic_transport_server::do_accept(accept_t handler) +{ + auto client = std::make_shared>(*this, acceptor_.get_io_service()); + + acceptor_.async_accept(client->stream().socket(), [this, client, handler] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + handler(std::move(code), std::move(client)); + }); +} + +} // !irccd + +#endif // !IRCCD_BASIC_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/command.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/command.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,729 @@ +/* + * command.cpp -- implementation of plugin-config command + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "command.hpp" +#include "irccd.hpp" +#include "json_util.hpp" +#include "rule_service.hpp" +#include "plugin_service.hpp" +#include "server_service.hpp" +#include "transport_client.hpp" +#include "string_util.hpp" +#include "util.hpp" + +using namespace std::string_literals; + +namespace irccd { + +namespace { + +void exec_set(transport_client& client, plugin& plugin, const nlohmann::json& args) +{ + assert(args.count("value") > 0); + + auto var = args.find("variable"); + auto value = args.find("value"); + + if (var == args.end() || !var->is_string()) + throw irccd_error(irccd_error::error::incomplete_message); + if (value == args.end() || !value->is_string()) + throw irccd_error(irccd_error::error::incomplete_message); + + auto config = plugin.config(); + + config[*var] = *value; + plugin.set_config(config); + client.success("plugin-config"); +} + +void exec_get(transport_client& client, plugin& plugin, const nlohmann::json& args) +{ + auto variables = nlohmann::json::object(); + auto var = args.find("variable"); + + if (var != args.end() && var->is_string()) + variables[var->get()] = plugin.config()[*var]; + else + for (const auto& pair : plugin.config()) + variables[pair.first] = pair.second; + + /* + * Don't put all variables into the response, put them into a sub property + * 'variables' instead. + * + * It's easier for the client to iterate over all. + */ + client.send({ + { "command", "plugin-config" }, + { "variables", variables } + }); +} + +nlohmann::json to_json(const rule& rule) +{ + auto join = [] (const auto& set) { + auto array = nlohmann::json::array(); + + for (const auto& entry : set) + array.push_back(entry); + + return array; + }; + auto str = [] (auto action) { + switch (action) { + case rule::action_type::accept: + return "accept"; + default: + return "drop"; + } + }; + + return { + { "servers", join(rule.servers()) }, + { "channels", join(rule.channels()) }, + { "plugins", join(rule.plugins()) }, + { "events", join(rule.events()) }, + { "action", str(rule.action()) } + }; +} + +rule from_json(const nlohmann::json& json) +{ + auto toset = [] (auto object, auto name) { + rule::set result; + + for (const auto& s : object[name]) + if (s.is_string()) + result.insert(s.template get()); + + return result; + }; + auto toaction = [] (auto object, auto name) { + auto v = object[name]; + + if (!v.is_string()) + throw rule_error(rule_error::invalid_action); + + auto s = v.template get(); + if (s == "accept") + return rule::action_type::accept; + if (s == "drop") + return rule::action_type::drop; + + throw rule_error(rule_error::invalid_action); + }; + + return { + toset(json, "servers"), + toset(json, "channels"), + toset(json, "origins"), + toset(json, "plugins"), + toset(json, "events"), + toaction(json, "action") + }; +} + +unsigned get_rule_index(const nlohmann::json& json, const std::string& key = "index") +{ + auto index = json.find(key); + + if (index == json.end() || !index->is_number_integer() || index->get() < 0) + throw rule_error(rule_error::invalid_index); + + return index->get(); +} + +std::shared_ptr get_server(irccd& daemon, const nlohmann::json& args) +{ + auto id = json_util::get_string(args, "server"); + + if (!string_util::is_identifier(id)) + throw server_error(server_error::invalid_identifier, ""); + + auto server = daemon.servers().get(id); + + if (!server) + throw server_error(server_error::not_found, id); + + return server; +} + +} // !namespace + +plugin_config_command::plugin_config_command() + : command("plugin-config") +{ +} + +void plugin_config_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin")); + + if (args.count("value") > 0) + exec_set(client, *plugin, args); + else + exec_get(client, *plugin, args); +} + +plugin_info_command::plugin_info_command() + : command("plugin-info") +{ +} + +void plugin_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto plugin = irccd.plugins().require(json_util::require_identifier(args, "plugin")); + + client.send({ + { "command", "plugin-info" }, + { "author", plugin->author() }, + { "license", plugin->license() }, + { "summary", plugin->summary() }, + { "version", plugin->version() } + }); +} + +plugin_list_command::plugin_list_command() + : command("plugin-list") +{ +} + +void plugin_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&) +{ + auto list = nlohmann::json::array(); + + for (const auto& plugin : irccd.plugins().list()) + list += plugin->name(); + + client.send({ + { "command", "plugin-list" }, + { "list", list } + }); +} + +plugin_load_command::plugin_load_command() + : command("plugin-load") +{ +} + +void plugin_load_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + irccd.plugins().load(json_util::require_identifier(args, "plugin")); + client.success("plugin-load"); +} + +plugin_reload_command::plugin_reload_command() + : command("plugin-reload") +{ +} + +void plugin_reload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + irccd.plugins().reload(json_util::require_identifier(args, "plugin")); + client.success("plugin-reload"); +} + +plugin_unload_command::plugin_unload_command() + : command("plugin-unload") +{ +} + +void plugin_unload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + irccd.plugins().unload(json_util::require_identifier(args, "plugin")); + client.success("plugin-unload"); +} + +server_connect_command::server_connect_command() + : command("server-connect") +{ +} + +void server_connect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = server_service::from_json(irccd.service(), args); + + if (irccd.servers().has(server->name())) + throw server_error(server_error::error::already_exists, server->name()); + + irccd.servers().add(std::move(server)); + client.success("server-connect"); +} + +server_disconnect_command::server_disconnect_command() + : command("server-disconnect") +{ +} + +void server_disconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto it = args.find("server"); + + if (it == args.end()) + irccd.servers().clear(); + else { + if (!it->is_string()) + throw server_error(server_error::invalid_identifier, ""); + + auto name = it->get(); + auto s = irccd.servers().get(name); + + if (!s) + throw server_error(server_error::not_found, name); + + irccd.servers().remove(name); + } + + client.success("server-disconnect"); +} + +server_info_command::server_info_command() + : command("server-info") +{ +} + +void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto response = nlohmann::json::object(); + auto server = get_server(irccd, args); + + // 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()}); + response.push_back({"nickname", server->nickname()}); + response.push_back({"username", server->username()}); + response.push_back({"realname", server->realname()}); + response.push_back({"channels", server->channels()}); + + // Optional stuff. + if (server->flags() & server::ipv6) + response.push_back({"ipv6", true}); + if (server->flags() & server::ssl) + response.push_back({"ssl", true}); + if (server->flags() & server::ssl_verify) + response.push_back({"sslVerify", true}); + + client.send(response); +} + +server_invite_command::server_invite_command() + : command("server-invite") +{ +} + +void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto target = json_util::get_string(args, "target"); + auto channel = json_util::get_string(args, "channel"); + + if (target.empty()) + throw server_error(server_error::invalid_nickname, server->name()); + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->invite(target, channel); + client.success("server-invite"); +} + +server_join_command::server_join_command() + : command("server-join") +{ +} + +void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "channel"); + auto password = json_util::get_string(args, "password"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->join(channel, password); + client.success("server-join"); +} + +server_kick_command::server_kick_command() + : command("server-kick") +{ +} + +void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto target = json_util::get_string(args, "target"); + auto channel = json_util::get_string(args, "channel"); + auto reason = json_util::get_string(args, "reason"); + + if (target.empty()) + throw server_error(server_error::invalid_nickname, server->name()); + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->kick(target, channel, reason); + client.success("server-kick"); +} + +server_list_command::server_list_command() + : command("server-list") +{ +} + +void server_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&) +{ + auto json = nlohmann::json::object(); + auto list = nlohmann::json::array(); + + for (const auto& server : irccd.servers().servers()) + list.push_back(server->name()); + + client.send({ + { "command", "server-list" }, + { "list", std::move(list) } + }); +} + +server_me_command::server_me_command() + : command("server-me") +{ +} + +void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "target"); + auto message = json_util::get_string(args, "message"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->me(channel, message); + client.success("server-me"); +} + +server_message_command::server_message_command() + : command("server-message") +{ +} + +void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "target"); + auto message = json_util::get_string(args, "message"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->message(channel, message); + client.success("server-message"); +} + +server_mode_command::server_mode_command() + : command("server-mode") +{ +} + +void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "channel"); + auto mode = json_util::get_string(args, "mode"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + if (mode.empty()) + throw server_error(server_error::invalid_mode, server->name()); + + auto limit = json_util::get_string(args, "limit"); + auto user = json_util::get_string(args, "user"); + auto mask = json_util::get_string(args, "mask"); + + server->mode(channel, mode, limit, user, mask); + client.success("server-mode"); +} + +server_nick_command::server_nick_command() + : command("server-nick") +{ +} + +void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto nick = json_util::get_string(args, "nickname"); + + if (nick.empty()) + throw server_error(server_error::invalid_nickname, server->name()); + + server->set_nickname(nick); + client.success("server-nick"); +} + +server_notice_command::server_notice_command() + : command("server-notice") +{ +} + +void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "target"); + auto message = json_util::get_string(args, "message"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->notice(channel, message); + client.success("server-notice"); +} + +server_part_command::server_part_command() + : command("server-part") +{ +} + +void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "channel"); + auto reason = json_util::get_string(args, "reason"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->part(channel, reason); + client.success("server-part"); +} + +server_reconnect_command::server_reconnect_command() + : command("server-reconnect") +{ +} + +void server_reconnect_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = args.find("server"); + + if (server == args.end()) { + for (auto& server : irccd.servers().servers()) + server->reconnect(); + } else { + if (!server->is_string() || !string_util::is_identifier(server->get())) + throw server_error(server_error::invalid_identifier, ""); + + auto name = server->get(); + auto s = irccd.servers().get(name); + + if (!s) + throw server_error(server_error::not_found, name); + + s->reconnect(); + } + + client.success("server-reconnect"); +} + +server_topic_command::server_topic_command() + : command("server-topic") +{ +} + +void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto server = get_server(irccd, args); + auto channel = json_util::get_string(args, "channel"); + auto topic = json_util::get_string(args, "topic"); + + if (channel.empty()) + throw server_error(server_error::invalid_channel, server->name()); + + server->topic(channel, topic); + client.success("server-topic"); +} + +rule_edit_command::rule_edit_command() + : command("rule-edit") +{ +} + +void rule_edit_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + static const auto updateset = [] (auto& set, auto args, const auto& key) { + for (const auto& v : args["remove-"s + key]) { + if (v.is_string()) + set.erase(v.template get()); + } + for (const auto& v : args["add-"s + key]) { + if (v.is_string()) + set.insert(v.template get()); + } + }; + + // Create a copy to avoid incomplete edition in case of errors. + auto index = get_rule_index(args); + auto rule = irccd.rules().require(index); + + updateset(rule.channels(), args, "channels"); + updateset(rule.events(), args, "events"); + updateset(rule.plugins(), args, "plugins"); + updateset(rule.servers(), args, "servers"); + + auto action = args.find("action"); + + if (action != args.end()) { + if (!action->is_string()) + throw rule_error(rule_error::error::invalid_action); + + if (action->get() == "accept") + rule.set_action(rule::action_type::accept); + else if (action->get() == "drop") + rule.set_action(rule::action_type::drop); + else + throw rule_error(rule_error::invalid_action); + } + + // All done, sync the rule. + irccd.rules().require(index) = rule; + client.success("rule-edit"); +} + +rule_list_command::rule_list_command() + : command("rule-list") +{ +} + +void rule_list_command::exec(irccd& irccd, transport_client& client, const nlohmann::json&) +{ + auto array = nlohmann::json::array(); + + for (const auto& rule : irccd.rules().list()) + array.push_back(to_json(rule)); + + client.send({ + { "command", "rule-list" }, + { "list", std::move(array) } + }); +} + +rule_info_command::rule_info_command() + : command("rule-info") +{ +} + +void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto json = to_json(irccd.rules().require(get_rule_index(args))); + + json.push_back({"command", "rule-info"}); + client.send(std::move(json)); +} + +rule_remove_command::rule_remove_command() + : command("rule-remove") +{ +} + +void rule_remove_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto index = get_rule_index(args); + + if (index >= irccd.rules().length()) + throw rule_error(rule_error::invalid_index); + + irccd.rules().remove(index); + client.success("rule-remove"); +} + +rule_move_command::rule_move_command() + : command("rule-move") +{ +} + +void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto from = get_rule_index(args, "from"); + auto to = get_rule_index(args, "to"); + + /* + * Examples of moves + * -------------------------------------------------------------- + * + * Before: [0] [1] [2] + * + * from = 0 + * to = 2 + * + * After: [1] [2] [0] + * + * -------------------------------------------------------------- + * + * Before: [0] [1] [2] + * + * from = 2 + * to = 0 + * + * After: [2] [0] [1] + * + * -------------------------------------------------------------- + * + * Before: [0] [1] [2] + * + * from = 0 + * to = 123 + * + * After: [1] [2] [0] + */ + + // Ignore dumb input. + if (from == to) { + client.success("rule-move"); + return; + } + + if (from >= irccd.rules().length()) + throw rule_error(rule_error::error::invalid_index); + + auto save = irccd.rules().list()[from]; + + irccd.rules().remove(from); + irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to); + client.success("rule-move"); +} + +rule_add_command::rule_add_command() + : command("rule-add") +{ +} + +void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args) +{ + auto index = json_util::get_uint(args, "index", irccd.rules().length()); + auto rule = from_json(args); + + if (index > irccd.rules().length()) + throw rule_error(rule_error::error::invalid_index); + + irccd.rules().insert(rule, index); + client.success("rule-add"); +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/command.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/command.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,656 @@ +/* + * command.hpp -- remote command + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_COMMAND_HPP +#define IRCCD_COMMAND_HPP + +/** + * \file command.hpp + * \brief Remote commands. + */ + +#include +#include +#include + +#include "json.hpp" +#include "sysconfig.hpp" + +namespace irccd { + +class irccd; +class irccdctl; +class transport_client; + +/** + * \brief Server side remote command + */ +class command { +private: + std::string name_; + +public: + /** + * Construct a command. + * + * \pre !name.empty() + * \param name the command name + */ + inline command(std::string name) noexcept + : name_(std::move(name)) + { + assert(!name_.empty()); + } + + /** + * Default destructor virtual. + */ + virtual ~command() = default; + + /** + * Return the command name, must not have spaces. + * + * \return the command name + */ + inline const std::string& name() const noexcept + { + return name_; + } + + /** + * Execute the command. + * + * If the command throw an exception, the error is sent to the client so be + * careful about sensitive information. + * + * The implementation should use client.success() or client.error() to send + * some data. + * + * \param irccd the irccd instance + * \param client the client + * \param args the client arguments + */ + virtual void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) = 0; +}; + +/** + * \brief Implementation of plugin-config transport command. + * + * Replies: + * + * - plugin_error::not_found + */ +class plugin_config_command : public command { +public: + /** + * Constructor. + */ + plugin_config_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of plugin-info transport command. + * + * Replies: + * + * - plugin_error::not_found + */ +class plugin_info_command : public command { +public: + /** + * Constructor. + */ + plugin_info_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of plugin-list transport command. + */ +class plugin_list_command : public command { +public: + /** + * Constructor. + */ + plugin_list_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& request) override; +}; + +/** + * \brief Implementation of plugin-load transport command. + * + * Replies: + * + * - plugin_error::already_exists + * - plugin_error::not_found + * - plugin_error::exec_error + */ +class plugin_load_command : public command { +public: + /** + * Constructor. + */ + plugin_load_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of plugin-reload transport command. + * + * Replies: + * + * - plugin_error::not_found + * - plugin_error::exec_error + */ +class plugin_reload_command : public command { +public: + /** + * Constructor. + */ + plugin_reload_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of plugin-unload transport command. + * + * Replies: + * + * - plugin_error::not_found + * - plugin_error::exec_error + */ +class plugin_unload_command : public command { +public: + /** + * Constructor. + */ + plugin_unload_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-connect transport command. + * + * Replies: + * + * - server_error::already_exists, + * - server_error::invalid_hostname, + * - server_error::invalid_identifier, + * - server_error::invalid_port_number, + * - server_error::ssl_disabled. + */ +class server_connect_command : public command { +public: + /** + * Constructor. + */ + server_connect_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-disconnect transport command. + * + * Replies: + * + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_disconnect_command : public command { +public: + /** + * Constructor. + */ + server_disconnect_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-info transport command. + * + * Replies: + * + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_info_command : public command { +public: + /** + * Constructor. + */ + server_info_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-invite transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::invalid_nickname, + * - server_error::not_found. + */ +class server_invite_command : public command { +public: + /** + * Constructor. + */ + server_invite_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-join transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_join_command : public command { +public: + /** + * Constructor. + */ + server_join_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-kick transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::invalid_nickname, + * - server_error::not_found. + */ +class server_kick_command : public command { +public: + /** + * Constructor. + */ + server_kick_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-list transport command. + */ +class server_list_command : public command { +public: + /** + * Constructor. + */ + server_list_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-me transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_me_command : public command { +public: + /** + * Constructor. + */ + server_me_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-message transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_message_command : public command { +public: + /** + * Constructor. + */ + server_message_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-mode transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::invalid_mode, + * - server_error::not_found. + */ +class server_mode_command : public command { +public: + /** + * Constructor. + */ + server_mode_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-nick transport command. + * + * Replies: + * + * - server_error::invalid_identifier, + * - server_error::invalid_nickname, + * - server_error::not_found. + */ +class server_nick_command : public command { +public: + /** + * Constructor. + */ + server_nick_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-notice transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_notice_command : public command { +public: + /** + * Constructor. + */ + server_notice_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-part transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_part_command : public command { +public: + /** + * Constructor. + */ + server_part_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-reconnect transport command. + * + * Replies: + * + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_reconnect_command : public command { +public: + /** + * Constructor. + */ + server_reconnect_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of server-topic transport command. + * + * Replies: + * + * - server_error::invalid_channel, + * - server_error::invalid_identifier, + * - server_error::not_found. + */ +class server_topic_command : public command { +public: + /** + * Constructor. + */ + server_topic_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of rule-edit transport command. + * + * Replies: + * + * - rule_error::invalid_index + * - rule_error::invalid_action + */ +class rule_edit_command : public command { +public: + /** + * Constructor. + */ + rule_edit_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of rule-list transport command. + */ +class rule_list_command : public command { +public: + /** + * Constructor. + */ + rule_list_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of rule-info transport command. + * + * Replies: + * + * - rule_error::invalid_index + */ +class rule_info_command : public command { +public: + /** + * Constructor. + */ + rule_info_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of rule-remove transport command. + * + * Replies: + * + * - rule_error::invalid_index + */ +class rule_remove_command : public command { +public: + /** + * Constructor. + */ + rule_remove_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of rule-move transport command. + * + * Replies: + * + * - rule_error::invalid_index + */ +class rule_move_command : public command { +public: + /** + * Constructor. + */ + rule_move_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +/** + * \brief Implementation of rule-add transport command. + * + * Replies: + * + * - rule_error::invalid_action + */ +class rule_add_command : public command { +public: + /** + * Constructor. + */ + rule_add_command(); + + /** + * \copydoc command::exec + */ + void exec(irccd& irccd, transport_client& client, const nlohmann::json& args) override; +}; + +} // !irccd + +#endif // !IRCCD_COMMAND_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/command_service.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/command_service.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,49 @@ +/* + * command_service.cpp -- command service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "command_service.hpp" + +namespace irccd { + +bool command_service::contains(const std::string& name) const noexcept +{ + return find(name) != nullptr; +} + +std::shared_ptr command_service::find(const std::string& name) const noexcept +{ + auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { + return cmd->name() == name; + }); + + return it == commands_.end() ? nullptr : *it; +} + +void command_service::add(std::shared_ptr command) +{ + auto it = std::find_if(commands_.begin(), commands_.end(), [&] (const auto& cmd) { + return cmd->name() == command->name(); + }); + + if (it != commands_.end()) + *it = std::move(command); + else + commands_.push_back(std::move(command)); +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/command_service.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/command_service.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,80 @@ +/* + * command_service.hpp -- command service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_COMMAND_SERVICE_HPP +#define IRCCD_COMMAND_SERVICE_HPP + +/** + * \file command_service.hpp + * \brief Command service. + */ + +#include +#include + +#include "command.hpp" + +namespace irccd { + +/** + * \brief Store remote commands. + * \ingroup services + */ +class command_service { +private: + std::vector> commands_; + +public: + /** + * Get all commands. + * + * \return the list of commands. + */ + inline const std::vector>& commands() const noexcept + { + return commands_; + } + + /** + * Tells if a command exists. + * + * \param name the command name + * \return true if the command exists + */ + bool contains(const std::string& name) const noexcept; + + /** + * Find a command by name. + * + * \param name the command name + * \return the command or empty one if not found + */ + std::shared_ptr find(const std::string& name) const noexcept; + + /** + * Add a command or replace existing one. + * + * \pre command != nullptr + * \param command the command name + */ + void add(std::shared_ptr command); +}; + +} // !irccd + +#endif // !IRCCD_COMMAND_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/config.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/config.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,41 @@ +/* + * config.cpp -- irccd configuration loader + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 + +#include + +#include + +#include "config.hpp" + +namespace irccd { + +config config::find(const std::string& name) +{ + for (const auto& path : sys::config_filenames(name)) { + boost::system::error_code ec; + + if (boost::filesystem::exists(path, ec) && !ec) + return config(path); + } + + throw std::runtime_error("no configuration file found"); +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/config.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/config.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,107 @@ +/* + * config.hpp -- irccd configuration loader + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_CONFIG_HPP +#define IRCCD_CONFIG_HPP + +/** + * \file config.hpp + * \brief Read .ini configuration file for irccd + */ + +#include "ini.hpp" + +namespace irccd { + +/** + * \brief Read .ini configuration file for irccd + */ +class config { +private: + std::string path_; + ini::document document_; + +public: + /** + * Search the configuration file into the standard defined paths. + * + * \param name the file name + * \return the config + * \throw std::exception on errors or if no config could be found + */ + static config find(const std::string& name); + + /** + * Load the configuration from the specified path. + * + * \param path the path + */ + inline config(std::string path = "") + : path_(std::move(path)) + { + if (!path_.empty()) + document_ = ini::read_file(path_); + } + + /** + * Get the underlying document. + * + * \return the document + */ + inline const ini::document& doc() const noexcept + { + return document_; + } + + /** + * Get the path to the configuration file. + * + * \return the path + */ + inline const std::string& path() const noexcept + { + return path_; + } + + /** + * Convenience function to access a section. + * + * \param section the section name + * \return the section or empty one + */ + inline ini::section section(const std::string& section) const noexcept + { + return document_.get(section); + } + + /** + * Convenience function to access an ini value. + * + * \param section the section name + * \param option the option name + * \return the value or empty string + */ + inline std::string value(const std::string& section, const std::string& option) const noexcept + { + return document_.get(section).get(option).value(); + } +}; + +} // !irccd + +#endif // !IRCCD_CONFIG_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/dynlib_plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/dynlib_plugin.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,164 @@ +/* + * dynlib_plugin.cpp -- native plugin implementation + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 +#include + +#include + +#include "dynlib_plugin.hpp" +#include "string_util.hpp" + +#if defined(IRCCD_SYSTEM_WINDOWS) +# define DYNLIB_EXTENSION ".dll" +#elif defined(IRCCD_SYSTEM_MAC) +# define DYNLIB_EXTENSION ".dylib" +#else +# define DYNLIB_EXTENSION ".so" +#endif + +namespace irccd { + +dynlib_plugin::dynlib_plugin(std::string name, std::string path) + : plugin(name, path) + , dso_(path) +{ + using load_t = std::unique_ptr(std::string, std::string); + + /* + * Function name is determined from the plugin filename where all non + * alphabetic characters are removed. + * + * Example: foo_bar-baz___.so becomes irccd_foobarbaz_load. + */ + auto base = boost::filesystem::path(path).stem().string(); + auto need_remove = [] (auto c) { + return !std::isalnum(c); + }; + + base.erase(std::remove_if(base.begin(), base.end(), need_remove), base.end()); + + auto fname = string_util::sprintf("irccd_%s_load", base); + auto load = dso_.get(fname); + + if (!load) + throw std::runtime_error(string_util::sprintf("missing plugin entry function '%s'", fname)); + + plugin_ = load(name, path); + + if (!plugin_) + throw std::runtime_error("plugin returned null"); +} + +void dynlib_plugin::on_command(irccd& irccd, const message_event& ev) +{ + plugin_->on_command(irccd, ev); +} + +void dynlib_plugin::on_connect(irccd& irccd, const connect_event& ev) +{ + plugin_->on_connect(irccd, ev); +} + +void dynlib_plugin::on_invite(irccd& irccd, const invite_event& ev) +{ + plugin_->on_invite(irccd, ev); +} + +void dynlib_plugin::on_join(irccd& irccd, const join_event& ev) +{ + plugin_->on_join(irccd, ev); +} + +void dynlib_plugin::on_kick(irccd& irccd, const kick_event& ev) +{ + plugin_->on_kick(irccd, ev); +} + +void dynlib_plugin::on_load(irccd& irccd) +{ + plugin_->on_load(irccd); +} + +void dynlib_plugin::on_message(irccd& irccd, const message_event& ev) +{ + plugin_->on_message(irccd, ev); +} + +void dynlib_plugin::on_me(irccd& irccd, const me_event& ev) +{ + plugin_->on_me(irccd, ev); +} + +void dynlib_plugin::on_mode(irccd& irccd, const mode_event& ev) +{ + plugin_->on_mode(irccd, ev); +} + +void dynlib_plugin::on_names(irccd& irccd, const names_event& ev) +{ + plugin_->on_names(irccd, ev); +} + +void dynlib_plugin::on_nick(irccd& irccd, const nick_event& ev) +{ + plugin_->on_nick(irccd, ev); +} + +void dynlib_plugin::on_notice(irccd& irccd, const notice_event& ev) +{ + plugin_->on_notice(irccd, ev); +} + +void dynlib_plugin::on_part(irccd& irccd, const part_event& ev) +{ + plugin_->on_part(irccd, ev); +} + +void dynlib_plugin::on_reload(irccd& irccd) +{ + plugin_->on_reload(irccd); +} + +void dynlib_plugin::on_topic(irccd& irccd, const topic_event& ev) +{ + plugin_->on_topic(irccd, ev); +} + +void dynlib_plugin::on_unload(irccd& irccd) +{ + plugin_->on_unload(irccd); +} + +void dynlib_plugin::on_whois(irccd& irccd, const whois_event& ev) +{ + plugin_->on_whois(irccd, ev); +} + +dynlib_plugin_loader::dynlib_plugin_loader(std::vector directories) noexcept + : plugin_loader(std::move(directories), { DYNLIB_EXTENSION }) +{ +} + +std::shared_ptr dynlib_plugin_loader::open(const std::string& id, + const std::string& path) noexcept +{ + return std::make_unique(id, path); +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/dynlib_plugin.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/dynlib_plugin.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,159 @@ +/* + * dynlib_plugin.hpp -- native plugin implementation + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_PLUGIN_DYNLIB_HPP +#define IRCCD_PLUGIN_DYNLIB_HPP + +/** + * \file plugin-dynlib.hpp + * \brief Native plugin implementation. + */ + +#include + +#include "plugin.hpp" + +namespace irccd { + +/** + * \brief Dynlib based plugin. + * \ingroup plugins + */ +class dynlib_plugin : public plugin { +private: + boost::dll::shared_library dso_; + std::unique_ptr plugin_; + +public: + /** + * Construct the plugin. + * + * \param name the name + * \param path the fully resolved path (must be absolute) + * \throw std::exception on failures + */ + dynlib_plugin(std::string name, std::string path); + + /** + * \copydoc plugin::on_command + */ + void on_command(irccd& irccd, const message_event& event) override; + + /** + * \copydoc plugin::on_connect + */ + void on_connect(irccd& irccd, const connect_event& event) override; + + /** + * \copydoc plugin::on_invite + */ + void on_invite(irccd& irccd, const invite_event& event) override; + + /** + * \copydoc plugin::on_join + */ + void on_join(irccd& irccd, const join_event& event) override; + + /** + * \copydoc plugin::on_kick + */ + void on_kick(irccd& irccd, const kick_event& event) override; + + /** + * \copydoc plugin::on_load + */ + void on_load(irccd& irccd) override; + + /** + * \copydoc plugin::on_message + */ + void on_message(irccd& irccd, const message_event& event) override; + + /** + * \copydoc plugin::on_me + */ + void on_me(irccd& irccd, const me_event& event) override; + + /** + * \copydoc plugin::on_mode + */ + void on_mode(irccd& irccd, const mode_event& event) override; + + /** + * \copydoc plugin::on_names + */ + void on_names(irccd& irccd, const names_event& event) override; + + /** + * \copydoc plugin::on_nick + */ + void on_nick(irccd& irccd, const nick_event& event) override; + + /** + * \copydoc plugin::on_notice + */ + void on_notice(irccd& irccd, const notice_event& event) override; + + /** + * \copydoc plugin::on_part + */ + void on_part(irccd& irccd, const part_event& event) override; + + /** + * \copydoc plugin::on_reload + */ + void on_reload(irccd& irccd) override; + + /** + * \copydoc plugin::on_topic + */ + void on_topic(irccd& irccd, const topic_event& event) override; + + /** + * \copydoc plugin::on_unload + */ + void on_unload(irccd& irccd) override; + + /** + * \copydoc plugin::on_whois + */ + void on_whois(irccd& irccd, const whois_event& event) override; +}; + +/** + * \brief Implementation for searching native plugins. + */ +class dynlib_plugin_loader : public plugin_loader { +public: + /** + * Constructor. + * + * \param directories optional directories to search, if empty use defaults. + */ + dynlib_plugin_loader(std::vector directories = {}) noexcept; + + /** + * \copydoc plugin_loader::find + */ + std::shared_ptr open(const std::string& id, + const std::string& path) noexcept override; +}; + +} // !irccd + +#endif // !IRCCD_PLUGIN_DYNLIB_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/ip_transport_server.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/ip_transport_server.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,38 @@ +/* + * ip_transport_server.hpp -- server side transports (TCP/IP support) + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_IP_TRANSPORT_SERVER_HPP +#define IRCCD_IP_TRANSPORT_SERVER_HPP + +/** + * \file ip_transport_server.hpp + * \brief Server side transports (TCP/IP support). + */ + +#include "basic_transport_server.hpp" + +namespace irccd { + +/** + * Convenient type for IP/TCP + */ +using ip_transport_server = basic_transport_server; + +} // !irccd + +#endif // !IRCCD_IP_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/irc.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/irc.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,275 @@ +/* + * irc.cpp -- low level IRC functions + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 +#include +#include + +#include "irc.hpp" + +namespace irccd { + +namespace irc { + +namespace { + +using boost::asio::ip::tcp; + +template +void wrap_connect(Socket& socket, tcp::resolver::iterator it, connection::connect_t handler) +{ + assert(handler); + + socket.close(); + socket.async_connect(*it, [&socket, it, handler] (auto code) mutable { + if (code && ++it != tcp::resolver::iterator()) + wrap_connect(socket, it, std::move(handler)); + else + handler(code); + }); +} + +template +void wrap_resolve(Socket& socket, + tcp::resolver& resolver, + const std::string& host, + const std::string& port, + connection::connect_t handler) +{ + assert(handler); + + tcp::resolver::query query(host, port); + + resolver.async_resolve(query, [&socket, handler] (auto code, auto it) { + if (code) + handler(code); + else + wrap_connect(socket, it, std::move(handler)); + }); +} + +template +void wrap_recv(Socket& socket, boost::asio::streambuf& buffer, connection::recv_t handler) +{ + assert(handler); + + boost::asio::async_read_until(socket, buffer, "\r\n", [&socket, &buffer, handler] (auto code, auto xfer) { + if (code || xfer == 0U) + handler(std::move(code), message()); + else { + std::string str( + boost::asio::buffers_begin(buffer.data()), + boost::asio::buffers_begin(buffer.data()) + xfer - 2 + ); + + buffer.consume(xfer); + handler(std::move(code), message::parse(str)); + } + }); +} + +template +void wrap_send(Socket& socket, const std::string& message, connection::send_t handler) +{ + assert(handler); + + boost::asio::async_write(socket, boost::asio::buffer(message), [handler, message] (auto code, auto) { + // TODO: xfer + handler(code); + }); +} + +} // !namespace + +void connection::rflush() +{ + if (input_.empty()) + return; + + do_recv(buffer_, [this] (auto code, auto message) { + input_.front()(code, std::move(message)); + input_.pop_front(); + + if (!code) + rflush(); + }); +} + +void connection::sflush() +{ + if (output_.empty()) + return; + + do_send(output_.front().first, [this] (auto code) { + if (output_.front().second) + output_.front().second(code); + + output_.pop_front(); + + if (!code) + sflush(); + }); +} + +void connection::connect(const std::string& host, const std::string& service, connect_t handler) +{ + assert(handler); + + do_connect(host, service, std::move(handler)); +} + +void connection::recv(recv_t handler) +{ + auto in_progress = !input_.empty(); + + input_.push_back(std::move(handler)); + + if (!in_progress) + rflush(); +} + +void connection::send(std::string message, send_t handler) +{ + auto in_progress = !output_.empty(); + + output_.emplace_back(std::move(message + "\r\n"), std::move(handler)); + + if (!in_progress) + sflush(); +} + +message message::parse(const std::string& line) +{ + std::istringstream iss(line); + std::string prefix; + + if (line.empty()) + return {}; + + // Prefix. + if (line[0] == ':') { + iss.ignore(1); + iss >> prefix; + iss.ignore(1); + } + + // Command. + std::string command; + iss >> command; + iss.ignore(1); + + // Arguments. + std::vector args; + std::istreambuf_iterator it(iss), end; + + while (it != end) { + std::string arg; + + if (*it == ':') + arg = std::string(++it, end); + else { + while (!isspace(*it) && it != end) + arg.push_back(*it++); + + // Skip space after param. + if (it != end) + ++it; + } + + args.push_back(std::move(arg)); + } + + return {std::move(prefix), std::move(command), std::move(args)}; +} + +bool message::is_ctcp(unsigned index) const noexcept +{ + auto a = arg(index); + + if (a.empty()) + return false; + + return a.front() == 0x01 && a.back() == 0x01; +} + +std::string message::ctcp(unsigned index) const +{ + assert(is_ctcp(index)); + + return args_[index].substr(1, args_[index].size() - 1); +} + +user user::parse(const std::string& line) +{ + if (line.empty()) + return {"", ""}; + + auto pos = line.find("!"); + + if (pos == std::string::npos) + return {line, ""}; + + return {line.substr(0, pos), line.substr(pos + 1)}; +} + +void ip_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept +{ + wrap_resolve(socket_, resolver_, host, service, std::move(handler)); +} + +void ip_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept +{ + wrap_recv(socket_, buffer, std::move(handler)); +} + +void ip_connection::do_send(const std::string& data, send_t handler) noexcept +{ + wrap_send(socket_, data, std::move(handler)); +} + +#if defined(HAVE_SSL) + +void tls_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept +{ + using boost::asio::ssl::stream_base; + + wrap_resolve(socket_.lowest_layer(), resolver_, host, service, [this, handler] (auto code) { + if (code) + handler(code); + else + socket_.async_handshake(stream_base::client, [this, handler] (auto code) { + handler(code); + }); + }); +} + +void tls_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept +{ + wrap_recv(socket_, buffer, std::move(handler)); +} + +void tls_connection::do_send(const std::string& data, send_t handler) noexcept +{ + wrap_send(socket_, data, std::move(handler)); +} + +#endif // !HAVE_SSL + +} // !irc + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/irc.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/irc.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,535 @@ +/* + * irc.hpp -- low level IRC functions + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_IRC_HPP +#define IRCCD_IRC_HPP + +/** + * \file irc.hpp + * \brief Low level IRC functions. + */ + +#include "sysconfig.hpp" + +#include +#include +#include +#include +#include + +#include + +#if defined(HAVE_SSL) +# include +#endif + +namespace irccd { + +namespace irc { + +class message; + +/** + * \brief Describe errors. + */ +enum class err { + nosuchnick = 401, + nosuchserver = 402, + nosuchchannel = 403, + cannotsendtochan = 404, + toomanychannels = 405, + wasnosuchnick = 406, + toomanytargets = 407, + noorigin = 409, + norecipient = 411, + notexttosend = 412, + notoplevel = 413, + wildtoplevel = 414, + unknowncommand = 421, + nomotd = 422, + noadmininfo = 423, + fileerror = 424, + nonicknamegiven = 431, + erroneusnickname = 432, + nicknameinuse = 433, + nickcollision = 436, + usernotinchannel = 441, + notonchannel = 442, + useronchannel = 443, + nologin = 444, + summondisabled = 445, + usersdisabled = 446, + notregistered = 451, + needmoreparams = 461, + alreadyregistred = 462, + nopermforhost = 463, + passwdmismatch = 464, + yourebannedcreep = 465, + keyset = 467, + channelisfull = 471, + unknownmode = 472, + inviteonlychan = 473, + bannedfromchan = 474, + badchannelkey = 475, + noprivileges = 481, + chanoprivsneeded = 482, + cantkillserver = 483, + nooperhost = 491, + umodeunknownflag = 501, + usersdontmatch = 502 +}; + +/** + * \brief Describe numeric replies. + */ +enum class rpl { + none = 300, + userhost = 302, + ison = 303, + away = 301, + unaway = 305, + nowaway = 306, + whoisuser = 311, + whoisserver = 312, + whoisoperator = 313, + whoisidle = 317, + endofwhois = 318, + whoischannels = 319, + whowasuser = 314, + endofwhowas = 369, + liststart = 321, + list = 322, + listend = 323, + channelmodeis = 324, + notopic = 331, + topic = 332, + inviting = 341, + summoning = 342, + version = 351, + whoreply = 352, + endofwho = 315, + namreply = 353, + endofnames = 366, + links = 364, + endoflinks = 365, + banlist = 367, + endofbanlist = 368, + info = 371, + endofinfo = 374, + motdstart = 375, + motd = 372, + endofmotd = 376, + youreoper = 381, + rehashing = 382, + time = 391, + userstart = 392, + users = 393, + endofusers = 394, + nousers = 395, + tracelink = 200, + traceconnecting = 201, + tracehandshake = 202, + traceunknown = 203, + traceoperator = 204, + traceuser = 205, + traceserver = 206, + tracenewtype = 208, + tracelog = 261, + statslinkinfo = 211, + statscommands = 212, + statscline = 213, + statsnline = 214, + statsiline = 215, + statskline = 216, + statsyline = 218, + endofstats = 219, + statslline = 241, + statsuptime = 242, + statsoline = 243, + statshline = 244, + umodeis = 221, + luserclient = 251, + luserop = 252, + luserunknown = 253, + luserchannels = 254, + luserme = 255, + adminme = 256, + adminloc1 = 257, + adminloc2 = 258, + adminemail = 259 +}; + +/** + * \brief Describe a IRC message + */ +class message { +private: + std::string prefix_; //!< optional prefix + std::string command_; //!< command (maybe string or code) + std::vector args_; //!< parameters + +public: + /** + * Constructor. + * + * \param prefix the optional prefix + * \param command the command string or number + * \param args the arguments + */ + inline message(std::string prefix = "", std::string command = "", std::vector args = {}) noexcept + : prefix_(std::move(prefix)) + , command_(std::move(command)) + , args_(std::move(args)) + { + } + + /** + * Get the prefix. + * + * \return the prefix + */ + inline const std::string& prefix() const noexcept + { + return prefix_; + } + + /** + * Get the command. + * + * \return the command + */ + inline const std::string& command() const noexcept + { + return command_; + } + + /** + * Get the arguments. + * + * \return the arguments + */ + inline const std::vector& args() const noexcept + { + return args_; + } + + /** + * Check if the message is defined. + * + * \return true if not empty + */ + inline operator bool() const noexcept + { + return !command_.empty(); + } + + /** + * Check if the message is empty. + * + * \return true if empty + */ + inline bool operator!() const noexcept + { + return command_.empty(); + } + + /** + * Check if the command is of the given enum number. + * + * \return true if command is a number and equals to e + */ + template + inline bool is(Enum e) const noexcept + { + try { + return std::stoi(command_) == static_cast(e); + } catch (...) { + return false; + } + } + + /** + * Convenient function that returns an empty string if the nth argument is + * not defined. + * + * \param index the index + * \return a string or empty if out of bounds + */ + inline const std::string& arg(unsigned short index) const noexcept + { + static const std::string dummy; + + return (index >= args_.size()) ? dummy : args_[index]; + } + + /** + * Tells if the message is a CTCP. + * + * \param index the param index (maybe out of bounds) + * \return true if CTCP + */ + bool is_ctcp(unsigned index) const noexcept; + + /** + * Parse a CTCP message. + * + * \pre is_ctcp(index) + * \param index the param index + * \return the CTCP command + */ + std::string ctcp(unsigned index) const; + + /** + * Parse a IRC message. + * + * \param line the buffer content (without \r\n) + * \return the message (maybe empty if line is empty) + */ + static message parse(const std::string& line); +}; + +/** + * \brief Describe a user. + */ +class user { +private: + std::string nick_; + std::string host_; + +public: + /** + * Construct a user. + * + * \param the nickname + * \param the hostname + */ + inline user(std::string nick, std::string host) noexcept + : nick_(std::move(nick)) + , host_(std::move(host)) + { + } + + /** + * Get the nick part. + * + * \return the nickname + */ + inline const std::string& nick() const noexcept + { + return nick_; + } + + /** + * Get the host part. + * + * \return the host part + */ + inline const std::string& host() const noexcept + { + return host_; + } + + /** + * Parse a nick/host combination. + * + * \param line the line to parse + * \return a user + */ + static user parse(const std::string& line); +}; + +/** + * \brief Abstract connection to a server. + */ +class connection { +public: + /** + * Handler for connecting. + */ + using connect_t = std::function; + + /** + * Handler for receiving. + */ + using recv_t = std::function; + + /** + * Handler for sending. + */ + using send_t = std::function; + +private: + using buffer_t = boost::asio::streambuf; + using input_t = std::deque; + using output_t = std::deque>; + + buffer_t buffer_; + input_t input_; + output_t output_; + + void rflush(); + void sflush(); + +protected: + /** + * Do the connection. + * + * \param host the hostname + * \param service the service or port number + * \param handler the non-null handler + */ + virtual void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept = 0; + + /** + * Receive some data. + * + * \param buffer the buffer to complete + * \param handler the non-null handler + */ + virtual void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept = 0; + + /** + * Send data. + * + * \param data the data to send + * \param handler the non-null handler + */ + virtual void do_send(const std::string& data, send_t handler) noexcept = 0; + +public: + /** + * Default constructor. + */ + connection() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~connection() = default; + + /** + * Connect to the host. + * + * \pre handler the handler + * \param host the host + * \param service the service or port number + * \param handler the non-null handler + */ + void connect(const std::string& host, const std::string& service, connect_t handler); + + /** + * Start receiving data. + * + * \param handler the handler to call + */ + void recv(recv_t handler); + + /** + * Start sending data. + * + * \param message the raw message + * \param handler the handler to call + */ + void send(std::string message, send_t handler = nullptr); +}; + +/** + * \brief Clear TCP connection + */ +class ip_connection : public connection { +private: + boost::asio::ip::tcp::socket socket_; + boost::asio::ip::tcp::resolver resolver_; + +protected: + /** + * \copydoc connection::do_connect + */ + void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override; + + /** + * \copydoc connection::do_recv + */ + void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override; + + /** + * \copydoc connection::do_send + */ + void do_send(const std::string& data, send_t handler) noexcept override; + +public: + /** + * Constructor. + * + * \param service the io service + */ + inline ip_connection(boost::asio::io_service& service) noexcept + : socket_(service) + , resolver_(service) + { + } +}; + +#if defined(HAVE_SSL) + +/** + * \brief SSL connection + */ +class tls_connection : public connection { +private: + boost::asio::ssl::context context_; + boost::asio::ssl::stream socket_; + boost::asio::ip::tcp::resolver resolver_; + +protected: + /** + * \copydoc connection::do_connect + */ + void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override; + + /** + * \copydoc connection::do_recv + */ + void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override; + + /** + * \copydoc connection::do_send + */ + void do_send(const std::string& data, send_t handler) noexcept override; + +public: + /** + * Constructor. + * + * \param service the io service + */ + inline tls_connection(boost::asio::io_service& service) noexcept + : context_(boost::asio::ssl::context::sslv23) + , socket_(service, context_) + , resolver_(service) + { + } +}; + +#endif // !HAVE_SSL + +} // !irc + +} // !irccd + +#endif // !IRCCD_IRC_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/irccd.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/irccd.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,297 @@ +/* + * irccd.cpp -- main irccd class + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 +#include +#include + +#include "command_service.hpp" +#include "irccd.hpp" +#include "plugin_service.hpp" +#include "rule_service.hpp" +#include "server_service.hpp" +#include "transport_service.hpp" + +namespace irccd { + +namespace { + +class log_filter : public log::filter { +private: + std::string info_; + std::string warning_; + std::string debug_; + + std::string convert(const std::string& tmpl, std::string input) const + { + if (tmpl.empty()) + return input; + + string_util::subst params; + + params.flags &= ~(string_util::subst_flags::irc_attrs); + params.keywords.emplace("message", std::move(input)); + + return string_util::format(tmpl, params); + } + +public: + inline log_filter(std::string info, std::string warning, std::string debug) noexcept + : info_(std::move(info)) + , warning_(std::move(warning)) + , debug_(std::move(debug)) + { + } + + std::string pre_debug(std::string input) const override + { + return convert(debug_, std::move(input)); + } + + std::string pre_info(std::string input) const override + { + return convert(info_, std::move(input)); + } + + std::string pre_warning(std::string input) const override + { + return convert(warning_, std::move(input)); + } +}; + +void load_log_file(const ini::section& sc) +{ + /* + * TODO: improve that with CMake options. + */ +#if defined(IRCCD_SYSTEM_WINDOWS) + std::string normal = "log.txt"; + std::string errors = "errors.txt"; +#else + std::string normal = "/var/log/irccd/log.txt"; + std::string errors = "/var/log/irccd/errors.txt"; +#endif + + ini::section::const_iterator it; + + if ((it = sc.find("path-logs")) != sc.end()) + normal = it->value(); + if ((it = sc.find("path-errors")) != sc.end()) + errors = it->value(); + + try { + log::set_logger(std::make_unique(std::move(normal), std::move(errors))); + } catch (const std::exception& ex) { + log::warning() << "logs: " << ex.what() << std::endl; + } +} + +void load_log_syslog() +{ +#if defined(HAVE_SYSLOG) + log::set_logger(std::make_unique()); +#else + log::warning() << "logs: syslog is not available on this platform" << std::endl; +#endif // !HAVE_SYSLOG +} + +} // !namespace + +void irccd::load_logs() +{ + auto sc = config_.section("logs"); + + if (sc.empty()) + return; + + log::set_verbose(string_util::is_identifier(sc.get("verbose").value())); + + auto type = sc.get("type").value(); + + if (!type.empty()) { + // Console is the default, no test case. + if (type == "file") + load_log_file(sc); + else if (type == "syslog") + load_log_syslog(); + else if (type != "console") + log::warning() << "logs: invalid log type '" << type << std::endl; + } +} + +void irccd::load_formats() +{ + auto sc = config_.section("format"); + + if (sc.empty()) + return; + + log::set_filter(std::make_unique( + sc.get("info").value(), + sc.get("warning").value(), + sc.get("debug").value() + )); +} + +void irccd::load_pid() +{ + auto path = config_.value("general", "pidfile"); + + if (path.empty()) + return; + +#if defined(HAVE_GETPID) + std::ofstream out(path, std::ofstream::trunc); + + if (!out) + log::warning() << "irccd: could not open" << path << ": " << std::strerror(errno) << std::endl; + else { + log::debug() << "irccd: pid written in " << path << std::endl; + out << getpid() << std::endl; + } +#else + log::warning() << "irccd: pidfile not supported on this platform" << std::endl; +#endif +} + +void irccd::load_gid() +{ + auto gid = config_.value("general", "gid"); + + if (gid.empty()) + return; + +#if defined(HAVE_SETGID) + try { + sys::set_gid(gid); + } catch (const std::exception& ex) { + log::warning() << "irccd: failed to set gid: " << ex.what() << std::endl; + } +#else + log::warning() << "irccd: gid option not supported" << std::endl; +#endif +} + +void irccd::load_uid() +{ + auto uid = config_.value("general", "gid"); + + if (uid.empty()) + return; + +#if defined(HAVE_SETUID) + try { + sys::set_uid(uid); + } catch (const std::exception& ex) { + log::warning() << "irccd: failed to set uid: " << ex.what() << std::endl; + } +#else + log::warning() << "irccd: uid option not supported" << std::endl; +#endif +} + +irccd::irccd(boost::asio::io_service& service, std::string config) + : config_(std::move(config)) + , service_(service) + , command_service_(std::make_unique()) + , server_service_(std::make_unique(*this)) + , tpt_service_(std::make_unique(*this)) + , rule_service_(std::make_unique()) + , plugin_service_(std::make_unique(*this)) +{ +} + +irccd::~irccd() = default; + +void irccd::load() noexcept +{ + /* + * Order matters, please be careful when changing this. + * + * 1. Open logs as early as possible to use the defined outputs on any + * loading errors. + */ + + // [logs] and [format] sections. + load_logs(); + load_formats(); + + if (!loaded_) + log::info() << "irccd: loading configuration from " << config_.path() << std::endl; + else + log::info() << "irccd: reloading configuration" << std::endl; + + // [general] section. + if (!loaded_) { + load_pid(); + load_gid(); + load_uid(); + } + + if (!loaded_) + tpt_service_->load(config_); + + server_service_->load(config_); + plugin_service_->load(config_); + rule_service_->load(config_); + + // Mark as loaded. + loaded_ = true; +} + +const boost::system::error_category& irccd_category() +{ + static const class category : public boost::system::error_category { + public: + const char* name() const noexcept override + { + return "irccd"; + } + + std::string message(int e) const override + { + switch (static_cast(e)) { + case irccd_error::error::not_irccd: + return "daemon is not irccd instance"; + case irccd_error::error::incompatible_version: + return "major version is incompatible"; + case irccd_error::error::auth_required: + return "authentication is required"; + case irccd_error::error::invalid_auth: + return "invalid authentication"; + case irccd_error::error::invalid_message: + return "invalid message"; + case irccd_error::error::invalid_command: + return "invalid command"; + case irccd_error::error::incomplete_message: + return "command requires more arguments"; + default: + return "no error"; + } + } + } category; + + return category; +} + +boost::system::error_code make_error_code(irccd_error::error e) +{ + return {static_cast(e), irccd_category()}; +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/irccd.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/irccd.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,259 @@ +/* + * irccd.hpp -- main irccd class + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_HPP +#define IRCCD_HPP + +/** + * \file irccd.hpp + * \brief Base class for irccd front end. + */ + +#include "sysconfig.hpp" + +#include + +#include + +#include "config.hpp" + +/** + * \brief Main irccd namespace + */ +namespace irccd { + +class command_service; +class plugin_service; +class rule_service; +class server_service; +class transport_service; + +/** + * \brief Irccd main instance. + */ +class irccd { +private: + // Configurations. + class config config_; + + // Main io service. + boost::asio::io_service& service_; + + // Tells if the configuration has already been called. + bool loaded_{false}; + + // Services. + std::shared_ptr command_service_; + std::shared_ptr server_service_; + std::shared_ptr tpt_service_; + std::shared_ptr rule_service_; + std::shared_ptr plugin_service_; + + // Not copyable and not movable because services has references to irccd. + irccd(const irccd&) = delete; + irccd(irccd&&) = delete; + + irccd& operator=(const irccd&) = delete; + irccd& operator=(irccd&&) = delete; + + // Load functions. + void load_logs(); + void load_formats(); + void load_pid(); + void load_gid(); + void load_uid(); + +public: + /** + * Prepare standard services. + * + * \param service the service + * \param config the optional path to the configuration. + */ + irccd(boost::asio::io_service& service, std::string config = ""); + + /** + * Default destructor. + */ + ~irccd(); + + /** + * Get the current configuration. + * + * \return the configuration + */ + inline const class config& config() const noexcept + { + return config_; + } + + /** + * Set the configuration. + * + * \param cfg the new config + */ + inline void set_config(class config cfg) noexcept + { + config_ = std::move(cfg); + } + + /** + * Get the underlying io service. + * + * \return the service + */ + inline const boost::asio::io_service& service() const noexcept + { + return service_; + } + + /** + * Overloaded function. + * + * \return the service + */ + inline boost::asio::io_service& service() noexcept + { + return service_; + } + + /** + * Access the command service. + * + * \return the service + */ + inline command_service& commands() noexcept + { + return *command_service_; + } + + /** + * Access the server service. + * + * \return the service + */ + inline server_service& servers() noexcept + { + return *server_service_; + } + + /** + * Access the transport service. + * + * \return the service + */ + inline transport_service& transports() noexcept + { + return *tpt_service_; + } + + /** + * Access the rule service. + * + * \return the service + */ + inline rule_service& rules() noexcept + { + return *rule_service_; + } + + /** + * Access the plugin service. + * + * \return the service + */ + inline plugin_service& plugins() noexcept + { + return *plugin_service_; + } + + /** + * Load and re-apply the configuration to the daemon. + */ + void load() noexcept; +}; + +/** + * \brief Irccd error. + */ +class irccd_error : public boost::system::system_error { +public: + /** + * \brief Irccd related errors (1..999) + */ + enum error { + //!< No error. + no_error = 0, + + //!< The connected peer is not irccd. + not_irccd, + + //!< The irccd version is too different. + incompatible_version, + + //!< Authentication was required but not issued. + auth_required, + + //!< Authentication was invalid. + invalid_auth, + + //!< The message was not a valid JSON object. + invalid_message, + + //!< The specified command does not exist. + invalid_command, + + //!< The specified command requires more arguments. + incomplete_message, + }; + + /** + * Inherited constructors. + */ + using system_error::system_error; +}; + +/** + * Get the irccd error category singleton. + * + * \return the singleton + */ +const boost::system::error_category& irccd_category(); + +/** + * Create a boost::system::error_code from irccd_error::error enum. + * + * \param e the error code + */ +boost::system::error_code make_error_code(irccd_error::error e); + +} // !irccd + +namespace boost { + +namespace system { + +template <> +struct is_error_code_enum : public std::true_type { +}; + +} // !system + +} // !boost + +#endif // !IRCCD_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/local_transport_server.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/local_transport_server.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,44 @@ +/* + * local_transport_server.hpp -- server side transports (Unix support) + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_LOCAL_TRANSPORT_SERVER_HPP +#define IRCCD_LOCAL_TRANSPORT_SERVER_HPP + +/** + * \file local_transport_server.hpp + * \brief Server side transports (Unix support). + */ + +#include + +#if !defined(IRCCD_SYSTEM_WINDOWS) + +#include "basic_transport_server.hpp" + +namespace irccd { + +/** + * Convenient type for UNIX local sockets. + */ +using local_transport_server = basic_transport_server; + +#endif // !_WIN32 + +} // !irccd + +#endif // !IRCCD_LOCAL_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/plugin.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/plugin.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,107 @@ +/* + * plugin.cpp -- irccd JavaScript plugin interface + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 + +#include + +#include "plugin.hpp" +#include "system.hpp" + +namespace fs = boost::filesystem; + +namespace irccd { + +std::shared_ptr plugin_loader::find(const std::string& name) +{ + std::vector filenames; + + if (directories_.empty()) + filenames = sys::plugin_filenames(name, extensions_); + else { + for (const auto& dir : directories_) + for (const auto& ext : extensions_) + filenames.push_back(dir + "/" + name + ext); + } + + for (const auto& candidate : filenames) { + boost::system::error_code ec; + + if (!boost::filesystem::exists(candidate, ec) || ec) + continue; + + auto plugin = open(name, candidate); + + if (plugin) + return plugin; + } + + throw plugin_error(plugin_error::not_found, name); +} + +plugin_error::plugin_error(error errc, std::string name, std::string message) noexcept + : system_error(make_error_code(errc)) + , name_(std::move(name)) + , message_(std::move(message)) +{ + std::ostringstream oss; + + oss << "plugin " << name_ << ": " << code().message(); + + std::istringstream iss(message_); + std::string line; + + while (getline(iss, line)) + oss << "\nplugin " << name_ << ": " << line; + + what_ = oss.str(); +} + +const boost::system::error_category& plugin_category() +{ + static const class category : public boost::system::error_category { + public: + const char* name() const noexcept override + { + return "plugin"; + } + + std::string message(int e) const override + { + switch (static_cast(e)) { + case plugin_error::not_found: + return "plugin not found"; + case plugin_error::exec_error: + return "plugin exec error"; + case plugin_error::already_exists: + return "plugin already exists"; + default: + return "no error"; + } + } + } category; + + return category; +} + +boost::system::error_code make_error_code(plugin_error::error e) +{ + return {static_cast(e), plugin_category()}; +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/plugin.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/plugin.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,620 @@ +/* + * plugin.hpp -- irccd JavaScript plugin interface + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_PLUGIN_HPP +#define IRCCD_PLUGIN_HPP + +/** + * \file plugin.hpp + * \brief irccd plugins + */ + +/** + * \defgroup plugins Plugins + * \brief Plugin management. + */ + +#include "sysconfig.hpp" + +#include +#include +#include +#include +#include + +#include "server.hpp" +#include "util.hpp" + +namespace irccd { + +class irccd; + +/** + * \brief Configuration map extract from config file. + */ +using plugin_config = std::unordered_map; + +/** + * \brief Formats for plugins. + */ +using plugin_formats = std::unordered_map; + +/** + * \brief Paths for plugins. + */ +using plugin_paths = std::unordered_map; + +/** + * \ingroup plugins + * \brief Abstract plugin. + * + * A plugin is identified by name and can be loaded and unloaded at runtime. + */ +class plugin : public std::enable_shared_from_this { +private: + // Plugin information + std::string name_; + std::string path_; + + // Metadata + std::string author_{"unknown"}; + std::string license_{"unknown"}; + std::string summary_{"unknown"}; + std::string version_{"unknown"}; + +public: + /** + * Constructor. + * + * \param name the plugin id + * \param path the fully resolved path to the plugin + * \throws std::runtime_error on errors + */ + inline plugin(std::string name, std::string path) noexcept + : name_(std::move(name)) + , path_(std::move(path)) + { + } + + /** + * Temporary, close all timers. + */ + virtual ~plugin() = default; + + /** + * Get the plugin name. + * + * \return the plugin name + */ + inline const std::string& name() const noexcept + { + return name_; + } + + /** + * Get the plugin path. + * + * \return the plugin path + * \note some plugins may not exist on the disk + */ + inline const std::string& path() const noexcept + { + return path_; + } + + /** + * Get the author. + * + * \return the author + */ + inline const std::string& author() const noexcept + { + return author_; + } + + /** + * Set the author. + * + * \param author the author + */ + inline void set_author(std::string author) noexcept + { + author_ = std::move(author); + } + + /** + * Get the license. + * + * \return the license + */ + inline const std::string& license() const noexcept + { + return license_; + } + + /** + * Set the license. + * + * \param license the license + */ + inline void set_license(std::string license) noexcept + { + license_ = std::move(license); + } + + /** + * Get the summary. + * + * \return the summary + */ + inline const std::string& summary() const noexcept + { + return summary_; + } + + /** + * Set the summary. + * + * \param summary the summary + */ + inline void set_summary(std::string summary) noexcept + { + summary_ = std::move(summary); + } + + /** + * Get the version. + * + * \return the version + */ + inline const std::string& version() const noexcept + { + return version_; + } + + /** + * Set the version. + * + * \param version the version + */ + inline void set_version(std::string version) noexcept + { + version_ = std::move(version); + } + + /** + * Access the plugin configuration. + * + * \return the config + */ + virtual plugin_config config() + { + return {}; + } + + /** + * Set the configuration. + * + * \param config the configuration + */ + virtual void set_config(plugin_config config) + { + util::unused(config); + } + + /** + * Access the plugin formats. + * + * \return the format + */ + virtual plugin_formats formats() + { + return {}; + } + + /** + * Set the formats. + * + * \param formats the formats + */ + virtual void set_formats(plugin_formats formats) + { + util::unused(formats); + } + + /** + * Access the plugin paths. + * + * \return the paths + */ + virtual plugin_paths paths() + { + return {}; + } + + /** + * Set the paths. + * + * \param paths the paths + */ + virtual void set_paths(plugin_paths paths) + { + util::unused(paths); + } + + /** + * On channel message. This event will call onMessage or + * onCommand if the messages starts with the command character + * plus the plugin name. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_command(irccd& irccd, const message_event& event) + { + util::unused(irccd, event); + } + + /** + * On successful connection. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_connect(irccd& irccd, const connect_event& event) + { + util::unused(irccd, event); + } + + /** + * On invitation. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_invite(irccd& irccd, const invite_event& event) + { + util::unused(irccd, event); + } + + /** + * On join. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_join(irccd& irccd, const join_event& event) + { + util::unused(irccd, event); + } + + /** + * On kick. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_kick(irccd& irccd, const kick_event& event) + { + util::unused(irccd, event); + } + + /** + * On load. + * + * \param irccd the irccd instance + */ + virtual void on_load(irccd& irccd) + { + util::unused(irccd); + } + + /** + * On channel message. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_message(irccd& irccd, const message_event& event) + { + util::unused(irccd, event); + } + + /** + * On CTCP Action. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_me(irccd& irccd, const me_event& event) + { + util::unused(irccd, event); + } + + /** + * On user mode change. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_mode(irccd& irccd, const mode_event& event) + { + util::unused(irccd, event); + } + + /** + * On names listing. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_names(irccd& irccd, const names_event& event) + { + util::unused(irccd, event); + } + + /** + * On nick change. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_nick(irccd& irccd, const nick_event& event) + { + util::unused(irccd, event); + } + + /** + * On user notice. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_notice(irccd& irccd, const notice_event& event) + { + util::unused(irccd, event); + } + + /** + * On part. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_part(irccd& irccd, const part_event& event) + { + util::unused(irccd, event); + } + + /** + * On reload. + * + * \param irccd the irccd instance + */ + virtual void on_reload(irccd& irccd) + { + util::unused(irccd); + } + + /** + * On topic change. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_topic(irccd& irccd, const topic_event& event) + { + util::unused(irccd, event); + } + + /** + * On unload. + * + * \param irccd the irccd instance + */ + virtual void on_unload(irccd& irccd) + { + util::unused(irccd); + } + + /** + * On whois information. + * + * \param irccd the irccd instance + * \param event the event + */ + virtual void on_whois(irccd& irccd, const whois_event& event) + { + util::unused(irccd, event); + } +}; + +/** + * \brief Abstract interface for searching plugins. + * + * This class is used to make loading of plugins extensible, the plugin_service + * knows some predefined plugins loaders and use them to search for available + * plugins. + * + * This makes easier to implement new plugins or new ways of loading them. + * + * \see dynlib_plugin_loader + * \see js_plugin_loader + */ +class plugin_loader { +private: + std::vector directories_; + std::vector extensions_; + +public: + /** + * Construct the loader with a predefined set of directories and extensions. + * + * If directories is not specified, a sensible default list of system and + * user paths are searched. + * + * \pre !extensions.empty() + * \param directories optional list of directories to search + * \param extensions the non empty list of extensions supported + */ + inline plugin_loader(std::vector directories, + std::vector extensions) noexcept + : directories_(std::move(directories)) + , extensions_(std::move(extensions)) + { + assert(!extensions_.empty()); + } + + /** + * Set directories where to search plugins. + * + * \param dirs the directories + */ + inline void set_directories(std::vector directories) + { + directories_ = std::move(directories); + } + + /** + * Set supported extensions for this loader. + * + * \pre !extensions.empty() + * \param extensions the extensions (with the dot) + */ + inline void set_extensions(std::vector extensions) + { + assert(!extensions.empty()); + + extensions_ = std::move(extensions); + } + + /** + * Try to open the plugin specified by path. + * + * The implementation must test if the plugin is suitable for opening, by + * testing extension for example. + * + * \param file the file + */ + virtual std::shared_ptr open(const std::string& id, + const std::string& file) = 0; + + /** + * Search for a plugin named by this id. + * + * \param id the plugin id + * \return the plugin + */ + virtual std::shared_ptr find(const std::string& id); +}; + +/** + * \brief Plugin error. + */ +class plugin_error : public boost::system::system_error { +public: + /** + * \brief Server related errors (3000..3999) + */ + enum error { + //!< No error. + no_error = 0, + + //!< The specified plugin is not found. + not_found = 2000, + + //!< The plugin was unable to run the function. + exec_error, + + //!< The plugin is already loaded. + already_exists, + }; + +private: + std::string name_; + std::string message_; + std::string what_; + +public: + /** + * Constructor. + * + * \param code the error code + * \param name the plugin name + * \param message the optional message (e.g. error from plugin) + */ + plugin_error(error code, std::string name, std::string message = "") noexcept; + + /** + * Get the plugin name. + * + * \return the name + */ + inline const std::string& name() const noexcept + { + return name_; + } + + /** + * Get the additional message. + * + * \return the message + */ + inline const std::string& message() const noexcept + { + return message_; + } + + /** + * Get message appropriate for use with logger. + */ + const char* what() const noexcept override + { + return what_.c_str(); + } +}; + +/** + * Get the plugin error category singleton. + * + * \return the singleton + */ +const boost::system::error_category& server_category(); + +/** + * Create a boost::system::error_code from plugin_error::error enum. + * + * \param e the error code + */ +boost::system::error_code make_error_code(plugin_error::error e); + +} // !irccd + +namespace boost { + +namespace system { + +template <> +struct is_error_code_enum : public std::true_type { +}; + +} // !system + +} // !boost + +#endif // !IRCCD_PLUGIN_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/plugin_service.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/plugin_service.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,232 @@ +/* + * plugin_service.cpp -- plugin service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 +#include + +#include "config.hpp" +#include "irccd.hpp" +#include "plugin_service.hpp" +#include "string_util.hpp" +#include "system.hpp" + +namespace irccd { + +namespace { + +template +Map to_map(const config& conf, const std::string& section) +{ + Map ret; + + for (const auto& opt : conf.doc().get(section)) + ret.emplace(opt.key(), opt.value()); + + return ret; +} + +} // !namespace + +plugin_service::plugin_service(irccd& irccd) noexcept + : irccd_(irccd) +{ +} + +plugin_service::~plugin_service() +{ + for (const auto& plugin : plugins_) { + try { + plugin->on_unload(irccd_); + } catch (const std::exception& ex) { + log::warning() << "plugin: " << plugin->name() << ": " << ex.what() << std::endl; + } + } +} + +bool plugin_service::has(const std::string& name) const noexcept +{ + return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) { + return plugin->name() == name; + }) > 0; +} + +std::shared_ptr plugin_service::get(const std::string& name) const noexcept +{ + auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { + return plugin->name() == name; + }); + + if (it == plugins_.end()) + return nullptr; + + return *it; +} + +std::shared_ptr plugin_service::require(const std::string& name) const +{ + auto plugin = get(name); + + if (!plugin) + throw plugin_error(plugin_error::not_found, name); + + return plugin; +} + +void plugin_service::add(std::shared_ptr plugin) +{ + plugins_.push_back(std::move(plugin)); +} + +void plugin_service::add_loader(std::unique_ptr loader) +{ + loaders_.push_back(std::move(loader)); +} + +plugin_config plugin_service::config(const std::string& id) +{ + return to_map(irccd_.config(), string_util::sprintf("plugin.%s", id)); +} + +plugin_formats plugin_service::formats(const std::string& id) +{ + return to_map(irccd_.config(), string_util::sprintf("format.%s", id)); +} + +plugin_paths plugin_service::paths(const std::string& id) +{ + auto defaults = to_map(irccd_.config(), "paths"); + auto paths = to_map(irccd_.config(), string_util::sprintf("paths.%s", id)); + + // Fill defaults paths. + if (!defaults.count("cache")) + defaults.emplace("cache", sys::cachedir() + "/plugin/" + id); + if (!defaults.count("data")) + paths.emplace("data", sys::datadir() + "/plugin/" + id); + if (!defaults.count("config")) + paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id); + + // Now fill missing fields. + if (!paths.count("cache")) + paths.emplace("cache", defaults["cache"]); + if (!paths.count("data")) + paths.emplace("data", defaults["data"]); + if (!paths.count("config")) + paths.emplace("config", defaults["config"]); + + return paths; +} + +std::shared_ptr plugin_service::open(const std::string& id, + const std::string& path) +{ + for (const auto& loader : loaders_) { + auto plugin = loader->open(id, path); + + if (plugin) + return plugin; + } + + return nullptr; +} + +std::shared_ptr plugin_service::find(const std::string& id) +{ + for (const auto& loader : loaders_) { + auto plugin = loader->find(id); + + if (plugin) + return plugin; + } + + return nullptr; +} + +void plugin_service::load(std::string name, std::string path) +{ + if (has(name)) + throw plugin_error(plugin_error::already_exists, name); + + std::shared_ptr plugin; + + if (path.empty()) + plugin = find(name); + else + plugin = open(name, std::move(path)); + + if (!plugin) + throw plugin_error(plugin_error::not_found, name); + + plugin->set_config(config(name)); + plugin->set_formats(formats(name)); + plugin->set_paths(paths(name)); + + exec(plugin, &plugin::on_load, irccd_); + add(std::move(plugin)); +} + +void plugin_service::reload(const std::string& name) +{ + auto plugin = get(name); + + if (!plugin) + throw plugin_error(plugin_error::not_found, name); + + exec(plugin, &plugin::on_reload, irccd_); +} + +void plugin_service::unload(const std::string& name) +{ + auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { + return plugin->name() == name; + }); + + if (it == plugins_.end()) + throw plugin_error(plugin_error::not_found, name); + + // Erase first, in case of throwing. + auto save = *it; + + plugins_.erase(it); + exec(save, &plugin::on_unload, irccd_); +} + +void plugin_service::load(const class config& cfg) noexcept +{ + for (const auto& option : cfg.section("plugins")) { + if (!string_util::is_identifier(option.key())) + continue; + + auto name = option.key(); + auto p = get(name); + + // Reload the plugin if already loaded. + if (p) { + p->set_config(config(name)); + p->set_formats(formats(name)); + p->set_paths(paths(name)); + } else { + try { + load(name, option.value()); + } catch (const std::exception& ex) { + log::warning(ex.what()); + } + } + } +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/plugin_service.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/plugin_service.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,237 @@ +/* + * plugin_service.hpp -- plugin service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_PLUGIN_SERVICE_HPP +#define IRCCD_PLUGIN_SERVICE_HPP + +/** + * \file plugin_service.hpp + * \brief Plugin service. + */ + +#include +#include +#include +#include + +#include "plugin.hpp" + +namespace irccd { + +class irccd; +class config; + +/** + * \brief Manage plugins. + * \ingroup services + */ +class plugin_service { +private: + irccd& irccd_; + std::vector> plugins_; + std::vector> loaders_; + +public: + /** + * Create the plugin service. + * + * \param irccd the irccd instance + */ + plugin_service(irccd& irccd) noexcept; + + /** + * Destroy plugins. + */ + ~plugin_service(); + + /** + * Get the list of plugins. + * + * \return the list of plugins + */ + inline const std::vector>& list() const noexcept + { + return plugins_; + } + + /** + * Check if a plugin is loaded. + * + * \param name the plugin id + * \return true if has plugin + */ + bool has(const std::string& name) const noexcept; + + /** + * Get a loaded plugin or null if not found. + * + * \param name the plugin id + * \return the plugin or empty one if not found + */ + std::shared_ptr get(const std::string& name) const noexcept; + + /** + * Find a loaded plugin. + * + * \param name the plugin id + * \return the plugin + * \throws std::out_of_range if not found + */ + std::shared_ptr require(const std::string& name) const; + + /** + * Add the specified plugin to the registry. + * + * \pre plugin != nullptr + * \param plugin the plugin + * \note the plugin is only added to the list, no action is performed on it + */ + void add(std::shared_ptr plugin); + + /** + * Add a loader. + * + * \param loader the loader + */ + void add_loader(std::unique_ptr loader); + + /** + * Get the configuration for the specified plugin. + * + * \return the configuration + */ + plugin_config config(const std::string& id); + + /** + * Get the formats for the specified plugin. + * + * \return the formats + */ + plugin_formats formats(const std::string& id); + + /** + * Get the paths for the specified plugin. + * + * If none is defined, return the default ones. + * + * \return the paths + */ + plugin_paths paths(const std::string& id); + + /** + * Generic function for opening the plugin at the given path. + * + * This function will search for every pluginLoader and call open() on it, + * the first one that success will be returned. + * + * \param id the plugin id + * \param path the path to the file + * \return the plugin or nullptr on failures + */ + std::shared_ptr open(const std::string& id, + const std::string& path); + + /** + * Generic function for finding a plugin. + * + * \param id the plugin id + * \return the plugin or nullptr on failures + */ + std::shared_ptr find(const std::string& id); + + /** + * Convenient wrapper that loads a plugin, call onLoad and add it to the + * registry. + * + * Any errors are printed using logger. + * + * \param name the name + * \param path the optional path (searched if empty) + */ + void load(std::string name, std::string path = ""); + + /** + * Unload a plugin and remove it. + * + * \param name the plugin id + */ + void unload(const std::string& name); + + /** + * Reload a plugin by calling onReload. + * + * \param name the plugin name + * \throw std::exception on failures + */ + void reload(const std::string& name); + + /** + * Call a plugin function and throw an exception with the following errors: + * + * - plugin_error::not_found if not loaded + * - plugin_error::exec_error if function failed + * + * \pre plugin != nullptr + * \param plugin the plugin + * \param func the plugin member function (pointer to member) + * \param args the arguments to pass + */ + template + void exec(std::shared_ptr plugin, Func fn, Args&&... args) + { + assert(plugin); + + // TODO: replace with C++17 std::invoke. + try { + ((*plugin).*(fn))(std::forward(args)...); + } catch (const std::exception& ex) { + throw plugin_error(plugin_error::exec_error, plugin->name(), ex.what()); + } catch (...) { + throw plugin_error(plugin_error::exec_error, plugin->name()); + } + } + + /** + * Overloaded function. + * + * \param name the plugin name + * \param func the plugin member function (pointer to member) + * \param args the arguments to pass + */ + template + void exec(const std::string& name, Func fn, Args&&... args) + { + auto plugin = find(name); + + if (!plugin) + throw plugin_error(plugin_error::not_found, plugin->name()); + + exec(plugin, fn, std::forward(args)...); + } + + /** + * Load all plugins. + * + * \param cfg the config + */ + void load(const class config& cfg) noexcept; +}; + +} // !irccd + +#endif // !IRCCD_PLUGIN_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/rule.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/rule.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,89 @@ +/* + * rule.cpp -- rule for server and channels + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 +#include + +#include "rule.hpp" + +namespace irccd { + +bool rule::match_set(const set& set, const std::string& value) const noexcept +{ + return value.empty() || set.empty() || set.count(value) == 1; +} + +rule::rule(set servers, set channels, set origins, set plugins, set events, action_type action) + : servers_(std::move(servers)) + , channels_(std::move(channels)) + , origins_(std::move(origins)) + , plugins_(std::move(plugins)) + , events_(std::move(events)) + , action_(action) +{ +} + +bool rule::match(const std::string& server, + const std::string& channel, + const std::string& nick, + const std::string& plugin, + const std::string& event) const noexcept +{ + auto tolower = [] (auto str) { + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + return str; + }; + + return match_set(servers_, tolower(server)) && + match_set(channels_, tolower(channel)) && + match_set(origins_, tolower(nick)) && + match_set(plugins_, tolower(plugin)) && + match_set(events_, event); +} + +const boost::system::error_category& rule_category() +{ + static const class category : public boost::system::error_category { + public: + const char* name() const noexcept override + { + return "rule"; + } + + std::string message(int e) const override + { + switch (static_cast(e)) { + case rule_error::invalid_action: + return "invalid action given"; + case rule_error::invalid_index: + return "invalid index"; + default: + return "no error"; + } + } + } category; + + return category; +} + +boost::system::error_code make_error_code(rule_error::error e) +{ + return {static_cast(e), rule_category()}; +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/rule.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/rule.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,270 @@ +/* + * rule.hpp -- rule for server and channels + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_RULE_HPP +#define IRCCD_RULE_HPP + +/** + * \file rule.hpp + * \brief Rule description + */ + +#include "sysconfig.hpp" + +#include +#include +#include + +#include + +namespace irccd { + +/** + * \brief Manage rule to activate or deactive events. + */ +class rule { +public: + /** + * List of criterias. + */ + using set = std::unordered_set; + + /** + * \brief Rule action type. + */ + enum class action_type { + accept, //!< The event is accepted (default) + drop //!< The event is dropped + }; + +private: + set servers_; + set channels_; + set origins_; + set plugins_; + set events_; + action_type action_{action_type::accept}; + + /* + * Check if a set contains the value and return true if it is or return + * true if value is empty (which means applicable). + */ + bool match_set(const set&, const std::string&) const noexcept; + +public: + /** + * Rule constructor. + * + * \param servers the server list + * \param channels the channels + * \param nicknames the nicknames + * \param plugins the plugins + * \param events the events + * \param action the rule action + * \throw std::invalid_argument if events are invalid + */ + rule(set servers = {}, + set channels = {}, + set nicknames = {}, + set plugins = {}, + set events = {}, + action_type action = action_type::accept); + + /** + * Check if that rule apply for the given criterias. + * + * \param server the server + * \param channel the channel + * \param nick the origin + * \param plugin the plugin + * \param event the event + * \return true if match + */ + bool match(const std::string& server, + const std::string& channel, + const std::string& nick, + const std::string& plugin, + const std::string& event) const noexcept; + + /** + * Get the action. + * + * \return the action + */ + inline action_type action() const noexcept + { + return action_; + } + + /** + * Set the action. + * + * \pre action must be valid + */ + inline void set_action(action_type action) noexcept + { + assert(action == action_type::accept || action == action_type::drop); + + action_ = action; + } + + /** + * Get the servers. + * + * \return the servers + */ + inline const set& servers() const noexcept + { + return servers_; + } + + /** + * Overloaded function. + * + * \return the servers + */ + inline set& servers() noexcept + { + return servers_; + } + + /** + * Get the channels. + * + * \return the channels + */ + inline const set& channels() const noexcept + { + return channels_; + } + + /** + * Overloaded function. + * + * \return the channels + */ + inline set& channels() noexcept + { + return channels_; + } + + /** + * Get the origins. + * + * \return the origins + */ + inline const set& origins() const noexcept + { + return origins_; + } + + /** + * Get the plugins. + * + * \return the plugins + */ + inline const set& plugins() const noexcept + { + return plugins_; + } + + /** + * Overloaded function. + * + * \return the plugins + */ + inline set& plugins() noexcept + { + return plugins_; + } + + /** + * Get the events. + * + * \return the events + */ + inline const set& events() const noexcept + { + return events_; + } + + /** + * Overloaded function. + * + * \return the events + */ + inline set& events() noexcept + { + return events_; + } +}; + +/** + * \brief Rule error. + */ +class rule_error : public boost::system::system_error { +public: + /** + * \brief Rule related errors (4000..4999) + */ + enum error { + //!< No error. + no_error = 0, + + //!< Invalid action given. + invalid_action = 4000, + + //!< Invalid rule index. + invalid_index, + }; + + /** + * Inherited constructors. + */ + using system_error::system_error; +}; + +/** + * Get the rule error category singleton. + * + * \return the singleton + */ +const boost::system::error_category& rule_category(); + +/** + * Create a boost::system::error_code from rule_error::error enum. + * + * \param e the error code + */ +boost::system::error_code make_error_code(rule_error::error e); + +} // !irccd + +namespace boost { + +namespace system { + +template <> +struct is_error_code_enum : public std::true_type { +}; + +} // !system + +} // !boost + +#endif // !IRCCD_RULE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/rule_service.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/rule_service.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,161 @@ +/* + * rule_service.cpp -- rule service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 + +#include +#include + +#include "config.hpp" +#include "rule_service.hpp" +#include "string_util.hpp" + +namespace irccd { + +namespace { + +rule load_rule(const ini::section& sc) +{ + assert(sc.key() == "rule"); + + // Simple converter from std::vector to std::unordered_set. + auto toset = [] (const auto& v) { + return std::unordered_set(v.begin(), v.end()); + }; + + rule::set servers, channels, origins, plugins, events; + rule::action_type action = rule::action_type::accept; + + // Get the sets. + ini::section::const_iterator it; + + if ((it = sc.find("servers")) != sc.end()) + servers = toset(*it); + if ((it = sc.find("channels")) != sc.end()) + channels = toset(*it); + if ((it = sc.find("origins")) != sc.end()) + origins = toset(*it); + if ((it = sc.find("plugins")) != sc.end()) + plugins = toset(*it); + if ((it = sc.find("channels")) != sc.end()) + channels = toset(*it); + + // Get the action. + auto actionstr = sc.get("action").value(); + + if (actionstr == "drop") + action = rule::action_type::drop; + else if (actionstr == "accept") + action = rule::action_type::accept; + else + throw rule_error(rule_error::invalid_action); + + return { + std::move(servers), + std::move(channels), + std::move(origins), + std::move(plugins), + std::move(events), + action + }; +} + +} // !namespace + +void rule_service::add(rule rule) +{ + rules_.push_back(std::move(rule)); +} + +void rule_service::insert(rule rule, unsigned position) +{ + assert(position <= rules_.size()); + + rules_.insert(rules_.begin() + position, std::move(rule)); +} + +void rule_service::remove(unsigned position) +{ + assert(position < rules_.size()); + + rules_.erase(rules_.begin() + position); +} + +const rule &rule_service::require(unsigned position) const +{ + if (position >= rules_.size()) + throw rule_error(rule_error::invalid_index); + + return rules_[position]; +} + +rule &rule_service::require(unsigned position) +{ + if (position >= rules_.size()) + throw rule_error(rule_error::invalid_index); + + return rules_[position]; +} + +bool rule_service::solve(const std::string& server, + const std::string& channel, + const std::string& origin, + const std::string& plugin, + const std::string& event) noexcept +{ + bool result = true; + + log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s", + server, channel, origin, plugin, event)); + + int i = 0; + for (const auto& rule : rules_) { + auto action = rule.action() == rule::action_type::accept ? "accept" : "drop"; + + log::debug() << " candidate " << i++ << ":\n" + << " servers: " << string_util::join(rule.servers()) << "\n" + << " channels: " << string_util::join(rule.channels()) << "\n" + << " origins: " << string_util::join(rule.origins()) << "\n" + << " plugins: " << string_util::join(rule.plugins()) << "\n" + << " events: " << string_util::join(rule.events()) << "\n" + << " action: " << action << std::endl; + + if (rule.match(server, channel, origin, plugin, event)) + result = rule.action() == rule::action_type::accept; + } + + return result; +} + +void rule_service::load(const config& cfg) noexcept +{ + rules_.clear(); + + for (const auto& section : cfg.doc()) { + if (section.key() != "rule") + continue; + + try { + rules_.push_back(load_rule(section)); + } catch (const std::exception& ex) { + log::warning() << "rule: " << ex.what() << std::endl; + } + } +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/rule_service.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/rule_service.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,129 @@ +/* + * rule_service.hpp -- rule service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_RULE_SERVICE_HPP +#define IRCCD_RULE_SERVICE_HPP + +/** + * \file rule_service.hpp + * \brief Rule service. + */ + +#include + +#include "rule.hpp" + +namespace irccd { + +class config; + +/** + * \brief Store and solve rules. + * \ingroup services + */ +class rule_service { +private: + std::vector rules_; + +public: + /** + * Get the list of rules. + * + * \return the list of rules + */ + inline const std::vector& list() const noexcept + { + return rules_; + } + + /** + * Get the number of rules. + * + * \return the number of rules + */ + inline std::size_t length() const noexcept + { + return rules_.size(); + } + + /** + * Append a rule. + * + * \param rule the rule to append + */ + void add(rule rule); + + /** + * Insert a new rule at the specified position. + * + * \param rule the rule + * \param position the position + */ + void insert(rule rule, unsigned position); + + /** + * Remove a new rule from the specified position. + * + * \pre position must be valid + * \param position the position + */ + void remove(unsigned position); + + /** + * Get a rule at the specified index or throw an exception if not found. + * + * \param position the position + * \return the rule + * \throw std::out_of_range if position is invalid + */ + const rule& require(unsigned position) const; + + /** + * Overloaded function. + * + * \copydoc require + */ + rule& require(unsigned position); + + /** + * Resolve the action to execute with the specified list of rules. + * + * \param server the server name + * \param channel the channel name + * \param origin the origin + * \param plugin the plugin name + * \param event the event name (e.g onKick) + * \return true if the plugin must be called + */ + bool solve(const std::string& server, + const std::string& channel, + const std::string& origin, + const std::string& plugin, + const std::string& event) noexcept; + + /** + * Load rules from the configuration. + * + * \param cfg the config + */ + void load(const config& cfg) noexcept; +}; + +} // !irccd + +#endif // !IRCCD_RULE_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/server.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/server.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,756 @@ +/* + * server.cpp -- an IRC server + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "sysconfig.hpp" + +#include +#include +#include +#include + +#if !defined(IRCCD_SYSTEM_WINDOWS) +# include +# include +# include +# include +#endif + +#include "json_util.hpp" +#include "logger.hpp" +#include "server.hpp" +#include "string_util.hpp" +#include "system.hpp" + +namespace irccd { + +namespace { + +/* + * clean_prefix + * ------------------------------------------------------------------ + * + * Remove the user prefix only if it is present in the mode table, for example + * removes @ from @irccd if and only if @ is a character mode (e.g. operator). + */ +std::string clean_prefix(const std::map& modes, std::string nickname) +{ + if (nickname.length() == 0) + return nickname; + + for (const auto& pair : modes) + if (nickname[0] == pair.second) + nickname.erase(0, 1); + + return nickname; +} + +/* + * isupport_extract_prefixes + * ------------------------------------------------------------------ + * + * Read modes from the IRC event numeric. + */ +std::map isupport_extract_prefixes(const std::string& line) +{ + // FIXME: what if line has different size? + std::pair table[16]; + std::string buf = line.substr(7); + std::map modes; + + for (int i = 0; i < 16; ++i) + table[i] = std::make_pair(-1, -1); + + int j = 0; + bool read_modes = true; + + for (size_t i = 0; i < buf.size(); ++i) { + if (buf[i] == '(') + continue; + if (buf[i] == ')') { + j = 0; + read_modes = false; + continue; + } + + if (read_modes) + table[j++].first = buf[i]; + else + table[j++].second = buf[i]; + } + + // Put these as a map of mode to prefix. + for (int i = 0; i < 16; ++i) { + auto key = static_cast(table[i].first); + auto value = table[i].second; + + modes.emplace(key, value); + } + + return modes; +} + +} // !namespace + +void server::remove_joined_channel(const std::string& channel) +{ + jchannels_.erase(std::remove(jchannels_.begin(), jchannels_.end(), channel), jchannels_.end()); +} + +channel server::split_channel(const std::string& value) +{ + auto pos = value.find(':'); + + if (pos != std::string::npos) + return {value.substr(0, pos), value.substr(pos + 1)}; + + return {value, ""}; +} + +server::server(boost::asio::io_service& service, std::string name) + : name_(std::move(name)) + , service_(service) + , timer_(service) +{ + // Initialize nickname and username. + auto user = sys::username(); + + nickname_ = user.empty() ? "irccd" : user; + username_ = user.empty() ? "irccd" : user; +} + +void server::dispatch_connect(const irc::message&) +{ + state_ = state_t::connected; + on_connect({shared_from_this()}); + + for (const auto& channel : rchannels_) { + log::info() << "server " << name_ << ": auto joining " << channel.name << std::endl; + join(channel.name, channel.password); + } +} + +void server::dispatch_endofnames(const irc::message& msg) +{ + /* + * Called when end of name listing has finished on a channel. + * + * params[0] == originator + * params[1] == channel + * params[2] == End of NAMES list + */ + if (msg.args().size() < 3 || msg.arg(1) == "") + return; + + auto it = names_map_.find(msg.arg(1)); + + if (it != names_map_.end()) { + std::vector list(it->second.begin(), it->second.end()); + + on_names({shared_from_this(), msg.arg(1), std::move(list)}); + + // Don't forget to remove the list. + names_map_.erase(it); + } +} + +void server::dispatch_endofwhois(const irc::message& msg) +{ + /* + * Called when whois is finished. + * + * params[0] == originator + * params[1] == nickname + * params[2] == End of WHOIS list + */ + auto it = whois_map_.find(msg.arg(1)); + + if (it != whois_map_.end()) { + on_whois({shared_from_this(), it->second}); + + // Don't forget to remove. + whois_map_.erase(it); + } +} + +void server::dispatch_invite(const irc::message& msg) +{ + // If join-invite is set, join the channel. + if ((flags_ & join_invite) && is_self(msg.arg(0))) + join(msg.arg(1)); + + on_invite({shared_from_this(), msg.prefix(), msg.arg(1), msg.arg(0)}); +} + +void server::dispatch_isupport(const irc::message& msg) +{ + for (unsigned int i = 0; i < msg.args().size(); ++i) { + if (msg.arg(i).compare(0, 6, "PREFIX") == 0) { + modes_ = isupport_extract_prefixes(msg.arg(i)); + +#if !defined(NDEBUG) + auto show = [this] (auto mode, auto title) { + auto it = modes_.find(mode); + + if (it != modes_.end()) + log::debug(string_util::sprintf(" %-12s: %c", title, it->second)); + }; + + log::debug(string_util::sprintf("server %s: isupport modes:", name_)); + show(channel_mode::creator, "creator"); + show(channel_mode::half_op, "half_op"); + show(channel_mode::op, "op"); + show(channel_mode::protection, "protection"); + show(channel_mode::voiced, "voiced"); +#endif // !NDEBUG + + break; + } + } +} + +void server::dispatch_join(const irc::message& msg) +{ + if (is_self(msg.prefix())) + jchannels_.push_back(msg.arg(0)); + + on_join({shared_from_this(), msg.prefix(), msg.arg(0)}); +} + +void server::dispatch_kick(const irc::message& msg) +{ + if (is_self(msg.arg(1))) { + // Remove the channel from the joined list. + remove_joined_channel(msg.arg(0)); + + // Rejoin the channel if the option has been set and I was kicked. + if (flags_ & auto_rejoin) + join(msg.arg(0)); + } + + on_kick({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1), msg.arg(2)}); +} + +void server::dispatch_mode(const irc::message& msg) +{ + on_mode({ + shared_from_this(), + msg.prefix(), + msg.arg(0), + msg.arg(1), + msg.arg(2), + msg.arg(3), + msg.arg(4) + }); +} + +void server::dispatch_namreply(const irc::message& msg) +{ + /* + * Called multiple times to list clients on a channel. + * + * params[0] == originator + * params[1] == '=' + * params[2] == channel + * params[3] == list of users with their prefixes + * + * IDEA for the future: maybe give the appropriate mode as a second + * parameter in onNames. + */ + if (msg.args().size() < 4 || msg.arg(2) == "" || msg.arg(3) == "") + return; + + auto users = string_util::split(msg.arg(3), " \t"); + + // The listing may add some prefixes, remove them if needed. + for (auto u : users) + names_map_[msg.arg(2)].insert(clean_prefix(modes_, u)); +} + +void server::dispatch_nick(const irc::message& msg) +{ + // Update our nickname. + if (is_self(msg.prefix())) + nickname_ = msg.arg(0); + + on_nick({shared_from_this(), msg.prefix(), msg.arg(0)}); +} + +void server::dispatch_notice(const irc::message& msg) +{ + on_notice({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)}); +} + +void server::dispatch_part(const irc::message& msg) +{ + // Remove the channel from the joined list if I left a channel. + if (is_self(msg.prefix())) + remove_joined_channel(msg.arg(1)); + + on_part({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)}); +} + +void server::dispatch_ping(const irc::message& msg) +{ + assert(msg.command() == "PING"); + + conn_->send(string_util::sprintf("PONG %s", msg.arg(0))); +} + +void server::dispatch_privmsg(const irc::message& msg) +{ + assert(msg.command() == "PRIVMSG"); + + if (msg.is_ctcp(1)) { + auto cmd = msg.ctcp(1); + + if (cmd.compare(0, 6, "ACTION") == 0) + on_me({shared_from_this(), msg.prefix(), msg.arg(0), cmd.substr(7)}); + } else if (is_self(msg.arg(0))) + on_query({shared_from_this(), msg.prefix(), msg.arg(1)}); + else + on_message({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)}); +} + +void server::dispatch_topic(const irc::message& msg) +{ + assert(msg.command() == "TOPIC"); + + on_topic({shared_from_this(), msg.arg(0), msg.arg(1), msg.arg(2)}); +} + +void server::dispatch_whoischannels(const irc::message& msg) +{ + /* + * Called when we have received channels for one user. + * + * params[0] == originator + * params[1] == nickname + * params[2] == list of channels with their prefixes + */ + if (msg.args().size() < 3 || msg.arg(1) == "" || msg.arg(2) == "") + return; + + auto it = whois_map_.find(msg.arg(1)); + + if (it != whois_map_.end()) { + auto channels = string_util::split(msg.arg(2), " \t"); + + // Clean their prefixes. + for (auto& s : channels) + s = clean_prefix(modes_, s); + + it->second.channels = std::move(channels); + } +} + +void server::dispatch_whoisuser(const irc::message& msg) +{ + /* + * Called when whois information has been partially received. + * + * params[0] == originator + * params[1] == nickname + * params[2] == username + * params[3] == host + * params[4] == * (no idea what is that) + * params[5] == realname + */ + if (msg.args().size() < 6 || msg.arg(1) == "" || msg.arg(2) == "" || msg.arg(3) == "" || msg.arg(5) == "") + return; + + class whois info; + + info.nick = msg.arg(1); + info.user = msg.arg(2); + info.host = msg.arg(3); + info.realname = msg.arg(5); + + whois_map_.emplace(info.nick, info); +} + +void server::dispatch(const irc::message& message) +{ + if (message.is(5)) + dispatch_isupport(message); + else if (message.is(irc::err::nomotd) || message.is(irc::rpl::endofmotd)) + dispatch_connect(message); + else if (message.command() == "INVITE") + dispatch_invite(message); + else if (message.command() == "JOIN") + dispatch_join(message); + else if (message.command() == "KICK") + dispatch_kick(message); + else if (message.command() == "MODE") + dispatch_mode(message); + else if (message.command() == "NICK") + dispatch_nick(message); + else if (message.command() == "NOTICE") + dispatch_notice(message); + else if (message.command() == "TOPIC") + dispatch_topic(message); + else if (message.command() == "PART") + dispatch_part(message); + else if (message.command() == "PING") + dispatch_ping(message); + else if (message.command() == "PRIVMSG") + dispatch_privmsg(message); + else if (message.is(irc::rpl::namreply)) + dispatch_namreply(message); + else if (message.is(irc::rpl::endofnames)) + dispatch_endofnames(message); + else if (message.is(irc::rpl::endofwhois)) + dispatch_endofwhois(message); + else if (message.is(irc::rpl::whoischannels)) + dispatch_whoischannels(message); + else if (message.is(irc::rpl::whoisuser)) + dispatch_whoisuser(message); +} + +void server::handle_recv(boost::system::error_code code, irc::message message) +{ + if (code) { + state_ = state_t::disconnected; + conn_ = nullptr; + } else { + dispatch(message); + recv(); + } +} + +void server::recv() +{ + conn_->recv([this] (auto code, auto message) { + handle_recv(std::move(code), std::move(message)); + }); +} + +void server::identify() +{ + assert(state_ == state_t::identifying); + + log::debug(string_util::sprintf("server %s: connected, identifying", name_)); + log::debug(string_util::sprintf("server %s: verifying server", name_)); + + if (!password_.empty()) + conn_->send(string_util::sprintf("PASS %s", password_)); + + conn_->send(string_util::sprintf("NICK %s", nickname_)); + conn_->send(string_util::sprintf("USER %s unknown unknown :%s", username_, realname_)); +} + +void server::wait() +{ + assert(state_ == state_t::waiting); + + timer_.expires_from_now(boost::posix_time::seconds(recodelay_)); + timer_.async_wait([this] (auto) { + recocur_ ++; + connect(); + }); +} + +void server::handle_connect(boost::system::error_code code) +{ + if (code) { + conn_ = nullptr; + log::warning(string_util::sprintf("server %s: error while connecting", name_)); + log::warning(string_util::sprintf("server %s: %s", name_, code.message())); + + // Wait before reconnecting. + if (recotries_ != 0) { + if (recotries_ > 0 && recocur_ >= recotries_) { + log::warning() << "server " << name_ << ": giving up" << std::endl; + + state_ = state_t::disconnected; + on_die(); + } else { + log::warning() << "server " << name_ << ": retrying in " << + recodelay_ << " seconds" << std::endl; + + state_ = state_t::waiting; + wait(); + } + } else + state_ = state_t::disconnected; + } else { + state_ = state_t::identifying; + recocur_ = 0U; + jchannels_.clear(); + + identify(); + recv(); + } +} + +server::~server() +{ + disconnect(); +} + +void server::set_nickname(std::string nickname) +{ + if (state_ == state_t::connected) + conn_->send(string_util::sprintf("NICK %s", nickname)); + else + nickname_ = std::move(nickname); +} + +void server::set_ctcp_version(std::string ctcpversion) +{ + ctcpversion_ = std::move(ctcpversion); +} + +void server::connect() noexcept +{ + assert(state_ == state_t::disconnected || state_ == state_t::waiting); + /* + * This is needed if irccd is started before DHCP or if DNS cache is + * outdated. + */ +#if !defined(IRCCD_SYSTEM_WINDOWS) + (void)res_init(); +#endif + + if (flags_ & ssl) { +#if defined(HAVE_SSL) + conn_ = std::make_unique(service_); +#else + /* + * If SSL is not compiled in, the caller is responsible of not setting + * the flag. + */ + assert(!(flags_ & ssl)); +#endif + } else + conn_ = std::make_unique(service_); + + state_ = state_t::connecting; + conn_->connect(host_, std::to_string(port_), [this] (auto code) { + handle_connect(std::move(code)); + }); +} + +void server::disconnect() noexcept +{ + conn_ = nullptr; + state_ = state_t::disconnected; + on_die(); +} + +void server::reconnect() noexcept +{ + disconnect(); + connect(); +} + +bool server::is_self(const std::string& target) const noexcept +{ + return nickname_ == irc::user::parse(target).nick(); +} + +void server::invite(std::string target, std::string channel) +{ + assert(!target.empty()); + assert(!channel.empty()); + + send(string_util::sprintf("INVITE %s %s", target, channel)); +} + +void server::join(std::string channel, std::string password) +{ + auto it = std::find_if(rchannels_.begin(), rchannels_.end(), [&] (const auto& c) { + return c.name == channel; + }); + + if (it == rchannels_.end()) + rchannels_.push_back({ channel, password }); + else + *it = { channel, password }; + + if (state_ == state_t::connected) { + if (password.empty()) + send(string_util::sprintf("JOIN %s", channel)); + else + send(string_util::sprintf("JOIN %s :%s", channel, password)); + } +} + +void server::kick(std::string target, std::string channel, std::string reason) +{ + assert(!target.empty()); + assert(!channel.empty()); + + if (!reason.empty()) + send(string_util::sprintf("KICK %s %s :%s", channel, target, reason)); + else + send(string_util::sprintf("KICK %s %s", channel, target)); +} + +void server::me(std::string target, std::string message) +{ + assert(!target.empty()); + assert(!message.empty()); + + send(string_util::sprintf("PRIVMSG %s :\x01" "ACTION %s\x01", target, message)); +} + +void server::message(std::string target, std::string message) +{ + assert(!target.empty()); + assert(!message.empty()); + + send(string_util::sprintf("PRIVMSG %s :%s", target, message)); +} + +void server::mode(std::string channel, + std::string mode, + std::string limit, + std::string user, + std::string mask) +{ + assert(!channel.empty()); + assert(!mode.empty()); + + std::ostringstream oss; + + oss << "MODE " << channel << " " << mode; + + if (!limit.empty()) + oss << " " << limit; + if (!user.empty()) + oss << " " << user; + if (!mask.empty()) + oss << " " << mask; + + send(oss.str()); +} + +void server::names(std::string channel) +{ + assert(channel.c_str()); + + send(string_util::sprintf("NAMES %s", channel)); +} + +void server::notice(std::string target, std::string message) +{ + assert(!target.empty()); + assert(!message.empty()); + + send(string_util::sprintf("NOTICE %s :%s", target, message)); +} + +void server::part(std::string channel, std::string reason) +{ + assert(!channel.empty()); + + if (!reason.empty()) + send(string_util::sprintf("PART %s :%s", channel, reason)); + else + send(string_util::sprintf("PART %s", channel)); +} + +void server::send(std::string raw) +{ + assert(state_ == state_t::connected); + assert(!raw.empty()); + + conn_->send(std::move(raw), [this] (auto code) { + if (code) { + state_ = state_t::disconnected; + conn_ = nullptr; + } + }); +} + +void server::topic(std::string channel, std::string topic) +{ + assert(!channel.empty()); + + if (!topic.empty()) + send(string_util::sprintf("TOPIC %s :%s", channel, topic)); + else + send(string_util::sprintf("TOPIC %s", channel)); +} + +void server::whois(std::string target) +{ + assert(!target.empty()); + + send(string_util::sprintf("WHOIS %s %s", target, target)); +} + +server_error::server_error(error code, std::string name) noexcept + : system_error(make_error_code(code)) + , name_(std::move(name)) +{ +} + +const boost::system::error_category& server_category() +{ + static const class category : public boost::system::error_category { + public: + const char* name() const noexcept override + { + return "server"; + } + + std::string message(int e) const override + { + switch (static_cast(e)) { + case server_error::not_found: + return "server not found"; + case server_error::invalid_identifier: + return "invalid identifier"; + case server_error::not_connected: + return "server is not connected"; + case server_error::already_connected: + return "server is already connected"; + case server_error::invalid_port: + return "invalid port number specified"; + case server_error::invalid_reconnect_tries: + return "invalid number of reconnection tries"; + case server_error::invalid_reconnect_timeout: + return "invalid reconnect timeout number"; + case server_error::invalid_hostname: + return "invalid hostname"; + case server_error::invalid_channel: + return "invalid or empty channel"; + case server_error::invalid_mode: + return "invalid or empty mode"; + case server_error::invalid_nickname: + return "invalid nickname"; + case server_error::invalid_ping_timeout: + return "invalid ping timeout"; + case server_error::ssl_disabled: + return "ssl is not enabled"; + default: + return "no error"; + } + } + } category; + + return category; +} + +boost::system::error_code make_error_code(server_error::error e) +{ + return {static_cast(e), server_category()}; +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/server.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/server.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,997 @@ +/* + * server.hpp -- an IRC server + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_SERVER_HPP +#define IRCCD_SERVER_HPP + +/** + * \file server.hpp + * \brief IRC Server. + */ + +#include "sysconfig.hpp" + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "irc.hpp" + +namespace irccd { + +class server; + +/** + * \brief Prefixes for nicknames. + */ +enum class channel_mode { + creator = 'O', //!< Channel creator + half_op = 'h', //!< Half operator + op = 'o', //!< Channel operator + protection = 'a', //!< Unkillable + voiced = 'v' //!< Voice power +}; + +/** + * \brief A channel to join with an optional password. + */ +class channel { +public: + std::string name; //!< the channel to join + std::string password; //!< the optional password +}; + +/** + * \brief Describe a whois information. + */ +class whois { +public: + std::string nick; //!< user's nickname + std::string user; //!< user's user + std::string host; //!< hostname + std::string realname; //!< realname + std::vector channels; //!< the channels where the user is +}; + +/** + * \brief Connection success event. + */ +class connect_event { +public: + std::shared_ptr server; //!< The server. +}; + +/** + * \brief Invite event. + */ +class invite_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. + std::string nickname; //!< The nickname (you). +}; + +/** + * \brief Join event. + */ +class join_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. +}; + +/** + * \brief Kick event. + */ +class kick_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. + std::string target; //!< The target. + std::string reason; //!< The reason (Optional). +}; + +/** + * \brief Message event. + */ +class message_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. + std::string message; //!< The message. +}; + +/** + * \brief CTCP action event. + */ +class me_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. + std::string message; //!< The message. +}; + +/** + * \brief Mode event. + */ +class mode_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel or target. + std::string mode; //!< The mode. + std::string limit; //!< The optional limit. + std::string user; //!< The optional user. + std::string mask; //!< The optional ban mask. +}; + +/** + * \brief Names listing event. + */ +class names_event { +public: + std::shared_ptr server; //!< The server. + std::string channel; //!< The channel. + std::vector names; //!< The names. +}; + +/** + * \brief Nick change event. + */ +class nick_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string nickname; //!< The new nickname. +}; + +/** + * \brief Notice event. + */ +class notice_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel or target. + std::string message; //!< The message. +}; + +/** + * \brief Part event. + */ +class part_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. + std::string reason; //!< The reason. +}; + +/** + * \brief Query event. + */ +class query_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string message; //!< The message. +}; + +/** + * \brief Topic event. + */ +class topic_event { +public: + std::shared_ptr server; //!< The server. + std::string origin; //!< The originator. + std::string channel; //!< The channel. + std::string topic; //!< The topic message. +}; + +/** + * \brief Whois event. + */ +class whois_event { +public: + std::shared_ptr server; //!< The server. + class whois whois; //!< The whois information. +}; + +/** + * \brief The class that connect to a IRC server + * + * The server is a class that stores callbacks which will be called on IRC + * events. It is the lowest part of the connection to a server, it can be used + * directly by the user to connect to a server. + * + * The server has several signals that will be emitted when data has arrived. + * + * When adding a server to the ServerService in irccd, these signals are + * connected to generate events that will be dispatched to the plugins and to + * the transports. + * + * Note: the server is set in non blocking mode, commands are placed in a queue + * and sent when only when they are ready. + */ +class server : public std::enable_shared_from_this { +public: + /** + * \brief Various options for server. + */ + enum { + ipv6 = (1 << 0), //!< Connect using IPv6 + ssl = (1 << 1), //!< Use SSL + ssl_verify = (1 << 2), //!< Verify SSL + auto_rejoin = (1 << 3), //!< Auto rejoin a kick + join_invite = (1 << 4) //!< Join a channel on invitation + }; + + /** + * \brief Describe current server state. + */ + enum class state_t { + disconnected, //!< not connected at all, + connecting, //!< network connection in progress, + identifying, //!< sending nick, user and password commands, + waiting, //!< waiting for reconnection, + connected //!< ready for use + }; + + /** + * Signal: on_connect + * ---------------------------------------------------------- + * + * Triggered when the server is successfully connected. + */ + boost::signals2::signal on_connect; + + /** + * Signal: on_die + * ---------------------------------------------------------- + * + * The server is dead. + */ + boost::signals2::signal on_die; + + /** + * Signal: on_invite + * ---------------------------------------------------------- + * + * Triggered when an invite has been sent to you (the bot). + */ + boost::signals2::signal on_invite; + + /** + * Signal: on_join + * ---------------------------------------------------------- + * + * Triggered when a user has joined the channel, it also includes you. + */ + boost::signals2::signal on_join; + + /** + * Signal: on_kick + * ---------------------------------------------------------- + * + * Triggered when someone has been kicked from a channel. + */ + boost::signals2::signal on_kick; + + /** + * Signal: on_message + * ---------------------------------------------------------- + * + * Triggered when a message on a channel has been sent. + */ + boost::signals2::signal on_message; + + /** + * Signal: on_me + * ---------------------------------------------------------- + * + * Triggered on a CTCP Action. + * + * This is both used in a channel and in a private message so the target may + * be a channel or your nickname. + */ + boost::signals2::signal on_me; + + /** + * Signal: on_mode + * ---------------------------------------------------------- + * + * Triggered when the server changed your user mode. + */ + boost::signals2::signal on_mode; + + /** + * Signal: on_names + * ---------------------------------------------------------- + * + * Triggered when names listing has finished on a channel. + */ + boost::signals2::signal on_names; + + /** + * Signal: on_nick + * ---------------------------------------------------------- + * + * Triggered when someone changed its nickname, it also includes you. + */ + boost::signals2::signal on_nick; + + /** + * Signal: on_notice + * ---------------------------------------------------------- + * + * Triggered when someone has sent a notice to you. + */ + boost::signals2::signal on_notice; + + /** + * Signal: on_part + * ---------------------------------------------------------- + * + * Triggered when someone has left the channel. + */ + boost::signals2::signal on_part; + + /** + * Signal: on_query + * ---------------------------------------------------------- + * + * Triggered when someone has sent you a private message. + */ + boost::signals2::signal on_query; + + /** + * Signal: on_topic + * ---------------------------------------------------------- + * + * Triggered when someone changed the channel topic. + */ + boost::signals2::signal on_topic; + + /** + * Signal: on_whois + * ---------------------------------------------------------- + * + * Triggered when whois information has been received. + */ + boost::signals2::signal on_whois; + +private: + state_t state_{state_t::disconnected}; + + // Requested and joined channels. + std::vector rchannels_; + std::vector jchannels_; + + // Identifier. + std::string name_; + + // Connection information. + std::string host_; + std::string password_; + std::uint16_t port_{6667}; + std::uint8_t flags_{0}; + + // Identity. + std::string nickname_; + std::string username_; + std::string realname_{"IRC Client Daemon"}; + std::string ctcpversion_{"IRC Client Daemon"}; + + // Settings. + std::string command_char_{"!"}; + std::int8_t recotries_{-1}; + std::uint16_t recodelay_{30}; + std::uint16_t timeout_{1000}; + + // Server information. + std::map modes_; + + // Misc. + boost::asio::io_service& service_; + boost::asio::deadline_timer timer_; + std::unique_ptr conn_; + std::int8_t recocur_{0}; + std::map> names_map_; + std::map whois_map_; + + void remove_joined_channel(const std::string& channel); + + void dispatch_connect(const irc::message&); + void dispatch_endofnames(const irc::message&); + void dispatch_endofwhois(const irc::message&); + void dispatch_invite(const irc::message&); + void dispatch_isupport(const irc::message&); + void dispatch_join(const irc::message&); + void dispatch_kick(const irc::message&); + void dispatch_mode(const irc::message&); + void dispatch_namreply(const irc::message&); + void dispatch_nick(const irc::message&); + void dispatch_notice(const irc::message&); + void dispatch_part(const irc::message&); + void dispatch_ping(const irc::message&); + void dispatch_privmsg(const irc::message&); + void dispatch_topic(const irc::message&); + void dispatch_whoischannels(const irc::message&); + void dispatch_whoisuser(const irc::message&); + void dispatch(const irc::message&); + + void handle_recv(boost::system::error_code code, irc::message message); + void handle_connect(boost::system::error_code); + void recv(); + void identify(); + void wait(); + +public: + /** + * Split a channel from the form channel:password into a server_channel + * object. + * + * \param value the value + * \return a channel + */ + static channel split_channel(const std::string& value); + + /** + * Construct a server. + * + * \param service the service + * \param name the identifier + */ + server(boost::asio::io_service& service, std::string name); + + /** + * Destructor. Close the connection if needed. + */ + virtual ~server(); + + /** + * Get the current server state. + * + * \return the state + */ + inline state_t state() const noexcept + { + return state_; + } + + /** + * Get the server identifier. + * + * \return the id + */ + inline const std::string& name() const noexcept + { + return name_; + } + + /** + * Get the hostname. + * + * \return the hostname + */ + inline const std::string& host() const noexcept + { + return host_; + } + + /** + * Set the hostname. + * + * \param host the hostname + */ + inline void set_host(std::string host) noexcept + { + host_ = std::move(host); + } + + /** + * Get the password. + * + * \return the password + */ + inline const std::string& password() const noexcept + { + return password_; + } + + /** + * Set the password. + * + * An empty password means no password. + * + * \param password the password + */ + inline void set_password(std::string password) noexcept + { + password_ = std::move(password); + } + + /** + * Get the port. + * + * \return the port + */ + inline std::uint16_t port() const noexcept + { + return port_; + } + + /** + * Set the port. + * + * \param port the port + */ + inline void set_port(std::uint16_t port) noexcept + { + port_ = port; + } + + /** + * Get the flags. + * + * \return the flags + */ + inline std::uint8_t flags() const noexcept + { + return flags_; + } + + /** + * Set the flags. + * + * \param flags the flags + */ + inline void set_flags(std::uint8_t flags) noexcept + { +#if !defined(HAVE_SSL) + assert(!(flags & ssl)); +#endif + + flags_ = flags; + } + + /** + * Get the nickname. + * + * \return the nickname + */ + inline const std::string& nickname() const noexcept + { + return nickname_; + } + + /** + * Set the nickname. + * + * If the server is connected, send a nickname command to the IRC server, + * otherwise change it locally. + * + * \param nickname the nickname + */ + virtual void set_nickname(std::string nickname); + + /** + * Get the username. + * + * \return the username + */ + inline const std::string& username() const noexcept + { + return username_; + } + + /** + * Set the username. + * + * \param name the username + * \note the username will be changed on the next connection + */ + inline void set_username(std::string name) noexcept + { + username_ = std::move(name); + } + + /** + * Get the realname. + * + * \return the realname + */ + inline const std::string& realname() const noexcept + { + return realname_; + } + + /** + * Set the realname. + * + * \param realname the username + * \note the username will be changed on the next connection + */ + inline void set_realname(std::string realname) noexcept + { + realname_ = std::move(realname); + } + + /** + * Get the CTCP version. + * + * \return the CTCP version + */ + inline const std::string& ctcp_version() const noexcept + { + return ctcpversion_; + } + + /** + * Set the CTCP version. + * + * \param ctcpversion the version + */ + void set_ctcp_version(std::string ctcpversion); + + /** + * Get the command character. + * + * \return the character + */ + inline const std::string& command_char() const noexcept + { + return command_char_; + } + + /** + * Set the command character. + * + * \pre !command_char_.empty() + * \param command_char the command character + */ + inline void set_command_char(std::string command_char) noexcept + { + assert(!command_char.empty()); + + command_char_ = std::move(command_char); + } + + /** + * Get the number of reconnections before giving up. + * + * \return the number of reconnections + */ + inline std::int8_t reconnect_tries() const noexcept + { + return recotries_; + } + + /** + * Set the number of reconnections to test before giving up. + * + * A value less than 0 means infinite. + * + * \param reconnect_tries the number of reconnections + */ + inline void set_reconnect_tries(std::int8_t reconnect_tries) noexcept + { + recotries_ = reconnect_tries; + } + + /** + * Get the reconnection delay before retrying. + * + * \return the number of seconds + */ + inline std::uint16_t reconnect_delay() const noexcept + { + return recodelay_; + } + + /** + * Set the number of seconds before retrying. + * + * \param reconnect_delay the number of seconds + */ + inline void set_reconnect_delay(std::uint16_t reconnect_delay) noexcept + { + recodelay_ = reconnect_delay; + } + + /** + * Get the ping timeout. + * + * \return the ping timeout + */ + inline std::uint16_t ping_timeout() const noexcept + { + return timeout_; + } + + /** + * Set the ping timeout before considering a server as dead. + * + * \param ping_timeout the delay in seconds + */ + inline void set_ping_timeout(std::uint16_t ping_timeout) noexcept + { + timeout_ = ping_timeout; + } + + /** + * Get the list of channels joined. + * + * \return the channels + */ + inline const std::vector& channels() const noexcept + { + return jchannels_; + } + + /** + * Determine if the nickname is the bot itself. + * + * \param nick the nickname to check + * \return true if it is the bot + */ + bool is_self(const std::string& nick) const noexcept; + + /** + * Start connecting. + */ + virtual void connect() noexcept; + + /** + * Force disconnection. + */ + virtual void disconnect() noexcept; + + /** + * Asks for a reconnection. + */ + virtual void reconnect() noexcept; + + /** + * Invite a user to a channel. + * + * \param target the target nickname + * \param channel the channel + */ + virtual void invite(std::string target, std::string channel); + + /** + * Join a channel, the password is optional and can be kept empty. + * + * \param channel the channel to join + * \param password the optional password + */ + virtual void join(std::string channel, std::string password = ""); + + /** + * Kick someone from the channel. Please be sure to have the rights + * on that channel because errors won't be reported. + * + * \param target the target to kick + * \param channel from which channel + * \param reason the optional reason + */ + virtual void kick(std::string target, std::string channel, std::string reason = ""); + + /** + * Send a CTCP Action as known as /me. The target may be either a + * channel or a nickname. + * + * \param target the nickname or the channel + * \param message the message + */ + virtual void me(std::string target, std::string message); + + /** + * Send a message to the specified target or channel. + * + * \param target the target + * \param message the message + */ + virtual void message(std::string target, std::string message); + + /** + * Change channel/user mode. + * + * \param channel the channel or nickname + * \param mode the mode + * \param limit the optional limit + * \param user the optional user + * \param mask the optional ban mask + */ + virtual void mode(std::string channel, + std::string mode, + std::string limit = "", + std::string user = "", + std::string mask = ""); + + /** + * Request the list of names. + * + * \param channel the channel + */ + virtual void names(std::string channel); + + /** + * Send a private notice. + * + * \param target the target + * \param message the notice message + */ + virtual void notice(std::string target, std::string message); + + /** + * Part from a channel. + * + * Please note that the reason is not supported on all servers so if you + * want portability, don't provide it. + * + * \param channel the channel to leave + * \param reason the optional reason + */ + virtual void part(std::string channel, std::string reason = ""); + + /** + * Send a raw message to the IRC server. You don't need to add + * message terminators. + * + * \pre state() == state_t::connected + * \param raw the raw message (without `\r\n\r\n`) + */ + virtual void send(std::string raw); + + /** + * Change the channel topic. + * + * \param channel the channel + * \param topic the desired topic + */ + virtual void topic(std::string channel, std::string topic); + + /** + * Request for whois information. + * + * \param target the target nickname + */ + virtual void whois(std::string target); +}; + +/** + * \brief Server error. + */ +class server_error : public boost::system::system_error { +public: + /** + * \brief Server related errors (1000..1999) + */ + enum error { + //!< No error. + no_error = 0, + + //!< The specified server was not found. + not_found = 1000, + + //!< The specified identifier is invalid. + invalid_identifier, + + //!< The server is not connected. + not_connected, + + //!< The server is already connected. + already_connected, + + //!< Server with same name already exists. + already_exists, + + //!< The specified port number is invalid. + invalid_port, + + //!< The specified reconnect tries number is invalid. + invalid_reconnect_tries, + + //!< The specified reconnect reconnect number is invalid. + invalid_reconnect_timeout, + + //!< The specified host was invalid. + invalid_hostname, + + //!< The channel was empty or invalid. + invalid_channel, + + //!< The mode given was empty. + invalid_mode, + + //!< The nickname was empty or invalid. + invalid_nickname, + + //!< Invalid ping timeout. + invalid_ping_timeout, + + //!< SSL was requested but is disabled. + ssl_disabled, + }; + +private: + std::string name_; + +public: + /** + * Constructor. + * + * \param code the error code + * \param name the server name + */ + server_error(error code, std::string name) noexcept; + + /** + * Get the server that triggered the error. + * + * \return the name + */ + inline const std::string& name() const noexcept + { + return name_; + } +}; + +/** + * Get the server error category singleton. + * + * \return the singleton + */ +const boost::system::error_category& server_category(); + +/** + * Create a boost::system::error_code from server_error::error enum. + * + * \param e the error code + */ +boost::system::error_code make_error_code(server_error::error e); + +} // !irccd + +namespace boost { + +namespace system { + +template <> +struct is_error_code_enum : public std::true_type { +}; + +} // !system + +} // !boost + +#endif // !IRCCD_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/server_service.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/server_service.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,721 @@ +/* + * server_service.hpp -- server service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 + +#include "irccd.hpp" +#include "logger.hpp" +#include "plugin_service.hpp" +#include "rule_service.hpp" +#include "server_service.hpp" +#include "string_util.hpp" +#include "transport_service.hpp" + +namespace irccd { + +namespace { + +template +void dispatch(irccd& daemon, + const std::string& server, + const std::string& origin, + const std::string& target, + EventNameFunc&& name_func, + ExecFunc exec_func) +{ + for (auto& plugin : daemon.plugins().list()) { + auto eventname = name_func(*plugin); + auto allowed = daemon.rules().solve(server, target, origin, plugin->name(), eventname); + + if (!allowed) { + log::debug() << "rule: event skipped on match" << std::endl; + continue; + } + + log::debug() << "rule: event allowed" << std::endl; + + try { + exec_func(*plugin); + } catch (const std::exception& ex) { + log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl; + } + } +} + +template +T to_int(const std::string& value, const std::string& name, server_error::error errc) +{ + try { + return string_util::to_int(value); + } catch (...) { + throw server_error(errc, name); + } +} + +template +T to_uint(const std::string& value, const std::string& name, server_error::error errc) +{ + try { + return string_util::to_uint(value); + } catch (...) { + throw server_error(errc, name); + } +} + +template +T to_uint(const nlohmann::json& value, const std::string& name, server_error::error errc) +{ + if (!value.is_number()) + throw server_error(errc, name); + + auto n = value.get(); + + if (n > std::numeric_limits::max()) + throw server_error(errc, name); + + return static_cast(n); +} + +std::string to_id(const ini::section& sc) +{ + auto id = sc.get("name"); + + if (!string_util::is_identifier(id.value())) + throw server_error(server_error::invalid_identifier, ""); + + return id.value(); +} + +std::string to_id(const nlohmann::json& object) +{ + auto id = json_util::get_string(object, "name"); + + if (!string_util::is_identifier(id)) + throw server_error(server_error::invalid_identifier, ""); + + return id; +} + +std::string to_host(const ini::section& sc, const std::string& name) +{ + auto value = sc.get("host"); + + if (value.empty()) + throw server_error(server_error::invalid_hostname, name); + + return name; +} + +std::string to_host(const nlohmann::json& object, const std::string& name) +{ + auto value = json_util::get_string(object, "host"); + + if (value.empty()) + throw server_error(server_error::invalid_hostname, name); + + return value; +} + +void load_server_identity(std::shared_ptr& server, + const config& cfg, + const std::string& identity) +{ + auto sc = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& sc) { + if (sc.key() != "identity") + return false; + + auto name = sc.find("name"); + + return name != sc.end() && name->value() == identity; + }); + + if (sc == cfg.doc().end()) + return; + + ini::section::const_iterator it; + + if ((it = sc->find("username")) != sc->end()) + server->set_username(it->value()); + if ((it = sc->find("realname")) != sc->end()) + server->set_realname(it->value()); + if ((it = sc->find("nickname")) != sc->end()) + server->set_nickname(it->value()); + if ((it = sc->find("ctcp-version")) != sc->end()) + server->set_ctcp_version(it->value()); +} + +std::shared_ptr load_server(boost::asio::io_service& service, + const config& cfg, + const ini::section& sc) +{ + assert(sc.key() == "server"); + + auto sv = std::make_shared(service, to_id(sc)); + + // Mandatory fields. + sv->set_host(to_host(sc, sv->name())); + + // Optional fields. + ini::section::const_iterator it; + + if ((it = sc.find("password")) != sc.end()) + sv->set_password(it->value()); + + // Optional flags + if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value())) + sv->set_flags(sv->flags() | server::ipv6); + + if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) { +#if defined(HAVE_SSL) + sv->set_flags(sv->flags() | server::ssl); +#else + throw server_error(server_error::ssl_disabled, sv->name()); +#endif + } + + if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value())) + sv->set_flags(sv->flags() | server::ssl_verify); + + // Optional identity + if ((it = sc.find("identity")) != sc.end()) + load_server_identity(sv, cfg, it->value()); + + // Options + if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value())) + sv->set_flags(sv->flags() | server::auto_rejoin); + if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value())) + sv->set_flags(sv->flags() | server::join_invite); + + // Channels + if ((it = sc.find("channels")) != sc.end()) { + for (const auto& s : *it) { + channel channel; + + if (auto pos = s.find(":") != std::string::npos) { + channel.name = s.substr(0, pos); + channel.password = s.substr(pos + 1); + } else + channel.name = s; + + sv->join(channel.name, channel.password); + } + } + if ((it = sc.find("command-char")) != sc.end()) + sv->set_command_char(it->value()); + + // Reconnect and ping timeout + if ((it = sc.find("port")) != sc.end()) + sv->set_port(to_uint(it->value(), + sv->name(), server_error::invalid_port)); + + if ((it = sc.find("reconnect-tries")) != sc.end()) + sv->set_reconnect_tries(to_int(it->value(), + sv->name(), server_error::invalid_reconnect_tries)); + + if ((it = sc.find("reconnect-timeout")) != sc.end()) + sv->set_reconnect_delay(to_uint(it->value(), + sv->name(), server_error::invalid_reconnect_timeout)); + + if ((it = sc.find("ping-timeout")) != sc.end()) + sv->set_ping_timeout(to_uint(it->value(), + sv->name(), server_error::invalid_ping_timeout)); + + return sv; +} + +} // !namespace + +void server_service::handle_connect(const connect_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onConnect" }, + { "server", ev.server->name() } + })); + + dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "", + [=] (plugin&) -> std::string { + return "onConnect"; + }, + [=] (plugin& plugin) { + plugin.on_connect(irccd_, ev); + } + ); +} + +void server_service::handle_invite(const invite_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onInvite:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " target: " << ev.nickname << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onInvite" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onInvite"; + }, + [=] (plugin& plugin) { + plugin.on_invite(irccd_, ev); + } + ); +} + +void server_service::handle_join(const join_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onJoin:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onJoin" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onJoin"; + }, + [=] (plugin& plugin) { + plugin.on_join(irccd_, ev); + } + ); +} + +void server_service::handle_kick(const kick_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onKick:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " target: " << ev.target << "\n"; + log::debug() << " reason: " << ev.reason << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onKick" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "target", ev.target }, + { "reason", ev.reason } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onKick"; + }, + [=] (plugin& plugin) { + plugin.on_kick(irccd_, ev); + } + ); +} + +void server_service::handle_message(const message_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onMessage:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onMessage" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "message", ev.message } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin& plugin) -> std::string { + return string_util::parse_message( + ev.message, + ev.server->command_char(), + plugin.name() + ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage"; + }, + [=] (plugin& plugin) mutable { + auto copy = ev; + auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name()); + + copy.message = pack.message; + + if (pack.type == string_util::message_pack::type::command) + plugin.on_command(irccd_, copy); + else + plugin.on_message(irccd_, copy); + } + ); +} + +void server_service::handle_me(const me_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onMe:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " target: " << ev.channel << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onMe" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "target", ev.channel }, + { "message", ev.message } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onMe"; + }, + [=] (plugin& plugin) { + plugin.on_me(irccd_, ev); + } + ); +} + +void server_service::handle_mode(const mode_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onMode\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " mode: " << ev.mode << "\n"; + log::debug() << " limit: " << ev.limit << "\n"; + log::debug() << " user: " << ev.user << "\n"; + log::debug() << " mask: " << ev.mask << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onMode" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "mode", ev.mode }, + { "limit", ev.limit }, + { "user", ev.user }, + { "mask", ev.mask } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin &) -> std::string { + return "onMode"; + }, + [=] (plugin &plugin) { + plugin.on_mode(irccd_, ev); + } + ); +} + +void server_service::handle_names(const names_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onNames:\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl; + + auto names = nlohmann::json::array(); + + for (const auto& v : ev.names) + names.push_back(v); + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onNames" }, + { "server", ev.server->name() }, + { "channel", ev.channel }, + { "names", std::move(names) } + })); + + dispatch(irccd_, ev.server->name(), /* origin */ "", ev.channel, + [=] (plugin&) -> std::string { + return "onNames"; + }, + [=] (plugin& plugin) { + plugin.on_names(irccd_, ev); + } + ); +} + +void server_service::handle_nick(const nick_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onNick:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " nickname: " << ev.nickname << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onNick" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "nickname", ev.nickname } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin&) -> std::string { + return "onNick"; + }, + [=] (plugin& plugin) { + plugin.on_nick(irccd_, ev); + } + ); +} + +void server_service::handle_notice(const notice_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onNotice:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " message: " << ev.message << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onNotice" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "message", ev.message } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "", + [=] (plugin&) -> std::string { + return "onNotice"; + }, + [=] (plugin& plugin) { + plugin.on_notice(irccd_, ev); + } + ); +} + +void server_service::handle_part(const part_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onPart:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " reason: " << ev.reason << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onPart" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "reason", ev.reason } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onPart"; + }, + [=] (plugin& plugin) { + plugin.on_part(irccd_, ev); + } + ); +} + +void server_service::handle_topic(const topic_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onTopic:\n"; + log::debug() << " origin: " << ev.origin << "\n"; + log::debug() << " channel: " << ev.channel << "\n"; + log::debug() << " topic: " << ev.topic << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onTopic" }, + { "server", ev.server->name() }, + { "origin", ev.origin }, + { "channel", ev.channel }, + { "topic", ev.topic } + })); + + dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, + [=] (plugin&) -> std::string { + return "onTopic"; + }, + [=] (plugin& plugin) { + plugin.on_topic(irccd_, ev); + } + ); +} + +void server_service::handle_whois(const whois_event& ev) +{ + log::debug() << "server " << ev.server->name() << ": event onWhois\n"; + log::debug() << " nickname: " << ev.whois.nick << "\n"; + log::debug() << " username: " << ev.whois.user << "\n"; + log::debug() << " host: " << ev.whois.host << "\n"; + log::debug() << " realname: " << ev.whois.realname << "\n"; + log::debug() << " channels: " << string_util::join(ev.whois.channels, ", ") << std::endl; + + irccd_.transports().broadcast(nlohmann::json::object({ + { "event", "onWhois" }, + { "server", ev.server->name() }, + { "nickname", ev.whois.nick }, + { "username", ev.whois.user }, + { "host", ev.whois.host }, + { "realname", ev.whois.realname } + })); + + dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "", + [=] (plugin&) -> std::string { + return "onWhois"; + }, + [=] (plugin& plugin) { + plugin.on_whois(irccd_, ev); + } + ); +} + +std::shared_ptr server_service::from_json(boost::asio::io_service& service, const nlohmann::json& object) +{ + // TODO: move this function in server_service. + auto sv = std::make_shared(service, to_id(object)); + + // Mandatory fields. + sv->set_host(to_host(object, sv->name())); + + // Optional fields. + if (object.count("port")) + sv->set_port(to_uint(object["port"], sv->name(), server_error::invalid_port)); + sv->set_password(json_util::get_string(object, "password")); + sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname())); + sv->set_realname(json_util::get_string(object, "realname", sv->realname())); + sv->set_username(json_util::get_string(object, "username", sv->username())); + sv->set_ctcp_version(json_util::get_string(object, "ctcpVersion", sv->ctcp_version())); + sv->set_command_char(json_util::get_string(object, "commandChar", sv->command_char())); + + if (json_util::get_bool(object, "ipv6")) + sv->set_flags(sv->flags() | server::ipv6); + if (json_util::get_bool(object, "sslVerify")) + sv->set_flags(sv->flags() | server::ssl_verify); + if (json_util::get_bool(object, "autoRejoin")) + sv->set_flags(sv->flags() | server::auto_rejoin); + if (json_util::get_bool(object, "joinInvite")) + sv->set_flags(sv->flags() | server::join_invite); + + if (json_util::get_bool(object, "ssl")) +#if defined(HAVE_SSL) + sv->set_flags(sv->flags() | server::ssl); +#else + throw server_error(server_error::ssl_disabled, sv->name()); +#endif + + return sv; +} + +server_service::server_service(irccd &irccd) + : irccd_(irccd) +{ +} + +bool server_service::has(const std::string& name) const noexcept +{ + return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) { + return server->name() == name; + }) > 0; +} + +void server_service::add(std::shared_ptr server) +{ + assert(!has(server->name())); + + std::weak_ptr ptr(server); + + server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1)); + server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1)); + server->on_join.connect(boost::bind(&server_service::handle_join, this, _1)); + server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1)); + server->on_message.connect(boost::bind(&server_service::handle_message, this, _1)); + server->on_me.connect(boost::bind(&server_service::handle_me, this, _1)); + server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1)); + server->on_names.connect(boost::bind(&server_service::handle_names, this, _1)); + server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1)); + server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1)); + server->on_part.connect(boost::bind(&server_service::handle_part, this, _1)); + server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1)); + server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1)); + server->on_die.connect([this, ptr] () { + auto server = ptr.lock(); + + if (server) { + log::info(string_util::sprintf("server %s: removed", server->name())); + servers_.erase(std::find(servers_.begin(), servers_.end(), server)); + } + }); + + server->connect(); + servers_.push_back(std::move(server)); +} + +std::shared_ptr server_service::get(const std::string& name) const noexcept +{ + auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { + return server->name() == name; + }); + + if (it == servers_.end()) + return nullptr; + + return *it; +} + +std::shared_ptr server_service::require(const std::string& name) const +{ + auto server = get(name); + + if (!server) + throw std::invalid_argument(string_util::sprintf("server %s not found", name)); + + return server; +} + +void server_service::remove(const std::string& name) +{ + auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { + return server->name() == name; + }); + + if (it != servers_.end()) { + (*it)->disconnect(); + servers_.erase(it); + } +} + +void server_service::clear() noexcept +{ + for (auto &server : servers_) + server->disconnect(); + + servers_.clear(); +} + +void server_service::load(const config& cfg) noexcept +{ + for (const auto& section : cfg.doc()) { + if (section.key() != "server") + continue; + + try { + add(load_server(irccd_.service(), cfg, section)); + } catch (const std::exception& ex) { + log::warning() << "server " << section.get("name").value() << ": " + << ex.what() << std::endl; + } + } +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/server_service.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/server_service.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,148 @@ +/* + * server_service.hpp -- server service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_SERVER_SERVICE_HPP +#define IRCCD_SERVER_SERVICE_HPP + +/** + * \file server_service.hpp + * \brief Server service. + */ + +#include +#include + +#include "server.hpp" + +namespace irccd { + +class config; +class irccd; + +/** + * \brief Manage IRC servers. + * \ingroup services + */ +class server_service { +private: + irccd& irccd_; + std::vector> servers_; + + void handle_connect(const connect_event&); + void handle_invite(const invite_event&); + void handle_join(const join_event&); + void handle_kick(const kick_event&); + void handle_message(const message_event&); + void handle_me(const me_event&); + void handle_mode(const mode_event&); + void handle_names(const names_event&); + void handle_nick(const nick_event&); + void handle_notice(const notice_event&); + void handle_part(const part_event&); + void handle_query(const query_event&); + void handle_topic(const topic_event&); + void handle_whois(const whois_event&); + +public: + /** + * Convert a JSON object as a server. + * + * Used in JavaScript API and transport commands. + * + * \param service the io service + * \param object the object + * \return the server + * \throw std::exception on failures + */ + static std::shared_ptr from_json(boost::asio::io_service& service, const nlohmann::json& object); + + /** + * Create the server service. + */ + server_service(irccd& instance); + + /** + * Get the list of servers + * + * \return the servers + */ + inline const std::vector>& servers() const noexcept + { + return servers_; + } + + /** + * Check if a server exists. + * + * \param name the name + * \return true if exists + */ + bool has(const std::string& name) const noexcept; + + /** + * Add a new server to the application. + * + * \pre hasServer must return false + * \param sv the server + */ + void add(std::shared_ptr sv); + + /** + * Get a server or empty one if not found + * + * \param name the server name + * \return the server or empty one if not found + */ + std::shared_ptr get(const std::string& name) const noexcept; + + /** + * Find a server by name. + * + * \param name the server name + * \return the server + * \throw std::out_of_range if the server does not exist + */ + std::shared_ptr require(const std::string& name) const; + + /** + * Remove a server from the irccd instance. + * + * The server if any, will be disconnected. + * + * \param name the server name + */ + void remove(const std::string& name); + + /** + * Remove all servers. + * + * All servers will be disconnected. + */ + void clear() noexcept; + + /** + * Load servers from the configuration. + * + * \param cfg the config + */ + void load(const config& cfg) noexcept; +}; + +} // !irccd + +#endif // !IRCCD_SERVER_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/tls_transport_server.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/tls_transport_server.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,52 @@ +/* + * tls_transport_server.cpp -- server side transports (SSL support) + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "tls_transport_server.hpp" + +#if defined(HAVE_SSL) + +namespace irccd { + +void tls_transport_server::do_handshake(std::shared_ptr client, accept_t handler) +{ + client->stream().socket().async_handshake(boost::asio::ssl::stream_base::server, [client, handler] (auto code) { + handler(std::move(code), std::move(client)); + }); +} + +tls_transport_server::tls_transport_server(acceptor_t acceptor, context_t context) + : ip_transport_server(std::move(acceptor)) + , context_(std::move(context)) +{ +} + +void tls_transport_server::do_accept(accept_t handler) +{ + auto client = std::make_shared(*this, acceptor_.get_io_service(), context_); + + acceptor_.async_accept(client->stream().socket().lowest_layer(), [this, client, handler] (auto code) { + if (code) + handler(std::move(code), nullptr); + else + do_handshake(std::move(client), std::move(handler)); + }); +} + +} // !irccd + +#endif // !HAVE_SSL diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/tls_transport_server.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/tls_transport_server.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,72 @@ +/* + * tls_transport_server.hpp -- server side transports (SSL support) + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_TLS_TRANSPORT_SERVER_HPP +#define IRCCD_TLS_TRANSPORT_SERVER_HPP + +/** + * \file tls_transport_server.hpp + * \brief Server side transports (SSL support). + */ + +#include + +#if defined(HAVE_SSL) + +#include + +#include "ip_transport_server.hpp" + +namespace irccd { + +/** + * \brief Secure layer implementation. + */ +class tls_transport_server : public ip_transport_server { +private: + using context_t = boost::asio::ssl::context; + using client_t = basic_transport_client>; + + context_t context_; + + void do_handshake(std::shared_ptr, 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 // !HAVE_SSL + +#endif // !IRCCD_TLS_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/transport_client.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/transport_client.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,73 @@ +/* + * transport_client.cpp -- server side transport clients + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 + +#include "transport_client.hpp" +#include "transport_server.hpp" + +namespace irccd { + +void transport_client::recv(network_recv_handler handler) +{ + if (state_ != state_t::closing) + do_recv(std::move(handler)); +} + +void transport_client::send(nlohmann::json json, network_send_handler handler) +{ + if (state_ != state_t::closing) + do_send(std::move(json), std::move(handler)); +} + +void transport_client::success(const std::string& cname, network_send_handler handler) +{ + assert(!cname.empty()); + + send({{ "command", cname }}, std::move(handler)); +} + +void transport_client::error(boost::system::error_code code, network_send_handler handler) +{ + error(std::move(code), "", std::move(handler)); +} + +void transport_client::error(boost::system::error_code code, + std::string cname, + network_send_handler handler) +{ + assert(code); + + auto json = nlohmann::json::object({ + { "error", code.value() } + }); + + if (!cname.empty()) + json["command"] = std::move(cname); + + send(std::move(json), [this, handler] (auto code) { + if (handler) + handler(code); + + parent_.clients().erase(shared_from_this()); + }); + + state_ = state_t::closing; +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/transport_client.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/transport_client.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,182 @@ +/* + * transport_client.hpp -- server side transport clients + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "network_stream.hpp" + +namespace irccd { + +class transport_server; + +/** + * \brief Abstract transport client class. + * + * This class is responsible of receiving/sending data. + */ +class transport_client : public std::enable_shared_from_this { +public: + /** + * Client state. + */ + enum class state_t { + authenticating, //!< client is authenticating + ready, //!< client is ready + closing //!< client is closing + }; + +private: + state_t state_{state_t::authenticating}; + transport_server& parent_; + +protected: + /** + * Request a receive operation. + * + * The implementation must call the handler once the operation has finished + * even in case of errors. + * + * \param handler the non-null handler + */ + virtual void do_recv(network_recv_handler handler) = 0; + + /** + * Request a send operation. + * + * The implementation must call the handler once the operation has finished + * even in case of errors. + * + * \param json the json message to send + * \param handler the non-null handler + */ + virtual void do_send(nlohmann::json json, network_send_handler handler) = 0; + +public: + /** + * Constructor. + * + * \param server the parent + */ + inline transport_client(transport_server& server) noexcept + : parent_(server) + { + } + + /** + * Virtual destructor defaulted. + */ + virtual ~transport_client() = 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 receiving if not closed. + * + * Possible error codes: + * + * - boost::system::errc::network_down in case of errors, + * - boost::system::errc::invalid_argument if the JSON message is invalid. + * + * \pre handler != nullptr + * \param handler the handler + */ + void recv(network_recv_handler handler); + + /** + * Start sending if not closed. + * + * Possible error codes: + * + * - boost::system::errc::network_down in case of errors, + * + * \param json the json message + * \param handler the optional handler + */ + void send(nlohmann::json json, network_send_handler handler = nullptr); + + /** + * Convenient success message. + * + * \param cname the command name + * \param handler the optional handler + */ + void success(const std::string& cname, network_send_handler handler = nullptr); + + /** + * Send an error code to the client. + * + * \pre code is not 0 + * \param code the error code + * \param handler the optional handler + */ + void error(boost::system::error_code code, network_send_handler handler = nullptr); + + /** + * Send an error code to the client. + * + * \pre code is not 0 + * \param code the error code + * \param handler the optional handler + */ + void error(boost::system::error_code code, + std::string cname, + network_send_handler handler = nullptr); +}; + +} // !irccd + +#endif // !IRCCD_TRANSPORT_CLIENT_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/transport_server.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/transport_server.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,103 @@ +/* + * transport_server.cpp -- server side transports + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "sysconfig.hpp" + +#include + +#include "irccd.hpp" +#include "json_util.hpp" +#include "transport_server.hpp" + +namespace irccd { + +void transport_server::do_auth(std::shared_ptr client, accept_t handler) +{ + assert(client); + assert(handler); + + client->recv([this, client, handler] (auto code, auto message) { + if (code) { + handler(std::move(code), std::move(client)); + return; + } + + auto command = json_util::to_string(message["command"]); + auto password = json_util::to_string(message["password"]); + + if (command != "auth") { + client->error(irccd_error::auth_required); + code = irccd_error::auth_required; + } else if (password != password_) { + client->error(irccd_error::invalid_auth); + code = irccd_error::invalid_auth; + } else { + client->set_state(transport_client::state_t::ready); + client->success("auth"); + code = irccd_error::no_error; + } + + handler(std::move(code), std::move(client)); + }); +} + +void transport_server::do_greetings(std::shared_ptr client, accept_t handler) +{ + assert(client); + assert(handler); + + auto greetings = nlohmann::json({ + { "program", "irccd" }, + { "major", IRCCD_VERSION_MAJOR }, + { "minor", IRCCD_VERSION_MINOR }, + { "patch", IRCCD_VERSION_PATCH }, +#if defined(HAVE_JS) + { "javascript", true }, +#endif +#if defined(HAVE_SSL) + { "ssl", true }, +#endif + }); + + client->send(greetings, [this, client, handler] (auto code) { + if (code) + handler(std::move(code), std::move(client)); + else if (!password_.empty()) + do_auth(std::move(client), std::move(handler)); + else { + client->set_state(transport_client::state_t::ready); + handler(std::move(code), std::move(client)); + } + }); +} + +void transport_server::accept(accept_t handler) +{ + assert(handler); + + do_accept([this, handler] (auto code, auto client) { + if (code) + handler(std::move(code), nullptr); + else { + clients_.insert(client); + do_greetings(std::move(client), std::move(handler)); + } + }); +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/transport_server.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/transport_server.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,134 @@ +/* + * transport_server.hpp -- server side transports + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 "sysconfig.hpp" + +#include +#include +#include +#include +#include + +#include + +#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>; + + /** + * Callback when a new client should be accepted. + */ + using accept_t = std::function)>; + +private: + client_set_t clients_; + std::string password_; + + void do_auth(std::shared_ptr, accept_t); + void do_greetings(std::shared_ptr, 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); + } +}; + +} // !irccd + +#endif // !IRCCD_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/transport_service.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/transport_service.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,287 @@ +/* + * transport_service.cpp -- transport service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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 + +#include + +#include + +#include "command_service.hpp" +#include "irccd.hpp" +#include "ip_transport_server.hpp" +#include "logger.hpp" +#include "transport_client.hpp" +#include "transport_service.hpp" + +#if !defined(IRCCD_SYSTEM_WINDOWS) +# include "local_transport_server.hpp" +#endif + +#if defined(HAVE_SSL) +# include "tls_transport_server.hpp" +#endif + +namespace irccd { + +namespace { + +std::unique_ptr load_transport_ip(boost::asio::io_service& service, const ini::section& sc) +{ + assert(sc.key() == "transport"); + + std::unique_ptr transport; + ini::section::const_iterator it; + + // Port. + if ((it = sc.find("port")) == sc.cend()) + throw std::invalid_argument("missing 'port' parameter"); + + auto port = string_util::to_uint(it->value()); + + // Address. + std::string address = "*"; + + if ((it = sc.find("address")) != sc.end()) + address = it->value(); + + // 0011 + // ^ define IPv4 + // ^ define IPv6 + auto mode = 1U; + + /* + * Documentation stated family but code checked for 'domain' option. + * + * As irccdctl uses domain, accept both and unify the option name to 'family'. + * + * See #637 + */ + if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) { + mode = 0U; + + for (const auto& v : *it) { + if (v == "ipv4") + mode |= (1U << 0); + if (v == "ipv6") + mode |= (1U << 1); + } + } + + if (mode == 0U) + throw std::invalid_argument("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; + + if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) { + if ((it = sc.find("certificate")) == sc.end()) + throw std::invalid_argument("missing 'certificate' parameter"); + + cert = it->value(); + + if ((it = sc.find("key")) == sc.end()) + throw std::invalid_argument("missing 'key' parameter"); + + pkey = it->value(); + } + + 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()) { + log::info() << "transport: listening on " << port << std::endl; + return std::make_unique(std::move(acceptor)); + } + +#if defined(HAVE_SSL) + 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); + + log::info() << "transport: listening on " << port << " using SSL" << std::endl; + + return std::make_unique(std::move(acceptor), std::move(ctx)); +#else + throw std::invalid_argument("SSL disabled"); +#endif +} + +std::unique_ptr 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) + ini::section::const_iterator it = sc.find("path"); + + if (it == sc.end()) + throw std::invalid_argument("missing 'path' parameter"); + + // Remove the file first. + std::remove(it->value().c_str()); + + stream_protocol::endpoint endpoint(it->value()); + stream_protocol::acceptor acceptor(service, std::move(endpoint)); + + log::info() << "transport: listening on " << it->value() << std::endl; + + return std::make_unique(std::move(acceptor)); +#else + (void)sc; + + throw std::invalid_argument("unix transports not supported on on this platform"); +#endif +} + +std::unique_ptr load_transport(boost::asio::io_service& service, const ini::section& sc) +{ + assert(sc.key() == "transport"); + + std::unique_ptr transport; + ini::section::const_iterator it = sc.find("type"); + + if (it == sc.end()) + throw std::invalid_argument("missing 'type' parameter"); + + if (it->value() == "ip") + transport = load_transport_ip(service, sc); + else if (it->value() == "unix") + transport = load_transport_unix(service, sc); + else + throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value())); + + if ((it = sc.find("password")) != sc.end()) + transport->set_password(it->value()); + + return transport; +} + +} // !namespace + +void transport_service::handle_command(std::shared_ptr tc, const nlohmann::json& object) +{ + assert(object.is_object()); + + auto name = object.find("command"); + + if (name == object.end() || !name->is_string()) { + tc->error(irccd_error::invalid_message); + return; + } + + auto cmd = irccd_.commands().find(*name); + + if (!cmd) + tc->error(irccd_error::invalid_command, name->get()); + else { + try { + cmd->exec(irccd_, *tc, object); + } catch (const boost::system::system_error& ex) { + tc->error(ex.code(), cmd->name()); + } catch (const std::exception& ex) { + log::warning() << "transport: unknown error not reported" << std::endl; + log::warning() << "transport: " << ex.what() << std::endl; + } + } +} + +void transport_service::do_recv(std::shared_ptr tc) +{ + tc->recv([this, tc] (auto code, auto json) { + switch (code.value()) { + case boost::system::errc::network_down: + log::warning("transport: client disconnected"); + break; + case boost::system::errc::invalid_argument: + tc->error(irccd_error::invalid_message); + break; + default: + handle_command(tc, json); + + if (tc->state() == transport_client::state_t::ready) + do_recv(std::move(tc)); + + break; + } + }); +} + +void transport_service::do_accept(transport_server& ts) +{ + ts.accept([this, &ts] (auto code, auto client) { + if (code) + log::warning() << "transport: new client error: " << code.message() << std::endl; + else { + do_accept(ts); + do_recv(std::move(client)); + + log::info() << "transport: new client connected" << std::endl; + } + }); +} + +transport_service::transport_service(irccd& irccd) noexcept + : irccd_(irccd) +{ +} + +transport_service::~transport_service() noexcept = default; + +void transport_service::add(std::unique_ptr ts) +{ + assert(ts); + + do_accept(*ts); + servers_.push_back(std::move(ts)); +} + +void transport_service::broadcast(const nlohmann::json& json) +{ + assert(json.is_object()); + + for (const auto& servers : servers_) + for (const auto& client : servers->clients()) + client->send(json); +} + +void transport_service::load(const config& cfg) noexcept +{ + for (const auto& section : cfg.doc()) { + if (section.key() != "transport") + continue; + + try { + add(load_transport(irccd_.service(), section)); + } catch (const std::exception& ex) { + log::warning() << "transport: " << ex.what() << std::endl; + } + } +} + +} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/daemon/transport_service.hpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libirccd/irccd/daemon/transport_service.hpp Wed Dec 06 11:42:44 2017 +0100 @@ -0,0 +1,88 @@ +/* + * transport_service.hpp -- transport service + * + * Copyright (c) 2013-2017 David Demelier + * + * 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_SERVICE_HPP +#define IRCCD_TRANSPORT_SERVICE_HPP + +#include +#include + +#include + +#include "transport_client.hpp" +#include "transport_server.hpp" + +namespace irccd { + +class config; + +/** + * \brief manage transport servers and clients. + * \ingroup services + */ +class transport_service { +public: + using servers_t = std::vector>; + +private: + irccd& irccd_; + servers_t servers_; + + void handle_command(std::shared_ptr, const nlohmann::json&); + void do_recv(std::shared_ptr); + void do_accept(transport_server&); + +public: + /** + * Create the transport service. + * + * \param irccd the irccd instance + */ + transport_service(irccd& irccd) noexcept; + + /** + * Default destructor. + */ + ~transport_service() noexcept; + + /** + * Add a transport server. + * + * \param ts the transport server + */ + void add(std::unique_ptr ts); + + /** + * Send data to all clients. + * + * \pre object.is_object() + * \param object the json object + */ + void broadcast(const nlohmann::json& object); + + /** + * Load transports from the configuration. + * + * \param cfg the config + */ + void load(const config& cfg) noexcept; +}; + +} // !irccd + +#endif // !IRCCD_TRANSPORT_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/dynlib_plugin.cpp --- a/libirccd/irccd/dynlib_plugin.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ -/* - * dynlib_plugin.cpp -- native plugin implementation - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 -#include - -#include - -#include "dynlib_plugin.hpp" -#include "string_util.hpp" - -#if defined(IRCCD_SYSTEM_WINDOWS) -# define DYNLIB_EXTENSION ".dll" -#elif defined(IRCCD_SYSTEM_MAC) -# define DYNLIB_EXTENSION ".dylib" -#else -# define DYNLIB_EXTENSION ".so" -#endif - -namespace irccd { - -dynlib_plugin::dynlib_plugin(std::string name, std::string path) - : plugin(name, path) - , dso_(path) -{ - using load_t = std::unique_ptr(std::string, std::string); - - /* - * Function name is determined from the plugin filename where all non - * alphabetic characters are removed. - * - * Example: foo_bar-baz___.so becomes irccd_foobarbaz_load. - */ - auto base = boost::filesystem::path(path).stem().string(); - auto need_remove = [] (auto c) { - return !std::isalnum(c); - }; - - base.erase(std::remove_if(base.begin(), base.end(), need_remove), base.end()); - - auto fname = string_util::sprintf("irccd_%s_load", base); - auto load = dso_.get(fname); - - if (!load) - throw std::runtime_error(string_util::sprintf("missing plugin entry function '%s'", fname)); - - plugin_ = load(name, path); - - if (!plugin_) - throw std::runtime_error("plugin returned null"); -} - -void dynlib_plugin::on_command(irccd& irccd, const message_event& ev) -{ - plugin_->on_command(irccd, ev); -} - -void dynlib_plugin::on_connect(irccd& irccd, const connect_event& ev) -{ - plugin_->on_connect(irccd, ev); -} - -void dynlib_plugin::on_invite(irccd& irccd, const invite_event& ev) -{ - plugin_->on_invite(irccd, ev); -} - -void dynlib_plugin::on_join(irccd& irccd, const join_event& ev) -{ - plugin_->on_join(irccd, ev); -} - -void dynlib_plugin::on_kick(irccd& irccd, const kick_event& ev) -{ - plugin_->on_kick(irccd, ev); -} - -void dynlib_plugin::on_load(irccd& irccd) -{ - plugin_->on_load(irccd); -} - -void dynlib_plugin::on_message(irccd& irccd, const message_event& ev) -{ - plugin_->on_message(irccd, ev); -} - -void dynlib_plugin::on_me(irccd& irccd, const me_event& ev) -{ - plugin_->on_me(irccd, ev); -} - -void dynlib_plugin::on_mode(irccd& irccd, const mode_event& ev) -{ - plugin_->on_mode(irccd, ev); -} - -void dynlib_plugin::on_names(irccd& irccd, const names_event& ev) -{ - plugin_->on_names(irccd, ev); -} - -void dynlib_plugin::on_nick(irccd& irccd, const nick_event& ev) -{ - plugin_->on_nick(irccd, ev); -} - -void dynlib_plugin::on_notice(irccd& irccd, const notice_event& ev) -{ - plugin_->on_notice(irccd, ev); -} - -void dynlib_plugin::on_part(irccd& irccd, const part_event& ev) -{ - plugin_->on_part(irccd, ev); -} - -void dynlib_plugin::on_reload(irccd& irccd) -{ - plugin_->on_reload(irccd); -} - -void dynlib_plugin::on_topic(irccd& irccd, const topic_event& ev) -{ - plugin_->on_topic(irccd, ev); -} - -void dynlib_plugin::on_unload(irccd& irccd) -{ - plugin_->on_unload(irccd); -} - -void dynlib_plugin::on_whois(irccd& irccd, const whois_event& ev) -{ - plugin_->on_whois(irccd, ev); -} - -dynlib_plugin_loader::dynlib_plugin_loader(std::vector directories) noexcept - : plugin_loader(std::move(directories), { DYNLIB_EXTENSION }) -{ -} - -std::shared_ptr dynlib_plugin_loader::open(const std::string& id, - const std::string& path) noexcept -{ - return std::make_unique(id, path); -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/dynlib_plugin.hpp --- a/libirccd/irccd/dynlib_plugin.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -/* - * dynlib_plugin.hpp -- native plugin implementation - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_PLUGIN_DYNLIB_HPP -#define IRCCD_PLUGIN_DYNLIB_HPP - -/** - * \file plugin-dynlib.hpp - * \brief Native plugin implementation. - */ - -#include - -#include "plugin.hpp" - -namespace irccd { - -/** - * \brief Dynlib based plugin. - * \ingroup plugins - */ -class dynlib_plugin : public plugin { -private: - boost::dll::shared_library dso_; - std::unique_ptr plugin_; - -public: - /** - * Construct the plugin. - * - * \param name the name - * \param path the fully resolved path (must be absolute) - * \throw std::exception on failures - */ - dynlib_plugin(std::string name, std::string path); - - /** - * \copydoc plugin::on_command - */ - void on_command(irccd& irccd, const message_event& event) override; - - /** - * \copydoc plugin::on_connect - */ - void on_connect(irccd& irccd, const connect_event& event) override; - - /** - * \copydoc plugin::on_invite - */ - void on_invite(irccd& irccd, const invite_event& event) override; - - /** - * \copydoc plugin::on_join - */ - void on_join(irccd& irccd, const join_event& event) override; - - /** - * \copydoc plugin::on_kick - */ - void on_kick(irccd& irccd, const kick_event& event) override; - - /** - * \copydoc plugin::on_load - */ - void on_load(irccd& irccd) override; - - /** - * \copydoc plugin::on_message - */ - void on_message(irccd& irccd, const message_event& event) override; - - /** - * \copydoc plugin::on_me - */ - void on_me(irccd& irccd, const me_event& event) override; - - /** - * \copydoc plugin::on_mode - */ - void on_mode(irccd& irccd, const mode_event& event) override; - - /** - * \copydoc plugin::on_names - */ - void on_names(irccd& irccd, const names_event& event) override; - - /** - * \copydoc plugin::on_nick - */ - void on_nick(irccd& irccd, const nick_event& event) override; - - /** - * \copydoc plugin::on_notice - */ - void on_notice(irccd& irccd, const notice_event& event) override; - - /** - * \copydoc plugin::on_part - */ - void on_part(irccd& irccd, const part_event& event) override; - - /** - * \copydoc plugin::on_reload - */ - void on_reload(irccd& irccd) override; - - /** - * \copydoc plugin::on_topic - */ - void on_topic(irccd& irccd, const topic_event& event) override; - - /** - * \copydoc plugin::on_unload - */ - void on_unload(irccd& irccd) override; - - /** - * \copydoc plugin::on_whois - */ - void on_whois(irccd& irccd, const whois_event& event) override; -}; - -/** - * \brief Implementation for searching native plugins. - */ -class dynlib_plugin_loader : public plugin_loader { -public: - /** - * Constructor. - * - * \param directories optional directories to search, if empty use defaults. - */ - dynlib_plugin_loader(std::vector directories = {}) noexcept; - - /** - * \copydoc plugin_loader::find - */ - std::shared_ptr open(const std::string& id, - const std::string& path) noexcept override; -}; - -} // !irccd - -#endif // !IRCCD_PLUGIN_DYNLIB_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/ip_transport_server.hpp --- a/libirccd/irccd/ip_transport_server.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,38 +0,0 @@ -/* - * ip_transport_server.hpp -- server side transports (TCP/IP support) - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_IP_TRANSPORT_SERVER_HPP -#define IRCCD_IP_TRANSPORT_SERVER_HPP - -/** - * \file ip_transport_server.hpp - * \brief Server side transports (TCP/IP support). - */ - -#include "basic_transport_server.hpp" - -namespace irccd { - -/** - * Convenient type for IP/TCP - */ -using ip_transport_server = basic_transport_server; - -} // !irccd - -#endif // !IRCCD_IP_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/irc.cpp --- a/libirccd/irccd/irc.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,275 +0,0 @@ -/* - * irc.cpp -- low level IRC functions - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 -#include -#include - -#include "irc.hpp" - -namespace irccd { - -namespace irc { - -namespace { - -using boost::asio::ip::tcp; - -template -void wrap_connect(Socket& socket, tcp::resolver::iterator it, connection::connect_t handler) -{ - assert(handler); - - socket.close(); - socket.async_connect(*it, [&socket, it, handler] (auto code) mutable { - if (code && ++it != tcp::resolver::iterator()) - wrap_connect(socket, it, std::move(handler)); - else - handler(code); - }); -} - -template -void wrap_resolve(Socket& socket, - tcp::resolver& resolver, - const std::string& host, - const std::string& port, - connection::connect_t handler) -{ - assert(handler); - - tcp::resolver::query query(host, port); - - resolver.async_resolve(query, [&socket, handler] (auto code, auto it) { - if (code) - handler(code); - else - wrap_connect(socket, it, std::move(handler)); - }); -} - -template -void wrap_recv(Socket& socket, boost::asio::streambuf& buffer, connection::recv_t handler) -{ - assert(handler); - - boost::asio::async_read_until(socket, buffer, "\r\n", [&socket, &buffer, handler] (auto code, auto xfer) { - if (code || xfer == 0U) - handler(std::move(code), message()); - else { - std::string str( - boost::asio::buffers_begin(buffer.data()), - boost::asio::buffers_begin(buffer.data()) + xfer - 2 - ); - - buffer.consume(xfer); - handler(std::move(code), message::parse(str)); - } - }); -} - -template -void wrap_send(Socket& socket, const std::string& message, connection::send_t handler) -{ - assert(handler); - - boost::asio::async_write(socket, boost::asio::buffer(message), [handler, message] (auto code, auto) { - // TODO: xfer - handler(code); - }); -} - -} // !namespace - -void connection::rflush() -{ - if (input_.empty()) - return; - - do_recv(buffer_, [this] (auto code, auto message) { - input_.front()(code, std::move(message)); - input_.pop_front(); - - if (!code) - rflush(); - }); -} - -void connection::sflush() -{ - if (output_.empty()) - return; - - do_send(output_.front().first, [this] (auto code) { - if (output_.front().second) - output_.front().second(code); - - output_.pop_front(); - - if (!code) - sflush(); - }); -} - -void connection::connect(const std::string& host, const std::string& service, connect_t handler) -{ - assert(handler); - - do_connect(host, service, std::move(handler)); -} - -void connection::recv(recv_t handler) -{ - auto in_progress = !input_.empty(); - - input_.push_back(std::move(handler)); - - if (!in_progress) - rflush(); -} - -void connection::send(std::string message, send_t handler) -{ - auto in_progress = !output_.empty(); - - output_.emplace_back(std::move(message + "\r\n"), std::move(handler)); - - if (!in_progress) - sflush(); -} - -message message::parse(const std::string& line) -{ - std::istringstream iss(line); - std::string prefix; - - if (line.empty()) - return {}; - - // Prefix. - if (line[0] == ':') { - iss.ignore(1); - iss >> prefix; - iss.ignore(1); - } - - // Command. - std::string command; - iss >> command; - iss.ignore(1); - - // Arguments. - std::vector args; - std::istreambuf_iterator it(iss), end; - - while (it != end) { - std::string arg; - - if (*it == ':') - arg = std::string(++it, end); - else { - while (!isspace(*it) && it != end) - arg.push_back(*it++); - - // Skip space after param. - if (it != end) - ++it; - } - - args.push_back(std::move(arg)); - } - - return {std::move(prefix), std::move(command), std::move(args)}; -} - -bool message::is_ctcp(unsigned index) const noexcept -{ - auto a = arg(index); - - if (a.empty()) - return false; - - return a.front() == 0x01 && a.back() == 0x01; -} - -std::string message::ctcp(unsigned index) const -{ - assert(is_ctcp(index)); - - return args_[index].substr(1, args_[index].size() - 1); -} - -user user::parse(const std::string& line) -{ - if (line.empty()) - return {"", ""}; - - auto pos = line.find("!"); - - if (pos == std::string::npos) - return {line, ""}; - - return {line.substr(0, pos), line.substr(pos + 1)}; -} - -void ip_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept -{ - wrap_resolve(socket_, resolver_, host, service, std::move(handler)); -} - -void ip_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept -{ - wrap_recv(socket_, buffer, std::move(handler)); -} - -void ip_connection::do_send(const std::string& data, send_t handler) noexcept -{ - wrap_send(socket_, data, std::move(handler)); -} - -#if defined(HAVE_SSL) - -void tls_connection::do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept -{ - using boost::asio::ssl::stream_base; - - wrap_resolve(socket_.lowest_layer(), resolver_, host, service, [this, handler] (auto code) { - if (code) - handler(code); - else - socket_.async_handshake(stream_base::client, [this, handler] (auto code) { - handler(code); - }); - }); -} - -void tls_connection::do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept -{ - wrap_recv(socket_, buffer, std::move(handler)); -} - -void tls_connection::do_send(const std::string& data, send_t handler) noexcept -{ - wrap_send(socket_, data, std::move(handler)); -} - -#endif // !HAVE_SSL - -} // !irc - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/irc.hpp --- a/libirccd/irccd/irc.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,535 +0,0 @@ -/* - * irc.hpp -- low level IRC functions - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_IRC_HPP -#define IRCCD_IRC_HPP - -/** - * \file irc.hpp - * \brief Low level IRC functions. - */ - -#include "sysconfig.hpp" - -#include -#include -#include -#include -#include - -#include - -#if defined(HAVE_SSL) -# include -#endif - -namespace irccd { - -namespace irc { - -class message; - -/** - * \brief Describe errors. - */ -enum class err { - nosuchnick = 401, - nosuchserver = 402, - nosuchchannel = 403, - cannotsendtochan = 404, - toomanychannels = 405, - wasnosuchnick = 406, - toomanytargets = 407, - noorigin = 409, - norecipient = 411, - notexttosend = 412, - notoplevel = 413, - wildtoplevel = 414, - unknowncommand = 421, - nomotd = 422, - noadmininfo = 423, - fileerror = 424, - nonicknamegiven = 431, - erroneusnickname = 432, - nicknameinuse = 433, - nickcollision = 436, - usernotinchannel = 441, - notonchannel = 442, - useronchannel = 443, - nologin = 444, - summondisabled = 445, - usersdisabled = 446, - notregistered = 451, - needmoreparams = 461, - alreadyregistred = 462, - nopermforhost = 463, - passwdmismatch = 464, - yourebannedcreep = 465, - keyset = 467, - channelisfull = 471, - unknownmode = 472, - inviteonlychan = 473, - bannedfromchan = 474, - badchannelkey = 475, - noprivileges = 481, - chanoprivsneeded = 482, - cantkillserver = 483, - nooperhost = 491, - umodeunknownflag = 501, - usersdontmatch = 502 -}; - -/** - * \brief Describe numeric replies. - */ -enum class rpl { - none = 300, - userhost = 302, - ison = 303, - away = 301, - unaway = 305, - nowaway = 306, - whoisuser = 311, - whoisserver = 312, - whoisoperator = 313, - whoisidle = 317, - endofwhois = 318, - whoischannels = 319, - whowasuser = 314, - endofwhowas = 369, - liststart = 321, - list = 322, - listend = 323, - channelmodeis = 324, - notopic = 331, - topic = 332, - inviting = 341, - summoning = 342, - version = 351, - whoreply = 352, - endofwho = 315, - namreply = 353, - endofnames = 366, - links = 364, - endoflinks = 365, - banlist = 367, - endofbanlist = 368, - info = 371, - endofinfo = 374, - motdstart = 375, - motd = 372, - endofmotd = 376, - youreoper = 381, - rehashing = 382, - time = 391, - userstart = 392, - users = 393, - endofusers = 394, - nousers = 395, - tracelink = 200, - traceconnecting = 201, - tracehandshake = 202, - traceunknown = 203, - traceoperator = 204, - traceuser = 205, - traceserver = 206, - tracenewtype = 208, - tracelog = 261, - statslinkinfo = 211, - statscommands = 212, - statscline = 213, - statsnline = 214, - statsiline = 215, - statskline = 216, - statsyline = 218, - endofstats = 219, - statslline = 241, - statsuptime = 242, - statsoline = 243, - statshline = 244, - umodeis = 221, - luserclient = 251, - luserop = 252, - luserunknown = 253, - luserchannels = 254, - luserme = 255, - adminme = 256, - adminloc1 = 257, - adminloc2 = 258, - adminemail = 259 -}; - -/** - * \brief Describe a IRC message - */ -class message { -private: - std::string prefix_; //!< optional prefix - std::string command_; //!< command (maybe string or code) - std::vector args_; //!< parameters - -public: - /** - * Constructor. - * - * \param prefix the optional prefix - * \param command the command string or number - * \param args the arguments - */ - inline message(std::string prefix = "", std::string command = "", std::vector args = {}) noexcept - : prefix_(std::move(prefix)) - , command_(std::move(command)) - , args_(std::move(args)) - { - } - - /** - * Get the prefix. - * - * \return the prefix - */ - inline const std::string& prefix() const noexcept - { - return prefix_; - } - - /** - * Get the command. - * - * \return the command - */ - inline const std::string& command() const noexcept - { - return command_; - } - - /** - * Get the arguments. - * - * \return the arguments - */ - inline const std::vector& args() const noexcept - { - return args_; - } - - /** - * Check if the message is defined. - * - * \return true if not empty - */ - inline operator bool() const noexcept - { - return !command_.empty(); - } - - /** - * Check if the message is empty. - * - * \return true if empty - */ - inline bool operator!() const noexcept - { - return command_.empty(); - } - - /** - * Check if the command is of the given enum number. - * - * \return true if command is a number and equals to e - */ - template - inline bool is(Enum e) const noexcept - { - try { - return std::stoi(command_) == static_cast(e); - } catch (...) { - return false; - } - } - - /** - * Convenient function that returns an empty string if the nth argument is - * not defined. - * - * \param index the index - * \return a string or empty if out of bounds - */ - inline const std::string& arg(unsigned short index) const noexcept - { - static const std::string dummy; - - return (index >= args_.size()) ? dummy : args_[index]; - } - - /** - * Tells if the message is a CTCP. - * - * \param index the param index (maybe out of bounds) - * \return true if CTCP - */ - bool is_ctcp(unsigned index) const noexcept; - - /** - * Parse a CTCP message. - * - * \pre is_ctcp(index) - * \param index the param index - * \return the CTCP command - */ - std::string ctcp(unsigned index) const; - - /** - * Parse a IRC message. - * - * \param line the buffer content (without \r\n) - * \return the message (maybe empty if line is empty) - */ - static message parse(const std::string& line); -}; - -/** - * \brief Describe a user. - */ -class user { -private: - std::string nick_; - std::string host_; - -public: - /** - * Construct a user. - * - * \param the nickname - * \param the hostname - */ - inline user(std::string nick, std::string host) noexcept - : nick_(std::move(nick)) - , host_(std::move(host)) - { - } - - /** - * Get the nick part. - * - * \return the nickname - */ - inline const std::string& nick() const noexcept - { - return nick_; - } - - /** - * Get the host part. - * - * \return the host part - */ - inline const std::string& host() const noexcept - { - return host_; - } - - /** - * Parse a nick/host combination. - * - * \param line the line to parse - * \return a user - */ - static user parse(const std::string& line); -}; - -/** - * \brief Abstract connection to a server. - */ -class connection { -public: - /** - * Handler for connecting. - */ - using connect_t = std::function; - - /** - * Handler for receiving. - */ - using recv_t = std::function; - - /** - * Handler for sending. - */ - using send_t = std::function; - -private: - using buffer_t = boost::asio::streambuf; - using input_t = std::deque; - using output_t = std::deque>; - - buffer_t buffer_; - input_t input_; - output_t output_; - - void rflush(); - void sflush(); - -protected: - /** - * Do the connection. - * - * \param host the hostname - * \param service the service or port number - * \param handler the non-null handler - */ - virtual void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept = 0; - - /** - * Receive some data. - * - * \param buffer the buffer to complete - * \param handler the non-null handler - */ - virtual void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept = 0; - - /** - * Send data. - * - * \param data the data to send - * \param handler the non-null handler - */ - virtual void do_send(const std::string& data, send_t handler) noexcept = 0; - -public: - /** - * Default constructor. - */ - connection() = default; - - /** - * Virtual destructor defaulted. - */ - virtual ~connection() = default; - - /** - * Connect to the host. - * - * \pre handler the handler - * \param host the host - * \param service the service or port number - * \param handler the non-null handler - */ - void connect(const std::string& host, const std::string& service, connect_t handler); - - /** - * Start receiving data. - * - * \param handler the handler to call - */ - void recv(recv_t handler); - - /** - * Start sending data. - * - * \param message the raw message - * \param handler the handler to call - */ - void send(std::string message, send_t handler = nullptr); -}; - -/** - * \brief Clear TCP connection - */ -class ip_connection : public connection { -private: - boost::asio::ip::tcp::socket socket_; - boost::asio::ip::tcp::resolver resolver_; - -protected: - /** - * \copydoc connection::do_connect - */ - void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override; - - /** - * \copydoc connection::do_recv - */ - void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override; - - /** - * \copydoc connection::do_send - */ - void do_send(const std::string& data, send_t handler) noexcept override; - -public: - /** - * Constructor. - * - * \param service the io service - */ - inline ip_connection(boost::asio::io_service& service) noexcept - : socket_(service) - , resolver_(service) - { - } -}; - -#if defined(HAVE_SSL) - -/** - * \brief SSL connection - */ -class tls_connection : public connection { -private: - boost::asio::ssl::context context_; - boost::asio::ssl::stream socket_; - boost::asio::ip::tcp::resolver resolver_; - -protected: - /** - * \copydoc connection::do_connect - */ - void do_connect(const std::string& host, const std::string& service, connect_t handler) noexcept override; - - /** - * \copydoc connection::do_recv - */ - void do_recv(boost::asio::streambuf& buffer, recv_t handler) noexcept override; - - /** - * \copydoc connection::do_send - */ - void do_send(const std::string& data, send_t handler) noexcept override; - -public: - /** - * Constructor. - * - * \param service the io service - */ - inline tls_connection(boost::asio::io_service& service) noexcept - : context_(boost::asio::ssl::context::sslv23) - , socket_(service, context_) - , resolver_(service) - { - } -}; - -#endif // !HAVE_SSL - -} // !irc - -} // !irccd - -#endif // !IRCCD_IRC_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/irccd.cpp --- a/libirccd/irccd/irccd.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,297 +0,0 @@ -/* - * irccd.cpp -- main irccd class - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 -#include -#include - -#include "command_service.hpp" -#include "irccd.hpp" -#include "plugin_service.hpp" -#include "rule_service.hpp" -#include "server_service.hpp" -#include "transport_service.hpp" - -namespace irccd { - -namespace { - -class log_filter : public log::filter { -private: - std::string info_; - std::string warning_; - std::string debug_; - - std::string convert(const std::string& tmpl, std::string input) const - { - if (tmpl.empty()) - return input; - - string_util::subst params; - - params.flags &= ~(string_util::subst_flags::irc_attrs); - params.keywords.emplace("message", std::move(input)); - - return string_util::format(tmpl, params); - } - -public: - inline log_filter(std::string info, std::string warning, std::string debug) noexcept - : info_(std::move(info)) - , warning_(std::move(warning)) - , debug_(std::move(debug)) - { - } - - std::string pre_debug(std::string input) const override - { - return convert(debug_, std::move(input)); - } - - std::string pre_info(std::string input) const override - { - return convert(info_, std::move(input)); - } - - std::string pre_warning(std::string input) const override - { - return convert(warning_, std::move(input)); - } -}; - -void load_log_file(const ini::section& sc) -{ - /* - * TODO: improve that with CMake options. - */ -#if defined(IRCCD_SYSTEM_WINDOWS) - std::string normal = "log.txt"; - std::string errors = "errors.txt"; -#else - std::string normal = "/var/log/irccd/log.txt"; - std::string errors = "/var/log/irccd/errors.txt"; -#endif - - ini::section::const_iterator it; - - if ((it = sc.find("path-logs")) != sc.end()) - normal = it->value(); - if ((it = sc.find("path-errors")) != sc.end()) - errors = it->value(); - - try { - log::set_logger(std::make_unique(std::move(normal), std::move(errors))); - } catch (const std::exception& ex) { - log::warning() << "logs: " << ex.what() << std::endl; - } -} - -void load_log_syslog() -{ -#if defined(HAVE_SYSLOG) - log::set_logger(std::make_unique()); -#else - log::warning() << "logs: syslog is not available on this platform" << std::endl; -#endif // !HAVE_SYSLOG -} - -} // !namespace - -void irccd::load_logs() -{ - auto sc = config_.section("logs"); - - if (sc.empty()) - return; - - log::set_verbose(string_util::is_identifier(sc.get("verbose").value())); - - auto type = sc.get("type").value(); - - if (!type.empty()) { - // Console is the default, no test case. - if (type == "file") - load_log_file(sc); - else if (type == "syslog") - load_log_syslog(); - else if (type != "console") - log::warning() << "logs: invalid log type '" << type << std::endl; - } -} - -void irccd::load_formats() -{ - auto sc = config_.section("format"); - - if (sc.empty()) - return; - - log::set_filter(std::make_unique( - sc.get("info").value(), - sc.get("warning").value(), - sc.get("debug").value() - )); -} - -void irccd::load_pid() -{ - auto path = config_.value("general", "pidfile"); - - if (path.empty()) - return; - -#if defined(HAVE_GETPID) - std::ofstream out(path, std::ofstream::trunc); - - if (!out) - log::warning() << "irccd: could not open" << path << ": " << std::strerror(errno) << std::endl; - else { - log::debug() << "irccd: pid written in " << path << std::endl; - out << getpid() << std::endl; - } -#else - log::warning() << "irccd: pidfile not supported on this platform" << std::endl; -#endif -} - -void irccd::load_gid() -{ - auto gid = config_.value("general", "gid"); - - if (gid.empty()) - return; - -#if defined(HAVE_SETGID) - try { - sys::set_gid(gid); - } catch (const std::exception& ex) { - log::warning() << "irccd: failed to set gid: " << ex.what() << std::endl; - } -#else - log::warning() << "irccd: gid option not supported" << std::endl; -#endif -} - -void irccd::load_uid() -{ - auto uid = config_.value("general", "gid"); - - if (uid.empty()) - return; - -#if defined(HAVE_SETUID) - try { - sys::set_uid(uid); - } catch (const std::exception& ex) { - log::warning() << "irccd: failed to set uid: " << ex.what() << std::endl; - } -#else - log::warning() << "irccd: uid option not supported" << std::endl; -#endif -} - -irccd::irccd(boost::asio::io_service& service, std::string config) - : config_(std::move(config)) - , service_(service) - , command_service_(std::make_unique()) - , server_service_(std::make_unique(*this)) - , tpt_service_(std::make_unique(*this)) - , rule_service_(std::make_unique()) - , plugin_service_(std::make_unique(*this)) -{ -} - -irccd::~irccd() = default; - -void irccd::load() noexcept -{ - /* - * Order matters, please be careful when changing this. - * - * 1. Open logs as early as possible to use the defined outputs on any - * loading errors. - */ - - // [logs] and [format] sections. - load_logs(); - load_formats(); - - if (!loaded_) - log::info() << "irccd: loading configuration from " << config_.path() << std::endl; - else - log::info() << "irccd: reloading configuration" << std::endl; - - // [general] section. - if (!loaded_) { - load_pid(); - load_gid(); - load_uid(); - } - - if (!loaded_) - tpt_service_->load(config_); - - server_service_->load(config_); - plugin_service_->load(config_); - rule_service_->load(config_); - - // Mark as loaded. - loaded_ = true; -} - -const boost::system::error_category& irccd_category() -{ - static const class category : public boost::system::error_category { - public: - const char* name() const noexcept override - { - return "irccd"; - } - - std::string message(int e) const override - { - switch (static_cast(e)) { - case irccd_error::error::not_irccd: - return "daemon is not irccd instance"; - case irccd_error::error::incompatible_version: - return "major version is incompatible"; - case irccd_error::error::auth_required: - return "authentication is required"; - case irccd_error::error::invalid_auth: - return "invalid authentication"; - case irccd_error::error::invalid_message: - return "invalid message"; - case irccd_error::error::invalid_command: - return "invalid command"; - case irccd_error::error::incomplete_message: - return "command requires more arguments"; - default: - return "no error"; - } - } - } category; - - return category; -} - -boost::system::error_code make_error_code(irccd_error::error e) -{ - return {static_cast(e), irccd_category()}; -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/irccd.hpp --- a/libirccd/irccd/irccd.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,259 +0,0 @@ -/* - * irccd.hpp -- main irccd class - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_HPP -#define IRCCD_HPP - -/** - * \file irccd.hpp - * \brief Base class for irccd front end. - */ - -#include "sysconfig.hpp" - -#include - -#include - -#include "config.hpp" - -/** - * \brief Main irccd namespace - */ -namespace irccd { - -class command_service; -class plugin_service; -class rule_service; -class server_service; -class transport_service; - -/** - * \brief Irccd main instance. - */ -class irccd { -private: - // Configurations. - class config config_; - - // Main io service. - boost::asio::io_service& service_; - - // Tells if the configuration has already been called. - bool loaded_{false}; - - // Services. - std::shared_ptr command_service_; - std::shared_ptr server_service_; - std::shared_ptr tpt_service_; - std::shared_ptr rule_service_; - std::shared_ptr plugin_service_; - - // Not copyable and not movable because services has references to irccd. - irccd(const irccd&) = delete; - irccd(irccd&&) = delete; - - irccd& operator=(const irccd&) = delete; - irccd& operator=(irccd&&) = delete; - - // Load functions. - void load_logs(); - void load_formats(); - void load_pid(); - void load_gid(); - void load_uid(); - -public: - /** - * Prepare standard services. - * - * \param service the service - * \param config the optional path to the configuration. - */ - irccd(boost::asio::io_service& service, std::string config = ""); - - /** - * Default destructor. - */ - ~irccd(); - - /** - * Get the current configuration. - * - * \return the configuration - */ - inline const class config& config() const noexcept - { - return config_; - } - - /** - * Set the configuration. - * - * \param cfg the new config - */ - inline void set_config(class config cfg) noexcept - { - config_ = std::move(cfg); - } - - /** - * Get the underlying io service. - * - * \return the service - */ - inline const boost::asio::io_service& service() const noexcept - { - return service_; - } - - /** - * Overloaded function. - * - * \return the service - */ - inline boost::asio::io_service& service() noexcept - { - return service_; - } - - /** - * Access the command service. - * - * \return the service - */ - inline command_service& commands() noexcept - { - return *command_service_; - } - - /** - * Access the server service. - * - * \return the service - */ - inline server_service& servers() noexcept - { - return *server_service_; - } - - /** - * Access the transport service. - * - * \return the service - */ - inline transport_service& transports() noexcept - { - return *tpt_service_; - } - - /** - * Access the rule service. - * - * \return the service - */ - inline rule_service& rules() noexcept - { - return *rule_service_; - } - - /** - * Access the plugin service. - * - * \return the service - */ - inline plugin_service& plugins() noexcept - { - return *plugin_service_; - } - - /** - * Load and re-apply the configuration to the daemon. - */ - void load() noexcept; -}; - -/** - * \brief Irccd error. - */ -class irccd_error : public boost::system::system_error { -public: - /** - * \brief Irccd related errors (1..999) - */ - enum error { - //!< No error. - no_error = 0, - - //!< The connected peer is not irccd. - not_irccd, - - //!< The irccd version is too different. - incompatible_version, - - //!< Authentication was required but not issued. - auth_required, - - //!< Authentication was invalid. - invalid_auth, - - //!< The message was not a valid JSON object. - invalid_message, - - //!< The specified command does not exist. - invalid_command, - - //!< The specified command requires more arguments. - incomplete_message, - }; - - /** - * Inherited constructors. - */ - using system_error::system_error; -}; - -/** - * Get the irccd error category singleton. - * - * \return the singleton - */ -const boost::system::error_category& irccd_category(); - -/** - * Create a boost::system::error_code from irccd_error::error enum. - * - * \param e the error code - */ -boost::system::error_code make_error_code(irccd_error::error e); - -} // !irccd - -namespace boost { - -namespace system { - -template <> -struct is_error_code_enum : public std::true_type { -}; - -} // !system - -} // !boost - -#endif // !IRCCD_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/local_transport_server.hpp --- a/libirccd/irccd/local_transport_server.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -/* - * local_transport_server.hpp -- server side transports (Unix support) - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_LOCAL_TRANSPORT_SERVER_HPP -#define IRCCD_LOCAL_TRANSPORT_SERVER_HPP - -/** - * \file local_transport_server.hpp - * \brief Server side transports (Unix support). - */ - -#include - -#if !defined(IRCCD_SYSTEM_WINDOWS) - -#include "basic_transport_server.hpp" - -namespace irccd { - -/** - * Convenient type for UNIX local sockets. - */ -using local_transport_server = basic_transport_server; - -#endif // !_WIN32 - -} // !irccd - -#endif // !IRCCD_LOCAL_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/plugin.cpp --- a/libirccd/irccd/plugin.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -/* - * plugin.cpp -- irccd JavaScript plugin interface - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 - -#include - -#include "plugin.hpp" -#include "system.hpp" - -namespace fs = boost::filesystem; - -namespace irccd { - -std::shared_ptr plugin_loader::find(const std::string& name) -{ - std::vector filenames; - - if (directories_.empty()) - filenames = sys::plugin_filenames(name, extensions_); - else { - for (const auto& dir : directories_) - for (const auto& ext : extensions_) - filenames.push_back(dir + "/" + name + ext); - } - - for (const auto& candidate : filenames) { - boost::system::error_code ec; - - if (!boost::filesystem::exists(candidate, ec) || ec) - continue; - - auto plugin = open(name, candidate); - - if (plugin) - return plugin; - } - - throw plugin_error(plugin_error::not_found, name); -} - -plugin_error::plugin_error(error errc, std::string name, std::string message) noexcept - : system_error(make_error_code(errc)) - , name_(std::move(name)) - , message_(std::move(message)) -{ - std::ostringstream oss; - - oss << "plugin " << name_ << ": " << code().message(); - - std::istringstream iss(message_); - std::string line; - - while (getline(iss, line)) - oss << "\nplugin " << name_ << ": " << line; - - what_ = oss.str(); -} - -const boost::system::error_category& plugin_category() -{ - static const class category : public boost::system::error_category { - public: - const char* name() const noexcept override - { - return "plugin"; - } - - std::string message(int e) const override - { - switch (static_cast(e)) { - case plugin_error::not_found: - return "plugin not found"; - case plugin_error::exec_error: - return "plugin exec error"; - case plugin_error::already_exists: - return "plugin already exists"; - default: - return "no error"; - } - } - } category; - - return category; -} - -boost::system::error_code make_error_code(plugin_error::error e) -{ - return {static_cast(e), plugin_category()}; -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/plugin.hpp --- a/libirccd/irccd/plugin.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,620 +0,0 @@ -/* - * plugin.hpp -- irccd JavaScript plugin interface - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_PLUGIN_HPP -#define IRCCD_PLUGIN_HPP - -/** - * \file plugin.hpp - * \brief irccd plugins - */ - -/** - * \defgroup plugins Plugins - * \brief Plugin management. - */ - -#include "sysconfig.hpp" - -#include -#include -#include -#include -#include - -#include "server.hpp" -#include "util.hpp" - -namespace irccd { - -class irccd; - -/** - * \brief Configuration map extract from config file. - */ -using plugin_config = std::unordered_map; - -/** - * \brief Formats for plugins. - */ -using plugin_formats = std::unordered_map; - -/** - * \brief Paths for plugins. - */ -using plugin_paths = std::unordered_map; - -/** - * \ingroup plugins - * \brief Abstract plugin. - * - * A plugin is identified by name and can be loaded and unloaded at runtime. - */ -class plugin : public std::enable_shared_from_this { -private: - // Plugin information - std::string name_; - std::string path_; - - // Metadata - std::string author_{"unknown"}; - std::string license_{"unknown"}; - std::string summary_{"unknown"}; - std::string version_{"unknown"}; - -public: - /** - * Constructor. - * - * \param name the plugin id - * \param path the fully resolved path to the plugin - * \throws std::runtime_error on errors - */ - inline plugin(std::string name, std::string path) noexcept - : name_(std::move(name)) - , path_(std::move(path)) - { - } - - /** - * Temporary, close all timers. - */ - virtual ~plugin() = default; - - /** - * Get the plugin name. - * - * \return the plugin name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the plugin path. - * - * \return the plugin path - * \note some plugins may not exist on the disk - */ - inline const std::string& path() const noexcept - { - return path_; - } - - /** - * Get the author. - * - * \return the author - */ - inline const std::string& author() const noexcept - { - return author_; - } - - /** - * Set the author. - * - * \param author the author - */ - inline void set_author(std::string author) noexcept - { - author_ = std::move(author); - } - - /** - * Get the license. - * - * \return the license - */ - inline const std::string& license() const noexcept - { - return license_; - } - - /** - * Set the license. - * - * \param license the license - */ - inline void set_license(std::string license) noexcept - { - license_ = std::move(license); - } - - /** - * Get the summary. - * - * \return the summary - */ - inline const std::string& summary() const noexcept - { - return summary_; - } - - /** - * Set the summary. - * - * \param summary the summary - */ - inline void set_summary(std::string summary) noexcept - { - summary_ = std::move(summary); - } - - /** - * Get the version. - * - * \return the version - */ - inline const std::string& version() const noexcept - { - return version_; - } - - /** - * Set the version. - * - * \param version the version - */ - inline void set_version(std::string version) noexcept - { - version_ = std::move(version); - } - - /** - * Access the plugin configuration. - * - * \return the config - */ - virtual plugin_config config() - { - return {}; - } - - /** - * Set the configuration. - * - * \param config the configuration - */ - virtual void set_config(plugin_config config) - { - util::unused(config); - } - - /** - * Access the plugin formats. - * - * \return the format - */ - virtual plugin_formats formats() - { - return {}; - } - - /** - * Set the formats. - * - * \param formats the formats - */ - virtual void set_formats(plugin_formats formats) - { - util::unused(formats); - } - - /** - * Access the plugin paths. - * - * \return the paths - */ - virtual plugin_paths paths() - { - return {}; - } - - /** - * Set the paths. - * - * \param paths the paths - */ - virtual void set_paths(plugin_paths paths) - { - util::unused(paths); - } - - /** - * On channel message. This event will call onMessage or - * onCommand if the messages starts with the command character - * plus the plugin name. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_command(irccd& irccd, const message_event& event) - { - util::unused(irccd, event); - } - - /** - * On successful connection. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_connect(irccd& irccd, const connect_event& event) - { - util::unused(irccd, event); - } - - /** - * On invitation. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_invite(irccd& irccd, const invite_event& event) - { - util::unused(irccd, event); - } - - /** - * On join. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_join(irccd& irccd, const join_event& event) - { - util::unused(irccd, event); - } - - /** - * On kick. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_kick(irccd& irccd, const kick_event& event) - { - util::unused(irccd, event); - } - - /** - * On load. - * - * \param irccd the irccd instance - */ - virtual void on_load(irccd& irccd) - { - util::unused(irccd); - } - - /** - * On channel message. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_message(irccd& irccd, const message_event& event) - { - util::unused(irccd, event); - } - - /** - * On CTCP Action. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_me(irccd& irccd, const me_event& event) - { - util::unused(irccd, event); - } - - /** - * On user mode change. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_mode(irccd& irccd, const mode_event& event) - { - util::unused(irccd, event); - } - - /** - * On names listing. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_names(irccd& irccd, const names_event& event) - { - util::unused(irccd, event); - } - - /** - * On nick change. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_nick(irccd& irccd, const nick_event& event) - { - util::unused(irccd, event); - } - - /** - * On user notice. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_notice(irccd& irccd, const notice_event& event) - { - util::unused(irccd, event); - } - - /** - * On part. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_part(irccd& irccd, const part_event& event) - { - util::unused(irccd, event); - } - - /** - * On reload. - * - * \param irccd the irccd instance - */ - virtual void on_reload(irccd& irccd) - { - util::unused(irccd); - } - - /** - * On topic change. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_topic(irccd& irccd, const topic_event& event) - { - util::unused(irccd, event); - } - - /** - * On unload. - * - * \param irccd the irccd instance - */ - virtual void on_unload(irccd& irccd) - { - util::unused(irccd); - } - - /** - * On whois information. - * - * \param irccd the irccd instance - * \param event the event - */ - virtual void on_whois(irccd& irccd, const whois_event& event) - { - util::unused(irccd, event); - } -}; - -/** - * \brief Abstract interface for searching plugins. - * - * This class is used to make loading of plugins extensible, the plugin_service - * knows some predefined plugins loaders and use them to search for available - * plugins. - * - * This makes easier to implement new plugins or new ways of loading them. - * - * \see dynlib_plugin_loader - * \see js_plugin_loader - */ -class plugin_loader { -private: - std::vector directories_; - std::vector extensions_; - -public: - /** - * Construct the loader with a predefined set of directories and extensions. - * - * If directories is not specified, a sensible default list of system and - * user paths are searched. - * - * \pre !extensions.empty() - * \param directories optional list of directories to search - * \param extensions the non empty list of extensions supported - */ - inline plugin_loader(std::vector directories, - std::vector extensions) noexcept - : directories_(std::move(directories)) - , extensions_(std::move(extensions)) - { - assert(!extensions_.empty()); - } - - /** - * Set directories where to search plugins. - * - * \param dirs the directories - */ - inline void set_directories(std::vector directories) - { - directories_ = std::move(directories); - } - - /** - * Set supported extensions for this loader. - * - * \pre !extensions.empty() - * \param extensions the extensions (with the dot) - */ - inline void set_extensions(std::vector extensions) - { - assert(!extensions.empty()); - - extensions_ = std::move(extensions); - } - - /** - * Try to open the plugin specified by path. - * - * The implementation must test if the plugin is suitable for opening, by - * testing extension for example. - * - * \param file the file - */ - virtual std::shared_ptr open(const std::string& id, - const std::string& file) = 0; - - /** - * Search for a plugin named by this id. - * - * \param id the plugin id - * \return the plugin - */ - virtual std::shared_ptr find(const std::string& id); -}; - -/** - * \brief Plugin error. - */ -class plugin_error : public boost::system::system_error { -public: - /** - * \brief Server related errors (3000..3999) - */ - enum error { - //!< No error. - no_error = 0, - - //!< The specified plugin is not found. - not_found = 2000, - - //!< The plugin was unable to run the function. - exec_error, - - //!< The plugin is already loaded. - already_exists, - }; - -private: - std::string name_; - std::string message_; - std::string what_; - -public: - /** - * Constructor. - * - * \param code the error code - * \param name the plugin name - * \param message the optional message (e.g. error from plugin) - */ - plugin_error(error code, std::string name, std::string message = "") noexcept; - - /** - * Get the plugin name. - * - * \return the name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the additional message. - * - * \return the message - */ - inline const std::string& message() const noexcept - { - return message_; - } - - /** - * Get message appropriate for use with logger. - */ - const char* what() const noexcept override - { - return what_.c_str(); - } -}; - -/** - * Get the plugin error category singleton. - * - * \return the singleton - */ -const boost::system::error_category& server_category(); - -/** - * Create a boost::system::error_code from plugin_error::error enum. - * - * \param e the error code - */ -boost::system::error_code make_error_code(plugin_error::error e); - -} // !irccd - -namespace boost { - -namespace system { - -template <> -struct is_error_code_enum : public std::true_type { -}; - -} // !system - -} // !boost - -#endif // !IRCCD_PLUGIN_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/plugin_service.cpp --- a/libirccd/irccd/plugin_service.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,232 +0,0 @@ -/* - * plugin_service.cpp -- plugin service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 -#include - -#include "config.hpp" -#include "irccd.hpp" -#include "plugin_service.hpp" -#include "string_util.hpp" -#include "system.hpp" - -namespace irccd { - -namespace { - -template -Map to_map(const config& conf, const std::string& section) -{ - Map ret; - - for (const auto& opt : conf.doc().get(section)) - ret.emplace(opt.key(), opt.value()); - - return ret; -} - -} // !namespace - -plugin_service::plugin_service(irccd& irccd) noexcept - : irccd_(irccd) -{ -} - -plugin_service::~plugin_service() -{ - for (const auto& plugin : plugins_) { - try { - plugin->on_unload(irccd_); - } catch (const std::exception& ex) { - log::warning() << "plugin: " << plugin->name() << ": " << ex.what() << std::endl; - } - } -} - -bool plugin_service::has(const std::string& name) const noexcept -{ - return std::count_if(plugins_.cbegin(), plugins_.cend(), [&] (const auto& plugin) { - return plugin->name() == name; - }) > 0; -} - -std::shared_ptr plugin_service::get(const std::string& name) const noexcept -{ - auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { - return plugin->name() == name; - }); - - if (it == plugins_.end()) - return nullptr; - - return *it; -} - -std::shared_ptr plugin_service::require(const std::string& name) const -{ - auto plugin = get(name); - - if (!plugin) - throw plugin_error(plugin_error::not_found, name); - - return plugin; -} - -void plugin_service::add(std::shared_ptr plugin) -{ - plugins_.push_back(std::move(plugin)); -} - -void plugin_service::add_loader(std::unique_ptr loader) -{ - loaders_.push_back(std::move(loader)); -} - -plugin_config plugin_service::config(const std::string& id) -{ - return to_map(irccd_.config(), string_util::sprintf("plugin.%s", id)); -} - -plugin_formats plugin_service::formats(const std::string& id) -{ - return to_map(irccd_.config(), string_util::sprintf("format.%s", id)); -} - -plugin_paths plugin_service::paths(const std::string& id) -{ - auto defaults = to_map(irccd_.config(), "paths"); - auto paths = to_map(irccd_.config(), string_util::sprintf("paths.%s", id)); - - // Fill defaults paths. - if (!defaults.count("cache")) - defaults.emplace("cache", sys::cachedir() + "/plugin/" + id); - if (!defaults.count("data")) - paths.emplace("data", sys::datadir() + "/plugin/" + id); - if (!defaults.count("config")) - paths.emplace("config", sys::sysconfigdir() + "/plugin/" + id); - - // Now fill missing fields. - if (!paths.count("cache")) - paths.emplace("cache", defaults["cache"]); - if (!paths.count("data")) - paths.emplace("data", defaults["data"]); - if (!paths.count("config")) - paths.emplace("config", defaults["config"]); - - return paths; -} - -std::shared_ptr plugin_service::open(const std::string& id, - const std::string& path) -{ - for (const auto& loader : loaders_) { - auto plugin = loader->open(id, path); - - if (plugin) - return plugin; - } - - return nullptr; -} - -std::shared_ptr plugin_service::find(const std::string& id) -{ - for (const auto& loader : loaders_) { - auto plugin = loader->find(id); - - if (plugin) - return plugin; - } - - return nullptr; -} - -void plugin_service::load(std::string name, std::string path) -{ - if (has(name)) - throw plugin_error(plugin_error::already_exists, name); - - std::shared_ptr plugin; - - if (path.empty()) - plugin = find(name); - else - plugin = open(name, std::move(path)); - - if (!plugin) - throw plugin_error(plugin_error::not_found, name); - - plugin->set_config(config(name)); - plugin->set_formats(formats(name)); - plugin->set_paths(paths(name)); - - exec(plugin, &plugin::on_load, irccd_); - add(std::move(plugin)); -} - -void plugin_service::reload(const std::string& name) -{ - auto plugin = get(name); - - if (!plugin) - throw plugin_error(plugin_error::not_found, name); - - exec(plugin, &plugin::on_reload, irccd_); -} - -void plugin_service::unload(const std::string& name) -{ - auto it = std::find_if(plugins_.begin(), plugins_.end(), [&] (const auto& plugin) { - return plugin->name() == name; - }); - - if (it == plugins_.end()) - throw plugin_error(plugin_error::not_found, name); - - // Erase first, in case of throwing. - auto save = *it; - - plugins_.erase(it); - exec(save, &plugin::on_unload, irccd_); -} - -void plugin_service::load(const class config& cfg) noexcept -{ - for (const auto& option : cfg.section("plugins")) { - if (!string_util::is_identifier(option.key())) - continue; - - auto name = option.key(); - auto p = get(name); - - // Reload the plugin if already loaded. - if (p) { - p->set_config(config(name)); - p->set_formats(formats(name)); - p->set_paths(paths(name)); - } else { - try { - load(name, option.value()); - } catch (const std::exception& ex) { - log::warning(ex.what()); - } - } - } -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/plugin_service.hpp --- a/libirccd/irccd/plugin_service.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,237 +0,0 @@ -/* - * plugin_service.hpp -- plugin service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_PLUGIN_SERVICE_HPP -#define IRCCD_PLUGIN_SERVICE_HPP - -/** - * \file plugin_service.hpp - * \brief Plugin service. - */ - -#include -#include -#include -#include - -#include "plugin.hpp" - -namespace irccd { - -class irccd; -class config; - -/** - * \brief Manage plugins. - * \ingroup services - */ -class plugin_service { -private: - irccd& irccd_; - std::vector> plugins_; - std::vector> loaders_; - -public: - /** - * Create the plugin service. - * - * \param irccd the irccd instance - */ - plugin_service(irccd& irccd) noexcept; - - /** - * Destroy plugins. - */ - ~plugin_service(); - - /** - * Get the list of plugins. - * - * \return the list of plugins - */ - inline const std::vector>& list() const noexcept - { - return plugins_; - } - - /** - * Check if a plugin is loaded. - * - * \param name the plugin id - * \return true if has plugin - */ - bool has(const std::string& name) const noexcept; - - /** - * Get a loaded plugin or null if not found. - * - * \param name the plugin id - * \return the plugin or empty one if not found - */ - std::shared_ptr get(const std::string& name) const noexcept; - - /** - * Find a loaded plugin. - * - * \param name the plugin id - * \return the plugin - * \throws std::out_of_range if not found - */ - std::shared_ptr require(const std::string& name) const; - - /** - * Add the specified plugin to the registry. - * - * \pre plugin != nullptr - * \param plugin the plugin - * \note the plugin is only added to the list, no action is performed on it - */ - void add(std::shared_ptr plugin); - - /** - * Add a loader. - * - * \param loader the loader - */ - void add_loader(std::unique_ptr loader); - - /** - * Get the configuration for the specified plugin. - * - * \return the configuration - */ - plugin_config config(const std::string& id); - - /** - * Get the formats for the specified plugin. - * - * \return the formats - */ - plugin_formats formats(const std::string& id); - - /** - * Get the paths for the specified plugin. - * - * If none is defined, return the default ones. - * - * \return the paths - */ - plugin_paths paths(const std::string& id); - - /** - * Generic function for opening the plugin at the given path. - * - * This function will search for every pluginLoader and call open() on it, - * the first one that success will be returned. - * - * \param id the plugin id - * \param path the path to the file - * \return the plugin or nullptr on failures - */ - std::shared_ptr open(const std::string& id, - const std::string& path); - - /** - * Generic function for finding a plugin. - * - * \param id the plugin id - * \return the plugin or nullptr on failures - */ - std::shared_ptr find(const std::string& id); - - /** - * Convenient wrapper that loads a plugin, call onLoad and add it to the - * registry. - * - * Any errors are printed using logger. - * - * \param name the name - * \param path the optional path (searched if empty) - */ - void load(std::string name, std::string path = ""); - - /** - * Unload a plugin and remove it. - * - * \param name the plugin id - */ - void unload(const std::string& name); - - /** - * Reload a plugin by calling onReload. - * - * \param name the plugin name - * \throw std::exception on failures - */ - void reload(const std::string& name); - - /** - * Call a plugin function and throw an exception with the following errors: - * - * - plugin_error::not_found if not loaded - * - plugin_error::exec_error if function failed - * - * \pre plugin != nullptr - * \param plugin the plugin - * \param func the plugin member function (pointer to member) - * \param args the arguments to pass - */ - template - void exec(std::shared_ptr plugin, Func fn, Args&&... args) - { - assert(plugin); - - // TODO: replace with C++17 std::invoke. - try { - ((*plugin).*(fn))(std::forward(args)...); - } catch (const std::exception& ex) { - throw plugin_error(plugin_error::exec_error, plugin->name(), ex.what()); - } catch (...) { - throw plugin_error(plugin_error::exec_error, plugin->name()); - } - } - - /** - * Overloaded function. - * - * \param name the plugin name - * \param func the plugin member function (pointer to member) - * \param args the arguments to pass - */ - template - void exec(const std::string& name, Func fn, Args&&... args) - { - auto plugin = find(name); - - if (!plugin) - throw plugin_error(plugin_error::not_found, plugin->name()); - - exec(plugin, fn, std::forward(args)...); - } - - /** - * Load all plugins. - * - * \param cfg the config - */ - void load(const class config& cfg) noexcept; -}; - -} // !irccd - -#endif // !IRCCD_PLUGIN_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/rule.cpp --- a/libirccd/irccd/rule.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,89 +0,0 @@ -/* - * rule.cpp -- rule for server and channels - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 -#include - -#include "rule.hpp" - -namespace irccd { - -bool rule::match_set(const set& set, const std::string& value) const noexcept -{ - return value.empty() || set.empty() || set.count(value) == 1; -} - -rule::rule(set servers, set channels, set origins, set plugins, set events, action_type action) - : servers_(std::move(servers)) - , channels_(std::move(channels)) - , origins_(std::move(origins)) - , plugins_(std::move(plugins)) - , events_(std::move(events)) - , action_(action) -{ -} - -bool rule::match(const std::string& server, - const std::string& channel, - const std::string& nick, - const std::string& plugin, - const std::string& event) const noexcept -{ - auto tolower = [] (auto str) { - std::transform(str.begin(), str.end(), str.begin(), ::tolower); - return str; - }; - - return match_set(servers_, tolower(server)) && - match_set(channels_, tolower(channel)) && - match_set(origins_, tolower(nick)) && - match_set(plugins_, tolower(plugin)) && - match_set(events_, event); -} - -const boost::system::error_category& rule_category() -{ - static const class category : public boost::system::error_category { - public: - const char* name() const noexcept override - { - return "rule"; - } - - std::string message(int e) const override - { - switch (static_cast(e)) { - case rule_error::invalid_action: - return "invalid action given"; - case rule_error::invalid_index: - return "invalid index"; - default: - return "no error"; - } - } - } category; - - return category; -} - -boost::system::error_code make_error_code(rule_error::error e) -{ - return {static_cast(e), rule_category()}; -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/rule.hpp --- a/libirccd/irccd/rule.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,270 +0,0 @@ -/* - * rule.hpp -- rule for server and channels - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_RULE_HPP -#define IRCCD_RULE_HPP - -/** - * \file rule.hpp - * \brief Rule description - */ - -#include "sysconfig.hpp" - -#include -#include -#include - -#include - -namespace irccd { - -/** - * \brief Manage rule to activate or deactive events. - */ -class rule { -public: - /** - * List of criterias. - */ - using set = std::unordered_set; - - /** - * \brief Rule action type. - */ - enum class action_type { - accept, //!< The event is accepted (default) - drop //!< The event is dropped - }; - -private: - set servers_; - set channels_; - set origins_; - set plugins_; - set events_; - action_type action_{action_type::accept}; - - /* - * Check if a set contains the value and return true if it is or return - * true if value is empty (which means applicable). - */ - bool match_set(const set&, const std::string&) const noexcept; - -public: - /** - * Rule constructor. - * - * \param servers the server list - * \param channels the channels - * \param nicknames the nicknames - * \param plugins the plugins - * \param events the events - * \param action the rule action - * \throw std::invalid_argument if events are invalid - */ - rule(set servers = {}, - set channels = {}, - set nicknames = {}, - set plugins = {}, - set events = {}, - action_type action = action_type::accept); - - /** - * Check if that rule apply for the given criterias. - * - * \param server the server - * \param channel the channel - * \param nick the origin - * \param plugin the plugin - * \param event the event - * \return true if match - */ - bool match(const std::string& server, - const std::string& channel, - const std::string& nick, - const std::string& plugin, - const std::string& event) const noexcept; - - /** - * Get the action. - * - * \return the action - */ - inline action_type action() const noexcept - { - return action_; - } - - /** - * Set the action. - * - * \pre action must be valid - */ - inline void set_action(action_type action) noexcept - { - assert(action == action_type::accept || action == action_type::drop); - - action_ = action; - } - - /** - * Get the servers. - * - * \return the servers - */ - inline const set& servers() const noexcept - { - return servers_; - } - - /** - * Overloaded function. - * - * \return the servers - */ - inline set& servers() noexcept - { - return servers_; - } - - /** - * Get the channels. - * - * \return the channels - */ - inline const set& channels() const noexcept - { - return channels_; - } - - /** - * Overloaded function. - * - * \return the channels - */ - inline set& channels() noexcept - { - return channels_; - } - - /** - * Get the origins. - * - * \return the origins - */ - inline const set& origins() const noexcept - { - return origins_; - } - - /** - * Get the plugins. - * - * \return the plugins - */ - inline const set& plugins() const noexcept - { - return plugins_; - } - - /** - * Overloaded function. - * - * \return the plugins - */ - inline set& plugins() noexcept - { - return plugins_; - } - - /** - * Get the events. - * - * \return the events - */ - inline const set& events() const noexcept - { - return events_; - } - - /** - * Overloaded function. - * - * \return the events - */ - inline set& events() noexcept - { - return events_; - } -}; - -/** - * \brief Rule error. - */ -class rule_error : public boost::system::system_error { -public: - /** - * \brief Rule related errors (4000..4999) - */ - enum error { - //!< No error. - no_error = 0, - - //!< Invalid action given. - invalid_action = 4000, - - //!< Invalid rule index. - invalid_index, - }; - - /** - * Inherited constructors. - */ - using system_error::system_error; -}; - -/** - * Get the rule error category singleton. - * - * \return the singleton - */ -const boost::system::error_category& rule_category(); - -/** - * Create a boost::system::error_code from rule_error::error enum. - * - * \param e the error code - */ -boost::system::error_code make_error_code(rule_error::error e); - -} // !irccd - -namespace boost { - -namespace system { - -template <> -struct is_error_code_enum : public std::true_type { -}; - -} // !system - -} // !boost - -#endif // !IRCCD_RULE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/rule_service.cpp --- a/libirccd/irccd/rule_service.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -/* - * rule_service.cpp -- rule service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 - -#include -#include - -#include "config.hpp" -#include "rule_service.hpp" -#include "string_util.hpp" - -namespace irccd { - -namespace { - -rule load_rule(const ini::section& sc) -{ - assert(sc.key() == "rule"); - - // Simple converter from std::vector to std::unordered_set. - auto toset = [] (const auto& v) { - return std::unordered_set(v.begin(), v.end()); - }; - - rule::set servers, channels, origins, plugins, events; - rule::action_type action = rule::action_type::accept; - - // Get the sets. - ini::section::const_iterator it; - - if ((it = sc.find("servers")) != sc.end()) - servers = toset(*it); - if ((it = sc.find("channels")) != sc.end()) - channels = toset(*it); - if ((it = sc.find("origins")) != sc.end()) - origins = toset(*it); - if ((it = sc.find("plugins")) != sc.end()) - plugins = toset(*it); - if ((it = sc.find("channels")) != sc.end()) - channels = toset(*it); - - // Get the action. - auto actionstr = sc.get("action").value(); - - if (actionstr == "drop") - action = rule::action_type::drop; - else if (actionstr == "accept") - action = rule::action_type::accept; - else - throw rule_error(rule_error::invalid_action); - - return { - std::move(servers), - std::move(channels), - std::move(origins), - std::move(plugins), - std::move(events), - action - }; -} - -} // !namespace - -void rule_service::add(rule rule) -{ - rules_.push_back(std::move(rule)); -} - -void rule_service::insert(rule rule, unsigned position) -{ - assert(position <= rules_.size()); - - rules_.insert(rules_.begin() + position, std::move(rule)); -} - -void rule_service::remove(unsigned position) -{ - assert(position < rules_.size()); - - rules_.erase(rules_.begin() + position); -} - -const rule &rule_service::require(unsigned position) const -{ - if (position >= rules_.size()) - throw rule_error(rule_error::invalid_index); - - return rules_[position]; -} - -rule &rule_service::require(unsigned position) -{ - if (position >= rules_.size()) - throw rule_error(rule_error::invalid_index); - - return rules_[position]; -} - -bool rule_service::solve(const std::string& server, - const std::string& channel, - const std::string& origin, - const std::string& plugin, - const std::string& event) noexcept -{ - bool result = true; - - log::debug(string_util::sprintf("rule: solving for server=%s, channel=%s, origin=%s, plugin=%s, event=%s", - server, channel, origin, plugin, event)); - - int i = 0; - for (const auto& rule : rules_) { - auto action = rule.action() == rule::action_type::accept ? "accept" : "drop"; - - log::debug() << " candidate " << i++ << ":\n" - << " servers: " << string_util::join(rule.servers()) << "\n" - << " channels: " << string_util::join(rule.channels()) << "\n" - << " origins: " << string_util::join(rule.origins()) << "\n" - << " plugins: " << string_util::join(rule.plugins()) << "\n" - << " events: " << string_util::join(rule.events()) << "\n" - << " action: " << action << std::endl; - - if (rule.match(server, channel, origin, plugin, event)) - result = rule.action() == rule::action_type::accept; - } - - return result; -} - -void rule_service::load(const config& cfg) noexcept -{ - rules_.clear(); - - for (const auto& section : cfg.doc()) { - if (section.key() != "rule") - continue; - - try { - rules_.push_back(load_rule(section)); - } catch (const std::exception& ex) { - log::warning() << "rule: " << ex.what() << std::endl; - } - } -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/rule_service.hpp --- a/libirccd/irccd/rule_service.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -/* - * rule_service.hpp -- rule service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_RULE_SERVICE_HPP -#define IRCCD_RULE_SERVICE_HPP - -/** - * \file rule_service.hpp - * \brief Rule service. - */ - -#include - -#include "rule.hpp" - -namespace irccd { - -class config; - -/** - * \brief Store and solve rules. - * \ingroup services - */ -class rule_service { -private: - std::vector rules_; - -public: - /** - * Get the list of rules. - * - * \return the list of rules - */ - inline const std::vector& list() const noexcept - { - return rules_; - } - - /** - * Get the number of rules. - * - * \return the number of rules - */ - inline std::size_t length() const noexcept - { - return rules_.size(); - } - - /** - * Append a rule. - * - * \param rule the rule to append - */ - void add(rule rule); - - /** - * Insert a new rule at the specified position. - * - * \param rule the rule - * \param position the position - */ - void insert(rule rule, unsigned position); - - /** - * Remove a new rule from the specified position. - * - * \pre position must be valid - * \param position the position - */ - void remove(unsigned position); - - /** - * Get a rule at the specified index or throw an exception if not found. - * - * \param position the position - * \return the rule - * \throw std::out_of_range if position is invalid - */ - const rule& require(unsigned position) const; - - /** - * Overloaded function. - * - * \copydoc require - */ - rule& require(unsigned position); - - /** - * Resolve the action to execute with the specified list of rules. - * - * \param server the server name - * \param channel the channel name - * \param origin the origin - * \param plugin the plugin name - * \param event the event name (e.g onKick) - * \return true if the plugin must be called - */ - bool solve(const std::string& server, - const std::string& channel, - const std::string& origin, - const std::string& plugin, - const std::string& event) noexcept; - - /** - * Load rules from the configuration. - * - * \param cfg the config - */ - void load(const config& cfg) noexcept; -}; - -} // !irccd - -#endif // !IRCCD_RULE_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/server.cpp --- a/libirccd/irccd/server.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,756 +0,0 @@ -/* - * server.cpp -- an IRC server - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "sysconfig.hpp" - -#include -#include -#include -#include - -#if !defined(IRCCD_SYSTEM_WINDOWS) -# include -# include -# include -# include -#endif - -#include "json_util.hpp" -#include "logger.hpp" -#include "server.hpp" -#include "string_util.hpp" -#include "system.hpp" - -namespace irccd { - -namespace { - -/* - * clean_prefix - * ------------------------------------------------------------------ - * - * Remove the user prefix only if it is present in the mode table, for example - * removes @ from @irccd if and only if @ is a character mode (e.g. operator). - */ -std::string clean_prefix(const std::map& modes, std::string nickname) -{ - if (nickname.length() == 0) - return nickname; - - for (const auto& pair : modes) - if (nickname[0] == pair.second) - nickname.erase(0, 1); - - return nickname; -} - -/* - * isupport_extract_prefixes - * ------------------------------------------------------------------ - * - * Read modes from the IRC event numeric. - */ -std::map isupport_extract_prefixes(const std::string& line) -{ - // FIXME: what if line has different size? - std::pair table[16]; - std::string buf = line.substr(7); - std::map modes; - - for (int i = 0; i < 16; ++i) - table[i] = std::make_pair(-1, -1); - - int j = 0; - bool read_modes = true; - - for (size_t i = 0; i < buf.size(); ++i) { - if (buf[i] == '(') - continue; - if (buf[i] == ')') { - j = 0; - read_modes = false; - continue; - } - - if (read_modes) - table[j++].first = buf[i]; - else - table[j++].second = buf[i]; - } - - // Put these as a map of mode to prefix. - for (int i = 0; i < 16; ++i) { - auto key = static_cast(table[i].first); - auto value = table[i].second; - - modes.emplace(key, value); - } - - return modes; -} - -} // !namespace - -void server::remove_joined_channel(const std::string& channel) -{ - jchannels_.erase(std::remove(jchannels_.begin(), jchannels_.end(), channel), jchannels_.end()); -} - -channel server::split_channel(const std::string& value) -{ - auto pos = value.find(':'); - - if (pos != std::string::npos) - return {value.substr(0, pos), value.substr(pos + 1)}; - - return {value, ""}; -} - -server::server(boost::asio::io_service& service, std::string name) - : name_(std::move(name)) - , service_(service) - , timer_(service) -{ - // Initialize nickname and username. - auto user = sys::username(); - - nickname_ = user.empty() ? "irccd" : user; - username_ = user.empty() ? "irccd" : user; -} - -void server::dispatch_connect(const irc::message&) -{ - state_ = state_t::connected; - on_connect({shared_from_this()}); - - for (const auto& channel : rchannels_) { - log::info() << "server " << name_ << ": auto joining " << channel.name << std::endl; - join(channel.name, channel.password); - } -} - -void server::dispatch_endofnames(const irc::message& msg) -{ - /* - * Called when end of name listing has finished on a channel. - * - * params[0] == originator - * params[1] == channel - * params[2] == End of NAMES list - */ - if (msg.args().size() < 3 || msg.arg(1) == "") - return; - - auto it = names_map_.find(msg.arg(1)); - - if (it != names_map_.end()) { - std::vector list(it->second.begin(), it->second.end()); - - on_names({shared_from_this(), msg.arg(1), std::move(list)}); - - // Don't forget to remove the list. - names_map_.erase(it); - } -} - -void server::dispatch_endofwhois(const irc::message& msg) -{ - /* - * Called when whois is finished. - * - * params[0] == originator - * params[1] == nickname - * params[2] == End of WHOIS list - */ - auto it = whois_map_.find(msg.arg(1)); - - if (it != whois_map_.end()) { - on_whois({shared_from_this(), it->second}); - - // Don't forget to remove. - whois_map_.erase(it); - } -} - -void server::dispatch_invite(const irc::message& msg) -{ - // If join-invite is set, join the channel. - if ((flags_ & join_invite) && is_self(msg.arg(0))) - join(msg.arg(1)); - - on_invite({shared_from_this(), msg.prefix(), msg.arg(1), msg.arg(0)}); -} - -void server::dispatch_isupport(const irc::message& msg) -{ - for (unsigned int i = 0; i < msg.args().size(); ++i) { - if (msg.arg(i).compare(0, 6, "PREFIX") == 0) { - modes_ = isupport_extract_prefixes(msg.arg(i)); - -#if !defined(NDEBUG) - auto show = [this] (auto mode, auto title) { - auto it = modes_.find(mode); - - if (it != modes_.end()) - log::debug(string_util::sprintf(" %-12s: %c", title, it->second)); - }; - - log::debug(string_util::sprintf("server %s: isupport modes:", name_)); - show(channel_mode::creator, "creator"); - show(channel_mode::half_op, "half_op"); - show(channel_mode::op, "op"); - show(channel_mode::protection, "protection"); - show(channel_mode::voiced, "voiced"); -#endif // !NDEBUG - - break; - } - } -} - -void server::dispatch_join(const irc::message& msg) -{ - if (is_self(msg.prefix())) - jchannels_.push_back(msg.arg(0)); - - on_join({shared_from_this(), msg.prefix(), msg.arg(0)}); -} - -void server::dispatch_kick(const irc::message& msg) -{ - if (is_self(msg.arg(1))) { - // Remove the channel from the joined list. - remove_joined_channel(msg.arg(0)); - - // Rejoin the channel if the option has been set and I was kicked. - if (flags_ & auto_rejoin) - join(msg.arg(0)); - } - - on_kick({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1), msg.arg(2)}); -} - -void server::dispatch_mode(const irc::message& msg) -{ - on_mode({ - shared_from_this(), - msg.prefix(), - msg.arg(0), - msg.arg(1), - msg.arg(2), - msg.arg(3), - msg.arg(4) - }); -} - -void server::dispatch_namreply(const irc::message& msg) -{ - /* - * Called multiple times to list clients on a channel. - * - * params[0] == originator - * params[1] == '=' - * params[2] == channel - * params[3] == list of users with their prefixes - * - * IDEA for the future: maybe give the appropriate mode as a second - * parameter in onNames. - */ - if (msg.args().size() < 4 || msg.arg(2) == "" || msg.arg(3) == "") - return; - - auto users = string_util::split(msg.arg(3), " \t"); - - // The listing may add some prefixes, remove them if needed. - for (auto u : users) - names_map_[msg.arg(2)].insert(clean_prefix(modes_, u)); -} - -void server::dispatch_nick(const irc::message& msg) -{ - // Update our nickname. - if (is_self(msg.prefix())) - nickname_ = msg.arg(0); - - on_nick({shared_from_this(), msg.prefix(), msg.arg(0)}); -} - -void server::dispatch_notice(const irc::message& msg) -{ - on_notice({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)}); -} - -void server::dispatch_part(const irc::message& msg) -{ - // Remove the channel from the joined list if I left a channel. - if (is_self(msg.prefix())) - remove_joined_channel(msg.arg(1)); - - on_part({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)}); -} - -void server::dispatch_ping(const irc::message& msg) -{ - assert(msg.command() == "PING"); - - conn_->send(string_util::sprintf("PONG %s", msg.arg(0))); -} - -void server::dispatch_privmsg(const irc::message& msg) -{ - assert(msg.command() == "PRIVMSG"); - - if (msg.is_ctcp(1)) { - auto cmd = msg.ctcp(1); - - if (cmd.compare(0, 6, "ACTION") == 0) - on_me({shared_from_this(), msg.prefix(), msg.arg(0), cmd.substr(7)}); - } else if (is_self(msg.arg(0))) - on_query({shared_from_this(), msg.prefix(), msg.arg(1)}); - else - on_message({shared_from_this(), msg.prefix(), msg.arg(0), msg.arg(1)}); -} - -void server::dispatch_topic(const irc::message& msg) -{ - assert(msg.command() == "TOPIC"); - - on_topic({shared_from_this(), msg.arg(0), msg.arg(1), msg.arg(2)}); -} - -void server::dispatch_whoischannels(const irc::message& msg) -{ - /* - * Called when we have received channels for one user. - * - * params[0] == originator - * params[1] == nickname - * params[2] == list of channels with their prefixes - */ - if (msg.args().size() < 3 || msg.arg(1) == "" || msg.arg(2) == "") - return; - - auto it = whois_map_.find(msg.arg(1)); - - if (it != whois_map_.end()) { - auto channels = string_util::split(msg.arg(2), " \t"); - - // Clean their prefixes. - for (auto& s : channels) - s = clean_prefix(modes_, s); - - it->second.channels = std::move(channels); - } -} - -void server::dispatch_whoisuser(const irc::message& msg) -{ - /* - * Called when whois information has been partially received. - * - * params[0] == originator - * params[1] == nickname - * params[2] == username - * params[3] == host - * params[4] == * (no idea what is that) - * params[5] == realname - */ - if (msg.args().size() < 6 || msg.arg(1) == "" || msg.arg(2) == "" || msg.arg(3) == "" || msg.arg(5) == "") - return; - - class whois info; - - info.nick = msg.arg(1); - info.user = msg.arg(2); - info.host = msg.arg(3); - info.realname = msg.arg(5); - - whois_map_.emplace(info.nick, info); -} - -void server::dispatch(const irc::message& message) -{ - if (message.is(5)) - dispatch_isupport(message); - else if (message.is(irc::err::nomotd) || message.is(irc::rpl::endofmotd)) - dispatch_connect(message); - else if (message.command() == "INVITE") - dispatch_invite(message); - else if (message.command() == "JOIN") - dispatch_join(message); - else if (message.command() == "KICK") - dispatch_kick(message); - else if (message.command() == "MODE") - dispatch_mode(message); - else if (message.command() == "NICK") - dispatch_nick(message); - else if (message.command() == "NOTICE") - dispatch_notice(message); - else if (message.command() == "TOPIC") - dispatch_topic(message); - else if (message.command() == "PART") - dispatch_part(message); - else if (message.command() == "PING") - dispatch_ping(message); - else if (message.command() == "PRIVMSG") - dispatch_privmsg(message); - else if (message.is(irc::rpl::namreply)) - dispatch_namreply(message); - else if (message.is(irc::rpl::endofnames)) - dispatch_endofnames(message); - else if (message.is(irc::rpl::endofwhois)) - dispatch_endofwhois(message); - else if (message.is(irc::rpl::whoischannels)) - dispatch_whoischannels(message); - else if (message.is(irc::rpl::whoisuser)) - dispatch_whoisuser(message); -} - -void server::handle_recv(boost::system::error_code code, irc::message message) -{ - if (code) { - state_ = state_t::disconnected; - conn_ = nullptr; - } else { - dispatch(message); - recv(); - } -} - -void server::recv() -{ - conn_->recv([this] (auto code, auto message) { - handle_recv(std::move(code), std::move(message)); - }); -} - -void server::identify() -{ - assert(state_ == state_t::identifying); - - log::debug(string_util::sprintf("server %s: connected, identifying", name_)); - log::debug(string_util::sprintf("server %s: verifying server", name_)); - - if (!password_.empty()) - conn_->send(string_util::sprintf("PASS %s", password_)); - - conn_->send(string_util::sprintf("NICK %s", nickname_)); - conn_->send(string_util::sprintf("USER %s unknown unknown :%s", username_, realname_)); -} - -void server::wait() -{ - assert(state_ == state_t::waiting); - - timer_.expires_from_now(boost::posix_time::seconds(recodelay_)); - timer_.async_wait([this] (auto) { - recocur_ ++; - connect(); - }); -} - -void server::handle_connect(boost::system::error_code code) -{ - if (code) { - conn_ = nullptr; - log::warning(string_util::sprintf("server %s: error while connecting", name_)); - log::warning(string_util::sprintf("server %s: %s", name_, code.message())); - - // Wait before reconnecting. - if (recotries_ != 0) { - if (recotries_ > 0 && recocur_ >= recotries_) { - log::warning() << "server " << name_ << ": giving up" << std::endl; - - state_ = state_t::disconnected; - on_die(); - } else { - log::warning() << "server " << name_ << ": retrying in " << - recodelay_ << " seconds" << std::endl; - - state_ = state_t::waiting; - wait(); - } - } else - state_ = state_t::disconnected; - } else { - state_ = state_t::identifying; - recocur_ = 0U; - jchannels_.clear(); - - identify(); - recv(); - } -} - -server::~server() -{ - disconnect(); -} - -void server::set_nickname(std::string nickname) -{ - if (state_ == state_t::connected) - conn_->send(string_util::sprintf("NICK %s", nickname)); - else - nickname_ = std::move(nickname); -} - -void server::set_ctcp_version(std::string ctcpversion) -{ - ctcpversion_ = std::move(ctcpversion); -} - -void server::connect() noexcept -{ - assert(state_ == state_t::disconnected || state_ == state_t::waiting); - /* - * This is needed if irccd is started before DHCP or if DNS cache is - * outdated. - */ -#if !defined(IRCCD_SYSTEM_WINDOWS) - (void)res_init(); -#endif - - if (flags_ & ssl) { -#if defined(HAVE_SSL) - conn_ = std::make_unique(service_); -#else - /* - * If SSL is not compiled in, the caller is responsible of not setting - * the flag. - */ - assert(!(flags_ & ssl)); -#endif - } else - conn_ = std::make_unique(service_); - - state_ = state_t::connecting; - conn_->connect(host_, std::to_string(port_), [this] (auto code) { - handle_connect(std::move(code)); - }); -} - -void server::disconnect() noexcept -{ - conn_ = nullptr; - state_ = state_t::disconnected; - on_die(); -} - -void server::reconnect() noexcept -{ - disconnect(); - connect(); -} - -bool server::is_self(const std::string& target) const noexcept -{ - return nickname_ == irc::user::parse(target).nick(); -} - -void server::invite(std::string target, std::string channel) -{ - assert(!target.empty()); - assert(!channel.empty()); - - send(string_util::sprintf("INVITE %s %s", target, channel)); -} - -void server::join(std::string channel, std::string password) -{ - auto it = std::find_if(rchannels_.begin(), rchannels_.end(), [&] (const auto& c) { - return c.name == channel; - }); - - if (it == rchannels_.end()) - rchannels_.push_back({ channel, password }); - else - *it = { channel, password }; - - if (state_ == state_t::connected) { - if (password.empty()) - send(string_util::sprintf("JOIN %s", channel)); - else - send(string_util::sprintf("JOIN %s :%s", channel, password)); - } -} - -void server::kick(std::string target, std::string channel, std::string reason) -{ - assert(!target.empty()); - assert(!channel.empty()); - - if (!reason.empty()) - send(string_util::sprintf("KICK %s %s :%s", channel, target, reason)); - else - send(string_util::sprintf("KICK %s %s", channel, target)); -} - -void server::me(std::string target, std::string message) -{ - assert(!target.empty()); - assert(!message.empty()); - - send(string_util::sprintf("PRIVMSG %s :\x01" "ACTION %s\x01", target, message)); -} - -void server::message(std::string target, std::string message) -{ - assert(!target.empty()); - assert(!message.empty()); - - send(string_util::sprintf("PRIVMSG %s :%s", target, message)); -} - -void server::mode(std::string channel, - std::string mode, - std::string limit, - std::string user, - std::string mask) -{ - assert(!channel.empty()); - assert(!mode.empty()); - - std::ostringstream oss; - - oss << "MODE " << channel << " " << mode; - - if (!limit.empty()) - oss << " " << limit; - if (!user.empty()) - oss << " " << user; - if (!mask.empty()) - oss << " " << mask; - - send(oss.str()); -} - -void server::names(std::string channel) -{ - assert(channel.c_str()); - - send(string_util::sprintf("NAMES %s", channel)); -} - -void server::notice(std::string target, std::string message) -{ - assert(!target.empty()); - assert(!message.empty()); - - send(string_util::sprintf("NOTICE %s :%s", target, message)); -} - -void server::part(std::string channel, std::string reason) -{ - assert(!channel.empty()); - - if (!reason.empty()) - send(string_util::sprintf("PART %s :%s", channel, reason)); - else - send(string_util::sprintf("PART %s", channel)); -} - -void server::send(std::string raw) -{ - assert(state_ == state_t::connected); - assert(!raw.empty()); - - conn_->send(std::move(raw), [this] (auto code) { - if (code) { - state_ = state_t::disconnected; - conn_ = nullptr; - } - }); -} - -void server::topic(std::string channel, std::string topic) -{ - assert(!channel.empty()); - - if (!topic.empty()) - send(string_util::sprintf("TOPIC %s :%s", channel, topic)); - else - send(string_util::sprintf("TOPIC %s", channel)); -} - -void server::whois(std::string target) -{ - assert(!target.empty()); - - send(string_util::sprintf("WHOIS %s %s", target, target)); -} - -server_error::server_error(error code, std::string name) noexcept - : system_error(make_error_code(code)) - , name_(std::move(name)) -{ -} - -const boost::system::error_category& server_category() -{ - static const class category : public boost::system::error_category { - public: - const char* name() const noexcept override - { - return "server"; - } - - std::string message(int e) const override - { - switch (static_cast(e)) { - case server_error::not_found: - return "server not found"; - case server_error::invalid_identifier: - return "invalid identifier"; - case server_error::not_connected: - return "server is not connected"; - case server_error::already_connected: - return "server is already connected"; - case server_error::invalid_port: - return "invalid port number specified"; - case server_error::invalid_reconnect_tries: - return "invalid number of reconnection tries"; - case server_error::invalid_reconnect_timeout: - return "invalid reconnect timeout number"; - case server_error::invalid_hostname: - return "invalid hostname"; - case server_error::invalid_channel: - return "invalid or empty channel"; - case server_error::invalid_mode: - return "invalid or empty mode"; - case server_error::invalid_nickname: - return "invalid nickname"; - case server_error::invalid_ping_timeout: - return "invalid ping timeout"; - case server_error::ssl_disabled: - return "ssl is not enabled"; - default: - return "no error"; - } - } - } category; - - return category; -} - -boost::system::error_code make_error_code(server_error::error e) -{ - return {static_cast(e), server_category()}; -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/server.hpp --- a/libirccd/irccd/server.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,997 +0,0 @@ -/* - * server.hpp -- an IRC server - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_SERVER_HPP -#define IRCCD_SERVER_HPP - -/** - * \file server.hpp - * \brief IRC Server. - */ - -#include "sysconfig.hpp" - -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "irc.hpp" - -namespace irccd { - -class server; - -/** - * \brief Prefixes for nicknames. - */ -enum class channel_mode { - creator = 'O', //!< Channel creator - half_op = 'h', //!< Half operator - op = 'o', //!< Channel operator - protection = 'a', //!< Unkillable - voiced = 'v' //!< Voice power -}; - -/** - * \brief A channel to join with an optional password. - */ -class channel { -public: - std::string name; //!< the channel to join - std::string password; //!< the optional password -}; - -/** - * \brief Describe a whois information. - */ -class whois { -public: - std::string nick; //!< user's nickname - std::string user; //!< user's user - std::string host; //!< hostname - std::string realname; //!< realname - std::vector channels; //!< the channels where the user is -}; - -/** - * \brief Connection success event. - */ -class connect_event { -public: - std::shared_ptr server; //!< The server. -}; - -/** - * \brief Invite event. - */ -class invite_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. - std::string nickname; //!< The nickname (you). -}; - -/** - * \brief Join event. - */ -class join_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. -}; - -/** - * \brief Kick event. - */ -class kick_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. - std::string target; //!< The target. - std::string reason; //!< The reason (Optional). -}; - -/** - * \brief Message event. - */ -class message_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. - std::string message; //!< The message. -}; - -/** - * \brief CTCP action event. - */ -class me_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. - std::string message; //!< The message. -}; - -/** - * \brief Mode event. - */ -class mode_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel or target. - std::string mode; //!< The mode. - std::string limit; //!< The optional limit. - std::string user; //!< The optional user. - std::string mask; //!< The optional ban mask. -}; - -/** - * \brief Names listing event. - */ -class names_event { -public: - std::shared_ptr server; //!< The server. - std::string channel; //!< The channel. - std::vector names; //!< The names. -}; - -/** - * \brief Nick change event. - */ -class nick_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string nickname; //!< The new nickname. -}; - -/** - * \brief Notice event. - */ -class notice_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel or target. - std::string message; //!< The message. -}; - -/** - * \brief Part event. - */ -class part_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. - std::string reason; //!< The reason. -}; - -/** - * \brief Query event. - */ -class query_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string message; //!< The message. -}; - -/** - * \brief Topic event. - */ -class topic_event { -public: - std::shared_ptr server; //!< The server. - std::string origin; //!< The originator. - std::string channel; //!< The channel. - std::string topic; //!< The topic message. -}; - -/** - * \brief Whois event. - */ -class whois_event { -public: - std::shared_ptr server; //!< The server. - class whois whois; //!< The whois information. -}; - -/** - * \brief The class that connect to a IRC server - * - * The server is a class that stores callbacks which will be called on IRC - * events. It is the lowest part of the connection to a server, it can be used - * directly by the user to connect to a server. - * - * The server has several signals that will be emitted when data has arrived. - * - * When adding a server to the ServerService in irccd, these signals are - * connected to generate events that will be dispatched to the plugins and to - * the transports. - * - * Note: the server is set in non blocking mode, commands are placed in a queue - * and sent when only when they are ready. - */ -class server : public std::enable_shared_from_this { -public: - /** - * \brief Various options for server. - */ - enum { - ipv6 = (1 << 0), //!< Connect using IPv6 - ssl = (1 << 1), //!< Use SSL - ssl_verify = (1 << 2), //!< Verify SSL - auto_rejoin = (1 << 3), //!< Auto rejoin a kick - join_invite = (1 << 4) //!< Join a channel on invitation - }; - - /** - * \brief Describe current server state. - */ - enum class state_t { - disconnected, //!< not connected at all, - connecting, //!< network connection in progress, - identifying, //!< sending nick, user and password commands, - waiting, //!< waiting for reconnection, - connected //!< ready for use - }; - - /** - * Signal: on_connect - * ---------------------------------------------------------- - * - * Triggered when the server is successfully connected. - */ - boost::signals2::signal on_connect; - - /** - * Signal: on_die - * ---------------------------------------------------------- - * - * The server is dead. - */ - boost::signals2::signal on_die; - - /** - * Signal: on_invite - * ---------------------------------------------------------- - * - * Triggered when an invite has been sent to you (the bot). - */ - boost::signals2::signal on_invite; - - /** - * Signal: on_join - * ---------------------------------------------------------- - * - * Triggered when a user has joined the channel, it also includes you. - */ - boost::signals2::signal on_join; - - /** - * Signal: on_kick - * ---------------------------------------------------------- - * - * Triggered when someone has been kicked from a channel. - */ - boost::signals2::signal on_kick; - - /** - * Signal: on_message - * ---------------------------------------------------------- - * - * Triggered when a message on a channel has been sent. - */ - boost::signals2::signal on_message; - - /** - * Signal: on_me - * ---------------------------------------------------------- - * - * Triggered on a CTCP Action. - * - * This is both used in a channel and in a private message so the target may - * be a channel or your nickname. - */ - boost::signals2::signal on_me; - - /** - * Signal: on_mode - * ---------------------------------------------------------- - * - * Triggered when the server changed your user mode. - */ - boost::signals2::signal on_mode; - - /** - * Signal: on_names - * ---------------------------------------------------------- - * - * Triggered when names listing has finished on a channel. - */ - boost::signals2::signal on_names; - - /** - * Signal: on_nick - * ---------------------------------------------------------- - * - * Triggered when someone changed its nickname, it also includes you. - */ - boost::signals2::signal on_nick; - - /** - * Signal: on_notice - * ---------------------------------------------------------- - * - * Triggered when someone has sent a notice to you. - */ - boost::signals2::signal on_notice; - - /** - * Signal: on_part - * ---------------------------------------------------------- - * - * Triggered when someone has left the channel. - */ - boost::signals2::signal on_part; - - /** - * Signal: on_query - * ---------------------------------------------------------- - * - * Triggered when someone has sent you a private message. - */ - boost::signals2::signal on_query; - - /** - * Signal: on_topic - * ---------------------------------------------------------- - * - * Triggered when someone changed the channel topic. - */ - boost::signals2::signal on_topic; - - /** - * Signal: on_whois - * ---------------------------------------------------------- - * - * Triggered when whois information has been received. - */ - boost::signals2::signal on_whois; - -private: - state_t state_{state_t::disconnected}; - - // Requested and joined channels. - std::vector rchannels_; - std::vector jchannels_; - - // Identifier. - std::string name_; - - // Connection information. - std::string host_; - std::string password_; - std::uint16_t port_{6667}; - std::uint8_t flags_{0}; - - // Identity. - std::string nickname_; - std::string username_; - std::string realname_{"IRC Client Daemon"}; - std::string ctcpversion_{"IRC Client Daemon"}; - - // Settings. - std::string command_char_{"!"}; - std::int8_t recotries_{-1}; - std::uint16_t recodelay_{30}; - std::uint16_t timeout_{1000}; - - // Server information. - std::map modes_; - - // Misc. - boost::asio::io_service& service_; - boost::asio::deadline_timer timer_; - std::unique_ptr conn_; - std::int8_t recocur_{0}; - std::map> names_map_; - std::map whois_map_; - - void remove_joined_channel(const std::string& channel); - - void dispatch_connect(const irc::message&); - void dispatch_endofnames(const irc::message&); - void dispatch_endofwhois(const irc::message&); - void dispatch_invite(const irc::message&); - void dispatch_isupport(const irc::message&); - void dispatch_join(const irc::message&); - void dispatch_kick(const irc::message&); - void dispatch_mode(const irc::message&); - void dispatch_namreply(const irc::message&); - void dispatch_nick(const irc::message&); - void dispatch_notice(const irc::message&); - void dispatch_part(const irc::message&); - void dispatch_ping(const irc::message&); - void dispatch_privmsg(const irc::message&); - void dispatch_topic(const irc::message&); - void dispatch_whoischannels(const irc::message&); - void dispatch_whoisuser(const irc::message&); - void dispatch(const irc::message&); - - void handle_recv(boost::system::error_code code, irc::message message); - void handle_connect(boost::system::error_code); - void recv(); - void identify(); - void wait(); - -public: - /** - * Split a channel from the form channel:password into a server_channel - * object. - * - * \param value the value - * \return a channel - */ - static channel split_channel(const std::string& value); - - /** - * Construct a server. - * - * \param service the service - * \param name the identifier - */ - server(boost::asio::io_service& service, std::string name); - - /** - * Destructor. Close the connection if needed. - */ - virtual ~server(); - - /** - * Get the current server state. - * - * \return the state - */ - inline state_t state() const noexcept - { - return state_; - } - - /** - * Get the server identifier. - * - * \return the id - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the hostname. - * - * \return the hostname - */ - inline const std::string& host() const noexcept - { - return host_; - } - - /** - * Set the hostname. - * - * \param host the hostname - */ - inline void set_host(std::string host) noexcept - { - host_ = std::move(host); - } - - /** - * Get the password. - * - * \return the password - */ - inline const std::string& password() const noexcept - { - return password_; - } - - /** - * Set the password. - * - * An empty password means no password. - * - * \param password the password - */ - inline void set_password(std::string password) noexcept - { - password_ = std::move(password); - } - - /** - * Get the port. - * - * \return the port - */ - inline std::uint16_t port() const noexcept - { - return port_; - } - - /** - * Set the port. - * - * \param port the port - */ - inline void set_port(std::uint16_t port) noexcept - { - port_ = port; - } - - /** - * Get the flags. - * - * \return the flags - */ - inline std::uint8_t flags() const noexcept - { - return flags_; - } - - /** - * Set the flags. - * - * \param flags the flags - */ - inline void set_flags(std::uint8_t flags) noexcept - { -#if !defined(HAVE_SSL) - assert(!(flags & ssl)); -#endif - - flags_ = flags; - } - - /** - * Get the nickname. - * - * \return the nickname - */ - inline const std::string& nickname() const noexcept - { - return nickname_; - } - - /** - * Set the nickname. - * - * If the server is connected, send a nickname command to the IRC server, - * otherwise change it locally. - * - * \param nickname the nickname - */ - virtual void set_nickname(std::string nickname); - - /** - * Get the username. - * - * \return the username - */ - inline const std::string& username() const noexcept - { - return username_; - } - - /** - * Set the username. - * - * \param name the username - * \note the username will be changed on the next connection - */ - inline void set_username(std::string name) noexcept - { - username_ = std::move(name); - } - - /** - * Get the realname. - * - * \return the realname - */ - inline const std::string& realname() const noexcept - { - return realname_; - } - - /** - * Set the realname. - * - * \param realname the username - * \note the username will be changed on the next connection - */ - inline void set_realname(std::string realname) noexcept - { - realname_ = std::move(realname); - } - - /** - * Get the CTCP version. - * - * \return the CTCP version - */ - inline const std::string& ctcp_version() const noexcept - { - return ctcpversion_; - } - - /** - * Set the CTCP version. - * - * \param ctcpversion the version - */ - void set_ctcp_version(std::string ctcpversion); - - /** - * Get the command character. - * - * \return the character - */ - inline const std::string& command_char() const noexcept - { - return command_char_; - } - - /** - * Set the command character. - * - * \pre !command_char_.empty() - * \param command_char the command character - */ - inline void set_command_char(std::string command_char) noexcept - { - assert(!command_char.empty()); - - command_char_ = std::move(command_char); - } - - /** - * Get the number of reconnections before giving up. - * - * \return the number of reconnections - */ - inline std::int8_t reconnect_tries() const noexcept - { - return recotries_; - } - - /** - * Set the number of reconnections to test before giving up. - * - * A value less than 0 means infinite. - * - * \param reconnect_tries the number of reconnections - */ - inline void set_reconnect_tries(std::int8_t reconnect_tries) noexcept - { - recotries_ = reconnect_tries; - } - - /** - * Get the reconnection delay before retrying. - * - * \return the number of seconds - */ - inline std::uint16_t reconnect_delay() const noexcept - { - return recodelay_; - } - - /** - * Set the number of seconds before retrying. - * - * \param reconnect_delay the number of seconds - */ - inline void set_reconnect_delay(std::uint16_t reconnect_delay) noexcept - { - recodelay_ = reconnect_delay; - } - - /** - * Get the ping timeout. - * - * \return the ping timeout - */ - inline std::uint16_t ping_timeout() const noexcept - { - return timeout_; - } - - /** - * Set the ping timeout before considering a server as dead. - * - * \param ping_timeout the delay in seconds - */ - inline void set_ping_timeout(std::uint16_t ping_timeout) noexcept - { - timeout_ = ping_timeout; - } - - /** - * Get the list of channels joined. - * - * \return the channels - */ - inline const std::vector& channels() const noexcept - { - return jchannels_; - } - - /** - * Determine if the nickname is the bot itself. - * - * \param nick the nickname to check - * \return true if it is the bot - */ - bool is_self(const std::string& nick) const noexcept; - - /** - * Start connecting. - */ - virtual void connect() noexcept; - - /** - * Force disconnection. - */ - virtual void disconnect() noexcept; - - /** - * Asks for a reconnection. - */ - virtual void reconnect() noexcept; - - /** - * Invite a user to a channel. - * - * \param target the target nickname - * \param channel the channel - */ - virtual void invite(std::string target, std::string channel); - - /** - * Join a channel, the password is optional and can be kept empty. - * - * \param channel the channel to join - * \param password the optional password - */ - virtual void join(std::string channel, std::string password = ""); - - /** - * Kick someone from the channel. Please be sure to have the rights - * on that channel because errors won't be reported. - * - * \param target the target to kick - * \param channel from which channel - * \param reason the optional reason - */ - virtual void kick(std::string target, std::string channel, std::string reason = ""); - - /** - * Send a CTCP Action as known as /me. The target may be either a - * channel or a nickname. - * - * \param target the nickname or the channel - * \param message the message - */ - virtual void me(std::string target, std::string message); - - /** - * Send a message to the specified target or channel. - * - * \param target the target - * \param message the message - */ - virtual void message(std::string target, std::string message); - - /** - * Change channel/user mode. - * - * \param channel the channel or nickname - * \param mode the mode - * \param limit the optional limit - * \param user the optional user - * \param mask the optional ban mask - */ - virtual void mode(std::string channel, - std::string mode, - std::string limit = "", - std::string user = "", - std::string mask = ""); - - /** - * Request the list of names. - * - * \param channel the channel - */ - virtual void names(std::string channel); - - /** - * Send a private notice. - * - * \param target the target - * \param message the notice message - */ - virtual void notice(std::string target, std::string message); - - /** - * Part from a channel. - * - * Please note that the reason is not supported on all servers so if you - * want portability, don't provide it. - * - * \param channel the channel to leave - * \param reason the optional reason - */ - virtual void part(std::string channel, std::string reason = ""); - - /** - * Send a raw message to the IRC server. You don't need to add - * message terminators. - * - * \pre state() == state_t::connected - * \param raw the raw message (without `\r\n\r\n`) - */ - virtual void send(std::string raw); - - /** - * Change the channel topic. - * - * \param channel the channel - * \param topic the desired topic - */ - virtual void topic(std::string channel, std::string topic); - - /** - * Request for whois information. - * - * \param target the target nickname - */ - virtual void whois(std::string target); -}; - -/** - * \brief Server error. - */ -class server_error : public boost::system::system_error { -public: - /** - * \brief Server related errors (1000..1999) - */ - enum error { - //!< No error. - no_error = 0, - - //!< The specified server was not found. - not_found = 1000, - - //!< The specified identifier is invalid. - invalid_identifier, - - //!< The server is not connected. - not_connected, - - //!< The server is already connected. - already_connected, - - //!< Server with same name already exists. - already_exists, - - //!< The specified port number is invalid. - invalid_port, - - //!< The specified reconnect tries number is invalid. - invalid_reconnect_tries, - - //!< The specified reconnect reconnect number is invalid. - invalid_reconnect_timeout, - - //!< The specified host was invalid. - invalid_hostname, - - //!< The channel was empty or invalid. - invalid_channel, - - //!< The mode given was empty. - invalid_mode, - - //!< The nickname was empty or invalid. - invalid_nickname, - - //!< Invalid ping timeout. - invalid_ping_timeout, - - //!< SSL was requested but is disabled. - ssl_disabled, - }; - -private: - std::string name_; - -public: - /** - * Constructor. - * - * \param code the error code - * \param name the server name - */ - server_error(error code, std::string name) noexcept; - - /** - * Get the server that triggered the error. - * - * \return the name - */ - inline const std::string& name() const noexcept - { - return name_; - } -}; - -/** - * Get the server error category singleton. - * - * \return the singleton - */ -const boost::system::error_category& server_category(); - -/** - * Create a boost::system::error_code from server_error::error enum. - * - * \param e the error code - */ -boost::system::error_code make_error_code(server_error::error e); - -} // !irccd - -namespace boost { - -namespace system { - -template <> -struct is_error_code_enum : public std::true_type { -}; - -} // !system - -} // !boost - -#endif // !IRCCD_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/server_service.cpp --- a/libirccd/irccd/server_service.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,721 +0,0 @@ -/* - * server_service.hpp -- server service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 - -#include "irccd.hpp" -#include "logger.hpp" -#include "plugin_service.hpp" -#include "rule_service.hpp" -#include "server_service.hpp" -#include "string_util.hpp" -#include "transport_service.hpp" - -namespace irccd { - -namespace { - -template -void dispatch(irccd& daemon, - const std::string& server, - const std::string& origin, - const std::string& target, - EventNameFunc&& name_func, - ExecFunc exec_func) -{ - for (auto& plugin : daemon.plugins().list()) { - auto eventname = name_func(*plugin); - auto allowed = daemon.rules().solve(server, target, origin, plugin->name(), eventname); - - if (!allowed) { - log::debug() << "rule: event skipped on match" << std::endl; - continue; - } - - log::debug() << "rule: event allowed" << std::endl; - - try { - exec_func(*plugin); - } catch (const std::exception& ex) { - log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl; - } - } -} - -template -T to_int(const std::string& value, const std::string& name, server_error::error errc) -{ - try { - return string_util::to_int(value); - } catch (...) { - throw server_error(errc, name); - } -} - -template -T to_uint(const std::string& value, const std::string& name, server_error::error errc) -{ - try { - return string_util::to_uint(value); - } catch (...) { - throw server_error(errc, name); - } -} - -template -T to_uint(const nlohmann::json& value, const std::string& name, server_error::error errc) -{ - if (!value.is_number()) - throw server_error(errc, name); - - auto n = value.get(); - - if (n > std::numeric_limits::max()) - throw server_error(errc, name); - - return static_cast(n); -} - -std::string to_id(const ini::section& sc) -{ - auto id = sc.get("name"); - - if (!string_util::is_identifier(id.value())) - throw server_error(server_error::invalid_identifier, ""); - - return id.value(); -} - -std::string to_id(const nlohmann::json& object) -{ - auto id = json_util::get_string(object, "name"); - - if (!string_util::is_identifier(id)) - throw server_error(server_error::invalid_identifier, ""); - - return id; -} - -std::string to_host(const ini::section& sc, const std::string& name) -{ - auto value = sc.get("host"); - - if (value.empty()) - throw server_error(server_error::invalid_hostname, name); - - return name; -} - -std::string to_host(const nlohmann::json& object, const std::string& name) -{ - auto value = json_util::get_string(object, "host"); - - if (value.empty()) - throw server_error(server_error::invalid_hostname, name); - - return value; -} - -void load_server_identity(std::shared_ptr& server, - const config& cfg, - const std::string& identity) -{ - auto sc = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& sc) { - if (sc.key() != "identity") - return false; - - auto name = sc.find("name"); - - return name != sc.end() && name->value() == identity; - }); - - if (sc == cfg.doc().end()) - return; - - ini::section::const_iterator it; - - if ((it = sc->find("username")) != sc->end()) - server->set_username(it->value()); - if ((it = sc->find("realname")) != sc->end()) - server->set_realname(it->value()); - if ((it = sc->find("nickname")) != sc->end()) - server->set_nickname(it->value()); - if ((it = sc->find("ctcp-version")) != sc->end()) - server->set_ctcp_version(it->value()); -} - -std::shared_ptr load_server(boost::asio::io_service& service, - const config& cfg, - const ini::section& sc) -{ - assert(sc.key() == "server"); - - auto sv = std::make_shared(service, to_id(sc)); - - // Mandatory fields. - sv->set_host(to_host(sc, sv->name())); - - // Optional fields. - ini::section::const_iterator it; - - if ((it = sc.find("password")) != sc.end()) - sv->set_password(it->value()); - - // Optional flags - if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value())) - sv->set_flags(sv->flags() | server::ipv6); - - if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) { -#if defined(HAVE_SSL) - sv->set_flags(sv->flags() | server::ssl); -#else - throw server_error(server_error::ssl_disabled, sv->name()); -#endif - } - - if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value())) - sv->set_flags(sv->flags() | server::ssl_verify); - - // Optional identity - if ((it = sc.find("identity")) != sc.end()) - load_server_identity(sv, cfg, it->value()); - - // Options - if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value())) - sv->set_flags(sv->flags() | server::auto_rejoin); - if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value())) - sv->set_flags(sv->flags() | server::join_invite); - - // Channels - if ((it = sc.find("channels")) != sc.end()) { - for (const auto& s : *it) { - channel channel; - - if (auto pos = s.find(":") != std::string::npos) { - channel.name = s.substr(0, pos); - channel.password = s.substr(pos + 1); - } else - channel.name = s; - - sv->join(channel.name, channel.password); - } - } - if ((it = sc.find("command-char")) != sc.end()) - sv->set_command_char(it->value()); - - // Reconnect and ping timeout - if ((it = sc.find("port")) != sc.end()) - sv->set_port(to_uint(it->value(), - sv->name(), server_error::invalid_port)); - - if ((it = sc.find("reconnect-tries")) != sc.end()) - sv->set_reconnect_tries(to_int(it->value(), - sv->name(), server_error::invalid_reconnect_tries)); - - if ((it = sc.find("reconnect-timeout")) != sc.end()) - sv->set_reconnect_delay(to_uint(it->value(), - sv->name(), server_error::invalid_reconnect_timeout)); - - if ((it = sc.find("ping-timeout")) != sc.end()) - sv->set_ping_timeout(to_uint(it->value(), - sv->name(), server_error::invalid_ping_timeout)); - - return sv; -} - -} // !namespace - -void server_service::handle_connect(const connect_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onConnect" }, - { "server", ev.server->name() } - })); - - dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "", - [=] (plugin&) -> std::string { - return "onConnect"; - }, - [=] (plugin& plugin) { - plugin.on_connect(irccd_, ev); - } - ); -} - -void server_service::handle_invite(const invite_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onInvite:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " target: " << ev.nickname << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onInvite" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onInvite"; - }, - [=] (plugin& plugin) { - plugin.on_invite(irccd_, ev); - } - ); -} - -void server_service::handle_join(const join_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onJoin:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onJoin" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onJoin"; - }, - [=] (plugin& plugin) { - plugin.on_join(irccd_, ev); - } - ); -} - -void server_service::handle_kick(const kick_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onKick:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " target: " << ev.target << "\n"; - log::debug() << " reason: " << ev.reason << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onKick" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "target", ev.target }, - { "reason", ev.reason } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onKick"; - }, - [=] (plugin& plugin) { - plugin.on_kick(irccd_, ev); - } - ); -} - -void server_service::handle_message(const message_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onMessage:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onMessage" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "message", ev.message } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin& plugin) -> std::string { - return string_util::parse_message( - ev.message, - ev.server->command_char(), - plugin.name() - ).type == string_util::message_pack::type::command ? "onCommand" : "onMessage"; - }, - [=] (plugin& plugin) mutable { - auto copy = ev; - auto pack = string_util::parse_message(copy.message, copy.server->command_char(), plugin.name()); - - copy.message = pack.message; - - if (pack.type == string_util::message_pack::type::command) - plugin.on_command(irccd_, copy); - else - plugin.on_message(irccd_, copy); - } - ); -} - -void server_service::handle_me(const me_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onMe:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " target: " << ev.channel << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onMe" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "target", ev.channel }, - { "message", ev.message } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onMe"; - }, - [=] (plugin& plugin) { - plugin.on_me(irccd_, ev); - } - ); -} - -void server_service::handle_mode(const mode_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onMode\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " mode: " << ev.mode << "\n"; - log::debug() << " limit: " << ev.limit << "\n"; - log::debug() << " user: " << ev.user << "\n"; - log::debug() << " mask: " << ev.mask << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onMode" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "mode", ev.mode }, - { "limit", ev.limit }, - { "user", ev.user }, - { "mask", ev.mask } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin &) -> std::string { - return "onMode"; - }, - [=] (plugin &plugin) { - plugin.on_mode(irccd_, ev); - } - ); -} - -void server_service::handle_names(const names_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onNames:\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " names: " << string_util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl; - - auto names = nlohmann::json::array(); - - for (const auto& v : ev.names) - names.push_back(v); - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onNames" }, - { "server", ev.server->name() }, - { "channel", ev.channel }, - { "names", std::move(names) } - })); - - dispatch(irccd_, ev.server->name(), /* origin */ "", ev.channel, - [=] (plugin&) -> std::string { - return "onNames"; - }, - [=] (plugin& plugin) { - plugin.on_names(irccd_, ev); - } - ); -} - -void server_service::handle_nick(const nick_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onNick:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " nickname: " << ev.nickname << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onNick" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "nickname", ev.nickname } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin&) -> std::string { - return "onNick"; - }, - [=] (plugin& plugin) { - plugin.on_nick(irccd_, ev); - } - ); -} - -void server_service::handle_notice(const notice_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onNotice:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " message: " << ev.message << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onNotice" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "message", ev.message } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, /* channel */ "", - [=] (plugin&) -> std::string { - return "onNotice"; - }, - [=] (plugin& plugin) { - plugin.on_notice(irccd_, ev); - } - ); -} - -void server_service::handle_part(const part_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onPart:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " reason: " << ev.reason << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onPart" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "reason", ev.reason } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onPart"; - }, - [=] (plugin& plugin) { - plugin.on_part(irccd_, ev); - } - ); -} - -void server_service::handle_topic(const topic_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onTopic:\n"; - log::debug() << " origin: " << ev.origin << "\n"; - log::debug() << " channel: " << ev.channel << "\n"; - log::debug() << " topic: " << ev.topic << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onTopic" }, - { "server", ev.server->name() }, - { "origin", ev.origin }, - { "channel", ev.channel }, - { "topic", ev.topic } - })); - - dispatch(irccd_, ev.server->name(), ev.origin, ev.channel, - [=] (plugin&) -> std::string { - return "onTopic"; - }, - [=] (plugin& plugin) { - plugin.on_topic(irccd_, ev); - } - ); -} - -void server_service::handle_whois(const whois_event& ev) -{ - log::debug() << "server " << ev.server->name() << ": event onWhois\n"; - log::debug() << " nickname: " << ev.whois.nick << "\n"; - log::debug() << " username: " << ev.whois.user << "\n"; - log::debug() << " host: " << ev.whois.host << "\n"; - log::debug() << " realname: " << ev.whois.realname << "\n"; - log::debug() << " channels: " << string_util::join(ev.whois.channels, ", ") << std::endl; - - irccd_.transports().broadcast(nlohmann::json::object({ - { "event", "onWhois" }, - { "server", ev.server->name() }, - { "nickname", ev.whois.nick }, - { "username", ev.whois.user }, - { "host", ev.whois.host }, - { "realname", ev.whois.realname } - })); - - dispatch(irccd_, ev.server->name(), /* origin */ "", /* channel */ "", - [=] (plugin&) -> std::string { - return "onWhois"; - }, - [=] (plugin& plugin) { - plugin.on_whois(irccd_, ev); - } - ); -} - -std::shared_ptr server_service::from_json(boost::asio::io_service& service, const nlohmann::json& object) -{ - // TODO: move this function in server_service. - auto sv = std::make_shared(service, to_id(object)); - - // Mandatory fields. - sv->set_host(to_host(object, sv->name())); - - // Optional fields. - if (object.count("port")) - sv->set_port(to_uint(object["port"], sv->name(), server_error::invalid_port)); - sv->set_password(json_util::get_string(object, "password")); - sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname())); - sv->set_realname(json_util::get_string(object, "realname", sv->realname())); - sv->set_username(json_util::get_string(object, "username", sv->username())); - sv->set_ctcp_version(json_util::get_string(object, "ctcpVersion", sv->ctcp_version())); - sv->set_command_char(json_util::get_string(object, "commandChar", sv->command_char())); - - if (json_util::get_bool(object, "ipv6")) - sv->set_flags(sv->flags() | server::ipv6); - if (json_util::get_bool(object, "sslVerify")) - sv->set_flags(sv->flags() | server::ssl_verify); - if (json_util::get_bool(object, "autoRejoin")) - sv->set_flags(sv->flags() | server::auto_rejoin); - if (json_util::get_bool(object, "joinInvite")) - sv->set_flags(sv->flags() | server::join_invite); - - if (json_util::get_bool(object, "ssl")) -#if defined(HAVE_SSL) - sv->set_flags(sv->flags() | server::ssl); -#else - throw server_error(server_error::ssl_disabled, sv->name()); -#endif - - return sv; -} - -server_service::server_service(irccd &irccd) - : irccd_(irccd) -{ -} - -bool server_service::has(const std::string& name) const noexcept -{ - return std::count_if(servers_.begin(), servers_.end(), [&] (const auto& server) { - return server->name() == name; - }) > 0; -} - -void server_service::add(std::shared_ptr server) -{ - assert(!has(server->name())); - - std::weak_ptr ptr(server); - - server->on_connect.connect(boost::bind(&server_service::handle_connect, this, _1)); - server->on_invite.connect(boost::bind(&server_service::handle_invite, this, _1)); - server->on_join.connect(boost::bind(&server_service::handle_join, this, _1)); - server->on_kick.connect(boost::bind(&server_service::handle_kick, this, _1)); - server->on_message.connect(boost::bind(&server_service::handle_message, this, _1)); - server->on_me.connect(boost::bind(&server_service::handle_me, this, _1)); - server->on_mode.connect(boost::bind(&server_service::handle_mode, this, _1)); - server->on_names.connect(boost::bind(&server_service::handle_names, this, _1)); - server->on_nick.connect(boost::bind(&server_service::handle_nick, this, _1)); - server->on_notice.connect(boost::bind(&server_service::handle_notice, this, _1)); - server->on_part.connect(boost::bind(&server_service::handle_part, this, _1)); - server->on_topic.connect(boost::bind(&server_service::handle_topic, this, _1)); - server->on_whois.connect(boost::bind(&server_service::handle_whois, this, _1)); - server->on_die.connect([this, ptr] () { - auto server = ptr.lock(); - - if (server) { - log::info(string_util::sprintf("server %s: removed", server->name())); - servers_.erase(std::find(servers_.begin(), servers_.end(), server)); - } - }); - - server->connect(); - servers_.push_back(std::move(server)); -} - -std::shared_ptr server_service::get(const std::string& name) const noexcept -{ - auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { - return server->name() == name; - }); - - if (it == servers_.end()) - return nullptr; - - return *it; -} - -std::shared_ptr server_service::require(const std::string& name) const -{ - auto server = get(name); - - if (!server) - throw std::invalid_argument(string_util::sprintf("server %s not found", name)); - - return server; -} - -void server_service::remove(const std::string& name) -{ - auto it = std::find_if(servers_.begin(), servers_.end(), [&] (const auto& server) { - return server->name() == name; - }); - - if (it != servers_.end()) { - (*it)->disconnect(); - servers_.erase(it); - } -} - -void server_service::clear() noexcept -{ - for (auto &server : servers_) - server->disconnect(); - - servers_.clear(); -} - -void server_service::load(const config& cfg) noexcept -{ - for (const auto& section : cfg.doc()) { - if (section.key() != "server") - continue; - - try { - add(load_server(irccd_.service(), cfg, section)); - } catch (const std::exception& ex) { - log::warning() << "server " << section.get("name").value() << ": " - << ex.what() << std::endl; - } - } -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/server_service.hpp --- a/libirccd/irccd/server_service.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/* - * server_service.hpp -- server service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_SERVER_SERVICE_HPP -#define IRCCD_SERVER_SERVICE_HPP - -/** - * \file server_service.hpp - * \brief Server service. - */ - -#include -#include - -#include "server.hpp" - -namespace irccd { - -class config; -class irccd; - -/** - * \brief Manage IRC servers. - * \ingroup services - */ -class server_service { -private: - irccd& irccd_; - std::vector> servers_; - - void handle_connect(const connect_event&); - void handle_invite(const invite_event&); - void handle_join(const join_event&); - void handle_kick(const kick_event&); - void handle_message(const message_event&); - void handle_me(const me_event&); - void handle_mode(const mode_event&); - void handle_names(const names_event&); - void handle_nick(const nick_event&); - void handle_notice(const notice_event&); - void handle_part(const part_event&); - void handle_query(const query_event&); - void handle_topic(const topic_event&); - void handle_whois(const whois_event&); - -public: - /** - * Convert a JSON object as a server. - * - * Used in JavaScript API and transport commands. - * - * \param service the io service - * \param object the object - * \return the server - * \throw std::exception on failures - */ - static std::shared_ptr from_json(boost::asio::io_service& service, const nlohmann::json& object); - - /** - * Create the server service. - */ - server_service(irccd& instance); - - /** - * Get the list of servers - * - * \return the servers - */ - inline const std::vector>& servers() const noexcept - { - return servers_; - } - - /** - * Check if a server exists. - * - * \param name the name - * \return true if exists - */ - bool has(const std::string& name) const noexcept; - - /** - * Add a new server to the application. - * - * \pre hasServer must return false - * \param sv the server - */ - void add(std::shared_ptr sv); - - /** - * Get a server or empty one if not found - * - * \param name the server name - * \return the server or empty one if not found - */ - std::shared_ptr get(const std::string& name) const noexcept; - - /** - * Find a server by name. - * - * \param name the server name - * \return the server - * \throw std::out_of_range if the server does not exist - */ - std::shared_ptr require(const std::string& name) const; - - /** - * Remove a server from the irccd instance. - * - * The server if any, will be disconnected. - * - * \param name the server name - */ - void remove(const std::string& name); - - /** - * Remove all servers. - * - * All servers will be disconnected. - */ - void clear() noexcept; - - /** - * Load servers from the configuration. - * - * \param cfg the config - */ - void load(const config& cfg) noexcept; -}; - -} // !irccd - -#endif // !IRCCD_SERVER_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/tls_transport_server.cpp --- a/libirccd/irccd/tls_transport_server.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -/* - * tls_transport_server.cpp -- server side transports (SSL support) - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "tls_transport_server.hpp" - -#if defined(HAVE_SSL) - -namespace irccd { - -void tls_transport_server::do_handshake(std::shared_ptr client, accept_t handler) -{ - client->stream().socket().async_handshake(boost::asio::ssl::stream_base::server, [client, handler] (auto code) { - handler(std::move(code), std::move(client)); - }); -} - -tls_transport_server::tls_transport_server(acceptor_t acceptor, context_t context) - : ip_transport_server(std::move(acceptor)) - , context_(std::move(context)) -{ -} - -void tls_transport_server::do_accept(accept_t handler) -{ - auto client = std::make_shared(*this, acceptor_.get_io_service(), context_); - - acceptor_.async_accept(client->stream().socket().lowest_layer(), [this, client, handler] (auto code) { - if (code) - handler(std::move(code), nullptr); - else - do_handshake(std::move(client), std::move(handler)); - }); -} - -} // !irccd - -#endif // !HAVE_SSL diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/tls_transport_server.hpp --- a/libirccd/irccd/tls_transport_server.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -/* - * tls_transport_server.hpp -- server side transports (SSL support) - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_TLS_TRANSPORT_SERVER_HPP -#define IRCCD_TLS_TRANSPORT_SERVER_HPP - -/** - * \file tls_transport_server.hpp - * \brief Server side transports (SSL support). - */ - -#include - -#if defined(HAVE_SSL) - -#include - -#include "ip_transport_server.hpp" - -namespace irccd { - -/** - * \brief Secure layer implementation. - */ -class tls_transport_server : public ip_transport_server { -private: - using context_t = boost::asio::ssl::context; - using client_t = basic_transport_client>; - - context_t context_; - - void do_handshake(std::shared_ptr, 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 // !HAVE_SSL - -#endif // !IRCCD_TLS_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/transport_client.cpp --- a/libirccd/irccd/transport_client.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -/* - * transport_client.cpp -- server side transport clients - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 - -#include "transport_client.hpp" -#include "transport_server.hpp" - -namespace irccd { - -void transport_client::recv(network_recv_handler handler) -{ - if (state_ != state_t::closing) - do_recv(std::move(handler)); -} - -void transport_client::send(nlohmann::json json, network_send_handler handler) -{ - if (state_ != state_t::closing) - do_send(std::move(json), std::move(handler)); -} - -void transport_client::success(const std::string& cname, network_send_handler handler) -{ - assert(!cname.empty()); - - send({{ "command", cname }}, std::move(handler)); -} - -void transport_client::error(boost::system::error_code code, network_send_handler handler) -{ - error(std::move(code), "", std::move(handler)); -} - -void transport_client::error(boost::system::error_code code, - std::string cname, - network_send_handler handler) -{ - assert(code); - - auto json = nlohmann::json::object({ - { "error", code.value() } - }); - - if (!cname.empty()) - json["command"] = std::move(cname); - - send(std::move(json), [this, handler] (auto code) { - if (handler) - handler(code); - - parent_.clients().erase(shared_from_this()); - }); - - state_ = state_t::closing; -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/transport_client.hpp --- a/libirccd/irccd/transport_client.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,182 +0,0 @@ -/* - * transport_client.hpp -- server side transport clients - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "network_stream.hpp" - -namespace irccd { - -class transport_server; - -/** - * \brief Abstract transport client class. - * - * This class is responsible of receiving/sending data. - */ -class transport_client : public std::enable_shared_from_this { -public: - /** - * Client state. - */ - enum class state_t { - authenticating, //!< client is authenticating - ready, //!< client is ready - closing //!< client is closing - }; - -private: - state_t state_{state_t::authenticating}; - transport_server& parent_; - -protected: - /** - * Request a receive operation. - * - * The implementation must call the handler once the operation has finished - * even in case of errors. - * - * \param handler the non-null handler - */ - virtual void do_recv(network_recv_handler handler) = 0; - - /** - * Request a send operation. - * - * The implementation must call the handler once the operation has finished - * even in case of errors. - * - * \param json the json message to send - * \param handler the non-null handler - */ - virtual void do_send(nlohmann::json json, network_send_handler handler) = 0; - -public: - /** - * Constructor. - * - * \param server the parent - */ - inline transport_client(transport_server& server) noexcept - : parent_(server) - { - } - - /** - * Virtual destructor defaulted. - */ - virtual ~transport_client() = 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 receiving if not closed. - * - * Possible error codes: - * - * - boost::system::errc::network_down in case of errors, - * - boost::system::errc::invalid_argument if the JSON message is invalid. - * - * \pre handler != nullptr - * \param handler the handler - */ - void recv(network_recv_handler handler); - - /** - * Start sending if not closed. - * - * Possible error codes: - * - * - boost::system::errc::network_down in case of errors, - * - * \param json the json message - * \param handler the optional handler - */ - void send(nlohmann::json json, network_send_handler handler = nullptr); - - /** - * Convenient success message. - * - * \param cname the command name - * \param handler the optional handler - */ - void success(const std::string& cname, network_send_handler handler = nullptr); - - /** - * Send an error code to the client. - * - * \pre code is not 0 - * \param code the error code - * \param handler the optional handler - */ - void error(boost::system::error_code code, network_send_handler handler = nullptr); - - /** - * Send an error code to the client. - * - * \pre code is not 0 - * \param code the error code - * \param handler the optional handler - */ - void error(boost::system::error_code code, - std::string cname, - network_send_handler handler = nullptr); -}; - -} // !irccd - -#endif // !IRCCD_TRANSPORT_CLIENT_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/transport_server.cpp --- a/libirccd/irccd/transport_server.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,103 +0,0 @@ -/* - * transport_server.cpp -- server side transports - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "sysconfig.hpp" - -#include - -#include "irccd.hpp" -#include "json_util.hpp" -#include "transport_server.hpp" - -namespace irccd { - -void transport_server::do_auth(std::shared_ptr client, accept_t handler) -{ - assert(client); - assert(handler); - - client->recv([this, client, handler] (auto code, auto message) { - if (code) { - handler(std::move(code), std::move(client)); - return; - } - - auto command = json_util::to_string(message["command"]); - auto password = json_util::to_string(message["password"]); - - if (command != "auth") { - client->error(irccd_error::auth_required); - code = irccd_error::auth_required; - } else if (password != password_) { - client->error(irccd_error::invalid_auth); - code = irccd_error::invalid_auth; - } else { - client->set_state(transport_client::state_t::ready); - client->success("auth"); - code = irccd_error::no_error; - } - - handler(std::move(code), std::move(client)); - }); -} - -void transport_server::do_greetings(std::shared_ptr client, accept_t handler) -{ - assert(client); - assert(handler); - - auto greetings = nlohmann::json({ - { "program", "irccd" }, - { "major", IRCCD_VERSION_MAJOR }, - { "minor", IRCCD_VERSION_MINOR }, - { "patch", IRCCD_VERSION_PATCH }, -#if defined(HAVE_JS) - { "javascript", true }, -#endif -#if defined(HAVE_SSL) - { "ssl", true }, -#endif - }); - - client->send(greetings, [this, client, handler] (auto code) { - if (code) - handler(std::move(code), std::move(client)); - else if (!password_.empty()) - do_auth(std::move(client), std::move(handler)); - else { - client->set_state(transport_client::state_t::ready); - handler(std::move(code), std::move(client)); - } - }); -} - -void transport_server::accept(accept_t handler) -{ - assert(handler); - - do_accept([this, handler] (auto code, auto client) { - if (code) - handler(std::move(code), nullptr); - else { - clients_.insert(client); - do_greetings(std::move(client), std::move(handler)); - } - }); -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/transport_server.hpp --- a/libirccd/irccd/transport_server.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -/* - * transport_server.hpp -- server side transports - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 "sysconfig.hpp" - -#include -#include -#include -#include -#include - -#include - -#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>; - - /** - * Callback when a new client should be accepted. - */ - using accept_t = std::function)>; - -private: - client_set_t clients_; - std::string password_; - - void do_auth(std::shared_ptr, accept_t); - void do_greetings(std::shared_ptr, 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); - } -}; - -} // !irccd - -#endif // !IRCCD_TRANSPORT_SERVER_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/transport_service.cpp --- a/libirccd/irccd/transport_service.cpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,287 +0,0 @@ -/* - * transport_service.cpp -- transport service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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 - -#include - -#include - -#include "command_service.hpp" -#include "irccd.hpp" -#include "ip_transport_server.hpp" -#include "logger.hpp" -#include "transport_client.hpp" -#include "transport_service.hpp" - -#if !defined(IRCCD_SYSTEM_WINDOWS) -# include "local_transport_server.hpp" -#endif - -#if defined(HAVE_SSL) -# include "tls_transport_server.hpp" -#endif - -namespace irccd { - -namespace { - -std::unique_ptr load_transport_ip(boost::asio::io_service& service, const ini::section& sc) -{ - assert(sc.key() == "transport"); - - std::unique_ptr transport; - ini::section::const_iterator it; - - // Port. - if ((it = sc.find("port")) == sc.cend()) - throw std::invalid_argument("missing 'port' parameter"); - - auto port = string_util::to_uint(it->value()); - - // Address. - std::string address = "*"; - - if ((it = sc.find("address")) != sc.end()) - address = it->value(); - - // 0011 - // ^ define IPv4 - // ^ define IPv6 - auto mode = 1U; - - /* - * Documentation stated family but code checked for 'domain' option. - * - * As irccdctl uses domain, accept both and unify the option name to 'family'. - * - * See #637 - */ - if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) { - mode = 0U; - - for (const auto& v : *it) { - if (v == "ipv4") - mode |= (1U << 0); - if (v == "ipv6") - mode |= (1U << 1); - } - } - - if (mode == 0U) - throw std::invalid_argument("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; - - if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) { - if ((it = sc.find("certificate")) == sc.end()) - throw std::invalid_argument("missing 'certificate' parameter"); - - cert = it->value(); - - if ((it = sc.find("key")) == sc.end()) - throw std::invalid_argument("missing 'key' parameter"); - - pkey = it->value(); - } - - 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()) { - log::info() << "transport: listening on " << port << std::endl; - return std::make_unique(std::move(acceptor)); - } - -#if defined(HAVE_SSL) - 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); - - log::info() << "transport: listening on " << port << " using SSL" << std::endl; - - return std::make_unique(std::move(acceptor), std::move(ctx)); -#else - throw std::invalid_argument("SSL disabled"); -#endif -} - -std::unique_ptr 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) - ini::section::const_iterator it = sc.find("path"); - - if (it == sc.end()) - throw std::invalid_argument("missing 'path' parameter"); - - // Remove the file first. - std::remove(it->value().c_str()); - - stream_protocol::endpoint endpoint(it->value()); - stream_protocol::acceptor acceptor(service, std::move(endpoint)); - - log::info() << "transport: listening on " << it->value() << std::endl; - - return std::make_unique(std::move(acceptor)); -#else - (void)sc; - - throw std::invalid_argument("unix transports not supported on on this platform"); -#endif -} - -std::unique_ptr load_transport(boost::asio::io_service& service, const ini::section& sc) -{ - assert(sc.key() == "transport"); - - std::unique_ptr transport; - ini::section::const_iterator it = sc.find("type"); - - if (it == sc.end()) - throw std::invalid_argument("missing 'type' parameter"); - - if (it->value() == "ip") - transport = load_transport_ip(service, sc); - else if (it->value() == "unix") - transport = load_transport_unix(service, sc); - else - throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value())); - - if ((it = sc.find("password")) != sc.end()) - transport->set_password(it->value()); - - return transport; -} - -} // !namespace - -void transport_service::handle_command(std::shared_ptr tc, const nlohmann::json& object) -{ - assert(object.is_object()); - - auto name = object.find("command"); - - if (name == object.end() || !name->is_string()) { - tc->error(irccd_error::invalid_message); - return; - } - - auto cmd = irccd_.commands().find(*name); - - if (!cmd) - tc->error(irccd_error::invalid_command, name->get()); - else { - try { - cmd->exec(irccd_, *tc, object); - } catch (const boost::system::system_error& ex) { - tc->error(ex.code(), cmd->name()); - } catch (const std::exception& ex) { - log::warning() << "transport: unknown error not reported" << std::endl; - log::warning() << "transport: " << ex.what() << std::endl; - } - } -} - -void transport_service::do_recv(std::shared_ptr tc) -{ - tc->recv([this, tc] (auto code, auto json) { - switch (code.value()) { - case boost::system::errc::network_down: - log::warning("transport: client disconnected"); - break; - case boost::system::errc::invalid_argument: - tc->error(irccd_error::invalid_message); - break; - default: - handle_command(tc, json); - - if (tc->state() == transport_client::state_t::ready) - do_recv(std::move(tc)); - - break; - } - }); -} - -void transport_service::do_accept(transport_server& ts) -{ - ts.accept([this, &ts] (auto code, auto client) { - if (code) - log::warning() << "transport: new client error: " << code.message() << std::endl; - else { - do_accept(ts); - do_recv(std::move(client)); - - log::info() << "transport: new client connected" << std::endl; - } - }); -} - -transport_service::transport_service(irccd& irccd) noexcept - : irccd_(irccd) -{ -} - -transport_service::~transport_service() noexcept = default; - -void transport_service::add(std::unique_ptr ts) -{ - assert(ts); - - do_accept(*ts); - servers_.push_back(std::move(ts)); -} - -void transport_service::broadcast(const nlohmann::json& json) -{ - assert(json.is_object()); - - for (const auto& servers : servers_) - for (const auto& client : servers->clients()) - client->send(json); -} - -void transport_service::load(const config& cfg) noexcept -{ - for (const auto& section : cfg.doc()) { - if (section.key() != "transport") - continue; - - try { - add(load_transport(irccd_.service(), section)); - } catch (const std::exception& ex) { - log::warning() << "transport: " << ex.what() << std::endl; - } - } -} - -} // !irccd diff -r 029667d16d12 -r 35832b7f4f9d libirccd/irccd/transport_service.hpp --- a/libirccd/irccd/transport_service.hpp Wed Dec 06 09:32:57 2017 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -/* - * transport_service.hpp -- transport service - * - * Copyright (c) 2013-2017 David Demelier - * - * 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_SERVICE_HPP -#define IRCCD_TRANSPORT_SERVICE_HPP - -#include -#include - -#include - -#include "transport_client.hpp" -#include "transport_server.hpp" - -namespace irccd { - -class config; - -/** - * \brief manage transport servers and clients. - * \ingroup services - */ -class transport_service { -public: - using servers_t = std::vector>; - -private: - irccd& irccd_; - servers_t servers_; - - void handle_command(std::shared_ptr, const nlohmann::json&); - void do_recv(std::shared_ptr); - void do_accept(transport_server&); - -public: - /** - * Create the transport service. - * - * \param irccd the irccd instance - */ - transport_service(irccd& irccd) noexcept; - - /** - * Default destructor. - */ - ~transport_service() noexcept; - - /** - * Add a transport server. - * - * \param ts the transport server - */ - void add(std::unique_ptr ts); - - /** - * Send data to all clients. - * - * \pre object.is_object() - * \param object the json object - */ - void broadcast(const nlohmann::json& object); - - /** - * Load transports from the configuration. - * - * \param cfg the config - */ - void load(const config& cfg) noexcept; -}; - -} // !irccd - -#endif // !IRCCD_TRANSPORT_SERVICE_HPP diff -r 029667d16d12 -r 35832b7f4f9d libirccdctl/irccd/ctl/controller.cpp --- a/libirccdctl/irccd/ctl/controller.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/libirccdctl/irccd/ctl/controller.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,10 +21,10 @@ #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include "controller.hpp" #include "connection.hpp" diff -r 029667d16d12 -r 35832b7f4f9d tests/src/irc/main.cpp --- a/tests/src/irc/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/irc/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "irc" #include -#include +#include namespace irccd { diff -r 029667d16d12 -r 35832b7f4f9d tests/src/js-plugin/main.cpp --- a/tests/src/js-plugin/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/js-plugin/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -20,8 +20,8 @@ #include #include -#include -#include +#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-ask/main.cpp --- a/tests/src/plugin-ask/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-ask/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "Ask plugin" #include -#include -#include +#include +#include #include "plugin_test.hpp" diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-auth/main.cpp --- a/tests/src/plugin-auth/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-auth/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "Auth plugin" #include -#include -#include +#include +#include #include "plugin_test.hpp" diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-config-command/main.cpp --- a/tests/src/plugin-config-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-config-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "plugin-config" #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-hangman/main.cpp --- a/tests/src/plugin-hangman/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-hangman/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -22,8 +22,8 @@ #define BOOST_TEST_MODULE "Hangman plugin" #include -#include -#include +#include +#include #include "plugin_test.hpp" diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-history/main.cpp --- a/tests/src/plugin-history/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-history/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #define BOOST_TEST_MODULE "History plugin" #include -#include -#include +#include +#include #include "plugin_test.hpp" diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-info-command/main.cpp --- a/tests/src/plugin-info-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-info-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "plugin-info" #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-list-command/main.cpp --- a/tests/src/plugin-list-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-list-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "plugin-list" #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-load-command/main.cpp --- a/tests/src/plugin-load-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-load-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "plugin-load" #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-logger/main.cpp --- a/tests/src/plugin-logger/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-logger/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -22,9 +22,10 @@ #define BOOST_TEST_MODULE "Logger plugin" #include -#include #include -#include + +#include +#include #include "plugin_test.hpp" diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-plugin/main.cpp --- a/tests/src/plugin-plugin/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-plugin/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,12 +19,13 @@ #define BOOST_TEST_MODULE "Plugin plugin" #include -#include #include -#include -#include #include +#include +#include +#include + #include "plugin_test.hpp" namespace irccd { diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-reload-command/main.cpp --- a/tests/src/plugin-reload-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-reload-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "plugin-reload" #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/plugin-unload-command/main.cpp --- a/tests/src/plugin-unload-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/plugin-unload-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,8 +19,8 @@ #define BOOST_TEST_MODULE "plugin-unload" #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rule-add-command/main.cpp --- a/tests/src/rule-add-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rule-add-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rule-edit-command/main.cpp --- a/tests/src/rule-edit-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rule-edit-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rule-info-command/main.cpp --- a/tests/src/rule-info-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rule-info-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rule-list-command/main.cpp --- a/tests/src/rule-list-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rule-list-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rule-move-command/main.cpp --- a/tests/src/rule-move-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rule-move-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rule-remove-command/main.cpp --- a/tests/src/rule-remove-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rule-remove-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -21,8 +21,8 @@ #include -#include -#include +#include +#include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/rules/main.cpp --- a/tests/src/rules/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/rules/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -20,7 +20,7 @@ #include #include -#include +#include namespace irccd { diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-connect-command/main.cpp --- a/tests/src/server-connect-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-connect-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-connect" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-disconnect-command/main.cpp --- a/tests/src/server-disconnect-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-disconnect-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-disconnect" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-info-command/main.cpp --- a/tests/src/server-info-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-info-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-info" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-invite-command/main.cpp --- a/tests/src/server-invite-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-invite-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-invite" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-join-command/main.cpp --- a/tests/src/server-join-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-join-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-join" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-kick-command/main.cpp --- a/tests/src/server-kick-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-kick-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-kick" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-list-command/main.cpp --- a/tests/src/server-list-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-list-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-list" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-me-command/main.cpp --- a/tests/src/server-me-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-me-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-me" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-message-command/main.cpp --- a/tests/src/server-message-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-message-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-message" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-mode-command/main.cpp --- a/tests/src/server-mode-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-mode-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-mode" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-nick-command/main.cpp --- a/tests/src/server-nick-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-nick-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-nick" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-notice-command/main.cpp --- a/tests/src/server-notice-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-notice-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-notice" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-part-command/main.cpp --- a/tests/src/server-part-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-part-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-part" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-reconnect-command/main.cpp --- a/tests/src/server-reconnect-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-reconnect-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-reconnect" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/server-topic-command/main.cpp --- a/tests/src/server-topic-command/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/server-topic-command/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,7 +19,7 @@ #define BOOST_TEST_MODULE "server-topic" #include -#include +#include #include #include diff -r 029667d16d12 -r 35832b7f4f9d tests/src/system-jsapi/main.cpp --- a/tests/src/system-jsapi/main.cpp Wed Dec 06 09:32:57 2017 +0100 +++ b/tests/src/system-jsapi/main.cpp Wed Dec 06 11:42:44 2017 +0100 @@ -19,9 +19,10 @@ #define BOOST_TEST_MODULE "System Javascript API" #include -#include #include +#include + #include #include