changeset 585:1ad88e2e3086

Irccd: config class no longer load things, closes #744
author David Demelier <markand@malikania.fr>
date Tue, 05 Dec 2017 20:45:21 +0100
parents 2c3122f23a04
children f462cc16b517
files irccd/main.cpp libcommon/irccd/ini.cpp libcommon/irccd/ini.hpp libirccd-js/irccd/js/js_plugin.cpp libirccd-js/irccd/js/js_plugin.hpp libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/plugin.cpp libirccd/irccd/plugin.hpp libirccd/irccd/plugin_service.cpp libirccd/irccd/plugin_service.hpp libirccd/irccd/rule_service.cpp libirccd/irccd/rule_service.hpp libirccd/irccd/server.cpp libirccd/irccd/server.hpp libirccd/irccd/server_service.cpp libirccd/irccd/server_service.hpp libirccd/irccd/transport_service.cpp libirccd/irccd/transport_service.hpp
diffstat 21 files changed, 753 insertions(+), 799 deletions(-) [+]
line wrap: on
line diff
--- a/irccd/main.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/irccd/main.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -156,120 +156,10 @@
 {
     auto it = result.find("-c");
 
-    if (it != result.end() || (it = result.find("--config")) != result.end()) {
-        try {
-            return config(it->second);
-        } catch (const std::exception &ex) {
-            throw std::runtime_error(string_util::sprintf("%s: %s", it->second, ex.what()));
-        }
-    }
-
-    return config::find();
-}
-
-void load_pid(const std::string& path)
-{
-    if (path.empty())
-        return;
-
-    try {
-#if defined(HAVE_GETPID)
-        std::ofstream out(path, std::ofstream::trunc);
-
-        if (!out)
-            throw std::runtime_error(string_util::sprintf("could not open pidfile %s: %s",
-                path, std::strerror(errno)));
-
-        log::debug() << "irccd: pid written in " << path << std::endl;
-        out << getpid() << std::endl;
-#else
-        throw std::runtime_error("pidfile option not supported on this platform");
-#endif
-    } catch (const std::exception& ex) {
-        log::warning() << "irccd: " << ex.what() << std::endl;
-    }
-}
-
-void load_gid(const std::string& gid)
-{
-    try {
-        if (!gid.empty())
-#if defined(HAVE_SETGID)
-            sys::set_gid(gid);
-#else
-            throw std::runtime_error(" gid option not supported on this platform");
-#endif
-    } catch (const std::exception& ex) {
-        log::warning() << "irccd: " << ex.what() << std::endl;
-    }
-}
+    if (it != result.end() || (it = result.find("--config")) != result.end())
+        return config(it->second);
 
-void load_uid(const std::string& uid)
-{
-    try {
-        if (!uid.empty())
-#if defined(HAVE_SETUID)
-            sys::set_uid(uid);
-#else
-            throw std::runtime_error("uid option not supported on this platform");
-#endif
-    } catch (const std::exception& ex) {
-        log::warning() << "irccd: " << ex.what() << std::endl;
-    }
-}
-
-void load_foreground(bool foreground, const option::result& options)
-{
-    try {
-#if defined(HAVE_DAEMON)
-        if (options.count("-f") == 0 && options.count("--foreground") == 0 && !foreground)
-            daemon(1, 0);
-#else
-        if (options.count("-f") > 0 || options.count("--foreground") > 0 || foreground)
-            throw std::runtime_error("foreground option not supported on this platform");
-#endif
-    } catch (const std::exception& ex) {
-        log::warning() << "irccd: " << ex.what() << std::endl;
-    }
-}
-
-void load(const config& config, const option::result& options)
-{
-    instance->set_config(config.path());
-
-    /*
-     * Order matters, please be careful when changing this.
-     *
-     * 1. Open logs as early as possible to use the defined outputs on any
-     *    loading errors.
-     */
-
-    // [logs] and [format] sections.
-    config.load_logs();
-    config.load_formats();
-
-    // Show message here to use the formats.
-    log::info() << "irccd: using " << config.path() << std::endl;
-
-    // [general] section.
-    load_pid(config.pidfile());
-    load_gid(config.gid());
-    load_uid(config.uid());
-    load_foreground(config.is_foreground(), options);
-
-    // [transport]
-    config.load_transports(*instance);
-
-    // [server] section.
-    for (const auto& server : config.load_servers(*instance))
-        instance->servers().add(server);
-
-    // [rule] section.
-    for (const auto& rule : config.load_rules())
-        instance->rules().add(rule);
-
-    // [plugin] section.
-    config.load_plugins(*instance);
+    return config::find("irccd.conf");
 }
 
 } // !namespace
@@ -336,9 +226,10 @@
 #endif
 
     try {
-        load(open(options), options);
+        instance->set_config(open(options));
+        instance->load();
     } catch (const std::exception& ex) {
-        log::warning() << "error: " << ex.what() << std::endl;
+        log::warning() << "abort: " << ex.what() << std::endl;
         return 1;
     }
 
--- a/libcommon/irccd/ini.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libcommon/irccd/ini.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -25,7 +25,7 @@
 #include <stdexcept>
 
 // for PathIsRelative.
-#if defined(_WIN32)
+#if defined(IRCCD_SYSTEM_WINDOWS)
 #  include <Shlwapi.h>
 #endif
 
@@ -42,7 +42,7 @@
 
 inline bool is_absolute(const std::string& path) noexcept
 {
-#if defined(_WIN32)
+#if defined(IRCCD_SYSTEM_WINDOWS)
     return !PathIsRelative(path.c_str());
 #else
     return path.size() > 0 && path[0] == '/';
@@ -286,7 +286,7 @@
     std::string file;
 
     if (!is_absolute(value)) {
-#if defined(_WIN32)
+#if defined(IRCCD_SYSTEM_WINDOWS)
         file = path + "\\" + value;
 #else
         file = path + "/" + value;
--- a/libcommon/irccd/ini.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libcommon/irccd/ini.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -30,12 +30,6 @@
  * \page Ini Ini
  * \brief Extended .ini file parser.
  *
- * ## Export macros
- *
- * You must define `INI_DLL` globally and `INI_BUILDING_DLL` when compiling the
- * library if you want a DLL, alternatively you can provide your own
- * `` macro instead.
- *
  *   - \subpage ini-syntax
  */
 
@@ -112,6 +106,8 @@
  * ````
  */
 
+#include "sysconfig.hpp"
+
 #include <algorithm>
 #include <cassert>
 #include <exception>
@@ -595,7 +591,7 @@
  * \return the list of tokens
  * \throws exception on errors
  */
- tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end);
+tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end);
 
 /**
  * Overloaded function for stream.
@@ -604,7 +600,7 @@
  * \return the list of tokens
  * \throws exception on errors
  */
