]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_filter.cpp
Use CommandBase::Params instead of std::vector<std::string>.
[user/henk/code/inspircd.git] / src / modules / m_filter.cpp
index 8bfefd6eb412559f9a17149add53ef22ad11f703..343a62a8d0c2f8167131f4f1a4a37dcd393ef5dc 100644 (file)
 #include "inspircd.h"
 #include "xline.h"
 #include "modules/regex.h"
-
-/* $ModDesc: Text (spam) filtering */
-
-class ModuleFilter;
+#include "modules/server.h"
+#include "modules/shun.h"
+#include "modules/stats.h"
 
 enum FilterFlags
 {
@@ -42,16 +41,18 @@ enum FilterAction
        FA_BLOCK,
        FA_SILENT,
        FA_KILL,
+       FA_SHUN,
        FA_NONE
 };
 
 class FilterResult
 {
  public:
+       Regex* regex;
        std::string freeform;
        std::string reason;
        FilterAction action;
-       long gline_time;
+       long duration;
 
        bool flag_no_opers;
        bool flag_part_message;
@@ -60,9 +61,15 @@ class FilterResult
        bool flag_notice;
        bool flag_strip_color;
 
-       FilterResult(const std::string free, const std::string &rea, FilterAction act, long gt, const std::string &fla) :
-                       freeform(free), reason(rea), action(act), gline_time(gt)
+       FilterResult(dynamic_reference<RegexFactory>& RegexEngine, const std::string& free, const std::string& rea, FilterAction act, long gt, const std::string& fla)
+               : freeform(free)
+               , reason(rea)
+               , action(act)
+               , duration(gt)
        {
+               if (!RegexEngine)
+                       throw ModuleException("Regex module implementing '"+RegexEngine.GetProvider()+"' is not loaded!");
+               regex = RegexEngine->Create(free);
                this->FillFlags(fla);
        }
 
@@ -144,28 +151,22 @@ class CommandFilter : public Command
                : Command(f, "FILTER", 1, 5)
        {
                flags_needed = 'o';
-               this->syntax = "<filter-definition> <action> <flags> [<gline-duration>] :<reason>";
+               this->syntax = "<filter-definition> <action> <flags> [<duration>] :<reason>";
        }
-       CmdResult Handle(const std::vector<std::string>&, User*);
+       CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
 
-       RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters)
+       RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
        {
                return ROUTE_BROADCAST;
        }
 };
 
-class ImplFilter : public FilterResult
+class ModuleFilter : public Module, public ServerEventListener, public Stats::EventListener
 {
- public:
-       Regex* regex;
-
-       ImplFilter(ModuleFilter* mymodule, const std::string &rea, FilterAction act, long glinetime, const std::string &pat, const std::string &flgs);
-};
+       typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet;
 
-
-class ModuleFilter : public Module
-{
        bool initing;
+       bool notifyuser;
        RegexFactory* factory;
        void FreeFilters();
 
@@ -173,35 +174,37 @@ class ModuleFilter : public Module
        CommandFilter filtcommand;
        dynamic_reference<RegexFactory> RegexEngine;
 
-       std::vector<ImplFilter> filters;
+       std::vector<FilterResult> filters;
        int flags;
 
-       std::set<std::string> exemptfromfilter; // List of channel names excluded from filtering.
+       // List of channel names excluded from filtering.
+       ExemptTargetSet exemptedchans;
+
+       // List of target nicknames excluded from filtering.
+       ExemptTargetSet exemptednicks;
 
        ModuleFilter();
-       void init();
-       CullResult cull();
-       ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
+       CullResult cull() CXX11_OVERRIDE;
+       ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE;
        FilterResult* FilterMatch(User* user, const std::string &text, int flags);
        bool DeleteFilter(const std::string &freeform);
        std::pair<bool, std::string> AddFilter(const std::string &freeform, FilterAction type, const std::string &reason, long duration, const std::string &flags);
-       ModResult OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
-       void OnRehash(User* user);
-       Version GetVersion();
+       void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE;
+       Version GetVersion() CXX11_OVERRIDE;
        std::string EncodeFilter(FilterResult* filter);
        FilterResult DecodeFilter(const std::string &data);
-       void OnSyncNetwork(Module* proto, void* opaque);
-       void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata);
-       ModResult OnStats(char symbol, User* user, string_list &results);
-       ModResult OnPreCommand(std::string &command, std::vector<std::string> &parameters, LocalUser *user, bool validated, const std::string &original_line);
-       void OnUnloadModule(Module* mod);
+       void OnSyncNetwork(ProtocolInterface::Server& server) CXX11_OVERRIDE;
+       void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata) CXX11_OVERRIDE;
+       ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE;
+       ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated, const std::string& original_line) CXX11_OVERRIDE;
+       void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
        bool AppliesToMe(User* user, FilterResult* filter, int flags);
        void ReadFilters();
        static bool StringToFilterAction(const std::string& str, FilterAction& fa);
        static std::string FilterActionToString(FilterAction fa);
 };
 
-CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User *user)
+CmdResult CommandFilter::Handle(User* user, const Params& parameters)
 {
        if (parameters.size() == 1)
        {
@@ -209,13 +212,13 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
                Module *me = creator;
                if (static_cast<ModuleFilter *>(me)->DeleteFilter(parameters[0]))
                {
-                       user->WriteServ("NOTICE %s :*** Removed filter '%s'", user->nick.c_str(), parameters[0].c_str());
+                       user->WriteNotice("*** Removed filter '" + parameters[0] + "'");
                        ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "FILTER: "+user->nick+" removed filter '"+parameters[0]+"'");
                        return CMD_SUCCESS;
                }
                else
                {
-                       user->WriteServ("NOTICE %s :*** Filter '%s' not found in list, try /stats s.", user->nick.c_str(), parameters[0].c_str());
+                       user->WriteNotice("*** Filter '" + parameters[0] + "' not found in list, try /stats s.");
                        return CMD_FAILURE;
                }
        }
@@ -232,11 +235,14 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
 
                        if (!ModuleFilter::StringToFilterAction(parameters[1], type))
                        {
-                               user->WriteServ("NOTICE %s :*** Invalid filter type '%s'. Supported types are 'gline', 'none', 'block', 'silent' and 'kill'.", user->nick.c_str(), parameters[1].c_str());
+                               if (ServerInstance->XLines->GetFactory("SHUN"))
+                                       user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent', 'kill', and 'shun'.");
+                               else
+                                       user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent', and 'kill'.");
                                return CMD_FAILURE;
                        }
 
-                       if (type == FA_GLINE)
+                       if (type == FA_GLINE || type == FA_SHUN)
                        {
                                if (parameters.size() >= 5)
                                {
@@ -245,7 +251,7 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
                                }
                                else
                                {
-                                       user->WriteServ("NOTICE %s :*** Not enough parameters: When setting a gline type filter, a gline duration must be specified as the third parameter.", user->nick.c_str());
+                                       user->WriteNotice("*** Not enough parameters: When setting a gline or shun type filter, a duration must be specified as the third parameter.");
                                        return CMD_FAILURE;
                                }
                        }
@@ -258,9 +264,9 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
                        std::pair<bool, std::string> result = static_cast<ModuleFilter *>(me)->AddFilter(freeform, type, parameters[reasonindex], duration, flags);
                        if (result.first)
                        {
-                               user->WriteServ("NOTICE %s :*** Added filter '%s', type '%s'%s%s, flags '%s', reason: '%s'", user->nick.c_str(), freeform.c_str(),
-                                               parameters[1].c_str(), (duration ? ", duration " : ""), (duration ? parameters[3].c_str() : ""),
-                                               flags.c_str(), parameters[reasonindex].c_str());
+                               user->WriteNotice("*** Added filter '" + freeform + "', type '" + parameters[1] + "'" +
+                                       (duration ? ", duration " +  parameters[3] : "") + ", flags '" + flags + "', reason: '" +
+                                       parameters[reasonindex] + "'");
 
                                ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "FILTER: "+user->nick+" added filter '"+freeform+"', type '"+parameters[1]+"', "+(duration ? "duration "+parameters[3]+", " : "")+"flags '"+flags+"', reason: "+parameters[reasonindex]);
 
