/*
 * server.c -- part of server.mod
 *   basic irc server support
 *
 * $Id: server.c,v 1.124 2006-03-28 02:35:51 wcc Exp $
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2006 Eggheads Development Team
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

#define MODULE_NAME "server"
#define MAKING_SERVER

#include "src/mod/module.h"
#include "server.h"

static Function *global = NULL;

static int ctcp_mode;
static int serv;                /* sock # of server currently */
static char newserver[121];     /* new server? */
static int newserverport;       /* new server port? */
static char newserverpass[121]; /* new server password? */
static time_t trying_server;    /* trying to connect to a server right now? */
static int server_lag;          /* how lagged (in seconds) is the server? */
static char altnick[NICKLEN];   /* possible alternate nickname to use */
static char raltnick[NICKLEN];  /* random nick created from altnick */
static int curserv;             /* current position in server list: */
static int flud_thr;            /* msg flood threshold */
static int flud_time;           /* msg flood time */
static int flud_ctcp_thr;       /* ctcp flood threshold */
static int flud_ctcp_time;      /* ctcp flood time */
static char initserver[121];    /* what, if anything, to send to the
                                 * server on connection */
static char botuserhost[121];   /* bot's user@host (refreshed whenever the
                                 * bot joins a channel) */
                                /* may not be correct user@host BUT it's
                                 * how the server sees it */
static int keepnick;            /* keep trying to regain my intended
                                 * nickname? */
static int nick_juped = 0;      /* True if origbotname is juped(RPL437) (dw) */
static int check_stoned;        /* Check for a stoned server? */
static int serverror_quit;      /* Disconnect from server if ERROR
                                 * messages received? */
static int waiting_for_awake;   /* set when i unidle myself, cleared when
                                 * i get the response */
static time_t server_online;    /* server connection time */
static time_t server_cycle_wait;        /* seconds to wait before
                                         * re-beginning the server list */
static char botrealname[121];   /* realname of bot */
static int min_servs;           /* minimum number of servers to be around */
static int server_timeout;      /* server timeout for connecting */
static int never_give_up;       /* never give up when connecting to servers? */
static struct server_list *serverlist;  /* old-style queue, still used by
                                         * server list */
static int cycle_time;          /* cycle time till next server connect */
static int default_port;        /* default IRC port */
static char oldnick[NICKLEN];   /* previous nickname *before* rehash */
static int trigger_on_ignore;   /* trigger bindings if user is ignored ? */
static int exclusive_binds;     /* configures PUBM and MSGM binds to be
                                 * exclusive of PUB and MSG binds. */
static int answer_ctcp;         /* answer how many stacked ctcp's ? */
static int lowercase_ctcp;      /* answer lowercase CTCP's (non-standard) */
static int check_mode_r;        /* check for IRCnet +r modes */
static int net_type;
static char connectserver[121]; /* what, if anything, to do before connect
                                 * to the server */
static int resolvserv;          /* in the process of resolving a server host */
static int double_mode;         /* allow a msgs to be twice in a queue? */
static int double_server;
static int double_help;
static int double_warned;
static int lastpingtime;        /* IRCnet LAGmeter support -- drummer */
static char stackablecmds[511];
static char stackable2cmds[511];
static time_t last_time;
static int use_penalties;
static int use_fastdeq;
static int nick_len;            /* Maximal nick length allowed on the
                                 * network. */
static int kick_method;
static int optimize_kicks;


static p_tcl_bind_list H_wall, H_raw, H_notc, H_msgm, H_msg, H_flud, H_ctcr,
                       H_ctcp;

static void empty_msgq(void);
static void next_server(int *, char *, unsigned int *, char *);
static void disconnect_server(int);
static char *get_altbotnick(void);
static int calc_penalty(char *);
static int fast_deq(int);
static char *splitnicks(char **);
static void check_queues(char *, char *);
static void parse_q(struct msgq_head *, char *, char *);
static void purge_kicks(struct msgq_head *);
static int deq_kick(int);
static void msgq_clear(struct msgq_head *qh);
static int stack_limit;
static char *realservername;

#include "servmsg.c"

#define MAXPENALTY 10

/* Number of seconds to wait between transmitting queued lines to the server
 * lower this value at your own risk.  ircd is known to start flood control
 * at 512 bytes/2 seconds.
 */
#define msgrate 2

/* Maximum messages to store in each queue. */
static int maxqmsg;
static struct msgq_head mq, hq, modeq;
static int burst;

#include "cmdsserv.c"
#include "tclserv.c"


/*
 *     Bot server queues
 */

/* Called periodically to shove out another queued item.
 *
 * 'mode' queue gets priority now.
 *
 * Most servers will allow 'busts' of upto 5 msgs, so let's put something
 * in to support flushing modeq a little faster if possible.
 * Will send upto 4 msgs from modeq, and then send 1 msg every time
 * it will *not* send anything from hq until the 'burst' value drops
 * down to 0 again (allowing a sudden mq flood to sneak through).
 */
static void deq_msg()
{
  struct msgq *q;
  int ok = 0;

  /* now < last_time tested 'cause clock adjustments could mess it up */
  if ((now - last_time) >= msgrate || now < (last_time - 90)) {
    last_time = now;
    if (burst > 0)
      burst--;
    ok = 1;
  }

  if (serv < 0)
    return;

  /* Send upto 4 msgs to server if the *critical queue* has anything in it */
  if (modeq.head) {
    while (modeq.head && (burst < 4) && ((last_time - now) < MAXPENALTY)) {
      if (deq_kick(DP_MODE)) {
        burst++;
        continue;
      }
      if (!modeq.head)
        break;
      if (fast_deq(DP_MODE)) {
        burst++;
        continue;
      }
      write_to_server(modeq.head->msg, modeq.head->len);
      modeq.tot--;
      last_time += calc_penalty(modeq.head->msg);
      if (raw_log)
        putlog(LOG_SRVOUT, "*", "[m->] %s", modeq.head->msg);
      q = modeq.head->next;
      nfree(modeq.head->msg);
      nfree(modeq.head);
      modeq.head = q;
      burst++;
    }
    if (!modeq.head)
      modeq.last = 0;
    return;
  }

  /* Send something from the normal msg q even if we're slightly bursting */
  if (burst > 1)
    return;

  if (mq.head) {
    burst++;

    if (deq_kick(DP_SERVER))
      return;

    if (fast_deq(DP_SERVER))
      return;

    write_to_server(mq.head->msg, mq.head->len);
    mq.tot--;
    last_time += calc_penalty(mq.head->msg);
    if (raw_log)
      putlog(LOG_SRVOUT, "*", "[s->] %s", mq.head->msg);
    q = mq.head->next;
    nfree(mq.head->msg);
    nfree(mq.head);
    mq.head = q;
    if (!mq.head)
      mq.last = NULL;
    return;
  }

  /* Never send anything from the help queue unless everything else is
   * finished.
   */
  if (!hq.head || burst || !ok)
    return;

  if (deq_kick(DP_HELP))
    return;

  if (fast_deq(DP_HELP))
    return;

  write_to_server(hq.head->msg, hq.head->len);
  hq.tot--;
  last_time += calc_penalty(hq.head->msg);
  if (raw_log)
    putlog(LOG_SRVOUT, "*", "[h->] %s", hq.head->msg);
  q = hq.head->next;
  nfree(hq.head->msg);
  nfree(hq.head);
  hq.head = q;
  if (!hq.head)
    hq.last = NULL;
}

