/*
 * net.c -- handles:
 *   all raw network i/o
 *
 * $Id: net.c,v 1.80 2008-02-16 21:41:04 guppy Exp $
 */
/*
 * This is hereby released into the public domain.
 * Robey Pointer, robey@netcom.com
 *
 * Changes after Feb 23, 1999 Copyright Eggheads Development Team
 *
 * 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 <fcntl.h>
#include "main.h"
#include <limits.h>
#include <string.h>
#include <netdb.h>
#include <sys/socket.h>
#if HAVE_SYS_SELECT_H
#  include <sys/select.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#if HAVE_UNISTD_H
#  include <unistd.h>
#endif
#include <setjmp.h>

#ifndef HAVE_GETDTABLESIZE
#  ifdef FD_SETSIZE
#    define getdtablesize() FD_SETSIZE
#  else
#    define getdtablesize() 200
#  endif
#endif

extern struct dcc_t *dcc;
extern int backgrd, use_stderr, resolve_timeout, dcc_total;
extern unsigned long otraffic_irc_today, otraffic_bn_today, otraffic_dcc_today,
                     otraffic_filesys_today, otraffic_trans_today,
                     otraffic_unknown_today;

char hostname[121] = "";      /* Hostname can be specified in the config file.*/
char myip[121] = "";          /* IP can be specified in the config file.      */
char firewall[121] = "";      /* Socks server for firewall.                   */
int firewallport = 1080;      /* Default port of socks 4/5 firewalls.         */
char botuser[21] = "eggdrop"; /* Username of the user running the bot.        */
int dcc_sanitycheck = 0;      /* Do some sanity checking on dcc connections.  */

sock_list *socklist = NULL;   /* Enough to be safe.                           */
int MAXSOCKS = 0;
sigjmp_buf alarmret;             /* Env buffer for alarm() returns.              */

/* Types of proxies */
#define PROXY_SOCKS   1
#define PROXY_SUN     2


/* I need an UNSIGNED long for dcc type stuff
 */
IP my_atoul(char *s)
{
  IP ret = 0;

  while ((*s >= '0') && (*s <= '9')) {
    ret *= 10;
    ret += ((*s) - '0');
    s++;
  }
  return ret;
}

/* Initialize the socklist
 */
void init_net()
{
  int i;

  for (i = 0; i < MAXSOCKS; i++) {
    socklist[i].flags = SOCK_UNUSED;
  }
}

int expmem_net()
{
  int i, tot = 0;

  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & SOCK_UNUSED)) {
      if (socklist[i].inbuf != NULL)
        tot += strlen(socklist[i].inbuf) + 1;
      if (socklist[i].outbuf != NULL)
        tot += socklist[i].outbuflen;
    }
  }
  return tot;
}

/* Get my ip number
 */
IP getmyip()
{
  struct hostent *hp;
  char s[121];
  IP ip;
  struct in_addr *in;
  if (myip[0]) {
    if ((myip[strlen(myip) - 1] >= '0') && (myip[strlen(myip) - 1] <= '9'))
      return (IP) inet_addr(myip);
  }
  /* Also could be pre-defined */
  if (hostname[0])
    hp = gethostbyname(hostname);
  else {
    gethostname(s, 120);
    hp = gethostbyname(s);
  }
  if (hp == NULL)
    fatal("Hostname self-lookup failed. Please set 'my-ip' in the config "
          "file.", 0);
  in = (struct in_addr *) (hp->h_addr_list[0]);
  ip = (IP) (in->s_addr);
  return ip;
}

void neterror(char *s)
{
  switch (errno) {
  case EADDRINUSE:
    strcpy(s, "Address already in use");
    break;
  case EADDRNOTAVAIL:
    strcpy(s, "Cannot assign requested address");
    break;
  case EAFNOSUPPORT:
    strcpy(s, "Address family not supported");
    break;
  case EALREADY:
    strcpy(s, "Socket already in use");
    break;
  case EBADF:
    strcpy(s, "Socket descriptor is bad");
    break;
  case ECONNREFUSED:
    strcpy(s, "Connection refused");
    break;
  case EFAULT:
    strcpy(s, "Bad address");
    break;
  case EINPROGRESS:
    strcpy(s, "Operation in progress");
    break;
  case EINTR:
    strcpy(s, "Timeout");
    break;
  case EINVAL:
    strcpy(s, "Invalid argument");
    break;
  case EISCONN:
    strcpy(s, "Socket already connected");
    break;
  case ENETUNREACH:
    strcpy(s, "Network unreachable");
    break;
  case ENOTSOCK:
    strcpy(s, "Socket operation on non-socket");
    break;
  case ETIMEDOUT:
    strcpy(s, "Connection timed out");
    break;
  case ENOTCONN:
    strcpy(s, "Socket is not connected");
    break;
  case EHOSTUNREACH:
    strcpy(s, "No route to host");
    break;
  case EPIPE:
    strcpy(s, "Broken pipe");
    break;
#ifdef ECONNRESET
  case ECONNRESET:
    strcpy(s, "Connection reset by peer");
    break;
#endif
#ifdef EACCES
  case EACCES:
    strcpy(s, "Permission denied");
    break;
#endif
#ifdef EMFILE
  case EMFILE:
    strcpy(s, "Too many open files");
    break;
#endif
  case 0:
    strcpy(s, "Error 0");
    break;
  default:
    sprintf(s, "Unforseen error %d", errno);
    break;
  }
}