@@ -268,13 +274,13 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
                        }
                        else
                        {
-                               user->WriteServ("NOTICE %s :*** Filter '%s' could not be added: %s", user->nick.c_str(), freeform.c_str(), result.second.c_str());
+                               user->WriteNotice("*** Filter '" + freeform + "' could not be added: " + result.second);
                                return CMD_FAILURE;
                        }
                }
                else
                {
-                       user->WriteServ("NOTICE %s :*** Not enough parameters.", user->nick.c_str());
+                       user->WriteNotice("*** Not enough parameters.");
                        return CMD_FAILURE;
                }
 
@@ -297,16 +303,12 @@ bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags)
 }
 
 ModuleFilter::ModuleFilter()
-       : initing(true), filtcommand(this), RegexEngine(this, "regex")
-{
-}
-
-void ModuleFilter::init()
+       : ServerEventListener(this)
+       , Stats::EventListener(this)
+       , initing(true)
+       , filtcommand(this)
+       , RegexEngine(this, "regex")
 {
-       ServerInstance->Modules->AddService(filtcommand);
-       Implementation eventlist[] = { I_OnPreCommand, I_OnStats, I_OnSyncNetwork, I_OnDecodeMetaData, I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash, I_OnUnloadModule };
-       ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
-       OnRehash(NULL);
 }
 
 CullResult ModuleFilter::cull()
@@ -317,43 +319,37 @@ CullResult ModuleFilter::cull()
 
 void ModuleFilter::FreeFilters()
 {
-       for (std::vector<ImplFilter>::const_iterator i = filters.begin(); i != filters.end(); ++i)
+       for (std::vector<FilterResult>::const_iterator i = filters.begin(); i != filters.end(); ++i)
                delete i->regex;
 
        filters.clear();
 }
 
-ModResult ModuleFilter::OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
+ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtarget, MessageDetails& details)
 {
+       // Leave remote users and servers alone
        if (!IS_LOCAL(user))
                return MOD_RES_PASSTHRU;
 
-       flags = FLAG_PRIVMSG;
-       return OnUserPreNotice(user,dest,target_type,text,status,exempt_list);
-}
+       flags = (details.type == MSG_PRIVMSG) ? FLAG_PRIVMSG : FLAG_NOTICE;
 
-ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
-{
-       /* Leave ulines alone */
-       if ((ServerInstance->ULine(user->server)) || (!IS_LOCAL(user)))
-               return MOD_RES_PASSTHRU;
-
-       if (!flags)
-               flags = FLAG_NOTICE;
-
-       FilterResult* f = this->FilterMatch(user, text, flags);
+       FilterResult* f = this->FilterMatch(user, details.text, flags);
        if (f)
        {
                std::string target;
-               if (target_type == TYPE_USER)
+               if (msgtarget.type == MessageTarget::TYPE_USER)
                {
-                       User* t = (User*)dest;
+                       User* t = msgtarget.Get<User>();
+                       // Check if the target nick is exempted, if yes, ignore this message
+                       if (exemptednicks.count(t->nick))
+                               return MOD_RES_PASSTHRU;
+
                        target = t->nick;
                }
-               else if (target_type == TYPE_CHANNEL)
+               else if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
                {
-                       Channel* t = (Channel*)dest;
-                       if (exemptfromfilter.find(t->name) != exemptfromfilter.end())
+                       Channel* t = msgtarget.Get<Channel>();
+                       if (exemptedchans.count(t->name))
                                return MOD_RES_PASSTHRU;
 
                        target = t->name;
@@ -361,25 +357,48 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s
                if (f->action == FA_BLOCK)
                {
                        ServerInstance->SNO->WriteGlobalSno('a', "FILTER: "+user->nick+" had their message filtered, target was "+target+": "+f->reason);
-                       if (target_type == TYPE_CHANNEL)
-                               user->WriteNumeric(404, "%s %s :Message to channel blocked and opers notified (%s)",user->nick.c_str(), target.c_str(), f->reason.c_str());
+                       if (notifyuser)
+                       {
+                               if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
+                                       user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked and opers notified (%s)", f->reason.c_str()));
+                               else
+                                       user->WriteNotice("Your message to "+target+" was blocked and opers notified: "+f->reason);
+                       }
                        else
-                               user->WriteServ("NOTICE "+user->nick+" :Your message to "+target+" was blocked and opers notified: "+f->reason);
+                               details.echooriginal = true;
                }
                else if (f->action == FA_SILENT)
                {
-                       if (target_type == TYPE_CHANNEL)
-                               user->WriteNumeric(404, "%s %s :Message to channel blocked (%s)",user->nick.c_str(), target.c_str(), f->reason.c_str());
+                       if (notifyuser)
+                       {
+                               if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
+                                       user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked (%s)", f->reason.c_str()));
+                               else
+                                       user->WriteNotice("Your message to "+target+" was blocked: "+f->reason);
+                       }
                        else
-                               user->WriteServ("NOTICE "+user->nick+" :Your message to "+target+" was blocked: "+f->reason);
+                               details.echooriginal = true;
                }
                else if (f->action == FA_KILL)
                {
+                       ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their message filtered and was killed, target was " + target + ": " + f->reason);
                        ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
                }
+               else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN")))
+               {
+                       Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
+                       ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their message filtered and was shunned, target was " + target + ": " + f->reason);
+                       if (ServerInstance->XLines->AddLine(sh, NULL))
+                       {
+                               ServerInstance->XLines->ApplyLines();
+                       }
+                       else
+                               delete sh;
+               }
                else if (f->action == FA_GLINE)
                {
-                       GLine* gl = new GLine(ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
+                       GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
+                       ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their message filtered and was G-Lined, target was " + target + ": " + f->reason);
                        if (ServerInstance->XLines->AddLine(gl,NULL))
                        {
                                ServerInstance->XLines->ApplyLines();
@@ -388,15 +407,15 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s
                                delete gl;
                }
 
-               ServerInstance->Logs->Log("FILTER",LOG_DEFAULT,"FILTER: "+ user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action));
+               ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action));
                return MOD_RES_DENY;
        }
        return MOD_RES_PASSTHRU;
 }
 
-ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::string> &parameters, LocalUser *user, bool validated, const std::string &original_line)
+ModResult ModuleFilter::OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated, const std::string& original_line)
 {
-       if (validated && IS_LOCAL(user))
+       if (validated)
        {
                flags = 0;
                bool parting;
@@ -416,7 +435,7 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri
                        if (parameters.size() < 2)
                                return MOD_RES_PASSTHRU;
 
-                       if (exemptfromfilter.find(parameters[0]) != exemptfromfilter.end())
+                       if (exemptedchans.count(parameters[0]))
                                return MOD_RES_PASSTHRU;
 
                        parting = true;
@@ -446,13 +465,14 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri
                        /* Are they parting, if so, kill is applicable */
                        if ((parting) && (f->action == FA_KILL))
                        {
-                               user->WriteServ("NOTICE %s :*** Your PART message was filtered: %s", user->nick.c_str(), f->reason.c_str());
+                               user->WriteNotice("*** Your PART message was filtered: " + f->reason);
                                ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
                        }
                        if (f->action == FA_GLINE)
                        {
                                /* Note: We gline *@IP so that if their host doesnt resolve the gline still applies. */
-                               GLine* gl = new GLine(ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
+                               GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
+                               ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their " + command + " message filtered and was G-Lined: " + f->reason);
                                if (ServerInstance->XLines->AddLine(gl,NULL))
                                {
                                        ServerInstance->XLines->ApplyLines();
@@ -460,24 +480,48 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri
                                else
                                        delete gl;
                        }
+                       else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN")))
+                       {
+                               /* Note: We shun *!*@IP so that if their host doesnt resolve the shun still applies. */
+                               Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
+                               ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their " + command + " message filtered and was shunned: " + f->reason);
+                               if (ServerInstance->XLines->AddLine(sh, NULL))
+                               {
+                                       ServerInstance->XLines->ApplyLines();
+                               }
+                               else
+                                       delete sh;
+                       }
                        return MOD_RES_DENY;
                }
        }
        return MOD_RES_PASSTHRU;
 }
 
-void ModuleFilter::OnRehash(User* user)
+void ModuleFilter::ReadConfig(ConfigStatus& status)
 {
        ConfigTagList tags = ServerInstance->Config->ConfTags("exemptfromfilter");
-       exemptfromfilter.clear();
+       exemptedchans.clear();
+       exemptednicks.clear();
+
        for (ConfigIter i = tags.first; i != tags.second; ++i)
        {
-               std::string chan = i->second->getString("channel");
-               if (!chan.empty())
-                       exemptfromfilter.insert(chan);
+               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"));
+               if (!target.empty())
+               {
+                       if (target[0] == '#')
+                               exemptedchans.insert(target);
+                       else
+                               exemptednicks.insert(target);
+               }
        }
 
-       std::string newrxengine = ServerInstance->Config->ConfValue("filteropts")->getString("engine");
+       ConfigTag* tag = ServerInstance->Config->ConfValue("filteropts");
+       std::string newrxengine = tag->getString("engine");
+       notifyuser = tag->getBool("notifyuser", true);
 
        factory = RegexEngine ? (RegexEngine.operator->()) : NULL;
 
@@ -523,7 +567,7 @@ std::string ModuleFilter::EncodeFilter(FilterResult* filter)
                if (*n == ' ')
                        *n = '\7';
 
-       stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->gline_time << " :" << filter->reason;
+       stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->duration << " :" << filter->reason;
        return stream.str();
 }
 
@@ -543,7 +587,7 @@ FilterResult ModuleFilter::DecodeFilter(const std::string &data)
        if (c != 0)
                throw ModuleException("Invalid flag: '" + std::string(1, c) + "'");
 
-       tokens.GetToken(res.gline_time);
+       tokens.GetToken(res.duration);
        tokens.GetToken(res.reason);
 
        /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
@@ -554,11 +598,11 @@ FilterResult ModuleFilter::DecodeFilter(const std::string &data)
        return res;
 }
 