static int calc_penalty(char *msg)
{
  char *cmd, *par1, *par2, *par3;
  register int penalty, i, ii;

  if (!use_penalties && net_type != NETT_UNDERNET &&
      net_type != NETT_HYBRID_EFNET)
    return 0;

  cmd = newsplit(&msg);
  if (msg)
    i = strlen(msg);
  else
    i = strlen(cmd);
  last_time -= 2;               /* undo eggdrop standard flood prot */
  if (net_type == NETT_UNDERNET || net_type == NETT_HYBRID_EFNET) {
    last_time += (2 + i / 120);
    return 0;
  }
  penalty = (1 + i / 100);
  if (!egg_strcasecmp(cmd, "KICK")) {
    par1 = newsplit(&msg);      /* channel */
    par2 = newsplit(&msg);      /* victim(s) */
    par3 = splitnicks(&par2);
    penalty++;
    while (strlen(par3) > 0) {
      par3 = splitnicks(&par2);
      penalty++;
    }
    ii = penalty;
    par3 = splitnicks(&par1);
    while (strlen(par1) > 0) {
      par3 = splitnicks(&par1);
      penalty += ii;
    }
  } else if (!egg_strcasecmp(cmd, "MODE")) {
    i = 0;
    par1 = newsplit(&msg);      /* channel */
    par2 = newsplit(&msg);      /* mode(s) */
    if (!strlen(par2))
      i++;
    while (strlen(par2) > 0) {
      if (strchr("ntimps", par2[0]))
        i += 3;
      else if (!strchr("+-", par2[0]))
        i += 1;
      par2++;
    }
    while (strlen(msg) > 0) {
      newsplit(&msg);
      i += 2;
    }
    ii = 0;
    while (strlen(par1) > 0) {
      splitnicks(&par1);
      ii++;
    }
    penalty += (ii * i);
  } else if (!egg_strcasecmp(cmd, "TOPIC")) {
    penalty++;
    par1 = newsplit(&msg);      /* channel */
    par2 = newsplit(&msg);      /* topic */
    if (strlen(par2) > 0) {     /* topic manipulation => 2 penalty points */
      penalty += 2;
      par3 = splitnicks(&par1);
      while (strlen(par1) > 0) {
        par3 = splitnicks(&par1);
        penalty += 2;
      }
    }
  } else if (!egg_strcasecmp(cmd, "PRIVMSG") ||
             !egg_strcasecmp(cmd, "NOTICE")) {
    par1 = newsplit(&msg);      /* channel(s)/nick(s) */
    /* Add one sec penalty for each recipient */
    while (strlen(par1) > 0) {
      splitnicks(&par1);
      penalty++;
    }
  } else if (!egg_strcasecmp(cmd, "WHO")) {
    par1 = newsplit(&msg);      /* masks */
    par2 = par1;
    while (strlen(par1) > 0) {
      par2 = splitnicks(&par1);
      if (strlen(par2) > 4)     /* long WHO-masks receive less penalty */
        penalty += 3;
      else
        penalty += 5;
    }
  } else if (!egg_strcasecmp(cmd, "AWAY")) {
    if (strlen(msg) > 0)
      penalty += 2;
    else
      penalty += 1;
  } else if (!egg_strcasecmp(cmd, "INVITE")) {
    /* Successful invite receives 2 or 3 penalty points. Let's go
     * with the maximum.
     */
    penalty += 3;
  } else if (!egg_strcasecmp(cmd, "JOIN")) {
    penalty += 2;
  } else if (!egg_strcasecmp(cmd, "PART")) {
    penalty += 4;
  } else if (!egg_strcasecmp(cmd, "VERSION")) {
    penalty += 2;
  } else if (!egg_strcasecmp(cmd, "TIME")) {
    penalty += 2;
  } else if (!egg_strcasecmp(cmd, "TRACE")) {
    penalty += 2;
  } else if (!egg_strcasecmp(cmd, "NICK")) {
    penalty += 3;
  } else if (!egg_strcasecmp(cmd, "ISON")) {
    penalty += 1;
  } else if (!egg_strcasecmp(cmd, "WHOIS")) {
    penalty += 2;
  } else if (!egg_strcasecmp(cmd, "DNS")) {
    penalty += 2;
  } else
    penalty++;                  /* just add standard-penalty */
  /* Shouldn't happen, but you never know... */
  if (penalty > 99)
    penalty = 99;
  if (penalty < 2) {
    putlog(LOG_SRVOUT, "*", "Penalty < 2sec; that's impossible!");
    penalty = 2;
  }
  if (raw_log && penalty != 0)
    putlog(LOG_SRVOUT, "*", "Adding penalty: %i", penalty);
  return penalty;
}

char *splitnicks(char **rest)
{
  register char *o, *r;

  if (!rest)
    return *rest = "";
  o = *rest;
  while (*o == ' ')
    o++;
  r = o;
  while (*o && *o != ',')
    o++;
  if (*o)
    *o++ = 0;
  *rest = o;
  return r;
}

static int fast_deq(int which)
{
  struct msgq_head *h;
  struct msgq *m, *nm;
  char msgstr[511], nextmsgstr[511], tosend[511], victims[511], stackable[511],
       *msg, *nextmsg, *cmd, *nextcmd, *to, *nextto, *stckbl;
  int len, doit = 0, found = 0, cmd_count = 0, stack_method = 1;

  if (!use_fastdeq)
    return 0;

  switch (which) {
  case DP_MODE:
    h = &modeq;
    break;
  case DP_SERVER:
    h = &mq;
    break;
  case DP_HELP:
    h = &hq;
    break;
  default:
    return 0;
  }

  m = h->head;
  strncpyz(msgstr, m->msg, sizeof msgstr);
  msg = msgstr;
  cmd = newsplit(&msg);
  if (use_fastdeq > 1) {
    strncpyz(stackable, stackablecmds, sizeof stackable);
    stckbl = stackable;
    while (strlen(stckbl) > 0) {
      if (!egg_strcasecmp(newsplit(&stckbl), cmd)) {
        found = 1;
        break;
      }
    }

    /* If use_fastdeq is 2, only commands in the list should be stacked. */
    if (use_fastdeq == 2 && !found)
      return 0;

    /* If use_fastdeq is 3, only commands _not_ in the list should be stacked. */
    if (use_fastdeq == 3 && found)
      return 0;

    /* we check for the stacking method (default=1) */
    strncpyz(stackable, stackable2cmds, sizeof stackable);
    stckbl = stackable;
    while (strlen(stckbl) > 0)
      if (!egg_strcasecmp(newsplit(&stckbl), cmd)) {
        stack_method = 2;
        break;
      }
  }
  to = newsplit(&msg);
  len = strlen(to);
  simple_sprintf(victims, "%s", to);
  while (m) {
    nm = m->next;
    if (!nm)
      break;
    strncpyz(nextmsgstr, nm->msg, sizeof nextmsgstr);
    nextmsg = nextmsgstr;
    nextcmd = newsplit(&nextmsg);
    nextto = newsplit(&nextmsg);
    len = strlen(nextto);
    if (strcmp(to, nextto) && !strcmp(cmd, nextcmd) && !strcmp(msg, nextmsg) &&
        ((strlen(cmd) + strlen(victims) + strlen(nextto) + strlen(msg) + 2) <
        510) && (!stack_limit || cmd_count < stack_limit - 1)) {
      cmd_count++;
      if (stack_method == 1)
        simple_sprintf(victims, "%s,%s", victims, nextto);
      else
        simple_sprintf(victims, "%s %s", victims, nextto);
      doit = 1;
      m->next = nm->next;
      if (!nm->next)
        h->last = m;
      nfree(nm->msg);
      nfree(nm);
      h->tot--;
    } else
      m = m->next;
  }
  if (doit) {
    simple_sprintf(tosend, "%s %s %s", cmd, victims, msg);
    len = strlen(tosend);
    write_to_server(tosend, len);
    m = h->head->next;
    nfree(h->head->msg);
    nfree(h->head);
    h->head = m;
    if (!h->head)
      h->last = 0;
    h->tot--;
    last_time += calc_penalty(tosend);
    if (raw_log) {
      switch (which) {
      case DP_MODE:
        putlog(LOG_SRVOUT, "*", "[m=>] %s", tosend);
        break;
      case DP_SERVER:
        putlog(LOG_SRVOUT, "*", "[s=>] %s", tosend);
        break;
      case DP_HELP:
        putlog(LOG_SRVOUT, "*", "[h=>] %s", tosend);
        break;
      }
    }
    return 1;
  }
  return 0;
}

