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

/* server commands - i.e. standard "handlers" that react to server
   packages in the client-server part of the napster protocol. */

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

#include <pwd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <fcntl.h>
#include <ncurses.h>

#include "getopt.h"
#include "defines.h"
#include "colors.h"
#include "codes.h"
#include "lists.h"
#include "handlers.h"
#include "alias.h"
#include "nap.h"
#include "sscr.h"
#include "event.h"
#include "winio.h"
#include "timer.h"
#include "scheck.h"
#include "irc.h"
#include "scmds.h"
#include "mp3s.h"
#include "missing.h"

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

extern info_t info;
extern chans_t *chanl, *curchan, *recent;
extern int tind, ircsock;
extern int noprint, ircmode;
extern int ipcs[2];
extern char *mnick;
extern hotlist_t *hlist;
extern void *hnd;
extern alias_t *alhead;
extern handler_t *hndhead;
extern int quit_now, quit_after_transfers, upsocks, downsocks;

extern ssearch_t *search;
extern int srch;
extern int noping;
extern upload_t *up;
extern download_t *down;
extern struct inbrowse_s directbrowse;
extern int wmode;
extern unsigned char *dwi;
extern int fl;

/* names of the different connection types, 0-10 */
const char *conns[] = { "Unknown", "14.4", "28.8", "33.6", "56k", "ISDN-64K",
                  "ISDN-128K", "Cable", "DSL", "T1", "T3" };

/* how many simultaneous uploads we allow by default, for each
   connection type */
int ups[] = { 3, 2, 3, 3, 4, 5, 5, 7, 7, 9, 9 };

/* what kind of minimal download speed we expect, roughly, from each
   connection type. Note: this is in kilobytes/second, not
   kilobits/second. The only place this is used is in
   nap.c:connection(). */
int speeds[] = { 0, 1, 1, 2, 3, 4, 8, 8, 8, 125, 2500 };

int tin = 0;
scount_t scount;           /* libraries / songs / gigs */

in_nap_cmd_t in[] = {
	 { NAP_SRET, ssret },
	 { NAP_SEND, ssend },
	 { NAP_RBROWSE, srbrowse },
	 { NAP_DBROWSE, sdbrowse },
	 { NAP_SGET, sget },
	 { NAP_NGET, snget },
	 { NAP_NACC, snacc },
	 { NAP_RQLIMIT, sqlimit },
	 { NAP_FREQ, sfreq },
	 { NAP_SSF, ssf },
	 { NAP_COUNT, sscount },
         { NAP_BROWSE2ACC, sbrowse2acc },
         { NAP_BROWSE2ERR, sbrowse2err },
         { NAP_BROWSE2, sbrowse2req },
	 { NAP_SWHOIS, swhois },
	 { NAP_SOFF, soff },
	 { NAP_UON, suon },
	 { NAP_UOFF, suoff },
	 { NAP_NOTICE, snotice },
	 { NAP_RY, sry },
	 { NAP_SJOIN, sjoin },
	 { NAP_SPART, spart },
	 { NAP_PART, smpart },
	 { NAP_TOPIC, stopic },
	 { NAP_SAID, ssay },
	 { NAP_NCHAN, snchan },
	 { NAP_JCHAN, sjchan },
	 { NAP_MNAME, snend },
	 { NAP_MNAME2, snend },
	 { NAP_TELL, stell },
	 { NAP_USER, suser },
	 { NAP_SOP, sop },
	 { NAP_PCHANGE, spchange },
	 { NAP_BPORT, sbport },
	 { NAP_ANNOUNCE, sannounce },
	 { NAP_SBANLIST, sbanlist },
	 { NAP_SBANLISTU, sbanlist },
	 { SERVER_PING, ssping },
	 { CLIENT_PING, scping },
	 { CLIENT_PONG, scpong },
	 { CLIENT_REDIRECT, sredir },
	 { CLIENT_CYCLE, scycle },
	 { NAP_SCLIST, sclist },
	 { CHANNEL_ENTRY2, sclist2 },
	 { NAP_NAMES, suser },
	 { NAP_SBLOCKLIST, sblocklist },
	 { IGNORE_LIST, signoreend },
	 { IGNORE_ENTRY, signorelist },
	 { IGNORE_ADD, signoreadd },
	 { IGNORE_REMOVE, signoreremove },
	 { IGNORE_UNKNOWN, signoreunknown },
	 { IGNORE_EXISTS, signoreexists },
	 { IGNORE_CLEAR, signoreclear },
	 { IGNORE_FAIL, signorefail },
	 { CHANNEL_BAN_ENTRY, scbanlist },
	 { NOTIFY_UNKNOWN, snerr },
	 { NOTIFY_EXISTS, snadd },
	 { CHAN_EMOTE, sme },
	 { 0x0, NULL },
};

/* return the part of the filename with all the (unix or dos)
   directory components removed. Note: we return a pointer into the
   original string */
char *ud_basename(char *fn) {
  char *p;

  p = strrchr(fn, '/');
  if (!p)
    p = strrchr(fn, '\\');
  if (!p)
    p = fn;
  else 
    p++;

  return p;
}

int
in_cksum(u_short *addr, int len)
{
	register int nleft = len;
	register u_short *w = addr;
	register int sum = 0;
	u_short answer = 0;

	/*
	 * Our algorithm is simple, using a 32 bit accumulator (sum), we add
	 * sequential 16 bit words to it, and at the end, fold back all the
	 * carry bits from the top 16 bits into the lower 16 bits.
	 */
	while (nleft > 1)  {
		sum += *w++;
		nleft -= 2;
	}

	/* mop up an odd byte, if necessary */
	if (nleft == 1) {
		*(u_char *)(&answer) = *(u_char *)w ;
		sum += answer;
	}

	/* add back carry outs from top 16 bits to low 16 bits */
	sum = (sum >> 16) + (sum & 0xffff);	/* add hi 16 to low 16 */
	sum += (sum >> 16);			/* add carry */
	answer = ~sum;				/* truncate to 16 bits */
	return(answer);
}

void
tvsub(register struct timeval *out, register struct timeval *in)
{
	if ((out->tv_usec -= in->tv_usec) < 0) {
	  --out->tv_sec;
	  out->tv_usec += 1000000;
	}
	out->tv_sec -= in->tv_sec;
}

/* receive ping response from "napping" process */
int icmpin(WINDOW *win, sock_t *m)
{
  ssearch_t *cur;
  char *line, *p;
  struct in_addr addr;
  long ms;
  int r;

  line = nap_getline(m->f);

  if (!line || line[0]==0) {
    fclose(m->f);
    delsock(m->fd);
    showresults(wchan, 2);
    free(line);
    return(1);
  }

  if (strncmp(line, "IP", 2) == 0) {
    wp(win, "* Waiting for ping results...\n");
    drw(win);
    for (cur=search; cur!=NULL; cur=cur->next) {
      cur->ping = -1;
    }
    free(line);
    return(1);
  }

  p = strchr(line, ' ');
  if (!p) {  /* syntax error from napping, ignore */
    return(1);
  }
  *p = 0;
  p++;
  r = inet_aton(line, &addr);
  free(line);

  if (!r) {  /* syntax error from napping, ignore */
    return(1);
  }
  ms = strtol(p, NULL, 10);
  
  for (cur=search; cur!=NULL; cur=cur->next) {
    if (cur->nip == addr.s_addr && cur->ping == -1) {
      cur->ping = ms/1000;
    }
  }
  
  return(1);
}

