#ifdef RCS
static char rcsid[]="$Id: bans.c,v 1.1.1.1 2000/11/13 02:42:38 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/bans.c,v $
 * $Revision: 1.1.1.1 $
 * $Date: 2000/11/13 02:42:38 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "list.h"
#include "function.h"
#include "transfer.h"
#include "user.h"
#include "seen.h"
#include "bans.h"
#include "flood.h"
#include "fplrun.h"

extern time_t now;

extern char channel[];
extern char nickname[];
extern char myaddr[];
extern char banfile[];    /* File name for stored ban info */
extern char warnfile[];   /* File name for stored warning info */
extern bool botop;        /* Are we chanop? */
extern bool reportban;    /* Report SET and BANs in public */
extern bool banuserkicks; /* Ban on many user-kicks */
extern bool fakenamemode;
extern bool mute;

extern time_t uptime;
extern int defbantime;
extern int warnmonths;
extern long levels[];

extern itemuser *userHead;
extern itemguest *guestHead;

/* --- Bans ------------------------------------------------------- */

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

 /***************************************************************************

  USER CONCEPTS:

  Dancer supports ban-supervising, ban-enforcing, timed bans and unbanning
  server bans.

  * supervising
    It records all bans set in the channel. It knows who set the ban and
    when. (should try to get the last nick used, and hopefully kick-message
    if the user was kicked out)
    Bans that match the bot itself are unbanned and the banner get
    deopped!

  * enforcing
    Some bans are really important ones and meant to be set. These are auto-
    matically set again if anyone unban them.
    A 2nd unban of an enforced ban within a short period (2 mins) will make
    Dancer keep the ban unset and wait X (~30) mins. X == configurable and then
    sets it back on channel again.
    High level users (100) remain capable of unbanning enforced bans just like
    normal unbans.

  * timed bans
    General bans set by or with Dancer are timed. They are automatically
    unbanned after the specified time. Bans that would include the bot itself
    are ignored. Available bans should be:
    'ban <nick>'    - regular user ban
    'ban *<nick>'   - ban the user's site
    'ban <pattern>' - ban the specified pattern. (pattern must match the form
                      <nick>!<user>@<site> - where ! and @ must be specified.)

  * server bans
   Server bans are automatically unbanned. (see details below)

 INTERNAL CONCEPTS

 It keeps track of all bans in a linked list. On start-up time it reads all
 bans from the banfile and adds them to the list WITHOUT the 'actual' flag
 set since we don't know for sure they are still set in the channel.

 The banlist will be requested right after joining, and all bans reported in
 the channel will be added to the Dancer list. If the ban already is in the
 list, the 'actual' flag will be set to show it is a "real" ban.

 Unbanning of 'enforced' bans, will keep the ban in the list until it gets
 removed properly. (To allow it to get set again later.)

 After the banlist-completed message is received, all non-enforced bans
 should get removed from the list. As soon as the bot gets opped, it should
 make sure that all enforced bans that aren't marked as 'actual' are banned
 (if 'autobanupdate' is set ON, otherwise the command 'banupdate' must be
 invoked)!
 If 18 bans (or more) are already set, the bot should notify that in public
 when bans are set to alert that the banlist is almost full.

 If the banlist-completed message is received and *no* bans in the list have
 been set to 'actual', we put the bot in 'perhaps-we're-on-a-newly-started-
 server-that-soon-will-netjoin-and-get-a-lot-of-server-bans' position. This
 means when we get the next server-ban, we will start a timer (~30 secs or
 so) and within that time *not* unban the server bans. When we are in this
 position, we should of course still put back the enforced bans. The not-
 unbanned bans are of course added to the list as any normal bans. The
 'possibly-newstarted-server' flag timeouts in 30 mins.

 *MOST PROBLEMATIC CASE*
 When the bot joins a channel without bans. (This following scenario only
 applies to this no-bans situation.) Everything else is normal, some
 persons ban and some unban. Then all of a sudden (they always seem to happen
 all of a sudden) we get one server-ban (or several ones).
 How do we know if that server-ban should be unbanned or if we should count
 that ban as that "first" server-ban?

 Solution:

 o Dancer stores all unbans (for at least an hour or so) and if the server-
   ban matches one of those it will get unbanned and the 'possibly-fresh-
   server' flag is set off.

****************************************************************************/

typedef struct WarnStruct {
  struct Header h;
  char *pattern;     /* Pattern to warn for WITHOUT NICK */
  char *nickpattern; /* Nick part of the pattern */
  char *nick;        /* Nick name of the added user (if known) */
  char *warning;     /* Text */
  char *warner;      /* Adding person (if known) */
  long warns;        /* Number of caused warnings */
  time_t settime;    /* Time of the warnadd */
  time_t lastwarn;   /* Time of the last warning */
  long flags;        /* To enable different kind of actions, i.e kickban the
                        really bad ones instead of just alerting */
  int id;            /* Unique number to avoid trouble */
} itemwarn;

typedef struct Banstruct {
  struct Header h;
  int flags;
  char *who;
  char *bannick;
  char *banpattern;
  long bansecs;
  time_t bantime;
  char *banreason;
  int id;
  int unbans;
  time_t unbantime; /* For enforced-bans checks */
} itemban;

/* Define flags stored on save */
#define BANWRITE(x) ((x) & (BAN_ENFORCE | BAN_ENFTIMEOUT))

bool bantimeouts = FALSE;   /* Disable timeout until end of ban-list is
                               received */
bool possiblyfresh = FALSE; /* This might be a restarted server!! */
int possiblyfreshtime;      /* Timeout timer for refreshed server netheals */

itemban *banHead;
itemkick *kickHead;
itemwarn *warnHead;


static int banId;  /* Used to make unique IDs to every single ban.
                      This is increased on *all* bans and never decreased! */
static int warnId; /* Same for the warning system */

/* --- BanKick ---------------------------------------------------- */

void BanKick(char *nick, char *banthis, char *message, char *remove)
{
  itemguest *g;

  snapshot;
  if (nick && (g = FindNick(nick))) {
    if (remove)
      Mode("-o-b+b %s %s %s", nick, remove, banthis);
    else
      Mode("-o+b %s %s", nick, banthis);
    StickyKick(g, message);
  }
  else {
    if (remove)
      Mode("-b+b %s %s", remove, banthis);
    else
      Mode("+b %s", banthis);
  }
}

/* --- NewBan ----------------------------------------------------- */

