]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_filter.cpp
Improve the message sent when overriding channel modes.
[user/henk/code/inspircd.git] / src / modules / m_filter.cpp
index ab7005c6baa9374c806fe9265331f2a6753f2cac..786ea673bb27cce92881c678b42e01ba7cedda5b 100644 (file)
@@ -1,27 +1,43 @@
-/*       +------------------------------------+
- *       | Inspire Internet Relay Chat Daemon |
- *       +------------------------------------+
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
  *
- *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
- * See: http://www.inspircd.org/wiki/index.php/Credits
+ *   Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
+ *   Copyright (C) 2019 Filippo Cortigiani <simos@simosnap.org>
+ *   Copyright (C) 2018-2019 linuxdaemon <linuxdaemon.irc@gmail.com>
+ *   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, 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>
  *
- * This program is free but copyrighted software; see
- *         the file COPYING for details.
+ * 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 <http://www.gnu.org/licenses/>.
  */
 
+
 #include "inspircd.h"
-#include "users.h"
-#include "channels.h"
-#include "modules.h"
 #include "xline.h"
-#include "m_regex.h"
-
-/* $ModDesc: Text (spam) filtering */
-
-static std::string RegexEngine = "";
-static Module* rxengine = NULL;
+#include "modules/regex.h"
+#include "modules/server.h"
+#include "modules/shun.h"
+#include "modules/stats.h"
+#include "modules/account.h"
 
 enum FilterFlags
 {
@@ -31,34 +47,55 @@ enum FilterFlags
        FLAG_NOTICE = 16
 };
 
-class FilterResult : public classbase
+enum FilterAction
+{
+       FA_GLINE,
+       FA_ZLINE,
+       FA_WARN,
+       FA_BLOCK,
+       FA_SILENT,
+       FA_KILL,
+       FA_SHUN,
+       FA_NONE
+};
+
+class FilterResult
 {
  public:
+       Regex* regex;
        std::string freeform;
        std::string reason;
-       std::string action;
-       long gline_time;
-       std::string flags;
+       FilterAction action;
+       unsigned long duration;
+       bool from_config;
 
        bool flag_no_opers;
        bool flag_part_message;
        bool flag_quit_message;
        bool flag_privmsg;
        bool flag_notice;
-
-       FilterResult(const std::string free, const std::string &rea, const std::string &act, long gt, const std::string &fla) :
-                       freeform(free), reason(rea), action(act), gline_time(gt), flags(fla)
+       bool flag_strip_color;
+       bool flag_no_registered;
+
+       FilterResult(dynamic_reference<RegexFactory>& RegexEngine, const std::string& free, const std::string& rea, FilterAction act, unsigned long gt, const std::string& fla, bool cfg)
+               : freeform(free)
+               , reason(rea)
+               , action(act)
+               , duration(gt)
+               , from_config(cfg)
        {
+               if (!RegexEngine)
+                       throw ModuleException("Regex module implementing '"+RegexEngine.GetProvider()+"' is not loaded!");
+               regex = RegexEngine->Create(free);
                this->FillFlags(fla);
        }
 
-       int FillFlags(const std::string &fl)
+       char FillFlags(const std::string &fl)
        {
-               flags = fl;
-               flag_no_opers = flag_part_message = flag_quit_message = flag_privmsg = flag_notice = false;
-               size_t x = 0;
+               flag_no_opers = flag_part_message = flag_quit_message = flag_privmsg =
+                       flag_notice = flag_strip_color = flag_no_registered = false;
 
-               for (std::string::const_iterator n = flags.begin(); n != flags.end(); ++n, ++x)
+               for (std::string::const_iterator n = fl.begin(); n != fl.end(); ++n)
                {
                        switch (*n)
                        {
@@ -77,150 +114,225 @@ class FilterResult : public classbase
                                case 'n':
                                        flag_notice = true;
                                break;
+                               case 'c':
+                                       flag_strip_color = true;
+                               break;
+                               case 'r':
+                                       flag_no_registered = true;
+                               break;
                                case '*':
                                        flag_no_opers = flag_part_message = flag_quit_message =
-                                               flag_privmsg = flag_notice = true;
+                                               flag_privmsg = flag_notice = flag_strip_color = true;
                                break;
                                default:
-                                       return x;
+                                       return *n;
                                break;
                        }
                }
                return 0;
        }
 
-       FilterResult()
+       std::string GetFlags()
        {
+               std::string flags;
+               if (flag_no_opers)
+                       flags.push_back('o');
+               if (flag_part_message)
+                       flags.push_back('P');
+               if (flag_quit_message)
+                       flags.push_back('q');
+               if (flag_privmsg)
+                       flags.push_back('p');
+               if (flag_notice)
+                       flags.push_back('n');
+
+               /* Order is important here, as the logic in FillFlags() stops parsing when it encounters
+                * an unknown character. So the following characters must be last in the string.
+                * 'c' is unsupported on < 2.0.10
+                * 'r' is unsupported on < 3.2.0
+                */
+               if (flag_strip_color)
+                       flags.push_back('c');
+               if (flag_no_registered)
+                       flags.push_back('r');
+
+               if (flags.empty())
+                       flags.push_back('-');
+
+               return flags;
        }
 
-       virtual ~FilterResult()
+       FilterResult()
        {
        }
 };
 
-class CommandFilter;
+class CommandFilter : public Command
+{
+ public:
+       CommandFilter(Module* f)
+               : Command(f, "FILTER", 1, 5)
+       {
+               flags_needed = 'o';
+               this->syntax = "<pattern> [<action> <flags> [<duration>] :<reason>]";
+       }
+       CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
+
+       RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
+       {
+               return ROUTE_BROADCAST;
+       }
+};
 
