/* * flood.c: handle channel flooding. * * Copyright (c) 1990-1992 Tomi Ollila * Copyright 1997, 2003 EPIC Software Labs * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notices, the above paragraph (the one permitting redistribution), * this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The names of the author(s) may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This attempts to give you some protection from flooding. Basically, it keeps * track of how far apart (timewise) messages come in from different people. * If a single nickname sends more than 3 messages in a row in under a * second, this is considered flooding. It then activates the ON FLOOD with * the nickname and type (appropriate for use with IGNORE). */ #include "irc.h" #include "flood.h" #include "hook.h" #include "ignore.h" #include "ircaux.h" #include "output.h" #include "server.h" #include "vars.h" #include "functions.h" #include "lastlog.h" #include "window.h" #include "reg.h" typedef struct flood_stru { char *nuh; char *channel; int server; int level; long cnt; Timeval start; int floods; } Flooding; static Flooding *flood = NULL; int users = 0; /* * If flood_maskuser is 0, proceed normally. If 2, keep * track of the @host only. If 1, keep track of the U@H * only if it begins with an alphanum, and use the @host * otherwise. */ static const char * normalize_nuh (const char *nuh) { int maskuser = get_int_var(FLOOD_MASKUSER_VAR); if (maskuser == 0) { } else if (!nuh) { } else if (maskuser == 1 && isalnum(*nuh)) { } else { char prefix = *nuh; char *nnuh = strrchr(nuh, '@'); if (nnuh - nuh >= 2) { *--nnuh = *star; if (maskuser == 1) *--nnuh = prefix; nuh = nnuh; } } return nuh; } /* * check_flooding: This checks for message flooding of the type specified for * the given nickname. This is described above. This will return 0 if no * flooding took place, or flooding is not being monitored from a certain * person. It will return 1 if flooding is being check for someone and an ON * FLOOD is activated. */ int new_check_flooding (const char *nick, const char *nuh, const char *chan, const char *line, int level) { static int pos = 0; int i, numusers, server, retval = 0; Timeval right_now; double diff; Flooding *tmp; int l; char * freeit; freeit = malloc_strdup(nuh); nuh = freeit; /* * Figure out how many people we want to track */ numusers = get_int_var(FLOOD_USERS_VAR); /* * If the number of users has changed, then resize the info array */ if (users != numusers) { i = users; for (i--; i >= numusers; i--) { new_free(&(flood[i].nuh)); new_free(&(flood[i].channel)); } RESIZE(flood, Flooding, numusers); for (i++; i < numusers; i++) { flood[i].nuh = NULL; flood[i].channel = NULL; flood[i].server = NOSERV; flood[i].level = LEVEL_NONE; flood[i].cnt = 0; get_time(&(flood[i].start)); flood[i].floods = 0; } users = numusers; if (users) pos %= users; } /* * Following 0 people turns off flood checking entirely. */ if (numusers == 0) { if (flood) new_free((char **)&flood); users = 0; new_free(&freeit); return 0; } if (nuh && *nuh) nuh = normalize_nuh(nuh); else { new_free(&freeit); return 0; } /* * What server are we using? */ server = (from_server == NOSERV) ? primary_server : from_server; /* * Look in the flooding array. * Find an entry that matches us: * It must be the same flooding type and server. * It must be for the same nickname * If we're for a channel, it must also be for a channel * and it must be for our channel * else if we're not for a channel, it must also not be for * a channel. */ for (i = 0; i < users; i++) { /* * Do some inexpensive tests first */ if (level != flood[i].level) continue; if (server != flood[i].server) continue; if (!flood[i].nuh) continue; /* * Must be for the person we're looking for */ if (my_stricmp(nuh, flood[i].nuh)) continue; /* * Must be for a channel if we're for a channel */ if (!!flood[i].channel != !!chan) continue; /* * Must be for the channel we're looking for. */ if (chan && my_stricmp(chan, flood[i].channel)) continue; /* * We have a winner! */ break; } get_time(&right_now); /* * We didnt find anybody. */ if (i == users) { /* * pos points at the next insertion point in the array. */ int old_pos = pos; do { pos = (0 < pos ? pos : users) - 1; } while (0 < --flood[pos].floods && pos != old_pos); tmp = flood + pos; malloc_strcpy(&tmp->nuh, nuh); if (chan) malloc_strcpy(&tmp->channel, chan); else new_free(&tmp->channel); tmp->server = server; tmp->level = level; tmp->cnt = 0; tmp->start = right_now; pos = (0 < old_pos ? old_pos : users) - 1; new_free(&freeit); return 0; } else tmp = flood + i; /* * Has the person flooded too much? */ if (++tmp->cnt >= get_int_var(FLOOD_AFTER_VAR)) { float rate = get_int_var(FLOOD_RATE_VAR); rate /= get_int_var(FLOOD_RATE_PER_VAR); diff = time_diff(tmp->start, right_now); if ((diff == 0.0 || tmp->cnt / diff >= rate) && (retval = do_hook(FLOOD_LIST, "%s %s %s %ld %s", nick, level_to_str(tmp->level), chan ? chan : "*", tmp->cnt, line))) { tmp->floods++; l = message_from(chan, LEVEL_OTHER); if (get_int_var(FLOOD_WARNING_VAR)) say("FLOOD: %ld %s detected from %s in %f seconds", tmp->cnt+1, level_to_str(tmp->level), nick, diff); pop_message_from(l); } else { /* * Not really flooding -- reset back to normal. */ tmp->cnt = 0; tmp->start = right_now; } } new_free(&freeit); if (get_int_var(FLOOD_IGNORE_VAR)) return retval; else return 0; } int check_flooding (const char *nick, const char *nuh, int mask, const char *line) { return new_check_flooding(nick, nuh, NULL, line, mask); } /* * Note: This will break whatever uses it when any of the arguments * contain a double quote. */ char * function_floodinfo (char *args) { char *arg; char *ret = NULL; size_t clue = 0; Timeval right_now; int i; double idiff; get_time(&right_now); while ((arg = new_next_arg(args, &args))) { const char *nuh = star; const char *chan = star; const char *level = star; int server = -1; int count = 0; double diff = 0; double rate = 0; int cless = 0, dless = 0, rless = 0; char * freeme = NULL; /* * CTCP enquoting handles \\, which is what I think * ce was going for here. */ freeme = arg = transform_string_dyn("-CTCP", arg, 0, NULL); if (arg && *arg) GET_FUNC_ARG(nuh, arg); if (arg && *arg) GET_FUNC_ARG(chan, arg); if (arg && *arg) GET_FUNC_ARG(level, arg); if (arg && *arg) GET_INT_ARG(server, arg); if (arg && *arg) GET_INT_ARG(count, arg); if (arg && *arg) GET_FLOAT_ARG(diff, arg); if (arg && *arg) GET_FLOAT_ARG(rate, arg); if (count < 0) count= -count, cless++; if (diff < 0) diff = -diff, dless++; if (rate < 0) rate = -rate, rless++; for (i = 0; i < users; i++) { if (server >= 0 && flood[i].server != server) { } else if (!flood[i].nuh) { } else if (!wild_match(nuh, flood[i].nuh) && !wild_match(flood[i].nuh, nuh)) { } else if (!wild_match(chan, flood[i].channel ? flood[i].channel : star)) { } else if (!wild_match(level, level_to_str(flood[i].level))) { } else if (!cless && flood[i].cnt < count) { } else if ( cless && flood[i].cnt > count) { } else if (!(idiff = time_diff(flood[i].start, right_now))) { } else if (!dless && idiff < diff) { } else if ( dless && idiff > diff) { } else if (!rless && flood[i].cnt / idiff < rate) { } else if ( rless && flood[i].cnt / idiff > rate) { } else { malloc_strcat_wordlist_c(&ret, space, "\"", &clue); malloc_strcat_wordlist_c(&ret, empty_string, flood[i].nuh, &clue); malloc_strcat_wordlist_c(&ret, space, flood[i].channel ? flood[i].channel : star, &clue); malloc_strcat_wordlist_c(&ret, space, level_to_str(flood[i].level), &clue); malloc_strcat_wordlist_c(&ret, space, ltoa(flood[i].server), &clue); malloc_strcat_wordlist_c(&ret, space, ltoa(flood[i].cnt), &clue); malloc_strcat_wordlist_c(&ret, space, ftoa(time_diff(flood[i].start, right_now)), &clue); malloc_strcat_wordlist_c(&ret, empty_string, "\"", &clue); } } new_free(&freeme); } RETURN_MSTR(ret); }