/* Sets/Unsets options for a specific socket.
 *
 * Returns:  0   - on success
 *           -1  - socket not found
 *           -2  - illegal operation
 */
int sockoptions(int sock, int operation, int sock_options)
{
  int i;

  for (i = 0; i < MAXSOCKS; i++)
    if ((socklist[i].sock == sock) && !(socklist[i].flags & SOCK_UNUSED)) {
      if (operation == EGG_OPTION_SET)
        socklist[i].flags |= sock_options;
      else if (operation == EGG_OPTION_UNSET)
        socklist[i].flags &= ~sock_options;
      else
        return -2;
      return 0;
    }
  return -1;
}

/* Return a free entry in the socket entry
 */
int allocsock(int sock, int options)
{
  int i;

  for (i = 0; i < MAXSOCKS; i++) {
    if (socklist[i].flags & SOCK_UNUSED) {
      /* yay!  there is table space */
      socklist[i].inbuf = socklist[i].outbuf = NULL;
      socklist[i].inbuflen = socklist[i].outbuflen = 0;
      socklist[i].flags = options;
      socklist[i].sock = sock;
      return i;
    }
  }
  fatal("Socket table is full!", 0);
  return -1;                    /* Never reached */
}

/* Request a normal socket for i/o
 */
void setsock(int sock, int options)
{
  int i = allocsock(sock, options), parm;

  if (((sock != STDOUT) || backgrd) && !(socklist[i].flags & SOCK_NONSOCK)) {
    parm = 1;
    setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *) &parm, sizeof(int));

    parm = 0;
    setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *) &parm, sizeof(int));
  }
  if (options & SOCK_LISTEN) {
    /* Tris says this lets us grab the same port again next time */
    parm = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *) &parm, sizeof(int));
  }
  /* Yay async i/o ! */
  fcntl(sock, F_SETFL, O_NONBLOCK);
}

int getsock(int options)
{
  int sock = socket(AF_INET, SOCK_STREAM, 0);

  if (sock >= 0)
    setsock(sock, options);
  else
    putlog(LOG_MISC, "*", "Warning: Can't create new socket!");
  return sock;
}

/* Done with a socket
 */
void killsock(register int sock)
{
  register int i;

  /* Ignore invalid sockets.  */
  if (sock < 0)
    return;

  for (i = 0; i < MAXSOCKS; i++) {
    if ((socklist[i].sock == sock) && !(socklist[i].flags & SOCK_UNUSED)) {
      close(socklist[i].sock);
      if (socklist[i].inbuf != NULL) {
        nfree(socklist[i].inbuf);
        socklist[i].inbuf = NULL;
      }
      if (socklist[i].outbuf != NULL) {
        nfree(socklist[i].outbuf);
        socklist[i].outbuf = NULL;
        socklist[i].outbuflen = 0;
      }
      socklist[i].flags = SOCK_UNUSED;
      return;
    }
  }
  putlog(LOG_MISC, "*", "Warning: Attempt to kill un-allocated socket %d!", sock);
}

/* Send connection request to proxy
 */
static int proxy_connect(int sock, char *host, int port, int proxy)
{
  unsigned char x[10];
  struct hostent *hp;
  char s[256];
  int i;

  /* socks proxy */
  if (proxy == PROXY_SOCKS) {
    /* numeric IP? */
    if (host[strlen(host) - 1] >= '0' && host[strlen(host) - 1] <= '9') {
      IP ip = ((IP) inet_addr(host));
      egg_memcpy(x, &ip, 4);
    } else {
      /* no, must be host.domain */
      if (!sigsetjmp(alarmret, 1)) {
        alarm(resolve_timeout);
        hp = gethostbyname(host);
        alarm(0);
      } else
        hp = NULL;
      if (hp == NULL) {
        killsock(sock);
        return -2;
      }
      egg_memcpy(x, hp->h_addr, hp->h_length);
    }
    for (i = 0; i < MAXSOCKS; i++)
      if (!(socklist[i].flags & SOCK_UNUSED) && socklist[i].sock == sock)
        socklist[i].flags |= SOCK_PROXYWAIT;    /* drummer */
      egg_snprintf(s, sizeof s, "\004\001%c%c%c%c%c%c%s", (port >> 8) % 256,
                   (port % 256), x[0], x[1], x[2], x[3], botuser);
    tputs(sock, s, strlen(botuser) + 9);        /* drummer */
  } else if (proxy == PROXY_SUN) {
    egg_snprintf(s, sizeof s, "%s %d\n", host, port);
    tputs(sock, s, strlen(s));  /* drummer */
  }
  return sock;
}

