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