]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_filter.cpp
Update copyright headers.
[user/henk/code/inspircd.git] / src / modules / m_filter.cpp
index 5eef97e3f8a9848f8d6e4b47d1bda1fdf824a9fb..c666f6ad265d855c31fde130a9fb78559387411f 100644 (file)
@@ -7,15 +7,15 @@
  *   Copyright (C) 2018 Michael Hazell <michaelhazell@hotmail.com>
  *   Copyright (C) 2017 B00mX0r <b00mx0r@aureus.pw>
  *   Copyright (C) 2012-2014, 2016 Attila Molnar <attilamolnar@hush.com>
- *   Copyright (C) 2012-2013, 2017-2020 Sadie Powell <sadie@witchery.services>
+ *   Copyright (C) 2012-2013, 2017-2021 Sadie Powell <sadie@witchery.services>
  *   Copyright (C) 2012, 2018-2019 Robby <robby@chatbelgie.be>
  *   Copyright (C) 2011 Adam <Adam@anope.org>
  *   Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
  *   Copyright (C) 2009 Robin Burchell <robin+git@viroteck.net>
  *   Copyright (C) 2009 Matt Smith <dz@inspircd.org>
  *   Copyright (C) 2009 John Brooks <special@inspircd.org>
- *   Copyright (C) 2007-2010 Craig Edwards <brain@inspircd.org>
  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ *   Copyright (C) 2006-2010 Craig Edwards <brain@inspircd.org>
  *
  * 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
@@ -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)
@@ -406,7 +415,7 @@ ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtar
                                break;
                        }
                        case MessageTarget::TYPE_SERVER:
-                               break;
+                               return MOD_RES_PASSTHRU;
                }
 
                if (is_selfmsg && warnonselfmsg)
@@ -428,9 +437,9 @@ ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtar
                        if (notifyuser)
                        {
                                if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
-                                       user->WriteNumeric(ERR_CANNOTSENDTOCHAN, msgtarget.GetName(), InspIRCd::Format("Message to channel blocked and opers notified (%s)", f->reason.c_str()));
+                                       user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<Channel>(), InspIRCd::Format("Your message to this channel was blocked: %s.", f->reason.c_str())));
                                else
-                                       user->WriteNotice("Your message to "+msgtarget.GetName()+" was blocked and opers notified: "+f->reason);
+                                       user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<User>(), InspIRCd::Format("Your message to this user was blocked: %s.", f->reason.c_str())));
                        }
                        else
                                details.echo_original = true;
@@ -440,9 +449,9 @@ ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtar
                        if (notifyuser)
                        {
                                if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
-                                       user->WriteNumeric(ERR_CANNOTSENDTOCHAN, msgtarget.GetName(), InspIRCd::Format("Message to channel blocked (%s)", f->reason.c_str()));
+                                       user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<Channel>(), InspIRCd::Format("Your message to this channel was blocked: %s.", f->reason.c_str())));
                                else
-                                       user->WriteNotice("Your message to "+msgtarget.GetName()+" was blocked: "+f->reason);
+                                       user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<User>(), InspIRCd::Format("Your message to this user was blocked: %s.", f->reason.c_str())));
                        }
                        else
                                details.echo_original = true;
@@ -542,7 +551,7 @@ ModResult ModuleFilter::OnPreCommand(std::string& command, CommandBase::Params&
                /* We cant block a part or quit, so instead we change the reason to 'Reason filtered' */
                parameters[parting ? 1 : 0] = "Reason filtered";
 
-               /* We're warning or blocking, OR theyre quitting and its a KILL action
+               /* We're warning or blocking, OR they're quitting and its a KILL action
                 * (we cant kill someone whos already quitting, so filter them anyway)
                 */
                if ((f->action == FA_WARN) || (f->action == FA_BLOCK) || (((!parting) && (f->action == FA_KILL))) || (f->action == FA_SILENT))
@@ -624,7 +633,7 @@ void ModuleFilter::ReadConfig(ConfigStatus& status)
                ConfigTag* tag = i->second;
 
                // If "target" is not found, try the old "channel" key to keep compatibility with 2.0 configs
-               const std::string target = tag->getString("target", tag->getString("channel"));
+               const std::string target = tag->getString("target", tag->getString("channel"), 1);
                if (!target.empty())
                {
                        if (target[0] == '#')
@@ -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;
 
@@ -670,7 +683,7 @@ void ModuleFilter::ReadConfig(ConfigStatus& status)
 
 Version ModuleFilter::GetVersion()
 {
-       return Version("Provides text (spam) filtering", VF_VENDOR | VF_COMMON, RegexEngine ? RegexEngine->name : "");
+       return Version("Adds the /FILTER command which allows server operators to define regex matches for inappropriate phrases that are not allowed to be used in channel messages, private messages, part messages, or quit messages.", VF_VENDOR | VF_COMMON, RegexEngine ? RegexEngine->name : "");
 }
 
 std::string ModuleFilter::EncodeFilter(FilterResult* filter)
@@ -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
@@ -899,7 +914,7 @@ ModResult ModuleFilter::OnStats(Stats::Context& stats)
        {
                for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
                {
-                       stats.AddRow(223, RegexEngine.GetProvider()+":"+i->freeform+" "+i->GetFlags()+" "+FilterActionToString(i->action)+" "+ConvToStr(i->duration)+" :"+i->reason);
+                       stats.AddRow(223, RegexEngine.GetProvider(), i->freeform, i->GetFlags(), FilterActionToString(i->action), i->duration, i->reason);
                }
                for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i)
                {
@@ -927,4 +942,72 @@ 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 was automatically generated by the " << INSPIRCD_VERSION << " filter module on " << InspIRCd::TimeString(ServerInstance->Time()) << "." << std::endl
+                       << "# Any changes to this file will be automatically 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)