static void check_queues(char *oldnick, char *newnick)
{
  if (optimize_kicks != 2)
    return;
  if (modeq.head)
    parse_q(&modeq, oldnick, newnick);
  if (mq.head)
    parse_q(&mq, oldnick, newnick);
  if (hq.head)
    parse_q(&hq, oldnick, newnick);
}

static void parse_q(struct msgq_head *q, char *oldnick, char *newnick)
{
  struct msgq *m, *lm = NULL;
  char buf[511], *msg, *nicks, *nick, *chan, newnicks[511], newmsg[511];
  int changed;

  for (m = q->head; m;) {
    changed = 0;
    if (optimize_kicks == 2 && !egg_strncasecmp(m->msg, "KICK ", 5)) {
      newnicks[0] = 0;
      strncpyz(buf, m->msg, sizeof buf);
      msg = buf;
      newsplit(&msg);
      chan = newsplit(&msg);
      nicks = newsplit(&msg);
      while (strlen(nicks) > 0) {
        nick = splitnicks(&nicks);
        if (!egg_strcasecmp(nick, oldnick) &&
            ((9 + strlen(chan) + strlen(newnicks) + strlen(newnick) +
              strlen(nicks) + strlen(msg)) < 510)) {
          if (newnick)
            egg_snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks, newnick);
          changed = 1;
        } else
          egg_snprintf(newnicks, sizeof newnicks, ",%s", nick);
      }
      egg_snprintf(newmsg, sizeof newmsg, "KICK %s %s %s", chan,
                   newnicks + 1, msg);
    }
    if (changed) {
      if (newnicks[0] == 0) {
        if (!lm)
          q->head = m->next;
        else
          lm->next = m->next;
        nfree(m->msg);
        nfree(m);
        m = lm;
        q->tot--;
        if (!q->head)
          q->last = 0;
      } else {
        nfree(m->msg);
        m->msg = nmalloc(strlen(newmsg) + 1);
        m->len = strlen(newmsg);
        strcpy(m->msg, newmsg);
      }
    }
    lm = m;
    if (m)
      m = m->next;
    else
      m = q->head;
  }
}

static void purge_kicks(struct msgq_head *q)
{
  struct msgq *m, *lm = NULL;
  char buf[511], *reason, *nicks, *nick, *chan, newnicks[511],
       newmsg[511], chans[511], *chns, *ch;
  int changed, found;
  struct chanset_t *cs;

  for (m = q->head; m;) {
    if (!egg_strncasecmp(m->msg, "KICK", 4)) {
      newnicks[0] = 0;
      changed = 0;
      strncpyz(buf, m->msg, sizeof buf);
      reason = buf;
      newsplit(&reason);
      chan = newsplit(&reason);
      nicks = newsplit(&reason);
      while (strlen(nicks) > 0) {
        found = 0;
        nick = splitnicks(&nicks);
        strncpyz(chans, chan, sizeof chans);
        chns = chans;
        while (strlen(chns) > 0) {
          ch = newsplit(&chns);
          cs = findchan(ch);
          if (!cs)
            continue;
          if (ismember(cs, nick))
            found = 1;
        }
        if (found)
          egg_snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks, nick);
        else {
          putlog(LOG_SRVOUT, "*", "%s isn't on any target channel; removing "
                 "kick.", nick);
          changed = 1;
        }
      }
      if (changed) {
        if (newnicks[0] == 0) {
          if (!lm)
            q->head = m->next;
          else
            lm->next = m->next;
          nfree(m->msg);
          nfree(m);
          m = lm;
          q->tot--;
          if (!q->head)
            q->last = 0;
        } else {
          nfree(m->msg);
          egg_snprintf(newmsg, sizeof newmsg, "KICK %s %s %s", chan,
                       newnicks + 1, reason);
          m->msg = nmalloc(strlen(newmsg) + 1);
          m->len = strlen(newmsg);
          strcpy(m->msg, newmsg);
        }
      }
    }
    lm = m;
    if (m)
      m = m->next;
    else
      m = q->head;
  }
}

static int deq_kick(int which)
{
  struct msgq_head *h;
  struct msgq *msg, *m, *lm;
  char buf[511], buf2[511], *reason2, *nicks, *chan, *chan2, *reason, *nick,
       newnicks[511], newnicks2[511], newmsg[511];
  int changed = 0, nr = 0;

  if (!optimize_kicks)
    return 0;

  newnicks[0] = 0;
  switch (which) {
  case DP_MODE:
    h = &modeq;
    break;
  case DP_SERVER:
    h = &mq;
    break;
  case DP_HELP:
    h = &hq;
    break;
  default:
    return 0;
  }

  if (egg_strncasecmp(h->head->msg, "KICK", 4))
    return 0;

  if (optimize_kicks == 2) {
    purge_kicks(h);
    if (!h->head)
      return 1;
  }

  if (egg_strncasecmp(h->head->msg, "KICK", 4))
    return 0;

  msg = h->head;
  strncpyz(buf, msg->msg, sizeof buf);
  reason = buf;
  newsplit(&reason);
  chan = newsplit(&reason);
  nicks = newsplit(&reason);
  while (strlen(nicks) > 0) {
    egg_snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks,
                 newsplit(&nicks));
    nr++;
  }
  for (m = msg->next, lm = NULL; m && (nr < kick_method);) {
    if (!egg_strncasecmp(m->msg, "KICK", 4)) {
      changed = 0;
      newnicks2[0] = 0;
      strncpyz(buf2, m->msg, sizeof buf2);
      reason2 = buf2;
      newsplit(&reason2);
      chan2 = newsplit(&reason2);
      nicks = newsplit(&reason2);
      if (!egg_strcasecmp(chan, chan2) && !egg_strcasecmp(reason, reason2)) {
        while (strlen(nicks) > 0) {
          nick = splitnicks(&nicks);
          if ((nr < kick_method) && ((9 + strlen(chan) + strlen(newnicks) +
              strlen(nick) + strlen(reason)) < 510)) {
            egg_snprintf(newnicks, sizeof newnicks, "%s,%s", newnicks, nick);
            nr++;
            changed = 1;
          } else
            egg_snprintf(newnicks2, sizeof newnicks2, "%s,%s", newnicks2, nick);
        }
      }
      if (changed) {
        if (newnicks2[0] == 0) {
          if (!lm)
            h->head->next = m->next;
          else
            lm->next = m->next;
          nfree(m->msg);
          nfree(m);
          m = lm;
          h->tot--;
          if (!h->head)
            h->last = 0;
        } else {
          nfree(m->msg);
          egg_snprintf(newmsg, sizeof newmsg, "KICK %s %s %s", chan2,
                       newnicks2 + 1, reason);
          m->msg = nmalloc(strlen(newmsg) + 1);
          m->len = strlen(newmsg);
          strcpy(m->msg, newmsg);
        }
      }
    }
    lm = m;
    if (m)
      m = m->next;
    else
      m = h->head->next;
  }
  egg_snprintf(newmsg, sizeof newmsg, "KICK %s %s %s", chan, newnicks + 1,
               reason);
  write_to_server(newmsg, strlen(newmsg));
  h->tot--;
  last_time += calc_penalty(newmsg);
  if (raw_log) {
    switch (which) {
    case DP_MODE:
      putlog(LOG_SRVOUT, "*", "[m->] %s", newmsg);
      break;
    case DP_SERVER:
      putlog(LOG_SRVOUT, "*", "[s->] %s", newmsg);
      break;
    case DP_HELP:
      putlog(LOG_SRVOUT, "*", "[h->] %s", newmsg);
      break;
    }
    debug3("Changed: %d, kick-method: %d, nr: %d", changed, kick_method, nr);
  }
  m = h->head->next;
  nfree(h->head->msg);
  nfree(h->head);
  h->head = m;
  if (!h->head)
    h->last = 0;
  return 1;
}

/* Clean out the msg queues (like when changing servers).
 */
static void empty_msgq()
{
  msgq_clear(&modeq);
  msgq_clear(&mq);
  msgq_clear(&hq);
  burst = 0;
}

