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

/* this file defines "user commands" such as /get and /search. */

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>
#include <netdb.h>
#include <ncurses.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>
#include <math.h>
#include <ctype.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 "cmds.h"
#include "scmds.h"
#include "mp3s.h"
#include "dlul_screen.h"
#include "missing.h"

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

extern info_t info;
extern scroll_t *mscroll, *mscrollend;
extern int lastlogflag;
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 sock_t *socklist;
extern cmds_t *cmdl;
extern sets_t *setl;

int fl, wmode = 0;
unsigned char *dwi=NULL;
char *tbuf = NULL;
upload_t *up = NULL;       /* the upload list */
download_t *down = NULL;   /* the download list */
ssearch_t *search=NULL;    /* search results */
int srch=0;                /* set while a search is in progress (and
                              thus the state of the search result list
                              is not well-defined) */
int noping;                /* when a search is in progress, this flag
                              indicates whether we should collect ping
                              results or not */
int quit_after_transfers = 0; /* set by /tquit and checked by tevent() */

inbrowse_t directbrowse = {COMPLETE, NULL, (time_t)0, NULL, NULL};
  /* holds state for an incoming direct browse connection */

out_nap_cmd_t out[] = {
          { "about", 1, dabout, "- Shows credits" },
          { "alias", 1, dalias, "[name] [args] - Creates an alias, or lists current aliases" },
          { "aliaslist", 1, daliaslist, "- Shows current list of aliases" },
          { "announce", 0, dannounce, "<msg> - Broadcasts a message to all users" },
	  { "ban", 0, dban, "[user/IP] - Bans specified user or IP, or lists banned users" },
          { "banlist", 0, dbanlist, "- Prints a list of the current bans on the server" },
          { "block", 0, dblock, "[IP] [reason] - Blocks the specified IP, or lists blocked users" },
          { "blocklist", 0, dblocklist, "- Gives a list of current blocked users" },
          { "break", 1, dbreak, "- Breaks out of a loop" },
          { "browse", 0, dbrowse, "<user> - Browses user's files" },
          { "browse2", 0, dbrowse2, "<user> - Directly browse user's files" },
          { "cban", 0, dcban, "[user] [reason] - Bans a user from a channel, or lists banned users" },
          { "cbanlist", 0, dcbanlist, "- Returns a list of banned users in a channel" },
          { "chupload", 1, dchupload, "<path> - Changes your upload path (still need to /rebuild to update your files)" },
          { "clear", 1, dclear, "- Clears your screen buffer" },
          { "clearalias", 1, dclearalias, "- Clears all aliases" },
          { "clearhandler", 1, dclearhandler, "- Clears all handlers" },
          { "clist", 0, dclist, "- Gets a list of channels" },
          { "clist2", 0, dclist2, "- Gets a list of channels (includes user created)" },
          { "cloak", 0, dcloak, "- Cloaks yourself" },
          { "conf", 0, dsetconf, "<config-string> - Request a change in server configuration variables" },
          { "cunban", 0, dcunban, "<user> [reason] - Unbans a user from a channel" },
          { "ddown", 0, dddown, "<number or range> - Deletes downloads by number as returned from /pdown" },
          { "dec", 1, ddec, "- Decreases the variable by one" },
          { "debug", 1, ddebug, "<level> - Sets debug level" },
          { "disconnect", 0, ddisconnect, "- Disconnects you from the server" },
          { "dlul", 1, ddlul, "- Switches to the download/upload monitor screen" },
          { "dns", 1, ddns, "<host/IP> - Attempts to resolve the specified address" },
          { "done", 1, ddone, "- Ends an alias" },
	  { "dtimer", 1, ddtimer, "<number> - Delete the timed event with the given number" },
          { "dup", 0, ddup, "<number or range> - Deletes uploads by number as returned from /pup" },
          { "echo", 1, decho, "<text> - Echos text to the screen" },
          { "eval", 1, deval, "<name> - Returns the value of a variable" },
          { "exec", 1, dexec, "[-o] <command> - Executes a command from a shell and redirects the input to the client" },
	  { "fdown", 0, dfdown, "<number or range> - Gets information on the user as returned from /pdown" },
	  { "finger", 0, dwhois, "<user> - Gets information on the specified user" },
	  { "force", 0, dforce, "<number or range> - Forces download of queued items, overriding download limit" },
	  { "fup", 0, dfup, "<number or range> - Gets information on the user as returned from /pup" },
	  { "g", 0, dg, "<number or range> - Gets file by number as returned from /search" },
	  { "get", 0, dg, "<number or range>... - Gets file by number as returned from /search" },
	  { "getservers", 0, dgetservers, "- Read server list from napigator-style metaserver" },
          { "gusers", 0, dgusers, "- Gets a global list of users" },
          { "handler", 1, dhandler, "[code] [args] - Adds a handler, or prints current handlers" },
          { "handlerlist", 1, dhandlerlist, "- Returns a list of handlers created" },
          { "help", 0, dhelp, "<command> - Returns help on the specified command" },
          { "hotlist", 0, dnotify, "[user] - Adds a user to your hotlist, or shows current hotlist" },
          { "if", 1, dif, "(<val> <op> <val>) <cmd> - Compares two values" },
          { "ignore", 0, dignore, "[user] - Ignores a user, or lists ignored users" },
          { "ignoreclear", 0, dignoreclear, "- Clears your ignore list" },
          { "ignorelist", 0, dignorelist, "- Lists ignored users" },
          { "inc", 1, dinc, "<var> - Increases the variable by 1" },
          { "irc", 0, dirc, NULL },
	  { "join", 0, djoin, "[chan] - Joins the specified channel, or prints a list of all channels" },
          { "kick", 0, dkick, "<user> [reason] - Kicks a user from a channel" },
          { "kickall", 0, dkickall, "<user> [reason] - Kicks a user from all channels you and the user are in" },
	  { "kill", 0, dkill, "<user> - Kills the specified user" },
	  { "lastlog", 1, dlastlog, "<str> - Returns all occurences of \"str\" that have been said or printed" },
          { "loadalias", 1, dloadalias, "[filename] - Loads a list of aliases from a file" },
          { "loadchannels", 1, dloadchannels, "[filename] - Reads channels from a filename and joins them" },
          { "loadconfig", 1, dloadconfig, "[filename] - Loads a list of settings from a filename" },
          { "loadhandler", 1, dloadhandler, "[filename] - Loads a list of handlers from a filename" },
          { "me", 0, dme, "<string> - Does an emotion" },
#ifdef MEMWATCH
	  { "memwatch", 0, dmemwatch, " - Prints details about memory usage to a log file" },
#endif
	  { "msg", 0, dtell, "<user> <msg> - Sends the user the message specified" },
          { "muzzle", 0, dmuzzle, "<user> <msg> - Muzzles the user with the specified message" },
          { "names", 0, dusers, "<channel> - Gets a list of channel users" },
          { "news", 1, dnews, "- Checks for any news on the client" },
          { "noprint", 1, dnoprint, "- Stops the client from echoing anything until the command returns" },
          { "notify", 0, dnotify, "[user] - Adds a user to your hotlist, or shows current hotlist" },
	  { "opsay", 0, dopsay, "<msg> - Broadcasts a message to all moderators/admins/elite" },
	  { "part", 0, dpart, "[chan/user] - Parts the specified or current channel or query" },
          { "pchans", 1, dpchans, "- Shows which channels you are on" },
	  { "pdown", 1, dpdown, "[dqsf] - Gives a listing of your current downloads. Optional flags select downloading, queued, succeeded, failed items." },
          { "ping", 0, dping, "<user> - Pings a user" },
          { "psocks", 1, dpsocks, "- Print the socket list (for debugging purposes)" },
	  { "pup", 0, dpup, "- Gives a listing of your current uploads" },
	  { "purge", 0, dpurge, "- Removes all stopped items from upload and download lists" },
	  { "purgedown", 0, dpurgedown, "- Removes all stopped items from download list" },
	  { "purgeup", 0, dpurgeup, "- Removes all stopped items from upload list" },
	  { "pvars", 0, dpvars, "- Prints the values of all variables currently set" },
          { "query", 0, dquery, "<user> - Queries a user" },
	  { "q", 0, dquit, "- Closes the program" }, 
	  { "quit", 0, dquit, "- Closes the program" }, 
	  { "rebuild", 1, drebuild, "- Rebuilds your library unconditionally. See also /update." },
          { "reconnect", 0, dreconnect, "- Reconnects you to the next available server on the list" },
          { "reload", 0, dreloadconf, "<config-variable> - Resets server configuration parameter to its default value" },
	  { "repeat", 1, drepeat, "<min:sec> <cmd> - Initiates a timed event which repeats every <min:sec> interval" },
          { "results", 1, dresults, "- Switches to the search results screen" },
	  { "retry", 0, dretry, "<number or range> - Puts stopped downloads back in the download queue" },
	  { "retryall", 0, dretryall, " - Puts all stopped downloads back in the download queue" },
          { "savealias", 1, dsavealias, "[filename] - Saves current aliases" },
          { "savechannels", 1, dsavechannels, "[filename] - Saves current channels to a filename" },
          { "saveconfig", 1, dsaveconfig, "[filename] - Saves current settings to a filename" },
          { "savehandler", 1, dsavehandler, "[filename] - Saves current handlers to a filename" },
          { "say", 0, dsay, "<msg> - Sends msg to the current channel" },
	  { "search", 0, dsearch, "[-b>bitrate] [-c>speed] [-r>freq] [-s>size] [-d>duration] [-x exclude]... [-t filetype] [-mmaxresults] [-l] [-p] [-f] <query> - Searches the napster database" },
	  { "serv", 0, dserver, "[IP:port] - Connects to the specificed server and port, or shows current server and port" },
	  { "server", 0, dserver, "[IP:port] - Connects to the specificed server and port, or shows current server and port" },
          { "set", 1, dset, "[name] [value] - Sets a user variable, or prints current values" },
	  { "setdataport", 0, ddatap, "<user> <port> - Sets a user's data port" },
          { "setlevel", 0, dsetlevel, "<channel> <level> - ?" },
          { "setlinespeed", 0, dlspeed, "<user> <speed> - Changes a user's linespeed" },
          { "setpassword", 0, duserpass, "<user> <password> - Sets a user's password" },
	  { "setuserlevel", 0, dlevel, "<user> <level> - Changes a user's userlevel" },
          { "sraw", 0, dsraw, "<type> <string> - Send raw command to the server" },
          { "stop", 1, dstop, "- Returns from the current command and stops all processing on it" },
          { "sver", 0, dsver, "- Returns the server version" },
	  { "tell", 0, dtell, "<user> <msg> - Sends the user the message specified" },
	  { "timer", 1, dtimer, "<min:sec> <cmd> - Initiates a timer to execute in the specified time" },
	  { "tlist", 1, dtlist, "- Prints out a list of the current timers" },
	  { "topic", 0, dtopic, "<channel> <topic> - Changes a channel's topic" },
          { "tquit", 0, dtquit, "- Quits when all remaining transfers have completed. Can be canceled with /unquit." },
          { "unalias", 1, dunalias, "<name> - Removes an alias" },
	  { "unban", 0, dunban, "<IP> - Unbans the specified IP" },
          { "unblock", 0, dunblock, "<IP> - Unblocks the specified IP" },
          { "unhandler", 1, dunhandler, "<code> - Removes a handler" },
          { "unhotlist", 0, dunnotify, "<user> - Removes a user from your hotlist" },
          { "unignore", 0, dunignore, "<user> - Unignores a user" },
          { "unmuzzle", 0, dunmuzzle, "<user> - Unmuzzles the user" },
          { "unnotify", 0, dunnotify, "<user> - Removes a user from your hotlist" },
	  { "unquit", 0, dunquit, "- Cancels the effect of /tquit" },
          { "unset", 1, dunset, "<name> - Unsets a variable" },
          { "update", 1, dupdate, "- Rebuilds your library if necessary. See also /rebuild." },
          { "while", 1, dwhile, "(<val> <op> <val>) cmd - Keeps executing cmd while the comparison is true" },
	  { "whois", 0, dwhois, "<user> - Gets information on the specified user" },
          { "window", 1, dwindow, "- Enables/disables window mode" },
          { "wstats", 1, dwstats, NULL },
	  { NULL, 0, NULL, NULL },
};


/* destructively replaces double quotes by single quotes - returns the
   same string. It used to be that napster servers would cut off
   private and channel message if they contained a double quote. With
   newer servers, this is no longer a problem; however, it's still
   needed for the /me command. */
char *fixquotes(char *s) {
  char *p = s;
  while (*p) {
    if (*p == '\"')
      *p = '\'';
    p++;
  }
  return s;
}

/* free the given channel datastructure */
void freechan(chans_t *c) {
  user_t *u;

  if (c) {
    list_forall_unlink(u, c->users) {
      free(u->nm);
      free(u->addr);
      free(u);
    }
    free(c->topic);
    free(c->nm);
    free(c->key);
    free(c);
  }
}
  
/* the following two functions replace the old "upchan" function */

/* rotate curchan through the channel list. If curchan==NULL, go to
   first channel. If there are no channels, set curchan to NULL. Also
   set channel title indentation level to 0. */
void nextchan() 
{
  tind = 0;
  if (curchan) {
    curchan = curchan->next;
  }
  if (!curchan) {
    curchan = chanl;
  }
}

/* delete the given channel c from the channel list. If c==curchan,
   also change curchan to the next valid channel in the list, or to
   NULL if the list is empty. Also set channel title indentation level
   to 0. */
void delchan(chans_t *c) 
{
  tind = 0;

  /* modify curchan */
  if (curchan == c) {
    curchan = c->next;
    if (curchan == NULL) {
      curchan = chanl;
    }
    if (curchan == c) {
      curchan = NULL;
    }
  }
  /* now curchan is definitely not c */

  /* delete c from channel list */
  list_unlink(chans_t, chanl, c);

  if (c) {
    freechan(c);
  }
}

/* reduces a list of tokens. Input is a list IN of CNT tokens. Output
   is a NULL-terminated list OUT of *RCNT tokens. The original list IN
   is deallocated. OUT is obtained from IN by replacing any
   consecutive tokens A B ... C by a single token, if A starts with
   "$" and if no initial segment of A B ... C has balanced
   parentheses. */
char **fxv(char **in, int cnt, int *rcnt)
{
  int i, k, j, c;
  char **out;
  
  out = (char **)malloc((cnt+1) * sizeof(char *));
  
  for (i=0, k=0; in[i]; k++)
  {
    out[k] = strdup(in[i]);
    if (*in[i] == '$' && strchr(in[i], '(')) {
      c = 0;
      while (1) {
        for (j=0;in[i][j];j++) {
          if (c < 0)
            c = 0;
          if (in[i][j] == '(')
            c++;
          else if (in[i][j] == ')')
            c--;
        }
	i++;
	if (!c || !in[i])
	  break;
	out[k] = (char *)realloc(out[k], strlen(out[k])+2+strlen(in[i]));
        strcat(out[k], " ");
        strcat(out[k], in[i]);
      }
    } else {
      i++;
    }
  } /* for (i=0,k=0,*rcnt=0;in[i];) */
  
  out[k] = NULL;
  *rcnt = k;
  
  for (i=0;i<cnt;i++)
    free(in[i]);
  free(in);
  
  return(out);
}

/* original version of fxv. Presumably they do the same thing, but
   fxv_old is more complicated and uses static limits on the size of
   tokens/token lists */
char **fxv_old(char **in, int cnt, int *rcnt)
{
  int i, k, j, n=cnt, c=0;
  char **ret;
  
  ret = (char **)malloc(4096);
  
  for (i=0,k=0,*rcnt=0;in[i];i++,k++)
  {
    if (*in[i] == '$' && strchr(in[i], '('))
    {
      ret[k] = (char *)malloc(512);
      ret[k+1] = NULL;
      memset(ret[k], 0, 512);
      do
      {
        if (!in[i])
          break;
        for (j=0;in[i][j];j++)
        {
          if (c < 0)
            c = 0;
          if (in[i][j] == '(')
            c++;
          else if (in[i][j] == ')')
            c--;
        }
        strcat(ret[k], in[i++]);
        strcat(ret[k], " ");
      } while (c);
      
      ret[k][strlen(ret[k])-1] = 0;
      ret[k] = (char *)realloc(ret[k], strlen(ret[k])+1);
      if (!in[i])
      {
        k++;
        break;
      }
      else
        i--;
    }
    else
    {
      ret[k] = strdup(in[i]);
      ret[k+1] = NULL;
    }
  }
  
  if (ret[k])
    k++;
  ret[k] = NULL;
  *rcnt = k;
  
  for (i=0;i<n;i++)
    free(in[i]);
  free(in);
  
  return(ret);
}

/* The functions parseout and parsenout, as well as the O_NAP_FUNC
 * functions (command handlers), use their return values to control
 * the control flow. The return values observe the following
 * convention: If the return value is
 *
 * 1: continue execution normally,
 *
 * 2: skip the remaining commands. If inside a loop, continue
 * with the next loop iteration. (This is like "continue" in C).
 * 
 * -4: break out of the most recent loop, then continue
 * normally. (This is like "break" in C).
 *
 * -3: stop execution and unwind to the top level.
 *
 * Here, "top level" means the enclosing call of parseout, normally
 * from winio.c:input(), but sometimes from timer.c or irc.c:inap.
 *
 * 2 is returned by dstop().
 * -4 is returned by dbreak().
 * -3 is returned by: ddone(), dquit(), dwhile() if the loop was
 * interrupted by a SIGINT, and sometimes by commands which produce
 * a fatal error (although this is not consistently done).
 *
 * Previously, the return values 0, -1, and -2 also existed, but they
 * were used in buggy ways and have been removed. In particular,
 * dquit() now uses a global variable to signal its intent to quit
 * nap, rather than doing it through a return value. -PS
 *
 **/

