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