itemban *NewBan(int flags,
                char *who, char *nick, char *pattern,
                int secs, time_t when, char *reason)
{
  itemban *b;

  snapshot;
  b = NewEntry(itemban);
  if (b) {
    InsertLast(banHead, b);
    if (who && !StrEqualCase(who, "*"))
      b->who = StrDuplicate(who);
    if (nick && !StrEqualCase(nick, "*"))
      b->bannick = StrDuplicate(nick);
    b->banpattern = StrDuplicate(pattern);
    b->bansecs   = secs;
    b->bantime   = when;
    b->flags     = flags;
    b->id        = ++banId;
    if (reason)
      b->banreason = StrDuplicate(reason);
  }
  return b;
}

void FreeBan(void *v)
{
  itemban *b;

  snapshot;
  b = (itemban *)v;
  if (b) {
    if (b->who)
      StrFree(b->who);
    if (b->bannick)
      StrFree(b->bannick);
    if (b->banpattern)
      StrFree(b->banpattern);
    if (b->banreason)
      StrFree(b->banreason);
  }
}

/* --- AddToBanList ----------------------------------------------- */

/*
 * New return code. Returns the flags of the ban prior to this call.
 */

int AddToBanList(int flags, char *who, char *nick, char *pattern,
                 int secs, char *reason)
{
  int result;
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (StrEqual(b->banpattern, pattern)) {
      result = b->flags; /* Return this */
      if (!(b->flags & BAN_ACTUAL)) {
        if (b->flags & BAN_UNBAN) {
          b->flags = flags | (b->flags & BAN_ENFORCE); /* New mode flags */
          if (secs >= 0) {
            b->bansecs = secs;
            b->bantime = now;
          }

          if (b->who)
            StrFree(b->who);
          b->who = who ? StrDuplicate(who) : NULL;

          if (b->bannick)
            StrFree(b->bannick);
          b->bannick = nick ? StrDuplicate(nick) : NULL;

          if (reason) {
            if (b->banreason)
              StrFree(b->banreason);
            b->banreason = StrDuplicate(reason);
          }
        }
        else
          b->flags |= (flags & (BAN_ACTUAL | BAN_ENFORCE)); /* True and real ban */
      }
      return result;
    }
  }
  NewBan(flags, who, nick, pattern, (secs < 0) ? 0 : secs, now, reason);
  return 0; /* Ban didn't exist */
}

/* --- CountBans -------------------------------------------------- */

int CountBans(int flags)
{
  int count = 0;
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (b->flags & flags)
      count++;
  }
  return count;
}

/* --- Unbanned --------------------------------------------------- */

void Unbanned(char *from, char *pattern, bool bot)
{
  itemguest *g;
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (StrEqual(b->banpattern, pattern)) {
      b->flags &= ~(BAN_ACTUAL | BAN_SENTUNBAN | BAN_SENTBAN);
      b->flags |= BAN_UNBAN;
      if (b->who)
        StrFree(b->who);
      b->who = from ? StrDuplicate(from) : NULL;
      b->unbans++;
      if (b->flags & BAN_ENFORCE) {
        g = FindNick(from);
        if (bot || (g && g->ident->user &&
                   (g->ident->user->level >= BAN_ENFORCELEVEL))) {
          /*
           * This was unbanned by a power-user. It is then removed just
           * as any other ban.
           */
          b->flags &= ~BAN_ENFORCE;
        }
        else if ((now - b->unbantime) < 2*SECINMIN) {
          /*
           * This was unbanned *again* within 2 minutes, let's sleep for
           * BANRETRY_ENFORCE minutes and then ban it again!
           */
          b->flags |= BAN_ENFTIMEOUT;
        }
        else if (botop) {
          Mode("+b %s", b->banpattern); /* Enforce! */
          b->flags |= BAN_SENTBAN;
        }
      }
      b->unbantime = now;
      return;
    }
  }

  b = NewBan(BAN_UNBAN, from, "*", pattern, 0, now, NULL);
  if (b)
    b->unbantime = now;
}

/* --- ChangeInBanList -------------------------------------------- */

bool ChangeInBanList(char *who, char *bannick, int newsecs,
                     char *pattern, char flag, char *newreason)
{
  int id;
  itemban *b;

  snapshot;
  id = atoi(bannick);
  for (b = First(banHead); b; b = Next(b)) {
    if ((b->id == id) ||
        (b->bannick && IRCEqual(b->bannick, bannick) ||
        StrEqual(b->banpattern, bannick))) {
      if (b->who)
        StrFree(b->who);
      b->who  = StrDuplicate(who);
      /* b->bantime = now; */
      if (newsecs >= 0)
        b->bansecs = newsecs;
      if (CHBAN_ENFORCE == flag)
        b->flags |= BAN_ENFORCE; /* Switch on */
      else if (CHBAN_UNENFORCE == flag)
        b->flags &= ~BAN_ENFORCE; /* Switch off */
      else if (CHBAN_REASON == flag) {
        if (b->banreason)
          StrFree(b->banreason);
        b->banreason = StrDuplicate(newreason);
      }
      StrCopy(pattern, b->banpattern);
      return TRUE;
    }
  }
  return FALSE;
}

/* --- BanInit ---------------------------------------------------- */

/*
 * This should be done at bot startup. None of these bans are *proved*
 * to be real, but we still want to know about them. Keep all those in the
 * list and perform actions later. (At end-of-banlist and when opped)
 */

void BanInit(void)
{
  char buf[MAXLINE];
  char by[MIDBUFFER];
  char nick[NICKLEN+1];
  char pattern[MIDBUFFER];
  char reason[BIGBUFFER];
  int secs, when, flags;
  FILE *f;

  snapshot;
  banHead = NewList(itemban);

  if ((char)0 == banfile[0])
    return;

  f = fopen(banfile, "r");
  if (f) {
    while (fgets(buf, sizeof(buf), f)) {
      reason[0] = (char)0;
      if (6 <= StrScan(buf, "%"MIDBUFFERTXT"s %"NICKLENTXT"s %"MIDBUFFERTXT"s %d %d %d %"BIGBUFFERTXT"[^\n]",
                       by, nick, pattern, &flags, &secs, &when, reason)) {
        NewBan(flags | BAN_UNSET, by, nick, pattern, secs, (time_t)when,
               reason[0] ? reason : NULL);
      }
    }
    fclose(f);
  }
}

/* --- BanSave ---------------------------------------------------- */

