/*
 * server.c: interactions with servers, tcp code, and DNS resolver
 *
 * Copyright(c) 1997,1998 - All Rights Reserved
 *
 * See the COPYRIGHT file.
 */

#ifndef lint
static char rcsid[] = "@(#)$Id: server.c,v 1.56 1998/08/31 14:52:29 kalt Exp $";
#endif

#include "os.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netdb.h>
#if HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif
#if HAVE_SYS_IOCTL_H
# include <sys/ioctl.h>
#endif
#include <sys/stat.h>
#include <fcntl.h>

/* BIND 8.x lossage */
#undef inet_ntoa
#undef inet_addr

#include "struct.h"
#include "term.h"
#include "window.h"
#include "option.h"
#include "config.h"
#include "utils.h"
#include "server.h"

extern char		need_collect;
extern struct window_	*current;

extern sic_format(char *, char *, char *, char *);

static struct server_	*slist = NULL;
struct server_		*server = NULL;

static int		dns_pid = -1, dns_pipe[2], dns_check = 0;

static void sic_connect(struct server_ *);

/* dns_init: start the slave DNS resolver. */
static void
dns_init()
{
  static int first = 1;
  int i;
  int smoke[2], ekoms[2];

  if (first)
    {
      signal(SIGPIPE, SIG_IGN);
      signal(SIGCHLD, SIG_IGN); /* to avoid zombies */
    }

  dns_check = 0;
  if (pipe(smoke) < 0)
    {
      term_end();
      perror("pipe: ");
      printf("Unable to start DNS resolver\n");
      exit(1);
    }
  if (pipe(ekoms) < 0)
    {
      term_end();
      perror("pipe: ");
      printf("Unable to start DNS resolver\n");
      exit(1);
    }
  dns_pipe[0] = smoke[0];
  dns_pipe[1] = ekoms[1];
  dns_pid = 0;

  switch (i = fork())
    {
  case -1:
      sic_slog(LOG_CLIENT, "--- fork() failed!");
      break;
  case 0:
      /* child */
      close(0);
      close(1);
      close(2);
      dup(ekoms[0]);
      dup(smoke[1]);
      close(smoke[0]); close(smoke[1]);
      close(ekoms[0]); close(ekoms[1]);
      execlp("./sdns", "./sdns (sic slave)", NULL);
      if (execlp("sdns", "sdns (sic slave)", NULL) < 0)
	  _exit(0);
      abort();
  default:
      /* parent */
      dns_pid = i;
      close(smoke[1]); close(ekoms[0]);
      break;
    }
}

/* dns_restart: to be called if the slave isn't running
 *	calls dns_init() if reasonnable to attempt to (re)start the slave
 *	if successful, it will retry outstanding server lookups.
 */
static void
dns_restart(what)
  char *what;
{
  static time_t restart_t = 0;
  static char loop = 0;

  if (loop)
      return;
  loop = 1;
  dns_pid = 0;
  if ((time(NULL) - restart_t) > 15)
    {
      dns_init();
      restart_t = time(NULL);
      if (write(dns_pipe[1], what, strlen(what)+1) < 0)
	  sic_slog(LOG_CLIENT,"--- Unable to restart slave resolver.");
      else
	{
	  struct server_ *stmp = slist;

	  sic_slog(LOG_CLIENT, "--- Restarted slave resolver.");
	  dns_check++;
	  while (stmp)
	    {
	      if (option(stmp->sopt, S_LOOKUP))
		  dns_lookup(stmp->sname);
	      stmp = stmp->nexts;
	    }
	}
    }
  loop = 0;
}

/* dns_lookup: performs async DNS lookup for the hostname/IP "what" */
void
dns_lookup(what)
  char *what;
{
  vsic_slog(LOG_DEBUG, "DNS: looking up \"%s\" (%d)", what, 1+dns_check);

  if (dns_pid == -1)
    {
      dns_init();
      if (dns_pid == 0)
	{
	  sic_slog(LOG_CLIENT, "--- Unable to start slave resolver!");
	  return;
	}
    }

  if (dns_pid > 0)
      /* slave DNS should be here */
      if (write(dns_pipe[1], what, strlen(what)+1) < 0)
	{
	  int ret = 0;
	  
	  vsic_slog(LOG_DEBUG, "DNS: write() failed: %s", strerror(errno));
	  if (dns_pid > 0 && (ret = kill(dns_pid, SIGKILL)))
	    {
	      vsic_slog(LOG_DEBUG, "DNS: kill() returned %d", ret);
	      if (ret == ESRCH)
		  sic_slog(LOG_CLIENT, "--- Slave resolver process is dead.");
	    }
	  else
	      sic_slog(LOG_CLIENT,
		       "--- Slave resolver process not responding!");
	  
	  vsic_slog(LOG_DEBUG, "DNS: kill()'ed %d", dns_pid);
	  dns_restart(what);
	}
      else
	  dns_check++;
  else
      dns_restart(what);
}

