#ifdef RCS
static char rcsid[]="$Id: flood.c,v 1.1.1.1 2000/11/13 02:42:41 holsta Exp $";
#endif
/******************************************************************************
 *                    Internetting Cooperating Programmers
 * ----------------------------------------------------------------------------
 *
 *  ____    PROJECT
 * |  _ \  __ _ _ __   ___ ___ _ __ 
 * | | | |/ _` | '_ \ / __/ _ \ '__|
 * | |_| | (_| | | | | (_|  __/ |   
 * |____/ \__,_|_| |_|\___\___|_|   the IRC bot
 *
 * All files in this archive are subject to the GNU General Public License.
 *
 * $Source: /cvsroot/dancer/dancer/src/flood.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:41 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "transfer.h"
#include "user.h"
#include "flood.h"
#include "bans.h"

extern time_t now;

extern bool uppercheck; /* check users for uppercase violations! */
extern bool beepcheck;  /* check users for beep violations! */
extern bool colourcheck;  /* check users for colourcode violations! */
extern bool ctcpmode;  /* CTCP flood checks */
extern bool mute;
extern long levels[];
extern itemguest *guestHead;

int floodrate, floodtime;  /* 'floodrate' messages within 'floodtime' secs */
int floodrepeatrate, floodrepeattime;  /* 'floodrepeatrate' repetitions within 'floodrepeattime' secs */
int floodbeeps;
int floodjoins; /* max amount of joins from the same hostpattern */

/****************************************************************************
*     _    _           _   __  __           _      
*    / \  | | ___ _ __| |_|  \/  | ___   __| | ___ 
*   / _ \ | |/ _ \ '__| __| |\/| |/ _ \ / _` |/ _ \
*  / ___ \| |  __/ |  | |_| |  | | (_) | (_| |  __/
* /_/   \_\_|\___|_|   \__|_|  |_|\___/ \__,_|\___|
*
***************************************************************************/

#define ALERT_TIMEOUT (10*60) /* at least 10 minutes from the last
                                 alert ON */

/* --- AlertMode -------------------------------------------------- */

int AlertMode(Alert what)
{
  static time_t alerted = 0;
  static int level = 0;

  switch (what) {
  case ALERT_ON:
    alerted = now;
    level++;
    break;
  case ALERT_RED:
    level += 100;
    alerted = now;
    break;
  case ALERT_OFF:
    if (alerted && ((now - alerted) > ALERT_TIMEOUT)) {
      Log(LOG, "Alert Mode OFF");
      alerted = 0;
      level = 0;
    }
    return 0;
  default:
    break;
  }

  if ((level > 0) && (level < 100))
    Logf(LOG, "Alert Mode level %d", level);
  else if ((level >= 100) && (level < 1000))
    Logf(LOG, "Alert Mode *RED* level %d", level);

  return level;
}

/* --- Warning ---------------------------------------------------- */

bool Warning(itemguest *g, char *msg, char *kickmsg)
{
  int level;

  level = g->ident->user ? g->ident->user->level : 0;
  if (level < FLOODMIDHIGH) {
    g->warnings++;
    if (level < FLOODLOWMID) {
      /* low -> mid */
      if (g->warnings > WARNLOW) {
        StickyKick(g, (g->warnings > 1) ? GetDefaultText(msg_no_more_mr_nice_bot) : kickmsg);
        g->warnings = 0;
        return TRUE; /* Kicked */
      }
    }
    else if (g->warnings > WARNMID) {
      /* mid -> high */
      StickyKick(g, (g->warnings > 1) ? GetDefaultText(msg_no_more_mr_nice_bot) : kickmsg);
      g->warnings = 0;
      return TRUE; /* Kicked */
    }
    if (!mute)
      Actionf(GetDefaultText(msg_usually_kicks_X), msg, g->warnings, g->ident->nick);
  } /* high -> inf */
  return FALSE;
}

/* --- FloodCheck ------------------------------------------------- */

void FloodCheck(itemguest *g)
{
  if ((now - g->posttime) <= floodtime) {
    g->postsame++;
    if (g->postsame >= floodrate)
      Warning(g, GetDefaultText(msg_flooders), GetDefaultText(msg_no_flooding));
  }
  else
    g->postsame = 0;
}

/* --- Check ------------------------------------------------------ */

void Check(itemguest *g, char *line)
{
  long uppers = 0, beeps = 0, colours = 0;
  long length;

  if (NULL == line)
    return;

  for (length = 0; line[length]; length++) {
    if (beepcheck && ('\x07' == line[length]))
      beeps++;
    if (colourcheck && ('\x03' == line[length]) && isdigit(line[length + 1]))
      colours++;
    if (uppercheck && isupper(line[length]))
      uppers++;
  }

  if (beeps) {
    if (beeps <= floodbeeps)
      Warning(g, GetDefaultText(msg_beepers), GetDefaultText(msg_kickmsg_no_beepers));
    else
      StickyKick(g, GetDefaultText(msg_kickmsg_no_beepers));
  }
  if (colours) {
    Warning(g, GetDefaultText(msg_colour_users), GetDefaultText(msg_kickmsg_no_colours));
  }
  if (uppers && (length > 10)) {
    if (uppers*1000/length > 750) /* This is a serious uppercase violation */
      Warning(g, GetDefaultText(msg_shouters), GetDefaultText(msg_no_shouting));
  }
}

