/*
 * botnet.c -- handles:
 *   keeping track of which bot's connected where in the chain
 *   dumping a list of bots or a bot tree to a user
 *   channel name associations on the party line
 *   rejecting a bot
 *   linking, unlinking, and relaying to another bot
 *   pinging the bots periodically and checking leaf status
 *
 * $Id: botnet.c,v 1.62 2008-02-16 21:41:03 guppy Exp $
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2008 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.
 */

#include "main.h"
#include "tandem.h"

extern int dcc_total, backgrd, connect_timeout, max_dcc, egg_numver;
extern struct userrec *userlist;
extern struct dcc_t *dcc;
extern time_t now;
extern Tcl_Interp *interp;

tand_t *tandbot;                   /* Keep track of tandem bots on the botnet */
party_t *party;                    /* Keep track of people on the botnet */
static int maxparty = 50;          /* Maximum space for party line members */
int tands = 0;                     /* Number of bots on the botnet */
int parties = 0;                   /* Number of people on the botnet */
char botnetnick[HANDLEN + 1] = ""; /* Botnet nickname */
int share_unlinks = 0;             /* Allow remote unlinks of my sharebots? */


int expmem_botnet()
{
  int size = 0, i;
  tand_t *bot;

  for (bot = tandbot; bot; bot = bot->next)
    size += sizeof(tand_t);
  size += (maxparty * sizeof(party_t));
  for (i = 0; i < parties; i++) {
    if (party[i].away)
      size += strlen(party[i].away) + 1;
    if (party[i].from)
      size += strlen(party[i].from) + 1;
  }
  return size;
}

void init_bots()
{
  tandbot = NULL;
  /* Grab space for 50 bots for now -- expand later as needed */
  maxparty = 50;
  party = nmalloc(maxparty * sizeof(*party));
}

tand_t *findbot(char *who)
{
  tand_t *ptr;

  for (ptr = tandbot; ptr; ptr = ptr->next)
    if (!egg_strcasecmp(ptr->bot, who))
      return ptr;
  return NULL;
}

/* Add a tandem bot to our chain list
 */
void addbot(char *who, char *from, char *next, char flag, int vernum)
{
  tand_t **ptr = &tandbot, *ptr2;

  while (*ptr) {
    if (!egg_strcasecmp((*ptr)->bot, who))
      putlog(LOG_BOTS, "*", "!!! Duplicate botnet bot entry!!");
    ptr = &((*ptr)->next);
  }
  ptr2 = nmalloc(sizeof(tand_t));
  strncpy(ptr2->bot, who, HANDLEN);
  ptr2->bot[HANDLEN] = 0;
  ptr2->share = flag;
  ptr2->ver = vernum;
  ptr2->next = *ptr;
  *ptr = ptr2;
  /* May be via itself */
  ptr2->via = findbot(from);
  if (!egg_strcasecmp(next, botnetnick))
    ptr2->uplink = (tand_t *) 1;
  else
    ptr2->uplink = findbot(next);
  tands++;
}

void updatebot(int idx, char *who, char share, int vernum)
{
  tand_t *ptr = findbot(who);

  if (ptr) {
    if (share)
      ptr->share = share;
    if (vernum)
      ptr->ver = vernum;
    botnet_send_update(idx, ptr);
  }
}

/* For backward 1.0 compatibility:
 * grab the (first) sock# for a user on another bot
 */
int partysock(char *bot, char *nick)
{
  int i;

  for (i = 0; i < parties; i++) {
    if ((!egg_strcasecmp(party[i].bot, bot)) &&
        (!egg_strcasecmp(party[i].nick, nick)))
      return party[i].sock;
  }
  return 0;
}

/* New botnet member
 */
int addparty(char *bot, char *nick, int chan, char flag, int sock,
             char *from, int *idx)
{
  int i;

  for (i = 0; i < parties; i++) {
    /* Just changing the channel of someone already on? */
    if (!egg_strcasecmp(party[i].bot, bot) && (party[i].sock == sock)) {
      int oldchan = party[i].chan;

      party[i].chan = chan;
      party[i].timer = now;
      if (from[0]) {
        if (flag == ' ')
          flag = '-';
        party[i].flag = flag;
        if (party[i].from)
          nfree(party[i].from);
        party[i].from = nmalloc(strlen(from) + 1);
        strcpy(party[i].from, from);
      }
      *idx = i;
      return oldchan;
    }
  }
  /* New member */
  if (parties == maxparty) {
    maxparty += 50;
    party = (party_t *) nrealloc((void *) party, maxparty * sizeof(party_t));
  }
  strncpy(party[parties].nick, nick, HANDLEN);
  party[parties].nick[HANDLEN] = 0;
  strncpy(party[parties].bot, bot, HANDLEN);
  party[parties].bot[HANDLEN] = 0;
  party[parties].chan = chan;
  party[parties].sock = sock;
  party[parties].status = 0;
  party[parties].away = 0;
  party[parties].timer = now;   /* cope. */
  if (from[0]) {
    if (flag == ' ')
      flag = '-';
    party[parties].flag = flag;
    party[parties].from = nmalloc(strlen(from) + 1);
    strcpy(party[parties].from, from);
  } else {
    party[parties].flag = ' ';
    party[parties].from = nmalloc(10);
    strcpy(party[parties].from, "(unknown)");
  }
  *idx = parties;
  parties++;
  return -1;
}

/* Alter status flags for remote party-line user.
 */
void partystat(char *bot, int sock, int add, int rem)
{
  int i;

  for (i = 0; i < parties; i++) {
    if ((!egg_strcasecmp(party[i].bot, bot)) && (party[i].sock == sock)) {
      party[i].status |= add;
      party[i].status &= ~rem;
    }
  }
}

/* Other bot is sharing idle info.
 */
void partysetidle(char *bot, int sock, int secs)
{
  int i;

  for (i = 0; i < parties; i++) {
    if ((!egg_strcasecmp(party[i].bot, bot)) && (party[i].sock == sock)) {
      party[i].timer = (now - (time_t) secs);
    }
  }
}

/* Return someone's chat channel.
 */
int getparty(char *bot, int sock)
{
  int i;

  for (i = 0; i < parties; i++) {
    if (!egg_strcasecmp(party[i].bot, bot) && (party[i].sock == sock)) {
      return i;
    }
  }
  return -1;
}

/* Un-idle someone
 */
int partyidle(char *bot, char *nick)
{
  int i, ok = 0;

  for (i = 0; i < parties; i++) {
    if ((!egg_strcasecmp(party[i].bot, bot)) &&
        (!egg_strcasecmp(party[i].nick, nick))) {
      party[i].timer = now;
      ok = 1;
    }
  }
  return ok;
}

/* Change someone's nick
 */
int partynick(char *bot, int sock, char *nick)
{
  char work[HANDLEN + 1];
  int i;

  for (i = 0; i < parties; i++) {
    if (!egg_strcasecmp(party[i].bot, bot) && (party[i].sock == sock)) {
      strcpy(work, party[i].nick);
      strncpy(party[i].nick, nick, HANDLEN);
      party[i].nick[HANDLEN] = 0;
      strcpy(nick, work);
      return i;
    }
  }
  return -1;
}

/* Set away message
 */