-class FilterBase : public Module
+class ModuleFilter
+       : public Module
+       , public ServerProtocol::SyncEventListener
+       , public Stats::EventListener
 {
-       CommandFilter* filtcommand;
-       int flags;
-protected:
-       std::vector<std::string> exemptfromfilter; // List of channel names excluded from filtering.
+       typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet;
+
+       bool initing;
+       bool notifyuser;
+       bool warnonselfmsg;
+       RegexFactory* factory;
+       void FreeFilters();
+
  public:
-       FilterBase(InspIRCd* Me, const std::string &source);
-       virtual ~FilterBase();
-       virtual int OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
-       virtual FilterResult* FilterMatch(User* user, const std::string &text, int flags) = 0;
-       virtual bool DeleteFilter(const std::string &freeform) = 0;
-       virtual void SyncFilters(Module* proto, void* opaque) = 0;
-       virtual void SendFilter(Module* proto, void* opaque, FilterResult* iter);
-       virtual std::pair<bool, std::string> AddFilter(const std::string &freeform, const std::string &type, const std::string &reason, long duration, const std::string &flags) = 0;
-       virtual int OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
-       virtual void OnRehash(User* user, const std::string &parameter);
-       virtual Version GetVersion();
+       CommandFilter filtcommand;
+       dynamic_reference<RegexFactory> RegexEngine;
+
+       std::vector<FilterResult> filters;
+       int flags;
+
+       // List of channel names excluded from filtering.
+       ExemptTargetSet exemptedchans;
+
+       // List of target nicknames excluded from filtering.
+       ExemptTargetSet exemptednicks;
+
+       ModuleFilter();
+       void init() CXX11_OVERRIDE;
+       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::string& reason);
+       std::pair<bool, std::string> AddFilter(const std::string& freeform, FilterAction type, const std::string& reason, unsigned long duration, const std::string& flags, bool config = false);
+       void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE;
+       Version GetVersion() CXX11_OVERRIDE;
        std::string EncodeFilter(FilterResult* filter);
        FilterResult DecodeFilter(const std::string &data);
-       virtual void OnSyncOtherMetaData(Module* proto, void* opaque, bool displayable = false);
-       virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata);
-       virtual int OnStats(char symbol, User* user, string_list &results) = 0;
-       virtual int OnPreCommand(std::string &command, std::vector<std::string> &parameters, User *user, bool validated, const std::string &original_line);
+       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) CXX11_OVERRIDE;
+       void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
        bool AppliesToMe(User* user, FilterResult* filter, int flags);
-       void OnLoadModule(Module* mod, const std::string& name);
+       void ReadFilters();
+       static bool StringToFilterAction(const std::string& str, FilterAction& fa);
+       static std::string FilterActionToString(FilterAction fa);
 };
 
-class CommandFilter : public Command
+CmdResult CommandFilter::Handle(User* user, const Params& parameters)
 {
-       FilterBase* Base;
- public:
-       CommandFilter(FilterBase* f, InspIRCd* Me, const std::string &ssource) : Command(Me, "FILTER", "o", 1), Base(f)
+       if (parameters.size() == 1)
        {
-               this->source = ssource;
-               this->syntax = "<filter-definition> <type> <flags> [<gline-duration>] :<reason>";
-       }
+               /* Deleting a filter */
+               Module* me = creator;
+               std::string reason;
 
-       CmdResult Handle(const std::vector<std::string> &parameters, User *user)
-       {
-               if (parameters.size() == 1)
+               if (static_cast<ModuleFilter*>(me)->DeleteFilter(parameters[0], reason))
                {
-                       /* Deleting a filter */
-                       if (Base->DeleteFilter(parameters[0]))
-                       {
-                               user->WriteServ("NOTICE %s :*** Deleted filter '%s'", user->nick.c_str(), parameters[0].c_str());
-                               return CMD_SUCCESS;
-                       }
-                       else
-                       {
-                               user->WriteServ("NOTICE %s :*** Filter '%s' not found on list.", user->nick.c_str(), parameters[0].c_str());
-                               return CMD_FAILURE;
-                       }
+                       user->WriteNotice("*** Removed filter '" + parameters[0] + "': " + reason);
+                       ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'f' : 'F', "%s removed filter '%s': %s",
+                               user->nick.c_str(), parameters[0].c_str(), reason.c_str());
+                       return CMD_SUCCESS;
                }
                else
                {
-                       /* Adding a filter */
-                       if (parameters.size() >= 4)
-                       {
-                               std::string freeform = parameters[0];
-                               std::string type = parameters[1];
-                               std::string flags = parameters[2];
-                               std::string reason;
-                               long duration = 0;
-
+                       user->WriteNotice("*** Filter '" + parameters[0] + "' not found on the list.");
+                       return CMD_FAILURE;
+               }
+       }
+       else
+       {
+               /* Adding a filter */
+               if (parameters.size() >= 4)
+               {
+                       const std::string& freeform = parameters[0];
+                       FilterAction type;
+                       const std::string& flags = parameters[2];
+                       unsigned int reasonindex;
+                       unsigned long duration = 0;
 
-                               if ((type != "gline") && (type != "none") && (type != "block") && (type != "kill") && (type != "silent"))
-                               {
-                                       user->WriteServ("NOTICE %s :*** Invalid filter type '%s'. Supported types are 'gline', 'none', 'block', 'silent' and 'kill'.", user->nick.c_str(), freeform.c_str());
-                                       return CMD_FAILURE;
-                               }
+                       if (!ModuleFilter::StringToFilterAction(parameters[1], type))
+                       {
+                               if (ServerInstance->XLines->GetFactory("SHUN"))
+                                       user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'zline', 'none', 'warn', 'block', 'silent', 'kill', and 'shun'.");
+                               else
+                                       user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'zline', 'none', 'warn', 'block', 'silent', and 'kill'.");
+                               return CMD_FAILURE;
+                       }
 
-                               if (type == "gline")
+                       if (type == FA_GLINE || type == FA_ZLINE || type == FA_SHUN)
+                       {
+                               if (parameters.size() >= 5)
                                {
-                                       if (parameters.size() >= 5)
+                                       if (!InspIRCd::Duration(parameters[3], duration))
                                        {
-                                               duration = ServerInstance->Duration(parameters[3]);
-                                               reason = parameters[4];
-                                       }
-                                       else
-                                       {
-                                               this->TooFewParams(user, " When setting a gline type filter, a gline duration must be specified as the third parameter.");
+                                               user->WriteNotice("*** Invalid duration for filter");
                                                return CMD_FAILURE;
                                        }
+                                       reasonindex = 4;
                                }
                                else
                                {
-                                       reason = parameters[3];
-                               }
-                               std::pair<bool, std::string> result = Base->AddFilter(freeform, type, reason, 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(),
-                                                       type.c_str(), (duration ? " duration: " : ""), (duration ? parameters[3].c_str() : ""),
-                                                       flags.c_str(), reason.c_str());
-                                       return CMD_SUCCESS;
-                               }
-                               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("*** Not enough parameters: When setting a '" + parameters[1] + "' type filter, a duration must be specified as the third parameter.");
                                        return CMD_FAILURE;
                                }
                        }
                        else
                        {
-                               this->TooFewParams(user, ".");
-                               return CMD_FAILURE;
+                               reasonindex = 3;
                        }
 
+                       Module* me = creator;
+                       std::pair<bool, std::string> result = static_cast<ModuleFilter*>(me)->AddFilter(freeform, type, parameters[reasonindex], duration, flags);
+                       if (result.first)
+                       {
+                               const std::string message = InspIRCd::Format("'%s', type '%s'%s, flags '%s', reason: %s",
+                                       freeform.c_str(), parameters[1].c_str(),
+                                       (duration ? InspIRCd::Format(", duration '%s'",
+                                               InspIRCd::DurationString(duration).c_str()).c_str()
+                                       : ""), flags.c_str(), parameters[reasonindex].c_str());
+
+                               user->WriteNotice("*** Added filter " + message);
+                               ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'f' : 'F',
+                                       "%s added filter %s", user->nick.c_str(), message.c_str());
+
+                               return CMD_SUCCESS;
+                       }
+                       else
+                       {
+                               user->WriteNotice("*** Filter '" + freeform + "' could not be added: " + result.second);
+                               return CMD_FAILURE;
+                       }
+               }
+               else
+               {
+                       user->WriteNotice("*** Not enough parameters.");
+                       return CMD_FAILURE;
                }