/* --- RepeatCheck ------------------------------------------------ */

void RepeatCheck(itemguest *g, char *line)
{
  unsigned long hash = 0;

  if (NULL == line)
    return;

  while (*line)
    hash = (hash<<1) + *line++;

  if ((hash == g->hashpost) && ((now - g->hashtime) <= floodrepeattime)) {
    g->hashsame++;
    if (g->hashsame >= floodrepeatrate)
      Warning(g, GetDefaultText(msg_repeaters), GetDefaultText(msg_no_repeaters));
  }
  else {
    g->hashpost = hash;
    g->hashtime = now;
    g->hashsame = 1;
  }
}

/* --- AvalanceCheck ---------------------------------------------- */

void AvalanceCheck(itemguest *g, char *param)
{
  char c;
  int cntone = 0, cntesc = 0, cntbeep = 0;

  if (NULL == param)
    return;

  while (c = *param++) {
    switch (c) {
      case '\001':
        cntone++;
        break;
      case '\x1b':
        if ('\026' == *param)
          cntesc++;
        break;
      case '\007':
        cntbeep++;
        break;
      default:
        break;
    }
  }

  if (cntone > 5) { /* Kick without warning */
    StickyKick(g, GetDefaultText(msg_ctcp_bomb_detected));
  }
  else if (cntesc > 4) {
    StickyKick(g, GetDefaultText(msg_tsunami_detected));
  }
  else if (cntbeep > 5) {
    StickyKick(g, GetDefaultText(msg_beep_flood_detected));
  }
}

/* --- CTCPFloodCheck --------------------------------------------- */

/* Number of codes */
#define MAX_ACTION 6
#define MAX_CTCP   3

/* Shortest time interval the MAX number of codes are accepted to flood */
#define TIME_ACTION 4
#define TIME_CTCP   6

bool CTCPFloodCheck(itemguest *g, bool action)
{
  bool flooding = FALSE;
  static int actcp = 0;
  static int nctcp = 0;
  int compare = 999;
  int tmp;
  static time_t atime[MAX_ACTION];
  static time_t ntime[MAX_CTCP];

  if (action) {
    tmp = actcp % MAX_ACTION;
    atime[tmp] = now;
    if (actcp < (MAX_ACTION - 1))
      tmp = -1;
    actcp++;
  }
  else {
    tmp = nctcp % MAX_CTCP;
    ntime[tmp] = now;
    if (nctcp < (MAX_CTCP - 1))
      tmp = -1;
    nctcp++;
  }

  if (tmp >= 0) {
    /* Compare to fetch a possible hit */
    if (action) {
#if 0
      int n;
      for (n=0; n < MAX_ACTION; n++) {
        fprintf(stderr, "(%d) %d ", n, now - atime[n]);
      }
      fprintf(stderr, "\nnow %d <-> %d\n",
              tmp, (tmp + 1) % MAX_ACTION);
#endif
      compare = now - atime[(tmp + 1) % MAX_ACTION];
    }
    else {
      compare = now - ntime[(tmp + 1) % MAX_CTCP];
    }
  }

  /* Now we have a time to compare with */
  if (g) {
    if (action) {
      if (compare <= TIME_ACTION)
        flooding = TRUE;
    }
    else {
      if (compare <= TIME_CTCP)
        flooding = TRUE;
    }

    if (flooding) {
#if 0
      StrFormatMax(buf, sizeof(buf), "Flood: CTCP%s flood by %s!%s",
                   action ? " ACTION" : "", g->ident->nick,
                   g->ident->host);
      Multicast(REPORTCAST, buf);
#endif
      if (ctcpmode && !action) {
        /* Don't do this on ACTION floods, but we can still report ACTION
           floods to trigger ignore */
        Warning(g, "CTCP flooders", "CTCP flood detected");
      }
    }
  }
  else /* A ctcp request from outside the channel */
    flooding = TRUE; /* Not really flooding, but we ignore it */
  return flooding;
}

/* --- MultiCheck ------------------------------------------------- */

/*
 * This should kick users that try to join using the same user account
 * for the Xth time.
 */

void MultiCheck(char *domain)
{
  int count = 0; /* None so far */
  itemguest *g;

  if (domain) {
    for (g = First(guestHead); g; g = Next(g)) {
      if (StrEqual(g->ident->userdomain, domain) &&
          ((g->ident->level < LEVELEXPERT) || !g->flags.chanop))
        count++; /* Count this */
    }

    if (count > floodjoins) {
      AlertMode(ALERT_ON);
      for (g = First(guestHead); g; g = Next(g)) {
        if (StrEqual(g->ident->userdomain, domain) &&
            ((g->ident->level < LEVELEXPERT) || !g->flags.chanop))
          StickyKick(g, "*bang* too many joined users");
      }
    }
  }
}


syntax highlighted by Code2HTML, v. 0.9.1