- tokens analyse(std::istream& stream);
+tokens analyse(std::istream& stream);
 
 /**
  * Parse the produced tokens.
@@ -614,7 +610,7 @@
  * \return the document
  * \throw exception on errors
  */
- document parse(const tokens& tokens, const std::string& path = ".");
+document parse(const tokens& tokens, const std::string& path = ".");
 
 /**
  * Parse a file.
@@ -623,7 +619,7 @@
  * \return the document
  * \throw exception on errors
  */
- document read_file(const std::string& filename);
+document read_file(const std::string& filename);
 
 /**
  * Parse a string.
@@ -635,17 +631,17 @@
  * \return the document
  * \throw exception on exceptions
  */
- document read_string(const std::string& buffer);
+document read_string(const std::string& buffer);
 
 /**
  * Show all tokens and their description.
  *
  * \param tokens the tokens
  */
- void dump(const tokens& tokens);
+void dump(const tokens& tokens);
 
 } // !ini
 
 } // !irccd
 
-#endif // !IRCCD_INI_HPP
+#endif // !INI_HPP
--- a/libirccd-js/irccd/js/js_plugin.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd-js/irccd/js/js_plugin.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -123,8 +123,9 @@
 {
     std::ifstream input(path());
 
+    // TODO: add error message here.
     if (!input)
-        throw std::runtime_error(std::strerror(errno));
+        throw plugin_error(plugin_error::exec_error);
 
     std::string data(
         std::istreambuf_iterator<char>(input.rdbuf()),
@@ -132,7 +133,7 @@
     );
 
     if (duk_peval_string(context_, data.c_str()))
-        throw dukx_stack(context_, -1);
+        throw plugin_error(plugin_error::exec_error);
 
     // Read metadata.
     duk_get_global_string(context_, "info");
@@ -343,25 +344,19 @@
 js_plugin_loader::~js_plugin_loader() noexcept = default;
 
 std::shared_ptr<plugin> js_plugin_loader::open(const std::string& id,
-                                               const std::string& path) noexcept
+                                               const std::string& path)
 {
     if (path.rfind(".js") == std::string::npos)
         return nullptr;
 
-    try {
-        auto plugin = std::make_shared<js_plugin>(id, path);
-
-        for (const auto& mod : modules_)
-            mod->load(irccd_, plugin);
+    auto plugin = std::make_shared<js_plugin>(id, path);
 
-        plugin->open();
+    for (const auto& mod : modules_)
+        mod->load(irccd_, plugin);
 
-        return plugin;
-    } catch (const std::exception &ex) {
-        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
-    }
+    plugin->open();
 
-    return nullptr;
+    return plugin;
 }
 
 } // !irccd
--- a/libirccd-js/irccd/js/js_plugin.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd-js/irccd/js/js_plugin.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -269,7 +269,7 @@
      * \copydoc PluginLoader::open
      */
     std::shared_ptr<plugin> open(const std::string& id,
-                                 const std::string& path) noexcept override;
+                                 const std::string& path) override;
 };
 
 } // !irccd
--- a/libirccd/irccd/config.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/config.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -16,539 +16,26 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <cassert>
+#include <stdexcept>
 
 #include <boost/filesystem.hpp>
 
+#include <irccd/system.hpp>
+
 #include "config.hpp"
