changeset 291:b490853404d9

Irccd: split lib into libirccd, #564
author David Demelier <markand@malikania.fr>
date Wed, 05 Oct 2016 13:27:15 +0200
parents 24bb45724dc0
children 671612cbc721
files CMakeLists.txt libirccd/CMakeLists.txt libirccd/irccd/cmd-plugin-config.cpp libirccd/irccd/cmd-plugin-config.hpp libirccd/irccd/cmd-plugin-info.cpp libirccd/irccd/cmd-plugin-info.hpp libirccd/irccd/cmd-plugin-list.cpp libirccd/irccd/cmd-plugin-list.hpp libirccd/irccd/cmd-plugin-load.cpp libirccd/irccd/cmd-plugin-load.hpp libirccd/irccd/cmd-plugin-reload.cpp libirccd/irccd/cmd-plugin-reload.hpp libirccd/irccd/cmd-plugin-unload.cpp libirccd/irccd/cmd-plugin-unload.hpp libirccd/irccd/cmd-server-cmode.cpp libirccd/irccd/cmd-server-cmode.hpp libirccd/irccd/cmd-server-cnotice.cpp libirccd/irccd/cmd-server-cnotice.hpp libirccd/irccd/cmd-server-connect.cpp libirccd/irccd/cmd-server-connect.hpp libirccd/irccd/cmd-server-disconnect.cpp libirccd/irccd/cmd-server-disconnect.hpp libirccd/irccd/cmd-server-info.cpp libirccd/irccd/cmd-server-info.hpp libirccd/irccd/cmd-server-invite.cpp libirccd/irccd/cmd-server-invite.hpp libirccd/irccd/cmd-server-join.cpp libirccd/irccd/cmd-server-join.hpp libirccd/irccd/cmd-server-kick.cpp libirccd/irccd/cmd-server-kick.hpp libirccd/irccd/cmd-server-list.cpp libirccd/irccd/cmd-server-list.hpp libirccd/irccd/cmd-server-me.cpp libirccd/irccd/cmd-server-me.hpp libirccd/irccd/cmd-server-message.cpp libirccd/irccd/cmd-server-message.hpp libirccd/irccd/cmd-server-mode.cpp libirccd/irccd/cmd-server-mode.hpp libirccd/irccd/cmd-server-nick.cpp libirccd/irccd/cmd-server-nick.hpp libirccd/irccd/cmd-server-notice.cpp libirccd/irccd/cmd-server-notice.hpp libirccd/irccd/cmd-server-part.cpp libirccd/irccd/cmd-server-part.hpp libirccd/irccd/cmd-server-reconnect.cpp libirccd/irccd/cmd-server-reconnect.hpp libirccd/irccd/cmd-server-topic.cpp libirccd/irccd/cmd-server-topic.hpp libirccd/irccd/command.cpp libirccd/irccd/command.hpp libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/dynlib.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/plugin-dynlib.cpp libirccd/irccd/plugin-dynlib.hpp libirccd/irccd/plugin.hpp libirccd/irccd/rule.cpp libirccd/irccd/rule.hpp libirccd/irccd/server.cpp libirccd/irccd/server.hpp libirccd/irccd/service-command.cpp libirccd/irccd/service-command.hpp libirccd/irccd/service-interrupt.cpp libirccd/irccd/service-interrupt.hpp libirccd/irccd/service-plugin.cpp libirccd/irccd/service-plugin.hpp libirccd/irccd/service-rule.cpp libirccd/irccd/service-rule.hpp libirccd/irccd/service-server.cpp libirccd/irccd/service-server.hpp libirccd/irccd/service-transport.cpp libirccd/irccd/service-transport.hpp libirccd/irccd/transport.cpp libirccd/irccd/transport.hpp
diffstat 76 files changed, 11317 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Wed Oct 05 13:06:00 2016 +0200
+++ b/CMakeLists.txt	Wed Oct 05 13:27:15 2016 +0200
@@ -79,6 +79,7 @@
 add_subdirectory(extern/json)
 add_subdirectory(doc)
 add_subdirectory(libcommon)
+add_subdirectory(libirccd)
 #add_subdirectory(lib)
 #add_subdirectory(irccd)
 #add_subdirectory(irccdctl)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/CMakeLists.txt	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,92 @@