/* Starts a connection attempt to a socket
 *
 * If given a normal hostname, this will be resolved to the corresponding
 * IP address first. PLEASE try to use the non-blocking dns functions
 * instead and then call this function with the IP address to avoid blocking.
 *
 * returns <0 if connection refused:
 *   -1  neterror() type error
 *   -2  can't resolve hostname
 */
int open_telnet_raw(int sock, char *server, int sport)
{
  struct sockaddr_in name;
  struct hostent *hp;
  char host[121];
  int i, port, rc;
  volatile int proxy;

  /* firewall?  use socks */
  if (firewall[0]) {
    if (firewall[0] == '!') {
      proxy = PROXY_SUN;
      strcpy(host, &firewall[1]);
    } else {
      proxy = PROXY_SOCKS;
      strcpy(host, firewall);
    }
    port = firewallport;
  } else {
    proxy = 0;
    strcpy(host, server);
    port = sport;
  }
  egg_bzero((char *) &name, sizeof(struct sockaddr_in));

  name.sin_family = AF_INET;
  name.sin_addr.s_addr = (myip[0] ? getmyip() : INADDR_ANY);
  if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) {
    killsock(sock);
    return -1;
  }
  egg_bzero((char *) &name, sizeof(struct sockaddr_in));

  name.sin_family = AF_INET;
  name.sin_port = htons(port);
  /* Numeric IP? */
  if ((host[strlen(host) - 1] >= '0') && (host[strlen(host) - 1] <= '9'))
    name.sin_addr.s_addr = inet_addr(host);
  else {
    /* No, must be host.domain */
    debug0("WARNING: open_telnet_raw() is about to block in gethostbyname()!");
    if (!sigsetjmp(alarmret, 1)) {
      alarm(resolve_timeout);
      hp = gethostbyname(host);
      alarm(0);
    } else
      hp = NULL;
    if (hp == NULL)
      return -2;
    egg_memcpy(&name.sin_addr, hp->h_addr, hp->h_length);
    name.sin_family = hp->h_addrtype;
  }
  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & SOCK_UNUSED) && (socklist[i].sock == sock))
      socklist[i].flags = (socklist[i].flags & ~SOCK_VIRTUAL) | SOCK_CONNECT;
  }
  rc = connect(sock, (struct sockaddr *) &name, sizeof(struct sockaddr_in));
  if (rc < 0) {
    if (errno == EINPROGRESS) {
      /* Firewall?  announce connect attempt to proxy */
      if (firewall[0])
        return proxy_connect(sock, server, sport, proxy);
      return sock; /* async success! */
    } else
      return -1;
  }
  /* Synchronous? :/ */
  if (firewall[0])
    return proxy_connect(sock, server, sport, proxy);
  return sock;
}

/* Ordinary non-binary connection attempt */
int open_telnet(char *server, int port)
{
  int sock = getsock(0), ret = open_telnet_raw(sock, server, port);

  return ret;
}

/* Returns a socket number for a listening socket that will accept any
 * connection on a certain address -- port # is returned in port
 */
int open_address_listen(IP addr, int *port)
 {
  int sock = 0;
  socklen_t addrlen;
  struct sockaddr_in name;

  if (firewall[0]) {
    /* FIXME: can't do listen port thru firewall yet */
    putlog(LOG_MISC, "*", "Can't open a listen port (you are using a "
           "firewall).");
    return -1;
  }

  if (getmyip() > 0) {
    sock = getsock(SOCK_LISTEN);
    if (sock < 1)
      return -1;

    egg_bzero((char *) &name, sizeof(struct sockaddr_in));
    name.sin_family = AF_INET;
    name.sin_port = htons(*port); /* 0 = just assign us a port */
    name.sin_addr.s_addr = addr;
    if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) {
      killsock(sock);
      return -1;
    }
    /* what port are we on? */
    addrlen = sizeof(name);
    if (getsockname(sock, (struct sockaddr *) &name, &addrlen) < 0) {
      killsock(sock);
      return -1;
    }
    *port = ntohs(name.sin_port);
    if (listen(sock, 1) < 0) {
      killsock(sock);
      return -1;
    }
  }

  return sock;
}

/* Returns a socket number for a listening socket that will accept any
 * connection -- port # is returned in port
 */
inline int open_listen(int *port)
{
  return open_address_listen(myip[0] ? getmyip() : INADDR_ANY, port);
}

/* Returns the given network byte order IP address in the
 * dotted format - "##.##.##.##"
 */
char *iptostr(IP ip)
{
  struct in_addr a;

  a.s_addr = ip;
  return inet_ntoa(a);
}

/* Short routine to answer a connect received on a socket made previously
 * by open_listen ... returns hostname of the caller & the new socket
 * does NOT dispose of old "public" socket!
 */
