#ifdef RCS
static char rcsid[]="$Id: server.c,v 1.2 2001/03/13 05:04:47 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/server.c,v $
 * $Revision: 1.2 $
 * $Date: 2001/03/13 05:04:47 $
 * $Author: holsta $
 * $State: Exp $
 * $Locker:  $
 *
 * ---------------------------------------------------------------------------
 *****************************************************************************/

#include "dancer.h"
#include "trio.h"
#include "strio.h"
#include "numeric.h" /* From the irc server source */
#include "list.h"
#include "function.h"
#include "user.h"
#include "command.h"
#include "ctcp.h"
#include "transfer.h"
#include "seen.h"
#include "server.h"
#include "bans.h"
#include "link.h"
#include "servfunc.h"
#include "fplrun.h"

/* --- Global ----------------------------------------------------- */

extern time_t now;

extern char cmodes[], chanmodes[], ckey[], chankey[];
extern char channel[], nickname[], servername[];
extern char botmatch[], myaddr[];
extern char askforops[];
extern char *errfrom;

extern bool nickflag, connected, restart, moderateflag;
extern bool lockmode, lockkey, locklimit;
extern bool netsplitmode;
extern bool deopprotect;
extern bool talkative;
extern bool warnmode;
extern bool multimode;
extern bool cleanup;
extern bool invitable;
extern bool autojoin;
extern bool autoop;
extern bool dispcomment;
extern bool autoop;
extern bool kickbans;
extern bool autounban;
extern bool strictopmode;
extern bool fakenamemode;
extern bool opprotect;
extern bool oplevel;
extern bool mute;
extern bool possiblyfresh;
extern bool bantimeouts;
extern bool chat;
extern bool public;

extern int possiblyfreshtime;
extern int pubbantime;
extern int retry;

extern long climit;
extern long levels[];

extern itemident *current;


time_t topictime = 0;
time_t lastservertime = 0;

char topic[BIGBUFFER];    /* Topic of the channel */
char topicwho[BIGBUFFER]; /* Who made the topic?  */

bool botop = FALSE;       /* TRUE if the bot has channel operator status */

long numofbotmodes;
long modecount[11] = { 0,0,0,0,0,0,0,0,0,0,0 };
long autoopswaiting = 0;
long limitc = 0;

#define MINMODEPARAMS 3
static int maxmodeparams;

/* ---------------------------------------------------------------- */

struct
{
  char *name;
  void (*function)(char *a, char *b);
} messages[] =
{
  {"PRIVMSG", OnPrivmsg}, {"MODE",    OnMode},   {"JOIN",    OnJoin},
  {"PART",    OnPart},    {"QUIT",    OnQuit},   {"NICK",    OnNick},
  {"KICK",    OnKick},    {"PING",    OnPing},   {"PONG",    OnPong},
  {"TOPIC",   OnTopic},   {"NOTICE",  OnNotice}, {"INVITE",  OnInvite},
  {"ERROR",   OnError},
  {"", (void(*)())(NULL)}
};

#ifndef HAVE_MEMMOVE
void memmove(char *to, char *from, int len)
{
  int i;

  if (to > from) {
    for (i = len - 1; i >= 0; i--)
      to[i] = from[i];
  }
  else {
    for (i=0; i > len; i++)
      to[i] = from[i];
  }
}
#endif

/* --- ParseServer ------------------------------------------------ */

#include "server_gperf.c"

void ParseServer(char *line)
{
  char *from, *command, *rest, *end;

  snapshot;
  if (':' == line[0]) {
    from = line + 1;
    command = StrIndex(from, ' ');
    if (NULL == command)
      return;
    *command++ = (char)0;
  }
  else {
    from = servername;
    command = line;
  }

  rest = StrIndex(command, ' ');
  if (NULL == rest)
    return;
  *rest++ = (char)0;

  end = StrIndex(rest, '\n');
  if (end) {
    if ('\r' == end[-1])
      end--;
    *end = (char)0;
  }

  if (isdigit(command[0])) {
    OnNumeric(from, atoi(command), rest);
  }
  else {
    struct Servercmds *p;

    p = FindServerKey(command, StrLength(command));
    if (p) {
      lastservertime = now;
      p->function(from, rest);
    }
  }
}

/* --- OnPrivmsg -------------------------------------------------- */

