#include "priv_thread.hpp" #include "command_thread.hpp" #include "connection.hpp" #include "write_irc.hpp" #include #include #include #include #include PrivThread::PrivThread(Connection& connection, std::string config_path) : connection_{connection} , config_path_{std::move(config_path)} { } auto PrivThread::check_command(CommandEvent& event, std::string priv) -> bool { return (not event.account.empty() && (check(account_privs_, priv, wildcard) || check(account_privs_, priv, std::string{event.account}))) || (not event.oper.empty() && (check(oper_privs_, priv, wildcard) || check(oper_privs_, priv, std::string{event.oper}))); } auto PrivThread::check( std::unordered_map> const& privs, std::string const& priv, std::string const& name ) -> bool { auto const cursor = privs.find(name); return cursor != privs.end() && cursor->second.contains(priv); } auto PrivThread::list_privs(std::string_view nick) -> void { for (auto const& [oper, privset] : oper_privs_) { std::string message = "Oper "; message += oper; message += ":"; for (auto const& priv : privset) { message += " "; message += priv; } send_notice(connection_, nick, message); } for (auto const& [account, privset] : account_privs_) { std::string message = "Account "; message += account; message += ":"; for (auto const& priv : privset) { message += " "; message += priv; } send_notice(connection_, nick, message); } } auto PrivThread::on_command(CommandEvent& event) -> void { if ("list_privs" == event.command) { if (check_command(event, PrivThread::owner_priv)) { list_privs(event.nick); } } else if ("add_priv" == event.command) { if (check_command(event, PrivThread::owner_priv)) { auto in = std::istringstream{std::string{event.arg}}; std::string kind, name, priv; if (in >> kind >> name >> priv) { if ("oper" == kind) { oper_privs_[name].insert(priv); save_config(); send_notice(connection_, event.nick, "ack"); return; } else if ("account" == kind) { account_privs_[name].insert(priv); save_config(); send_notice(connection_, event.nick, "ack"); return; } } send_notice(connection_, event.nick, "nak"); } } else if ("remove_priv" == event.command) { if (check_command(event, PrivThread::owner_priv)) { auto in = std::istringstream{std::string{event.arg}}; std::string kind, name, priv; if (in >> kind >> name >> priv) { if ("oper" == kind) { oper_privs_[name].erase(priv); if (oper_privs_[name].empty()) oper_privs_.erase(name); save_config(); send_notice(connection_, event.nick, "ack"); return; } else if ("account" == kind) { account_privs_[name].erase(priv); if (account_privs_[name].empty()) account_privs_.erase(name); save_config(); send_notice(connection_, event.nick, "ack"); return; } } send_notice(connection_, event.nick, "nak"); } } } auto PrivThread::save_config() -> void { auto serialize_table = [](auto map) { auto tab = toml::table{}; for (auto const& [oper, privs] : map) { auto privset = toml::array{}; for (auto const& priv : privs) { privset.push_back(priv); } tab.insert(oper, privset); } return tab; }; auto config = toml::table{}; if (not oper_privs_.empty()) { config.insert("oper", serialize_table(oper_privs_)); } if (not account_privs_.empty()) { config.insert("account", serialize_table(account_privs_)); } std::string tmp = config_path_ + ".tmp"; std::ofstream{tmp} << config; std::filesystem::rename(tmp, config_path_); } auto PrivThread::load_config() -> void { if (auto in = std::ifstream{config_path_}) { auto const config = toml::parse(in); if (config.contains("oper")) { for (auto const [oper, privs] : *config["oper"].as_table()) { auto& privset = oper_privs_[std::string{oper.str()}]; for (auto const& priv : *privs.as_array()) { privset.insert(priv.as_string()->get()); } } } if (config.contains("account")) { for (auto const [account, privs] : *config["account"].as_table()) { auto& privset = account_privs_[std::string{account.str()}]; for (auto const& priv : *privs.as_array()) { privset.insert(priv.as_string()->get()); } } } } } auto PrivThread::start(Connection& connection, std::string config_path) -> std::shared_ptr { auto thread = std::make_shared(connection, config_path); thread->load_config(); connection.add_listener([thread](CommandEvent& event) { thread->on_command(event); }); return thread; }