-#include "irccd.hpp"
-#include "logger.hpp"
-#include "plugin_service.hpp"
-#include "rule.hpp"
-#include "server.hpp"
-#include "string_util.hpp"
-#include "sysconfig.hpp"
-#include "system.hpp"
-#include "transport_server.hpp"
-#include "transport_service.hpp"
 
 namespace irccd {
 
-namespace {
-
-class irccd_log_filter : public log::filter {
-private:
-    std::string convert(const std::string& tmpl, std::string input) const
-    {
-        if (tmpl.empty())
-            return input;
-
-        string_util::subst params;
-
-        params.flags &= ~(string_util::subst_flags::irc_attrs);
-        params.keywords.emplace("message", std::move(input));
-
-        return string_util::format(tmpl, params);
-    }
-
-public:
-    std::string debug_;
-    std::string info_;
-    std::string warning_;
-
-    std::string pre_debug(std::string input) const override
-    {
-        return convert(debug_, std::move(input));
-    }
-
-    std::string pre_info(std::string input) const override
-    {
-        return convert(info_, std::move(input));
-    }
-
-    std::string pre_warning(std::string input) const override
-    {
-        return convert(warning_, std::move(input));
-    }
-};
-
-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();
-}
-
-std::unique_ptr<log::logger> load_log_file(const ini::section& sc)
-{
-    /*
-     * TODO: improve that with CMake options.
-     */
-#if defined(IRCCD_SYSTEM_WINDOWS)
-    std::string normal = "log.txt";
-    std::string errors = "errors.txt";
-#else
-    std::string normal = "/var/log/irccd/log.txt";
-    std::string errors = "/var/log/irccd/errors.txt";
-#endif
-
-    ini::section::const_iterator it;
-
-    if ((it = sc.find("path-logs")) != sc.end())
-        normal = it->value();
-    if ((it = sc.find("path-errors")) != sc.end())
-        errors = it->value();
-
-    return std::make_unique<log::file_logger>(std::move(normal), std::move(errors));
-}
-
-std::unique_ptr<log::logger> load_log_syslog()
-{
-#if defined(HAVE_SYSLOG)
-    return std::make_unique<log::syslog_logger>();
-#else
-    throw std::runtime_error("logs: syslog is not available on this platform");
-#endif // !HAVE_SYSLOG
-}
-
-std::unique_ptr<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
+config config::find(const std::string& name)
 {
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> 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 = string_util::to_uint<std::uint16_t>(it->value());
-    } catch (const std::exception&) {
-        throw std::invalid_argument(string_util::sprintf("transport: invalid port number: %s", it->value()));
-    }
-
-    // Address.
-    std::string address = "*";
-
-    if ((it = sc.find("address")) != sc.end())
-        address = it->value();
-
-    // 0011
-    //    ^ define IPv4
-    //   ^  define IPv6
-    auto mode = 1U;
-
-    /*
-     * Documentation stated family but code checked for 'domain' option.
-     *
-     * As irccdctl uses domain, accept both and unify the option name to 'family'.
-     *
-     * See #637
-     */
-    if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
-        for (const auto& v : *it) {
-            if (v == "ipv4")
-                mode |= (1U << 0);
-            if (v == "ipv6")
-                mode |= (1U << 1);
-        }
-    }
-
-    if (mode == 0U)
-        throw std::invalid_argument("transport: family must at least have ipv4 or ipv6");
-
-    auto protocol = (mode & 0x2U)
-        ? boost::asio::ip::tcp::v4()
-        : boost::asio::ip::tcp::v6();
-
-    // Optional SSL.
-    std::string pkey;
-    std::string cert;
-
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(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();
-    }
-
-    auto endpoint = (address == "*")
-        ? boost::asio::ip::tcp::endpoint(protocol, port)
-        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port);
-
-    boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
-
-    if (pkey.empty())
-        return std::make_unique<ip_transport_server>(std::move(acceptor));
-
-#if defined(HAVE_SSL)
-    boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
-
-    ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem);
-    ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
-
-    return std::make_unique<tls_transport_server>(std::move(acceptor), std::move(ctx));
-#else
-    throw std::invalid_argument("transport: SSL disabled");
-#endif
-}
-
-std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
-{
-    using boost::asio::local::stream_protocol;
-
-    assert(sc.key() == "transport");
+    for (const auto& path : sys::config_filenames(name)) {
+        boost::system::error_code ec;
 
-#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");
-
-    // Remove the file first.
-    std::remove(it->value().c_str());
-
-    stream_protocol::endpoint endpoint(it->value());
-    stream_protocol::acceptor acceptor(service, std::move(endpoint));
-
-    return std::make_unique<local_transport_server>(std::move(acceptor));
-#else
-    (void)sc;
-
-    throw std::invalid_argument("transport: unix transport not supported on on this platform");
-#endif
-}
-
-std::unique_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
-{
-    assert(sc.key() == "transport");
-
-    std::unique_ptr<transport_server> transport;
-    ini::section::const_iterator it = sc.find("type");
-
-    if (it == sc.end())
-        throw std::invalid_argument("transport: missing 'type' parameter");
-
-    if (it->value() == "ip")
-        transport = load_transport_ip(service, sc);
-    else if (it->value() == "unix")
-        transport = load_transport_unix(service, sc);
-    else
-        throw std::invalid_argument(string_util::sprintf("transport: invalid type given: %s", it->value()));
-
-    if ((it = sc.find("password")) != sc.end())
-        transport->set_password(it->value());
-
-    return transport;
-}
-
-rule load_rule(const ini::section& sc)
-{
-    assert(sc.key() == "rule");
-
-    // Simple converter from std::vector to std::unordered_set.
-    auto toset = [] (const auto& v) {
-        return std::unordered_set<std::string>(v.begin(), v.end());
-    };
-
-    rule::set servers, channels, origins, plugins, events;
-    rule::action_type action = rule::action_type::accept;
-
-    // Get the sets.
-    ini::section::const_iterator it;
-
-    if ((it = sc.find("servers")) != sc.end())
-        servers = toset(*it);
-    if ((it = sc.find("channels")) != sc.end())
-        channels = toset(*it);
-    if ((it = sc.find("origins")) != sc.end())
-        origins = toset(*it);
-    if ((it = sc.find("plugins")) != sc.end())
-        plugins = toset(*it);
-    if ((it = sc.find("channels")) != sc.end())
-        channels = toset(*it);
-
-    // Get the action.
-    if ((it = sc.find("action")) == sc.end())
-        throw std::invalid_argument("rule: missing 'action'' parameter");
-
-    if (it->value() == "drop")
-        action = rule::action_type::drop;
-    else if (it->value() == "accept")
-        action = rule::action_type::accept;
-    else
-        throw std::invalid_argument(string_util::sprintf("rule: invalid action given: %s", it->value()));
-
-    return {
-        std::move(servers),
-        std::move(channels),
-        std::move(origins),
-        std::move(plugins),
-        std::move(events),
-        action
-    };
-}
-
-std::shared_ptr<server> load_server(irccd& daemon, 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 (!string_util::is_identifier(it->value()))
-        throw std::invalid_argument(string_util::sprintf("server: invalid identifier: %s", it->value()));
-
-    auto sv = std::make_shared<server>(daemon.service(), it->value());
-
-    // Host
-    if ((it = sc.find("host")) == sc.end())
-        throw std::invalid_argument(string_util::sprintf("server %s: missing host", sv->name()));
-
-    sv->set_host(it->value());
-
-    // Optional password
-    if ((it = sc.find("password")) != sc.end())
-        sv->set_password(it->value());
-
-    // Optional flags
-    if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::ipv6);
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
-#if defined(HAVE_SSL)
-        sv->set_flags(sv->flags() | server::ssl);
-#else
-        throw std::invalid_argument(string_util::sprintf("server %s: SSL support disabled", sv->name()));
-#endif
-    if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::ssl_verify);
-
-    // Optional identity
-    if ((it = sc.find("identity")) != sc.end())
-        config.load_server_identity(*sv, it->value());
-
-    // Options
-    if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::auto_rejoin);
-    if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::join_invite);
-
-    // Channels
-    if ((it = sc.find("channels")) != sc.end()) {
-        for (const auto& s : *it) {
-            channel channel;
-
-            if (auto pos = s.find(":") != std::string::npos) {
-                channel.name = s.substr(0, pos);
-                channel.password = s.substr(pos + 1);
-            } else
-                channel.name = s;
-
-            sv->join(channel.name, channel.password);
-        }
-    }
-    if ((it = sc.find("command-char")) != sc.end())
-        sv->set_command_char(it->value());
-
-    // Reconnect and ping timeout
-    try {
-        if ((it = sc.find("port")) != sc.end())
-            sv->set_port(string_util::to_uint<std::uint16_t>(it->value()));
-        if ((it = sc.find("reconnect-tries")) != sc.end())
-            sv->set_reconnect_tries(string_util::to_int<std::int8_t>(it->value()));
-        if ((it = sc.find("reconnect-timeout")) != sc.end())
-            sv->set_reconnect_delay(string_util::to_uint<std::uint16_t>(it->value()));
-        if ((it = sc.find("ping-timeout")) != sc.end())
-            sv->set_ping_timeout(string_util::to_uint<std::uint16_t>(it->value()));
-    } catch (const std::exception&) {
-        log::warning(string_util::sprintf("server %s: invalid number for %s: %s",
-            sv->name(), it->key(), it->value()));
-    }
-
-    return sv;
-}
-
-} // !namespace
-
-config config::find()
-{
-    for (const auto& path : sys::config_filenames("irccd.conf")) {
-        try {
-            boost::system::error_code ec;
-
-            if (boost::filesystem::exists(path, ec) && !ec)
-                return config(path);
-        } catch (const std::exception& ex) {
-            log::warning() << path << ": " << ex.what() << std::endl;
-        }
+        if (boost::filesystem::exists(path, ec) && !ec)
+            return config(path);
     }
 
     throw std::runtime_error("no configuration file found");
 }
 