/* receive error message from "napping" process */
int icmperr(WINDOW *win, sock_t *m)
{
  char *line;

  line = nap_getline(m->f);

  if (line) {
    wp(win, "* Could not obtain ping results: %s\n", line);
    drw(win);
    free(line);
  }
  
  fclose(m->f);
  delsock(m->fd);

  return(1);
}

char **form_toks(char *buf, int *cnt)
{
  int i, j, k, l = 0, c=0;
  char **ret;
  
  ret = (char **)malloc(4096);
  ret[0] = NULL;
  
  for (j=0,i=0;buf[j];i++)
  {
    while (buf[j] == ' ')
      j++;
    if (!buf[j])
      break;
    ret[i] = (char *)malloc(2048);
    memset(ret[i], 0, 2048);
    if (buf[j] == '\\')
    {
      for (k=0,j++;buf[j]!=' '&&buf[j]&&k!=2048;k++,j++)
        ret[i][k] = buf[j];
      ret[i][k] = 0;
    }
    else if (buf[j] == '\'')
    {
      j++;
      while (buf[j] && buf[j] == ' ')
        j++;
      for (k=0;buf[j]!='\''&&buf[j]&&k!=2048;k++,j++)
        ret[i][k] = buf[j];
      ret[i][k--] = 0;
      while (k >= 0 && ret[i][k] == ' ')
        ret[i][k--] = 0;
      j++;
    }
    else if (buf[j] == '\"')
    {
      for (k=0,j++;buf[j]!='\"'&&buf[j]&&k!=2048;k++,j++)
        ret[i][k] = buf[j];
      ret[i][k] = 0;
      j++;
    }
    else if (buf[j] == '(')
    {
      for (k=0,c=0;buf[j]&&k!=2047;k++,j++)
      {
        if (buf[j] == ')' && c == 1)
          break;
        if (c < 0)
          c = 0;
        if (buf[j] == '(')
          c++;
        else if (buf[j] == ')')
          c--;
        ret[i][k] = buf[j];
      }
      if (buf[j])
      {
        ret[i][k] = ')';
        ret[i][k+1] = 0;
        j++;
      }
    }
    else if (buf[j] == '{')
    {
      for (k=0,c=0;buf[j]&&k!=2047;k++,j++)
      {
        if (buf[j] == '}' && c == 1)
          break;
        if (c < 0)
          c = 0;
        if (buf[j] == '{')
          c++;
        else if (buf[j] == '}')
          c--;
        ret[i][k] = buf[j];
      }
      if (buf[j])
      {
        ret[i][k] = '}';
        ret[i][k+1] = 0;
        j++;
      }
    }
    else
    {
      for (k=0;buf[j]!=' '&&buf[j]&&k!=2048;k++,j++)
        ret[i][k] = buf[j];
      ret[i][k] = 0;
    }
    ret[i] = (char *)realloc(ret[i], strlen(ret[i])+1);
    l+=strlen(ret[i])+1;
  }
  
  ret[i] = NULL;
  
  *cnt = i;
  
  return(ret);
}

/* break up the contents of a server packet (or remote client packet)
 * into an array of tokens */
char **form_tokso(char *buf, int *cnt)
{
  int i, j, k, l = 0;
  char **ret;
  
  ret = (char **)malloc(4096);
  ret[0] = NULL;
  
  for (j=0,i=0;buf[j];i++)
  {
    while (buf[j] == ' ')
      j++;
    if (!buf[j])
      break;
    ret[i] = (char *)malloc(2048);
    memset(ret[i], 0, 2048);
    if (buf[j] == '\"')
    {
      /* tokens in double quotes (e.g filenames) get copied without the
       * double quotes */
      for (k=0,j++;buf[j]!='\"'&&buf[j]&&k!=2048;k++,j++)
        ret[i][k] = buf[j];
      ret[i][k] = 0;
      j++;
    }
    else
    {
      for (k=0;buf[j]!=' '&&buf[j]&&k!=2048;k++,j++)
        ret[i][k] = buf[j];
      ret[i][k] = 0;
    }
    ret[i] = (char *)realloc(ret[i], strlen(ret[i])+1);
    l+=strlen(ret[i])+1;
  }
  
  ret[i] = NULL;
  
/*  ret = (char **)realloc(ret, l+sizeof(char *)); */
  
  *cnt = i;
  
  return(ret);
}

int parsein(int s, unsigned short op, char *buf, WINDOW *win)
{
  int i, cnt, r=0;
  char **tok, *b, *t=NULL;
  handler_t *cur;
  
  for (cur=hndhead;cur;cur=cur->next)
  {
    if (op == cur->op)
    {
      msprintf(&t, "%i %s", op, buf);
      tok = form_toks(t, &cnt);
      free(t);
      t = NULL;
      b = dohandler(cur, tok, cnt);
      for (i=0;b[i]=='\n'||b[i]==' ';i++);
      msprintf(&t, "/%s", b+i);
      free(b);
      r = parseout(s, t, win);
      free(t);
      for (i=0;i<cnt;i++)
        free(tok[i]);
      free(tok);
      break;
    }
  }
  
  if (r == 2)
    return(1);
  
  if (noprint)
    noprint = 2;
  
  for (i=0;;i++)
  {
    if (in[i].func == NULL)
    {
      if (nvar("debug") == 1)
      {
        wp(win, ""RED"UNKNOWN OP: (0x%x - %i) |%s|"WHITE"\n", op, op, buf);
        drw(win);
      }
      return(1);
    }
    
    if (in[i].op == op)
    {
      if (strlen(buf))
      {
        tok = form_tokso(buf, &cnt);
        r = in[i].func(s, buf, tok, cnt, win);
        for (i=0;i<cnt;i++)
          free(tok[i]);
        free(tok);
      }
      else
        r = in[i].func(s, NULL, NULL, 0, win);
      return(r);
    }
  }
}

/* 0x193 <channel> <nick> <text> [SERVER]: public message */
/* a message arrived. Color code it based on whether it was from us,
   and whether it is on the current channel or some other channel. */
I_NAP_FUNC(ssay)
{
  char *hilit;

  if (!(recent = findchan(chanl, tok[0])))
    return(1);  /* ?? we don't seem to be on that channel */

  /* highlight our own messages in MAGENTA, all others in BLUE */
  if (!strcasecmp(info.user, tok[1])) {
    hilit = MAGENTA;
  } else {
    hilit = BLUE;
  }

  if (recent == curchan || wmode)
  {
    wp(win, ""BOLD"%s<%s%s"BOLD"%s>%s %s\n", hilit, WHITE, tok[1], hilit, WHITE, str+strlen(tok[1])+strlen(tok[0])+2);
  }
  else
  {
    wp(win, "%s<%s%s%s:%s%s%s>%s %s\n", hilit, WHITE, tok[1], DARK, WHITE, tok[0], hilit, WHITE, str+strlen(tok[1])+strlen(tok[0])+2);
  }
  drw(win);
  recent = NULL;
  
  return(1);
}