/* dns_parse: parse a reply from the slave DNS resolver and use it */
static void
dns_parse()
{
  char buf[4096], *wb = buf, *wp;
  int i, j = 0;

  i = read(dns_pipe[0], buf, 4096);
  while (j < i)
    {
      struct server_ *stmp = slist;
      int rc = 0;

      if ((wp = index(wb, '=')) == NULL)
	{
	  wp = index(wb, ':');
	  rc = -1;
	}
      assert(wp);
      *wp++ = '\0';
      vsic_slog(LOG_DEBUG, "DNS: answer \"%s\"", wb);
	  
      while (stmp)
	{
	  if (option(stmp->sopt, S_DNS)
	      && (!strcasecmp(stmp->sname, wb)
		  || !strcasecmp((stmp->ip) ? stmp->ip : "?" , wb)))
	      break;
	  stmp = stmp->nexts;
	}
      if (rc == 0)
	  if (stmp)
	      if (stmp->ip)
		{
		  free(stmp->sname);
		  stmp->sname = strdup(wp);
		}
	      else
		{
		  stmp->ip = strdup(wp);
		  unset_option(stmp->sopt, S_LOOKUP);
		  unset_option(stmp->sopt, S_DNS);
		  if (option(stmp->sopt, S_CONNECTING))
		    {
		      vsic_slog(LOG_CLIENT, "--- Connecting to %s %d [%s]",
				stmp->sname, stmp->port, stmp->ip);
		      sic_connect(stmp);
		    }
		}
	  else
	      vsic_slog(LOG_CLIENT, "--- DNS: %s is %s", wb, wp);
      else /* rc == -1 */
	{
	  rc = atoi(wp);

	  vsic_slog(LOG_CLIENT, "--- %s: \"%s\" %s",
		    (stmp) ?
		    (option(stmp->sopt, S_CONNECTING)) ? "Connect failed":
		    "Server lookup failed" : "DNS lookup",
		    wb, ((rc = atoi(wp)) == HOST_NOT_FOUND) ? "not found." :
		    (rc == TRY_AGAIN) ? "not found, try again." :
		    (rc == NO_DATA) ? "has no IP address." : wp);
	  if (stmp)
	    {
	      unset_option(stmp->sopt, S_LOOKUP);
	      unset_option(stmp->sopt, S_DNS);
	      if (option(stmp->sopt, S_CONNECTING))
		  unset_option(stmp->sopt, S_CONNECTING);
	    }
	}
      *(wp-1) = ' ';
      j += strlen(wb)+1;
      assert( buf[j-1] == '\0' );
      wb = buf+j;
      dns_check--;
    }
}

/* sic_connect: connect() to a server (given a struct server_ pointer) */
static void
sic_connect(srv)
  struct server_ *srv;
{
  struct  sockaddr_in	sock_sin;
  int			fd, opt = 1;
  char			*vif;

  assert (srv->ip);

  bzero((char *)&sock_sin, sizeof(sock_sin));
  sock_sin.sin_family = AF_INET;

  if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      vsic_slog(LOG_CLIENT, "--- socket() call failed: %s", strerror(errno));
  if (fd > 0 && (vif = getenv("SICIP")))
    {
      sock_sin.sin_addr.s_addr = inet_addr(vif);
      if (bind(fd, (struct sockaddr *) &sock_sin, sizeof(sock_sin)) < 0)
	  vsic_slog(LOG_CLIENT, "--- Unable to bind to IP %s: %s", vif,
		    strerror(errno));
    }
  if (fd > 0 && ioctl(fd, FIONBIO, &opt) < 0)
      vsic_slog(LOG_CLIENT, "--- ioctl() for %s:%d failed: %s", srv->sname,
		srv->port, strerror(errno));

  sock_sin.sin_port = htons(srv->port);
  sock_sin.sin_addr.s_addr = inet_addr(srv->ip);

  if (fd > 0 && connect(fd, (struct sockaddr *)&sock_sin, sizeof(sock_sin)) < 0
      && errno != EINPROGRESS)
    {
      vsic_slog(LOG_CLIENT, "--- Connection to %s:%d failed: %s", srv->sname,
		srv->port, strerror(errno));
      close(fd);
      fd = -1;
    }
  if (fd == -1)
    {
      unset_option(srv->sopt, S_CONNECTING);
      if (!option(srv->sopt, S_DCC)) /* needed? */
	  srv->ts = time(NULL);
    }
  else
    {
      set_option(srv->sopt, S_CONNECTING);
      
      if (!option(srv->sopt, S_CONFIG))
	  cfg_read(srv->sname);
      set_option(srv->sopt, S_CONFIG);
    }
  unset_option(srv->sopt, S_RECONNECT);
  unset_option(srv->sopt, S_QUIT);
  srv->fd = fd;
  srv->lastr = 0;
  srv->readbuf[0] = '\0';
}

/* sic_listen: create a new socket, and bind it */
static void
sic_listen(dcc)
  struct server_ *dcc;
{
  struct  sockaddr_in	sock_sin;
  int			fd, sz = sizeof(struct sockaddr_in);
  char			*vif;

  if (getsockname(server->fd, (struct sockaddr *)&sock_sin, &sz) < 0)
    {
      vsic_slog(LOG_CLIENT, "--- Unable to get my own IP: %s",strerror(errno));
      dcc->fd = -1;
      return;
    }
  else
      sz = sizeof(struct sockaddr_in);

  dcc->ip = strdup((char *)inet_ntoa(sock_sin.sin_addr));

  bzero((char *)&sock_sin, sizeof(sock_sin));
  sock_sin.sin_family = AF_INET;

  if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      vsic_slog(LOG_CLIENT, "--- socket() call failed: %s", strerror(errno));
  if (vif = getenv("SICIP"))
      sock_sin.sin_addr.s_addr = inet_addr(vif);
  else
      sock_sin.sin_addr.s_addr = INADDR_ANY;
  if (bind(fd, (struct sockaddr *) &sock_sin, sizeof(sock_sin)) < 0)
    {
      vsic_slog(LOG_CLIENT, "--- bind() call failed: %s", strerror(errno));
      close(fd);
      fd = -1;
    }
  if (fd > 0 && listen(fd, 1) < 0)
    {
      vsic_slog(LOG_CLIENT, "--- listen() call failed: %s", strerror(errno));
      close(fd);
      fd = -1;
    }
  if (fd > 0)
      if (getsockname(fd, (struct sockaddr *)&sock_sin, &sz) < 0)
	{
	  vsic_slog(LOG_CLIENT, "--- getsockname() call failed: %s",
		    strerror(errno));
	  free(dcc->ip);
	  close(fd);
	}
      else
	{
	  dcc->port = sock_sin.sin_port;
	  dcc->sname = strdup(server->sname);
	  set_option(dcc->sopt, S_LISTEN);
	}
  dcc->fd = fd;
}

