]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_filter.cpp
Merge branch 'insp20' into master.
[user/henk/code/inspircd.git] / src / modules / m_filter.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2004, 2008 Craig Edwards <craigedwards@brainbox.cc>
6  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
7  *   Copyright (C) 2007 Robin Burchell <robin+git@viroteck.net>
8  *
9  * This file is part of InspIRCd.  InspIRCd is free software: you can
10  * redistribute it and/or modify it under the terms of the GNU General Public
11  * License as published by the Free Software Foundation, version 2.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21
22
23 #include "inspircd.h"
24 #include "xline.h"
25 #include "modules/regex.h"
26 #include "modules/server.h"
27 #include "modules/shun.h"
28 #include "modules/stats.h"
29
30 enum FilterFlags
31 {
32         FLAG_PART = 2,
33         FLAG_QUIT = 4,
34         FLAG_PRIVMSG = 8,
35         FLAG_NOTICE = 16
36 };
37
38 enum FilterAction
39 {
40         FA_GLINE,
41         FA_BLOCK,
42         FA_SILENT,
43         FA_KILL,
44         FA_SHUN,
45         FA_NONE
46 };
47
48 class FilterResult
49 {
50  public:
51         Regex* regex;
52         std::string freeform;
53         std::string reason;
54         FilterAction action;
55         long duration;
56
57         bool flag_no_opers;
58         bool flag_part_message;
59         bool flag_quit_message;
60         bool flag_privmsg;
61         bool flag_notice;
62         bool flag_strip_color;
63
64         FilterResult(dynamic_reference<RegexFactory>& RegexEngine, const std::string& free, const std::string& rea, FilterAction act, long gt, const std::string& fla)
65                 : freeform(free)
66                 , reason(rea)
67                 , action(act)
68                 , duration(gt)
69         {
70                 if (!RegexEngine)
71                         throw ModuleException("Regex module implementing '"+RegexEngine.GetProvider()+"' is not loaded!");
72                 regex = RegexEngine->Create(free);
73                 this->FillFlags(fla);
74         }
75
76         char FillFlags(const std::string &fl)
77         {
78                 flag_no_opers = flag_part_message = flag_quit_message = flag_privmsg =
79                         flag_notice = flag_strip_color = false;
80
81                 for (std::string::const_iterator n = fl.begin(); n != fl.end(); ++n)
82                 {
83                         switch (*n)
84                         {
85                                 case 'o':
86                                         flag_no_opers = true;
87                                 break;
88                                 case 'P':
89                                         flag_part_message = true;
90                                 break;
91                                 case 'q':
92                                         flag_quit_message = true;
93                                 break;
94                                 case 'p':
95                                         flag_privmsg = true;
96                                 break;
97                                 case 'n':
98                                         flag_notice = true;
99                                 break;
100                                 case 'c':
101                                         flag_strip_color = true;
102                                 break;
103                                 case '*':
104                                         flag_no_opers = flag_part_message = flag_quit_message =
105                                                 flag_privmsg = flag_notice = flag_strip_color = true;
106                                 break;
107                                 default:
108                                         return *n;
109                                 break;
110                         }
111                 }
112                 return 0;
113         }
114
115         std::string GetFlags()
116         {
117                 std::string flags;
118                 if (flag_no_opers)
119                         flags.push_back('o');
120                 if (flag_part_message)
121                         flags.push_back('P');
122                 if (flag_quit_message)
123                         flags.push_back('q');
124                 if (flag_privmsg)
125                         flags.push_back('p');
126                 if (flag_notice)
127                         flags.push_back('n');
128
129                 /* Order is important here, 'c' must be the last char in the string as it is unsupported
130                  * on < 2.0.10, and the logic in FillFlags() stops parsing when it ecounters an unknown
131                  * character.
132                  */
133                 if (flag_strip_color)
134                         flags.push_back('c');
135
136                 if (flags.empty())
137                         flags.push_back('-');
138
139                 return flags;
140         }
141
142         FilterResult()
143         {
144         }
145 };
146
147 class CommandFilter : public Command
148 {
149  public:
150         CommandFilter(Module* f)
151                 : Command(f, "FILTER", 1, 5)
152         {
153                 flags_needed = 'o';
154                 this->syntax = "<filter-definition> <action> <flags> [<duration>] :<reason>";
155         }
156         CmdResult Handle(const std::vector<std::string>& , User* ) CXX11_OVERRIDE;
157
158         RouteDescriptor GetRouting(User* user, const std::vector<std::string>& parameters) CXX11_OVERRIDE
159         {
160                 return ROUTE_BROADCAST;
161         }
162 };
163
164 class ModuleFilter : public Module, public ServerEventListener, public Stats::EventListener
165 {
166         typedef insp::flat_set<std::string, irc::insensitive_swo> ExemptTargetSet;
167
168         bool initing;
169         bool notifyuser;
170         RegexFactory* factory;
171         void FreeFilters();
172
173  public:
174         CommandFilter filtcommand;
175         dynamic_reference<RegexFactory> RegexEngine;
176
177         std::vector<FilterResult> filters;
178         int flags;
179
180         // List of channel names excluded from filtering.
181         ExemptTargetSet exemptedchans;
182
183         // List of target nicknames excluded from filtering.
184         ExemptTargetSet exemptednicks;
185
186         ModuleFilter();
187         CullResult cull() CXX11_OVERRIDE;
188         ModResult OnUserPreMessage(User* user, const MessageTarget& target, MessageDetails& details) CXX11_OVERRIDE;
189         FilterResult* FilterMatch(User* user, const std::string &text, int flags);
190         bool DeleteFilter(const std::string &freeform);
191         std::pair<bool, std::string> AddFilter(const std::string &freeform, FilterAction type, const std::string &reason, long duration, const std::string &flags);
192         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE;
193         Version GetVersion() CXX11_OVERRIDE;
194         std::string EncodeFilter(FilterResult* filter);
195         FilterResult DecodeFilter(const std::string &data);
196         void OnSyncNetwork(ProtocolInterface::Server& server) CXX11_OVERRIDE;
197         void OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata) CXX11_OVERRIDE;
198         ModResult OnStats(Stats::Context& stats) CXX11_OVERRIDE;
199         ModResult OnPreCommand(std::string &command, std::vector<std::string> &parameters, LocalUser *user, bool validated, const std::string &original_line) CXX11_OVERRIDE;
200         void OnUnloadModule(Module* mod) CXX11_OVERRIDE;
201         bool AppliesToMe(User* user, FilterResult* filter, int flags);
202         void ReadFilters();
203         static bool StringToFilterAction(const std::string& str, FilterAction& fa);
204         static std::string FilterActionToString(FilterAction fa);
205 };
206
207 CmdResult CommandFilter::Handle(const std::vector<std::string> &parameters, User *user)
208 {
209         if (parameters.size() == 1)
210         {
211                 /* Deleting a filter */
212                 Module *me = creator;
213                 if (static_cast<ModuleFilter *>(me)->DeleteFilter(parameters[0]))
214                 {
215                         user->WriteNotice("*** Removed filter '" + parameters[0] + "'");
216                         ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "FILTER: "+user->nick+" removed filter '"+parameters[0]+"'");
217                         return CMD_SUCCESS;
218                 }
219                 else
220                 {
221                         user->WriteNotice("*** Filter '" + parameters[0] + "' not found in list, try /stats s.");
222                         return CMD_FAILURE;
223                 }
224         }
225         else
226         {
227                 /* Adding a filter */
228                 if (parameters.size() >= 4)
229                 {
230                         const std::string& freeform = parameters[0];
231                         FilterAction type;
232                         const std::string& flags = parameters[2];
233                         unsigned int reasonindex;
234                         long duration = 0;
235
236                         if (!ModuleFilter::StringToFilterAction(parameters[1], type))
237                         {
238                                 if (ServerInstance->XLines->GetFactory("SHUN"))
239                                         user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent', 'kill', and 'shun'.");
240                                 else
241                                         user->WriteNotice("*** Invalid filter type '" + parameters[1] + "'. Supported types are 'gline', 'none', 'block', 'silent', and 'kill'.");
242                                 return CMD_FAILURE;
243                         }
244
245                         if (type == FA_GLINE || type == FA_SHUN)
246                         {
247                                 if (parameters.size() >= 5)
248                                 {
249                                         duration = InspIRCd::Duration(parameters[3]);
250                                         reasonindex = 4;
251                                 }
252                                 else
253                                 {
254                                         user->WriteNotice("*** Not enough parameters: When setting a gline or shun type filter, a duration must be specified as the third parameter.");
255                                         return CMD_FAILURE;
256                                 }
257                         }
258                         else
259                         {
260                                 reasonindex = 3;
261                         }
262
263                         Module *me = creator;
264                         std::pair<bool, std::string> result = static_cast<ModuleFilter *>(me)->AddFilter(freeform, type, parameters[reasonindex], duration, flags);
265                         if (result.first)
266                         {
267                                 user->WriteNotice("*** Added filter '" + freeform + "', type '" + parameters[1] + "'" +
268                                         (duration ? ", duration " +  parameters[3] : "") + ", flags '" + flags + "', reason: '" +
269                                         parameters[reasonindex] + "'");
270
271                                 ServerInstance->SNO->WriteToSnoMask(IS_LOCAL(user) ? 'a' : 'A', "FILTER: "+user->nick+" added filter '"+freeform+"', type '"+parameters[1]+"', "+(duration ? "duration "+parameters[3]+", " : "")+"flags '"+flags+"', reason: "+parameters[reasonindex]);
272
273                                 return CMD_SUCCESS;
274                         }
275                         else
276                         {
277                                 user->WriteNotice("*** Filter '" + freeform + "' could not be added: " + result.second);
278                                 return CMD_FAILURE;
279                         }
280                 }
281                 else
282                 {
283                         user->WriteNotice("*** Not enough parameters.");
284                         return CMD_FAILURE;
285                 }
286
287         }
288 }
289
290 bool ModuleFilter::AppliesToMe(User* user, FilterResult* filter, int iflags)
291 {
292         if ((filter->flag_no_opers) && user->IsOper())
293                 return false;
294         if ((iflags & FLAG_PRIVMSG) && (!filter->flag_privmsg))
295                 return false;
296         if ((iflags & FLAG_NOTICE) && (!filter->flag_notice))
297                 return false;
298         if ((iflags & FLAG_QUIT)   && (!filter->flag_quit_message))
299                 return false;
300         if ((iflags & FLAG_PART)   && (!filter->flag_part_message))
301                 return false;
302         return true;
303 }
304
305 ModuleFilter::ModuleFilter()
306         : ServerEventListener(this)
307         , Stats::EventListener(this)
308         , initing(true)
309         , filtcommand(this)
310         , RegexEngine(this, "regex")
311 {
312 }
313
314 CullResult ModuleFilter::cull()
315 {
316         FreeFilters();
317         return Module::cull();
318 }
319
320 void ModuleFilter::FreeFilters()
321 {
322         for (std::vector<FilterResult>::const_iterator i = filters.begin(); i != filters.end(); ++i)
323                 delete i->regex;
324
325         filters.clear();
326 }
327
328 ModResult ModuleFilter::OnUserPreMessage(User* user, const MessageTarget& msgtarget, MessageDetails& details)
329 {
330         // Leave remote users and servers alone
331         if (!IS_LOCAL(user))
332                 return MOD_RES_PASSTHRU;
333
334         flags = (details.type == MSG_PRIVMSG) ? FLAG_PRIVMSG : FLAG_NOTICE;
335
336         FilterResult* f = this->FilterMatch(user, details.text, flags);
337         if (f)
338         {
339                 std::string target;
340                 if (msgtarget.type == MessageTarget::TYPE_USER)
341                 {
342                         User* t = msgtarget.Get<User>();
343                         // Check if the target nick is exempted, if yes, ignore this message
344                         if (exemptednicks.count(t->nick))
345                                 return MOD_RES_PASSTHRU;
346
347                         target = t->nick;
348                 }
349                 else if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
350                 {
351                         Channel* t = msgtarget.Get<Channel>();
352                         if (exemptedchans.count(t->name))
353                                 return MOD_RES_PASSTHRU;
354
355                         target = t->name;
356                 }
357                 if (f->action == FA_BLOCK)
358                 {
359                         ServerInstance->SNO->WriteGlobalSno('a', "FILTER: "+user->nick+" had their message filtered, target was "+target+": "+f->reason);
360                         if (notifyuser)
361                         {
362                                 if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
363                                         user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked and opers notified (%s)", f->reason.c_str()));
364                                 else
365                                         user->WriteNotice("Your message to "+target+" was blocked and opers notified: "+f->reason);
366                         }
367                         else
368                                 details.echooriginal = true;
369                 }
370                 else if (f->action == FA_SILENT)
371                 {
372                         if (notifyuser)
373                         {
374                                 if (msgtarget.type == MessageTarget::TYPE_CHANNEL)
375                                         user->WriteNumeric(ERR_CANNOTSENDTOCHAN, target, InspIRCd::Format("Message to channel blocked (%s)", f->reason.c_str()));
376                                 else
377                                         user->WriteNotice("Your message to "+target+" was blocked: "+f->reason);
378                         }
379                         else
380                                 details.echooriginal = true;
381                 }
382                 else if (f->action == FA_KILL)
383                 {
384                         ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their message filtered and was killed, target was " + target + ": " + f->reason);
385                         ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
386                 }
387                 else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN")))
388                 {
389                         Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
390                         ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their message filtered and was shunned, target was " + target + ": " + f->reason);
391                         if (ServerInstance->XLines->AddLine(sh, NULL))
392                         {
393                                 ServerInstance->XLines->ApplyLines();
394                         }
395                         else
396                                 delete sh;
397                 }
398                 else if (f->action == FA_GLINE)
399                 {
400                         GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
401                         ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their message filtered and was G-Lined, target was " + target + ": " + f->reason);
402                         if (ServerInstance->XLines->AddLine(gl,NULL))
403                         {
404                                 ServerInstance->XLines->ApplyLines();
405                         }
406                         else
407                                 delete gl;
408                 }
409
410                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + ModuleFilter::FilterActionToString(f->action));
411                 return MOD_RES_DENY;
412         }
413         return MOD_RES_PASSTHRU;
414 }
415
416 ModResult ModuleFilter::OnPreCommand(std::string &command, std::vector<std::string> &parameters, LocalUser *user, bool validated, const std::string &original_line)
417 {
418         if (validated)
419         {
420                 flags = 0;
421                 bool parting;
422
423                 if (command == "QUIT")
424                 {
425                         /* QUIT with no reason: nothing to do */
426                         if (parameters.size() < 1)
427                                 return MOD_RES_PASSTHRU;
428
429                         parting = false;
430                         flags = FLAG_QUIT;
431                 }
432                 else if (command == "PART")
433                 {
434                         /* PART with no reason: nothing to do */
435                         if (parameters.size() < 2)
436                                 return MOD_RES_PASSTHRU;
437
438                         if (exemptedchans.count(parameters[0]))
439                                 return MOD_RES_PASSTHRU;
440
441                         parting = true;
442                         flags = FLAG_PART;
443                 }
444                 else
445                         /* We're only messing with PART and QUIT */
446                         return MOD_RES_PASSTHRU;
447
448                 FilterResult* f = this->FilterMatch(user, parameters[parting ? 1 : 0], flags);
449                 if (!f)
450                         /* PART or QUIT reason doesnt match a filter */
451                         return MOD_RES_PASSTHRU;
452
453                 /* We cant block a part or quit, so instead we change the reason to 'Reason filtered' */
454                 parameters[parting ? 1 : 0] = "Reason filtered";
455
456                 /* We're blocking, OR theyre quitting and its a KILL action
457                  * (we cant kill someone whos already quitting, so filter them anyway)
458                  */
459                 if ((f->action == FA_BLOCK) || (((!parting) && (f->action == FA_KILL))) || (f->action == FA_SILENT))
460                 {
461                         return MOD_RES_PASSTHRU;
462                 }
463                 else
464                 {
465                         /* Are they parting, if so, kill is applicable */
466                         if ((parting) && (f->action == FA_KILL))
467                         {
468                                 user->WriteNotice("*** Your PART message was filtered: " + f->reason);
469                                 ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
470                         }
471                         if (f->action == FA_GLINE)
472                         {
473                                 /* Note: We gline *@IP so that if their host doesnt resolve the gline still applies. */
474                                 GLine* gl = new GLine(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), "*", user->GetIPString());
475                                 ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their " + command + " message filtered and was G-Lined: " + f->reason);
476                                 if (ServerInstance->XLines->AddLine(gl,NULL))
477                                 {
478                                         ServerInstance->XLines->ApplyLines();
479                                 }
480                                 else
481                                         delete gl;
482                         }
483                         else if (f->action == FA_SHUN && (ServerInstance->XLines->GetFactory("SHUN")))
484                         {
485                                 /* Note: We shun *!*@IP so that if their host doesnt resolve the shun still applies. */
486                                 Shun* sh = new Shun(ServerInstance->Time(), f->duration, ServerInstance->Config->ServerName.c_str(), f->reason.c_str(), user->GetIPString());
487                                 ServerInstance->SNO->WriteGlobalSno('a', "FILTER: " + user->nick + " had their " + command + " message filtered and was shunned: " + f->reason);
488                                 if (ServerInstance->XLines->AddLine(sh, NULL))
489                                 {
490                                         ServerInstance->XLines->ApplyLines();
491                                 }
492                                 else
493                                         delete sh;
494                         }
495                         return MOD_RES_DENY;
496                 }
497         }
498         return MOD_RES_PASSTHRU;
499 }
500
501 void ModuleFilter::ReadConfig(ConfigStatus& status)
502 {
503         ConfigTagList tags = ServerInstance->Config->ConfTags("exemptfromfilter");
504         exemptedchans.clear();
505         exemptednicks.clear();
506
507         for (ConfigIter i = tags.first; i != tags.second; ++i)
508         {
509                 ConfigTag* tag = i->second;
510
511                 // If "target" is not found, try the old "channel" key to keep compatibility with 2.0 configs
512                 const std::string target = tag->getString("target", tag->getString("channel"));
513                 if (!target.empty())
514                 {
515                         if (target[0] == '#')
516                                 exemptedchans.insert(target);
517                         else
518                                 exemptednicks.insert(target);
519                 }
520         }
521
522         ConfigTag* tag = ServerInstance->Config->ConfValue("filteropts");
523         std::string newrxengine = tag->getString("engine");
524         notifyuser = tag->getBool("notifyuser", true);
525
526         factory = RegexEngine ? (RegexEngine.operator->()) : NULL;
527
528         if (newrxengine.empty())
529                 RegexEngine.SetProvider("regex");
530         else
531                 RegexEngine.SetProvider("regex/" + newrxengine);
532
533         if (!RegexEngine)
534         {
535                 if (newrxengine.empty())
536                         ServerInstance->SNO->WriteGlobalSno('a', "WARNING: No regex engine loaded - Filter functionality disabled until this is corrected.");
537                 else
538                         ServerInstance->SNO->WriteGlobalSno('a', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", newrxengine.c_str());
539
540                 initing = false;
541                 FreeFilters();
542                 return;
543         }
544
545         if ((!initing) && (RegexEngine.operator->() != factory))
546         {
547                 ServerInstance->SNO->WriteGlobalSno('a', "Dumping all filters due to regex engine change");
548                 FreeFilters();
549         }
550
551         initing = false;
552         ReadFilters();
553 }
554
555 Version ModuleFilter::GetVersion()
556 {
557         return Version("Text (spam) filtering", VF_VENDOR | VF_COMMON, RegexEngine ? RegexEngine->name : "");
558 }
559
560 std::string ModuleFilter::EncodeFilter(FilterResult* filter)
561 {
562         std::ostringstream stream;
563         std::string x = filter->freeform;
564
565         /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
566         for (std::string::iterator n = x.begin(); n != x.end(); n++)
567                 if (*n == ' ')
568                         *n = '\7';
569
570         stream << x << " " << FilterActionToString(filter->action) << " " << filter->GetFlags() << " " << filter->duration << " :" << filter->reason;
571         return stream.str();
572 }
573
574 FilterResult ModuleFilter::DecodeFilter(const std::string &data)
575 {
576         std::string filteraction;
577         FilterResult res;
578         irc::tokenstream tokens(data);
579         tokens.GetToken(res.freeform);
580         tokens.GetToken(filteraction);
581         if (!StringToFilterAction(filteraction, res.action))
582                 throw ModuleException("Invalid action: " + filteraction);
583
584         std::string filterflags;
585         tokens.GetToken(filterflags);
586         char c = res.FillFlags(filterflags);
587         if (c != 0)
588                 throw ModuleException("Invalid flag: '" + std::string(1, c) + "'");
589
590         tokens.GetToken(res.duration);
591         tokens.GetToken(res.reason);
592
593         /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
594         for (std::string::iterator n = res.freeform.begin(); n != res.freeform.end(); n++)
595                 if (*n == '\7')
596                         *n = ' ';
597
598         return res;
599 }
600
601 void ModuleFilter::OnSyncNetwork(ProtocolInterface::Server& server)
602 {
603         for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
604         {
605                 server.SendMetaData("filter", EncodeFilter(&(*i)));
606         }
607 }
608
609 void ModuleFilter::OnDecodeMetaData(Extensible* target, const std::string &extname, const std::string &extdata)
610 {
611         if ((target == NULL) && (extname == "filter"))
612         {
613                 try
614                 {
615                         FilterResult data = DecodeFilter(extdata);
616                         this->AddFilter(data.freeform, data.action, data.reason, data.duration, data.GetFlags());
617                 }
618                 catch (ModuleException& e)
619                 {
620                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Error when unserializing filter: " + e.GetReason());
621                 }
622         }
623 }
624
625 FilterResult* ModuleFilter::FilterMatch(User* user, const std::string &text, int flgs)
626 {
627         static std::string stripped_text;
628         stripped_text.clear();
629
630         for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); ++i)
631         {
632                 FilterResult* filter = &*i;
633
634                 /* Skip ones that dont apply to us */
635                 if (!AppliesToMe(user, filter, flgs))
636                         continue;
637
638                 if ((filter->flag_strip_color) && (stripped_text.empty()))
639                 {
640                         stripped_text = text;
641                         InspIRCd::StripColor(stripped_text);
642                 }
643
644                 if (filter->regex->Matches(filter->flag_strip_color ? stripped_text : text))
645                         return filter;
646         }
647         return NULL;
648 }
649
650 bool ModuleFilter::DeleteFilter(const std::string &freeform)
651 {
652         for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
653         {
654                 if (i->freeform == freeform)
655                 {
656                         delete i->regex;
657                         filters.erase(i);
658                         return true;
659                 }
660         }
661         return false;
662 }
663
664 std::pair<bool, std::string> ModuleFilter::AddFilter(const std::string &freeform, FilterAction type, const std::string &reason, long duration, const std::string &flgs)
665 {
666         for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
667         {
668                 if (i->freeform == freeform)
669                 {
670                         return std::make_pair(false, "Filter already exists");
671                 }
672         }
673
674         try
675         {
676                 filters.push_back(FilterResult(RegexEngine, freeform, reason, type, duration, flgs));
677         }
678         catch (ModuleException &e)
679         {
680                 ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason().c_str());
681                 return std::make_pair(false, e.GetReason());
682         }
683         return std::make_pair(true, "");
684 }
685
686 bool ModuleFilter::StringToFilterAction(const std::string& str, FilterAction& fa)
687 {
688         if (stdalgo::string::equalsci(str, "gline"))
689                 fa = FA_GLINE;
690         else if (stdalgo::string::equalsci(str, "block"))
691                 fa = FA_BLOCK;
692         else if (stdalgo::string::equalsci(str, "silent"))
693                 fa = FA_SILENT;
694         else if (stdalgo::string::equalsci(str, "kill"))
695                 fa = FA_KILL;
696         else if (stdalgo::string::equalsci(str, "shun") && (ServerInstance->XLines->GetFactory("SHUN")))
697                 fa = FA_SHUN;
698         else if (stdalgo::string::equalsci(str, "none"))
699                 fa = FA_NONE;
700         else
701                 return false;
702
703         return true;
704 }
705
706 std::string ModuleFilter::FilterActionToString(FilterAction fa)
707 {
708         switch (fa)
709         {
710                 case FA_GLINE:  return "gline";
711                 case FA_BLOCK:  return "block";
712                 case FA_SILENT: return "silent";
713                 case FA_KILL:   return "kill";
714                 case FA_SHUN:   return "shun";
715                 default:                return "none";
716         }
717 }
718
719 void ModuleFilter::ReadFilters()
720 {
721         ConfigTagList tags = ServerInstance->Config->ConfTags("keyword");
722         for (ConfigIter i = tags.first; i != tags.second; ++i)
723         {
724                 std::string pattern = i->second->getString("pattern");
725                 this->DeleteFilter(pattern);
726
727                 std::string reason = i->second->getString("reason");
728                 std::string action = i->second->getString("action");
729                 std::string flgs = i->second->getString("flags");
730                 unsigned long duration = i->second->getDuration("duration", 10*60, 1);
731                 if (flgs.empty())
732                         flgs = "*";
733
734                 FilterAction fa;
735                 if (!StringToFilterAction(action, fa))
736                         fa = FA_NONE;
737
738                 try
739                 {
740                         filters.push_back(FilterResult(RegexEngine, pattern, reason, fa, duration, flgs));
741                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Regular expression %s loaded.", pattern.c_str());
742                 }
743                 catch (ModuleException &e)
744                 {
745                         ServerInstance->Logs->Log(MODNAME, LOG_DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason().c_str());
746                 }
747         }
748 }
749
750 ModResult ModuleFilter::OnStats(Stats::Context& stats)
751 {
752         if (stats.GetSymbol() == 's')
753         {
754                 for (std::vector<FilterResult>::iterator i = filters.begin(); i != filters.end(); i++)
755                 {
756                         stats.AddRow(223, RegexEngine.GetProvider()+":"+i->freeform+" "+i->GetFlags()+" "+FilterActionToString(i->action)+" "+ConvToStr(i->duration)+" :"+i->reason);
757                 }
758                 for (ExemptTargetSet::const_iterator i = exemptedchans.begin(); i != exemptedchans.end(); ++i)
759                 {
760                         stats.AddRow(223, "EXEMPT "+(*i));
761                 }
762                 for (ExemptTargetSet::const_iterator i = exemptednicks.begin(); i != exemptednicks.end(); ++i)
763                 {
764                         stats.AddRow(223, "EXEMPT "+(*i));
765                 }
766         }
767         return MOD_RES_PASSTHRU;
768 }
769
770 void ModuleFilter::OnUnloadModule(Module* mod)
771 {
772         // If the regex engine became unavailable or has changed, remove all filters
773         if (!RegexEngine)
774         {
775                 FreeFilters();
776         }
777         else if (RegexEngine.operator->() != factory)
778         {
779                 factory = RegexEngine.operator->();
780                 FreeFilters();
781         }
782 }
783
784 MODULE_INIT(ModuleFilter)