void partyaway(char *bot, int sock, char *msg)
{
  int i;

  for (i = 0; i < parties; i++) {
    if ((!egg_strcasecmp(party[i].bot, bot)) && (party[i].sock == sock)) {
      if (party[i].away)
        nfree(party[i].away);
      if (msg[0]) {
        party[i].away = nmalloc(strlen(msg) + 1);
        strcpy(party[i].away, msg);
      } else
        party[i].away = 0;
    }
  }
}

/* Remove a tandem bot from the chain list. */
void rembot(char *whoin)
{
  tand_t **ptr = &tandbot, *ptr2;
  struct userrec *u;
  char *who = NULL;
  size_t len = 0;

  /* Need to save the nick for later as it MAY be a pointer to ptr->bot, and we free(ptr) in here. */
  len = strlen(whoin);
  who = nmalloc(len + 1);
  strncpyz(who, whoin, len + 1);

  while (*ptr) {
    if (!egg_strcasecmp((*ptr)->bot, who))
      break;
    ptr = &((*ptr)->next);
  }
  if (!*ptr) {
    /* May have just .unlink *'d. */
    nfree(who);
    return;
  }
  check_tcl_disc(who);

  u = get_user_by_handle(userlist, who);
  if (u != NULL)
    touch_laston(u, "unlinked", now);

  ptr2 = *ptr;
  *ptr = ptr2->next;
  nfree(ptr2);
  tands--;

  dupwait_notify(who);
  nfree(who);
}

void remparty(char *bot, int sock)
{
  int i;

  for (i = 0; i < parties; i++)
    if ((!egg_strcasecmp(party[i].bot, bot)) && (party[i].sock == sock)) {
      parties--;
      if (party[i].from)
        nfree(party[i].from);
      if (party[i].away)
        nfree(party[i].away);
      if (i < parties) {
        strcpy(party[i].bot, party[parties].bot);
        strcpy(party[i].nick, party[parties].nick);
        party[i].chan = party[parties].chan;
        party[i].sock = party[parties].sock;
        party[i].flag = party[parties].flag;
        party[i].status = party[parties].status;
        party[i].timer = party[parties].timer;
        party[i].from = party[parties].from;
        party[i].away = party[parties].away;
      }
    }
}

/* Cancel every user that was on a certain bot
 */
void rempartybot(char *bot)
{
  int i;

  for (i = 0; i < parties; i++)
    if (!egg_strcasecmp(party[i].bot, bot)) {
      if (party[i].chan >= 0)
        check_tcl_chpt(bot, party[i].nick, party[i].sock, party[i].chan);
      remparty(bot, party[i].sock);
      i--;
    }
}

/* Remove every bot linked 'via' bot <x>
 */
void unvia(int idx, tand_t *who)
{
  tand_t *bot, *bot2;

  if (!who)
    return;                     /* Safety */
  rempartybot(who->bot);
  bot = tandbot;
  while (bot) {
    if (bot->uplink == who) {
      unvia(idx, bot);
      bot2 = bot->next;
      rembot(bot->bot);
      bot = bot2;
    } else
      bot = bot->next;
  }
#ifndef NO_OLD_BOTNET
  /* Every bot unvia's bots behind anyway, so why send msg's for
   * EVERY one? - will this break things?!
   */
  tandout_but(idx, "unlinked %s\n", who->bot);
#endif
}

/* Return index into dcc list of the bot that connects us to bot <x>
 */
int nextbot(char *who)
{
  int j;
  tand_t *bot = findbot(who);

  if (!bot)
    return -1;

  for (j = 0; j < dcc_total; j++)
    if (bot->via && !egg_strcasecmp(bot->via->bot, dcc[j].nick) &&
        (dcc[j].type == &DCC_BOT))
      return j;
  return -1;                    /* We're not connected to 'via' */
}

/* Return name of the bot that is directly connected to bot X
 */
char *lastbot(char *who)
{
  tand_t *bot = findbot(who);

  if (!bot)
    return "*";
  else if (bot->uplink == (tand_t *) 1)
    return botnetnick;
  else
    return bot->uplink->bot;
}

/* Modern version of 'whom' (use local data)
 */
void answer_local_whom(int idx, int chan)
{
  char format[81];
  char c, idle[40];
  int i, t, nicklen, botnicklen, total = 0;

  if (chan == -1)
    dprintf(idx, "%s (+: %s, *: %s)\n", BOT_BOTNETUSERS, BOT_PARTYLINE,
            BOT_LOCALCHAN);
  else if (chan > 0) {
    simple_sprintf(idle, "assoc %d", chan);
    if ((Tcl_Eval(interp, idle) != TCL_OK) || !interp->result[0])
      dprintf(idx, "%s %s%d:\n", BOT_USERSONCHAN,
              (chan < GLOBAL_CHANS) ? "" : "*", chan % GLOBAL_CHANS);
    else
      dprintf(idx, "%s '%s%s' (%s%d):\n", BOT_USERSONCHAN,
              (chan < GLOBAL_CHANS) ? "" : "*", interp->result,
              (chan < GLOBAL_CHANS) ? "" : "*", chan % GLOBAL_CHANS);
  }
  /* Find longest nick and botnick */
  nicklen = botnicklen = 0;
  for (i = 0; i < dcc_total; i++)
    if (dcc[i].type == &DCC_CHAT) {
      if ((chan == -1) || ((chan >= 0) && (dcc[i].u.chat->channel == chan))) {
        t = strlen(dcc[i].nick);
        if (t > nicklen)
          nicklen = t;
        t = strlen(botnetnick);
        if (t > botnicklen)
          botnicklen = t;
      }
    }
  for (i = 0; i < parties; i++) {
    if ((chan == -1) || ((chan >= 0) && (party[i].chan == chan))) {
      t = strlen(party[i].nick);
      if (t > nicklen)
        nicklen = t;
      t = strlen(party[i].bot);
      if (t > botnicklen)
        botnicklen = t;
    }
  }
  if (nicklen < 9)
    nicklen = 9;
  if (botnicklen < 9)
    botnicklen = 9;

  egg_snprintf(format, sizeof format, "%%-%us   %%-%us  %%s\n",
               nicklen, botnicklen);
  dprintf(idx, format, " Nick", " Bot", " Host");
  dprintf(idx, format, "----------", "---------", "--------------------");
  egg_snprintf(format, sizeof format, "%%c%%-%us %%c %%-%us  %%s%%s\n",
               nicklen, botnicklen);
  for (i = 0; i < dcc_total; i++)
    if (dcc[i].type == &DCC_CHAT) {
      if ((chan == -1) || ((chan >= 0) && (dcc[i].u.chat->channel == chan))) {
        c = geticon(i);
        if (c == '-')
          c = ' ';
        if (now - dcc[i].timeval > 300) {
          unsigned long days, hrs, mins;

          days = (now - dcc[i].timeval) / 86400;
          hrs = ((now - dcc[i].timeval) - (days * 86400)) / 3600;
          mins = ((now - dcc[i].timeval) - (hrs * 3600)) / 60;
          if (days > 0)
            sprintf(idle, " [idle %lud%luh]", days, hrs);
          else if (hrs > 0)
            sprintf(idle, " [idle %luh%lum]", hrs, mins);
          else
            sprintf(idle, " [idle %lum]", mins);
        } else
          idle[0] = 0;
        total++;
        dprintf(idx, format, c, dcc[i].nick,
                (dcc[i].u.chat->channel == 0) && (chan == -1) ? '+' :
                (dcc[i].u.chat->channel >= GLOBAL_CHANS) &&
                (chan == -1) ? '*' : ' ', botnetnick, dcc[i].host, idle);
        if (dcc[i].u.chat->away != NULL)
          dprintf(idx, "   AWAY: %s\n", dcc[i].u.chat->away);
      }
    }
  for (i = 0; i < parties; i++) {
    if ((chan == -1) || ((chan >= 0) && (party[i].chan == chan))) {
      c = party[i].flag;
      if (c == '-')
        c = ' ';
      if (party[i].timer == 0L)
        strcpy(idle, " [idle?]");
      else if (now - party[i].timer > 300) {
        unsigned long days, hrs, mins;

        days = (now - party[i].timer) / 86400;
        hrs = ((now - party[i].timer) - (days * 86400)) / 3600;
        mins = ((now - party[i].timer) - (hrs * 3600)) / 60;
        if (days > 0)
          sprintf(idle, " [idle %lud%luh]", days, hrs);
        else if (hrs > 0)
          sprintf(idle, " [idle %luh%lum]", hrs, mins);
        else
          sprintf(idle, " [idle %lum]", mins);
      } else
        idle[0] = 0;
      total++;
      dprintf(idx, format, c, party[i].nick,
              (party[i].chan == 0) && (chan == -1) ? '+' : ' ',
              party[i].bot, party[i].from, idle);
      if (party[i].status & PLSTAT_AWAY)
        dprintf(idx, "   %s: %s\n", MISC_AWAY,
                party[i].away ? party[i].away : "");
    }
  }
  dprintf(idx, "Total users: %d\n", total);
}