/* select_server: set a different server */
struct server_ *
select_server(name)
  char *name;
{
  struct server_ *stmp = slist;

  assert(name);
  while (stmp && strcasecmp(stmp->sname, name))
      stmp = stmp->nexts;
  if (stmp && option(stmp->sopt, S_CONNECTED))
      return server = stmp;
  return NULL;
}

/* sic_server: adds a server to the list, does NOT connect */
struct server_ *
sic_server(nick, name, port, pass)
  char *nick, *name, *pass;
  int port;
{
  struct server_ **stmp = &slist;

  while (*stmp)
      stmp = &((*stmp)->nexts);

  server = (struct server_ *) malloc(sizeof(struct server_));
  bzero(server, sizeof(struct server_));

  *stmp = server;

  server->nick = strdup(nick);
  if (isdigit(*name))
      server->ip = strdup(name);
  server->sname = strdup(name);
  server->port = port;
  if (pass)
      server->pass = strdup(pass);

  if (server->ip == NULL)
    {
      dns_lookup(name);
      set_option(server->sopt, S_LOOKUP);
      set_option(server->sopt, S_DNS);
    }

  set_option(server->sopt, S_NOTCONNECTED);
  set_option(server->sopt, S_QUIT);
  server->fd = -1;
  return server;
}

/* sic_dserver: removes a server from the list */
void
sic_dserver(del)
  struct server_ *del;
{
  struct server_ **stmp = &slist;

  while (*stmp != del)
      stmp = &((*stmp)->nexts);
  /* pending DNS queries.. */
  /* bound windows/channels.. */
  *stmp = del->nexts;
  opt_free(&(del->custs));
  free(del->sname);
  if (del->ip)
      free(del->ip);
  if (del->pass)
      free(del->pass);
  free(del->nick);
  if (del->uname)
      free(del->uname);
  if (del->rname)
      free(del->rname);
  if (del->readf)
      fclose(del->readf);
  if (del->sendf)
      close(del->sendf);
  free(del);
}

static void
sic_write(str)
     char *str;
{
  if (server && server->fd >= 0)
    {
      if (write(server->fd, str, strlen(str)) == -1
	  && (errno == EAGAIN || errno == EWOULDBLOCK))
	  /* should be dealt with in a better way, not worth my time unless
	     it happens */
	  vsic_slog(LOG_CLIENT, "--- write() to server failed: %s",
		   strerror(errno));
      str[strlen(str)-1] = '\0'; /* hmmpf */
      sic_slog(LOG_OSNIF, str);
    }
  else
      sic_slog(LOG_CLIENT, "--- No server");
}

void
vsic_write(char *format, ...)
{
  char	buffer[1024];
  va_list va;

  va_start(va, format);
  vsprintf(buffer, format, va);
  va_end(va);
  strcat(buffer, "\n");
  sic_write(buffer);
}

static int
readln(fd, buffer, length)
     int fd, length;
     char *buffer;
{
  int status, ptr;

  ptr = -1;
  while (buffer[++ptr]);

  while (((status = read(fd, &buffer[ptr], 1)) > 0) && (ptr < length - 1))
    {
      if (buffer[ptr++] == '\n')
	  break;
    }

  if (status == -1 && (errno == EWOULDBLOCK || errno == EAGAIN))
    {
      /* incomplete line */
      buffer[ptr] = '\0';
      return -2;
    }

  if (status > 0)
    {
      if (buffer[ptr-2] == '\r')
	  buffer[ptr-2] = '\0';
      else
	  buffer[ptr-1] = '\0';
      sic_slog(LOG_ISNIF, buffer);
    }

  return (status);
}

/* this function is totally buggy, but it's simple and works most of the time*/
static void
parse(str)
     char *str;
{
  char		*sender, *cmd, *dest, *para;

  if (option(server->sopt, S_DCC))
    {
      char snder[267];
      
      sprintf(snder, "%s@%s", server->nick, server->sname);
      sic_format(snder, NULL, NULL, str);
      return;
    }

  if (*str == ':')
    {
      sender = ++str;
      while (*++str != ' ');
      *str++ = '\0';
    }
  else
      sender = server->sname;

  if (!strncmp(str, "ERROR ", 6))
    {
      cmd = "ERROR";
      dest = server->nick;
      para = str;
    }
  else
    {
      cmd = str;
      while (*++str != ' ');
      *str++ = '\0';
      if (*str == ':')
	{
	  vsic_slog(LOG_DEBUG, "dest begins with : for `%s %s %s'",sender,cmd,str);
	  dest = str+1;
	  para = "";
	}
      else
	{
	  dest = str;
	  while (*++str != ' ' && *str);
	  if (*str)
	    {
	      *str++ = '\0';
	      if (*str == ':')
		  para = str + 1;
	      else
		  para = str;
	    }
	  else
	      para = "";
	}
    }

  if (strcmp(cmd, "PONG"))
      sic_format(sender, cmd, dest, para);
}

