/* Copyright (c) 2000  Kevin Sullivan <nite@gis.net>
 *
 * Please refer to the COPYRIGHT file for more information.
 */

/* This file contains functions that are used to handle socket events.
   The scheduler for these events (main loop) is in scheck.c. */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <ncurses.h>

#include "alias.h"
#include "codes.h"
#include "colors.h"
#include "defines.h"
#include "event.h"
#include "scheck.h"
#include "nap.h"
#include "sscr.h"
#include "cmds.h"
#include "scmds.h"
#include "timer.h"
#include "winio.h"
#include "lists.h"

#ifdef MEMWATCH
  #include "memwatch.h"
#endif

extern upload_t *up;
extern download_t *down;
extern info_t info; 
extern int srch; /* 1 while search or browse is in progress */
extern struct inbrowse_s directbrowse; 

int noprint=0;

/* not used */
void initssock(int port)
{
  struct sockaddr_in frm;
  int s, on = 1;
  
  s = socket(AF_INET, SOCK_DGRAM, 0);
  
  frm.sin_addr.s_addr = INADDR_ANY;
  frm.sin_port = htons(port);
  frm.sin_family = AF_INET;
  
  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
  setkeepalive(s);
  
  bind(s, (struct sockaddr *)&frm, sizeof(frm));
  
  addsock(s, "ssock", S_R, inssock);
}

/* not used */
int inssock(WINDOW *win, sock_t *m)
{
  char buf[1024];  /* note: don't need malloc */
  struct sockaddr_in frm;
  int frmlen = sizeof(frm);
  
  memset(buf, 0, 1024);
  
  recvfrom(m->fd, buf, 1024, 0, (struct sockaddr *)&frm, &frmlen);
  if (!strncmp(buf, "VERSION", strlen("VERSION")))
  {
    if (strcmp(strchr(buf, ' ')+1, VERSION))
    {
      wp(win, "%s* Attention: Version %s of nap is out!%s\n", BRIGHT(BLUE), strchr(buf, ' ')+1, WHITE);
      wp(win, "%s* Attention: Please visit http://www.gis.net/~nite/ to upgrade immediately%s\n", BRIGHT(BLUE), WHITE);
      drw(win);
    }
  }
  else if (!strncmp(buf, "MSG", strlen("MSG")))
  {
    wp(win, "%s* %s%s\n", BRIGHT(BLUE), strchr(buf, ' ')+1, WHITE);
    drw(win);
  }
  
  return(1);
}

/* "ipc" stands for "inter process communication". This is simply a
 * pipeline, created at the beginning of main(). It is used by child
 * processes to send output to the main screen. For instance, when
 * doing time-consuming things such as rebuilding the library, we fork
 * a child, but the child must then be able to send output to the main
 * screen. Using wp() directly won't work, both because the output
 * would be asynchronous, and because the child can't update the
 * parents' data structures.
 *
 * The reading end of the connection is here, and simply prints any
 * received data to the main screen. The sending happens in the many
 * calls in cmds.c of the form
 *
 *   ssock(ipcs[1], "* Successfully rebuilt your library\n"); 
 */

/* receive on ipc socket */
int inipc(WINDOW *win, sock_t *m)
{
  int s = m->fd;
  char *buf;
  
  rsock(s, &buf);
  if (!buf)
    return(1);
  
  /* addscroll(win, buf);
     dscr(win); */
  wp(win, buf);  /* use wp so the output goes to the log file also */
  drw(win);
  
  free(buf);
  
  return(1);
}

/* receive from server socket */
int inserv(WINDOW *win, sock_t *m)
{
  int s = m->fd, j, n, r;
  phead_t *pk;
  char *data;
  
  r = recvpack(s, &data, &pk);
  if (r == -2)
    return(1);
  else if (r == -1) {
    wp(win, ""RED"* Error: %s"WHITE"\n", strerror(errno));
    drw(win);
    delsock(m->fd);
    return(1);
  } else if (r == -3) {
    wp(win, ""RED"* Error: unexpected end of file from server"WHITE"\n");
    drw(win);
    delsock(m->fd);
    return(1);
  }

  for (j=0;data[j];j++)
    if (data[j] == 10)
    {
      strncpy(data+j, data+j+1, strlen(data+j+1));
      data[strlen(data)-1] = 0;
    }
    
  n = parsein(s, pk->op, data, win);
  noprint = 0;
  if (n == -1) {
    delsock(m->fd);
    return(1);
  }
  else if (n == -2)
    return(-1);      /* don't know what this does, or used to
			do. Don't know of a concrete situation where
			parsein returns -2, or in fact -1. */
  
  free(pk);
  free(data);
  
  dstatus();

  return(1);
}

/* a remote client has connected to our data port; we just accept the
   connection and then listen on the dedicated port for that connection */
int gconn(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  struct sockaddr_in frm;
  int frmlen = sizeof(frm);
  
  r = accept(s, (struct sockaddr *)&frm, &frmlen);
  if (r == -1)
    return(1);
  
  setkeepalive(s);

  addsock(r, "conn", S_R, lsock);
  ssock(r, "1");
  
  return(1);
}