I_NAP_FUNC(stell)
{
  char *autoreply;
  static char *lastreply = NULL;  /* nick of last user we autoreplied to */

  recent = findquery(chanl, tok[0]);

  wp(win, "%s* [%s%s%s]%s %s\n", BRIGHT(GREEN), WHITE, tok[0], BRIGHT(GREEN), WHITE, str+strlen(tok[0])+1);
  drw(win);
  recent = NULL;
    
  if ((autoreply = getval("autoreply")) != NULL) {
    /* do not autoreply to the same user twice in a row, to discourage
       infinite chats between two autoreplying clients */
    if (!(lastreply && !strcmp(lastreply, tok[0]))) {
      autoreply = strdup(autoreply);
      sendpack(s, NAP_TELL, "%s %s", tok[0], fixquotes(autoreply));
      wp(win, "%s* --> (%s%s%s)%s %s\n", GREEN, WHITE, tok[0], GREEN, WHITE, autoreply);
      drw(win);
      free(autoreply);
      free(lastreply);
      lastreply = strdup(tok[0]);
    }
  }

  return(1);
}

/* 406 (0x196) join message (user has joined channel) [SERVER]
   <channel> <user> <sharing> <link-type> */
I_NAP_FUNC(sjoin)
{
  if (!(recent = findchan(chanl, tok[0])))
    return(1);
  if (!strcasecmp(tok[1], info.user))
    wp(win, "\n");
  wp(win, "%s* %s (%s%s%s) [%ssharing %i songs%s] has joined %s.%s\n", BRIGHT(GREEN), tok[1], WHITE, atoi(tok[3])>10?"Unknown":conns[atoi(tok[3])], BRIGHT(GREEN), WHITE, atoi(tok[2]), BRIGHT(GREEN), tok[0], WHITE);
  drw(win);
  adduser(recent, tok[1], atoi(tok[2]), atoi(tok[3]));
  recent = NULL;
  
  return(1);
}

/* 407 (0x197)     user parted channel [SERVER]
   <channel> <nick> <sharing> <linespeed>  */
I_NAP_FUNC(spart)
{
  if (!(recent = findchan(chanl, tok[0])))
    return(1);
  wp(win, "%s* %s (%s%s%s) [%ssharing %i songs%s] has left %s.%s\n", GREEN, tok[1], WHITE, conns[atoi(tok[3])], GREEN, WHITE, atoi(tok[2]), GREEN, tok[0], WHITE);
  drw(win);
  deluser(recent, tok[1]);
  recent = NULL;
  
  return(1);
}

I_NAP_FUNC(stopic)
{
  if (!(recent = findchan(chanl, tok[0])))
    return(1);
  wp(win, "%s* Topic for %s: %s%s\n", YELLOW, tok[0], str+strlen(tok[0])+1, WHITE);
  drw(win);
  
  if (recent->topic)
    free(recent->topic);
  recent->topic = strdup(str+strlen(tok[0])+1);
  if (recent == curchan)
    tind = 0;
  recent = NULL;
  
  return(1);
}

I_NAP_FUNC(sjchan)
{
  chans_t *cur;

  cur = findchan(chanl, tok[0]);
  if (cur) {
    wp(win, ""RED"* Error: Duplicate channel join!"WHITE"\n");
    drw(win);
    return 1;
  }

  cur = (chans_t *)malloc(sizeof(chans_t));
  cur->nm = strdup(tok[0]);
  cur->users = NULL;
  cur->q = 0;
  cur->topic = NULL;
  cur->l = 0;
  cur->flag = 0;
  cur->key = NULL;
  cur->p = 0;
  cur->next = NULL;
  list_append(chans_t, chanl, cur);

  curchan = cur;
  indraw();    /* update command line with new channel */
  
  tin = -1;

  return(1);
}

/* 0x194 (404) server error message */
I_NAP_FUNC(snchan)
{
  if (dwi && ((!strcasecmp(tok[0], "information") && !strcasecmp(tok[2], dwi)) || (!strcasecmp(tok[0], "user") && !strcasecmp(tok[1], dwi))))
  {
    wp(win, "* Unable to resolve %s\n", dwi);
    drw(win);
    free(dwi);
    dwi = NULL;
  }

  wp(win, "%s* Server: %s%s\n", RED, str, WHITE);
  drw(win);
  
  if (!strcmp(str, "You are now cloaked.") || !strcmp(str, "You are cloaked."))
    cloaked = 1;
  else if (!strcmp(str, "You are no longer cloaked."))
    cloaked = 0;
  return(1);
}

I_NAP_FUNC(snotice)
{
  wp(win, "%s* %s%s\n", YELLOW, (str)?str:"", WHITE);
  drw(win);
  return(1);
}

I_NAP_FUNC(sscount)
{
  scount.libraries = atoi(tok[0]);
  scount.songs = atoi(tok[1]);
  scount.gigs = atoi(tok[2]);
  return(1);
}

/* 604 (0x25c)	whois response [SERVER]
 *
 *	<nick> "<user-level>" <time> "<channels>" "<status>" <shared>
 *	<downloads> <uploads> <link-type> "<client-info>" [ <total
 *	downloads> <total_uploads> <ip> <connecting port> <data port>
 *	<email> ] 
 */
I_NAP_FUNC(swhois)
{
  int hr, min, sec, t;
  struct hostent *d1, *d2;
  struct sockaddr_in in;
  
  if (dwi && !strcasecmp(dwi, tok[0]))
  {
    free(dwi);
    dwi = NULL;
    if (num < 13)
      return(1);
    if (fork())
      return(1);
      
    d1 = gethostbyname(tok[12]);
    if (!d1)
    {
      ssock(ipcs[1], "* Unable to resolve %s\n", tok[0]);
      exit(1);
    }
    
    memcpy(&in.sin_addr, d1->h_addr, d1->h_length);
    d2 = gethostbyaddr((char *)&in.sin_addr, d1->h_length, AF_INET);
    if (!d2)
    {
      ssock(ipcs[1], "* %s is %s\n", tok[0], inet_ntoa(in.sin_addr));
      exit(1);
    }
     
    ssock(ipcs[1], "* %s is %s [%s]\n", tok[0], d2->h_name, inet_ntoa(in.sin_addr));
    
    exit(1);
  }
  
  t = atoi(tok[2]);
  min = t/60;
  hr = min/60;
  sec = t%60;
  
  while (min >= 60)
    min-=60;

  wp(win, "%s ______________________%s\n", DARK, WHITE);
  wp(win, "%s|%s User: %s\n", DARK, WHITE, tok[0]);
  if (!strcasecmp(tok[0], "Ignitor"))
    wp(win, "%s|%s Class: Coder (%s)\n", DARK, WHITE, tok[1]);
  else if (!strcasecmp(tok[0], "nYtr0"))
    wp(win, "%s|%s Class: MoM (%s)\n", DARK, WHITE, tok[1]);
  else
    wp(win, "%s|%s Class: %s\n", DARK, WHITE, tok[1]);
  wp(win, "%s|%s Line: %s\n", DARK, WHITE, conns[atoi(tok[8])]);
  wp(win, "%s|%s Time: %i h %i m %i s\n", DARK, WHITE, hr, min, sec);
  wp(win, "%s|%s Channels: %s\n", DARK, WHITE, tok[3]);
  wp(win, "%s|%s Status: %s\n", DARK, WHITE, tok[4]);
  wp(win, "%s|%s Shared: %s\n", DARK, WHITE, tok[5]);
  wp(win, "%s|%s Client: %s\n", DARK, WHITE, tok[9]);
  wp(win, "%s|%s %s Downloading, %s Uploading\n", DARK, WHITE, tok[6], tok[7]);
  if (num > 10)
  {
    wp(win, "%s|%s %s Downloads, %s Uploads\n", DARK, WHITE, tok[10], tok[11]);
    wp(win, "%s|%s Address: %s\n", DARK, WHITE, tok[12]);
    wp(win, "%s|%s Data port: %s\n", DARK, WHITE, tok[14]);
  }
  drw(win);
  
  return(1);
}