/* sic_select: big fat io loop */
int
sic_select()
{
  fd_set	rfd, wfd, efd;
  int		n;
  struct server_ *stmp = slist;
  struct timeval timeout;
  char		buf[10240];

  FD_ZERO(&rfd); FD_ZERO(&wfd); FD_ZERO(&efd);
  timeout.tv_usec = 0; timeout.tv_sec = 45;

  FD_SET(0, &rfd);
  while (stmp)
    {
      server = stmp;
      if (option(stmp->sopt, S_NOTCONNECTED)
	  && !option(stmp->sopt, S_CONNECTING)
	  && !option(stmp->sopt, S_CONNECTED)
	  && !option(stmp->sopt, S_QUIT)
	  && !option(stmp->sopt, S_DCC)
	  && (time(NULL) - stmp->ts) >= 1800
	  && get_option(Z_RECONNECT, NULL))
	{
	  select_active(NULL, 0);
	  vsic_slog(LOG_CLIENT, "--- Attempting to reconnect to %s:%d",
		    stmp->sname, stmp->port);
	  set_option(stmp->sopt, S_RECONNECT);
	}
      if (option(stmp->sopt, S_RECONNECT) && (time(NULL) - stmp->ts) >= 5)
	{
	  select_active(NULL, 0);
	  sic_connect(stmp);
	}
      if (option(stmp->sopt, S_CONNECTED))
	{
	  if (!option(stmp->sopt, S_DCC))
	    {
	      server = stmp;
	      select_active(NULL, 0);
	      if (stmp->lastr && time(NULL) - stmp->lastr > 60)
		{
		  if ((time(NULL) - stmp->lastr) % 120 < 60)
		    {
		      if (!option(stmp->sopt, S_PING))
			{
			  vsic_write("PING .");
			  set_option(stmp->sopt, S_PING);
			}
		    }
		  else
		      unset_option(stmp->sopt, S_PING);
		}
	      if (need_collect)
		  collect_channel();
	    }
	  FD_SET(stmp->fd, &rfd);
	}
      if (option(stmp->sopt, S_SEND) && stmp->sent < stmp->size)
	  FD_SET(stmp->fd, &wfd);
      if (option(stmp->sopt, S_LISTEN))
	  FD_SET(stmp->fd, &rfd);
      if (option(stmp->sopt, S_CONNECTING) && !option(stmp->sopt, S_LOOKUP))
	  FD_SET(stmp->fd, &wfd);
      stmp = stmp->nexts;
    }
  if (dns_pid > 0 && dns_check)
      FD_SET(dns_pipe[0], &rfd);

  display_status();
  term_input(NULL, 0);
  term_flush();
  n = select(255, &rfd, &wfd, NULL, &timeout);

  stmp = slist;
  while (stmp)
    {
      /* first deal with pending DCCs */
      if (stmp->fd >= 0 && option(stmp->sopt, S_LISTEN))
	{
	  if (FD_ISSET(stmp->fd, &rfd))
	    {
	      struct sockaddr_in sock_sin;
	      int newfd, sz = sizeof(struct sockaddr_in);

	      server = stmp;
	      unset_option(stmp->sopt, S_LISTEN);
	      set_option(stmp->sopt, S_DCC);
	      select_active(NULL, 0);
	      if ((newfd=accept(stmp->fd,(struct sockaddr *)&sock_sin,&sz))<0)
		{
		  vsic_slog(LOG_CLIENT, "DCC accept() failed for %s@%s: %s",
			    stmp->nick, inet_ntoa(sock_sin.sin_addr),
			    strerror(errno));
		  stmp = stmp->nexts;
		  sic_dserver(server);
		}
	      else
		{
		  close(stmp->fd);
		  stmp->fd = newfd;
		  free(stmp->sname);
		  stmp->sname = strdup((char *)inet_ntoa(sock_sin.sin_addr));
		  set_option(stmp->sopt, S_CONNECTED);
		  vsic_slog(LOG_CLIENT,
			    "--- DCC connection received from %s@%s",
			    stmp->nick, stmp->sname);
		  dns_lookup(stmp->sname);
		  sz = 1;
		  if (ioctl(stmp->fd, FIONBIO, &sz) < 0)
		      vsic_slog(LOG_CLIENT, "--- ioctl() failed: %s",
				strerror(errno));
		  if (stmp->pass)
		      set_option(stmp->sopt, S_SEND);
		  stmp->ts = time(NULL);
		}
	    }
	}
      /* DCC sends */
      if (option(stmp->sopt, S_SEND))
	{
	  server = stmp;
          select_active(NULL, 0);
	  if (FD_ISSET(stmp->fd, &rfd))
	    {
	      u_32int_t total; /* DCC ack */
	      
	      if (recv(stmp->fd, (char *) &total, sizeof(u_32int_t),0) > 0)
		  stmp->read = ntohl(total);
	      else
		{
		  vsic_slog(LOG_CLIENT, "--- DCC \"%s\" to %s lost: %s",
			    stmp->pass, stmp->nick, strerror(errno));
		  vsic_slog(LOG_CLIENT, "--- Acked %lu out of %lu (%u%%).",
			    stmp->read, stmp->size,
			    (int)(100*stmp->read/stmp->size));
		  close(stmp->fd);
		  stmp = stmp->nexts;
		  sic_dserver(server);
		  continue;
		}
	      if (stmp->read == stmp->size)
		{
		  vsic_slog(LOG_CLIENT,
			    "--- DCC \"%s\" to %s completed in %s (%ub/s)",
			    stmp->pass, stmp->nick,
			    sic_tdiff(time(NULL)-stmp->ts, 0),
			    stmp->size/(1+time(NULL)-stmp->ts));
		  shutdown(stmp->fd, 2);
		  close(stmp->fd);
		  stmp = stmp->nexts;
		  sic_dserver(server);
		  continue;
		}
	    }
	  if (FD_ISSET(stmp->fd, &wfd) && stmp->sent - stmp->read < 10240)
	    {
	      char error = 0;
	      unsigned long rd, st = 0;

	      if (lseek(stmp->sendf, stmp->sent, SEEK_SET) < 0)
		  error = 1;
	      if ((rd = read(stmp->sendf, buf, 10240)) < 0)
		  error = 2;
	      if (rd > 0 && ((st = write(stmp->fd, buf, rd)) < 0))
		  error = 3;
	      if (error)
		{
		  if (error != 3)
		      vsic_slog(LOG_CLIENT, "--- %s() failed: %s!",
				(error == 1) ?"lseek":"read",strerror(errno));
		  vsic_slog(LOG_CLIENT, "--- DCC \"%s\" to %s lost.",
			    stmp->pass, stmp->nick);
		  vsic_slog(LOG_CLIENT, "--- Acked %lu out of %lu (%u%%).",
			    stmp->read, stmp->size,
			    (int)(100*stmp->read/stmp->size));
		  close(stmp->fd);
		  stmp = stmp->nexts;
		  sic_dserver(server);
		  continue;
		}
	      stmp->sent += st;
	    }
	  stmp = stmp->nexts;
	  continue;
	}
      /* DCC chat, DCC get, and normal server traffic */
      if (stmp->fd >= 0 && !option(stmp->sopt, S_LISTEN))
	{
	  server = stmp;
	  select_active(NULL, 0);	/* default window for this server */
	  if (FD_ISSET(stmp->fd, &rfd))
	    {
	      int sz;

	      if (option(stmp->sopt, S_DCC) && stmp->pass)
		  sz = read(stmp->fd, buf, 8192); /* big buffer for files */
	      else
		{
		  /*read up to \r\n otherwise*/
		  unset_option(stmp->sopt, S_PING);
		  sz = readln(stmp->fd, stmp->readbuf, 520);
		}
	      if (sz > 0)
		{
		  stmp->lastr = time(NULL);
		  if (option(stmp->sopt, S_NOTCONNECTED))
		    {
		      assert(!option(stmp->sopt, S_DCC));
		      unset_option(stmp->sopt, S_NOTCONNECTED);
		      sic_slog(LOG_CLIENT, "--- Connection established.");
		    }
		  if (option(stmp->sopt, S_DCC) && stmp->pass)
		    { /* file transfer */
		      if (stmp->readf == NULL)
			  stmp->readf = fopen(stmp->pass, "w");
		      if (stmp->readf == NULL)
			{
			  vsic_slog(LOG_CLIENT,
				    "--- Unable to create file \"%s\"!",
				    stmp->pass);
			  close(stmp->fd);
			  stmp = stmp->nexts;
			  sic_dserver(server); /* "continue;" below */
			}
		      else
			{
			  u_32int_t total; /* DCC ack */
			  
			  buf[sz] = '\0';
			  fprintf(stmp->readf, "%s", buf); /* ugly! */
			  total = htonl(stmp->read += sz);
			  send(stmp->fd, (char *) &total, sizeof(u_32int_t),0);
			  if (stmp->read == stmp->size) /* got all of it? */
			    {
			      stmp->size /= 1024;
			      vsic_slog(LOG_CLIENT,
			"--- DCC \"%s\" from %s completed in %s (%ub/s)",
					stmp->pass, stmp->nick,
					sic_tdiff(time(NULL)-stmp->ts, 0),
					stmp->size/(1+time(NULL)-stmp->ts));
			      shutdown(stmp->fd, 2);
			      close(stmp->fd);
			      stmp = stmp->nexts;
			      sic_dserver(server); /* "continue;" below */
			    }
			  else
			      stmp = stmp->nexts;
			}
		      continue;
		    }
		  else
		    {
		      /* readln() */
		      if (!option(stmp->sopt, S_DCC)
			  && !strncmp("PING ", stmp->readbuf, 5))
			  vsic_write("PONG %s :%s", server->nick,
				     (stmp->readbuf[4] && stmp->readbuf[5]) ?
				     stmp->readbuf+6 : "");
		      else
			{
			  /* current window for this server */
			  select_active(NULL, 1);
			  parse(stmp->readbuf);
			}
		      stmp->readbuf[0] = '\0';
		    }
		}
	      else if (sz == -2)
		{
		  vsic_slog(LOG_DEBUG, "Incomplete line from %s", stmp->sname);
		}
	      else
		{
		  close(stmp->fd);
		  stmp->fd = -1;
		  if (option(stmp->sopt, S_NOTCONNECTED))
		    {
		      /* this can happen when connecting locally */
		      assert(!option(stmp->sopt, S_DCC));
		      vsic_slog(LOG_CLIENT,
				"--- Connection to %s %d [%s] failed.",
				stmp->sname, stmp->port, stmp->ip);
		    }
		  set_option(stmp->sopt, S_NOTCONNECTED);
		  unset_option(stmp->sopt, S_CONNECTED);
		  if ((time(NULL) - stmp->ts) > 60
		      && !option(stmp->sopt, S_DCC)
		      && !option(stmp->sopt, S_QUIT)
		      && get_option(Z_RECONNECT, NULL))
		      set_option(stmp->sopt, S_RECONNECT);
		  stmp->ts = time(NULL);
		  if (option(stmp->sopt, S_DCC))
		    {
		      vsic_slog(LOG_CLIENT, "--- DCC to %s@%s lost.",
				stmp->nick, stmp->sname);
		      if (stmp->pass)
			{
			  vsic_slog(LOG_CLIENT,
				    "--- Got %lu out of %lu (%u%% of %s).",
				    stmp->read, stmp->size,
				    (int)(100*stmp->read/stmp->size),
				    stmp->pass);
			  stmp = stmp->nexts;
			  sic_dserver(server);
			}
		    }
		  else
		      vsic_slog(LOG_CLIENT,
				"--- Lost connection to %s:%d : %s",
				stmp->sname, stmp->port,
				option(stmp->sopt, S_RECONNECT) ?
				"Reconnecting..." :
		option(stmp->sopt, S_QUIT) || !get_option(Z_RECONNECT, NULL) ?
				"No reconnect.":"Will attempt to reconnect.");
		  continue;
		}
	    }
	  /* connection establishment */
	  if (FD_ISSET(stmp->fd, &wfd))
	    {
	      char *wp, buffer[512];
	      
	      unset_option(stmp->sopt, S_CONNECTING);

	      if (!option(stmp->sopt, S_DCC))
		{
		  server->umode[0] = '\0';
		  sprintf(buffer, "%s%s%sNICK %s\nUSER %s myhost 0 :%s\n",
			  (server->pass) ? "PASS " : "",
			  (server->pass) ? server->pass : "",
			  (server->pass) ? "\n" : "",
			  stmp->nick,
			  (stmp->uname) ? stmp->uname :
			  	(wp = getenv("SICUSER")) ? wp :
			  		(wp = getenv("USER")) ? wp : "sicuser",
			  (stmp->rname) ? stmp->rname :
			  	(wp = getenv("SICNAME")) ? wp :"wasting time");
		  if (write(stmp->fd, buffer, strlen(buffer)) == -1)
		    {
		      close(stmp->fd);
		      stmp->fd = -1;
		      stmp->ts = time(NULL);
		      set_option(stmp->sopt, S_NOTCONNECTED);
		      sic_slog(LOG_CLIENT, "--- Connection failed.");
		    }
		  else
		    {
		      del_member(NULL, NULL);
		      set_option(stmp->sopt, S_CONNECTED);
		    }
		}
	      else
		{
		  vsic_slog(LOG_CLIENT, "--- DCC to %s@%s established.",
			   stmp->nick, stmp->sname);
		  set_option(stmp->sopt, S_CONNECTED);
		  unset_option(stmp->sopt, S_NOTCONNECTED);
		}
	    }
	}
      stmp = stmp->nexts;
    }
  
  if (dns_pid > 0 && FD_ISSET(dns_pipe[0], &rfd))
    {
      select_active(NULL, 2);
      dns_parse();
    }

  if (FD_ISSET(0, &rfd))
    return 1;
  else
    return 0;
}