/* a remote client has connected to us and is about to say "GET", "SEND",
 * "GETLIST", or "SENDLIST" (the latter two are for direct browsing) */

int lsock(WINDOW *win, sock_t *m)
{
  char buf[8];
  int r;

  memset(buf, 0, sizeof(buf));
  r = recv(m->fd, &buf, sizeof(buf), MSG_PEEK);

  if (r==-1)
  {
    wp(win, ""RED"* Bogus connection from remote client (%s): %s"WHITE"\n", getpeerip(m->fd), strerror(errno));
    drw(win);
    delsock(m->fd);
    return(1);
  }

  /* GET and SEND are prefixes of GETLIST and SENDLIST respectively,
   * so have to check for the longer words first */
  if (!strncmp(buf, "GETLIST", 7))
  {
    recv(m->fd, &buf, 7, 0);
    m->func = dogetlist;
    m->t = S_W; /* prepare to write */
  }
  else if (!strncmp(buf, "SENDLIST", 8))
  {
    recv(m->fd, &buf, 8, 0);
    /* we rename the connection here so that we can find it and delete it
     * if the direct browse request times out. This is necessary because
     * a broken client like Napster v2.0 BETA 9.6 will send a SENDLIST and
     * nothing else. As a result, dosendlist will never be called, and the 
     * socket will remain open until the program quits. */
    free(m->socknm);
    m->socknm = strdup("sendlist");
    m->func = dosendlist;
  }
  else              /* note: trailing "else" goes with following "if"!! */

  if (!strncmp(buf, "GET", 3))
  {
    recv(m->fd, &buf, 3, 0);
    m->func = doget;
  }
  else if (!strncmp(buf, "SEND", 4))
  {
    recv(m->fd, &buf, 4, 0);
    m->func = dosend;
  }
  else
  {
    char *data=NULL;

    /* see what the peer was saying */
    r = rsock(m->fd, &data);

    wp(win, ""RED"* Bogus connection from remote client (%s) [%s]"WHITE"\n", getpeerip(m->fd), data ? quote(data) : "");
    drw(win);

    ssock(m->fd, "INVALID REQUEST\n");

    free(data);
    delsock(m->fd);
    return(1);
  }
    
  return(1);
}

/* remote client connected to our data port and issued a GET command */
int doget(WINDOW *win, sock_t *m)
{
  char *buf, *nick, *rfn;
  size_t bsz;
  upload_t *task;
  struct stat st;
  sock_t *sv;
  char **tok;
  int i, cnt;
  FILE *f;
  
  if (rsock(m->fd, &buf) == -1) {
    delsock(m->fd);
    return(1);
  }    

  tok = form_tokso(buf, &cnt); 
  /* we should be able to use form_tokso() here instead of the more general
   * form_toks() */
  
  /* the client should have sent a string of the form
   * <remotenick> "<filename>" <offset> */

  if (cnt < 3)
  {
    wp(win, ""RED"* Bogus GET command from remote client (%s) [GET%s]"WHITE"\n", getpeerip(m->fd), quote(buf));
    drw(win);
    ssock(m->fd, "INVALID REQUEST\n");
    free(buf);
    for (i=0;i<cnt;i++)
      free(tok[i]);
    free(tok);
    delsock(m->fd);
    return(1);
  }
  
  nick = strdup(tok[0]);
  rfn = strdup(tok[1]);
  bsz = strtoul(tok[2], 0, 10);
  free(buf);
  
  for (i=0;i<cnt;i++)
    free(tok[i]);
  free(tok);
  
  list_find(task, up, !strcasecmp(task->nick, nick) && !strcasecmp(task->rfn, rfn) && task->state==WAITING);

  if (!task)
  {
    /* the following error message is often a result of an upload request
       which we rejected, but we sent an accept message to the server. It
       is more likely to be confusing than useful to the user, so we 
       omit it. */
    /* wp(win, "%s* %s requested upload of \"%s\" but it isn't in the upload list! (GET)%s\n", RED, nick, rfn, WHITE);
       drw(win); */
    ssock(m->fd, "INVALID REQUEST\n");
    free(nick);
    free(rfn);
    delsock(m->fd);
    return(1);
  }
  
  free(nick);
  free(rfn);

  /* otherwise accept the request. */

  /* Open the local file for reading. Note: we already checked that
     this file was in the shared library before we created this
     task. Thus, the local file *should* exist. However, it might be
     unreadable, or the library might be out of date. */

  f = fopen(task->lfn, "rb");
  if (!f)
  {
    wp(win, ""RED"* \"%s\" not found (requested by %s)"WHITE"\n", task->lfn, task->nick);
    drw(win);
    ssock(m->fd, "FILE NOT FOUND\n");
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }
  
  /* determine file size */
  fstat(fileno(f), &st);

  if (bsz > st.st_size)  /* note: bsz is unsigned, thus bsz<0 impossible */
  {
    wp(win, ""RED"* Error sending file \"%s\" to %s: illegal offset requested (%lu/%lu)"WHITE"\n", task->fn, task->nick, (long)bsz, (long)st.st_size);
    drw(win);
    ssock(m->fd, "INVALID REQUEST\n");
    fclose(f);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }
  
  fseek(f, bsz, SEEK_SET); 
  
  /* go to state IN_PROGRESS */
  
  task->sk = m;
  task->f = f;
  task->size = st.st_size;
  task->bsz = bsz;
  task->pos = bsz;
  task->p_time = time(0);
  task->state = IN_PROGRESS;

  /* link task to socket */
  m->utask = task;
  m->bwlimit = 1;
  bandwidth_init(&m->bw);
  m->func = sfile;
  m->t = S_W;
  free(m->socknm);
  m->socknm = strdup(gnum(1));
  
  sv = findsock("server");
  if (sv)
    sendpack(sv->fd, NAP_UP, NULL);
  
  /* send the file size */
  ssock(m->fd, "%lu", (long)task->size);
  
  wp(win, "* Sending file \"%s\" to %s (%lu bytes)\n", task->fn, task->nick, (long)task->size);
  drw(win);
  
  return(1);
}