/* Show z a list of all bots connected
 */
void tell_bots(int idx)
{
  char s[512];
  int i;
  tand_t *bot;

  if (!tands) {
    dprintf(idx, "%s\n", BOT_NOBOTSLINKED);
    return;
  }
  strcpy(s, botnetnick);
  i = strlen(botnetnick);

  for (bot = tandbot; bot; bot = bot->next) {
    if (i > (500 - HANDLEN)) {
      dprintf(idx, "Bots: %s\n", s);
      s[0] = 0;
      i = 0;
    }
    if (i) {
      s[i++] = ',';
      s[i++] = ' ';
    }
    strcpy(s + i, bot->bot);
    i += strlen(bot->bot);
  }
  if (s[0])
    dprintf(idx, "Bots: %s\n", s);
  dprintf(idx, "%s: %d\n", MISC_TOTAL, tands + 1);
}

/* Show a simpleton bot tree
 */
void tell_bottree(int idx, int showver)
{
  char s[161];
  tand_t *last[20], *this, *bot, *bot2 = NULL;
  int lev = 0, more = 1, mark[20], ok, cnt, i, imark;
  char work[1024];
  int tothops = 0;

  if (tands == 0) {
    dprintf(idx, "%s\n", BOT_NOBOTSLINKED);
    return;
  }
  s[0] = 0;
  i = 0;

  for (bot = tandbot; bot; bot = bot->next)
    if (!bot->uplink) {
      if (i) {
        s[i++] = ',';
        s[i++] = ' ';
      }
      strcpy(s + i, bot->bot);
      i += strlen(bot->bot);
    }
  if (s[0])
    dprintf(idx, "(%s %s)\n", BOT_NOTRACEINFO, s);
  if (showver)
    dprintf(idx, "%s (%d.%d.%d.%d)\n", botnetnick,
            egg_numver / 1000000,
            egg_numver % 1000000 / 10000,
            egg_numver % 10000 / 100, egg_numver % 100);
  else
    dprintf(idx, "%s\n", botnetnick);
  this = (tand_t *) 1;
  work[0] = 0;
  while (more) {
    if (lev == 20) {
      dprintf(idx, "\n%s\n", BOT_COMPLEXTREE);
      return;
    }
    cnt = 0;
    tothops += lev;
    for (bot = tandbot; bot; bot = bot->next)
      if (bot->uplink == this)
        cnt++;
    if (cnt) {
      imark = 0;
      for (i = 0; i < lev; i++) {
        if (mark[i])
          strcpy(work + imark, "  |  ");
        else
          strcpy(work + imark, "     ");
        imark += 5;
      }
      if (cnt > 1)
        strcpy(work + imark, "  |-");
      else
        strcpy(work + imark, "  `-");
      s[0] = 0;
      bot = tandbot;
      while (!s[0]) {
        if (bot->uplink == this) {
          if (bot->ver) {
            i = sprintf(s, "%c%s", bot->share, bot->bot);
            if (showver)
              sprintf(s + i, " (%d.%d.%d.%d)",
                      bot->ver / 1000000,
                      bot->ver % 1000000 / 10000,
                      bot->ver % 10000 / 100, bot->ver % 100);
          } else
            sprintf(s, "-%s", bot->bot);
        } else
          bot = bot->next;
      }
      dprintf(idx, "%s%s\n", work, s);
      if (cnt > 1)
        mark[lev] = 1;
      else
        mark[lev] = 0;
      work[0] = 0;
      last[lev] = this;
      this = bot;
      lev++;
      more = 1;
    } else {
      while (cnt == 0) {
        /* No subtrees from here */
        if (lev == 0) {
          dprintf(idx, "(( tree error ))\n");
          return;
        }
        ok = 0;
        for (bot = tandbot; bot; bot = bot->next) {
          if (bot->uplink == last[lev - 1]) {
            if (this == bot)
              ok = 1;
            else if (ok) {
              cnt++;
              if (cnt == 1) {
                bot2 = bot;
                if (bot->ver) {
                  i = sprintf(s, "%c%s", bot->share, bot->bot);
                  if (showver)
                    sprintf(s + i, " (%d.%d.%d.%d)",
                            bot->ver / 1000000,
                            bot->ver % 1000000 / 10000,
                            bot->ver % 10000 / 100, bot->ver % 100);
                } else
                  sprintf(s, "-%s", bot->bot);
              }
            }
          }
        }
        if (cnt) {
          imark = 0;
          for (i = 1; i < lev; i++) {
            if (mark[i - 1])
              strcpy(work + imark, "  |  ");
            else
              strcpy(work + imark, "     ");
            imark += 5;
          }
          more = 1;
          if (cnt > 1)
            dprintf(idx, "%s  |-%s\n", work, s);
          else
            dprintf(idx, "%s  `-%s\n", work, s);
          this = bot2;
          work[0] = 0;
          if (cnt > 1)
            mark[lev - 1] = 1;
          else
            mark[lev - 1] = 0;
        } else {
          /* This was the last child */
          lev--;
          if (lev == 0) {
            more = 0;
            cnt = 999;
          } else {
            more = 1;
            this = last[lev];
          }
        }
      }
    }
  }
  /* Hop information: (9d) */
  dprintf(idx, "Average hops: %3.1f, total bots: %d\n",
          ((float) tothops) / ((float) tands), tands + 1);
}

/* Dump list of links to a new bot
 */