/* Queues outgoing messages so there's no flooding.
 */
static void queue_server(int which, char *buf, int len)
{
  struct msgq_head *h = NULL, tempq;
  struct msgq *q, *tq, *tqq;
  int doublemsg = 0, qnext = 0;

  /* Don't even BOTHER if there's no server online. */
  if (serv < 0)
    return;

  /* Remove \r\n. We will add these back when we send the text to the server.
   * - Wcc [01/09/2004]
   */
  remove_crlf(&buf);
  buf[510] = 0;
  len = strlen(buf);

  /* No queue for PING and PONG - drummer */
  if (!egg_strncasecmp(buf, "PING", 4) || !egg_strncasecmp(buf, "PONG", 4)) {
    if (buf[1] == 'I' || buf[1] == 'i')
      lastpingtime = now;
    write_to_server(buf, len);
    if (raw_log)
      putlog(LOG_SRVOUT, "*", "[m->] %s", buf);
    return;
  }

  switch (which) {
  case DP_MODE_NEXT:
    qnext = 1;
    /* Fallthrough */

  case DP_MODE:
    h = &modeq;
    tempq = modeq;
    if (double_mode)
      doublemsg = 1;
    break;

  case DP_SERVER_NEXT:
    qnext = 1;
    /* Fallthrough */

  case DP_SERVER:
    h = &mq;
    tempq = mq;
    if (double_server)
      doublemsg = 1;
    break;

  case DP_HELP_NEXT:
    qnext = 1;
    /* Fallthrough */

  case DP_HELP:
    h = &hq;
    tempq = hq;
    if (double_help)
      doublemsg = 1;
    break;

  default:
    putlog(LOG_MISC, "*", "Warning: queuing unknown type to server!");
    return;
  }

  if (h->tot < maxqmsg) {
    /* Don't queue msg if it's already queued?  */
    if (!doublemsg) {
      for (tq = tempq.head; tq; tq = tqq) {
        tqq = tq->next;
        if (!egg_strcasecmp(tq->msg, buf)) {
          if (!double_warned) {
            debug1("Message already queued; skipping: %s", buf);
            double_warned = 1;
          }
          return;
        }
      }
    }

    q = nmalloc(sizeof(struct msgq));

    /* Insert into queue. */
    if (qnext) {
      q->next = h->head;
      h->head = q;
      if (!h->last)
        h->last = q;
    }
    else {
      q->next = NULL;
      if (h->last)
        h->last->next = q;
      else
        h->head = q;
      h->last = q;
    }

    q->len = len;
    q->msg = nmalloc(len + 1);
    egg_memcpy(q->msg, buf, len);
    q->msg[len] = 0;
    h->tot++;
    h->warned = 0;
    double_warned = 0;

    if (raw_log) {
      switch (which) {
      case DP_MODE:
        putlog(LOG_SRVOUT, "*", "[!m] %s", buf);
        break;
      case DP_SERVER:
        putlog(LOG_SRVOUT, "*", "[!s] %s", buf);
        break;
      case DP_HELP:
        putlog(LOG_SRVOUT, "*", "[!h] %s", buf);
        break;
      case DP_MODE_NEXT:
        putlog(LOG_SRVOUT, "*", "[!!m] %s", buf);
        break;
      case DP_SERVER_NEXT:
        putlog(LOG_SRVOUT, "*", "[!!s] %s", buf);
        break;
      case DP_HELP_NEXT:
        putlog(LOG_SRVOUT, "*", "[!!h] %s", buf);
        break;
      }
    }
  } else {
    if (!h->warned) {
      switch (which) {
      case DP_MODE_NEXT:
        /* Fallthrough */
      case DP_MODE:
        putlog(LOG_MISC, "*", "Warning: over maximum mode queue!");
        break;

      case DP_SERVER_NEXT:
        /* Fallthrough */
      case DP_SERVER:
        putlog(LOG_MISC, "*", "Warning: over maximum server queue!");
        break;

      case DP_HELP_NEXT:
        /* Fallthrough */
      case DP_HELP:
        putlog(LOG_MISC, "*", "Warning: over maximum help queue!");
        break;
      }
    }
    h->warned = 1;
  }

  if (which == DP_MODE || which == DP_MODE_NEXT)
    deq_msg(); /* DP_MODE needs to be sent ASAP, flush if possible. */
}

/* Add a new server to the server_list.
 */
static void add_server(char *ss)
{
  struct server_list *x, *z;
  char *p, *q;

  for (z = serverlist; z && z->next; z = z->next);
  while (ss) {
    p = strchr(ss, ',');
    if (p)
      *p++ = 0;
    x = nmalloc(sizeof(struct server_list));

    x->next = 0;
    x->realname = 0;
    x->port = 0;
    if (z)
      z->next = x;
    else
      serverlist = x;
    z = x;
    q = strchr(ss, ':');
    if (!q) {
      x->port = default_port;
      x->pass = 0;
      x->name = nmalloc(strlen(ss) + 1);
      strcpy(x->name, ss);
    } else {
      *q++ = 0;
      x->name = nmalloc(q - ss);
      strcpy(x->name, ss);
      ss = q;
      q = strchr(ss, ':');
      if (!q) {
        x->pass = 0;
      } else {
        *q++ = 0;
        x->pass = nmalloc(strlen(q) + 1);
        strcpy(x->pass, q);
      }
      x->port = atoi(ss);
    }
    ss = p;
  }
}


/* Clear out the given server_list.
 */
static void clearq(struct server_list *xx)
{
  struct server_list *x;

  while (xx) {
    x = xx->next;
    if (xx->name)
      nfree(xx->name);
    if (xx->pass)
      nfree(xx->pass);
    if (xx->realname)
      nfree(xx->realname);
    nfree(xx);
    xx = x;
  }
}

/* Set botserver to the next available server.
 *
 * -> if (*ptr == -1) then jump to that particular server
 */
static void next_server(int *ptr, char *serv, unsigned int *port, char *pass)
{
  struct server_list *x = serverlist;
  int i = 0;

  if (x == NULL)
    return;
  /* -1  -->  Go to specified server */
  if (*ptr == -1) {
    for (; x; x = x->next) {
      if (x->port == *port) {
        if (!egg_strcasecmp(x->name, serv)) {
          *ptr = i;
          return;
        } else if (x->realname && !egg_strcasecmp(x->realname, serv)) {
          *ptr = i;
          strncpyz(serv, x->realname, sizeof serv);
          return;
        }
      }
      i++;
    }
    /* Gotta add it: */
    x = nmalloc(sizeof(struct server_list));

    x->next = 0;
    x->realname = 0;
    x->name = nmalloc(strlen(serv) + 1);
    strcpy(x->name, serv);
    x->port = *port ? *port : default_port;
    if (pass && pass[0]) {
      x->pass = nmalloc(strlen(pass) + 1);
      strcpy(x->pass, pass);
    } else
      x->pass = NULL;
    list_append((struct list_type **) (&serverlist), (struct list_type *) x);
    *ptr = i;
    return;
  }
  /* Find where i am and boogie */
  i = (*ptr);
  while (i > 0 && x != NULL) {
    x = x->next;
    i--;
  }
  if (x != NULL) {
    x = x->next;
    (*ptr)++;
  }                             /* Go to next server */
  if (x == NULL) {
    x = serverlist;
    *ptr = 0;
  }                             /* Start over at the beginning */
  strcpy(serv, x->name);
  *port = x->port ? x->port : default_port;
  if (x->pass)
    strcpy(pass, x->pass);
  else
    pass[0] = 0;
}

static int server_6char STDVAR
{
  Function F = (Function) cd;
  char x[20];

  BADARGS(7, 7, " nick user@host handle dest/chan keyword text");

  CHECKVALIDITY(server_6char);
  egg_snprintf(x, sizeof x, "%d",
               F(argv[1], argv[2], argv[3], argv[4], argv[5], argv[6]));
  Tcl_AppendResult(irp, x, NULL);
  return TCL_OK;
}