void BanSave(void)
{
  char tempfile[MIDBUFFER];
  bool ok = TRUE;
  itemban *b;
  FILE *f;

  snapshot;
  if ((char)0 == banfile[0])
    return;

  StrFormatMax(tempfile, sizeof(tempfile), "%s~", banfile);

  f = fopen(tempfile, "w");
  if (f) {
    for (b = First(banHead); b; b = Next(b)) {
      if (0 > fprintf(f, "%s %s %s %d %d %d %s\n",
                      b->who ? b->who : "*", b->bannick ? b->bannick : "*",
                      b->banpattern, BANWRITE(b->flags), b->bansecs,
                      b->bantime, b->banreason ? b->banreason : "")) {
        ok = FALSE;
        break;
      }
    }
    fclose(f);

    if (ok)
      rename(tempfile, banfile);
  }
}

/* --- BanList ---------------------------------------------------- */

int BanList(char *from, bool all, bool unbanned, char *match)
{
  char buffer[BIGBUFFER];
  int numlisted = 0;
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    /* Only show ACTUAL, UNBANned or bans */
    if (!(b->flags & (BAN_ACTUAL | BAN_UNBAN | BAN_ENFTIMEOUT))) {
      continue;
    }
    if (!unbanned && (b->flags & BAN_UNBAN)) {
      if (!(b->flags & BAN_ENFTIMEOUT))
        continue;
    }
    if (!Match(b->banpattern, match))
      continue;

    /* Display the ban! */
    ++numlisted;
    StrFormatMax(buffer, sizeof(buffer), "%d %s", b->id, b->banpattern);

    if (b->bannick && !StrEqualCase(b->bannick, "*")) {
      if (b->flags & BAN_GUESSNICK)
        StrFormatAppendMax(buffer, sizeof(buffer), " {%s}", b->bannick);
      else
        StrFormatAppendMax(buffer, sizeof(buffer), " (%s)", b->bannick);
    }
    if (b->who && !StrEqualCase(b->who, "*")) {
      StrFormatAppendMax(buffer, sizeof(buffer), GetText(msg_ban_by), b->who);
    }
    if (b->bansecs && !(b->flags & BAN_UNBAN)) {
      StrFormatAppendMax(buffer, sizeof(buffer), GetText(msg_ban_another),
                         SecsToString(b->bansecs + b->bantime - now));
    }
    if (b->flags & BAN_ENFORCE) {
      StrAppendMax(buffer, sizeof(buffer), " [ENFORCE]");
    }
    if (b->flags & BAN_UNBAN) {
      StrAppendMax(buffer, sizeof(buffer), " [UNBANNED]");
    }
    Send(from, buffer);

    if (all) {
      buffer[0] = (char)0;
      if (b->bantime) {
        StrFormatAppendMax(buffer, sizeof(buffer), GetText(msg_ban_ago),
                           TimeAgo(b->bantime));
      }
      if (b->flags & BAN_UNBAN) {
        if (b->unbantime) {
          StrFormatAppendMax(buffer, sizeof(buffer), GetText(msg_unban_ago),
                             TimeAgo(b->unbantime));
        }
      }
      else {
        if (b->bansecs) {
          StrFormatAppendMax(buffer, sizeof(buffer), GetText(msg_time_ban),
                             SecsToString(b->bansecs));
        }
      }
      if (b->banreason) {
        StrFormatAppendMax(buffer, sizeof(buffer), " \"%s\"", b->banreason);
      }
      Send(from, buffer);
    }
  }
  return numlisted;
}

/* --- BanCleanup ------------------------------------------------- */

void BanCleanup(void)
{
  snapshot;
  BanSave();
  DeleteList(banHead, FreeBan);
}

void BanDisable(void)
{
  itemban *b;

  snapshot;
  bantimeouts = FALSE; /* No more timeouts until we get a new banlist */
  for (b = First(banHead); b; b = Next(b))
    b->flags &= ~BAN_ACTUAL;
}

/* ---- BanTimeout ------------------------------------------------ */

void BanTimeout(void)
{
  char buffer[BIGBUFFER];
  int len = 0;
  int num = 0;
  itemban *b, *next;

  snapshot;
  if (!bantimeouts)
    return;

  for (b = First(banHead); b; b = next) {
    next = Next(b);
    if (b->flags & BAN_ACTUAL) {
      if (botop) {
        /*
         * This is only done while and if the bot is channel operator.
         * We only check actual bans
         */
        if (b->bansecs &&
            ((b->bantime + b->bansecs) <= now) &&
            !(b->flags & BAN_SENTUNBAN)) {
          /*
           * This ban has timed out now!
           */
          Mode("-b %s", b->banpattern);

          /*
           * We mark this ban as sent to the server to prevent it from
           * getting unbanned again due to server lag.
           */
          b->flags |= BAN_SENTUNBAN;

          if (b->bansecs > 20*SECINMIN) {
            /*
             * Only if more than 20 minutes since the ban was called.
             */
            if (reportban && !mute) {
              if (b->bannick && !StrEqualCase(b->bannick, "*")) {
                Sayf(GetDefaultText(msg_report_unban_nick),
                     b->bannick, TimeAgo(b->bantime), b->banreason);
              }
              else
                Sayf(GetDefaultText(msg_report_unban), TimeAgo(b->bantime));

              /* RemoveFromBanList(bannick, bandomain);
               * We intercept the mode change to the channel anyway and store
               * this unban!
               */
            }
          }
        }
      }
    }
    else {
      /*
       * We check for non-actual non-enforced bans, that have been unbanned
       * for more than REFRESH_TIMEOUT minutes. They are removed from the
       * list.
       */
      if (!(b->flags & BAN_ENFORCE) &&
          ((b->bantime + REFRESH_TIMEOUT) <= now) &&
          ((b->unbantime + REFRESH_TIMEOUT) <= now)) {
        DeleteEntry(banHead, b, FreeBan); /* Removed */
        continue;
      }
      else if ((b->flags & BAN_ENFORCE) &&
               (b->flags & BAN_SENTBAN) &&
               !(b->flags & BAN_ACTUAL) &&
               ((b->bantime + 10) <= now)) {
        /*
         * This ban is:
         * ENFORCED + SENT and yet not ACTUAL 10 seconds since sending.
         * In my world that means the ban wasn't possible to set. Remove it
         * from the internal list.
         */
        DeleteEntry(banHead, b, FreeBan); /* Removed */
        continue;
      }
      else if (botop && (b->flags & BAN_ENFORCE) &&
                       !(b->flags & BAN_SENTBAN)) {
        if (b->flags & BAN_ENFTIMEOUT) {
          if ((now - b->unbantime) > BANRETRY_ENFORCE*SECINMIN) {
            num++;
            StrFormatMax(&buffer[len], sizeof(buffer) - len, " %s", b->banpattern);
            len += StrLength(&buffer[len]);
            b->flags &= ~BAN_ENFTIMEOUT;
            b->flags |= BAN_SENTBAN;
          }
        }
        else {
          num++;
          StrFormatMax(&buffer[len], sizeof(buffer) - len, " %s", b->banpattern);
          len += StrLength(&buffer[len]);
          b->flags |= BAN_SENTBAN;
        }
        if (3 == num) {
          Mode("+bbb%s", buffer);
          num = len = 0;
        }
      }
    }
  }

  if (num)
    Mode("+bbb%s", buffer);

  if (possiblyfresh && ((now - uptime) > REFRESH_TIMEOUT))
    possiblyfresh = FALSE;

  if (possiblyfreshtime) {
    if ((now - possiblyfreshtime) > SERVERBANTIMEOUT)
      possiblyfreshtime = 0;
  }
}