int
cmd_server(p)
  char *p;
{
  struct server_ *stmp = slist;
  int n = 0;

  if (p == NULL)
    {
      if (server == NULL)
	  return 0;
      if (option(server->sopt, S_CONNECTING)
	  || option(server->sopt, S_CONNECTED))
	{
	  if (option(server->sopt, S_QUIT))
	    {
	      if (server->fd >= 0)
		  close(server->fd);
	      server->fd = -1;
	      set_option(server->sopt, S_NOTCONNECTED);
	      unset_option(server->sopt, S_CONNECTING);
	      unset_option(server->sopt, S_CONNECTED);
	      vsic_slog(LOG_CLIENT, "--- Closed connection to %s:%d",
			server->sname, server->port);
	    }
	}
      unset_option(server->sopt, S_RECONNECT);
      set_option(server->sopt, S_QUIT);
      return 0;
    }
  if (!*p)
    {
      sic_slog(LOG_CLIENT, "--- Server list:");
      while (stmp)
	{
	  if (!option(stmp->sopt, S_DCC) && !option(stmp->sopt, S_LISTEN))
	      vsic_slog(LOG_CLIENT, "---   %2d: %s %d %s(%s%s)%s",
			n++, stmp->sname, stmp->port,
			(stmp->pass) ? "*password* " : "",
			(option(stmp->sopt, S_CONNECTING)) ? "will be " :
			(option(stmp->sopt, S_CONNECTED)) ? "" : "was ",
			stmp->nick, (stmp->ip) ? "" : " Unknown IP address");
	  stmp = stmp->nexts;
	}
    }
  else
      if (isdigit(*p) && !index(p, '.'))
	{
	  if (current->via && (option(current->via->sopt, S_CONNECTED)
			       || option(current->via->sopt, S_CONNECTING)))
	    {
	      sic_slog(LOG_CLIENT, "--- This window is already active.");
	      return 0;
	    }
	  while (stmp)
	    {
	      if (!option(stmp->sopt, S_DCC) && !option(stmp->sopt, S_LISTEN)
		  && n++ == atoi(p))
		  break;
	      stmp = stmp->nexts;
	    }
	  if (stmp)
	    {
	      server = stmp;
	      if (stmp->ip || option(stmp->sopt, S_LOOKUP))
		{
		  if (sic_swin(1))
		    {
		      if (option(stmp->sopt, S_CONNECTED))
			{
			  /*
			  vsic_slog(LOG_CLIENT,
				    "--- Window now bound to %s %d [%s]",
				    stmp->sname, stmp->port, stmp->ip);
			  */
			}
		      else
			{
			  set_option(stmp->sopt, S_CONNECTING);
			  if (!option(stmp->sopt, S_LOOKUP))
			    {
			      vsic_slog(LOG_CLIENT,
					"--- Connecting to %s %d [%s]",
					stmp->sname, stmp->port, stmp->ip);
			      sic_connect(stmp);
			    }
			}
		    }
		  /*
		  else
		      sic_slog(LOG_CLIENT, "--- No available window.");
		  */
		}
	      else
		  vsic_slog(LOG_CLIENT, "--- Unknown IP for %s", stmp->sname);
	    }
	  else
	      sic_slog(LOG_CLIENT, "--- No such server.");
	}
      else
	{
	  /* format: /server name port pass */
	  char *port, *pass = NULL;
	  int portn = 6667;

	  if (port = index(p, ' '))
	    {
	      *port++ = '\0';
	      if (pass = index(port, ' '))
		  *pass++ = '\0';
	      portn = atoi(port);
	    }
	  sic_server((current->via) ? current->via->nick : "sic",
		     p, portn, pass);
	  cmd_server("");
	}
  return 0;
}