static int server_5char STDVAR
{
  Function F = (Function) cd;

  BADARGS(6, 6, " nick user@host handle dest/channel text");

  CHECKVALIDITY(server_5char);
  F(argv[1], argv[2], argv[3], argv[4], argv[5]);
  return TCL_OK;
}

static int server_2char STDVAR
{
  Function F = (Function) cd;

  BADARGS(3, 3, " nick msg");

  CHECKVALIDITY(server_2char);
  F(argv[1], argv[2]);
  return TCL_OK;
}

static int server_msg STDVAR
{
  Function F = (Function) cd;

  BADARGS(5, 5, " nick uhost hand buffer");

  CHECKVALIDITY(server_msg);
  F(argv[1], argv[2], get_user_by_handle(userlist, argv[3]), argv[4]);
  return TCL_OK;
}

static int server_raw STDVAR
{
  Function F = (Function) cd;

  BADARGS(4, 4, " from code args");

  CHECKVALIDITY(server_raw);
  Tcl_AppendResult(irp, int_to_base10(F(argv[1], argv[3])), NULL);
  return TCL_OK;
}

/* Read/write normal string variable.
 */
static char *nick_change(ClientData cdata, Tcl_Interp *irp,
                         EGG_CONST char *name1,
                         EGG_CONST char *name2, int flags)
{
  EGG_CONST char *new;

  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
    Tcl_SetVar2(interp, name1, name2, origbotname, TCL_GLOBAL_ONLY);
    if (flags & TCL_TRACE_UNSETS)
      Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
                   TCL_TRACE_UNSETS, nick_change, cdata);
  } else {                        /* writes */
    new = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
    if (strcmp(origbotname, (char *) new)) {
      if (origbotname[0]) {
        putlog(LOG_MISC, "*", "* IRC NICK CHANGE: %s -> %s", origbotname, new);
        nick_juped = 0;
      }
      strncpyz(origbotname, new, NICKLEN);
      if (server_online)
        dprintf(DP_SERVER, "NICK %s\n", origbotname);
    }
  }
  return NULL;
}

/* Replace all '?'s in s with a random number.
 */
static void rand_nick(char *nick)
{
  register char *p = nick;

  while ((p = strchr(p, '?')) != NULL) {
    *p = '0' + randint(10);
    p++;
  }
}

/* Return the alternative bot nick.
 */
static char *get_altbotnick(void)
{
  /* A random-number nick? */
  if (strchr(altnick, '?')) {
    if (!raltnick[0]) {
      strncpyz(raltnick, altnick, NICKLEN);
      rand_nick(raltnick);
    }
    return raltnick;
  } else
    return altnick;
}

static char *altnick_change(ClientData cdata, Tcl_Interp *irp,
                            EGG_CONST char *name1,
                            EGG_CONST char *name2, int flags)
{
  /* Always unset raltnick. Will be regenerated when needed. */
  raltnick[0] = 0;
  return NULL;
}

static char *traced_serveraddress(ClientData cdata, Tcl_Interp *irp,
                                  EGG_CONST char *name1,
                                  EGG_CONST char *name2, int flags)
{
  char s[1024];

  if (server_online) {
    int servidx = findanyidx(serv);

    simple_sprintf(s, "%s:%u", dcc[servidx].host, dcc[servidx].port);
  } else
    s[0] = 0;
  Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
  if (flags & TCL_TRACE_UNSETS)
    Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
                 TCL_TRACE_UNSETS, traced_serveraddress, cdata);
  return NULL;
}

static char *traced_server(ClientData cdata, Tcl_Interp *irp,
                           EGG_CONST char *name1,
                           EGG_CONST char *name2, int flags)
{
  char s[1024];

  if (server_online && realservername) {
    int servidx = findanyidx(serv);

    /* return real server name */
    simple_sprintf(s, "%s:%u", realservername, dcc[servidx].port);
  } else
    s[0] = 0;
  Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
  if (flags & TCL_TRACE_UNSETS)
    Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
                 TCL_TRACE_UNSETS, traced_server, cdata);
  return NULL;
}

static char *traced_botname(ClientData cdata, Tcl_Interp *irp,
                            EGG_CONST char *name1,
                            EGG_CONST char *name2, int flags)
{
  char s[1024];

  simple_sprintf(s, "%s%s%s", botname,
                 botuserhost[0] ? "!" : "", botuserhost[0] ? botuserhost : "");
  Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
  if (flags & TCL_TRACE_UNSETS)
    Tcl_TraceVar(irp, name1, TCL_TRACE_READS | TCL_TRACE_WRITES |
                 TCL_TRACE_UNSETS, traced_botname, cdata);
  return NULL;
}

static void do_nettype(void)
{
  switch (net_type) {
  case NETT_EFNET:
    check_mode_r = 0;
    nick_len = 9;
    break;
  case NETT_IRCNET:
    check_mode_r = 1;
    use_penalties = 1;
    use_fastdeq = 3;
    nick_len = 9;
    simple_sprintf(stackablecmds, "INVITE AWAY VERSION NICK");
    kick_method = 4;
    break;
  case NETT_UNDERNET:
    check_mode_r = 0;
    use_fastdeq = 2;
    nick_len = 12;
    simple_sprintf(stackablecmds,
                   "PRIVMSG NOTICE TOPIC PART WHOIS USERHOST USERIP ISON");
    simple_sprintf(stackable2cmds, "USERHOST USERIP ISON");
    break;
  case NETT_DALNET:
    check_mode_r = 0;
    use_fastdeq = 2;
    nick_len = 32;
    simple_sprintf(stackablecmds,
                   "PRIVMSG NOTICE PART WHOIS WHOWAS USERHOST ISON WATCH DCCALLOW");
    simple_sprintf(stackable2cmds, "USERHOST ISON WATCH");
    break;
  case NETT_HYBRID_EFNET:
    check_mode_r = 0;
    nick_len = 9;
    break;
  }
}

static char *traced_nettype(ClientData cdata, Tcl_Interp *irp,
                            EGG_CONST char *name1,
                            EGG_CONST char *name2, int flags)
{
  do_nettype();
  return NULL;
}

static char *traced_nicklen(ClientData cdata, Tcl_Interp *irp,
                            EGG_CONST char *name1,
                            EGG_CONST char *name2, int flags)
{
  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
    char s[40];

    egg_snprintf(s, sizeof s, "%d", nick_len);
    Tcl_SetVar2(interp, name1, name2, s, TCL_GLOBAL_ONLY);
    if (flags & TCL_TRACE_UNSETS)
      Tcl_TraceVar(irp, name1, TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                   traced_nicklen, cdata);
  } else {
    EGG_CONST char *cval = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
    long lval = 0;

    if (cval && Tcl_ExprLong(interp, cval, &lval) != TCL_ERROR) {
      if (lval > NICKMAX)
        lval = NICKMAX;
      nick_len = (int) lval;
    }
  }
  return NULL;
}

static tcl_strings my_tcl_strings[] = {
  {"botnick",             NULL,           0,       STR_PROTECT},
  {"altnick",             altnick,        NICKMAX,           0},
  {"realname",            botrealname,    80,                0},
  {"init-server",         initserver,     120,               0},
  {"connect-server",      connectserver,  120,               0},
  {"stackable-commands",  stackablecmds,  510,               0},
  {"stackable2-commands", stackable2cmds, 510,               0},
  {NULL,                  NULL,           0,                 0}
};

static tcl_coups my_tcl_coups[] = {
  {"flood-ctcp", &flud_ctcp_thr, &flud_ctcp_time},
  {"flood-msg",  &flud_thr,           &flud_time},
  {NULL,         NULL,                      NULL}
};