int answer(int sock, char *caller, unsigned long *ip, unsigned short *port,
           int binary)
{
  int new_sock;
  socklen_t addrlen;
  struct sockaddr_in from;

  addrlen = sizeof(struct sockaddr);
  new_sock = accept(sock, (struct sockaddr *) &from, &addrlen);

  if (new_sock < 0)
    return -1;
  if (ip != NULL) {
    *ip = from.sin_addr.s_addr;
    /* DNS is now done asynchronously. We now only provide the IP address. */
    strncpyz(caller, iptostr(*ip), 121);
    *ip = ntohl(*ip);
  }
  if (port != NULL)
    *port = ntohs(from.sin_port);
  /* Set up all the normal socket crap */
  setsock(new_sock, (binary ? SOCK_BINARY : 0));
  return new_sock;
}

/* Like open_telnet, but uses server & port specifications of dcc
 */
int open_telnet_dcc(int sock, char *server, char *port)
{
  int p;
  unsigned long addr;
  char sv[500];
  unsigned char c[4];

  if (port != NULL)
    p = atoi(port);
  else
    p = 2000;
  if (server != NULL)
    addr = my_atoul(server);
  else
    addr = 0L;
  if (addr < (1 << 24))
    return -3;                  /* fake address */
  c[0] = (addr >> 24) & 0xff;
  c[1] = (addr >> 16) & 0xff;
  c[2] = (addr >> 8) & 0xff;
  c[3] = addr & 0xff;
  sprintf(sv, "%u.%u.%u.%u", c[0], c[1], c[2], c[3]);
  p = open_telnet_raw(sock, sv, p);
  return p;
}

/* Attempts to read from all the sockets in socklist
 * fills s with up to 511 bytes if available, and returns the array index
 *
 *              on EOF:  returns -1, with socket in len
 *     on socket error:  returns -2
 * if nothing is ready:  returns -3
 */
static int sockread(char *s, int *len)
{
  fd_set fd;
  int fds, i, x, fdtmp;
  struct timeval t;
  int grab = 511;

  fds = getdtablesize();
#ifdef FD_SETSIZE
  if (fds > FD_SETSIZE)
    fds = FD_SETSIZE;           /* Fixes YET ANOTHER freebsd bug!!! */
#endif
  /* timeout: 1 sec */
  t.tv_sec = 1;
  t.tv_usec = 0;
  FD_ZERO(&fd);

  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & (SOCK_UNUSED | SOCK_VIRTUAL))) {
      if ((socklist[i].sock == STDOUT) && !backgrd)
        fdtmp = STDIN;
      else
        fdtmp = socklist[i].sock;
      /*
       * Looks like that having more than a call, in the same
       * program, to the FD_SET macro, triggers a bug in gcc.
       * SIGBUS crashing binaries used to be produced on a number
       * (prolly all?) of 64 bits architectures.
       * Make your best to avoid to make it happen again.
       *
       * ITE
       */
      FD_SET(fdtmp, &fd);
    }
  }
  x = select((SELECT_TYPE_ARG1) fds, SELECT_TYPE_ARG234 &fd,
             SELECT_TYPE_ARG234 NULL, SELECT_TYPE_ARG234 NULL,
             SELECT_TYPE_ARG5 &t);
  if (x > 0) {
    /* Something happened */
    for (i = 0; i < MAXSOCKS; i++) {
      if ((!(socklist[i].flags & SOCK_UNUSED)) &&
          ((FD_ISSET(socklist[i].sock, &fd)) ||
          ((socklist[i].sock == STDOUT) && (!backgrd) &&
          (FD_ISSET(STDIN, &fd))))) {
        if (socklist[i].flags & (SOCK_LISTEN | SOCK_CONNECT)) {
          /* Listening socket -- don't read, just return activity */
          /* Same for connection attempt */
          /* (for strong connections, require a read to succeed first) */
          if (socklist[i].flags & SOCK_PROXYWAIT) /* drummer */
            /* Hang around to get the return code from proxy */
            grab = 10;
          else if (!(socklist[i].flags & SOCK_STRONGCONN)) {
            debug1("net: connect! sock %d", socklist[i].sock);
            s[0] = 0;
            *len = 0;
            return i;
          }
        } else if (socklist[i].flags & SOCK_PASS) {
          s[0] = 0;
          *len = 0;
          return i;
        }
        errno = 0;
        if ((socklist[i].sock == STDOUT) && !backgrd)
          x = read(STDIN, s, grab);
        else
          x = read(socklist[i].sock, s, grab);
        if (x <= 0) {           /* eof */
          if (errno != EAGAIN) { /* EAGAIN happens when the operation would
                                  * block on a non-blocking socket, if the
                                  * socket is going to die, it will die later,
                                  * otherwise it will connect. */
            *len = socklist[i].sock;
            socklist[i].flags &= ~SOCK_CONNECT;
            debug1("net: eof!(read) socket %d", socklist[i].sock);
            return -1;
          } else {
            debug3("sockread EAGAIN: %d %d (%s)", socklist[i].sock, errno,
                   strerror(errno));
            continue;           /* EAGAIN */
          }
        }
        s[x] = 0;
        *len = x;
        if (socklist[i].flags & SOCK_PROXYWAIT) {
          debug2("net: socket: %d proxy errno: %d", socklist[i].sock, s[1]);
          socklist[i].flags &= ~(SOCK_CONNECT | SOCK_PROXYWAIT);
          switch (s[1]) {
          case 90:             /* Success */
            s[0] = 0;
            *len = 0;
            return i;
          case 91:             /* Failed */
            errno = ECONNREFUSED;
            break;
          case 92:             /* No identd */
          case 93:             /* Identd said wrong username */
            /* A better error message would be "socks misconfigured"
             * or "identd not working" but this is simplest.
             */
            errno = ENETUNREACH;
            break;
          }
          *len = socklist[i].sock;
          return -1;
        }
        return i;
      }
    }
  } else if (x == -1)
    return -2;                  /* socket error */
  else {
    s[0] = 0;
    *len = 0;
  }
  return -3;
}