/*
 * DCC code
 */

static void
sic_dwrite(nick, str)
     char *nick, *str;
{
  struct server_ *dtmp = slist;

  while (dtmp && (!option(dtmp->sopt, S_DCC) || strcasecmp(dtmp->nick, nick)))
      dtmp = dtmp->nexts;
  if (dtmp)
      if (dtmp->fd >= 0)
	{
	  if (write(dtmp->fd, str, strlen(str)) == -1
	      && (errno == EAGAIN || errno == EWOULDBLOCK))
	      /* should be dealt with in a better way, not worth my time unless
		 it happens */
	      vsic_slog(LOG_CLIENT, "--- write() to DCC failed: %s",
		       strerror(errno));
	  str[strlen(str)-1] = '\0'; /* hmmpf */
	  sic_slog(LOG_OSNIF, str);
	}
      else
	  sic_slog(LOG_CLIENT, "--- DCC not established.");
  else
      sic_slog(LOG_CLIENT, "--- No such DCC.");
}

void
vsic_dwrite(char *nick, char *format, ...)
{
  char	buffer[1024];
  va_list va;

  va_start(va, format);
  vsprintf(buffer, format, va);
  va_end(va);
  strcat(buffer, "\n");
  sic_dwrite(nick, buffer);
}

/* sic_dcc: called to deal with DCC requests
 *	0 -> chat , 1 -> send
 */