static tcl_ints my_tcl_ints[] = {
  {"servlimit",         &min_servs,                 0},
  {"server-timeout",    &server_timeout,            0},
  {"lowercase-ctcp",    &lowercase_ctcp,            0},
  {"server-online",     (int *) &server_online,     2},
  {"never-give-up",     &never_give_up,             0},
  {"keep-nick",         &keepnick,                  0},
  {"check-stoned",      &check_stoned,              0},
  {"serverror-quit",    &serverror_quit,            0},
  {"max-queue-msg",     &maxqmsg,                   0},
  {"trigger-on-ignore", &trigger_on_ignore,         0},
  {"answer-ctcp",       &answer_ctcp,               0},
  {"server-cycle-wait", (int *) &server_cycle_wait, 0},
  {"default-port",      &default_port,              0},
  {"check-mode-r",      &check_mode_r,              0},
  {"net-type",          &net_type,                  0},
  {"ctcp-mode",         &ctcp_mode,                 0},
  {"double-mode",       &double_mode,               0},
  {"double-server",     &double_server,             0},
  {"double-help",       &double_help,               0},
  {"use-penalties",     &use_penalties,             0},
  {"use-fastdeq",       &use_fastdeq,               0},
  {"nicklen",           &nick_len,                  0},
  {"nick-len",          &nick_len,                  0},
  {"optimize-kicks",    &optimize_kicks,            0},
  {"isjuped",           &nick_juped,                0},
  {"stack-limit",       &stack_limit,               0},
  {"exclusive-binds",   &exclusive_binds,           0},
  {NULL,                NULL,                       0}
};


/*
 *     Tcl variable trace functions
 */

/* Read or write the server list.
 */

static char *tcl_eggserver(ClientData cdata, Tcl_Interp *irp,
                           EGG_CONST char *name1,
                           EGG_CONST char *name2, int flags)
{
  int lc, code, i;
  char x[1024];
  EGG_CONST char **list, *slist;
  struct server_list *q;
  Tcl_DString ds;

  if (flags & (TCL_TRACE_READS | TCL_TRACE_UNSETS)) {
    /* Create server list */
    Tcl_DStringInit(&ds);
    for (q = serverlist; q; q = q->next) {
      egg_snprintf(x, sizeof x, "%s:%d%s%s %s", q->name,
                   q->port ? q->port : default_port, q->pass ? ":" : "",
                   q->pass ? q->pass : "", q->realname ? q->realname : "");
      Tcl_DStringAppendElement(&ds, x);
    }
    slist = Tcl_DStringValue(&ds);
    Tcl_SetVar2(interp, name1, name2, slist, TCL_GLOBAL_ONLY);
    Tcl_DStringFree(&ds);
  } else {                        /* TCL_TRACE_WRITES */
    if (serverlist) {
      clearq(serverlist);
      serverlist = NULL;
    }
    slist = Tcl_GetVar2(interp, name1, name2, TCL_GLOBAL_ONLY);
    if (slist != NULL) {
      code = Tcl_SplitList(interp, slist, &lc, &list);
      if (code == TCL_ERROR)
        return interp->result;
      for (i = 0; i < lc && i < 50; i++)
        add_server((char *) list[i]);

      /* Tricky way to make the bot reset its server pointers
       * perform part of a '.jump <current-server>':
       */
      if (server_online) {
        int servidx = findanyidx(serv);

        curserv = -1;
        next_server(&curserv, dcc[servidx].host, &dcc[servidx].port, "");
      }
      Tcl_Free((char *) list);
    }
  }
  return NULL;
}

/* Trace the servers */
#define tcl_traceserver(name, ptr)                                      \
  Tcl_TraceVar(interp, name, TCL_TRACE_READS | TCL_TRACE_WRITES |       \
               TCL_TRACE_UNSETS, tcl_eggserver, (ClientData) ptr)

#define tcl_untraceserver(name, ptr)                                    \
  Tcl_UntraceVar(interp, name, TCL_TRACE_READS | TCL_TRACE_WRITES |     \
                 TCL_TRACE_UNSETS, tcl_eggserver, (ClientData) ptr)


/*
 *     CTCP DCC CHAT functions
 */

static void dcc_chat_hostresolved(int);

/* This only handles CHAT requests, otherwise it's handled in filesys.
 */
static int ctcp_DCC_CHAT(char *nick, char *from, char *handle,
                         char *object, char *keyword, char *text)
{
  char *action, *param, *ip, *prt, buf[512], *msg = buf;
  int i;
  struct userrec *u = get_user_by_handle(userlist, handle);
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  strcpy(msg, text);
  action = newsplit(&msg);
  param = newsplit(&msg);
  ip = newsplit(&msg);
  prt = newsplit(&msg);
  if (egg_strcasecmp(action, "CHAT") || egg_strcasecmp(object, botname) || !u)
    return 0;
  get_user_flagrec(u, &fr, 0);
  if (dcc_total == max_dcc) {
    if (!quiet_reject)
      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, DCC_TOOMANYDCCS1);
    putlog(LOG_MISC, "*", DCC_TOOMANYDCCS2, "CHAT", param, nick, from);
  } else if (!(glob_party(fr) || (!require_p && chan_op(fr)))) {
    if (glob_xfer(fr))
      return 0;                 /* Allow filesys to pick up the chat */
    if (!quiet_reject)
      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, DCC_REFUSED2);
    putlog(LOG_MISC, "*", "%s: %s!%s", DCC_REFUSED, nick, from);
  } else if (u_pass_match(u, "-")) {
    if (!quiet_reject)
      dprintf(DP_HELP, "NOTICE %s :%s\n", nick, DCC_REFUSED3);
    putlog(LOG_MISC, "*", "%s: %s!%s", DCC_REFUSED4, nick, from);
  } else if (atoi(prt) < 1024 || atoi(prt) > 65535) {
    /* Invalid port */
    if (!quiet_reject)
      dprintf(DP_HELP, "NOTICE %s :%s (invalid port)\n", nick,
              DCC_CONNECTFAILED1);
    putlog(LOG_MISC, "*", "%s: CHAT (%s!%s)", DCC_CONNECTFAILED3, nick, from);
  } else {
    if (!sanitycheck_dcc(nick, from, ip, prt))
      return 1;
    i = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
    if (i < 0) {
      putlog(LOG_MISC, "*", "DCC connection: CHAT (%s!%s)", nick, ip);
      return 1;
    }
    dcc[i].addr = my_atoul(ip);
    dcc[i].port = atoi(prt);
    dcc[i].sock = -1;
    strcpy(dcc[i].nick, u->handle);
    strcpy(dcc[i].host, from);
    dcc[i].timeval = now;
    dcc[i].user = u;
    dcc[i].u.dns->ip = dcc[i].addr;
    dcc[i].u.dns->dns_type = RES_HOSTBYIP;
    dcc[i].u.dns->dns_success = dcc_chat_hostresolved;
    dcc[i].u.dns->dns_failure = dcc_chat_hostresolved;
    dcc[i].u.dns->type = &DCC_CHAT_PASS;
    dcc_dnshostbyip(dcc[i].addr);
  }
  return 1;
}

static void dcc_chat_hostresolved(int i)
{
  char buf[512], ip[512];
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  egg_snprintf(buf, sizeof buf, "%d", dcc[i].port);
  if (!hostsanitycheck_dcc(dcc[i].nick, dcc[i].host, dcc[i].addr,
                           dcc[i].u.dns->host, buf)) {
    lostdcc(i);
    return;
  }
  egg_snprintf(ip, sizeof ip, "%lu", iptolong(htonl(dcc[i].addr)));
  dcc[i].sock = getsock(0);
  if (dcc[i].sock < 0 || open_telnet_dcc(dcc[i].sock, ip, buf) < 0) {
    neterror(buf);
    if (!quiet_reject)
      dprintf(DP_HELP, "NOTICE %s :%s (%s)\n", dcc[i].nick,
              DCC_CONNECTFAILED1, buf);
    putlog(LOG_MISC, "*", "%s: CHAT (%s!%s)", DCC_CONNECTFAILED2,
           dcc[i].nick, dcc[i].host);
    putlog(LOG_MISC, "*", "    (%s)", buf);
    killsock(dcc[i].sock);
    lostdcc(i);
  } else {
    changeover_dcc(i, &DCC_CHAT_PASS, sizeof(struct chat_info));
    dcc[i].status = STAT_ECHO;
    get_user_flagrec(dcc[i].user, &fr, 0);
    if (glob_party(fr))
      dcc[i].status |= STAT_PARTY;
    strcpy(dcc[i].u.chat->con_chan, (chanset) ? chanset->dname : "*");
    dcc[i].timeval = now;
    /* Ok, we're satisfied with them now: attempt the connect */
    putlog(LOG_MISC, "*", "DCC connection: CHAT (%s!%s)", dcc[i].nick,
           dcc[i].host);
    dprintf(i, "%s\n", DCC_ENTERPASS);
  }
  return;
}