/* sockgets: buffer and read from sockets
 *
 * Attempts to read from all registered sockets for up to one second.  if
 * after one second, no complete data has been received from any of the
 * sockets, 's' will be empty, 'len' will be 0, and sockgets will return -3.
 * if there is returnable data received from a socket, the data will be
 * in 's' (null-terminated if non-binary), the length will be returned
 * in len, and the socket number will be returned.
 * normal sockets have their input buffered, and each call to sockgets
 * will return one line terminated with a '\n'.  binary sockets are not
 * buffered and return whatever coems in as soon as it arrives.
 * listening sockets will return an empty string when a connection comes in.
 * connecting sockets will return an empty string on a successful connect,
 * or EOF on a failed connect.
 * if an EOF is detected from any of the sockets, that socket number will be
 * put in len, and -1 will be returned.
 * the maximum length of the string returned is 512 (including null)
 *
 * Returns -4 if we handled something that shouldn't be handled by the
 * dcc functions. Simply ignore it.
 */

int sockgets(char *s, int *len)
{
  char xx[514], *p, *px;
  int ret, i, data = 0;

  for (i = 0; i < MAXSOCKS; i++) {
    /* Check for stored-up data waiting to be processed */
    if (!(socklist[i].flags & SOCK_UNUSED) &&
        !(socklist[i].flags & SOCK_BUFFER) && (socklist[i].inbuf != NULL)) {
      if (!(socklist[i].flags & SOCK_BINARY)) {
        /* look for \r too cos windows can't follow RFCs */
        p = strchr(socklist[i].inbuf, '\n');
        if (p == NULL)
          p = strchr(socklist[i].inbuf, '\r');
        if (p != NULL) {
          *p = 0;
          if (strlen(socklist[i].inbuf) > 510)
            socklist[i].inbuf[510] = 0;
          strcpy(s, socklist[i].inbuf);
          px = nmalloc(strlen(p + 1) + 1);
          strcpy(px, p + 1);
          nfree(socklist[i].inbuf);
          if (px[0])
            socklist[i].inbuf = px;
          else {
            nfree(px);
            socklist[i].inbuf = NULL;
          }
          /* Strip CR if this was CR/LF combo */
          if (s[strlen(s) - 1] == '\r')
            s[strlen(s) - 1] = 0;
          *len = strlen(s);
          return socklist[i].sock;
        }
      } else {
        /* Handling buffered binary data (must have been SOCK_BUFFER before). */
        if (socklist[i].inbuflen <= 510) {
          *len = socklist[i].inbuflen;
          egg_memcpy(s, socklist[i].inbuf, socklist[i].inbuflen);
          nfree(socklist[i].inbuf);
          socklist[i].inbuf = NULL;
          socklist[i].inbuflen = 0;
        } else {
          /* Split up into chunks of 510 bytes. */
          *len = 510;
          egg_memcpy(s, socklist[i].inbuf, *len);
          egg_memcpy(socklist[i].inbuf, socklist[i].inbuf + *len, *len);
          socklist[i].inbuflen -= *len;
          socklist[i].inbuf = nrealloc(socklist[i].inbuf, socklist[i].inbuflen);
        }
        return socklist[i].sock;
      }
    }
    /* Also check any sockets that might have EOF'd during write */
    if (!(socklist[i].flags & SOCK_UNUSED) &&
        (socklist[i].flags & SOCK_EOFD)) {
      s[0] = 0;
      *len = socklist[i].sock;
      return -1;
    }
  }
  /* No pent-up data of any worth -- down to business */
  *len = 0;
  ret = sockread(xx, len);
  if (ret < 0) {
    s[0] = 0;
    return ret;
  }
  /* Binary, listening and passed on sockets don't get buffered. */
  if (socklist[ret].flags & SOCK_CONNECT) {
    if (socklist[ret].flags & SOCK_STRONGCONN) {
      socklist[ret].flags &= ~SOCK_STRONGCONN;
      /* Buffer any data that came in, for future read. */
      socklist[ret].inbuflen = *len;
      socklist[ret].inbuf = nmalloc(*len + 1);
      /* It might be binary data. You never know. */
      egg_memcpy(socklist[ret].inbuf, xx, *len);
      socklist[ret].inbuf[*len] = 0;
    }
    socklist[ret].flags &= ~SOCK_CONNECT;
    s[0] = 0;
    return socklist[ret].sock;
  }
  if (socklist[ret].flags & SOCK_BINARY) {
    egg_memcpy(s, xx, *len);
    return socklist[ret].sock;
  }
  if ((socklist[ret].flags & SOCK_LISTEN) || (socklist[ret].flags & SOCK_PASS))
    return socklist[ret].sock;
  if (socklist[ret].flags & SOCK_BUFFER) {
    socklist[ret].inbuf = (char *) nrealloc(socklist[ret].inbuf,
                                            socklist[ret].inbuflen + *len + 1);
    egg_memcpy(socklist[ret].inbuf + socklist[ret].inbuflen, xx, *len);
    socklist[ret].inbuflen += *len;
    /* We don't know whether it's binary data. Make sure normal strings
     * will be handled properly later on too. */
    socklist[ret].inbuf[socklist[ret].inbuflen] = 0;
    return -4;                  /* Ignore this one. */
  }
  /* Might be necessary to prepend stored-up data! */
  if (socklist[ret].inbuf != NULL) {
    p = socklist[ret].inbuf;
    socklist[ret].inbuf = nmalloc(strlen(p) + strlen(xx) + 1);
    strcpy(socklist[ret].inbuf, p);
    strcat(socklist[ret].inbuf, xx);
    nfree(p);
    if (strlen(socklist[ret].inbuf) < 512) {
      strcpy(xx, socklist[ret].inbuf);
      nfree(socklist[ret].inbuf);
      socklist[ret].inbuf = NULL;
      socklist[ret].inbuflen = 0;
    } else {
      p = socklist[ret].inbuf;
      socklist[ret].inbuflen = strlen(p) - 510;
      socklist[ret].inbuf = nmalloc(socklist[ret].inbuflen + 1);
      strcpy(socklist[ret].inbuf, p + 510);
      *(p + 510) = 0;
      strcpy(xx, p);
      nfree(p);
      /* (leave the rest to be post-pended later) */
    }
  }
  /* Look for EOL marker; if it's there, i have something to show */
  p = strchr(xx, '\n');
  if (p == NULL)
    p = strchr(xx, '\r');
  if (p != NULL) {
    *p = 0;
    strcpy(s, xx);
    strcpy(xx, p + 1);
    if (s[strlen(s) - 1] == '\r')
      s[strlen(s) - 1] = 0;
    data = 1; /* DCC_CHAT may now need to process a blank line */
/* NO! */
/* if (!s[0]) strcpy(s," ");  */
  } else {
    s[0] = 0;
    if (strlen(xx) >= 510) {
      /* String is too long, so just insert fake \n */
      strcpy(s, xx);
      xx[0] = 0;
      data = 1;
    }
  }
  *len = strlen(s);
  /* Anything left that needs to be saved? */
  if (!xx[0]) {
    if (data)
      return socklist[ret].sock;
    else
      return -3;
  }
  /* Prepend old data back */
  if (socklist[ret].inbuf != NULL) {
    p = socklist[ret].inbuf;
    socklist[ret].inbuflen = strlen(p) + strlen(xx);
    socklist[ret].inbuf = nmalloc(socklist[ret].inbuflen + 1);
    strcpy(socklist[ret].inbuf, xx);
    strcat(socklist[ret].inbuf, p);
    nfree(p);
  } else {
    socklist[ret].inbuflen = strlen(xx);
    socklist[ret].inbuf = nmalloc(socklist[ret].inbuflen + 1);
    strcpy(socklist[ret].inbuf, xx);
  }
  if (data)
    return socklist[ret].sock;
  else
    return -3;
}

