]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_filter.cpp
Add a config option for exempting classes from connflood.
[user/henk/code/inspircd.git] / src / modules / m_filter.cpp
index d2cffdb5dd8e712ae56a68846d0e424210b62704..786ea673bb27cce92881c678b42e01ba7cedda5b 100644 (file)
@@ -1,10 +1,21 @@
 /*
  * InspIRCd -- Internet Relay Chat Daemon
  *
- *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
- *   Copyright (C) 2004, 2008 Craig Edwards <craigedwards@brainbox.cc>
+ *   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>
- *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
  *
  * 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
 
 #include "inspircd.h"
 #include "xline.h"
-#include "m_regex.h"
-
-/* $ModDesc: Text (spam) filtering */
-
-class ModuleFilter;
+#include "modules/regex.h"
+#include "modules/server.h"
+#include "modules/shun.h"
+#include "modules/stats.h"
+#include "modules/account.h"
 
 enum FilterFlags
 {
@@ -36,34 +47,55 @@ enum FilterFlags
        FLAG_NOTICE = 16
 };
 
+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)
                        {
@@ -82,23 +114,55 @@ class FilterResult
                                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;
        }
 
-       ~FilterResult()
+       FilterResult()
        {
        }
 };
@@ -110,80 +174,82 @@ class CommandFilter : public Command
                : Command(f, "FILTER", 1, 5)
        {
                flags_needed = 'o';
-               this->syntax = "<filter-definition> <action> <flags> [<gline-duration>] :<reason>";
+               this->syntax = "<pattern> [<action> <flags> [<duration>] :<reason>]";
        }
-       CmdResult Handle(const std::vector<std::string>&, User*);
+       CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
 
-       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());
-       }
-
-       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 ServerProtocol::SyncEventListener
+       , public Stats::EventListener
 {
- public:
-       Regex* regex;
-
-       ImplFilter(ModuleFilter* mymodule, const std::string &rea, const std::string &act, long glinetime, const std::string &pat, const std::string &flgs);
-};
+       typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet;
 
+       bool initing;
+       bool notifyuser;
+       bool warnonselfmsg;
+       RegexFactory* factory;
+       void FreeFilters();
 