void dump_links(int z)
{
  register int i, l;
  char x[1024];
  tand_t *bot;

  for (bot = tandbot; bot; bot = bot->next) {
    char *p;

    if (bot->uplink == (tand_t *) 1)
      p = botnetnick;
    else
      p = bot->uplink->bot;
#ifndef NO_OLD_BOTNET
    if (b_numver(z) < NEAT_BOTNET)
      l = simple_sprintf(x, "nlinked %s %s %c%d\n", bot->bot,
                         p, bot->share, bot->ver);
    else
#endif
      l = simple_sprintf(x, "n %s %s %c%D\n", bot->bot, p,
                         bot->share, bot->ver);
    tputs(dcc[z].sock, x, l);
  }
  if (!(bot_flags(dcc[z].user) & BOT_ISOLATE)) {
    /* Dump party line members */
    for (i = 0; i < dcc_total; i++) {
      if (dcc[i].type == &DCC_CHAT) {
        if ((dcc[i].u.chat->channel >= 0) &&
            (dcc[i].u.chat->channel < GLOBAL_CHANS)) {
#ifndef NO_OLD_BOTNET
          if (b_numver(z) < NEAT_BOTNET)
            l = simple_sprintf(x, "join %s %s %d %c%d %s\n",
                               botnetnick, dcc[i].nick,
                               dcc[i].u.chat->channel, geticon(i),
                               dcc[i].sock, dcc[i].host);
          else
#endif
            l = simple_sprintf(x, "j !%s %s %D %c%D %s\n",
                               botnetnick, dcc[i].nick,
                               dcc[i].u.chat->channel, geticon(i),
                               dcc[i].sock, dcc[i].host);
          tputs(dcc[z].sock, x, l);
#ifndef NO_OLD_BOTNET
          if (b_numver(z) < NEAT_BOTNET) {
            if (dcc[i].u.chat->away) {
              l = simple_sprintf(x, "away %s %d %s\n", botnetnick,
                                 dcc[i].sock, dcc[i].u.chat->away);
              tputs(dcc[z].sock, x, l);
            }
            l = simple_sprintf(x, "idle %s %d %d\n", botnetnick,
                               dcc[i].sock, now - dcc[i].timeval);
          } else
#endif
            l = simple_sprintf(x, "i %s %D %D %s\n", botnetnick,
                               dcc[i].sock, now - dcc[i].timeval,
                               dcc[i].u.chat->away ? dcc[i].u.chat->away : "");
          tputs(dcc[z].sock, x, l);
        }
      }
    }
    for (i = 0; i < parties; i++) {
#ifndef NO_OLD_BOTNET
      if (b_numver(z) < NEAT_BOTNET)
        l = simple_sprintf(x, "join %s %s %d %c%d %s\n",
                           party[i].bot, party[i].nick,
                           party[i].chan, party[i].flag,
                           party[i].sock, party[i].from);
      else
#endif
        l = simple_sprintf(x, "j %s %s %D %c%D %s\n",
                           party[i].bot, party[i].nick,
                           party[i].chan, party[i].flag,
                           party[i].sock, party[i].from);
      tputs(dcc[z].sock, x, l);
      if ((party[i].status & PLSTAT_AWAY) || (party[i].timer != 0)) {
#ifndef NO_OLD_BOTNET
        if (b_numver(z) < NEAT_BOTNET) {
          if (party[i].status & PLSTAT_AWAY) {
            l = simple_sprintf(x, "away %s %d %s\n", party[i].bot,
                               party[i].sock, party[i].away);
            tputs(dcc[z].sock, x, l);
          }
          l = simple_sprintf(x, "idle %s %d %d\n", party[i].bot,
                             party[i].sock, now - party[i].timer);
        } else
#endif
          l = simple_sprintf(x, "i %s %D %D %s\n", party[i].bot,
                             party[i].sock, now - party[i].timer,
                             party[i].away ? party[i].away : "");
        tputs(dcc[z].sock, x, l);
      }
    }
  }
}

int in_chain(char *who)
{
  if (findbot(who))
    return 1;
  if (!egg_strcasecmp(who, botnetnick))
    return 1;
  return 0;
}

int bots_in_subtree(tand_t *bot)
{
  int nr = 1;
  tand_t *b;

  if (!bot)
    return 0;
  for (b = tandbot; b; b = b->next) {
    if (b->bot && (b->uplink == bot)) {
      nr += bots_in_subtree(b);
    }
  }
  return nr;
}

int users_in_subtree(tand_t *bot)
{
  int i, nr;
  tand_t *b;

  nr = 0;
  if (!bot)
    return 0;
  for (i = 0; i < parties; i++)
    if (!egg_strcasecmp(party[i].bot, bot->bot))
      nr++;
  for (b = tandbot; b; b = b->next)
    if (b->bot && (b->uplink == bot))
      nr += users_in_subtree(b);
  return nr;
}

/* Break link with a tandembot
 */
int botunlink(int idx, char *nick, char *reason, char *from)
{
  char s[20];
  register int i;
  int bots, users;
  tand_t *bot;

  if (nick[0] == '*')
    dprintf(idx, "%s\n", BOT_UNLINKALL);
  for (i = 0; i < dcc_total; i++) {
    if ((nick[0] == '*') || !egg_strcasecmp(dcc[i].nick, nick)) {
      if (dcc[i].type == &DCC_FORK_BOT) {
        if (idx >= 0)
          dprintf(idx, "%s: %s -> %s.\n", BOT_KILLLINKATTEMPT,
                  dcc[i].nick, dcc[i].host);
        putlog(LOG_BOTS, "*", "%s: %s -> %s:%d",
               BOT_KILLLINKATTEMPT, dcc[i].nick, dcc[i].host, dcc[i].port);
        killsock(dcc[i].sock);
        lostdcc(i);
        if (nick[0] != '*')
          return 1;
      } else if (dcc[i].type == &DCC_BOT_NEW) {
        if (idx >= 0)
          dprintf(idx, "%s %s.\n", BOT_ENDLINKATTEMPT, dcc[i].nick);
        putlog(LOG_BOTS, "*", "%s %s @ %s:%d",
               "Stopped trying to link", dcc[i].nick, dcc[i].host, dcc[i].port);
        killsock(dcc[i].sock);
        lostdcc(i);
        if (nick[0] != '*')
          return 1;
      } else if (dcc[i].type == &DCC_BOT) {
        char s[1024];

        if (idx >= 0)
          dprintf(idx, "%s %s.\n", BOT_BREAKLINK, dcc[i].nick);
        else if ((idx == -3) && (b_status(i) & STAT_SHARE) && !share_unlinks)
          return -1;
        bot = findbot(dcc[i].nick);
        bots = bots_in_subtree(bot);
        users = users_in_subtree(bot);
        if (reason && reason[0]) {
          simple_sprintf(s, "%s %s (%s (%s)) (lost %d bot%s and %d user%s)",
                         BOT_UNLINKEDFROM, dcc[i].nick, reason, from, bots,
                         (bots != 1) ? "s" : "", users, (users != 1) ?
                         "s" : "");
          dprintf(i, "bye %s\n", reason);
        } else {
          simple_sprintf(s, "%s %s (%s) (lost %d bot%s and %d user%s)",
                         BOT_UNLINKEDFROM, dcc[i].nick, from, bots,
                         (bots != 1) ? "s" : "", users,
                         (users != 1) ? "s" : "");
          dprintf(i, "bye No reason\n");
        }
        chatout("*** %s\n", s);
        botnet_send_unlinked(i, dcc[i].nick, s);
        killsock(dcc[i].sock);
        lostdcc(i);
        if (nick[0] != '*')
          return 1;
      }
    }
  }
  if (idx >= 0 && nick[0] != '*')
    dprintf(idx, "%s\n", BOT_NOTCONNECTED);
  if (nick[0] != '*') {
    bot = findbot(nick);
    if (bot) {
      /* The internal bot list is desynched from the dcc list
       * sometimes. While we still search for the bug, provide
       * an easy way to clear out those `ghost'-bots.
       * Fabian (2000-08-02)  */
      char *ghost = "BUG!!: Found bot `%s' in internal bot list, but it\n"
                    "   shouldn't have been there! Removing.\n"
                    "   This is a known bug we haven't fixed yet. If this\n"
                    "   bot is the newest eggdrop version available and you\n"
                    "   know a *reliable* way to reproduce the bug, please\n"
                    "   contact us - we need your help!\n";
      if (idx >= 0)
        dprintf(idx, ghost, nick);
      else
        putlog(LOG_MISC, "*", ghost, nick);
      rembot(bot->bot);
      return 1;
    }
  }
  if (nick[0] == '*') {
    dprintf(idx, "%s\n", BOT_WIPEBOTTABLE);
    while (tandbot)
      rembot(tandbot->bot);
    while (parties) {
      parties--;
      /* Assert? */
      if (party[i].chan >= 0)
        check_tcl_chpt(party[i].bot, party[i].nick, party[i].sock,
                       party[i].chan);
    }
    strcpy(s, "killassoc &");
    Tcl_Eval(interp, s);
  }
  return 0;
}