-       }
 
-       void TooFewParams(User* user, const std::string &extra_text)
-       {
-               user->WriteServ("NOTICE %s :*** Not enough parameters%s", user->nick.c_str(), extra_text.c_str());
        }
-};
+}
 
-bool FilterBase::AppliesToMe(User* user, FilterResult* filter, int iflags)
+bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags)
 {
-       if ((filter->flag_no_opers) && IS_OPER(user))
+       const AccountExtItem* accountext = GetAccountExtItem();
+
+       if ((filter->flag_no_opers) && user->IsOper())
+               return false;
+       if ((filter->flag_no_registered) && accountext && accountext->get(user))
                return false;
        if ((iflags & FLAG_PRIVMSG) && (!filter->flag_privmsg))
                return false;
@@ -233,67 +345,135 @@ bool FilterBase::AppliesToMe(User* user, FilterResult* filter, int iflags)
        return true;
 }
 
-FilterBase::FilterBase(InspIRCd* Me, const std::string &source) : Module(Me)
+ModuleFilter::ModuleFilter()
+       : ServerProtocol::SyncEventListener(this)
+       , Stats::EventListener(this)
+       , initing(true)
+       , filtcommand(this)
+       , RegexEngine(this, "regex")
 {
-       Me->Modules->UseInterface("RegularExpression");
-       filtcommand = new CommandFilter(this, Me, source);
-       ServerInstance->AddCommand(filtcommand);
-       Implementation eventlist[] = { I_OnPreCommand, I_OnStats, I_OnSyncOtherMetaData, I_OnDecodeMetaData, I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash, I_OnLoadModule };
-       ServerInstance->Modules->Attach(eventlist, this, 8);
 }
 
-FilterBase::~FilterBase()
+void ModuleFilter::init()
 {
-       ServerInstance->Modules->DoneWithInterface("RegularExpression");
+       ServerInstance->SNO->EnableSnomask('f', "FILTER");
 }
 
-int FilterBase::OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
+CullResult ModuleFilter::cull()
 {
-       flags = FLAG_PRIVMSG;
-       return OnUserPreNotice(user,dest,target_type,text,status,exempt_list);
+       FreeFilters();
+       return Module::cull();
 }
 
-int FilterBase::OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
+void ModuleFilter::FreeFilters()
 {
-       if (!flags)
-               flags = FLAG_NOTICE;
+       for (std::vector<FilterResult>::const_iterator i = filters.begin(); i != filters.end(); ++i)
+               delete i->regex;
 
-       /* Leave ulines alone */
-       if ((ServerInstance->ULine(user->server)) || (!IS_LOCAL(user)))
-               return 0;
+       filters.clear();
+}
 