void
sic_dcc(type, nick, ip, port, file, size)
  int type;
  char *nick, *file;
  unsigned long ip, size;
  unsigned int port;
{
  struct server_ *dtmp = slist;
  unsigned long	lip;

  assert(type == 0 || type == 1);

  while (dtmp)
    {
      if (option(dtmp->sopt, S_DCC)
	  && !strcasecmp(dtmp->nick, nick) 
	  && (!dtmp->pass || !strcmp(dtmp->pass, file)))
	  break;
      dtmp = dtmp->nexts;
    }
  if (dtmp)
    {
      /* should be dealt with more gracefully */
      if ((option(dtmp->sopt, S_CONNECTING) || option(dtmp->sopt, S_CONNECTED))
	  && (type == 0 || (dtmp->pass && !strcmp(dtmp->pass, file))))
	{
	  vsic_slog(LOG_CLIENT,
		    "--- DCC chat request from %s ignored (duplicate).", nick);
	  return;
	}
      vsic_slog(LOG_CLIENT,
		"--- old DCC chat request from %s deleted.", nick);
      sic_dserver(dtmp);
    }
  if (type == 1 && size == 0)
    {
      vsic_slog(LOG_CLIENT, "--- Invalid DCC request (file size = 0) from %s.",
		nick);
      return;
    }
  dtmp = (struct server_ *) malloc(sizeof(struct server_));
  bzero(dtmp, sizeof(struct server_));
  dtmp->fd = -1;
  dtmp->ts = time(NULL);
  set_option(dtmp->sopt, S_DCC);
  dtmp->nick = strdup(nick);
  if (type == 1)
    {
      dtmp->pass = strdup(file);
      dtmp->size = size;
    }
  lip = ntohl(ip);
  dtmp->sname = strdup((char *)inet_ntoa(*(struct in_addr*)&lip));
  dtmp->ip = strdup((char *)inet_ntoa(*(struct in_addr*)&lip));
  set_option(dtmp->sopt, S_NOTCONNECTED);
  dns_lookup(dtmp->ip);
  switch (type)
    {
  case 0:
      vsic_slog(LOG_CLIENT, "--- DCC CHAT request from %s@%s.",
		dtmp->nick, dtmp->ip);
      break;
  case 1:
      vsic_slog(LOG_CLIENT, "--- DCC SEND request from %s@%s: %s %lu",
		dtmp->nick, dtmp->ip, file, size);
      break;
  default:
      abort(); /* never */
    }
  if ((dtmp->port = port) < 1024)
      vsic_slog(LOG_CLIENT, "--- DCC request from a privileged port %d!",port);
  dtmp->nexts = slist;
  slist = dtmp;
}

