]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_repeat.cpp
5be0fd6227e37f04067255de8c29a1dd393433d6
[user/henk/code/inspircd.git] / src / modules / m_repeat.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2013 Daniel Vassdal <shutter@canternet.org>
5  *
6  * This file is part of InspIRCd.  InspIRCd is free software: you can
7  * redistribute it and/or modify it under the terms of the GNU General Public
8  * License as published by the Free Software Foundation, version 2.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19
20 #include "inspircd.h"
21
22 #ifdef _WIN32
23 // windows.h defines this
24 #undef min
25 #endif
26
27 class RepeatMode : public ModeHandler
28 {
29  private:
30         struct RepeatItem
31         {
32                 time_t ts;
33                 std::string line;
34                 RepeatItem(time_t TS, const std::string& Line) : ts(TS), line(Line) { }
35         };
36
37         typedef std::deque<RepeatItem> RepeatItemList;
38
39         struct MemberInfo
40         {
41                 RepeatItemList ItemList;
42                 unsigned int Counter;
43                 MemberInfo() : Counter(0) {}
44         };
45
46         struct ModuleSettings
47         {
48                 unsigned int MaxLines;
49                 unsigned int MaxSecs;
50                 unsigned int MaxBacklog;
51                 unsigned int MaxDiff;
52                 ModuleSettings() : MaxLines(0), MaxSecs(0), MaxBacklog(0), MaxDiff() { }
53         };
54
55         std::vector<std::vector<unsigned int> > mx;
56         ModuleSettings ms;
57
58         bool CompareLines(const std::string& message, const std::string& historyline, unsigned int trigger)
59         {
60                 if (trigger)
61                         return (Levenshtein(message, historyline) <= trigger);
62                 else
63                         return (message == historyline);
64         }
65
66         unsigned int Levenshtein(const std::string& s1, const std::string& s2)
67         {
68                 unsigned int l1 = s1.size();
69                 unsigned int l2 = s2.size();
70
71                 for (unsigned int i = 0; i <= l1; i++)
72                         mx[i][0] = i;
73                 for (unsigned int i = 0; i <= l2; i++)
74                         mx[0][i] = i;
75                 for (unsigned int i = 1; i <= l1; i++)
76                         for (unsigned int j = 1; j <= l2; j++)
77                                 mx[i][j] = std::min(std::min(mx[i - 1][j] + 1, mx[i][j - 1] + 1), mx[i - 1][j - 1] + (s1[i - 1] == s2[j - 1] ? 0 : 1));
78                 return (mx[l1][l2]);
79         }
80
81  public:
82         enum RepeatAction
83         {
84                 ACT_KICK,
85                 ACT_BLOCK,
86                 ACT_BAN
87         };
88
89         class ChannelSettings
90         {
91          public:
92                 RepeatAction Action;
93                 unsigned int Backlog;
94                 unsigned int Lines;
95                 unsigned int Diff;
96                 unsigned int Seconds;
97
98                 std::string serialize()
99                 {
100                         std::string ret = ((Action == ACT_BAN) ? "*" : (Action == ACT_BLOCK ? "~" : "")) + ConvToStr(Lines) + ":" + ConvToStr(Seconds);
101                         if (Diff)
102                         {
103                                 ret += ":" + ConvToStr(Diff);
104                                 if (Backlog)
105                                         ret += ":" + ConvToStr(Backlog);
106                         }
107                         return ret;
108                 }
109         };
110
111         SimpleExtItem<MemberInfo> MemberInfoExt;
112         SimpleExtItem<ChannelSettings> ChanSet;
113
114         RepeatMode(Module* Creator)
115                 : ModeHandler(Creator, "repeat", 'E', PARAM_SETONLY, MODETYPE_CHANNEL)
116                 , MemberInfoExt("repeat_memb", Creator)
117                 , ChanSet("repeat", Creator)
118         {
119         }
120
121         ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding)
122         {
123                 if (!adding)
124                 {
125                         if (!channel->IsModeSet(this))
126                                 return MODEACTION_DENY;
127
128                         // Unset the per-membership extension when the mode is removed
129                         const UserMembList* users = channel->GetUsers();
130                         for (UserMembCIter i = users->begin(); i != users->end(); ++i)
131                                 MemberInfoExt.unset(i->second);
132
133                         ChanSet.unset(channel);
134                         channel->SetModeParam(this, "");
135                         return MODEACTION_ALLOW;
136                 }
137
138                 if (channel->GetModeParameter(this) == parameter)
139                         return MODEACTION_DENY;
140
141                 ChannelSettings settings;
142                 if (!ParseSettings(source, parameter, settings))
143                 {
144                         source->WriteNotice("*** Invalid syntax. Syntax is {[~*]}[lines]:[time]{:[difference]}{:[backlog]}");
145                         return MODEACTION_DENY;
146                 }
147
148                 if ((settings.Backlog > 0) && (settings.Lines > settings.Backlog))
149                 {
150                         source->WriteNotice("*** You can't set needed lines higher than backlog");
151                         return MODEACTION_DENY;
152                 }
153
154                 LocalUser* localsource = IS_LOCAL(source);
155                 if ((localsource) && (!ValidateSettings(localsource, settings)))
156                         return MODEACTION_DENY;
157
158                 ChanSet.set(channel, settings);
159                 channel->SetModeParam(this, parameter);
160
161                 return MODEACTION_ALLOW;
162         }
163
164         bool MatchLine(Membership* memb, ChannelSettings* rs, std::string message)
165         {
166                 // If the message is larger than whatever size it's set to,
167                 // let's pretend it isn't. If the first 512 (def. setting) match, it's probably spam.
168                 if (message.size() > mx.size())
169                         message.erase(mx.size());
170
171                 MemberInfo* rp = MemberInfoExt.get(memb);
172                 if (!rp)
173                 {
174                         rp = new MemberInfo;
175                         MemberInfoExt.set(memb, rp);
176                 }
177
178                 unsigned int matches = 0;
179                 if (!rs->Backlog)
180                         matches = rp->Counter;
181
182                 RepeatItemList& items = rp->ItemList;
183                 const unsigned int trigger = (message.size() * rs->Diff / 100);
184                 const time_t now = ServerInstance->Time();
185
186                 std::transform(message.begin(), message.end(), message.begin(), ::tolower);
187
188                 for (std::deque<RepeatItem>::iterator it = items.begin(); it != items.end(); ++it)
189                 {
190                         if (it->ts < now)
191                         {
192                                 items.erase(it, items.end());
193                                 matches = 0;
194                                 break;
195                         }
196
197                         if (CompareLines(message, it->line, trigger))
198                         {
199                                 if (++matches >= rs->Lines)
200                                 {
201                                         if (rs->Action != ACT_BLOCK)
202                                                 rp->Counter = 0;
203                                         return true;
204                                 }
205                         }
206                         else if ((ms.MaxBacklog == 0) || (rs->Backlog == 0))
207                         {
208                                 matches = 0;
209                                 items.clear();
210                                 break;
211                         }
212                 }
213
214                 unsigned int max_items = (rs->Backlog ? rs->Backlog : 1);
215                 if (items.size() >= max_items)
216                         items.pop_back();
217
218                 items.push_front(RepeatItem(now + rs->Seconds, message));
219                 rp->Counter = matches;
220                 return false;
221         }
222
223         void Resize(size_t size)
224         {
225                 if (size == mx.size())
226                         return;
227                 mx.resize(size);
228
229                 if (mx.size() > size)
230                 {
231                         mx.resize(size);
232                         for (unsigned int i = 0; i < mx.size(); i++)
233                                 mx[i].resize(size);
234                 }
235                 else
236                 {
237                         for (unsigned int i = 0; i < mx.size(); i++)
238                         {
239                                 mx[i].resize(size);
240                                 std::vector<unsigned int>(mx[i]).swap(mx[i]);
241                         }
242                         std::vector<std::vector<unsigned int> >(mx).swap(mx);
243                 }
244         }
245
246         void ReadConfig()
247         {
248                 ConfigTag* conf = ServerInstance->Config->ConfValue("repeat");
249                 ms.MaxLines = conf->getInt("maxlines", 20);
250                 ms.MaxBacklog = conf->getInt("maxbacklog", 20);
251                 ms.MaxSecs = conf->getInt("maxsecs", 0);
252
253                 ms.MaxDiff = conf->getInt("maxdistance", 50);
254                 if (ms.MaxDiff > 100)
255                         ms.MaxDiff = 100;
256
257                 unsigned int newsize = conf->getInt("size", 512);
258                 if (newsize > ServerInstance->Config->Limits.MaxLine)
259                         newsize = ServerInstance->Config->Limits.MaxLine;
260                 Resize(newsize);
261         }
262
263         std::string GetModuleSettings() const
264         {
265                 return ConvToStr(ms.MaxLines) + ":" + ConvToStr(ms.MaxSecs) + ":" + ConvToStr(ms.MaxDiff) + ":" + ConvToStr(ms.MaxBacklog);
266         }
267
268  private:
269         bool ParseSettings(User* source, std::string& parameter, ChannelSettings& settings)
270         {
271                 irc::sepstream stream(parameter, ':');
272                 std::string     item;
273                 if (!stream.GetToken(item))
274                         // Required parameter missing
275                         return false;
276
277                 if ((item[0] == '*') || (item[0] == '~'))
278                 {
279                         settings.Action = ((item[0] == '*') ? ACT_BAN : ACT_BLOCK);
280                         item.erase(item.begin());
281                 }
282                 else
283                         settings.Action = ACT_KICK;
284
285                 if ((settings.Lines = ConvToInt(item)) == 0)
286                         return false;
287
288                 if ((!stream.GetToken(item)) || ((settings.Seconds = InspIRCd::Duration(item)) == 0))
289                         // Required parameter missing
290                         return false;
291
292                 // The diff and backlog parameters are optional
293                 settings.Diff = settings.Backlog = 0;
294                 if (stream.GetToken(item))
295                 {
296                         // There is a diff parameter, see if it's valid (> 0)
297                         if ((settings.Diff = ConvToInt(item)) == 0)
298                                 return false;
299
300                         if (stream.GetToken(item))
301                         {
302                                 // There is a backlog parameter, see if it's valid
303                                 if ((settings.Backlog = ConvToInt(item)) == 0)
304                                         return false;
305
306                                 // If there are still tokens, then it's invalid because we allow only 4
307                                 if (stream.GetToken(item))
308                                         return false;
309                         }
310                 }
311
312                 parameter = settings.serialize();
313                 return true;
314         }
315
316         bool ValidateSettings(LocalUser* source, const ChannelSettings& settings)
317         {
318                 if (settings.Backlog && !ms.MaxBacklog)
319                 {
320                         source->WriteNotice("*** The server administrator has disabled backlog matching");
321                         return false;
322                 }
323
324                 if (settings.Diff)
325                 {
326                         if (settings.Diff > ms.MaxDiff)
327                         {
328                                 if (ms.MaxDiff == 0)
329                                         source->WriteNotice("*** The server administrator has disabled matching on edit distance");
330                                 else
331                                         source->WriteNotice("*** The distance you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxDiff));
332                                 return false;
333                         }
334
335                         if (ms.MaxLines && settings.Lines > ms.MaxLines)
336                         {
337                                 source->WriteNotice("*** The line number you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxLines));
338                                 return false;
339                         }
340
341                         if (ms.MaxSecs && settings.Seconds > ms.MaxSecs)
342                         {
343                                 source->WriteNotice("*** The seconds you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxSecs));
344                                 return false;
345                         }
346                 }
347
348                 return true;
349         }
350 };
351
352 class RepeatModule : public Module
353 {
354         RepeatMode rm;
355
356  public:
357         RepeatModule() : rm(this) {}
358
359         void init() CXX11_OVERRIDE
360         {
361                 ServerInstance->Modules->AddService(rm);
362                 ServerInstance->Modules->AddService(rm.ChanSet);
363                 ServerInstance->Modules->AddService(rm.MemberInfoExt);
364                 Implementation eventlist[] = { I_OnUserPreMessage, I_OnRehash };
365                 ServerInstance->Modules->Attach(eventlist, this, sizeof(eventlist)/sizeof(Implementation));
366                 rm.ReadConfig();
367         }
368
369         void OnRehash(User* user) CXX11_OVERRIDE
370         {
371                 rm.ReadConfig();
372         }
373
374         ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE
375         {
376                 if (target_type != TYPE_CHANNEL || !IS_LOCAL(user))
377                         return MOD_RES_PASSTHRU;
378
379                 Membership* memb = ((Channel*)dest)->GetUser(user);
380                 if (!memb || !memb->chan->IsModeSet(&rm))
381                         return MOD_RES_PASSTHRU;
382
383                 if (ServerInstance->OnCheckExemption(user, memb->chan, "repeat") == MOD_RES_ALLOW)
384                         return MOD_RES_PASSTHRU;
385
386                 RepeatMode::ChannelSettings* settings = rm.ChanSet.get(memb->chan);
387                 if (!settings)
388                         return MOD_RES_PASSTHRU;
389
390                 if (rm.MatchLine(memb, settings, text))
391                 {
392                         if (settings->Action == RepeatMode::ACT_BLOCK)
393                         {
394                                 user->WriteNotice("*** This line is too similiar to one of your last lines.");
395                                 return MOD_RES_DENY;
396                         }
397
398                         if (settings->Action == RepeatMode::ACT_BAN)
399                         {
400                                 std::vector<std::string> parameters;
401                                 parameters.push_back(memb->chan->name);
402                                 parameters.push_back("+b");
403                                 parameters.push_back("*!*@" + user->dhost);
404                                 ServerInstance->SendGlobalMode(parameters, ServerInstance->FakeClient);
405                         }
406
407                         memb->chan->KickUser(ServerInstance->FakeClient, user, "Repeat flood");
408                         return MOD_RES_DENY;
409                 }
410                 return MOD_RES_PASSTHRU;
411         }
412
413         void Prioritize() CXX11_OVERRIDE
414         {
415                 ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST);
416         }
417
418         Version GetVersion() CXX11_OVERRIDE
419         {
420                 return Version("Provides the +E channel mode - for blocking of similiar messages", VF_COMMON|VF_VENDOR, rm.GetModuleSettings());
421         }
422 };
423
424 MODULE_INIT(RepeatModule)