/*
 * dcc.c -- handles:
 *   activity on a dcc socket
 *   disconnect on a dcc socket
 *   ...and that's it!  (but it's a LOT)
 */
/*
 * Copyright (C) 1997 Robey Pointer
 * Copyright (C) 1999 - 2018 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 <ctype.h>
#include <errno.h>
#include "modules.h"
#include "tandem.h"

/* Includes for botnet md5 challenge/response code <cybah> */
#include "md5/md5.h"

extern struct userrec *userlist;
extern struct chanset_t *chanset;
extern Tcl_Interp *interp;
extern time_t now;
extern char botnetnick[], ver[], origbotname[], notify_new[];
extern int egg_numver, connect_timeout, conmask, backgrd, max_dcc,
           make_userfile, default_flags, raw_log, ignore_time,
           par_telnet_flood;

#ifdef TLS
extern int tls_vfybots, ssl_files_loaded;

int tls_vfyclients = 0;         /* Certificate validation mode for clients    */
int tls_vfydcc = 0;             /* Verify DCC chat/send user certificates     */
int tls_auth = 0;               /* Allow certificate authentication           */
#endif

struct dcc_t *dcc = NULL;       /* DCC list                                   */
int dcc_total = 0;              /* Total dcc's                                */
int require_p = 0;              /* Require 'p' access to get on the
                                 * party line?                                */
int allow_new_telnets = 0;      /* Allow people to introduce themselves
                                 * via telnet                                 */
int stealth_telnets = 0;        /* Be paranoid? <cybah>                       */
int use_telnet_banner = 0;      /* Display telnet banner?                     */
int password_timeout = 180;     /* Time to wait for a password from a user    */
int bot_timeout = 60;           /* Bot timeout value                          */
int identtimeout = 5;           /* Timeout value for ident lookups            */
int dupwait_timeout = 5;        /* Timeout for rejecting duplicate entries    */
int protect_telnet = 1;         /* Even bother with ident lookups :)          */
int flood_telnet_thr = 5;       /* Number of telnet connections to be
                                 * considered a flood                         */
int flood_telnet_time = 60;     /* In how many seconds?                       */
char network[41] = "unknown-net";      /* Name of the IRC network you're on   */
char bannerfile[121] = "text/banner";  /* File displayed on telnet login      */
char stealth_prompt[81] = "\n\nNickname.\n"; /* stealth_telnet prompt string  */

static void dcc_telnet_hostresolved(int);
static void dcc_telnet_got_ident(int, char *);
static void dcc_telnet_pass(int, int);


/* This is not a universal telnet detector. You need to send WILL STATUS to the
 * other side and pass the reply to this function. A telnet client will respond
 * to this with either DO or DONT STATUS.
 */
static int detect_telnet(unsigned char *buf)
{
  if (!buf || !buf[0] || !buf[1])
    return 0;
  while (buf[2]) {
    if (buf[0] == TLN_IAC && (buf[1] == TLN_DO || buf[1] == TLN_DONT) &&
        buf[2] == TLN_STATUS)
      return 1;
    buf++;
  }
  return 0;
}

/* Escape telnet IAC and prepend CR to LF */
static char *escape_telnet(char *s)
{
  static char buf[1024];
  char *p;

  for (p = buf; *s && (p < (buf + sizeof(buf) - 2)); *p++ = *s++)
    if ((unsigned char) *s == TLN_IAC)
      *p++ = *s;
    else if (*s == '\n')
      *p++ = '\r';
  *p = 0;

  return buf;
}

static void strip_telnet(int sock, char *buf, int *len)
{
  unsigned char *p = (unsigned char *) buf, *o = (unsigned char *) buf;
  int mark;

  while (*p != 0) {
    while ((*p != TLN_IAC) && (*p != 0))
      *o++ = *p++;
    if (*p == TLN_IAC) {
      p++;
      mark = 2;
      if (!*p)
        mark = 1;               /* bogus */
      if ((*p >= TLN_WILL) && (*p <= TLN_DONT)) {
        mark = 3;
        if (!*(p + 1))
          mark = 2;             /* bogus */
      } else if (*p == TLN_WILL) {
        /* WILL X -> response: DONT X */
        /* except WILL ECHO which we just smile and ignore */
        if (*(p + 1) != TLN_ECHO) {
          safe_write(sock, TLN_IAC_C TLN_DONT_C, 2);
          safe_write(sock, p + 1, 1);
        }
      } else if (*p == TLN_DO) {
        /* DO X -> response: WONT X */
        /* except DO ECHO which we just smile and ignore */
        if (*(p + 1) != TLN_ECHO) {
          safe_write(sock, TLN_IAC_C TLN_WONT_C, 2);
          safe_write(sock, p + 1, 1);
        }
      } else if (*p == TLN_AYT) {
        /* "Are You There?" */
        /* response is: "Hell, yes!" */
        static unsigned char sb[] = "\r\n[Hell, yes!]\r\n";
        safe_write(sock, sb, sizeof sb);
      } else if (*p == TLN_IAC) {
        /* IAC character in data, escaped with another IAC */
        *o++ = *p++;
        mark = 1;
      }
      /* Anything else can probably be ignored */
      p += mark - 1;
      *len = *len - mark;
    }
  }
  *o = *p;
}