void OnPrivmsg(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char target[MIDBUFFER], buffer[BIGBUFFER];
  char *xs;

  snapshot;
  if ('$' == line[0])  /* Ignore global messages */
    return;

  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (2 == StrScan(line, "%"MIDBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
                    target, buffer))) {

    errfrom = nick;
    public = IsChannel(target);
    chat = FALSE;

    xs = StrIndex(buffer, '\001');
    if (xs) {
      char ctcpfrom[MIDBUFFER];
      char ctcpbuffer[BIGBUFFER];
      char *ys;
      int len;

      do {
        ys = StrIndex(xs + 1, '\001');
        if (NULL == ys)
          break;  /* do - while */
        len = ys - xs - 1;
        if (len > 0) {
          memmove(ctcpbuffer, xs + 1, len);
          ctcpbuffer[len] = (char)0;
          len = StrLength(ys + 1);
          if (len > 0 ) {
            memmove(xs, ys + 1, len);
            xs[len] = (char)0;
          }
          else
            *xs = (char)0;

          StrCopyMax(ctcpfrom, sizeof(ctcpfrom), from);
          if (public)
            PubCTCP(ctcpfrom, ctcpbuffer);
          else
            CTCP(ctcpfrom, ctcpbuffer);
        }
        else
          xs += 2; /* Empty ctcp */
      } while (xs = StrIndex(xs, '\001'));
    }

    if (buffer[0]) {
      if (public)
        PubCommand(nick, userhost, buffer);
      else
        Command(nick, userhost, buffer);
    }
  }
  else
    Debug("Parse error in OnPrivmsg(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnNotice --------------------------------------------------- */

void OnNotice(char *from, char *line)
{
  char target[MIDBUFFER], buffer[BIGBUFFER];

  snapshot;
  if (2 == StrScan(line, "%"MIDBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
                   target, buffer)) {

    if (IsServer(from)) {  /* Server notices */
      char bywho[MIDBUFFER];
      char channelbuffer[MIDBUFFER];
      char modebuffer[BIGBUFFER];

      if (3 == StrScan(buffer, "Fake: %"MIDBUFFERTXT"s MODE %"MIDBUFFERTXT"s %"BIGBUFFERTXT"[^\n]",
                       bywho, channelbuffer, modebuffer)) {
        StrFormatMax(buffer, sizeof(buffer), "Fake: \"%s\" on channel %s by %s [%s]",
                     modebuffer, channelbuffer, bywho, from);
        Log(LOG, buffer);
        Multicast(REPORTCAST, buffer);
      }
    }
#if 0
    else {
      char nick[NICKLEN+1];
      char userhost[MIDBUFFER];
      itemguest *g;

      if (2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                       nick, userhost)) {
        g = FindNick(nick);
        if (g) {
          floodcheck();
        }
      }
    }
#endif
  }
  else
    Debug("Parse error in OnNotice(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnTopic ---------------------------------------------------- */

void OnTopic(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char target[MIDBUFFER], buffer[BIGBUFFER] = "";

  snapshot;
  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (1 <= StrScan(line, "%"MIDBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
                    target, buffer))) {

    Logf(LOGTOPIC, "\"%s\" by %s (%s)", buffer, nick, userhost);

#ifdef HAVE_LIBFPL
    if (runfpl(RUN_TOPIC, line, FPLRUN_PRE))
      return;
#endif

    StrCopyMax(topic, sizeof(topic), buffer);
    StrCopyMax(topicwho, sizeof(topicwho), from);
    topictime = now;

    runfpl(RUN_TOPIC, line, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnTopic(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnInvite --------------------------------------------------- */

void OnInvite(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char target[MIDBUFFER];
  itemuser *u;

  snapshot;
  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (1 == StrScan(line, "%*s :%"MIDBUFFERTXT"s", target))) {

    Logf(LOG, "Invited to %s by %s", target, from);

#ifdef HAVE_LIBFPL
    if (runfpl(RUN_INVITE, target, FPLRUN_PRE))
      return;
#endif

    u = FindUser(nick, userhost);
    if (u && (u->level >= LEVELTRUST)) {
      if (invitable) {
        errfrom = nick;
        CmdJoin(nick, target);
      }
      else
        Send(nick, GetDefaultText(msg_no_invitation));
    }
    runfpl(RUN_INVITE, target, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnInvite(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnMode ----------------------------------------------------- */

#define MAXMODEPARAMS 6
#define MAXSOLOMODES 8

struct Modeparamlist {
  char *param[MAXMODEPARAMS];
  int index;
};

void OnMode(char *from, char *line)
{
  char flagbuffer[MINIBUFFER], parambuffer[BIGBUFFER];
  char domode[MAXSOLOMODES+1], undomode[MAXSOLOMODES+1];
  char *userhost, *modes, *param;
  char c, cursign = '+';
  bool servermode, botmode;
  bool keymode = FALSE, limitmode = FALSE;
  bool dirty_hack = FALSE;
  int params = 0, i = 0, j = 0;
  itemguest *pc, *who;
  struct Modeparamlist chopon, chopoff, voiceon, voiceoff;
  struct Modeparamlist banon, banoff;
  struct Modeparamlist bxcepton, bxceptoff, ixcepton, ixceptoff;
  struct Modeparamlist temp;

  snapshot;
  StrTokenize(from, "!");
  userhost = StrTokenize(NULL, "");

  Logf(LOGMODE, "\"%s\" by %s%s%s%s", line, from,
       userhost ? " (" : "",
       userhost ? userhost : "",
       userhost ? ")" : "");

  if (!IsChannel(NextWord(line)))
    return; /* Ignore user mode changes */

  servermode = IsServer(from);
  if (servermode) {
    botmode = FALSE;
    who = NULL;
  }
  else {
    botmode = StrEqualCase(from, nickname);
    who = FindNick(from); /* Who did the mode change? */
  }

  modes = NextWord(line);
  if (NULL == modes)
    return; /* Parse error! */

  chopon.index = chopoff.index = voiceon.index = voiceoff.index = \
  banon.index = banoff.index = bxcepton.index = bxceptoff.index = \
  ixcepton.index = ixceptoff.index = 0;

  while (c = *modes++) {
    switch (c) {

      case '+':
      case '-':
        cursign = c;
        break;

      case 'o':
        param = NextWord(line);
        if (param) {
          params++;

          if ('+' == cursign) {
            modecount[0]++;
            if (MAXMODEPARAMS > chopon.index)
              chopon.param[chopon.index++] = param;
          }
          else {
            if (MAXMODEPARAMS > chopoff.index)
              chopoff.param[chopoff.index++] = param;
          }

          if (StrEqualCase(param, nickname)) {
            /* This is us! */
            if (talkative && !mute) {
              if ('+' == cursign) {
                if (!botop)
                  Actionf(GetDefaultText(msg_thanks_for_ops), from);
              }
              else {
                if (botop)
                  Actionf(GetDefaultText(msg_slaps_for_deop), from);
              }
            }

            botop = ('+' == cursign);
          }

          if (botmode) {
            pc = FindNick(param);
            if (pc) {
              pc->flags.chanop = ('+' == cursign);
              pc->flags.splitop = FALSE;
            }
          }
        }
        break;

      case 'b':
        param = NextWord(line);
        if (param) {
          params++;

          if ('+' == cursign) {
            modecount[1]++;
            if (MAXMODEPARAMS > banon.index)
              banon.param[banon.index++] = param;
          }
          else {
            if (MAXMODEPARAMS > banoff.index)
              banoff.param[banoff.index++] = param;
          }

          if (servermode) {
            if ('+' == cursign)
              AddToBanList(BAN_ACTUAL, from, NULL, param, 0, NULL);
          }
          else {
            if ('+' == cursign) {
              char *nick = NULL;

              if (who) {
                who->bans++;
                if (!botmode &&
                    (who->ident->level < LEVELBOT) &&
                    IllegalBan(param)) {
                  /*
                   * Not set by bot AND the banner has too little level AND
                   * matches the bot or a ban-proctected person (when
                   * banprotection is on).
                   */
                  Mode("%s-b%s%s %s",
                       dirty_hack ? "" : "-o",
                       dirty_hack ? "" : " ",
                       dirty_hack ? "" : from,
                       param);

                  /* Lets not deop the abuser again */
                  dirty_hack = TRUE;
                }
              }

              /*
               * We make an attempt to see if we can find a nick to attach
               * with the specified ban pattern. This works [pretty good]
               * in all cases when the ban is set before the kick...
               */
              pc = FindHost(param);
              if (pc) {
                nick = pc->ident->nick;
              }
              else {
                /*
                 * Ok, no user matched the banpattern that was set. We're
                 * going for more casual guesses. Now, let's take a quick look
                 * and see if any of the recent kicks match the pattern.
                 *
                 * We don't try the SEEN functions since they could be very
                 * extensive and take quite some time (a few months in #Amiga
                 * gives a lot more than 10000 hosts and 20000 nicks).
                 */
                itemkick *k;

                k = KickMatch(param);
                if (k)
                  nick = k->nick;
              }

              /* If we found the nick the 'guess-way', we set the GUESS bit */
              AddToBanList(BAN_ACTUAL | (nick ? BAN_GUESSNICK : 0),
                           from, nick, param,
                           botmode ? -1 : pubbantime, NULL);
            }
            else
              Unbanned(from, param, botmode);
          }

          if ('+' == cursign) {
            int num;

            num = CountBans(BAN_ACTUAL);
            if (BANLIST_ALERTSIZE <= num) {
              static time_t lastwarn = 0;

              if (!autounban || !botop || !UnbanLoprio(from)) {
                /*
                 * The bot can be told to automatically unban the least important
                 * ban when reaching this amount. If it isn't, or it isn't
                 * opped, or the unban failed for some reason, we procede
                 * (and make the ALERT output).
                 */

                if ((now - lastwarn) > 10*SECINMIN) {
                  /* Max-rate is every 10th minute! */
                  if (!mute)
                    Actionf(GetDefaultText(msg_alert_banlist_full), num);

                  Logf(LOGWARN, "Banlist contains %d bans!", num);
                  lastwarn = now;
                }
              }
            }
          }
        }
        break;

      case 'e':
        param = NextWord(line);
        if (param) {
          params++;

          if ('+' == cursign) {
            if (MAXMODEPARAMS > bxcepton.index)
              bxcepton.param[bxcepton.index++] = param;
          }
          else {
            if (MAXMODEPARAMS > bxceptoff.index)
              bxceptoff.param[bxceptoff.index++] = param;
          }
        }
        break;

      case 'I':
        param = NextWord(line);
        if (param) {
          params++;

          if ('+' == cursign) {
            if (MAXMODEPARAMS > ixcepton.index)
              ixcepton.param[ixcepton.index++] = param;
          }
          else {
            if (MAXMODEPARAMS > ixceptoff.index)
              ixceptoff.param[ixceptoff.index++] = param;
          }
        }
        break;

      case 'k':
        param = NextWord(line);
        if (param) {
          params++;

          if ('+' == cursign) {
            modecount[2]++;
            StrCopyMax(chankey, 32, param);
          }
          else {
            chankey[0] = (char)0;
          }
        }

        keymode = TRUE;
        break;

      case 'l':
        if ('+' == cursign) {
          modecount[3]++;

          param = NextWord(line);
          if (param) {
            params++;
            limitc = atoi(param);
          }
        }
        else {
          limitc = 0;
        }

        limitmode = TRUE;
        break;

      case 'v':
        param = NextWord(line);
        if (param) {
          params++;

          if ('+' == cursign)
            modecount[4]++;

          pc = FindNick(param);
          if (pc)
            pc->flags.voice = ('+' == cursign);
        }
        break;

      case 'p':
      case 's':
      case 'i':
      case 't':
      case 'n':
      case 'm': /* undo changes */
        if (StrIndex(cmodes, c)) {
          if ('-' == cursign) {
            if (MAXSOLOMODES > i)
              domode[i++] = c;
          }
        }
        else {
          if ('+' == cursign) {
            if (MAXSOLOMODES > j)
              undomode[j++] = c;
          }
        }

        if ('+' == cursign) {
          switch(c) {
            case 'p': modecount[5]++; break;
            case 's': modecount[6]++; break;
            case 'i': modecount[7]++; break;
            case 't': modecount[8]++; break;
            case 'n': modecount[9]++; break;
            case 'm': modecount[10]++; break;
          }
        }
        break;
    } /* switch */
  } /* while */

  domode[i] = undomode[j] = (char)0;

  if (maxmodeparams < params) {
    maxmodeparams = params;
  }

  /* Reacts to non-bot modes only */
  if (botmode) {
    numofbotmodes++; /* Bot changed a mode in the channel */
  }
  else {
    flagbuffer[0] = parambuffer[0] = (char)0;
    params = 0;

    /* Handle netsplit hacks, strictop and opprotect */
    for (i = temp.index = 0; i < chopon.index; i++) {
      pc = FindNick(chopon.param[i]);
      if (pc) {
        if (!pc->flags.chanop &&
            (pc->ident->level < MAX(LEVELRECOG, oplevel))) {
          if (servermode) {
            if (netsplitmode && !pc->flags.splitop) {
              temp.param[temp.index++] = chopon.param[i];
            }
          }
          else {
            /* Not by server */
            if (strictopmode) {
              if (!StrEqualCase(chopon.param[i], nickname) &&
                  who && (who->ident->level < MAX(LEVELBOT, oplevel))) {
                temp.param[temp.index++] = chopon.param[i];
              }
            }
          }
        }

        /* The guest *IS* chanop right now */
        pc->flags.chanop = TRUE;
      }
    }

    if (botop && temp.index) {
      /* We won't inform mass-oppers */
      if (!servermode && (1 == temp.index) && !mute) {
        SendNickf(from, GetDefaultText(msg_no_ops_allowed), temp.param[0]);
      }

      StrCopyMax(flagbuffer, sizeof(flagbuffer), "-");

      if (who && (who->ident->level < MAX(LEVELCHANOP, oplevel)) &&
          !dirty_hack) {
        dirty_hack = TRUE;
        StrAppendMax(flagbuffer, sizeof(flagbuffer), "o");
        StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s", from);
        params++;
      }

      for (i = 0; i < temp.index; i++) {
        if (maxmodeparams <= params) {
          Mode("%s%s", flagbuffer, parambuffer);
          StrCopyMax(flagbuffer, sizeof(flagbuffer), "-");
          parambuffer[0] = (char)0;
          params = 0;
        }

        StrAppendMax(flagbuffer, sizeof(flagbuffer), "o");
        StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s", temp.param[i]);
        params++;
      }
    }

    for (i = temp.index = 0; i < chopoff.index; i++) {
      pc = FindNick(chopoff.param[i]);
      if (pc) {
        if (pc->flags.chanop &&
            (pc->ident->level >= MAX(LEVELCHANOP, oplevel))) {
          if (servermode) {
            if (netsplitmode) {
              temp.param[temp.index++] = chopoff.param[i];
            }
          }
          else {
            if (opprotect) {
              if (who && (who->ident->level < MAX(LEVELCHANOP, oplevel))) {
                temp.param[temp.index++] = chopoff.param[i];
              }
            }
          }
        }

        /* Guest *IS NOT* a chanop anymore */
        pc->flags.chanop = pc->flags.splitop = FALSE;
      }
    }

    if (botop && temp.index) {
      if (who && (who->ident->level < MAX(LEVELCHANOP, oplevel)) &&
          !dirty_hack) {
        dirty_hack = TRUE;

        if (maxmodeparams <= params) {
          Mode("%s%s", flagbuffer, parambuffer);
          params = 0;
        }

        if (0 == params) {
          StrCopyMax(flagbuffer, sizeof(flagbuffer), "-");
          parambuffer[0] = (char)0;
        }

        StrAppendMax(flagbuffer, sizeof(flagbuffer), "o");
        StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s", from);
        params++;
      }

      if (maxmodeparams > params) {
        StrAppendMax(flagbuffer, sizeof(flagbuffer), "+");
      }

      for (i = 0; i < temp.index; i++) {
        if (maxmodeparams <= params) {
          Mode("%s%s", flagbuffer, parambuffer);
          StrCopyMax(flagbuffer, sizeof(flagbuffer), "+");
          parambuffer[0] = (char)0;
          params = 0;
        }

        StrAppendMax(flagbuffer, sizeof(flagbuffer), "o");
        StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s", temp.param[i]);
        params++;
      }
    }

    if (botop) {
      if (flagbuffer[0]) {
        Mode("%s%s", flagbuffer, parambuffer);
      }

      /* Only LEVELBOT or higher users are allowed to set/remove exceptions */
      if (who && (who->ident->level < MAX(LEVELBOT, oplevel))) {
        flagbuffer[0] = parambuffer[0] = (char)0;

        if (bxcepton.index || ixcepton.index) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "-");
        }

        /* Remove user +e's */
        for (i = 0; i < bxcepton.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "e");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             bxcepton.param[i]);
        }

        /* Remove user +I's */
        for (i = 0; i < ixcepton.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "I");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             ixcepton.param[i]);
        }

#if 0
/* We need to keep a list of exceptions to compare with */
        if (bxceptoff.index || ixceptoff.index) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "+");
        }

        /* Reset user -e's */
        for (i = 0; i < bxceptoff.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "e");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             bxceptoff.param[i]);
        }

        /* Reset user -I's */
        for (i = 0; i < ixceptoff.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "I");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             ixceptoff.param[i]);
        }