-void config::load_server_identity(server& server, const std::string& identity) const
-{
-    auto sc = std::find_if(document_.begin(), 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 == document_.end())
-        return;
-
-    ini::section::const_iterator it;
-
-    if ((it = sc->find("username")) != sc->end())
-        server.set_username(it->value());
-    if ((it = sc->find("realname")) != sc->end())
-        server.set_realname(it->value());
-    if ((it = sc->find("nickname")) != sc->end())
-        server.set_nickname(it->value());
-    if ((it = sc->find("ctcp-version")) != sc->end())
-        server.set_ctcp_version(it->value());
-}
-
-bool config::is_verbose() const noexcept
-{
-    return string_util::is_boolean(get(document_, "logs", "verbose"));
-}
-
-bool config::is_foreground() const noexcept
-{
-    return string_util::is_boolean(get(document_, "general", "foreground"));
-}
-
-std::string config::pidfile() const
-{
-    return get(document_, "general", "pidfile");
-}
-
-std::string config::uid() const
-{
-    return get(document_, "general", "uid");
-}
-
-std::string config::gid() const
-{
-    return get(document_, "general", "gid");
-}
-
-void config::load_logs() const
-{
-    ini::document::const_iterator sc = document_.find("logs");
-
-    if (sc == 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 = load_log_file(*sc);
-        else if (it->value() == "syslog")
-            iface = load_log_syslog();
-        else if (it->value() != "console")
-            throw std::runtime_error(string_util::sprintf("logs: unknown log type: %s", it->value()));
-
-        if (iface)
-            log::set_logger(std::move(iface));
-    }
-}
-
-void config::load_formats() const
-{
-    ini::document::const_iterator sc = document_.find("format");
-    ini::section::const_iterator it;
-
-    if (sc == document_.end())
-        return;
-
-    auto filter = std::make_unique<irccd_log_filter>();
-
-    if ((it = sc->find("debug")) != sc->cend())
-        filter->debug_ = it->value();
-    if ((it = sc->find("info")) != sc->cend())
-        filter->info_ = it->value();
-    if ((it = sc->find("warning")) != sc->cend())
-        filter->warning_ = it->value();
-
-    log::set_filter(std::move(filter));
-}
-
-void config::load_transports(irccd& irccd) const
-{
-    for (const auto& section : document_)
-        if (section.key() == "transport")
-            irccd.transports().add(load_transport(irccd.service(), section));
-}
-
-std::vector<rule> config::load_rules() const
-{
-    std::vector<rule> rules;
-
-    for (const auto& section : document_)
-        if (section.key() == "rule")
-            rules.push_back(load_rule(section));
-
-    return rules;
-}
-
-std::vector<std::shared_ptr<server>> config::load_servers(irccd& daemon) const
-{
-    std::vector<std::shared_ptr<server>> servers;
-
-    for (const auto& section : document_) {
-        if (section.key() != "server")
-            continue;
-
-        try {
-            servers.push_back(load_server(daemon, section, *this));
-        } catch (const std::exception& ex) {
-            log::warning(ex.what());
-        }
-    }
-
-    return servers;
-}
-
-void config::load_plugins(irccd& irccd) const
-{
-    auto it = document_.find("plugins");
-
-    if (it == document_.end())
-        return;
-
-    for (const auto& option : *it) {
-        if (!string_util::is_identifier(option.key()))
-            continue;
-
-        irccd.plugins().load(option.key(), option.value());
-    }
-}
-
 } // !irccd
--- a/libirccd/irccd/config.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/config.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -24,21 +24,10 @@
  * \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 transport_server;
-
 /**
  * \brief Read .ini configuration file for irccd
  */
@@ -51,10 +40,11 @@
     /**
      * Search the configuration file into the standard defined paths.
      *
+     * \param name the file name
      * \return the config
      * \throw std::exception on errors or if no config could be found
      */
-    static config find();
+    static config find(const std::string& name);
 
     /**
      * Load the configuration from the specified path.
@@ -88,90 +78,28 @@
         return path_;
     }
 
-   /**
-     * Find an entity if defined in the configuration file.
+    /**
+     * Convenience function to access a section.
      *
-     * \pre util::isValidIdentifier(name)
-     * \param server the server to update
-     * \param name the identity name
-     * \return default identity if cannot be found
-     */
-    void load_server_identity(server& server, const std::string& name) const;
-
-    /**
-     * Get the path to the pidfile.
-     *
-     * \return the path or empty if not defined
+     * \param section the section name
+     * \return the section or empty one
      */
-    std::string pidfile() const;
-
-    /**
-     * Get the uid.
-     *
-     * \return the uid or empty one if no one is set
-     */
-    std::string uid() const;
-
-    /**
-     * Get the gid.
-     *
-     * \return the gid or empty one if no one is set
-     */
-    std::string gid() const;
-
-    /**
-     * Check if verbosity is enabled.
-     *
-     * \return true if verbosity was requested
-     */
-    bool is_verbose() const noexcept;
+    inline ini::section section(const std::string& section) const noexcept
+    {
+        return document_.get(section);
+    }
 
     /**
-     * Check if foreground is specified (= no daemonize).
+     * Convenience function to access an ini value.
      *
-     * \return true if foreground was requested
-     */
-    bool is_foreground() const noexcept;
-
-    /**
-     * Load logging interface.
-     */
-    void load_logs() const;
-
-    /**
-     * Load formats for logging.
-     */
-    void load_formats() const;
-
-    /**
-     * Load transports.
-     *
-     * \param irccd the irccd instance
+     * \param section the section name
+     * \param option the option name
+     * \return the value or empty string
      */
-    void load_transports(irccd& irccd) const;
-
-    /**
-     * Load rules.
-     *
-     * \return the rules
-     */
-    std::vector<rule> load_rules() const;
-
-    /**
-     * Get the list of servers defined.
-     *
-     * \param daemon the irccd instance
-     * \return the list of servers
-     */
-    std::vector<std::shared_ptr<server>> load_servers(irccd& daemon) const;
-
-    /**
-     * Get the list of defined plugins.
-     *
-     * \param irccd the irccd instance
-     * \return the list of plugins
-     */
-    void load_plugins(irccd& irccd) const;
+    inline std::string value(const std::string& section, const std::string& option) const noexcept
+    {
+        return document_.get(section).get(option).value();
+    }
 };
 
 } // !irccd
--- a/libirccd/irccd/irccd.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/irccd.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -16,6 +16,10 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/logger.hpp>
+#include <irccd/string_util.hpp>
+#include <irccd/system.hpp>
+
 #include "command_service.hpp"
 #include "irccd.hpp"
 #include "plugin_service.hpp"