/* parseout: parse and execute the (user) command in buf. If it's an
   alias, expand it, otherwise let parseoutn handle it (or its various
   subparts individually?) */
/* ### warning: this function contains static string limits */
int parseout(int s, char *buf, WINDOW *win)
{
  char **tok, *st, t[2048], *b, *p, nb[2048];
  alias_t *cur;
  int i, j=0, k, cnt, c = 0, rn, sw, js=0, x, y, f=0, bc = 0;
  
  if (*buf == '/')
  {
    b = strdup(buf+1);
    sw = 1;
  }
  else {
    b = strdup(buf);
    sw = 0;
  }

  while (1)
  {
    if (!b[j])
      break;
    
    if (sw && !bc)
    {
      memset(t, 0, sizeof(t));
      while (b[j] && b[j] == ' ')   /* read past spaces */
        j++;
      for (k=0, c=0, f=j; b[j]; j++)
      {
        if (b[j] == '{')
          c++;
        else if (b[j] == '}')
          c--;
        else if ((b[j] == '|' || b[j] == '\n' || !b[j]) && !c)
          break;

        t[k] = b[j];
        k++;
        if (c < 0)
          c = 0;
      }
    }
    else
    {
      strcpy(t, b);
      j = strlen(t);
    }
    
    if (*t == '#')
    {
      if (b[j])
        j++;
      continue;
    }
    
    tok = form_toks(t, &cnt);
    if (tok[0] && *tok[0] == '#')
    {
      if (b[j])
        j++;
      for (i=0;i<cnt;i++)
        free(tok[i]);
      free(tok);
      continue;
    }
    
    if (*tok && *tok[0] == '.')
    {
      bc = 1;
      memmove(tok[0], tok[0]+1, strlen(tok[0]+1)+1);
      memmove(t, t+1, strlen(t+1)+1);
      f++;
    }
    
    for (cur=alhead;;cur=cur->next)
    {
      if (!cur || !tok[0])
      {
        if (sw && tok[0] && !bc)
        {
          js = 0;
          tok = fxv(tok, cnt, &cnt);
          for (i=0;i<cnt;i++)
          {
            if (*tok[i] == '$' && tok[i][1] && tok[i][1] != '+')
            {
              p = dovars(tok[i]+1);
              if (p)
              {
                free(tok[i]);
                tok[i] = p;
                js = 1;
              }
            }
          } /* for i */
          for (i=0; i<cnt; i++)
          {
            if (*tok[i] == '$' && tok[i][1] == '+' && !tok[i][2])
            {
              if (i > 0 && tok[i-1] && tok[i+1])
              {
                for (x=1;tok[i-x]&&!(*tok[i-x]);x++);
                for (y=1;tok[i+y]&&!(*tok[i+y]);y++);
                if (!tok[i-x])
                  x--;
                if (!tok[i+y])
                  y--;
                tok[i] = (char *)realloc(tok[i], strlen(tok[i-x])+strlen(tok[i+y])+1);
                if (strcmp(tok[i-x], "$+"))
                  strcpy(tok[i], tok[i-x]);
                else
                  *tok[i] = 0;
                if (strcmp(tok[i+y], "$+"))
                  strcat(tok[i], tok[i+y]);
                *tok[i-x] = 0;
                *tok[i+y] = 0;
              }
              else
                *tok[i] = 0;
              js = 1;
            }
          } /* for i */
          if (js)
          {
            memset(nb, 0, sizeof(nb));
            for (i=0;tok[i];i++)
            {
              if (*tok[i])
              {
                strcat(nb, tok[i]);
                strcat(nb, " ");
              }
            }
            nb[strlen(nb)-1] = 0;
            p = ins(b, nb, j-strlen(t), j);
            j = f;
            free(b);
            b = p;
            break;
          }
        } /* if (sw && tok[0] && !bc) */
        if (b[j])
          j++;
        break;
      }
      if (!strcasecmp(tok[0], cur->nm))
      {
        st = doalias(cur, tok, cnt); /* doalias just does the substitution */
        if (!st)
        {
          wp(win, "%s* Error: insufficient parameters%s\n", RED, WHITE);
          drw(win);
          free(b);
          free(st);
          for (i=0;i<cnt;i++)
            free(tok[i]);
          free(tok);
          return(1);
        }
        p = ins(b, st, j-strlen(t), j);
        free(b);
        b = p;
        free(st);
        j = f;
        sw = 1;
        break;
      } /* if */
    } /* for (cur=alhead;;cur=cur->next) */
    
    for (i=0;i<cnt;i++)
      free(tok[i]);
    free(tok);
    
    if (j != f)
    {
      for (i=f,k=0;i<j;i++,k++)
        t[k] = b[i];
      t[k] = 0;
      if (sw)
      {
        if (t[strlen(t)-1] == '|' || t[strlen(t)-1] == '\n')
          t[strlen(t)-1] = 0;
        for (i=strlen(t)-1;t[i]==' '&&i>=0;i--)
          t[i] = 0;
      }
      rn = parseoutn(s, t, win);
      if (rn == -3 || rn == -4 || rn == 2 ) { /* see block comment above */
        free(b);
        return(rn);
      }
      if (bc == 1)
        bc = 0;
    } /* if (j != f) */
  } /* while (1) */
  
  free(b);
  return(1);
}

/* parseoutn: parse and execute a (user) command, but do not do alias
   expansion. Try whether it's a usercmd, irc stuff, or a regular
   built-in command from out[]. */
int parseoutn(int s, char *buf, WINDOW *win)
{
  int i, cnt, r = 0;
  char **tok;
  unsigned char *t = buf, f=0;
  
  tok = form_toks(t, &cnt);
  if (!tok[0])
  {
    for (i=0;i<cnt;i++)
      free(tok[i]);
    free(tok);
    return(1);
  }
  
  if (ircmode && strcasecmp(tok[0], "server"))
  {
    if (!ircsock) {
      for (i=0;i<cnt;i++)
        free(tok[i]);
      free(tok);
      return(1);
    }
    if (!checkouts(ircsock, t, tok, cnt, win))
      f = 1;
    else
    {
      for (i=0;i<cnt;i++)
        free(tok[i]);
      free(tok);
      return(1);
    }
  }
  
  for (i=0;;i++)
  {
    if (out[i].func == NULL)
    {
      if (!r && !f)
      {
        wp(win, "%s* Unknown command: %s. Try /help.%s\n", RED, tok[0], WHITE);
        drw(win);
	return(-3);
      }
      else if (f)
        ssock(ircsock, "%s\n", t);
      for (i=0;i<cnt;i++)
        free(tok[i]);
      free(tok);
      return(1);
    }
    
    if ((!f || out[i].a) && !strcasecmp(tok[0], out[i].nm))
    {
      r = out[i].func(s, t, tok, cnt, win);
      for (i=0;i<cnt;i++)
        free(tok[i]);
      free(tok);
      return(r);
    }
  }
 
  for (i=0;i<cnt;i++)
    free(tok[i]);
  free(tok);

  return(r);
}

/* return a unique connection name for an upload or a download
   connection. Upload connections must start with "u" and download
   connections with "d" (the functions in scheck.c rely on this).
   Never use the same name twice. The result of this function is a
   pointer to a static string, which will be overwritten with the next
   call. t==1 for uploads, t==0 for downloads. */
/* Added t==2 for outgoing direct browse connections (there can be
 * several outgoing direct browse connections (multiple remote clients
 * simultaneously browsing our files), but there can only be one incoming
 * direct browse connection (we can only browse one client at a time) */
char *gnum(int t)
{
  static int count[3] = { 0, 0, 0 };
  static const char *template[3] = {"d %i", "u %i", "b %i"};
  static char buf[10];

  sprintf(buf, template[t], count[t]++);

  return buf;
}

/* skip n space-delimited tokens, and return a copy of the remainder
   of the string. Each call frees the string that was returned by the
   previous call. */
char *cstr(char *str, int n)
{
  static char *ctbuf = NULL;
  int i=0, c;
  
  if (ctbuf)
    free(ctbuf);
  
  while (str[i] == ' ' && str[i])
    i++;
    
  if (!str[i])
  {
    ctbuf = strdup("");
    return(ctbuf);
  }
  
  for (c=0; str[i] && c!=n; i++)
  {
    if (str[i] == ' ')
    {
      c++;
      while (str[i] == ' ' && str[i])
        i++;
      if (c == n)
        break;
      if (!str[i])
        break;
    }
  }
  
  if (!str[i])
    ctbuf = strdup("");
  else
    ctbuf = strdup(str+i);
  
  return(ctbuf);
}

/* concatenates tok[beg]..tok[cnt-1] into a space-delimited string.
   Each call frees the string that was returned by the previous
   call. */
char *cspstr(char **tok, int cnt, int beg)
{
  static char *ctbuf = NULL;
  int size, i;
  
  if (ctbuf)
    free(ctbuf);
  
  if (beg >= cnt)
  {
    ctbuf = strdup("");
    return(ctbuf);
  }
  
  size = 0;
  for (i=beg; i<cnt; i++) {
    size += strlen(tok[i]) + 1;  /* including a space or '\0' */
  }
  ctbuf = (char *)malloc(size);

  strcpy(ctbuf, tok[beg]);

  for (i=beg+1; i<cnt; i++) {
    strcat(ctbuf, " ");
    strcat(ctbuf, tok[i]);
  }
  
  return(ctbuf);
}

/* delete an upload from the upload list. Frees all the resources
   associated with this upload, including deleting any timer set for
   it. Also delete the item's socket, if any. Return -1 if item was
   not in list, else 0. */
int dupload(upload_t *task) 
{ 
  list_unlink(upload_t, up, task);

  if (!task) {  /* ?? not found */
    return(-1);
  }

  free(task->nick);
  free(task->rfn);
  free(task->fn);
  free(task->lfn);
  if (task->state == CONNECTING || task->state == CONNECTING1 || task->state == IN_PROGRESS) {
    if (task->sk) 
      delsock(task->sk->fd);
  }
  if (task->state == IN_PROGRESS) {
    if (task->f)
      fclose(task->f);
  }    
  free(task);
  return(0);
}

/* delete an element from the download task lists. Also close this
   download's file (if any), move the file to the appropriate place or
   delete turds. Also delete the item's socket, if any. Return -1 if
   item was not in list, 0 on success */
int ddownload(WINDOW *win, download_t *task)
{
  int r;

  list_unlink(download_t, down, task);

  if (!task)  /* ?? task was not in the list */
    return(-1);

  if (task->state == IN_PROGRESS) {
    r = interrupt_download(win, task);
    if (r==2) {
      wp(win, "* Removed turd \"%s\" (%d bytes)\n", task->fn, task->pos);
      drw(win);
    }
    if (task->sk)
      delsock(task->sk->fd);
  }

  switch (task->state) {

  case CONNECTING:
    if (task->sk)
      delsock(task->sk->fd);
    /* fall through to next case*/

  case WAITING:
    free(task->check);
    /* fall through to next case */
    
  default:
    free(task->nick);
    free(task->rfn);
    free(task->fn);
    free(task);
  }
  
  return(0);
}

/* move a task from IN_PROGRESS to either INCOMPLETE or COMPLETE,
   depending on which is the case. Free all resources that would be
   inappropriate for a stopped task. I.e., close the download's file
   (if any), move the file to the appropriate place or delete
   turds. However, do not delete the item's socket. Return 1 if an
   incomplete file was saved, 2 if it was a turd, 3 if a complete file
   was saved. */
int interrupt_download(WINDOW *win, download_t *task)
{
  int turdsize;
  int ret = 0;
  int r;

  rqueued_hook(task->nick);

  if (task->f) {
    fclose(task->f);
    turdsize = nvar_default("turdsize", TURDSIZE);
    
    if (task->pos >= task->size) {      /* completed download */
      move_to_downloaddir(win, task);
      task->state = COMPLETE;
      task->d_time = time(0);
      ret = 3;
    } else if ((ssize_t)(task->pos) <= turdsize) {   /* turd */
      r = unlink(task->lfn);
      task->state = INCOMPLETE;
      task->d_time = time(0);
      ret = (r == -1 ? 1 : 2);
    } else {                      /* prepare incomplete for later resume */
      mark_incomplete(win, task);
      task->state = INCOMPLETE;
      task->d_time = time(0);
      ret = 1;
    }
    }
  free(task->lfn);
  free(task->check);
  
  return(ret);
}

/* move a completed download to dir, renaming it as appropriate.
   Return >=0 on success. Return -1 on error with errno set. Return >0
   if the file was renamed. Also, if lfn!=NULL, set *lfn to the new
   filename (on success as well as -1). */
int move_to_dir(WINDOW *win, download_t *task, const char *dir, char **lfnp) {
  char *lfn, *newfn;
  int r, s;

  r = unused_filename(dir, task->fn, NULL, &lfn, &newfn);
  
  s = move_file(task->lfn, lfn);

  free(newfn);
  if (lfnp) {
    *lfnp = lfn;
  } else {
    free(lfn);
  }

  if (s==-1) {
    return(-1);
  }

  return(0);
}

/* move a completed download from the incomplete directory to the
   download directory, renaming it as appropriate. Do this silently
   unless there is an error */
void move_to_downloaddir(WINDOW *win, download_t *task) {
  char *path, *suffix;
  char *lfn;
  int r;

  /* figure out the filename to move it to. */
  path = getval("download");
  /* if no download path given, use CWD, not HOME */
  if (path) {
    path = home_file(path);
  } else {
    path = strdup(".");
  }

  r = move_to_dir(win, task, path, &lfn);
  free(path);

  if (r==0) {
    free(lfn);
    return;
  } else if (r>0) {
    wp(win, "* File \"%s\" exists. Download saved as \"%s\" instead.\n", task->fn, lfn);
    drw(win);    
    free(lfn);
    return;
  } else if (r==-1) {
    wp(win, ""RED"* Error: could not move download to \"%s\": %s"WHITE"\n", lfn, strerror(errno));
    drw(win);
    free(lfn);
  }

  /* if move failed, at least attempt to remove the "incomplete" suffix */
  suffix = getval("incompletesuffix");
  if (!suffix) {
    suffix = ".incomplete";
  }
  path = getval("download");
  if (*suffix && path) {
    path = getval("incomplete");
    /* if no incomplete path given, use CWD, not HOME */
    if (path) {
      path = home_file(path);
    } else {
      path = strdup(".");
    }
    r = move_to_dir(win, task, path, &lfn);
    free(path);

    if (r==-1) {
      free(lfn);
      lfn = strdup(task->lfn);
    }
  } else {
    lfn = strdup(task->lfn);
  }
  wp(win, "* Download saved as \"%s\".\n", lfn);
  drw(win);
  free(lfn);
  
  return;
}

/* add some information to an incomplete file, so that we can later do
   a proper "resume" on that file (if that is ever implemented). The
   idea is to append to the end of the file one or several MD5 hashes.
   If a remote client's (valid) MD5 hash was reported by the server,
   we add that. If the file is long enough to calculate our own MD5
   hashes, then we add them too - two of them in fact, a hash of the
   first 299008 bytes, and one of the first 300032 bytes, since it is
   unclear which one a remote client is going to be using. (We add
   these hashes so that we don't have to do it each time we are
   looking for a match for a potential resume. Task is still
   IN_PROGRESS. */

/* some care needs to be taken because most OpenNap servers do not
   send valid hashes; rather they send the string 0000...(32 times) as
   the "checksum". Of course one should not base a resume on such a
   hash. */

/* In the future, when downloading a file, we will check if any of the
   hashes in the incomplete directory match, and if so, treat it as a
   resume, if not, not. In the far future, we'll also be able to
   request a resume for a specific incomplete file and (gasp) maybe
   even have an autoresume command. */
void mark_incomplete(WINDOW *win, download_t *task) {
  mhdr_t *m;
  itag_t itag;
  int i;

  itag.n = 0;
  itag.fn = task->fn;

  if (task->pos >= 300032) {
    /* we have a chance to calculate a valid md5 ourselves. */
    m = filestats(task->lfn);
    if (m) {
      if (m->sz1 >= 300032) {
	itag.hash[itag.n] = strdup(m->check);
	itag.n++;
      }
      free(m);
    }
  }
  if (task->check && strncmp(task->check, "00000000", 8)) {
    /* have a valid checksum from remote client */
    itag.hash[itag.n] = strdup(task->check);
    itag.n++;
  }
  if (itag.n > 0) {
    tagincompletefile(task->lfn, &itag);
  }
  for (i=0; i<itag.n; i++) {
    free(itag.hash[i]);
  }
}

/* attach an "incomplete tag" at the end of a file. The content of the
 * incomplete tag is that of the itag_s structure, minus the "lfn" and
 * "next" fields. The point is to attach one or several md5 checksums
 * to incomplete files, for convenient reference when resuming later.
 *
 * The format of the tag is: INCTAG_START size n 0 hash1 [ 0 hash2 [ 0
 * hash3 ]] 0 fn 0 n size INCTAG_END, where INCTAG_START and
 * INCTAG_END are defined in defines.h, and size is a 4-byte unsigned
 * integer in big-endian byte order equal to the size of the entire
 * tag. n is the number of hashes represented, as a single byte.  Note
 * that strings are terminated by 0 on both ends, and we give the size
 * and n twice; this way, the tag is conveniently scannable both
 * forward and backward.
 **/