#endif

        if (flagbuffer[0]) {
          if (who->ident->level < MAX(LEVELCHANOP, oplevel))
            Mode("-o %s", from);
          Mode("%s%s", flagbuffer, parambuffer);
        }
      }

      /* React to channel key changes */
      if (lockkey && keymode) {
        if (servermode || (who && (who->ident->level < MAX(LEVELBOT, oplevel)))) {
          if (!StrEqualCase(ckey, chankey)) {
            if (chankey[0])
              Mode("-k %s", chankey);
            if (ckey[0])
              Mode("+k %s", ckey);
          }
        }
      }

      /* React to channel mode changes */
      if (lockmode && (domode[0] || undomode[0])) {
        if (servermode || (who && (who->ident->level < MAX(LEVELCHANOP, oplevel)))) {
          Mode("%s%s%s%s", domode[0] ? "+" : "", domode,
               undomode[0] ? "-" : "", undomode);
        }
      }

      /* React to channel limit changes */
      if (locklimit && limitmode) {
        if (servermode || (who && (who->ident->level < MAX(LEVELCHANOP, oplevel)))) {
          if (climit != limitc) {
            /* We got a limit we don't like */
            if (0 == climit)
              Mode("-l");
            else
              Mode("+l %d", climit);
          }
        }
      }
    }

    if (servermode) {
      /* Server actions are only here on net-heals! */
      NetHeal();

      /* Remove bans set by server */
      if (possiblyfreshtime &&
          ((now - possiblyfreshtime) < SERVERBANTIMEOUT)) {

        for (i = 0; i < banon.index; i++) {
          if (IsUnban(banon.param[i])) {
            /* If one of them bans matched an unbanned one! It means that we
               don't consider this a "fresh" split! */
            possiblyfreshtime = 0;
            break;
          }
        }

        if (possiblyfreshtime) {
          /* Don't unban serverbans */
          banon.index = 0;
        }
      }

      if (botop) {
        flagbuffer[0] = parambuffer[0] = (char)0;

        /* Remove +b server modes */
        for (i = 0; i < banon.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "b");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             banon.param[i]);
        }

        /* Remove all +e server modes for the moment */
        for (i = 0; i < bxcepton.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "e");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             bxcepton.param[i]);
        }

        /* Remove all +I server for the moment */
        for (i = 0; i < ixcepton.index; i++) {
          StrAppendMax(flagbuffer, sizeof(flagbuffer), "I");
          StrFormatAppendMax(parambuffer, sizeof(parambuffer), " %s",
                             ixcepton.param[i]);
        }

        if (flagbuffer[0])
          Mode("-%s%s", flagbuffer, parambuffer);
      }
    }
    else {
      /* Not server */
      if (deopprotect && (temp.index > 1) && botop && who)
        Warning(who, "deoppers", "Mass-deop detected");
    }
  }
}