/* we requested a file from a firewalled client. The remote client has
 * connected to us and is about to start uploading the file */
int dosend(WINDOW *win, sock_t *m)
{
  char *buf, *nm, *rfn, *fn, *lfn;
  FILE *f;
  size_t size;
  download_t *task;
  sock_t *sv;
  char **tok;
  int i, r, cnt;
  
  if (rsock(m->fd, &buf) == -1)
  {
    delsock(m->fd);
    return(1);
  }

  tok = form_tokso(buf, &cnt);
  
  /* the remote client should have sent a string of the form 
   * <remotenick> "<filename>" <size> */

  if (cnt < 3)
  {
    wp(win, ""RED"* Bogus SEND command from remote client (%s) [SEND%s]"WHITE"\n", getpeerip(m->fd), quote(buf));
    drw(win);
    ssock(m->fd, "INVALID REQUEST\n");
    free(buf);
    for (i=0;i<cnt;i++)
      free(tok[i]);
    free(tok);
    delsock(m->fd);
    return(1);
  }
  
  nm = strdup(tok[0]);
  rfn = strdup(tok[1]);
  size = strtoul(tok[2], 0, 10);

  free(buf);
  
  for (i=0;i<cnt;i++)
    free(tok[i]);
  free(tok);

  /* can remote client send "INVALID REQUEST" after a "SEND" ? */
  if (!strcasecmp(nm, "FILE")) /* "FILE NOT SHARED" or "FILE NOT FOUND" */
  {
    wp(win, "%s* Error downloading from firewalled remote client " \
        "[FILE NOT SHARED]%s\n", RED, WHITE);
    drw(win);
    /* the download this error message corresponds to will be removed
       from the download list when the item's timer times out - we
       can't do much better since we don't know the nick+filename yet
       (and going by IP address would be unreliable) */
    free(nm);
    free(rfn);
    delsock(m->fd);
    return(1);
  }
  
  list_find(task, down, !strcasecmp(task->nick, nm) && !strcasecmp(task->rfn, rfn) && task->state==WAITING);

  fn = ud_basename(rfn);  /* note: fn is a pointer into rfn! Do not free
                          rfn while fn is still needed. */
  
  if (!size)
  {
    rsock(m->fd, &buf);
      
    wp(win, "* Error downloading \"%s\" from %s (returned a size of 0) " \
        "[%s]\n", fn, nm, buf?quote(buf):"");
    drw(win);
    if (task) {
      free(task->check);
      task->state = FAILED;
      task->d_time = time(0);
    }
    free(rfn);
    free(nm);
    delsock(m->fd);
    return(1);
  }
  
  if (!task)
  {
    /* this error is frequently the result of a download request which timed
       out before the remote client got in touch with us. We omit the 
       error message, since it is confusing and not useful */
    /*    wp(win, "%s* Error: %s tried to send (push) \"%s\", but we did not request it!%s\n", RED, nm, fn, WHITE);
	  drw(win); */
    free(nm);
    free(rfn);
    delsock(m->fd);
    return(1);
  }

  /* try to open local file for this download */
  r = opendownloadfile(fn, &f, &lfn);

  if (r==-1) {
    wp(win, ""RED"* Error: could not open local file \"%s\": %s"WHITE"\n", lfn, strerror(errno));
    drw(win);
    free(lfn);
  }

  if (r==-1 || r==-2) {
    free(nm);
    free(rfn);
    free(task->check);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }

  free(nm);
  free(rfn);

  task->sk = m;
  task->lfn = lfn;
  task->f = f;
  task->size = size;
  task->bsz = 0;
  task->pos = 0;
  task->p_time = time(0);
  task->state = IN_PROGRESS;

  /* rename connection from "conn" to "d #", and connect it to this task */
  free(m->socknm);
  m->socknm = strdup(gnum(0));
  m->dtask = task;
  m->bwlimit = 1;
  bandwidth_init(&m->bw);
  m->func = gfile;
  sv = findsock("server");
  if (sv)
    sendpack(sv->fd, NAP_DOWN, NULL);
  
  /* send offset */
  ssock(m->fd, "%lu", (long)0);
  
  return(1);
}

