]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Implement support for saving filters.
authorSadie Powell <sadie@witchery.services>
Sun, 14 Mar 2021 02:20:32 +0000 (02:20 +0000)
committerSadie Powell <sadie@witchery.services>
Sun, 14 Mar 2021 02:55:03 +0000 (02:55 +0000)
src/modules/m_filter.cpp

index 010f2a85514de23048e8facd722ed9af248662f4..566b4691279cfa9f68a3cebc19deca7736556ff7 100644 (file)
@@ -39,6 +39,8 @@
 #include "modules/stats.h"
 #include "modules/account.h"
 
+#include <fstream>
+
 enum FilterFlags
 {
        FLAG_PART = 2,
@@ -132,7 +134,7 @@ class FilterResult
                return 0;
        }
 
-       std::string GetFlags()
+       std::string GetFlags() const
        {
                std::string flags;
                if (flag_no_opers)
@@ -188,12 +190,15 @@ class ModuleFilter
        : public Module
        , public ServerProtocol::SyncEventListener
        , public Stats::EventListener
+       , public Timer
 {
        typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet;
 
        bool initing;
        bool notifyuser;
        bool warnonselfmsg;
+       bool dirty;
+       std::string filterconf;
        RegexFactory* factory;
        void FreeFilters();
 
@@ -226,6 +231,7 @@ class ModuleFilter
        ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE;
        ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE;
        void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
+       bool Tick(time_t) CXX11_OVERRIDE;
        bool AppliesToMe(User* user, FilterResult* filter, int flags);
        void ReadFilters();
        static bool StringToFilterAction(const std::string& str, FilterAction& fa);
@@ -348,7 +354,9 @@ bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags)
 ModuleFilter::ModuleFilter()
        : ServerProtocol::SyncEventListener(this)
        , Stats::EventListener(this)
+       , Timer(0, true)
        , initing(true)
+       , dirty(false)
        , filtcommand(this)
        , RegexEngine(this, "regex")
 {
@@ -371,6 +379,7 @@ void ModuleFilter::FreeFilters()
                delete i->regex;
 
        filters.clear();
+       dirty = true;
 }
 
 ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtarget, MessageDetails& details)
@@ -638,6 +647,10 @@ void ModuleFilter::ReadConfig(ConfigStatus& status)
        std::string newrxengine = tag->getString("engine");
        notifyuser = tag->getBool("notifyuser", true);
        warnonselfmsg = tag->getBool("warnonselfmsg");
+       filterconf = tag->getString("filename");
+       if (!filterconf.empty())
+               filterconf = ServerInstance->Config->Paths.PrependConfig(filterconf);
+       SetInterval(tag->getDuration("saveperiod", 5));
 
        factory = RegexEngine ? (RegexEngine.operator->()) : NULL;
 
@@ -779,6 +792,7 @@ bool ModuleFilter::DeleteFilter(const std::string& freeform, std::string& reason
                        reason.assign(i->reason);
                        delete i->regex;
                        filters.erase(i);
+                       dirty = true;
                        return true;
                }
        }
@@ -798,6 +812,7 @@ std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string& freeform
        try
        {
                filters.push_back(FilterResult(RegexEngine, freeform, reason, type, duration, flgs, config));
+               dirty = true;
        }
        catch (ModuleException &e)
        {
@@ -879,7 +894,7 @@ void ModuleFilter::ReadFilters()
                if (!StringToFilterAction(action, fa))
                        fa = FA_NONE;
 
-               std::pair<bool, std::string> result = static_cast<ModuleFilter*>(this)->AddFilter(pattern, fa, reason, duration, flgs, true);
+               std::pair<bool, std::string> result = static_cast<ModuleFilter*>(this)->AddFilter(pattern, fa, reason, duration, flgs, !i->second->getBool("generated"));
                if (result.first)
                        removedfilters.erase(pattern);
                else
@@ -927,4 +942,70 @@ void ModuleFilter::OnUnloadModule(Module* mod)
        }
 }
 
+bool ModuleFilter::Tick(time_t)
+{
+               if (!dirty) // No need to write.
+                       return true;
+
+               if (filterconf.empty()) // Nothing to write to.
+               {
+                       dirty = false;
+                       return true;
+               }
+
+               const std::string newfilterconf = filterconf + ".tmp";
+               std::ofstream stream(newfilterconf.c_str());
+               if (!stream.is_open()) // Filesystem probably not writable.
+               {
+                       ServerInstance->SNO->WriteToSnoMask('f', "Unable to save filters to \"%s\": %s (%d)",
+                                       newfilterconf.c_str(), strerror(errno), errno);
+                       return true;
+               }
+
+               stream << "# This file is automatically generated by the filter module. Any changes will be overwritten." << std::endl
+                       << "# If you want to convert this to a normal config file you *MUST* remove the generated=\"yes\" keys!" << std::endl
+                       << std::endl
+                       << "<config format=\"xml\">" << std::endl;
+
+               for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
+               {
+                       // # <keyword reason="You qwertied!" action="block" flags="pn">
+                       const FilterResult& filter = (*i);
+                       if (filter.from_config)
+                               continue;
+
+                       stream << "<keyword generated=\"yes"
+                       << "\" pattern=\"" << ServerConfig::Escape(filter.freeform)
+                       << "\" reason=\"" << ServerConfig::Escape(filter.reason)
+                       << "\" action=\"" << FilterActionToString(filter.action)
+                       << "\" flags=\"" << filter.GetFlags();
+                       if (filter.duration)
+                               stream << "\" duration=\"" << InspIRCd::DurationString(filter.duration);
+                       stream << "\">" << std::endl;
+               }
+
+               if (stream.fail()) // Filesystem probably not writable.
+               {
+                       ServerInstance->SNO->WriteToSnoMask('f', "Unable to save filters to \"%s\": %s (%d)",
+                               newfilterconf.c_str(), strerror(errno), errno);
+                       return true;
+               }
+               stream.close();
+
+#ifdef _WIN32
+               remove(filterconf.c_str());
+#endif
+
+               // Use rename to move temporary to new db - this is guaranteed not to fuck up, even in case of a crash.
+               if (rename(newfilterconf.c_str(), filterconf.c_str()) < 0)
+               {
+                       ServerInstance->SNO->WriteToSnoMask('f', "Unable to replace old filter config \"%s\" with \"%s\": %s (%d)",
+                               filterconf.c_str(), newfilterconf.c_str(), strerror(errno), errno);
+                       return true;
+               }
+
+               dirty = false;
+               return true;
+}
+
 MODULE_INIT(ModuleFilter)