Mercurial > irccd
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