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

#ifndef lint
static char rcsid[] = "@(#)$Id: server.c,v 1.83 2001/08/01 01:21:42 kalt Exp $";
#endif

#include "os.h"

#if HAVE_SYS_FILIO_H
# include <sys/filio.h>
#endif

#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 void sic_format(char *, char *, char *, char *);

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

static int
sic_nextaddr(srv)
struct server_ *srv;
{
#if defined(HAVE_GETADDRINFO)
  struct addrinfo *last;

  assert(srv->address);
  assert(srv->ip);

  last = srv->addrip;
  do
    {
      srv->addrip = srv->addrip->ai_next;
      if (srv->addrip == NULL)
	  srv->addrip = srv->address;
    }
  while (srv->addrip->ai_family != PF_INET &&
	 srv->addrip->ai_family != PF_INET6);
  free(srv->ip);
  srv->ip = aitoip(srv->addrip);

  vsic_slog(LOG_DEBUG, "sic_nextaddr: srv->addrip %x, last %x",
	    srv->addrip, last);
  if (srv->addrip != last)
      return 1;
#endif
  vsic_slog(LOG_DEBUG, "sic_nextaddr returns 0");
  return 0;
}

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

  assert (srv->ip);	/* even if srv->addrip is defined */

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

#if defined(HAVE_GETADDRINFO)
  if (srv->addrip)
      fd = socket(srv->addrip->ai_family, SOCK_STREAM,
		  srv->addrip->ai_protocol);
  else
#endif
      fd = socket(AF_INET, SOCK_STREAM, 0);

  if (fd < 0)
      vsic_slog(LOG_CLIENT, "--- socket() call failed: %s", strerror(errno));
  if (fd > 0 && (vif = getenv("SICIP")))
    {
      /* the following should be fixed rather than simply reporting an error */
      sock_sin.sin_addr.s_addr = inet_addr(vif);
      if (sock_sin.sin_addr.s_addr == INADDR_NONE)
	  vsic_slog(LOG_CLIENT, "--- Bad IP address: SICIP = %s", vif);
      else
	  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));

#if defined(HAVE_GETADDRINFO)
  if (srv->addrip)
      rc = connect(fd, srv->addrip->ai_addr, srv->addrip->ai_addrlen);
  else
#endif
    {
      sock_sin.sin_port = htons(srv->port);
      sock_sin.sin_addr.s_addr = inet_addr(srv->ip);
      rc = connect(fd, (struct sockaddr *)&sock_sin, sizeof(sock_sin));
    }

  if (fd > 0 && rc < 0 && errno != EINPROGRESS)
    {
      rc = errno;
      vsic_slog(LOG_CLIENT, "--- Connection to %s:%d failed: %s", srv->sname,
		srv->port, strerror(errno));
      close(fd);
      if (rc == EHOSTUNREACH && !option(srv->sopt, S_DCC))
	  fd = -2;
      else
	  fd = -1;
    }
  unset_option(srv->sopt, S_RECONNECT);
  unset_option(srv->sopt, S_QUIT);
  switch (fd)
    {
    case -2: /* no route to host, may be there's another address to try? */
	unset_option(srv->sopt, S_CONNECTING);
	if (sic_nextaddr(srv))
	  {
	    set_option(srv->sopt, S_RECONNECT);
	    srv->ts = time(NULL) - 4;
	    break;
	  }
	srv->ts = time(NULL);
	break;
    case -1: /* other type of failures */
	unset_option(srv->sopt, S_CONNECTING);
	if (!option(srv->sopt, S_DCC)) /* needed? */
	    srv->ts = time(NULL);
	break;
    default: /* connection in progress */
	set_option(srv->sopt, S_CONNECTING);
	
	if (!option(srv->sopt, S_CONFIG))
	    cfg_read(srv->sname);
	set_option(srv->sopt, S_CONFIG);
	break;
    }
  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)
		  || !option(stmp->sopt, S_CONNECTED)))
      stmp = stmp->nexts;
  
  return server = stmp;
}

