1 /*
2 * flood.c: handle channel flooding.
3 *
4 * Copyright (c) 1990-1992 Tomi Ollila
5 * Copyright 1997, 2003 EPIC Software Labs
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notices, the above paragraph (the one permitting redistribution),
15 * this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. The names of the author(s) may not be used to endorse or promote
18 * products derived from this software without specific prior written
19 * permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33 /*
34 * This attempts to give you some protection from flooding. Basically, it keeps
35 * track of how far apart (timewise) messages come in from different people.
36 * If a single nickname sends more than 3 messages in a row in under a
37 * second, this is considered flooding. It then activates the ON FLOOD with
38 * the nickname and type (appropriate for use with IGNORE).
39 */
40
41 #include "irc.h"
42 #include "flood.h"
43 #include "hook.h"
44 #include "ignore.h"
45 #include "ircaux.h"
46 #include "output.h"
47 #include "server.h"
48 #include "vars.h"
49 #include "functions.h"
50 #include "lastlog.h"
51 #include "window.h"
52 #include "reg.h"
53
54 typedef struct flood_stru
55 {
56 char *nuh;
57 char *channel;
58 int server;
59
60 int level;
61 long cnt;
62 Timeval start;
63 int floods;
64 } Flooding;
65
66 static Flooding *flood = (Flooding *) 0;
67 int users = 0;
68
69
70 /*
71 * If flood_maskuser is 0, proceed normally. If 2, keep
72 * track of the @host only. If 1, keep track of the U@H
73 * only if it begins with an alphanum, and use the @host
74 * otherwise.
75 */
normalize_nuh(const char * nuh)76 static const char * normalize_nuh (const char *nuh)
77 {
78 int maskuser = get_int_var(FLOOD_MASKUSER_VAR);
79
80 if (maskuser == 0) {
81 } else if (!nuh) {
82 } else if (maskuser == 1 && isalnum(*nuh)) {
83 } else {
84 char prefix = *nuh;
85 char *nnuh = strrchr(nuh, '@');
86 if (nnuh - nuh >= 2) {
87 *--nnuh = *star;
88 if (maskuser == 1)
89 *--nnuh = prefix;
90 nuh = nnuh;
91 }
92 }
93
94 return nuh;
95 }
96
97 /*
98 * check_flooding: This checks for message flooding of the type specified for
99 * the given nickname. This is described above. This will return 0 if no
100 * flooding took place, or flooding is not being monitored from a certain
101 * person. It will return 1 if flooding is being check for someone and an ON
102 * FLOOD is activated.
103 */
new_check_flooding(const char * nick,const char * nuh,const char * chan,const char * line,int level)104 int new_check_flooding (const char *nick, const char *nuh, const char *chan, const char *line, int level)
105 {
106 static int pos = 0;
107 int i,
108 numusers,
109 server,
110 retval = 0;
111 Timeval right_now;
112 double diff;
113 Flooding *tmp;
114 int l;
115 char * freeit;
116
117 freeit = malloc_strdup(nuh);
118 nuh = freeit;
119
120 /*
121 * Figure out how many people we want to track
122 */
123 numusers = get_int_var(FLOOD_USERS_VAR);
124
125 /*
126 * If the number of users has changed, then resize the info array
127 */
128 if (users != numusers)
129 {
130 i = users;
131 for (i--; i >= numusers; i--)
132 {
133 new_free(&(flood[i].nuh));
134 new_free(&(flood[i].channel));
135 }
136 RESIZE(flood, Flooding, numusers);
137 for (i++; i < numusers; i++)
138 {
139 flood[i].nuh = NULL;
140 flood[i].channel = NULL;
141 flood[i].server = NOSERV;
142 flood[i].level = LEVEL_NONE;
143 flood[i].cnt = 0;
144 get_time(&(flood[i].start));
145 flood[i].floods = 0;
146 }
147 users = numusers;
148 if (users)
149 pos %= users;
150 }
151
152 /*
153 * Following 0 people turns off flood checking entirely.
154 */
155 if (numusers == 0)
156 {
157 if (flood)
158 new_free((char **)&flood);
159 users = 0;
160 new_free(&freeit);
161 return 0;
162 }
163
164 if (nuh && *nuh)
165 nuh = normalize_nuh(nuh);
166 else {
167 new_free(&freeit);
168 return 0;
169 }
170
171 /*
172 * What server are we using?
173 */
174 server = (from_server == NOSERV) ? primary_server : from_server;
175
176 /*
177 * Look in the flooding array.
178 * Find an entry that matches us:
179 * It must be the same flooding type and server.
180 * It must be for the same nickname
181 * If we're for a channel, it must also be for a channel
182 * and it must be for our channel
183 * else if we're not for a channel, it must also not be for
184 * a channel.
185 */
186 for (i = 0; i < users; i++)
187 {
188 /*
189 * Do some inexpensive tests first
190 */
191 if (level != flood[i].level)
192 continue;
193 if (server != flood[i].server)
194 continue;
195 if (!flood[i].nuh)
196 continue;
197
198 /*
199 * Must be for the person we're looking for
200 */
201 if (my_stricmp(nuh, flood[i].nuh))
202 continue;
203
204 /*
205 * Must be for a channel if we're for a channel
206 */
207 if (!!flood[i].channel != !!chan)
208 continue;
209
210 /*
211 * Must be for the channel we're looking for.
212 */
213 if (chan && my_stricmp(chan, flood[i].channel))
214 continue;
215
216 /*
217 * We have a winner!
218 */
219 break;
220 }
221
222 get_time(&right_now);
223
224 /*
225 * We didnt find anybody.
226 */
227 if (i == users)
228 {
229 /*
230 * pos points at the next insertion point in the array.
231 */
232 int old_pos = pos;
233 do {
234 pos = (0 < pos ? pos : users) - 1;
235 } while (0 < --flood[pos].floods && pos != old_pos);
236
237 tmp = flood + pos;
238 malloc_strcpy(&tmp->nuh, nuh);
239 if (chan)
240 malloc_strcpy(&tmp->channel, chan);
241 else
242 new_free(&tmp->channel);
243
244 tmp->server = server;
245 tmp->level = level;
246 tmp->cnt = 0;
247 tmp->start = right_now;
248
249 pos = (0 < old_pos ? old_pos : users) - 1;
250 new_free(&freeit);
251 return 0;
252 }
253 else
254 tmp = flood + i;
255
256 /*
257 * Has the person flooded too much?
258 */
259 if (++tmp->cnt >= get_int_var(FLOOD_AFTER_VAR))
260 {
261 float rate = get_int_var(FLOOD_RATE_VAR);
262 rate /= get_int_var(FLOOD_RATE_PER_VAR);
263 diff = time_diff(tmp->start, right_now);
264
265 if ((diff == 0.0 || tmp->cnt / diff >= rate) &&
266 (retval = do_hook(FLOOD_LIST, "%s %s %s %ld %s",
267 nick, level_to_str(tmp->level),
268 chan ? chan : "*", tmp->cnt, line)))
269 {
270 tmp->floods++;
271 l = message_from(chan, LEVEL_OTHER);
272 if (get_int_var(FLOOD_WARNING_VAR))
273 say("FLOOD: %ld %s detected from %s in %f seconds",
274 tmp->cnt+1, level_to_str(tmp->level), nick, diff);
275 pop_message_from(l);
276 }
277 else
278 {
279 /*
280 * Not really flooding -- reset back to normal.
281 */
282 tmp->cnt = 0;
283 tmp->start = right_now;
284 }
285 }
286
287 new_free(&freeit);
288
289 if (get_int_var(FLOOD_IGNORE_VAR))
290 return retval;
291 else
292 return 0;
293 }
294
check_flooding(const char * nick,const char * nuh,int mask,const char * line)295 int check_flooding (const char *nick, const char *nuh, int mask, const char *line)
296 {
297 return new_check_flooding(nick, nuh, NULL, line, mask);
298 }
299
300 /*
301 * Note: This will break whatever uses it when any of the arguments
302 * contain a double quote.
303 */
function_floodinfo(char * args)304 char * function_floodinfo (char *args)
305 {
306 char *arg;
307 char *ret = NULL;
308 size_t clue = 0;
309 Timeval right_now;
310 int i;
311 double idiff;
312
313 get_time(&right_now);
314
315 while ((arg = new_next_arg(args, &args)))
316 {
317 const char *nuh = star;
318 const char *chan = star;
319 const char *level = star;
320 int server = -1;
321 int count = 0;
322 double diff = 0;
323 double rate = 0;
324 int cless = 0, dless = 0, rless = 0;
325 char * freeme = NULL;
326
327 /*
328 * CTCP enquoting handles \\, which is what I think
329 * ce was going for here.
330 */
331 freeme = arg = transform_string_dyn("-CTCP", arg, 0, NULL);
332 if (arg && *arg)
333 GET_FUNC_ARG(nuh, arg);
334 if (arg && *arg)
335 GET_FUNC_ARG(chan, arg);
336 if (arg && *arg)
337 GET_FUNC_ARG(level, arg);
338 if (arg && *arg)
339 GET_INT_ARG(server, arg);
340 if (arg && *arg)
341 GET_INT_ARG(count, arg);
342 if (arg && *arg)
343 GET_FLOAT_ARG(diff, arg);
344 if (arg && *arg)
345 GET_FLOAT_ARG(rate, arg);
346 if (count < 0)
347 count= -count, cless++;
348 if (diff < 0)
349 diff = -diff, dless++;
350 if (rate < 0)
351 rate = -rate, rless++;
352
353 for (i = 0; i < users; i++) {
354 if (server >= 0 && flood[i].server != server) {
355 } else if (!flood[i].nuh) {
356 } else if (!wild_match(nuh, flood[i].nuh) && !wild_match(flood[i].nuh, nuh)) {
357 } else if (!wild_match(chan, flood[i].channel ? flood[i].channel : star)) {
358 } else if (!wild_match(level, level_to_str(flood[i].level))) {
359 } else if (!cless && flood[i].cnt < count) {
360 } else if ( cless && flood[i].cnt > count) {
361 } else if (!(idiff = time_diff(flood[i].start, right_now))) {
362 } else if (!dless && idiff < diff) {
363 } else if ( dless && idiff > diff) {
364 } else if (!rless && flood[i].cnt / idiff < rate) {
365 } else if ( rless && flood[i].cnt / idiff > rate) {
366 } else {
367 malloc_strcat_wordlist_c(&ret, space, "\"", &clue);
368 malloc_strcat_wordlist_c(&ret, empty_string, flood[i].nuh, &clue);
369 malloc_strcat_wordlist_c(&ret, space, flood[i].channel ? flood[i].channel : star, &clue);
370 malloc_strcat_wordlist_c(&ret, space, level_to_str(flood[i].level), &clue);
371 malloc_strcat_wordlist_c(&ret, space, ltoa(flood[i].server), &clue);
372 malloc_strcat_wordlist_c(&ret, space, ltoa(flood[i].cnt), &clue);
373 malloc_strcat_wordlist_c(&ret, space, ftoa(time_diff(flood[i].start, right_now)), &clue);
374 malloc_strcat_wordlist_c(&ret, empty_string, "\"", &clue);
375 }
376 }
377
378 new_free(&freeme);
379 }
380
381 RETURN_MSTR(ret);
382 }
383