/* try to open a port that is specified by the string
   dataport. Dataport must either be a port number such as "6699" or a
   range such as "6699-6799". Return port number used if successful,
   or -1 if failure (with errno set). If port 0 is requested, return 0
   without opening anything - this probably was configured by the user
   if we're behind a firewall. */

int initfserv(char *dataport)
{
  struct sockaddr_in me;
  int s, on = 1;
  int port, first, last;
  char *p;

  if (!dataport)
    return(0);
  
  /* parse dataport */
  first = last = strtol(dataport, &p, 10);
  while (p && isspace(*p)) {
    p++;
  }
  if (p && *p=='-') { /* range given? parse second part */
    last = strtol(p+1, &p, 10);
  }
  if (!p || *p!='\0') { /* parse error */
    errno = EINVAL;
    return -1;
  }

  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s == -1)
    return(-1);
  
  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
  setkeepalive(s);
  
  for (port = first; port <= last; port++) {
    me.sin_addr.s_addr = INADDR_ANY;
    me.sin_port = htons(port);
    me.sin_family = AF_INET;
    
    if (bind(s, (struct sockaddr *)&me, sizeof(me)) != -1) { 
      /* success */
      listen(s, 5);
      addsock(s, "fserv", S_R, gconn);
      return(port);
    }
  }
  return(-1);  /* overall failure */
}

void closefserv()
{
  sock_t *sk;
  
  sk = findsock("fserv");
  if (!sk)
    return;
  
  delsock(sk->fd);
}

/* We are about to upload a file to a remote client. We had to initiate
 * the connection because we are firewalled and cannot accept incoming
 * connections. The upload is in state CONNECTING. */
int initsend(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  upload_t *task = m->utask;
  sock_t *sk;
  struct stat st;
  char c;
  FILE *f;

  /* read the initial "1" from remote client */
  if (recv(s, &c, 1, 0) <= 0)
  {
    wp(win, "%s* Error sending file \"%s\" to %s: connection refused by remote client%s\n", RED, task->fn, task->nick, WHITE);
    drw(win);
    task->state=FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }

  /* Open the local file for reading. Note: we already checked that
     this file was in the shared library before we created this
     task. Thus, the local file *should* exist. However, it might be
     unreadable, or the library might be out of date. */

  f = fopen(task->lfn, "rb");
  if (!f) {
    wp(win, ""RED"* \"%s\" not found (requested by %s)"WHITE"\n", task->lfn, task->nick);
    drw(win);
    task->state=FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }
  
  /* determine file size */
  fstat(fileno(f), &st);
  
  r = ssock(s, "SEND");
  
  /* send "mynick filename filesize" */
  r = ssock(s, "%s \"%s\" %lu", info.user, task->rfn, st.st_size);
  
  if (r <= 0)
  {
    wp(win, ""RED"* Error sending file (%s) to %s: %s%s"WHITE"", RED, task->rfn, task->nick, r ? strerror(errno) : "remote client is misconfigured");
    drw(win);
    sk = findsock("server");
    if (sk)
      sendpack(sk->fd, NAP_MISCONFIGURE, "%s", task->nick);
    fclose(f);
    task->state=FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }
  
  /* go to state CONNECTING1, continue with ssize() */
  task->state = CONNECTING1;
  task->f = f;
  task->size = st.st_size;
  
  m->func = ssize;
  m->t = S_R;
  
  return(1);
}

/* continue what was started by initsend. Next the remote (firewalled)
   client will send the byte offset. Upload's state is CONNECTING1. */
int ssize(WINDOW *win, sock_t *m)
{
  char buf[32];
  int r;
  upload_t *task = m->utask;
  size_t bsz;

  memset(buf, 0, 32);
  
  r = recv(m->fd, buf, sizeof(buf), 0);
  
  if (r<=0) {
    wp(win, ""RED"* Error sending file \"%s\" to %s: %s"WHITE"\n", task->fn, task->nick, r ? strerror(errno) : "did not receive a proper offset");
    drw(win);
    fclose(task->f);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }

  bsz = strtoul(buf, 0, 10);

  if (bsz > task->size) {
    wp(win, ""RED"* Error sending file \"%s\" to %s: illegal offset requested (%lu/%lu)"WHITE"\n", task->fn, task->nick, (long)bsz, (long)task->size);
    drw(win);
    fclose(task->f);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }
  
  fseek(task->f, bsz, SEEK_SET);
  
  /* go to state IN_PROGRESS */

  task->bsz = bsz;
  task->pos = bsz;
  task->p_time = time(0);
  task->state = IN_PROGRESS;
  
  m->bwlimit = 1;
  bandwidth_init(&m->bw);
  m->func = sfile;
  m->t = S_W;
  
  wp(win, "* Sending file \"%s\" to %s (%lu bytes)\n", task->fn, task->nick, (long)(task->size - bsz));
  drw(win);
  
  return(1);
}