@@ -25,6 +29,182 @@
 
 namespace irccd {
 
+namespace {
+
+class log_filter : public log::filter {
+private:
+    std::string info_;
+    std::string warning_;
+    std::string debug_;
+
+    std::string convert(const std::string& tmpl, std::string input) const
+    {
+        if (tmpl.empty())
+            return input;
+
+        string_util::subst params;
+
+        params.flags &= ~(string_util::subst_flags::irc_attrs);
+        params.keywords.emplace("message", std::move(input));
+
+        return string_util::format(tmpl, params);
+    }
+
+public:
+    inline log_filter(std::string info, std::string warning, std::string debug) noexcept
+        : info_(std::move(info))
+        , warning_(std::move(warning))
+        , debug_(std::move(debug))
+    {
+    }
+
+    std::string pre_debug(std::string input) const override
+    {
+        return convert(debug_, std::move(input));
+    }
+
+    std::string pre_info(std::string input) const override
+    {
+        return convert(info_, std::move(input));
+    }
+
+    std::string pre_warning(std::string input) const override
+    {
+        return convert(warning_, std::move(input));
+    }
+};
+
+void load_log_file(const ini::section& sc)
+{
+    /*
+     * TODO: improve that with CMake options.
+     */
+#if defined(IRCCD_SYSTEM_WINDOWS)
+    std::string normal = "log.txt";
+    std::string errors = "errors.txt";
+#else
+    std::string normal = "/var/log/irccd/log.txt";
+    std::string errors = "/var/log/irccd/errors.txt";
+#endif
+
+    ini::section::const_iterator it;
+
+    if ((it = sc.find("path-logs")) != sc.end())
+        normal = it->value();
+    if ((it = sc.find("path-errors")) != sc.end())
+        errors = it->value();
+
+    try {
+        log::set_logger(std::make_unique<log::file_logger>(std::move(normal), std::move(errors)));
+    } catch (const std::exception& ex) {
+        log::warning() << "logs: " << ex.what() << std::endl;
+    }
+}
+
+void load_log_syslog()
+{
+#if defined(HAVE_SYSLOG)
+    log::set_logger(std::make_unique<log::syslog_logger>());
+#else
+    log::warning() << "logs: syslog is not available on this platform" << std::endl;
+#endif // !HAVE_SYSLOG
+}
+
+} // !namespace
+
+void irccd::load_logs()
+{
+    auto sc = config_.section("logs");
+
+    if (sc.empty())
+        return;
+
+    log::set_verbose(string_util::is_identifier(sc.get("verbose").value()));
+
+    auto type = sc.get("type").value();
+
+    if (!type.empty()) {
+        // Console is the default, no test case.
+        if (type == "file")
+            load_log_file(sc);
+        else if (type == "syslog")
+            load_log_syslog();
+        else if (type != "console")
+            log::warning() << "logs: invalid log type '" << type << std::endl;
+    }
+}
+
+void irccd::load_formats()
+{
+    auto sc = config_.section("format");
+
+    if (sc.empty())
+        return;
+
+    log::set_filter(std::make_unique<log_filter>(
+        sc.get("info").value(),
+        sc.get("warning").value(),
+        sc.get("debug").value()
+    ));
+}
+
+void irccd::load_pid()
+{
+    auto path = config_.value("general", "pidfile");
+
+    if (path.empty())
+        return;
+
+#if defined(HAVE_GETPID)
+    std::ofstream out(path, std::ofstream::trunc);
+
+    if (!out)
+        log::warning() << "irccd: could not open" << path << ": " << std::strerror(errno) << std::endl;
+    else {
+        log::debug() << "irccd: pid written in " << path << std::endl;
+        out << getpid() << std::endl;
+    }
+#else
+    log::warning() << "irccd: pidfile not supported on this platform" << std::endl;
+#endif
+}
+
+void irccd::load_gid()
+{
+    auto gid = config_.value("general", "gid");
+
+    if (gid.empty())
+        return;
+
+#if defined(HAVE_SETGID)
+    try {
+        sys::set_gid(gid);
+    } catch (const std::exception& ex) {
+        log::warning() << "irccd: failed to set gid: " << ex.what() << std::endl;
+    }
+#else
+    log::warning() << "irccd: gid option not supported" << std::endl;
+#endif
+}
+
+void irccd::load_uid()
+{
+    auto uid = config_.value("general", "gid");
+
+    if (uid.empty())
+        return;
+
+#if defined(HAVE_SETUID)
+    try {
+        sys::set_uid(uid);
+    } catch (const std::exception& ex) {
+        log::warning() << "irccd: failed to set uid: " << ex.what() << std::endl;
+    }
+#else
+    log::warning() << "irccd: uid option not supported" << std::endl;
+#endif
+}
+
 irccd::irccd(boost::asio::io_service& service, std::string config)
     : config_(std::move(config))
     , service_(service)
@@ -38,6 +218,42 @@
 
 irccd::~irccd() = default;
 
+void irccd::load() noexcept
+{
+    /*
+     * Order matters, please be careful when changing this.
+     *
+     * 1. Open logs as early as possible to use the defined outputs on any
+     *    loading errors.
+     */
+
+    // [logs] and [format] sections.
+    load_logs();
+    load_formats();
+
+    if (!loaded_)
+        log::info() << "irccd: loading configuration from " << config_.path() << std::endl;
+    else
+        log::info() << "irccd: reloading configuration" << std::endl;
+
+    // [general] section.
+    if (!loaded_) {
+        load_pid();
+        load_gid();
+        load_uid();
+    }
+
+    if (!loaded_)
+        tpt_service_->load(config_);
+
+    server_service_->load(config_);
+    plugin_service_->load(config_);
+    rule_service_->load(config_);
+
+    // Mark as loaded.
+    loaded_ = true;
+}
+
 const boost::system::error_category& irccd_category()
 {
     static const class category : public boost::system::error_category {
--- a/libirccd/irccd/irccd.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/irccd.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -54,6 +54,9 @@
     // Main io service.
     boost::asio::io_service& service_;
 
+    // Tells if the configuration has already been called.
+    bool loaded_{false};
+
     // Services.
     std::shared_ptr<command_service> command_service_;
     std::shared_ptr<server_service> server_service_;
@@ -68,6 +71,13 @@
     irccd& operator=(const irccd&) = delete;
     irccd& operator=(irccd&&) = delete;
 
+    // Load functions.
+    void load_logs();
+    void load_formats();
+    void load_pid();
+    void load_gid();
+    void load_uid();
+
 public:
     /**
      * Prepare standard services.
@@ -171,6 +181,11 @@
     {
         return *plugin_service_;
     }
+
+    /**
+     * Load and re-apply the configuration to the daemon.
+     */
+    void load() noexcept;
 };
 
 /**
--- a/libirccd/irccd/plugin.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/plugin.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -25,22 +25,8 @@
 
 namespace irccd {
 
-namespace {
-
-} // !namespace
-
-plugin_loader::plugin_loader(std::vector<std::string> directories,
-                             std::vector<std::string> extensions)
-    : directories_(std::move(directories))
-    , extensions_(std::move(extensions))
+std::shared_ptr<plugin> plugin_loader::find(const std::string& name)
 {
-}
-
-std::shared_ptr<plugin> plugin_loader::find(const std::string& name) noexcept
-{
-    if (extensions_.empty())
-        return nullptr;
-
     std::vector<std::string> filenames;
 
     if (directories_.empty())
@@ -51,21 +37,19 @@
                 filenames.push_back(dir + "/" + name + ext);
     }
 
-    std::shared_ptr<plugin> plugin;
-
     for (const auto& candidate : filenames) {
         boost::system::error_code ec;
 
         if (!boost::filesystem::exists(candidate, ec) || ec)
             continue;
 
-        plugin = open(name, candidate);
+        auto plugin = open(name, candidate);
 
         if (plugin)
-            break;
+            return plugin;
     }
 
-    return plugin;
+    throw plugin_error(plugin_error::not_found);
 }
 
 const boost::system::error_category& plugin_category()
--- a/libirccd/irccd/plugin.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/plugin.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -29,13 +29,15 @@
  * \brief Plugin management.
  */
 
+#include "sysconfig.hpp"
+
+#include <cassert>
 #include <memory>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
 #include "server.hpp"
-#include "sysconfig.hpp"
 #include "util.hpp"
 
 namespace irccd {
@@ -466,32 +468,38 @@
      * If directories is not specified, a sensible default list of system and
      * user paths are searched.
      *
-     * If extensions is empty, default find function implementation does
-     * nothing.
-     *
-     * \param directories directories to search
-     * \param extensions the list of extensions supported
+     * \pre !extensions.empty()
+     * \param directories optional list of directories to search
+     * \param extensions the non empty list of extensions supported
      */
-    plugin_loader(std::vector<std::string> directories = {},
-                  std::vector<std::string> extensions = {});
+    inline plugin_loader(std::vector<std::string> directories,
+                  std::vector<std::string> extensions) noexcept
+        : directories_(std::move(directories))
+        , extensions_(std::move(extensions))
+    {
+        assert(!extensions_.empty());
+    }
 
     /**
      * Set directories where to search plugins.
      *
      * \param dirs the directories
      */
-    inline void set_directories(std::vector<std::string> dirs)
+    inline void set_directories(std::vector<std::string> directories)
     {
-        directories_ = std::move(dirs);
+        directories_ = std::move(directories);
     }
 
     /**
      * Set supported extensions for this loader.
      *
+     * \pre !extensions.empty()
      * \param extensions the extensions (with the dot)
      */
     inline void set_extensions(std::vector<std::string> extensions)
     {
+        assert(!extensions.empty());
+
         extensions_ = std::move(extensions);
     }
 
@@ -504,7 +512,7 @@
      * \param file the file
      */
     virtual std::shared_ptr<plugin> open(const std::string& id,
-                                         const std::string& file) noexcept = 0;
+                                         const std::string& file) = 0;
 
     /**
      * Search for a plugin named by this id.
@@ -512,7 +520,7 @@
      * \param id the plugin id
      * \return the plugin
      */
-    virtual std::shared_ptr<plugin> find(const std::string& id) noexcept;
+    virtual std::shared_ptr<plugin> find(const std::string& id);
 };
 
 /**
--- a/libirccd/irccd/plugin_service.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/plugin_service.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -16,9 +16,11 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/string_util.hpp>
+#include <irccd/logger.hpp>
+
 #include "config.hpp"
 #include "irccd.hpp"
-#include "logger.hpp"
 #include "plugin_service.hpp"
 #include "string_util.hpp"
 #include "system.hpp"
@@ -203,4 +205,28 @@
     exec(save, &plugin::on_unload, irccd_);
 }
 
+void plugin_service::load(const class config& cfg) noexcept
+{
+    for (const auto& option : cfg.section("plugins")) {
+        if (!string_util::is_identifier(option.key()))
+            continue;
+
+        auto name = option.key();
+        auto p = get(name);
+
+        // Reload the plugin if already loaded.
+        if (p) {
+            p->set_config(config(name));
+            p->set_formats(formats(name));
+            p->set_paths(paths(name));
+        } else {
+            try {
+                load(name, option.value());
+            } catch (const plugin_error& ex) {
+                log::warning() << "plugin " << option.key() << ": " << ex.what() << std::endl;
+            }
+        }
+    }
+}
+
 } // !irccd
--- a/libirccd/irccd/plugin_service.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/plugin_service.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -34,6 +34,7 @@
 namespace irccd {
 
 class irccd;
+class config;
 
 /**
  * \brief Manage plugins.
@@ -222,6 +223,13 @@
 
         exec(plugin, fn, std::forward<Args>(args)...);
     }
+
+    /**
+     * Load all plugins.
+     *
+     * \param cfg the config
+     */
+    void load(const class config& cfg) noexcept;
 };
 
 } // !irccd