-class ModuleFilter : public Module
-{
  public:
        CommandFilter filtcommand;
        dynamic_reference<RegexFactory> RegexEngine;
 
-       std::vector<ImplFilter> filters;
-       const char *error;
-       int erroffset;
+       std::vector<FilterResult> filters;
        int flags;
 
-       std::vector<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();
-       ~ModuleFilter();
-       ModResult OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
+       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);
-       void SyncFilters(Module* proto, void* opaque);
-       void SendFilter(Module* proto, void* opaque, FilterResult* iter);
-       std::pair<bool, std::string> AddFilter(const std::string &freeform, const std::string &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();
+       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);
-       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 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 ReadFilters(ConfigReader &MyConf);
+       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)
        {
                /* Deleting a filter */
-               Module *me = creator;
-               if (static_cast<ModuleFilter *>(me)->DeleteFilter(parameters[0]))
+               Module* me = creator;
+               std::string reason;
+
+               if (static_cast<ModuleFilter*>(me)->DeleteFilter(parameters[0], reason))
                {
-                       user->WriteServ("NOTICE %s :*** Removed filter '%s'", user->nick.c_str(), parameters[0].c_str());
-                       ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', std::string("FILTER: ")+user->nick+" removed filter '"+parameters[0]+"'");
+                       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
                {
-                       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 on the list.");
                        return CMD_FAILURE;
                }
        }
@@ -192,58 +258,68 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
                /* 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;
+                       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"))
+                       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(), type.c_str());
+                               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)
                                {
-                                       duration = ServerInstance->Duration(parameters[3]);
-                                       reason = parameters[4];
+                                       if (!InspIRCd::Duration(parameters[3], duration))
+                                       {
+                                               user->WriteNotice("*** Invalid duration for filter");
+                                               return CMD_FAILURE;
+                                       }
+                                       reasonindex = 4;
                                }
                                else
                                {
-                                       this->TooFewParams(user, ": When setting a gline type filter, a gline duration must be specified as the third parameter.");
+                                       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
                        {
-                               reason = parameters[3];
+                               reasonindex = 3;
                        }
-                       
-                       Module *me = creator;
-                       std::pair<bool, std::string> result = static_cast<ModuleFilter *>(me)->AddFilter(freeform, type, reason, duration, flags);
+
+                       Moduleme = creator;
+                       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(),
-                                               type.c_str(), (duration ? ", duration " : ""), (duration ? parameters[3].c_str() : ""),
-                                               flags.c_str(), reason.c_str());
+                               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());
 
-                               ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', std::string("FILTER: ")+user->nick+" added filter '"+freeform+"', type '"+type+"', "+(duration ? "duration "+parameters[3]+", " : "")+"flags '"+flags+"', reason: "+reason);
+                               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->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
                {
-                       this->TooFewParams(user, ".");
+                       user->WriteNotice("*** Not enough parameters.");
                        return CMD_FAILURE;
                }
 
@@ -252,7 +328,11 @@ CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User
 
 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;
@@ -265,78 +345,135 @@ bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags)
        return true;
 }
 
-ModuleFilter::ModuleFilter() : filtcommand(this), RegexEngine(this, "regex")
+ModuleFilter::ModuleFilter()
+       : ServerProtocol::SyncEventListener(this)
+       , Stats::EventListener(this)
+       , initing(true)
+       , filtcommand(this)
+       , RegexEngine(this, "regex")
 {
 }
 
 void ModuleFilter::init()
 {
-       ServerInstance->AddCommand(&filtcommand);
-       Implementation eventlist[] = { I_OnPreCommand, I_OnStats, I_OnSyncNetwork, I_OnDecodeMetaData, I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash };
-       ServerInstance->Modules->Attach(eventlist, this, 7);
-       OnRehash(NULL);
+       ServerInstance->SNO->EnableSnomask('f', "FILTER");
 }
 
-ModuleFilter::~ModuleFilter()
+CullResult ModuleFilter::cull()
 {
+       FreeFilters();
+       return Module::cull();
 }
 
-ModResult ModuleFilter::OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
+void ModuleFilter::FreeFilters()
 {
-       if (!IS_LOCAL(user))
-               return MOD_RES_PASSTHRU;
+       for (std::vector<FilterResult>::const_iterator i = filters.begin(); i != filters.end(); ++i)
+               delete i->regex;
 
-       flags = FLAG_PRIVMSG;
-       return OnUserPreNotice(user,dest,target_type,text,status,exempt_list);
+       filters.clear();
 }
 
-ModResult ModuleFilter::OnUserPreNotice(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 ulines alone */
-       if ((ServerInstance->ULine(user->server)) || (!IS_LOCAL(user)))
+       // Leave remote users and servers alone
+       if (!IS_LOCAL(user))
                return MOD_RES_PASSTHRU;
 
-       if (!flags)
-               flags = FLAG_NOTICE;
+       flags = (details.type == MSG_PRIVMSG) ? FLAG_PRIVMSG : 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)
+               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 MOD_RES_PASSTHRU;
+                       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")
+               else if (f->action == FA_WARN)
                {
-                       ServerInstance->SNO->WriteGlobalSno('a', std::string("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());
+                       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;
+               }
+               else if (f->action == FA_BLOCK)
+               {
+                       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
-                               user->WriteServ("NOTICE "+std::string(user->nick)+" :Your message to "+target+" was blocked and opers notified: "+f->reason);
+                               details.echo_original = true;
                }
-               if (f->action == "silent")
+               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(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
-                               user->WriteServ("NOTICE "+std::string(user->nick)+" :Your message to "+target+" was blocked: "+f->reason);
+                               details.echo_original = true;
                }
-               if (f->action == "kill")
+               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")))
                {
-                       GLine* gl = new GLine(ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
+                       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->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();
@@ -344,21 +481,33 @@ ModResult ModuleFilter::OnUserPreNotice(User* user,void* dest,int target_type, s
                        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);
+               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 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)
 {
-       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")
                {
@@ -366,8 +515,6 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri
                        if (parameters.size() < 1)
                                return MOD_RES_PASSTHRU;
 
-                       checkline = parameters[0];
-                       replacepoint = 0;
                        parting = false;
                        flags = FLAG_QUIT;
                }
@@ -377,10 +524,9 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri
                        if (parameters.size() < 2)
                                return MOD_RES_PASSTHRU;
 
-                       std::vector<std::string>::iterator i = find(exemptfromfilter.begin(), exemptfromfilter.end(), parameters[0]);
-                       if (i != exemptfromfilter.end()) return MOD_RES_PASSTHRU;
-                       checkline = parameters[1];
-                       replacepoint = 1;
+                       if (exemptedchans.count(parameters[0]))
+                               return MOD_RES_PASSTHRU;
+
                        parting = true;
                        flags = FLAG_PART;
                }
@@ -388,93 +534,145 @@ ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::stri
                        /* We're only messing with PART and QUIT */
                        return MOD_RES_PASSTHRU;
 
-               FilterResult* f = NULL;
-
-               if (flags)
-                       f = this->FilterMatch(user, checkline, flags);
-
+               FilterResult* f = this->FilterMatch(user, parameters[parting ? 1 : 0], flags);
                if (!f)
                        /* PART or QUIT reason doesnt match a filter */
                        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 MOD_RES_DENY;
+                               user->WriteNotice("*** Your PART message was filtered: " + f->reason);
+                               ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
                        }
-                       else
+                       if (f->action == FA_GLINE)
+                       {
+                               /* 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))
+                               {
+                                       ServerInstance->XLines->ApplyLines();
+                               }
+                               else
+                                       delete gl;
+                       }
+                       if (f->action == FA_ZLINE)
                        {
-                               /* Are they parting, if so, kill is applicable */
-                               if ((parting) && (f->action == "kill"))
+                               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))
                                {
-                                       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 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))
                                {
-                                       /* 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());
-                                       if (ServerInstance->XLines->AddLine(gl,NULL))
-                                       {
-                                               ServerInstance->XLines->ApplyLines();
-                                       }
-                                       else
-                                               delete gl;
+                                       ServerInstance->XLines->ApplyLines();
                                }
-                               return MOD_RES_DENY;
+                               else
+                                       delete sh;
                        }
+                       return MOD_RES_DENY;
                }
-               return MOD_RES_PASSTHRU;
        }
        return MOD_RES_PASSTHRU;
 }
 
-void ModuleFilter::OnRehash(User* user)
+void ModuleFilter::ReadConfig(ConfigStatus& status)
 {
-       ConfigReader MyConf;
-       std::vector<std::string>().swap(exemptfromfilter);
-       for (int index = 0; index < MyConf.Enumerate("exemptfromfilter"); ++index)
+       ConfigTagList tags = ServerInstance->Config->ConfTags("exemptfromfilter");
+       exemptedchans.clear();
+       exemptednicks.clear();
+
+       for (ConfigIter i = tags.first; i != tags.second; ++i)
        {
-               std::string chan = MyConf.ReadValue("exemptfromfilter", "channel", index);
-               if (!chan.empty()) {
-                       exemptfromfilter.push_back(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"), 1);
+               if (!target.empty())
+               {
+                       if (target[0] == '#')
+                               exemptedchans.insert(target);
+                       else
+                               exemptednicks.insert(target);
                }
        }
-       std::string newrxengine = "regex/" + MyConf.ReadValue("filteropts", "engine", 0);
-       if (newrxengine == "regex/")
-               newrxengine = "regex";
-       if (RegexEngine.GetProvider() == newrxengine)
-               return;
 
-       //ServerInstance->SNO->WriteGlobalSno('a', "Dumping all filters due to regex engine change (was '%s', now '%s')", RegexEngine.GetProvider().c_str(), newrxengine.c_str());
-       //ServerInstance->XLines->DelAll("R");
+       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);
 
-       RegexEngine.SetProvider(newrxengine);
        if (!RegexEngine)
        {
-               ServerInstance->SNO->WriteGlobalSno('a', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", newrxengine.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());
+
+               initing = false;
+               FreeFilters();
+               return;
        }
-       ReadFilters(MyConf);
+
+       if ((!initing) && (RegexEngine.operator->() != factory))
+       {
+               ServerInstance->SNO->WriteGlobalSno('f', "Dumping all filters due to regex engine change");
+               FreeFilters();
+       }
+
+       initing = false;
+       ReadFilters();
 }
 
 Version ModuleFilter::GetVersion()
 {
-       return Version("Text (spam) filtering", VF_VENDOR | VF_COMMON, RegexEngine ? RegexEngine->name : "");
+       return Version("Adds the /FILTER command which allows server operators to define regex matches for inappropriate phrases that are not allowed to be used in channel messages, private messages, part messages, or quit messages.", VF_VENDOR | VF_COMMON, RegexEngine ? RegexEngine->name : "");
 }
 
-
 std::string ModuleFilter::EncodeFilter(FilterResult* filter)
 {
        std::ostringstream stream;
@@ -485,22 +683,31 @@ std::string ModuleFilter::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 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++)
@@ -510,65 +717,66 @@ FilterResult ModuleFilter::DecodeFilter(const std::string &data)
        return res;
 }
 
-void ModuleFilter::OnSyncNetwork(Module* proto, void* opaque)
+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 ModuleFilter::SendFilter(Module* proto, void* opaque, FilterResult* iter)
-{
-       proto->ProtoSendMetaData(opaque, NULL, "filter", EncodeFilter(iter));
+               server.SendMetaData("filter", EncodeFilter(&filter));
+       }
 }
 
 void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata)
 {
        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());
+               }
        }
 }
 
-ImplFilter::ImplFilter(ModuleFilter* 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)
-{
-       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)
 {
-       for (std::vector<ImplFilter>::iterator index = filters.begin(); index != filters.end(); index++)
+       static std::string stripped_text;
+       stripped_text.clear();
+
+       for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
        {
+               FilterResult* filter = &*i;
+
                /* Skip ones that dont apply to us */
-               if (!AppliesToMe(user, dynamic_cast<FilterResult*>(&(*index)), flgs))
+               if (!AppliesToMe(user, filter, flgs))
                        continue;
 
-               //ServerInstance->Logs->Log("m_filter", DEBUG, "Match '%s' against '%s'", text.c_str(), index->freeform.c_str());
-               if (index->regex->Matches(text))
+               if ((filter->flag_strip_color) && (stripped_text.empty()))
                {
-                       //ServerInstance->Logs->Log("m_filter", DEBUG, "MATCH");
-                       ImplFilter fr = *index;
-                       if (index != filters.begin())
-                       {
-                               /* Move to head of list for efficiency */
-                               filters.erase(index);
-                               filters.insert(filters.begin(), fr);
-                       }
-                       return &*filters.begin();
+                       stripped_text = text;
+                       InspIRCd::StripColor(stripped_text);
                }
-               //ServerInstance->Logs->Log("m_filter", 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)
+bool ModuleFilter::DeleteFilter(const std::string& freeform, std::string& reason)
 {
-       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)
                {
+                       reason.assign(i->reason);
                        delete i->regex;
                        filters.erase(i);
                        return true;
@@ -577,17 +785,9 @@ bool ModuleFilter::DeleteFilter(const std::string &freeform)
        return false;
 }
 
-void ModuleFilter::SyncFilters(Module* proto, void* opaque)
+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)
 {
-       for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
-       {
-               this->SendFilter(proto, opaque, &(*i));
-       }
-}
-
-std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform, const std::string &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)
                {
@@ -597,59 +797,134 @@ 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, config));
        }
        catch (ModuleException &e)
        {
-               ServerInstance->Logs->Log("m_filter", 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, "");
 }
 