/* we have connected to a remote client and we are about to issue a request
 * for a file */
int initget(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  download_t *task = m->dtask;  /* state of task is CONNECTING */
  download_t *elt;
  sock_t *sk;
  char ch;
  
  /* remote client should send a '1' (ASCII character 49) */
  r = recv(s, &ch, 1, 0);
  if (r <= 0)
  {
    wp(win, "%s* Error getting file (%s) from %s: %s%s\n", RED, task->fn, task->nick, r ? strerror(errno) : "connection refused", WHITE);
    drw(win);
    free(task->check);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  } 
  
  /* make sure the task is not stale */
  list_find(elt, down, elt==task);
  
  if (!elt) {
    delsock(m->fd);
    return(1);
  }

  r = ssock(s, "GET");
  r = ssock(s, "%s \"%s\" %lu", info.user, task->rfn, (long)0);
  
  if (r <= 0)
  {
    wp(win, "%s* Error getting file (%s) from %s: %s%s\n", RED, task->fn, task->nick, r ? strerror(errno) : "remote client misconfigured", WHITE);
    drw(win);
    sk = findsock("server");
    if (sk)
      sendpack(sk->fd, NAP_MISCONFIGURE, "%s", task->nick);
    free(task->check);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }
  
  m->func = gsize;
  /*  m->t = S_R; */
  
  return(1);
}

/* continue the GET started by initget, now receive remote host's answer */

int gsize(WINDOW *win, sock_t *m)
{
  char buf[32];
  int i, s = m->fd;
  download_t *task = m->dtask;  /* state is still CONNECTING */
  sock_t *sk;
  int r;
  FILE *f;
  char *lfn;

  /* read file size. drscholl's napster spec says:
   * "keep reading until you hit a character that is not a digit" */
  for (i=0; i<31; i++)       /* but don't overflow buf[] ! */
  {
    r = recv(s, &buf[i], 1, MSG_PEEK);
    if (r <= 0 && !i)
    {
      wp(win, "%s* Error getting file (%s) from %s: %s%s\n", RED, task->fn, task->nick, r ? strerror(errno) : "remote client misconfigured", WHITE);
      drw(win);
      sk = findsock("server");
      if (sk)
        sendpack(sk->fd, NAP_MISCONFIGURE, "%s", task->nick);
      free(task->check);
      task->state = FAILED;
      task->d_time = time(0);
      delsock(m->fd);
      return(1);
    }
    /* abort only if first character is not a digit.
     * this catches messages such as "INVALID REQUEST" or "FILE NOT SHARED"
     * but does not barf on ID3 tags that follow the file size.
     * (Before, an error was signaled if an alphabetic character 
     * followed 0 or more numeric characters. This caused a problem with
     * mp3 files that start with ID3 tags.) -NBL */
    if (!isdigit(buf[i]) && (i == 0))
    {
      recv(s, buf, 32, 0);
      buf[31]=0;          /* make sure it's 0 terminated */
      wp(win, ""RED"* Error getting \"%s\" from %s [%s]"WHITE"\n", task->fn, task->nick, quote(buf));
      drw(win);
      free(task->check);
      task->state = FAILED;
      task->d_time = time(0);
      delsock(m->fd);
      return(1);
    }
    if (!isdigit(buf[i]))
      break;
    recv(s, &buf[i], 1, 0);
  }
  /* get rid of the non-digit that was just read
   * (or if i reached 31, make sure that buf[] is '\0'-terminated) */
  buf[i] = '\0'; 

  /* we need the debugging code here, since we didn't call rsock for this */
  if (nvar("debug") == 2)
  {
    wp(win, ""DARK GREEN"<-- [from %d=%s] <%s>"WHITE"\n", s, m->socknm, quote(buf));
    drw(win);
  }
  
  /* try to open local file for this download */
  r = opendownloadfile(task->fn, &f, &lfn);

  if (r==-1) {
    wp(win, ""RED"* Error: could not open local file \"%s\": %s"WHITE"\n", lfn, strerror(errno));
    drw(win);
    free(lfn);
  }

  if (r==-1 || r==-2) {
    free(task->check);
    task->state = FAILED;
    task->d_time = time(0);
    delsock(m->fd);
    return(1);
  }

  task->lfn = lfn;
  task->f = f;
  task->size = strtoul(buf, 0, 10);
  task->bsz = 0;
  task->pos = 0;
  task->p_time = time(0);
  task->state = IN_PROGRESS;

  m->bwlimit = 1;
  bandwidth_init(&m->bw);
  m->func = gfile;

  return(1);
}

