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