+project(libirccd)
+
+set(
+    HEADERS
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-config.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-info.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-list.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-load.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-reload.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-unload.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-cmode.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-cnotice.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-connect.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-disconnect.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-info.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-invite.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-join.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-kick.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-list.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-me.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-message.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-mode.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-nick.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-notice.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-part.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-reconnect.hpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-topic.hpp
+    ${libirccd_SOURCE_DIR}/irccd/command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/config.hpp
+    ${libirccd_SOURCE_DIR}/irccd/dynlib.hpp
+    ${libirccd_SOURCE_DIR}/irccd/irccd.hpp
+    ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.hpp
+    ${libirccd_SOURCE_DIR}/irccd/plugin.hpp
+    ${libirccd_SOURCE_DIR}/irccd/rule.hpp
+    ${libirccd_SOURCE_DIR}/irccd/server.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service-command.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service-interrupt.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service-plugin.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service-rule.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service-server.hpp
+    ${libirccd_SOURCE_DIR}/irccd/service-transport.hpp
+    ${libirccd_SOURCE_DIR}/irccd/transport.hpp
+)
+
+set(
+    SOURCES
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-config.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-info.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-list.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-load.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-reload.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-plugin-unload.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-cmode.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-cnotice.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-connect.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-disconnect.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-info.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-invite.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-join.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-kick.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-list.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-me.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-message.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-mode.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-nick.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-notice.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-part.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-reconnect.cpp
+    ${libirccd_SOURCE_DIR}/irccd/cmd-server-topic.cpp
+    ${libirccd_SOURCE_DIR}/irccd/command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/config.cpp
+    ${libirccd_SOURCE_DIR}/irccd/irccd.cpp
+    ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.cpp
+    ${libirccd_SOURCE_DIR}/irccd/rule.cpp
+    ${libirccd_SOURCE_DIR}/irccd/server.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service-command.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service-interrupt.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service-plugin.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service-rule.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service-server.cpp
+    ${libirccd_SOURCE_DIR}/irccd/service-transport.cpp
+    ${libirccd_SOURCE_DIR}/irccd/transport.cpp
+)
+
+irccd_define_library(
+    TARGET libirccd
+    SOURCES
+        ${libirccd_SOURCE_DIR}/CMakeLists.txt
+        ${HEADERS}
+        ${SOURCES}
+    LIBRARIES extern-ircclient libcommon
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-config.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,131 @@
+/*
+ * cmd-plugin-config.cpp -- implementation of plugin-config command
+ *
+ * Copyright (c) 2013-2016 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 <iomanip>
+#include <iostream>
+
+#include "irccd.hpp"
+#include "cmd-plugin-config.hpp"
+#include "service-plugin.hpp"
+
+namespace irccd {
+
+namespace command {
+
+namespace {
+
+nlohmann::json execSet(Irccd &irccd, const nlohmann::json &request, const std::string &var, const std::string &value)
+{
+    auto plugin = irccd.plugins().require(request["plugin"].get<std::string>());
+    auto config = plugin->config();
+
+    config[var] = value;
+    plugin->setConfig(config);
+
+    return nullptr;
+}
+
+nlohmann::json execGet(Irccd &irccd, const nlohmann::json &request, const nlohmann::json::const_iterator &var)
+{
+    auto config = irccd.plugins().require(request["plugin"].get<std::string>())->config();
+
+    // 'vars' property.
+    std::map<std::string, nlohmann::json> vars;
+
+    if (var != request.end())
+        vars.emplace(var->get<std::string>(), config[var->get<std::string>()]);
+    else
+        for (const auto &pair : config)
+            vars.emplace(pair.first, pair.second);
+
+    return nlohmann::json::object({{ "variables", nlohmann::json(vars) }});
+}
+
+} // !namespace
+
+PluginConfigCommand::PluginConfigCommand()
+    : Command("plugin-config", "Plugins", "Get or set a plugin config variable")
+{
+}
+
+std::vector<Command::Arg> PluginConfigCommand::args() const
+{
+    return {
+        { "plugin",     true    },
+        { "variable",   false   },
+        { "value",      false   }
+    };
+}
+
+std::vector<Command::Property> PluginConfigCommand::properties() const
+{
+    return {{ "plugin", { nlohmann::json::value_t::string }}};
+}
+
+nlohmann::json PluginConfigCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    auto object = nlohmann::json::object({
+        { "plugin", args.arg(0) }
+    });
+
+    if (args.length() >= 2U) {
+        object.push_back({"variable", args.arg(1)});
+
+        if (args.length() == 3U)
+            object.push_back({"value", args.arg(2)});
+    }
+
+    return object;
+}
+
+nlohmann::json PluginConfigCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    auto var = request.find("variable");
+
+    if (var != request.end() && var->is_string())
+        throw InvalidPropertyError("variable", nlohmann::json::value_t::string, var->type());
+
+    auto value = request.find("value");
+
+    if (value != request.end())
+        return execSet(irccd, request, var->dump(), value->dump());
+
+    return execGet(irccd, request, var);
+}
+
+void PluginConfigCommand::result(Irccdctl &irccdctl, const nlohmann::json &response) const
+{
+    Command::result(irccdctl, response);
+
+    auto it = response.find("variables");
+
+    if (it == response.end() || !it->is_object())
+        return;
+
+    if (it->size() > 1U)
+        for (auto v = it->begin(); v != it->end(); ++v)
+            std::cout << std::setw(16) << std::left << v.key() << " : " << v->dump() << std::endl;
+    else
+        std::cout << it->begin()->dump() << std::endl;
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-config.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,73 @@
+/*
+ * cmd-plugin-config.hpp -- implementation of plugin-config command
+ *
+ * Copyright (c) 2013-2016 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_CMD_PLUGIN_CONFIG_HPP
+#define IRCCD_CMD_PLUGIN_CONFIG_HPP
+
+/**
+ * \file cmd-plugin-config.hpp
+ * \brief Implementation of plugin-config transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of plugin-config transport command.
+ */
+class PluginConfigCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT PluginConfigCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+
+    /**
+     * \copydoc Command::result
+     */
+    IRCCD_EXPORT void result(Irccdctl &irccdctl, const nlohmann::json &response) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_PLUGIN_CONFIG_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-info.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,93 @@
+/*
+ * cmd-plugin-info.cpp -- implementation of plugin-info command
+ *
+ * Copyright (c) 2013-2016 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 <iostream>
+
+#include "cmd-plugin-info.hpp"
+#include "irccd.hpp"
+#include "plugin.hpp"
+#include "service-plugin.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+namespace command {
+
+PluginInfoCommand::PluginInfoCommand()
+    : Command("plugin-info", "Plugins", "Get plugin information")
+{
+}
+
+std::vector<Command::Arg> PluginInfoCommand::args() const
+{
+    return {{ "plugin", true }};
+}
+
+std::vector<Command::Property> PluginInfoCommand::properties() const
+{
+    return {{ "plugin", { nlohmann::json::value_t::string }}};
+}
+
+nlohmann::json PluginInfoCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({{ "plugin", args.arg(0) }});
+}
+
+nlohmann::json PluginInfoCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    auto plugin = irccd.plugins().require(request.at("plugin").get<std::string>());
+
+    return nlohmann::json::object({
+        { "author",     plugin->author()    },
+        { "license",    plugin->license()   },
+        { "summary",    plugin->summary()   },
+        { "version",    plugin->version()   }
+    });
+}
+
+void PluginInfoCommand::result(Irccdctl &irccdctl, const nlohmann::json &result) const
+{
+    Command::result(irccdctl, result);
+
+    auto it = result.find("status");
+
+    if (!it->is_boolean() || !*it)
+        return;
+
+    auto get = [&] (auto key) -> std::string {
+        auto v = result.find(key);
+
+        if (v == result.end() || !v->is_primitive())
+            return "";
+
+        return v->dump();
+    };
+
+    std::cout << std::boolalpha;
+    std::cout << "Author         : " << get("author") << std::endl;
+    std::cout << "License        : " << get("license") << std::endl;
+    std::cout << "Summary        : " << get("summary") << std::endl;
+    std::cout << "Version        : " << get("version") << std::endl;
+}
+
+} // !command
+
+} // !irccd
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-info.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,73 @@
+/*
+ * cmd-plugin-info.hpp -- implementation of plugin-info command
+ *
+ * Copyright (c) 2013-2016 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_CMD_PLUGIN_INFO_HPP
+#define IRCCD_CMD_PLUGIN_INFO_HPP
+
+/**
+ * \file cmd-plugin-info.hpp
+ * \brief Implementation of plugin-info transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of plugin-info transport command.
+ */
+class PluginInfoCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT PluginInfoCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+
+    /**
+     * \copydoc Command::result
+     */
+    IRCCD_EXPORT void result(Irccdctl &irccdctl, const nlohmann::json &response) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_PLUGIN_INFO_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-list.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,62 @@
+/*
+ * cmd-plugin-list.cpp -- implementation of plugin-list transport command
+ *
+ * Copyright (c) 2013-2016 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 <iostream>
+
+#include "cmd-plugin-list.hpp"
+#include "irccd.hpp"
+#include "plugin.hpp"
+#include "service-plugin.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+namespace command {
+
+PluginListCommand::PluginListCommand()
+    : Command("plugin-list", "Plugins", "Get the list of loaded plugins")
+{
+}
+
+nlohmann::json PluginListCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    auto response = Command::exec(irccd, request);
+    auto list = nlohmann::json::array();
+
+    for (const auto &plugin : irccd.plugins().list())
+        list += plugin->name();
+
+    response.push_back({"list", std::move(list)});
+
+    return response;
+}
+
+void PluginListCommand::result(Irccdctl &irccdctl, const nlohmann::json &object) const
+{
+    Command::result(irccdctl, object);
+
+    auto it = object.find("list");
+
+    if (it != object.end() && it->is_array())
+        for (const auto &n : *it)
+            std::cout << n.dump() << std::endl;
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-list.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,58 @@
+/*
+ * cmd-plugin-list.hpp -- implementation of plugin-list transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_PLUGIN_LIST_HPP
+#define IRCCD_CMD_PLUGIN_LIST_HPP
+
+/**
+ * \file cmd-plugin-list.hpp
+ * \brief Implementation of plugin-list transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of plugin-list transport command.
+ */
+class PluginListCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT PluginListCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+
+    /**
+     * \copydoc Command::result
+     */
+    IRCCD_EXPORT void result(Irccdctl &irccdctl, const nlohmann::json &response) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_PLUGIN_LIST_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-load.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,59 @@
+/*
+ * cmd-plugin-load.cpp -- implementation of plugin-load transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-plugin-load.hpp"
+#include "irccd.hpp"
+#include "service-plugin.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+namespace command {
+
+PluginLoadCommand::PluginLoadCommand()
+    : Command("plugin-load", "Plugins", "Load a plugin")
+{
+}
+
+std::vector<Command::Arg> PluginLoadCommand::args() const
+{
+    return {{ "plugin", true }};
+}
+
+std::vector<Command::Property> PluginLoadCommand::properties() const
+{
+    return {{ "plugin", { nlohmann::json::value_t::string }}};
+}
+
+nlohmann::json PluginLoadCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({{ "plugin", args.arg(0) }});
+}
+
+nlohmann::json PluginLoadCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.plugins().load(request["plugin"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-load.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-plugin-load.hpp -- implementation of plugin-load transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_PLUGIN_LOAD_HPP
+#define IRCCD_CMD_PLUGIN_LOAD_HPP
+
+/**
+ * \file cmd-plugin-load.hpp
+ * \brief Implementation of plugin-load transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of plugin-load transport command.
+ */
+class PluginLoadCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT PluginLoadCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_PLUGIN_LOAD_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-reload.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,60 @@
+/*
+ * cmd-plugin-reload.cpp -- implementation of plugin-reload transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-plugin-reload.hpp"
+#include "irccd.hpp"
+#include "plugin.hpp"
+#include "service-plugin.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+namespace command {
+
+PluginReloadCommand::PluginReloadCommand()
+    : Command("plugin-reload", "Plugins", "Reload a plugin")
+{
+}
+
+std::vector<Command::Arg> PluginReloadCommand::args() const
+{
+    return {{ "plugin", true }};
+}
+
+std::vector<Command::Property> PluginReloadCommand::properties() const
+{
+    return {{ "plugin", { nlohmann::json::value_t::string }}};
+}
+
+nlohmann::json PluginReloadCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({{ "plugin", args.arg(0) }});
+}
+
+nlohmann::json PluginReloadCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.plugins().require(request["plugin"])->onReload(irccd);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-reload.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-plugin-reload.hpp -- implementation of plugin-reload transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_PLUGIN_RELOAD_HPP
+#define IRCCD_CMD_PLUGIN_RELOAD_HPP
+
+/**
+ * \file cmd-plugin-reload.hpp
+ * \brief Implementation of plugin-reload transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of plugin-reload transport command.
+ */
+class PluginReloadCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT PluginReloadCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_PLUGIN_RELOAD_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-unload.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,59 @@
+/*
+ * cmd-plugin-unload.cpp -- implementation of plugin-unload transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-plugin-unload.hpp"
+#include "irccd.hpp"
+#include "service-plugin.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+namespace command {
+
+PluginUnloadCommand::PluginUnloadCommand()
+    : Command("plugin-unload", "Plugins", "Unload a plugin")
+{
+}
+
+std::vector<Command::Arg> PluginUnloadCommand::args() const
+{
+    return {{ "plugin", true }};
+}
+
+std::vector<Command::Property> PluginUnloadCommand::properties() const
+{
+    return {{ "plugin", { nlohmann::json::value_t::string }}};
+}
+
+nlohmann::json PluginUnloadCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({{ "plugin", args.arg(0) }});
+}
+
+nlohmann::json PluginUnloadCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.plugins().unload(request["plugin"].get<std::string>());
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-plugin-unload.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-plugin-unload.hpp -- implementation of plugin-unload transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_PLUGIN_UNLOAD_HPP
+#define IRCCD_CMD_PLUGIN_UNLOAD_HPP
+
+/**
+ * \file cmd-plugin-unload.hpp
+ * \brief Implementation of plugin-unload transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of plugin-unload transport command.
+ */
+class PluginUnloadCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT PluginUnloadCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_PLUGIN_UNLOAD_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-cmode.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,66 @@
+/*
+ * cmd-server-cmode.cpp -- implementation of server-cmode transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-cmode.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerChannelModeCommand::ServerChannelModeCommand()
+    : Command("server-cmode", "Server", "Change a channel mode")
+{
+}
+
+std::vector<Command::Arg> ServerChannelModeCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "channel",    true },
+        { "mode",       true }
+    };
+}
+
+std::vector<Command::Property> ServerChannelModeCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }},
+        { "mode",       { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerChannelModeCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"].get<std::string>())->cmode(
+        request["channel"].get<std::string>(),
+        request["mode"].get<std::string>()
+    );
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-cmode.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,63 @@
+/*
+ * cmd-server-cmode.hpp -- implementation of server-cmode transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_CMODE_HPP
+#define IRCCD_CMD_SERVER_CMODE_HPP
+
+/**
+ * \file cmd-server-cmode.hpp
+ * \brief Implementation of server-cmode transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-cmode transport command.
+ */
+class ServerChannelModeCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerChannelModeCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_CMODE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-cnotice.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,65 @@
+/*
+ * cmd-server-cnotice.cpp -- implementation of server-cnotice transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-cnotice.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerChannelNoticeCommand::ServerChannelNoticeCommand()
+    : Command("server-cnotice", "Server", "Send a channel notice")
+{
+}
+
+std::vector<Command::Arg> ServerChannelNoticeCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "channel",    true },
+        { "message",    true }
+    };
+}
+
+std::vector<Command::Property> ServerChannelNoticeCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }},
+        { "message",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerChannelNoticeCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"].get<std::string>())->cnotice(
+        request["channel"].get<std::string>(),
+        request["message"].get<std::string>()
+    );
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-cnotice.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,72 @@
+/*
+ * cmd-server-cnotice.hpp -- implementation of server-cnotice transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_CNOTICE_HPP
+#define IRCCD_CMD_SERVER_CNOTICE_HPP
+
+/**
+ * \file cmd-server-cnotice.hpp
+ * \brief Implementation of server-cnotice transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-cnotice transport command.
+ *
+ * Send a channel notice to the specified channel.
+ *
+ * {
+ *   "command": "server-cnotice",
+ *   "server": "the server name",
+ *   "channel": "name",
+ *   "message": "the message"
+ * }
+ */
+class ServerChannelNoticeCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerChannelNoticeCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_CNOTICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-connect.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,85 @@
+/*
+ * cmd-server-connect.cpp -- implementation of server-connect transport command
+ *
+ * Copyright (c) 2013-2016 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 <limits>
+
+#include <format.h>
+
+#include "cmd-server-connect.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+#include "util.hpp"
+
+using namespace fmt::literals;
+
+using json = nlohmann::json;
+
+namespace irccd {
+
+namespace command {
+
+ServerConnectCommand::ServerConnectCommand()
+    : Command("server-connect", "Server", "Connect to a server")
+{
+}
+
+std::vector<Command::Option> ServerConnectCommand::options() const
+{
+    return {
+        { "command",    "c", "command",     "char",     "command character to use"  },
+        { "nickname",   "n", "nickname",    "nickname", "nickname to use"           },
+        { "realname",   "r", "realname",    "realname", "realname to use"           },
+        { "sslverify",  "S", "ssl-verify",  "",         "verify SSL"                },
+        { "ssl",        "s", "ssl",         "",         "connect with SSL"          },
+        { "username",   "u", "username",    "",         "username to use"           }
+    };
+}
+
+std::vector<Command::Arg> ServerConnectCommand::args() const
+{
+    return {
+        { "id",     true    },
+        { "host",   true    },
+        { "port",   false   }
+    };
+}
+
+std::vector<Command::Property> ServerConnectCommand::properties() const
+{
+    return {
+        { "name",   { json::value_t::string }},
+        { "host",   { json::value_t::string }}
+    };
+}
+
+json ServerConnectCommand::exec(Irccd &irccd, const json &request) const
+{
+    auto server = Server::fromJson(request);
+
+    if (irccd.servers().has(server->name()))
+        throw std::invalid_argument("server '{}' already exists"_format(server->name()));
+
+    irccd.servers().add(std::move(server));
+
+    return Command::exec(irccd, request);
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-connect.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-connect.hpp -- implementation of server-connect transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_CONNECT_HPP
+#define IRCCD_CMD_SERVER_CONNECT_HPP
+
+/**
+ * \file cmd-server-connect.hpp
+ * \brief Implementation of server-connect transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-connect transport command.
+ */
+class ServerConnectCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerConnectCommand();
+
+    /**
+     * \copydoc Command::options
+     */
+    IRCCD_EXPORT std::vector<Option> options() const override;
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_CONNECT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-disconnect.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,52 @@
+/*
+ * cmd-server-disconnect.cpp -- implementation of server-disconnect transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-disconnect.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerDisconnectCommand::ServerDisconnectCommand()
+    : Command("server-disconnect", "Server", "Disconnect one or more servers")
+{
+}
+
+std::vector<Command::Arg> ServerDisconnectCommand::args() const
+{
+    return {{ "server", false }};
+}
+
+nlohmann::json ServerDisconnectCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    auto it = request.find("server");
+
+    if (it == request.end())
+        irccd.servers().clear();
+    else
+        irccd.servers().remove(*it);
+
+    return Command::exec(irccd, request);
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-disconnect.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,60 @@
+/*
+ * cmd-server-disconnect.hpp -- implementation of server-disconnect transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_DISCONNECT_HPP
+#define IRCCD_CMD_SERVER_DISCONNECT_HPP
+
+/**
+ * \file cmd-server-disconnect.hpp
+ * \brief Implementation of server-disconnect transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-disconnect transport command.
+ */
+class ServerDisconnectCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerDisconnectCommand();
+
+    /**
+     * Get list of arguments required.
+     *
+     * \return the arguments required
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_DISCONNECT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-info.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,114 @@
+/*
+ * cmd-server-info.cpp -- implementation of server-info transport command
+ *
+ * Copyright (c) 2013-2016 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 <iostream>
+
+#include "cmd-server-info.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerInfoCommand::ServerInfoCommand()
+    : Command("server-info", "Server", "Get server information")
+{
+}
+
+std::vector<Command::Arg> ServerInfoCommand::args() const
+{
+    return {{ "server", true }};
+}
+
+std::vector<Command::Property> ServerInfoCommand::properties() const
+{
+    return {{ "server", { nlohmann::json::value_t::string }}};
+}
+
+nlohmann::json ServerInfoCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return {{ "server", args.args()[0] }};
+}
+
+nlohmann::json ServerInfoCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    auto response = Command::exec(irccd, request);
+    auto server = irccd.servers().require(request["server"]);
+
+    // General stuff.
+    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::SslVerify)
+        response.push_back({"sslVerify", true});
+
+    return response;
+}
+
+void ServerInfoCommand::result(Irccdctl &irccdctl, const nlohmann::json &response) const
+{
+    Command::result(irccdctl, response);
+
+    auto get = [&] (auto key) -> std::string {
+        auto v = response.find(key);
+
+        if (v == response.end() || !v->is_primitive())
+            return "";
+
+        return v->dump();
+    };
+
+    // Server information.
+    std::cout << std::boolalpha;
+    std::cout << "Name           : " << get("name") << std::endl;
+    std::cout << "Host           : " << get("host") << std::endl;
+    std::cout << "Port           : " << get("port") << std::endl;
+    std::cout << "Ipv6           : " << get("ipv6") << std::endl;
+    std::cout << "SSL            : " << get("ssl") << std::endl;
+    std::cout << "SSL verified   : " << get("sslVerify") << std::endl;
+
+    // Channels.
+    std::cout << "Channels       : ";
+
+    if (response.count("channels") != 0)
+        for (const auto &v : response["channels"])
+            std::cout << v.dump() << " ";
+
+    std::cout << std::endl;
+
+    // Identity.
+    std::cout << "Nickname       : " << get("nickname") << std::endl;
+    std::cout << "User name      : " << get("username") << std::endl;
+    std::cout << "Real name      : " << get("realname") << std::endl;
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-info.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,73 @@
+/*
+ * cmd-server-info.hpp -- implementation of server-info transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_INFO_HPP
+#define IRCCD_CMD_SERVER_INFO_HPP
+
+/**
+ * \file cmd-server-info.hpp
+ * \brief Implementation of server-info transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-info transport command.
+ */
+class ServerInfoCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerInfoCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+
+    /**
+     * \copydoc Command::result
+     */
+    IRCCD_EXPORT void result(Irccdctl &irccdctl, const nlohmann::json &response) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_INFO_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-invite.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,72 @@
+/*
+ * cmd-server-invite.cpp -- implementation of server-invite transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-invite.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerInviteCommand::ServerInviteCommand()
+    : Command("server-invite", "Server", "Invite someone into a channel")
+{
+}
+
+std::vector<Command::Arg> ServerInviteCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "nickname",   true },
+        { "channel",    true }
+    };
+}
+
+std::vector<Command::Property> ServerInviteCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "target",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerInviteCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.args()[0] },
+        { "target",     args.args()[1] },
+        { "channel",    args.args()[2] }
+    });
+}
+
+nlohmann::json ServerInviteCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->invite(request["target"], request["channel"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-invite.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-invite.hpp -- implementation of server-invite transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_INVITE_HPP
+#define IRCCD_CMD_SERVER_INVITE_HPP
+
+/**
+ * \file cmd-server-invite.hpp
+ * \brief Implementation of server-invite transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-invite transport command.
+ */
+class ServerInviteCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerInviteCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_INVITE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-join.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,83 @@
+/*
+ * cmd-server-join.cpp -- implementation of server-join transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-join.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerJoinCommand::ServerJoinCommand()
+    : Command("server-join", "Server", "Join a channel")
+{
+}
+
+std::vector<Command::Arg> ServerJoinCommand::args() const
+{
+    return {
+        { "server",     true    },
+        { "channel",    true    },
+        { "password",   false   }
+    };
+}
+
+std::vector<Command::Property> ServerJoinCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerJoinCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    auto req = nlohmann::json::object({
+        { "server",     args.args()[0] },
+        { "channel",    args.args()[1] }
+    });
+
+    if (args.length() == 3)
+        req.push_back({"password", args.args()[2]});
+
+    return req;
+}
+
+nlohmann::json ServerJoinCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    std::string password;
+
+    if (request.find("password") != request.end())
+        password = request["password"];
+
+    irccd.servers().require(
+        request.at("server").get<std::string>())->join(
+        request.at("channel").get<std::string>(),
+        password
+    );
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-join.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-join.hpp -- implementation of server-join transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_JOIN_HPP
+#define IRCCD_CMD_SERVER_JOIN_HPP
+
+/**
+ * \file cmd-server-join.hpp
+ * \brief Implementation of server-join transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-join transport command.
+ */
+class ServerJoinCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerJoinCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_JOIN_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-kick.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,81 @@
+/*
+ * cmd-server-kick.cpp -- implementation of server-kick transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-kick.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerKickCommand::ServerKickCommand()
+    : Command("server-kick", "Server", "Kick someone from a channel")
+{
+}
+
+std::vector<Command::Arg> ServerKickCommand::args() const
+{
+    return {
+        { "server",     true    },
+        { "target",     true    },
+        { "channel",    true    },
+        { "reason",     false   }
+    };
+}
+
+std::vector<Command::Property> ServerKickCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "target",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerKickCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    auto req = nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "target",     args.arg(1) },
+        { "channel",    args.arg(2) }
+    });
+
+    if (args.length() == 4)
+        req.push_back({"reason", args.arg(3)});
+
+    return req;
+}
+
+nlohmann::json ServerKickCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->kick(
+        request["target"],
+        request["channel"],
+        request.count("reason") > 0 ? request["reason"] : ""
+    );
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-kick.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-kick.hpp -- implementation of server-kick transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_KICK_HPP
+#define IRCCD_CMD_SERVER_KICK_HPP
+
+/**
+ * \file cmd-server-kick.hpp
+ * \brief Implementation of server-kick transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-kick transport command.
+ */
+class ServerKickCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerKickCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_KICK_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-list.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,62 @@
+/*
+ * cmd-server-list.cpp -- implementation of server-list transport command
+ *
+ * Copyright (c) 2013-2016 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 <iostream>
+
+#include "cmd-server-list.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerListCommand::ServerListCommand()
+    : Command("server-list", "Server", "Get the list of servers")
+{
+}
+
+nlohmann::json ServerListCommand::exec(Irccd &irccd, const nlohmann::json &) const
+{
+    auto json = nlohmann::json::object();
+    auto list = nlohmann::json::array();
+
+    for (const auto &server : irccd.servers().servers())
+        list.push_back(server->name());
+
+    json.push_back({"list", std::move(list)});
+
+    return json;
+}
+
+void ServerListCommand::result(Irccdctl &, const nlohmann::json &response) const
+{
+    auto list = response.find("list");
+
+    if (list == response.end())
+        return;
+
+    for (auto v : *list)
+        if (v.is_string())
+            std::cout << v.get<std::string>() << std::endl;
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-list.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,58 @@
+/*
+ * cmd-server-list.hpp -- implementation of server-list transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_LIST_HPP
+#define IRCCD_CMD_SERVER_LIST_HPP
+
+/**
+ * \file cmd-server-list.hpp
+ * \brief Implementation of server-list transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-list transport command.
+ */
+class ServerListCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerListCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+
+    /**
+     * \copydoc Command::result
+     */
+    IRCCD_EXPORT void result(Irccdctl &irccdctl, const nlohmann::json &response) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_LIST_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-me.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,71 @@
+/*
+ * cmd-server-me.cpp -- implementation of server-me transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-me.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerMeCommand::ServerMeCommand()
+    : Command("server-me", "Server", "Send an action emote")
+{
+}
+
+std::vector<Command::Arg> ServerMeCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "target",     true },
+        { "message",    true }
+    };
+}
+
+std::vector<Command::Property> ServerMeCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "target",     { nlohmann::json::value_t::string }},
+        { "message",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerMeCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "target",     args.arg(1) },
+        { "message",    args.arg(2) }
+    });
+}
+
+nlohmann::json ServerMeCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->me(request["target"], request["message"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-me.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-me.hpp -- implementation of server-me transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_ME_HPP
+#define IRCCD_CMD_SERVER_ME_HPP
+
+/**
+ * \file cmd-server-me.hpp
+ * \brief Implementation of server-me transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-me transport command.
+ */
+class ServerMeCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerMeCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_ME_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-message.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,71 @@
+/*
+ * cmd-server-message.cpp -- implementation of server-message transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-message.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerMessageCommand::ServerMessageCommand()
+    : Command("server-message", "Server", "Send a message")
+{
+}
+
+std::vector<Command::Arg> ServerMessageCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "target",     true },
+        { "message",    true }
+    };
+}
+
+std::vector<Command::Property> ServerMessageCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "target",     { nlohmann::json::value_t::string }},
+        { "message",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerMessageCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "target",     args.arg(1) },
+        { "message",    args.arg(2) }
+    });
+}
+
+nlohmann::json ServerMessageCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->me(request["target"], request["message"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-message.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-message.hpp -- implementation of server-message transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_MESSAGE_HPP
+#define IRCCD_CMD_SERVER_MESSAGE_HPP
+
+/**
+ * \file cmd-server-message.hpp
+ * \brief Implementation of server-message transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-message transport command.
+ */
+class ServerMessageCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerMessageCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_MESSAGE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-mode.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-mode.cpp -- implementation of server-mode transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-mode.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerModeCommand::ServerModeCommand()
+    : Command("server-mode", "Server", "Change your mode")
+{
+}
+
+std::vector<Command::Arg> ServerModeCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "mode",       true }
+    };
+}
+
+std::vector<Command::Property> ServerModeCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "mode",       { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerModeCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "mode",       args.arg(1) }
+    });
+}
+
+nlohmann::json ServerModeCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->mode(request["mode"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-mode.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-mode.hpp -- implementation of server-mode transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_MODE_HPP
+#define IRCCD_CMD_SERVER_MODE_HPP
+
+/**
+ * \file cmd-server-mode.hpp
+ * \brief Implementation of server-mode transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-mode transport command.
+ */
+class ServerModeCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerModeCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_MODE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-nick.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-nick.cpp -- implementation of server-nick transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-nick.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerNickCommand::ServerNickCommand()
+    : Command("server-nick", "Server", "Change your nickname")
+{
+}
+
+std::vector<Command::Arg> ServerNickCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "nickname",   true }
+    };
+}
+
+std::vector<Command::Property> ServerNickCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "nickname",   { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerNickCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "nickname",   args.arg(1) }
+    });
+}
+
+nlohmann::json ServerNickCommand::exec(Irccd &irccd, const nlohmann::json &object) const
+{
+    Command::exec(irccd, object);
+
+    irccd.servers().require(object["server"])->setNickname(object["nickname"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-nick.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-nick.hpp -- implementation of server-nick transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_NICK_HPP
+#define IRCCD_CMD_SERVER_NICK_HPP
+
+/**
+ * \file cmd-server-nick.hpp
+ * \brief Implementation of server-nick transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-nick transport command.
+ */
+class ServerNickCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerNickCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_NICK_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-notice.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,71 @@
+/*
+ * cmd-server-notice.cpp -- implementation of server-notice transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-notice.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerNoticeCommand::ServerNoticeCommand()
+    : Command("server-notice", "Server", "Send a private notice")
+{
+}
+
+std::vector<Command::Arg> ServerNoticeCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "target",     true },
+        { "message",    true }
+    };
+}
+
+std::vector<Command::Property> ServerNoticeCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "target",     { nlohmann::json::value_t::string }},
+        { "message",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerNoticeCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "target",     args.arg(1) },
+        { "message",    args.arg(2) }
+    });
+}
+
+nlohmann::json ServerNoticeCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->notice(request["target"], request["message"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-notice.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-notice.hpp -- implementation of server-notice transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_NOTICE_HPP
+#define IRCCD_CMD_SERVER_NOTICE_HPP
+
+/**
+ * \file cmd-server-notice.hpp
+ * \brief Implementation of server-notice transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-notice transport command.
+ */
+class ServerNoticeCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerNoticeCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_NOTICE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-part.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,77 @@
+/*
+ * cmd-server-part.cpp -- implementation of server-part transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-part.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerPartCommand::ServerPartCommand()
+    : Command("server-part", "Server", "Leave a channel")
+{
+}
+
+std::vector<Command::Arg> ServerPartCommand::args() const
+{
+    return {
+        { "server",     true    },
+        { "channel",    true    },
+        { "reason",     false    }
+    };
+}
+
+std::vector<Command::Property> ServerPartCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerPartCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    auto req = nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "channel",    args.arg(1) }
+    });
+
+    if (args.length() == 3)
+        req.push_back({"reason", args.arg(2)});
+
+    return req;
+}
+
+nlohmann::json ServerPartCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->part(
+        request["channel"],
+        request.count("reason") > 0 ? request["reason"] : ""
+    );
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-part.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,69 @@
+/*
+ * cmd-server-part.hpp -- implementation of server-part transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_PART_HPP
+#define IRCCD_CMD_SERVER_PART_HPP
+
+/**
+ * \file cmd-server-part.hpp
+ * \brief Implementation of server-part transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \class ServerPart
+ * \brief Implementation of server-part transport command.
+ */
+class ServerPartCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerPartCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_PART_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-reconnect.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,58 @@
+/*
+ * cmd-server-reconnect.cpp -- implementation of server-reconnect transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-reconnect.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerReconnectCommand::ServerReconnectCommand()
+    : Command("server-reconnect", "Server", "Force reconnection of one or more servers")
+{
+}
+
+std::vector<Command::Arg> ServerReconnectCommand::args() const
+{
+    return {{ "server", false }};
+}
+
+nlohmann::json ServerReconnectCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return args.length() == 0 ? nlohmann::json::object() : nlohmann::json::object({ { "server", args.arg(0) } });
+}
+
+nlohmann::json ServerReconnectCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    auto server = request.find("server");
+
+    if (server != request.end() && server->is_string())
+        irccd.servers().require(*server)->reconnect();
+    else
+        for (auto &server : irccd.servers().servers())
+            server->reconnect();
+
+    return nullptr;
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-reconnect.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,63 @@
+/*
+ * cmd-server-reconnect.hpp -- implementation of server-reconnect transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_RECONNECT_HPP
+#define IRCCD_CMD_SERVER_RECONNECT_HPP
+
+/**
+ * \file cmd-server-reconnect.hpp
+ * \brief Implementation of server-reconnect transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-reconnect transport command.
+ */
+class ServerReconnectCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerReconnectCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_RECONNECT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-topic.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,71 @@
+/*
+ * cmd-server-topic.cpp -- implementation of server-topic transport command
+ *
+ * Copyright (c) 2013-2016 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 "cmd-server-topic.hpp"
+#include "irccd.hpp"
+#include "server.hpp"
+#include "service-server.hpp"
+
+namespace irccd {
+
+namespace command {
+
+ServerTopicCommand::ServerTopicCommand()
+    : Command("server-topic", "Server", "Change a channel topic")
+{
+}
+
+std::vector<Command::Arg> ServerTopicCommand::args() const
+{
+    return {
+        { "server",     true },
+        { "channel",    true },
+        { "topic",      true }
+    };
+}
+
+std::vector<Command::Property> ServerTopicCommand::properties() const
+{
+    return {
+        { "server",     { nlohmann::json::value_t::string }},
+        { "channel",    { nlohmann::json::value_t::string }},
+        { "topic",      { nlohmann::json::value_t::string }}
+    };
+}
+
+nlohmann::json ServerTopicCommand::request(Irccdctl &, const CommandRequest &args) const
+{
+    return nlohmann::json::object({
+        { "server",     args.arg(0) },
+        { "channel",    args.arg(1) },
+        { "topic",      args.arg(2) }
+    });
+}
+
+nlohmann::json ServerTopicCommand::exec(Irccd &irccd, const nlohmann::json &request) const
+{
+    Command::exec(irccd, request);
+
+    irccd.servers().require(request["server"])->topic(request["channel"], request["topic"]);
+
+    return nlohmann::json::object();
+}
+
+} // !command
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/cmd-server-topic.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * cmd-server-topic.hpp -- implementation of server-topic transport command
+ *
+ * Copyright (c) 2013-2016 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_CMD_SERVER_TOPIC_HPP
+#define IRCCD_CMD_SERVER_TOPIC_HPP
+
+/**
+ * \file cmd-server-topic.hpp
+ * \brief Implementation of server-topic transport command.
+ */
+
+#include "command.hpp"
+
+namespace irccd {
+
+namespace command {
+
+/**
+ * \brief Implementation of server-topic transport command.
+ */
+class ServerTopicCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    IRCCD_EXPORT ServerTopicCommand();
+
+    /**
+     * \copydoc Command::args
+     */
+    IRCCD_EXPORT std::vector<Arg> args() const override;
+
+    /**
+     * \copydoc Command::properties
+     */
+    IRCCD_EXPORT std::vector<Property> properties() const override;
+
+    /**
+     * \copydoc Command::request
+     */
+    IRCCD_EXPORT nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const override;
+
+    /**
+     * \copydoc Command::exec
+     */
+    IRCCD_EXPORT nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const override;
+};
+
+} // !command
+
+} // !irccd
+
+#endif // !IRCCD_CMD_SERVER_TOPIC_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/command.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,272 @@
+/*
+ * command.cpp -- remote command
+ *
+ * Copyright (c) 2013-2016 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 <iomanip>
+#include <numeric>
+#include <sstream>
+
+#include <format.h>
+
+#include "command.hpp"
+#include "logger.hpp"
+#include "system.hpp"
+
+using namespace std::string_literals;
+
+using namespace fmt::literals;
+
+using json = nlohmann::json;
+
+namespace irccd {
+
+namespace {
+
+/*
+ * typeName
+ * ------------------------------------------------------------------
+ *
+ * Convert a JSON value type to string for convenience.
+ */
+std::string typeName(nlohmann::json::value_t type) noexcept
+{
+    switch (type) {
+    case nlohmann::json::value_t::array:
+        return "array";
+    case nlohmann::json::value_t::boolean:
+        return "bool";
+    case nlohmann::json::value_t::number_float:
+        return "float";
+    case nlohmann::json::value_t::number_integer:
+        return "integer";
+    case nlohmann::json::value_t::number_unsigned:
+        return "unsigned";
+    case nlohmann::json::value_t::null:
+        return "null";
+    case nlohmann::json::value_t::object:
+        return "object";
+    case nlohmann::json::value_t::string:
+        return "string";
+    default:
+        return "";
+    }
+}
+
+/*
+ * typeNameList
+ * ------------------------------------------------------------------
+ *
+ * Construct a list of names to send a convenient error message if properties
+ * are invalid, example: string, int or bool expected.
+ */
+
+std::string typeNameList(const std::vector<json::value_t> &types)
+{
+    std::ostringstream oss;
+
+    if (types.size() == 1)
+        return typeName(types[0]);
+
+    for (std::size_t i = 0; i < types.size(); ++i) {
+        oss << typeName(types[i]);
+
+        if (i == types.size() - 2)
+            oss << " or ";
+        else if (i < types.size() - 1)
+            oss << ", ";
+    }
+
+    return oss.str();
+}
+
+} // !namespace
+
+/*
+ * JSON errors
+ * ------------------------------------------------------------------
+ */
+
+MissingPropertyError::MissingPropertyError(std::string name, std::vector<nlohmann::json::value_t> types)
+    : m_name(std::move(name))
+    , m_types(std::move(types))
+{
+    m_message = "missing '" + m_name + "' property (" + typeNameList(m_types) + " expected)";
+}
+
+InvalidPropertyError::InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result)
+    : m_name(std::move(name))
+    , m_expected(expected)
+    , m_result(result)
+{
+    m_message += "invalid '" + m_name + "' property ";
+    m_message += "(" + typeName(expected) + " expected, ";
+    m_message += "got " + typeName(result) + ")";
+}
+
+PropertyRangeError::PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value)
+    : m_name(std::move(name))
+    , m_min(min)
+    , m_max(max)
+    , m_value(value)
+{
+    assert(value < min || value > max);
+
+    m_message += "property '" + m_name + "' is out of range ";
+    m_message += std::to_string(min) + ".." + std::to_string(max) + ", got " + std::to_string(value);
+}
+
+PropertyError::PropertyError(std::string name, std::string message)
+    : m_name(std::move(name))
+{
+    m_message += "property '" + m_name + "': " + message;
+}
+
+/*
+ * Command implementation
+ * ------------------------------------------------------------------
+ */
+
+std::string Command::usage() const
+{
+    std::ostringstream oss;
+
+    oss << m_name << " ";
+
+    // Options.
+    auto optlist = options();
+
+    if (optlist.size() > 0) {
+        for (const auto &opt : optlist) {
+            oss << "[";
+
+            /*
+             * Long options are too big so only show them in the help
+             * command usage or only if no short option is available.
+             */
+            if (opt.simpleKey().size() > 0)
+                oss << "-" << opt.simpleKey();
+            else if (opt.longKey().size() > 0)
+                oss << " --" << opt.longKey();
+
+            oss << (opt.arg().empty() ? "" : " ") << opt.arg() << "] ";
+        }
+    }
+
+    // Arguments.
+    auto argslist = args();
+
+    if (argslist.size() > 0) {
+        for (const auto &arg : argslist)
+            oss << (arg.required() ? "" : "[")
+                << arg.name()
+                << (arg.required() ? "" : "]") << " ";
+    }
+
+    return oss.str();
+}
+
+std::string Command::help() const
+{
+    std::ostringstream oss;
+
+    oss << "usage: " << sys::programName() << " " << m_name;
+
+    // Options summary.
+    if (options().size() > 0)
+        oss << " [options...]";
+
+    // Arguments summary.
+    if (args().size() > 0) {
+        oss << " ";
+
+        for (const auto &arg : args())
+            oss << (arg.required() ? "" : "[") << arg.name() << (arg.required() ? "" : "]") << " ";
+    }
+
+    // Description.
+    oss << "\n\n" << m_description << "\n\n";
+
+    // Options.
+    if (options().size() > 0) {
+        oss << "Options:\n";
+
+        for (const auto &opt : options()) {
+            std::ostringstream optoss;
+
+            // Construct the line for the option in a single string to pad it correctly.
+            optoss << "  ";
+            optoss << (!opt.simpleKey().empty() ? ("-"s + opt.simpleKey() + " ") : "   ");
+            optoss << (!opt.longKey().empty() ? ("--"s + opt.longKey() + " "s) : "");
+            optoss << opt.arg();
+
+            // Add it padded with spaces.
+            oss << std::left << std::setw(28) << optoss.str();
+            oss << opt.description() << "\n";
+        }
+    }
+
+    return oss.str();
+}
+
+unsigned Command::min() const noexcept
+{
+    auto list = args();
+
+    return std::accumulate(list.begin(), list.end(), 0U, [] (unsigned i, const auto &arg) noexcept -> unsigned {
+        return i + (arg.required() ? 1 : 0);
+    });
+}
+
+unsigned Command::max() const noexcept
+{
+    return (unsigned)args().size();
+}
+
+nlohmann::json Command::request(Irccdctl &, const CommandRequest &) const
+{
+    return nlohmann::json::object({});
+}
+
+nlohmann::json Command::exec(Irccd &, const nlohmann::json &request) const
+{
+    // Verify that requested properties are present in the request.
+    for (const auto &prop : properties()) {
+        auto it = request.find(prop.name());
+
+        if (it == request.end())
+            throw std::invalid_argument("missing '{}' property"_format(prop.name()));
+
+        if (std::find(prop.types().begin(), prop.types().end(), it->type()) == prop.types().end()) {
+            auto expected = typeNameList(prop.types());
+            auto got = typeName(it->type());
+
+            throw std::invalid_argument("invalid '{}' property ({} expected, got {})"_format(prop.name(), expected, got));
+        }
+    }
+
+    return nlohmann::json::object({});
+}
+
+void Command::result(Irccdctl &, const nlohmann::json &response) const
+{
+    auto it = response.find("error");
+
+    if (it != response.end() && it->is_string())
+        log::warning() << "irccdctl: " << it->dump() << std::endl;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/command.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,709 @@
+/*
+ * command.hpp -- remote command
+ *
+ * Copyright (c) 2013-2016 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;
+
+/**
+ * \brief A JSON property is missing.
+ */
+class MissingPropertyError : public std::exception {
+private:
+    std::string m_message;
+    std::string m_name;
+    std::vector<nlohmann::json::value_t> m_types;
+
+public:
+    /**
+     * Constructor.
+     */
+    MissingPropertyError(std::string name, std::vector<nlohmann::json::value_t> types);
+
+    /**
+     * Get human error message.
+     *
+     * \return a message
+     */
+    const char *what() const noexcept override
+    {
+        return m_message.c_str();
+    }
+};
+
+/**
+ * \brief A JSON property is invalid
+ */
+class InvalidPropertyError : public std::exception {
+private:
+    std::string m_message;
+    std::string m_name;
+
+    nlohmann::json::value_t m_expected;
+    nlohmann::json::value_t m_result;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param name the property name
+     * \param expected the expected type
+     * \param result the type received
+     */
+    InvalidPropertyError(std::string name, nlohmann::json::value_t expected, nlohmann::json::value_t result);
+
+    /**
+     * Get human error message.
+     *
+     * \return a message
+     */
+    const char *what() const noexcept override
+    {
+        return m_message.c_str();
+    }
+};
+
+/**
+ * \brief Property range error.
+ */
+class PropertyRangeError : public std::exception {
+private:
+    std::string m_message;
+    std::string m_name;
+    std::uint64_t m_min;
+    std::uint64_t m_max;
+    std::uint64_t m_value;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \pre value < min || value > max
+     * \param name the property name
+     * \param min the minimum value
+     * \param max the maximum value
+     * \param value the actual value
+     */
+    PropertyRangeError(std::string name, std::uint64_t min, std::uint64_t max, std::uint64_t value);
+
+    /**
+     * Get human error message.
+     *
+     * \return a message
+     */
+    const char *what() const noexcept override
+    {
+        return m_message.c_str();
+    }
+};
+
+/**
+ * \brief Generic error for JSON properties.
+ */
+class PropertyError : public std::exception {
+private:
+    std::string m_message;
+    std::string m_name;
+
+public:
+    /**
+     * Constructor.
+     *
+     * \param name the property name
+     * \param message the error message
+     */
+    PropertyError(std::string name, std::string message);
+
+    /**
+     * Get human error message.
+     *
+     * \return a message
+     */
+    const char *what() const noexcept override
+    {
+        return m_message.c_str();
+    }
+};
+
+
+/**
+ * \brief Namespace for remote commands.
+ */
+//namespace command {
+
+/**
+ * \brief Command line arguments to irccdctl.
+ *
+ * This class contains the resolved arguments from command line that can apply
+ * to the command.
+ */
+class CommandRequest {
+public:
+    /**
+     * The options given by command line.
+     */
+    using Options = std::multimap<std::string, std::string>;
+
+    /**
+     * Command line arguments in the same order.
+     */
+    using Args = std::vector<std::string>;
+
+private:
+    Options m_options;
+    Args m_args;
+
+public:
+    /**
+     * Construct the request.
+     *
+     * \param options the options
+     * \param args the arguments
+     */
+    inline CommandRequest(Options options, Args args) noexcept
+        : m_options(std::move(options))
+        , m_args(std::move(args))
+    {
+    }
+
+    /**
+     * Get the arguments.
+     *
+     * \return the arguments
+     */
+    inline const Args &args() const noexcept
+    {
+        return m_args;
+    }
+
+    /**
+     * Get the options.
+     *
+     * \return the options
+     */
+    inline const Options &options() const noexcept
+    {
+        return m_options;
+    }
+
+    /**
+     * Get the number of arguments.
+     *
+     * \return the number of arguments
+     */
+    inline unsigned length() const noexcept
+    {
+        return (unsigned)m_args.size();
+    }
+
+    /**
+     * Check if the request has the given option id.
+     *
+     * \param option the option id
+     * \return true if the option is available
+     */
+    inline bool has(const std::string &option) const noexcept
+    {
+        return m_options.count(option) != 0;
+    }
+
+    /**
+     * Get the argument at the specified index.
+     *
+     * \pre index < length()
+     * \param index the argument index
+     * \return the argument
+     */
+    inline const std::string &arg(unsigned index) const noexcept
+    {
+        assert(index < m_args.size());
+
+        return m_args[index];
+    }
+
+    /**
+     * Get the argument or default value if not available.
+     *
+     * \param index the index
+     * \param defaultValue the value if index is out of range
+     * \return the argument
+     */
+    inline std::string argOr(unsigned index, std::string defaultValue) const noexcept
+    {
+        return index < m_args.size() ? m_args[index] : defaultValue;
+    }
+
+    /**
+     * Get the given option by its id.
+     *
+     * \pre has(key)
+     * \param key the option id
+     * \return the option
+     */
+    inline const std::string &option(const std::string &key) const noexcept
+    {
+        assert(m_options.count(key) != 0);
+
+        return m_options.find(key)->second;
+    }
+
+    /**
+     * Get the given option by its id or defaultValue if not found.
+     *
+     * \param key the option id
+     * \param defaultValue the value replacement
+     * \return the option
+     */
+    inline std::string optionOr(const std::string &key, std::string defaultValue) const noexcept
+    {
+        auto it = m_options.find(key);
+
+        if (it == m_options.end())
+            return defaultValue;
+
+        return it->second;
+    }
+};
+
+/**
+ * \brief Invokable command.
+ *
+ * A remote command is a invokable command in the irccd daemon. You can register
+ * dynamically any remote command you like using Application::addCommand.
+ *
+ * The remote command will be usable directly from irccdctl without any other
+ * code.
+ *
+ * A remote command can have options and arguments. Options always come first,
+ * before arguments.
+ *
+ * The command workflow is defined as follow:
+ *
+ * 1. User wants to invoke a command, request() is called and return a JSON
+ *    object containaing the request, it it send to the daemon.
+ *
+ * 2. The daemon receive the request and execute it using exec(). It returns a
+ *    JSON object containint the request result or error if any.
+ *
+ * 3. Finally, the command receives the result in result() function and user can
+ *    manipulate it. For convenience, the default implementation shows the error
+ *    if any.
+ */
+class Command {
+public:
+    /**
+     * \brief Defines available options for this command.
+     */
+    class Option;
+
+    /**
+     * \brief Defines available arguments for this command.
+     */
+    class Arg;
+
+    /**
+     * \brief Defines properties that must be available in the JSON request.
+     */
+    class Property;
+
+private:
+    std::string m_name;
+    std::string m_category;
+    std::string m_description;
+    bool m_visible;
+
+public:
+    /**
+     * Create the remote command.
+     *
+     * \pre name must not be empty
+     * \pre category must not be empty
+     * \param name the command name (e.g. server-list)
+     * \param category the category (e.g. Server)
+     * \param description a one line description with no dots, no new line
+     * \param visible true if the command should be visible without verbosity
+     */
+    inline Command(std::string name,
+                   std::string category,
+                   std::string description,
+                   bool visible = true) noexcept
+        : m_name(std::move(name))
+        , m_category(std::move(category))
+        , m_description(std::move(description))
+        , m_visible(visible)
+    {
+        assert(!m_name.empty());
+        assert(!m_category.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 m_name;
+    }
+
+    /**
+     * Get the command category.
+     *
+     * Irccdctl will sort commands by categories.
+     *
+     * \return the category
+     */
+    inline const std::string &category() const noexcept
+    {
+        return m_category;
+    }
+
+    /**
+     * Get the command description.
+     *
+     * \return the description
+     */
+    inline const std::string &description() const noexcept
+    {
+        return m_description;
+    }
+
+    /**
+     * Hide the command in non-verbose mode.
+     *
+     * \return true if the command should be visible in non-verbose mode
+     */
+    inline bool visible() const noexcept
+    {
+        return m_visible;
+    }
+
+    /**
+     * Return the command documentation usage.
+     *
+     * \return the usage
+     */
+    IRCCD_EXPORT std::string usage() const;
+
+    /**
+     * Return the help message.
+     *
+     * \return the help message
+     */
+    IRCCD_EXPORT std::string help() const;
+
+    /**
+     * Get the supported irccdctl options.
+     *
+     * \return the options
+     */
+    virtual std::vector<Option> options() const
+    {
+        return {};
+    }
+
+    /**
+     * Get the supported arguments.
+     *
+     * \return the arguments
+     */
+    virtual std::vector<Arg> args() const
+    {
+        return {};
+    }
+
+    /**
+     * Get the properties required in the JSON request.
+     *
+     * Default implementation returns empty list.
+     *
+     * \return the required properties
+     * \note Put only **required** properties
+     */
+    virtual std::vector<Property> properties() const
+    {
+        return {};
+    }
+
+    /**
+     * Get the minimum number of arguments required.
+     *
+     * \return the minimum
+     */
+    IRCCD_EXPORT unsigned min() const noexcept;
+
+    /**
+     * Get the maximum number of arguments required.
+     *
+     * \return the maximum
+     */
+    IRCCD_EXPORT unsigned max() const noexcept;
+
+    /**
+     * Prepare a JSON request to the daemon.
+     *
+     * If the command is local and does not need to send anything to irccd's
+     * instance, return a null JSON value.
+     *
+     * The default implementation just send the command name with no arguments.
+     *
+     * \param irccdctl the irccdctl instance
+     * \param args the command line arguments and options
+     * \return the JSON object to send to the daemon
+     * \post the returned JSON value must be an object
+     */
+    IRCCD_EXPORT virtual nlohmann::json request(Irccdctl &irccdctl, const CommandRequest &args) const;
+
+    /**
+     * Execute the command in the daemon.
+     *
+     * The user can return an object with any properties to forward to the
+     * client. Irccd will automatically add the command name and the appropriate
+     * status code.
+     *
+     * The default return an empty object which indicates success.
+     *
+     * If any exception is thrown from this function, it is forwarded to the
+     * client as error status.
+     *
+     * \param irccd the instance
+     * \param request the JSON request
+     * \return the response
+     */
+    IRCCD_EXPORT virtual nlohmann::json exec(Irccd &irccd, const nlohmann::json &request) const;
+
+    /**
+     * What to do when receiving the response from irccd.
+     *
+     * This default implementation just check for an error string and shows it
+     * if any.
+     *
+     * \param irccdctl the irccdctl instance
+     * \param response the JSON response
+     */
+    IRCCD_EXPORT virtual void result(Irccdctl &irccdctl, const nlohmann::json &response) const;
+};
+
+/**
+ * \brief Option description for a command.
+ */
+class Command::Option {
+private:
+    std::string m_id;
+    std::string m_simple;
+    std::string m_long;
+    std::string m_arg;
+    std::string m_description;
+
+public:
+    /**
+     * Constructor an option description.
+     *
+     * Simple and long keys must not start with '-' or '--', they will be added
+     * automatically.
+     *
+     * If arg is not empty, the option takes an argument.
+     *
+     * \pre id must not be empty
+     * \pre at least simpleKey or longKey must not be empty
+     * \pre description must not be empty
+     * \param id the option id
+     * \param simpleKey the key the option key
+     * \param longKey the long option name
+     * \param arg the argument name if needed
+     * \param description the description
+     */
+    inline Option(std::string id,
+              std::string simpleKey,
+              std::string longKey,
+              std::string arg,
+              std::string description) noexcept
+        : m_id(std::move(id))
+        , m_simple(std::move(simpleKey))
+        , m_long(std::move(longKey))
+        , m_arg(std::move(arg))
+        , m_description(std::move(description))
+    {
+        assert(!m_id.empty());
+        assert(!m_simple.empty() || !m_long.empty());
+        assert(!m_description.empty());
+    }
+
+    /**
+     * Get the id.
+     *
+     * \return the id
+     */
+    inline const std::string &id() const noexcept
+    {
+        return m_id;
+    }
+
+    /**
+     * Get the option key.
+     *
+     * \return the key
+     */
+    inline const std::string &simpleKey() const noexcept
+    {
+        return m_simple;
+    }
+
+    /**
+     * Get the long option.
+     *
+     * \return the long option
+     */
+    inline const std::string &longKey() const noexcept
+    {
+        return m_long;
+    }
+
+    /**
+     * Get the option description.
+     *
+     * \return the description
+     */
+    inline const std::string &description() const noexcept
+    {
+        return m_description;
+    }
+
+    /**
+     * Get the option argument name.
+     *
+     * \return the argument name if any
+     */
+    inline const std::string &arg() const noexcept
+    {
+        return m_arg;
+    }
+};
+
+/**
+ * \brief Argument description for command.
+ */
+class Command::Arg {
+private:
+    std::string m_name;
+    bool m_required;
+
+public:
+    /**
+     * Construct an argument.
+     *
+     * \param name the name
+     * \param required true if the argument is required
+     */
+    inline Arg(std::string name, bool required) noexcept
+        : m_name(std::move(name))
+        , m_required(required)
+    {
+    }
+
+    /**
+     * Get the argument name.
+     *
+     * \return the name
+     */
+    inline const std::string &name() const noexcept
+    {
+        return m_name;
+    }
+
+    /**
+     * Tells if the argument is required.
+     *
+     * \return true if required
+     */
+    inline bool required() const noexcept
+    {
+        return m_required;
+    }
+};
+
+/**
+ * \brief Property description for JSON request.
+ */
+class Command::Property {
+private:
+    std::string m_name;
+    std::vector<nlohmann::json::value_t> m_types;
+
+public:
+    /**
+     * Construct the property description.
+     *
+     * \pre !name.empty()
+     * \pre types.size() >= 1
+     * \param name the name
+     * \param types the json types allowed
+     */
+    inline Property(std::string name, std::vector<nlohmann::json::value_t> types = { nlohmann::json::value_t::string }) noexcept
+        : m_name(std::move(name))
+        , m_types(std::move(types))
+    {
+        assert(!m_name.empty());
+        assert(m_types.size() >= 1);
+    }
+
+    /**
+     * Get the property name.
+     *
+     * \return the name
+     */
+    inline const std::string &name() const noexcept
+    {
+        return m_name;
+    }
+
+    /**
+     * Get the property types.
+     *
+     * \return the types
+     */
+    inline const std::vector<nlohmann::json::value_t> &types() const noexcept
+    {
+        return m_types;
+    }
+};
+
+} // !irccd
+
+#endif // !IRCCD_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/config.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,568 @@
+/*
+ * config.cpp -- irccd configuration loader
+ *
+ * Copyright (c) 2013-2016 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 <format.h>
+
+#include "config.hpp"
+#include "fs.hpp"
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "path.hpp"
+#include "plugin.hpp"
+#include "rule.hpp"
+#include "server.hpp"
+#include "service-plugin.hpp"
+#include "sysconfig.hpp"
+#include "transport.hpp"
+#include "util.hpp"
+
+using namespace fmt::literals;
+
+namespace irccd {
+
+namespace {
+
+class IrccdLogFilter : public log::Filter {
+private:
+    std::string convert(const std::string &tmpl, std::string input) const
+    {
+        if (tmpl.empty())
+            return input;
+
+        util::Substitution params;
+
+        params.flags &= ~(util::Substitution::IrcAttrs);
+        params.keywords.emplace("message", std::move(input));
+
+        return util::format(tmpl, params);
+    }
+
+public:
+    std::string m_debug;
+    std::string m_info;
+    std::string m_warning;
+
+    std::string preDebug(std::string input) const override
+    {
+        return convert(m_debug, std::move(input));
+    }
+
+    std::string preInfo(std::string input) const override
+    {
+        return convert(m_info, std::move(input));
+    }
+
+    std::string preWarning(std::string input) const override
+    {
+        return convert(m_warning, std::move(input));
+    }
+};
+
+std::string get(const ini::Document &doc, const std::string &section, const std::string &key)
+{
+    auto its = doc.find(section);
+
+    if (its == doc.end())
+        return "";
+
+    auto ito = its->find(key);
+
+    if (ito == its->end())
+        return "";
+
+    return ito->value();
+}
+
+PluginConfig loadPluginConfig(const ini::Section &sc)
+{
+    PluginConfig config;
+
+    for (const auto &option : sc)
+        config.emplace(option.key(), option.value());
+
+    return config;
+}
+
+std::unique_ptr<log::Logger> loadLogFile(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();
+
+    return std::make_unique<log::FileLogger>(std::move(normal), std::move(errors));
+}
+
+std::unique_ptr<log::Logger> loadLogSyslog()
+{
+#if defined(HAVE_SYSLOG)
+    return std::make_unique<log::SyslogLogger>();
+#else
+    throw std::runtime_error("logs: syslog is not available on this platform");
+#endif // !HAVE_SYSLOG
+}
+
+std::shared_ptr<TransportServer> loadTransportIp(const ini::Section &sc)
+{
+    assert(sc.key() == "transport");
+
+    std::shared_ptr<TransportServer> transport;
+    ini::Section::const_iterator it;
+
+    // Port.
+    int port;
+
+    if ((it = sc.find("port")) == sc.cend())
+        throw std::invalid_argument("transport: missing 'port' parameter");
+
+    try {
+        port = util::toNumber<std::uint16_t>(it->value());
+    } catch (const std::exception &) {
+        throw std::invalid_argument("transport: invalid port number: {}"_format(it->value()));
+    }
+
+    // Address.
+    std::string address = "*";
+
+    if ((it = sc.find("address")) != sc.end())
+        address = it->value();
+
+    // Domain
+    std::uint8_t mode = TransportServerIp::v4;
+
+    if ((it = sc.find("domain")) != sc.end()) {
+        mode = 0;
+
+        for (const auto &v : *it) {
+            if (v == "ipv4")
+                mode |= TransportServerIp::v4;
+            if (v == "ipv6")
+                mode |= TransportServerIp::v6;
+        }
+    }
+
+    // Optional SSL.
+    std::string pkey;
+    std::string cert;
+
+    if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value())) {
+        if ((it = sc.find("certificate")) == sc.end())
+            throw std::invalid_argument("transport: missing 'certificate' parameter");
+
+        cert = it->value();
+
+        if ((it = sc.find("key")) == sc.end())
+            throw std::invalid_argument("transport: missing 'key' parameter");
+
+        pkey = it->value();
+    }
+
+    if (mode == 0)
+        throw std::invalid_argument("transport: domain must at least have ipv4 or ipv6");
+
+    if (pkey.empty())
+        return std::make_shared<TransportServerIp>(address, port, mode);
+
+    return std::make_shared<TransportServerTls>(pkey, cert, address, port, mode);
+}
+
+std::shared_ptr<TransportServer> loadTransportUnix(const ini::Section &sc)
+{
+    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("transport: missing 'path' parameter");
+
+    return std::make_shared<TransportServerLocal>(it->value());
+#else
+    (void)sc;
+
+    throw std::invalid_argument("transport: unix transport not supported on on this platform");
+#endif
+}
+
+std::shared_ptr<TransportServer> loadTransport(const ini::Section &sc)
+{
+    assert(sc.key() == "transport");
+
+    std::shared_ptr<TransportServer> transport;
+    ini::Section::const_iterator it = sc.find("type");
+
+    if (it == sc.end())
+        throw std::invalid_argument("transport: missing 'type' parameter");
+
+    if (it->value() == "ip")
+        transport = loadTransportIp(sc);
+    else if (it->value() == "unix")
+        transport = loadTransportUnix(sc);
+    else
+        throw std::invalid_argument("transport: invalid type given: {}"_format(it->value()));
+
+    if ((it = sc.find("password")) != sc.end())
+        transport->setPassword(it->value());
+
+    return transport;
+}
+
+Rule loadRule(const ini::Section &sc)
+{
+    assert(sc.key() == "rule");
+
+    // Simple converter from std::vector to std::unordered_set.
+    auto toSet = [] (const std::vector<std::string> &v) -> std::unordered_set<std::string> {
+        return std::unordered_set<std::string>(v.begin(), v.end());
+    };
+
+    RuleSet servers, channels, origins, plugins, events;
+    RuleAction action = RuleAction::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.
+    if ((it = sc.find("action")) == sc.end())
+        throw std::invalid_argument("rule: missing 'action'' parameter");
+
+    if (it->value() == "drop")
+        action = RuleAction::Drop;
+    else if (it->value() == "accept")
+        action = RuleAction::Accept;
+    else
+        throw std::invalid_argument("rule: invalid action given: {}"_format(it->value()));
+
+    return Rule(std::move(servers),
+            std::move(channels),
+            std::move(origins),
+            std::move(plugins),
+            std::move(events),
+            action);
+}
+
+std::shared_ptr<Server> loadServer(const ini::Section &sc, const Config &config)
+{
+    assert(sc.key() == "server");
+
+    // Name.
+    ini::Section::const_iterator it;
+
+    if ((it = sc.find("name")) == sc.end())
+        throw std::invalid_argument("server: missing 'name' parameter");
+    else if (!util::isIdentifierValid(it->value()))
+        throw std::invalid_argument("server: invalid identifier: {}"_format(it->value()));
+
+    auto server = std::make_shared<Server>(it->value());
+
+    // Host
+    if ((it = sc.find("host")) == sc.end())
+        throw std::invalid_argument("server {}: missing host"_format(server->name()));
+
+    server->setHost(it->value());
+
+    // Optional password
+    if ((it = sc.find("password")) != sc.end())
+        server->setPassword(it->value());
+
+    // Optional flags
+    if ((it = sc.find("ipv6")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::Ipv6);
+    if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::Ssl);
+    if ((it = sc.find("ssl-verify")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::SslVerify);
+
+    // Optional identity
+    if ((it = sc.find("identity")) != sc.end())
+        config.loadServerIdentity(*server, it->value());
+
+    // Options
+    if ((it = sc.find("auto-rejoin")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::AutoRejoin);
+    if ((it = sc.find("join-invite")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::JoinInvite);
+
+    // Channels
+    if ((it = sc.find("channels")) != sc.end()) {
+        for (const std::string &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;
+
+            //server.channels.push_back(std::move(channel));
+            //server->join()
+            server->join(channel.name, channel.password);
+        }
+    }
+    if ((it = sc.find("command-char")) != sc.end())
+        server->setCommandCharacter(it->value());
+
+    // Reconnect and ping timeout
+    try {
+        if ((it = sc.find("port")) != sc.end())
+            server->setPort(util::toNumber<std::uint16_t>(it->value()));
+        if ((it = sc.find("reconnect-tries")) != sc.end())
+            server->setReconnectTries(util::toNumber<std::int8_t>(it->value()));
+        if ((it = sc.find("reconnect-timeout")) != sc.end())
+            server->setReconnectDelay(util::toNumber<std::uint16_t>(it->value()));
+        if ((it = sc.find("ping-timeout")) != sc.end())
+            server->setPingTimeout(util::toNumber<std::uint16_t>(it->value()));
+    } catch (const std::exception &) {
+        log::warning("server {}: invalid number for {}: {}"_format(server->name(), it->key(), it->value()));
+    }
+
+    return server;
+}
+
+} // !namespace
+
+Config Config::find()
+{
+    for (const auto &path : path::list(path::PathConfig)) {
+        std::string fullpath = path + "irccd.conf";
+
+        if (!fs::isReadable(fullpath))
+            continue;
+
+        try {
+            return Config(fullpath);
+        } catch (const std::exception &ex) {
+            throw std::runtime_error("{}: {}"_format(fullpath, ex.what()));
+        }
+    }
+
+    throw std::runtime_error("no configuration file found");
+}
+
+void Config::loadServerIdentity(Server &server, const std::string &identity) const
+{
+    ini::Document::const_iterator sc = std::find_if(m_document.begin(), m_document.end(), [&] (const auto &sc) {
+        if (sc.key() != "identity")
+            return false;
+
+        auto name = sc.find("name");
+
+        return name != sc.end() && name->value() == identity;
+    });
+
+    if (sc == m_document.end())
+        return;
+
+    ini::Section::const_iterator it;
+
+    if ((it = sc->find("username")) != sc->end())
+        server.setUsername(it->value());
+    if ((it = sc->find("realname")) != sc->end())
+        server.setRealname(it->value());
+    if ((it = sc->find("nickname")) != sc->end())
+        server.setNickname(it->value());
+    if ((it = sc->find("ctcp-version")) != sc->end())
+        server.setCtcpVersion(it->value());
+}
+
+PluginConfig Config::findPluginConfig(const std::string &name) const
+{
+    assert(util::isIdentifierValid(name));
+
+    std::string fullname = std::string("plugin.") + name;
+
+    for (const auto &section : m_document) {
+        if (section.key() != fullname)
+            continue;
+
+        return loadPluginConfig(section);
+    }
+
+    return PluginConfig();
+}
+
+PluginFormats Config::findPluginFormats(const std::string &name) const
+{
+    assert(util::isIdentifierValid(name));
+
+    auto section = m_document.find(std::string("format.") + name);
+
+    if (section == m_document.end())
+        return PluginFormats();
+
+    PluginFormats formats;
+
+    for (const auto &opt : *section)
+        formats.emplace(opt.key(), opt.value());
+
+    return formats;
+}
+
+bool Config::isVerbose() const noexcept
+{
+    return util::isBoolean(get(m_document, "logs", "verbose"));
+}
+
+bool Config::isForeground() const noexcept
+{
+    return util::isBoolean(get(m_document, "general", "foreground"));
+}
+
+std::string Config::pidfile() const
+{
+    return get(m_document, "general", "pidfile");
+}
+
+std::string Config::uid() const
+{
+    return get(m_document, "general", "uid");
+}
+
+std::string Config::gid() const
+{
+    return get(m_document, "general", "gid");
+}
+
+void Config::loadLogs() const
+{
+    ini::Document::const_iterator sc = m_document.find("logs");
+
+    if (sc == m_document.end())
+        return;
+
+    ini::Section::const_iterator it;
+
+    if ((it = sc->find("type")) != sc->end()) {
+        std::unique_ptr<log::Logger> iface;
+
+        // Console is the default, no test case.
+        if (it->value() == "file")
+            iface = loadLogFile(*sc);
+        else if (it->value() == "syslog")
+            iface = loadLogSyslog();
+        else
+            throw std::runtime_error("logs: unknown log type: {}"_format(it->value()));
+
+        if (iface)
+            log::setLogger(std::move(iface));
+    }
+}
+
+void Config::loadFormats() const
+{
+    ini::Document::const_iterator sc = m_document.find("format");
+
+    if (sc == m_document.end())
+        return;
+
+    std::unique_ptr<IrccdLogFilter> filter = std::make_unique<IrccdLogFilter>();
+    ini::Section::const_iterator it;
+
+    if ((it = sc->find("debug")) != sc->cend())
+        filter->m_debug = it->value();
+    if ((it = sc->find("info")) != sc->cend())
+        filter->m_info = it->value();
+    if ((it = sc->find("warning")) != sc->cend())
+        filter->m_warning = it->value();
+
+    log::setFilter(std::move(filter));
+}
+
+std::vector<std::shared_ptr<TransportServer>> Config::loadTransports() const
+{
+    std::vector<std::shared_ptr<TransportServer>> transports;
+
+    for (const auto &section : m_document)
+        if (section.key() == "transport")
+            transports.push_back(loadTransport(section));
+
+    return transports;
+}
+
+std::vector<Rule> Config::loadRules() const
+{
+    std::vector<Rule> rules;
+
+    for (const auto &section : m_document)
+        if (section.key() == "rule")
+            rules.push_back(loadRule(section));
+
+    return rules;
+}
+
+std::vector<std::shared_ptr<Server>> Config::loadServers() const
+{
+    std::vector<std::shared_ptr<Server>> servers;
+
+    for (const auto &section : m_document) {
+        if (section.key() != "server")
+            continue;
+
+        try {
+            servers.push_back(loadServer(section, *this));
+        } catch (const std::exception &ex) {
+            log::warning(ex.what());
+        }
+    }
+
+    return servers;
+}
+
+void Config::loadPlugins(Irccd &irccd) const
+{
+    auto it = m_document.find("plugins");
+
+    if (it != m_document.end()) {
+        for (const auto &option : *it) {
+            if (!util::isIdentifierValid(option.key()))
+                continue;
+
+            irccd.plugins().setConfig(option.key(), findPluginConfig(option.key()));
+            irccd.plugins().setFormats(option.key(), findPluginFormats(option.key()));
+            irccd.plugins().load(option.key(), option.value());
+        }
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/config.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,184 @@
+/*
+ * config.hpp -- irccd configuration loader
+ *
+ * Copyright (c) 2013-2016 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 <memory>
+#include <string>
+#include <vector>
+
+#include "ini.hpp"
+#include "plugin.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+class Irccd;
+class Rule;
+class Server;
+class TransportServer;
+
+/**
+ * \class Config
+ * \brief Read .ini configuration file for irccd
+ */
+class Config {
+private:
+    std::string m_path;
+    ini::Document m_document;
+
+public:
+    /**
+     * Search the configuration file into the standard defined paths.
+     *
+     * \return the config
+     * \throw std::exception on errors or if no config could be found
+     */
+    IRCCD_EXPORT static Config find();
+
+    /**
+     * Load the configuration from the specified path.
+     *
+     * \param path the path
+     */
+    inline Config(std::string path)
+        : m_path(std::move(path))
+        , m_document(ini::readFile(m_path))
+    {
+    }
+
+    /**
+     * Get the path to the configuration file.
+     *
+     * \return the path
+     */
+    inline const std::string &path() const noexcept
+    {
+        return m_path;
+    }
+
+   /**
+     * Find an entity if defined in the configuration file.
+     *
+     * \pre util::isValidIdentifier(name)
+     * \param server the server to update
+     * \param name the identity name
+     * \return default identity if cannot be found
+     */
+    IRCCD_EXPORT void loadServerIdentity(Server &server, const std::string &name) const;
+
+     /**
+     * Find a plugin configuration if defined in the configuration file.
+     *
+     * \pre util::isValidIdentifier(name)
+     * \return the configuration or empty if not found
+     */
+    IRCCD_EXPORT PluginConfig findPluginConfig(const std::string &name) const;
+
+    /**
+     * Find plugin formats if defined.
+     *
+     * \pre util::isValidIdentifier(name)
+     * \return the formats or empty one if not found
+     */
+    IRCCD_EXPORT PluginFormats findPluginFormats(const std::string &name) const;
+
+    /**
+     * Get the path to the pidfile.
+     *
+     * \return the path or empty if not defined
+     */
+    IRCCD_EXPORT std::string pidfile() const;
+
+    /**
+     * Get the uid.
+     *
+     * \return the uid or empty one if no one is set
+     */
+    IRCCD_EXPORT std::string uid() const;
+
+    /**
+     * Get the gid.
+     *
+     * \return the gid or empty one if no one is set
+     */
+    IRCCD_EXPORT std::string gid() const;
+
+    /**
+     * Check if verbosity is enabled.
+     *
+     * \return true if verbosity was requested
+     */
+    IRCCD_EXPORT bool isVerbose() const noexcept;
+
+    /**
+     * Check if foreground is specified (= no daemonize).
+     *
+     * \return true if foreground was requested
+     */
+    IRCCD_EXPORT bool isForeground() const noexcept;
+
+    /**
+     * Load logging interface.
+     */
+    IRCCD_EXPORT void loadLogs() const;
+
+    /**
+     * Load formats for logging.
+     */
+    IRCCD_EXPORT void loadFormats() const;
+
+    /**
+     * Load transports.
+     *
+     * \return the set of transports
+     */
+    IRCCD_EXPORT std::vector<std::shared_ptr<TransportServer>> loadTransports() const;
+
+    /**
+     * Load rules.
+     *
+     * \return the rules
+     */
+    IRCCD_EXPORT std::vector<Rule> loadRules() const;
+
+    /**
+     * Get the list of servers defined.
+     *
+     * \return the list of servers
+     */
+    IRCCD_EXPORT std::vector<std::shared_ptr<Server>> loadServers() const;
+
+    /**
+     * Get the list of defined plugins.
+     *
+     * \param irccd the irccd instance
+     * \return the list of plugins
+     */
+    IRCCD_EXPORT void loadPlugins(Irccd &irccd) const;
+};
+
+} // !irccd
+
+#endif // !IRCCD_CONFIG_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/dynlib.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,338 @@
+/*
+ * dynlib.hpp -- portable shared library loader
+ *
+ * Copyright (c) 2013-2016 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_DYNLIB_HPP
+#define IRCCD_DYNLIB_HPP
+
+/**
+ * \file dynlib.hpp
+ * \brief Portable shared library loader.
+ * \author David Demelier <markand@malikania.fr>
+ */
+
+/**
+ * \page Dynlib Dynlib
+ * \brief Portable shared library loader.
+ *
+ * The dynlib module let you open shared libraries dynamically at runtime.
+ *
+ * ## Operating system support
+ *
+ * | System  | Support | Remarks            |
+ * |---------|---------|--------------------|
+ * | Apple   | Ok      |                    |
+ * | FreeBSD | Ok      |                    |
+ * | Linux   | Ok      | Needs -ldl library |
+ * | Windows | Ok      |                    |
+ *
+ * ## How to export symbols
+ *
+ * When you want to dynamically load symbols from your shared library, make sure they are in a `extern "C"` block, if
+ * not they will be [mangled][name-mangling].
+ *
+ * Note, this does not mean that you can't write C++ code, it just mean that you can't use namespaces and function
+ * overloading.
+ *
+ * Example of **plugin.cpp**:
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * extern "C" {
+ *
+ * DYNLIB_EXPORT void plugin_load()
+ * {
+ *   std::cout << "Loading plugin" << std::endl;
+ * }
+ *
+ * DYNLIB_EXPORT void plugin_unload()
+ * {
+ *   std::cout << "Unloading plugin" << std::endl;
+ * }
+ *
+ * }
+ * ````
+ *
+ * The \ref DYNLIB_EXPORT macro is necessary on some platforms to be sure that symbol will be visible. Make sure you always
+ * add it before any function.
+ *
+ * To compile, see your compiler documentation or build system. For gcc you can use the following:
+ *
+ * ````
+ * gcc -std=c++14 -shared plugin.cpp -o plugin.so
+ * ````
+ *
+ * ## How to load the library
+ *
+ * The dynlib module will search for the library in various places, thus you can use relative paths names but be sure
+ * that the library can be found. Otherwise, just use an absolute path to the file.
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * int main()
+ * {
+ *   try {
+ *     Dynlib dso("./plugin" DYNLIB_SUFFIX);
+ *   } catch (const std::exception &ex) {
+ *     std::cerr << ex.what() << std::endl;
+ *   }
+ *
+ *   return 0;
+ * }
+ * ````
+ *
+ * ## How to load symbol
+ *
+ * The last part is symbol loading, you muse use raw C function pointer and the Dynlib::sym function.
+ *
+ * ````cpp
+ * #include <iostream>
+ *
+ * #include "dynlib.hpp"
+ *
+ * using PluginLoad = void (*)();
+ * using PluginUnload = void (*)();
+ *
+ * int main()
+ * {
+ *    try {
+ *        Dynlib dso("./plugin" DYNLIB_SUFFIX);
+ *
+ *        dso.sym<PluginLoad>("plugin_load")();
+ *        dso.sym<PluginUnload>("plugin_unload")();
+ *    } catch (const std::exception &ex) {
+ *        std::cerr << ex.what() << std::endl;
+ *    }
+ *
+ *    return 0;
+ * }
+ * ````
+ *
+ * [name-mangling]: https://en.wikipedia.org/wiki/Name_mangling
+ */
+
+#include <stdexcept>
+#include <string>
+
+#if defined(_WIN32)
+#  include <windows.h>
+#else
+#  include <dlfcn.h>
+#endif
+
+/**
+ * \brief Export the symbol.
+ *
+ * This is required on some platforms and you should put it before your function signature.
+ *
+ * \code{.cpp}
+ * extern "C" {
+ *
+ * DYNLIB_EXPORT void my_function()
+ * {
+ * }
+ *
+ * }
+ * \endcode
+ */
+#if defined(_WIN32)
+#  define DYNLIB_EXPORT    __declspec(dllexport)
+#else
+#  define DYNLIB_EXPORT
+#endif
+
+/**
+ * \brief Usual suffix for the library.
+ *
+ * This macro expands to the suffix convention for this platform.
+ *
+ * \code{.cpp}
+ * Dynlib library("./myplugin" DYNLIB_SUFFIX);
+ * \endcode
+ *
+ * \note Don't use the suffix expanded value shown in Doxygen as it may be wrong.
+ */
+#if defined(_WIN32)
+#  define DYNLIB_SUFFIX ".dll"
+#elif defined(__APPLE__)
+#  define DYNLIB_SUFFIX ".dylib"
+#else
+#  define DYNLIB_SUFFIX ".so"
+#endif
+
+namespace irccd {
+
+/**
+ * \class Dynlib
+ * \brief Load a dynamic module.
+ *
+ * This class is a portable wrapper to load shared libraries on supported systems.
+ */
+class Dynlib {
+private:
+#if defined(_WIN32)
+    using Handle    = HMODULE;
+    using Sym    = FARPROC;
+#else
+    using Handle    = void *;
+    using Sym    = void *;
+#endif
+
+public:
+    /**
+     * \brief Policy for symbol resolution.
+     */
+    enum Policy {
+        Immediately,        //!< load symbols immediately
+        Lazy            //!< load symbols when needed
+    };
+
+private:
+    Handle    m_handle;
+
+    Dynlib(const Dynlib &) = delete;
+    Dynlib &operator=(const Dynlib &) = delete;
+
+    Dynlib(Dynlib &&) = delete;
+    Dynlib &operator=(Dynlib &&) = delete;
+
+#if defined(_WIN32)
+    std::string error()
+    {
+        LPSTR error = nullptr;
+        std::string errmsg;
+
+        FormatMessageA(
+            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+            NULL,
+            GetLastError(),
+            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+            (LPSTR)&error, 0, NULL);
+
+        if (error) {
+            errmsg = std::string(error);
+            LocalFree(error);
+        }
+
+        return errmsg;
+    }
+#endif
+
+public:
+    /**
+     * Constructor to load a shared module.
+     *
+     * \param path the absolute path
+     * \param policy the policy to load
+     * \throw std::runtime_error on error
+     */
+    inline Dynlib(const std::string &path, Policy policy = Immediately);
+
+    /**
+     * Close the library automatically.
+     */
+    inline ~Dynlib();
+
+    /**
+     * Get a symbol from the library.
+     *
+     * On some platforms the symbol must be manually exported.
+     *
+     * \param name the symbol
+     * \return the symbol
+     * \throw std::runtime_error on error
+     * \see DYNLIB_EXPORT
+     */
+    template <typename T>
+    inline T sym(const std::string &name);
+};
+
+#if defined(_WIN32)
+
+/*
+ * Windows implementation
+ * ------------------------------------------------------------------
+ */
+
+Dynlib::Dynlib(const std::string &path, Policy)
+{
+    m_handle = LoadLibraryA(path.c_str());
+
+    if (m_handle == nullptr)
+        throw std::runtime_error(error());
+}
+
+Dynlib::~Dynlib()
+{
+    FreeLibrary(m_handle);
+    m_handle = nullptr;
+}
+
+template <typename T>
+T Dynlib::sym(const std::string &name)
+{
+    Sym sym = GetProcAddress(m_handle, name.c_str());
+
+    if (sym == nullptr)
+        throw std::runtime_error(error());
+
+    return reinterpret_cast<T>(sym);
+}
+
+#else
+
+/*
+ * Unix implementation
+ * ------------------------------------------------------------------
+ */
+
+Dynlib::Dynlib(const std::string &path, Policy policy)
+{
+    m_handle = dlopen(path.c_str(), policy == Immediately ? RTLD_NOW : RTLD_LAZY);
+
+    if (m_handle == nullptr)
+        throw std::runtime_error(dlerror());
+}
+
+Dynlib::~Dynlib()
+{
+    dlclose(m_handle);
+    m_handle = nullptr;
+}
+
+template <typename T>
+T Dynlib::sym(const std::string &name)
+{
+    Sym sym = dlsym(m_handle, name.c_str());
+
+    if (sym == nullptr)
+        throw std::runtime_error(dlerror());
+
+    return reinterpret_cast<T>(sym);
+}
+
+#endif
+
+#endif // !IRCCD_DYNLIB_HPP
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/irccd.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,102 @@
+/*
+ * irccd.cpp -- main irccd class
+ *
+ * Copyright (c) 2013-2016 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.hpp"
+#include "logger.hpp"
+#include "net.hpp"
+#include "service-command.hpp"
+#include "service-interrupt.hpp"
+#include "service-plugin.hpp"
+#include "service-rule.hpp"
+#include "service-server.hpp"
+#include "service-transport.hpp"
+#include "util.hpp"
+
+using namespace std;
+using namespace std::placeholders;
+using namespace std::string_literals;
+
+namespace irccd {
+
+Irccd::Irccd()
+    : m_commandService(std::make_shared<CommandService>())
+    , m_interruptService(std::make_shared<InterruptService>())
+    , m_servers(std::make_shared<ServerService>(*this))
+    , m_transports(std::make_shared<TransportService>(*this))
+    , m_ruleService(std::make_shared<RuleService>())
+    , m_plugins(std::make_shared<PluginService>(*this))
+{
+}
+
+void Irccd::post(std::function<void (Irccd &)> ev) noexcept
+{
+    std::lock_guard<mutex> lock(m_mutex);
+
+    m_events.push_back(move(ev));
+    m_interruptService->interrupt();
+}
+
+void Irccd::run()
+{
+    while (m_running) {
+        util::poller::poll(250, *m_interruptService, *m_servers, *m_transports);
+        dispatch();
+    }
+}
+
+void Irccd::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    util::poller::prepare(in, out, max, *m_interruptService, *m_servers, *m_transports);
+}
+
+void Irccd::sync(fd_set &in, fd_set &out)
+{
+    util::poller::sync(in, out, *m_interruptService, *m_servers, *m_transports);
+}
+
+void Irccd::dispatch()
+{
+    /*
+     * Make a copy because the events can add other events while we are
+     * iterating it. Also lock because the timers may alter these events too.
+     */
+    std::vector<std::function<void (Irccd &)>> copy;
+
+    {
+        std::lock_guard<mutex> lock(m_mutex);
+
+        copy = move(m_events);
+        m_events.clear();
+    }
+
+    if (copy.size() > 0)
+        log::debug() << "irccd: dispatching " << copy.size() << " event" << (copy.size() > 1 ? "s" : "") << endl;
+
+    for (auto &ev : copy)
+        ev(*this);
+}
+
+void Irccd::stop()
+{
+    log::debug() << "irccd: requesting to stop now" << endl;
+
+    m_running = false;
+    m_interruptService->interrupt();
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/irccd.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,174 @@
+/*
+ * irccd.hpp -- main irccd class
+ *
+ * Copyright (c) 2013-2016 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 <atomic>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <vector>
+
+#include "net.hpp"
+#include "sysconfig.hpp"
+
+/**
+ * \brief Main irccd namespace
+ */
+namespace irccd {
+
+class CommandService;
+class InterruptService;
+class PluginService;
+class RuleService;
+class ServerService;
+class TransportService;
+
+/**
+ * \class Irccd
+ * \brief Irccd main instance.
+ */
+class Irccd {
+private:
+    // Main loop stuff.
+    std::atomic<bool> m_running{true};
+    std::mutex m_mutex;
+    std::vector<std::function<void (Irccd &)>> m_events;
+
+    // Services.
+    std::shared_ptr<CommandService> m_commandService;
+    std::shared_ptr<InterruptService> m_interruptService;
+    std::shared_ptr<ServerService> m_servers;
+    std::shared_ptr<TransportService> m_transports;
+    std::shared_ptr<RuleService> m_ruleService;
+    std::shared_ptr<PluginService> m_plugins;
+
+    // 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;
+
+public:
+    /**
+     * Prepare standard services.
+     */
+    IRCCD_EXPORT Irccd();
+
+    /**
+     * Access the command service.
+     *
+     * \return the service
+     */
+    inline CommandService &commands() noexcept
+    {
+        return *m_commandService;
+    }
+
+    /**
+     * Access the server service.
+     *
+     * \return the service
+     */
+    inline ServerService &servers() noexcept
+    {
+        return *m_servers;
+    }
+
+    /**
+     * Access the transport service.
+     *
+     * \return the service
+     */
+    inline TransportService &transports() noexcept
+    {
+        return *m_transports;
+    }
+
+    /**
+     * Access the rule service.
+     *
+     * \return the service
+     */
+    inline RuleService &rules() noexcept
+    {
+        return *m_ruleService;
+    }
+
+    /**
+     * Access the plugin service.
+     *
+     * \return the service
+     */
+    inline PluginService &plugins() noexcept
+    {
+        return *m_plugins;
+    }
+
+    /**
+     * Prepare the services for selection.
+     *
+     * \param in the input set
+     * \param out the output set
+     * \param max the maximum handle
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * Synchronize the services.
+     *
+     * \param in the input set
+     * \param out the output set
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Add an event to the queue. This will immediately signals the event loop
+     * to interrupt itself to dispatch the pending events.
+     *
+     * \param ev the event
+     * \note Thread-safe
+     */
+    IRCCD_EXPORT void post(std::function<void (Irccd &)> ev) noexcept;
+
+    /**
+     * Loop forever by calling poll() and dispatch() indefinitely.
+     */
+    IRCCD_EXPORT void run();
+
+    /**
+     * Dispatch the pending events, usually after calling poll().
+     */
+    IRCCD_EXPORT void dispatch();
+
+    /**
+     * Request to stop, usually from a signal.
+     */
+    IRCCD_EXPORT void stop();
+};
+
+} // !irccd
+
+#endif // !IRCCD_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/plugin-dynlib.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,210 @@
+/*
+ * plugin-dynlib.cpp -- native plugin implementation
+ *
+ * Copyright (c) 2013-2016 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 "fs.hpp"
+#include "logger.hpp"
+#include "path.hpp"
+#include "plugin-dynlib.hpp"
+
+namespace irccd {
+
+namespace {
+
+template <typename Sym>
+inline Sym sym(Dynlib &dynlib, const std::string &name)
+{
+    try {
+        return dynlib.sym<Sym>(name);
+    } catch (...) {
+        return nullptr;
+    }
+}
+
+template <typename Sym, typename... Args>
+inline void call(Sym sym, Args&&... args)
+{
+    if (sym)
+        sym(std::forward<Args>(args)...);
+}
+
+} // !namespace
+
+DynlibPlugin::DynlibPlugin(std::string name, std::string path)
+    : Plugin(name, path)
+    , m_dso(std::move(path))
+    , m_onCommand(sym<OnCommand>(m_dso, "irccd_onCommand"))
+    , m_onConnect(sym<OnConnect>(m_dso, "irccd_onConnect"))
+    , m_onChannelMode(sym<OnChannelMode>(m_dso, "irccd_onChannelMode"))
+    , m_onChannelNotice(sym<OnChannelNotice>(m_dso, "irccd_onChannelNotice"))
+    , m_onInvite(sym<OnInvite>(m_dso, "irccd_onInvite"))
+    , m_onJoin(sym<OnJoin>(m_dso, "irccd_onJoin"))
+    , m_onKick(sym<OnKick>(m_dso, "irccd_onKick"))
+    , m_onLoad(sym<OnLoad>(m_dso, "irccd_onLoad"))
+    , m_onMessage(sym<OnMessage>(m_dso, "irccd_onMessage"))
+    , m_onMe(sym<OnMe>(m_dso, "irccd_onMe"))
+    , m_onMode(sym<OnMode>(m_dso, "irccd_onMode"))
+    , m_onNames(sym<OnNames>(m_dso, "irccd_onNames"))
+    , m_onNick(sym<OnNick>(m_dso, "irccd_onNick"))
+    , m_onNotice(sym<OnNotice>(m_dso, "irccd_onNotice"))
+    , m_onPart(sym<OnPart>(m_dso, "irccd_onPart"))
+    , m_onQuery(sym<OnQuery>(m_dso, "irccd_onQuery"))
+    , m_onQueryCommand(sym<OnQueryCommand>(m_dso, "irccd_onQueryCommand"))
+    , m_onReload(sym<OnReload>(m_dso, "irccd_onReload"))
+    , m_onTopic(sym<OnTopic>(m_dso, "irccd_onTopic"))
+    , m_onUnload(sym<OnUnload>(m_dso, "irccd_onUnload"))
+    , m_onWhois(sym<OnWhois>(m_dso, "irccd_onWhois"))
+{
+}
+
+void DynlibPlugin::onCommand(Irccd &irccd, const MessageEvent &ev)
+{
+    call(m_onCommand, irccd, ev);
+}
+
+void DynlibPlugin::onConnect(Irccd &irccd, const ConnectEvent &ev)
+{
+    call(m_onConnect, irccd, ev);
+}
+
+void DynlibPlugin::onChannelMode(Irccd &irccd, const ChannelModeEvent &ev)
+{
+    call(m_onChannelMode, irccd, ev);
+}
+
+void DynlibPlugin::onChannelNotice(Irccd &irccd, const ChannelNoticeEvent &ev)
+{
+    call(m_onChannelNotice, irccd, ev);
+}
+
+void DynlibPlugin::onInvite(Irccd &irccd, const InviteEvent &ev)
+{
+    call(m_onInvite, irccd, ev);
+}
+
+void DynlibPlugin::onJoin(Irccd &irccd, const JoinEvent &ev)
+{
+    call(m_onJoin, irccd, ev);
+}
+
+void DynlibPlugin::onKick(Irccd &irccd, const KickEvent &ev)
+{
+    call(m_onKick, irccd, ev);
+}
+
+void DynlibPlugin::onLoad(Irccd &irccd)
+{
+    call(m_onLoad, irccd, *this);
+}
+
+void DynlibPlugin::onMessage(Irccd &irccd, const MessageEvent &ev)
+{
+    call(m_onMessage, irccd, ev);
+}
+
+void DynlibPlugin::onMe(Irccd &irccd, const MeEvent &ev)
+{
+    call(m_onMe, irccd, ev);
+}
+
+void DynlibPlugin::onMode(Irccd &irccd, const ModeEvent &ev)
+{
+    call(m_onMode, irccd, ev);
+}
+
+void DynlibPlugin::onNames(Irccd &irccd, const NamesEvent &ev)
+{
+    call(m_onNames, irccd, ev);
+}
+
+void DynlibPlugin::onNick(Irccd &irccd, const NickEvent &ev)
+{
+    call(m_onNick, irccd, ev);
+}
+
+void DynlibPlugin::onNotice(Irccd &irccd, const NoticeEvent &ev)
+{
+    call(m_onNotice, irccd, ev);
+}
+
+void DynlibPlugin::onPart(Irccd &irccd, const PartEvent &ev)
+{
+    call(m_onPart, irccd, ev);
+}
+
+void DynlibPlugin::onQuery(Irccd &irccd, const QueryEvent &ev)
+{
+    call(m_onQuery, irccd, ev);
+}
+
+void DynlibPlugin::onQueryCommand(Irccd &irccd, const QueryEvent &ev)
+{
+    call(m_onQueryCommand, irccd, ev);
+}
+
+void DynlibPlugin::onReload(Irccd &irccd)
+{
+    call(m_onReload, irccd, *this);
+}
+
+void DynlibPlugin::onTopic(Irccd &irccd, const TopicEvent &ev)
+{
+    call(m_onTopic, irccd, ev);
+}
+
+void DynlibPlugin::onUnload(Irccd &irccd)
+{
+    call(m_onUnload, irccd, *this);
+}
+
+void DynlibPlugin::onWhois(Irccd &irccd, const WhoisEvent &ev)
+{
+    call(m_onWhois, irccd, ev);
+}
+
+std::shared_ptr<Plugin> DynlibPluginLoader::open(const std::string &id,
+                                                 const std::string &path) noexcept
+{
+    if (path.rfind(DYNLIB_SUFFIX) == std::string::npos)
+        return nullptr;
+
+    try {
+        return std::make_shared<DynlibPlugin>(id, path);
+    } catch (const std::exception &ex) {
+        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<Plugin> DynlibPluginLoader::find(const std::string &id) noexcept
+{
+    for (const auto &dir : path::list(path::PathNativePlugins)) {
+        auto path = dir + id + DYNLIB_SUFFIX;
+
+        if (!fs::isReadable(path))
+            continue;
+
+        log::info() << "plugin " << id << ": trying " << path << std::endl;
+
+        return open(id, path);
+    }
+
+    return nullptr;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/plugin-dynlib.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,222 @@
+/*
+ * plugin-dynlib.hpp -- native plugin implementation
+ *
+ * Copyright (c) 2013-2016 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 "dynlib.hpp"
+#include "plugin.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Dynlib based plugin.
+ * \ingroup plugins
+ */
+class DynlibPlugin : public Plugin {
+private:
+    using OnCommand = void (*)(Irccd &, const MessageEvent &);
+    using OnConnect = void (*)(Irccd &, const ConnectEvent &);
+    using OnChannelMode = void (*)(Irccd &, const ChannelModeEvent &);
+    using OnChannelNotice = void (*)(Irccd &, const ChannelNoticeEvent &);
+    using OnInvite = void (*)(Irccd &, const InviteEvent &);
+    using OnJoin = void (*)(Irccd &, const JoinEvent &);
+    using OnKick = void (*)(Irccd &, const KickEvent &);
+    using OnLoad = void (*)(Irccd &, DynlibPlugin &);
+    using OnMessage = void (*)(Irccd &, const MessageEvent &);
+    using OnMe = void (*)(Irccd &, const MeEvent &);
+    using OnMode = void (*)(Irccd &, const ModeEvent &);
+    using OnNames = void (*)(Irccd &, const NamesEvent &);
+    using OnNick = void (*)(Irccd &, const NickEvent &);
+    using OnNotice = void (*)(Irccd &, const NoticeEvent &);
+    using OnPart = void (*)(Irccd &, const PartEvent &);
+    using OnQuery = void (*)(Irccd &, const QueryEvent &);
+    using OnQueryCommand = void (*)(Irccd &, const QueryEvent &);
+    using OnReload = void (*)(Irccd &, DynlibPlugin &);
+    using OnTopic = void (*)(Irccd &, const TopicEvent &);
+    using OnUnload = void (*)(Irccd &, DynlibPlugin &);
+    using OnWhois = void (*)(Irccd &, const WhoisEvent &);
+
+    Dynlib m_dso;
+    OnCommand m_onCommand;
+    OnConnect m_onConnect;
+    OnChannelMode m_onChannelMode;
+    OnChannelNotice m_onChannelNotice;
+    OnInvite m_onInvite;
+    OnJoin m_onJoin;
+    OnKick m_onKick;
+    OnLoad m_onLoad;
+    OnMessage m_onMessage;
+    OnMe m_onMe;
+    OnMode m_onMode;
+    OnNames m_onNames;
+    OnNick m_onNick;
+    OnNotice m_onNotice;
+    OnPart m_onPart;
+    OnQuery m_onQuery;
+    OnQueryCommand m_onQueryCommand;
+    OnReload m_onReload;
+    OnTopic m_onTopic;
+    OnUnload m_onUnload;
+    OnWhois m_onWhois;
+
+    // Configuration and formats.
+    PluginConfig m_config;
+    PluginFormats m_formats;
+
+public:
+    /**
+     * Construct the plugin.
+     *
+     * \param name the name
+     * \param path the fully resolved path (must be absolute)
+     * \throw std::exception on failures
+     */
+    DynlibPlugin(std::string name, std::string path);
+
+    /**
+     * \copydoc Plugin::onCommand
+     */
+    IRCCD_EXPORT void onCommand(Irccd &irccd, const MessageEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onConnect
+     */
+    IRCCD_EXPORT void onConnect(Irccd &irccd, const ConnectEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onChannelMode
+     */
+    IRCCD_EXPORT void onChannelMode(Irccd &irccd, const ChannelModeEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onChannelNotice
+     */
+    IRCCD_EXPORT void onChannelNotice(Irccd &irccd, const ChannelNoticeEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onInvite
+     */
+    IRCCD_EXPORT void onInvite(Irccd &irccd, const InviteEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onJoin
+     */
+    IRCCD_EXPORT void onJoin(Irccd &irccd, const JoinEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onKick
+     */
+    IRCCD_EXPORT void onKick(Irccd &irccd, const KickEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onLoad
+     */
+    IRCCD_EXPORT void onLoad(Irccd &irccd) override;
+
+    /**
+     * \copydoc Plugin::onMessage
+     */
+    IRCCD_EXPORT void onMessage(Irccd &irccd, const MessageEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onMe
+     */
+    IRCCD_EXPORT void onMe(Irccd &irccd, const MeEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onMode
+     */
+    IRCCD_EXPORT void onMode(Irccd &irccd, const ModeEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onNames
+     */
+    IRCCD_EXPORT void onNames(Irccd &irccd, const NamesEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onNick
+     */
+    IRCCD_EXPORT void onNick(Irccd &irccd, const NickEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onNotice
+     */
+    IRCCD_EXPORT void onNotice(Irccd &irccd, const NoticeEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onPart
+     */
+    IRCCD_EXPORT void onPart(Irccd &irccd, const PartEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onQuery
+     */
+    IRCCD_EXPORT void onQuery(Irccd &irccd, const QueryEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onQueryCommand
+     */
+    IRCCD_EXPORT void onQueryCommand(Irccd &irccd, const QueryEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onReload
+     */
+    IRCCD_EXPORT void onReload(Irccd &irccd) override;
+
+    /**
+     * \copydoc Plugin::onTopic
+     */
+    IRCCD_EXPORT void onTopic(Irccd &irccd, const TopicEvent &event) override;
+
+    /**
+     * \copydoc Plugin::onUnload
+     */
+    IRCCD_EXPORT void onUnload(Irccd &irccd) override;
+
+    /**
+     * \copydoc Plugin::onWhois
+     */
+    IRCCD_EXPORT void onWhois(Irccd &irccd, const WhoisEvent &event) override;
+};
+
+/**
+ * \brief Implementation for searching native plugins.
+ */
+class DynlibPluginLoader : public PluginLoader {
+public:
+    /**
+     * \copydoc PluginLoader::find
+     */
+    std::shared_ptr<Plugin> open(const std::string &id,
+                                 const std::string &path) noexcept override;
+
+    /**
+     * \copydoc PluginLoader::find
+     */
+    std::shared_ptr<Plugin> find(const std::string &id) noexcept override;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_DYNLIB_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/plugin.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,500 @@
+/*
+ * plugin.hpp -- irccd JavaScript plugin interface
+ *
+ * Copyright (c) 2013-2016 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 <memory>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "server.hpp"
+#include "sysconfig.hpp"
+#include "util.hpp"
+
+namespace irccd {
+
+class Irccd;
+
+/**
+ * \brief Configuration map extract from config file.
+ */
+using PluginConfig = std::unordered_map<std::string, std::string>;
+
+/**
+ * \brief Formats for plugins.
+ */
+using PluginFormats = 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 m_name;
+    std::string m_path;
+
+    // Metadata
+    std::string m_author{"unknown"};
+    std::string m_license{"unknown"};
+    std::string m_summary{"unknown"};
+    std::string m_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
+        : m_name(std::move(name))
+        , m_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 m_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 m_path;
+    }
+
+    /**
+     * Get the author.
+     *
+     * \return the author
+     */
+    inline const std::string &author() const noexcept
+    {
+        return m_author;
+    }
+
+    /**
+     * Set the author.
+     *
+     * \param author the author
+     */
+    inline void setAuthor(std::string author) noexcept
+    {
+        m_author = std::move(author);
+    }
+
+    /**
+     * Get the license.
+     *
+     * \return the license
+     */
+    inline const std::string &license() const noexcept
+    {
+        return m_license;
+    }
+
+    /**
+     * Set the license.
+     *
+     * \param license the license
+     */
+    inline void setLicense(std::string license) noexcept
+    {
+        m_license = std::move(license);
+    }
+
+    /**
+     * Get the summary.
+     *
+     * \return the summary
+     */
+    inline const std::string &summary() const noexcept
+    {
+        return m_summary;
+    }
+
+    /**
+     * Set the summary.
+     *
+     * \param summary the summary
+     */
+    inline void setSummary(std::string summary) noexcept
+    {
+        m_summary = std::move(summary);
+    }
+
+    /**
+     * Get the version.
+     *
+     * \return the version
+     */
+    inline const std::string &version() const noexcept
+    {
+        return m_version;
+    }
+
+    /**
+     * Set the version.
+     *
+     * \param version the version
+     */
+    inline void setVersion(std::string version) noexcept
+    {
+        m_version = std::move(version);
+    }
+
+    /**
+     * Access the plugin configuration.
+     *
+     * \return the config
+     */
+    virtual PluginConfig config()
+    {
+        return {};
+    }
+
+    /**
+     * Set the configuration.
+     *
+     * \param config the configuration
+     */
+    virtual void setConfig(PluginConfig config)
+    {
+        util::unused(config);
+    }
+
+    /**
+     * Access the plugin formats.
+     *
+     * \return the format
+     */
+    virtual PluginFormats formats()
+    {
+        return {};
+    }
+
+    /**
+     * Set the formats.
+     *
+     * \param formats the formats
+     */
+    virtual void setFormats(PluginFormats formats)
+    {
+        util::unused(formats);
+    }
+
+    /**
+     * 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 onCommand(Irccd &irccd, const MessageEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On successful connection.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onConnect(Irccd &irccd, const ConnectEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On channel mode.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onChannelMode(Irccd &irccd, const ChannelModeEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On a channel notice.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onChannelNotice(Irccd &irccd, const ChannelNoticeEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On invitation.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onInvite(Irccd &irccd, const InviteEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On join.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onJoin(Irccd &irccd, const JoinEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On kick.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onKick(Irccd &irccd, const KickEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On load.
+     *
+     * \param irccd the irccd instance
+     */
+    virtual void onLoad(Irccd &irccd)
+    {
+        util::unused(irccd);
+    }
+
+    /**
+     * On channel message.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onMessage(Irccd &irccd, const MessageEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On CTCP Action.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onMe(Irccd &irccd, const MeEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On user mode change.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onMode(Irccd &irccd, const ModeEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On names listing.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onNames(Irccd &irccd, const NamesEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On nick change.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onNick(Irccd &irccd, const NickEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On user notice.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onNotice(Irccd &irccd, const NoticeEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On part.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onPart(Irccd &irccd, const PartEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On user query.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onQuery(Irccd &irccd, const QueryEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On user query command.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onQueryCommand(Irccd &irccd, const QueryEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On reload.
+     *
+     * \param irccd the irccd instance
+     */
+    virtual void onReload(Irccd &irccd)
+    {
+        util::unused(irccd);
+    }
+
+    /**
+     * On topic change.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onTopic(Irccd &irccd, const TopicEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+
+    /**
+     * On unload.
+     *
+     * \param irccd the irccd instance
+     */
+    virtual void onUnload(Irccd &irccd)
+    {
+        util::unused(irccd);
+    }
+
+    /**
+     * On whois information.
+     *
+     * \param irccd the irccd instance
+     * \param event the event
+     */
+    virtual void onWhois(Irccd &irccd, const WhoisEvent &event)
+    {
+        util::unused(irccd, event);
+    }
+};
+
+/**
+ * \brief Abstract interface for searching plugins.
+ *
+ * This class is used to make loading of plugins extensible, the PluginService
+ * 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 DynlibPluginLoader
+ * \see JsPluginLoader
+ */
+class PluginLoader {
+public:
+    /**
+     * 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) noexcept = 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) noexcept = 0;
+};
+
+} // !irccd
+
+#endif // !IRCCD_PLUGIN_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/rule.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,114 @@
+/*
+ * rule.cpp -- rule for server and channels
+ *
+ * Copyright (c) 2013-2016 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 "logger.hpp"
+#include "rule.hpp"
+#include "util.hpp"
+
+using namespace std;
+
+namespace {
+
+const std::unordered_set<std::string> validEvents{
+    "onChannelMode"
+    "onChannelNotice",
+    "onCommand",
+    "onConnect",
+    "onInvite",
+    "onJoin",
+    "onKick",
+    "onMessage",
+    "onMode",
+    "onNames",
+    "onNick",
+    "onNotice",
+    "onPart",
+    "onQuery",
+    "onQueryCommand",
+    "onTopic",
+    "onWhois"
+};
+
+} // !namespace
+
+namespace irccd {
+
+bool Rule::matchMap(const RuleSet &map, const std::string &value) const noexcept
+{
+    return value.empty() || map.empty() || map.count(value) == 1;
+}
+
+Rule::Rule(RuleSet servers, RuleSet channels, RuleSet origins, RuleSet plugins, RuleSet events, RuleAction action)
+    : m_servers(std::move(servers))
+    , m_channels(std::move(channels))
+    , m_origins(std::move(origins))
+    , m_plugins(std::move(plugins))
+    , m_events(std::move(events))
+    , m_action(action)
+{
+    for (const std::string &n : m_events)
+        if (validEvents.count(n) == 0)
+            throw std::invalid_argument(n + " is not a valid event name");
+}
+
+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
+{
+    return matchMap(m_servers, server) &&
+           matchMap(m_channels, channel) &&
+           matchMap(m_origins, nick) &&
+           matchMap(m_plugins, plugin) &&
+           matchMap(m_events, event);
+}
+
+RuleAction Rule::action() const noexcept
+{
+    return m_action;
+}
+
+const RuleSet &Rule::servers() const noexcept
+{
+    return m_servers;
+}
+
+const RuleSet &Rule::channels() const noexcept
+{
+    return m_channels;
+}
+
+const RuleSet &Rule::origins() const noexcept
+{
+    return m_origins;
+}
+
+const RuleSet &Rule::plugins() const noexcept
+{
+    return m_plugins;
+}
+
+const RuleSet &Rule::events() const noexcept
+{
+    return m_events;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/rule.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,150 @@
+/*
+ * rule.hpp -- rule for server and channels
+ *
+ * Copyright (c) 2013-2016 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 <sstream>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+/**
+ * List of criterias.
+ */
+using RuleSet = std::unordered_set<std::string>;
+
+/**
+ * \enum RuleAction
+ * \brief Rule action
+ */
+enum class RuleAction {
+    Accept,         //!< The event is accepted (default)
+    Drop            //!< The event is dropped
+};
+
+/**
+ * \class Rule
+ * \brief Manage rule to activate or deactive events.
+ */
+class Rule {
+private:
+    RuleSet m_servers;
+    RuleSet m_channels;
+    RuleSet m_origins;
+    RuleSet m_plugins;
+    RuleSet m_events;
+    RuleAction m_action{RuleAction::Accept};
+
+    /*
+     * Check if a map contains the value and return true if it is
+     * or return true if value is empty (which means applicable).
+     */
+    bool matchMap(const RuleSet &map, const std::string &value) 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
+     */
+    IRCCD_EXPORT Rule(RuleSet servers = RuleSet{},
+                      RuleSet channels = RuleSet{},
+                      RuleSet nicknames = RuleSet{},
+                      RuleSet plugins = RuleSet{},
+                      RuleSet events = RuleSet{},
+                      RuleAction action = RuleAction::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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT RuleAction action() const noexcept;
+
+    /**
+     * Get the servers.
+     *
+     * \return the servers
+     */
+    IRCCD_EXPORT const RuleSet &servers() const noexcept;
+
+    /**
+     * Get the channels.
+     *
+     * \return the channels
+     */
+    IRCCD_EXPORT const RuleSet &channels() const noexcept;
+
+    /**
+     * Get the origins.
+     *
+     * \return the origins
+     */
+    IRCCD_EXPORT const RuleSet &origins() const noexcept;
+
+    /**
+     * Get the plugins.
+     *
+     * \return the plugins
+     */
+    IRCCD_EXPORT const RuleSet &plugins() const noexcept;
+
+    /**
+     * Get the events.
+     *
+     * \return the events
+     */
+    IRCCD_EXPORT const RuleSet &events() const noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_RULE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/server.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,910 @@
+/*
+ * server.cpp -- an IRC server
+ *
+ * Copyright (c) 2013-2016 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 <cerrno>
+#include <cstring>
+#include <stdexcept>
+
+#include <format.h>
+
+#include <libircclient.h>
+#include <libirc_rfcnumeric.h>
+
+#include "sysconfig.hpp"
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+#  include <sys/types.h>
+#  include <netinet/in.h>
+#  include <arpa/nameser.h>
+#  include <resolv.h>
+#endif
+
+#include "logger.hpp"
+#include "util.hpp"
+#include "server.hpp"
+
+using namespace fmt::literals;
+
+namespace irccd {
+
+/*
+ * Server::Session declaration.
+ * ------------------------------------------------------------------
+ */
+
+class Server::Session {
+public:
+    std::unique_ptr<irc_session_t, void (*)(irc_session_t *)> m_handle{nullptr, nullptr};
+
+    inline operator const irc_session_t *() const noexcept
+    {
+        return m_handle.get();
+    }
+
+    inline operator irc_session_t *() noexcept
+    {
+        return m_handle.get();
+    }
+
+    inline bool isConnected() const noexcept
+    {
+        return irc_is_connected(m_handle.get());
+    }
+};
+
+/*
+ * Server::State declaration.
+ * ------------------------------------------------------------------
+ */
+
+class Server::State {
+public:
+    State() = default;
+    virtual ~State() = default;
+    virtual void prepare(Server &, fd_set &, fd_set &, net::Handle &) = 0;
+    virtual std::string ident() const = 0;
+};
+
+/*
+ * Server::DisconnectedState declaration.
+ * ------------------------------------------------------------------
+ */
+
+class Server::DisconnectedState : public Server::State {
+private:
+    ElapsedTimer m_timer;
+
+public:
+    void prepare(Server &, fd_set &, fd_set &, net::Handle &) override;
+    std::string ident() const override;
+};
+
+/*
+ * Server::ConnectingState declaration.
+ * ------------------------------------------------------------------
+ */
+
+class Server::ConnectingState : public State {
+private:
+    enum {
+        Disconnected,
+        Connecting
+    } m_state{Disconnected};
+
+    ElapsedTimer m_timer;
+
+    bool connect(Server &server);
+
+public:
+    void prepare(Server &, fd_set &, fd_set &, net::Handle &) override;
+    std::string ident() const override;
+};
+
+/*
+ * Server::ConnectedState declaration.
+ * ------------------------------------------------------------------
+ */
+
+class Server::ConnectedState : public State {
+public:
+    void prepare(Server &, fd_set &, fd_set &, net::Handle &) override;
+    std::string ident() const override;
+};
+
+namespace {
+
+/*
+ * strify
+ * ------------------------------------------------------------------
+ *
+ * Make sure to build a C++ string with a not-null C string.
+ */
+inline std::string strify(const char *s)
+{
+    return (s == nullptr) ? "" : std::string(s);
+}
+
+/*
+ * cleanPrefix
+ * ------------------------------------------------------------------
+ *
+ * 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 cleanPrefix(const std::map<ChannelMode, 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;
+}
+
+/*
+ * extractPrefixes
+ * ------------------------------------------------------------------
+ *
+ * Read modes from the IRC event numeric.
+ */
+std::map<ChannelMode, char> extractPrefixes(const std::string &line)
+{
+    std::pair<char, char> table[16];
+    std::string buf = line.substr(7);
+    std::map<ChannelMode, char> modes;
+
+    for (int i = 0; i < 16; ++i)
+        table[i] = std::make_pair(-1, -1);
+
+    int j = 0;
+    bool readModes = true;
+    for (size_t i = 0; i < buf.size(); ++i) {
+        if (buf[i] == '(')
+            continue;
+        if (buf[i] == ')') {
+            j = 0;
+            readModes = false;
+            continue;
+        }
+
+        if (readModes)
+            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<ChannelMode>(table[i].first);
+        auto value = table[i].second;
+
+        modes.emplace(key, value);
+    }
+
+    return modes;
+}
+
+} // !namespace
+
+void Server::removeJoinedChannel(const std::string &channel)
+{
+    m_jchannels.erase(std::remove(m_jchannels.begin(), m_jchannels.end(), channel), m_jchannels.end());
+}
+
+void Server::handleConnect(const char *, const char **) noexcept
+{
+    // Reset the number of tried reconnection.
+    m_recocur = 1;
+
+    // Reset the timer.
+    m_timer.reset();
+
+    // Reset joined channels.
+    m_jchannels.clear();
+
+    // Don't forget to change state and notify.
+    next(std::make_unique<ConnectedState>());
+    onConnect(ConnectEvent{shared_from_this()});
+
+    // Auto join listed channels.
+    for (const auto &channel : m_rchannels) {
+        log::info() << "server " << m_name << ": auto joining " << channel.name << std::endl;
+        join(channel.name, channel.password);
+    }
+}
+
+void Server::handleChannel(const char *orig, const char **params) noexcept
+{
+    onMessage(MessageEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
+}
+
+void Server::handleChannelMode(const char *orig, const char **params) noexcept
+{
+    onChannelMode(ChannelModeEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1]), strify(params[2])});
+}
+
+void Server::handleChannelNotice(const char *orig, const char **params) noexcept
+{
+    onChannelNotice(ChannelNoticeEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
+}
+
+void Server::handleCtcpAction(const char *orig, const char **params) noexcept
+{
+    onMe(MeEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
+}
+
+void Server::handleInvite(const char *orig, const char **params) noexcept
+{
+    // If joininvite is set, join the channel.
+    if ((m_flags & JoinInvite) && isSelf(strify(params[0])))
+        join(strify(params[1]));
+
+    /*
+     * The libircclient says that invite contains the target nickname, it's
+     * quit uncommon to need it so it is passed as the last argument to be
+     * optional in the plugin.
+     */
+    onInvite(InviteEvent{shared_from_this(), strify(orig), strify(params[1]), strify(params[0])});
+}
+
+void Server::handleJoin(const char *orig, const char **params) noexcept
+{
+    if (isSelf(strify(orig)))
+        m_jchannels.push_back(strify(params[0]));
+
+    onJoin(JoinEvent{shared_from_this(), strify(orig), strify(params[0])});
+}
+
+void Server::handleKick(const char *orig, const char **params) noexcept
+{
+    if (isSelf(strify(params[1]))) {
+        // Remove the channel from the joined list.
+        removeJoinedChannel(strify(params[0]));
+
+        // Rejoin the channel if the option has been set and I was kicked.
+        if (m_flags & AutoRejoin)
+            join(strify(params[0]));
+    }
+
+    onKick(KickEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1]), strify(params[2])});
+}
+
+void Server::handleMode(const char *orig, const char **params) noexcept
+{
+    onMode(ModeEvent{shared_from_this(), strify(orig), strify(params[1])});
+}
+
+void Server::handleNick(const char *orig, const char **params) noexcept
+{
+    // Update our nickname.
+    if (isSelf(strify(orig)))
+        m_nickname = strify(params[0]);
+
+    onNick(NickEvent{shared_from_this(), strify(orig), strify(params[0])});
+}
+
+void Server::handleNotice(const char *orig, const char **params) noexcept
+{
+    /*
+     * Like handleInvite, the notice provides the target nickname, we discard
+     * it.
+     */
+    onNotice(NoticeEvent{shared_from_this(), strify(orig), strify(params[1])});
+}
+
+void Server::handleNumeric(unsigned int event, const char **params, unsigned int c) noexcept
+{
+    if (event == LIBIRC_RFC_RPL_NAMREPLY) {
+        /*
+         * 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 (c < 4 || params[2] == nullptr || params[3] == nullptr)
+            return;
+
+        std::vector<std::string> users = util::split(params[3], " \t");
+
+        // The listing may add some prefixes, remove them if needed.
+        for (std::string u : users)
+            m_namesMap[params[2]].insert(cleanPrefix(m_modes, u));
+    } else if (event == LIBIRC_RFC_RPL_ENDOFNAMES) {
+        /*
+         * Called when end of name listing has finished on a channel.
+         *
+         * params[0] == originator
+         * params[1] == channel
+         * params[2] == End of NAMES list
+         */
+        if (c < 3 || params[1] == nullptr)
+            return;
+
+        auto it = m_namesMap.find(params[1]);
+        if (it != m_namesMap.end()) {
+            onNames(NamesEvent{shared_from_this(), params[1], std::vector<std::string>(it->second.begin(), it->second.end())});
+
+            // Don't forget to remove the list.
+            m_namesMap.erase(it);
+        }
+    } else if (event == LIBIRC_RFC_RPL_WHOISUSER) {
+        /*
+         * 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 (c < 6 || !params[1] || !params[2] || !params[3] || !params[5])
+            return;
+
+        Whois info;
+
+        info.nick = strify(params[1]);
+        info.user = strify(params[2]);
+        info.host = strify(params[3]);
+        info.realname = strify(params[5]);
+
+        m_whoisMap.emplace(info.nick, info);
+    } else if (event == LIBIRC_RFC_RPL_WHOISCHANNELS) {
+        /*
+         * Called when we have received channels for one user.
+         *
+         * params[0] == originator
+         * params[1] == nickname
+         * params[2] == list of channels with their prefixes
+         */
+        if (c < 3 || !params[1] || !params[2])
+            return;
+
+        auto it = m_whoisMap.find(params[1]);
+        if (it != m_whoisMap.end()) {
+            std::vector<std::string> channels = util::split(params[2], " \t");
+
+            // Clean their prefixes.
+            for (auto &s : channels)
+                s = cleanPrefix(m_modes, s);
+
+            it->second.channels = std::move(channels);
+        }
+    } else if (event == LIBIRC_RFC_RPL_ENDOFWHOIS) {
+        /*
+         * Called when whois is finished.
+         *
+         * params[0] == originator
+         * params[1] == nickname
+         * params[2] == End of WHOIS list
+         */
+        auto it = m_whoisMap.find(params[1]);
+        if (it != m_whoisMap.end()) {
+            onWhois(WhoisEvent{shared_from_this(), it->second});
+
+            // Don't forget to remove.
+            m_whoisMap.erase(it);
+        }
+    } else if (event == /* RPL_BOUNCE */ 5) {
+        /*
+         * The event 5 is usually RPL_BOUNCE, but we always see it as ISUPPORT.
+         */
+        for (unsigned int i = 0; i < c; ++i) {
+            if (strncmp(params[i], "PREFIX", 6) == 0) {
+                m_modes = extractPrefixes(params[i]);
+                break;
+            }
+        }
+    }
+}
+
+void Server::handlePart(const char *orig, const char **params) noexcept
+{
+    // Remove the channel from the joined list if I left a channel.
+    if (isSelf(strify(orig)))
+        removeJoinedChannel(strify(params[0]));
+
+    onPart(PartEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
+}
+
+void Server::handlePing(const char *, const char **params) noexcept
+{
+    // Reset the timer to detect disconnection.
+    m_timer.reset();
+
+    // Don't forget to respond.
+    send("PONG {}"_format(params[0]));
+}
+
+void Server::handleQuery(const char *orig, const char **params) noexcept
+{
+    onQuery(QueryEvent{shared_from_this(), strify(orig), strify(params[1])});
+}
+
+void Server::handleTopic(const char *orig, const char **params) noexcept
+{
+    onTopic(TopicEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
+}
+
+std::shared_ptr<Server> Server::fromJson(const nlohmann::json &object)
+{
+    auto server = std::make_shared<Server>(util::json::requireIdentifier(object, "name"));
+
+    server->setHost(util::json::requireString(object, "host"));
+    server->setPassword(util::json::getString(object, "password"));
+    server->setNickname(util::json::getString(object, "nickname", server->nickname()));
+    server->setRealname(util::json::getString(object, "realname", server->realname()));
+    server->setUsername(util::json::getString(object, "username", server->username()));
+    server->setCtcpVersion(util::json::getString(object, "ctcpVersion", server->ctcpVersion()));
+    server->setCommandCharacter(util::json::getString(object, "commandChar", server->commandCharacter()));
+
+    if (object.find("port") != object.end())
+        server->setPort(util::json::getUintRange<std::uint16_t>(object, "port"));
+    if (util::json::getBool(object, "ipv6"))
+        server->setFlags(server->flags() | Server::Ipv6);
+    if (util::json::getBool(object, "ssl"))
+        server->setFlags(server->flags() | Server::Ssl);
+    if (util::json::getBool(object, "sslVerify"))
+        server->setFlags(server->flags() | Server::SslVerify);
+    if (util::json::getBool(object, "autoRejoin"))
+        server->setFlags(server->flags() | Server::AutoRejoin);
+    if (util::json::getBool(object, "joinInvite"))
+        server->setFlags(server->flags() | Server::JoinInvite);
+
+    return server;
+}
+
+Channel Server::splitChannel(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(std::string name)
+    : m_name(std::move(name))
+    , m_session(std::make_unique<Session>())
+    , m_state(std::make_unique<ConnectingState>())
+{
+    irc_callbacks_t callbacks;
+
+    /*
+     * GCC 4.9.2 triggers some missing-field-initializers warnings when
+     * using uniform initialization so use a std::memset as a workaround.
+     */
+    std::memset(&callbacks, 0, sizeof (irc_callbacks_t));
+
+    /*
+     * Convert the raw pointer functions from libircclient to Server member
+     * function.
+     *
+     * While doing this, discard useless arguments.
+     */
+    callbacks.event_channel = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleChannel(orig, params);
+    };
+    callbacks.event_channel_notice = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleChannelNotice(orig, params);
+    };
+    callbacks.event_connect = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleConnect(orig, params);
+    };
+    callbacks.event_ctcp_action = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleCtcpAction(orig, params);
+    };
+    callbacks.event_invite = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleInvite(orig, params);
+    };
+    callbacks.event_join = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleJoin(orig, params);
+    };
+    callbacks.event_kick = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleKick(orig, params);
+    };
+    callbacks.event_mode = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleChannelMode(orig, params);
+    };
+    callbacks.event_nick = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleNick(orig, params);
+    };
+    callbacks.event_notice = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleNotice(orig, params);
+    };
+    callbacks.event_numeric = [] (irc_session_t *session, unsigned int event, const char *, const char **params, unsigned int count) {
+        static_cast<Server *>(irc_get_ctx(session))->handleNumeric(event, params, count);
+    };
+    callbacks.event_part = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handlePart(orig, params);
+    };
+    callbacks.event_ping = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handlePing(orig, params);
+    };
+    callbacks.event_privmsg = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleQuery(orig, params);
+    };
+    callbacks.event_topic = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleTopic(orig, params);
+    };
+    callbacks.event_umode = [] (irc_session_t *session, const char *, const char *orig, const char **params, unsigned) {
+        static_cast<Server *>(irc_get_ctx(session))->handleMode(orig, params);
+    };
+
+    m_session->m_handle = {irc_create_session(&callbacks), irc_destroy_session};
+
+    // Save this to the session.
+    irc_set_ctx(*m_session, this);
+    irc_set_ctcp_version(*m_session, m_ctcpversion.c_str());
+}
+
+Server::~Server()
+{
+    irc_disconnect(*m_session);
+}
+
+void Server::setNickname(std::string nickname)
+{
+    if (m_session->isConnected())
+        m_queue.push([=] () {
+            return irc_cmd_nick(*m_session, nickname.c_str()) == 0;
+        });
+    else
+        m_nickname = std::move(nickname);
+}
+
+void Server::setCtcpVersion(std::string ctcpversion)
+{
+    m_ctcpversion = std::move(ctcpversion);
+    irc_set_ctcp_version(*m_session, ctcpversion.c_str());
+}
+
+void Server::next(std::unique_ptr<State> state) noexcept
+{
+    m_stateNext = std::move(state);
+}
+
+void Server::update() noexcept
+{
+    if (m_stateNext) {
+        log::debug("server {}: switch state {} -> {}"_format(m_name, m_state->ident(), m_stateNext->ident()));
+
+        m_state = std::move(m_stateNext);
+        m_stateNext = nullptr;
+
+        // Reset channels.
+        m_jchannels.clear();
+    }
+}
+
+void Server::disconnect() noexcept
+{
+    using namespace std::placeholders;
+
+    irc_disconnect(*m_session);
+    onDie();
+}
+
+void Server::reconnect() noexcept
+{
+    irc_disconnect(*m_session);
+    next(std::make_unique<ConnectingState>());
+}
+
+void Server::prepare(fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) noexcept
+{
+    m_state->prepare(*this, setinput, setoutput, maxfd);
+}
+
+void Server::sync(fd_set &setinput, fd_set &setoutput)
+{
+    /*
+     * 1. Send maximum of command possible if available for write
+     *
+     * Break on the first failure to avoid changing the order of the
+     * commands if any of them fails.
+     */
+    bool done = false;
+
+    while (!m_queue.empty() && !done) {
+        if (m_queue.front()())
+            m_queue.pop();
+        else
+            done = true;
+    }
+
+    // 2. Read data.
+    irc_process_select_descriptors(*m_session, &setinput, &setoutput);
+}
+
+bool Server::isSelf(const std::string &nick) const noexcept
+{
+    char target[32]{0};
+
+    irc_target_get_nick(nick.c_str(), target, sizeof (target));
+
+    return m_nickname == target;
+}
+
+void Server::cmode(std::string channel, std::string mode)
+{
+    m_queue.push([=] () {
+        return irc_cmd_channel_mode(*m_session, channel.c_str(), mode.c_str()) == 0;
+    });
+}
+
+void Server::cnotice(std::string channel, std::string message)
+{
+    m_queue.push([=] () {
+        return irc_cmd_notice(*m_session, channel.c_str(), message.c_str()) == 0;
+    });
+}
+
+void Server::invite(std::string target, std::string channel)
+{
+    m_queue.push([=] () {
+        return irc_cmd_invite(*m_session, target.c_str(), channel.c_str()) == 0;
+    });
+}
+
+void Server::join(std::string channel, std::string password)
+{
+    // 1. Add the channel or update it to the requested channels.
+    auto it = std::find_if(m_rchannels.begin(), m_rchannels.end(), [&] (const auto &c) {
+        return c.name == channel;
+    });
+
+    if (it == m_rchannels.end())
+        m_rchannels.push_back({ channel, password });
+    else
+        *it = { channel, password };
+
+    // 2. Join if not found and connected.
+    if (m_session->isConnected())
+        irc_cmd_join(*m_session, channel.c_str(), password.empty() ? nullptr : password.c_str());
+}
+
+void Server::kick(std::string target, std::string channel, std::string reason)
+{
+    m_queue.push([=] () {
+        return irc_cmd_kick(*m_session, target.c_str(), channel.c_str(), reason.c_str()) == 0;
+    });
+}
+
+void Server::me(std::string target, std::string message)
+{
+    m_queue.push([=] () {
+        return irc_cmd_me(*m_session, target.c_str(), message.c_str()) == 0;
+    });
+}
+
+void Server::message(std::string target, std::string message)
+{
+    m_queue.push([=] () {
+        return irc_cmd_msg(*m_session, target.c_str(), message.c_str()) == 0;
+    });
+}
+
+void Server::mode(std::string mode)
+{
+    m_queue.push([=] () {
+        return irc_cmd_user_mode(*m_session, mode.c_str()) == 0;
+    });
+}
+
+void Server::names(std::string channel)
+{
+    m_queue.push([=] () {
+        return irc_cmd_names(*m_session, channel.c_str()) == 0;
+    });
+}
+
+void Server::notice(std::string target, std::string message)
+{
+    m_queue.push([=] () {
+        return irc_cmd_notice(*m_session, target.c_str(), message.c_str()) == 0;
+    });
+}
+
+void Server::part(std::string channel, std::string reason)
+{
+    m_queue.push([=] () -> bool {
+        if (reason.empty())
+            return irc_cmd_part(*m_session, channel.c_str()) == 0;
+
+        return irc_send_raw(*m_session, "PART %s :%s", channel.c_str(), reason.c_str());
+    });
+}
+
+void Server::send(std::string raw)
+{
+    m_queue.push([=] () {
+        return irc_send_raw(*m_session, raw.c_str()) == 0;
+    });
+}
+
+void Server::topic(std::string channel, std::string topic)
+{
+    m_queue.push([=] () {
+        return irc_cmd_topic(*m_session, channel.c_str(), topic.c_str()) == 0;
+    });
+}
+
+void Server::whois(std::string target)
+{
+    m_queue.push([=] () {
+        return irc_cmd_whois(*m_session, target.c_str()) == 0;
+    });
+}
+
+/*
+ * Server::DisconnectedState implementation
+ * ------------------------------------------------------------------
+ */
+
+void Server::DisconnectedState::prepare(Server &server, fd_set &, fd_set &, net::Handle &)
+{
+    if (server.m_recotries == 0) {
+        log::warning() << "server " << server.m_name << ": reconnection disabled, skipping" << std::endl;
+        server.onDie();
+    } else if (server.m_recotries > 0 && server.m_recocur > server.m_recotries) {
+        log::warning() << "server " << server.m_name << ": giving up" << std::endl;
+        server.onDie();
+    } else {
+        if (m_timer.elapsed() > static_cast<unsigned>(server.m_recodelay * 1000)) {
+            irc_disconnect(*server.m_session);
+
+            server.m_recocur ++;
+            server.next(std::make_unique<ConnectingState>());
+        }
+    }
+}
+
+std::string Server::DisconnectedState::ident() const
+{
+    return "Disconnected";
+}
+
+/*
+ * Server::ConnectingState implementation
+ * ------------------------------------------------------------------
+ */
+
+bool Server::ConnectingState::connect(Server &server)
+{
+    const char *password = server.m_password.empty() ? nullptr : server.m_password.c_str();
+    std::string host = server.m_host;
+    int code;
+
+    // libircclient requires # for SSL connection.
+#if defined(WITH_SSL)
+    if (server.m_flags & Server::Ssl)
+        host.insert(0, 1, '#');
+    if (!(server.m_flags & Server::SslVerify))
+        irc_option_set(*server.m_session, LIBIRC_OPTION_SSL_NO_VERIFY);
+#endif
+
+    if (server.flags() & Server::Ipv6) {
+        code = irc_connect6(*server.m_session, host.c_str(), server.m_port, password,
+                            server.m_nickname.c_str(),
+                            server.m_username.c_str(),
+                            server.m_realname.c_str());
+    } else {
+        code = irc_connect(*server.m_session, host.c_str(), server.m_port, password,
+                           server.m_nickname.c_str(),
+                           server.m_username.c_str(),
+                           server.m_realname.c_str());
+    }
+
+    return code == 0;
+}
+
+void Server::ConnectingState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd)
+{
+    /*
+     * The connect function will either fail if the hostname wasn't resolved or
+     * if any of the internal functions fail.
+     *
+     * It returns success if the connection was successful but it does not mean
+     * that connection is established.
+     *
+     * Because this function will be called repeatidly, the connection was
+     * started and we're still not connected in the specified timeout time, we
+     * mark the server as disconnected.
+     *
+     * Otherwise, the libircclient event_connect will change the state.
+     */
+    if (m_state == Connecting) {
+        if (m_timer.elapsed() > static_cast<unsigned>(server.m_recodelay * 1000)) {
+            log::warning() << "server " << server.name() << ": timeout while connecting" << std::endl;
+            server.next(std::make_unique<DisconnectedState>());
+        } else if (!irc_is_connected(*server.m_session)) {
+            log::warning() << "server " << server.m_name << ": error while connecting: ";
+            log::warning() << irc_strerror(irc_errno(*server.m_session)) << std::endl;
+
+            if (server.m_recotries != 0)
+                log::warning("server {}: retrying in {} seconds"_format(server.m_name, server.m_recodelay));
+
+            server.next(std::make_unique<DisconnectedState>());
+        } else
+            irc_add_select_descriptors(*server.m_session, &setinput, &setoutput, reinterpret_cast<int *>(&maxfd));
+    } else {
+        /*
+         * This is needed if irccd is started before DHCP or if DNS cache is
+         * outdated.
+         */
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+        (void)res_init();
+#endif
+        log::info("server {}: trying to connect to {}, port {}"_format(server.m_name, server.m_host, server.m_port));
+
+        if (!connect(server)) {
+            log::warning() << "server " << server.m_name << ": disconnected while connecting: ";
+            log::warning() << irc_strerror(irc_errno(*server.m_session)) << std::endl;
+            server.next(std::make_unique<DisconnectedState>());
+        } else {
+            m_state = Connecting;
+
+            if (irc_is_connected(*server.m_session))
+                irc_add_select_descriptors(*server.m_session, &setinput, &setoutput, reinterpret_cast<int *>(&maxfd));
+        }
+    }
+}
+
+std::string Server::ConnectingState::ident() const
+{
+    return "Connecting";
+}
+
+/*
+ * Server::ConnectedState implementation
+ * ------------------------------------------------------------------
+ */
+
+void Server::ConnectedState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd)
+{
+    if (!irc_is_connected(*server.m_session)) {
+        log::warning() << "server " << server.m_name << ": disconnected" << std::endl;
+
+        if (server.m_recodelay > 0)
+            log::warning("server {}: retrying in {} seconds"_format(server.m_name, server.m_recodelay));
+
+        server.next(std::make_unique<DisconnectedState>());
+    } else if (server.m_timer.elapsed() >= server.m_timeout * 1000) {
+        log::warning() << "server " << server.m_name << ": ping timeout after "
+                   << (server.m_timer.elapsed() / 1000) << " seconds" << std::endl;
+        server.next(std::make_unique<DisconnectedState>());
+    } else
+        irc_add_select_descriptors(*server.m_session, &setinput, &setoutput, reinterpret_cast<int *>(&maxfd));
+}
+
+std::string Server::ConnectedState::ident() const
+{
+    return "Connected";
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/server.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,957 @@
+/*
+ * server.hpp -- an IRC server
+ *
+ * Copyright (c) 2013-2016 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 <cstdint>
+#include <functional>
+#include <map>
+#include <memory>
+#include <queue>
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <json.hpp>
+
+#include "elapsed-timer.hpp"
+#include "net.hpp"
+#include "signals.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+class Server;
+
+/**
+ * \brief Prefixes for nicknames.
+ */
+enum class ChannelMode {
+    Creator         = 'O',                  //!< Channel creator
+    HalfOperator    = 'h',                  //!< Half operator
+    Operator        = '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 Channel event.
+ */
+class ChannelModeEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string mode;                       //!< The mode.
+    std::string argument;                   //!< The mode argument (Optional).
+};
+
+/**
+ * \brief Channel notice event.
+ */
+class ChannelNoticeEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string message;                    //!< The notice message.
+};
+
+/**
+ * \brief Connection success event.
+ */
+class ConnectEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+};
+
+/**
+ * \brief Invite event.
+ */
+class InviteEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string nickname;                   //!< The nickname (you).
+};
+
+/**
+ * \brief Join event.
+ */
+class JoinEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+};
+
+/**
+ * \brief Kick event.
+ */
+class KickEvent {
+public:
+    std::shared_ptr<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 MessageEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string message;                    //!< The message.
+};
+
+/**
+ * \brief CTCP action event.
+ */
+class MeEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string message;                    //!< The message.
+};
+
+/**
+ * \brief Mode event.
+ */
+class ModeEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string mode;                       //!< The mode.
+};
+
+/**
+ * \brief Names listing event.
+ */
+class NamesEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string channel;                    //!< The channel.
+    std::vector<std::string> names;         //!< The names.
+};
+
+/**
+ * \brief Nick change event.
+ */
+class NickEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string nickname;                   //!< The new nickname.
+};
+
+/**
+ * \brief Notice event.
+ */
+class NoticeEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string message;                    //!< The message.
+};
+
+/**
+ * \brief Part event.
+ */
+class PartEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string reason;                     //!< The reason.
+};
+
+/**
+ * \brief Query event.
+ */
+class QueryEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string message;                    //!< The message.
+};
+
+/**
+ * \brief Topic event.
+ */
+class TopicEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    std::string origin;                     //!< The originator.
+    std::string channel;                    //!< The channel.
+    std::string topic;                      //!< The topic message.
+};
+
+/**
+ * \brief Whois event.
+ */
+class WhoisEvent {
+public:
+    std::shared_ptr<Server> server;         //!< The server.
+    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:
+    /**
+     * Bridge for libircclient.
+     */
+    class Session;
+
+    /**
+     * \brief Various options for server.
+     */
+    enum {
+        Ipv6        = (1 << 0),             //!< Connect using IPv6
+        Ssl         = (1 << 1),             //!< Use SSL
+        SslVerify   = (1 << 2),             //!< Verify SSL
+        AutoRejoin  = (1 << 3),             //!< Auto rejoin a kick
+        JoinInvite  = (1 << 4)              //!< Join a channel on invitation
+    };
+
+    /**
+     * Signal: onChannelMode
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone changed the channel mode.
+     */
+    Signal<ChannelModeEvent> onChannelMode;
+
+    /**
+     * Signal: onChannelNotice
+     * ----------------------------------------------------------
+     *
+     * Triggered when a notice has been sent on a channel.
+     */
+    Signal<ChannelNoticeEvent> onChannelNotice;
+
+    /**
+     * Signal: onConnect
+     * ----------------------------------------------------------
+     *
+     * Triggered when the server is successfully connected.
+     */
+    Signal<ConnectEvent> onConnect;
+
+    /**
+     * Signal: onDie
+     * ----------------------------------------------------------
+     *
+     * The server is dead.
+     */
+    Signal<> onDie;
+
+    /**
+     * Signal: onInvite
+     * ----------------------------------------------------------
+     *
+     * Triggered when an invite has been sent to you (the bot).
+     */
+    Signal<InviteEvent> onInvite;
+
+    /**
+     * Signal: onJoin
+     * ----------------------------------------------------------
+     *
+     * Triggered when a user has joined the channel, it also includes you.
+     */
+    Signal<JoinEvent> onJoin;
+
+    /**
+     * Signal: onKick
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has been kicked from a channel.
+     */
+    Signal<KickEvent> onKick;
+
+    /**
+     * ServerEvent: onMessage
+     * ----------------------------------------------------------
+     *
+     * Triggered when a message on a channel has been sent.
+     */
+    Signal<MessageEvent> onMessage;
+
+    /**
+     * Signal: onMe
+     * ----------------------------------------------------------
+     *
+     * 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.
+     */
+    Signal<MeEvent> onMe;
+
+    /**
+     * Signal: onMode
+     * ----------------------------------------------------------
+     *
+     * Triggered when the server changed your user mode.
+     */
+    Signal<ModeEvent> onMode;
+
+    /**
+     * Signal: onNames
+     * ----------------------------------------------------------
+     *
+     * Triggered when names listing has finished on a channel.
+     */
+    Signal<NamesEvent> onNames;
+
+    /**
+     * Signal: onNick
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone changed its nickname, it also includes you.
+     */
+    Signal<NickEvent> onNick;
+
+    /**
+     * Signal: onNotice
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has sent a notice to you.
+     */
+    Signal<NoticeEvent> onNotice;
+
+    /**
+     * Signal: onPart
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has left the channel.
+     */
+    Signal<PartEvent> onPart;
+
+    /**
+     * Signal: onQuery
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone has sent you a private message.
+     */
+    Signal<QueryEvent> onQuery;
+
+    /**
+     * Signal: onTopic
+     * ----------------------------------------------------------
+     *
+     * Triggered when someone changed the channel topic.
+     */
+    Signal<TopicEvent> onTopic;
+
+    /**
+     * Signal: onWhois
+     * ----------------------------------------------------------
+     *
+     * Triggered when whois information has been received.
+     */
+    Signal<WhoisEvent> onWhois;
+
+private:
+    class State;
+    class ConnectedState;
+    class ConnectingState;
+    class DisconnectedState;
+
+    // Requested and joined channels.
+    std::vector<Channel> m_rchannels;
+    std::vector<std::string> m_jchannels;
+
+    // Identifier.
+    std::string m_name;
+
+    // Connection information
+    std::string m_host;
+    std::string m_password;
+    std::uint16_t m_port{6667};
+    std::uint8_t m_flags{0};
+
+    // Identity.
+    std::string m_nickname{"irccd"};
+    std::string m_username{"irccd"};
+    std::string m_realname{"IRC Client Daemon"};
+    std::string m_ctcpversion{"IRC Client Daemon"};
+
+    // Settings.
+    std::string m_commandCharacter{"!"};
+    std::int8_t m_recotries{-1};
+    std::uint16_t m_recodelay{30};
+    std::uint16_t m_timeout{300};
+
+    // Queue of requests to send.
+    std::queue<std::function<bool ()>> m_queue;
+
+    // libircclient session (bridge).
+    std::unique_ptr<Session> m_session;
+
+    // States.
+    std::unique_ptr<State> m_state;
+    std::unique_ptr<State> m_stateNext;
+
+    // Misc.
+    ElapsedTimer m_timer;
+    std::map<ChannelMode, char> m_modes;
+    std::int8_t m_recocur{0};
+    std::map<std::string, std::set<std::string>> m_namesMap;
+    std::map<std::string, Whois> m_whoisMap;
+
+    // Private helpers.
+    void removeJoinedChannel(const std::string &channel);
+
+    // Handle libircclient callbacks.
+    void handleChannel(const char *, const char **) noexcept;
+    void handleChannelMode(const char *, const char **) noexcept;
+    void handleChannelNotice(const char *, const char **) noexcept;
+    void handleConnect(const char *, const char **) noexcept;
+    void handleCtcpAction(const char *, const char **) noexcept;
+    void handleInvite(const char *, const char **) noexcept;
+    void handleJoin(const char *, const char **) noexcept;
+    void handleKick(const char *, const char **) noexcept;
+    void handleMode(const char *, const char **) noexcept;
+    void handleNick(const char *, const char **) noexcept;
+    void handleNotice(const char *, const char **) noexcept;
+    void handleNumeric(unsigned int, const char **, unsigned int) noexcept;
+    void handlePart(const char *, const char **) noexcept;
+    void handlePing(const char *, const char **) noexcept;
+    void handleQuery(const char *, const char **) noexcept;
+    void handleTopic(const char *, const char **) noexcept;
+
+public:
+    /**
+     * Convert a JSON object as a server.
+     *
+     * Used in JavaScript API and transport commands.
+     *
+     * \param object the object
+     * \return the server
+     * \throw std::exception on failures
+     */
+    IRCCD_EXPORT static std::shared_ptr<Server> fromJson(const nlohmann::json &object);
+
+    /**
+     * Split a channel from the form channel:password into a ServerChannel
+     * object.
+     *
+     * \param value the value
+     * \return a channel
+     */
+    IRCCD_EXPORT static Channel splitChannel(const std::string &value);
+
+    /**
+     * Construct a server.
+     *
+     * \param name the identifier
+     */
+    IRCCD_EXPORT Server(std::string name);
+
+    /**
+     * Destructor. Close the connection if needed.
+     */
+    IRCCD_EXPORT virtual ~Server();
+
+    /**
+     * Get the server identifier.
+     *
+     * \return the id
+     */
+    inline const std::string &name() const noexcept
+    {
+        return m_name;
+    }
+
+    /**
+     * Get the hostname.
+     *
+     * \return the hostname
+     */
+    inline const std::string &host() const noexcept
+    {
+        return m_host;
+    }
+
+    /**
+     * Set the hostname.
+     *
+     * \param host the hostname
+     */
+    inline void setHost(std::string host) noexcept
+    {
+        m_host = std::move(host);
+    }
+
+    /**
+     * Get the password.
+     *
+     * \return the password
+     */
+    inline const std::string &password() const noexcept
+    {
+        return m_password;
+    }
+
+    /**
+     * Set the password.
+     *
+     * An empty password means no password.
+     *
+     * \param password the password
+     */
+    inline void setPassword(std::string password) noexcept
+    {
+        m_password = std::move(password);
+    }
+
+    /**
+     * Get the port.
+     *
+     * \return the port
+     */
+    inline std::uint16_t port() const noexcept
+    {
+        return m_port;
+    }
+
+    /**
+     * Set the port.
+     *
+     * \param port the port
+     */
+    inline void setPort(std::uint16_t port) noexcept
+    {
+        m_port = port;
+    }
+
+    /**
+     * Get the flags.
+     *
+     * \return the flags
+     */
+    inline std::uint8_t flags() const noexcept
+    {
+        return m_flags;
+    }
+
+    /**
+     * Set the flags.
+     *
+     * \pre flags must be valid
+     * \param flags the flags
+     */
+    inline void setFlags(std::uint8_t flags) noexcept
+    {
+        assert(flags <= 0x1f);
+
+        m_flags = flags;
+    }
+
+    /**
+     * Get the nickname.
+     *
+     * \return the nickname
+     */
+    inline const std::string &nickname() const noexcept
+    {
+        return m_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
+     */
+    IRCCD_EXPORT void setNickname(std::string nickname);
+
+    /**
+     * Get the username.
+     *
+     * \return the username
+     */
+    inline const std::string &username() const noexcept
+    {
+        return m_username;
+    }
+
+    /**
+     * Set the username.
+     *
+     * \param name the username
+     * \note the username will be changed on the next connection
+     */
+    inline void setUsername(std::string name) noexcept
+    {
+        m_username = std::move(name);
+    }
+
+    /**
+     * Get the realname.
+     *
+     * \return the realname
+     */
+    inline const std::string &realname() const noexcept
+    {
+        return m_realname;
+    }
+
+    /**
+     * Set the realname.
+     *
+     * \param realname the username
+     * \note the username will be changed on the next connection
+     */
+    inline void setRealname(std::string realname) noexcept
+    {
+        m_realname = std::move(realname);
+    }
+
+    /**
+     * Get the CTCP version.
+     *
+     * \return the CTCP version
+     */
+    inline const std::string &ctcpVersion() const noexcept
+    {
+        return m_ctcpversion;
+    }
+
+    /**
+     * Set the CTCP version.
+     *
+     * \param ctcpversion the version
+     */
+    IRCCD_EXPORT void setCtcpVersion(std::string ctcpversion);
+
+    /**
+     * Get the command character.
+     *
+     * \return the character
+     */
+    inline const std::string &commandCharacter() const noexcept
+    {
+        return m_commandCharacter;
+    }
+
+    /**
+     * Set the command character.
+     *
+     * \pre !commandCharacter.empty()
+     * \param commandCharacter the command character
+     */
+    inline void setCommandCharacter(std::string commandCharacter) noexcept
+    {
+        assert(!commandCharacter.empty());
+
+        m_commandCharacter = std::move(commandCharacter);
+    }
+
+    /**
+     * Get the number of reconnections before giving up.
+     *
+     * \return the number of reconnections
+     */
+    inline std::int8_t reconnectTries() const noexcept
+    {
+        return m_recotries;
+    }
+
+    /**
+     * Set the number of reconnections to test before giving up.
+     *
+     * A value less than 0 means infinite.
+     *
+     * \param reconnectTries the number of reconnections
+     */
+    inline void setReconnectTries(std::int8_t reconnectTries) noexcept
+    {
+        m_recotries = reconnectTries;
+    }
+
+    /**
+     * Get the reconnection delay before retrying.
+     *
+     * \return the number of seconds
+     */
+    inline std::uint16_t reconnectDelay() const noexcept
+    {
+        return m_recodelay;
+    }
+
+    /**
+     * Set the number of seconds before retrying.
+     *
+     * \param reconnectDelay the number of seconds
+     */
+    inline void setReconnectDelay(std::uint16_t reconnectDelay) noexcept
+    {
+        m_recodelay = reconnectDelay;
+    }
+
+    /**
+     * Get the ping timeout.
+     *
+     * \return the ping timeout
+     */
+    inline std::uint16_t pingTimeout() const noexcept
+    {
+        return m_timeout;
+    }
+
+    /**
+     * Set the ping timeout before considering a server as dead.
+     *
+     * \param pingTimeout the delay in seconds
+     */
+    inline void setPingTimeout(std::uint16_t pingTimeout) noexcept
+    {
+        m_timeout = pingTimeout;
+    }
+
+    /**
+     * Get the list of channels joined.
+     *
+     * \return the channels
+     */
+    inline const std::vector<std::string> &channels() const noexcept
+    {
+        return m_jchannels;
+    }
+
+    /**
+     * Set the next state, it is not changed immediately but on next iteration.
+     *
+     * \param state the new state
+     */
+    IRCCD_EXPORT void next(std::unique_ptr<State> state) noexcept;
+
+    /**
+     * Switch to next state if it has.
+     */
+    IRCCD_EXPORT void update() noexcept;
+
+    /**
+     * Force disconnection.
+     */
+    IRCCD_EXPORT void disconnect() noexcept;
+
+    /**
+     * Asks for a reconnection.
+     */
+    IRCCD_EXPORT void reconnect() noexcept;
+
+    /**
+     * Prepare the IRC session.
+     *
+     * \warning Not thread-safe
+     */
+    IRCCD_EXPORT void prepare(fd_set &setinput, fd_set &setoutput, net::Handle &maxfd) noexcept;
+
+    /**
+     * Process incoming/outgoing data after selection.
+     *
+     * \param setinput
+     * \param setoutput
+     * \throw any exception that have been throw from user functions
+     */
+    IRCCD_EXPORT void sync(fd_set &setinput, fd_set &setoutput);
+
+    /**
+     * Determine if the nickname is the bot itself.
+     *
+     * \param nick the nickname to check
+     * \return true if it is the bot
+     */
+    IRCCD_EXPORT bool isSelf(const std::string &nick) const noexcept;
+
+    /**
+     * Change the channel mode.
+     *
+     * \param channel the channel
+     * \param mode the new mode
+     */
+    IRCCD_EXPORT virtual void cmode(std::string channel, std::string mode);
+
+    /**
+     * Send a channel notice.
+     *
+     * \param channel the channel
+     * \param message message notice
+     */
+    IRCCD_EXPORT virtual void cnotice(std::string channel, std::string message);
+
+    /**
+     * Invite a user to a channel.
+     *
+     * \param target the target nickname
+     * \param channel the channel
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT virtual void message(std::string target, std::string message);
+
+    /**
+     * Change your user mode.
+     *
+     * \param mode the mode
+     */
+    IRCCD_EXPORT virtual void mode(std::string mode);
+
+    /**
+     * Request the list of names.
+     *
+     * \param channel the channel
+     */
+    IRCCD_EXPORT virtual void names(std::string channel);
+
+    /**
+     * Send a private notice.
+     *
+     * \param target the target
+     * \param message the notice message
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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.
+     *
+     * \warning Use this function with care
+     * \param raw the raw message (without `\r\n\r\n`)
+     */
+    IRCCD_EXPORT virtual void send(std::string raw);
+
+    /**
+     * Change the channel topic.
+     *
+     * \param channel the channel
+     * \param topic the desired topic
+     */
+    IRCCD_EXPORT virtual void topic(std::string channel, std::string topic);
+
+    /**
+     * Request for whois information.
+     *
+     * \param target the target nickname
+     */
+    IRCCD_EXPORT virtual void whois(std::string target);
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-command.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,39 @@
+/*
+ * service-command.cpp -- store remote commands
+ *
+ * Copyright (c) 2013-2016 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 "service-command.hpp"
+
+namespace irccd {
+
+bool CommandService::contains(const std::string &name) const noexcept
+{
+    return find(name) != nullptr;
+}
+
+std::shared_ptr<Command> CommandService::find(const std::string &name) const noexcept
+{
+    auto it = std::find_if(m_commands.begin(), m_commands.end(), [&] (const auto &cmd) {
+        return cmd->name() == name;
+    });
+
+    return it == m_commands.end() ? nullptr : *it;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-command.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,73 @@
+/*
+ * service-command.hpp -- store remote commands
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_COMMAND_HPP
+#define IRCCD_SERVICE_COMMAND_HPP
+
+/**
+ * \file service-command.hpp
+ * \brief Store remote commands.
+ */
+
+#include <memory>
+#include <vector>
+
+#include "command.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Store remote commands.
+ * \ingroup services
+ */
+class CommandService {
+private:
+    std::vector<std::shared_ptr<Command>> m_commands;
+
+public:
+    /**
+     * Get all commands.
+     *
+     * \return the list of commands.
+     */
+    inline const std::vector<std::shared_ptr<Command>> &commands() const noexcept
+    {
+        return m_commands;
+    }
+
+    /**
+     * Tells if a command exists.
+     *
+     * \param name the command name
+     * \return true if the command exists
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT std::shared_ptr<Command> find(const std::string &name) const noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_COMMAND_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-interrupt.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,75 @@
+/*
+ * service-interrupt.cpp -- interrupt irccd event loop
+ *
+ * Copyright (c) 2013-2016 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 <array>
+
+#include "logger.hpp"
+#include "service-interrupt.hpp"
+
+namespace irccd {
+
+InterruptService::InterruptService()
+    : m_in(AF_INET, 0)
+    , m_out(AF_INET, 0)
+{
+    // Bind a socket to any port.
+    m_in.set(net::option::SockReuseAddress(true));
+    m_in.bind(net::ipv4::any(0));
+    m_in.listen(1);
+
+    // Do the socket pair.
+    m_out.connect(net::ipv4::pton("127.0.0.1", net::ipv4::port(m_in.getsockname())));
+    m_in = m_in.accept();
+    m_out.set(net::option::SockBlockMode(false));
+}
+
+void InterruptService::prepare(fd_set &in, fd_set &, net::Handle &max)
+{
+    FD_SET(m_in.handle(), &in);
+
+    if (m_in.handle() > max)
+        max = m_in.handle();
+}
+
+void InterruptService::sync(fd_set &in, fd_set &)
+{
+    if (FD_ISSET(m_in.handle(), &in)) {
+        static std::array<char, 32> tmp;
+
+        try {
+            log::debug("irccd: interrupt service recv");
+            m_in.recv(tmp.data(), 32);
+        } catch (const std::exception &ex) {
+            log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
+        }
+    }
+}
+
+void InterruptService::interrupt() noexcept
+{
+    try {
+        static char byte;
+
+        log::debug("irccd: interrupt service send");
+        m_out.send(&byte, 1);
+    } catch (const std::exception &ex) {
+        log::warning() << "irccd: interrupt service error: " << ex.what() << std::endl;
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-interrupt.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,66 @@
+/*
+ * service-interrupt.hpp -- interrupt irccd event loop
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_INTERRUPT_HPP
+#define IRCCD_SERVICE_INTERRUPT_HPP
+
+/**
+ * \file service-interrupt.hpp
+ * \brief Interrupt irccd event loop.
+ */
+
+#include "net.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Interrupt irccd event loop.
+ * \ingroup services
+ */
+class InterruptService {
+private:
+    net::TcpSocket m_in;
+    net::TcpSocket m_out;
+
+public:
+    /**
+     * Prepare the socket pair.
+     *
+     * \throw std::runtime_error on errors
+     */
+    IRCCD_EXPORT InterruptService();
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Request interruption.
+     */
+    IRCCD_EXPORT void interrupt() noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_INTERRUPT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-plugin.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,178 @@
+/*
+ * service-plugin.cpp -- manage plugins
+ *
+ * Copyright (c) 2013-2016 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 <functional>
+#include <regex>
+#include <stdexcept>
+
+#include <format.h>
+
+#include "logger.hpp"
+#include "service-plugin.hpp"
+
+using namespace fmt::literals;
+
+namespace irccd {
+
+PluginService::PluginService(Irccd &irccd) noexcept
+    : m_irccd(irccd)
+{
+}
+
+PluginService::~PluginService()
+{
+    for (const auto &plugin : m_plugins)
+        plugin->onUnload(m_irccd);
+}
+
+bool PluginService::has(const std::string &name) const noexcept
+{
+    return std::count_if(m_plugins.cbegin(), m_plugins.cend(), [&] (const auto &plugin) {
+        return plugin->name() == name;
+    }) > 0;
+}
+
+std::shared_ptr<Plugin> PluginService::get(const std::string &name) const noexcept
+{
+    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it == m_plugins.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<Plugin> PluginService::require(const std::string &name) const
+{
+    auto plugin = get(name);
+
+    if (!plugin)
+        throw std::invalid_argument("plugin {} not found"_format(name));
+
+    return plugin;
+}
+
+void PluginService::add(std::shared_ptr<Plugin> plugin)
+{
+    m_plugins.push_back(std::move(plugin));
+}
+
+void PluginService::setConfig(const std::string &name, PluginConfig config)
+{
+    m_config.emplace(name, std::move(config));
+}
+
+PluginConfig PluginService::config(const std::string &name) const
+{
+    auto it = m_config.find(name);
+
+    if (it != m_config.end())
+        return it->second;
+
+    return PluginConfig();
+}
+
+void PluginService::setFormats(const std::string &name, PluginFormats formats)
+{
+    m_formats.emplace(name, std::move(formats));
+}
+
+PluginFormats PluginService::formats(const std::string &name) const
+{
+    auto it = m_formats.find(name);
+
+    if (it != m_formats.end())
+        return it->second;
+
+    return PluginFormats();
+}
+
+std::shared_ptr<Plugin> PluginService::open(const std::string &id,
+                                            const std::string &path)
+{
+    for (const auto &loader : m_loaders) {
+        auto plugin = loader->open(id, path);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+std::shared_ptr<Plugin> PluginService::find(const std::string &id)
+{
+    for (const auto &loader : m_loaders) {
+        auto plugin = loader->find(id);
+
+        if (plugin)
+            return plugin;
+    }
+
+    return nullptr;
+}
+
+void PluginService::load(std::string name, std::string path)
+{
+    if (has(name))
+        return;
+
+    try {
+        std::shared_ptr<Plugin> plugin;
+
+        if (path.empty())
+            plugin = find(name);
+        else
+            plugin = open(name, std::move(path));
+
+        if (plugin) {
+            plugin->setConfig(m_config[name]);
+            plugin->setFormats(m_formats[name]);
+            plugin->onLoad(m_irccd);
+
+            add(std::move(plugin));
+        }
+    } catch (const std::exception &ex) {
+        log::warning("plugin {}: {}"_format(name, ex.what()));
+    }
+}
+
+void PluginService::reload(const std::string &name)
+{
+    auto plugin = get(name);
+
+    if (plugin)
+        plugin->onReload(m_irccd);
+}
+
+void PluginService::unload(const std::string &name)
+{
+    auto it = std::find_if(m_plugins.begin(), m_plugins.end(), [&] (const auto &plugin) {
+        return plugin->name() == name;
+    });
+
+    if (it != m_plugins.end()) {
+        (*it)->onUnload(m_irccd);
+        m_plugins.erase(it);
+    }
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-plugin.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,190 @@
+/*
+ * service-plugin.hpp -- manage plugins
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_PLUGIN_HPP
+#define IRCCD_SERVICE_PLUGIN_HPP
+
+/**
+ * \file service-plugin.hpp
+ * \brief Manage plugins.
+ */
+
+#include <unordered_map>
+#include <memory>
+#include <vector>
+
+#include "plugin.hpp"
+
+namespace irccd {
+
+class Irccd;
+
+/**
+ * \brief Manage plugins.
+ * \ingroup services
+ */
+class PluginService {
+private:
+    Irccd &m_irccd;
+    std::vector<std::shared_ptr<Plugin>> m_plugins;
+    std::vector<std::unique_ptr<PluginLoader>> m_loaders;
+    std::unordered_map<std::string, PluginConfig> m_config;
+    std::unordered_map<std::string, PluginFormats> m_formats;
+
+public:
+    /**
+     * Create the plugin service.
+     *
+     * \param irccd the irccd instance
+     */
+    IRCCD_EXPORT PluginService(Irccd &irccd) noexcept;
+
+    /**
+     * Destroy plugins.
+     */
+    IRCCD_EXPORT ~PluginService();
+
+    /**
+     * Get the list of plugins.
+     *
+     * \return the list of plugins
+     */
+    inline const std::vector<std::shared_ptr<Plugin>> &list() const noexcept
+    {
+        return m_plugins;
+    }
+
+    /**
+     * Check if a plugin is loaded.
+     *
+     * \param name the plugin id
+     * \return true if has plugin
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT void add(std::shared_ptr<Plugin> plugin);
+
+    /**
+     * Configure a plugin.
+     *
+     * If the plugin is already loaded, its configuration is updated.
+     *
+     * \param name the plugin name
+     * \param config the new configuration
+     */
+    IRCCD_EXPORT void setConfig(const std::string &name, PluginConfig config);
+
+    /**
+     * Get a configuration for a plugin.
+     *
+     * \param name the plugin name
+     * \return the configuration or default one if not found
+     */
+    IRCCD_EXPORT PluginConfig config(const std::string &name) const;
+
+    /**
+     * Add formatting for a plugin.
+     *
+     * \param name the plugin name
+     * \param formats the formats
+     */
+    IRCCD_EXPORT void setFormats(const std::string &name, PluginFormats formats);
+
+    /**
+     * Get formats for a plugin.
+     *
+     * \param name the plugin name
+     * \return the formats
+     */
+    IRCCD_EXPORT PluginFormats formats(const std::string &name) const;
+
+    /**
+     * 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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)
+     */
+    IRCCD_EXPORT void load(std::string name, std::string path = "");
+
+    /**
+     * Unload a plugin and remove it.
+     *
+     * \param name the plugin id
+     */
+    IRCCD_EXPORT void unload(const std::string &name);
+
+    /**
+     * Reload a plugin by calling onReload.
+     *
+     * \param name the plugin name
+     * \throw std::exception on failures
+     */
+    IRCCD_EXPORT void reload(const std::string &name);
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_PLUGIN_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-rule.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,78 @@
+/*
+ * service-rule.cpp -- store and solve rules
+ *
+ * Copyright (c) 2013-2016 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 <format.h>
+
+#include "logger.hpp"
+#include "service-rule.hpp"
+#include "util.hpp"
+
+using namespace fmt::literals;
+
+namespace irccd {
+
+void RuleService::add(Rule rule)
+{
+    m_rules.push_back(std::move(rule));
+}
+
+void RuleService::insert(Rule rule, unsigned position)
+{
+    assert(position <= m_rules.size());
+
+    m_rules.insert(m_rules.begin() + position, std::move(rule));
+}
+
+void RuleService::remove(unsigned position)
+{
+    assert(position < m_rules.size());
+
+    m_rules.erase(m_rules.begin() + position);
+}
+
+bool RuleService::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("rule: solving for server={}, channel={}, origin={}, plugin={}, event={}"_format(server, channel,
+           origin, plugin, event));
+
+    int i = 0;
+    for (const Rule &rule : m_rules) {
+        log::debug() << "  candidate " << i++ << ":\n"
+                 << "    servers: " << util::join(rule.servers().begin(), rule.servers().end()) << "\n"
+                 << "    channels: " << util::join(rule.channels().begin(), rule.channels().end()) << "\n"
+                 << "    origins: " << util::join(rule.origins().begin(), rule.origins().end()) << "\n"
+                 << "    plugins: " << util::join(rule.plugins().begin(), rule.plugins().end()) << "\n"
+                 << "    events: " << util::join(rule.events().begin(), rule.events().end()) << "\n"
+                 << "    action: " << ((rule.action() == RuleAction::Accept) ? "accept" : "drop") << std::endl;
+
+        if (rule.match(server, channel, origin, plugin, event))
+            result = rule.action() == RuleAction::Accept;
+    }
+
+    return result;
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-rule.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,104 @@
+/*
+ * service-rule.hpp -- store and solve rules
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_RULE_HPP
+#define IRCCD_SERVICE_RULE_HPP
+
+/**
+ * \file service-rule.hpp
+ * \brief Store and solve rules.
+ */
+
+#include <vector>
+
+#include "rule.hpp"
+
+namespace irccd {
+
+/**
+ * \brief Store and solve rules.
+ * \ingroup services
+ */
+class RuleService {
+private:
+    std::vector<Rule> m_rules;
+
+public:
+    /**
+     * Get the list of rules.
+     *
+     * \return the list of rules
+     */
+    inline const std::vector<Rule> &list() const noexcept
+    {
+        return m_rules;
+    }
+
+    /**
+     * Get the number of rules.
+     *
+     * \return the number of rules
+     */
+    inline std::size_t length() const noexcept
+    {
+        return m_rules.size();
+    }
+
+    /**
+     * Append a rule.
+     *
+     * \param rule the rule to append
+     */
+    IRCCD_EXPORT void add(Rule rule);
+
+    /**
+     * Insert a new rule at the specified position.
+     *
+     * \param rule the rule
+     * \param position the position
+     */
+    IRCCD_EXPORT void insert(Rule rule, unsigned position);
+
+    /**
+     * Remove a new rule from the specified position.
+     *
+     * \pre position must be valid
+     * \param position the position
+     */
+    IRCCD_EXPORT void remove(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
+     */
+    IRCCD_EXPORT bool solve(const std::string &server,
+                            const std::string &channel,
+                            const std::string &origin,
+                            const std::string &plugin,
+                            const std::string &event) noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_RULE_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-server.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,586 @@
+/*
+ * service-server.cpp -- manage IRC servers
+ *
+ * Copyright (c) 2013-2016 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 <json.hpp>
+#include <format.h>
+
+#include "irccd.hpp"
+#include "logger.hpp"
+#include "plugin.hpp"
+#include "server.hpp"
+#include "service-plugin.hpp"
+#include "service-rule.hpp"
+#include "service-server.hpp"
+#include "service-transport.hpp"
+#include "util.hpp"
+
+using namespace fmt::literals;
+using namespace nlohmann;
+
+namespace irccd {
+
+class EventHandler {
+public:
+    std::string server;
+    std::string origin;
+    std::string target;
+    std::function<std::string (Plugin &)> functionName;
+    std::function<void (Plugin &)> functionExec;
+
+    void operator()(Irccd &irccd) const
+    {
+        for (auto &plugin : irccd.plugins().list()) {
+            auto eventname = functionName(*plugin);
+            auto allowed = irccd.rules().solve(server, target, origin, plugin->name(), eventname);
+
+            if (!allowed) {
+                log::debug() << "rule: event skipped on match" << std::endl;
+                continue;
+            } else
+                log::debug() << "rule: event allowed" << std::endl;
+
+            // TODO: server-event must not know which type of plugin.
+            // TODO: get generic error.
+            // TODO: this is the responsability of service-plugin.
+            try {
+                functionExec(*plugin);
+            } catch (const std::exception &ex) {
+                log::warning() << "plugin " << plugin->name() << ": error: " << ex.what() << std::endl;
+            }
+        }
+    }
+};
+
+void ServerService::handleChannelMode(const ChannelModeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onChannelMode:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  mode: " << ev.mode << "\n";
+    log::debug() << "  argument: " << ev.argument << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onChannelMode"     },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "mode",       ev.mode             },
+        { "argument",   ev.argument         }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onChannelMode";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onChannelMode(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleChannelNotice(const ChannelNoticeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onChannelNotice:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onChannelNotice"   },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onChannelNotice";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onChannelNotice(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleConnect(const ConnectEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onConnect" << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onConnect"         },
+        { "server",     ev.server->name()   }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onConnect";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onConnect(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleInvite(const InviteEvent &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;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onInvite"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onInvite";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onInvite(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleJoin(const JoinEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onJoin:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  channel: " << ev.channel << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onJoin"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onJoin";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onJoin(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleKick(const KickEvent &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;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onKick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "target",     ev.target           },
+        { "reason",     ev.reason           }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onKick";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onKick(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleMessage(const MessageEvent &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;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMessage"         },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &plugin) -> std::string {
+            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage";
+        },
+        [=] (Plugin &plugin) mutable {
+            auto copy = ev;
+            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
+
+            copy.message = pack.first;
+
+            if (pack.second == util::MessageType::Command)
+                plugin.onCommand(m_irccd, copy);
+            else
+                plugin.onMessage(m_irccd, copy);
+        }
+    });
+}
+
+void ServerService::handleMe(const MeEvent &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;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMe"              },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "target",     ev.channel          },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onMe";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onMe(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleMode(const ModeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onMode\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  mode: " << ev.mode << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onMode"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "mode",       ev.mode             }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onMode";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onMode(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleNames(const NamesEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNames:\n";
+    log::debug() << "  channel: " << ev.channel << "\n";
+    log::debug() << "  names: " << util::join(ev.names.begin(), ev.names.end(), ", ") << std::endl;
+
+    auto names = json::array();
+
+    for (const auto &v : ev.names)
+        names.push_back(v);
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNames"           },
+        { "server",     ev.server->name()   },
+        { "channel",    ev.channel          },
+        { "names",      std::move(names)    }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onNames";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onNames(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleNick(const NickEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNick:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  nickname: " << ev.nickname << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNick"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "nickname",   ev.nickname         }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onNick";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onNick(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleNotice(const NoticeEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onNotice:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onNotice"          },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onNotice";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onNotice(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handlePart(const PartEvent &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;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onPart"            },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "reason",     ev.reason           }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onPart";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onPart(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleQuery(const QueryEvent &ev)
+{
+    log::debug() << "server " << ev.server->name() << ": event onQuery:\n";
+    log::debug() << "  origin: " << ev.origin << "\n";
+    log::debug() << "  message: " << ev.message << std::endl;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onQuery"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "message",    ev.message          }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
+        [=] (Plugin &plugin) -> std::string {
+            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery";
+        },
+        [=] (Plugin &plugin) mutable {
+            auto copy = ev;
+            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
+
+            copy.message = pack.first;
+
+            if (pack.second == util::MessageType::Command)
+                plugin.onQueryCommand(m_irccd, copy);
+            else
+                plugin.onQuery(m_irccd, copy);
+        }
+    });
+}
+
+void ServerService::handleTopic(const TopicEvent &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;
+
+    m_irccd.transports().broadcast(nlohmann::json::object({
+        { "event",      "onTopic"           },
+        { "server",     ev.server->name()   },
+        { "origin",     ev.origin           },
+        { "channel",    ev.channel          },
+        { "topic",      ev.topic            }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
+        [=] (Plugin &) -> std::string {
+            return "onTopic";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onTopic(m_irccd, ev);
+        }
+    });
+}
+
+void ServerService::handleWhois(const WhoisEvent &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: " << util::join(ev.whois.channels.begin(), ev.whois.channels.end()) << std::endl;
+
+    m_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   }
+    }));
+
+    m_irccd.post(EventHandler{ev.server->name(), /* origin */ "", /* channel */ "",
+        [=] (Plugin &) -> std::string {
+            return "onWhois";
+        },
+        [=] (Plugin &plugin) {
+            plugin.onWhois(m_irccd, ev);
+        }
+    });
+}
+
+ServerService::ServerService(Irccd &irccd)
+    : m_irccd(irccd)
+{
+}
+
+void ServerService::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    for (auto &server : m_servers) {
+        server->update();
+        server->prepare(in, out, max);
+    }
+}
+
+void ServerService::sync(fd_set &in, fd_set &out)
+{
+    for (auto &server : m_servers)
+        server->sync(in, out);
+}
+
+bool ServerService::has(const std::string &name) const noexcept
+{
+    return std::count_if(m_servers.cbegin(), m_servers.end(), [&] (const auto &server) {
+        return server->name() == name;
+    }) > 0;
+}
+
+void ServerService::add(std::shared_ptr<Server> server)
+{
+    assert(!has(server->name()));
+
+    using namespace std::placeholders;
+
+    std::weak_ptr<Server> ptr(server);
+
+    server->onChannelMode.connect(std::bind(&ServerService::handleChannelMode, this, _1));
+    server->onChannelNotice.connect(std::bind(&ServerService::handleChannelNotice, this, _1));
+    server->onConnect.connect(std::bind(&ServerService::handleConnect, this, _1));
+    server->onInvite.connect(std::bind(&ServerService::handleInvite, this, _1));
+    server->onJoin.connect(std::bind(&ServerService::handleJoin, this, _1));
+    server->onKick.connect(std::bind(&ServerService::handleKick, this, _1));
+    server->onMessage.connect(std::bind(&ServerService::handleMessage, this, _1));
+    server->onMe.connect(std::bind(&ServerService::handleMe, this, _1));
+    server->onMode.connect(std::bind(&ServerService::handleMode, this, _1));
+    server->onNames.connect(std::bind(&ServerService::handleNames, this, _1));
+    server->onNick.connect(std::bind(&ServerService::handleNick, this, _1));
+    server->onNotice.connect(std::bind(&ServerService::handleNotice, this, _1));
+    server->onPart.connect(std::bind(&ServerService::handlePart, this, _1));
+    server->onQuery.connect(std::bind(&ServerService::handleQuery, this, _1));
+    server->onTopic.connect(std::bind(&ServerService::handleTopic, this, _1));
+    server->onWhois.connect(std::bind(&ServerService::handleWhois, this, _1));
+    server->onDie.connect([this, ptr] () {
+        m_irccd.post([=] (Irccd &) {
+            auto server = ptr.lock();
+
+            if (server) {
+                log::info("server {}: removed"_format(server->name()));
+                m_servers.erase(std::find(m_servers.begin(), m_servers.end(), server));
+            }
+        });
+    });
+
+    m_servers.push_back(std::move(server));
+}
+
+std::shared_ptr<Server> ServerService::get(const std::string &name) const noexcept
+{
+    auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) {
+        return server->name() == name;
+    });
+
+    if (it == m_servers.end())
+        return nullptr;
+
+    return *it;
+}
+
+std::shared_ptr<Server> ServerService::require(const std::string &name) const
+{
+    auto server = get(name);
+
+    if (!server)
+        throw std::invalid_argument("server {} not found"_format(name));
+
+    return server;
+}
+
+void ServerService::remove(const std::string &name)
+{
+    auto it = std::find_if(m_servers.begin(), m_servers.end(), [&] (const auto &server) {
+        return server->name() == name;
+    });
+
+    if (it != m_servers.end()) {
+        (*it)->disconnect();
+        m_servers.erase(it);
+    }
+}
+
+void ServerService::clear() noexcept
+{
+    for (auto &server : m_servers)
+        server->disconnect();
+
+    m_servers.clear();
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-server.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,141 @@
+/*
+ * service-server.hpp -- manage IRC servers
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_SERVER_HPP
+#define IRCCD_SERVICE_SERVER_HPP
+
+/**
+ * \file service-server.hpp
+ * \brief Manage IRC servers.
+ */
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "server.hpp"
+
+namespace irccd {
+
+class Irccd;
+
+/**
+ * \brief Manage IRC servers.
+ * \ingroup services
+ */
+class ServerService {
+private:
+    Irccd &m_irccd;
+    std::vector<std::shared_ptr<Server>> m_servers;
+
+    void handleChannelMode(const ChannelModeEvent &);
+    void handleChannelNotice(const ChannelNoticeEvent &);
+    void handleConnect(const ConnectEvent &);
+    void handleInvite(const InviteEvent &);
+    void handleJoin(const JoinEvent &);
+    void handleKick(const KickEvent &);
+    void handleMessage(const MessageEvent &);
+    void handleMe(const MeEvent &);
+    void handleMode(const ModeEvent &);
+    void handleNames(const NamesEvent &);
+    void handleNick(const NickEvent &);
+    void handleNotice(const NoticeEvent &);
+    void handlePart(const PartEvent &);
+    void handleQuery(const QueryEvent &);
+    void handleTopic(const TopicEvent &);
+    void handleWhois(const WhoisEvent &);
+
+public:
+    /**
+     * Create the server service.
+     */
+    IRCCD_EXPORT ServerService(Irccd &instance);
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Get the list of servers
+     *
+     * \return the servers
+     */
+    inline const std::vector<std::shared_ptr<Server>> &servers() const noexcept
+    {
+        return m_servers;
+    }
+
+    /**
+     * Check if a server exists.
+     *
+     * \param name the name
+     * \return true if exists
+     */
+    IRCCD_EXPORT bool has(const std::string &name) const noexcept;
+
+    /**
+     * Add a new server to the application.
+     *
+     * \pre hasServer must return false
+     * \param sv the server
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT 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
+     */
+    IRCCD_EXPORT void remove(const std::string &name);
+
+    /**
+     * Remove all servers.
+     *
+     * All servers will be disconnected.
+     */
+    IRCCD_EXPORT void clear() noexcept;
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_SERVER_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-transport.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,164 @@
+/*
+ * service-transport.cpp -- manage transport servers and clients
+ *
+ * Copyright (c) 2013-2016 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.hpp"
+#include "logger.hpp"
+#include "service-command.hpp"
+#include "service-transport.hpp"
+#include "transport.hpp"
+
+namespace irccd {
+
+void TransportService::handleCommand(std::weak_ptr<TransportClient> ptr, const nlohmann::json &object)
+{
+    assert(object.is_object());
+
+    m_irccd.post([=] (Irccd &) {
+        // 0. Be sure the object still exists.
+        auto tc = ptr.lock();
+
+        if (!tc)
+            return;
+
+        // 1. Check if the Json object is valid.
+        auto name = object.find("command");
+        if (name == object.end() || !name->is_string()) {
+            // TODO: send error.
+            log::warning("invalid command object");
+            return;
+        }
+
+        // 2. Search for a command
+        auto cmd = m_irccd.commands().find(*name);
+
+        if (!cmd) {
+            // TODO: send error again.
+            log::warning("command does not exists");
+            return;
+        }
+
+        // 3. Try to execute it.
+        auto response = nlohmann::json::object({});
+
+        try {
+            response = cmd->exec(m_irccd, object);
+
+            // Adjust if command has returned something else.
+            if (!response.is_object())
+                response = nlohmann::json::object({});
+
+            response.push_back({"status", true});
+        } catch (const std::exception &ex) {
+            response.push_back({"status", false});
+            response.push_back({"error", ex.what()});
+        }
+
+        // 4. Store the command name result.
+        response.push_back({"response", *name});
+
+        // 5. Send the result.
+        tc->send(response);
+    });
+}
+
+void TransportService::handleDie(std::weak_ptr<TransportClient> ptr)
+{
+    m_irccd.post([=] (Irccd &) {
+        log::info("transport: client disconnected");
+
+        auto tc = ptr.lock();
+
+        if (tc)
+            m_clients.erase(std::find(m_clients.begin(), m_clients.end(), tc));
+    });
+}
+
+TransportService::TransportService(Irccd &irccd) noexcept
+    : m_irccd(irccd)
+{
+}
+
+void TransportService::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    // Add transport servers.
+    for (const auto &transport : m_servers) {
+        FD_SET(transport->handle(), &in);
+
+        if (transport->handle() > max)
+            max = transport->handle();
+    }
+
+    // Transport clients.
+    for (const auto &client : m_clients)
+        client->prepare(in, out, max);
+}
+
+void TransportService::sync(fd_set &in, fd_set &out)
+{
+    using namespace std::placeholders;
+
+    // Transport clients.
+    for (const auto &client : m_clients) {
+        try {
+            client->sync(in, out);
+        } catch (const std::exception &ex) {
+            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
+            handleDie(client);
+        }
+    }
+
+    // Transport servers.
+    for (const auto &transport : m_servers) {
+        if (!FD_ISSET(transport->handle(), &in))
+            continue;
+
+        log::debug("transport: new client connected");
+
+        std::shared_ptr<TransportClient> client = transport->accept();
+        std::weak_ptr<TransportClient> ptr(client);
+
+        try {
+            // Connect signals.
+            client->onCommand.connect(std::bind(&TransportService::handleCommand, this, ptr, _1));
+            client->onDie.connect(std::bind(&TransportService::handleDie, this, ptr));
+
+            // Register it.
+            m_clients.push_back(std::move(client));
+        } catch (const std::exception &ex) {
+            log::info() << "transport: client disconnected: " << ex.what() << std::endl;
+        }
+    }
+}
+
+void TransportService::add(std::shared_ptr<TransportServer> ts)
+{
+    m_servers.push_back(std::move(ts));
+}
+
+void TransportService::broadcast(const nlohmann::json &json)
+{
+    assert(json.is_object());
+
+    for (const auto &client : m_clients)
+        if (client->state() == TransportClient::Ready)
+            client->send(json);
+}
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/service-transport.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,84 @@
+/*
+ * service-transport.hpp -- manage transport servers and clients
+ *
+ * Copyright (c) 2013-2016 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_SERVICE_TRANSPORT_HPP
+#define IRCCD_SERVICE_TRANSPORT_HPP
+
+/**
+ * \file service-transport.hpp
+ * \brief manage transport servers and clients.
+ */
+
+#include <json.hpp>
+
+namespace irccd {
+
+class TransportServer;
+class TransportClient;
+
+/**
+ * \brief manage transport servers and clients.
+ * \ingroup services
+ */
+class TransportService {
+private:
+    Irccd &m_irccd;
+
+    std::vector<std::shared_ptr<TransportServer>> m_servers;
+    std::vector<std::shared_ptr<TransportClient>> m_clients;
+
+    void handleCommand(std::weak_ptr<TransportClient>, const nlohmann::json &);
+    void handleDie(std::weak_ptr<TransportClient>);
+
+public:
+    /**
+     * Create the transport service.
+     *
+     * \param irccd the irccd instance
+     */
+    IRCCD_EXPORT TransportService(Irccd &irccd) noexcept;
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT void sync(fd_set &in, fd_set &out);
+
+    /**
+     * Add a transport server.
+     *
+     * \param ts the transport server
+     */
+    IRCCD_EXPORT void add(std::shared_ptr<TransportServer> ts);
+
+    /**
+     * Send data to all clients.
+     *
+     * \pre object.is_object()
+     * \param object the json object
+     */
+    IRCCD_EXPORT void broadcast(const nlohmann::json &object);
+};
+
+} // !irccd
+
+#endif // !IRCCD_SERVICE_TRANSPORT_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/transport.cpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,403 @@
+/*
+ * transport.cpp -- irccd transports
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+#include <cstdio>
+
+#include "transport.hpp"
+
+namespace irccd {
+
+/*
+ * TransportClient
+ * ------------------------------------------------------------------
+ */
+
+void TransportClient::error(const std::string &msg)
+{
+    m_state = Closing;
+
+    send({{ "error", msg }});
+}
+
+void TransportClient::flush() noexcept
+{
+    for (std::size_t pos; (pos = m_input.find("\r\n\r\n")) != std::string::npos; ) {
+        auto message = m_input.substr(0, pos);
+
+        m_input.erase(m_input.begin(), m_input.begin() + pos + 4);
+
+        try {
+            auto document = nlohmann::json::parse(message);
+
+            if (!document.is_object())
+                error("invalid argument");
+            else
+                onCommand(document);
+        } catch (const std::exception &ex) {
+            error(ex.what());
+        }
+    }
+}
+
+void TransportClient::authenticate() noexcept
+{
+    auto pos = m_input.find("\r\n\r\n");
+
+    if (pos == std::string::npos)
+        return;
+
+    auto msg = m_input.substr(0, pos);
+
+    m_input.erase(m_input.begin(), m_input.begin() + pos + 4);
+
+    try {
+        auto doc = nlohmann::json::parse(msg);
+
+        if (!doc.is_object())
+            error("invalid argument");
+
+        auto cmd = doc.find("command");
+
+        if (cmd == doc.end() || !cmd->is_string() || *cmd != "auth")
+            error("authentication required");
+
+        auto pw = doc.find("password");
+        auto result = true;
+
+        if (pw == doc.end() || !pw->is_string() || *pw != m_parent.password()) {
+            m_state = Closing;
+            result = false;
+        } else
+            m_state = Ready;
+
+        send({
+            { "response", "auth" },
+            { "result", result }
+        });
+    } catch (const std::exception &ex) {
+        error(ex.what());
+    }
+}
+
+void TransportClient::recv() noexcept
+{
+    try {
+        std::string buffer;
+
+        buffer.resize(512);
+        buffer.resize(recv(&buffer[0], buffer.size()));
+
+        if (buffer.empty())
+            onDie();
+
+        m_input += std::move(buffer);
+    } catch (const std::exception &) {
+        onDie();
+    }
+}
+
+void TransportClient::send() noexcept
+{
+    try {
+        auto ns = send(&m_output[0], m_output.size());
+
+        if (ns == 0)
+            onDie();
+
+        m_output.erase(0, ns);
+    } catch (const std::exception &ex) {
+        onDie();
+    }
+}
+
+unsigned TransportClient::recv(void *buffer, unsigned length)
+{
+    return m_socket.recv(buffer, length);
+}
+
+unsigned TransportClient::send(const void *buffer, unsigned length)
+{
+    return m_socket.send(buffer, length);
+}
+
+TransportClient::TransportClient(TransportServer &parent, net::TcpSocket socket)
+    : m_parent(parent)
+    , m_socket(std::move(socket))
+{
+    assert(m_socket.isOpen());
+
+    m_socket.set(net::option::SockBlockMode(false));
+
+    // Send some information.
+    auto object = nlohmann::json::object({
+        { "program",    "irccd"                 },
+        { "major",      IRCCD_VERSION_MAJOR     },
+        { "minor",      IRCCD_VERSION_MINOR     },
+        { "patch",      IRCCD_VERSION_PATCH     }
+    });
+
+#if defined(WITH_JS)
+    object.push_back({"javascript", true});
+#endif
+#if defined(WITH_SSL)
+    object.push_back({"ssl", true});
+#endif
+
+    send(object);
+}
+
+void TransportClient::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    if (m_socket.handle() > max)
+        max = m_socket.handle();
+
+    switch (m_state) {
+    case Greeting:
+        FD_SET(m_socket.handle(), &out);
+        break;
+    case Authenticating:
+        FD_SET(m_socket.handle(), &in);
+        break;
+    case Ready:
+        FD_SET(m_socket.handle(), &in);
+
+        if (!m_output.empty())
+            FD_SET(m_socket.handle(), &out);
+        break;
+    case Closing:
+        if (!m_output.empty())
+            FD_SET(m_socket.handle(), &out);
+        else
+            onDie();
+        break;
+    default:
+        break;
+    }
+}
+
+void TransportClient::sync(fd_set &in, fd_set &out)
+{
+    switch (m_state) {
+    case Greeting:
+        send();
+
+        if (m_output.empty())
+            m_state = m_parent.password().empty() ? Ready : Authenticating;
+
+        break;
+    case Authenticating:
+        if (FD_ISSET(m_socket.handle(), &in))
+            recv();
+
+        authenticate();
+        break;
+    case Ready:
+        if (FD_ISSET(m_socket.handle(), &in))
+            recv();
+        if (FD_ISSET(m_socket.handle(), &out))
+            send();
+
+        flush();
+        break;
+    case Closing:
+        if (FD_ISSET(m_socket.handle(), &out))
+            send();
+        break;
+    default:
+        break;
+    }
+}
+
+void TransportClient::send(const nlohmann::json &json)
+{
+    assert(json.is_object());
+
+    m_output += json.dump();
+    m_output += "\r\n\r\n";
+}
+
+/*
+ * TransportClientTls
+ * ------------------------------------------------------------------
+ */
+
+void TransportClientTls::handshake()
+{
+    try {
+        m_ssl.handshake();
+        m_handshake = HandshakeReady;
+    } catch (const net::WantReadError &) {
+        m_handshake = HandshakeRead;
+    } catch (const net::WantWriteError &) {
+        m_handshake = HandshakeWrite;
+    } catch (const std::exception &) {
+        onDie();
+    }
+}
+
+TransportClientTls::TransportClientTls(const std::string &pkey,
+                                       const std::string &cert,
+                                       TransportServer &server,
+                                       net::TcpSocket socket)
+    : TransportClient(server, std::move(socket))
+    , m_ssl(m_socket)
+{
+    m_ssl.setPrivateKey(pkey);
+    m_ssl.setCertificate(cert);
+
+    handshake();
+}
+
+unsigned TransportClientTls::recv(void *buffer, unsigned length)
+{
+    unsigned nread = 0;
+
+    try {
+        nread = m_ssl.recv(buffer, length);
+    } catch (const net::WantReadError &) {
+        m_handshake = HandshakeRead;
+    } catch (const net::WantWriteError &) {
+        m_handshake = HandshakeWrite;
+    }
+
+    return nread;
+}
+
+unsigned TransportClientTls::send(const void *buffer, unsigned length)
+{
+    unsigned nsent = 0;
+
+    try {
+        nsent = m_ssl.send(buffer, length);
+    } catch (const net::WantReadError &) {
+        m_handshake = HandshakeRead;
+    } catch (const net::WantWriteError &) {
+        m_handshake = HandshakeWrite;
+    }
+
+    return nsent;
+}
+
+void TransportClientTls::prepare(fd_set &in, fd_set &out, net::Handle &max)
+{
+    if (m_socket.handle() > max)
+        max = m_socket.handle();
+
+    switch (m_handshake) {
+    case HandshakeRead:
+        FD_SET(m_socket.handle(), &in);
+        break;
+    case HandshakeWrite:
+        FD_SET(m_socket.handle(), &out);
+        break;
+    default:
+        TransportClient::prepare(in, out, max);
+        break;
+    }
+}
+
+void TransportClientTls::sync(fd_set &in, fd_set &out)
+{
+    switch (m_handshake) {
+    case HandshakeRead:
+    case HandshakeWrite:
+        handshake();
+        break;
+    default:
+        TransportClient::sync(in, out);
+    }
+}
+
+/*
+ * TransportServerIp
+ * ------------------------------------------------------------------
+ */
+
+TransportServerIp::TransportServerIp(const std::string &address,
+                                     std::uint16_t port,
+                                     std::uint8_t mode)
+    : TransportServer(net::TcpSocket((mode & v6) ? AF_INET6 : AF_INET, 0))
+{
+    assert((mode & v6) || (mode & v4));
+
+    m_socket.set(net::option::SockReuseAddress(true));
+
+    if (mode & v6) {
+        if (address == "*")
+            m_socket.bind(net::ipv6::any(port));
+        else
+            m_socket.bind(net::ipv6::pton(address, port));
+
+        // Disable or enable IPv4 when using IPv6.
+        if (!(mode & v4))
+            m_socket.set(net::option::Ipv6Only(true));
+    } else {
+        if (address == "*")
+            m_socket.bind(net::ipv4::any(port));
+        else
+            m_socket.bind(net::ipv4::pton(address, port));
+    }
+
+    m_socket.listen();
+}
+
+/*
+ * TransportServerTls
+ * ------------------------------------------------------------------
+ */
+
+TransportServerTls::TransportServerTls(const std::string &pkey,
+                                       const std::string &cert,
+                                       const std::string &address,
+                                       std::uint16_t port,
+                                       std::uint8_t mode)
+    : TransportServerIp(address, port, mode)
+    , m_privatekey(pkey)
+    , m_cert(cert)
+{
+}
+
+std::unique_ptr<TransportClient> TransportServerTls::accept()
+{
+    return std::make_unique<TransportClientTls>(m_privatekey, m_cert, *this, m_socket.accept());
+}
+
+/*
+ * TransportServerLocal
+ * ------------------------------------------------------------------
+ */
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+TransportServerLocal::TransportServerLocal(std::string path)
+    : TransportServer(net::TcpSocket(AF_LOCAL, 0))
+    , m_path(std::move(path))
+{
+    m_socket.bind(net::local::create(m_path, true));
+    m_socket.listen();
+}
+
+TransportServerLocal::~TransportServerLocal()
+{
+    ::remove(m_path.c_str());
+}
+
+#endif
+
+} // !irccd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/transport.hpp	Wed Oct 05 13:27:15 2016 +0200
@@ -0,0 +1,388 @@
+/*
+ * transport.hpp -- irccd transports
+ *
+ * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef IRCCD_TRANSPORT_HPP
+#define IRCCD_TRANSPORT_HPP
+
+/**
+ * \file transport.hpp
+ * \brief Irccd transports.
+ */
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include <json.hpp>
+
+#include "net.hpp"
+#include "signals.hpp"
+#include "sysconfig.hpp"
+
+namespace irccd {
+
+class TransportServer;
+
+/**
+ * \class TransportClient
+ * \brief Client connected to irccd.
+ *
+ * This class emits a warning upon clients request through onCommand signal.
+ */
+class TransportClient {
+public:
+    /**
+     * \brief Client state
+     */
+    enum State {
+        Greeting,               //!< client is getting irccd info
+        Authenticating,         //!< client requires authentication
+        Ready,                  //!< client is ready to use
+        Closing                 //!< client must disconnect
+    };
+
+    /**
+     * Signal: onCommand
+     * ----------------------------------------------------------
+     *
+     * Arguments:
+     *   - the command
+     */
+    Signal<const nlohmann::json &> onCommand;
+
+    /**
+     * Signal: onDie
+     * ----------------------------------------------------------
+     *
+     * The client has disconnected.
+     */
+    Signal<> onDie;
+
+private:
+    void error(const std::string &msg);
+    void flush() noexcept;
+    void authenticate() noexcept;
+
+protected:
+    State m_state{Greeting};    //!< current client state
+    TransportServer &m_parent;  //!< parent transport server
+    net::TcpSocket m_socket;    //!< socket
+    std::string m_input;        //!< input buffer
+    std::string m_output;       //!< output buffer
+
+    /**
+     * Fill the input buffer with available data.
+     */
+    void recv() noexcept;
+
+    /**
+     * Flush the output buffer from available pending data.
+     */
+    void send() noexcept;
+
+    /**
+     * Try to receive some data into the given buffer.
+     *
+     * \param buffer the destination buffer
+     * \param length the buffer length
+     * \return the number of bytes received
+     */
+    IRCCD_EXPORT virtual unsigned recv(void *buffer, unsigned length);
+
+    /**
+     * Try to send some data into the given buffer.
+     *
+     * \param buffer the source buffer
+     * \param length the buffer length
+     * \return the number of bytes sent
+     */
+    IRCCD_EXPORT virtual unsigned send(const void *buffer, unsigned length);
+
+public:
+    /**
+     * Create a transport client from the socket.
+     *
+     * \pre socket must be valid
+     * \param parent the parent server
+     * \param socket the new socket
+     */
+    IRCCD_EXPORT TransportClient(TransportServer &parent, net::TcpSocket socket);
+
+    /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~TransportClient() = default;
+
+    /**
+     * Get the client state.
+     *
+     * \return the client state
+     */
+    inline State state() const noexcept
+    {
+        return m_state;
+    }
+
+    /**
+     * Append some data to the output queue.
+     *
+     * \pre json.is_object()
+     * \param json the json object
+     */
+    IRCCD_EXPORT void send(const nlohmann::json &json);
+
+    /**
+     * \copydoc Service::prepare
+     */
+    IRCCD_EXPORT virtual void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc Service::sync
+     */
+    IRCCD_EXPORT virtual void sync(fd_set &in, fd_set &out);
+};
+
+/*
+ * TransportClientTls
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief TLS version of transport client.
+ */
+class TransportClientTls : public TransportClient {
+private:
+    enum {
+        HandshakeWrite,
+        HandshakeRead,
+        HandshakeReady
+    } m_handshake{HandshakeReady};
+
+    net::TlsSocket m_ssl;
+
+    void handshake();
+
+protected:
+    /**
+     * \copydoc TransportClient::recv
+     */
+    unsigned recv(void *buffer, unsigned length) override;
+
+    /**
+     * \copydoc TransportClient::send
+     */
+    unsigned send(const void *buffer, unsigned length) override;
+
+public:
+    /**
+     * Create the transport client.
+     *
+     * \pre socket.isOpen()
+     * \param pkey the private key
+     * \param cert the certificate file
+     * \param socket the accepted socket
+     * \param parent the parent server
+     * \param socket the new socket
+     */
+    IRCCD_EXPORT TransportClientTls(const std::string &pkey,
+                                    const std::string &cert,
+                                    TransportServer &server,
+                                    net::TcpSocket socket);
+
+    /**
+     * \copydoc TransportClient::prepare
+     */
+    IRCCD_EXPORT virtual void prepare(fd_set &in, fd_set &out, net::Handle &max);
+
+    /**
+     * \copydoc TransportClient::sync
+     */
+    IRCCD_EXPORT virtual void sync(fd_set &in, fd_set &out);
+};
+
+/*
+ * TransportServer
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Bring networking between irccd and irccdctl.
+ *
+ * This class contains a master sockets for listening to TCP connections, it is
+ * then processed by irccd.
+ *
+ * The transport class supports the following domains:
+ *
+ * | Domain                | Class                 |
+ * |-----------------------|-----------------------|
+ * | IPv4, IPv6            | TransportServerIp     |
+ * | Unix (not on Windows) | TransportServerUnix   |
+ *
+ * Note: IPv4 and IPv6 can be combined, using TransportServer::IPv6 and its
+ * option.
+ */
+class TransportServer {
+private:
+    TransportServer(const TransportServer &) = delete;
+    TransportServer(TransportServer &&) = delete;
+
+    TransportServer &operator=(const TransportServer &) = delete;
+    TransportServer &operator=(TransportServer &&) = delete;
+
+protected:
+    net::TcpSocket m_socket;
+    std::string m_password;
+
+public:
+    /**
+     * Default constructor.
+     */
+    inline TransportServer(net::TcpSocket socket)
+        : m_socket(std::move(socket))
+    {
+    }
+
+    /**
+     * Get the socket handle for this transport.
+     *
+     * \return the handle
+     */
+    inline net::Handle handle() const noexcept
+    {
+        return m_socket.handle();
+    }
+
+    /**
+     * Get the password.
+     *
+     * \return the password
+     */
+    inline const std::string &password() const noexcept
+    {
+        return m_password;
+    }
+
+    /**
+     * Set an optional password.
+     *
+     * \return the password
+     */
+    inline void setPassword(std::string password) noexcept
+    {
+        m_password = std::move(password);
+    }
+
+    /**
+     * Destructor defaulted.
+     */
+    virtual ~TransportServer() = default;
+
+    /**
+     * Accept a new client depending on the domain.
+     *
+     * \return the new client
+     */
+    virtual std::unique_ptr<TransportClient> accept()
+    {
+        return std::make_unique<TransportClient>(*this, m_socket.accept());
+    }
+};
+
+/**
+ * \brief Create IP transport.
+ */
+class TransportServerIp : public TransportServer {
+public:
+    /**
+     * \brief Domain to use.
+     */
+    enum Mode {
+        v4 = (1 << 0),      //!< IPv6
+        v6 = (1 << 1)       //!< IPv4
+    };
+
+    /**
+     * Constructor.
+     * \pre mode > 0
+     * \param address the address (* for any)
+     * \param port the port number
+     * \param mode the domains to use (can be OR'ed)
+     */
+    IRCCD_EXPORT TransportServerIp(const std::string &address,
+                                   std::uint16_t port,
+                                   std::uint8_t mode = v4);
+};
+
+/**
+ * \brief TLS over IP transport.
+ */
+class TransportServerTls : public TransportServerIp {
+private:
+    std::string m_privatekey;
+    std::string m_cert;
+
+public:
+    /**
+     * Constructor.
+     * \pre mode > 0
+     * \param pkey the private key file
+     * \param cert the certificate file
+     * \param address the address (* for any)
+     * \param port the port number
+     * \param mode the domains to use (can be OR'ed)
+     */
+    IRCCD_EXPORT TransportServerTls(const std::string &pkey,
+                                    const std::string &cert,
+                                    const std::string &address,
+                                    std::uint16_t port,
+                                    std::uint8_t mode = v4);
+
+    /**
+     * \copydoc TransportServer::accept
+     */
+    IRCCD_EXPORT std::unique_ptr<TransportClient> accept() override;
+};
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+
+/**
+ * \brief Implementation of transports for Unix sockets.
+ */
+class TransportServerLocal : public TransportServer {
+private:
+    std::string m_path;
+
+public:
+    /**
+     * Create a Unix transport.
+     *
+     * \param path the path
+     */
+    IRCCD_EXPORT TransportServerLocal(std::string path);
+
+    /**
+     * Destroy the transport and remove the file.
+     */
+    IRCCD_EXPORT ~TransportServerLocal();
+};
+
+#endif // !IRCCD_SYSTEM_WINDOWS
+
+} // !irccd
+
+#endif // !IRCCD_TRANSPORT_HPP