static itemban *FindToUnban(char *what, int flags)
{
  int id;
  itemban *b;

  snapshot;
  id = atoi(what);
  for (b = First(banHead); b; b = Next(b)) {
    if ((b->id == id) ||
        (b->bannick && IRCEqual(b->bannick, what)) ||
        StrEqual(b->banpattern, what)) {
      if (b->flags & flags)
        return b;
    }
  }
  return NULL;
}

bool UnBan(char *from, char *nick)
{
  itemban *b;

  snapshot;
  b = FindToUnban(nick, BAN_ACTUAL);
  if (NULL == b) {
    b = FindToUnban(nick, BAN_ALL);
    if (NULL == b)
      return FALSE;
  }

  b->flags &= ~BAN_ENFORCE;
  b->bansecs = 0;

  if (!(b->flags & BAN_ACTUAL)) {
    if (b->flags & BAN_ENFTIMEOUT) {
      /*
       * The ban isn't active in the channel, but is ENFORCED UNBANNED.
       * Allow removal here.
       */
      Unbanned(from, b->banpattern, TRUE);
    }
    return TRUE;
  }
  Mode("-b %s", b->banpattern); /* This gets removed from the list by the
                                   normal OnMode() stuff in server.c */
  b->flags |= BAN_SENTUNBAN;
  if (reportban && !mute) {
    if (b->bannick && !StrEqualCase(b->bannick, "*")) {
      /* This was *not* a "caught" ban */
      Sayf(GetDefaultText(msg_report_unbanforce_nick), from, b->bannick);
    }
    else
      Sayf(GetDefaultText(msg_report_unbanforce), from);
  }
  return TRUE;
}

/* --- IsUnban ---------------------------------------------------- */

/*
 * Returns TRUE if the specified pattern matches one of the
 * unbanned ones.
 */

bool IsUnban(char *pattern)
{
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (StrEqual(b->banpattern, pattern) && (b->flags & BAN_UNBAN))
      return TRUE;
  }
  return FALSE;
}

/* --- IsBan ------------------------------------------------------ */

/*
 * Checks for *actual* bans matching the parameter.
 */

bool IsBan(char *checkthis)
{
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if ((b->flags &BAN_ACTUAL) && Match(checkthis, b->banpattern))
      return TRUE;
  }
  return FALSE;
}

/* --- IsBanListed ------------------------------------------------ */

/*
 * The functions below differ from IsBan() in the way that they doen't
 * only check for actual bans, but also for to-be bans that is marked to
 * get added.
 */

bool IsBanListed(char *checkthis)
{
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (!(b->flags & (BAN_UNSET | BAN_UNBAN)) && Match(checkthis, b->banpattern))
      return TRUE;
  }
  return FALSE;
}

/* --- IsMatchBan ------------------------------------------------- */

char *IsMatchBan(char *checkthis, int *amount)
{
  char *pattern = NULL;
  int num = 0;
  itemban *b;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (!(b->flags & (BAN_UNSET | BAN_UNBAN)) && Match(b->banpattern, checkthis)) {
      pattern = b->banpattern;
      num++;
    }
  }
  *amount = num;
  return pattern;
}

/* --- IllegalBan ------------------------------------------------- */

bool IllegalBan(char *checkthis)
{
  extern bool banprotect;
  char nickpart[NICKLEN+1], nickmatch[NICKLEN+1] = "*";
  char *hostpart, *hostmatch;
  register itemuser *u;
  register itemlist *t;

  snapshot;
  /* Split the pattern into nick and host parts respectively */
  hostmatch = StrIndex(checkthis, '!');
  if (hostmatch) {
    hostmatch++;
    StrScan(checkthis, "%"NICKLENTXT"[^!]", nickmatch);
  }
  else
    hostmatch = checkthis;

  if (banprotect) {
    for (u = First(userHead); u; u = Next(u)) {
      if (u->flags.banprotect) {
        for (t = First(u->domainhead); t; t = Next(t)) {
          hostpart = StrIndex((char *)t->pointer, '!');
          if (hostpart) {
            /* Make hostpart point to userhost part of the pattern */
            hostpart++;
            if (1 == StrScan((char *)t->pointer, "%"NICKLENTXT"[^!]", nickpart)) {
              /* If nick part doesn't match, the whole pattern doesn't match */
              if (!MatchMatch(nickmatch, nickpart))
                continue;
            }
          }
          else
            hostpart = (char *)t->pointer;

          if (MatchMatch(hostmatch, hostpart))
            return TRUE;

          if (IsPrefix(hostmatch))
            if (MatchMatch(&hostmatch[1], hostpart))
              return TRUE;

          if (IsPrefix(hostpart))
            if (MatchMatch(hostmatch, &hostpart[1]))
              return TRUE;
        }
      }
    }
  }
  return (Match(nickname, nickmatch) && Match(myaddr, hostmatch));
}

#define SCANBAN_TIMED         1
#define SCANBAN_NONENFORCED   2
#define SCANBAN_TIMEDENFORCED 3
#define SCANBAN_ENFORCED      4

