]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_filter.cpp
ec5293d95107396362f9715be773993b1d9df656
[user/henk/code/inspircd.git] / src / modules / m_filter.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *          the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "xline.h"
16 #include "m_regex.h"
17
18 /* $ModDesc: Text (spam) filtering */
19
20 static std::string RegexEngine = "";
21 static Module* rxengine = NULL;
22
23 enum FilterFlags
24 {
25         FLAG_PART = 2,
26         FLAG_QUIT = 4,
27         FLAG_PRIVMSG = 8,
28         FLAG_NOTICE = 16
29 };
30
31 class FilterResult : public classbase
32 {
33  public:
34         std::string freeform;
35         std::string reason;
36         std::string action;
37         long gline_time;
38         std::string flags;
39
40         bool flag_no_opers;
41         bool flag_part_message;
42         bool flag_quit_message;
43         bool flag_privmsg;
44         bool flag_notice;
45
46         FilterResult(const std::string free, const std::string &rea, const std::string &act, long gt, const std::string &fla) :
47                         freeform(free), reason(rea), action(act), gline_time(gt), flags(fla)
48         {
49                 this->FillFlags(fla);
50         }
51
52         int FillFlags(const std::string &fl)
53         {
54                 flags = fl;
55                 flag_no_opers = flag_part_message = flag_quit_message = flag_privmsg = flag_notice = false;
56                 size_t x = 0;
57
58                 for (std::string::const_iterator n = flags.begin(); n != flags.end(); ++n, ++x)
59                 {
60                         switch (*n)
61                         {
62                                 case 'o':
63                                         flag_no_opers = true;
64                                 break;
65                                 case 'P':
66                                         flag_part_message = true;
67                                 break;
68                                 case 'q':
69                                         flag_quit_message = true;
70                                 break;
71                                 case 'p':
72                                         flag_privmsg = true;
73                                 break;
74                                 case 'n':
75                                         flag_notice = true;
76                                 break;
77                                 case '*':
78                                         flag_no_opers = flag_part_message = flag_quit_message =
79                                                 flag_privmsg = flag_notice = true;
80                                 break;
81                                 default:
82                                         return x;
83                                 break;
84                         }
85                 }
86                 return 0;
87         }
88
89         FilterResult()
90         {
91         }
92
93         virtual ~FilterResult()
94         {
95         }
96 };
97
98 class CommandFilter;
99
100 class FilterBase : public Module
101 {
102         CommandFilter* filtcommand;
103         int flags;
104 protected:
105         std::vector<std::string> exemptfromfilter; // List of channel names excluded from filtering.
106  public:
107         FilterBase(InspIRCd* Me, const std::string &source);
108         virtual ~FilterBase();
109         virtual int OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
110         virtual FilterResult* FilterMatch(User* user, const std::string &text, int flags) = 0;
111         virtual bool DeleteFilter(const std::string &freeform) = 0;
112         virtual void SyncFilters(Module* proto, void* opaque) = 0;
113         virtual void SendFilter(Module* proto, void* opaque, FilterResult* iter);
114         virtual std::pair<bool, std::string> AddFilter(const std::string &freeform, const std::string &type, const std::string &reason, long duration, const std::string &flags) = 0;
115         virtual int OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list);
116         virtual void OnRehash(User* user, const std::string &parameter);
117         virtual Version GetVersion();
118         std::string EncodeFilter(FilterResult* filter);
119         FilterResult DecodeFilter(const std::string &data);
120         virtual void OnSyncOtherMetaData(Module* proto, void* opaque, bool displayable = false);
121         virtual void OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata);
122         virtual int OnStats(char symbol, User* user, string_list &results) = 0;
123         virtual int OnPreCommand(std::string &command, std::vector<std::string> &parameters, User *user, bool validated, const std::string &original_line);
124         bool AppliesToMe(User* user, FilterResult* filter, int flags);
125         void OnLoadModule(Module* mod, const std::string& name);
126         virtual void ReadFilters(ConfigReader &MyConf) = 0;
127 };
128
129 class CommandFilter : public Command
130 {
131         FilterBase* Base;
132  public:
133         CommandFilter(FilterBase* f, InspIRCd* Me, const std::string &ssource) : Command(Me, "FILTER", "o", 1, 5), Base(f)
134         {
135                 this->source = ssource;
136                 this->syntax = "<filter-definition> <type> <flags> [<gline-duration>] :<reason>";
137         }
138
139         CmdResult Handle(const std::vector<std::string> &parameters, User *user)
140         {
141                 if (parameters.size() == 1)
142                 {
143                         /* Deleting a filter */
144                         if (Base->DeleteFilter(parameters[0]))
145                         {
146                                 user->WriteServ("NOTICE %s :*** Deleted filter '%s'", user->nick.c_str(), parameters[0].c_str());
147                                 return CMD_SUCCESS;
148                         }
149                         else
150                         {
151                                 user->WriteServ("NOTICE %s :*** Filter '%s' not found on list.", user->nick.c_str(), parameters[0].c_str());
152                                 return CMD_FAILURE;
153                         }
154                 }
155                 else
156                 {
157                         /* Adding a filter */
158                         if (parameters.size() >= 4)
159                         {
160                                 std::string freeform = parameters[0];
161                                 std::string type = parameters[1];
162                                 std::string flags = parameters[2];
163                                 std::string reason;
164                                 long duration = 0;
165
166
167                                 if ((type != "gline") && (type != "none") && (type != "block") && (type != "kill") && (type != "silent"))
168                                 {
169                                         user->WriteServ("NOTICE %s :*** Invalid filter type '%s'. Supported types are 'gline', 'none', 'block', 'silent' and 'kill'.", user->nick.c_str(), freeform.c_str());
170                                         return CMD_FAILURE;
171                                 }
172
173                                 if (type == "gline")
174                                 {
175                                         if (parameters.size() >= 5)
176                                         {
177                                                 duration = ServerInstance->Duration(parameters[3]);
178                                                 reason = parameters[4];
179                                         }
180                                         else
181                                         {
182                                                 this->TooFewParams(user, " When setting a gline type filter, a gline duration must be specified as the third parameter.");
183                                                 return CMD_FAILURE;
184                                         }
185                                 }
186                                 else
187                                 {
188                                         reason = parameters[3];
189                                 }
190                                 std::pair<bool, std::string> result = Base->AddFilter(freeform, type, reason, duration, flags);
191                                 if (result.first)
192                                 {
193                                         user->WriteServ("NOTICE %s :*** Added filter '%s', type '%s'%s%s, flags '%s', reason: '%s'", user->nick.c_str(), freeform.c_str(),
194                                                         type.c_str(), (duration ? " duration: " : ""), (duration ? parameters[3].c_str() : ""),
195                                                         flags.c_str(), reason.c_str());
196                                         return CMD_SUCCESS;
197                                 }
198                                 else
199                                 {
200                                         user->WriteServ("NOTICE %s :*** Filter '%s' could not be added: %s", user->nick.c_str(), freeform.c_str(), result.second.c_str());
201                                         return CMD_FAILURE;
202                                 }
203                         }
204                         else
205                         {
206                                 this->TooFewParams(user, ".");
207                                 return CMD_FAILURE;
208                         }
209
210                 }
211         }
212
213         void TooFewParams(User* user, const std::string &extra_text)
214         {
215                 user->WriteServ("NOTICE %s :*** Not enough parameters%s", user->nick.c_str(), extra_text.c_str());
216         }
217 };
218
219 bool FilterBase::AppliesToMe(User* user, FilterResult* filter, int iflags)
220 {
221         if ((filter->flag_no_opers) && IS_OPER(user))
222                 return false;
223         if ((iflags & FLAG_PRIVMSG) && (!filter->flag_privmsg))
224                 return false;
225         if ((iflags & FLAG_NOTICE) && (!filter->flag_notice))
226                 return false;
227         if ((iflags & FLAG_QUIT)   && (!filter->flag_quit_message))
228                 return false;
229         if ((iflags & FLAG_PART)   && (!filter->flag_part_message))
230                 return false;
231         return true;
232 }
233
234 FilterBase::FilterBase(InspIRCd* Me, const std::string &source) : Module(Me)
235 {
236         Me->Modules->UseInterface("RegularExpression");
237         filtcommand = new CommandFilter(this, Me, source);
238         ServerInstance->AddCommand(filtcommand);
239         Implementation eventlist[] = { I_OnPreCommand, I_OnStats, I_OnSyncOtherMetaData, I_OnDecodeMetaData, I_OnUserPreMessage, I_OnUserPreNotice, I_OnRehash, I_OnLoadModule };
240         ServerInstance->Modules->Attach(eventlist, this, 8);
241 }
242
243 FilterBase::~FilterBase()
244 {
245         ServerInstance->Modules->DoneWithInterface("RegularExpression");
246 }
247
248 int FilterBase::OnUserPreMessage(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
249 {
250         flags = FLAG_PRIVMSG;
251         return OnUserPreNotice(user,dest,target_type,text,status,exempt_list);
252 }
253
254 int FilterBase::OnUserPreNotice(User* user,void* dest,int target_type, std::string &text, char status, CUList &exempt_list)
255 {
256         if (!flags)
257                 flags = FLAG_NOTICE;
258
259         /* Leave ulines alone */
260         if ((ServerInstance->ULine(user->server)) || (!IS_LOCAL(user)))
261                 return 0;
262
263         FilterResult* f = this->FilterMatch(user, text, flags);
264         if (f)
265         {
266                 std::string target = "";
267                 if (target_type == TYPE_USER)
268                 {
269                         User* t = (User*)dest;
270                         target = std::string(t->nick);
271                 }
272                 else if (target_type == TYPE_CHANNEL)
273                 {
274                         Channel* t = (Channel*)dest;
275                         target = std::string(t->name);
276                         std::vector<std::string>::iterator i = find(exemptfromfilter.begin(), exemptfromfilter.end(), target);
277                         if (i != exemptfromfilter.end()) return 0;
278                 }
279                 if (f->action == "block")
280                 {       
281                         ServerInstance->SNO->WriteToSnoMask('A', std::string("FILTER: ")+user->nick+" had their message filtered, target was "+target+": "+f->reason);
282                         user->WriteServ("NOTICE "+std::string(user->nick)+" :Your message has been filtered and opers notified: "+f->reason);
283                 }
284                 if (f->action == "silent")
285                 {
286                         user->WriteServ("NOTICE "+std::string(user->nick)+" :Your message has been filtered: "+f->reason);
287                 }
288                 if (f->action == "kill")
289                 {
290                         ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
291                 }
292                 if (f->action == "gline")
293                 {
294                         GLine* gl = new GLine(ServerInstance, ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName, f->reason.c_str(), "*", user->GetIPString());
295                         if (ServerInstance->XLines->AddLine(gl,NULL))
296                         {
297                                 ServerInstance->XLines->ApplyLines();
298                         }
299                         else
300                                 delete gl;
301                 }
302
303                 ServerInstance->Logs->Log("FILTER",DEFAULT,"FILTER: "+ user->nick + " had their message filtered, target was " + target + ": " + f->reason + " Action: " + f->action);
304                 return 1;
305         }
306         return 0;
307 }
308
309 int FilterBase::OnPreCommand(std::string &command, std::vector<std::string> &parameters, User *user, bool validated, const std::string &original_line)
310 {
311         flags = 0;
312         if (validated && IS_LOCAL(user))
313         {
314                 std::string checkline;
315                 int replacepoint = 0;
316                 bool parting = false;
317         
318                 if (command == "QUIT")
319                 {
320                         /* QUIT with no reason: nothing to do */
321                         if (parameters.size() < 1)
322                                 return 0;
323
324                         checkline = parameters[0];
325                         replacepoint = 0;
326                         parting = false;
327                         flags = FLAG_QUIT;
328                 }
329                 else if (command == "PART")
330                 {
331                         /* PART with no reason: nothing to do */
332                         if (parameters.size() < 2)
333                                 return 0;
334
335                         std::vector<std::string>::iterator i = find(exemptfromfilter.begin(), exemptfromfilter.end(), parameters[0]);
336                         if (i != exemptfromfilter.end()) return 0;
337                         checkline = parameters[1];
338                         replacepoint = 1;
339                         parting = true;
340                         flags = FLAG_PART;
341                 }
342                 else
343                         /* We're only messing with PART and QUIT */
344                         return 0;
345
346                 FilterResult* f = NULL;
347                 
348                 if (flags)
349                         f = this->FilterMatch(user, checkline, flags);
350
351                 if (!f)
352                         /* PART or QUIT reason doesnt match a filter */
353                         return 0;
354
355                 /* We cant block a part or quit, so instead we change the reason to 'Reason filtered' */
356                 Command* c = ServerInstance->Parser->GetHandler(command);
357                 if (c)
358                 {
359                         std::vector<std::string> params;
360                         for (int item = 0; item < (int)parameters.size(); item++)
361                                 params.push_back(parameters[item]);
362                         params[replacepoint] = "Reason filtered";
363
364                         /* We're blocking, OR theyre quitting and its a KILL action
365                          * (we cant kill someone whos already quitting, so filter them anyway)
366                          */
367                         if ((f->action == "block") || (((!parting) && (f->action == "kill"))) || (f->action == "silent"))
368                         {
369                                 c->Handle(params, user);
370                                 return 1;
371                         }
372                         else
373                         {
374                                 /* Are they parting, if so, kill is applicable */
375                                 if ((parting) && (f->action == "kill"))
376                                 {
377                                         user->WriteServ("NOTICE %s :*** Your PART message was filtered: %s", user->nick.c_str(), f->reason.c_str());
378                                         ServerInstance->Users->QuitUser(user, "Filtered: " + f->reason);
379                                 }
380                                 if (f->action == "gline")
381                                 {
382                                         /* Note: We gline *@IP so that if their host doesnt resolve the gline still applies. */
383                                         GLine* gl = new GLine(ServerInstance, ServerInstance->Time(), f->gline_time, ServerInstance->Config->ServerName, f->reason.c_str(), "*", user->GetIPString());
384                                         if (ServerInstance->XLines->AddLine(gl,NULL))
385                                         {
386                                                 ServerInstance->XLines->ApplyLines();
387                                         }
388                                         else
389                                                 delete gl;
390                                 }
391                                 return 1;
392                         }
393                 }
394                 return 0;
395         }
396         return 0;
397 }
398
399 void FilterBase::OnRehash(User* user, const std::string &parameter)
400 {
401         ConfigReader* MyConf = new ConfigReader(ServerInstance);
402         std::vector<std::string>().swap(exemptfromfilter);
403         for (int index = 0; index < MyConf->Enumerate("exemptfromfilter"); ++index)
404         {
405                 std::string chan = MyConf->ReadValue("exemptfromfilter", "channel", index);
406                 if (!chan.empty()) {
407                         exemptfromfilter.push_back(chan);
408                 }
409         }
410         std::string newrxengine = MyConf->ReadValue("filteropts", "engine", 0);
411         if (!RegexEngine.empty())
412         {
413                 if (RegexEngine == newrxengine)
414                         return;
415
416                 ServerInstance->SNO->WriteToSnoMask('A', "Dumping all filters due to regex engine change (was '%s', now '%s')", RegexEngine.c_str(), newrxengine.c_str());
417                 //ServerInstance->XLines->DelAll("R");
418         }
419         rxengine = NULL;
420
421         RegexEngine = newrxengine;
422         modulelist* ml = ServerInstance->Modules->FindInterface("RegularExpression");
423         if (ml)
424         {
425                 for (modulelist::iterator i = ml->begin(); i != ml->end(); ++i)
426                 {
427                         if (RegexNameRequest(this, *i).Send() == newrxengine)
428                         {
429                                 ServerInstance->SNO->WriteToSnoMask('A', "Filter now using engine '%s'", RegexEngine.c_str());
430                                 rxengine = *i;
431                         }
432                 }
433         }
434         if (!rxengine)
435         {
436                 ServerInstance->SNO->WriteToSnoMask('A', "WARNING: Regex engine '%s' is not loaded - Filter functionality disabled until this is corrected.", RegexEngine.c_str());
437         }
438
439         delete MyConf;
440 }
441
442 void FilterBase::OnLoadModule(Module* mod, const std::string& name)
443 {
444         if (ServerInstance->Modules->ModuleHasInterface(mod, "RegularExpression"))
445         {
446                 std::string rxname = RegexNameRequest(this, mod).Send();
447                 if (rxname == RegexEngine)
448                 {
449                         rxengine = mod;
450                         /* Force a rehash to make sure that any filters that couldnt be applied from the conf
451                          * on startup or on load are applied right now.
452                          */
453                         ConfigReader Config(ServerInstance);
454                         ServerInstance->SNO->WriteToSnoMask('A', "Found and activated regex module '%s' for m_filter.so.", RegexEngine.c_str());
455                         ReadFilters(Config);
456                 }
457         }
458 }
459
460
461 Version FilterBase::GetVersion()
462 {
463         return Version("$Id$", VF_VENDOR | VF_COMMON, API_VERSION);
464 }
465
466
467 std::string FilterBase::EncodeFilter(FilterResult* filter)
468 {
469         std::ostringstream stream;
470         std::string x = filter->freeform;
471
472         /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
473         for (std::string::iterator n = x.begin(); n != x.end(); n++)
474                 if (*n == ' ')
475                         *n = '\7';
476
477         stream << x << " " << filter->action << " " << (filter->flags.empty() ? "-" : filter->flags) << " " << filter->gline_time << " :" << filter->reason;
478         return stream.str();
479 }
480
481 FilterResult FilterBase::DecodeFilter(const std::string &data)
482 {
483         FilterResult res;
484         irc::tokenstream tokens(data);
485         tokens.GetToken(res.freeform);
486         tokens.GetToken(res.action);
487         tokens.GetToken(res.flags);
488         if (res.flags == "-")
489                 res.flags = "";
490         res.FillFlags(res.flags);
491         tokens.GetToken(res.gline_time);
492         tokens.GetToken(res.reason);
493
494         /* Hax to allow spaces in the freeform without changing the design of the irc protocol */
495         for (std::string::iterator n = res.freeform.begin(); n != res.freeform.end(); n++)
496                 if (*n == '\7')
497                         *n = ' ';
498
499         return res;
500 }
501
502 void FilterBase::OnSyncOtherMetaData(Module* proto, void* opaque, bool displayable)
503 {
504         this->SyncFilters(proto, opaque);
505 }
506
507 void FilterBase::SendFilter(Module* proto, void* opaque, FilterResult* iter)
508 {
509         proto->ProtoSendMetaData(opaque, TYPE_OTHER, NULL, "filter", EncodeFilter(iter));
510 }
511
512 void FilterBase::OnDecodeMetaData(int target_type, void* target, const std::string &extname, const std::string &extdata)
513 {
514         if ((target_type == TYPE_OTHER) && (extname == "filter"))
515         {
516                 FilterResult data = DecodeFilter(extdata);
517                 this->AddFilter(data.freeform, data.action, data.reason, data.gline_time, data.flags);
518         }
519 }
520
521 class ImplFilter : public FilterResult
522 {
523  public:
524         Regex* regex;
525
526         ImplFilter(Module* mymodule, const std::string &rea, const std::string &act, long glinetime, const std::string &pat, const std::string &flgs)
527                 : FilterResult(pat, rea, act, glinetime, flgs)
528         {
529                 if (!rxengine)
530                         throw ModuleException("Regex module implementing '"+RegexEngine+"' is not loaded!");
531
532                 regex = RegexFactoryRequest(mymodule, rxengine, pat).Create();
533         }
534
535         ImplFilter()
536         {
537         }
538 };
539
540 class ModuleFilter : public FilterBase
541 {
542         std::vector<ImplFilter> filters;
543         const char *error;
544         int erroffset;
545         ImplFilter fr;
546
547  public:
548         ModuleFilter(InspIRCd* Me)
549         : FilterBase(Me, "m_filter.so")
550         {
551                 OnRehash(NULL,"");
552         }
553
554         virtual ~ModuleFilter()
555         {
556         }
557
558         virtual FilterResult* FilterMatch(User* user, const std::string &text, int flgs)
559         {
560                 for (std::vector<ImplFilter>::iterator index = filters.begin(); index != filters.end(); index++)
561                 {
562                         /* Skip ones that dont apply to us */
563                         if (!FilterBase::AppliesToMe(user, dynamic_cast<FilterResult*>(&(*index)), flgs))
564                                 continue;
565
566                         //ServerInstance->Logs->Log("m_filter", DEBUG, "Match '%s' against '%s'", text.c_str(), index->freeform.c_str());
567                         if (index->regex->Matches(text))
568                         {
569                                 //ServerInstance->Logs->Log("m_filter", DEBUG, "MATCH");
570                                 fr = *index;
571                                 if (index != filters.begin())
572                                 {
573                                         /* Move to head of list for efficiency */
574                                         filters.erase(index);
575                                         filters.insert(filters.begin(), fr);
576                                 }
577                                 return &fr;
578                         }
579                         //ServerInstance->Logs->Log("m_filter", DEBUG, "NO MATCH");
580                 }
581                 return NULL;
582         }
583
584         virtual bool DeleteFilter(const std::string &freeform)
585         {
586                 for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
587                 {
588                         if (i->freeform == freeform)
589                         {
590                                 delete i->regex;
591                                 filters.erase(i);
592                                 return true;
593                         }
594                 }
595                 return false;
596         }
597
598         virtual void SyncFilters(Module* proto, void* opaque)
599         {
600                 for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
601                 {
602                         this->SendFilter(proto, opaque, &(*i));
603                 }
604         }
605
606         virtual std::pair<bool, std::string> AddFilter(const std::string &freeform, const std::string &type, const std::string &reason, long duration, const std::string &flgs)
607         {
608                 for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
609                 {
610                         if (i->freeform == freeform)
611                         {
612                                 return std::make_pair(false, "Filter already exists");
613                         }
614                 }
615
616                 try
617                 {
618                         filters.push_back(ImplFilter(this, reason, type, duration, freeform, flgs));
619                 }
620                 catch (ModuleException &e)
621                 {
622                         ServerInstance->Logs->Log("m_filter", DEFAULT, "Error in regular expression '%s': %s", freeform.c_str(), e.GetReason());
623                         return std::make_pair(false, e.GetReason());
624                 }
625                 return std::make_pair(true, "");
626         }
627
628         virtual void OnRehash(User* user, const std::string &parameter)
629         {
630                 ConfigReader MyConf(ServerInstance);
631                 FilterBase::OnRehash(user, parameter);
632                 ReadFilters(MyConf);
633         }
634
635         void ReadFilters(ConfigReader &MyConf)
636         {
637                 for (int index = 0; index < MyConf.Enumerate("keyword"); index++)
638                 {
639                         this->DeleteFilter(MyConf.ReadValue("keyword", "pattern", index));
640
641                         std::string pattern = MyConf.ReadValue("keyword", "pattern", index);
642                         std::string reason = MyConf.ReadValue("keyword", "reason", index);
643                         std::string action = MyConf.ReadValue("keyword", "action", index);
644                         std::string flgs = MyConf.ReadValue("keyword", "flags", index);
645                         long gline_time = ServerInstance->Duration(MyConf.ReadValue("keyword", "duration", index));
646                         if (action.empty())
647                                 action = "none";
648                         if (flgs.empty())
649                                 flgs = "*";
650
651                         try
652                         {
653                                 filters.push_back(ImplFilter(this, reason, action, gline_time, pattern, flgs));
654                                 ServerInstance->Logs->Log("m_filter", DEFAULT, "Regular expression %s loaded.", pattern.c_str());
655                         }
656                         catch (ModuleException &e)
657                         {
658                                 ServerInstance->Logs->Log("m_filter", DEFAULT, "Error in regular expression '%s': %s", pattern.c_str(), e.GetReason());
659                         }
660                 }
661         }
662
663         virtual int OnStats(char symbol, User* user, string_list &results)
664         {
665                 if (symbol == 's')
666                 {
667                         std::string sn = ServerInstance->Config->ServerName;
668                         for (std::vector<ImplFilter>::iterator i = filters.begin(); i != filters.end(); i++)
669                         {
670                                 results.push_back(sn+" 223 "+user->nick+" :"+RegexEngine+":"+i->freeform+" "+i->flags+" "+i->action+" "+ConvToStr(i->gline_time)+" :"+i->reason);
671                         }
672                         for (std::vector<std::string>::iterator i = exemptfromfilter.begin(); i != exemptfromfilter.end(); ++i)
673                         {
674                                 results.push_back(sn+" 223 "+user->nick+" :EXEMPT "+(*i));
675                         }
676                 }
677                 return 0;
678         }
679 };
680
681 MODULE_INIT(ModuleFilter)
682