changeset 596:35832b7f4f9d

Irccd: move files to daemon folder, closes #731
author David Demelier <markand@malikania.fr>
date Wed, 06 Dec 2017 11:42:44 +0100
parents 029667d16d12
children 3763a7e63776
files irccd/main.cpp libirccd-js/irccd/js/plugin_jsapi.cpp libirccd-js/irccd/js/server_jsapi.cpp libirccd-js/irccd/js/timer_jsapi.cpp libirccd-test/irccd/command_test.hpp libirccd-test/irccd/js_test.hpp libirccd-test/irccd/plugin_test.cpp libirccd/CMakeLists.txt libirccd/irccd/basic_transport_client.hpp libirccd/irccd/basic_transport_server.hpp libirccd/irccd/command.cpp libirccd/irccd/command.hpp libirccd/irccd/command_service.cpp libirccd/irccd/command_service.hpp libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/daemon/basic_transport_client.hpp libirccd/irccd/daemon/basic_transport_server.hpp libirccd/irccd/daemon/command.cpp libirccd/irccd/daemon/command.hpp libirccd/irccd/daemon/command_service.cpp libirccd/irccd/daemon/command_service.hpp libirccd/irccd/daemon/config.cpp libirccd/irccd/daemon/config.hpp libirccd/irccd/daemon/dynlib_plugin.cpp libirccd/irccd/daemon/dynlib_plugin.hpp libirccd/irccd/daemon/ip_transport_server.hpp libirccd/irccd/daemon/irc.cpp libirccd/irccd/daemon/irc.hpp libirccd/irccd/daemon/irccd.cpp libirccd/irccd/daemon/irccd.hpp libirccd/irccd/daemon/local_transport_server.hpp libirccd/irccd/daemon/plugin.cpp libirccd/irccd/daemon/plugin.hpp libirccd/irccd/daemon/plugin_service.cpp libirccd/irccd/daemon/plugin_service.hpp libirccd/irccd/daemon/rule.cpp libirccd/irccd/daemon/rule.hpp libirccd/irccd/daemon/rule_service.cpp libirccd/irccd/daemon/rule_service.hpp libirccd/irccd/daemon/server.cpp libirccd/irccd/daemon/server.hpp libirccd/irccd/daemon/server_service.cpp libirccd/irccd/daemon/server_service.hpp libirccd/irccd/daemon/tls_transport_server.cpp libirccd/irccd/daemon/tls_transport_server.hpp libirccd/irccd/daemon/transport_client.cpp libirccd/irccd/daemon/transport_client.hpp libirccd/irccd/daemon/transport_server.cpp libirccd/irccd/daemon/transport_server.hpp libirccd/irccd/daemon/transport_service.cpp libirccd/irccd/daemon/transport_service.hpp libirccd/irccd/dynlib_plugin.cpp libirccd/irccd/dynlib_plugin.hpp libirccd/irccd/ip_transport_server.hpp libirccd/irccd/irc.cpp libirccd/irccd/irc.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/local_transport_server.hpp libirccd/irccd/plugin.cpp libirccd/irccd/plugin.hpp libirccd/irccd/plugin_service.cpp libirccd/irccd/plugin_service.hpp libirccd/irccd/rule.cpp libirccd/irccd/rule.hpp libirccd/irccd/rule_service.cpp libirccd/irccd/rule_service.hpp libirccd/irccd/server.cpp libirccd/irccd/server.hpp libirccd/irccd/server_service.cpp libirccd/irccd/server_service.hpp libirccd/irccd/tls_transport_server.cpp libirccd/irccd/tls_transport_server.hpp libirccd/irccd/transport_client.cpp libirccd/irccd/transport_client.hpp libirccd/irccd/transport_server.cpp libirccd/irccd/transport_server.hpp libirccd/irccd/transport_service.cpp libirccd/irccd/transport_service.hpp libirccdctl/irccd/ctl/controller.cpp tests/src/irc/main.cpp tests/src/js-plugin/main.cpp tests/src/plugin-ask/main.cpp tests/src/plugin-auth/main.cpp tests/src/plugin-config-command/main.cpp tests/src/plugin-hangman/main.cpp tests/src/plugin-history/main.cpp tests/src/plugin-info-command/main.cpp tests/src/plugin-list-command/main.cpp tests/src/plugin-load-command/main.cpp tests/src/plugin-logger/main.cpp tests/src/plugin-plugin/main.cpp tests/src/plugin-reload-command/main.cpp tests/src/plugin-unload-command/main.cpp tests/src/rule-add-command/main.cpp tests/src/rule-edit-command/main.cpp tests/src/rule-info-command/main.cpp tests/src/rule-list-command/main.cpp tests/src/rule-move-command/main.cpp tests/src/rule-remove-command/main.cpp tests/src/rules/main.cpp tests/src/server-connect-command/main.cpp tests/src/server-disconnect-command/main.cpp tests/src/server-info-command/main.cpp tests/src/server-invite-command/main.cpp tests/src/server-join-command/main.cpp tests/src/server-kick-command/main.cpp tests/src/server-list-command/main.cpp tests/src/server-me-command/main.cpp tests/src/server-message-command/main.cpp tests/src/server-mode-command/main.cpp tests/src/server-nick-command/main.cpp tests/src/server-notice-command/main.cpp tests/src/server-part-command/main.cpp tests/src/server-reconnect-command/main.cpp tests/src/server-topic-command/main.cpp tests/src/system-jsapi/main.cpp
diffstat 118 files changed, 9213 insertions(+), 9207 deletions(-) [+]
line wrap: on
line diff
--- 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 <csignal>
 #include <iostream>
 
-#include <irccd/command_service.hpp>
-#include <irccd/config.hpp>
-#include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
 #include <irccd/options.hpp>
-#include <irccd/plugin_service.hpp>
-#include <irccd/rule_service.hpp>
-#include <irccd/server_service.hpp>
 #include <irccd/string_util.hpp>
 #include <irccd/system.hpp>
-#include <irccd/transport_service.hpp>
+
+#include <irccd/daemon/command_service.hpp>
+#include <irccd/daemon/config.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
+#include <irccd/daemon/server_service.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #if defined(HAVE_JS)
 #   include <irccd/js/directory_jsapi.hpp>
--- 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 <irccd/irccd.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
--- 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 <sstream>
 #include <unordered_map>
 
-#include <irccd.hpp>
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include "duktape_vector.hpp"
 #include "irccd_jsapi.hpp"
--- 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 <boost/asio.hpp>
 
-#include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
 
+#include <irccd/daemon/irccd.hpp>
+
 #include "irccd_jsapi.hpp"
 #include "js_plugin.hpp"
 #include "plugin_jsapi.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 <irccd/logger.hpp>
 
-#include <irccd/command_service.hpp>
-#include <irccd/ip_transport_server.hpp>
-#include <irccd/irccd.hpp>
-#include <irccd/transport_service.hpp>
+#include <irccd/daemon/command_service.hpp>
+#include <irccd/daemon/ip_transport_server.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/transport_service.hpp>
 
 #include <irccd/ctl/ip_connection.hpp>
 #include <irccd/ctl/controller.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 <irccd/logger.hpp>
 
-#include <irccd/irccd.hpp>
+#include <irccd/daemon/irccd.hpp>
 
 #include <irccd/js/js_plugin.hpp>
 #include <irccd/js/irccd_jsapi.hpp>
--- 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 <cassert>
 
 #include <irccd/logger.hpp>
-#include <irccd/plugin_service.hpp>
-#include <irccd/server_service.hpp>
+
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <irccd/js/directory_jsapi.hpp>
 #include <irccd/js/elapsed_timer_jsapi.hpp>