void tagincompletefile(char *fn, itag_t *itag) {
  int size;
  char sz[4];
  FILE *f;
  int i;

  size = 12 + strlen(INCTAG_START) + strlen(INCTAG_END) + strlen(itag->fn);
  for (i=0; i<itag->n; i++) {
    size += 1 + strlen(itag->hash[i]);
  }

  sz[0] = (size >> 24) & 0xff;
  sz[1] = (size >> 16) & 0xff;
  sz[2] = (size >> 8) & 0xff;
  sz[3] = (size >> 0) & 0xff;
  
  f = fopen(fn, "ab");
  if (!f) {
    return;  /* fail silently, since there is not much we can do */
  }
  fputs(INCTAG_START, f);
  fwrite(sz, 4, 1, f);
  fputc(itag->n, f);
  for (i=0; i<itag->n; i++) {
    fputc(0, f);
    fputs(itag->hash[i], f);
  }
  fputc(0, f);
  fputs(itag->fn, f);
  fputc(0, f);
  fputc(itag->n, f);
  fwrite(sz, 4, 1, f);
  fputs(INCTAG_END, f);
 
  fclose(f);
}

void adduser(chans_t *c, char *nm, int scount, unsigned char conn)
{
  user_t *cur, *cur1 = NULL;
  
  if (!c)
    return;
  
  if (!c->users)
  {
    c->users = (user_t *)malloc(sizeof(user_t));
    cur = c->users;
  }
  else
  {
    for (cur=c->users;cur;cur=cur->next)
    {
      if (!strcasecmp(cur->nm, nm))
        return;
      cur1 = cur;
    }
    cur = (user_t *)malloc(sizeof(user_t));
    cur1->next = cur;
  }
  
  cur->nm = strdup(nm);
  cur->addr = NULL;
  cur->conn = conn;
  cur->scount = scount;
  cur->flag = 0;
  cur->next = NULL;
}

/* delete a user from a channel */
void deluser(chans_t *c, char *nm)
{
  user_t *cur, *cur1 = NULL;
  
  for (cur=c->users;;cur=cur->next)
  {
    if (!cur)
      return;
    if (!strcasecmp(cur->nm, nm))
      break;
    cur1 = cur;
  }
  
  if (cur1)
    cur1->next = cur->next;
  else if (cur->next)
    c->users = cur->next;
  else
    c->users = NULL;
  
  free(cur->nm);
  if (cur->addr)
    free(cur->addr);
  free(cur);
}

/* find user in a channel or return NULL if not found */
user_t *finduser(chans_t *c, char *nm)
{
  user_t *cur;

  if (!nm)
    return(NULL);

  for (cur=c->users;;cur=cur->next)
  {
    if (!cur)
      return(NULL);
    if (!strcasecmp(cur->nm, nm))
      return(cur);
  }
}

/* a callback function for timer to run a user command */
void timed_command(void *data) {
  char *cmd = (char *)data;
  int s;
  sock_t *sk;
  
  wp(wchan, ""BRIGHT(BLUE)"* Timed event: "WHITE"%s\n", cmd);
  drw(wchan);
  
  sk = findsock("server");
  s = sk ? sk->fd : -1;

  parseout(s, cmd, wchan);
}

/* find an unused filename based on path/fn/suffix. Return 0 if the
   filename was unchanged, >=1 if it was changed. Set *lfn to the new
   full filename, and set *newfn to the new short filename. Note: we
   do not check whether a file by the given name can actually be
   created (directory exists, is writable, etc) - we simply return the
   first filename which does not yet exist. */

int unused_filename(const char *path, const char *fn, const char *suffix, char **lfn, char **newfn) {
  char *fullname = NULL;
  char *newname = NULL;
  int n;
  char *q;
  struct stat st;
  char *cpath, *base, *suf;

  cpath = strdup(path);
  if (cpath[strlen(cpath)-1] == '/') {
    cpath[strlen(cpath)-1] = 0;  /* cosmetic */
  }
  base = strdup(fn);
  if (suffix && *suffix) {
    suf = strdup(suffix);
  } else {
    q = strrchr(base, '.');
    if (q) {
      suf = strdup(q);
      *q = 0;
    } else {
      suf = strdup("");
    }
  }

  msprintf(&newname, "%s%s", base, suf);
  msprintf(&fullname, "%s/%s", cpath, newname);

  n=0;
  while (stat(fullname, &st)==0) {
    n++;
    msprintf(&newname, "%s-%d%s", base, n, suf);
    msprintf(&fullname, "%s/%s", cpath, newname);
  }

  free(cpath);
  free(base);
  free(suf);

  /* success */
  if (lfn) {
    *lfn = fullname;
  } else {
    free(fullname);
  }
  if (newfn) {
    *newfn = newname;
  } else {
    free(newname);
  }
  return n;
}

/* Opens a new file, whose name is based on fn, in the incomplete
   directory || download directory || cwd.  The file is renamed if a
   file of the exact name already exists. Returns the file *f and its
   name *lfn. Returns 0 on success, -1 on failure with errno set. In
   case -1 is returned, *lfn is also set to the unsuccessful
   filename. */

int opendownloadfile(char *fn, FILE **f, char **lfn) {
  char *path;
  int r;
  char *suffix = getval("incompletesuffix");

  if (!suffix) {
    suffix = ".incomplete";
  }

  path = getval("incomplete");
  if (!path) {
    path = getval("download");
  }
  /* if no download path given, use CWD, not HOME */
  if (path) {
    path = home_file(path);
  } else {
    path = strdup(".");
  }

  r = unused_filename(path, fn, suffix, lfn, NULL);
  free(path);

  *f = fopen(*lfn, "w");
  if (!*f) 
  {
    return(-1);
  }
  
  return(0);
}

/* connects to a napigator-style metaserver and sets the "servers"
   user variable. Return -1, with *errmsg set, on error. Return
   number of servers found on success. */
int metaserver(char *url, int timeout, const char **errmsg) {
  FILE *f;
  char *servers;
  int i, cnt;
  char **tok;
  char *b, *p;
  int count;  /* number of servers */

  f = open_url(url, timeout, errmsg);

  if (!f)
    return -1;

  servers = strdup("");
  count = 0;

  /* parse each line of the file */
  while ((b = nap_getline(f)) != NULL) {

    char *ip = NULL;
    char *port = NULL;

    /* tokenize line */
    tok = form_tokso(b, &cnt);
    free(b);

    /* check the format of the line just read. */
    if (cnt == 7) {
      /* If it consists of 7 tokens, it's in the old server list format.
	 The first token is the IP address, the second is the port. */
      ip = tok[0];
      port = tok[1];
    }
    else if (cnt == 14) {
      /* If it consists of 14 tokens, it's in the new server list format;
	 the second token is the IP address and the third is the port. */
      ip = tok[1];
      port = tok[2];
    }
    else {
      goto nextline;
    }

    for (p=ip; *p; p++) {
      if ((*p < '0' || *p > '9') && *p != '.') 
	goto nextline;
    }
    for (p=port; *p; p++) {
      if (*p < '0' || *p > '9') 
	goto nextline;
    }

    servers = realloc(servers, strlen(servers)+strlen(ip)+strlen(port)+3);
    if (servers[0] != 0)
      strcat(servers, ";");
    strcat(servers, ip);
    strcat(servers, ":");
    strcat(servers, port);
    count++;
    
  nextline:
    for (i=0; i<cnt; i++) {
      free(tok[i]);
    }
    free(tok);
  } /* while */

  fclose(f);

  if (!count) {
    *errmsg = "invalid server list";
    free(servers);
    return -1;
  }

  chset("servers", servers);
  free(servers);

  return count;
}

/* decompose url into host, port, and path. Return 0 on success.
   Return allocated strings. Pointers may be NULL, in which case the
   corresponding result is not returned. */
int decompose_url(const char *url, char **hostp, int *portp, char **pathp) {
  char *p;
  char *host;
  char *path;
  int port;

  if (strncmp(url, "http://", 7) == 0) {
    url += 7;
  }

  p = strchr(url, '/');
  if (!p) {
    path = strdup("/");
    host = strdup(url);
  } else {
    path = strdup(p);
    host = strdup(url);
    host[p-url] = 0;
  }

  p = strchr(host, ':');

  if (p) {
    *p = 0;
    port = atoi(p+1);
  } else {
    port = 80;   /* standard HTTP port */
  }

  if (hostp) {
    *hostp = host;
  } else {
    free(host);
  }

  if (portp) {
    *portp = port;
  }

  if (pathp) {
    *pathp = path;
  } else {
    free(path);
  }

  return 0;
}

/* opens the given http-url for reading. Skips HTTP headers. Optional
   "http://" at beginning of url is skipped.  On error, return NULL
   with *errmsg set to a (static) error string. Otherwise, return file
   open for reading. */

FILE *open_url(const char *url, int timeout, const char **errmsg) {
  char *path;
  char *host;
  int port;
  int r, s;
  struct sockaddr_in dst;
  FILE *f;
  char *b;
  int i;
  char **tok;
  int cnt;
  char *proxy;
  char *urlhost;

  proxy = getval("proxy");

  if (proxy) {
    decompose_url(proxy, &host, &port, NULL);
    decompose_url(url, &urlhost, NULL, NULL);
    path = strdup(url);
  } else {
    decompose_url(url, &host, &port, &path);
    urlhost = strdup(host);
  }

  /* now, connect to host:port with path */

  s = socket(AF_INET, SOCK_STREAM, 0);

  dst.sin_port = htons(port);
  dst.sin_family = AF_INET;

  r = resolve(host, &dst.sin_addr);
  if (r==0) {
    *errmsg = hstrerror(h_errno);
    return NULL;
  }
  r = connect_t(s, (struct sockaddr *)&dst, sizeof(dst), timeout);
  if (r==-1) {
    *errmsg = strerror(errno);
    return NULL;
  }

  setkeepalive(s);

  r = ssock(s, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path, urlhost);
  if (r==-1) {
    *errmsg = strerror(errno);
    return NULL;
  }

  free(host);
  free(path);

  f = fdopen(s, "r");

  b = nap_getline(f);   /* expect something like "HTTP/1.1 200 OK" */
  if (!b) {
    fclose(f);
    *errmsg = "empty document";
    return NULL;
  }
  tok = form_tokso(b, &cnt);
  if (cnt<2) {
    for (i=0; i<cnt; i++)
      free(tok[i]);
    free(tok);
    free(b);
    fclose(f);
    *errmsg = "ill-formed document";
    return NULL;
  }
  if (atoi(tok[1]) != 200) {            /* HTTP error */
    for (i=0; i<cnt; i++)
      free(tok[i]);
    free(tok);
    fclose(f);
    *errmsg = cstr(b, 1);
    free(b);
    return NULL;
  }
  free(b);

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

  /* skip remaining HTTP headers */
  while ((b = nap_getline(f)) != NULL) {
    if (*b==0)
      break;
    free(b);
  }

  free(b);
  
  return f;
}

/* format a time as a string, for instance, "1h 5m 24s". The time is
   given in seconds. Return a statically allocated string. */
char *format_time(time_t t) {
  static char buf[100];

  time_t s   = t>=0 ? t : -t;
  char *sign = t>=0 ? "" : "-";

  if (s>=3600) {
    sprintf(buf, "%s%ldh %ldm %lds", sign, s/3600, (s/60)%60, s%60);
  } else if (s>=60) {
    sprintf(buf, "%s%ldm %lds", sign, (s/60)%60, s%60);
  } else {
    sprintf(buf, "%s%lds", sign, s%60);
  }
  return buf;
}

/* ---------------------------------------------------------------------- */
/* begin user commands */

O_NAP_FUNC(dquit)
{
  int n, m;
  download_t *dtask;
  upload_t *utask;

  if (num > 1 && !strcasecmp(tok[1], "yes")) {
    quit_now = 1;
    return(-3); /* jump out of all nested loops */
  }
  list_count(down, n, dtask, !STOPPED(dtask->state));
  list_count(up, m, utask, !STOPPED(utask->state));

  if (m || n)
  {
    wp(win, m+n==1 ? ""RED"* There is " : ""RED"* There are ");
    if (m) {
      wp(win, m==1 ? "%d upload" : "%d uploads", m);
    }
    if (m && n) {
      wp(win, " and ");
    }
    if (n) {
      wp(win, n==1 ? "%d download" : "%d downloads", n);
    }
    wp(win, " pending. Use \"/quit yes\" if you want to quit immediately, and \"/tquit\" if you want to quit after pending transfers have finished."WHITE"\n");
    drw(win);
    return(1);
  }
  quit_now = 1;
  return(-3);   /* return to top level */
}

/* conditional quit: quit when all pending transfers have been completed */
O_NAP_FUNC(dtquit)
{
  int n, m;
  download_t *dtask;
  upload_t *utask;

  quit_after_transfers = 1;

  list_count(down, n, dtask, !STOPPED(dtask->state));
  list_count(up, m, utask, !STOPPED(utask->state));

  wp(win, ""RED"* Scheduled to quit after %d uploads and %d downloads have completed."WHITE"\n", m, n);
  drw(win);

  /* note that a tquit does not cause execution of a macro to be aborted. */
  return(1);
}

O_NAP_FUNC(dunquit) 
{
  quit_after_transfers = 0; 
  
  wp(win, "* Effect of /tquit was canceled\n");
  drw(win);
  return(1);
}

O_NAP_FUNC(djoin)
{
  chans_t *t;

  if (num < 2)  /* no argument: print list of channels */
  {
    sendpack(s, CHANNEL_LIST2, NULL);
  
    wp(win, ""BOLD"Channel"WHITE" | Users | Topic\n");
    wp(win, "-----------------------\n");
  
    return(1);
  }
  
  /* else join the channel */

  t = findchan(chanl, tok[1]);
  if (t && !t->p)
  {
    curchan = t;
    return(1);
  }
                  
  sendpack(s, NAP_JOIN, "%s", tok[1]);
  
  return(1);
}

