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