-void ModuleFilter::OnSyncNetwork(Module* proto, void* opaque)
+void ModuleFilter::OnSyncNetwork(ProtocolInterface::Server& server)
 {
-       for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); ++i)
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
        {
-               proto->ProtoSendMetaData(opaque, NULL, "filter", EncodeFilter(&(*i)));
+               server.SendMetaData("filter", EncodeFilter(&(*i)));
        }
 }
 
@@ -569,31 +613,23 @@ void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extna
                try
                {
                        FilterResult data = DecodeFilter(extdata);
-                       this->AddFilter(data.freeform, data.action, data.reason, data.gline_time, data.GetFlags());
+                       this->AddFilter(data.freeform, data.action, data.reason, data.duration, data.GetFlags());
                }
                catch (ModuleException& e)
                {
-                       ServerInstance->Logs->Log("m_filter", LOG_DEBUG, "Error when unserializing filter: " + std::string(e.GetReason()));
+                       ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error when unserializing filter: " + e.GetReason());
                }
        }
 }
 
-ImplFilter::ImplFilter(ModuleFilter* mymodule, const std::string &rea, FilterAction act, long glinetime, const std::string &pat, const std::string &flgs)
-               : FilterResult(pat, rea, act, glinetime, flgs)
-{
-       if (!mymodule->RegexEngine)
-               throw ModuleException("Regex module implementing '"+mymodule->RegexEngine.GetProvider()+"' is not loaded!");
-       regex = mymodule->RegexEngine->Create(pat);
-}
-
 FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int flgs)
 {
        static std::string stripped_text;
        stripped_text.clear();
 
-       for (std::vector<ImplFilter>::iterator index = filters.begin(); index != filters.end(); index++)
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
        {
-               FilterResult* filter = dynamic_cast<FilterResult*>(&(*index));
+               FilterResult* filter = &*i;
 
                /* Skip ones that dont apply to us */
                if (!AppliesToMe(user, filter, flgs))
@@ -605,20 +641,15 @@ FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int
                        InspIRCd::StripColor(stripped_text);
                }
 
-               //ServerInstance->Logs->Log("m_filter", LOG_DEBUG, "Match '%s' against '%s'", text.c_str(), index->freeform.c_str());
-               if (index->regex->Matches(filter->flag_strip_color ? stripped_text : text))
-               {
-                       //ServerInstance->Logs->Log("m_filter", LOG_DEBUG, "MATCH");
-                       return &*index;
-               }
-               //ServerInstance->Logs->Log("m_filter", LOG_DEBUG, "NO MATCH");
+               if (filter->regex->Matches(filter->flag_strip_color ? stripped_text : text))
+                       return filter;
        }
        return NULL;
 }
 
 bool ModuleFilter::DeleteFilter(const std::string &freeform)
 {
-       for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
        {
                if (i->freeform == freeform)
                {
@@ -632,7 +663,7 @@ bool ModuleFilter::DeleteFilter(const std::string &freeform)
 
 std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform, FilterAction type, const std::string &reason, long duration, const std::string &flgs)
 {
-       for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
        {
                if (i->freeform == freeform)
                {
@@ -642,11 +673,11 @@ std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform
 
        try
        {
-               filters.push_back(ImplFilter(this, reason, type, duration, freeform, flgs));
+               filters.push_back(FilterResult(RegexEngine, freeform, reason, type, duration, flgs));
        }
        catch (ModuleException &e)
        {
-               ServerInstance->Logs->Log("m_filter", LOG_DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason());
+               ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason().c_str());
                return std::make_pair(false, e.GetReason());
        }
        return std::make_pair(true, "");
@@ -654,17 +685,17 @@ std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform
 
 bool ModuleFilter::StringToFilterAction(const std::string& str, FilterAction& fa)
 {
-       irc::string s(str.c_str());
-
-       if (s == "gline")
+       if (stdalgo::string::equalsci(str, "gline"))
                fa = FA_GLINE;
-       else if (s == "block")
+       else if (stdalgo::string::equalsci(str, "block"))
                fa = FA_BLOCK;
-       else if (s == "silent")
+       else if (stdalgo::string::equalsci(str, "silent"))
                fa = FA_SILENT;
-       else if (s == "kill")
+       else if (stdalgo::string::equalsci(str, "kill"))
                fa = FA_KILL;
-       else if (s == "none")
+       else if (stdalgo::string::equalsci(str, "shun") && (ServerInstance->XLines->GetFactory("SHUN")))
+               fa = FA_SHUN;
+       else if (stdalgo::string::equalsci(str, "none"))
                fa = FA_NONE;
        else
                return false;
@@ -680,6 +711,7 @@ std::string ModuleFilter::FilterActionToString(FilterAction fa)
                case FA_BLOCK:  return "block";
                case FA_SILENT: return "silent";
                case FA_KILL:   return "kill";
+               case FA_SHUN:   return "shun";
                default:                return "none";
        }
 }
@@ -695,7 +727,7 @@ void ModuleFilter::ReadFilters()
                std::string reason = i->second->getString("reason");
                std::string action = i->second->getString("action");
                std::string flgs = i->second->getString("flags");
-               unsigned long gline_time = InspIRCd::Duration(i->second->getString("duration"));
+               unsigned long duration = i->second->getDuration("duration", 10*60, 1);
                if (flgs.empty())
                        flgs = "*";
 
@@ -705,27 +737,31 @@ void ModuleFilter::ReadFilters()
 
                try
                {
-                       filters.push_back(ImplFilter(this, reason, fa, gline_time, pattern, flgs));
-                       ServerInstance->Logs->Log("m_filter", LOG_DEFAULT, "Regular expression %s loaded.", pattern.c_str());
+                       filters.push_back(FilterResult(RegexEngine, pattern, reason, fa, duration, flgs));
+                       ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Regular expression %s loaded.", pattern.c_str());
                }
                catch (ModuleException &e)
                {
-                       ServerInstance->Logs->Log("m_filter", LOG_DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason());
+                       ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason().c_str());
                }
        }
 }
 
-ModResult ModuleFilter::OnStats(char symbol, User* user, string_list &results)
+ModResult ModuleFilter::OnStats(Stats::Context& stats)
 {
-       if (symbol == 's')
+       if (stats.GetSymbol() == 's')
        {
-               for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
+               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);
+               }
+               for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i)
                {
-                       results.push_back(ServerInstance->Config->ServerName+" 223 "+user->nick+" :"+RegexEngine.GetProvider()+":"+i->freeform+" "+i->GetFlags()+" "+FilterActionToString(i->action)+" "+ConvToStr(i->gline_time)+" :"+i->reason);
+                       stats.AddRow(223, "EXEMPT "+(*i));
                }
-               for (std::set<std::string>::iterator i = exemptfromfilter.begin(); i != exemptfromfilter.end(); ++i)
+               for (ExemptTargetSet::const_iterator i = exemptednicks.begin(); i != exemptednicks.end(); ++i)
                {
-                       results.push_back(ServerInstance->Config->ServerName+" 223 "+user->nick+" :EXEMPT "+(*i));
+                       stats.AddRow(223, "EXEMPT "+(*i));
                }
        }
        return MOD_RES_PASSTHRU;