/* * InspIRCd -- Internet Relay Chat Daemon * * Copyright (C) 2018-2023 Sadie Powell * Copyright (C) 2013-2014, 2016, 2018 Attila Molnar * * This file is part of InspIRCd. InspIRCd is free software: you can * redistribute it and/or modify it under the terms of the GNU General Public * License as published by the Free Software Foundation, version 2. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "inspircd.h" #include "clientprotocolmsg.h" enum { // From UnrealIRCd. RPL_RULES = 232, RPL_RULESTART = 308, RPL_RULESEND = 309, ERR_NORULES = 434 }; class CommandShowFile final : public Command { enum Method { SF_MSG, SF_NOTICE, SF_NUMERIC }; std::string introtext; std::string endtext; unsigned int intronumeric; unsigned int textnumeric; unsigned int endnumeric; std::vector contents; Method method; public: CommandShowFile(Module* parent, const std::string& cmdname) : Command(parent, cmdname) { } CmdResult Handle(User* user, const Params& parameters) override { if (method == SF_NUMERIC) { if (!introtext.empty() && intronumeric) user->WriteRemoteNumeric(intronumeric, introtext); for (const auto& line : contents) user->WriteRemoteNumeric(textnumeric, line); if (!endtext.empty() && endnumeric) user->WriteRemoteNumeric(endnumeric, endtext); } else if (IS_LOCAL(user)) { LocalUser* const localuser = IS_LOCAL(user); for (const auto& line : contents) { ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, line, ((method == SF_MSG) ? MessageType::PRIVMSG : MessageType::NOTICE)); localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg); } } return CmdResult::SUCCESS; } void UpdateSettings(const std::shared_ptr& tag, const std::string& filecontents) { introtext = tag->getString("introtext", "Showing " + name); endtext = tag->getString("endtext", "End of " + name); intronumeric = tag->getNum("intronumeric", RPL_RULESTART, 0, 999); textnumeric = tag->getNum("numeric", RPL_RULES, 0, 999); endnumeric = tag->getNum("endnumeric", RPL_RULESEND, 0, 999); std::string smethod = tag->getString("method"); method = SF_NUMERIC; if (smethod == "msg") method = SF_MSG; else if (smethod == "notice") method = SF_NOTICE; // Process the entry. contents.clear(); irc::sepstream linestream(filecontents, '\n'); for (std::string line; linestream.GetToken(line); ) { // Some clients can not handle receiving NOTICE/PRIVMSG/RPL_RULES // with an empty trailing parameter so if a line is empty we // replace it with a single space. contents.push_back(line.empty() ? " " : line); } InspIRCd::ProcessColors(contents); contents.shrink_to_fit(); } }; class ModuleShowFile final : public Module { private: std::vector cmds; void ReadTag(const std::shared_ptr& tag, std::vector& newcmds) { std::string cmdname = tag->getString("name"); if (cmdname.empty()) throw ModuleException(this, "Empty value for 'name'"); std::transform(cmdname.begin(), cmdname.end(), cmdname.begin(), ::toupper); const std::string file = tag->getString("file", cmdname); if (file.empty()) throw ModuleException(this, "Empty value for 'file'"); auto reader = ServerInstance->Config->ReadFile(file); if (!reader) throw ModuleException(this, "Unable to read " + file + ": " + reader.error); CommandShowFile* sfcmd; Command* handler = ServerInstance->Parser.GetHandler(cmdname); if (handler) { // Command exists, check if it is ours if (handler->creator != this) throw ModuleException(this, "Command " + cmdname + " already exists"); // This is our command, make sure we don't have the same entry twice sfcmd = static_cast(handler); if (stdalgo::isin(newcmds, sfcmd)) throw ModuleException(this, "Command " + cmdname + " is already used in a tag"); } else { // Command doesn't exist, create it sfcmd = new CommandShowFile(this, cmdname); ServerInstance->Modules.AddService(*sfcmd); } sfcmd->UpdateSettings(tag, reader.contents); newcmds.push_back(sfcmd); } public: ModuleShowFile() : Module(VF_VENDOR, "Adds support for showing the contents of files to users when they execute a command.") { } void ReadConfig(ConfigStatus& status) override { std::vector newcmds; for (const auto& [_, tag] : ServerInstance->Config->ConfTags("showfile")) { try { ReadTag(tag, newcmds); } catch (const CoreException& ex) { ServerInstance->Logs.Warning(MODNAME, "Error: " + ex.GetReason() + " at " + tag->source.str()); } } // Remove all commands that were removed from the config std::vector removed(cmds.size()); std::sort(newcmds.begin(), newcmds.end()); std::set_difference(cmds.begin(), cmds.end(), newcmds.begin(), newcmds.end(), removed.begin()); stdalgo::delete_all(removed); cmds.swap(newcmds); } ~ModuleShowFile() override { stdalgo::delete_all(cmds); } }; MODULE_INIT(ModuleShowFile)