-       FilterResult* f = this->FilterMatch(user, text, flags);
+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 = (details.type == MSG_PRIVMSG) ? FLAG_PRIVMSG : FLAG_NOTICE;
+
+       FilterResult* f = this->FilterMatch(user, details.text, flags);
        if (f)
        {
-               std::string target = "";
-               if (target_type == TYPE_USER)
+               bool is_selfmsg = false;
+               switch (msgtarget.type)
                {
-                       User* t = (User*)dest;
-                       target = std::string(t->nick);
+                       case MessageTarget::TYPE_USER:
+                       {
+                               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;
+
+                               if (user == t)
+                                       is_selfmsg = true;
+                               break;
+                       }
+                       case MessageTarget::TYPE_CHANNEL:
+                       {
+                               Channel* t = msgtarget.Get<Channel>();
+                               if (exemptedchans.count(t->name))
+                                       return MOD_RES_PASSTHRU;
+                               break;
+                       }
+                       case MessageTarget::TYPE_SERVER:
+                               return MOD_RES_PASSTHRU;
                }
-               else if (target_type == TYPE_CHANNEL)
+
+               if (is_selfmsg && warnonselfmsg)
                {
-                       Channel* t = (Channel*)dest;
-                       target = std::string(t->name);
-                       std::vector<std::string>::iterator i = find(exemptfromfilter.begin(), exemptfromfilter.end(), target);
-                       if (i != exemptfromfilter.end()) return 0;
+                       ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("WARNING: %s's self message matched %s (%s)",
+                               user->nick.c_str(), f->freeform.c_str(), f->reason.c_str()));
+                       return MOD_RES_PASSTHRU;
                }
-               if (f->action == "block")
-               {       
-                       ServerInstance->SNO->WriteToSnoMask('A', std::string("FILTER: ")+user->nick+" had their message filtered, target was "+target+": "+f->reason);
-                       user->WriteServ("NOTICE "+std::string(user->nick)+" :Your message has been filtered and opers notified: "+f->reason);
+               else if (f->action == FA_WARN)
+               {
+                       ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("WARNING: %s's message to %s matched %s (%s)",
+                               user->nick.c_str(), msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
+                       return MOD_RES_PASSTHRU;
                }
-               if (f->action == "silent")
+               else if (f->action == FA_BLOCK)
                {
-                       user->WriteServ("NOTICE "+std::string(user->nick)+" :Your message has been filtered: "+f->reason);
+                       ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s had their message to %s filtered as it matched %s (%s)",
+                               user->nick.c_str(), msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
+                       if (notifyuser)
+                       {
+                               if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
+                                       user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<Channel>(), InspIRCd::Format("Your message to this channel was blocked: %s.", f->reason.c_str())));
+                               else
+                                       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;
                }
-               if (f->action == "kill")
+               else if (f->action == FA_SILENT)
                {
+                       if (notifyuser)
+                       {
+                               if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
+                                       user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<Channel>(), InspIRCd::Format("Your message to this channel was blocked: %s.", f->reason.c_str())));
+                               else
+                                       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;
+               }
+               else if (f->action == FA_KILL)
+               {
+                       ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was killed because their message to %s matched %s (%s)",
+                               user->nick.c_str(), msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
                        ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
                }