--- a/libirccd/irccd/rule_service.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/rule_service.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -16,12 +16,67 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include "logger.hpp"
+#include <stdexcept>
+
+#include <irccd/logger.hpp>
+#include <irccd/string_util.hpp>
+
+#include "config.hpp"
 #include "rule_service.hpp"
 #include "string_util.hpp"
 
 namespace irccd {
 
+namespace {
+
+rule load_rule(const ini::section& sc)
+{
+    assert(sc.key() == "rule");
+
+    // Simple converter from std::vector to std::unordered_set.
+    auto toset = [] (const auto& v) {
+        return std::unordered_set<std::string>(v.begin(), v.end());
+    };
+
+    rule::set servers, channels, origins, plugins, events;
+    rule::action_type action = rule::action_type::accept;
+
+    // Get the sets.
+    ini::section::const_iterator it;
+
+    if ((it = sc.find("servers")) != sc.end())
+        servers = toset(*it);
+    if ((it = sc.find("channels")) != sc.end())
+        channels = toset(*it);
+    if ((it = sc.find("origins")) != sc.end())
+        origins = toset(*it);
+    if ((it = sc.find("plugins")) != sc.end())
+        plugins = toset(*it);
+    if ((it = sc.find("channels")) != sc.end())
+        channels = toset(*it);
+
+    // Get the action.
+    auto actionstr = sc.get("action").value();
+
+    if (actionstr == "drop")
+        action = rule::action_type::drop;
+    else if (actionstr == "accept")
+        action = rule::action_type::accept;
+    else
+        throw rule_error(rule_error::invalid_action);
+
+    return {
+        std::move(servers),
+        std::move(channels),
+        std::move(origins),
+        std::move(plugins),
+        std::move(events),
+        action
+    };
+}
+
+} // !namespace
+
 void rule_service::add(rule rule)
 {
     rules_.push_back(std::move(rule));
@@ -87,4 +142,20 @@
     return result;
 }
 
+void rule_service::load(const config& cfg) noexcept
+{
+    rules_.clear();
+
+    for (const auto& section : cfg.doc()) {
+        if (section.key() != "rule")
+            continue;
+
+        try {
+            rules_.push_back(load_rule(section));
+        } catch (const std::exception& ex) {
+            log::warning() << "rule: " << ex.what() << std::endl;
+        }
+    }
+}
+
 } // !irccd