/* --- OnJoin ----------------------------------------------------- */

void OnJoin(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char target[MIDBUFFER];
  char *servermodes, *pointer;
  extern char unetserv[];

  snapshot;
  switch (line[0]) {

    case ':':
      pointer = &line[1];
      break;

    default:
      pointer = line;
      break;

  }

  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (1 == StrScan(pointer, "%"MIDBUFFERTXT"s", target))) {

    runfpl(RUN_JOIN, nick, FPLRUN_PRE);

    /* ircd2.9 uses '\a' to append modes (server ops/voice) */
    servermodes = StrIndex(target, '\a');
    if (servermodes)
      *servermodes++ = (char)0;

    if (StrEqualCase(nick, nickname)) {
      StrCopyMax(channel, 200, target);
#ifdef NICKSERV
      WriteServer("CHANSERV op %s %s", channel, nickname);
#endif

#ifdef UNDERNET
      WriteServer("PRIVMSG %s :OP %s %s", unetserv, channel, nickname);
#endif
      WriteServer("WHO %s", target);    /* Who are on the channel */
      WriteServer("MODE %s", target);   /* What are the channel modes */
      WriteServer("MODE %s b", target); /* What are the bans */
    }
    else {
      bool nethealjoin = FALSE;
      itemguest *g;

      if (AddGuest(nick, userhost, FALSE, FALSE, FALSE, &g)) {
        if (moderateflag && botop)
          Mode("+v %s", nick);
      }
      else {
        /* Netjoin or error */
        nethealjoin = TRUE;
      }

      if (g) {
        current = g->ident;

        if (fakenamemode && g->ident->illegalname)
          StickyKick(g, GetDefaultText(msg_illegal_name));

        if (kickbans) {
          /* Check done even if not opped for the purpose of being better aware
             of the situation when later opped */
          if (IsBan(from))
            StickyKick(g, "Go away, you're banned here!");
        }

        if (servermodes) {
          /* Make a fake mode change */
          char xfrom[MIDBUFFER], xline[MIDBUFFER];

          StrCopyMax(xfrom, sizeof(xfrom), servername);
          StrFormatMax(xline, sizeof(xline), "%s +%s %s %s",
                       target, servermodes, nick,
                       (servermodes[1] ? nick : ""));
          OnMode(xfrom, xline);
        }

        if (autoop && IsAutoOp(g)) {
          /* DONT op anyone at this join, when several bots/people run services
             like this the channel simply gets flooded after long lasting netheals.
             Let's wait a (random) while before checking this user if it is op,
             and if it still isn't, op the poor thing. */
          g->op_this_person = now + Rnd()*10+10;
          autoopswaiting++;
        }

        if (multimode && (g->ident->level < LEVELEXPERT) && !g->flags.bot) {
          /* Do this check even if not opped, the knowledge can be good if we
             are suddenly opped in the middle of an attack */
          MultiCheck(g->ident->userdomain);
        }

        if (warnmode)
          WarnCheck(nick, userhost);

        if (dispcomment && !nethealjoin)
          DispComment(g);
      }
    }

    runfpl(RUN_JOIN, nick, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnJoin(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnPart ----------------------------------------------------- */

void OnPart(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char target[MIDBUFFER], buffer[BIGBUFFER] = "";

  snapshot;
  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (1 <= StrScan(line, "%"MIDBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
                    target, buffer))) {

    Logf(LOGPART, "%s%s%s%s", nick,
         buffer[0] ? " ("   : "",
         buffer[0] ? buffer : "",
         buffer[0] ? ")"    : "");

    runfpl(RUN_LEAVE, nick, FPLRUN_PRE);

    if (StrEqualCase(nick, nickname)) {  /* I left */
      SeenInsertAll(SEENLEFT, NULL, NULL);
      DeleteGuests();
      BanDisable(); /* No valid bans anymore */
    }
    else {
      itemguest *g;

      g = FindNick(nick);
      if (g) {
        SeenInsert(g, SEENLEAVE, NULL,
                   (buffer[0] && !StrEqualCase(buffer, nick)) ? buffer : NULL);
        RemoveGuest(g);
      }
      else
        Debug("Internal confusion. Unknown user \"%s\" left.", from);
    }

    runfpl(RUN_LEAVE, nick, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnPart(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnQuit ----------------------------------------------------- */

void OnQuit(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char buffer[BIGBUFFER] = "";

  snapshot;
  /* There might be no reason at all, ie. only ":\000" */
  StrScan(line, ":%"BIGBUFFERTXT"[^\n]", buffer);

  if (2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                   nick, userhost)) {

    Logf(LOGQUIT, "%s (%s)", nick, buffer);

    runfpl(RUN_QUIT, nick, FPLRUN_PRE);

    if (StrEqualCase(nick, nickname)) { /* I quitted */
      SeenInsertAll(SEENQUITED, NULL, NULL);
      DeleteGuests();
      BanDisable();
    }
    else {
      bool split = FALSE;
      itemguest *g;

      g = FindNick(nick);
      if (g) {
        /* Enhanced split detection to trap even more faked ones */
        if (PatternExist(buffer, "^[-A-Z0-9a-z.*]+[.][A-Za-z]+ [-A-Z0-9a-z.*]+[.][A-Za-z]+")) {
          char name1[MIDBUFFER];
          char name2[MIDBUFFER];
          char *p;

          if (2 == StrScan(buffer, "%"MIDBUFFERTXT"s %"MIDBUFFERTXT"s",
                           name1, name2)) {
            p = StrIndexLast(name1, '.');
            if (p && FindByCode(&p[1])) {
              p = StrIndexLast(name2, '.');
              if (p && FindByCode(&p[1]))
                split = TRUE;
            }
          }
        }

        SeenInsert(g, SEENQUIT, NULL, buffer);
        if (split)
          AddSplitter(g, buffer);
        else
          RemoveGuest(g);
      }
      else
        Debug("Internal confusion. Unknown user \"%s\" quit.", from);
    }

    runfpl(RUN_QUIT, nick, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnQuit(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnKick ----------------------------------------------------- */

void OnKick(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char target[MIDBUFFER], victim[NICKLEN+1], buffer[BIGBUFFER];

  snapshot;
  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (3 == StrScan(line, "%"MIDBUFFERTXT"s %"NICKLENTXT"s :%"BIGBUFFERTXT"[^\n]",
                    target, victim, buffer))) {

    Logf(LOGKICK, "(%s) by %s", line, from);

    runfpl(RUN_KICK, line, FPLRUN_PRE);

    if (StrEqualCase(victim, nickname)) { /* I was kicked */
      SeenInsertAll(SEENKICKED, from, NULL);
      DeleteGuests();
      BanDisable(); /* No valid bans anymore */
      if (autojoin)
        WriteServer("JOIN %s %s", channel, chankey);
#ifdef HAVE_LIBFPL
      if (!runfpl(RUN_KICKBOT, from, FPLRUN_PRE))
        runfpl(RUN_KICKBOT, from, FPLRUN_POST);
#endif
    }
    else {
      itemguest *g, *w;

      w = FindNick(nick);
      if (w)
        w->kicks++;

      g = FindNick(victim); /* This _CAN_ return NULL */
      if (g) {
        if (StrEqualCase(nick, nickname)) { /* Bot kick */
          AddKick(g->ident, from, buffer, KICK_COMMON);
        }
        else { /* Not a bot kick */
          AddKick(g->ident, from, buffer,
                  (g->ident->level < LEVELTRUST) ? KICK_COMMON : KICK_TRUSTEDUSER);

          if (opprotect) {
            if (botop && w &&
                (w->ident->level < MAX(LEVELCHANOP, oplevel)) &&
                (g->ident->level >= MAX(LEVELCHANOP, oplevel))) {
              Mode("-o %s", nick);
            }
          }
        }
        SeenInsert(g, SEENKICK, from, buffer);
        RemoveGuest(g);
      }
      else
        Debug("Internal confusion. Unknown user \"%s\" was kicked.", victim);
    }

    runfpl(RUN_KICK, target, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnKick(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnNick ----------------------------------------------------- */

void OnNick(char *from, char *line)
{
  char nick[NICKLEN+1], userhost[MIDBUFFER];
  char newnick[NICKLEN+1];

  snapshot;
  if ((2 == StrScan(from, "%"NICKLENTXT"[^!]!%"MIDBUFFERTXT"s",
                    nick, userhost)) &&
      (1 == StrScan(line, ":%"NICKLENTXT"s", newnick))) {

    Logf(LOGNICK, "%s is now known as %s (%s)", nick, newnick, userhost);

    runfpl(RUN_NICK, nick, FPLRUN_PRE);

    if (StrEqualCase(nick, nickname)) {
      StrCopy(nickname, newnick);
      StrFormatMax(botmatch, MIDBUFFER, "%s!%s", newnick, myaddr);
      SendLinkAll(IBCP_NICKNAME, "%s", newnick);
    }
    ChangeGuest(nick, newnick);

    runfpl(RUN_NICK, newnick, FPLRUN_POST);
  }
  else
    Debug("Parse error in OnNick(from = \"%s\", line = \"%s\")", from, line);
}

/* --- OnPing ----------------------------------------------------- */

void OnPing(char *from, char *line)
{
  char forward[MIDBUFFER];

  snapshot;
  if (1 == StrScan(line, "%*s %"MIDBUFFERTXT"s", forward))
    WriteServer("PONG %s", forward);
  else
    WriteServer("PONG %s", (':' == line[0]) ? &line[1] : line);
}

/* --- OnPong ----------------------------------------------------- */

int pendingpings = 0;
struct timeval pingval;
struct timeval pongval;
struct timeval delayval;

void OnPong(char *from, char *line)
{
  snapshot;
  pendingpings--;
  gettimeofday(&pongval, NULL);
  delayval.tv_sec = pongval.tv_sec - pingval.tv_sec;
  delayval.tv_usec = pongval.tv_usec - pingval.tv_usec;

  /* Normalize the time values */
  while (delayval.tv_usec > MILLION) {
    delayval.tv_usec -= MILLION;
    delayval.tv_sec++;
  }
  while (delayval.tv_usec < 0) {
    delayval.tv_usec += MILLION;
    delayval.tv_sec--;
  }
}

void PingServer(void)
{
  snapshot;
  pendingpings++;
  gettimeofday(&pingval, NULL);
  WriteServer("PING :%s", servername);
}

/* --- OnError ---------------------------------------------------- */

void OnError(char *from, char *line)
{
  char buffer[BIGBUFFER];

  snapshot;
#ifdef HAVE_LIBFPL
  if (!runfpl(RUN_ERROR, line, FPLRUN_PRE))
    runfpl(RUN_ERROR, line, FPLRUN_POST);
#endif

  StrFormatMax(buffer, sizeof(buffer), "ERROR: (%s) from %s",
               (':' == line[0]) ? &line[1] : line, from);
  Log(LOG, buffer);
  DisconnectServ(buffer);
  connected = FALSE;

  SeenInsertAll(SEENERROR, line, NULL);
  DeleteGuests();
  BanDisable();

  restart = cleanup = TRUE;
}

/* --- OnKill ----------------------------------------------------- */

#if 0
void OnKill(char *from, char *line)
{
  snapshot;
  Logf(LOGKILL, "(%s) by %s", line, from);

#ifdef HAVE_LIBFPL
  if (!runfpl(RUN_KILL, line, FPLRUN_PRE))
    runfpl(RUN_KILL, line, FPLRUN_POST);
#endif

  DisconnectServ(line);
  connected = FALSE;

  SeenInsertAll(SEENKILL, from, line);
  DeleteGuests();
  BanDisable();

  restart = cleanup = TRUE;
}
#endif

#ifdef NICKSERV
extern char nickpasswd[];
#endif

#ifdef UNDERNET
extern char unetpasswd[];
extern char unetnick[];
extern char unetserv[];
#endif

/* --- OnNumeric -------------------------------------------------- */

void OnNumeric(char *from, int num, char *line)
{
  char buffer[BIGBUFFER];
  char number[22];

  snapshot;
  StrFormatMax(number, sizeof(number), "%d", num);
  runfpl(RUN_NUMERIC, number, FPLRUN_PRE);

  switch (num) {

    case RPL_WELCOME: /* Welcomming message */
      connected = TRUE;
      StrCopyMax(servername, MIDBUFFER, from);
      StrScan(line, "%"NICKLENTXT"s", nickname);
#ifndef DBUG
      WriteServer("MODE %s +is", nickname);
#else
      WriteServer("MODE %s +i", nickname);
#endif
#ifdef NICKSERV
      WriteServer("NICKSERV identify %s", nickpasswd); 
#endif
#ifdef UNDERNET
      WriteServer("PRIVMSG %s :LOGIN %s %s", unetserv, unetnick, unetpasswd);
#endif
      WriteServer("JOIN %s %s", channel, chankey);
      gettimeofday(&pongval, NULL);
      retry = 0;
      maxmodeparams = MINMODEPARAMS;
      break;

    case ERR_NICKNAMEINUSE: /* Nickname already in use */
    case ERR_ERRONEUSNICKNAME: /* or wrong nickname */
    case ERR_UNAVAILRESOURCE: /* Nick/channel is temporarily unavailable */
      if (!nickflag)
        nickflag = TRUE;
      else {
        NewNick();
        WriteServer("NICK %s", nickname);
      }
      break;

    case ERR_NICKCOLLISION: /* Nickname collision */
      Log(LOG, "Nickname collision");
      restart = cleanup = TRUE;
      break;

    case ERR_NOTREGISTERED: /* You have not registered */
      Hello(NULL);
      break;

    case RPL_WHOREPLY:  /* Users on channel at join time */
      NewGuest_Who(line);
      break;

    case RPL_TOPIC:  /* The topic on join */
      topictime = now;
      topic[0] = (char)0;
      StrScan(line, "%*s %*s :%"BIGBUFFERTXT"[^\n]", topic);
      StrCopyMax(topicwho, sizeof(topicwho), from);
      break;

    case RPL_NOTOPIC:
      topictime = 0;
      break;

    case RPL_CHANNELMODEIS:
      chanmodes[0] = (char)0;
      StrScan(line, "%*s %*s +%15s", chanmodes);
      break;

    case RPL_BANLIST: /* get 3rd word */
      if (1 == StrScan(line, "%*s %*s %"BIGBUFFERTXT"s", buffer)) {
        /* fprintf(stderr, "BANLIST ENTRY: '%s'\n", buffer); */
        AddToBanList(BAN_ACTUAL, NULL, NULL, buffer, -1, NULL);
      }
      break;

    case RPL_ENDOFBANLIST:
      /* we're at the end of banlist */
      bantimeouts = TRUE; /* enable timeouts now */
      if (!CountBans(BAN_ACTUAL)) {
        /* no actual bans in the channel */
        possiblyfresh = TRUE; /* restarted server? */
        /* fprintf(stderr, "### no bans in channel! ###\n"); */
      }
      break;

    case ERR_NOSUCHNICK: /* Flush messages if target has disappeared */
    case ERR_NOSUCHCHANNEL:
      if (1 == StrScan(line, "%*s %"NICKLENTXT"s", buffer))
        MessageReaper(buffer);
      break;

    case ERR_CANNOTSENDTOCHAN:  /* Check for channel desync */
    case ERR_CHANOPRIVSNEEDED:
      if (!StrEqual(from, servername)) {
        StrFormatMax(buffer, sizeof(buffer), "Desync: Server %s rejected request", from);
        Log(LOG, buffer);
        Multicast(REPORTCAST, buffer);
      }
      break;

    case RPL_ENDOFNAMES: /* End of /NAMES list */
      if (*askforops && !botop)
        Action(askforops);
      break;

    case ERR_CHANNELISFULL:
      StrFormatMax(buffer, sizeof(buffer), "Channel %s is full", channel);
      Log(LOG, buffer);
      Multicast(REPORTCAST, buffer);
      break;

    case ERR_INVITEONLYCHAN:
      StrFormatMax(buffer, sizeof(buffer), "Channel %s is invite-only", channel);
      Log(LOG, buffer);
      Multicast(REPORTCAST, buffer);
      break;

    case ERR_BANNEDFROMCHAN:
      StrFormatMax(buffer, sizeof(buffer), "Banned from channel %s", channel);
      Log(LOG, buffer);
      Multicast(REPORTCAST, buffer);
      break;

    case ERR_BADCHANNELKEY:
      StrFormatMax(buffer, sizeof(buffer), "Bad channel key for %s", channel);
      Log(LOG, buffer);
      Multicast(REPORTCAST, buffer);
      break;

    case ERR_NOPERMFORHOST: /* Not I-lined */
      Logf(LOG, "No permission to connect to server %s", servername);
      break;

    case ERR_YOUREBANNEDCREEP: /* K-lined */
      Logf(LOG, "Banned from server %s", servername);
      break;

#if 0
      /* Messages we definetely ignore: */

      /* MOTD junk */
    case RPL_MOTDSTART:
    case RPL_MOTD:
    case RPL_ENDOFMOTD:

      /* Inital crap info: */
    case RPL_YOURHOST: /* 'Darxide :Your host is irc.ludd.luth.se, running
                           version 2.9.2+Cr8' */
    case RPL_CREATED: /* 'Darxide :This server was created Sun Jan 26 
                         1997 at 16:54:21 MET' */
    case RPL_MYINFO: /* 'Darxide irc.ludd.luth.se 2.9.2+Cr8 oirw abiklmnopqstv' */

      /* Luser statistics: */
    case RPL_LUSERCLIENT: /* 'Darxide :There are 11523 users and 0 services
                             on 61 servers' */
    case RPL_LUSEROP: /* 'Darxide 88 :operators online' */
    case RPL_LUSERUNKNOWN: /* 'Darxide 2 :unknown connections' */
    case RPL_LUSERCHANNELS: /* 'Darxide 4319 :channels formed' */
    case RPL_LUSERME: /* 'Darxide :I have 309 clients, 0 services and 3
                         servers' */

    case RPL_NAMREPLY: /* the name list we get automatically when we join a
                          channel */

    case RPL_ENDOFWHO: /* End of /WHO list */

#endif

    default: /* Recommended ending of a switch() */

      /* case ERR_RESTRICTED:  we're restricted here, could try to change to a
         different server */

      if (num >= ERR_NOSUCHNICK) {
        /* Log errors only */
        Logf(LOG, "Server message %d: '%s'", num, line);
      }
      break;
  }
  runfpl(RUN_NUMERIC, number, FPLRUN_POST);
}


syntax highlighted by Code2HTML, v. 0.9.1