static void botlink_resolve_success(int);
static void botlink_resolve_failure(int);

/* Link to another bot
 */
int botlink(char *linker, int idx, char *nick)
{
  struct bot_addr *bi;
  struct userrec *u;
  register int i;

  u = get_user_by_handle(userlist, nick);
  if (!u || !(u->flags & USER_BOT)) {
    if (idx >= 0)
      dprintf(idx, "%s %s\n", nick, BOT_BOTUNKNOWN);
  } else if (!egg_strcasecmp(nick, botnetnick)) {
    if (idx >= 0)
      dprintf(idx, "%s\n", BOT_CANTLINKMYSELF);
  } else if (in_chain(nick) && (idx != -3)) {
    if (idx >= 0)
      dprintf(idx, "%s\n", BOT_ALREADYLINKED);
  } else {
    for (i = 0; i < dcc_total; i++)
      if ((dcc[i].user == u) &&
          ((dcc[i].type == &DCC_FORK_BOT) || (dcc[i].type == &DCC_BOT_NEW))) {
        if (idx >= 0)
          dprintf(idx, "%s\n", BOT_ALREADYLINKING);
        return 0;
      }
    /* Address to connect to is in 'info' */
    bi = (struct bot_addr *) get_user(&USERENTRY_BOTADDR, u);
    if (!bi || !strlen(bi->address) || !bi->telnet_port ||
        (bi->telnet_port <= 0)) {
      if (idx >= 0) {
        dprintf(idx, "%s '%s'.\n", BOT_NOTELNETADDY, nick);
        dprintf(idx, "%s .chaddr %s %s\n",
                MISC_USEFORMAT, nick, MISC_CHADDRFORMAT);
      }
    } else if (dcc_total == max_dcc) {
      if (idx >= 0)
        dprintf(idx, "%s\n", DCC_TOOMANYDCCS1);
    } else {
      correct_handle(nick);

      if (idx > -2)
        putlog(LOG_BOTS, "*", "%s %s at %s:%d ...", BOT_LINKING, nick,
               bi->address, bi->telnet_port);

      i = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
      dcc[i].timeval = now;
      dcc[i].port = bi->telnet_port;
      dcc[i].user = u;
      strcpy(dcc[i].nick, nick);
      strcpy(dcc[i].host, bi->address);
      dcc[i].u.dns->ibuf = idx;
      dcc[i].u.dns->cptr = get_data_ptr(strlen(linker) + 1);
      strcpy(dcc[i].u.dns->cptr, linker);
      dcc[i].u.dns->host = get_data_ptr(strlen(dcc[i].host) + 1);
      strcpy(dcc[i].u.dns->host, dcc[i].host);
      dcc[i].u.dns->dns_success = botlink_resolve_success;
      dcc[i].u.dns->dns_failure = botlink_resolve_failure;
      dcc[i].u.dns->dns_type = RES_IPBYHOST;
      dcc[i].u.dns->type = &DCC_FORK_BOT;
      dcc_dnsipbyhost(bi->address);
      return 1;
    }
  }
  return 0;
}

static void botlink_resolve_failure(int i)
{
  char s[81];

  putlog(LOG_BOTS, "*", DCC_LINKFAIL, dcc[i].nick);
  strcpy(s, dcc[i].nick);
  nfree(dcc[i].u.dns->cptr);
  lostdcc(i);
  autolink_cycle(s);            /* Check for more auto-connections */
}

static void botlink_resolve_success(int i)
{
  int idx = dcc[i].u.dns->ibuf;
  char *linker = dcc[i].u.dns->cptr;

  dcc[i].addr = dcc[i].u.dns->ip;
  changeover_dcc(i, &DCC_FORK_BOT, sizeof(struct bot_info));
  dcc[i].timeval = now;
  strcpy(dcc[i].u.bot->linker, linker);
  strcpy(dcc[i].u.bot->version, "(primitive bot)");
  dcc[i].u.bot->numver = idx;
  dcc[i].u.bot->port = dcc[i].port;     /* Remember where i started */
  dcc[i].sock = getsock(SOCK_STRONGCONN);
  nfree(linker);
  if (dcc[i].sock < 0 ||
      open_telnet_raw(dcc[i].sock, iptostr(htonl(dcc[i].addr)),
                      dcc[i].port) < 0)
    failed_link(i);
}