--- 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
-    $<$<BOOL:${HAVE_SSL}>:${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
+    $<$<BOOL:${HAVE_SSL}>:${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
-    $<$<BOOL:${HAVE_SSL}>:${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
+    $<$<BOOL:${HAVE_SSL}>:${libirccd_SOURCE_DIR}/irccd/daemon/tls_transport_server.cpp>
 )
 
 irccd_define_library(
@@ -76,7 +76,7 @@
         $<$<BOOL:${IRCCD_SYSTEM_MAC}>:resolv>
         libcommon
     PUBLIC_INCLUDES
-        $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}/irccd>
+        $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}/irccd/daemon>
         $<BUILD_INTERFACE:${libirccd_SOURCE_DIR}>
         $<INSTALL_INTERFACE:${WITH_INCLUDEDIR}>
 )
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_BASIC_TRANSPORT_CLIENT_HPP
-#define IRCCD_BASIC_TRANSPORT_CLIENT_HPP
-
-#include <irccd/network_stream.hpp>
-
-#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 <typename Socket>
-class basic_transport_client : public transport_client {
-private:
-    network_stream<Socket> stream_;
-
-public:
-    /**
-     * Construct the client.
-     *
-     * \param parent the parent
-     * \param args the argument to pass to the network_stream
-     */
-    template <typename... Args>
-    inline basic_transport_client(transport_server& parent, Args&&... args)
-        : transport_client(parent)
-        , stream_(std::forward<Args>(args)...)
-    {
-    }
-
-    /**
-     * Get the underlying stream.
-     *
-     * \return the stream
-     */
-    inline const network_stream<Socket>& stream() const noexcept
-    {
-        return stream_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the stream
-     */
-    inline network_stream<Socket>& 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_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 <typename Protocol>
-class basic_transport_server : public transport_server {
-public:
-    /**
-     * Type for underlying socket.
-     */
-    using socket_t = typename Protocol::socket;
-
-    /**
-     * Type for underlying acceptor.
-     */
-    using acceptor_t = typename Protocol::acceptor;
-
-protected:
-    /**
-     * The acceptor object.
-     */
-    acceptor_t acceptor_;
-
-protected:
-    /**
-     * \copydoc transport_server::accept
-     */
-    void do_accept(accept_t handler) override;
-
-public:
-    /**
-     * Constructor with an acceptor in parameter.
-     *
-     * \pre acceptor.is_open()
-     * \param acceptor the already bound acceptor
-     */
-    basic_transport_server(acceptor_t acceptor);
-};
-
-template <typename Protocol>
-basic_transport_server<Protocol>::basic_transport_server(acceptor_t acceptor)
-    : acceptor_(std::move(acceptor))
-{
-    assert(acceptor_.is_open());
-}
-
-template <typename Protocol>
-void basic_transport_server<Protocol>::do_accept(accept_t handler)
-{
-    auto client = std::make_shared<basic_transport_client<socket_t>>(*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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "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<std::string>()] = 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<std::string>());
-
-        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<std::string>();
-        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<int>() < 0)
-        throw rule_error(rule_error::invalid_index);
-
-    return index->get<int>();
-}
-
-std::shared_ptr<server> 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<std::string>();
-        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<std::string>()))
-            throw server_error(server_error::invalid_identifier, "");
-
-        auto name = server->get<std::string>();
-        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<std::string>());
-        }
-        for (const auto& v : args["add-"s + key]) {
-            if (v.is_string())
-                set.insert(v.template get<std::string>());
-        }
-    };
-
-    // 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<std::string>() == "accept")
-            rule.set_action(rule::action_type::accept);
-        else if (action->get<std::string>() == "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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMAND_HPP
-#define IRCCD_COMMAND_HPP
-
-/**
- * \file command.hpp
- * \brief Remote commands.
- */
-
-#include <cassert>
-#include <map>
-#include <vector>
-
-#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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "command_service.hpp"
-
-namespace irccd {
-
-bool command_service::contains(const std::string& name) const noexcept
-{
-    return find(name) != nullptr;
-}
-
-std::shared_ptr<command> 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> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_COMMAND_SERVICE_HPP
-#define IRCCD_COMMAND_SERVICE_HPP
-
-/**
- * \file command_service.hpp
- * \brief Command service.
- */
-
-#include <memory>
-#include <vector>
-
-#include "command.hpp"
-
-namespace irccd {
-
-/**
- * \brief Store remote commands.
- * \ingroup services
- */
-class command_service {
-private:
-    std::vector<std::shared_ptr<command>> commands_;
-
-public:
-    /**
-     * Get all commands.
-     *
-     * \return the list of commands.
-     */
-    inline const std::vector<std::shared_ptr<command>>& 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<command> 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> command);
-};
-
-} // !irccd
-
-#endif // !IRCCD_COMMAND_SERVICE_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <stdexcept>
-
-#include <boost/filesystem.hpp>
-
-#include <irccd/system.hpp>
-
-#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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_BASIC_TRANSPORT_CLIENT_HPP
+#define IRCCD_BASIC_TRANSPORT_CLIENT_HPP
+
+#include <irccd/network_stream.hpp>
+
+#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 <typename Socket>
+class basic_transport_client : public transport_client {
+private:
+    network_stream<Socket> stream_;
+
+public:
+    /**
+     * Construct the client.
+     *
+     * \param parent the parent
+     * \param args the argument to pass to the network_stream
+     */
+    template <typename... Args>
+    inline basic_transport_client(transport_server& parent, Args&&... args)
+        : transport_client(parent)
+        , stream_(std::forward<Args>(args)...)
+    {
+    }
+
+    /**
+     * Get the underlying stream.
+     *
+     * \return the stream
+     */
+    inline const network_stream<Socket>& stream() const noexcept
+    {
+        return stream_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the stream
+     */
+    inline network_stream<Socket>& 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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 <typename Protocol>
+class basic_transport_server : public transport_server {
+public:
+    /**
+     * Type for underlying socket.
+     */
+    using socket_t = typename Protocol::socket;
+
+    /**
+     * Type for underlying acceptor.
+     */
+    using acceptor_t = typename Protocol::acceptor;
+
+protected:
+    /**
+     * The acceptor object.
+     */
+    acceptor_t acceptor_;
+
+protected:
+    /**
+     * \copydoc transport_server::accept
+     */
+    void do_accept(accept_t handler) override;
+
+public:
+    /**
+     * Constructor with an acceptor in parameter.
+     *
+     * \pre acceptor.is_open()
+     * \param acceptor the already bound acceptor
+     */
+    basic_transport_server(acceptor_t acceptor);
+};
+
+template <typename Protocol>
+basic_transport_server<Protocol>::basic_transport_server(acceptor_t acceptor)
+    : acceptor_(std::move(acceptor))
+{
+    assert(acceptor_.is_open());
+}
+
+template <typename Protocol>
+void basic_transport_server<Protocol>::do_accept(accept_t handler)
+{
+    auto client = std::make_shared<basic_transport_client<socket_t>>(*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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "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<std::string>()] = 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<std::string>());
+
+        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<std::string>();
+        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<int>() < 0)
+        throw rule_error(rule_error::invalid_index);
+
+    return index->get<int>();
+}
+
+std::shared_ptr<server> 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<std::string>();
+        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<std::string>()))
+            throw server_error(server_error::invalid_identifier, "");
+
+        auto name = server->get<std::string>();
+        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<std::string>());
+        }
+        for (const auto& v : args["add-"s + key]) {
+            if (v.is_string())
+                set.insert(v.template get<std::string>());
+        }
+    };
+
+    // 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<std::string>() == "accept")
+            rule.set_action(rule::action_type::accept);
+        else if (action->get<std::string>() == "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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMAND_HPP
+#define IRCCD_COMMAND_HPP
+
+/**
+ * \file command.hpp
+ * \brief Remote commands.
+ */
+
+#include <cassert>
+#include <map>
+#include <vector>
+
+#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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "command_service.hpp"
+
+namespace irccd {
+
+bool command_service::contains(const std::string& name) const noexcept
+{
+    return find(name) != nullptr;
+}
+
+std::shared_ptr<command> 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> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_COMMAND_SERVICE_HPP
+#define IRCCD_COMMAND_SERVICE_HPP
+
+/**
+ * \file command_service.hpp
+ * \brief Command service.
+ */
+
+#include <memory>
+#include <vector>
+
+#include "command.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Store remote commands.
+ * \ingroup services
+ */
+class command_service {
+private:
+    std::vector<std::shared_ptr<command>> commands_;
+
+public:
+    /**
+     * Get all commands.
+     *
+     * \return the list of commands.
+     */
+    inline const std::vector<std::shared_ptr<command>>& 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<command> 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> command);
+};
+
+} // !irccd
+
+#endif // !IRCCD_COMMAND_SERVICE_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdexcept>
+
+#include <boost/filesystem.hpp>
+
+#include <irccd/system.hpp>
+
+#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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cctype>
+#include <algorithm>
+
+#include <boost/filesystem.hpp>
+
+#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<plugin>(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<load_t>(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<std::string> directories) noexcept
+    : plugin_loader(std::move(directories), { DYNLIB_EXTENSION })
+{
+}
+
+std::shared_ptr<plugin> dynlib_plugin_loader::open(const std::string& id,
+                                                   const std::string& path) noexcept
+{
+    return std::make_unique<dynlib_plugin>(id, path);
+}
+
+} // !irccd
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_PLUGIN_DYNLIB_HPP
+#define IRCCD_PLUGIN_DYNLIB_HPP
+
+/**
+ * \file plugin-dynlib.hpp
+ * \brief Native plugin implementation.
+ */
+
+#include <boost/dll.hpp>
+
+#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> 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<std::string> directories = {}) noexcept;
+
+    /**
+     * \copydoc plugin_loader::find
+     */
+    std::shared_ptr<plugin> open(const std::string& id,
+                                 const std::string& path) noexcept override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_DYNLIB_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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_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<boost::asio::ip::tcp>;
+
+} // !irccd
+
+#endif // !IRCCD_IP_TRANSPORT_SERVER_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <iterator>
+#include <sstream>
+
+#include "irc.hpp"
+
+namespace irccd {
+
+namespace irc {
+
+namespace {
+
+using boost::asio::ip::tcp;
+
+template <typename Socket>
+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 <typename Socket>
+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 <typename Socket>
+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 <typename Socket>
+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<std::string> args;
+    std::istreambuf_iterator<char> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_IRC_HPP
+#define IRCCD_IRC_HPP
+
+/**
+ * \file irc.hpp
+ * \brief Low level IRC functions.
+ */
+
+#include "sysconfig.hpp"
+
+#include <deque>
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/asio.hpp>
+
+#if defined(HAVE_SSL)
+#   include <boost/asio/ssl.hpp>
+#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<std::string> 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<std::string> 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<std::string>& 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 <typename Enum>
+    inline bool is(Enum e) const noexcept
+    {
+        try {
+            return std::stoi(command_) == static_cast<int>(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<void (boost::system::error_code)>;
+
+    /**
+     * Handler for receiving.
+     */
+    using recv_t = std::function<void (boost::system::error_code, message)>;
+
+    /**
+     * Handler for sending.
+     */
+    using send_t = std::function<void (boost::system::error_code)>;
+
+private:
+    using buffer_t = boost::asio::streambuf;
+    using input_t = std::deque<recv_t>;
+    using output_t = std::deque<std::pair<std::string, send_t>>;
+
+    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<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 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/logger.hpp>
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+
+#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<log::file_logger>(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<log::syslog_logger>());
+#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<log_filter>(
+        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<command_service>())
+    , server_service_(std::make_unique<server_service>(*this))
+    , tpt_service_(std::make_unique<transport_service>(*this))
+    , rule_service_(std::make_unique<rule_service>())
+    , plugin_service_(std::make_unique<plugin_service>(*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<irccd_error::error>(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<int>(e), irccd_category()};
+}
+
+} // !irccd
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_HPP
+#define IRCCD_HPP
+
+/**
+ * \file irccd.hpp
+ * \brief Base class for irccd front end.
+ */
+
+#include "sysconfig.hpp"
+
+#include <memory>
+
+#include <boost/asio.hpp>
+
+#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> command_service_;
+    std::shared_ptr<server_service> server_service_;
+    std::shared_ptr<transport_service> tpt_service_;
+    std::shared_ptr<rule_service> rule_service_;
+    std::shared_ptr<plugin_service> 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<irccd::irccd_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
+#endif // !IRCCD_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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_LOCAL_TRANSPORT_SERVER_HPP
+#define IRCCD_LOCAL_TRANSPORT_SERVER_HPP
+
+/**
+ * \file local_transport_server.hpp
+ * \brief Server side transports (Unix support).
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#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<boost::asio::local::stream_protocol>;
+
+#endif // !_WIN32
+
+} // !irccd
+
+#endif // !IRCCD_LOCAL_TRANSPORT_SERVER_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <boost/filesystem.hpp>
+
+#include <sstream>
+
+#include "plugin.hpp"
+#include "system.hpp"
+
+namespace fs = boost::filesystem;
+
+namespace irccd {
+
+std::shared_ptr<plugin> plugin_loader::find(const std::string& name)
+{
+    std::vector<std::string> 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<plugin_error::error>(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<int>(e), plugin_category()};
+}
+
+} // !irccd
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_PLUGIN_HPP
+#define IRCCD_PLUGIN_HPP
+
+/**
+ * \file plugin.hpp
+ * \brief irccd plugins
+ */
+
+/**
+ * \defgroup plugins Plugins
+ * \brief Plugin management.
+ */
+
+#include "sysconfig.hpp"
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "server.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+class irccd;
+
+/**
+ * \brief Configuration map extract from config file.
+ */
+using plugin_config = std::unordered_map<std::string, std::string>;
+
+/**
+ * \brief Formats for plugins.
+ */
+using plugin_formats = std::unordered_map<std::string, std::string>;
+
+/**
+ * \brief Paths for plugins.
+ */
+using plugin_paths = std::unordered_map<std::string, std::string>;
+
+/**
+ * \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<plugin> {
+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<std::string> directories_;
+    std::vector<std::string> 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<std::string> directories,
+                  std::vector<std::string> 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<std::string> 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<std::string> 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<plugin> 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<plugin> 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<irccd::plugin_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
+#endif // !IRCCD_PLUGIN_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/string_util.hpp>
+#include <irccd/logger.hpp>
+
+#include "config.hpp"
+#include "irccd.hpp"
+#include "plugin_service.hpp"
+#include "string_util.hpp"
+#include "system.hpp"
+
+namespace irccd {
+
+namespace {
+
+template <typename Map>
+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> 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> 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> plugin)
+{
+    plugins_.push_back(std::move(plugin));
+}
+
+void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
+{
+    loaders_.push_back(std::move(loader));
+}
+
+plugin_config plugin_service::config(const std::string& id)
+{
+    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
+}
+
+plugin_formats plugin_service::formats(const std::string& id)
+{
+    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
+}
+
+plugin_paths plugin_service::paths(const std::string& id)
+{
+    auto defaults = to_map<plugin_paths>(irccd_.config(), "paths");
+    auto paths = to_map<plugin_paths>(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> 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> 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> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_PLUGIN_SERVICE_HPP
+#define IRCCD_PLUGIN_SERVICE_HPP
+
+/**
+ * \file plugin_service.hpp
+ * \brief Plugin service.
+ */
+
+#include <cassert>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "plugin.hpp"
+
+namespace irccd {
+
+class irccd;
+class config;
+
+/**
+ * \brief Manage plugins.
+ * \ingroup services
+ */
+class plugin_service {
+private:
+    irccd& irccd_;
+    std::vector<std::shared_ptr<plugin>> plugins_;
+    std::vector<std::unique_ptr<plugin_loader>> 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<std::shared_ptr<plugin>>& 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<plugin> 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<plugin> 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> plugin);
+
+    /**
+     * Add a loader.
+     *
+     * \param loader the loader
+     */
+    void add_loader(std::unique_ptr<plugin_loader> 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<plugin> 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<plugin> 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 <typename Func, typename... Args>
+    void exec(std::shared_ptr<plugin> plugin, Func fn, Args&&... args)
+    {
+        assert(plugin);
+
+        // TODO: replace with C++17 std::invoke.
+        try {
+            ((*plugin).*(fn))(std::forward<Args>(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 <typename Func, typename... Args>
+    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>(args)...);
+    }
+
+    /**
+     * Load all plugins.
+     *
+     * \param cfg the config
+     */
+    void load(const class config& cfg) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_SERVICE_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <algorithm>
+#include <cctype>
+
+#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<rule_error::error>(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<int>(e), rule_category()};
+}
+
+} // !irccd
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_RULE_HPP
+#define IRCCD_RULE_HPP
+
+/**
+ * \file rule.hpp
+ * \brief Rule description
+ */
+
+#include "sysconfig.hpp"
+
+#include <cassert>
+#include <string>
+#include <unordered_set>
+
+#include <boost/system/system_error.hpp>
+
+namespace irccd {
+
+/**
+ * \brief Manage rule to activate or deactive events.
+ */
+class rule {
+public:
+    /**
+     * List of criterias.
+     */
+    using set = std::unordered_set<std::string>;
+
+    /**
+     * \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<irccd::rule_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
+#endif // !IRCCD_RULE_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdexcept>
+
+#include <irccd/logger.hpp>
+#include <irccd/string_util.hpp>
+
+#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<std::string>(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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_RULE_SERVICE_HPP
+#define IRCCD_RULE_SERVICE_HPP
+
+/**
+ * \file rule_service.hpp
+ * \brief Rule service.
+ */
+
+#include <vector>
+
+#include "rule.hpp"
+
+namespace irccd {
+
+class config;
+
+/**
+ * \brief Store and solve rules.
+ * \ingroup services
+ */
+class rule_service {
+private:
+    std::vector<rule> rules_;
+
+public:
+    /**
+     * Get the list of rules.
+     *
+     * \return the list of rules
+     */
+    inline const std::vector<rule>& 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "sysconfig.hpp"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstring>
+#include <stdexcept>
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+#  include <sys/types.h>
+#  include <netinet/in.h>
+#  include <arpa/nameser.h>
+#  include <resolv.h>
+#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<channel_mode, char>& 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<channel_mode, char> isupport_extract_prefixes(const std::string& line)
+{
+    // FIXME: what if line has different size?
+    std::pair<char, char> table[16];
+    std::string buf = line.substr(7);
+    std::map<channel_mode, char> 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<channel_mode>(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<std::string> 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<irc::tls_connection>(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<irc::ip_connection>(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<server_error::error>(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<int>(e), server_category()};
+}
+
+} // !irccd
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_SERVER_HPP
+#define IRCCD_SERVER_HPP
+
+/**
+ * \file server.hpp
+ * \brief IRC Server.
+ */
+
+#include "sysconfig.hpp"
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <boost/signals2/signal.hpp>
+
+#include <json.hpp>
+
+#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<std::string> channels;      //!< the channels where the user is
+};
+
+/**
+ * \brief Connection success event.
+ */
+class connect_event {
+public:
+    std::shared_ptr<class server> server;   //!< The server.
+};
+
+/**
+ * \brief Invite event.
+ */
+class invite_event {
+public:
+    std::shared_ptr<class server> 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<class server> server;   //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+};
+
+/**
+ * \brief Kick event.
+ */
+class kick_event {
+public:
+    std::shared_ptr<class server> 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<class server> 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<class server> 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<class server> 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<class server> server;   //!< The server.
+    std::string channel;                    //!< The channel.
+    std::vector<std::string> names;         //!< The names.
+};
+
+/**
+ * \brief Nick change event.
+ */
+class nick_event {
+public:
+    std::shared_ptr<class server> server;   //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string nickname;                   //!< The new nickname.
+};
+
+/**
+ * \brief Notice event.
+ */
+class notice_event {
+public:
+    std::shared_ptr<class server> 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<class server> 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<class server> server;   //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string message;                    //!< The message.
+};
+
+/**
+ * \brief Topic event.
+ */
+class topic_event {
+public:
+    std::shared_ptr<class server> 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<class server> 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<server> {
+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<void (connect_event)> on_connect;
+
+    /**
+     * Signal: on_die
+     * ----------------------------------------------------------
+     *
+     * The server is dead.
+     */
+    boost::signals2::signal<void ()> on_die;
+
+    /**
+     * Signal: on_invite
+     * ----------------------------------------------------------
+     *
+     * Triggered when an invite has been sent to you (the bot).
+     */
+    boost::signals2::signal<void (invite_event)> on_invite;
+
+    /**
+     * Signal: on_join
+     * ----------------------------------------------------------
+     *
+     * Triggered when a user has joined the channel, it also includes you.
+     */
+    boost::signals2::signal<void (join_event)> on_join;
+
+    /**
+     * Signal: on_kick
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has been kicked from a channel.
+     */
+    boost::signals2::signal<void (kick_event)> on_kick;
+
+    /**
+     * Signal: on_message
+     * ----------------------------------------------------------
+     *
+     * Triggered when a message on a channel has been sent.
+     */
+    boost::signals2::signal<void (message_event)> 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<void (me_event)> on_me;
+
+    /**
+     * Signal: on_mode
+     * ----------------------------------------------------------
+     *
+     * Triggered when the server changed your user mode.
+     */
+    boost::signals2::signal<void (mode_event)> on_mode;
+
+    /**
+     * Signal: on_names
+     * ----------------------------------------------------------
+     *
+     * Triggered when names listing has finished on a channel.
+     */
+    boost::signals2::signal<void (names_event)> on_names;
+
+    /**
+     * Signal: on_nick
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone changed its nickname, it also includes you.
+     */
+    boost::signals2::signal<void (nick_event)> on_nick;
+
+    /**
+     * Signal: on_notice
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has sent a notice to you.
+     */
+    boost::signals2::signal<void (notice_event)> on_notice;
+
+    /**
+     * Signal: on_part
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has left the channel.
+     */
+    boost::signals2::signal<void (part_event)> on_part;
+
+    /**
+     * Signal: on_query
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has sent you a private message.
+     */
+    boost::signals2::signal<void (query_event)> on_query;
+
+    /**
+     * Signal: on_topic
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone changed the channel topic.
+     */
+    boost::signals2::signal<void (topic_event)> on_topic;
+
+    /**
+     * Signal: on_whois
+     * ----------------------------------------------------------
+     *
+     * Triggered when whois information has been received.
+     */
+    boost::signals2::signal<void (whois_event)> on_whois;
+
+private:
+    state_t state_{state_t::disconnected};
+
+    // Requested and joined channels.
+    std::vector<channel> rchannels_;
+    std::vector<std::string> 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<channel_mode, char> modes_;
+
+    // Misc.
+    boost::asio::io_service& service_;
+    boost::asio::deadline_timer timer_;
+    std::unique_ptr<irc::connection> conn_;
+    std::int8_t recocur_{0};
+    std::map<std::string, std::set<std::string>> names_map_;
+    std::map<std::string, class whois> 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<std::string>& 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<irccd::server_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
+#endif // !IRCCD_SERVER_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/json_util.hpp>
+
+#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 <typename EventNameFunc, typename ExecFunc>
+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 <typename T>
+T to_int(const std::string& value, const std::string& name, server_error::error errc)
+{
+    try {
+        return string_util::to_int<T>(value);
+    } catch (...) {
+        throw server_error(errc, name);
+    }
+}
+
+template <typename T>
+T to_uint(const std::string& value, const std::string& name, server_error::error errc)
+{
+    try {
+        return string_util::to_uint<T>(value);
+    } catch (...) {
+        throw server_error(errc, name);
+    }
+}
+
+template <typename T>
+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<unsigned>();
+
+    if (n > std::numeric_limits<T>::max())
+        throw server_error(errc, name);
+
+    return static_cast<T>(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>& 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<server> load_server(boost::asio::io_service& service,
+                                    const config& cfg,
+                                    const ini::section& sc)
+{
+    assert(sc.key() == "server");
+
+    auto sv = std::make_shared<server>(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<std::uint16_t>(it->value(),
+            sv->name(), server_error::invalid_port));
+
+    if ((it = sc.find("reconnect-tries")) != sc.end())
+        sv->set_reconnect_tries(to_int<std::int8_t>(it->value(),
+            sv->name(), server_error::invalid_reconnect_tries));
+
+    if ((it = sc.find("reconnect-timeout")) != sc.end())
+        sv->set_reconnect_delay(to_uint<std::uint16_t>(it->value(),
+            sv->name(), server_error::invalid_reconnect_timeout));
+
+    if ((it = sc.find("ping-timeout")) != sc.end())
+        sv->set_ping_timeout(to_uint<std::uint16_t>(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> 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<server>(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<std::uint16_t>(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> server)
+{
+    assert(!has(server->name()));
+
+    std::weak_ptr<class server> 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> 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> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_SERVER_SERVICE_HPP
+#define IRCCD_SERVER_SERVICE_HPP
+
+/**
+ * \file server_service.hpp
+ * \brief Server service.
+ */
+
+#include <memory>
+#include <vector>
+
+#include "server.hpp"
+
+namespace irccd {
+
+class config;
+class irccd;
+
+/**
+ * \brief Manage IRC servers.
+ * \ingroup services
+ */
+class server_service {
+private:
+    irccd& irccd_;
+    std::vector<std::shared_ptr<server>> 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<server> 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<std::shared_ptr<server>>& 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<server> 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<server> 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<server> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "tls_transport_server.hpp"
+
+#if defined(HAVE_SSL)
+
+namespace irccd {
+
+void tls_transport_server::do_handshake(std::shared_ptr<client_t> 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<client_t>(*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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_TLS_TRANSPORT_SERVER_HPP
+#define IRCCD_TLS_TRANSPORT_SERVER_HPP
+
+/**
+ * \file tls_transport_server.hpp
+ * \brief Server side transports (SSL support).
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#if defined(HAVE_SSL)
+
+#include <boost/asio/ssl.hpp>
+
+#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<boost::asio::ssl::stream<socket_t>>;
+
+    context_t context_;
+
+    void do_handshake(std::shared_ptr<client_t>, 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include "transport_client.hpp"
+#include "transport_server.hpp"
+
+namespace irccd {
+
+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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_TRANSPORT_CLIENT_HPP
+#define IRCCD_TRANSPORT_CLIENT_HPP
+
+#include "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<transport_client> {
+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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "sysconfig.hpp"
+
+#include <cassert>
+
+#include "irccd.hpp"
+#include "json_util.hpp"
+#include "transport_server.hpp"
+
+namespace irccd {
+
+void transport_server::do_auth(std::shared_ptr<transport_client> 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<transport_client> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_TRANSPORT_SERVER_HPP
+#define IRCCD_TRANSPORT_SERVER_HPP
+
+#include "sysconfig.hpp"
+
+#include <cassert>
+#include <functional>
+#include <memory>
+#include <unordered_set>
+#include <type_traits>
+
+#include <boost/asio.hpp>
+
+#include "transport_client.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Abstract transport server class.
+ *
+ * This class create asynchronous operation to accept new clients.
+ */
+class transport_server {
+protected:
+    /**
+     * Set of clients.
+     */
+    using client_set_t = std::unordered_set<std::shared_ptr<transport_client>>;
+
+    /**
+     * Callback when a new client should be accepted.
+     */
+    using accept_t = std::function<void (boost::system::error_code, std::shared_ptr<transport_client>)>;
+
+private:
+    client_set_t clients_;
+    std::string password_;
+
+    void do_auth(std::shared_ptr<transport_client>, accept_t);
+    void do_greetings(std::shared_ptr<transport_client>, accept_t);
+
+protected:
+    /**
+     * Start accept operation, the implementation should not block and call
+     * the handler function on error or completion.
+     *
+     * \pre handler must not be null
+     * \param handler the handler function
+     */
+    virtual void do_accept(accept_t handler) = 0;
+
+public:
+    /**
+     * Default constructor.
+     */
+    transport_server() noexcept = default;
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~transport_server() noexcept = default;
+
+    /**
+     * Wrapper that automatically add the new client into the list.
+     *
+     * If handler is not null it is called on error or on successful accept
+     * operation.
+     *
+     * \param handler the handler
+     */
+    void accept(accept_t handler);
+
+    /**
+     * Get the clients.
+     *
+     * \return the clients
+     */
+    inline const client_set_t& clients() const noexcept
+    {
+        return clients_;
+    }
+
+    /**
+     * Overloaded function.
+     *
+     * \return the clients
+     */
+    inline client_set_t& clients() noexcept
+    {
+        return clients_;
+    }
+
+    /**
+     * Get the current password, empty string means no password.
+     *
+     * \return the password
+     */
+    inline const std::string& password() const noexcept
+    {
+        return password_;
+    }
+
+    /**
+     * Set an optional password, empty string means no password.
+     *
+     * \param password the password
+     */
+    inline void set_password(std::string password) noexcept
+    {
+        password_ = std::move(password);
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_TRANSPORT_SERVER_HPP
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <irccd/sysconfig.hpp>
+
+#include <cassert>
+
+#include <irccd/string_util.hpp>
+
+#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<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    std::unique_ptr<transport_server> 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<std::uint16_t>(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<ip_transport_server>(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<tls_transport_server>(std::move(acceptor), std::move(ctx));
+#else
+    throw std::invalid_argument("SSL disabled");
+#endif
+}
+
+std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
+{
+    using boost::asio::local::stream_protocol;
+
+    assert(sc.key() == "transport");
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+    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<local_transport_server>(std::move(acceptor));
+#else
+    (void)sc;
+
+    throw std::invalid_argument("unix transports not supported on on this platform");
+#endif
+}
+
+std::unique_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    std::unique_ptr<transport_server> 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<transport_client> 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<std::string>());
+    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<transport_client> 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<transport_server> 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
--- /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 <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_TRANSPORT_SERVICE_HPP
+#define IRCCD_TRANSPORT_SERVICE_HPP
+
+#include <memory>
+#include <vector>
+
+#include <json.hpp>
+
+#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<std::unique_ptr<transport_server>>;
+
+private:
+    irccd& irccd_;
+    servers_t servers_;
+
+    void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&);
+    void do_recv(std::shared_ptr<transport_client>);
+    void do_accept(transport_server&);
+
+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<transport_server> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cctype>
-#include <algorithm>
-
-#include <boost/filesystem.hpp>
-
-#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<plugin>(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<load_t>(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<std::string> directories) noexcept
-    : plugin_loader(std::move(directories), { DYNLIB_EXTENSION })
-{
-}
-
-std::shared_ptr<plugin> dynlib_plugin_loader::open(const std::string& id,
-                                                   const std::string& path) noexcept
-{
-    return std::make_unique<dynlib_plugin>(id, path);
-}
-
-} // !irccd
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_PLUGIN_DYNLIB_HPP
-#define IRCCD_PLUGIN_DYNLIB_HPP
-
-/**
- * \file plugin-dynlib.hpp
- * \brief Native plugin implementation.
- */
-
-#include <boost/dll.hpp>
-
-#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> 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<std::string> directories = {}) noexcept;
-
-    /**
-     * \copydoc plugin_loader::find
-     */
-    std::shared_ptr<plugin> open(const std::string& id,
-                                 const std::string& path) noexcept override;
-};
-
-} // !irccd
-
-#endif // !IRCCD_PLUGIN_DYNLIB_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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_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<boost::asio::ip::tcp>;
-
-} // !irccd
-
-#endif // !IRCCD_IP_TRANSPORT_SERVER_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-#include <iterator>
-#include <sstream>
-
-#include "irc.hpp"
-
-namespace irccd {
-
-namespace irc {
-
-namespace {
-
-using boost::asio::ip::tcp;
-
-template <typename Socket>
-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 <typename Socket>
-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 <typename Socket>
-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 <typename Socket>
-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<std::string> args;
-    std::istreambuf_iterator<char> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_IRC_HPP
-#define IRCCD_IRC_HPP
-
-/**
- * \file irc.hpp
- * \brief Low level IRC functions.
- */
-
-#include "sysconfig.hpp"
-
-#include <deque>
-#include <functional>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <boost/asio.hpp>
-
-#if defined(HAVE_SSL)
-#   include <boost/asio/ssl.hpp>
-#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<std::string> 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<std::string> 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<std::string>& 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 <typename Enum>
-    inline bool is(Enum e) const noexcept
-    {
-        try {
-            return std::stoi(command_) == static_cast<int>(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<void (boost::system::error_code)>;
-
-    /**
-     * Handler for receiving.
-     */
-    using recv_t = std::function<void (boost::system::error_code, message)>;
-
-    /**
-     * Handler for sending.
-     */
-    using send_t = std::function<void (boost::system::error_code)>;
-
-private:
-    using buffer_t = boost::asio::streambuf;
-    using input_t = std::deque<recv_t>;
-    using output_t = std::deque<std::pair<std::string, send_t>>;
-
-    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<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 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/logger.hpp>
-#include <irccd/string_util.hpp>
-#include <irccd/system.hpp>
-
-#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<log::file_logger>(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<log::syslog_logger>());
-#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<log_filter>(
-        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<command_service>())
-    , server_service_(std::make_unique<server_service>(*this))
-    , tpt_service_(std::make_unique<transport_service>(*this))
-    , rule_service_(std::make_unique<rule_service>())
-    , plugin_service_(std::make_unique<plugin_service>(*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<irccd_error::error>(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<int>(e), irccd_category()};
-}
-
-} // !irccd
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_HPP
-#define IRCCD_HPP
-
-/**
- * \file irccd.hpp
- * \brief Base class for irccd front end.
- */
-
-#include "sysconfig.hpp"
-
-#include <memory>
-
-#include <boost/asio.hpp>
-
-#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> command_service_;
-    std::shared_ptr<server_service> server_service_;
-    std::shared_ptr<transport_service> tpt_service_;
-    std::shared_ptr<rule_service> rule_service_;
-    std::shared_ptr<plugin_service> 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<irccd::irccd_error::error> : public std::true_type {
-};
-
-} // !system
-
-} // !boost
-
-#endif // !IRCCD_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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_LOCAL_TRANSPORT_SERVER_HPP
-#define IRCCD_LOCAL_TRANSPORT_SERVER_HPP
-
-/**
- * \file local_transport_server.hpp
- * \brief Server side transports (Unix support).
- */
-
-#include <irccd/sysconfig.hpp>
-
-#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<boost::asio::local::stream_protocol>;
-
-#endif // !_WIN32
-
-} // !irccd
-
-#endif // !IRCCD_LOCAL_TRANSPORT_SERVER_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <boost/filesystem.hpp>
-
-#include <sstream>
-
-#include "plugin.hpp"
-#include "system.hpp"
-
-namespace fs = boost::filesystem;
-
-namespace irccd {
-
-std::shared_ptr<plugin> plugin_loader::find(const std::string& name)
-{
-    std::vector<std::string> 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<plugin_error::error>(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<int>(e), plugin_category()};
-}
-
-} // !irccd
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_PLUGIN_HPP
-#define IRCCD_PLUGIN_HPP
-
-/**
- * \file plugin.hpp
- * \brief irccd plugins
- */
-
-/**
- * \defgroup plugins Plugins
- * \brief Plugin management.
- */
-
-#include "sysconfig.hpp"
-
-#include <cassert>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "server.hpp"
-#include "util.hpp"
-
-namespace irccd {
-
-class irccd;
-
-/**
- * \brief Configuration map extract from config file.
- */
-using plugin_config = std::unordered_map<std::string, std::string>;
-
-/**
- * \brief Formats for plugins.
- */
-using plugin_formats = std::unordered_map<std::string, std::string>;
-
-/**
- * \brief Paths for plugins.
- */
-using plugin_paths = std::unordered_map<std::string, std::string>;
-
-/**
- * \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<plugin> {
-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<std::string> directories_;
-    std::vector<std::string> 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<std::string> directories,
-                  std::vector<std::string> 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<std::string> 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<std::string> 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<plugin> 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<plugin> 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<irccd::plugin_error::error> : public std::true_type {
-};
-
-} // !system
-
-} // !boost
-
-#endif // !IRCCD_PLUGIN_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/string_util.hpp>
-#include <irccd/logger.hpp>
-
-#include "config.hpp"
-#include "irccd.hpp"
-#include "plugin_service.hpp"
-#include "string_util.hpp"
-#include "system.hpp"
-
-namespace irccd {
-
-namespace {
-
-template <typename Map>
-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> 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> 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> plugin)
-{
-    plugins_.push_back(std::move(plugin));
-}
-
-void plugin_service::add_loader(std::unique_ptr<plugin_loader> loader)
-{
-    loaders_.push_back(std::move(loader));
-}
-
-plugin_config plugin_service::config(const std::string& id)
-{
-    return to_map<plugin_config>(irccd_.config(), string_util::sprintf("plugin.%s", id));
-}
-
-plugin_formats plugin_service::formats(const std::string& id)
-{
-    return to_map<plugin_formats>(irccd_.config(), string_util::sprintf("format.%s", id));
-}
-
-plugin_paths plugin_service::paths(const std::string& id)
-{
-    auto defaults = to_map<plugin_paths>(irccd_.config(), "paths");
-    auto paths = to_map<plugin_paths>(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> 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> 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> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_PLUGIN_SERVICE_HPP
-#define IRCCD_PLUGIN_SERVICE_HPP
-
-/**
- * \file plugin_service.hpp
- * \brief Plugin service.
- */
-
-#include <cassert>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "plugin.hpp"
-
-namespace irccd {
-
-class irccd;
-class config;
-
-/**
- * \brief Manage plugins.
- * \ingroup services
- */
-class plugin_service {
-private:
-    irccd& irccd_;
-    std::vector<std::shared_ptr<plugin>> plugins_;
-    std::vector<std::unique_ptr<plugin_loader>> 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<std::shared_ptr<plugin>>& 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<plugin> 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<plugin> 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> plugin);
-
-    /**
-     * Add a loader.
-     *
-     * \param loader the loader
-     */
-    void add_loader(std::unique_ptr<plugin_loader> 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<plugin> 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<plugin> 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 <typename Func, typename... Args>
-    void exec(std::shared_ptr<plugin> plugin, Func fn, Args&&... args)
-    {
-        assert(plugin);
-
-        // TODO: replace with C++17 std::invoke.
-        try {
-            ((*plugin).*(fn))(std::forward<Args>(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 <typename Func, typename... Args>
-    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>(args)...);
-    }
-
-    /**
-     * Load all plugins.
-     *
-     * \param cfg the config
-     */
-    void load(const class config& cfg) noexcept;
-};
-
-} // !irccd
-
-#endif // !IRCCD_PLUGIN_SERVICE_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <algorithm>
-#include <cctype>
-
-#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<rule_error::error>(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<int>(e), rule_category()};
-}
-
-} // !irccd
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_RULE_HPP
-#define IRCCD_RULE_HPP
-
-/**
- * \file rule.hpp
- * \brief Rule description
- */
-
-#include "sysconfig.hpp"
-
-#include <cassert>
-#include <string>
-#include <unordered_set>
-
-#include <boost/system/system_error.hpp>
-
-namespace irccd {
-
-/**
- * \brief Manage rule to activate or deactive events.
- */
-class rule {
-public:
-    /**
-     * List of criterias.
-     */
-    using set = std::unordered_set<std::string>;
-
-    /**
-     * \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<irccd::rule_error::error> : public std::true_type {
-};
-
-} // !system
-
-} // !boost
-
-#endif // !IRCCD_RULE_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <stdexcept>
-
-#include <irccd/logger.hpp>
-#include <irccd/string_util.hpp>
-
-#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<std::string>(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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_RULE_SERVICE_HPP
-#define IRCCD_RULE_SERVICE_HPP
-
-/**
- * \file rule_service.hpp
- * \brief Rule service.
- */
-
-#include <vector>
-
-#include "rule.hpp"
-
-namespace irccd {
-
-class config;
-
-/**
- * \brief Store and solve rules.
- * \ingroup services
- */
-class rule_service {
-private:
-    std::vector<rule> rules_;
-
-public:
-    /**
-     * Get the list of rules.
-     *
-     * \return the list of rules
-     */
-    inline const std::vector<rule>& 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "sysconfig.hpp"
-
-#include <algorithm>
-#include <cerrno>
-#include <cstring>
-#include <stdexcept>
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-#  include <sys/types.h>
-#  include <netinet/in.h>
-#  include <arpa/nameser.h>
-#  include <resolv.h>
-#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<channel_mode, char>& 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<channel_mode, char> isupport_extract_prefixes(const std::string& line)
-{
-    // FIXME: what if line has different size?
-    std::pair<char, char> table[16];
-    std::string buf = line.substr(7);
-    std::map<channel_mode, char> 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<channel_mode>(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<std::string> 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<irc::tls_connection>(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<irc::ip_connection>(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<server_error::error>(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<int>(e), server_category()};
-}
-
-} // !irccd
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVER_HPP
-#define IRCCD_SERVER_HPP
-
-/**
- * \file server.hpp
- * \brief IRC Server.
- */
-
-#include "sysconfig.hpp"
-
-#include <cstdint>
-#include <map>
-#include <memory>
-#include <set>
-#include <string>
-#include <vector>
-
-#include <boost/signals2/signal.hpp>
-
-#include <json.hpp>
-
-#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<std::string> channels;      //!< the channels where the user is
-};
-
-/**
- * \brief Connection success event.
- */
-class connect_event {
-public:
-    std::shared_ptr<class server> server;   //!< The server.
-};
-
-/**
- * \brief Invite event.
- */
-class invite_event {
-public:
-    std::shared_ptr<class server> 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<class server> server;   //!< The server.
-    std::string origin;                     //!< The originator.
-    std::string channel;                    //!< The channel.
-};
-
-/**
- * \brief Kick event.
- */
-class kick_event {
-public:
-    std::shared_ptr<class server> 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<class server> 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<class server> 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<class server> 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<class server> server;   //!< The server.
-    std::string channel;                    //!< The channel.
-    std::vector<std::string> names;         //!< The names.
-};
-
-/**
- * \brief Nick change event.
- */
-class nick_event {
-public:
-    std::shared_ptr<class server> server;   //!< The server.
-    std::string origin;                     //!< The originator.
-    std::string nickname;                   //!< The new nickname.
-};
-
-/**
- * \brief Notice event.
- */
-class notice_event {
-public:
-    std::shared_ptr<class server> 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<class server> 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<class server> server;   //!< The server.
-    std::string origin;                     //!< The originator.
-    std::string message;                    //!< The message.
-};
-
-/**
- * \brief Topic event.
- */
-class topic_event {
-public:
-    std::shared_ptr<class server> 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<class server> 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<server> {
-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<void (connect_event)> on_connect;
-
-    /**
-     * Signal: on_die
-     * ----------------------------------------------------------
-     *
-     * The server is dead.
-     */
-    boost::signals2::signal<void ()> on_die;
-
-    /**
-     * Signal: on_invite
-     * ----------------------------------------------------------
-     *
-     * Triggered when an invite has been sent to you (the bot).
-     */
-    boost::signals2::signal<void (invite_event)> on_invite;
-
-    /**
-     * Signal: on_join
-     * ----------------------------------------------------------
-     *
-     * Triggered when a user has joined the channel, it also includes you.
-     */
-    boost::signals2::signal<void (join_event)> on_join;
-
-    /**
-     * Signal: on_kick
-     * ----------------------------------------------------------
-     *
-     * Triggered when someone has been kicked from a channel.
-     */
-    boost::signals2::signal<void (kick_event)> on_kick;
-
-    /**
-     * Signal: on_message
-     * ----------------------------------------------------------
-     *
-     * Triggered when a message on a channel has been sent.
-     */
-    boost::signals2::signal<void (message_event)> 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<void (me_event)> on_me;
-
-    /**
-     * Signal: on_mode
-     * ----------------------------------------------------------
-     *
-     * Triggered when the server changed your user mode.
-     */
-    boost::signals2::signal<void (mode_event)> on_mode;
-
-    /**
-     * Signal: on_names
-     * ----------------------------------------------------------
-     *
-     * Triggered when names listing has finished on a channel.
-     */
-    boost::signals2::signal<void (names_event)> on_names;
-
-    /**
-     * Signal: on_nick
-     * ----------------------------------------------------------
-     *
-     * Triggered when someone changed its nickname, it also includes you.
-     */
-    boost::signals2::signal<void (nick_event)> on_nick;
-
-    /**
-     * Signal: on_notice
-     * ----------------------------------------------------------
-     *
-     * Triggered when someone has sent a notice to you.
-     */
-    boost::signals2::signal<void (notice_event)> on_notice;
-
-    /**
-     * Signal: on_part
-     * ----------------------------------------------------------
-     *
-     * Triggered when someone has left the channel.
-     */
-    boost::signals2::signal<void (part_event)> on_part;
-
-    /**
-     * Signal: on_query
-     * ----------------------------------------------------------
-     *
-     * Triggered when someone has sent you a private message.
-     */
-    boost::signals2::signal<void (query_event)> on_query;
-
-    /**
-     * Signal: on_topic
-     * ----------------------------------------------------------
-     *
-     * Triggered when someone changed the channel topic.
-     */
-    boost::signals2::signal<void (topic_event)> on_topic;
-
-    /**
-     * Signal: on_whois
-     * ----------------------------------------------------------
-     *
-     * Triggered when whois information has been received.
-     */
-    boost::signals2::signal<void (whois_event)> on_whois;
-
-private:
-    state_t state_{state_t::disconnected};
-
-    // Requested and joined channels.
-    std::vector<channel> rchannels_;
-    std::vector<std::string> 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<channel_mode, char> modes_;
-
-    // Misc.
-    boost::asio::io_service& service_;
-    boost::asio::deadline_timer timer_;
-    std::unique_ptr<irc::connection> conn_;
-    std::int8_t recocur_{0};
-    std::map<std::string, std::set<std::string>> names_map_;
-    std::map<std::string, class whois> 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<std::string>& 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<irccd::server_error::error> : public std::true_type {
-};
-
-} // !system
-
-} // !boost
-
-#endif // !IRCCD_SERVER_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/json_util.hpp>
-
-#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 <typename EventNameFunc, typename ExecFunc>
-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 <typename T>
-T to_int(const std::string& value, const std::string& name, server_error::error errc)
-{
-    try {
-        return string_util::to_int<T>(value);
-    } catch (...) {
-        throw server_error(errc, name);
-    }
-}
-
-template <typename T>
-T to_uint(const std::string& value, const std::string& name, server_error::error errc)
-{
-    try {
-        return string_util::to_uint<T>(value);
-    } catch (...) {
-        throw server_error(errc, name);
-    }
-}
-
-template <typename T>
-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<unsigned>();
-
-    if (n > std::numeric_limits<T>::max())
-        throw server_error(errc, name);
-
-    return static_cast<T>(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>& 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<server> load_server(boost::asio::io_service& service,
-                                    const config& cfg,
-                                    const ini::section& sc)
-{
-    assert(sc.key() == "server");
-
-    auto sv = std::make_shared<server>(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<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_port));
-
-    if ((it = sc.find("reconnect-tries")) != sc.end())
-        sv->set_reconnect_tries(to_int<std::int8_t>(it->value(),
-            sv->name(), server_error::invalid_reconnect_tries));
-
-    if ((it = sc.find("reconnect-timeout")) != sc.end())
-        sv->set_reconnect_delay(to_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_reconnect_timeout));
-
-    if ((it = sc.find("ping-timeout")) != sc.end())
-        sv->set_ping_timeout(to_uint<std::uint16_t>(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> 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<server>(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<std::uint16_t>(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> server)
-{
-    assert(!has(server->name()));
-
-    std::weak_ptr<class server> 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> 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> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_SERVER_SERVICE_HPP
-#define IRCCD_SERVER_SERVICE_HPP
-
-/**
- * \file server_service.hpp
- * \brief Server service.
- */
-
-#include <memory>
-#include <vector>
-
-#include "server.hpp"
-
-namespace irccd {
-
-class config;
-class irccd;
-
-/**
- * \brief Manage IRC servers.
- * \ingroup services
- */
-class server_service {
-private:
-    irccd& irccd_;
-    std::vector<std::shared_ptr<server>> 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<server> 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<std::shared_ptr<server>>& 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<server> 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<server> 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<server> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "tls_transport_server.hpp"
-
-#if defined(HAVE_SSL)
-
-namespace irccd {
-
-void tls_transport_server::do_handshake(std::shared_ptr<client_t> 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<client_t>(*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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_TLS_TRANSPORT_SERVER_HPP
-#define IRCCD_TLS_TRANSPORT_SERVER_HPP
-
-/**
- * \file tls_transport_server.hpp
- * \brief Server side transports (SSL support).
- */
-
-#include <irccd/sysconfig.hpp>
-
-#if defined(HAVE_SSL)
-
-#include <boost/asio/ssl.hpp>
-
-#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<boost::asio::ssl::stream<socket_t>>;
-
-    context_t context_;
-
-    void do_handshake(std::shared_ptr<client_t>, 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include "transport_client.hpp"
-#include "transport_server.hpp"
-
-namespace irccd {
-
-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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_TRANSPORT_CLIENT_HPP
-#define IRCCD_TRANSPORT_CLIENT_HPP
-
-#include "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<transport_client> {
-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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include "sysconfig.hpp"
-
-#include <cassert>
-
-#include "irccd.hpp"
-#include "json_util.hpp"
-#include "transport_server.hpp"
-
-namespace irccd {
-
-void transport_server::do_auth(std::shared_ptr<transport_client> 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<transport_client> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_TRANSPORT_SERVER_HPP
-#define IRCCD_TRANSPORT_SERVER_HPP
-
-#include "sysconfig.hpp"
-
-#include <cassert>
-#include <functional>
-#include <memory>
-#include <unordered_set>
-#include <type_traits>
-
-#include <boost/asio.hpp>
-
-#include "transport_client.hpp"
-
-namespace irccd {
-
-/**
- * \brief Abstract transport server class.
- *
- * This class create asynchronous operation to accept new clients.
- */
-class transport_server {
-protected:
-    /**
-     * Set of clients.
-     */
-    using client_set_t = std::unordered_set<std::shared_ptr<transport_client>>;
-
-    /**
-     * Callback when a new client should be accepted.
-     */
-    using accept_t = std::function<void (boost::system::error_code, std::shared_ptr<transport_client>)>;
-
-private:
-    client_set_t clients_;
-    std::string password_;
-
-    void do_auth(std::shared_ptr<transport_client>, accept_t);
-    void do_greetings(std::shared_ptr<transport_client>, accept_t);
-
-protected:
-    /**
-     * Start accept operation, the implementation should not block and call
-     * the handler function on error or completion.
-     *
-     * \pre handler must not be null
-     * \param handler the handler function
-     */
-    virtual void do_accept(accept_t handler) = 0;
-
-public:
-    /**
-     * Default constructor.
-     */
-    transport_server() noexcept = default;
-
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~transport_server() noexcept = default;
-
-    /**
-     * Wrapper that automatically add the new client into the list.
-     *
-     * If handler is not null it is called on error or on successful accept
-     * operation.
-     *
-     * \param handler the handler
-     */
-    void accept(accept_t handler);
-
-    /**
-     * Get the clients.
-     *
-     * \return the clients
-     */
-    inline const client_set_t& clients() const noexcept
-    {
-        return clients_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the clients
-     */
-    inline client_set_t& clients() noexcept
-    {
-        return clients_;
-    }
-
-    /**
-     * Get the current password, empty string means no password.
-     *
-     * \return the password
-     */
-    inline const std::string& password() const noexcept
-    {
-        return password_;
-    }
-
-    /**
-     * Set an optional password, empty string means no password.
-     *
-     * \param password the password
-     */
-    inline void set_password(std::string password) noexcept
-    {
-        password_ = std::move(password);
-    }
-};
-
-} // !irccd
-
-#endif // !IRCCD_TRANSPORT_SERVER_HPP
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <irccd/sysconfig.hpp>
-
-#include <cassert>
-
-#include <irccd/string_util.hpp>
-
-#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<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
-{
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> 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<std::uint16_t>(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<ip_transport_server>(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<tls_transport_server>(std::move(acceptor), std::move(ctx));
-#else
-    throw std::invalid_argument("SSL disabled");
-#endif
-}
-
-std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
-{
-    using boost::asio::local::stream_protocol;
-
-    assert(sc.key() == "transport");
-
-#if !defined(IRCCD_SYSTEM_WINDOWS)
-    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<local_transport_server>(std::move(acceptor));
-#else
-    (void)sc;
-
-    throw std::invalid_argument("unix transports not supported on on this platform");
-#endif
-}
-
-std::unique_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
-{
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> 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<transport_client> 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<std::string>());
-    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<transport_client> 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<transport_server> 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
--- 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 <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef IRCCD_TRANSPORT_SERVICE_HPP
-#define IRCCD_TRANSPORT_SERVICE_HPP
-
-#include <memory>
-#include <vector>
-
-#include <json.hpp>
-
-#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<std::unique_ptr<transport_server>>;
-
-private:
-    irccd& irccd_;
-    servers_t servers_;
-
-    void handle_command(std::shared_ptr<transport_client>, const nlohmann::json&);
-    void do_recv(std::shared_ptr<transport_client>);
-    void do_accept(transport_server&);
-
-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<transport_server> 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
--- 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 <irccd/sysconfig.hpp>
 #include <irccd/json_util.hpp>
 
-#include <irccd/irccd.hpp>
-#include <irccd/server.hpp>
-#include <irccd/plugin.hpp>
-#include <irccd/rule.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
+#include <irccd/daemon/plugin.hpp>
+#include <irccd/daemon/rule.hpp>
 
 #include "controller.hpp"
 #include "connection.hpp"
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irc.hpp>
+#include <irccd/daemon/irc.hpp>
 
 namespace irccd {
 
--- 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 <boost/asio.hpp>
 #include <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <irccd/js/irccd_jsapi.hpp>
 #include <irccd/js/js_plugin.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
-#include <irccd/server.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
 
 #include "plugin_test.hpp"
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
-#include <irccd/server.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
 
 #include "plugin_test.hpp"
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
-#include <irccd/server.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
 
 #include "plugin_test.hpp"
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
-#include <irccd/server.hpp>
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
 
 #include "plugin_test.hpp"
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
-#include <irccd/server.hpp>
+
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/server.hpp>
 
 #include "plugin_test.hpp"
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
 #include <irccd/logger.hpp>
-#include <irccd/plugin_service.hpp>
-#include <irccd/server.hpp>
 #include <irccd/string_util.hpp>
 
+#include <irccd/daemon/irccd.hpp>
+#include <irccd/daemon/plugin_service.hpp>
+#include <irccd/daemon/server.hpp>
+
 #include "plugin_test.hpp"
 
 namespace irccd {
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/plugin_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/plugin_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <irccd/json_util.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <irccd/json_util.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <irccd/json_util.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <irccd/json_util.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <irccd/json_util.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <irccd/json_util.hpp>
 
-#include <irccd/command.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/command.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 #include <command_test.hpp>
 
--- 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 <boost/test/unit_test.hpp>
 
 #include <irccd/logger.hpp>
-#include <irccd/rule_service.hpp>
+#include <irccd/daemon/rule_service.hpp>
 
 namespace irccd {
 
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/server_service.hpp>
+#include <irccd/daemon/server_service.hpp>
 
 #include <journal_server.hpp>
 #include <command_test.hpp>
--- 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 <boost/test/unit_test.hpp>
 
-#include <irccd/irccd.hpp>
 #include <irccd/system.hpp>
 
+#include <irccd/daemon/irccd.hpp>
+
 #include <irccd/js/file_jsapi.hpp>
 #include <irccd/js/system_jsapi.hpp>