/* 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);
  server->sname = strdup(name);
  server->port = port;
  if (pass)
      server->pass = strdup(pass);

#if defined(HAVE_GETADDRINFO)
  {
    struct addrinfo *address, hints;
    char port_str[10];
    
    bzero((char *)&hints, sizeof(hints));
    hints.ai_flags = AI_CANONNAME|AI_NUMERICHOST;
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    sprintf(port_str, "%d", port);
    if (getaddrinfo(name, port_str, &hints, &address) == 0)
      {
	server->ip = strdup(name);
	server->address = address;
	server->addrip = address;
      }
  }
#else
  if (inet_addr(name) != INADDR_NONE)
      server->ip = strdup(name);
#endif

  if (server->ip == NULL)
    {
      dns_lookup(name, server);
      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;

  assert(sic_scnt(del) == 0);
  assert(option(del->sopt, S_CONNECTED) == 0);
  assert(option(del->sopt, S_DNS) == 0);
  while (*stmp != del)
      stmp = &((*stmp)->nexts);
  *stmp = del->nexts;
  opt_free(&(del->custs));
  free(del->sname);
  if (del->ip)
      free(del->ip);
#if defined(HAVE_GETADDRINFO)
  if (del->address)
      freeaddrinfo(del->address);
#endif
  if (del->pass)
      free(del->pass);
  free(del->nick);
  if (del->uname)
      free(del->uname);
  if (del->rname)
      free(del->rname);
  if (del->readf)
      close(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, maxfd = 0;
  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 = 5;

  FD_SET(0, &rfd);
  while (stmp)
    {
      if (stmp->fd > maxfd)
	  maxfd = stmp->fd;
      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_DNS))
	  FD_SET(stmp->fd, &wfd);
      stmp = stmp->nexts;
    }

  display_status();
  term_input(NULL, 0);
  term_flush();
#if defined(GNU_PTH)
  n = pth_select(maxfd + 1, &rfd, &wfd, NULL, &timeout);
#else
  n = select(maxfd + 1, &rfd, &wfd, &efd, &timeout);
#endif

  if (n < 0)
      return 0;

  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, NULL);
		  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 == 0)
			  stmp->readf = open(stmp->pass,
					     O_WRONLY|O_TRUNC|O_CREAT, 0644);
		      if (stmp->readf == 0)
			{
			  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 */
			  
			  write(stmp->readf, buf, sz); /* 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 (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)
    {
      /* NULL argument, this is only used internally */
      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;
    }
  /* real user input */
  if (!*p || !strcasecmp(p, "-v"))
    {
      /* no argument or just `-v', user is simply requesting a list */
      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) ? "" : option(stmp->sopt, S_DNS) ?
				" Looking up address" : " Unknown address");
	  if (*p && stmp->ip)
#if defined(HAVE_GETADDRINFO)
	    {
	      struct addrinfo *address = stmp->address;

	      vsic_slog(LOG_CLIENT, "---       => %s[%s]",
			(stmp->address && stmp->address->ai_canonname) ?
			stmp->address->ai_canonname : "???", stmp->ip);
	      if (address && address->ai_next)
		  while (address)
		    {
		      if (address->ai_family == PF_INET ||
			  address->ai_family == PF_INET6)
			  vsic_slog(LOG_CLIENT, "---        > %s[%s]",
				    (address->ai_canonname) ?
				    address->ai_canonname : "???",
				    aitoip(address));
		      address = address->ai_next;
		    }
	    }
#else
	      vsic_slog(LOG_CLIENT, "---       => [%s]", stmp->ip);
#endif
	  stmp = stmp->nexts;
	}
    }
  else if (!strncasecmp(p, "-d", 2))
    {
      char *number = p;

      while (*number && !isdigit(*number))
	  number += 1;
      if (*number == '\0')
	  return -4;

      n = 0;
      while (stmp)
	{
	  if (!option(stmp->sopt, S_DCC) && !option(stmp->sopt, S_LISTEN)
	      && n++ == atoi(number))
	      break;
	  stmp = stmp->nexts;
	}
      if (stmp == NULL)
	  return -4;

      if (option(stmp->sopt, S_DNS))
	{
	  sic_slog(LOG_CLIENT,
	   "--- DNS lookup in progress, cannot delete server. (try again)");
	  return 0;
	}
      if (option(stmp->sopt, S_CONNECTED))
	{
	  sic_slog(LOG_CLIENT, "--- Close connection first.");
	  return 0;
	}
      if (n = sic_scnt(stmp))
	{
	  vsic_slog(LOG_CLIENT,"--- %d window(s) are bound to this server.",n);
	  return 0;
	}
      assert(server != stmp); /* this shouldn't be possible? */
      vsic_slog(LOG_CLIENT,"--- Deleted server %d (%s).", atoi(number),
		stmp->sname);
      sic_dserver(stmp);
      return 0;
    }
  else
    {
      char *number = p;

      if (*p == '+')
	  number = p+1;
      while (*number)
	  if (!isdigit(*number++))
	    {
	      number = NULL;
	      break;
	    }

      if (number)
	{
	  /* argument is a number, find selected server and try to connect */
	  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_DNS))
		{
		  if (sic_swin(1))
		    {
		      if (!option(stmp->sopt, S_CONNECTED))
			{
			  set_option(stmp->sopt, S_CONNECTING);
			  if (option(stmp->sopt, S_DNS))
			    {
			      vsic_slog(LOG_CLIENT,
				"--- Connection to %s pending DNS lookup.",
					stmp->sname);
			    }
			  else
			    {
			      if (*p == '+')
				  sic_nextaddr(stmp);
			      vsic_slog(LOG_CLIENT,
					"--- Connecting to %s %d [%s]",
					stmp->sname, stmp->port, stmp->ip);
			      sic_connect(stmp);
			    }
			}
		    }
		}
	      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, NULL);
  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;
}


syntax highlighted by Code2HTML, v. 0.9.1