static void failed_tandem_relay(int idx)
{
  int uidx = -1, i;

  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_PRE_RELAY) &&
        (dcc[i].u.relay->sock == dcc[idx].sock))
      uidx = i;
  if (uidx < 0) {
    putlog(LOG_MISC, "*", "%s  %d -> %d", BOT_CANTFINDRELAYUSER,
           dcc[idx].sock, dcc[idx].u.relay->sock);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  if (dcc[idx].port >= dcc[idx].u.relay->port + 3) {
    struct chat_info *ci = dcc[uidx].u.relay->chat;

    dprintf(uidx, "%s %s.\n", BOT_CANTLINKTO, dcc[idx].nick);
    dcc[uidx].status = dcc[uidx].u.relay->old_status;
    nfree(dcc[uidx].u.relay);
    dcc[uidx].u.chat = ci;
    dcc[uidx].type = &DCC_CHAT;
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  killsock(dcc[idx].sock);
  dcc[idx].sock = getsock(SOCK_STRONGCONN);
  dcc[uidx].u.relay->sock = dcc[idx].sock;
  dcc[idx].port++;
  dcc[idx].timeval = now;
  if (dcc[idx].sock < 0 ||
      open_telnet_raw(dcc[idx].sock, dcc[idx].addr ?
                      iptostr(htonl(dcc[idx].addr)) :
                      dcc[idx].host, dcc[idx].port) < 0)
    failed_tandem_relay(idx);
}


static void tandem_relay_resolve_failure(int);
static void tandem_relay_resolve_success(int);

/* Relay to another tandembot
 */
void tandem_relay(int idx, char *nick, register int i)
{
  struct userrec *u;
  struct bot_addr *bi;
  struct chat_info *ci;

  u = get_user_by_handle(userlist, nick);
  if (!u || !(u->flags & USER_BOT)) {
    dprintf(idx, "%s %s\n", nick, BOT_BOTUNKNOWN);
    return;
  }
  if (!egg_strcasecmp(nick, botnetnick)) {
    dprintf(idx, "%s\n", BOT_CANTRELAYMYSELF);
    return;
  }
  /* Address to connect to is in 'info' */
  bi = (struct bot_addr *) get_user(&USERENTRY_BOTADDR, u);
  if (!bi || !strlen(bi->address) || !bi->relay_port || (bi->relay_port <= 0)) {
    dprintf(idx, "%s '%s'.\n", BOT_NOTELNETADDY, nick);
    dprintf(idx, "%s .chaddr %s %s\n", MISC_USEFORMAT, nick,
            MISC_CHADDRFORMAT);
    return;
  }
  i = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
  if (i < 0) {
    dprintf(idx, "%s\n", DCC_TOOMANYDCCS1);
    return;
  }

  dcc[i].sock = getsock(SOCK_STRONGCONN | SOCK_VIRTUAL);
  if (dcc[i].sock < 0) {
    lostdcc(i);
    dprintf(idx, "%s\n", MISC_NOFREESOCK);
    return;
  }

  dcc[i].port = bi->relay_port;
  dcc[i].addr = 0L;
  strcpy(dcc[i].nick, nick);
  dcc[i].user = u;
  strcpy(dcc[i].host, bi->address);
  dprintf(idx, "%s %s @ %s:%d ...\n", BOT_CONNECTINGTO, nick,
          bi->address, bi->relay_port);
  dprintf(idx, "%s\n", BOT_BYEINFO1);
  dcc[idx].type = &DCC_PRE_RELAY;
  ci = dcc[idx].u.chat;
  dcc[idx].u.relay = get_data_ptr(sizeof(struct relay_info));
  dcc[idx].u.relay->chat = ci;
  dcc[idx].u.relay->old_status = dcc[idx].status;
  dcc[idx].u.relay->sock = dcc[i].sock;
  dcc[i].timeval = now;
  dcc[i].u.dns->ibuf = dcc[idx].sock;
  dcc[i].u.dns->host = get_data_ptr(strlen(bi->address) + 1);
  strcpy(dcc[i].u.dns->host, bi->address);
  dcc[i].u.dns->dns_success = tandem_relay_resolve_success;
  dcc[i].u.dns->dns_failure = tandem_relay_resolve_failure;
  dcc[i].u.dns->dns_type = RES_IPBYHOST;
  dcc[i].u.dns->type = &DCC_FORK_RELAY;
  dcc_dnsipbyhost(bi->address);
}

static void tandem_relay_resolve_failure(int idx)
{
  struct chat_info *ci;
  register int uidx = -1, i;

  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_PRE_RELAY) &&
        (dcc[i].u.relay->sock == dcc[idx].sock)) {
      uidx = i;
      break;
    }
  if (uidx < 0) {
    putlog(LOG_MISC, "*", "%s  %d -> %d", BOT_CANTFINDRELAYUSER,
           dcc[idx].sock, dcc[idx].u.relay->sock);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  ci = dcc[uidx].u.relay->chat;
  dprintf(uidx, "%s %s.\n", BOT_CANTLINKTO, dcc[idx].nick);
  dcc[uidx].status = dcc[uidx].u.relay->old_status;
  nfree(dcc[uidx].u.relay);
  dcc[uidx].u.chat = ci;
  dcc[uidx].type = &DCC_CHAT;
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void tandem_relay_resolve_success(int i)
{
  int sock = dcc[i].u.dns->ibuf;

  dcc[i].addr = dcc[i].u.dns->ip;
  changeover_dcc(i, &DCC_FORK_RELAY, sizeof(struct relay_info));
  dcc[i].u.relay->chat = get_data_ptr(sizeof(struct chat_info));

  dcc[i].u.relay->sock = sock;
  dcc[i].u.relay->port = dcc[i].port;
  dcc[i].u.relay->chat->away = NULL;
  dcc[i].u.relay->chat->msgs_per_sec = 0;
  dcc[i].u.relay->chat->con_flags = 0;
  dcc[i].u.relay->chat->buffer = NULL;
  dcc[i].u.relay->chat->max_line = 0;
  dcc[i].u.relay->chat->line_count = 0;
  dcc[i].u.relay->chat->current_lines = 0;
  dcc[i].timeval = now;
  if (open_telnet_raw(dcc[i].sock, iptostr(htonl(dcc[i].addr)),
                      dcc[i].port) < 0)
    failed_tandem_relay(i);
}

/* Input from user before connect is ready
 */
static void pre_relay(int idx, char *buf, register int i)
{
  register int tidx = -1;

  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_FORK_RELAY) &&
        (dcc[i].u.relay->sock == dcc[idx].sock)) {
      tidx = i;
      break;
    }
  if (tidx < 0) {
    /* Now try to find it among the DNSWAIT sockets instead. */
    for (i = 0; i < dcc_total; i++)
      if ((dcc[i].type == &DCC_DNSWAIT) &&
          (dcc[i].sock == dcc[idx].u.relay->sock)) {
        tidx = i;
        break;
      }
  }
  if (tidx < 0) {
    putlog(LOG_MISC, "*", "%s  %d -> %d", BOT_CANTFINDRELAYUSER,
           dcc[idx].sock, dcc[idx].u.relay->sock);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  if (!egg_strcasecmp(buf, "*bye*")) {
    /* Disconnect */
    struct chat_info *ci = dcc[idx].u.relay->chat;

    dprintf(idx, "%s %s.\n", BOT_ABORTRELAY1, dcc[tidx].nick);
    dprintf(idx, "%s %s.\n\n", BOT_ABORTRELAY2, botnetnick);
    putlog(LOG_MISC, "*", "%s %s -> %s", BOT_ABORTRELAY3, dcc[idx].nick,
           dcc[tidx].nick);
    dcc[idx].status = dcc[idx].u.relay->old_status;
    nfree(dcc[idx].u.relay);
    dcc[idx].u.chat = ci;
    dcc[idx].type = &DCC_CHAT;
    killsock(dcc[tidx].sock);
    lostdcc(tidx);
    return;
  }
}

/* User disconnected before her relay had finished connecting
 */