/* Dump something to a socket
 *
 * NOTE: Do NOT put Contexts in here if you want DEBUG to be meaningful!!
 */
void tputs(register int z, char *s, unsigned int len)
{
  register int i, x, idx;
  char *p;
  static int inhere = 0;

  if (z < 0) /* um... HELLO?! sanity check please! */
    return;

  if (((z == STDOUT) || (z == STDERR)) && (!backgrd || use_stderr)) {
    write(z, s, len);
    return;
  }

  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & SOCK_UNUSED) && (socklist[i].sock == z)) {
      for (idx = 0; idx < dcc_total; idx++) {
        if ((dcc[idx].sock == z) && dcc[idx].type && dcc[idx].type->name) {
          if (!strncmp(dcc[idx].type->name, "BOT", 3))
            otraffic_bn_today += len;
          else if (!strcmp(dcc[idx].type->name, "SERVER"))
            otraffic_irc_today += len;
          else if (!strncmp(dcc[idx].type->name, "CHAT", 4))
            otraffic_dcc_today += len;
          else if (!strncmp(dcc[idx].type->name, "FILES", 5))
            otraffic_filesys_today += len;
          else if (!strcmp(dcc[idx].type->name, "SEND"))
            otraffic_trans_today += len;
          else if (!strncmp(dcc[idx].type->name, "GET", 3))
            otraffic_trans_today += len;
          else
            otraffic_unknown_today += len;
          break;
        }
      }

      if (socklist[i].outbuf != NULL) {
        /* Already queueing: just add it */
        p = (char *) nrealloc(socklist[i].outbuf, socklist[i].outbuflen + len);
        egg_memcpy(p + socklist[i].outbuflen, s, len);
        socklist[i].outbuf = p;
        socklist[i].outbuflen += len;
        return;
      }
      /* Try. */
      x = write(z, s, len);
      if (x == -1)
        x = 0;
      if (x < len) {
        /* Socket is full, queue it */
        socklist[i].outbuf = nmalloc(len - x);
        egg_memcpy(socklist[i].outbuf, &s[x], len - x);
        socklist[i].outbuflen = len - x;
      }
      return;
    }
  }
  /* Make sure we don't cause a crash by looping here */
  if (!inhere) {
    inhere = 1;

    putlog(LOG_MISC, "*", "!!! writing to nonexistent socket: %d", z);
    s[strlen(s) - 1] = 0;
    putlog(LOG_MISC, "*", "!-> '%s'", s);

    inhere = 0;
  }
}