-               if (f->action == "gline")
+               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('f', InspIRCd::Format("%s (%s) was shunned for %s (expires on %s) because their message to %s matched %s (%s)",
+                               user->nick.c_str(), sh->Displayable().c_str(), InspIRCd::DurationString(f->duration).c_str(),
+                               InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
+                               msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
+                       if (ServerInstance->XLines->AddLine(sh, NULL))
+                       {
+                               ServerInstance->XLines->ApplyLines();
+                       }
+                       else
+                               delete sh;
+               }
+               else if (f->action == FA_GLINE)
                {
-                       GLine* gl = new GLine(ServerInstance, ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName, 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('f', InspIRCd::Format("%s (%s) was G-lined for %s (expires on %s) because their message to %s matched %s (%s)",
+                               user->nick.c_str(), gl->Displayable().c_str(), InspIRCd::DurationString(f->duration).c_str(),
+                               InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
+                               msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
                        if (ServerInstance->XLines->AddLine(gl,NULL))
                        {
                                ServerInstance->XLines->ApplyLines();
@@ -301,30 +481,40 @@ int FilterBase::OnUserPreNotice(User* user,void* dest,int target_type, std::stri
                        else
                                delete gl;
                }
+               else if (f->action == FA_ZLINE)
+               {
+                       ZLine* zl = new ZLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
+                       ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was Z-lined for %s (expires on %s) because their message to %s matched %s (%s)",
+                               user->nick.c_str(), zl->Displayable().c_str(), InspIRCd::DurationString(f->duration).c_str(),
+                               InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
+                               msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
+                       if (ServerInstance->XLines->AddLine(zl,NULL))
+                       {
+                               ServerInstance->XLines->ApplyLines();
+                       }
+                       else
+                               delete zl;
+               }
 
-               ServerInstance->Logs->Log("FILTER",DEFAULT,"FILTER: "+ user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + f->action);
-               return 1;
+               ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, user->nick + " had their message filtered, target was " + msgtarget.GetName() + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action));
+               return MOD_RES_DENY;
        }
-       return 0;
+       return MOD_RES_PASSTHRU;
 }
 
-int FilterBase::OnPreCommand(std::string &command, std::vector<std::string> &parameters, User *user, bool validated, const std::string &original_line)
+ModResult ModuleFilter::OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated)
 {
-       flags = 0;
-       if (validated && IS_LOCAL(user))
+       if (validated)
        {
-               std::string checkline;
-               int replacepoint = 0;
-               bool parting = false;
-       
+               flags = 0;
+               bool parting;
+
                if (command == "QUIT")
                {
                        /* QUIT with no reason: nothing to do */
                        if (parameters.size() < 1)
-                               return 0;
+                               return MOD_RES_PASSTHRU;
 
-                       checkline = parameters[0];
-                       replacepoint = 0;
                        parting = false;
                        flags = FLAG_QUIT;
                }
@@ -332,136 +522,158 @@ int FilterBase::OnPreCommand(std::string &command, std::vector<std::string> &par
                {
                        /* PART with no reason: nothing to do */
                        if (parameters.size() < 2)
-                               return 0;
+                               return MOD_RES_PASSTHRU;
+
+                       if (exemptedchans.count(parameters[0]))
+                               return MOD_RES_PASSTHRU;
 
-                       std::vector<std::string>::iterator i = find(exemptfromfilter.begin(), exemptfromfilter.end(), parameters[0]);
-                       if (i != exemptfromfilter.end()) return 0;
-                       checkline = parameters[1];
-                       replacepoint = 1;
                        parting = true;
                        flags = FLAG_PART;
                }
                else
                        /* We're only messing with PART and QUIT */
-                       return 0;
-
-               FilterResult* f = NULL;
-               
-               if (flags)
-                       f = this->FilterMatch(user, checkline, flags);
+                       return MOD_RES_PASSTHRU;
 
+               FilterResult* f = this->FilterMatch(user, parameters[parting ? 1 : 0], flags);
                if (!f)
                        /* PART or QUIT reason doesnt match a filter */
-                       return 0;
+                       return MOD_RES_PASSTHRU;
 
                /* We cant block a part or quit, so instead we change the reason to 'Reason filtered' */
-               Command* c = ServerInstance->Parser->GetHandler(command);
-               if (c)
+               parameters[parting ? 1 : 0] = "Reason filtered";
+
+               /* 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))
+               {
+                       return MOD_RES_PASSTHRU;
+               }
+               else
                {
-                       std::vector<std::string> params;
-                       for (int item = 0; item < (int)parameters.size(); item++)
-                               params.push_back(parameters[item]);
-                       params[replacepoint] = "Reason filtered";
-
-                       /* We're blocking, OR theyre quitting and its a KILL action
-                        * (we cant kill someone whos already quitting, so filter them anyway)
-                        */
-                       if ((f->action == "block") || (((!parting) && (f->action == "kill"))) || (f->action == "silent"))
+                       /* Are they parting, if so, kill is applicable */
+                       if ((parting) && (f->action == FA_KILL))
                        {
-                               c->Handle(params, user);
-                               return 1;
+                               user->WriteNotice("*** Your PART message was filtered: " + f->reason);
+                               ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
                        }
-                       else
+                       if (f->action == FA_GLINE)
                        {
-                               /* Are they parting, if so, kill is applicable */
-                               if ((parting) && (f->action == "kill"))
+                               /* Note: We G-line *@IP so that if their host doesn't resolve the G-line still applies. */
+                               GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
+                               ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was G-lined for %s (expires on %s) because their %s message matched %s (%s)",
+                                       user->nick.c_str(), gl->Displayable().c_str(),
+                                       InspIRCd::DurationString(f->duration).c_str(),
+                                       InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
+                                       command.c_str(), f->freeform.c_str(), f->reason.c_str()));
+
+                               if (ServerInstance->XLines->AddLine(gl,NULL))
                                {
-                                       user->WriteServ("NOTICE %s :*** Your PART message was filtered: %s", user->nick.c_str(), f->reason.c_str());
-                                       ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
+                                       ServerInstance->XLines->ApplyLines();
                                }
-                               if (f->action == "gline")
+                               else
+                                       delete gl;
+                       }
+                       if (f->action == FA_ZLINE)
+                       {
+                               ZLine* zl = new ZLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
+                               ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was Z-lined for %s (expires on %s) because their %s message matched %s (%s)",
+                                       user->nick.c_str(), zl->Displayable().c_str(),
+                                       InspIRCd::DurationString(f->duration).c_str(),
+                                       InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
+                                       command.c_str(), f->freeform.c_str(), f->reason.c_str()));
+
+                               if (ServerInstance->XLines->AddLine(zl,NULL))
                                {
-                                       /* Note: We gline *@IP so that if their host doesnt resolve the gline still applies. */
-                                       GLine* gl = new GLine(ServerInstance, ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName, f->reason.c_str(), "*", user->GetIPString());
-                                       if (ServerInstance->XLines->AddLine(gl,NULL))
-                                       {
-                                               ServerInstance->XLines->ApplyLines();
-                                       }
-                                       else
-                                               delete gl;
+                                       ServerInstance->XLines->ApplyLines();
+                               }
+                               else
+                                       delete zl;
+                       }
+                       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('f', InspIRCd::Format("%s (%s) was shunned for %s (expires on %s) because their %s message matched %s (%s)",
+                                       user->nick.c_str(), sh->Displayable().c_str(),
+                                       InspIRCd::DurationString(f->duration).c_str(),
+                                       InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
+                                       command.c_str(), f->freeform.c_str(), f->reason.c_str()));
+
+                               if (ServerInstance->XLines->AddLine(sh, NULL))
+                               {
+                                       ServerInstance->XLines->ApplyLines();
                                }
-                               return 1;
+                               else
+                                       delete sh;
                        }
+                       return MOD_RES_DENY;
                }
-               return 0;
        }
-       return 0;
+       return MOD_RES_PASSTHRU;
 }
 