/*
 *     Server timer functions
 */

static void server_secondly()
{
  if (cycle_time)
    cycle_time--;
  deq_msg();
  if (!resolvserv && serv < 0)
    connect_server();
}

static void server_5minutely()
{
  if (check_stoned && server_online) {
    if (waiting_for_awake) {
      /* Uh oh!  Never got pong from last time, five minutes ago!
       * Server is probably stoned.
       */
      int servidx = findanyidx(serv);

      disconnect_server(servidx);
      lostdcc(servidx);
      putlog(LOG_SERV, "*", IRC_SERVERSTONED);
    } else if (!trying_server) {
      /* Check for server being stoned. */
      dprintf(DP_MODE, "PING :%li\n", now);
      waiting_for_awake = 1;
    }
  }
}

static void server_prerehash()
{
  strcpy(oldnick, botname);
}

static void server_postrehash()
{
  strncpyz(botname, origbotname, NICKLEN);
  if (!botname[0])
    fatal("NO BOT NAME.", 0);
  if (serverlist == NULL)
    fatal("NO SERVER.", 0);
  if (oldnick[0] && !rfc_casecmp(oldnick, botname) &&
      !rfc_casecmp(oldnick, get_altbotnick())) {
    /* Change botname back, don't be premature. */
    strcpy(botname, oldnick);
    dprintf(DP_SERVER, "NICK %s\n", origbotname);
  }
  /* Change botname back incase we were using altnick previous to rehash. */
  else if (oldnick[0])
    strcpy(botname, oldnick);
}

static void server_die()
{
  cycle_time = 100;
  if (server_online) {
    dprintf(-serv, "QUIT :%s\n", quit_msg[0] ? quit_msg : "");
    sleep(3);                   /* Give the server time to understand */
  }
  nuke_server(NULL);
}


static void msgq_clear(struct msgq_head *qh)
{
  register struct msgq *q, *qq;

  for (q = qh->head; q; q = qq) {
    qq = q->next;
    nfree(q->msg);
    nfree(q);
  }
  qh->head = qh->last = NULL;
  qh->tot = qh->warned = 0;
}

static int msgq_expmem(struct msgq_head *qh)
{
  register int tot = 0;
  register struct msgq *m;

  for (m = qh->head; m; m = m->next) {
    tot += m->len + 1;
    tot += sizeof(struct msgq);
  }
  return tot;
}

static int server_expmem()
{
  int tot = 0;
  struct server_list *s = serverlist;

  for (; s; s = s->next) {
    if (s->name)
      tot += strlen(s->name) + 1;
    if (s->pass)
      tot += strlen(s->pass) + 1;
    if (s->realname)
      tot += strlen(s->realname) + 1;
    tot += sizeof(struct server_list);
  }

  if (realservername)
    tot += strlen(realservername) + 1;
  tot += msgq_expmem(&mq) + msgq_expmem(&hq) + msgq_expmem(&modeq);

  return tot;
}

static void server_report(int idx, int details)
{
  char s1[64], s[128];
  int servidx;

  if (server_online) {
    dprintf(idx, "    Online as: %s%s%s (%s)\n", botname, botuserhost[0] ?
            "!" : "", botuserhost[0] ? botuserhost : "", botrealname);
    if (nick_juped)
      dprintf(idx, "    NICK IS JUPED: %s%s\n", origbotname,
              keepnick ? " (trying)" : "");
    daysdur(now, server_online, s1);
    egg_snprintf(s, sizeof s, "(connected %s)", s1);
    if (server_lag && !waiting_for_awake) {
      if (server_lag == -1)
        egg_snprintf(s1, sizeof s1, " (bad pong replies)");
      else
        egg_snprintf(s1, sizeof s1, " (lag: %ds)", server_lag);
      strcat(s, s1);
    }
  }

  if ((trying_server || server_online) &&
      ((servidx = findanyidx(serv)) != -1)) {
    dprintf(idx, "    Server %s:%d %s\n", dcc[servidx].host, dcc[servidx].port,
            trying_server ? "(trying)" : s);
  } else
    dprintf(idx, "    %s\n", IRC_NOSERVER);

  if (modeq.tot)
    dprintf(idx, "    %s %d%% (%d msgs)\n", IRC_MODEQUEUE,
            (int) ((float) (modeq.tot * 100.0) / (float) maxqmsg),
            (int) modeq.tot);
  if (mq.tot)
    dprintf(idx, "    %s %d%% (%d msgs)\n", IRC_SERVERQUEUE,
            (int) ((float) (mq.tot * 100.0) / (float) maxqmsg), (int) mq.tot);
  if (hq.tot)
    dprintf(idx, "    %s %d%% (%d msgs)\n", IRC_HELPQUEUE,
            (int) ((float) (hq.tot * 100.0) / (float) maxqmsg), (int) hq.tot);

  if (details) {
    int size = server_expmem();

    if (min_servs)
      dprintf(idx, "    Requiring a network with at least %d server%s\n",
              min_servs, (min_servs != 1) ? "s" : "");
    if (initserver[0])
      dprintf(idx, "    On connect, I do: %s\n", initserver);
    if (connectserver[0])
      dprintf(idx, "    Before connect, I do: %s\n", connectserver);
    dprintf(idx, "    Msg flood: %d msg%s/%d second%s\n", flud_thr,
            (flud_thr != 1) ? "s" : "", flud_time,
            (flud_time != 1) ? "s" : "");
    dprintf(idx, "    CTCP flood: %d msg%s/%d second%s\n", flud_ctcp_thr,
            (flud_ctcp_thr != 1) ? "s" : "", flud_ctcp_time,
            (flud_ctcp_time != 1) ? "s" : "");
    dprintf(idx, "    Using %d byte%s of memory\n", size,
            (size != 1) ? "s" : "");
  }
}

static cmd_t my_ctcps[] = {
  {"DCC", "",   ctcp_DCC_CHAT, "server:DCC"},
  {NULL,  NULL, NULL,                  NULL}
};

static char *server_close()
{
  cycle_time = 100;
  nuke_server("Connection reset by peer");
  clearq(serverlist);
  rem_builtins(H_dcc, C_dcc_serv);
  rem_builtins(H_raw, my_raw_binds);
  rem_builtins(H_ctcp, my_ctcps);
  /* Restore original commands. */
  del_bind_table(H_wall);
  del_bind_table(H_raw);
  del_bind_table(H_notc);
  del_bind_table(H_msgm);
  del_bind_table(H_msg);
  del_bind_table(H_flud);
  del_bind_table(H_ctcr);
  del_bind_table(H_ctcp);
  rem_tcl_coups(my_tcl_coups);
  rem_tcl_strings(my_tcl_strings);
  rem_tcl_ints(my_tcl_ints);
  rem_help_reference("server.help");
  rem_tcl_commands(my_tcl_cmds);
  Tcl_UntraceVar(interp, "nick",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 nick_change, NULL);
  Tcl_UntraceVar(interp, "altnick",
                 TCL_TRACE_WRITES | TCL_TRACE_UNSETS, altnick_change, NULL);
  Tcl_UntraceVar(interp, "botname",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 traced_botname, NULL);
  Tcl_UntraceVar(interp, "server",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 traced_server, NULL);
  Tcl_UntraceVar(interp, "serveraddress",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 traced_serveraddress, NULL);
  Tcl_UntraceVar(interp, "net-type",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 traced_nettype, NULL);
  Tcl_UntraceVar(interp, "nick-len",
                 TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
                 traced_nicklen, NULL);
  tcl_untraceserver("servers", NULL);
  empty_msgq();
  del_hook(HOOK_SECONDLY, (Function) server_secondly);
  del_hook(HOOK_5MINUTELY, (Function) server_5minutely);
  del_hook(HOOK_QSERV, (Function) queue_server);
  del_hook(HOOK_MINUTELY, (Function) minutely_checks);
  del_hook(HOOK_PRE_REHASH, (Function) server_prerehash);
  del_hook(HOOK_REHASH, (Function) server_postrehash);
  del_hook(HOOK_DIE, (Function) server_die);
  module_undepend(MODULE_NAME);
  return NULL;
}