/* tputs might queue data for sockets, let's dump as much of it as
 * possible.
 */
void dequeue_sockets()
{
  int i, x;

  int z = 0, fds;
  fd_set wfds;
  struct timeval tv;

/* ^-- start poptix test code, this should avoid writes to sockets not ready to be written to. */
  fds = getdtablesize();

#ifdef FD_SETSIZE
  if (fds > FD_SETSIZE)
    fds = FD_SETSIZE;           /* Fixes YET ANOTHER freebsd bug!!! */
#endif
  FD_ZERO(&wfds);
  tv.tv_sec = 0;
  tv.tv_usec = 0;               /* we only want to see if it's ready for writing, no need to actually wait.. */
  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & SOCK_UNUSED) && socklist[i].outbuf != NULL) {
      FD_SET(socklist[i].sock, &wfds);
      z = 1;
    }
  }
  if (!z)
    return;                     /* nothing to write */

  select((SELECT_TYPE_ARG1) fds, SELECT_TYPE_ARG234 NULL,
         SELECT_TYPE_ARG234 &wfds, SELECT_TYPE_ARG234 NULL,
         SELECT_TYPE_ARG5 &tv);

/* end poptix */

  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & SOCK_UNUSED) &&
        (socklist[i].outbuf != NULL) && (FD_ISSET(socklist[i].sock, &wfds))) {
      /* Trick tputs into doing the work */
      errno = 0;
      x = write(socklist[i].sock, socklist[i].outbuf, socklist[i].outbuflen);
      if ((x < 0) && (errno != EAGAIN)
#ifdef EBADSLT
          && (errno != EBADSLT)
#endif
#ifdef ENOTCONN
          && (errno != ENOTCONN)
#endif
        ) {
        /* This detects an EOF during writing */
        debug3("net: eof!(write) socket %d (%s,%d)", socklist[i].sock,
               strerror(errno), errno);
        socklist[i].flags |= SOCK_EOFD;
      } else if (x == socklist[i].outbuflen) {
        /* If the whole buffer was sent, nuke it */
        nfree(socklist[i].outbuf);
        socklist[i].outbuf = NULL;
        socklist[i].outbuflen = 0;
      } else if (x > 0) {
        char *p = socklist[i].outbuf;

        /* This removes any sent bytes from the beginning of the buffer */
        socklist[i].outbuf = nmalloc(socklist[i].outbuflen - x);
        egg_memcpy(socklist[i].outbuf, p + x, socklist[i].outbuflen - x);
        socklist[i].outbuflen -= x;
        nfree(p);
      } else {
        debug3("dequeue_sockets(): errno = %d (%s) on %d", errno,
               strerror(errno), socklist[i].sock);
      }
      /* All queued data was sent. Call handler if one exists and the
       * dcc entry wants it.
       */
      if (!socklist[i].outbuf) {
        int idx = findanyidx(socklist[i].sock);

        if (idx > 0 && dcc[idx].type && dcc[idx].type->outdone)
          dcc[idx].type->outdone(idx);
      }
    }
  }
}


/*
 *      Debugging stuff
 */

void tell_netdebug(int idx)
{
  int i;
  char s[80];

  dprintf(idx, "Open sockets:");
  for (i = 0; i < MAXSOCKS; i++) {
    if (!(socklist[i].flags & SOCK_UNUSED)) {
      sprintf(s, " %d", socklist[i].sock);
      if (socklist[i].flags & SOCK_BINARY)
        strcat(s, " (binary)");
      if (socklist[i].flags & SOCK_LISTEN)
        strcat(s, " (listen)");
      if (socklist[i].flags & SOCK_PASS)
        strcat(s, " (passed on)");
      if (socklist[i].flags & SOCK_CONNECT)
        strcat(s, " (connecting)");
      if (socklist[i].flags & SOCK_STRONGCONN)
        strcat(s, " (strong)");
      if (socklist[i].flags & SOCK_NONSOCK)
        strcat(s, " (file)");
      if (socklist[i].inbuf != NULL)
        sprintf(&s[strlen(s)], " (inbuf: %04X)", strlen(socklist[i].inbuf));
      if (socklist[i].outbuf != NULL)
        sprintf(&s[strlen(s)], " (outbuf: %06lX)", socklist[i].outbuflen);
      strcat(s, ",");
      dprintf(idx, "%s", s);
    }
  }
  dprintf(idx, " done.\n");
}