static void greet_new_bot(int idx)
{
  int bfl = bot_flags(dcc[idx].user);
  int i;

  dcc[idx].timeval = now;
  dcc[idx].u.bot->version[0] = 0;
  dcc[idx].u.bot->numver = 0;
  if (bfl & BOT_REJECT) {
    putlog(LOG_BOTS, "*", DCC_REJECT, dcc[idx].nick);
    dprintf(idx, "bye %s\n", "rejected");
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  if (bfl & BOT_LEAF)
    dcc[idx].status |= STAT_LEAF;
  dcc[idx].status |= STAT_LINKING;
#ifndef NO_OLD_BOTNET
  dprintf(idx, "version %d %d %s <%s>\n", egg_numver, HANDLEN, ver, network);
#else
  dprintf(idx, "v %d %d %s <%s>\n", egg_numver, HANDLEN, ver, network);
#endif
  for (i = 0; i < dcc_total; i++)
    if (dcc[i].type == &DCC_FORK_BOT) {
      killsock(dcc[i].sock);
      lostdcc(i);
    }
}

static void bot_version(int idx, char *par)
{
  char x[1024];
  int l;

  dcc[idx].timeval = now;
  if (in_chain(dcc[idx].nick)) {
    dprintf(idx, "error Sorry, already connected.\n");
    dprintf(idx, "bye\n");
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  if ((par[0] >= '0') && (par[0] <= '9')) {
    char *work;

    work = newsplit(&par);
    dcc[idx].u.bot->numver = atoi(work);
  } else
    dcc[idx].u.bot->numver = 0;

#ifndef NO_OLD_BOTNET
  if (b_numver(idx) < NEAT_BOTNET) {
#if HANDLEN != 9
    putlog(LOG_BOTS, "*", "Non-matching handle lengths with %s, they use 9 "
           "characters.", dcc[idx].nick);
    dprintf(idx, "error Non-matching handle length: mine %d, yours 9\n",
            HANDLEN);
    dprintf(idx, "bye %s\n", "bad handlen");
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
#else
    dprintf(idx, "thisbot %s\n", botnetnick);
#endif
  } else {
#endif
    dprintf(idx, "tb %s\n", botnetnick);
    l = atoi(newsplit(&par));
    if (l != HANDLEN) {
      putlog(LOG_BOTS, "*", "Non-matching handle lengths with %s, they use %d "
             "characters.", dcc[idx].nick, l);
      dprintf(idx, "error Non-matching handle length: mine %d, yours %d\n",
              HANDLEN, l);
      dprintf(idx, "bye %s\n", "bad handlen");
      killsock(dcc[idx].sock);
      lostdcc(idx);
      return;
    }
#ifndef NO_OLD_BOTNET
  }
#endif
  strlcpy(dcc[idx].u.bot->version, par, 120);
  putlog(LOG_BOTS, "*", DCC_LINKED, dcc[idx].nick);
  botnet_send_nlinked(idx, dcc[idx].nick, botnetnick, '!',
                      dcc[idx].u.bot->numver);
  touch_laston(dcc[idx].user, "linked", now);
  dump_links(idx);
  dcc[idx].type = &DCC_BOT;
  addbot(dcc[idx].nick, dcc[idx].nick, botnetnick, '-', dcc[idx].u.bot->numver);
  check_tcl_link(dcc[idx].nick, botnetnick);
  egg_snprintf(x, sizeof x, "v %d", dcc[idx].u.bot->numver);
  bot_share(idx, x);
  dprintf(idx, "el\n");
}

void failed_link(int idx)
{
  char s[NICKLEN + 18], s1[512];

#ifdef TLS
  /* Stop trying when we are sslport+3 */
  if (dcc[idx].port >= dcc[idx].u.bot->port + 3 && dcc[idx].ssl) {
#else
  if (dcc[idx].port >= dcc[idx].u.bot->port + 3) {
#endif
    if (dcc[idx].u.bot->linker[0]) {
      egg_snprintf(s, sizeof s, "Couldn't link to %s.", dcc[idx].nick);
      strcpy(s1, dcc[idx].u.bot->linker);
      add_note(s1, botnetnick, s, -2, 0);
    }
    if (dcc[idx].u.bot->numver >= -1)
      putlog(LOG_BOTS, "*", DCC_LINKFAIL, dcc[idx].nick);
    killsock(dcc[idx].sock);
    strcpy(s, dcc[idx].nick);
    lostdcc(idx);
    autolink_cycle(s);          /* Check for more auto-connections */
    return;
  }

  /* Try next port, if it makes sense (no AF_UNSPEC, ...) */
  killsock(dcc[idx].sock);
  dcc[idx].timeval = now;
#ifdef TLS
  /* Order of attempts:
   * If initial SSL: sslport+1; sslport+2; sslport+3
   * Else: sslport; plain+1; sslport+1; plain+2; sslport+2; plain+3; sslport+3
   */
  if (dcc[idx].u.bot->ssl) {
    ++dcc[idx].port;
  } else if (dcc[idx].ssl) {
    dcc[idx].ssl = 0;
    ++dcc[idx].port;
  } else {
    dcc[idx].ssl = 1;
  }
#else
    ++dcc[idx].port;
#endif

  if (open_telnet(idx, dcc[idx].host, dcc[idx].port) < 0)
    failed_link(idx);
#ifdef TLS
  else if (dcc[idx].ssl && ssl_handshake(dcc[idx].sock, TLS_CONNECT,
           tls_vfybots, LOG_BOTS, dcc[idx].host, NULL))
    failed_link(idx);
#endif
}

static void cont_link(int idx, char *buf, int i)
{
  char x[1024];
  int atr = bot_flags(dcc[idx].user);
  int users, bots;

  if (atr & BOT_HUB) {
    /* Disconnect all +a bots because we just got a hub */
    for (i = 0; i < dcc_total; i++) {
      if ((i != idx) && (bot_flags(dcc[i].user) & BOT_ALT)) {
        if ((dcc[i].type == &DCC_FORK_BOT) || (dcc[i].type == &DCC_BOT_NEW)) {
          killsock(dcc[i].sock);
          lostdcc(i);
        }
      }
    }
    /* Just those currently in the process of linking */
    if (in_chain(dcc[idx].nick)) {
      i = nextbot(dcc[idx].nick);
      if (i > 0) {
        bots = bots_in_subtree(findbot(dcc[idx].nick));
        users = users_in_subtree(findbot(dcc[idx].nick));
        egg_snprintf(x, sizeof x,
                     "Unlinked %s (restructure) (lost %d bot%s and %d user%s)",
                     dcc[i].nick, bots, (bots != 1) ? "s" : "",
                     users, (users != 1) ? "s" : "");
        putlog(LOG_BOTS, "*", "%s.", x);
        botnet_send_unlinked(i, dcc[i].nick, x);
        dprintf(i, "bye %s\n", "restructure");
        killsock(dcc[i].sock);
        lostdcc(i);
      }
    }
  }
  dcc[idx].type = &DCC_BOT_NEW;
  dcc[idx].u.bot->numver = 0;

  /* Don't send our password here, just the username. The code later on
   * will determine if the password needs to be sent in cleartext or if
   * we can send an MD5 digest. <cybah>
   */
  dprintf(idx, "%s\n", botnetnick);
  return;
}

/* This function generates a digest by combining 'challenge' with
 * 'password' and then sends it to the other bot. <Cybah>
 */
static void dcc_bot_digest(int idx, char *challenge, char *password)
{
  MD5_CTX md5context;
  char digest_string[33];       /* 32 for digest in hex + null */
  unsigned char digest[16];
  int i;

  MD5_Init(&md5context);
  MD5_Update(&md5context, (unsigned char *) challenge, strlen(challenge));
  MD5_Update(&md5context, (unsigned char *) password, strlen(password));
  MD5_Final(digest, &md5context);

  for (i = 0; i < 16; i++)
    sprintf(digest_string + (i * 2), "%.2x", digest[i]);
  dprintf(idx, "digest %s\n", digest_string);
  putlog(LOG_BOTS, "*", "Received challenge from %s... sending response ...",
         dcc[idx].nick);
}

static void dcc_bot_new(int idx, char *buf, int x)
{
  struct userrec *u = get_user_by_handle(userlist, dcc[idx].nick);
  char *code;

  if (raw_log) {
    if (!strncmp(buf, "s ", 2))
      putlog(LOG_BOTSHRIN, "*", "{b<-%s} %s", dcc[idx].nick, buf + 2);
    else
      putlog(LOG_BOTNETIN, "*", "[b<-%s] %s", dcc[idx].nick, buf);
  }

  code = newsplit(&buf);
  if (!egg_strcasecmp(code, "*hello!"))
    greet_new_bot(idx);
  else if (!egg_strcasecmp(code, "version") || !egg_strcasecmp(code, "v"))
    bot_version(idx, buf);
  else if (!egg_strcasecmp(code, "badpass"))
    /* We entered the wrong password */
    putlog(LOG_BOTS, "*", DCC_BADPASS, dcc[idx].nick);
  else if (!egg_strcasecmp(code, "passreq")) {
    char *pass = get_user(&USERENTRY_PASS, u);

#ifdef TLS
    /* We got a STARTTLS request earlier. Switch to ssl NOW. Doing this
     * in two steps is necessary in order to synchronize the handshake.
     */
    if (dcc[idx].status & STAT_STARTTLS) {
      dcc[idx].ssl = 1;
      if (ssl_handshake(dcc[idx].sock, TLS_CONNECT, tls_vfybots, LOG_BOTS,
                    dcc[idx].host, NULL)) {
        putlog(LOG_BOTS, "*", "STARTTLS failed while linking to %s",
               dcc[idx].nick);
        if (!ssl_files_loaded)
          putlog(LOG_BOTS, "*", "SSL cert and/or key file not loaded");
      }
      dcc[idx].status &= ~STAT_STARTTLS;
    }
#endif
    if (!pass || !strcmp(pass, "-")) {
      putlog(LOG_BOTS, "*", DCC_PASSREQ, dcc[idx].nick);
      dprintf(idx, "-\n");
    } else {
      /* Determine if the other end supports an MD5 digest instead of a
       * cleartext password. <Cybah>
       */
      if (buf && buf[0] && strchr(buf, '<') && strchr(buf + 1, '>'))
        dcc_bot_digest(idx, buf, pass);
      else
        dprintf(idx, "%s\n", pass);
    }
#ifdef TLS
  } else if (!egg_strcasecmp(code, "starttls") && !dcc[idx].ssl) {
    /* Mark the connection for secure communication, but don't switch yet.
     * The hub has to send a plaintext passreq right after the starttls command
     * and if we switch now, we'll break the handshake. Instead, we'll only
     * send a confirmation to the peer and wait for the passreq.
     */
    putlog(LOG_BOTS, "*", "Got STARTTLS from %s. Replying...", dcc[idx].nick);
    dcc[idx].status |= STAT_STARTTLS;
    /* needs to have space to be distinguished from a plaintext password */
    dprintf(idx, "starttls -\n");
#endif
  } else if (!egg_strcasecmp(code, "error"))
    putlog(LOG_BOTS, "*", DCC_LINKERROR, dcc[idx].nick, buf);
  /* Ignore otherwise */
}

static void eof_dcc_bot_new(int idx)
{
  putlog(LOG_BOTS, "*", DCC_LOSTBOT, dcc[idx].nick, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void timeout_dcc_bot_new(int idx)
{
  putlog(LOG_BOTS, "*", DCC_TIMEOUT, dcc[idx].nick,
         dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_dcc_bot_new(int idx, char *buf)
{
  long tv;

  tv = now - dcc[idx].timeval;
  sprintf(buf, "bot*  waited %lis", tv);
}

static int expmem_dcc_bot_(void *x)
{
  return sizeof(struct bot_info);
}

static void free_dcc_bot_(int n, void *x)
{
  if (dcc[n].type == &DCC_BOT) {
    unvia(n, findbot(dcc[n].nick));
    rembot(dcc[n].nick);
  }
  nfree(x);
}

static void out_dcc_bot(int idx, char *buf, void *x)
{
  size_t len = strlen(buf);
  /* We don't really use x here, so "use it" for the compiler */
  (void)x;

  if (raw_log) {
    /* strip \n from end as putlog appends this */
    char *p = buf, *fnd = NULL;

    if (len && buf[len - 1] == '\n') {
      /* Make a copy as buf could be const */
      fnd = nmalloc(len);
      strlcpy(fnd, buf, len);
      p = fnd;
    }

    if (!strncmp(p, "s ", 2))
      putlog(LOG_BOTSHROUT, "*", "{b->%s} %s", dcc[idx].nick, p + 2);
    else
      putlog(LOG_BOTNETOUT, "*", "[b->%s] %s", dcc[idx].nick, p);

    if (fnd)
      nfree(fnd);

  }

  tputs(dcc[idx].sock, buf, len);
}

struct dcc_table DCC_BOT_NEW = {
  "BOT_NEW",
  0,
  eof_dcc_bot_new,
  dcc_bot_new,
  &bot_timeout,
  timeout_dcc_bot_new,
  display_dcc_bot_new,
  expmem_dcc_bot_,
  free_dcc_bot_,
  out_dcc_bot
};

/* Hash function for tandem bot commands */
extern botcmd_t C_bot[];

static void dcc_bot(int idx, char *code, int i)
{
  char *msg;
  int f;

  if (raw_log) {
    if (!strncmp(code, "s ", 2))
      putlog(LOG_BOTSHRIN, "*", "{b<-%s} %s", dcc[idx].nick, code + 2);
    else
      putlog(LOG_BOTNETIN, "*", "[b<-%s] %s", dcc[idx].nick, code);
  }
  msg = strchr(code, ' ');
  if (msg) {
    *msg = 0;
    msg++;
  } else
    msg = "";
  for (f = i = 0; C_bot[i].name && !f; i++) {
    int y = egg_strcasecmp(code, C_bot[i].name);

    if (!y) {
      /* Found a match */
      (C_bot[i].func) (idx, msg);
      f = 1;
    } else if (y < 0)
      return;
  }
}

static void eof_dcc_bot(int idx)
{
  char x[1024];
  int bots, users;

  bots = bots_in_subtree(findbot(dcc[idx].nick));
  users = users_in_subtree(findbot(dcc[idx].nick));
  egg_snprintf(x, sizeof x,
               "Lost bot: %s (lost %d bot%s and %d user%s)",
               dcc[idx].nick, bots, (bots != 1) ? "s" : "", users,
               (users != 1) ? "s" : "");
  putlog(LOG_BOTS, "*", "%s.", x);
  botnet_send_unlinked(idx, dcc[idx].nick, x);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_dcc_bot(int idx, char *buf)
{
  int i = simple_sprintf(buf, "bot   flags: ");

  buf[i++] = b_status(idx) & STAT_PINGED ? 'P' : 'p';
  buf[i++] = b_status(idx) & STAT_SHARE ? 'U' : 'u';
  buf[i++] = b_status(idx) & STAT_CALLED ? 'C' : 'c';
  buf[i++] = b_status(idx) & STAT_OFFERED ? 'O' : 'o';
  buf[i++] = b_status(idx) & STAT_SENDING ? 'S' : 's';
  buf[i++] = b_status(idx) & STAT_GETTING ? 'G' : 'g';
  buf[i++] = b_status(idx) & STAT_WARNED ? 'W' : 'w';
  buf[i++] = b_status(idx) & STAT_LEAF ? 'L' : 'l';
  buf[i++] = b_status(idx) & STAT_LINKING ? 'I' : 'i';
  buf[i++] = b_status(idx) & STAT_AGGRESSIVE ? 'a' : 'A';
  buf[i++] = 0;
}

static void display_dcc_fork_bot(int idx, char *buf)
{
  sprintf(buf, "conn  bot");
}

struct dcc_table DCC_BOT = {
  "BOT",
  DCT_BOT | DCT_VALIDIDX,
  eof_dcc_bot,
  dcc_bot,
  NULL,
  NULL,
  display_dcc_bot,
  expmem_dcc_bot_,
  free_dcc_bot_,
  out_dcc_bot
};

struct dcc_table DCC_FORK_BOT = {
  "FORK_BOT",
  0,
  failed_link,
  cont_link,
  &connect_timeout,
  failed_link,
  display_dcc_fork_bot,
  expmem_dcc_bot_,
  free_dcc_bot_,
  out_dcc_bot
};

/* This function generates a digest by combining a challenge consisting
 * of our process id + connection time + botnetnick.  The digest is then
 * compared to the one given by the remote bot.
 *
 * Returns 1 if the digest matches, otherwise returns 0.
 * <Cybah>
 */
static int dcc_bot_check_digest(int idx, char *remote_digest)
{
  MD5_CTX md5context;
  char digest_string[33];       /* 32 for digest in hex + null */
  unsigned char digest[16];
  int i;
  char *password = get_user(&USERENTRY_PASS, dcc[idx].user);

  if (!password)
    return 1;

  MD5_Init(&md5context);

  egg_snprintf(digest_string, 33, "<%lx%x@", (long) getpid(),
               (unsigned int) dcc[idx].timeval);
  MD5_Update(&md5context, (unsigned char *) digest_string,
             strlen(digest_string));
  MD5_Update(&md5context, (unsigned char *) botnetnick, strlen(botnetnick));
  MD5_Update(&md5context, (unsigned char *) ">", 1);
  MD5_Update(&md5context, (unsigned char *) password, strlen(password));

  MD5_Final(digest, &md5context);

  for (i = 0; i < 16; i++)
    sprintf(digest_string + (i * 2), "%.2x", digest[i]);

  if (!strcmp(digest_string, remote_digest))
    return 1;

  putlog(LOG_BOTS, "*", "Response (password hash) from %s incorrect",
         dcc[idx].nick);
  return 0;
}

static void dcc_chat_pass(int idx, char *buf, int atr)
{
  if (!atr)
    return;
  if (dcc[idx].status & STAT_TELNET)
    strip_telnet(dcc[idx].sock, buf, &atr);
  else if (detect_telnet((unsigned char *) buf))
    buf += 3; /* 'IAC','DO(DONT)','STATUS' */
  atr = dcc[idx].user ? dcc[idx].user->flags : 0;

  if (atr & USER_BOT) {
    if (raw_log) {
      if (!strncmp(buf, "s ", 2))
        putlog(LOG_BOTSHRIN, "*", "{b<-%s} %s", dcc[idx].nick, buf + 2);
      else
        putlog(LOG_BOTNETIN, "*", "[b<-%s] %s", dcc[idx].nick, buf);
    }
#ifdef TLS
    if (!egg_strncasecmp(buf, "starttls ", 9)) {
      dcc[idx].ssl = 1;
      if (ssl_handshake(dcc[idx].sock, TLS_LISTEN, tls_vfybots, LOG_BOTS,
                        dcc[idx].host, NULL)) {
        killsock(dcc[idx].sock);
        lostdcc(idx);
      }
      return;
    }
#endif
    /* No password set? */
    if (u_pass_match(dcc[idx].user, "-")) {
      char ps[20];

      makepass(ps);
      set_user(&USERENTRY_PASS, dcc[idx].user, ps);
      changeover_dcc(idx, &DCC_BOT_NEW, sizeof(struct bot_info));

      dcc[idx].status = STAT_CALLED;
      dprintf(idx, "*hello!\n");
      greet_new_bot(idx);
#ifdef NO_OLD_BOTNET
      dprintf(idx, "h %s\n", ps);
#else
      dprintf(idx, "handshake %s\n", ps);
#endif
      return;
    }
  }
  /* Check for MD5 digest from remote _bot_. <cybah> */
  if ((atr & USER_BOT) && !egg_strncasecmp(buf, "digest ", 7)) {
    if (dcc_bot_check_digest(idx, buf + 7)) {
      nfree(dcc[idx].u.chat);
      dcc[idx].type = &DCC_BOT_NEW;
      dcc[idx].u.bot = get_data_ptr(sizeof(struct bot_info));
      dcc[idx].status = STAT_CALLED;
      dprintf(idx, "*hello!\n");
      greet_new_bot(idx);
      return;
    } else {
      /* Invalid password/digest */
      /* change here temp to use bot output */
      struct dcc_table *old = dcc[idx].type;
      dcc[idx].type = &DCC_BOT_NEW;
      dprintf(idx, "badpass\n");
      /* change back to original for general kill */
      dcc[idx].type = old;
      putlog(LOG_MISC, "*", DCC_BADLOGIN, dcc[idx].nick, dcc[idx].host,
             dcc[idx].port);
      killsock(dcc[idx].sock);
      lostdcc(idx);
      return;
    }
  }

#ifdef TLS
  /* Skip checking the password if the user is already identified by
   * fingerprint.
   */
  if (dcc[idx].status & STAT_FPRINT || u_pass_match(dcc[idx].user, buf)) {
#else
  if (u_pass_match(dcc[idx].user, buf)) {
#endif
    if (atr & USER_BOT) {
      nfree(dcc[idx].u.chat);
      dcc[idx].type = &DCC_BOT_NEW;
      dcc[idx].u.bot = get_data_ptr(sizeof(struct bot_info));

      dcc[idx].status = STAT_CALLED;
      dprintf(idx, "*hello!\n");
      greet_new_bot(idx);
    } else {
      /* Log entry for successful login -slennox 3/28/1999 */
      putlog(LOG_MISC, "*", DCC_LOGGEDIN, dcc[idx].nick,
             dcc[idx].host, dcc[idx].port);
      if (dcc[idx].u.chat->away) {
        nfree(dcc[idx].u.chat->away);
        dcc[idx].u.chat->away = NULL;
      }
      dcc[idx].type = &DCC_CHAT;
      dcc[idx].status &= ~STAT_CHAT;
      dcc[idx].u.chat->con_flags = (atr & USER_MASTER) ? conmask : 0;
      dcc[idx].u.chat->channel = -2;
      /* Turn echo back on for telnet sessions (send IAC WON'T ECHO). */
      if (dcc[idx].status & STAT_TELNET)
        tputs(dcc[idx].sock, TLN_IAC_C TLN_WONT_C TLN_ECHO_C "\n", 4);
      dcc_chatter(idx);
    }
  } else {
    if (atr & USER_BOT) {
      /* change here temp to use bot output */
      struct dcc_table *old = dcc[idx].type;
      dcc[idx].type = &DCC_BOT_NEW;
      dprintf(idx, "badpass\n");
      dcc[idx].type = old;
    } else
      dprintf(idx, DCC_HOUSTON);
    putlog(LOG_MISC, "*", DCC_BADLOGIN, dcc[idx].nick,
           dcc[idx].host, dcc[idx].port);
    if (dcc[idx].u.chat->away) {        /* su from a dumb user */
      /* Turn echo back on for telnet sessions (send IAC WON'T ECHO). */
      if (dcc[idx].status & STAT_TELNET)
        tputs(dcc[idx].sock, TLN_IAC_C TLN_WONT_C TLN_ECHO_C "\n", 4);
      dcc[idx].user = get_user_by_handle(userlist, dcc[idx].u.chat->away);
      strlcpy(dcc[idx].nick, dcc[idx].u.chat->away, sizeof dcc[idx].nick);
      nfree(dcc[idx].u.chat->away);
      nfree(dcc[idx].u.chat->su_nick);
      dcc[idx].u.chat->away = NULL;
      dcc[idx].u.chat->su_nick = NULL;
      dcc[idx].type = &DCC_CHAT;
      if (dcc[idx].u.chat->channel < GLOBAL_CHANS)
        botnet_send_join_idx(idx, -1);
      chanout_but(-1, dcc[idx].u.chat->channel, DCC_JOIN, dcc[idx].nick);
    } else {
      killsock(dcc[idx].sock);
      lostdcc(idx);
    }
  }
}

static void eof_dcc_general(int idx)
{
  putlog(LOG_MISC, "*", DCC_LOSTDCC, dcc[idx].nick,
         dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void tout_dcc_chat_pass(int idx)
{
  dprintf(idx, "Timeout.\n");
  putlog(LOG_MISC, "*", DCC_PWDTIMEOUT, dcc[idx].nick, dcc[idx].host);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_dcc_chat_pass(int idx, char *buf)
{
  long tv;

  tv = now - dcc[idx].timeval;
  sprintf(buf, "pass  waited %lis", tv);
}

static int expmem_dcc_general(void *x)
{
  struct chat_info *p = (struct chat_info *) x;
  int tot = sizeof(struct chat_info);

  if (p->away)
    tot += strlen(p->away) + 1;
  if (p->buffer) {
    struct msgq *q = p->buffer;

    while (q) {
      tot += sizeof(struct list_type);

      tot += q->len + 1;
      q = q->next;
    }
  }
  if (p->su_nick)
    tot += strlen(p->su_nick) + 1;
  return tot;
}

static void kill_dcc_general(int idx, void *x)
{
  struct chat_info *p = (struct chat_info *) x;

  if (p) {
    if (p->buffer) {
      struct msgq *r, *q;

      for (r = dcc[idx].u.chat->buffer; r; r = q) {
        q = r->next;
        nfree(r->msg);
        nfree(r);
      }
    }
    if (p->away) {
      nfree(p->away);
    }
    nfree(p);
  }
}

/* Remove the color control codes that mIRC,pIRCh etc use to make
 * their client seem so fecking cool! (Sorry, Khaled, you are a nice
 * guy, but when you added this feature you forced people to either
 * use your *SHAREWARE* client or face screenfulls of crap!)
 */
void strip_mirc_codes(int flags, char *text)
{
  char *dd = text;

  while (*text) {
    switch (*text) {
    case 3:                    /* mIRC colors? */
      if (flags & STRIP_COLOR) {
        if (egg_isdigit(text[1])) { /* Is the first char a number? */
          text += 2;            /* Skip over the ^C and the first digit */
          if (egg_isdigit(*text))
            text++;             /* Is this a double digit number? */
          if (*text == ',') {   /* Do we have a background color next? */
            if (egg_isdigit(text[1]))
              text += 2;        /* Skip over the first background digit */
            if (egg_isdigit(*text))
              text++;           /* Is it a double digit? */
          }
        } else
          text++;
        continue;
      }
      break;
    case 2:                    /* Bold text */
      if (flags & STRIP_BOLD) {
        text++;
        continue;
      }
      break;
    case 0x16:                 /* Reverse video */
      if (flags & STRIP_REVERSE) {
        text++;
        continue;
      }
      break;
    case 0x1f:                 /* Underlined text */
      if (flags & STRIP_UNDERLINE) {
        text++;
        continue;
      }
      break;
    case 033:
      if (flags & STRIP_ANSI) {
        text++;
        if (*text == '[') {
          text++;
          while ((*text == ';') || egg_isdigit(*text))
            text++;
          if (*text)
            text++;             /* also kill the following char */
        }
        continue;
      }
      break;
    case 7:
      if (flags & STRIP_BELLS) {
        text++;
        continue;
      }
      break;
    case 017:                  /* Ordinary (mIRC ctrl+o) */
      if (flags & STRIP_ORDINARY) {
        text++;
        continue;
      }
      break;
    case 29:                   /* Italics */
      if (flags & STRIP_ITALICS) {
        text++;
        continue;
      }
      break;
    }
    *dd++ = *text++;            /* Move on to the next char */
  }
  *dd = 0;
}

static void append_line(int idx, char *line)
{
  int l = strlen(line);
  struct msgq *p, *q;
  struct chat_info *c = (dcc[idx].type == &DCC_CHAT) ? dcc[idx].u.chat :
                        dcc[idx].u.file->chat;

  if (c->current_lines > 1000) {
    /* They're probably trying to fill up the bot nuke the sods :) */
    for (p = c->buffer; p; p = q) {
      q = p->next;
      nfree(p->msg);
      nfree(p);
    }
    c->buffer = 0;
    dcc[idx].status &= ~STAT_PAGE;
    do_boot(idx, botnetnick, "too many pages - sendq full");
    return;
  }
  if ((c->line_count < c->max_line) && (c->buffer == NULL)) {
    c->line_count++;
    tputs(dcc[idx].sock, line, l);
  } else {
    c->current_lines++;
    if (c->buffer == NULL)
      q = NULL;
    else
      for (q = c->buffer; q->next; q = q->next);

    p = get_data_ptr(sizeof(struct msgq));

    p->len = l;
    p->msg = get_data_ptr(l + 1);
    p->next = NULL;
    strcpy(p->msg, line);
    if (q == NULL)
      c->buffer = p;
    else
      q->next = p;
  }
}

static void out_dcc_general(int idx, char *buf, void *x)
{
  struct chat_info *p = (struct chat_info *) x;
  char *y = buf;

  strip_mirc_codes(p->strip_flags, buf);
  if (dcc[idx].status & STAT_TELNET)
    y = escape_telnet(buf);
  if (dcc[idx].status & STAT_PAGE)
    append_line(idx, y);
  else
    tputs(dcc[idx].sock, y, strlen(y));
}

struct dcc_table DCC_CHAT_PASS = {
  "CHAT_PASS",
  0,
  eof_dcc_general,
  dcc_chat_pass,
  &password_timeout,
  tout_dcc_chat_pass,
  display_dcc_chat_pass,
  expmem_dcc_general,
  kill_dcc_general,
  out_dcc_general
};

/* Make sure ANSI code is just for color-changing */
int check_ansi(char *v)
{
  int count = 2;

  if (*v++ != '\033')
    return 1;
  if (*v++ != '[')
    return 1;
  while (*v) {
    if (*v == 'm')
      return 0;
    if ((*v != ';') && ((*v < '0') || (*v > '9')))
      return count;
    v++;
    count++;
  }
  return count;
}

static void eof_dcc_chat(int idx)
{
  putlog(LOG_MISC, "*", DCC_LOSTDCC, dcc[idx].nick,
         dcc[idx].host, dcc[idx].port);
  if (dcc[idx].u.chat->channel >= 0) {
    chanout_but(idx, dcc[idx].u.chat->channel, "*** %s lost dcc link.\n",
                dcc[idx].nick);
    if (dcc[idx].u.chat->channel < GLOBAL_CHANS)
      botnet_send_part_idx(idx, "lost dcc link");
    check_tcl_chpt(botnetnick, dcc[idx].nick, dcc[idx].sock,
                   dcc[idx].u.chat->channel);
  }
  check_tcl_chof(dcc[idx].nick, dcc[idx].sock);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void dcc_chat(int idx, char *buf, int i)
{
  int nathan = 0, doron = 0, fixed = 0;
  char *v, *d, filtbuf[2048];

  if (dcc[idx].status & STAT_TELNET)
    strip_telnet(dcc[idx].sock, buf, &i);
  if (buf[0] && (buf[0] != '.') &&
      detect_dcc_flood(&dcc[idx].timeval, dcc[idx].u.chat, idx))
    return;
  dcc[idx].timeval = now;
  if (buf[0]) {
    const char *filt = check_tcl_filt(idx, buf);
    if (filt != buf) {
      strlcpy(filtbuf, filt, sizeof(filtbuf));
      buf = filtbuf;
    }
  }
  if (buf[0]) {
    /* Check for beeps and cancel annoying ones */
    v = buf;
    d = buf;
    while (*v)
      switch (*v) {
      case 7:                  /* Beep - no more than 3 */
        nathan++;
        if (nathan > 3)
          v++;
        else
          *d++ = *v++;
        break;
      case 8:                  /* Backspace - for lame telnet's :) */
        if (d > buf) {
          d--;
        }
        v++;
        break;
      case 27:                 /* ESC - ansi code? */
        doron = check_ansi(v);
        /* If it's valid, append a return-to-normal code at the end */
        if (!doron) {
          *d++ = *v++;
          fixed = 1;
        } else
          v += doron;
        break;
      case '\r':               /* Weird pseudo-linefeed */
        v++;
        break;
      default:
        *d++ = *v++;
      }
    if (fixed)
      strcpy(d, "\033[0m");
    else
      *d = 0;
    if (buf[0]) {               /* Nothing to say - maybe paging... */
      if ((buf[0] == '.') || (dcc[idx].u.chat->channel < 0)) {
        if (buf[0] == '.')
          buf++;
        v = newsplit(&buf);
        rmspace(buf);
        if (check_tcl_dcc(v, idx, buf)) {
          if (dcc[idx].u.chat->channel >= 0)
            check_tcl_chpt(botnetnick, dcc[idx].nick, dcc[idx].sock,
                           dcc[idx].u.chat->channel);
          check_tcl_chof(dcc[idx].nick, dcc[idx].sock);
          dprintf(idx, "*** Ja mata!\n");
          flush_lines(idx, dcc[idx].u.chat);
          putlog(LOG_MISC, "*", DCC_CLOSED, dcc[idx].nick, dcc[idx].host);
          if (dcc[idx].u.chat->channel >= 0) {
            chanout_but(-1, dcc[idx].u.chat->channel,
                        "*** %s left the party line%s%s\n",
                        dcc[idx].nick, buf[0] ? ": " : ".", buf);
            if (dcc[idx].u.chat->channel < GLOBAL_CHANS)
              botnet_send_part_idx(idx, buf);
          }
          if (dcc[idx].u.chat->su_nick) {
            dcc[idx].user = get_user_by_handle(userlist,
                                               dcc[idx].u.chat->su_nick);
            strlcpy(dcc[idx].nick, dcc[idx].u.chat->su_nick, sizeof dcc[idx].nick);
            dcc[idx].type = &DCC_CHAT;
            dprintf(idx, "Returning to real nick %s!\n",
                    dcc[idx].u.chat->su_nick);
            nfree(dcc[idx].u.chat->su_nick);
            dcc[idx].u.chat->su_nick = NULL;
            dcc_chatter(idx);
            if (dcc[idx].u.chat->channel < GLOBAL_CHANS &&
                dcc[idx].u.chat->channel >= 0)
              botnet_send_join_idx(idx, -1);
            return;
          } else if ((dcc[idx].sock != STDOUT) || backgrd) {
            killsock(dcc[idx].sock);
            lostdcc(idx);
            return;
          } else {
            dprintf(DP_STDOUT, "\n### SIMULATION RESET\n\n");
            dcc_chatter(idx);
            return;
          }
        }
      } else if (buf[0] == ',') {
        int me = 0;

        if ((buf[1] == 'm') && (buf[2] == 'e') && buf[3] == ' ')
          me = 1;
        for (i = 0; i < dcc_total; i++) {
          int ok = 0;

          if ((dcc[i].type->flags & DCT_MASTER) &&
              ((dcc[i].type != &DCC_CHAT) || (dcc[i].u.chat->channel >= 0)) &&
              ((i != idx) || (dcc[idx].status & STAT_ECHO)))
            ok = 1;
          if (ok) {
            struct userrec *u = get_user_by_handle(userlist, dcc[i].nick);

            if (u && (u->flags & USER_MASTER)) {
              if (me)
                dprintf(i, "-> %s%s\n", dcc[idx].nick, buf + 3);
              else
                dprintf(i, "-%s-> %s\n", dcc[idx].nick, buf + 1);
            }
          }
        }
      } else if (buf[0] == '\'') {
        int me = 0;

        if ((buf[1] == 'm') && (buf[2] == 'e') &&
            ((buf[3] == ' ') || (buf[3] == '\'') || (buf[3] == ',')))
          me = 1;
        for (i = 0; i < dcc_total; i++) {
          if (dcc[i].type->flags & DCT_CHAT) {
            if (me)
              dprintf(i, "=> %s%s\n", dcc[idx].nick, buf + 3);
            else
              dprintf(i, "=%s=> %s\n", dcc[idx].nick, buf + 1);
          }
        }
      } else {
        if (dcc[idx].u.chat->away != NULL)
          not_away(idx);
        if (dcc[idx].status & STAT_ECHO)
          chanout_but(-1, dcc[idx].u.chat->channel,
                      "<%s> %s\n", dcc[idx].nick, buf);
        else
          chanout_but(idx, dcc[idx].u.chat->channel, "<%s> %s\n",
                      dcc[idx].nick, buf);
        botnet_send_chan(-1, botnetnick, dcc[idx].nick,
                         dcc[idx].u.chat->channel, buf);
        check_tcl_chat(dcc[idx].nick, dcc[idx].u.chat->channel, buf);
      }
    }
  }
  if (dcc[idx].type == &DCC_CHAT)       /* Could have change to files */
    if (dcc[idx].status & STAT_PAGE)
      flush_lines(idx, dcc[idx].u.chat);
}

static void display_dcc_chat(int idx, char *buf)
{
  int i = simple_sprintf(buf, "chat  flags: ");

  buf[i++] = dcc[idx].status & STAT_CHAT ? 'C' : 'c';
  buf[i++] = dcc[idx].status & STAT_PARTY ? 'P' : 'p';
  buf[i++] = dcc[idx].status & STAT_TELNET ? 'T' : 't';
  buf[i++] = dcc[idx].status & STAT_ECHO ? 'E' : 'e';
  buf[i++] = dcc[idx].status & STAT_PAGE ? 'P' : 'p';
  simple_sprintf(buf + i, "/%d", dcc[idx].u.chat->channel);
}

struct dcc_table DCC_CHAT = {
  "CHAT",
  DCT_CHAT | DCT_MASTER | DCT_SHOWWHO | DCT_VALIDIDX | DCT_SIMUL |
    DCT_CANBOOT | DCT_REMOTEWHO,
  eof_dcc_chat,
  dcc_chat,
  NULL,
  NULL,
  display_dcc_chat,
  expmem_dcc_general,
  kill_dcc_general,
  out_dcc_general
};

static int lasttelnets;
static char lasttelnethost[81];
static time_t lasttelnettime;

/* A modified detect_flood for incoming telnet flood protection.
 */
static int detect_telnet_flood(char *floodhost)
{
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  get_user_flagrec(get_user_by_host(floodhost), &fr, NULL);
  if (!flood_telnet_thr || (glob_friend(fr) && !par_telnet_flood))
    return 0;                   /* No flood protection */
  if (egg_strcasecmp(lasttelnethost, floodhost)) {      /* New... */
    strlcpy(lasttelnethost, floodhost, sizeof lasttelnethost);
    lasttelnettime = now;
    lasttelnets = 0;
    return 0;
  }
  if (lasttelnettime < now - flood_telnet_time) {
    /* Flood timer expired, reset it */
    lasttelnettime = now;
    lasttelnets = 0;
    return 0;
  }
  lasttelnets++;
  if (lasttelnets >= flood_telnet_thr) {        /* FLOOD! */
    /* Reset counters */
    lasttelnets = 0;
    lasttelnettime = 0;
    lasttelnethost[0] = 0;
    putlog(LOG_MISC, "*", IRC_TELNETFLOOD, floodhost);
    addignore(floodhost, origbotname, "Telnet connection flood",
              now + (60 * ignore_time));
    return 1;
  }
  return 0;
}

static void dcc_telnet(int idx, char *buf, int i)
{
  uint16_t port;
  int j = 0, sock;

  if (dcc_total + 1 > max_dcc && increase_socks_max()) {
    sockname_t name;
    j = answer(dcc[idx].sock, &name, &port, 0);
    if (j != -1) {
      dprintf(-j, "Sorry, too many connections already.\r\n");
      killsock(j);
    }
    return;
  }
  i = new_dcc(&DCC_DNSWAIT, sizeof(struct dns_info));
  sock = answer(dcc[idx].sock, &dcc[i].sockname, &port, 0);
  while ((sock == -1) && (errno == EAGAIN))
    sock = answer(dcc[idx].sock, &dcc[i].sockname, &port, 0);
  if (sock < 0) {
    putlog(LOG_MISC, "*", DCC_FAILED, strerror(errno));
    return;
  }
  /* Buffer data received on this socket.  */
  sockoptions(sock, EGG_OPTION_SET, SOCK_BUFFER);

  if (port < 1024) {
    putlog(LOG_BOTS, "*", DCC_BADSRC, iptostr(&dcc[i].sockname.addr.sa), port);
    killsock(sock);
    lostdcc(i);
    return;
  }

  dcc[i].u.dns->ip = &dcc[i].sockname;
  dcc[i].sock = sock;
  dcc[i].port = port;
#ifdef TLS
  dcc[i].ssl = dcc[idx].ssl;
  if (dcc[i].ssl) {
    /* In order to support certificate CN validation we need to wait for the dns
     * lookup to complete. Additional we need to mark the socket virtual so we
     * won't read SSL client-hello before ssl_handshake has happened.
     * NOTE: TLS_VERIFYCN configurations value is inverted
     */
    if (!(tls_vfyclients & TLS_VERIFYCN))
      threaddata()->socklist[findsock(sock)].flags |= SOCK_VIRTUAL;
    else if (ssl_handshake(dcc[i].sock, TLS_LISTEN, tls_vfyclients,
        LOG_MISC, NULL, NULL)) {
      killsock(dcc[i].sock);
      lostdcc(i);
      return;
    }
  }
#endif
  dcc[i].timeval = now;
  strcpy(dcc[i].nick, "*");
  dcc[i].u.dns->dns_success = dcc_telnet_hostresolved;
  dcc[i].u.dns->dns_failure = dcc_telnet_hostresolved;
  dcc[i].u.dns->dns_type = RES_HOSTBYIP;
  dcc[i].u.dns->ibuf = dcc[idx].sock;
  dcc[i].u.dns->type = &DCC_IDENTWAIT;
  dcc_dnshostbyip(&dcc[i].sockname);
}

static void dcc_telnet_hostresolved(int i)
{
  int idx;
  int j = 0, sock;
  char s[UHOSTLEN + 20], *userhost;

  strlcpy(dcc[i].host, dcc[i].u.dns->host, UHOSTLEN);

  for (idx = 0; idx < dcc_total; idx++)
    if ((dcc[idx].type == &DCC_TELNET) &&
        (dcc[idx].sock == dcc[i].u.dns->ibuf)) {
      break;
    }
  if (dcc_total == idx) {
    putlog(LOG_BOTS, "*", "Lost listening socket while resolving %s",
           dcc[i].host);
    killsock(dcc[i].sock);
    lostdcc(i);
    return;
  }
  if (dcc[idx].host[0] == '@') {
    /* Restrict by hostname */
    if (!wild_match(dcc[idx].host + 1, dcc[i].host)) {
      putlog(LOG_BOTS, "*", DCC_BADHOST, dcc[i].host);
      killsock(dcc[i].sock);
      lostdcc(i);
      return;
    }
  }
  sprintf(s, "-telnet!telnet@%s", dcc[i].host);
  userhost = s + strlen("-telnet!");
  if (match_ignore(s) || detect_telnet_flood(s)) {
    killsock(dcc[i].sock);
    lostdcc(i);
    return;
  }

  putlog(LOG_MISC, "*", DCC_TELCONN, dcc[i].host, dcc[i].port);

#ifdef TLS
  /* Remove SOCK_VIRTUAL and call ssl_handshake
   * Check out dcc_telnet for more details
   */
  if (dcc[i].ssl && !(tls_vfyclients & TLS_VERIFYCN)) {
    threaddata()->socklist[findsock(dcc[i].sock)].flags &= ~SOCK_VIRTUAL;
    if (ssl_handshake(dcc[i].sock, TLS_LISTEN, tls_vfyclients,
        LOG_MISC, dcc[i].host, NULL)) {
      killsock(dcc[i].sock);
      lostdcc(i);
      return;
    }
  }
#endif

  /* Skip ident lookup for public script listeners */
  if ((dcc[idx].status & LSTN_PUBLIC) && !strcmp(dcc[idx].nick, "(script)")) {
    changeover_dcc(i, &DCC_SOCKET, 0);
    dcc[i].u.other = NULL;
    strcpy(dcc[i].nick, "*");
    strlcpy(dcc[i].host, userhost, UHOSTLEN);
    check_tcl_listen(dcc[idx].host, dcc[i].sock);
    return;
  }

  /* Skip ident lookup if disabled */
  if (identtimeout <= 0) {
    dcc[i].u.ident_sock = dcc[idx].sock;
    dcc_telnet_got_ident(i, userhost);
    return;
  }

  changeover_dcc(i, &DCC_IDENTWAIT, 0);
  dcc[i].timeval = now;
  dcc[i].u.ident_sock = dcc[idx].sock;
  sock = -1;
  j = new_dcc(&DCC_IDENT, 0);
  if (j < 0)
    putlog(LOG_MISC, "*", DCC_IDENTFAIL, dcc[i].host, strerror(errno));
  else {
    memcpy(&dcc[j].sockname, &dcc[i].sockname, sizeof(sockname_t));
    dcc[j].sock = getsock(dcc[j].sockname.family, 0);
    if (dcc[j].sock >= 0) {
      sockname_t name;
      name.addrlen = sizeof(name.addr);
      getsockname(dcc[i].sock, &name.addr.sa, &name.addrlen);
      bind(dcc[j].sock, &name.addr.sa, name.addrlen);
      setsnport(dcc[j].sockname, 113);
      if (connect(dcc[j].sock, &dcc[j].sockname.addr.sa,
          dcc[j].sockname.addrlen) < 0 && (errno != EINPROGRESS)) {
        killsock(dcc[j].sock);
        lostdcc(j);
        putlog(LOG_MISC, "*", DCC_IDENTFAIL, dcc[i].host, strerror(errno));
        j = 0;
      }
      sock = dcc[j].sock;
    }
  }
  if (j < 0) {
    dcc_telnet_got_ident(i, userhost);
    return;
  }
  dcc[j].sock = sock;
  dcc[j].port = 113;
  dcc[j].addr = dcc[i].addr;
  strcpy(dcc[j].host, dcc[i].host);
  strcpy(dcc[j].nick, "*");
  dcc[j].u.ident_sock = dcc[i].sock;
  dcc[j].timeval = now;
  dprintf(j, "%d, %d\n", dcc[i].port, dcc[idx].port);
}

static void eof_dcc_telnet(int idx)
{
  putlog(LOG_MISC, "*", DCC_PORTDIE, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_telnet(int idx, char *buf)
{
#ifdef TLS
  sprintf(buf, "lstn  %s%d%s", dcc[idx].ssl ? "+" : "", dcc[idx].port,
          (dcc[idx].status & LSTN_PUBLIC) ? " pub" : "");
#else
  sprintf(buf, "lstn  %d%s", dcc[idx].port,
          (dcc[idx].status & LSTN_PUBLIC) ? " pub" : "");
#endif
}

struct dcc_table DCC_TELNET = {
  "TELNET",
  DCT_LISTEN,
  eof_dcc_telnet,
  dcc_telnet,
  NULL,
  NULL,
  display_telnet,
  NULL,
  NULL,
  NULL
};

static void eof_dcc_dupwait(int idx)
{
  putlog(LOG_BOTS, "*", DCC_LOSTDUP, dcc[idx].host);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void dcc_dupwait(int idx, char *buf, int i)
{
  /* We just ignore any data at this point. */
  return;
}

/* We now check again. If the bot is still marked as duplicate, there is no
 * botnet lag we could push it on, so we just drop the connection.
 */
static void timeout_dupwait(int idx)
{
  char x[NICKLEN + UHOSTLEN];

  /* Still duplicate? */
  if (in_chain(dcc[idx].nick)) {
    egg_snprintf(x, sizeof x, "%s!%s", dcc[idx].nick, dcc[idx].host);
    dprintf(idx, "error Already connected.\n");
    putlog(LOG_BOTS, "*", DCC_DUPLICATE, x);
    killsock(dcc[idx].sock);
    lostdcc(idx);
  } else {
    /* Ha! Now it's gone and we can grant this bot access. */
    dcc_telnet_pass(idx, dcc[idx].u.dupwait->atr);
  }
}

static void display_dupwait(int idx, char *buf)
{
  sprintf(buf, "wait  duplicate?");
}

static int expmem_dupwait(void *x)
{
  struct dupwait_info *p = (struct dupwait_info *) x;
  int tot = sizeof(struct dupwait_info);

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

static void kill_dupwait(int idx, void *x)
{
  struct dupwait_info *p = (struct dupwait_info *) x;

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

struct dcc_table DCC_DUPWAIT = {
  "DUPWAIT",
  DCT_VALIDIDX,
  eof_dcc_dupwait,
  dcc_dupwait,
  &dupwait_timeout,
  timeout_dupwait,
  display_dupwait,
  expmem_dupwait,
  kill_dupwait,
  out_dcc_bot
};

/* This function is called if a bot gets removed from the list. It checks
 * whether we have a pending duplicate connection for that bot and continues
 * with the login in that case.
 */
void dupwait_notify(char *who)
{
  int idx;

  Assert(who);
  for (idx = 0; idx < dcc_total; idx++)
    if ((dcc[idx].type == &DCC_DUPWAIT) &&
        !egg_strcasecmp(dcc[idx].nick, who)) {
      dcc_telnet_pass(idx, dcc[idx].u.dupwait->atr);
      break;
    }
}

static void dcc_telnet_id(int idx, char *buf, int atr)
{
  int ok = 0;
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };
  struct dcc_table *old = dcc[idx].type;

  if (detect_telnet((unsigned char *) buf)) {
    dcc[idx].status |= STAT_TELNET;
    strip_telnet(dcc[idx].sock, buf, &atr);
  } else
    dcc[idx].status &= ~STAT_TELNET;
  buf[HANDLEN] = 0;
  /* Toss out bad nicknames */
  if (dcc[idx].nick[0] != '@' && !wild_match(dcc[idx].nick, buf)) {
    dprintf(idx, "Sorry, that nickname format is invalid.\n");
    putlog(LOG_BOTS, "*", DCC_BADNICK, dcc[idx].host);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  /* rxvt-unicode */
  if (!strncmp(buf, "\x1B[?1;2c", 7)) /* \e isn't C standard so we use \x1B instead */
    buf += 7;
  dcc[idx].user = get_user_by_handle(userlist, buf);
  get_user_flagrec(dcc[idx].user, &fr, NULL);

  if (glob_bot(fr) && raw_log) {
    if (!strncmp(buf, "s ", 2))
      putlog(LOG_BOTSHRIN, "*", "{b<-%s} %s", dcc[idx].user->handle, buf + 2);
    else
      putlog(LOG_BOTNETIN, "*", "[b<-%s] %s", dcc[idx].user->handle, buf);
  }
#ifdef TLS
  if (dcc[idx].ssl && (tls_auth == 2)) {
    const char *uid = ssl_getuid(dcc[idx].sock);

    if (!uid || strcasecmp(uid, buf)) {
      if (glob_bot(fr)) {
        /* change here temp to use bot output */
        dcc[idx].type = &DCC_BOT_NEW;
        dprintf(idx, "error Certificate UID doesn't match handle\n");
        dcc[idx].type = old;
      } else
        dprintf(idx, "Your certificate UID doesn't match your handle.\n");
      killsock(dcc[idx].sock);
      lostdcc(idx);
      return;
    }
  }
#endif
  /* Make sure users-only/bots-only connects are honored */
  if ((dcc[idx].status & STAT_BOTONLY) && !glob_bot(fr)) {
    dprintf(idx, "This telnet port is for bots only.\n");
    putlog(LOG_BOTS, "*", DCC_NONBOT, dcc[idx].host);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  if ((dcc[idx].status & STAT_USRONLY) && glob_bot(fr)) {
    /* change here temp to use bot output */
    dcc[idx].type = &DCC_BOT_NEW;
    dprintf(idx, "error Only users may connect at this port.\n");
    dcc[idx].type = old;
    putlog(LOG_BOTS, "*", DCC_NONUSER, dcc[idx].host);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  dcc[idx].status &= ~(STAT_BOTONLY | STAT_USRONLY);
  if (!egg_strcasecmp(buf, "NEW") && (allow_new_telnets || make_userfile)) {
    dcc[idx].type = &DCC_TELNET_NEW;
    dcc[idx].timeval = now;
    dprintf(idx, "\n");
    dprintf(idx, IRC_TELNET, botnetnick);
    dprintf(idx, IRC_TELNET1);
    dprintf(idx, "\nEnter the nickname you would like to use.\n");
    return;
  }
  if (chan_op(fr)) {
    if (!require_p)
      ok = 1;
  }
  if (!ok && (glob_party(fr) || glob_bot(fr)))
    ok = 1;

  if (!ok) {
    dprintf(idx, "You don't have access.\n");
    putlog(LOG_MISC, "*", DCC_INVHANDLE, dcc[idx].host, buf);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  correct_handle(buf);
  strlcpy(dcc[idx].nick, buf, sizeof dcc[idx].nick);
  if (glob_bot(fr)) {
    if (!egg_strcasecmp(botnetnick, dcc[idx].nick)) {
      /* change here temp to use bot output */
      dcc[idx].type = &DCC_BOT_NEW;
      dprintf(idx, "error You cannot link using my botnetnick.\n");
      dcc[idx].type = old;
      putlog(LOG_BOTS, "*", DCC_MYBOTNETNICK, dcc[idx].host);
      killsock(dcc[idx].sock);
      lostdcc(idx);
      return;
    } else if (in_chain(dcc[idx].nick)) {
      struct chat_info *ci;

      ci = dcc[idx].u.chat;
      dcc[idx].type = &DCC_DUPWAIT;
      dcc[idx].u.dupwait = get_data_ptr(sizeof(struct dupwait_info));
      dcc[idx].u.dupwait->chat = ci;
      dcc[idx].u.dupwait->atr = atr;
      return;
    }
  }
  dcc_telnet_pass(idx, atr);
}

#ifdef TLS
int dcc_fingerprint(int idx)
{
  char *cf, *uf;
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  get_user_flagrec(dcc[idx].user, &fr, NULL);
  /* Check if fingerprint authentication is allowed or required. */
  if (dcc[idx].ssl && tls_auth) {
    /* Get the fingerprint of the current certificate */
    cf = ssl_getfp(dcc[idx].sock);
    /* Get the fingerprint of the user, if set */
    uf = get_user(&USERENTRY_FPRINT, dcc[idx].user);
    if (cf && uf && !strcasecmp(cf, uf)) {
      if (!glob_bot(fr))
        dprintf(idx, "Used your fingerprint for automatic authentication.\n");
      dcc[idx].status |= STAT_FPRINT;
      dcc_chat_pass(idx, "+", 1);
    /* Required? */
    } else if (tls_auth == 2) {
      if (glob_bot(fr)) {
        /* change here temp to use bot output */
        struct dcc_table *old = dcc[idx].type;
        dcc[idx].type = &DCC_BOT_NEW;
        dprintf(idx, "error fingerprint required\n");
        dcc[idx].type = old;
      } else
        dprintf(idx, "Certificate authentication required. "
                "You need to set your fingerprint.\n");
      killsock(dcc[idx].sock);
      lostdcc(idx);
    }
    return 0;
  }
  return 1;
}
#endif

static void dcc_telnet_pass(int idx, int atr)
{
  int ok = 0;
  struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_ANYWH, 0, 0, 0, 0, 0 };

  get_user_flagrec(dcc[idx].user, &fr, NULL);
#ifdef TLS
  /* Check if fingerprint authentication is allowed or required. */
  if (dcc[idx].ssl && tls_auth) {
    char *cf, *uf;

    /* Get the fingerprint of the current certificate */
    cf = ssl_getfp(dcc[idx].sock);
    /* Get the fingerprint of the user, if set */
    uf = get_user(&USERENTRY_FPRINT, dcc[idx].user);
    if (cf && uf && !strcasecmp(cf, uf)) {
      char fakepass[2] = "+";

      if (!glob_bot(fr))
        dprintf(idx, "Used your fingerprint for automatic authentication.\n");
      dcc[idx].status |= STAT_FPRINT;
      dcc_chat_pass(idx, fakepass, 1);
      return;
    /* Required? */
    } else if (tls_auth == 2) {
      if (glob_bot(fr)) {
        /* change here temp to use bot output */
        struct dcc_table *old = dcc[idx].type;
        dcc[idx].type = &DCC_BOT_NEW;
        dprintf(idx, "error fingerprint required\n");
        dcc[idx].type = old;
      } else
        dprintf(idx, "Certificate authentication required. "
                "You need to set your fingerprint.\n");
      killsock(dcc[idx].sock);
      lostdcc(idx);
      return;
    }
  }
#endif
  /* No password set? */
  if (!glob_bot(fr) && u_pass_match(dcc[idx].user, "-")) {
    dprintf(idx, "Can't telnet until you have a password set.\n");
    putlog(LOG_MISC, "*", DCC_NOPASS, dcc[idx].nick, dcc[idx].host);
    killsock(dcc[idx].sock);
    lostdcc(idx);
    return;
  }
  ok = 0;
  if (dcc[idx].type == &DCC_DUPWAIT) {
    struct chat_info *ci;

    ci = dcc[idx].u.dupwait->chat;
    nfree(dcc[idx].u.dupwait);
    dcc[idx].u.chat = ci;
  }
  dcc[idx].type = &DCC_CHAT_PASS;
  dcc[idx].timeval = now;
  if (glob_botmast(fr))
    ok = 1;
  else if (chan_op(fr)) {
    if (!require_p)
      ok = 1;
    else if (glob_party(fr))
      ok = 1;
  } else if (glob_party(fr)) {
    ok = 1;
    dcc[idx].status |= STAT_PARTY;
  }
  if (glob_bot(fr))
    ok = 1;
  if (!ok) {
    struct chat_info *ci;

    ci = dcc[idx].u.chat;
    dcc[idx].u.file = get_data_ptr(sizeof(struct file_info));
    dcc[idx].u.file->chat = ci;
  }

  if (glob_bot(fr)) {
    /* change here temp to use bot output */
    struct dcc_table *old = dcc[idx].type;
    dcc[idx].type = &DCC_BOT_NEW;
#ifdef TLS
  /* Ask the peer to switch to ssl communication. We'll continue using plain
   * text, until it replies with starttls itself. Bots which don't support ssl
   * will simply ignore the request and everything will go on as usual.
   */
    if (!dcc[idx].ssl) {
      /* find number in socklist */
      int i = findsock(dcc[idx].sock);
      struct threaddata *td = threaddata();
      /* mark socket to read next incoming at reduced len */
      td->socklist[i].flags |= SOCK_SENTTLS;
      /* Prefix with \n in case of newline-less ending stealth_prompt */
      dprintf(idx, "\nstarttls\n");
      putlog(LOG_BOTS, "*", "Sent STARTTLS to %s...", dcc[idx].nick);
    }
#endif
    /* Must generate a string consisting of our process ID and the current
     * time. The bot will add it's password to the end and use it to generate
     * an MD5 checksum (always 128bit). The checksum is sent back and this
     * end does the same. The remote bot is only allowed access if the
     * checksums match.
     *
     * Please don't fuck with 'timeval', or the digest we generate later for
     * authentication will not be correct - you've been warned ;)
     * <Cybah>
     */
    putlog(LOG_BOTS, "*", "Challenging %s...", dcc[idx].nick);
    /* Prefix with \n in case of newline-less ending stealth_prompt */
    dprintf(idx, "\npassreq <%x%x@%s>\n", getpid(), dcc[idx].timeval, botnetnick);
    dcc[idx].type = old;
  } else {
    /* NOTE: The MD5 digest used above to prevent cleartext passwords being
     *       sent across the net will _only_ work when we have the cleartext
     *       password. User passwords are encrypted (with blowfish usually)
     *       so the same thing cant be done. Botnet passwords are always
     *       stored in cleartext, or at least something that can be reversed.
     *       <Cybah>
     */

    /* Turn off remote telnet echo (send IAC WILL ECHO). */
    if (dcc[idx].status & STAT_TELNET) {
      char buf[1030];
      egg_snprintf(buf, sizeof buf, "\n%s%s\r\n", escape_telnet(DCC_ENTERPASS),
               TLN_IAC_C TLN_WILL_C TLN_ECHO_C);
      tputs(dcc[idx].sock, buf, strlen(buf));
    } else
      dprintf(idx, "\n%s\n", DCC_ENTERPASS);
  }
}

static void eof_dcc_telnet_id(int idx)
{
  putlog(LOG_MISC, "*", DCC_LOSTCON, dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void timeout_dcc_telnet_id(int idx)
{
  dprintf(idx, "Timeout.\n");
  putlog(LOG_MISC, "*", DCC_TTIMEOUT, dcc[idx].host);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_dcc_telnet_id(int idx, char *buf)
{
  long tv;

  tv = now - dcc[idx].timeval;
  sprintf(buf, "t-in  waited %lis", tv);
}

struct dcc_table DCC_TELNET_ID = {
  "TELNET_ID",
  0,
  eof_dcc_telnet_id,
  dcc_telnet_id,
  &password_timeout,
  timeout_dcc_telnet_id,
  display_dcc_telnet_id,
  expmem_dcc_general,
  kill_dcc_general,
  out_dcc_general
};

static void dcc_telnet_new(int idx, char *buf, int x)
{
  int ok = 1;
  char work[1024], *p, *q, *r;

  buf[HANDLEN] = 0;
  if (dcc[idx].status & STAT_TELNET)
    strip_telnet(dcc[idx].sock, buf, &x);
  dcc[idx].timeval = now;
  for (x = 0; x < strlen(buf); x++)
    if (buf[x] <= 32)
      ok = 0;
  if (!ok) {
    dprintf(idx, "\nYou can't use weird symbols in your nick.\n");
    dprintf(idx, "Try another one please:\n");
  } else if (strchr(BADHANDCHARS, buf[0]) != NULL) {
    dprintf(idx, "\nYou can't start your nick with the character '%c'\n",
            buf[0]);
    dprintf(idx, "Try another one please:\n");
  } else if (get_user_by_handle(userlist, buf)) {
    dprintf(idx, "\nSorry, that nickname is taken already.\n");
    dprintf(idx, "Try another one please:\n");
    return;
  } else if (!egg_strcasecmp(buf, botnetnick))
    dprintf(idx, "Sorry, can't use my name for a nick.\n");
  else {
    strcpy(dcc[idx].nick, buf);
    if (make_userfile)
      userlist = adduser(userlist,
                 buf, "-telnet!*@*", "-", sanity_check(default_flags |
                 USER_PARTY | USER_MASTER | USER_OWNER));
    else {
      p = strchr(dcc[idx].host, '@');
      if (p) {
        q = p;
        *q = 0;
        p++;
        r = strchr(p, '.');
        if (!r)
          simple_sprintf(work, "-telnet!%s@%s", dcc[idx].host, p);
        else
          simple_sprintf(work, "-telnet!%s@*%s", dcc[idx].host, r);
        *q = '@';
      } else
        simple_sprintf(work, "-telnet!*@*%s", dcc[idx].host);
      userlist = adduser(userlist, buf, work, "-",
                         sanity_check(USER_PARTY | default_flags));
    }
    reaffirm_owners();
    dcc[idx].status = STAT_ECHO | STAT_TELNET;
    dcc[idx].type = &DCC_CHAT;  /* Just so next line will work */
    dcc[idx].user = get_user_by_handle(userlist, buf);
    check_dcc_attrs(dcc[idx].user, USER_PARTY | default_flags);
    dcc[idx].type = &DCC_TELNET_PW;
    if (make_userfile) {
      dprintf(idx, "\nYOU ARE THE MASTER/OWNER ON THIS BOT NOW\n");
      dprintf(idx, IRC_LIMBO);
      putlog(LOG_MISC, "*", DCC_INSTCOMPL, buf);
      make_userfile = 0;
      write_userfile(-1);
      add_note(buf, botnetnick, "Welcome to eggdrop! :)", -1, 0);
    }
    dprintf(idx, "\nOkay, now choose and enter a password:\n");
    dprintf(idx, "(Only the first 15 letters are significant.)\n");
  }
}

static void dcc_telnet_pw(int idx, char *buf, int x)
{
  char *newpass;
  int ok;

  if (dcc[idx].status & STAT_TELNET)
    strip_telnet(dcc[idx].sock, buf, &x);
  buf[16] = 0;
  ok = 1;
  if (strlen(buf) < 4) {
    dprintf(idx, "\nTry to use at least 4 characters in your password.\n");
    dprintf(idx, "Choose and enter a password:\n");
    return;
  }
  for (x = 0; x < strlen(buf); x++)
    if ((buf[x] <= 32) || (buf[x] == 127))
      ok = 0;
  if (!ok) {
    dprintf(idx, "\nYou can't use weird symbols in your password.\n");
    dprintf(idx, "Try another one please:\n");
    return;
  }
  putlog(LOG_MISC, "*", DCC_NEWUSER, dcc[idx].nick, dcc[idx].host,
         dcc[idx].port);
  if (notify_new[0]) {
    char s[NICKLEN+UHOSTMAX+32], s1[NICKLEN+UHOSTMAX+32], s2[NICKLEN+UHOSTMAX+32];

    sprintf(s, "Introduced to %s, %s", dcc[idx].nick, dcc[idx].host);
    strcpy(s1, notify_new);
    splitc(s2, s1, ',');
    while (s2[0]) {
      rmspace(s2);
      add_note(s2, botnetnick, s, -1, 0);
      splitc(s2, s1, ',');
    }
    rmspace(s1);
    add_note(s1, botnetnick, s, -1, 0);
  }
  newpass = newsplit(&buf);
  set_user(&USERENTRY_PASS, dcc[idx].user, newpass);
  dprintf(idx, "\nRemember that!  You'll need it next time you log in.\n");
  dprintf(idx, "You now have an account on %s...\n\n\n", botnetnick);
  dcc[idx].type = &DCC_CHAT;
  dcc[idx].u.chat->channel = -2;
  dcc_chatter(idx);
}

static void eof_dcc_telnet_new(int idx)
{
  putlog(LOG_MISC, "*", DCC_LOSTNEWUSER, dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void eof_dcc_telnet_pw(int idx)
{
  putlog(LOG_MISC, "*", DCC_LOSTNEWUSR2, dcc[idx].nick, dcc[idx].host,
         dcc[idx].port);
  deluser(dcc[idx].nick);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void tout_dcc_telnet_new(int idx)
{
  dprintf(idx, "Guess you're not there.  Bye.\n");
  putlog(LOG_MISC, "*", DCC_TIMEOUTUSER, dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void tout_dcc_telnet_pw(int idx)
{
  dprintf(idx, "Guess you're not there.  Bye.\n");
  putlog(LOG_MISC, "*", DCC_TIMEOUTUSR2, dcc[idx].nick,
         dcc[idx].host, dcc[idx].port);
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_dcc_telnet_new(int idx, char *buf)
{
  long tv;

  tv = now - dcc[idx].timeval;
  sprintf(buf, "new   waited %lis", tv);
}

static void display_dcc_telnet_pw(int idx, char *buf)
{
  long tv;

  tv = now - dcc[idx].timeval;
  sprintf(buf, "newp  waited %lis", tv);
}

struct dcc_table DCC_TELNET_NEW = {
  "TELNET_NEW",
  0,
  eof_dcc_telnet_new,
  dcc_telnet_new,
  &password_timeout,
  tout_dcc_telnet_new,
  display_dcc_telnet_new,
  expmem_dcc_general,
  kill_dcc_general,
  out_dcc_general
};

struct dcc_table DCC_TELNET_PW = {
  "TELNET_PW",
  0,
  eof_dcc_telnet_pw,
  dcc_telnet_pw,
  &password_timeout,
  tout_dcc_telnet_pw,
  display_dcc_telnet_pw,
  expmem_dcc_general,
  kill_dcc_general,
  out_dcc_general
};

static int call_tcl_func(char *name, int idx, char *args)
{
  char s[12];

  snprintf(s, sizeof s, "%d", idx);
  Tcl_SetVar(interp, "_n", s, 0);
  Tcl_SetVar(interp, "_a", args, 0);
  if (Tcl_VarEval(interp, name, " $_n $_a", NULL) == TCL_ERROR) {
    putlog(LOG_MISC, "*", DCC_TCLERROR, name, tcl_resultstring());
    Tcl_BackgroundError(interp);
    return -1;
  }
  return tcl_resultint();
}

static void dcc_script(int idx, char *buf, int len)
{
  long oldsock;

  if (dcc[idx].status & STAT_TELNET)
    strip_telnet(dcc[idx].sock, buf, &len);
  if (!len)
    return;

  dcc[idx].timeval = now;
  oldsock = dcc[idx].sock;      /* Remember the socket number.  */
  if (call_tcl_func(dcc[idx].u.script->command, dcc[idx].sock, buf)) {
    void *old_other = NULL;

    /* Check whether the socket and dcc entry are still valid. They
     * might have been killed by `killdcc'. */
    if (dcc[idx].sock != oldsock || idx > max_dcc)
      return;

    old_other = dcc[idx].u.script->u.other;
    dcc[idx].type = dcc[idx].u.script->type;
    nfree(dcc[idx].u.script);
    dcc[idx].u.other = old_other;
    if (dcc[idx].type == &DCC_SOCKET) {
      /* Kill the whole thing off */
      killsock(dcc[idx].sock);
      lostdcc(idx);
      return;
    }
    if (dcc[idx].type == &DCC_CHAT) {
      if (dcc[idx].u.chat->channel >= 0) {
        chanout_but(-1, dcc[idx].u.chat->channel, DCC_RETURN, dcc[idx].nick);
        if (dcc[idx].u.chat->channel < 10000)
          botnet_send_join_idx(idx, -1);
        check_tcl_chjn(botnetnick, dcc[idx].nick, dcc[idx].u.chat->channel,
                       geticon(idx), dcc[idx].sock, dcc[idx].host);
      }
    }
  }
}

static void eof_dcc_script(int idx)
{
  void *old;
  int oldflags;

  /* This will stop a killdcc from working, in case the script tries
   * to kill it's controlling socket while handling an EOF <cybah>
   */
  oldflags = dcc[idx].type->flags;
  dcc[idx].type->flags &= ~(DCT_VALIDIDX);
  /* Tell the script they're gone: */
  call_tcl_func(dcc[idx].u.script->command, dcc[idx].sock, "");
  /* Restore the flags */
  dcc[idx].type->flags = oldflags;
  old = dcc[idx].u.script->u.other;
  dcc[idx].type = dcc[idx].u.script->type;
  nfree(dcc[idx].u.script);
  dcc[idx].u.other = old;
  /* Then let it fall thru to the real one */
  if (dcc[idx].type && dcc[idx].type->eof)
    dcc[idx].type->eof(idx);
  else {
    putlog(LOG_MISC, "*", DCC_DEADSOCKET, dcc[idx].sock, dcc[idx].type->name);
    killsock(dcc[idx].sock);
    lostdcc(idx);
  }
}

static void display_dcc_script(int idx, char *buf)
{
  sprintf(buf, "scri  %s", dcc[idx].u.script->command);
}

static int expmem_dcc_script(void *x)
{
  struct script_info *p = (struct script_info *) x;
  int tot = sizeof(struct script_info);

  if (p->type && p->u.other)
    tot += p->type->expmem(p->u.other);
  return tot;
}

static void kill_dcc_script(int idx, void *x)
{
  struct script_info *p = (struct script_info *) x;

  if (p->type && p->u.other)
    p->type->kill(idx, p->u.other);
  nfree(p);
}

static void out_dcc_script(int idx, char *buf, void *x)
{
  struct script_info *p = (struct script_info *) x;

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

struct dcc_table DCC_SCRIPT = {
  "SCRIPT",
  DCT_VALIDIDX,
  eof_dcc_script,
  dcc_script,
  NULL,
  NULL,
  display_dcc_script,
  expmem_dcc_script,
  kill_dcc_script,
  out_dcc_script
};

static void dcc_socket(int idx, char *buf, int len)
{
}

static void eof_dcc_socket(int idx)
{
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

static void display_dcc_socket(int idx, char *buf)
{
  strcpy(buf, "sock  (stranded)");
}

struct dcc_table DCC_SOCKET = {
  "SOCKET",
  DCT_VALIDIDX,
  eof_dcc_socket,
  dcc_socket,
  NULL,
  NULL,
  display_dcc_socket,
  NULL,
  NULL,
  NULL
};

static void display_dcc_lost(int idx, char *buf)
{
  strcpy(buf, "lost");
}

struct dcc_table DCC_LOST = {
  "LOST",
  0,
  NULL,
  dcc_socket,
  NULL,
  NULL,
  display_dcc_lost,
  NULL,
  NULL,
  NULL
};

void dcc_identwait(int idx, char *buf, int len)
{
  /* Ignore anything now */
}

void eof_dcc_identwait(int idx)
{
  int i;

  putlog(LOG_MISC, "*", DCC_LOSTCONN, dcc[idx].host, dcc[idx].port);
  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_IDENT) &&
        (dcc[i].u.ident_sock == dcc[idx].sock)) {
      killsock(dcc[i].sock);    /* Cleanup ident socket */
      dcc[i].u.other = 0;
      lostdcc(i);
      break;
    }
  killsock(dcc[idx].sock);      /* Cleanup waiting socket */
  dcc[idx].u.other = 0;
  lostdcc(idx);
}

static void display_dcc_identwait(int idx, char *buf)
{
  long tv;

  tv = now - dcc[idx].timeval;
  sprintf(buf, "idtw  waited %lis", tv);
}

struct dcc_table DCC_IDENTWAIT = {
  "IDENTWAIT",
  0,
  eof_dcc_identwait,
  dcc_identwait,
  NULL,
  NULL,
  display_dcc_identwait,
  NULL,
  NULL,
  NULL
};

void dcc_ident(int idx, char *buf, int len)
{
  char response[512], uid[512], buf1[UHOSTLEN];
  int i;

  *response = *uid = '\0';
  sscanf(buf, "%*[^:]:%[^:]:%*[^:]:%[^\n]\n", response, uid);
  rmspace(response);
  if (response[0] != 'U') {
    dcc[idx].timeval = now;
    return;
  }
  rmspace(uid);
  uid[20] = 0;                  /* 20 character ident max */
  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_IDENTWAIT) &&
        (dcc[i].sock == dcc[idx].u.ident_sock)) {
      simple_sprintf(buf1, "%s@%s", uid, dcc[idx].host);
      dcc_telnet_got_ident(i, buf1);
    }
  dcc[idx].u.other = 0;
  killsock(dcc[idx].sock);
  lostdcc(idx);
}

void eof_dcc_ident(int idx)
{
  char buf[UHOSTLEN];
  int i;

  for (i = 0; i < dcc_total; i++)
    if ((dcc[i].type == &DCC_IDENTWAIT) &&
        (dcc[i].sock == dcc[idx].u.ident_sock)) {
      putlog(LOG_MISC, "*", DCC_EOFIDENT);
      simple_sprintf(buf, "telnet@%s", dcc[idx].host);
      dcc_telnet_got_ident(i, buf);
    }
  killsock(dcc[idx].sock);
  dcc[idx].u.other = 0;
  lostdcc(idx);
}

static void display_dcc_ident(int idx, char *buf)
{
  sprintf(buf, "idnt  (sock %d)", dcc[idx].u.ident_sock);
}

struct dcc_table DCC_IDENT = {
  "IDENT",
  0,
  eof_dcc_ident,
  dcc_ident,
  &identtimeout,
  eof_dcc_ident,
  display_dcc_ident,
  NULL,
  NULL,
  NULL
};

static void dcc_telnet_got_ident(int i, char *host)
{
  int idx;
  char x[1024];

  for (idx = 0; idx < dcc_total; idx++)
    if ((dcc[idx].type == &DCC_TELNET) &&
        (dcc[idx].sock == dcc[i].u.ident_sock))
      break;
  dcc[i].u.other = 0;
  if (dcc_total == idx) {
    putlog(LOG_MISC, "*", DCC_LOSTIDENT);
    killsock(dcc[i].sock);
    lostdcc(i);
    return;
  }
  strlcpy(dcc[i].host, host, UHOSTLEN);
  egg_snprintf(x, sizeof x, "-telnet!%s", dcc[i].host);
  if (protect_telnet && !make_userfile) {
    struct userrec *u;
    int ok = 1;

    u = get_user_by_host(x);
    /* Not a user or +p & require p OR +o */
    if (!u)
      ok = 0;
    else if (require_p && !(u->flags & USER_PARTY))
      ok = 0;
    else if (!require_p && !(u->flags & USER_OP))
      ok = 0;
    if (!ok && u && (u->flags & USER_BOT))
      ok = 1;
    if (!ok && (dcc[idx].status & LSTN_PUBLIC))
      ok = 1;
    if (!ok) {
      putlog(LOG_MISC, "*", DCC_NOACCESS, dcc[i].host);
      killsock(dcc[i].sock);
      lostdcc(i);
      return;
    }
  }
  if (match_ignore(x)) {
    killsock(dcc[i].sock);
    lostdcc(i);
    return;
  }

  /* Script? */
  if (!strcmp(dcc[idx].nick, "(script)")) {
    dcc[i].type = &DCC_SOCKET;
    dcc[i].u.other = NULL;
    strcpy(dcc[i].nick, "*");
    check_tcl_listen(dcc[idx].host, dcc[i].sock);
    return;
  }
  /* Do not buffer data anymore. All received and stored data is passed
   * over to the dcc functions from now on.  */
  sockoptions(dcc[i].sock, EGG_OPTION_UNSET, SOCK_BUFFER);

  dcc[i].type = &DCC_TELNET_ID;
  dcc[i].u.chat = get_data_ptr(sizeof(struct chat_info));
  egg_bzero(dcc[i].u.chat, sizeof(struct chat_info));

  /* Note: we don't really care about telnet status here. We use the
   * STATUS option as a hopefully harmless way to detect if the other
   * side is a telnet client or not. */
  dprintf(i, TLN_IAC_C TLN_WILL_C TLN_STATUS_C);

  /* Copy acceptable-nick/host mask */
  dcc[i].status = STAT_TELNET | STAT_ECHO;
  if (!strcmp(dcc[idx].nick, "(bots)"))
    dcc[i].status |= STAT_BOTONLY;
  if (!strcmp(dcc[idx].nick, "(users)"))
    dcc[i].status |= STAT_USRONLY;
  /* Copy acceptable-nick/host mask */
  strlcpy(dcc[i].nick, dcc[idx].host, HANDLEN);
  dcc[i].timeval = now;
  strcpy(dcc[i].u.chat->con_chan, chanset ? chanset->dname : "*");
  /* Displays a customizable banner. */
  if (use_telnet_banner)
    show_banner(i);
  /* This is so we don't tell someone doing a portscan anything
   * about ourselves. <cybah>
   */
  if (stealth_telnets) {
    /* Show here so it doesn't interfere with newline-less stealth_prompt */
    if (allow_new_telnets)
      dprintf(i, "(If you are new, enter 'NEW' here.)\n");
    dprintf(i, stealth_prompt);
  } else {
    dprintf(i, "\n\n");
    sub_lang(i, MISC_BANNER);
    /* Show here so it doesn't get lost before the banner */
    if (allow_new_telnets)
      dprintf(i, "(If you are new, enter 'NEW' here.)\n");
  }
}