I_NAP_FUNC(soff)
{
  time_t pt;
  char *t;
  
  if (dwi && !strcasecmp(dwi, tok[0]))
  {
    wp(win, "* Unable to resolve %s\n", tok[0]);
    drw(win);
    free(dwi);
    dwi = NULL;
  }
  
  pt = atol(tok[2]);
  t = ctime(&pt);
  t[strlen(t)-1] = '\0';
  t[strlen(t)] = 0;
  
  wp(win, "%s ______________________%s\n", DARK, WHITE);
  wp(win, "%s|%s User: %s\n", DARK, WHITE, tok[0]);
  if (!strcasecmp(tok[0], "Ignitor"))
    wp(win, "%s|%s Class: Coder (%s)\n", DARK, WHITE, tok[1]);
  else if (!strcasecmp(tok[0], "nYtr0"))
    wp(win, "%s|%s Class: MoM (%s)\n", DARK, WHITE, tok[1]);
  else
    wp(win, "%s|%s Class: %s\n", DARK, WHITE, tok[1]);
  wp(win, "%s|%s Last On: %s\n", DARK, WHITE, t);
  wp(win, "%s|%s Status: Offline\n", DARK, WHITE);
  drw(win);
  
  return(1);
}

/* 620 (0x26c) remote client queue limit reached:
 * <nick> "<filename>" <filesize> <digit> */
I_NAP_FUNC(sqlimit)
{
  download_t *task;
  int max;

  /* find matching REQUESTED or RRQUEUED item in the download list */
  list_find(task, down, !strcasecmp(task->nick, tok[0]) && !strcasecmp(task->rfn, tok[1]) && (task->state == REQUESTED || task->state == RRQUEUED));

  if (!task) {  /* we didn't request this item, so we don't care */
    return(1);
  }

  max = atoi(tok[3]);  /* maximal number of uploads the remote client allows */

  /* if max<=0, the remote client allows 0 uploads, and we may as well
     not bother queueing it. This is what we did in nap 1.4.8. Alas,
     the stupid WinMX client *always* sends max=0, even when it is
     not. Thus, we are essentially forced to ignore this value. */

  if (task->state == REQUESTED) {
    wp(win, "* Remotely queued \"%s\" from %s.\n", task->fn, task->nick);
    drw(win);
  }

  task->state = RQUEUED;
  task->r_time = time(0);

  return(1);
}

/* 206 (0xce) get error: <nick> "<filename>"  */
I_NAP_FUNC(snget)
{
  download_t *task;

  /* find matching REQUESTED or RRQUEUED item in the download list */
  list_find(task, down, !strcasecmp(task->nick, tok[0]) && !strcasecmp(task->rfn, tok[1]) && (task->state == REQUESTED || task->state == RRQUEUED));

  if (!task) {  /* we didn't request this item, so we don't care */
    return(1);
  }

  wp(win, "%s* Unable to get \"%s\" from %s (206 get error)%s\n", RED, \
     task->fn, tok[0], WHITE);
  drw(win);
  
  task->state = FAILED;
  task->d_time = time(0);

  return(1);
}

/* 609 (0x261) accept failed: <nick> "<filename>" */
I_NAP_FUNC(snacc)
{
  download_t *task;

  /* find matching REQUESTED or RRQUEUED item in the download list */
  list_find(task, down, !strcasecmp(task->nick, tok[0]) && !strcasecmp(task->rfn, tok[1]) && (task->state == REQUESTED || task->state == RRQUEUED));

  if (!task) {  /* we didn't request this item, so we don't care */
    return(1);
  }

  wp(win, "%s* Unable to get \"%s\" from %s (609 accept failed)%s\n", RED, \
      task->fn, tok[0], WHITE);
  drw(win);
  
  task->state = FAILED;
  task->d_time = time(0);

  return(1);
}

I_NAP_FUNC(nothing)
{
  return(1);
}

/* 401 (0x191)	part channel [CLIENT, SERVER]
   <channel-name> */
I_NAP_FUNC(smpart)
{
  chans_t *pt;

  pt = findchan(chanl, tok[0]);
  
  if (!pt)
    return(1);
  
  wp(win, "%s* Left channel %s%s\n", GREEN, pt->nm, WHITE);
  drw(win);
  
  delchan(pt);

  indraw();

  if (wmode)
  {
    dscr(win);
    drw(win);
  }
  
  return(1);
}

/* 408 (0x198)    channel user list entry [SERVER] 
   <channel> <user> <sharing> <link-type> */
/* 825 (0x339) user list entry [SERVER]
   <channel> <user> <files shared> <speed> */
I_NAP_FUNC(suser)
{
  int n, sp;
  const char *c;
  
  if (num < 4)
    return(1);
  
  n = 16-strlen(tok[1]);
  sp = atoi(tok[3]);
  
  if (sp <= 3)
    c = RED;
  else if (sp <= 6)
    c = BRIGHT(YELLOW);
  else
    c = GREEN;
  
  recent = findchan(chanl, tok[0]);
  
  adduser(recent, tok[1], atoi(tok[2]), sp);
  
  if (tin == -1)
  {
    tin = 0;
    wp(win, "%s* Users:%s\n", YELLOW, WHITE);
  }

  if (tin == 4)
  {
    tin = 0;
    wp(win, "\n");
  }
  wp(win, "%s[%s%s", c, WHITE, tok[1]);
  for (;n>=0;n--)
    wp(win, " ");
  wp(win, "%s]%s", c, WHITE);
  
  recent = NULL;
  
  tin++;
  
  return(1);
}

/* server sends a 0x33e packet to end a list of users in a channel */
I_NAP_FUNC(snend)
{
  tin = -1;
  wp(win, "\n");
  drw(win);
  
  return(1);
}

/* 607 (0x25f) upload request: <nick> "<filename>" <speed>. Note: not
   all servers send the <speed>.  */