/* Sending data to a remote client. State is IN_PROGRESS */
int sfile(WINDOW *win, sock_t *m)
{
  char buf[2048], *start, *end;
  upload_t *task = m->utask;
  FILE *f;
  int n, r;
  time_t t;

  /* is this mix of low-level and high-level i/o safe? Why use FILE*
   object if using low-level i/o? -PS */

  n = read(fileno(task->f), buf, sizeof(buf)); 
  if (n <= 0)
  {
    t = time(0);
    wp(win, "* Finished sending \"%s\" to %s (%lu of %lu bytes in %lu seconds)\n", task->fn, task->nick, (long)(task->pos-task->bsz), (long)(task->size-task->bsz), (long)(t-task->p_time));
    drw(win);

    if (info.logfile) {
      f = fopen(info.logfile, "a");
      if (f) {
	start = strdup(ctime(&task->p_time));
	start[strlen(start)-1] = 0;
	end = strdup(ctime(&t));
	end[strlen(end)-1] = 0;
	fprintf(f, "S %s %s \"%s\" %s\n", start, task->nick, task->lfn, end);
	fclose(f);
	free(start);
	free(end);
      }
    }
    
    fclose(task->f);
    task->d_time = t;
    task->state = COMPLETE;

    delsock(m->fd);
    return(1);
  }
  
  r = send(m->fd, buf, n, 0);
  if (r <= 0)
  {
    t = time(0);
    wp(win, "* Transfer interrupted while sending \"%s\" to %s (%lu of %lu bytes in %lu seconds): %s\n", task->fn, task->nick, (long)(task->pos-task->bsz), (long)(task->size-task->bsz), (long)(t-task->p_time), r ? strerror(errno) : "connection lost?");
    drw(win);
    
    if (info.logfile) {
      f = fopen(info.logfile, "a");
      if (f) {
	start = strdup(ctime(&task->p_time));
	start[strlen(start)-1] = 0;
	end = strdup(ctime(&t));
	end[strlen(end)-1] = 0;
	fprintf(f, "SI %s %s \"%s\" %s\n", start, task->nick, task->lfn, end);
	fclose(f);
	free(start);
	free(end);
      }
    }
    fclose(task->f);
    task->d_time = t;
    task->state = INCOMPLETE;

    delsock(m->fd);
    return(1);
  }
  
  task->pos += n;

  /* update byte counts for bandwidth limiting */
  bandwidth_register(&m->bw, n);
  bandwidth_register(&bwup, n);
  
  return(1);
}

/* ------------------------------------------------------------------------ */
/* we have connected to a remote client in order to issue a GETLIST command.
 * The connection was initiated in scmds.c:sbrowse2acc(). */
int initgetlist(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  char c;
 
  /* remote client should send a '1' (ASCII character 49) */
  r = recv(s, &c, 1, 0);
  if (r <= 0) {
    wp(win, ""RED"* Error browsing %s: %s"WHITE"\n", directbrowse.nick, \
        r ? strerror(errno) : "connection refused");
    drw(win);
    directbrowse.state = FAILED;
    srch = 0;
    delsock(m->fd);
    return(1);
  } 
  
  r = ssock(s, "GETLIST");
  if (r <= 0) {
    wp(win, ""RED"* Error browsing %s: %s"WHITE"\n", directbrowse.nick, \
        r ? strerror(errno) : "0 bytes written to socket");
    drw(win);
    directbrowse.state = FAILED;
    srch = 0;
    delsock(m->fd);
    return(1);
  }
 
  /* still in CONNECTING state */
  m->func = glistnick; /* wait for remote client to send its nick */
  return(1);
}

/* ------------------------------------------------------------------------ */
/* after we issue a GETLIST command, the remote client sends it nick before
 * sending its shared file list. */
int glistnick(WINDOW *win, sock_t *m)
{
  char *nick = NULL;
  FILE *f;
  
  /* we duplicate the file descriptor so the new descriptor can be closed 
   * independently of the original one when fclose() is called. */
  directbrowse.f = f = fdopen(dup(m->fd), "r");
  /* remote client sends its nick as "<nick>\n" */
  nick = nap_getline(f); /* nap_getline uses fgets() to read a line */
  if (!nick) {
    wp(win, ""RED"* Error browsing %s: remote client did not send its nick"\
        WHITE"\n", directbrowse.nick);
    drw(win);
    directbrowse.state = FAILED;
    srch = 0;
    fclose(f);
    /* closing the stream will also close the associated file descriptor,
     * but remember that this descriptor is a duplicate of m->fd, so 
     * we can safely close m->fd. */
    delsock(m->fd);
    return(1);
  }
  /* is this who we were expecting? */
  if (strcasecmp(directbrowse.nick, nick) != 0) {
    wp(win, ""RED"* Error browsing %s: remote client reports nick as %s"\
        WHITE"\n", directbrowse.nick, quote(nick));
    drw(win);
    free(nick);
    directbrowse.state = FAILED;
    srch = 0;
    fclose(f);
    delsock(m->fd);
    return(1);
  }

  free(nick);
  
  wp(win, "* Receiving shared file list from %s...\n", directbrowse.nick);
  drw(win);
  directbrowse.state = IN_PROGRESS;
  m->func = glist;
  return(1);
}