static itemban *ScanBan(long options)
{
  int i;
  int max = 0;
  itemban *b;
  itemban *thisone = NULL;

  snapshot;
  for (b = First(banHead); b; b = Next(b)) {
    if (!(b->flags & BAN_ACTUAL) || (b->flags & BAN_SENTUNBAN))
      continue;
    switch (options) {
    case SCANBAN_TIMED:
      if (b->bansecs && !(b->flags & BAN_ENFORCE)) {
        i = b->bansecs + b->bantime - now; /* Time left */
        if (!thisone || (i < max)) {
          max = i;     /* Max is minimum in this case! */
          thisone = b; /* Our favourite so far */
        }
      }
      break;
    case SCANBAN_NONENFORCED:
      if (!b->bansecs && !(b->flags & BAN_ENFORCE)) {
        i = b->bantime; /* Age */
        if (!thisone || (i < max)) {
          max = i;     /* Max the oldest ban! */
          thisone = b; /* Our favourite so far */
        }
      }
      break;
    case SCANBAN_TIMEDENFORCED:
      if (b->bansecs && (b->flags & BAN_ENFORCE)) {
        i = b->bansecs + b->bantime - now; /* Time left */
        if (!thisone || (i < max)) {
          max = i;     /* Max is minimum in this case! */
          thisone = b; /* Our favourite so far */
        }
      }
      break;
    case SCANBAN_ENFORCED:
      if (!b->bansecs && (b->flags & BAN_ENFORCE)) {
        i = b->bantime; /* Age */
        if (!thisone || (i < max)) {
          max = i;     /* Max the oldest ban! */
          thisone = b; /* Our favourite so far */
        }
      }
      break;
    }
  }
  return thisone;
}

/*
 * UnbanLoprio()
 *
 * Unbans the least important ban in the channel. The algo to find that one
 * is:
 * 1 - the NON-ENFORCED ban with the least time left
 * 2 - the oldest NON-ENFORCED ban
 * 3 - the ENFORCED ban with the least time left
 * 4 - the oldest ENFORCED ban
 *
 * Returns TRUE if a pattern was sent for unban.
 */

bool UnbanLoprio(char *from)
{
  itemban *target;

  snapshot;
  target = ScanBan(SCANBAN_TIMED);
  if (NULL == target) {
    target = ScanBan(SCANBAN_NONENFORCED);
    if (NULL == target) {
      target = ScanBan(SCANBAN_TIMEDENFORCED);
      if (NULL == target)
        target = ScanBan(SCANBAN_ENFORCED);
    }
  }
  if (target)
    return UnBan(from, target->banpattern);
  else
    return FALSE;
}

/***************************************************************************
*  _  ___      _             _          __  __ 
* | |/ (_) ___| | __     ___| |_ _   _ / _|/ _|
* | ' /| |/ __| |/ /____/ __| __| | | | |_| |_ 
* | . \| | (__|   <_____\__ \ |_| |_| |  _|  _|
* |_|\_\_|\___|_|\_\    |___/\__|\__,_|_| |_|  
*
***************************************************************************/

#define KICK_PERIOD 600 /* Kicks within this period are counted   */
#define KICK_NUMBER 15  /* Number of patterns to keep in the list */

#define KICKBAN_BOT 3   /* This many bot kicks makes a ban */
#define KICKBAN_REG 5   /* This many regular kicks make a ban */

/*
 * Clue-time:
 * ----------
 * All kicks are recorded, the last KICK_NUMBER (default 10) patterns are
 * stored in memory.
 * Kicks that that bot *intends* to do due to violation of rules are marked
 * separately, as are regular (non-bot) kicks of trusted users.
 * 3 bot-marks/kicks or 5 regular+bot kicks make the bot ban the user and
 * kick all other users joined from the same account-pattern.
 *
 */

/*
 * 'ident' is the user that is kicked.
 * 'kicker' is the full userhost of the person that kicked.
 * 'kickmsg' reason/message to assign to the kick
 * 'status' is the level of the kicker, 3 levels exist
 *
 * Returns TRUE if the amount of kicks during the last KICK_PERIOD seconds
 * of that same pattern is KICKBAN_REG or more, or if the amount of botkicks
 * is KICKBAN_BOT or greater.
 *
 * This function only stores KICK_NUMBER of kicks in the list, the
 * oldest one should be removed from the list when the list gets filled.
 */