I_NAP_FUNC(sfreq)
{
  char *lfn, *vfn, *fn, *libraryfile;
  upload_t *task;
  int i, r;

  /* replace backslashes by forward slashes */

  vfn = strdup(tok[1]);
  for (i=0; i<strlen(vfn); i++) {
    if (vfn[i] == '\\') {
      vfn[i] = '/';
    }
  }
  /* unmasquerade filename - note: here we do not check the "nomasq"
     user variable; thus, if masquerading went from "on" to "off"
     during a single session, we still do the right thing. */

  lfn = unmasquerade(vfn);  
  free(vfn);

  /* calculate short name without directory components */
  fn = ud_basename(lfn);
  
  /* check all the different reasons we might reject this upload */

  /* first check that we are really sharing the requested file. This
     check was missing from previous versions of nap (pre 1.4.4-ps5).
     This was a major security hole, since it would allow a remote
     client to download any file, whether it was shared or not */

  if (info.shared_filename) {
    libraryfile = strdup(info.shared_filename);
  } else {
    libraryfile = home_file(LIBRARYFILE);
  }

  if (!isshared(lfn, libraryfile)) {
    sendpack(s, NAP_GFR, "%s", str);  /* see comment below */
    wp(win, "%s* File \"%s\" was requested by %s, but is not shared!%s\n", RED, lfn, tok[0], WHITE);
    drw(win);
    free(lfn);
    free(libraryfile);
    return(1);
  }

  free(libraryfile);

  /* if /tquit is in effect, don't accept any new uploads */
  if (quit_after_transfers) {
    sendpack(s, NAP_GFR, "%s", str);
    wp(win, ""RED"* Not accepting new upload of \"%s\" to %s because /tquit is in effect"WHITE"\n", fn, tok[0]);
    drw(win);
    free(lfn);
    return(1);
  }

  list_find(task, up, !strcasecmp(task->nick, tok[0]) && !strcasecmp(task->rfn, tok[1]) && !STOPPED(task->state));

  if (task != NULL)
  {
    /* we are already uploading the file to nick */
    /* send a NAP_QLIMIT or send nothing ? NAP_QLIMIT would be
     * inappropriate.  If the download is not yet IN_PROGRESS, there
     * is no harm in sending an accept to the server (but not adding a
     * duplicate to the list) - maybe the remote user already deleted
     * the first download (which we would not know). -PS */
    if (task->state != IN_PROGRESS) {
      sendpack(s, NAP_GFR, "%s", str);
    } else {
      wp(win, ""RED"* Already uploading \"%s\" to %s (duplicate request)"WHITE"\n", fn, tok[0]);
      drw(win);
    }
    free(lfn);
    return(1);
  }

  /* see if global or per-user upload limit exceeded */
  r = upload_limit_reached(tok[0]);
  if (r) {
    /* sendpack(s, NAP_GFR, "%s", str); */ /* why is this here ? */ 
    /* apparently the official napster server does not forward QLIMIT
       packets to the requestor, thus keeping them waiting
       forever. However, this is too bad, I don't think we should use
       NAP_GFR here. Do the opennap servers react differently? ### check
       this another time. */
    sendpack(s, NAP_QLIMIT, "%s \"%s\" %li", tok[0], tok[1], r==2 ? nvar("maxuploads") : nvar("maxupuser"));
    if (nvar_default("showtoomanyuploads", 0)) {
      wp(win, "%s* Too many uploads %s(can't send \"%s\" to %s)%s\n", RED, r==2 ? "" : "for this user ", tok[1], tok[0], WHITE);
      drw(win);
    }
    free(lfn);
    return(1);
  }

  /* otherwise, accept the upload. Create an item in the upload list */
  task = (upload_t *)malloc(sizeof(upload_t));
  task->state = WAITING;
  task->nick = strdup(tok[0]);
  task->rfn = strdup(tok[1]);
  task->fn = strdup(fn);
  task->lfn = lfn;  /* already allocated with strdup */
  task->linespeed = num>=3 ? atoi(tok[2]) : 0;
  task->c_time = time(0);

  /* add it to the upload list */
  list_append(upload_t, up, task);

  /* sendpack(s, NAP_SX, "%s", task->nm); */ /* why send a link speed query? */
  /* accept upload request: <nick> "<filename>" */
  sendpack(s, NAP_GFR, "%s", str); 

  return(1);
}


/* link speed response: <nick> <linespeed> */
I_NAP_FUNC(sry)
{
  return(1);
}

/* 501 (0x1f5) alternate download ack: 
 * <nick> <ip> <port> "<filename>" <md5> <speed>
 * we are being asked to UPLOAD a file to a remote client (presumably
 * because we are firewalled and cannot accept incoming connections) */
I_NAP_FUNC(ssf)
{
  upload_t *task;
  sock_t *sk=NULL;
  int k, r;
  struct sockaddr_in dst;
  
  /* find the corresponding upload */
  list_find(task, up, task->state == WAITING && !strcasecmp(task->nick, tok[0]) && !strcasecmp(task->rfn, tok[3]));

  if (!task) { 
    /* we were not waiting for this upload. Ignore silently. */
    /* note: if in the future, we choose to accept 501 without prior 607, we
       must then also do the security checks; check the file is actually
       shared, /tquit is not active, etc */
    return(1);
  }
  
  /* contact remote client */

  /* note: the integer which represents the IP address is sent by the
   * napster protocol in "twisted" form. For instance, 1.0.0.0 = 1,
   * 0.1.0.0 = 256, etc.  If this integer is stored in its native
   * format on a given host, it will end up representing the IP
   * address in *non-host* byte order. On little-endian machines, such
   * as x86 and Alpha, this happens to be the correct network
   * byteorder (=big-endian), but on big-endian machines, it is
   * not. Thus, the correct thing to do is to first swap the byte
   * order to convert from *non-host* to *host* byte order, then apply
   * htonl(3) to convert from *host* to *network* byte order. This
   * causes some unnecessary work on little-endian machines, but
   * to heck with that. -PS */

  dst.sin_addr.s_addr = htonl(swapl(strtoul(tok[1], (char **)NULL, 10)));
  dst.sin_port = htons(atoi(tok[2]));
  dst.sin_family = AF_INET;
  
  k = socket(AF_INET, SOCK_STREAM, 0);
  
  /* use non-blocking i/o so that connect will not hang */
  fcntl(k, F_SETFL, O_NONBLOCK);

  r = connect(k, (struct sockaddr *)&dst, sizeof(dst));

  if (r == -1 && errno != EINPROGRESS) {
    wp(win, ""RED"* Error sending file (%s) to %s: %s"WHITE"\n", task->fn, task->nick, strerror(errno));
    drw(win);
    task->state = FAILED;
    task->d_time = time(0);
    close(k);
    return(1);
  }

  setkeepalive(k);
  sk = addsock(k, gnum(1), S_R, initsend);

  /* update task */
  task->state = CONNECTING;
  task->sk = sk;

  /* connect task to socket */
  
  sk->utask = task;
  
  return(1);
}

/* 0xcc: got a download ack from the server. It is a string of the form:
 * <nick> <ip> <port> "<filename>" <md5> <linespeed>
 *   0      1     2        3          4       5       <-- token numbers */
I_NAP_FUNC(sget)
{
  download_t *task;
  int rport;
  int r, k;
  sock_t *sk;

  /* find matching REQUESTED or RRQUEUED item in the download list */
  list_find(task, down, !strcasecmp(task->nick, tok[0]) && !strcasecmp(task->rfn, tok[3]) && (task->state == REQUESTED || task->state == RRQUEUED));

  if (!task)  /* we received an ack for an item we had not requested. */
    return(1);  /* ignore it silently. */
  
  rport = atoi(tok[2]);
  
  if (!rport && !info.port)
  {
    wp(win, "%s* Unable to get \"%s\" from %s because both parties are " \
        "firewalled%s\n", RED, task->fn, task->nick, WHITE);
    drw(win);
    task->state = FAILED;
    task->d_time = time(0);
    return(1);
  }

  task->check = strdup(tok[4]);
  task->linespeed = atoi(tok[5]);
  task->addr.sin_family = AF_INET;
  task->addr.sin_port = htons(rport);
  task->addr.sin_addr.s_addr = htonl(swapl(strtoul(tok[1], 0, 10)));

  if (!rport)   /* firewalled client, send push request and go WAITING */
  {
    sendpack(s, NAP_DSF, "%s \"%s\"", tok[0], tok[3]);
    task->state = WAITING;
    return(1);
  }

  /* else connect to the remote client and go CONNECTING */

  /* sendpack(s, NAP_SX, "%s", tok[0]); */ /* why send a link speed query? */

  k = socket(AF_INET, SOCK_STREAM, 0);
    
  /* make socket non-blocking, so that connect will return immediately */
  fcntl(k, F_SETFL, O_NONBLOCK);
  
  r = connect(k, (struct sockaddr *)&task->addr, sizeof(task->addr));

  if (r == -1 && errno != EINPROGRESS) {
    wp(win, ""RED"* Error getting file (%s) from %s: %s"WHITE"\n", task->fn, tok[0], strerror(errno));
    drw(win);
    free(task->check);
    task->state = FAILED;
    task->d_time = time(0);
    close(k);
    return(1);
  }

  setkeepalive(k);
  if (task->state == RRQUEUED) {
    wp(win, "* Getting \"%s\" from %s\n", task->fn, task->nick);
    drw(win);
  }

  sk = addsock(k, gnum(0), S_R, initget);
  
  sk->dtask = task;
  task->sk = sk;
  task->state = CONNECTING;
  
  return(1);
}