-void ModuleFilter::ReadFilters(ConfigReader &MyConf)
+bool ModuleFilter::StringToFilterAction(const std::string& str, FilterAction& fa)
 {
-       for (int index = 0; index < MyConf.Enumerate("keyword"); index++)
+       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;
+
+       return true;
+}
+
+std::string ModuleFilter::FilterActionToString(FilterAction fa)
+{
+       switch (fa)
        {
-               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 = "*";
+               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";
+       }
+}
 
-               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)
+void ModuleFilter::ReadFilters()
+{
+       insp::flat_set<std::string> removedfilters;
+
+       for (std::vector<FilterResult>::iterator filter = filters.begin(); filter != filters.end(); )
+       {
+               if (filter->from_config)
                {
-                       ServerInstance->Logs->Log("m_filter", DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason());
+                       removedfilters.insert(filter->freeform);
+                       delete filter->regex;
+                       filter = filters.erase(filter);
+                       continue;
                }
+
+               // The filter is not from the config.
+               filter++;
+       }
+
+       ConfigTagList tags = ServerInstance->Config->ConfTags("keyword");
+       for (ConfigIter i = tags.first; i != tags.second; ++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());
+       }
+
+       if (!removedfilters.empty())
+       {
+               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(char symbol, User* user, string_list &results)
+ModResult ModuleFilter::OnStats(Stats::Context& stats)
 {
-       if (symbol == 's')
+       if (stats.GetSymbol() == 's')
        {
-               std::string sn = ServerInstance->Config->ServerName;
-               for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
+               for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
                {
-                       results.push_back(sn+" 223 "+user->nick+" :"+RegexEngine.GetProvider()+":"+i->freeform+" "+i->flags+" "+i->action+" "+ConvToStr(i->gline_time)+" :"+i->reason);
+                       stats.AddRow(223, RegexEngine.GetProvider(), i->freeform, i->GetFlags(), FilterActionToString(i->action), i->duration, i->reason);
                }
-               for (std::vector<std::string>::iterator i = exemptfromfilter.begin(); i != exemptfromfilter.end(); ++i)
+               for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i)
                {
-                       results.push_back(sn+" 223 "+user->nick+" :EXEMPT "+(*i));
+                       stats.AddRow(223, "EXEMPT "+(*i));
+               }
+               for (ExemptTargetSet::const_iterator i = exemptednicks.begin(); i != exemptednicks.end(); ++i)
+               {
+                       stats.AddRow(223, "EXEMPT "+(*i));
                }
        }
        return MOD_RES_PASSTHRU;
 }
 
+void ModuleFilter::OnUnloadModule(Module* mod)
+{
+       // If the regex engine became unavailable or has changed, remove all filters
+       if (!RegexEngine)
+       {
+               FreeFilters();
+       }
+       else if (RegexEngine.operator->() != factory)
+       {
+               factory = RegexEngine.operator->();
+               FreeFilters();
+       }
+}
+
 MODULE_INIT(ModuleFilter)