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