/* 209 (0xd1) <user> <speed>: user signon [SERVER]. server is
   notifying client that a user in their hotlist, <user>, has signed
   on the server with link <speed>
 */
I_NAP_FUNC(suon)
{
  hotlist_t *elt;
  
  list_find(elt, hlist, !strcmp(elt->nm, tok[0]));

  if (elt)
  {
    elt->conn = atoi(tok[1]);
    elt->on = 1;
    wp(win, "%s* %s (%s) is on napster%s\n", BRIGHT(GREEN), tok[0], conns[atoi(tok[1])], WHITE);
    drw(win);
  }
  
  return(1);
}

/* 210 (0xd2) <user>: user signoff [SERVER]. server is notifying
   client that a user on their hotlist, <user>, has signed off the
   server.

   this message is also sent by the server when the client attempts to
   browse a nonexistent client.  [why don't they just use 404 for
   this? -ed]
*/
I_NAP_FUNC(suoff)
{
  hotlist_t *elt;

  list_find(elt, hlist, !strcmp(elt->nm, tok[0]));
  
  if (elt && elt->on)
  {
    elt->on = 0;
    wp(win, "%s* %s has left napster%s\n", GREEN, tok[0], WHITE);
    drw(win);
  }
  
  return(1);
}

/* 201 (0xc9) search response:
 * "<filename>" <md5> <size> <bitrate> <frequency> <length> <nick> <ip>
 *  <link-type> [weight] */
I_NAP_FUNC(ssret)
{
  ssearch_t *elt;
  char *t;
  
  if (num < 9)
    return(1);
  
  /* calculate short filename */
  t = ud_basename(tok[0]);
  
  /* create a new search list element */
  elt = (ssearch_t *)malloc(sizeof(ssearch_t));
  
  elt->song = strdup(quote(t));
  elt->rfn = strdup(tok[0]);
  elt->sz = atoi(tok[2]);
  elt->brate = atoi(tok[3]);
  elt->freq = atoi(tok[4]);
  elt->time = atoi(tok[5]);
  elt->nick = strdup(tok[6]);
  elt->nip = htonl(swapl(strtoul(tok[7], (char **)NULL, 10)));
  elt->conn = atoi(tok[8]);
  if (elt->conn > 10 || elt->conn < 0)
    elt->conn = 0;
  elt->ping = -3;
  elt->next = NULL;
  
  /* and add it to the list. Note that it is faster to order the list
     at the end, rather than keeping it ordered here. */

  list_prepend(search, elt);

  return(1);
}

/* 202 (0xca) end of search response from server */
I_NAP_FUNC(ssend)
{
  ssearch_t *cur, *cur1;
  sock_t *sk;
  int pin[2], pout[2], perr[2];
  FILE *fin;
  struct in_addr addr;
  char *args[2];
  char *napping;
  int i;

  list_length(ssearch_t, search, i);
  wp(win, "* Received %d search %s\n", i, i==1 ? "result" : "results");
  drw(win);

  /* the easy case is if we can skip sending out pings. */
  if (noping || !search) {
    showresults(win, 1);
    return(1);
  }

  /* attempt to run "napping" application, which will collect ping
     information for us. */
  
  /* first create pipes to and from */
  /* do we need to do anything to avoid file descriptors 0-2? */
  if (pipe(pin) < 0 || pipe(pout) < 0 || pipe(perr) < 0) {
    wp(win, "* Could not collect ping results. pipe: %s\n", strerror(errno));
    drw(win);
    showresults(win, 1);
    return(1);
  }

  switch(fork()) {
  case -1: /* error */
    wp(win, "* Could not collect ping results. fork: %s\n", strerror(errno));
    drw(win);
    showresults(win, 1);
    return(1);
    break;

  case 0: /* child */
    /* attach pipes to stdin, stdout, stderr */
    close(pin[1]);
    close(pout[0]);
    close(perr[0]);
    dup2(pin[0], 0);
    dup2(pout[1], 1);
    dup2(perr[1], 2);
    close(pin[0]);
    close(pout[1]);
    close(perr[1]);

    napping = getval("napping");
    if (!napping)
      napping = NAPPING;

    args[0] = napping;
    args[1] = NULL;

    execvp(napping, args);

    /* execlp only returns if there is an error. */
    if (errno == ENOENT && getval("napping")==NULL) {
      /* if napping was neither found nor explicitly requested, just
	 ignore it silently and proceed without pings */
      exit(1);
    } else if (errno == EACCES) {
      fprintf(stderr, "%s is not executable\n", napping);
      exit(1);
    } else {
      fprintf(stderr, "%s: %s\n", napping, strerror(errno));
      exit(1);
    }
    break;

  default: /* parent */
    close(pin[0]);
    close(pout[1]);
    close(perr[1]);

    fin = fdopen(pin[1], "w");
    
    /* send the IP addresses to napping */
    for (cur=search; cur; cur=cur->next) {
      if (cur->ping == -2)
	continue;
      
      addr.s_addr = cur->nip; 
      fprintf(fin, "%s\n", inet_ntoa(addr));
            
      for (cur1=search;cur1;cur1=cur1->next)
	if (cur1->nip == cur->nip)
	  cur1->ping = -2;
    }
    fprintf(fin, "\n");
    fclose(fin);
    
    /* hook up sockets to receive ping results */
    addsock(pout[0], "icmp", S_R, icmpin);
    addsock(perr[0], "icmperr", S_R, icmperr);
    sk = findsock("icmp");
    sk->f = fdopen(dup(pout[0]), "r");
    sk = findsock("icmperr");
    sk->f = fdopen(dup(perr[0]), "r");

    /* set timer for 3 seconds */
    /* timer is not currently used, since napping, if installed, times
       out after 3 seconds anyway. */
    /* 
       desc = strdup("[waiting for ping responses]");
       addtimer(3, timed_pingresults, (void *)NULL, desc);
    */
    return (1);
  }
}

/* timer is not currently used, since napping, if installed, times
   out after 3 seconds anyway. */
void timed_pingresults(void *dummy) {
  sock_t *sk;

  sk = findsock("icmp");
  if (sk) {
    fclose(sk->f);
    delsock(sk->fd);
  }

  sk = findsock("icmperr");
  if (sk) {
    fclose(sk->f);
    delsock(sk->fd);
  }

  showresults(wchan, 2);
}

/* diplay search results, either on the main screen or on the result
   screen. LEVEL is 0 for browse, 1 for regular search, and 2 for
   search with pings. */