-void FilterBase::OnRehash(User* user, const std::string &parameter)
+void ModuleFilter::ReadConfig(ConfigStatus& status)
 {
-       ConfigReader* MyConf = new ConfigReader(ServerInstance);
-       std::vector<std::string>().swap(exemptfromfilter);
-       for (int index = 0; index < MyConf->Enumerate("exemptfromfilter"); ++index)
-       {
-               std::string chan = MyConf->ReadValue("exemptfromfilter", "channel", index);
-               if (!chan.empty()) {
-                       exemptfromfilter.push_back(chan);
-               }
-       }
-       std::string newrxengine = MyConf->ReadValue("filteropts", "engine", 0);
-       if (!RegexEngine.empty())
-       {
-               if (RegexEngine == newrxengine)
-                       return;
-
-               ServerInstance->SNO->WriteToSnoMask('A', "Dumping all filters due to regex engine change (was '%s', now '%s')", RegexEngine.c_str(), newrxengine.c_str());
-               //ServerInstance->XLines->DelAll("R");
-       }
-       rxengine = NULL;
+       ConfigTagList tags = ServerInstance->Config->ConfTags("exemptfromfilter");
+       exemptedchans.clear();
+       exemptednicks.clear();
 
-       RegexEngine = newrxengine;
-       modulelist* ml = ServerInstance->Modules->FindInterface("RegularExpression");
-       if (ml)
+       for (ConfigIter i = tags.first; i != tags.second; ++i)
        {
-               for (modulelist::iterator i = ml->begin(); i != ml->end(); ++i)
+               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"), 1);
+               if (!target.empty())
                {
-                       if (RegexNameRequest(this, *i).Send() == newrxengine)
-                       {
-                               ServerInstance->SNO->WriteToSnoMask('A', "Filter now using engine '%s'", RegexEngine.c_str());
-                               rxengine = *i;
-                       }
+                       if (target[0] == '#')
+                               exemptedchans.insert(target);
+                       else
+                               exemptednicks.insert(target);
                }
        }
-       if (!rxengine)
+
+       ConfigTag* tag = ServerInstance->Config->ConfValue("filteropts");
+       std::string newrxengine = tag->getString("engine");
+       notifyuser = tag->getBool("notifyuser", true);
+       warnonselfmsg = tag->getBool("warnonselfmsg");
+
+       factory = RegexEngine ? (RegexEngine.operator->()) : NULL;
+
+       if (newrxengine.empty())
+               RegexEngine.SetProvider("regex");
+       else
+               RegexEngine.SetProvider("regex/" + newrxengine);
+
+       if (!RegexEngine)
        {
-               ServerInstance->SNO->WriteToSnoMask('A', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", RegexEngine.c_str());
-       }
+               if (newrxengine.empty())
+                       ServerInstance->SNO->WriteGlobalSno('f', "WARNING: No regex engine loaded - Filter functionality disabled until this is corrected.");
+               else
+                       ServerInstance->SNO->WriteGlobalSno('f', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", newrxengine.c_str());
 
-       delete MyConf;
-}
+               initing = false;
+               FreeFilters();
+               return;
+       }
 
-void FilterBase::OnLoadModule(Module* mod, const std::string& name)
-{
-       if (ServerInstance->Modules->ModuleHasInterface(mod, "RegularExpression"))
+       if ((!initing) && (RegexEngine.operator->() != factory))
        {
-               std::string rxname = RegexNameRequest(this, mod).Send();
-               if (rxname == RegexEngine)
-               {
-                       ServerInstance->SNO->WriteToSnoMask('A', "Filter now using engine '%s'", RegexEngine.c_str());
-                       rxengine = mod;
-               }
+               ServerInstance->SNO->WriteGlobalSno('f', "Dumping all filters due to regex engine change");
+               FreeFilters();
        }
-}
 
+       initing = false;
+       ReadFilters();
+}
 
-Version FilterBase::GetVersion()
+Version ModuleFilter::GetVersion()
 {
-       return Version("$Id$", VF_VENDOR | VF_COMMON, API_VERSION);
+       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 FilterBase::EncodeFilter(FilterResult* filter)
+std::string ModuleFilter::EncodeFilter(FilterResult* filter)
 {
        std::ostringstream stream;
        std::string x = filter->freeform;
@@ -471,22 +683,31 @@ std::string FilterBase::EncodeFilter(FilterResult* filter)
                if (*n == ' ')
                        *n = '\7';
 
-       stream << x << " " << filter->action << " " << (filter->flags.empty() ? "-" : filter->flags) << " " << filter->gline_time << " :" << filter->reason;
+       stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->duration << " :" << filter->reason;
        return stream.str();
 }
 
-FilterResult FilterBase::DecodeFilter(const std::string &data)
+FilterResult ModuleFilter::DecodeFilter(const std::string &data)
 {
+       std::string filteraction;
        FilterResult res;
        irc::tokenstream tokens(data);
-       tokens.GetToken(res.freeform);
-       tokens.GetToken(res.action);
-       tokens.GetToken(res.flags);
-       if (res.flags == "-")
-               res.flags = "";
-       res.FillFlags(res.flags);
-       tokens.GetToken(res.gline_time);
-       tokens.GetToken(res.reason);
+       tokens.GetMiddle(res.freeform);
+       tokens.GetMiddle(filteraction);
+       if (!StringToFilterAction(filteraction, res.action))
+               throw ModuleException("Invalid action: " + filteraction);
+
+       std::string filterflags;
+       tokens.GetMiddle(filterflags);
+       char c = res.FillFlags(filterflags);
+       if (c != 0)
+               throw ModuleException("Invalid flag: '" + std::string(1, c) + "'");
+
+       std::string duration;
+       tokens.GetMiddle(duration);
+       res.duration = ConvToNum<unsigned long>(duration);
+
+       tokens.GetTrailing(res.reason);
 
        /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
        for (std::string::iterator n = res.freeform.begin(); n != res.freeform.end(); n++)
@@ -496,180 +717,214 @@ FilterResult FilterBase::DecodeFilter(const std::string &data)
        return res;
 }
 
-void FilterBase::OnSyncOtherMetaData(Module* proto, void* opaque, bool displayable)
+void ModuleFilter::OnSyncNetwork(ProtocolInterface::Server& server)
 {
-       this->SyncFilters(proto, opaque);
-}
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
+       {
+               FilterResult& filter = *i;
+               if (filter.from_config)
+                       continue;
 
-void FilterBase::SendFilter(Module* proto, void* opaque, FilterResult* iter)
-{
-       proto->ProtoSendMetaData(opaque, TYPE_OTHER, NULL, "filter", EncodeFilter(iter));
+               server.SendMetaData("filter", EncodeFilter(&filter));
+       }
 }
 
-void FilterBase::OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)
+void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata)
 {
-       if ((target_type == TYPE_OTHER) && (extname == "filter"))
+       if ((target == NULL) && (extname == "filter"))
        {
-               FilterResult data = DecodeFilter(extdata);
-               this->AddFilter(data.freeform, data.action, data.reason, data.gline_time, data.flags);
+               try
+               {
+                       FilterResult data = DecodeFilter(extdata);
+                       this->AddFilter(data.freeform, data.action, data.reason, data.duration, data.GetFlags());
+               }
+               catch (ModuleException& e)
+               {
+                       ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error when unserializing filter: " + e.GetReason());
+               }
        }
 }
 