int
cmd_dcc(p)
  char *p;
{
  struct server_ *dtmp = slist;

  if (!*p)
    {
      sic_slog(LOG_CLIENT, "--- DCC list:");
      while (dtmp)
	{
	  if (option(dtmp->sopt, S_DCC))
	      if (dtmp->pass)
		  vsic_slog(LOG_CLIENT,
			    "---   %s@%s:%d %s [%lu] %s %s: %d%% %s",
			    dtmp->nick, dtmp->sname, dtmp->port, dtmp->pass,
			    dtmp->size,
			    (option(dtmp->sopt, S_CONNECTED)) ? "active" :
			    (option(dtmp->sopt, S_CONNECTING)) ? "connecting" :
			    "not connected",
			    (option(dtmp->sopt,S_SEND)) ? "Sending":"Reading", 
			    (int)(100*dtmp->read/dtmp->size),
			    sic_tdiff(time(NULL)-dtmp->ts, 0));
	      else
		  vsic_slog(LOG_CLIENT, "---   %s@%s:%d %s",
			    dtmp->nick, dtmp->sname, dtmp->port,
			    (option(dtmp->sopt, S_CONNECTED)) ? "active" :
			    (option(dtmp->sopt, S_CONNECTING)) ? "connecting" :
			    "not connected");
	  if (option(dtmp->sopt, S_LISTEN))
	      vsic_slog(LOG_CLIENT, "---   %s@%s pending %s%s", dtmp->nick,
			dtmp->sname, (dtmp->pass) ? "Send: " : "chat",
			(dtmp->pass) ? dtmp->pass : "");
	  dtmp = dtmp->nexts;
	}
      return 0;
    }
  if (server == NULL)
      return -5;
  if (!strncasecmp("CHAT ", p, 5))
    {
      p += 5;
      if (!*p)
	  return -1;
      while (dtmp && (!option(dtmp->sopt, S_DCC) || strcasecmp(dtmp->nick, p)
		      || dtmp->pass))
	  dtmp = dtmp->nexts;
      if (dtmp)
	{
	  if (option(dtmp->sopt, S_CONNECTING)
	      || option(dtmp->sopt, S_CONNECTED))
	    {
	      sic_slog(LOG_CLIENT, "--- Connection already initiated.");
	      return 0;
	    }
	  sic_connect(dtmp);
	}
      else
	{
	  dtmp = (struct server_ *) malloc(sizeof(struct server_));
	  bzero(dtmp, sizeof(struct server_));
	  sic_listen(dtmp);
	  if (dtmp->fd < 0)
	      free(dtmp);
	  else
	    {
	      dtmp->nick = strdup(p);
	      vsic_write("PRIVMSG %s :\001DCC CHAT chat %lu %u\001", p,
			 htonl(inet_addr(dtmp->ip)), htons(dtmp->port));
	      vsic_slog(LOG_CLIENT, "--- DCC chat offered to %s", p);
	      dtmp->nexts = slist;
	      slist = dtmp;
	    }
	}
      return 0;
    }
  if (!strncasecmp("SEND ", p, 5))
    {
      char *file = index(p+5, ' '), *basename;
 
      if (file == NULL)
          return -1;
      *file++ = '\0';
      if ((basename = rindex(file, '/')) == NULL)
	  basename = file;
      else
	  basename++;
      p += 5;

      dtmp = (struct server_ *) malloc(sizeof(struct server_));
      bzero(dtmp, sizeof(struct server_));

      if ((dtmp->sendf = open(file, O_RDONLY)) < 0)
	{
	  free(dtmp);
	  vsic_slog(LOG_CLIENT, "--- Unable to open %s: %s", file,
		    strerror(errno));
	  return 0;
	}
      dtmp->pass = strdup(file);

      sic_listen(dtmp);
      if (dtmp->fd < 0)
	{
	  close(dtmp->sendf);
	  free(dtmp->pass);
	  free(dtmp);
	}
      else
	{
	  struct stat st;

	  if (fstat(dtmp->sendf, &st) < 0)
	    {
	      vsic_slog(LOG_CLIENT, "--- Unable to find the file size: %s",
			strerror(errno));
	      close(dtmp->fd);
	      close(dtmp->sendf);
	      free(dtmp->pass);
	      free(dtmp);
	      return 0;
	    }
	  dtmp->size = st.st_size;
	  dtmp->nick = strdup(p);
	  vsic_write("PRIVMSG %s :\001DCC SEND %s %lu %u %u\001", p, basename,
		     htonl(inet_addr(dtmp->ip)), htons(dtmp->port),st.st_size);
	  vsic_slog(LOG_CLIENT, "--- \"%s\" offered to %s", file, p);
	  dtmp->nexts = slist;
	  slist = dtmp;
	}
      return 0;
    }
  if (!strncasecmp("GET ", p, 4))
    {
      char *path = index(p+4, ' '), *file;

      if (path == NULL)
	  return -1;
      p += 4;
      *path++ = '\0';
      if (file = rindex(path, '/'))
	  file++;
      else
	  file = path;
      while (dtmp 
	     && (dtmp->pass == NULL || !rmatch(file, dtmp->pass)
		 || !option(dtmp->sopt, S_DCC) || strcasecmp(dtmp->nick, p)))
	  dtmp = dtmp->nexts;
      if (dtmp)
	{
	  if (option(dtmp->sopt, S_CONNECTING)
	      || option(dtmp->sopt, S_CONNECTED))
	    {
	      sic_slog(LOG_CLIENT, "--- Connection already initiated.");
	      return 0;
	    }
	  sic_connect(dtmp);
	  dtmp->ts = time(NULL);
	}
      else
	return -4; /* invalid parameter */
      return 0;
    }
  if (!strncasecmp("delete ", p, 7))
    {
      char *file = index(p+7, ' ');

      p += 7;
      if (file)
	  *file++ = '\0';
      while (dtmp
	     && ((!option(dtmp->sopt, S_DCC) && !option(dtmp->sopt, S_LISTEN))
		 || strcasecmp(dtmp->nick, p)
		 || (file && !rmatch(file, dtmp->pass))))
	  dtmp = dtmp->nexts;
      if (dtmp)
	{
	  if (option(dtmp->sopt, S_CONNECTING)
	      || option(dtmp->sopt, S_CONNECTED))
	    {
	      sic_slog(LOG_CLIENT, "--- Closing DCC.");
	      close(dtmp->fd);
	    }
	  sic_dserver(dtmp);
	}
      else
	  sic_slog(LOG_CLIENT, "--- No such DCC.");
      return 0;
    }
  return -1;
}