/* ------------------------------------------------------------------------ */
/* receive a shared file list from a remote client, one line per call */
int glist(WINDOW *win, sock_t *m)
{
  char *buf;
  char **tok;
  char *tmptok[7];
  int cnt, i;
  FILE *f = directbrowse.f;

  /* remote client sends a list of files consisting of lines like
   * "<filename>" <md5> <size> <bitrate> <frequency> <time>\n 
   * Last line is a \n by itself. */

  buf = nap_getline(f); /* nap_getline will strip the trailing newline */
  /* last line (single newline) will be returned as an empty string */
  if (buf && *buf != '\0') {
    if (nvar("debug") == 2) {
      /* trailing newline was already stripped, so it won't show up here */
      wp(win, ""DARK GREEN"<-- [from %d=%s] <%s>"WHITE"\n", m->fd, \
          m->socknm, quote(buf));
      drw(win);
    }
    tok = form_tokso(buf, &cnt);
    /* expect exactly 6 tokens */
    if (cnt == 6) {
        /* srbrowse expects 7 tokens */
        tmptok[0] = directbrowse.nick;
        for (i = 0; i < 6; i++) {
          tmptok[i+1] = tok[i];
        }
        /* re-use the function that processes browse responses from the
         * server. The first arg is normally the file descriptor for the
         * socket connected to the server, but it isn't used by srbrowse
	 * anyway. */
        srbrowse(m->fd, buf, tmptok, 7, win);
    }
    for (i = 0; i < cnt; i++)
      free(tok[i]);
    free(tok);
    free(buf);
  } else {
    free(buf);
    fclose(f);
    directbrowse.state = COMPLETE;
    showresults(win, 0); /* also resets srch to 0 */
    delsock(m->fd);
    return(1);
  }
  
  return(1);
} 

/* ------------------------------------------------------------------------ */
/* a remote client connected to us and issued a SENDLIST command so that it 
 * can send us its shared file list. */
int dosendlist(WINDOW *win, sock_t *m)
{
  char *nick = NULL;
  FILE *f;
  
  /* were we expecting a browse list? */
  if (directbrowse.state != REQUESTED) {
    delsock(m->fd);
    return(1);
  }
  
  /* "<nick>\n" immediately follows the SENDLIST command. */
  directbrowse.f = f = fdopen(dup(m->fd), "r");
  nick = nap_getline(f); /* nap_getline will strip leading spaces if there are any */

  /* is this who we were expecting? */
  if (!nick || strcasecmp(directbrowse.nick, nick) != 0) {
    if (nvar("debug") == 2) {
      wp(win, ""DARK GREEN"<-- [from %d=%s] Got unexpected SENDLIST from %s"\
          " (%s)"WHITE"\n", m->fd, m->socknm, nick ? quote(nick):"?", \
          getpeerip(m->fd));
      drw(win);
    }
    free(nick);
    /* directbrowse.state remains REQUESTED */
    fclose(f);
    delsock(m->fd);
    return(1);
  }
  
  free(nick);

  /* rename the connection */
  free(m->socknm);
  m->socknm = strdup("dirbrowse");
  
  if (nvar("debug") == 2) {
    wp(win, ""DARK GREEN"<-- [from %d=%s] Got SENDLIST from %s"WHITE"\n", \
        m->fd, m->socknm, directbrowse.nick);
  }
  wp(win, "* Receiving shared file list from %s...\n", directbrowse.nick);
  drw(win);

  directbrowse.sk = m;
  directbrowse.state = IN_PROGRESS;
  m->func = glist;
  return(1);
}

/* ------------------------------------------------------------------------ */
/* a remote client connected to us and issued a GETLIST command
 * (this is an outgoing direct browse connection) */
int dogetlist(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  FILE *g;
  char *libraryfile;

  /* XXX if we limit the number of outgoing browse connections,
   * check that we are within the limit here. */

  if (nvar("debug") == 2) {
    wp(win, ""DARK GREEN"<-- [from %d=%s] Got GETLIST"WHITE"\n", m->fd, m->socknm);
    drw(win);
  }

  /* rename the connection to "b #", where # is an integer */
  free(m->socknm);
  m->socknm = strdup(gnum(2));

  /* send "<mynick>\n" */
  r = ssock(s, "%s\n", info.user);
  if (r <= 0) {                         
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list: %s"WHITE"\n", \
          r ? strerror(errno) : "0 bytes written to socket");
      drw(win);
    }
    delsock(m->fd);
    return(1);
  }       
  
  if (info.shared_filename) {
    libraryfile = strdup(info.shared_filename);
  } else {
    libraryfile = home_file(LIBRARYFILE);
  }

  g = fopen(libraryfile, "r");
  if (!g) {
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list: cannot open %s (%s)"WHITE"\n", \
          libraryfile, strerror(errno));
      drw(win);
    }
    delsock(m->fd);
    free(libraryfile);
    return(1);
  }
  free(libraryfile);
  
  m->btask = (outbrowse_t *) malloc(sizeof(outbrowse_t));
  m->btask->g = g;
  
  m->func = slist;
  m->t = S_W;
  return(1);
}

/* ------------------------------------------------------------------------ */
/* we have connected to a remote client to issue a SENDLIST command and 
 * send our shared file list. (this is an outgoing direct browse connection)
 * The connection was initiated in scmds.c:sbrowse2req() and is named "b #",
 * where # is an integer. */