bool AddKick(itemident *ident, char *kicker, char *kickmsg, int status)
{
  extern int kickQ;
  static time_t failedsiteban = 0;
  char kickpattern[MIDBUFFER];
  char userpattern[MIDBUFFER];
  char sitepattern[MIDBUFFER];
  char *banpattern;
  bool banthis = FALSE;
  bool forget = FALSE;
  time_t oldtime = 999999999;
  int num = 0;
  itemkick *oldkick = NULL;
  itemkick *k;

  snapshot;
  for (k = First(kickHead); k; k = Next(k)) {
    num++;
    if (StrEqual(k->pattern, ident->userdomain)) {
      if (k->norecurse)
        return FALSE;
      k->norecurse = TRUE; /* Disable recursion at this point */
      if ((k->lastkick + KICK_PERIOD) < now)
        k->botkicks = k->kicks = k->trustkicks = 0;
      k->totkicks++; /* Total amount, never set to zero */
      switch (status) {
      case KICK_BOT:
        k->botkicks++;
        break;
      case KICK_COMMON:
        k->kicks++;
        break;
      case KICK_TRUSTEDUSER:
        k->trustkicks++;
        break;
      }
      if (!ident->illegalname &&
         /* Illegal names won't be banned since we won't have their host mask
            properly */

         ((banuserkicks && ((k->kicks + k->trustkicks) >= KICKBAN_REG)) ||
          (k->botkicks >= KICKBAN_BOT))) {
        /*
         * This user has now got kicked enough number of times.
         * Wipe him off the channel for good (=ban).
         */
        char *inthere;
        int number;
        AlertMode(ALERT_ON);

        NickToPattern(ident->nick, kickpattern, FALSE); /* User match */

        StrFormatMax(userpattern, sizeof(userpattern), "*!%s", kickpattern); /* User ban pattern */
        StrFormatMax(sitepattern, sizeof(sitepattern), "*!*@%s", ident->puserdomain); /* Site ban */

        inthere = IsMatchBan(sitepattern, &number);

        if (!inthere || (number > 1) ||
            HostISP(ident->host) ||
            DontSiteBan(ident->host)) {
          /* The site is not previously banned OR
           * banned with more than one pattern OR
           * joined from a hostisp site OR
           * joined from a site-ban-prevented ISP,
           * so use a normal ban.
           */
          banpattern = userpattern;
          inthere = NULL; /* DON'T unban the previous one */
          if ((number > 1) && !DontSiteBan(ident->host)) {
            /* Well, we could still KICK all of them from that site */
            StrFormatMax(kickpattern, sizeof(kickpattern), "*@%s", ident->puserdomain); /* Site match */
            if ((failedsiteban + TRIGGER_RED_ALERT) > now)
              AlertMode(ALERT_RED); /* Go highest level */
            failedsiteban = now;
          }
        }
        else {
          /* Another account already banned on that site, now ban
             the whole site! */
          if (!Match(inthere, userpattern)) {
            /*
             * Exactly this account isn't previously banned. Remove the
             * user ban and ban the site.
             */
            banpattern = sitepattern;
            StrFormatMax(kickpattern, sizeof(kickpattern), "*@%s", ident->puserdomain); /* Site match */
          }
          else {
            /*
             * This pattern is already banned!
             */
            forget = TRUE;
          }
        }
        if (botop && !forget) {
          if (!(AddToBanList(BAN_SENTBAN,
                             kicker, k->nick, banpattern, defbantime,
                             k->kickmsg?k->kickmsg:
                             GetDefaultText(msg_kick_repeated_misbehavior))
              & BANISTHERE)) {
            if (inthere)
              Mode("-b+b %s %s", inthere, banpattern);
            else
              Mode("+b %s", banpattern);
          }
        }
        /* kickQ++; - force these kicks to get enqueued */
        KickAll(kickpattern, kickmsg?kickmsg:
                GetDefaultText(msg_kick_learn_your_lesson));
        banthis = TRUE;
      }
      k->lastkick = now;
      if (k->nick)
        StrFree(k->nick);
      k->nick = StrDuplicate(ident->nick);

      if (kickmsg) {
        if (k->kickmsg)
          StrFree(k->kickmsg);
        k->kickmsg = StrDuplicate(kickmsg);
      }
      if (k->kicker)
        StrFree(k->kicker);
      k->kicker = StrDuplicate(kicker);
      k->norecurse = FALSE; /* Enable recursion again */
      return banthis;
    }
    /*
     * Remember the oldest item in the list in case we
     * wanna remove it.
     */
    if (oldtime > k->lastkick) {
      oldtime = k->lastkick;
      oldkick = k;
    }
  }

  /* After being discussed on the mailing list, initiated by a bug report by
     Tero Jänkä, I think this may fix the problem he has experienced during
     extensive bot floods. */

  /* Ok, this is the second version -- as proposed by Tero after 4.12 has
     been released. This decreases the size of the list in a much quicker
     fashion down to the KICK_NUMBER size as soon as possible again after an
     extensive attack. */

  while ((KICK_NUMBER <= num--) &&
         oldkick &&
         !oldkick->norecurse &&
         ((now - oldtime) > KICK_PERIOD)) {
    /* there are more entries than should fit AND
       we have an oldest kick AND
       the oldest kick is not being in use AND
       it was kicked > KICK_PERIOD seconds ago */

    /* Remove this entry: */
    DeleteEntry(kickHead, oldkick, FreeKick);

    /* Do we have more entries to remove? */
    if (KICK_NUMBER <= num) {
      oldtime = 999999999;
      oldkick = NULL;

      /* Find the oldest entry in the list now */
      for (k = First(kickHead); k; k = Next(k)) {
        if (oldtime > k->lastkick) {
          oldtime = k->lastkick;
          oldkick = k;
        }
      }
    }
    else
      break; /* No, we break out of loop */
  }

  k = NewEntry(itemkick);
  if (k) {
    InsertLast(kickHead, k);
    switch (status) {
    case KICK_BOT:
      k->botkicks = 1;
      break;
    case KICK_COMMON:
      k->kicks = 1;
      break;
    case KICK_TRUSTEDUSER:
      k->trustkicks = 1;
      break;
    }
    k->lastkick = now;
    k->kicker = StrDuplicate(kicker);

    /*
     * This used the NickToPattern() previously which is no good.
     * The only thing this "pattern" (that more strictly should be
     * considered as a straight string) is for us to recognize when
     * a user with the _exact_ same pattern is kicked again.
     * We don't need no fancy stuff for that. - Bagder
     */
    k->pattern = StrDuplicate(ident->userdomain);
    k->nick = StrDuplicate(ident->nick);
    if (kickmsg)
      k->kickmsg = StrDuplicate(kickmsg);
  }
  return FALSE;
}

void FreeKick(void *v)
{
  itemkick *k;

  snapshot;
  k = (itemkick *)v;
  if (k) {
    if (k->pattern)
      StrFree(k->pattern);
    if (k->kicker)
      StrFree(k->kicker);
    if (k->nick)
      StrFree(k->nick);
    if (k->kickmsg)
      StrFree(k->kickmsg);
  }
}

void KickAll(char *pattern, char *msg)
{
  itemguest *g;

  snapshot;
  for (g = First(guestHead); g; g = Next(g)) {
    if (g->ident->host &&
        Match(g->ident->host, pattern) &&
        (g->ident->level < LEVELBOT)) { /* Can't pattern-kick BOT or higher */
      StickyKick(g, msg);
    }
  }
}

void KickInit(void)
{
  snapshot;
  kickHead = NewList(itemkick);
}

void KickList(char *from, char *line)
{
  char showmatch[MIDBUFFER] = "*";
  int num = 0;
  int total = 0;
  itemkick *k;

  snapshot;
  if (line) {
    StrScan(line, "%"MIDBUFFERTXT"s", showmatch);
  }

  for (k = First(kickHead); k; k = Next(k)) {
    total++;
    if (Match(k->pattern, showmatch)) {
      num++;
      Sendf(from, GetText(msg_kicklist_item),
            k->pattern, k->nick ? k->nick : UNKNOWN,
            k->kicker, TimeAgo(k->lastkick),
            k->kicks + k->trustkicks);
    }
  }

  if (0 == num)
    Send(from, GetText(msg_no_matching_kick));
  else if (total != num)
    Sendf(from, GetText(msg_X_out_of_Y_items), num ,total);
}

/*
 * Perform a full *!*@* style matching against all entries in the
 * kicklist, and return the matching entry.
 */