static void failed_pre_relay(int idx)
{
  register int tidx = -1, i;

  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_FORK_RELAY) &&
        (dcc[i].u.relay->sock == dcc[idx].sock)) {
      tidx = i;
      break;
    }
  if (tidx < 0) {
    /* Now try to find it among the DNSWAIT sockets instead. */
    for (i = 0; i < dcc_total; i++)
      if ((dcc[i].type == &DCC_DNSWAIT) &&
          (dcc[i].sock == dcc[idx].u.relay->sock)) {
        tidx = i;
        break;
      }
  }
  if (tidx < 0) {
    putlog(LOG_MISC, "*", "%s  %d -> %d", BOT_CANTFINDRELAYUSER,
           dcc[idx].sock, dcc[idx].u.relay->sock);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  putlog(LOG_MISC, "*", "%s [%s]%s/%d", BOT_LOSTDCCUSER, dcc[idx].nick,
         dcc[idx].host, dcc[idx].port);
  putlog(LOG_MISC, "*", "(%s %s)", BOT_DROPPINGRELAY, dcc[tidx].nick);
  if ((dcc[tidx].sock != STDOUT) || backgrd) {
    if (idx > tidx) {
      int t = tidx;

      tidx = idx;
      idx = t;
    }
    killsock(dcc[tidx].sock);
    lostdcc(tidx);
  } else
    fatal("Lost my terminal?!", 0);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void cont_tandem_relay(int idx, char *buf, register int i)
{
  register int uidx = -1;
  struct relay_info *ri;

  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_PRE_RELAY) &&
        (dcc[i].u.relay->sock == dcc[idx].sock))
      uidx = i;
  if (uidx < 0) {
    putlog(LOG_MISC, "*", "%s  %d -> %d", BOT_CANTFINDRELAYUSER,
           dcc[i].sock, dcc[i].u.relay->sock);
    killsock(dcc[i].sock);
    lostdcc(i);
    return;
  }
  dcc[idx].type = &DCC_RELAY;
  dcc[idx].u.relay->sock = dcc[uidx].sock;
  dcc[uidx].u.relay->sock = dcc[idx].sock;
  dprintf(uidx, "%s %s ...\n", BOT_RELAYSUCCESS, dcc[idx].nick);
  dprintf(uidx, "%s\n\n", BOT_BYEINFO2);
  putlog(LOG_MISC, "*", "%s %s -> %s", BOT_RELAYLINK,
         dcc[uidx].nick, dcc[idx].nick);
  ri = dcc[uidx].u.relay;       /* YEAH */
  dcc[uidx].type = &DCC_CHAT;
  dcc[uidx].u.chat = ri->chat;
  if (dcc[uidx].u.chat->channel >= 0) {
    chanout_but(-1, dcc[uidx].u.chat->channel, "*** %s %s\n",
                dcc[uidx].nick, BOT_PARTYLEFT);
    if (dcc[uidx].u.chat->channel < GLOBAL_CHANS)
      botnet_send_part_idx(uidx, NULL);
    check_tcl_chpt(botnetnick, dcc[uidx].nick, dcc[uidx].sock,
                   dcc[uidx].u.chat->channel);
  }
  check_tcl_chof(dcc[uidx].nick, dcc[uidx].sock);
  dcc[uidx].type = &DCC_RELAYING;
  dcc[uidx].u.relay = ri;
}

