Mercurial > irccd
view lib/irccd/config.cpp @ 111:1ed760f6e0c6
Irccd: new brace styles, #487
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 27 Apr 2016 21:37:09 +0200 |
parents | 04d672ab41a4 |
children | 6a99814c2317 |
line wrap: on
line source
/* * config.cpp -- irccd configuration loader * * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "sysconfig.hpp" #if defined(HAVE_GETPID) # include <sys/types.h> # include <unistd.h> # include <cerrno> # include <cstring> # include <fstream> #endif #if defined(HAVE_DAEMON) # include <cstdlib> #endif #include "config.hpp" #include "ini.hpp" #include "logger.hpp" #include "path.hpp" #include "sockets.hpp" #include "system.hpp" #include "util.hpp" #include "irccd.hpp" using namespace std; using namespace std::string_literals; namespace irccd { void Config::loadGeneral(const ini::Document &config) const { ini::Document::const_iterator sc = config.find("general"); ini::Section::const_iterator it; #if defined(HAVE_GETPID) if (sc != config.end()) { it = sc->find("pidfile"); if (it != sc->end() && !it->value().empty()) { std::string path = it->value(); std::ofstream out(path, std::ofstream::trunc); if (!out) { log::warning() << "irccd: could not open pidfile " << path << ": " << std::strerror(errno) << std::endl; } else { log::debug() << "irccd: pid written in " << path << std::endl; out << getpid(); } } } #endif #if defined(HAVE_DAEMON) /* CLI priority is higher */ bool daemonize = m_options.count("-f") == 0 && m_options.count("--foreground") == 0; if (daemonize && sc != config.end()) { it = sc->find("foreground"); if (it != sc->end()) { daemonize = !util::isBoolean(it->value()); } } if (daemonize) { daemon(1, 0); } #endif if (sc != config.end()) { try { #if defined(HAVE_SETGID) if ((it = sc->find("gid")) != sc->end()) { sys::setGid(it->value()); } #endif #if defined(HAVE_SETUID) if ((it = sc->find("uid")) != sc->end()) { sys::setUid(it->value()); } #endif } catch (const std::exception &ex) { log::warning() << "irccd: could not set " << it->key() << ": " << ex.what() << std::endl; } } } void Config::loadLogFile(const ini::Section &sc) const { /* * TODO: improve that with CMake options. */ #if defined(IRCCD_SYSTEM_WINDOWS) string normal = "log.txt"; string errors = "errors.txt"; #else string normal = "/var/log/irccd/log.txt"; 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(); } log::setInterface(make_unique<log::File>(move(normal), move(errors))); } void Config::loadLogSyslog() const { #if defined(HAVE_SYSLOG) log::setInterface(make_unique<log::Syslog>()); #else log::warning() << "irccd: syslog is not available on this platform" << endl; #endif // !HAVE_SYSLOG } void Config::loadLogs(const ini::Document &config) const { ini::Document::const_iterator sc = config.find("logs"); if (sc == config.end()) { return; } ini::Section::const_iterator it; if ((it = sc->find("verbose")) != sc->end() && m_options.count("-v") == 0 && m_options.count("--verbose")) { log::setVerbose(util::isBoolean(it->value())); } if ((it = sc->find("type")) != sc->end()) { /* Console is the default, no test case */ if (it->value() == "file") { loadLogFile(*sc); } else if (it->value() == "syslog") { loadLogSyslog(); } else { log::warning() << "irccd: unknown log type: " << it->value() << std::endl; } } } void Config::loadPlugins(Irccd &irccd, const ini::Section &sc) const { #if defined(WITH_JS) for (const ini::Option &option : sc) { try { if (option.value().empty()) { irccd.loadPlugin(option.key(), option.key(), true); } else { irccd.loadPlugin(option.key(), option.value(), false); } } catch (const std::exception &ex) { log::warning() << "plugin " << option.key() << ": " << ex.what() << std::endl; } } #else (void)irccd; (void)sc; #endif } void Config::loadPluginConfig(Irccd &irccd, const ini::Section &sc, string name) const { #if defined(WITH_JS) PluginConfig config; for (const ini::Option &option : sc) { config.emplace(option.key(), option.value()); } irccd.addPluginConfig(std::move(name), std::move(config)); #else (void)irccd; (void)sc; (void)name; #endif } void Config::loadPlugins(Irccd &irccd, const ini::Document &config) const { #if defined(WITH_JS) std::regex regex("^plugin\\.([A-Za-z0-9-_]+)$"); std::smatch match; /* * Load plugin configurations before we load plugins since we use them * when we load the plugin itself. */ for (const ini::Section §ion : config) { if (regex_match(section.key(), match, regex)) { loadPluginConfig(irccd, section, match[1]); } } ini::Document::const_iterator it = config.find("plugins"); if (it != config.end()) { loadPlugins(irccd, *it); } #else (void)irccd; (void)config; log::warning() << "irccd: JavaScript disabled, ignoring plugins" << std::endl; #endif } void Config::loadServer(Irccd &irccd, const ini::Section &sc) const { ServerInfo info; ServerIdentity identity; ServerSettings settings; /* Name */ ini::Section::const_iterator it; if ((it = sc.find("name")) == sc.end()) { throw std::invalid_argument("server: missing name"); } else if (!util::isIdentifierValid(it->value())) { throw std::invalid_argument("server " + it->value() + ": name is not valid"); } else if (irccd.hasServer(it->value())) { throw std::invalid_argument("server " + it->value() + ": already exists"); } info.name = it->value(); /* Host */ if ((it = sc.find("host")) == sc.end()) { throw std::invalid_argument("server " + info.name + ": missing host"); } info.host = it->value(); /* Optional identity */ if ((it = sc.find("identity")) != sc.end()) { identity = irccd.findIdentity(it->value()); } /* Optional port */ if ((it = sc.find("port")) != sc.end()) { try { info.port = std::stoi(it->value()); } catch (const std::exception &) { throw std::invalid_argument("server " + info.name + ": invalid port number: " + it->value()); } } /* Optional password */ if ((it = sc.find("password")) != sc.end()) { info.password = it->value(); } /* Optional flags */ if ((it = sc.find("ipv6")) != sc.end() && util::isBoolean(it->value())) { info.flags |= ServerInfo::Ipv6; } if ((it = sc.find("ssl")) != sc.end()) { if (util::isBoolean(it->value())) { #if defined(WITH_SSL) info.flags |= ServerInfo::Ssl; #else throw std::invalid_argument("server " + info.name + ": ssl is disabled"); #endif } } if ((it = sc.find("ssl-verify")) != sc.end()) { if (util::isBoolean(it->value())) { #if defined(WITH_SSL) info.flags |= ServerInfo::SslVerify; #else throw std::invalid_argument("server " + info.name + ": ssl is disabled"); #endif } } /* Options */ if ((it = sc.find("auto-rejoin")) != sc.end() && util::isBoolean(it->value())) { settings.flags |= ServerSettings::AutoRejoin; } if ((it = sc.find("join-invite")) != sc.end() && util::isBoolean(it->value())) { settings.flags |= ServerSettings::JoinInvite; } /* Channels */ if ((it = sc.find("channels")) != sc.end()) { for (const std::string &s : *it) { ServerChannel 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; } settings.channels.push_back(std::move(channel)); } } if ((it = sc.find("command-char")) != sc.end()) { settings.command = it->value(); } /* Reconnect */ try { if ((it = sc.find("reconnect-tries")) != sc.end()) { settings.reconnect_tries = std::stoi(it->value()); } if ((it = sc.find("reconnect-timeout")) != sc.end()) { settings.reconnect_timeout = std::stoi(it->value()); } if ((it = sc.find("ping-timeout")) != sc.end()) { settings.ping_timeout = std::stoi(it->value()); } } catch (const std::exception &) { throw std::invalid_argument("server " + info.name + ": invalid number for " + it->key() + ": " + it->value()); } irccd.addServer(std::make_shared<Server>(std::move(info), std::move(identity), std::move(settings))); } void Config::loadServers(Irccd &irccd, const ini::Document &config) const { for (const ini::Section §ion : config) { if (section.key() == "server") { try { loadServer(irccd, section); } catch (const exception &ex) { log::warning() << ex.what() << endl; } } } } void Config::loadIdentity(Irccd &irccd, const ini::Section &sc) const { ServerIdentity identity; ini::Section::const_iterator it; if ((it = sc.find("name")) == sc.end()) { throw invalid_argument("missing name"); } else if (!util::isIdentifierValid(it->value())) { throw invalid_argument("identity name not valid"); } identity.name = it->value(); /* Optional stuff */ if ((it = sc.find("username")) != sc.end()) { identity.username = it->value(); } if ((it = sc.find("realname")) != sc.end()) { identity.realname = it->value(); } if ((it = sc.find("nickname")) != sc.end()) { identity.nickname = it->value(); } if ((it = sc.find("ctcp-version")) != sc.end()) { identity.ctcpversion = it->value(); } log::debug() << "identity " << identity.name << ": "; log::debug() << "nickname=" << identity.nickname << ", username=" << identity.username << ", "; log::debug() << "realname=" << identity.realname << ", ctcp-version=" << identity.ctcpversion << endl; irccd.addIdentity(move(identity)); } void Config::loadIdentities(Irccd &irccd, const ini::Document &config) const { for (const ini::Section §ion : config) { if (section.key() == "identity") { try { loadIdentity(irccd, section); } catch (const exception &ex) { log::warning() << "identity: " << ex.what() << endl; } } } } void Config::loadRule(Irccd &irccd, const ini::Section &sc) const { /* Simple converter from std::vector to std::unordered_set */ auto toSet = [] (const std::vector<std::string> &v) -> std::unordered_set<std::string> { return std::unordered_set<std::string>(v.begin(), v.end()); }; RuleSet servers, channels, origins, plugins, events; RuleAction action = RuleAction::Accept; /* Get the sets */ ini::Section::const_iterator it; if ((it = sc.find("servers")) != sc.end()) { servers = toSet(*it); } if ((it = sc.find("channels")) != sc.end()) { channels = toSet(*it); } if ((it = sc.find("origins")) != sc.end()) { origins = toSet(*it); } if ((it = sc.find("plugins")) != sc.end()) { plugins = toSet(*it); } if ((it = sc.find("channels")) != sc.end()) { channels = toSet(*it); } /* Get the action */ if ((it = sc.find("action")) == sc.end()) { throw std::invalid_argument("missing action parameter"); } if (it->value() == "drop") { action = RuleAction::Drop; } else if (it->value() == "accept") { action = RuleAction::Accept; } else { throw std::invalid_argument("invalid action given: " + it->value()); } irccd.addRule(Rule(move(servers), move(channels), move(origins), move(plugins), move(events), action)); } void Config::loadRules(Irccd &irccd, const ini::Document &config) const { for (const ini::Section &sc : config) { if (sc.key() == "rule") { try { loadRule(irccd, sc); } catch (const std::exception &ex) { log::warning() << "rule: " << ex.what() << std::endl; } } } } void Config::loadTransportIp(Irccd &irccd, const ini::Section &sc) const { bool ipv6 = true; bool ipv4 = true; ini::Section::const_iterator it; /* Port */ int port; if ((it = sc.find("port")) == sc.end()) { throw invalid_argument("missing port"); } try { port = stoi(it->value()); } catch (const std::exception &) { throw std::invalid_argument("invalid port number: " + it->value()); } /* Address*/ std::string address = "*"; if ((it = sc.find("address")) != sc.end()) { address = it->value(); } /* Domain */ if ((it = sc.find("domain")) != sc.end()) { ipv6 = false; ipv4 = false; for (const string &v : *it) { if (v == "ipv4") { ipv4 = true; } if (v == "ipv6") { ipv6 = true; } } } if (ipv6) { irccd.addTransport(std::make_shared<TransportServerIp>(AF_INET6, move(address), port, !ipv4)); } else if (ipv4) { irccd.addTransport(std::make_shared<TransportServerIp>(AF_INET, move(address), port)); } else { throw std::invalid_argument("domain must at least have ipv4 or ipv6"); } } void Config::loadTransportUnix(Irccd &irccd, const ini::Section &sc) const { #if !defined(IRCCD_SYSTEM_WINDOWS) /* Path */ ini::Section::const_iterator it = sc.find("path"); if (it == sc.end()) { throw std::invalid_argument("missing path parameter"); } else { string path = sc["path"].value(); irccd.addTransport(std::make_shared<TransportServerUnix>(move(path))); } #else (void)irccd; (void)sc; throw std::invalid_argument("local transport not supported on on this platform"); #endif } void Config::loadTransports(Irccd &irccd, const ini::Document &config) const { for (const ini::Section &sc : config) { if (sc.key() == "transport") { try { ini::Section::const_iterator it = sc.find("type"); if (it == sc.end()) { log::warning() << "transport: missing type parameter" << std::endl; } else if (it->value() == "ip") { loadTransportIp(irccd, sc); } else if (it->value() == "unix") { loadTransportUnix(irccd, sc); } else { log::warning() << "transport: invalid type given: " << std::endl; } } catch (const net::Error &error) { log::warning() << "transport: " << error.function() << ": " << error.what() << std::endl; } catch (const exception &ex) { log::warning() << "transport: error: " << ex.what() << endl; } } } } bool Config::openConfig(Irccd &irccd, const string &path) const { try { /* * Order matters, take care when you change this. */ ini::Document config(ini::File{path}); loadGeneral(config); loadLogs(config); loadIdentities(irccd, config); loadServers(irccd, config); loadRules(irccd, config); loadPlugins(irccd, config); loadTransports(irccd, config); } catch (const ini::Error &ex) { log::warning() << sys::programName() << ": " << path << ":" << ex.line() << ":" << ex.column() << ": " << ex.what() << std::endl; return false; } catch (const std::exception &ex) { log::warning() << sys::programName() << ": " << path << ": " << ex.what() << std::endl; return false; } return true; } Config::Config(parser::Result options) noexcept : m_options(move(options)) { } void Config::load(Irccd &irccd) { auto it = m_options.find("-c"); auto found = false; if (it != m_options.end()) { found = openConfig(irccd, it->second); } else if ((it = m_options.find("--config")) != m_options.end()) { found = openConfig(irccd, it->second); } else { /* Search for a configuration file */ for (const string &path : path::list(path::PathConfig)) { string fullpath = path + "irccd.conf"; log::info() << "irccd: trying " << fullpath << endl; if (openConfig(irccd, fullpath)) { found = true; break; } } } if (!found) { log::warning() << "irccd: no configuration file could be found, exiting" << endl; exit(1); } } } // !irccd