]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_repeat.cpp
Allow Timers to delete themselves in Tick()
[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 class RepeatMode : public ModeHandler
23 {
24  private:
25         struct RepeatItem
26         {
27                 time_t ts;
28                 std::string line;
29                 RepeatItem(time_t TS, const std::string& Line) : ts(TS), line(Line) { }
30         };
31
32         typedef std::deque<RepeatItem> RepeatItemList;
33
34         struct MemberInfo
35         {
36                 RepeatItemList ItemList;
37                 unsigned int Counter;
38                 MemberInfo() : Counter(0) {}
39         };
40
41         struct ModuleSettings
42         {
43                 unsigned int MaxLines;
44                 unsigned int MaxSecs;
45                 unsigned int MaxBacklog;
46                 unsigned int MaxDiff;
47                 unsigned int MaxMessageSize;
48                 ModuleSettings() : MaxLines(0), MaxSecs(0), MaxBacklog(0), MaxDiff() { }
49         };
50
51         std::vector<unsigned int> mx[2];
52         ModuleSettings ms;
53
54         bool CompareLines(const std::string& message, const std::string& historyline, unsigned int trigger)
55         {
56                 if (message == historyline)
57                         return true;
58                 else if (trigger)
59                         return (Levenshtein(message, historyline) <= trigger);
60
61                 return false;
62         }
63
64         unsigned int Levenshtein(const std::string& s1, const std::string& s2)
65         {
66                 unsigned int l1 = s1.size();
67                 unsigned int l2 = s2.size();
68
69                 for (unsigned int i = 0; i < l2; i++)
70                         mx[0][i] = i;
71                 for (unsigned int i = 0; i < l1; i++)
72                 {
73                         mx[1][0] = i + 1;
74                         for (unsigned int j = 0; j < l2; j++)
75                     mx[1][j + 1] = std::min(std::min(mx[1][j] + 1, mx[0][j + 1] + 1), mx[0][j] + ((s1[i] == s2[j]) ? 0 : 1));
76
77                         mx[0].swap(mx[1]);
78                 }
79                 return mx[0][l2];
80         }
81
82  public:
83         enum RepeatAction
84         {
85                 ACT_KICK,
86                 ACT_BLOCK,
87                 ACT_BAN
88         };
89
90         class ChannelSettings
91         {
92          public:
93                 RepeatAction Action;
94                 unsigned int Backlog;
95                 unsigned int Lines;
96                 unsigned int Diff;
97                 unsigned int Seconds;
98
99                 std::string serialize()
100                 {
101                         std::string ret = ((Action == ACT_BAN) ? "*" : (Action == ACT_BLOCK ? "~" : "")) + ConvToStr(Lines) + ":" + ConvToStr(Seconds);
102                         if (Diff)
103                         {
104                                 ret += ":" + ConvToStr(Diff);
105                                 if (Backlog)
106                                         ret += ":" + ConvToStr(Backlog);
107                         }
108                         return ret;
109                 }
110         };
111
112         SimpleExtItem<MemberInfo> MemberInfoExt;
113         SimpleExtItem<ChannelSettings> ChanSet;
114
115         RepeatMode(Module* Creator)
116                 : ModeHandler(Creator, "repeat", 'E', PARAM_SETONLY, MODETYPE_CHANNEL)
117                 , MemberInfoExt("repeat_memb", Creator)
118                 , ChanSet("repeat", Creator)
119         {
120         }
121
122         ModeAction OnModeChange(User* source, User* dest, Channel* channel, std::string& parameter, bool adding)
123         {
124                 if (!adding)
125                 {
126                         if (!channel->IsModeSet(this))
127                                 return MODEACTION_DENY;
128
129                         // Unset the per-membership extension when the mode is removed
130                         const UserMembList* users = channel->GetUsers();
131                         for (UserMembCIter i = users->begin(); i != users->end(); ++i)
132                                 MemberInfoExt.unset(i->second);
133
134                         ChanSet.unset(channel);
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
160                 return MODEACTION_ALLOW;
161         }
162
163         bool MatchLine(Membership* memb, ChannelSettings* rs, std::string message)
164         {
165                 // If the message is larger than whatever size it's set to,
166                 // let's pretend it isn't. If the first 512 (def. setting) match, it's probably spam.
167                 if (message.size() > ms.MaxMessageSize)
168                         message.erase(ms.MaxMessageSize);
169
170                 MemberInfo* rp = MemberInfoExt.get(memb);
171                 if (!rp)
172                 {
173                         rp = new MemberInfo;
174                         MemberInfoExt.set(memb, rp);
175                 }
176
177                 unsigned int matches = 0;
178                 if (!rs->Backlog)
179                         matches = rp->Counter;
180
181                 RepeatItemList& items = rp->ItemList;
182                 const unsigned int trigger = (message.size() * rs->Diff / 100);
183                 const time_t now = ServerInstance->Time();
184
185                 std::transform(message.begin(), message.end(), message.begin(), ::tolower);
186
187                 for (std::deque<RepeatItem>::iterator it = items.begin(); it != items.end(); ++it)
188                 {
189                         if (it->ts < now)
190                         {
191                                 items.erase(it, items.end());
192                                 matches = 0;
193                                 break;
194                         }
195
196                         if (CompareLines(message, it->line, trigger))
197                         {
198                                 if (++matches >= rs->Lines)
199                                 {
200                                         if (rs->Action != ACT_BLOCK)
201                                                 rp->Counter = 0;
202                                         return true;
203                                 }
204                         }
205                         else if ((ms.MaxBacklog == 0) || (rs->Backlog == 0))
206                         {
207                                 matches = 0;
208                                 items.clear();
209                                 break;
210                         }
211                 }
212
213                 unsigned int max_items = (rs->Backlog ? rs->Backlog : 1);
214                 if (items.size() >= max_items)
215                         items.pop_back();
216
217                 items.push_front(RepeatItem(now + rs->Seconds, message));
218                 rp->Counter = matches;
219                 return false;
220         }
221
222         void Resize(size_t size)
223         {
224                 size_t newsize = size+1;
225                 if (newsize <= mx[0].size())
226                         return;
227                 ms.MaxMessageSize = size;
228                 mx[0].resize(newsize);
229                 mx[1].resize(newsize);
230         }
231
232         void ReadConfig()
233         {
234                 ConfigTag* conf = ServerInstance->Config->ConfValue("repeat");
235                 ms.MaxLines = conf->getInt("maxlines", 20);
236                 ms.MaxBacklog = conf->getInt("maxbacklog", 20);
237                 ms.MaxSecs = conf->getInt("maxsecs", 0);
238
239                 ms.MaxDiff = conf->getInt("maxdistance", 50);
240                 if (ms.MaxDiff > 100)
241                         ms.MaxDiff = 100;
242
243                 unsigned int newsize = conf->getInt("size", 512);
244                 if (newsize > ServerInstance->Config->Limits.MaxLine)
245                         newsize = ServerInstance->Config->Limits.MaxLine;
246                 Resize(newsize);
247         }
248
249         std::string GetModuleSettings() const
250         {
251                 return ConvToStr(ms.MaxLines) + ":" + ConvToStr(ms.MaxSecs) + ":" + ConvToStr(ms.MaxDiff) + ":" + ConvToStr(ms.MaxBacklog);
252         }
253
254  private:
255         bool ParseSettings(User* source, std::string& parameter, ChannelSettings& settings)
256         {
257                 irc::sepstream stream(parameter, ':');
258                 std::string     item;
259                 if (!stream.GetToken(item))
260                         // Required parameter missing
261                         return false;
262
263                 if ((item[0] == '*') || (item[0] == '~'))
264                 {
265                         settings.Action = ((item[0] == '*') ? ACT_BAN : ACT_BLOCK);
266                         item.erase(item.begin());
267                 }
268                 else
269                         settings.Action = ACT_KICK;
270
271                 if ((settings.Lines = ConvToInt(item)) == 0)
272                         return false;
273
274                 if ((!stream.GetToken(item)) || ((settings.Seconds = InspIRCd::Duration(item)) == 0))
275                         // Required parameter missing
276                         return false;
277
278                 // The diff and backlog parameters are optional
279                 settings.Diff = settings.Backlog = 0;
280                 if (stream.GetToken(item))
281                 {
282                         // There is a diff parameter, see if it's valid (> 0)
283                         if ((settings.Diff = ConvToInt(item)) == 0)
284                                 return false;
285
286                         if (stream.GetToken(item))
287                         {
288                                 // There is a backlog parameter, see if it's valid
289                                 if ((settings.Backlog = ConvToInt(item)) == 0)
290                                         return false;
291
292                                 // If there are still tokens, then it's invalid because we allow only 4
293                                 if (stream.GetToken(item))
294                                         return false;
295                         }
296                 }
297
298                 parameter = settings.serialize();
299                 return true;
300         }
301
302         bool ValidateSettings(LocalUser* source, const ChannelSettings& settings)
303         {
304                 if (settings.Backlog && !ms.MaxBacklog)
305                 {
306                         source->WriteNotice("*** The server administrator has disabled backlog matching");
307                         return false;
308                 }
309
310                 if (settings.Diff)
311                 {
312                         if (settings.Diff > ms.MaxDiff)
313                         {
314                                 if (ms.MaxDiff == 0)
315                                         source->WriteNotice("*** The server administrator has disabled matching on edit distance");
316                                 else
317                                         source->WriteNotice("*** The distance you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxDiff));
318                                 return false;
319                         }
320
321                         if (ms.MaxLines && settings.Lines > ms.MaxLines)
322                         {
323                                 source->WriteNotice("*** The line number you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxLines));
324                                 return false;
325                         }
326
327                         if (ms.MaxSecs && settings.Seconds > ms.MaxSecs)
328                         {
329                                 source->WriteNotice("*** The seconds you specified is too great. Maximum allowed is " + ConvToStr(ms.MaxSecs));
330                                 return false;
331                         }
332                 }
333
334                 return true;
335         }
336 };
337
338 class RepeatModule : public Module
339 {
340         RepeatMode rm;
341
342  public:
343         RepeatModule() : rm(this) {}
344
345         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
346         {
347                 rm.ReadConfig();
348         }
349
350         ModResult OnUserPreMessage(User* user, void* dest, int target_type, std::string& text, char status, CUList& exempt_list, MessageType msgtype) CXX11_OVERRIDE
351         {
352                 if (target_type != TYPE_CHANNEL || !IS_LOCAL(user))
353                         return MOD_RES_PASSTHRU;
354
355                 Membership* memb = ((Channel*)dest)->GetUser(user);
356                 if (!memb || !memb->chan->IsModeSet(&rm))
357                         return MOD_RES_PASSTHRU;
358
359                 if (ServerInstance->OnCheckExemption(user, memb->chan, "repeat") == MOD_RES_ALLOW)
360                         return MOD_RES_PASSTHRU;
361
362                 RepeatMode::ChannelSettings* settings = rm.ChanSet.get(memb->chan);
363                 if (!settings)
364                         return MOD_RES_PASSTHRU;
365
366                 if (rm.MatchLine(memb, settings, text))
367                 {
368                         if (settings->Action == RepeatMode::ACT_BLOCK)
369                         {
370                                 user->WriteNotice("*** This line is too similiar to one of your last lines.");
371                                 return MOD_RES_DENY;
372                         }
373
374                         if (settings->Action == RepeatMode::ACT_BAN)
375                         {
376                                 std::vector<std::string> parameters;
377                                 parameters.push_back(memb->chan->name);
378                                 parameters.push_back("+b");
379                                 parameters.push_back("*!*@" + user->dhost);
380                                 ServerInstance->Modes->Process(parameters, ServerInstance->FakeClient);
381                         }
382
383                         memb->chan->KickUser(ServerInstance->FakeClient, user, "Repeat flood");
384                         return MOD_RES_DENY;
385                 }
386                 return MOD_RES_PASSTHRU;
387         }
388
389         void Prioritize() CXX11_OVERRIDE
390         {
391                 ServerInstance->Modules->SetPriority(this, I_OnUserPreMessage, PRIORITY_LAST);
392         }
393
394         Version GetVersion() CXX11_OVERRIDE
395         {
396                 return Version("Provides the +E channel mode - for blocking of similiar messages", VF_COMMON|VF_VENDOR, rm.GetModuleSettings());
397         }
398 };
399
400 MODULE_INIT(RepeatModule)