-class ImplFilter : public FilterResult
+FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int flgs)
 {
- public:
-       Regex* regex;
+       static std::string stripped_text;
+       stripped_text.clear();
 
-       ImplFilter(Module* mymodule, const std::string &rea, const std::string &act, long glinetime, const std::string &pat, const std::string &flgs)
-               : FilterResult(pat, rea, act, glinetime, flgs)
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
        {
-               if (!rxengine)
-                       throw ModuleException("Regex module implementing '"+RegexEngine+"' is not loaded!");
+               FilterResult* filter = &*i;
+
+               /* Skip ones that dont apply to us */
+               if (!AppliesToMe(user, filter, flgs))
+                       continue;
 
-               regex = RegexFactoryRequest(mymodule, rxengine, pat).Create();
+               if ((filter->flag_strip_color) && (stripped_text.empty()))
+               {
+                       stripped_text = text;
+                       InspIRCd::StripColor(stripped_text);
+               }
+
+               if (filter->regex->Matches(filter->flag_strip_color ? stripped_text : text))
+                       return filter;
        }
+       return NULL;
+}
 
-       ImplFilter()
+bool ModuleFilter::DeleteFilter(const std::string& freeform, std::string& reason)
+{
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
        {
-               delete regex;
+               if (i->freeform == freeform)
+               {
+                       reason.assign(i->reason);
+                       delete i->regex;
+                       filters.erase(i);
+                       return true;
+               }
        }
-};
+       return false;
+}
 