static void eof_dcc_relay(int idx)
{
  register int j;
  struct chat_info *ci;

  for (j = 0; j < dcc_total; j++)
    if (dcc[j].sock == dcc[idx].u.relay->sock)
      break;
  if (j == dcc_total) {
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  dcc[j].status = dcc[j].u.relay->old_status;
  /* In case echo was off, turn it back on (send IAC WON'T ECHO): */
  if (dcc[j].status & STAT_TELNET)
    dprintf(j, TLN_IAC_C TLN_WONT_C TLN_ECHO_C "\n");
  putlog(LOG_MISC, "*", "%s: %s -> %s", BOT_ENDRELAY1, dcc[j].nick,
         dcc[idx].nick);
  dprintf(j, "\n\n*** %s %s\n", BOT_ENDRELAY2, botnetnick);
  ci = dcc[j].u.relay->chat;
  nfree(dcc[j].u.relay);
  dcc[j].u.chat = ci;
  dcc[j].type = &DCC_CHAT;
  if (dcc[j].u.chat->channel >= 0) {
    chanout_but(-1, dcc[j].u.chat->channel, "*** %s %s.\n",
                dcc[j].nick, BOT_PARTYREJOINED);
    if (dcc[j].u.chat->channel < GLOBAL_CHANS)
      botnet_send_join_idx(j, -1);
  }
  check_tcl_chon(dcc[j].nick, dcc[j].sock);
  check_tcl_chjn(botnetnick, dcc[j].nick, dcc[j].u.chat->channel,
                 geticon(j), dcc[j].sock, dcc[j].host);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void eof_dcc_relaying(int idx)
{
  register int j, x = dcc[idx].u.relay->sock;

  putlog(LOG_MISC, "*", "%s [%s]%s/%d", BOT_LOSTDCCUSER, dcc[idx].nick,
         dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
  for (j = 0; (dcc[j].sock != x) || (dcc[j].type == &DCC_FORK_RELAY); j++);
  putlog(LOG_MISC, "*", "(%s %s)", BOT_DROPPEDRELAY, dcc[j].nick);
  killsock(dcc[j].sock);
  lostdcc(j);                   /* Drop connection to the bot */
}

static void dcc_relay(int idx, char *buf, int j)
{
  unsigned char *p = (unsigned char *) buf;
  int mark;

  for (j = 0; dcc[j].sock != dcc[idx].u.relay->sock ||
       dcc[j].type != &DCC_RELAYING; j++);
  /* If redirecting to a non-telnet user, swallow telnet codes and
   * escape sequences. */
  if (!(dcc[j].status & STAT_TELNET)) {
    while (*p != 0) {
      while (*p != 255 && (*p != '\033' || *(p + 1) != '[') && *p != '\r' && *p)
        p++;                    /* Search for IAC, escape sequences and CR. */
      if (*p == 255) {
        mark = 2;
        if (!*(p + 1))
          mark = 1;             /* Bogus */
        if ((*(p + 1) >= 251) || (*(p + 1) <= 254)) {
          mark = 3;
          if (!*(p + 2))
            mark = 2;           /* Bogus */
        }
        strcpy((char *) p, (char *) (p + mark));
      } else if (*p == '\033') {
        unsigned char *e;

        /* Search for the end of the escape sequence. */
        for (e = p + 2; *e != 'm' && *e; e++);
        strcpy((char *) p, (char *) (e + 1));
      } else if (*p == '\r')
        strcpy((char *) p, (char *) (p + 1));
    }
    if (!buf[0])
      dprintf(-dcc[idx].u.relay->sock, " \n");
    else
      dprintf(-dcc[idx].u.relay->sock, "%s\n", buf);
    return;
  }
  /* Telnet user */
  if (!buf[0])
    dprintf(-dcc[idx].u.relay->sock, " \r\n");
  else
    dprintf(-dcc[idx].u.relay->sock, "%s\r\n", buf);
}

static void dcc_relaying(int idx, char *buf, int j)
{
  struct chat_info *ci;

  if (egg_strcasecmp(buf, "*bye*")) {
    dprintf(-dcc[idx].u.relay->sock, "%s\n", buf);
    return;
  }
  for (j = 0; (dcc[j].sock != dcc[idx].u.relay->sock) ||
       (dcc[j].type != &DCC_RELAY); j++);
  dcc[idx].status = dcc[idx].u.relay->old_status;
  /* In case echo was off, turn it back on (send IAC WON'T ECHO): */
  if (dcc[idx].status & STAT_TELNET)
    dprintf(idx, TLN_IAC_C TLN_WONT_C TLN_ECHO_C "\n");
  dprintf(idx, "\n(%s %s.)\n", BOT_BREAKRELAY, dcc[j].nick);
  dprintf(idx, "%s %s.\n\n", BOT_ABORTRELAY2, botnetnick);
  putlog(LOG_MISC, "*", "%s: %s -> %s", BOT_RELAYBROKEN,
         dcc[idx].nick, dcc[j].nick);
  if (dcc[idx].u.relay->chat->channel >= 0) {
    chanout_but(-1, dcc[idx].u.relay->chat->channel,
                "*** %s joined the party line.\n", dcc[idx].nick);
    if (dcc[idx].u.relay->chat->channel < GLOBAL_CHANS)
      botnet_send_join_idx(idx, -1);
  }
  ci = dcc[idx].u.relay->chat;
  nfree(dcc[idx].u.relay);
  dcc[idx].u.chat = ci;
  dcc[idx].type = &DCC_CHAT;
  check_tcl_chon(dcc[idx].nick, dcc[idx].sock);
  if (dcc[idx].u.chat->channel >= 0)
    check_tcl_chjn(botnetnick, dcc[idx].nick, dcc[idx].u.chat->channel,
                   geticon(idx), dcc[idx].sock, dcc[idx].host);
  killsock(dcc[j].sock);
  lostdcc(j);
}

static void display_relay(int i, char *other)
{
  sprintf(other, "rela  -> sock %d", dcc[i].u.relay->sock);
}

static void display_relaying(int i, char *other)
{
  sprintf(other, ">rly  -> sock %d", dcc[i].u.relay->sock);
}

static void display_tandem_relay(int i, char *other)
{
  strcpy(other, "other  rela");
}

static void display_pre_relay(int i, char *other)
{
  strcpy(other, "other  >rly");
}

static int expmem_relay(void *x)
{
  register struct relay_info *p = (struct relay_info *) x;
  int tot = sizeof(struct relay_info);

  if (p->chat)
    tot += DCC_CHAT.expmem(p->chat);
  return tot;
}

static void kill_relay(int idx, void *x)
{
  register struct relay_info *p = (struct relay_info *) x;

  if (p->chat)
    DCC_CHAT.kill(idx, p->chat);
  nfree(p);
}

struct dcc_table DCC_RELAY = {
  "RELAY",
  0,                            /* Flags */
  eof_dcc_relay,
  dcc_relay,
  NULL,
  NULL,
  display_relay,
  expmem_relay,
  kill_relay,
  NULL
};

static void out_relay(int idx, char *buf, void *x)
{
  register struct relay_info *p = (struct relay_info *) x;

  if (p && p->chat)
    DCC_CHAT.output(idx, buf, p->chat);
  else
    tputs(dcc[idx].sock, buf, strlen(buf));
}

struct dcc_table DCC_RELAYING = {
  "RELAYING",
  0,                            /* Flags */
  eof_dcc_relaying,
  dcc_relaying,
  NULL,
  NULL,
  display_relaying,
  expmem_relay,
  kill_relay,
  out_relay
};

struct dcc_table DCC_FORK_RELAY = {
  "FORK_RELAY",
  0,                            /* Flags */
  failed_tandem_relay,
  cont_tandem_relay,
  &connect_timeout,
  failed_tandem_relay,
  display_tandem_relay,
  expmem_relay,
  kill_relay,
  NULL
};

struct dcc_table DCC_PRE_RELAY = {
  "PRE_RELAY",
  0,                            /* Flags */
  failed_pre_relay,
  pre_relay,
  NULL,
  NULL,
  display_pre_relay,
  expmem_relay,
  kill_relay,
  NULL
};

/* Once a minute, send 'ping' to each bot -- no exceptions
 */
void check_botnet_pings()
{
  int i;
  int bots, users;
  tand_t *bot;

  for (i = 0; i < dcc_total; i++)
    if (dcc[i].type == &DCC_BOT)
      if (dcc[i].status & STAT_PINGED) {
        char s[1024];

        putlog(LOG_BOTS, "*", "%s: %s", BOT_PINGTIMEOUT, dcc[i].nick);
        bot = findbot(dcc[i].nick);
        bots = bots_in_subtree(bot);
        users = users_in_subtree(bot);
        simple_sprintf(s, "%s: %s (lost %d bot%s and %d user%s)",
                       BOT_PINGTIMEOUT, dcc[i].nick, bots,
                       (bots != 1) ? "s" : "", users, (users != 1) ? "s" : "");
        chatout("*** %s\n", s);
        botnet_send_unlinked(i, dcc[i].nick, s);
        killsock(dcc[i].sock);
        lostdcc(i);
      }
  for (i = 0; i < dcc_total; i++)
    if (dcc[i].type == &DCC_BOT) {
      botnet_send_ping(i);
      dcc[i].status |= STAT_PINGED;
    }
  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_BOT) && (dcc[i].status & STAT_LEAF)) {
      tand_t *bot, *via = findbot(dcc[i].nick);

      for (bot = tandbot; bot; bot = bot->next) {
        if ((via == bot->via) && (bot != via)) {
          /* Not leaflike behavior */
          if (dcc[i].status & STAT_WARNED) {
            char s[1024];

            putlog(LOG_BOTS, "*", "%s %s (%s).", BOT_DISCONNECTED,
                   dcc[i].nick, BOT_BOTNOTLEAFLIKE);
            dprintf(i, "bye %s\n", BOT_BOTNOTLEAFLIKE);
            bot = findbot(dcc[i].nick);
            bots = bots_in_subtree(bot);
            users = users_in_subtree(bot);
            simple_sprintf(s, "%s %s (%s) (lost %d bot%s and %d user%s)",
                           BOT_DISCONNECTED, dcc[i].nick, BOT_BOTNOTLEAFLIKE,
                           bots, (bots != 1) ? "s" : "", users, (users != 1) ?
                           "s" : "");
            chatout("*** %s\n", s);
            botnet_send_unlinked(i, dcc[i].nick, s);
            killsock(dcc[i].sock);
            lostdcc(i);
          } else {
            botnet_send_reject(i, botnetnick, NULL, bot->bot, NULL, NULL);
            dcc[i].status |= STAT_WARNED;
          }
        } else
          dcc[i].status &= ~STAT_WARNED;
      }
    }
}

void zapfbot(int idx)
{
  char s[1024];
  int bots, users;
  tand_t *bot;

  bot = findbot(dcc[idx].nick);
  bots = bots_in_subtree(bot);
  users = users_in_subtree(bot);
  simple_sprintf(s, "%s: %s (lost %d bot%s and %d user%s)", BOT_BOTDROPPED,
                 dcc[idx].nick, bots, (bots != 1) ? "s" : "", users,
                 (users != 1) ? "s" : "");
  chatout("*** %s\n", s);
  botnet_send_unlinked(idx, dcc[idx].nick, s);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

void restart_chons()
{
  int i;

  /* Dump party line members */
  for (i = 0; i < dcc_total; i++) {
    if (dcc[i].type == &DCC_CHAT) {
      check_tcl_chon(dcc[i].nick, dcc[i].sock);
      check_tcl_chjn(botnetnick, dcc[i].nick, dcc[i].u.chat->channel,
                     geticon(i), dcc[i].sock, dcc[i].host);
    }
  }
  for (i = 0; i < parties; i++) {
    check_tcl_chjn(party[i].bot, party[i].nick, party[i].chan,
                   party[i].flag, party[i].sock, party[i].from);
  }
}