--- a/libirccd/irccd/rule_service.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/rule_service.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -30,6 +30,8 @@
 
 namespace irccd {
 
+class config;
+
 /**
  * \brief Store and solve rules.
  * \ingroup services
@@ -113,6 +115,13 @@
                const std::string& origin,
                const std::string& plugin,
                const std::string& event) noexcept;
+
+    /**
+     * Load rules from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
 };
 
 } // !irccd
--- a/libirccd/irccd/server.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/server.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -135,7 +135,7 @@
 
     if (v == json.end())
         return fallback;
-    if (!v->is_number_integer())
+    if (!v->is_number())
         throw server_error(error);
 
     auto n = v->get<unsigned>();
@@ -160,7 +160,7 @@
 
     sv->set_host(from_json_get_host(object));
     sv->set_port(from_json_get_uint(object, "port",
-        server_error::invalid_port_number, sv->port()));
+        server_error::invalid_port, sv->port()));
     sv->set_password(json_util::get_string(object, "password"));
     sv->set_nickname(json_util::get_string(object, "nickname", sv->nickname()));
     sv->set_realname(json_util::get_string(object, "realname", sv->realname()));
@@ -769,11 +769,11 @@
                 return "server is not connected";
             case server_error::already_connected:
                 return "server is already connected";
-            case server_error::invalid_port_number:
+            case server_error::invalid_port:
                 return "invalid port number specified";
-            case server_error::invalid_reconnect_tries_number:
+            case server_error::invalid_reconnect_tries:
                 return "invalid number of reconnection tries";
-            case server_error::invalid_reconnect_timeout_number:
+            case server_error::invalid_reconnect_timeout:
                 return "invalid reconnect timeout number";
             case server_error::invalid_hostname:
                 return "invalid hostname";
@@ -783,6 +783,8 @@
                 return "invalid or empty mode";
             case server_error::invalid_nickname:
                 return "invalid nickname";
+            case server_error::invalid_ping_timeout:
+                return "invalid ping timeout";
             case server_error::ssl_disabled:
                 return "ssl is not enabled";
             default:
--- a/libirccd/irccd/server.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/server.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -926,13 +926,13 @@
         already_exists,
 
         //!< The specified port number is invalid.
-        invalid_port_number,
+        invalid_port,
 
         //!< The specified reconnect tries number is invalid.
-        invalid_reconnect_tries_number,
+        invalid_reconnect_tries,
 
         //!< The specified reconnect reconnect number is invalid.
-        invalid_reconnect_timeout_number,
+        invalid_reconnect_timeout,
 
         //!< The specified host was invalid.
         invalid_hostname,
@@ -946,6 +946,9 @@
         //!< The nickname was empty or invalid.
         invalid_nickname,
 
+        //!< Invalid ping timeout.
+        invalid_ping_timeout,
+
         //!< SSL was requested but is disabled.
         ssl_disabled,
     };
--- a/libirccd/irccd/server_service.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/server_service.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -55,6 +55,135 @@
     }
 }
 
+template <typename T>
+T to_uint(const std::string& value, server_error::error errc)
+{
+    try {
+        return string_util::to_uint<T>(value);
+    } catch (...) {
+        throw server_error(errc);
+    }
+}
+
+template <typename T>
+T to_int(const std::string& value, server_error::error errc)
+{
+    try {
+        return string_util::to_int<T>(value);
+    } catch (...) {
+        throw server_error(errc);
+    }
+}
+
+void load_server_identity(std::shared_ptr<server>& server,
+                          const config& cfg,
+                          const std::string& identity)
+{
+    auto sc = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& sc) {
+        if (sc.key() != "identity")
+            return false;
+
+        auto name = sc.find("name");
+
+        return name != sc.end() && name->value() == identity;
+    });
+
+    if (sc == cfg.doc().end())
+        return;
+
+    ini::section::const_iterator it;
+
+    if ((it = sc->find("username")) != sc->end())
+        server->set_username(it->value());
+    if ((it = sc->find("realname")) != sc->end())
+        server->set_realname(it->value());
+    if ((it = sc->find("nickname")) != sc->end())
+        server->set_nickname(it->value());
+    if ((it = sc->find("ctcp-version")) != sc->end())
+        server->set_ctcp_version(it->value());
+}
+
+std::shared_ptr<server> load_server(boost::asio::io_service& service,
+                                    const config& cfg,
+                                    const ini::section& sc)
+{
+    assert(sc.key() == "server");
+
+    ini::section::const_iterator it;
+
+    // Name.
+    auto name = sc.get("name").value();
+
+    if (!string_util::is_identifier(name))
+        throw server_error(server_error::invalid_identifier);
+
+    auto sv = std::make_shared<server>(service, name);
+
+    // Host
+    if ((it = sc.find("host")) == sc.end())
+        throw server_error(server_error::invalid_hostname);
+
+    sv->set_host(it->value());
+
+    // Optional password
+    if ((it = sc.find("password")) != sc.end())
+        sv->set_password(it->value());
+
+    // Optional flags
+    if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::ipv6);
+
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
+#if defined(HAVE_SSL)
+        sv->set_flags(sv->flags() | server::ssl);
+#else
+        throw server_error(server_error::ssl_disabled);
+#endif
+    }
+
+    if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::ssl_verify);
+
+    // Optional identity
+    if ((it = sc.find("identity")) != sc.end())
+        load_server_identity(sv, cfg, it->value());
+
+    // Options
+    if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::auto_rejoin);
+    if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value()))
+        sv->set_flags(sv->flags() | server::join_invite);
+
+    // Channels
+    if ((it = sc.find("channels")) != sc.end()) {
+        for (const auto& s : *it) {
+            channel channel;
+
+            if (auto pos = s.find(":") != std::string::npos) {
+                channel.name = s.substr(0, pos);
+                channel.password = s.substr(pos + 1);
+            } else
+                channel.name = s;
+
+            sv->join(channel.name, channel.password);
+        }
+    }
+    if ((it = sc.find("command-char")) != sc.end())
+        sv->set_command_char(it->value());
+
+    // Reconnect and ping timeout
+    if ((it = sc.find("port")) != sc.end())
+        sv->set_port(to_uint<std::uint16_t>(it->value(), server_error::invalid_port));
+    if ((it = sc.find("reconnect-tries")) != sc.end())
+        sv->set_reconnect_tries(to_int<std::int8_t>(it->value(), server_error::invalid_reconnect_tries));
+    if ((it = sc.find("reconnect-timeout")) != sc.end())
+        sv->set_reconnect_delay(to_uint<std::uint16_t>(it->value(), server_error::invalid_reconnect_timeout));
+    if ((it = sc.find("ping-timeout")) != sc.end())
+        sv->set_ping_timeout(to_uint<std::uint16_t>(it->value(), server_error::invalid_ping_timeout));
+
+    return sv;
+}
+
 } // !namespace
 
 void server_service::handle_connect(const connect_event& ev)
@@ -483,4 +612,19 @@
     servers_.clear();
 }
 
+void server_service::load(const config& cfg) noexcept
+{
+    for (const auto& section : cfg.doc()) {
+        if (section.key() != "server")
+            continue;
+
+        try {
+            add(load_server(irccd_.service(), cfg, section));
+        } catch (const std::exception& ex) {
+            log::warning() << "server " << section.get("name").value() << ": "
+                << ex.what() << std::endl;
+        }
+    }
+}
+
 } // !irccd
--- a/libirccd/irccd/server_service.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/server_service.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -121,6 +121,13 @@
      * All servers will be disconnected.
      */
     void clear() noexcept;
+
+    /**
+     * Load servers from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
 };
 
 } // !irccd
--- a/libirccd/irccd/transport_service.cpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/transport_service.cpp	Tue Dec 05 20:45:21 2017 +0100
@@ -18,6 +18,8 @@
 
 #include <cassert>
 
+#include <irccd/string_util.hpp>
+
 #include "command_service.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
@@ -27,6 +29,145 @@
 
 namespace irccd {
 
+namespace {
+
+std::unique_ptr<transport_server> load_transport_ip(boost::asio::io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    std::unique_ptr<transport_server> transport;
+    ini::section::const_iterator it;
+
+    // Port.
+    if ((it = sc.find("port")) == sc.cend())
+        throw std::invalid_argument("missing 'port' parameter");
+
+    auto port = string_util::to_uint<std::uint16_t>(it->value());
+
+    // Address.
+    std::string address = "*";
+
+    if ((it = sc.find("address")) != sc.end())
+        address = it->value();
+
+    // 0011
+    //    ^ define IPv4
+    //   ^  define IPv6
+    auto mode = 1U;
+
+    /*
+     * Documentation stated family but code checked for 'domain' option.
+     *
+     * As irccdctl uses domain, accept both and unify the option name to 'family'.
+     *
+     * See #637
+     */
+    if ((it = sc.find("domain")) != sc.end() || (it = sc.find("family")) != sc.end()) {
+        mode = 0U;
+
+        for (const auto& v : *it) {
+            if (v == "ipv4")
+                mode |= (1U << 0);
+            if (v == "ipv6")
+                mode |= (1U << 1);
+        }
+    }
+
+    if (mode == 0U)
+        throw std::invalid_argument("family must at least have ipv4 or ipv6");
+
+    auto protocol = (mode & 0x2U)
+        ? boost::asio::ip::tcp::v4()
+        : boost::asio::ip::tcp::v6();
+
+    // Optional SSL.
+    std::string pkey;
+    std::string cert;
+
+    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
+        if ((it = sc.find("certificate")) == sc.end())
+            throw std::invalid_argument("missing 'certificate' parameter");
+
+        cert = it->value();
+
+        if ((it = sc.find("key")) == sc.end())
+            throw std::invalid_argument("missing 'key' parameter");
+
+        pkey = it->value();
+    }
+
+    auto endpoint = (address == "*")
+        ? boost::asio::ip::tcp::endpoint(protocol, port)
+        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port);
+
+    boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
+
+    if (pkey.empty())
+        return std::make_unique<ip_transport_server>(std::move(acceptor));
+
+#if defined(HAVE_SSL)
+    boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
+
+    ctx.use_private_key_file(pkey, boost::asio::ssl::context::pem);
+    ctx.use_certificate_file(cert, boost::asio::ssl::context::pem);
+
+    return std::make_unique<tls_transport_server>(std::move(acceptor), std::move(ctx));
+#else
+    throw std::invalid_argument("SSL disabled");
+#endif
+}
+
+std::unique_ptr<transport_server> load_transport_unix(boost::asio::io_service& service, const ini::section& sc)
+{
+    using boost::asio::local::stream_protocol;
+
+    assert(sc.key() == "transport");
+
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+    ini::section::const_iterator it = sc.find("path");
+
+    if (it == sc.end())
+        throw std::invalid_argument("missing 'path' parameter");
+
+    // Remove the file first.
+    std::remove(it->value().c_str());
+
+    stream_protocol::endpoint endpoint(it->value());
+    stream_protocol::acceptor acceptor(service, std::move(endpoint));
+
+    return std::make_unique<local_transport_server>(std::move(acceptor));
+#else
+    (void)sc;
+
+    throw std::invalid_argument("unix transports not supported on on this platform");
+#endif
+}
+
+std::unique_ptr<transport_server> load_transport(boost::asio::io_service& service, const ini::section& sc)
+{
+    assert(sc.key() == "transport");
+
+    std::unique_ptr<transport_server> transport;
+    ini::section::const_iterator it = sc.find("type");
+
+    if (it == sc.end())
+        throw std::invalid_argument("missing 'type' parameter");
+
+    if (it->value() == "ip")
+        transport = load_transport_ip(service, sc);
+    else if (it->value() == "unix")
+        transport = load_transport_unix(service, sc);
+    else
+        throw std::invalid_argument(string_util::sprintf("invalid type given: %s", it->value()));
+
+    if ((it = sc.find("password")) != sc.end())
+        transport->set_password(it->value());
+
+    return transport;
+}
+
+} // !namespace
+
 void transport_service::handle_command(std::shared_ptr<transport_client> tc, const nlohmann::json& object)
 {
     assert(object.is_object());
@@ -113,4 +254,18 @@
             client->send(json);
 }
 
+void transport_service::load(const config& cfg) noexcept
+{
+    for (const auto& section : cfg.doc())
+        if (section.key() != "transport") {
+            continue;
+
+        try {
+            add(load_transport(irccd_.service(), section));
+        } catch (const std::exception& ex) {
+            log::warning() << "transport: " << ex.what() << std::endl;
+        }
+    }
+}
+
 } // !irccd
--- a/libirccd/irccd/transport_service.hpp	Mon Dec 04 15:23:53 2017 +0100
+++ b/libirccd/irccd/transport_service.hpp	Tue Dec 05 20:45:21 2017 +0100
@@ -29,6 +29,8 @@
 
 namespace irccd {
 
+class config;
+
 /**
  * \brief manage transport servers and clients.
  * \ingroup services
@@ -72,6 +74,13 @@
      * \param object the json object
      */
     void broadcast(const nlohmann::json& object);
+
+    /**
+     * Load transports from the configuration.
+     *
+     * \param cfg the config
+     */
+    void load(const config& cfg) noexcept;
 };
 
 } // !irccd