EXPORT_SCOPE char *server_start();

static Function server_table[] = {
  (Function) server_start,
  (Function) server_close,
  (Function) server_expmem,
  (Function) server_report,
  /* 4 - 7 */
  (Function) NULL,              /* char * (points to botname later on)  */
  (Function) botuserhost,       /* char *                               */
  (Function) NULL,              /* Was quiet_reject <Wcc[01/21/03]>.    */
  (Function) & serv,            /* int                                  */
  /* 8 - 11 */
  (Function) & flud_thr,        /* int                                  */
  (Function) & flud_time,       /* int                                  */
  (Function) & flud_ctcp_thr,   /* int                                  */
  (Function) & flud_ctcp_time,  /* int                                  */
  /* 12 - 15 */
  (Function) match_my_nick,
  (Function) check_tcl_flud,
  (Function) NULL,              /* fixfrom - moved to core (drummer)    */
  (Function) & answer_ctcp,     /* int                                  */
  /* 16 - 19 */
  (Function) & trigger_on_ignore, /* int                                */
  (Function) check_tcl_ctcpr,
  (Function) detect_avalanche,
  (Function) nuke_server,
  /* 20 - 23 */
  (Function) newserver,         /* char *                               */
  (Function) & newserverport,   /* int                                  */
  (Function) newserverpass,     /* char *                               */
  (Function) & cycle_time,      /* int                                  */
  /* 24 - 27 */
  (Function) & default_port,    /* int                                  */
  (Function) & server_online,   /* int                                  */
  (Function) & min_servs,       /* int                                  */
  (Function) & H_raw,           /* p_tcl_bind_list                      */
  /* 28 - 31 */
  (Function) & H_wall,          /* p_tcl_bind_list                      */
  (Function) & H_msg,           /* p_tcl_bind_list                      */
  (Function) & H_msgm,          /* p_tcl_bind_list                      */
  (Function) & H_notc,          /* p_tcl_bind_list                      */
  /* 32 - 35 */
  (Function) & H_flud,          /* p_tcl_bind_list                      */
  (Function) & H_ctcp,          /* p_tcl_bind_list                      */
  (Function) & H_ctcr,          /* p_tcl_bind_list                      */
  (Function) ctcp_reply,
  /* 36 - 39 */
  (Function) get_altbotnick,    /* char *                               */
  (Function) & nick_len,        /* int                                  */
  (Function) check_tcl_notc,
  (Function) & exclusive_binds  /* int                                  */
};

char *server_start(Function *global_funcs)
{
  EGG_CONST char *s;

  global = global_funcs;

  /*
   * Init of all the variables *must* be done in _start rather than
   * globally.
   */
  serv = -1;
  botname[0] = 0;
  trying_server = 0L;
  server_lag = 0;
  altnick[0] = 0;
  raltnick[0] = 0;
  curserv = 0;
  flud_thr = 5;
  flud_time = 60;
  flud_ctcp_thr = 3;
  flud_ctcp_time = 60;
  initserver[0] = 0;
  connectserver[0] = 0;         /* drummer */
  botuserhost[0] = 0;
  keepnick = 1;
  check_stoned = 1;
  serverror_quit = 1;
  waiting_for_awake = 0;
  server_online = 0;
  server_cycle_wait = 60;
  strcpy(botrealname, "A deranged product of evil coders");
  min_servs = 0;
  server_timeout = 60;
  never_give_up = 0;
  serverlist = NULL;
  cycle_time = 0;
  default_port = 6667;
  oldnick[0] = 0;
  trigger_on_ignore = 0;
  exclusive_binds = 0;
  answer_ctcp = 1;
  lowercase_ctcp = 0;
  check_mode_r = 0;
  maxqmsg = 300;
  burst = 0;
  net_type = NETT_EFNET;
  double_mode = 0;
  double_server = 0;
  double_help = 0;
  use_penalties = 0;
  use_fastdeq = 0;
  stackablecmds[0] = 0;
  strcpy(stackable2cmds, "USERHOST ISON");
  resolvserv = 0;
  lastpingtime = 0;
  last_time = 0;
  nick_len = 9;
  kick_method = 1;
  optimize_kicks = 0;
  stack_limit = 4;
  realservername = 0;

  server_table[4] = (Function) botname;
  module_register(MODULE_NAME, server_table, 1, 2);
  if (!module_depend(MODULE_NAME, "eggdrop", 106, 7)) {
    module_undepend(MODULE_NAME);
    return "This module requires Eggdrop 1.6.7 or later.";
  }

  /* Fool bot in reading the values. */
  tcl_eggserver(NULL, interp, "servers", NULL, 0);
  tcl_traceserver("servers", NULL);
  s = Tcl_GetVar(interp, "nick", TCL_GLOBAL_ONLY);
  if (s)
    strncpyz(origbotname, s, NICKLEN);
  Tcl_TraceVar(interp, "nick",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               nick_change, NULL);
  Tcl_TraceVar(interp, "altnick",
               TCL_TRACE_WRITES | TCL_TRACE_UNSETS, altnick_change, NULL);
  Tcl_TraceVar(interp, "botname",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               traced_botname, NULL);
  Tcl_TraceVar(interp, "server",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               traced_server, NULL);
  Tcl_TraceVar(interp, "serveraddress",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               traced_serveraddress, NULL);
  Tcl_TraceVar(interp, "net-type",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               traced_nettype, NULL);
  Tcl_TraceVar(interp, "nick-len",
               TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS,
               traced_nicklen, NULL);

  H_wall = add_bind_table("wall", HT_STACKABLE, server_2char);
  H_raw = add_bind_table("raw", HT_STACKABLE, server_raw);
  H_notc = add_bind_table("notc", HT_STACKABLE, server_5char);
  H_msgm = add_bind_table("msgm", HT_STACKABLE, server_msg);
  H_msg = add_bind_table("msg", 0, server_msg);
  H_flud = add_bind_table("flud", HT_STACKABLE, server_5char);
  H_ctcr = add_bind_table("ctcr", HT_STACKABLE, server_6char);
  H_ctcp = add_bind_table("ctcp", HT_STACKABLE, server_6char);
  add_builtins(H_raw, my_raw_binds);
  add_builtins(H_dcc, C_dcc_serv);
  add_builtins(H_ctcp, my_ctcps);
  add_help_reference("server.help");
  my_tcl_strings[0].buf = botname;
  add_tcl_strings(my_tcl_strings);
  add_tcl_ints(my_tcl_ints);
  add_tcl_commands(my_tcl_cmds);
  add_tcl_coups(my_tcl_coups);
  add_hook(HOOK_SECONDLY, (Function) server_secondly);
  add_hook(HOOK_5MINUTELY, (Function) server_5minutely);
  add_hook(HOOK_MINUTELY, (Function) minutely_checks);
  add_hook(HOOK_QSERV, (Function) queue_server);
  add_hook(HOOK_PRE_REHASH, (Function) server_prerehash);
  add_hook(HOOK_REHASH, (Function) server_postrehash);
  add_hook(HOOK_DIE, (Function) server_die);
  mq.head = hq.head = modeq.head = NULL;
  mq.last = hq.last = modeq.last = NULL;
  mq.tot = hq.tot = modeq.tot = 0;
  mq.warned = hq.warned = modeq.warned = 0;
  double_warned = 0;
  newserver[0] = 0;
  newserverport = 0;
  curserv = 999;
  do_nettype();
  return NULL;
}