void showresults(WINDOW *win, int level) {
  int min, sec, i;
  float mb;
  ssearch_t *a, *b, *cur;
  
  /* sort the list, by ping times if we have them, else connection
     speed, or by filename if this is a browse. A ping time of -1 is
     considered "infinitely large" */

  switch (level) {
  case 2: /* display pings and connection speed, sort by pings first */
    list_mergesort(ssearch_t, search, a, b, 
		   (a->ping == b->ping && a->conn >= b->conn) ||
		   (a->ping != -1 && (b->ping == -1 || a->ping < b->ping)));
    break;
  case 1: /* no pings; sort by connection speed */
    list_mergesort(ssearch_t, search, a, b, a->conn >= b->conn);
    break;
  case 0: default: /* browse; sort by filename */
    list_mergesort(ssearch_t, search, a, b, strcmp(a->song, b->song) <= 0);
    break;
  }

  srch = 0;
  
  /* show search result on main screen or result screen, depending
     on whether "noresultscreen" was selected. */
  if (nvar_default("noresultscreen", 0)) 
  {
    if (!search) {
      return;
    }

    wp(win, ""BOLD"#"WHITE" | "BOLD"Song"WHITE" | Bitrate | Frequency | "BOLD"Length"WHITE" | "BOLD"Size"WHITE" | User%s%s\n", level>=1 ? " | "BOLD"Speed"WHITE"" : "" , level>=2 ? " | Ping" : "");
    wp(win, "-----------------------------------------------------%s%s\n", level>=1 ? "--------" : "", level>=2 ? "-------" : "");
    i=1;
    list_forall (cur, search) {
      min = cur->time/60;
      sec = cur->time%60;
      mb = ((float)cur->sz)/1048576.0;
      
      wp(win, ""BOLD"%i. %s"WHITE" %ibps %ihz "BOLD"%i:%02i %.02fmb"WHITE" %s", i, (fl)?cur->rfn:cur->song, cur->brate, cur->freq, min, sec, mb, cur->nick);

      if (level>=1) {
	wp(win, " "BOLD"%s"WHITE"", conns[cur->conn]);
      }
      if (level>=2 && cur->ping == -1)
	wp(win, " N/A");
      else if (level>=2)
	wp(win, " %i", cur->ping);

      wp(win, "\n");
      i++;
    }
    drw(win);
  }
  else 
  {
    if (search) {
      resetsscr();
      switchtoscreen(RESULT_SCREEN);
    }
    plist();
  }
} 
     

/* received a browse response from server:
 * <nick> "<filename>" <md5> <size> <bitrate> <frequency> <time> 
 *   0        1          2     3       4          5         6    */
I_NAP_FUNC(srbrowse)
{
  ssearch_t *cur, *cur1;
  char *t;
  
  if (num < 7)
    return(1);
  
  if (search == NULL)
  {
    search = (ssearch_t *)malloc(sizeof(ssearch_t));
    cur = search;
  }
  else
  {
    for (cur1=NULL,cur=search;cur!=NULL;cur=cur->next)
      cur1 = cur;
    cur = (ssearch_t *)malloc(sizeof(ssearch_t));
    cur1->next = cur;
  }
  
  t = ud_basename(tok[1]);
  
  cur->ping = -2;
  cur->song = strdup(quote(t));
  cur->rfn = strdup(tok[1]);
  cur->sz = atoi(tok[3]);
  cur->brate = atoi(tok[4]);
  cur->freq = atoi(tok[5]);
  cur->time = atoi(tok[6]);
  cur->nick = strdup(tok[0]);
  cur->conn = -1;
  cur->next = NULL;
  
  return(1);
}

/* received an "end of browse list" packet from server:
 * <nick> [ip] */
I_NAP_FUNC(sdbrowse)
{
  int i;

  list_length(ssearch_t, search, i);
  wp(win, "* Received %d browse %s\n", i, i==1 ? "result" : "results");
  drw(win);

  showresults(win, 0);
  return(1);
}

/* 642 (0x282) direct browse error: <nick> "message" */
I_NAP_FUNC(sbrowse2err)
{
  if (num < 2)
    return(1);

  srch = 0;
  directbrowse.state = FAILED;

  wp(win, "* Unable to directly browse %s: %s\n", tok[0], tok[1]);
  drw(win);
  return(1);
}

/* 641 (0x281) direct browse accept: <nick> <ip> <port>
 * <nick> accepted our direct browse request and the server is giving us 
 * the IP address and port to connect to. 
 * Note: If <nick> is firewalled, we will not receive this packet.
 * The remote client will connect to us and issue a SENDLIST command
 * (assuming we are not also firewalled). */
I_NAP_FUNC(sbrowse2acc)
{
  struct sockaddr_in dst;
  int k, r;
  sock_t *sk=NULL;
  unsigned short int dstport;
  
  if (num < 3) /* error, ignore the message */
    return(1);

  if (directbrowse.state != REQUESTED || \
      strcasecmp(directbrowse.nick, tok[0]) != 0) {
    /* didn't ask to browse this nick (or maybe we did, but that request
     * has timed out). Just ignore the message. */
    return(1);
  }
  
  dstport = htons(atoi(tok[2]));
  if (dstport == 0) {
    return(1); 
  }
  
  /* connect to the remote client */
  dst.sin_addr.s_addr = htonl(swapl(strtoul(tok[1], (char **)NULL, 10)));
  dst.sin_port = dstport;
  dst.sin_family = AF_INET;

  k = socket(AF_INET, SOCK_STREAM, 0);

  fcntl(k, F_SETFL, O_NONBLOCK);

  r = connect(k, (struct sockaddr *)&dst, sizeof(dst));

  if (r == -1 && errno != EINPROGRESS) {
    wp(win, ""RED"* Error browsing %s: %s"WHITE"\n", tok[0], strerror(errno));
    drw(win);
    directbrowse.state = FAILED;
    srch = 0;
    close(k);
    return(1);
  }

  setkeepalive(k);
  sk = addsock(k, "dirbrowse", S_R, initgetlist); /* initgetlist in event.c */
  
  directbrowse.state = CONNECTING;
  directbrowse.sk = sk;

  if (nvar("debug") == 2) {
    wp(win, ""DARK GREEN"* Connecting to %s to get list of shared files"\
        WHITE"\n", tok[0]);
    drw(win);
  }

  return(1);
}

/* 640 (0x280) direct browse request: <nick> [ip port] 
 * <nick> is requesting to directly browse our shared files.
 * If we are firewalled, the server also provides <nick>'s IP address and port
 * so that we can initiate the connection. */
I_NAP_FUNC(sbrowse2req)
{
  struct sockaddr_in dst;
  int k, r;
  sock_t *sk=NULL;
  unsigned short int dstport;
  
  if (num < 1)
    return(1);

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

  if (num < 3) {
    /* accept the direct browse request: <nick> */
    sendpack(s, NAP_BROWSE2ACC, "%s", tok[0]); 
    /* remote client will initiate the connection */
    return(1); 
  }

  /* Note the Napster v2.0 BETA 8 & BETA 9 clients don't send a
   * 641 direct browse accept message if they are firewalled. They
   * simply connect to the remote client and issue a SENDLIST command.
   * (They also don't understand SENDLIST commands that are issued
   * to them, but that is a separate matter...) */

  dstport = htons(atoi(tok[2]));
  if (dstport == 0)
    return(1); /* this is an error but we ignore it */

  /* initiate connection because we are firewalled */
  dst.sin_addr.s_addr = htonl(swapl(strtoul(tok[1], (char **)NULL, 10)));
  dst.sin_port = dstport;
  dst.sin_family = AF_INET;

  k = socket(AF_INET, SOCK_STREAM, 0);

  fcntl(k, F_SETFL, O_NONBLOCK);

  r = connect(k, (struct sockaddr *)&dst, sizeof(dst));

  if (r == -1 && errno != EINPROGRESS) {
    if (nvar("debug") == 2) {
      wp(win, ""RED"* Error sending list to %s: %s"WHITE"\n", tok[0], strerror(errno));
      drw(win);
    }
    close(k);
    return(1);
  }

  setkeepalive(k);
  sk = addsock(k, gnum(2), S_R, initsendlist); /* initsendlist in event.c */

  if (nvar("debug") == 2) {
    wp(win, ""DARK GREEN"* Connecting to %s to send list of shared files"\
        WHITE"\n", tok[0]);
    drw(win);
  }
  
  return(1);
}