O_NAP_FUNC(dpart)
{
  chans_t *pt;

  if (num == 1)
    pt = curchan;
  else
    pt = findchan(chanl, tok[1]);
  
  if (!pt && num > 1)
  {
    wp(win, "%s* You are not on channel %s%s\n", RED, tok[1], WHITE);
    drw(win);
    return(1);
  }
  else if (!pt)
  {
    wp(win, "%s* You are not on a channel %s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  if (!pt->q)
  {
    sendpack(s, NAP_PART, "%s", pt->nm);
    pt->p |= 1;
    return(1);
  }
  else if (pt->q == 2)
  {
    ssock(ircsock, "PART %s\n", pt->nm);
    pt->p |= 1;
    return(1);
  }

  wp(win, "%s* Ended query with %s%s\n", GREEN, pt->nm, WHITE);
  drw(win);
  
  /* remove the channel pt from the channel list */

  delchan(pt);

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

void usage(WINDOW *win, char *cmd) 
{
  int i;
  char *help, *p;

  for (i=0; out[i].nm; i++) {
    if (!strcasecmp(out[i].nm, cmd)) {
      break;
    }
  }
  if (!out[i].nm || !out[i].help) {
    wp(win, ""RED"* /%s: Invalid usage (no help available)"WHITE"\n", cmd);
  } else {
    help = strdup(out[i].help);
    p = strstr(help, " - ");
    if (p) {
      *p = 0;
    }
    wp(win, ""RED"* Usage: /%s %s"WHITE"\n", cmd, help);
    free(help);
  }
  drw(win);
}

O_NAP_FUNC(dtell)
{
  char *msg;
  if (num < 3)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  msg = fixquotes(cstr(str, 2));
  sendpack(s, NAP_TELL, "%s %s", tok[1], msg);
  recent = findquery(chanl, tok[1]);
  wp(win, "%s* --> (%s%s%s)%s %s\n", GREEN, WHITE, tok[1], GREEN, WHITE, msg);
  drw(win);

  recent = NULL;
  return(1);
}

O_NAP_FUNC(dwhois)
{
  if (num < 2)
    sendpack(s, NAP_WHOIS, "%s", info.user);
  else
    sendpack(s, NAP_WHOIS, "%s", tok[1]);
  return(1);
}

O_NAP_FUNC(dfup)
{
  stringlist_t *l = NULL;  /* a list of nicks to avoid duplicate requests */
  stringlist_t *elt;
  upload_t *task;
  int n;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  for (n=parse_range(cstr(str, 1)); n!=-1; n=parse_range(NULL)) {

    /* find the n-th element in the upload list */
    list_nth(task, up, n-1);

    if (!task) {
      wp(win, "* Upload number %d does not exist\n", n);
      drw(win);
    } else {
      list_find(elt, l, !strcasecmp(elt->s, task->nick));
      if (!elt) {
	wp(win, "* Requesting information on %s\n", task->nick);
	drw(win);
	sendpack(s, NAP_WHOIS, "%s", task->nick);
	elt = (stringlist_t *)malloc(sizeof(stringlist_t));
	elt->s = task->nick;
	if (elt) {
	  list_prepend(l, elt);
	}
      }
    }
  }
  /* clear the list */
  list_forall_unlink(elt, l) {
    free(elt);
  }

  return(1);
}

O_NAP_FUNC(dfdown)
{
  stringlist_t *l = NULL;  /* a list of nicks to avoid duplicate requests */
  stringlist_t *elt;
  download_t *task;
  int n; 
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }

  for (n=parse_range(cstr(str, 1)); n!=-1; n=parse_range(NULL)) {
  
    /* find the n-th element in the download list */
    list_nth(task, down, n-1);

    if (!task) {
      wp(win, "* Download number %d does not exist\n", n);
      drw(win);
    } else {
      list_find(elt, l, !strcasecmp(elt->s, task->nick));
      if (!elt) {
	wp(win, "* Requesting information on %s\n", task->nick);
	drw(win);
	sendpack(s, NAP_WHOIS, "%s", task->nick);
	elt = (stringlist_t *)malloc(sizeof(stringlist_t));
	elt->s = task->nick;
	if (elt) {
	  list_prepend(l, elt);
	}
      }
    }
  }
  return(1);
}

/* parses a range of non-negative numbers, e.g. "1,3-4,5" or "1 3-4
   5". The first time this is called, RANGE should be a string
   representing the range of numbers to be parsed. Each subsequent
   call should pass RANGE=NULL. Each call will return the next
   available integer, or -1 when done. */
int parse_range(char *range) {
  static char *ran=NULL;  /* next subrange */
  static int i=0;         /* next number to return */
  static int e=-1;         /* last number in current subrange to return */

  if (range) {
    ran = strtok(range, ", ");
    i = 0;
    e = -1;
  }
  while (1) {
    if (i<=e) {
      i++;
      return i-1;
    }
    if (!ran)
      return -1;
    if (*ran && strchr(ran+1, '-')) /* assume token is a subrange */
      sscanf(ran, "%i-%i", &i, &e);
    else                            /* token is a single number */
      i = e = atoi(ran);
    if (i<0) 
      i=0;
    ran = strtok(NULL, ", ");
  }
}

/* get files by number e.g. "/g 1,3-4,5" or "/g 1 3-4 5" */
O_NAP_FUNC(dg)
{
  ssearch_t *cur=search;
  int n;
  int r;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (!search)
  {
    wp(win, "%s* No search performed yet%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  for (n = parse_range(cstr(str, 1)); n != -1; n = parse_range(NULL)) {
    list_nth(cur, search, n-1);
    if (cur == NULL) {   /* no matching item */
      wp(win, ""RED"* Error: no search result numbered %d"WHITE"\n", n);
      drw(win);
      return(1); /* skip rest of range */
    }
    r = requestdownload(s, cur);
    
    switch(r) {
    case 0: 
      wp(win, "* Getting \"%s\" from %s\n", cur->song, cur->nick);
      break;
    case 1: case 2:
      wp(win, "* Queued download of \"%s\" from %s\n", cur->song, cur->nick);
      break;
    case 3:
      wp(win, "* Remotely queued download of \"%s\" from %s\n", cur->song, cur->nick);
      break;
    case -1: 
      wp(win, "* Already getting \"%s\" from %s\n", cur->song, cur->nick);
      break;
    case -2:
      wp(win, "* Download of \"%s\" from %s is already queued\n", cur->song, cur->nick);
      break;
    case -3:
      wp(win, "* You can't get \"%s\" from yourself\n", cur->song);
      break;
    default:
      break;
    }
    drw(win);
  }
  
  drw(win);
  
  return(1);
}

/* request to download the given search item. Returns: 0 on success,
   -1 if we are already getting this item, -2 if the item is already
   queued, -3 if we can't get this item because we are the user
   offering it, 1 if this item was queued because the per-user
   download limit was reached, 2 if it was queued because the overall
   download limit was reached, 3 if it was rqueued because there is
   already an rqueued or rrqueued item by this nick. */

int requestdownload(int s, ssearch_t *sitem) {
  download_t *task;
  int r;

  /* see if the user offering this item is us */
  if (!strcasecmp(sitem->nick, info.user)) {
    return -3;
  }

  /* create item for the download list */
  task = (download_t *)malloc(sizeof(download_t));
  task->nick = strdup(sitem->nick);
  task->rfn = strdup(sitem->rfn);
  task->fn = strdup(sitem->song);

  r = retrydownload(s, task);

  /* failure? */
  if (r==-1 || r==-2) {
    free(task->nick);
    free(task->rfn);
    free(task->fn);
    free(task);
    return r;
  }

  /* else: append item to the download list */
  list_append(download_t, down, task);
  return r;
}

/* request to download the given search item. Returns: 0 on success,
   -1 if we are already getting this item, -2 if the item is already
   queued, 1 if this item was queued because the per-user download
   limit was reached, 2 if it was queued because the overall download
   limit was reached, 3 if it was rqueued because there is already an
   rqueued or rrqueued item by this nick. */

int retrydownload(int s, download_t *dl) {
  download_t *elt;
  int r;

  /* see if we're already getting this item -- this is possible
     because a stopped item can occur several times in the download
     list */

  list_find(elt, down, !strcasecmp(elt->nick, dl->nick) && !strcasecmp(elt->fn, dl->fn) && !STOPPED(elt->state));
  
  if (elt) {
    if (elt->state == QUEUED || elt->state == RQUEUED)
      return -2;
    else
      return -1;
  }

  /* check whether we will queue or request this item */
  r = download_limit_reached(dl->nick);

  if (r) {
    dl->state = QUEUED;
    return r;
  } else {

    /* check whether there is already an RQUEUED or RRQUEUED item by
       this nick */
    list_find(elt, down, !strcasecmp(dl->nick, elt->nick) && (elt->state == RQUEUED || elt->state == RRQUEUED));
    if (elt) {
      dl->state = RQUEUED;
      dl->r_time = time(0);
      return 3;
    } else {
      sendpack(s, NAP_DGET, "%s \"%s\"", dl->nick, dl->rfn);
      dl->state = REQUESTED;
      dl->c_time = time(0);
      return 0;
    }
  }
}

/* check whether the download limit for this nick is currently
   reached. Return 0 if not reached, 2 if overall limit reached, 1 if
   per-user limit reached */
int download_limit_reached(char *nick) {
  download_t *elt;
  int maxd = nvar("maxdownloads");
  int maxdu = nvar("maxdownuser");
  int n;

  /* see if the overall download limit is reached */
  list_count(down, n, elt, ACTIVE(elt->state));
  if (maxd >= 0 && n >= maxd) {
    return 2;
  }

  /* see if the per-user download limit is reached */
  list_count(down, n, elt, !strcasecmp(elt->nick, nick) && ACTIVE(elt->state));
  if (maxdu >= 0 && n >= maxdu) {
    return 1;
  }
  return 0;
}

/* check whether there is an earlier task than dtask in the download
   queue which belongs to the same nick and is RQUEUED or RRQUEUED,
   and which is eligible for immediate download. */
int nick_has_earlier_rqueued_task(download_t *dtask, time_t t) {
  download_t *elt;

  list_forall(elt, down) {
    if (elt==dtask)
      return 0;
    if (!strcasecmp(elt->nick, dtask->nick) && 
	((elt->state == RQUEUED && t-elt->r_time >= 15) || elt->state == RRQUEUED))
      return 1;
  }
  return 0;
}

/* check whether the upload limit for this nick is currently
   reached. Return 0 if not reached, 2 if overall limit reached, 1 if
   per-user limit reached */
int upload_limit_reached(char *nick) {
  upload_t *elt;
  int maxu = nvar("maxuploads");
  int maxuu = nvar("maxupuser");
  int n;

  /* see if the overall upload limit is reached */
  list_count(up, n, elt, ACTIVE(elt->state));
  if (maxu >= 0 && n >= maxu) {
    return 2;
  }

  /* see if the per-user upload limit is reached */
  list_count(up, n, elt, !strcasecmp(elt->nick, nick) && ACTIVE(elt->state));
  if (maxuu >= 0 && n >= maxuu) {
    return 1;
  }
  return 0;
}

/* force queued or stopped item to be downloaded, overriding any
   download limit. Return 0 on success, or -1 if the item was already
   being downloaded. */
int forcedownload(int s, download_t *dl) { 
  if (dl->state == RRQUEUED) { /* we've already requested this item from
	  the server, so this is just a change of label and verbosity */
    dl->state = REQUESTED;
    return 0;
  }
  
  if (ACTIVE(dl->state)) {
    return -1;
  }
  
  /* otherwise, go ahead request the download */
  sendpack(s, NAP_DGET, "%s \"%s\"", dl->nick, dl->rfn);
  dl->state = REQUESTED;
  dl->c_time = time(0);
  return 0;
}

/* "/force <range>": force queued items to be downloaded, overriding
   any download limit, or cause a stopped item to be downloaded. */
O_NAP_FUNC(dforce)
{
  download_t *dl;
  int n;
  int r;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  for (n=parse_range(cstr(str, 1)); n!=-1; n=parse_range(NULL)) {

    /* find the n-th element in the download list */
    list_nth(dl, down, n-1);
    
    if (!dl) {
      wp(win, "* Download number %d does not exist\n", n);
      drw(win);
      continue;
    }
    
    r = forcedownload(s, dl);
    switch (r) {
    case 0: 
      wp(win, "* Getting \"%s\" from %s\n", dl->fn, dl->nick);
      drw(win);
      break;
    case -1: default:
      wp(win, "* Already getting \"%s\" from %s\n", dl->fn, dl->nick);
      drw(win);
      break;
    }
  }
  return(1);
}

/* "/retry <range>": put stopped items back in the queue. This only
   applies to items that are FAILED, INCOMPLETE, or TIMED_OUT */

O_NAP_FUNC(dretry)
{
  download_t *task;
  int n;
  int r;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  for (n=parse_range(cstr(str, 1)); n!=-1; n=parse_range(NULL)) {

    /* find the n-th element in the download list */
    list_nth(task, down, n-1);
    
    if (!task) {
      wp(win, "* Download number %d does not exist\n", n);
      drw(win);
      continue;
    }
    
    if (task->state == COMPLETE) {
      wp(win, "* Download \"%s\" from %s is already finished\n", task->fn, task->nick);
      drw(win);
      continue;
    }
    
    /* try to re-request the item */
    r = retrydownload(s, task);

    switch(r) {
    case 0: 
      wp(win, "* Getting \"%s\" from %s\n", task->fn, task->nick);
      break;
    case 1: case 2:
      wp(win, "* Queued download of \"%s\" from %s\n", task->fn, task->nick);
      break;
    case 3:
      wp(win, "* Remotely queued download of \"%s\" from %s\n", task->fn, task->nick);
      break;
    case -1: 
      wp(win, "* Already getting \"%s\" from %s\n", task->fn, task->nick);
      break;
    case -2:
      wp(win, "* Download of \"%s\" from %s is already queued\n", task->fn, task->nick);
      break;
    default:
      break;
    }
    drw(win);
  }
  return(1);
}

/* "/retryall": put all stopped items back in the queue. This only
   applies to items that are FAILED, INCOMPLETE, or TIMED_OUT */

O_NAP_FUNC(dretryall)
{
  download_t *task;
  int n=0;
  int r;

  list_forall(task, down) {
    
    if (ACTIVE(task->state) || task->state == QUEUED 
	|| task->state == RQUEUED || task->state == COMPLETE) {
      continue;
    }
    
    /* try to re-request the item */
    r = retrydownload(s, task);

    switch(r) {
    case 0: 
      wp(win, "* Getting \"%s\" from %s\n", task->fn, task->nick);
      n++;
      break;
    case 1: case 2:
      wp(win, "* Queued download of \"%s\" from %s\n", task->fn, task->nick);
      n++;
      break;
    case 3:
      wp(win, "* Remotely queued download of \"%s\" from %s\n", task->fn, task->nick);
      n++;
      break;
    case -1: case -2: default:
      break;  /* can't get this item - ignore silently */
    }
    drw(win);
  }

  if (!n) {
    wp(win, "* Nothing to retry\n");
    drw(win);
  }
  
  return(1);
}

O_NAP_FUNC(dpup)
{
  int count, c, d, p, max;
  upload_t *task;
  float rate;
  int bytes, togo;
  time_t tm, eta;

  wp(win, "* Uploads:\n");
  drw(win);

  count = 0;
  c = d = 0;
  list_forall (task, up) {
    count++;
    wp(win, ""BOLD"%i."WHITE" ", count);
    switch (task->state) {
    case WAITING:
      c++;
      wp(win, "Waiting | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      break;
    case CONNECTING: case CONNECTING1: /* note: this state always firewalled */
      c++;
      wp(win, "Connecting | %s (firewalled) | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      break;
    case IN_PROGRESS:
      c++;
      p = (int)(100*(float)task->pos/(float)task->size);
      bytes = task->pos - task->bsz;
      togo = task->size - task->pos;
      tm = time(0) - task->p_time;
      rate = tm ? bytes / 1024.0 / tm : 0.0;
      eta = bytes > 10000 ? ((double)tm) * togo / bytes : -1;
      wp(win, "Sending | %s | "BOLD"%s"WHITE" at %.2f k/s (%i%%)", task->nick, task->fn, rate, p); 
      if (eta>=0) {
	wp(win, " | Left: %s", format_time(eta));
      }
      wp(win, "\n");
      break;
    case FAILED:
      d++;
      wp(win, "Failed | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      break;
    case INCOMPLETE:
      d++;
      wp(win, "Interrupted | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      break;
    case COMPLETE:
      d++;
      wp(win, "Finished | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      break;
    case TIMED_OUT:
      d++;
      wp(win, "Timed out | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      break;
    default:
      break;
    }
  }
  max = nvar("maxuploads");
  if (max>=0) {
    wp(win, ""BOLD"%i uploading (limit %i), %i stopped"WHITE"\n", c, max, d);
  } else {
    wp(win, ""BOLD"%i uploading, %i stopped"WHITE"\n", c, d);
  }
  drw(win);
  return(1);
}

/* print download list and download queue, numbering the items in the order
 * they appear in the lists. */
O_NAP_FUNC(dpdown)
{
  int count, c, q, d, p, max;
  download_t *task;
  float rate;
  int bytes, togo;
  time_t tm, eta;
  char *ch;
  int sd, sf, ss, sq;

  if (num == 2) {
    sd = ss = sq = sf = 0;
    ch = tok[1];
    while (*ch) {
      switch (*ch) {
      case 'd':
	sd = 1;
	break;
      case 's':
	ss = 1;
	break;
      case 'q':
	sq = 1;
	break;
      case 'f':
	sf = 1;
	break;
      default:
	usage(win, tok[0]);
	return(-3);
	break;
      }
      
      ch++;
    }
  } else if (num == 1) {
    sd = ss = sq = sf = 1;
  } else {
    usage(win, tok[0]);
    return(-3);
  }

  wp(win, "* Downloads:\n");
  drw(win);

  count = 0;
  c = q = d = 0;
  list_forall (task, down) {
    count++;
    switch (task->state) {
    case QUEUED:
      q++;
      if (sq) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Queued | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    case RQUEUED: case RRQUEUED:
      q++;
      if (sq) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Remotely queued | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    case REQUESTED:
      c++;
      if (sd) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Requested | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    case WAITING:
      c++;
      if (sd) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Waiting | %s%s | "BOLD"%s"WHITE"\n", task->nick, task->addr.sin_port ? "" : " (firewalled)", task->fn);
      }
      break;
    case CONNECTING:
      c++;
      if (sd) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Connecting | %s%s | "BOLD"%s"WHITE"\n", task->nick, task->addr.sin_port ? "" : " (firewalled)", task->fn);
      }
      break;
    case IN_PROGRESS:
      c++;
      if (sd) {
	p = (int)(100*(float)task->pos/(float)task->size);
	bytes = task->pos - task->bsz;
	togo = task->size - task->pos;
	tm = time(0) - task->p_time;
	rate = tm ? bytes / 1024.0 / tm : 0.0;
	eta = bytes > 10000 ? ((double)tm) * togo / bytes : -1;
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Getting | %s%s | "BOLD"%s"WHITE" at %.2f k/s (%i%%)", task->nick, task->addr.sin_port ? "" : " (firewalled)", task->fn, rate, p); 
	if (eta>=0) {
	  wp(win, " | Left: %s", format_time(eta));
	}
	wp(win, "\n");
      }
      break;
    case FAILED:
      d++;
      if (sf) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Failed | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    case INCOMPLETE:
      d++;
      if (sf) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Interrupted | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    case COMPLETE:
      d++;
      if (ss) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Finished | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    case TIMED_OUT:
      d++;
      if (sf) {
	wp(win, ""BOLD"%i."WHITE" ", count);
	wp(win, "Timed out | %s | "BOLD"%s"WHITE"\n", task->nick, task->fn);
      }
      break;
    default:
      break;
    }
  }
  max = nvar("maxdownloads");
  if (max>=0) {
    wp(win, ""BOLD"%i downloading (limit %i), %i queued, %i stopped"WHITE"\n", c, max, q, d);
  } else {
    wp(win, ""BOLD"%i downloading, %i queued, %i stopped"WHITE"\n", c, q, d);
  }
  drw(win);
  return(1);
}

O_NAP_FUNC(dsearch)
{
  ssearch_t *cur;
  char *brate, *freq, *speed, *size, *len, *mime, *xclu, *keys;
  int c, max, l, i;
  
  if (s==-1)
  {
    wp(win, ""RED"* Not connected to server"WHITE"\n");
    drw(win);
    return(1);
  }
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (srch)
  {
    wp(win, "%s* Another search is in progress%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  brate = freq = speed = size = len = mime = xclu = keys = NULL;

  opterr = 0;
  max = 250;
  optind = 0;
  fl = 0;
  l = 0;
  noping = nvar_default("noping", 0);

  while ((c = getopt(num, tok, "lfpb:c:r:s:m:x:d:t:")) != -1) {
    switch (c) {
    case 'f':
      fl = 1;
      break;
    case 'l':
      l = 1;
      break;
    case 'p':
      noping = !noping;
      break;
    case 'b':
      if (*optarg == '=') {
	msprintf(&brate, " BITRATE \"EQUAL TO\" \"%s\"", optarg+1);
      } else if (*optarg == '<') {
	msprintf(&brate, " BITRATE \"AT BEST\" \"%s\"", optarg+1);
      } else if (*optarg == '>') {
	msprintf(&brate, " BITRATE \"AT LEAST\" \"%s\"", optarg+1);
      } else {
	msprintf(&brate, " BITRATE \"AT LEAST\" \"%s\"", optarg);
      }
      break;
    case 'c':
      if (*optarg == '=') {
	msprintf(&speed, " LINESPEED \"EQUAL TO\" %s", optarg+1);
      } else if (*optarg == '<') {
	msprintf(&speed, " LINESPEED \"AT BEST\" %s", optarg+1);
      } else if (*optarg == '>') {
	msprintf(&speed, " LINESPEED \"AT LEAST\" %s", optarg+1);
      } else {
	msprintf(&speed, " LINESPEED \"AT LEAST\" %s", optarg);
      }
      break;
    case 'r':
      if (*optarg == '=') {
	msprintf(&freq, " FREQ \"EQUAL TO\" \"%s\"", optarg+1);
      } else if (*optarg == '<') {
	msprintf(&freq, " FREQ \"AT BEST\" \"%s\"", optarg+1);
      } else if (*optarg == '>') {
	msprintf(&freq, " FREQ \"AT LEAST\" \"%s\"", optarg+1);
      } else {
	msprintf(&freq, " FREQ \"AT LEAST\" \"%s\"", optarg);
      }
      break;
    case 's':
      if (*optarg == '=') {
	msprintf(&size, " SIZE \"EQUAL TO\" \"%s\"", optarg+1);
      } else if (*optarg == '<') {
	msprintf(&size, " SIZE \"AT BEST\" \"%s\"", optarg+1);
      } else if (*optarg == '>') {
	msprintf(&size, " SIZE \"AT LEAST\" \"%s\"", optarg+1);
      } else {
	msprintf(&size, " SIZE \"AT LEAST\" \"%s\"", optarg);
      }
      break;
    case 'd':
      if (*optarg == '=') {
	msprintf(&len, " DURATION \"EQUAL TO\" \"%s\"", optarg+1);
      } else if (*optarg == '<') {
	msprintf(&len, " DURATION \"AT BEST\" \"%s\"", optarg+1);
      } else if (*optarg == '>') {
	msprintf(&len, " DURATION \"AT LEAST\" \"%s\"", optarg+1);
      } else {
	msprintf(&len, " DURATION \"AT LEAST\" \"%s\"", optarg);
      }
      break;
    case 't':
      msprintf(&mime, " TYPE %s", optarg);
      break;
    case 'x':
      if (!xclu) {
	msprintf(&xclu, " FILENAME EXCLUDES \"%s\"", optarg);
      } else {
	char *tmp = NULL;
	msprintf(&tmp, "%s FILENAME EXCLUDES \"%s\"", xclu, optarg);
	free(xclu);
	xclu = tmp;
      }
    case 'm':
      max = atoi(optarg);
      break;
    default:
      break;
    }
  }
  
  for (i=optind; i<num; i++) {
    if (!keys) {
      msprintf(&keys, "%s", tok[i]);
    } else {
      char *tmp = NULL;
      msprintf(&tmp, "%s %s", keys, tok[i]);
      keys = tmp;
    }
  }

  if (keys) {
    wp(win, "* Searching for \"%s\"...\n", cstr(str, optind));
    drw(win);

    srch = 1;
  
    /* free the current search results */
    list_forall_unlink(cur, search) {
      free(cur->song);
      free(cur->rfn);
      free(cur->nick);
      free(cur);
    }
  
    sendpack(s, NAP_SEARCH, "FILENAME CONTAINS \"%s\" MAX_RESULTS %i%s%s%s%s%s%s%s%s", keys, max, l ? " LOCAL_ONLY" : "", xclu ? xclu : "", speed ? speed : "", brate ? brate : "", freq ? freq : "", size ? size : "", len ? len : "", mime ? mime : "");
  } else {
    usage(win, tok[0]);
    free(speed);
    free(brate);
    free(freq);
    free(size);
    free(len);
    free(mime);
    free(xclu);
    free(keys);
    return -3;
  }

  free(speed);
  free(brate);
  free(freq);
  free(size);
  free(len);
  free(mime);
  free(xclu);
  free(keys);

  return(1);
}

O_NAP_FUNC(dpvars)
{
  wp(win, "* User variables:\n");
  drw(win);

  printsets(win);
  return(1);
}

O_NAP_FUNC(dkill)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (num == 2)
    sendpack(s, NAP_KILL, "%s", tok[1]);
  else
    sendpack(s, NAP_KILL, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(dban)
{
  if (num < 2)  /* no argument: list banned users */
  {
    wp(win, "* Banned users:\n");
    drw(win);

    sendpack(s, NAP_DBANLIST, "%s", cstr(str, 1));
  
    return(1);
  }

  /* else, ban a user */
  if (num == 2)
    sendpack(s, NAP_BAN, "%s", tok[1]);
  else
    sendpack(s, NAP_BAN, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(dunban)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (num == 2)
    sendpack(s, NAP_UNBAN, "%s", tok[1]);
  else
    sendpack(s, NAP_UNBAN, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(ddatap)
{
  if (num < 3)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sendpack(s, NAP_DATAP, "%s %i", tok[1], atoi(tok[2]));
  
  return(1);
}

O_NAP_FUNC(dtopic)
{
  chans_t *cur;

  if (num == 1)
  {
    if (!curchan)
    {
      wp(win, "%s* Error: You're not on channel%s\n", RED, WHITE);
      drw(win);
      return(1);
    }
    wp(win, "%s* Topic for %s: %s%s\n", YELLOW, curchan->nm, curchan->topic, WHITE);
    drw(win);
    return(1);
  }
  else if (num == 2)
  {
    cur = findchan(chanl, tok[1]);
    if (!cur)
    {
      wp(win, "%s* Error: Not on channel %s%s\n", RED, tok[1], WHITE);
      drw(win);
      return(1);
    }
    wp(win, "%s* Topic for %s: %s%s\n", YELLOW, cur->nm, cur->topic, WHITE);
    drw(win);
    return(1);
  }
      
  sendpack(s, NAP_TOPIC, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dlevel)
{
  if (num < 3)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sendpack(s, NAP_LEVEL, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dopsay)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sendpack(s, NAP_SOP, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(ddns)
{
  struct hostent *d1, *d2;
  struct sockaddr_in in;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  wp(win, "* Resolving %s...\n", tok[1]);
  drw(win);

  if (!strchr(tok[1], '.'))
  {
    dwi = strdup(tok[1]);
    sendpack(s, NAP_WHOIS, "%s", dwi);
    return(1);
  }
  
  if (fork())
    return(1);
  
  d1 = gethostbyname(tok[1]);
  if (!d1)
  {
    ssock(ipcs[1], "* Unable to resolve %s\n", tok[1]);
    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[1], inet_ntoa(in.sin_addr));
    exit(1);
  }
  
  ssock(ipcs[1], "* %s is %s [%s]\n", tok[1], d2->h_name, inet_ntoa(in.sin_addr));
  
  exit(1);
}

O_NAP_FUNC(dannounce)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sendpack(s, NAP_ANNOUNCE, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dpchans)
{
  chans_t *cur;
  
  wp(win, "* Channels:\n");
  drw(win);

  if (!chanl)
    wp(win, "No channels\n");
  else if (curchan)
    wp(win, "Current: %s\n", curchan->nm);
  else
    wp(win, "Current: None\n");
  for (cur=chanl;cur!=NULL;cur=cur->next)
    wp(win, "%s\n", cur->nm);
  
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dbanlist)
{
  wp(win, "* Banned users:\n");
  drw(win);

  sendpack(s, NAP_DBANLIST, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dlspeed)
{
  if (num < 3)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sendpack(s, NAP_LSCHANGE, "%s %s", tok[1], tok[2]);
  
  return(1);
}

O_NAP_FUNC(dpsocks)
{
  wp(win, "* Sockets:\n");
  drw(win);

  psocks(win);
  
  return(1);
}

/* /dup <range> - delete an entry from the upload list */
O_NAP_FUNC(ddup)
{
  upload_t *task;
  int count, n;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  /* as we are deleting items, their relative numbering also changes.
     To avoid strange effects, we first number all the items in the
     list, and then delete them by number, rather than by position */

  count = 0;
  list_forall(task, up) {
    count++;
    task->tmp = count;
  }
  
  for (n=parse_range(cstr(str, 1)); n!=-1; n=parse_range(NULL)) {
  
    /* find the n-th element in the upload list */
    list_find(task, up, task->tmp == n);

    if (!task && 0<n && n<=count) {
      /* double delete of the same item; ignore */
    } else if (!task) {
      wp(win, "* Upload number %d does not exist\n", n);
      drw(win);
    } else {
      wp(win, "* Deleted upload of \"%s\" to %s\n", task->fn, task->nick);
      drw(win);
      dupload(task);
    }
  }
  return(1);
}

/* /ddown <range> - delete one or more items from the download list or
 * the download queue.  Items are (implicitly) numbered according to
 * their position in the list.  Use /pdown to view the numbering. */
O_NAP_FUNC(dddown)
{
  download_t *task;
  int count, n; 
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  /* as we are deleting items, their relative numbering also changes.
     To avoid strange effects, we first number all the items in the
     list, and then delete them by number, rather than by position */

  count = 0;
  list_forall(task, down) {
    count++;
    task->tmp = count;
  }

  for (n=parse_range(cstr(str, 1)); n!=-1; n=parse_range(NULL)) {
  
    /* find the n-th element in the download list */
    list_find(task, down, task->tmp == n);

    if (!task && 0<n && n<=count) {
      /* double delete of the same item; ignore */
    } else if (!task) {
      wp(win, "* Download number %d does not exist\n", n);
      drw(win);
    } else {
      wp(win, "* Deleted download of \"%s\" from %s\n", task->fn, task->nick);
      drw(win);
      ddownload(win, task);
    }
  }
  return(1);
}

/* /purgedown - remove all stopped items from the download list */
O_NAP_FUNC(dpurgedown)
{
  int c;

  c = purgedown(win);
  
  wp(win, "* Purged %d items from the download list\n", c);
  drw(win);

  return(1);
}

/* /purgeup - remove all stopped items from the upload list */
O_NAP_FUNC(dpurgeup)
{
  int c;

  c = purgeup();
  
  wp(win, "* Purged %d items from the upload list\n", c);
  drw(win);

  return(1);
}

/* /purgeup - remove all stopped items from the upload and download lists */
O_NAP_FUNC(dpurge)
{
  int u, d;

  u = purgeup();
  d = purgedown(win);

  wp(win, "* Purged %d uploads and %d downloads\n", u, d);
  drw(win);

  return(1);
}

/* remove all stopped items from the download list. Return number of
   items deleted. */
int purgedown(WINDOW *win)
{
  download_t *task;
  int c=0;

  task = down;
  while (task) {
    if (STOPPED(task->state)) {
      c++;
      ddownload(win, task);
      task=down;
      continue;
    }
    task = task->next;
  }
  return(c);
}

/* remove all stopped items from the upload list. Return number of
   items deleted.  */
int purgeup(void)
{
  upload_t *task;
  int c=0;

  task = up;
  while (task) {
    if (STOPPED(task->state)) {
      c++;
      dupload(task);
      task=up;
      continue;
    }
    task = task->next;
  }
  return(c);
}

O_NAP_FUNC(dmuzzle)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (num == 2)
    sendpack(s, NAP_MUZZLE, "%s", tok[1]);
  else
    sendpack(s, NAP_MUZZLE, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(dunmuzzle)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (num == 2)
    sendpack(s, NAP_UNMUZZLE, "%s", tok[1]);
  else
    sendpack(s, NAP_UNMUZZLE, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(ddisconnect)
{
  sock_t *sk;

  curchan=NULL;
  
  if (chanl) {
    delnapchans();
  }  

  sk = findsock("server");
  if (sk)
    delsock(sk->fd);
  
  wp(win, "%s* Disconnected from server%s\n", RED, WHITE);
  drw(win);

  return(1);
}

O_NAP_FUNC(dreconnect)
{
  int t;
  sock_t *sk;
  chans_t *cur;
  char *t1 = NULL;
  char *srv;
  int c;
  char *libraryfile;

  wp(win, "Getting best host...\n");
  drw(win);

  srv = next_server(info.serverlist);

  while (1) {
    if (lpbrk) {   /* sigint was caught */
      lpbrk = 0;
      wp(win, ""RED"* Interrupted"WHITE"\n");
      drw(win);
      return(-3);  /* return to top level */
    }
    if (srv==NULL) {  /* loop endlessly */
      srv = next_server(info.serverlist);
      if (srv==NULL) {
	wp(win, ""RED"* Your server list is empty. Try running /getservers"WHITE"\n");
	drw(win);
	return(1);
      }
      sleep(1);
    }
    srv = strip(srv);   /* remove whitespace on both ends */
    wp(win, "Trying %s\n", srv);
    drw(win);
    t = conn(srv);
    if (t == -1)
      {
	srv = next_server(info.serverlist);
	continue;
      }
    
    sk = findsock("server");
    if (sk) {
      close(sk->fd);
      delsock(sk->fd);
    }
    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);
	srv = next_server(info.serverlist);
	continue;
      }
    break;
  }

  {
    struct sockaddr_in dst;
    int frmlen = sizeof(dst);
    if (!getpeername(t, (struct sockaddr *)&dst, &frmlen))
      wp(win, "* Connected to %s:%i\n", inet_ntoa(dst.sin_addr), ntohs(dst.sin_port));
  }

  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);

  t1 = glistn(info.user);
  checkhotlist(t, t1);
  free(t1);
  
  curchan = NULL;
  
  if (chanl)
  {
    for (cur=chanl;cur;cur=cur->next)
      if (!cur->q)
        sendpack(sk->fd, NAP_JOIN, "%s", cur->nm);
    delnapchans();
  }
  
  return(1);
}

O_NAP_FUNC(dbrowse)
{
  ssearch_t *cur, *cur1;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (srch)
  {
    wp(win, "%s* Another search is in progress%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  for (cur=search,cur1=cur;cur1!=NULL;cur=cur1)
  {
    cur1 = cur->next;
    free(cur->song);
    free(cur->rfn);
    free(cur->nick);
    free(cur);
  }
  search = NULL;
  
  sendpack(s, NAP_BROWSE, "%s", tok[1]);
  
  wp(win, "* Browsing %s...\n", tok[1]);
  drw(win);
  
  srch = 1;

  return(1);
}

/* request to directly browse another client */
O_NAP_FUNC(dbrowse2)
{
  ssearch_t *cur, *cur1;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (srch)
  {
    wp(win, "%s* Another search is in progress%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  for (cur=search,cur1=cur;cur1!=NULL;cur=cur1)
  {
    cur1 = cur->next;
    free(cur->song);
    free(cur->rfn);
    free(cur->nick);
    free(cur);
  }
  search = NULL;
  
  sendpack(s, NAP_BROWSE2, "%s", tok[1]);
  wp(win, "* Attempting to directly browse %s...\n", tok[1]);
  drw(win);
  
  directbrowse.state = REQUESTED;
  directbrowse.reqtime = time(NULL);
  free(directbrowse.nick);
  directbrowse.nick = strdup(tok[1]);
  
  srch = 1;

  /* XXX There's a problem in opennap 0.41 wrt direct browse requests. If the
   * remote user does not exist or is not online, the server will return a
   * generic 404 error instead of a 642 direct browse error. The problem is
   * that the srch flag is not reset to 0 when a 404 is received (and, in
   * general, it shouldn't be), so subsequent search or browse commands will
   * be refused by nap! It's hard to to find a good workaround for this
   * problem, because there doesn't seem to be a way to tell what the 404 is
   * in response to; it could be in reponse to an unrelated command, like a
   * /whois. 
   *    There will be a timeout if we don't get a 641 accept or 642 error
   * within a certain amount of time, and srch will be cleared at that time,
   * but it will still be confusing to the user because the 404 message will
   * be displayed much earlier. */

  /* Now wait for a 641 direct browse accept (scmds.c:sbrowse2acc)
   * or an incoming SENDLIST connection */
  return(1);
}

O_NAP_FUNC(dhelp)
{
  int i, j, l, columns;
  int width = (COLS-1)/4;
  char buf[width+1];
  char *cmd;

  memset(buf, ' ', width);   
  buf[width] = 0;

  if (num < 2)
  {
    wp(win, ""MAGENTA"* Commands:"WHITE"\n");

    j=0;  /* next columns to print in */
    for (i=0; out[i].nm; i++) {
      l = strlen(out[i].nm);
      columns = l/width+1;  /* number of columns this entry takes */
      if (j && j + columns > 4) {
	wp(win, "\n");
	j = 0;
      }
      wp(win, ""MAGENTA"%s"WHITE"", out[i].nm);
      j += columns;
      if (j<4 && l<=width*columns) {  /* note columns*width <= l+width */
	wp(win, buf+width+l-columns*width);
      }
    }

    wp(win, "\n"MAGENTA"* To get help on a specific command, type /help <command>"WHITE"\n");

    drw(win);
    
    return(1);
  }
      
  cmd = tok[1];
  if (cmd[0] == '/')
    cmd++;
      
  for (i=0;;i++)
  {
    if (!out[i].nm)
    {
      wp(win, "%s* Could not find help on command: %s%s\n", RED, cmd, WHITE);
      drw(win);
      return(1);
    }
    if (!strcasecmp(out[i].nm, cmd))
    {
      if (!out[i].help)
      {
        wp(win, "%s* No help available on %s%s\n", MAGENTA, out[i].nm, WHITE);
        drw(win);
        return(1);
      }
      wp(win, "%s* Help on %s:%s\n", MAGENTA, out[i].nm, WHITE);
      wp(win, "%s/%s %s%s\n", MAGENTA, out[i].nm, out[i].help, WHITE);
      drw(win);
      return(1);
    }
  }
}

O_NAP_FUNC(dping)
{
  sendpack(s, CLIENT_PING, "%s", cstr(str, 1));
  
  return(1);
};

O_NAP_FUNC(duserpass)
{
  sendpack(s, SET_USER_PASSWORD, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dreloadconf)
{
  sendpack(s, SERVER_RELOAD_CONFIG, "%s", cstr(str, 1));

  return(1);
}

O_NAP_FUNC(dsver)
{
  sendpack(s, SERVER_VERSION, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dchanclear)
{
  sendpack(s, CHANNEL_CLEAR, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dsetlevel)
{
  sendpack(s, CHANNEL_SETLEVEL, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dme)
{
  if (!curchan)
  {
    wp(win, "%sYou're not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  sendpack(s, CHAN_EMOTE, "%s \"%s\"", curchan->nm, fixquotes(cstr(str, 1)));
  
  return(1);
}

O_NAP_FUNC(dusers)
{
  if (num < 2)
  {
    if (!curchan)
    {
      wp(win, "%s* You're not on a channel%s\n", RED, WHITE);
      drw(win);
      return(1);
    }
    sendpack(s, CHANNEL_USERLIST, "%s", curchan->nm);
  }
  else
    sendpack(s, CHANNEL_USERLIST, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dgusers)
{
  sendpack(s, SERVER_USERLIST, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dsetconf)
{
  sendpack(s, SERVER_SET_CONFIG, "%s", cstr(str, 1));
  
  return(1);
}

O_NAP_FUNC(dclist)
{
  sendpack(s, NAP_CLIST, NULL);
  
  wp(win, ""BOLD"Channel"WHITE" | Users | Topic\n");
  wp(win, "-----------------------\n");
  
  return(1);
}

O_NAP_FUNC(dclist2)
{
  sendpack(s, CHANNEL_LIST2, NULL);
  
  wp(win, ""BOLD"Channel"WHITE" | Users | Topic\n");
  wp(win, "-----------------------\n");
  
  return(1);
}

O_NAP_FUNC(dclear)
{
  clearscroll();
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dnews)
{
  int r;
  const char *errmsg;

  wp(win, "* Retrieving news...\n");
  drw(win);

  if (!fork()) {         /* do it in the background */
    r = checknv(ipcs[1], 0, &errmsg, NULL);
    
    if (r==-1) {
      ssock(ipcs[1], ""RED"* Could not access news: %s"WHITE"\n", errmsg);
    } else if (r==0) {
      ssock(ipcs[1], "* No news\n");
    }
    exit(1);
  }
  return(1);
}

O_NAP_FUNC(dsraw)
{
  if (num < 2) {
    usage(win, tok[0]);
    return(-3);
  }
  sendpack(s, atoi(tok[1]), "%s", cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(dquery)
{
  chans_t *cur, *cur1=NULL;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  if ((cur = findchan(chanl, tok[1])))
  {
    curchan = cur;
    return(1);
  }

  if (!chanl)
  {
    chanl = (chans_t *)malloc(sizeof(chans_t));
    cur = chanl;
  }
  else
  {
    for (cur=chanl;cur!=NULL;cur=cur->next)
      cur1 = cur;
    cur = (chans_t *)malloc(sizeof(chans_t));
    cur1->next = cur;
  }
  
  cur->nm = strdup(tok[1]);
  cur->q = 1;
  cur->topic = NULL;
  cur->p = 0;
  cur->key = NULL;
  cur->next = NULL;
  cur->users = NULL;
  curchan = cur;
  
  recent = cur;
  wp(win, "%s* Querying %s%s\n", GREEN, cur->nm, WHITE);
  drw(win);
  recent = NULL;
  
  return(1);
}

O_NAP_FUNC(dcloak)
{
  sendpack(s, NAP_CLOAK, NULL);
  
  return(1);
}

O_NAP_FUNC(dblocklist)
{
  wp(win, "* Blocked users:\n");
  drw(win);

  sendpack(s, NAP_BLOCKLIST, NULL);
  
  return(1);
}

O_NAP_FUNC(dblock)
{
  if (num < 2)   /* no arguments: list blocked users */
  {
    wp(win, "* Blocked users:\n");
    drw(win);

    sendpack(s, NAP_BLOCKLIST, NULL);
  
    return(1);
  }
  
  /* else, block a user */
  if (num == 2)
    sendpack(s, NAP_BLOCK, "%s", tok[1]);
  else
    sendpack(s, NAP_BLOCK, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(dunblock)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (num == 2)
    sendpack(s, NAP_UNBLOCK, "%s", tok[1]);
  else
    sendpack(s, NAP_UNBLOCK, "%s \"%s\"", tok[1], cstr(str, 2));
  
  return(1);
}

O_NAP_FUNC(dwindow)
{
  if (!wmode)
  {
    wp(win, "%s* Window mode enabled%s\n", GREEN, WHITE);
    wmode = 1;
  }
  else
  {
    wp(win, "%s* Window mode disabled%s\n", GREEN, WHITE);
    wmode = 0;
  }
  
  dscr(win);
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dignore)
{
  if (num < 2) {  /* no argument: print list of ignored users */
    wp(win, "* Ignored users:\n");
    drw(win);

    sendpack(s, IGNORE_LIST, NULL);
  
    return(1);
  }
    
  /* else, ignore a user */

  sendpack(s, IGNORE_ADD, "%s", tok[1]);
  
  return(1);
}

O_NAP_FUNC(dunignore)
{
  if (num < 2) {
    usage(win, tok[0]);
    return(-3);
  }

  sendpack(s, IGNORE_REMOVE, "%s", tok[1]);
  
  return(1);
}

O_NAP_FUNC(dignoreclear)
{
  sendpack(s, IGNORE_CLEAR, NULL);
  
  return(1);
}

O_NAP_FUNC(dignorelist)
{
  wp(win, "* Ignored users:\n");
  drw(win);

  sendpack(s, IGNORE_LIST, NULL);
  
  return(1);
}

O_NAP_FUNC(dalias)
{
  alias_t *cur, *old;
  
  if (num < 2)    /* no arguments: print list of all aliases */
  {
    wp(win, "* Aliases:\n");
    drw(win);
    
    if (!alhead)
      {
	wp(win, "No aliases\n");
	drw(win);
      }
    
    for (cur=alhead;cur;cur=cur->next)
      {
	if (*cur->args == '{' && cur->args[1] == '\n')
	  wp(win, "%s%s ->%s\n%s\n", BRIGHT(BLUE), cur->nm, WHITE, cur->args);
	else
	  wp(win, "%s%s ->%s %s\n", BRIGHT(BLUE), cur->nm, WHITE, cur->args);
	drw(win);
      }
    
    return(1);
  }

  if (num == 2)  /* one argument: print that alias */
  {
    list_find(cur, alhead, !strcasecmp(cur->nm, tok[1]));
    if (cur) {
      if (*cur->args == '{' && cur->args[1] == '\n')
	wp(win, "%s%s ->%s\n%s\n", BRIGHT(BLUE), cur->nm, WHITE, cur->args);
      else
	wp(win, "%s%s ->%s %s\n", BRIGHT(BLUE), cur->nm, WHITE, cur->args);
    } else {
      wp(win, ""RED"* Alias %s is not defined"WHITE"\n", tok[1]);
    }
    drw(win);
    return(1);
  }

  /* otherwise, define new alias */

  /* unlink (any) old alias of that name */
  list_unlink_cond(alias_t, alhead, old, !strcasecmp(old->nm, tok[1]));
  
  /* and add the new one */

  cur = (alias_t *)malloc(sizeof(alias_t));
  cur->nm = strdup(tok[1]);
  cur->args = strdup(cstr(str, 2));
  list_append(alias_t, alhead, cur);
  
  if (old) {
    free(old->nm);
    free(old->args);
    free(old);
    wp(win, ""BRIGHT(BLUE)"* Updated alias \"%s\""WHITE"\n", tok[1]);
  } else {
    wp(win, ""BRIGHT(BLUE)"* Added alias \"%s\""WHITE"\n", tok[1]);
  }
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dunalias)
{
  alias_t *cur;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  list_unlink_cond(alias_t, alhead, cur, !strcasecmp(cur->nm, tok[1]));
  if (!cur)
  {
    wp(win, ""BRIGHT(BLUE)"* No such alias \"%s\""WHITE"\n", tok[1]);
  }
  else
  {
    free(cur->nm);
    free(cur->args);
    free(cur);
    
    wp(win, "%s* Removed alias \"%s\"%s\n", BRIGHT(BLUE), tok[1], WHITE);
  }
  drw(win);

  return(1);
}

O_NAP_FUNC(dhandler)
{
  handler_t *cur, *cur1=NULL;
  
  if (num < 2)  /* no argument: list all handlers */
  {
    if (!hndhead)
    {
      wp(win, "%s* No handlers%s\n", BRIGHT(BLUE), WHITE);
      drw(win);
    }
    
    for (cur=hndhead;cur;cur=cur->next)
    {
      if (*cur->args == '{' && cur->args[1] == '\n')
	wp(win, "%s* Handler: (%i) ->%s\n%s\n", BRIGHT(BLUE), cur->op, WHITE, cur->args);
      else
	wp(win, "%s* Handler: (%i) ->%s %s\n", BRIGHT(BLUE), cur->op, WHITE, cur->args);
      drw(win);
    }
    
    return(1);
  }
  
  if (num == 2) {  /* one argument: list this handler */
    list_find(cur, hndhead, cur->op == atoi(tok[1]));
    if (cur) {
      if (*cur->args == '{' && cur->args[1] == '\n')
        wp(win, "%s* Handler: (%i) ->%s\n%s\n", BRIGHT(BLUE), cur->op, WHITE, cur->args);
      else
        wp(win, "%s* Handler: (%i) ->%s %s\n", BRIGHT(BLUE), cur->op, WHITE, cur->args);
    } else {
      wp(win, ""RED"* Handler %s is not defined"WHITE"\n", tok[1]);
    }
    drw(win);
    return(1);
  }

  /* else, define new handler */
  if (!hndhead)
  {
    cur = (handler_t *)malloc(sizeof(handler_t));
    hndhead = cur;
  }
  else
  {
    for (cur=hndhead;;cur=cur->next)
    {
      if (!cur)
        break;
      if (cur->op == atoi(tok[1]))
      {
        wp(win, "%s* Error: a handler by that name already exists%s\n", RED, WHITE);
        drw(win);
        return(1);
      }
      cur1 = cur;
    }
    cur = (handler_t *)malloc(sizeof(handler_t));
    cur1->next = cur;
  }
  
  cur->op = atoi(tok[1]);
  cur->args = strdup(cspstr(tok, num, 2));
  cur->next = NULL;
  
  wp(win, "%s* Added handler for code (%i)%s\n", BRIGHT(BLUE), cur->op, WHITE);
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dunhandler)
{
  handler_t *cur, *cur1=NULL;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  for (cur=hndhead;;cur=cur->next)
  {
    if (!cur)
    {
      wp(win, "%s* Error: handler could not be found%s\n", RED, WHITE);
      drw(win);
      return(1);
    }
    if (cur->op == atoi(tok[1]))
      break;
    cur1 = cur;
  }
  
  if (cur1)
    cur1->next = cur->next;
  else if (cur->next)
    hndhead = cur->next;
  else
    hndhead = NULL;
  free(cur->args);
  free(cur);
  
  wp(win, "%s* Removed handler for code (%i)%s\n", BRIGHT(BLUE), atoi(tok[1]), WHITE);
  drw(win);
  
  return(1);
}

O_NAP_FUNC(daliaslist)
{
  alias_t *cur;
  
  wp(win, "* Aliases:\n");
  drw(win);

  if (!alhead)
  {
    wp(win, "No aliases\n");
    drw(win);
  }
  
  for (cur=alhead;cur;cur=cur->next)
  {
    if (*cur->args == '{' && cur->args[1] == '\n')
      wp(win, "%s%s ->%s\n%s\n", BRIGHT(BLUE), cur->nm, WHITE, cur->args);
    else
      wp(win, "%s%s ->%s %s\n", BRIGHT(BLUE), cur->nm, WHITE, cur->args);
    drw(win);
  }
  
  return(1);
}

O_NAP_FUNC(dhandlerlist)
{
  handler_t *cur;
  
  if (!hndhead)
  {
    wp(win, "%s* No handlers%s\n", BRIGHT(BLUE), WHITE);
    drw(win);
  }
  
  for (cur=hndhead;cur;cur=cur->next)
  {
    if (*cur->args == '{' && cur->args[1] == '\n')
      wp(win, "%s* Handler: (%i) ->%s\n%s\n", BRIGHT(BLUE), cur->op, WHITE, cur->args);
    else
      wp(win, "%s* Handler: (%i) ->%s %s\n", BRIGHT(BLUE), cur->op, WHITE, cur->args);
    drw(win);
  }
  
  return(1);
}

O_NAP_FUNC(dkick)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  if (num == 2)
    sendpack(s, CHANNEL_KICK, "%s %s", curchan->nm, tok[1]);
  else
    sendpack(s, CHANNEL_KICK, "%s %s \"%s\"", curchan->nm, tok[1], cstr(str, 2));

  return(1);
}

O_NAP_FUNC(dcbanlist)
{
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }

  wp(win, "* Banned users on %s:\n", curchan->nm);
  drw(win);

  sendpack(s, CHANNEL_BAN_LIST, "%s", curchan->nm);
  
  return(1);
}

O_NAP_FUNC(dcban)
{
  if (num < 2)  /* no arguments: list banned users on channel */
  {
    if (!curchan)
    {
      wp(win, "%s* Not on a channel%s\n", RED, WHITE);
      drw(win);
      return(1);
    }
    
    wp(win, "* Banned users on %s:\n", curchan->nm);
    drw(win);
    
    sendpack(s, CHANNEL_BAN_LIST, "%s", curchan->nm);
    
    return(1);
  }
  
  /* else ban a user */
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  if (num == 2)
    sendpack(s, CHANNEL_BAN_ADD, "%s %s", curchan->nm, tok[1]);
  else
    sendpack(s, CHANNEL_BAN_ADD, "%s %s \"%s\"", curchan->nm, tok[1], cstr(str, 2));

  return(1);
}

O_NAP_FUNC(dcunban)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  sendpack(s, CHANNEL_BAN_REMOVE, "%s %s", curchan->nm, tok[1]);

  return(1);
}

O_NAP_FUNC(dloadalias)
{
  char *filename;

  if (num < 2) {
    filename = home_file(ALIASFILE);
  } else {
    filename = home_file(strdup(tok[1]));
  }

  if (loadaliases(filename) == -1) {
    wp(win, "%s* Error loading aliases from %s: %s%s\n", RED, filename, strerror(errno), WHITE);
  } else {
    wp(win, "%s* Successfully loaded aliases from %s%s\n", BRIGHT(BLUE), filename, WHITE);
  }
  drw(win);
  
  free(filename);
  return(1);
}

O_NAP_FUNC(dsavealias)
{
  char *filename;

  if (num < 2) {
    filename = home_file(ALIASFILE);
  } else {
    filename = home_file(tok[1]);
  }
  
  if (savealiases(filename) == -1) {
    wp(win, "%s* Error saving aliases to %s: %s%s\n", RED, filename, strerror(errno), WHITE);
  } else {
    wp(win, "%s* Successfully saved aliases to %s%s\n", BRIGHT(BLUE), filename, WHITE);
  }
  drw(win);
  
  free(filename);
  return(1);
}

O_NAP_FUNC(dclearalias)
{
  alias_t *cur, *cur1=NULL;
  
  for (cur=alhead;;cur=cur1)
  {
    if (!cur)
    {
      if (!cur1)
        alhead = NULL;
      break;
    }
    cur1 = cur->next;
    free(cur->nm);
    free(cur->args);
    free(cur);
  }
  
  wp(win, "%s* Cleared all aliases%s\n", BRIGHT(BLUE), WHITE);
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dloadhandler)
{
  char *filename;

  if (num < 2) {
    filename = home_file(HANDLERFILE);
  } else {
    filename = home_file(strdup(tok[1]));
  }
  
  if (loadhandlers(filename) == -1) {
    wp(win, "%s* Error loading handlers from %s: %s%s\n", RED, filename, strerror(errno), WHITE);
  } else {
    wp(win, "%s* Successfully loaded handlers from %s%s\n", BRIGHT(BLUE), filename, WHITE);
  }
  drw(win);
  
  free(filename);
  return(1);
}

O_NAP_FUNC(dsavehandler)
{
  char *filename;

  if (num < 2) {
    filename = home_file(HANDLERFILE);
  } else {
    filename = home_file(tok[1]);
  }
  
  if (savehandlers(filename) == -1) {
    wp(win, "%s* Error saving handlers to %s: %s%s\n", RED, filename, strerror(errno), WHITE);
  } else {
    wp(win, "%s* Successfully saved handlers to %s%s\n", BRIGHT(BLUE), filename, WHITE);
  }
  drw(win);
  
  free(filename);
  return(1);
}

O_NAP_FUNC(dclearhandler)
{
  handler_t *cur, *cur1=NULL;
  
  for (cur=hndhead;;cur=cur1)
  {
    if (!cur)
    {
      if (!cur1)
        hndhead = NULL;
      break;
    }
    cur1 = cur->next;
    free(cur->args);
    free(cur);
  }
  
  wp(win, "%s* Cleared all handlers%s\n", BRIGHT(BLUE), WHITE);
  drw(win);
  
  return(1);
}

/* /notify [user] - Adds a user to your hotlist, or print hotlist */
O_NAP_FUNC(dnotify)
{
  hotlist_t *elt;
  
  if (num < 2)   /* no arguments: print current hotlist */
  {
    if (!hlist)
    {
      wp(win, "%s* No users on your hotlist%s\n", BRIGHT(BLUE), WHITE);
      drw(win);
      return(1);
    }
    
    wp(win, "%s* Hotlist:%s\n", BRIGHT(BLUE), WHITE);
    
    list_forall(elt, hlist) {
      wp(win, "%s* %s - %s%s\n", (elt->on)?BRIGHT(GREEN):BRIGHT(BLUE), elt->nm, (elt->on)?"On":"Off", WHITE);
    }
    
    drw(win);
    return(1);
  }
  
  /* otherwise, add user to hotlist */

  list_find(elt, hlist, !strcasecmp(elt->nm, tok[1]));

  if (elt) {
    wp(win, ""BRIGHT(BLUE)"* %s is already on your hotlist"WHITE"\n", tok[1]);
    drw(win);
    return(1);
  }

  wp(win, ""BRIGHT(BLUE)"* Adding %s to your hotlist"WHITE"\n", tok[1]);
  drw(win);

  /* create new hotlist entry */
  elt = (hotlist_t *)malloc(sizeof(hotlist_t));
  elt->nm = strdup(tok[1]);
  elt->on = 0;
  elt->conn = 0;

  /* add it to the list */
  list_append(hotlist_t, hlist, elt);

  /* send request to server */
  sendpack(s, ADD_NOTIFY, "%s", elt->nm);

  /* and keep file up-to-date */
  save_hotlist(win);

  return(1);
}

/* save the hotlist to the standard file. Return 0 on success, 1 on
   error. Print error message to win. */
int save_hotlist(WINDOW *win) {
  FILE *f;
  char *fn;
  hotlist_t *elt;

  fn = glistn(info.user);
  f = fopen(fn, "w");
  if (!f) {
    wp(win, ""RED"* Error writing your hotlist file %s: %s"WHITE"\n", fn, strerror(errno));
    drw(win);
    free(fn);
    return(1);
  }
  free(fn);
  list_forall(elt, hlist) {
    fprintf(f, "%s\n", elt->nm);
  }
  fclose(f);
  return 0;
}
    

/* /unnotify <user> - Removes a user from your hotlist */
O_NAP_FUNC(dunnotify)
{
  hotlist_t *elt;
  
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  list_unlink_cond(hotlist_t, hlist, elt, !strcmp(elt->nm, tok[1]));

  if (!elt) {
    wp(win, ""BRIGHT(BLUE)" * %s was not on your hotlist"WHITE"\n", tok[1]);
    drw(win);
    return(1);
  }

  wp(win, ""BRIGHT(BLUE)"* Removing %s from your hotlist"WHITE"\n", tok[1]);
  drw(win); 
  
  free(elt->nm);
  free(elt);

  sendpack(s, REMOVE_NOTIFY, "%s", tok[1]);
  
  /* and keep file up-to-date */
  save_hotlist(win);

  return(1);
}

O_NAP_FUNC(dwstats)
{
  wstats(win);
  
  return(1);
}

O_NAP_FUNC(dsay)
{
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  sendpack(s, NAP_SAY, "%s %s", curchan->nm, fixquotes(cstr(str, 1)));
  
  return(1);
}

O_NAP_FUNC(dabout)
{
  wp(win, "%s* nap v%s by Ignitor (Kevin Sullivan), modified by Peter Selinger and others%s\n", BRIGHT(RED), VERSION, WHITE);
  wp(win, "%s* Thanks: nocarrier, napster, fizban, amputee, nytr0, fletch, Plugh, pea and the rest of the admins and mods%s\n", BRIGHT(RED), WHITE);
  wp(win, "%s* Thanks: nyt for info on ncurses and drscholl for opennap (useful for testing)%s\n", BRIGHT(RED), WHITE);
  wp(win, "%s* Special Thanks: everyone that has emailed me with bug reports, comments and suggestions%s\n", BRIGHT(RED), WHITE);
  if (num == 5)
    wp(win, "%s* Extra Special Thanks: Brigand for being such a bitch :P%s\n", BRIGHT(RED), WHITE);
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dirc)
{
  struct sockaddr_in dst;
  struct passwd *pw;
  char *t=NULL;
  int i;

  if (num < 2)
  {
    if (!ircsock)
      return(1);
    wp(win, "%s* IRC mode toggled: On%s\n", RED, WHITE);
    drw(win);
    ircmode = 1;
    return(1);
  }

  if (strcasecmp(tok[1], "server"))
  {
    if (!ircsock)
      return(1);
    if (!checkouts(ircsock, cstr(str, 1), tok+1, num-1, win))
      ssock(ircsock, "%s\n", cstr(str, 1));
    return(1);
  }
  
  if (num < 4) {
    usage(win, tok[0]);
    return(-3);
  }  

  if (ircsock)
  {
    wp(win, "%s* Disconnect from your current IRC server first%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  if (!resolve(tok[2], &dst.sin_addr)) {
    wp(win, "%s* Error resolving host %s: %s%s\n", RED, tok[2], hstrerror(h_errno), WHITE);
    drw(win);
    return(1);
  }
  dst.sin_port = htons(atoi(tok[3]));
  dst.sin_family = AF_INET;
  
  ircsock = socket(AF_INET, SOCK_STREAM, 0);
  
  if (connect(ircsock, (struct sockaddr *)&dst, sizeof(dst)) == -1)
  {
    wp(win, "%s* Error connecting socket: %s%s\n", RED, strerror(errno), WHITE);
    drw(win);
    close(ircsock);
    ircsock = 0;
    return(1);
  }
  
  setkeepalive(ircsock);
  addsock(ircsock, "irc", S_R, inirc);
  
  pw = getpwuid(getuid());
  if (pw)
  {
    msprintf(&t, "%s", pw->pw_gecos);
    for (i=0;t[i];i++)
    {
      if (t[i] == ',')
      {
        t[i] = 0;
        break;
      }
    }
  }
  
  ssock(ircsock, "NICK %s\nUSER %s NULL NULL :%s\n", info.user, info.user, (t)?t:info.user);
  if (t)
    free(t);
  mnick = strdup(info.user);
  ircmode = 1;
  
  return(1);
}

O_NAP_FUNC(dkickall)
{
  chans_t *cur;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  for (cur=chanl;cur;cur=cur->next)
  {
    if (finduser(cur, tok[1]))
    {
      if (num == 2)
        sendpack(s, CHANNEL_KICK, "%s %s", cur->nm, tok[1]);
      else
        sendpack(s, CHANNEL_KICK, "%s %s \"%s\"", cur->nm, tok[1], cstr(str, 2));
    }
  }

  return(1);
}


O_NAP_FUNC(deval)
{
  unsigned char *p;

  if (num < 2) {
    usage(win, tok[0]);
    return(-3);
  }

  p = dovars(tok[1]);
  
  wp(win, "%s\n", p);
  drw(win);

  free(p);
  
  return(1);
}

/* /set [var] [value]: set variable to value. If value not given,
   print current value. If var not given, print current value of all
   variables */
O_NAP_FUNC(dset)
{
  if (num < 2)   /* /set with no arguments is like /pvars */
  {
    wp(win, "* User variables:\n");
    drw(win);
    
    printsets(win);
    return(1);
  }

  if (num == 2)   /* /set with one argument prints that value */
  {
    char *val = getval(tok[1]);

    if (val) {
      wp(win, ""BRIGHT(BLUE)"%s = "WHITE"%s\n", tok[1], getval(tok[1]));
    } else {
      wp(win, ""RED"* %s is not set"WHITE"\n", tok[1]);
    }
    drw(win);
    return(1);
  }

  chset(tok[1], cstr(str, 2));

  if (nvar_default("noechosets", 0) == 0)
  {
    wp(win, "%s* Set variable %s to \"%s\"%s\n", BRIGHT(BLUE), tok[1], getval(tok[1]), WHITE);
    drw(win);
  }
  
  return(1);
}

O_NAP_FUNC(dunset)
{
  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (!getval(tok[1]))
  {
    if (nvar_default("noechosets", 0) == 0)
    {
      wp(win, "%s* %s was not set%s\n", RED, tok[1], WHITE);
      drw(win);
    }
    return(1);
  }
  
  delset(tok[1]);
  
  if (nvar_default("noechosets", 0) == 0)
  {
    wp(win, "%s* Unset variable %s%s\n", BRIGHT(BLUE), tok[1], WHITE);
    drw(win);
  }
  
  return(1);
}

O_NAP_FUNC(dloadconfig)
{
  int r;
  char *fn;

  if (num < 2)
    fn = getval("configfile");
  else
    fn = tok[1];

  if (!fn) 
    fn = home_file(CONFIGFILE);
  else
    fn = home_file(fn);

  r = loadsets(fn, win, 1, 1);

  if (r == -1) {
    wp(win, "%s* Error loading config file %s: %s%s\n", RED, fn, strerror(errno), WHITE);
  } else if (r == 1) {
    wp(win, "%s* Loaded config file %s with some warnings%s\n", BRIGHT(BLUE), fn, WHITE);
  } else {  
    wp(win, "%s* Successfully loaded config file %s%s\n", BRIGHT(BLUE), fn, WHITE);
  }

  free(fn);

  drw(win);
  
  return(1);
}

O_NAP_FUNC(dsaveconfig)
{
  char *fn;  /* config filename */

  if (num < 2)
    fn = getval("configfile");
  else
    fn = tok[1];

  if (!fn) 
    fn = home_file(CONFIGFILE);
  else
    fn = home_file(fn);

  if (savesets(fn) == -1)
  {
    wp(win, "%s* Error saving config file %s: %s%s\n", RED, fn, strerror(errno), WHITE);
    drw(win);
    return(1);
  }
  
  wp(win, "%s* Successfully saved config file %s%s\n", BRIGHT(BLUE), fn, WHITE);
  drw(win);
  
  free(fn);

  return(1);
}

O_NAP_FUNC(dloadchannels)
{
  char *fn;
  sock_t *sk;

  if (num < 2) {
    char *tmp = NULL;
    msprintf(&tmp, CHANNELFILE, info.user);
    fn = home_file(tmp);
    free(tmp);
  }
  else {
    fn = home_file(strdup(tok[1]));
  }
  
  sk = findsock("server");
  if (sk)
  {
    if (loadchans(sk->fd, fn)==-1) {
      wp(win, ""RED"* Error loading channels from %s: %s"WHITE"\n", fn, \
	 strerror(errno));
    } else {
      wp(win, "* Joining channels from %s\n", fn);
    }
  } else 
  {
    wp(win, "* Not connected to server\n");
  }
  free(fn);

  drw(win);
  
  return(1);
}

O_NAP_FUNC(dsavechannels)
{
  char *fn;

  if (num < 2) {
    char *tmp = NULL;
    msprintf(&tmp, CHANNELFILE, info.user);
    fn = home_file(tmp);
    free(tmp);
  }
  else {
    fn = home_file(tok[1]);
  }
  
  if (savechans(chanl, fn)==-1) {
    wp(win, ""RED"* Error saving channels to %s: %s"WHITE"\n", fn, \
       strerror(errno));
  } else {
    wp(win, "* Saved channels to %s\n", fn);
  }
  free(fn);

  drw(win);
  
  return(1);
}

O_NAP_FUNC(decho)
{
  wp(win, "%s\n", cstr(str, 1));
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dresults)
{
  switchtoscreen(RESULT_SCREEN);
  
  return(1);
}

O_NAP_FUNC(ddlul)
{
  switchtoscreen(DLUL_SCREEN);

  return(1);
}

O_NAP_FUNC(dexec)
{
  char buf[512]; /* ### static string limit - avoid "runaway" commands? */
  FILE *f;
  int fl, i;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  if (strncmp(tok[1], "-o", 2)==0)
  {
    fl = 1;
    f = popen(cstr(str, 2), "r");
  }
  else {
    fl = 0;
    f = popen(cstr(str, 1), "r");
  }

  if (!f)
  {
    wp(win, "%s* Error executing command%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  
  memset(buf, 0, sizeof(buf));
  fread(&buf, sizeof(buf)-1, 1, f);
  pclose(f);
  for (i=0;buf[i];i++)
    if (buf[i] == '\n' || buf[i] == '\r')
    {
      strncpy(buf+i, buf+i+1, strlen(buf+i+1));
      buf[strlen(buf)-1] = 0;
    } 
  
  if (!fl)
  {
    wp(win, "%s\n", buf);
    drw(win);
    return(1);
  }
  
  if (!curchan)
  {
    wp(win, "%s* Not on a channel%s\n", RED, WHITE);
    drw(win);
  }
  else if (curchan->q == 1)
  {
    sendpack(s, NAP_TELL, "%s %s", curchan->nm, fixquotes(buf));
    recent = curchan;
    wp(win, "%s* --> (%s%s%s)%s %s\n", GREEN, WHITE, curchan->nm, GREEN, WHITE, buf);
    drw(win);
    recent = NULL;
  }
  else if (curchan->q == 2 && buf[0] != '\0')
  {
    ssock(ircsock, "PRIVMSG %s :%s\n", curchan->nm, buf);
    recent = curchan;
    wp(win, "%s<%s%s%s>%s %s\n", BRIGHT(MAGENTA), WHITE, mnick, BRIGHT(MAGENTA), WHITE, buf);
    drw(win);
    recent = NULL;
  }
  else if (buf[0] != '\0')
    sendpack(s, NAP_SAY, "%s %s", curchan->nm, fixquotes(buf));
  
  return(1);
}

/* /timer <[min:]sec> command */
O_NAP_FUNC(dtimer)
{
  int sec;
  char *data;

  if (num < 3)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sec = parse_time(tok[1]);
  if (sec < 0) {
    wp(win, "%s* Invalid time \"%s\". Usage: /%s <min:sec> <command>%s\n",
       RED, tok[0], tok[1], WHITE);
    drw(win);
    return(1);
  }

  /* set timer */
  data = strdup(cstr(str, 2));
  addtimer(sec, timed_command, (void *)data, data, FALSE);

  wp(win, ""BRIGHT(BLUE)"* Scheduled (in %i:%02i): "WHITE"%s\n", sec/60, sec%60, data);
  drw(win);

  return(1);
}

/* /repeat <[min:]sec> command */
O_NAP_FUNC(drepeat)
{
  int sec;
  char *data;

  if (num < 3)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  sec = parse_time(tok[1]);
  if (sec < 0) {
    wp(win, "%s* Invalid time \"%s\". Usage: /%s <min:sec> <command>%s\n",
       RED, tok[0], tok[1], WHITE);
    drw(win);
    return(1);
  }

  /* set timer */
  data = strdup(cstr(str, 2));
  addtimer(sec, timed_command, (void *)data, data, TRUE);

  wp(win, ""BRIGHT(BLUE)"* Scheduled (in %i:%02i, repeating): "WHITE"%s\n",
     sec/60, sec%60, data);
  drw(win);

  return(1);
}

/* Parse a time string of the form "M:S" or just "S". Return the number of
   seconds represented, or -1 if the string has an invalid format.
 */
int parse_time(char *str)
{
  char *p, *q;
  int min, sec=0;

  p = str;

  /* parse minutes */
  if (strchr(p, ':')) {
    min = strtol(p, &q, 10);
    if (!q || *q != ':') {
      min = -1;
    } else {
      q++;
    }
  } else {
    min = 0;
    q = p;
  }
  
  /* parse seconds */
  if (min>=0) {
    sec = strtol(q, &p, 10);
    if (!p || *p || p==q) {
      sec = -1;
    }
  }

  if (min<0 || sec<0)
    return -1;
  else
    return min*60 + sec;
}

O_NAP_FUNC(ddtimer)
{
  int index;

  if (num != 2) {
    usage(win, tok[0]);
    return (-3);
  }
  index = atoi(tok[1]);
  if (index > 0 && deltimer_by_index(index-1))
    wp(win, "* Timed event #%d cancelled\n", index);
  else
    wp(win, "%s* Timed event #%i does not exist%s\n", RED, index, WHITE);
  drw(win);
  return (1);
}

O_NAP_FUNC(dif)
{
  char *t, *b;
  int i, c=0, r, f = 0;
  
  if (num < 2) {
    usage(win, tok[0]);
    return(-3);
  }

  b = strev(tok[1], '(', ')', cmp);
  if (b)
    r = atoi(b);
  else
    r = -1;
  if (b)
    free(b);
  
  if (r == -1)
  {
    wp(win, "%s* Error parsing script%s\n", RED, WHITE);
    drw(win);
    return(1);
  }
  else if (!r)
  {
    return(1);
  }
  
  for (i=0,c=0;str[i];i++)
  {
    if (!c && f)
      break;
    if (c < 0)
      c = 0;
    if (str[i] == '(')
    {
      f = 1;
      c++;
    }
    else if (str[i] == ')')
      c--;
  }
  
  t = (char *)malloc(2+strlen(str+i));
  t[0] = '/';
  strcpy(t+1, str+i);
  
  for (i=0,c=0;t[i];i++)
  {
    if (c < 0)
      c = 0;
    if (t[i] == '{' && !c)
    {
      t[i] = ' ';
      c++;
    }
    else if (t[i] == '{')
      c++;
    else if (t[i] == '}' && c == 1)
    {
      t[i] = ' ';
      c--;
    }
    else if (t[i] == '}')
      c--;
  }
  
  r = parseout(s, t, win);
  
  free(t);

  return(r);
}

O_NAP_FUNC(dwhile)
{
  char *t, *b, *cm;
  int i, c=0, r, f = 0;
  
  if (num < 2) {
    usage(win, tok[0]);
    return(-3);
  }

  cm = strdup(tok[1]);
  
  b = strev(cm, '(', ')', cmp);
  if (b)
    r = atoi(b);
  else
    r = -1;
  if (b)
    free(b);
  
  if (r == -1)
  {
    wp(win, "%s* Error parsing script%s\n", RED, WHITE);
    drw(win);
    free(cm);
    return(1);
  }
  else if (!r)
  {
    free(cm);
    return(1);
  }
  
  for (i=0,c=0;str[i];i++)
  {
    if (!c && f)
      break;
    if (c < 0)
      c = 0;
    if (str[i] == '(')
    {
      f = 1;
      c++;
    }
    else if (str[i] == ')')
      c--;
  }
  
  t = (char *)malloc(2+strlen(str+i));
  t[0] = '/';
  strcpy(t+1, str+i);
  
  for (i=0,c=0;t[i];i++)
  {
    if (c < 0)
      c = 0;
    if (t[i] == '{' && !c)
    {
      t[i] = ' ';
      c++;
    }
    else if (t[i] == '{')
      c++;
    else if (t[i] == '}' && c == 1)
    {
      t[i] = ' ';
      c--;
    }
    else if (t[i] == '}')
      c--;
  }
  
  while (1)
  {
    b = strev(cm, '(', ')', cmp);
    if (b)
    {
      r = atoi(b);
      free(b);
    }
    else
      break;
    if (!r)
      break;
    r = parseout(s, t, win);
    if (r == -3)       /* break out of nested loops; return -3 again */
      break;
    else if (r == -4)  /* break out of this loop only; return 1 */
    {
      r = 1;
      break;
    }
    if (lpbrk) 
    {
      lpbrk = 0; 
      /* note: prevoiusly r=1 was returned here, making it impossible
	 to interrupt a nested loop. -3 will bring us to the top
	 level. -PS */
      r = -3;
      break;
    }
  }
  
  free(t);
  free(cm);
  
  return(r);
}

O_NAP_FUNC(dinc)
{
  int t;
  char *p;
  char *tp=NULL;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  p = getval_t(tok[1], &t);
  if (!p) {
    wp(win, "%s* Variable %s is not set%s\n", RED, tok[1], WHITE);
    drw(win);
    return(1);
  }

  if (t && strlen(p))
  {
    tp = strdup(p+1);
    chset(tok[1], tp);
    free(tp);
  }
  else if (!t)
  {
    msprintf(&tp, "%i", atoi(p)+1);
    chset(tok[1], tp);
    free(tp);
  }

  p = getval_t(tok[1], &t);
  
  if (nvar_default("noechosets", 0) == 0)
  {
    wp(win, "%s* Set variable %s to \"%s\"%s\n", BRIGHT(BLUE), tok[1], p, WHITE);
    drw(win);
  }
  
  return(1);
}

O_NAP_FUNC(ddec)
{
  char *p;
  char *tp=NULL;
  int t;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }
  
  p = getval_t(tok[1], &t);
  if (!p) {
    wp(win, "%s* Variable %s is not set%s\n", RED, tok[1], WHITE);
    drw(win);
    return(1);
  }

  if (t && strlen(p))
  {
    tp = strdup(p);
    tp[strlen(tp)-1] = 0;
    chset(tok[1], tp);
    free(tp);
  }
  else if (!t)
  {
    msprintf(&tp, "%i", atoi(p)-1);
    chset(tok[1], tp);
    free(tp);
  }

  p = getval_t(tok[1], &t);
  
  if (nvar_default("noechosets", 0) == 0)
  {
    wp(win, "%s* Set variable %s to \"%s\"%s\n", BRIGHT(BLUE), tok[1], p, WHITE);
    drw(win);
  }
  
  return(1);
}
  
O_NAP_FUNC(dstop)
{
  return(2);
}

O_NAP_FUNC(ddone)
{
  return(-3);
}

O_NAP_FUNC(dbreak)
{
  return(-4);
}

O_NAP_FUNC(dlastlog)
{
  scroll_t *elt;
  int matches = 0;

  if (num < 2)
  {
    usage(win, tok[0]);
    return(-3);
  }

  lastlogflag = 1;  /* signal to wp to mark output as being from
		       lastlog.  This causes future lastlog commands
		       to ignore this output */
  
  elt = mscroll;
  while (elt) {
    if (strstr(elt->line, tok[1]) && !elt->lastlog) {
      if (elt->own)
        elt = elt->own;

      if (matches == 0) {
	wp(win, "%s* Matches:%s\n", BRIGHT(MAGENTA), WHITE);
      }
      matches++;
      
      do {
	wp(win, "%s\n", elt->line);
	elt = elt->next;
      } while (elt && elt->own);

    } else {
      elt = elt->next;
    }
  }
  
  if (matches) {
    wp(win, "%s* End of matches%s\n", BRIGHT(MAGENTA), WHITE);
  } else {
    wp(win, "%s* No matches%s\n", BRIGHT(MAGENTA), WHITE);
  }
  drw(win);

  lastlogflag = 0;
  
  return(1);
}

/* user command to rebuild the library */
O_NAP_FUNC(drebuild)
{
  char *libraryfile;

  wp(win, "* Rebuilding your library\n");
  drw(win);
  
  if (info.shared_filename) {
    libraryfile = strdup(info.shared_filename);
  } else {
    libraryfile = home_file(LIBRARYFILE);
  }

  if (rebuild(s, libraryfile, info.up) == -1) {
    wp(win, "%s* Error building your library%s\n", RED, WHITE);
  } else {  
    wp(win, "* Successfully rebuilt your library\n");
  }
  drw(win);
  free(libraryfile);
  return 1;
}

/* user command to rebuild the library, but only if necessary */
O_NAP_FUNC(dupdate)
{
  int ret;
  char *libraryfile;

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

  ret = up_to_date(libraryfile, getval("upload"));
  if (ret) {
    wp(win, "* Your library is up-to-date.\n");
    drw(win);
    free(libraryfile);
    return(1);
  }
  wp(win, "* Your library is not up-to-date. Rebuilding...\n");
  drw(win);
  
  if (rebuild(s, libraryfile, info.up) == -1) {
    wp(win, "%s* Error building your library%s\n", RED, WHITE);
  } else {
    wp(win, "* Successfully rebuilt your library\n");
  }
  drw(win);
  free(libraryfile);
  return 1;
}

O_NAP_FUNC(dchupload)
{
  if (num < 2)
  {
    wp(win, "* Current upload path: %s\n", info.up);
    drw(win);
    return(1);
  }
  
  free(info.up);
  info.up = strdup(tok[1]);
  
  wp(win, "* Set your upload path to: %s\n", info.up);
  drw(win);
  
  return(1);
}

O_NAP_FUNC(dtlist)
{
  wp(win, "* Timers:\n");
  timerlist(win);
  
  return(1);
}

O_NAP_FUNC(dnoprint)
{
  if (noprint)
    noprint = 0;
  else
    noprint = 1;
  
  return(1);
}

O_NAP_FUNC(ddebug)
{
  if (num < 2) {
    usage(win, tok[0]);
    return(-3);
  }

  chset("debug", tok[1]);
  
  wp(win, "%s* Debugging level set to: %s%s\n", RED, getval("debug"), WHITE);
  drw(win);

  return(1);
}

O_NAP_FUNC(dserver)
{
  int t;
  sock_t *sk;
  chans_t *cur;
  char *t1 = NULL;
  int c;
  struct sockaddr_in dst;
  int frmlen = sizeof(dst);
  char *libraryfile;

  if (num < 2)  /* no argument: show current server */
  {
    if (getpeername(s, (struct sockaddr *)&dst, &frmlen))
    {
      wp(win, "%s* Not connected to server%s\n", RED, WHITE);
      drw(win);
      return(1);
    }
    
    wp(win, "* Currently connected to: %s:%i\n", inet_ntoa(dst.sin_addr), ntohs(dst.sin_port));
    drw(win);
    
    return(1);
  }
  
  /* else, connect to the specified server */

  if (!strchr(tok[1], ':'))
  {
    wp(win, "%s* Invalid parameter%s\n", RED, WHITE);
    drw(win);
    return(1);
  }

  t = conn(tok[1]);
  if (t == -1)
  {
    drw(win);
    return(1);
  }
  wp(win, "Logging in...\n");
  drw(win);
  sk = findsock("server");
  if (sk) {
    close(sk->fd);
    delsock(sk->fd);
  }
  
  c = connection();
  if (login(t, info.user, info.pass, info.port, c, info.email) == -1)
  {
    drw(win);
    close(t);
    return(1);
  }
  
  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);

  t1 = glistn(info.user);
  checkhotlist(t, t1);
  free(t1);
  
  curchan = NULL;
  
  if (chanl)
  {
    for (cur=chanl;cur;cur=cur->next)
      if (!cur->q)
        sendpack(sk->fd, NAP_JOIN, "%s", cur->nm);
    delnapchans();
  }
  
  return(1);
}

/* read server list from napigator-style metaserver. Metaserver url is
   in "metaserver", timeout is in "metatimeout", result is put into
   "servers" */

O_NAP_FUNC(dgetservers) {
  int r;
  char *url;
  int timeout;
  const char *errmsg;

  url = getval("metaserver");
  if (!url) 
    /* "randomly" pick a metaserver. */
    url = ((time(NULL) % 2) ? METASERVER_1 : METASERVER_2);

  timeout = nvar_default("metatimeout", METATIMEOUT);
  if (timeout<0)
    timeout = METATIMEOUT;

  wp(win, "* Reading server list from %s...\n", url);
  drw(win);

  /* note: I would like to put the call to metaserver in a child
     process, since it can take several seconds. However, in this case
     we would need to communicate the result back to the parent
     process (i.e., modify the parent process's "servers" variable) */

  r = metaserver(url, timeout, &errmsg);
  switch (r) {
  case -1:
    wp(win, ""RED"* Could not read server list: %s"WHITE"\n", errmsg);
    drw(win);
    break;
  default:
    wp(win, "* Got %d servers\n", r);
    drw(win);
    break;
  }
  return(1);
}    

#ifdef MEMWATCH

O_NAP_FUNC(dmemwatch) {
  sock_t *sock;
  upload_t *utask;
  download_t *dtask;
  ssearch_t *sitem;
  scroll_t *scur;
  cmds_t *ccur;
  sets_t *set;
  hotlist_t *hl;
  alias_t *aitem;

  /* tag some memory cells that are reachable (alive) */
  memwatch_cleartags();

  list_forall(sock, socklist) {
    memwatch_tag(sock, 1);
    memwatch_tag(sock->socknm, 1);
  }

  list_forall(utask, up) {
    memwatch_tag(utask, 2);
    memwatch_tag(utask->nick, 2);
    memwatch_tag(utask->rfn, 2);
    memwatch_tag(utask->fn, 2);
    memwatch_tag(utask->lfn, 2);
  }

  list_forall(dtask, down) {
    memwatch_tag(dtask, 3);
    memwatch_tag(dtask->nick, 3);
    memwatch_tag(dtask->rfn, 3);
    memwatch_tag(dtask->fn, 3);
    if (dtask->state == WAITING || dtask->state == CONNECTING 
	|| dtask->state == IN_PROGRESS) {
      memwatch_tag(dtask->check, 3);
    }
  }

  list_forall(sitem, search) {
    memwatch_tag(sitem, 4);
    memwatch_tag(sitem->song, 4);
    memwatch_tag(sitem->rfn, 4);
    memwatch_tag(sitem->nick, 4);
  }

  list_forall(scur, mscroll) {
    memwatch_tag(scur, 5);
    memwatch_tag(scur->line, 5);
  }

  list_forall(ccur, cmdl) {
    memwatch_tag(ccur, 6);
    memwatch_tag(ccur->cmd, 6);
  }

  list_forall(set, setl) {
    memwatch_tag(set, 7);
    memwatch_tag(set->d, 7);
    memwatch_tag(set->nm, 7);
  }

  list_forall(hl, hlist) {
    memwatch_tag(hl, 8);
    memwatch_tag(hl->nm, 8);
  }

  memwatch_tag(info.user, 9);
  memwatch_tag(info.pass, 9);
  memwatch_tag(info.email, 9);
  memwatch_tag(info.up, 9);
  memwatch_tag(info.down, 9);
  memwatch_tag(info.incomplete, 9);
  memwatch_tag(info.serverlist, 9);
  memwatch_tag(info.logallfile, 9);
  memwatch_tag(info.logfile, 9);
  memwatch_tag(info.dataport, 9);
  memwatch_tag(info.shared_filename, 9);

  list_forall(aitem, alhead) {
    memwatch_tag(aitem, 10);
    memwatch_tag(aitem->nm, 10);
    memwatch_tag(aitem->args, 10);
  }

  memwatch_dump();
  wp(win, "* Memory usage dumped to file\n");
  drw(win);
  return(1);
}

#endif /* MEMWATCH */

/* ---------------------------------------------------------------------- */

/* once a second or so check download list, both for queued items that
   might be requested, and for items that are expired. Also do the
   check for quit_after_transfers. Also also, refresh the dl/ul screen if
   it's up. */

/* these list manipulations are inefficient, but so is all the rest of
   nap, so we don't worry about it too much. */

void qevent(void) {
  download_t *dtask, *dnext, *a;
  upload_t *utask, *unext, *b;
  int c=0, d;
  int autopurge = nvar("autopurge");  
  int autopurgeup = nvar("autopurgeup");  
  int autopurgedown = nvar("autopurgedown");  
  sock_t *sk;
  time_t t = time(0);
  
  /* 1. check whether to quit if all transfers have completed */
  if (quit_after_transfers) {
    list_count(down, c, a, !STOPPED(a->state));
    list_count(up, d, b, !STOPPED(b->state));
    if (c==0 && d==0) {
      quit_now = 1;
      wp(wchan, "* All uploads and downloads have been completed. Quitting now.\n");
      drw(wchan);
      return;
    }
  }

  /* 2. no longer exists */

  /* 3. check for queued items that can be activated */
  /* note: if a nick has more than one rqueued task, we will only try
     the first one */
  sk = findsock("server");
  if (sk)
  {
    list_forall(dtask, down) {
      if (dtask->state == QUEUED || 
	  (dtask->state == RQUEUED && t-dtask->r_time>=15 
	                    && !nick_has_earlier_rqueued_task(dtask, t))) {
	if (!download_limit_reached(dtask->nick)) {
	  sendpack(sk->fd, NAP_DGET, "%s \"%s\"", dtask->nick, dtask->rfn);
	  if (dtask->state == QUEUED) {
	    wp(wchan, "* Getting \"%s\" from %s\n", dtask->fn, dtask->nick);
	    drw(wchan);
	    dtask->state = REQUESTED;
	  } else {
	    dtask->state = RRQUEUED;
	  }
	  dtask->c_time = t;
	}
      }
    }
  }  
  
  /* 4. check for items that are timed out */
  
  list_forall(dtask, down) {
    if (TIMED(dtask->state) && t - dtask->c_time > 90) {

      /* if server's lazyness timed us out, re-request the item */
      if (sk && (dtask->state == REQUESTED || dtask->state == RRQUEUED)) {
	sendpack(sk->fd, NAP_DGET, "%s \"%s\"", dtask->nick, dtask->rfn);
	dtask->c_time = t;
	continue;
      }

      /* if remote client's lazyness timed us out, or server not
         available, go to TIMED_OUT */
      if (dtask->state == CONNECTING) {
	if (dtask->sk) {   /* should always succeed */
	  delsock(dtask->sk->fd);
	}
      }
      if (dtask->state == CONNECTING || dtask->state == WAITING) {
	free(dtask->check);
      }
      dtask->state = TIMED_OUT;
      dtask->d_time = t;
      wp(wchan, "* Download of \"%s\" from %s timed out\n", dtask->fn, dtask->nick);
      drw(wchan);
    }
  }

  list_forall(utask, up) {
    if (TIMED(utask->state) && t - utask->c_time > 90) {
      /* go to TIMED_OUT */
      if (utask->state == CONNECTING || utask->state == CONNECTING1) {
	if (utask->sk) {  /* should always succeed */
	  delsock(utask->sk->fd);
	}
      }
      utask->state = TIMED_OUT;
      utask->d_time = t;
      wp(wchan, "* Upload of \"%s\" to %s timed out\n", utask->fn, utask->nick);
      drw(wchan);
    }
  }


  /* 5. Do autopurge */

  if (0 <= autopurge && autopurge <= autopurgeup) {
    autopurgeup = autopurge;
  }

  if (0 <= autopurge && autopurge <= autopurgedown) {
    autopurgedown = autopurge;
  }

  if (autopurgedown>=0) {
    for (dtask=down; dtask; dtask=dnext) {
      /* remember next in case we delete this item. This works because
         ddownload deletes precisely the one item and no others */
      dnext = dtask->next;  
      if (STOPPED(dtask->state) && t-dtask->d_time>=autopurgedown) {
	ddownload(wchan, dtask);
      }
    }
  }
  if (autopurgeup>=0) {
    for (utask=up; utask; utask=unext) {
      /* remember next in case we delete this item. This works because
         dupload deletes precisely the one item and no others */
      unext = utask->next; 
      if (STOPPED(utask->state) && t-utask->d_time>=autopurgeup) {
	dupload(utask);
      }
    }
  }	  

  /* 6. Clean up orphaned direct browse requests */

  /* These aren't strictly "queue events" but we put them here anyway */
  /* Check if a direct browse request has timed out while in the REQUESTED
   * or CONNECTING states. */
  if (TIMED(directbrowse.state) && t - directbrowse.reqtime > 30) {
    if (directbrowse.state == CONNECTING) {
      /* we were waiting for remote to send "1" or its nick */
      if (directbrowse.sk) { /* should always succeed */
        delsock(directbrowse.sk->fd);
      }
    }
    /* close any dead incoming SENDLIST connections (the Napster v2.0 BETA 9.6
     * client, for example, will issue a SENDLIST and then hang) */
    while ((sk = findsock("sendlist")) != NULL) {
      delsock(sk->fd);
    }
    directbrowse.state = TIMED_OUT;
    srch = 0;
    wp(wchan, "* Direct browse request timed out\n");
    drw(wchan);
  }

  /* 7. Refresh the dl/ul screen, if it's up */
  if (screen == DLUL_SCREEN)
      dlul_refresh();
}
  
/* a hook called whenever a download completes: check immediately if
   there are remotely queued items by this user, and if yes, decrease
   its r_time so that it will be requested immediately (provided other
   criteria are also met, such as, there are no other downloads whose
   turn it is first). */
void rqueued_hook(char *nick) {
  download_t *task;
  sock_t *sk;
  
  sk = findsock("server");
  if (sk)
  {
    list_find(task, down, !strcasecmp(task->nick, nick) && task->state == RQUEUED);
    if (!task) 
      return;
    task->r_time -= 100;
  }
}
  