int initsendlist(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  char c;
  FILE *g;
  char *libraryfile;
 
  /* remote client should send a '1' */
  r = recv(s, &c, 1, 0);
  if (r <= 0) {
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list: %s"WHITE"\n", \
        r ? strerror(errno) : "connection refused");
      drw(win);
    }
    delsock(m->fd);
    return(1);
  } 
  
  /* send "SENDLIST" then "<mynick>\n" (this is what the Napster v2.0 BETA 8
   * and BETA 9 clients seem to do; there is no space in between.) */
  r = ssock(s, "SENDLIST");
  r = ssock(s, "%s\n", info.user);
  if (r <= 0) {
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list: %s"WHITE"\n", \
          r ? strerror(errno) : "0 bytes written to socket");
      drw(win);
    }
    delsock(m->fd);
    return(1);
  }
  
  if (info.shared_filename) {
    libraryfile = strdup(info.shared_filename);
  } else {
    libraryfile = home_file(LIBRARYFILE);
  }

  g = fopen(libraryfile, "r");
  if (!g) {
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list: cannot open %s (%s)"WHITE"\n", \
          libraryfile, strerror(errno));
      drw(win);
    }
    delsock(m->fd);
    free(libraryfile);
    return(1);
  }
  free(libraryfile);

  m->btask = (outbrowse_t *) malloc(sizeof(outbrowse_t));
  m->btask->g = g;
 
  m->func = slist;
  m->t = S_W;
  return(1);
}

/* ------------------------------------------------------------------------ */
/* send shared file list, one line per call */
int slist(WINDOW *win, sock_t *m)
{
  int s = m->fd, r;
  FILE *g;
  char *buf, *p;
  
  if (!m->btask) {
    delsock(m->fd);
    return(1); /* fatal error */
  }
  g = m->btask->g;
  buf = nap_getline(g); /* nap_getline strips the trailing newline */
  if (!buf) { /* EOF */
    r = ssock(s, "\n"); /* terminate the list, ignore errors */
    fclose(g);
    free(m->btask);
    delsock(m->fd);
    return(1);
  }
  if (*buf != '\"') { 
    free(buf);
    return(1); /* skip headers and junk */
  }
  for (p=buf; *p; p++) {
    if (*p == '/')
      *p = '\\';
  }
  /* send the line; don't forget to terminate with a newline */
  r = ssock(s, "%s\n", buf);
  if (r <= 0) {
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list: %s"WHITE"\n",  \
          r ? strerror(errno) : "0 bytes written to socket");
      drw(win);
    }
    free(buf);
    fclose(g);
    free(m->btask);
    delsock(m->fd);
    return(1);
  }
 
  free(buf);
  return(1);
}

/* receive a raw file on socket, after initget and gsize did the
   initial communication. */
int gfile(WINDOW *win, sock_t *m)
{
  char buf[2049], *st1, *end; /* note: don't need malloc for buf */
  download_t *task = m->dtask;
  FILE *f;
  int n;
  time_t t;
  int r;
  char *lfn;

  memset(buf, 0, sizeof(buf));
  n = recv(m->fd, buf, sizeof(buf)-1, 0);
  if (n > 0) {      /* we got some bytes - write them to the local file */
    write(fileno(task->f), buf, n);
    
    task->pos += n;
    
    /* update byte counts for bandwidth limiting */
    bandwidth_register(&m->bw, n);
    bandwidth_register(&bwdown, n);
  }

  if (n<=0 || task->pos >= task->size) {  /* end of file */
    t = time(NULL);
    lfn = strdup(task->lfn);
    r = interrupt_download(win, task);
    
    switch (r) {
    case 1:
      wp(win, "* Download of \"%s\" from %s interrupted (%lu of %lu bytes in %lu seconds)\n", task->fn, task->nick, (long)(task->pos-task->bsz), (long)(task->size-task->bsz), (long)(t - task->p_time));
      break;
    case 2:
      wp(win, "* Download of \"%s\" from %s interrupted, turd removed (%lu of %lu bytes in %lu seconds)\n", task->fn, task->nick, (long)(task->pos-task->bsz), (long)(task->size-task->bsz), (long)(t - task->p_time));
      break;
    case 3:
      wp(win, "* Completed download of \"%s\" from %s (%lu of %lu bytes in %lu seconds)\n", task->fn, task->nick, (long)(task->pos-task->bsz), (long)(task->size-task->bsz), (long)(t - task->p_time));
      break;
    default:
      break;
    }
    drw(win);
    
    if (info.logfile) {
      f = fopen(info.logfile, "a");
      if (f) {
	st1 = strdup(ctime(&task->p_time));
	st1[strlen(st1)-1] = 0;
	end = strdup(ctime(&t));
	end[strlen(end)-1] = 0;
	fprintf(f, "%s %s %s \"%s\" %s\n", r==3 ? "R" : "RI", st1, task->nick, lfn, end);
	fflush(f);
	free(st1);
	free(end);
      }
    }
    
    free(lfn);
    delsock(m->fd);
  }
  return(1);
}

