2 * InspIRCd -- Internet Relay Chat Daemon
4 * Copyright (C) 2019 Matt Schatz <genius3000@g3k.solutions>
5 * Copyright (C) 2019 Filippo Cortigiani <simos@simosnap.org>
6 * Copyright (C) 2018-2019 linuxdaemon <linuxdaemon.irc@gmail.com>
7 * Copyright (C) 2018 Michael Hazell <michaelhazell@hotmail.com>
8 * Copyright (C) 2017 B00mX0r <b00mx0r@aureus.pw>
9 * Copyright (C) 2012-2014, 2016 Attila Molnar <attilamolnar@hush.com>
10 * Copyright (C) 2012-2013, 2017-2020 Sadie Powell <sadie@witchery.services>
11 * Copyright (C) 2012, 2018-2019 Robby <robby@chatbelgie.be>
12 * Copyright (C) 2011 Adam <Adam@anope.org>
13 * Copyright (C) 2009-2010 Daniel De Graaf <danieldg@inspircd.org>
14 * Copyright (C) 2009 Robin Burchell <robin+git@viroteck.net>
15 * Copyright (C) 2009 Matt Smith <dz@inspircd.org>
16 * Copyright (C) 2009 John Brooks <special@inspircd.org>
17 * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
18 * Copyright (C) 2006-2010 Craig Edwards <brain@inspircd.org>
20 * This file is part of InspIRCd. InspIRCd is free software: you can
21 * redistribute it and/or modify it under the terms of the GNU General Public
22 * License as published by the Free Software Foundation, version 2.
24 * This program is distributed in the hope that it will be useful, but WITHOUT
25 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
26 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
29 * You should have received a copy of the GNU General Public License
30 * along with this program. If not, see <http://www.gnu.org/licenses/>.
36 #include "modules/regex.h"
37 #include "modules/server.h"
38 #include "modules/shun.h"
39 #include "modules/stats.h"
40 #include "modules/account.h"
69 unsigned long duration;
73 bool flag_part_message;
74 bool flag_quit_message;
77 bool flag_strip_color;
78 bool flag_no_registered;
80 FilterResult(dynamic_reference<RegexFactory>& RegexEngine, const std::string& free, const std::string& rea, FilterAction act, unsigned long gt, const std::string& fla, bool cfg)
88 throw ModuleException("Regex module implementing '"+RegexEngine.GetProvider()+"' is not loaded!");
89 regex = RegexEngine->Create(free);
93 char FillFlags(const std::string &fl)
95 flag_no_opers = flag_part_message = flag_quit_message = flag_privmsg =
96 flag_notice = flag_strip_color = flag_no_registered = false;
98 for (std::string::const_iterator n = fl.begin(); n != fl.end(); ++n)
103 flag_no_opers = true;
106 flag_part_message = true;
109 flag_quit_message = true;
118 flag_strip_color = true;
121 flag_no_registered = true;
124 flag_no_opers = flag_part_message = flag_quit_message =
125 flag_privmsg = flag_notice = flag_strip_color = true;
135 std::string GetFlags()
139 flags.push_back('o');
140 if (flag_part_message)
141 flags.push_back('P');
142 if (flag_quit_message)
143 flags.push_back('q');
145 flags.push_back('p');
147 flags.push_back('n');
149 /* Order is important here, as the logic in FillFlags() stops parsing when it encounters
150 * an unknown character. So the following characters must be last in the string.
151 * 'c' is unsupported on < 2.0.10
152 * 'r' is unsupported on < 3.2.0
154 if (flag_strip_color)
155 flags.push_back('c');
156 if (flag_no_registered)
157 flags.push_back('r');
160 flags.push_back('-');
170 class CommandFilter : public Command
173 CommandFilter(Module* f)
174 : Command(f, "FILTER", 1, 5)
177 this->syntax = "<pattern> [<action> <flags> [<duration>] :<reason>]";
179 CmdResult Handle(User* user, const Params& parameters) CXX11_OVERRIDE;
181 RouteDescriptor GetRouting(User* user, const Params& parameters) CXX11_OVERRIDE
183 return ROUTE_BROADCAST;
189 , public ServerProtocol::SyncEventListener
190 , public Stats::EventListener
192 typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet;
197 RegexFactory* factory;
201 CommandFilter filtcommand;
202 dynamic_reference<RegexFactory> RegexEngine;
204 std::vector<FilterResult> filters;
207 // List of channel names excluded from filtering.
208 ExemptTargetSet exemptedchans;
210 // List of target nicknames excluded from filtering.
211 ExemptTargetSet exemptednicks;
214 void init() CXX11_OVERRIDE;
215 CullResult cull() CXX11_OVERRIDE;
216 ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE;
217 FilterResult* FilterMatch(User* user, const std::string &text, int flags);
218 bool DeleteFilter(const std::string& freeform, std::string& reason);
219 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);
220 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE;
221 Version GetVersion() CXX11_OVERRIDE;
222 std::string EncodeFilter(FilterResult* filter);
223 FilterResult DecodeFilter(const std::string &data);
224 void OnSyncNetwork(ProtocolInterface::Server& server) CXX11_OVERRIDE;
225 void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata) CXX11_OVERRIDE;
226 ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE;
227 ModResult OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated) CXX11_OVERRIDE;
228 void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
229 bool AppliesToMe(User* user, FilterResult* filter, int flags);
231 static bool StringToFilterAction(const std::string& str, FilterAction& fa);
232 static std::string FilterActionToString(FilterAction fa);
235 CmdResult CommandFilter::Handle(User* user, const Params& parameters)
237 if (parameters.size() == 1)
239 /* Deleting a filter */
240 Module* me = creator;
243 if (static_cast<ModuleFilter*>(me)->DeleteFilter(parameters[0], reason))
245 user->WriteNotice("*** Removed filter '" + parameters[0] + "': " + reason);
246 ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'f' : 'F', "%s removed filter '%s': %s",
247 user->nick.c_str(), parameters[0].c_str(), reason.c_str());
252 user->WriteNotice("*** Filter '" + parameters[0] + "' not found on the list.");
258 /* Adding a filter */
259 if (parameters.size() >= 4)
261 const std::string& freeform = parameters[0];
263 const std::string& flags = parameters[2];
264 unsigned int reasonindex;
265 unsigned long duration = 0;
267 if (!ModuleFilter::StringToFilterAction(parameters[1], type))
269 if (ServerInstance->XLines->GetFactory("SHUN"))
270 user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'zline', 'none', 'warn', 'block', 'silent', 'kill', and 'shun'.");
272 user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'zline', 'none', 'warn', 'block', 'silent', and 'kill'.");
276 if (type == FA_GLINE || type == FA_ZLINE || type == FA_SHUN)
278 if (parameters.size() >= 5)
280 if (!InspIRCd::Duration(parameters[3], duration))
282 user->WriteNotice("*** Invalid duration for filter");
289 user->WriteNotice("*** Not enough parameters: When setting a '" + parameters[1] + "' type filter, a duration must be specified as the third parameter.");
298 Module* me = creator;
299 std::pair<bool, std::string> result = static_cast<ModuleFilter*>(me)->AddFilter(freeform, type, parameters[reasonindex], duration, flags);
302 const std::string message = InspIRCd::Format("'%s', type '%s'%s, flags '%s', reason: %s",
303 freeform.c_str(), parameters[1].c_str(),
304 (duration ? InspIRCd::Format(", duration '%s'",
305 InspIRCd::DurationString(duration).c_str()).c_str()
306 : ""), flags.c_str(), parameters[reasonindex].c_str());
308 user->WriteNotice("*** Added filter " + message);
309 ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'f' : 'F',
310 "%s added filter %s", user->nick.c_str(), message.c_str());
316 user->WriteNotice("*** Filter '" + freeform + "' could not be added: " + result.second);
322 user->WriteNotice("*** Not enough parameters.");
329 bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags)
331 const AccountExtItem* accountext = GetAccountExtItem();
333 if ((filter->flag_no_opers) && user->IsOper())
335 if ((filter->flag_no_registered) && accountext && accountext->get(user))
337 if ((iflags & FLAG_PRIVMSG) && (!filter->flag_privmsg))
339 if ((iflags & FLAG_NOTICE) && (!filter->flag_notice))
341 if ((iflags & FLAG_QUIT) && (!filter->flag_quit_message))
343 if ((iflags & FLAG_PART) && (!filter->flag_part_message))
348 ModuleFilter::ModuleFilter()
349 : ServerProtocol::SyncEventListener(this)
350 , Stats::EventListener(this)
353 , RegexEngine(this, "regex")
357 void ModuleFilter::init()
359 ServerInstance->SNO->EnableSnomask('f', "FILTER");
362 CullResult ModuleFilter::cull()
365 return Module::cull();
368 void ModuleFilter::FreeFilters()
370 for (std::vector<FilterResult>::const_iterator i = filters.begin(); i != filters.end(); ++i)
376 ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtarget, MessageDetails& details)
378 // Leave remote users and servers alone
380 return MOD_RES_PASSTHRU;
382 flags = (details.type == MSG_PRIVMSG) ? FLAG_PRIVMSG : FLAG_NOTICE;
384 FilterResult* f = this->FilterMatch(user, details.text, flags);
387 bool is_selfmsg = false;
388 switch (msgtarget.type)
390 case MessageTarget::TYPE_USER:
392 User* t = msgtarget.Get<User>();
393 // Check if the target nick is exempted, if yes, ignore this message
394 if (exemptednicks.count(t->nick))
395 return MOD_RES_PASSTHRU;
401 case MessageTarget::TYPE_CHANNEL:
403 Channel* t = msgtarget.Get<Channel>();
404 if (exemptedchans.count(t->name))
405 return MOD_RES_PASSTHRU;
408 case MessageTarget::TYPE_SERVER:
409 return MOD_RES_PASSTHRU;
412 if (is_selfmsg && warnonselfmsg)
414 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("WARNING: %s's self message matched %s (%s)",
415 user->nick.c_str(), f->freeform.c_str(), f->reason.c_str()));
416 return MOD_RES_PASSTHRU;
418 else if (f->action == FA_WARN)
420 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("WARNING: %s's message to %s matched %s (%s)",
421 user->nick.c_str(), msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
422 return MOD_RES_PASSTHRU;
424 else if (f->action == FA_BLOCK)
426 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s had their message to %s filtered as it matched %s (%s)",
427 user->nick.c_str(), msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
430 if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
431 user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<Channel>(), InspIRCd::Format("Your message to this channel was blocked: %s.", f->reason.c_str())));
433 user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<User>(), InspIRCd::Format("Your message to this user was blocked: %s.", f->reason.c_str())));
436 details.echo_original = true;
438 else if (f->action == FA_SILENT)
442 if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
443 user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<Channel>(), InspIRCd::Format("Your message to this channel was blocked: %s.", f->reason.c_str())));
445 user->WriteNumeric(Numerics::CannotSendTo(msgtarget.Get<User>(), InspIRCd::Format("Your message to this user was blocked: %s.", f->reason.c_str())));
448 details.echo_original = true;
450 else if (f->action == FA_KILL)
452 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s was killed because their message to %s matched %s (%s)",
453 user->nick.c_str(), msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
454 ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
456 else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN")))
458 Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
459 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was shunned for %s (expires on %s) because their message to %s matched %s (%s)",
460 user->nick.c_str(), sh->Displayable().c_str(), InspIRCd::DurationString(f->duration).c_str(),
461 InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
462 msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
463 if (ServerInstance->XLines->AddLine(sh, NULL))
465 ServerInstance->XLines->ApplyLines();
470 else if (f->action == FA_GLINE)
472 GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
473 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was G-lined for %s (expires on %s) because their message to %s matched %s (%s)",
474 user->nick.c_str(), gl->Displayable().c_str(), InspIRCd::DurationString(f->duration).c_str(),
475 InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
476 msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
477 if (ServerInstance->XLines->AddLine(gl,NULL))
479 ServerInstance->XLines->ApplyLines();
484 else if (f->action == FA_ZLINE)
486 ZLine* zl = new ZLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
487 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was Z-lined for %s (expires on %s) because their message to %s matched %s (%s)",
488 user->nick.c_str(), zl->Displayable().c_str(), InspIRCd::DurationString(f->duration).c_str(),
489 InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
490 msgtarget.GetName().c_str(), f->freeform.c_str(), f->reason.c_str()));
491 if (ServerInstance->XLines->AddLine(zl,NULL))
493 ServerInstance->XLines->ApplyLines();
499 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, user->nick + " had their message filtered, target was " + msgtarget.GetName() + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action));
502 return MOD_RES_PASSTHRU;
505 ModResult ModuleFilter::OnPreCommand(std::string& command, CommandBase::Params& parameters, LocalUser* user, bool validated)
512 if (command == "QUIT")
514 /* QUIT with no reason: nothing to do */
515 if (parameters.size() < 1)
516 return MOD_RES_PASSTHRU;
521 else if (command == "PART")
523 /* PART with no reason: nothing to do */
524 if (parameters.size() < 2)
525 return MOD_RES_PASSTHRU;
527 if (exemptedchans.count(parameters[0]))
528 return MOD_RES_PASSTHRU;
534 /* We're only messing with PART and QUIT */
535 return MOD_RES_PASSTHRU;
537 FilterResult* f = this->FilterMatch(user, parameters[parting ? 1 : 0], flags);
539 /* PART or QUIT reason doesnt match a filter */
540 return MOD_RES_PASSTHRU;
542 /* We cant block a part or quit, so instead we change the reason to 'Reason filtered' */
543 parameters[parting ? 1 : 0] = "Reason filtered";
545 /* We're warning or blocking, OR they're quitting and its a KILL action
546 * (we cant kill someone whos already quitting, so filter them anyway)
548 if ((f->action == FA_WARN) || (f->action == FA_BLOCK) || (((!parting) && (f->action == FA_KILL))) || (f->action == FA_SILENT))
550 return MOD_RES_PASSTHRU;
554 /* Are they parting, if so, kill is applicable */
555 if ((parting) && (f->action == FA_KILL))
557 user->WriteNotice("*** Your PART message was filtered: " + f->reason);
558 ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
560 if (f->action == FA_GLINE)
562 /* Note: We G-line *@IP so that if their host doesn't resolve the G-line still applies. */
563 GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
564 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was G-lined for %s (expires on %s) because their %s message matched %s (%s)",
565 user->nick.c_str(), gl->Displayable().c_str(),
566 InspIRCd::DurationString(f->duration).c_str(),
567 InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
568 command.c_str(), f->freeform.c_str(), f->reason.c_str()));
570 if (ServerInstance->XLines->AddLine(gl,NULL))
572 ServerInstance->XLines->ApplyLines();
577 if (f->action == FA_ZLINE)
579 ZLine* zl = new ZLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
580 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was Z-lined for %s (expires on %s) because their %s message matched %s (%s)",
581 user->nick.c_str(), zl->Displayable().c_str(),
582 InspIRCd::DurationString(f->duration).c_str(),
583 InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
584 command.c_str(), f->freeform.c_str(), f->reason.c_str()));
586 if (ServerInstance->XLines->AddLine(zl,NULL))
588 ServerInstance->XLines->ApplyLines();
593 else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN")))
595 /* Note: We shun *!*@IP so that if their host doesnt resolve the shun still applies. */
596 Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
597 ServerInstance->SNO->WriteGlobalSno('f', InspIRCd::Format("%s (%s) was shunned for %s (expires on %s) because their %s message matched %s (%s)",
598 user->nick.c_str(), sh->Displayable().c_str(),
599 InspIRCd::DurationString(f->duration).c_str(),
600 InspIRCd::TimeString(ServerInstance->Time() + f->duration).c_str(),
601 command.c_str(), f->freeform.c_str(), f->reason.c_str()));
603 if (ServerInstance->XLines->AddLine(sh, NULL))
605 ServerInstance->XLines->ApplyLines();
613 return MOD_RES_PASSTHRU;
616 void ModuleFilter::ReadConfig(ConfigStatus& status)
618 ConfigTagList tags = ServerInstance->Config->ConfTags("exemptfromfilter");
619 exemptedchans.clear();
620 exemptednicks.clear();
622 for (ConfigIter i = tags.first; i != tags.second; ++i)
624 ConfigTag* tag = i->second;
626 // If "target" is not found, try the old "channel" key to keep compatibility with 2.0 configs
627 const std::string target = tag->getString("target", tag->getString("channel"), 1);
630 if (target[0] == '#')
631 exemptedchans.insert(target);
633 exemptednicks.insert(target);
637 ConfigTag* tag = ServerInstance->Config->ConfValue("filteropts");
638 std::string newrxengine = tag->getString("engine");
639 notifyuser = tag->getBool("notifyuser", true);
640 warnonselfmsg = tag->getBool("warnonselfmsg");
642 factory = RegexEngine ? (RegexEngine.operator->()) : NULL;
644 if (newrxengine.empty())
645 RegexEngine.SetProvider("regex");
647 RegexEngine.SetProvider("regex/" + newrxengine);
651 if (newrxengine.empty())
652 ServerInstance->SNO->WriteGlobalSno('f', "WARNING: No regex engine loaded - Filter functionality disabled until this is corrected.");
654 ServerInstance->SNO->WriteGlobalSno('f', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", newrxengine.c_str());
661 if ((!initing) && (RegexEngine.operator->() != factory))
663 ServerInstance->SNO->WriteGlobalSno('f', "Dumping all filters due to regex engine change");
671 Version ModuleFilter::GetVersion()
673 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 : "");
676 std::string ModuleFilter::EncodeFilter(FilterResult* filter)
678 std::ostringstream stream;
679 std::string x = filter->freeform;
681 /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
682 for (std::string::iterator n = x.begin(); n != x.end(); n++)
686 stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->duration << " :" << filter->reason;
690 FilterResult ModuleFilter::DecodeFilter(const std::string &data)
692 std::string filteraction;
694 irc::tokenstream tokens(data);
695 tokens.GetMiddle(res.freeform);
696 tokens.GetMiddle(filteraction);
697 if (!StringToFilterAction(filteraction, res.action))
698 throw ModuleException("Invalid action: " + filteraction);
700 std::string filterflags;
701 tokens.GetMiddle(filterflags);
702 char c = res.FillFlags(filterflags);
704 throw ModuleException("Invalid flag: '" + std::string(1, c) + "'");
706 std::string duration;
707 tokens.GetMiddle(duration);
708 res.duration = ConvToNum<unsigned long>(duration);
710 tokens.GetTrailing(res.reason);
712 /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
713 for (std::string::iterator n = res.freeform.begin(); n != res.freeform.end(); n++)
720 void ModuleFilter::OnSyncNetwork(ProtocolInterface::Server& server)
722 for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
724 FilterResult& filter = *i;
725 if (filter.from_config)
728 server.SendMetaData("filter", EncodeFilter(&filter));
732 void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata)
734 if ((target == NULL) && (extname == "filter"))
738 FilterResult data = DecodeFilter(extdata);
739 this->AddFilter(data.freeform, data.action, data.reason, data.duration, data.GetFlags());
741 catch (ModuleException& e)
743 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error when unserializing filter: " + e.GetReason());
748 FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int flgs)
750 static std::string stripped_text;
751 stripped_text.clear();
753 for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
755 FilterResult* filter = &*i;
757 /* Skip ones that dont apply to us */
758 if (!AppliesToMe(user, filter, flgs))
761 if ((filter->flag_strip_color) && (stripped_text.empty()))
763 stripped_text = text;
764 InspIRCd::StripColor(stripped_text);
767 if (filter->regex->Matches(filter->flag_strip_color ? stripped_text : text))
773 bool ModuleFilter::DeleteFilter(const std::string& freeform, std::string& reason)
775 for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
777 if (i->freeform == freeform)
779 reason.assign(i->reason);
788 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)
790 for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
792 if (i->freeform == freeform)
794 return std::make_pair(false, "Filter already exists");
800 filters.push_back(FilterResult(RegexEngine, freeform, reason, type, duration, flgs, config));
802 catch (ModuleException &e)
804 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason().c_str());
805 return std::make_pair(false, e.GetReason());
807 return std::make_pair(true, "");
810 bool ModuleFilter::StringToFilterAction(const std::string& str, FilterAction& fa)
812 if (stdalgo::string::equalsci(str, "gline"))
814 else if (stdalgo::string::equalsci(str, "zline"))
816 else if (stdalgo::string::equalsci(str, "warn"))
818 else if (stdalgo::string::equalsci(str, "block"))
820 else if (stdalgo::string::equalsci(str, "silent"))
822 else if (stdalgo::string::equalsci(str, "kill"))
824 else if (stdalgo::string::equalsci(str, "shun") && (ServerInstance->XLines->GetFactory("SHUN")))
826 else if (stdalgo::string::equalsci(str, "none"))
834 std::string ModuleFilter::FilterActionToString(FilterAction fa)
838 case FA_GLINE: return "gline";
839 case FA_ZLINE: return "zline";
840 case FA_WARN: return "warn";
841 case FA_BLOCK: return "block";
842 case FA_SILENT: return "silent";
843 case FA_KILL: return "kill";
844 case FA_SHUN: return "shun";
845 default: return "none";
849 void ModuleFilter::ReadFilters()
851 insp::flat_set<std::string> removedfilters;
853 for (std::vector<FilterResult>::iterator filter = filters.begin(); filter != filters.end(); )
855 if (filter->from_config)
857 removedfilters.insert(filter->freeform);
858 delete filter->regex;
859 filter = filters.erase(filter);
863 // The filter is not from the config.
867 ConfigTagList tags = ServerInstance->Config->ConfTags("keyword");
868 for (ConfigIter i = tags.first; i != tags.second; ++i)
870 std::string pattern = i->second->getString("pattern");
871 std::string reason = i->second->getString("reason");
872 std::string action = i->second->getString("action");
873 std::string flgs = i->second->getString("flags");
874 unsigned long duration = i->second->getDuration("duration", 10*60, 1);
879 if (!StringToFilterAction(action, fa))
882 std::pair<bool, std::string> result = static_cast<ModuleFilter*>(this)->AddFilter(pattern, fa, reason, duration, flgs, true);
884 removedfilters.erase(pattern);
886 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Filter '%s' could not be added: %s", pattern.c_str(), result.second.c_str());
889 if (!removedfilters.empty())
891 for (insp::flat_set<std::string>::const_iterator it = removedfilters.begin(); it != removedfilters.end(); ++it)
892 ServerInstance->SNO->WriteGlobalSno('f', "Removing filter '" + *(it) + "' due to config rehash.");
896 ModResult ModuleFilter::OnStats(Stats::Context& stats)
898 if (stats.GetSymbol() == 's')
900 for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
902 stats.AddRow(223, RegexEngine.GetProvider(), i->freeform, i->GetFlags(), FilterActionToString(i->action), i->duration, i->reason);
904 for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i)
906 stats.AddRow(223, "EXEMPT "+(*i));
908 for (ExemptTargetSet::const_iterator i = exemptednicks.begin(); i != exemptednicks.end(); ++i)
910 stats.AddRow(223, "EXEMPT "+(*i));
913 return MOD_RES_PASSTHRU;
916 void ModuleFilter::OnUnloadModule(Module* mod)
918 // If the regex engine became unavailable or has changed, remove all filters
923 else if (RegexEngine.operator->() != factory)
925 factory = RegexEngine.operator->();
930 MODULE_INIT(ModuleFilter)