itemkick *KickMatch(char *pattern)
{
  char nickpart[NICKLEN+1];
  char hostpart[MIDBUFFER];
  itemkick *k;

  snapshot;
  if (2 == StrScan(pattern, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                   nickpart, hostpart)) {
    for (k = First(kickHead); k; k = Next(k)) {
      if (Match(k->nick, nickpart) &&
          Match(k->pattern, hostpart))
        return k;
    }
  }
  return NULL;
}

void KickCleanup(void)
{
  snapshot;
  DeleteList(kickHead, FreeKick);
}

/*
 * KickFromQueue()
 *
 * Called when there is believed to exist users on the channel that
 * are marked to get kicked.
 * Performs a kick, with flood-control
 *
 * Returns TRUE if a kick-marked guest is left in the queue when this
 * function quits.
 *
 * The ircd server does support a sequence like nick1,nick2,nick3,nick4
 * to get specified. Although this will give us very high penalty
 * in the server. Let's refrain from using that until we have a better
 * scheme. I.e we _cannot_ use multi-kick on >1 nick every 2nd second.
 */
/* #define MULTIKICK */


bool KickFromQueue(time_t *lastsentp)
{
#ifndef MULTIKICK
  static int Qkicks = 0;
  itemguest *g;

  snapshot;
  for (g = First(guestHead); g; g = Next(g)) {
    if (g->flags.kick && !g->flags.kicked) {
      if ((++Qkicks > 3) && ((*lastsentp + 1) >= now))
        return TRUE;
      WriteServer("KICK %s %s :%s",
                  channel, g->ident->nick,
                  (g->kickmsg ? g->kickmsg : nickname));
      *lastsentp = now;
      Logf(LOGDBUG, "Q-Sent KICK %s to server", g->ident->nick);
      g->flags.kicked = TRUE;
      return TRUE;
    }
  }
  Qkicks = 0;
  return FALSE;
#else

/*
 * This cute define sets the maximum amount of nicks to send for kick
 * in each single KICK command. */
#define KICK_THIS_MANY 3

  static int Qkicks = 0;
  bool found = FALSE;
  itemguest *g;
  int amount = 0;
  itemguest *victim[KICK_THIS_MANY];

  /* 'evenmore' is TRUE if there are more users left to kick after this
     round of multi-kick has been performed */
  bool evenmore = FALSE;

  snapshot;
  for (g = First(guestHead); g; g = Next(g)) {
    if (g->flags.kick && !g->flags.kicked) {
      found = TRUE;
      if (amount < KICK_THIS_MANY) {
        victim[ amount++ ] = g;
        continue;
      }
      evenmore = TRUE;
      break;
    }
  }
  if (found) {
    char nickbuffer[(NICKLEN+1)*KICK_THIS_MANY+2];
    int len = 0;
    int i;

    if ((++Qkicks > 3) && ((*lastsentp + 1) >= now))
      return TRUE;

    for (i=0; i < amount; i++) {
      /* Add name to the buffer */
      StrFormatMax(&nickbuffer[len], sizeof(nickbuffer) - len, "%s%s", i ? "," : "", victim[i]->ident->nick);
      /* Mark nick that we have sent him for termination */
      victim[i]->flags.kicked = TRUE;
      /* Set pointer to end of string */
      len += StrLength(StrLength(&nickbuffer[len]);
    }

    /* Set multi-nick KICK to server, works for ircd2.8 and later */
    WriteServer("KICK %s %s :%s",
                channel, nickbuffer,
                (amount > 1) ? "byebye" :
                (victim[0]->kickmsg ? victim[0]->kickmsg : "byebye"));
    *lastsentp = now;
    Logf(LOGDBUG, "Q-Sent KICK %s to server", nickbuffer);

    if (!evenmore)
      Qkicks = 0;
    return evenmore;
  }
  Qkicks = 0;
  return FALSE;
#endif
}

/***********************************************************************
 ***********************************************************************
 *
 * WARNING system functions below this separator
 *
 ***********************************************************************
 **********************************************************************/

#define WARNING_FREQUENCY 3600 /* minimum time between warnings displayed
                                  for the same pattern */

void FreeWarn(void *v)
{
  itemwarn *w;

  snapshot;
  w = (itemwarn *)v;
  if (w) {
    if (w->pattern)
      StrFree(w->pattern);
    if (w->nickpattern)
      StrFree(w->nickpattern);
    if (w->warner)
      StrFree(w->warner);
    if (w->warning)
      StrFree(w->warning);
    if (w->nick)
      StrFree(w->nick);
  }
}

void WarnCleanup(void)
{
  snapshot;
  WarnSave();
  DeleteList(warnHead, FreeWarn);
}

/* --- FindWarn --------------------------------------------------- */

itemwarn *FindWarn(char *nick, char *userhost)
{
  itemwarn *w;

  snapshot;
  if (nick && userhost) {
    for (w = First(warnHead); w; w = Next(w)) {
      if (Match(nick, w->nickpattern) && Match(userhost, w->pattern))
        return w;
    }
  }
  return NULL;
}

/* --- WarnAdd ---------------------------------------------------- */

bool WarnAdd(char *nick, /* Nick of the abuser */
             char *pattern,
             char *nickpattern,
             char *warning, /* Text */
             char *adder, /* Who added this */
             int flags) /* Various options */
{
  itemwarn *w;

  snapshot;
  w = FindWarn(nickpattern, pattern);
  if (w)
    return FALSE; /* Already warned for! */

  w = NewEntry(itemwarn);
  if (w) {
    InsertLast(warnHead, w);
    w->pattern = StrDuplicate(pattern);
    w->nickpattern = StrDuplicate(nickpattern);
    w->warning = StrDuplicate(warning);
    w->warner  = adder ? StrDuplicate(adder) : NULL;
    w->nick    = nick ? StrDuplicate(nick) : NULL;
    w->settime = now;
    w->id      = ++warnId;
    w->flags   = flags;
  }
  return TRUE;
}

/* --- WarnSave --------------------------------------------------- */

void WarnSave(void)
{
  char tempfile[MIDBUFFER];
  bool ok = TRUE;
  itemwarn *w;
  FILE *f;

  snapshot;
  if ((char)0 == warnfile[0])
    return;

  StrFormatMax(tempfile, sizeof(tempfile), "%s~", warnfile);

  f = fopen(tempfile, "w");
  if (f) {
    for (w = First(warnHead); w; w = Next(w)) {
      if ((w->lastwarn && ((now - w->lastwarn) < warnmonths*SECINMONTH)) ||
          ((now - w->settime) < warnmonths*SECINMONTH)) {
        if (0 > fprintf(f, "%s %s %s!%s %d %d %d %d %s\n",
                        w->warner ? w->warner : "*",
                        w->nick ? w->nick : "*",
                        w->nickpattern,
                        w->pattern,
                        w->warns, w->settime, w->lastwarn, w->flags,
                        w->warning)) {
          ok = FALSE;
          break;
        }
      }
    }
    fclose(f);

    if (ok)
      rename(tempfile, warnfile);
  }
}

/* --- WarnDel ---------------------------------------------------- */

bool WarnDel(char *from, char *what) /* Nick or warnId */
{
  int id;
  itemwarn *w;

  snapshot;
  id = atoi(what);
  for (w = First(warnHead); w; w = Next(w)) {
    if ((w->id == id) ||
        (w->nick && IRCEqual(w->nick, what)) ||
        StrEqual(w->pattern, what)) {
      Sendf(from, GetText(msg_warn_pattern_removed), w->nickpattern, w->pattern);
      DeleteEntry(warnHead, w, FreeWarn); /* Removed */
      return TRUE;
    }
  }
  return FALSE;
}

/* --- WarnList --------------------------------------------------- */

bool WarnList(char *from, char *match, int flags)
{
  char buffer[MIDBUFFER];
  int shown = 0;
  itemwarn *w;

  snapshot;
  for (w = First(warnHead); w; w = Next(w)) {
    StrFormatMax(buffer, sizeof(buffer), "%s!%s", w->nickpattern, w->pattern);
    if (Match(buffer, match)) {
      if ((flags & WLIST_BAN) &&
         !(w->flags & WARNF_KICKBAN))
        continue;
      shown++;
      Sendf(from, "%d %s!%s (%s) %s %s%s",
            w->id, w->nickpattern, w->pattern, w->nick ? w->nick : "*",
            GetText(msg_is),
            w->warning,
            (w->flags & WARNF_KICKBAN) ? " [KICKBAN]" : "");
      if (flags & WLIST_ALL) {
        if (w->lastwarn)
          StrFormatMax(buffer, sizeof(buffer), GetText(msg_warn_list_all),
                       w->warns, TimeAgo(w->lastwarn));
        else
          StrCopyMax(buffer, sizeof(buffer), GetText(msg_warn_not_warned));
        Sendf(from, GetText(msg_warn_by_when),
              w->warner ? w->warner : UNKNOWN, TimeAgo(w->settime), buffer);
      }
    }
  }
  return (shown ? TRUE : FALSE);
}

/* --- WarnInit --------------------------------------------------- */

void WarnInit(void)
{
  char buf[MAXLINE];
  char by[MIDBUFFER];
  char nick[NICKLEN+1];
  char bigpattern[MIDBUFFER];
  char hostpattern[MIDBUFFER];
  char nickpattern[NICKLEN+1];
  char reason[BIGBUFFER];
  int flags, settime, last, warns;
  itemwarn *w;
  FILE *f;

  snapshot;
  warnHead = NewList(itemwarn);

  if ((char)0 == warnfile[0])
    return;

  f = fopen(warnfile, "r");
  if (f) {
    while (fgets(buf, MAXLINE, f)) {
      hostpattern[0] = nickpattern[0] = reason[0] = 0;
      if (7 <= StrScan(buf, "%"MIDBUFFERTXT"s %"NICKLENTXT"s %"MIDBUFFERTXT"s %d %d %d %d %"BIGBUFFERTXT"[^\n]",
                       by, nick, bigpattern, &warns, &settime, &last, &flags,
                       reason)) {
        /* Designed like this to work with the old nick-less version: */
        if (2 > StrScan(bigpattern, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                         nickpattern, hostpattern)) {
          StrCopyMax(hostpattern, sizeof(hostpattern), bigpattern);
          StrCopyMax(nickpattern, sizeof(nickpattern), "*");
        }

        w = NewEntry(itemwarn);
        if (w) {
          InsertLast(warnHead, w);
          w->pattern = StrDuplicate(hostpattern);
          w->nickpattern = StrDuplicate(nickpattern);
          w->warning = StrDuplicate(reason);
          w->warner  = !StrEqualCase(by, "*") ? StrDuplicate(by) : NULL;
          w->nick    = !StrEqualCase(nick, "*") ? StrDuplicate(nick) : NULL;
          w->warns   = warns;
          w->settime = settime;
          w->lastwarn= last;
          w->flags   = flags;
          w->id      = ++warnId;
        }
      }
    }
    fclose(f);
  }
}

/* --- WarnCheck -------------------------------------------------- */

bool WarnCheck(char *nick, char *userhost)
{
  char buffer[MIDBUFFER];
  itemwarn *w;

  snapshot;
  w = FindWarn(nick, userhost);
  if (w) {
    AlertMode(ALERT_ON);
    StrFormatMax(buffer, sizeof(buffer), "%s!%s",
                 w->nickpattern, w->pattern); /* If ban is wanted */
    if (botop && (w->flags & WARNF_KICKBAN)) {
      w->warns++;
      if (AddToBanList(BAN_SENTBAN, nickname, nick, buffer,
                       defbantime, w->warning) & BANISTHERE)
        Kick(nick, w->warning);
      else
        BanKick(nick, buffer, w->warning, NULL);
      w->lastwarn = now;
      Logf(LOGWARN, "Autokickbanned %s matched %s!%s", nick,
           w->nickpattern, w->pattern);
    }
    else if ((now - w->lastwarn) > WARNING_FREQUENCY) {
#ifdef HAVE_LIBFPL
      if (runfpl(RUN_ALERT, nick, FPLRUN_PRE))
        return TRUE;
#endif
      if (!mute) {
        w->warns++;
        Actionf(GetDefaultText(msg_alerts), nick, w->warning);
        w->lastwarn = now;
      }
      Logf(LOGWARN, "%s matches %s!%s, '%s'", nick, w->nickpattern,
           w->pattern, w->warning);
      runfpl(RUN_ALERT, nick, FPLRUN_POST);
    }
    return TRUE;
  }
  return FALSE;
}

/* --- CountWarns ------------------------------------------------- */

int CountWarns(int flags)
{
  int count = 0;
  itemwarn *w;

  snapshot;
  for (w = First(warnHead); w; w = Next(w)) {
    if (flags && !(w->flags & flags))
      continue;
    count++;
  }
  return count;
}


syntax highlighted by Code2HTML, v. 0.9.1