-class ModuleFilter : public FilterBase
+std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string& freeform, FilterAction type, const std::string& reason, unsigned long duration, const std::string& flgs, bool config)
 {
-       std::vector<ImplFilter> filters;
-       const char *error;
-       int erroffset;
-       ImplFilter fr;
-
- public:
-       ModuleFilter(InspIRCd* Me)
-       : FilterBase(Me, "m_filter.so")
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
        {
-               OnRehash(NULL,"");
-
+               if (i->freeform == freeform)
+               {
+                       return std::make_pair(false, "Filter already exists");
+               }
        }
 
-       virtual ~ModuleFilter()
+       try
        {
+               filters.push_back(FilterResult(RegexEngine, freeform, reason, type, duration, flgs, config));
        }
-
-       virtual FilterResult* FilterMatch(User* user, const std::string &text, int flgs)
+       catch (ModuleException &e)
        {
-               for (std::vector<ImplFilter>::iterator index = filters.begin(); index != filters.end(); index++)
-               {
-                       /* Skip ones that dont apply to us */
+               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, "");
+}
 
-                       if (!FilterBase::AppliesToMe(user, dynamic_cast<FilterResult*>(&(*index)), flgs))
-                               continue;
+bool ModuleFilter::StringToFilterAction(const std::string& str, FilterAction& fa)
+{
+       if (stdalgo::string::equalsci(str, "gline"))
+               fa = FA_GLINE;
+       else if (stdalgo::string::equalsci(str, "zline"))
+               fa = FA_ZLINE;
+       else if (stdalgo::string::equalsci(str, "warn"))
+               fa = FA_WARN;
+       else if (stdalgo::string::equalsci(str, "block"))
+               fa = FA_BLOCK;
+       else if (stdalgo::string::equalsci(str, "silent"))
+               fa = FA_SILENT;
+       else if (stdalgo::string::equalsci(str, "kill"))
+               fa = FA_KILL;
+       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;
 
-                       if (index->regex->Matches(text))
-                       {
-                               fr = *index;
-                               if (index != filters.begin())
-                               {
-                                       /* Move to head of list for efficiency */
-                                       filters.erase(index);
-                                       filters.insert(filters.begin(), fr);
-                               }
-                               return &fr;
-                       }
-               }
-               return NULL;
+       return true;
+}
+
+std::string ModuleFilter::FilterActionToString(FilterAction fa)
+{
+       switch (fa)
+       {
+               case FA_GLINE:  return "gline";
+               case FA_ZLINE:  return "zline";
+               case FA_WARN:   return "warn";
+               case FA_BLOCK:  return "block";
+               case FA_SILENT: return "silent";
+               case FA_KILL:   return "kill";
+               case FA_SHUN:   return "shun";
+               default:                return "none";
        }
+}
+
+void ModuleFilter::ReadFilters()
+{
+       insp::flat_set<std::string> removedfilters;
 
-       virtual bool DeleteFilter(const std::string &freeform)
+       for (std::vector<FilterResult>::iterator filter = filters.begin(); filter != filters.end(); )
        {
-               for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
+               if (filter->from_config)
                {
-                       if (i->freeform == freeform)
-                       {
-                               filters.erase(i);
-                               return true;
-                       }
+                       removedfilters.insert(filter->freeform);
+                       delete filter->regex;
+                       filter = filters.erase(filter);
+                       continue;
                }
-               return false;
+
+               // The filter is not from the config.
+               filter++;
        }
 
-       virtual void SyncFilters(Module* proto, void* opaque)
+       ConfigTagList tags = ServerInstance->Config->ConfTags("keyword");
+       for (ConfigIter i = tags.first; i != tags.second; ++i)
        {
-               for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
-               {
-                       this->SendFilter(proto, opaque, &(*i));
-               }
+               std::string pattern = i->second->getString("pattern");
+               std::string reason = i->second->getString("reason");
+               std::string action = i->second->getString("action");
+               std::string flgs = i->second->getString("flags");
+               unsigned long duration = i->second->getDuration("duration", 10*60, 1);
+               if (flgs.empty())
+                       flgs = "*";
+
+               FilterAction fa;
+               if (!StringToFilterAction(action, fa))
+                       fa = FA_NONE;
+
+               std::pair<bool, std::string> result = static_cast<ModuleFilter*>(this)->AddFilter(pattern, fa, reason, duration, flgs, true);
+               if (result.first)
+                       removedfilters.erase(pattern);
+               else
+                       ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Filter '%s' could not be added: %s", pattern.c_str(), result.second.c_str());
        }
 
-       virtual std::pair<bool, std::string> AddFilter(const std::string &freeform, const std::string &type, const std::string &reason, long duration, const std::string &flgs)
+       if (!removedfilters.empty())
        {
-               for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
+               for (insp::flat_set<std::string>::const_iterator it = removedfilters.begin(); it != removedfilters.end(); ++it)
+                       ServerInstance->SNO->WriteGlobalSno('f', "Removing filter '" + *(it) + "' due to config rehash.");
+       }
+}
+
+ModResult ModuleFilter::OnStats(Stats::Context& stats)
+{
+       if (stats.GetSymbol() == 's')
+       {
+               for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
                {
-                       if (i->freeform == freeform)
-                       {
-                               return std::make_pair(false, "Filter already exists");
-                       }
+                       stats.AddRow(223, RegexEngine.GetProvider(), i->freeform, i->GetFlags(), FilterActionToString(i->action), i->duration, i->reason);
                }
-
-               try
+               for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i)
                {
-                       filters.push_back(ImplFilter(this, reason, type, duration, freeform, flgs));
+                       stats.AddRow(223, "EXEMPT "+(*i));
                }
-               catch (ModuleException &e)
+               for (ExemptTargetSet::const_iterator i = exemptednicks.begin(); i != exemptednicks.end(); ++i)
                {
-                       ServerInstance->Logs->Log("m_filter", DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason());
-                       return std::make_pair(false, e.GetReason());
+                       stats.AddRow(223, "EXEMPT "+(*i));
                }
-               return std::make_pair(true, "");
        }
+       return MOD_RES_PASSTHRU;
+}
 
-       virtual void OnRehash(User* user, const std::string &parameter)
+void ModuleFilter::OnUnloadModule(Module* mod)
+{
+       // If the regex engine became unavailable or has changed, remove all filters
+       if (!RegexEngine)
        {
-               ConfigReader MyConf(ServerInstance);
-
-               FilterBase::OnRehash(user, parameter);
-
-               for (int index = 0; index < MyConf.Enumerate("keyword"); index++)
-               {
-                       this->DeleteFilter(MyConf.ReadValue("keyword", "pattern", index));
-
-                       std::string pattern = MyConf.ReadValue("keyword", "pattern", index);
-                       std::string reason = MyConf.ReadValue("keyword", "reason", index);
-                       std::string action = MyConf.ReadValue("keyword", "action", index);
-                       std::string flgs = MyConf.ReadValue("keyword", "flags", index);
-                       long gline_time = ServerInstance->Duration(MyConf.ReadValue("keyword", "duration", index));
-                       if (action.empty())
-                               action = "none";
-                       if (flgs.empty())
-                               flgs = "*";
-
-                       try
-                       {
-                               filters.push_back(ImplFilter(this, reason, action, gline_time, pattern, flgs));
-                               ServerInstance->Logs->Log("m_filter",DEFAULT,"Regular expression %s loaded.", pattern.c_str());
-                       }
-                       catch (ModuleException &e)
-                       {
-                               ServerInstance->Logs->Log("m_filter",DEFAULT,"Error in regular expression '%s': %s", pattern.c_str(), e.GetReason());
-                       }
-               }
+               FreeFilters();
        }
-
-       virtual int OnStats(char symbol, User* user, string_list &results)
+       else if (RegexEngine.operator->() != factory)
        {
-               if (symbol == 's')
-               {
-                       std::string sn = ServerInstance->Config->ServerName;
-                       for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
-                       {
-                               results.push_back(sn+" 223 "+user->nick+" :REGEXP:"+i->freeform+" "+i->flags+" "+i->action+" "+ConvToStr(i->gline_time)+" :"+i->reason);
-                       }
-                       for (std::vector<std::string>::iterator i = exemptfromfilter.begin(); i != exemptfromfilter.end(); ++i)
-                       {
-                               results.push_back(sn+" 223 "+user->nick+" :EXEMPT "+(*i));
-                       }
-               }
-               return 0;
+               factory = RegexEngine.operator->();
+               FreeFilters();
        }
-};
+}
 
 MODULE_INIT(ModuleFilter)
-