/* Security-flavoured sanity checking on DCC connections of all sorts can be
 * done with this routine.  Feed it the proper information from your DCC
 * before you attempt the connection, and this will make an attempt at
 * figuring out if the connection is really that person, or someone screwing
 * around.  It's not foolproof, but anything that fails this check probably
 * isn't going to work anyway due to masquerading firewalls, NAT routers,
 * or bugs in mIRC.
 */
int sanitycheck_dcc(char *nick, char *from, char *ipaddy, char *port)
{
  /* According to the latest RFC, the clients SHOULD be able to handle
   * DNS names that are up to 255 characters long.  This is not broken.
   */

  char badaddress[16];
  IP ip = my_atoul(ipaddy);
  int prt = atoi(port);

  /* It is disabled HERE so we only have to check in *one* spot! */
  if (!dcc_sanitycheck)
    return 1;

  if (prt < 1) {
    putlog(LOG_MISC, "*", "ALERT: (%s!%s) specified an impossible port of %u!",
           nick, from, prt);
    return 0;
  }
  sprintf(badaddress, "%u.%u.%u.%u", (ip >> 24) & 0xff, (ip >> 16) & 0xff,
          (ip >> 8) & 0xff, ip & 0xff);
  if (ip < (1 << 24)) {
    putlog(LOG_MISC, "*", "ALERT: (%s!%s) specified an impossible IP of %s!",
           nick, from, badaddress);
    return 0;
  }
  return 1;
}

int hostsanitycheck_dcc(char *nick, char *from, IP ip, char *dnsname,
                        char *prt)
{
  /* According to the latest RFC, the clients SHOULD be able to handle
   * DNS names that are up to 255 characters long.  This is not broken.
   */
  char hostn[256], badaddress[16];

  /* It is disabled HERE so we only have to check in *one* spot! */
  if (!dcc_sanitycheck)
    return 1;
  sprintf(badaddress, "%u.%u.%u.%u", (ip >> 24) & 0xff, (ip >> 16) & 0xff,
          (ip >> 8) & 0xff, ip & 0xff);
  /* These should pad like crazy with zeros, since 120 bytes or so is
   * where the routines providing our data currently lose interest. I'm
   * using the n-variant in case someone changes that...
   */
  strncpyz(hostn, extracthostname(from), sizeof hostn);
  if (!egg_strcasecmp(hostn, dnsname)) {
    putlog(LOG_DEBUG, "*", "DNS information for submitted IP checks out.");
    return 1;
  }
  if (!strcmp(badaddress, dnsname))
    putlog(LOG_MISC, "*", "ALERT: (%s!%s) sent a DCC request with bogus IP "
           "information of %s port %s. %s does not resolve to %s!", nick, from,
           badaddress, prt, from, badaddress);
  else
    return 1;                   /* <- usually happens when we have
                                 * a user with an unresolved hostmask! */
  return 0;
}

/* Checks wether the referenced socket has data queued.
 *
 * Returns true if the incoming/outgoing (depending on 'type') queues
 * contain data, otherwise false.
 */
int sock_has_data(int type, int sock)
{
  int ret = 0, i;

  for (i = 0; i < MAXSOCKS; i++)
    if (!(socklist[i].flags & SOCK_UNUSED) && socklist[i].sock == sock)
      break;
  if (i < MAXSOCKS) {
    switch (type) {
    case SOCK_DATA_OUTGOING:
      ret = (socklist[i].outbuf != NULL);
      break;
    case SOCK_DATA_INCOMING:
      ret = (socklist[i].inbuf != NULL);
      break;
    }
  } else
    debug1("sock_has_data: could not find socket #%d, returning false.", sock);
  return ret;
}

/* flush_inbuf():
 * checks if there's data in the incoming buffer of an connection
 * and flushs the buffer if possible
 *
 * returns: -1 if the dcc entry wasn't found
 *          -2 if dcc[idx].type->activity doesn't exist and the data couldn't
 *             be handled
 *          0 if buffer was empty
 *          otherwise length of flushed buffer
 */
int flush_inbuf(int idx)
{
  int i, len;
  char *inbuf;

  Assert((idx >= 0) && (idx < dcc_total));
  for (i = 0; i < MAXSOCKS; i++) {
    if ((dcc[idx].sock == socklist[i].sock) &&
        !(socklist[i].flags & SOCK_UNUSED)) {
      len = socklist[i].inbuflen;
      if ((len > 0) && socklist[i].inbuf) {
        if (dcc[idx].type && dcc[idx].type->activity) {
          inbuf = socklist[i].inbuf;
          socklist[i].inbuf = NULL;
          dcc[idx].type->activity(idx, inbuf, len);
          nfree(inbuf);
          return len;
        } else
          return -2;
      } else
        return 0;
    }
  }
  return -1;
}