I_NAP_FUNC(sop)
{
  wp(win, "%s!%s%s%s!%s %s\n", GREEN, WHITE, tok[0], GREEN, WHITE, str+strlen(tok[0])+1);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(spchange)
{
  wp(win, "%s* Your data port was changed to %s%s\n", RED, tok[0], WHITE);
  
  closefserv();
  info.port = initfserv(tok[0]);
  if (info.port == -1) {
    wp(win, ""RED"Warning: could not open port %s: %s"WHITE"\n", tok[0], strerror(errno));
    info.port = 0;
  }

  drw(win);
  return(1);
}

I_NAP_FUNC(sbport)
{
  wp(win, "%s* Your dataport is misconfigured%s\n", RED, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(sannounce)
{
  wp(win, "%s[%s%s%s]%s %s\n", RED, WHITE, tok[0], RED, WHITE, str+strlen(tok[0])+1);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(sbanlist)
{
  wp(win, "%s* Banned: %s%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(ssping)
{
  sendpack(s, SERVER_PING, NULL);
  
  return(1);
}

I_NAP_FUNC(scping)
{
  wp(win, "%s* Received PING from %s%s\n", RED, tok[0], WHITE);
  drw(win);
  sendpack(s, CLIENT_PONG, "%s", str);
  
  return(1);
}

I_NAP_FUNC(scpong)
{
  if (nvar_default("announcepongs", 0))
  {
    wp(win, "%s* Received PONG from %s%s\n", RED, tok[0], WHITE);
    drw(win);
  }
  return(1);
}

I_NAP_FUNC(sredir)
{
  int t;
  sock_t *sk;
  chans_t *cur;
  char *t1=NULL;
  int c;
  char *libraryfile;

  wp(win, "%s* You've been redirected%s\n", RED, WHITE);
  drw(win);
  
  msprintf(&t1, "%s:%s", tok[0], tok[1]);
  
  wp(win, "Connecting to %s...\n", t1);
  drw(win);

  t = conn(t1);
  if (t == -1)
  {
    drw(win);
    free(t1);
    return(1);
  }
  free(t1);
  wp(win, "Logging in...\n");
  drw(win);
  c = connection();
  if (login(t, info.user, info.pass, info.port, c, info.email) == -1)
  {
    drw(win);
    close(t);
    return(1);
  }
  
  sk = findsock("server");
  if (sk)
    delsock(sk->fd);
  sk = addsock(t, "server", S_R, inserv);
  
  /* schedule shared file list for sending */

  if (info.shared_filename) {
    libraryfile = strdup(info.shared_filename);
  } else {
    libraryfile = home_file(LIBRARYFILE);
  }

  lfiles(sk->fd, libraryfile);

  free(libraryfile);

  curchan=NULL;

  if (chanl)
  {
    for (cur=chanl;;cur=cur->next)
      if (!cur->q)
        sendpack(sk->fd, NAP_JOIN, "%s", cur->nm);
    delnapchans();
  }
  
  indraw();
  
  return(1);
}

I_NAP_FUNC(scycle)
{
  int t;
  sock_t *sk;
  chans_t *cur;
  int c;
  char *libraryfile;

  wp(win, "%s* You've been cycled%s\n", RED, WHITE);
  drw(win);
  
  wp(win, "Connecting to %s...\n", tok[0]);
  drw(win);
  t = conn(tok[0]);
  if (t == -1)
  {
    drw(win);
    return(1);
  }
  wp(win, "Logging in...\n");
  drw(win);
  c = connection();
  if (login(t, info.user, info.pass, info.port, c, info.email) == -1)
  {
    drw(win);
    close(t);
    return(1);
  }
  
  sk = findsock("server");
  if (sk)
    delsock(sk->fd);
  sk = addsock(t, "server", S_R, inserv);
  
  /* schedule shared file list for sending */

  if (info.shared_filename) {
    libraryfile = strdup(info.shared_filename);
  } else {
    libraryfile = home_file(LIBRARYFILE);
  }

  lfiles(sk->fd, libraryfile);

  free(libraryfile);

  curchan=NULL;
  
  if (chanl)
  {
    for (cur=chanl;;cur=cur->next)
      if (!cur->q)
        sendpack(sk->fd, NAP_JOIN, "%s", cur->nm);
    delnapchans();
  }
  
  indraw();
  
  return(1);
}

I_NAP_FUNC(sclist)
{
  wp(win, "%s%s%s - %s - %s\n", BOLD, tok[0], WHITE, tok[1], str+strlen(tok[0])+strlen(tok[1])+2);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(sclist2)
{
  wp(win, "%s%s%s - %s - %s\n", BOLD, tok[0], WHITE, tok[1], tok[5] /* str+strlen(tok[0])+strlen(tok[1])+strlen(tok[2])+strlen(tok[3])+strlen(tok[4])+5 */ );
  drw(win);
  
  return(1);
}

I_NAP_FUNC(sblocklist)
{
  wp(win, "%s* Blocked: %s%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signorelist)
{
  wp(win, "%s* Ignored: %s%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signoreend)
{
  wp(win, "%s* Total users ignored: %s%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signoreadd)
{
  wp(win, "%s* Added %s to the ignore list%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signoreremove)
{
  wp(win, "%s* Removed %s from the ignore list%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signoreunknown)
{
  wp(win, "%s* %s is not ignored or does not exist%s\n", RED, str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signoreexists)
{
  wp(win, "%s* %s is already on your ignore list%s\n", RED, str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signoreclear)
{
  wp(win, "%s* Cleared %s users from your ignore list%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(signorefail)
{
  wp(win, "%s* Ignoring %s failed!%s\n", RED, str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(scbanlist)
{
  wp(win, "%s* Channel ban: %s%s\n", BRIGHT(BLUE), str, WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(snerr)
{
  wp(win, "%s* Server: Could not add %s to your hotlist%s\n", RED, tok[0], WHITE);
  drw(win);
  
  return(1);
}

I_NAP_FUNC(snadd)
{
  /* the server is telling us that it successfully added somebody to
     our hotlist. Since we keep our own copy of our hotlist, do we
     really care? */
    
  return(1);
}

I_NAP_FUNC(sme)
{
  if (!(recent = findchan(chanl, tok[0])))
    return(1);
  if (!strcasecmp(curchan->nm, tok[0]) || wmode)
  {
    wp(win, "%s* %s%s %s\n", CYAN, tok[1], WHITE, tok[2]);
    drw(win);
  }
  else
  {
    wp(win, "%s* %s%s/%s%s%s %s\n", CYAN, tok[1], WHITE, CYAN, tok[0], WHITE, tok[2]);
    drw(win);
  }
  recent = NULL;
  
  return(1);
}

