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

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

#define _GNU_SOURCE /* needed for stdio.h:vasprintf */
#include <stdio.h>
#include <time.h>
#include <stdarg.h>
#include <termios.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <utime.h>
#include <ncurses.h>
#include <stdlib.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netdb.h>
#include <errno.h>

#include "getopt.h"
#include "defines.h"
#include "codes.h"
#include "colors.h"
#include "title.h"
#include "alias.h"
#include "event.h"
#include "handlers.h"
#include "lists.h"
#include "nap.h"
#include "scheck.h"
#include "winio.h"
#include "missing.h"

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

/* standard napster servers */

extern int ircin;
extern int ircsock;
extern int ups[], speeds[];
extern char cbuf[];
extern upload_t *up;
extern download_t *down;

chans_t *chanl = NULL, *curchan = NULL;
hotlist_t *hlist = NULL;
info_t info;          /* struct to hold some command line options */
int cloaked = 0;      /* whether we are cloaked */
int lpbrk=0, noresolv=0, reconnect=0;
int ipcs[2];
int dresize = 0;

void doquit()
{
  scrollbottom();
  if (wchan)
  {
    dscr(wchan);
    drw(wchan);
  }
  if (ircin)
  {
    ssock(ircsock, "QUIT :Leaving\n");
    close(ircsock);
    ircsock = 0;
  }
  if (nvar_default("savechannels", 0)) {
    char *tmp = NULL, *fn;
    msprintf(&tmp, CHANNELFILE, info.user);
    fn = home_file(tmp);
    free(tmp);

    if (savechans(chanl, fn)==-1) {
      wp(wchan, ""RED"* Could not save channels to %s: %s"WHITE"\n", fn, \
	 strerror(errno));
    } else {
      wp(wchan, "* Saved channels to %s\n", fn);
    }
    free(fn);
  }

  /* close incomplete files, remove turds, etc */
  while (up)
    dupload(up);
  while (down)
    ddownload(wchan, down);
  endwin();

  exit(1);
}

void tresize(int dummy)
{
  dresize = 1;
}

/* catch interrupt signal, i.e. usually "Control-C". In interactive
   mode, quit program only after receiving two interrupts within one
   second. Interrupt also causes a "while" loop to be broken - see
   dwhile in cmds.c */

void sigint(int dummy)
{
  int save_errno = errno;
  static time_t cct = 0;

  if ((time(NULL)-cct) <= 1)
    doquit();
  
  cct = time(NULL);
  lpbrk = 1;
  errno = save_errno;
}

/* the following is used as a signal "handler" if we just want the
   signal to cause a side effect, such as interrupting sleep(3) */
void noop(int dummy)
{
  /* empty */
}

/* handle the USR1 signal. This should schedule the "reconnect" command. */

void sigusr1(int dummy)
{
  reconnect = 1;
}  

void dochecks()
{
  if (dresize)
  {
    dresize = 0;
    resize();
  }
}

/* quotes a string so that it is printable. Each call frees the
   previously returned value. */
char *quote(char *s) {
  static char *buf = NULL;
  int i, size;

  if (buf) {
    free(buf);
  }

  size = strlen(s) * 1.2;

  buf = (char *)malloc(size);

  for (i=0; *s != 0; s++) {
    if (i>size-10) {
      size += 100;
      buf = (char *)realloc(buf, size);
    }
    if (32 <= *s && *s < 127) { /* printable ASCII */
      buf[i++] = *s;
    } else if (*s == '\n') {
      i += sprintf(&buf[i], "\\n");
    } else if (*s == '\r') {
      i += sprintf(&buf[i], "\\r");
    } else {
      i += sprintf(&buf[i], "\\%03o", (unsigned char)*s);
    }
  }
  buf[i] = 0;
  return buf;
}

/* returns the full path of file fn relative to user's home
   directory. Always returns an allocated string. */
char *home_file(const char *fn) {
  char *home, *res;
  int len;

  if (fn[0] == '/')         /* absolute filename? */
    return strdup(fn);

  if (0==strncmp(fn,"~/",2))   /* handle also ~/ syntax - dz */
    fn += 2;

  home = getenv("HOME");   /* else, try to find home directory */
  if (!home)
    return strdup(fn);

  len = strlen(home);
  res = (char *)malloc(len+strlen(fn)+2);
  strcpy(res, home);
  if (home[len-1] != '/')
    strcat(res, "/");
  strcat(res, fn);
  return res;
}

/* make a an allocated string from an integer, similar to strdup */
char *itoa(int n) {
  char buf[20];

  sprintf(buf, "%d", n);
  return strdup(buf);
}

/* strip whitespace from both ends of a string, descructively */
char *strip(char *s) {
  char *p;

  if (!s)
    return NULL;

  while (isspace(*s)) {
    s++;
  }

  p = s+strlen(s);
  while (p>s && isspace(p[-1]))
    p--;
  *p = 0;
  return s;
}

/* read an allocated, stripped line from stream. Return NULL on eof or
   error. */

char *nap_getline(FILE *f) {
  char *buf;
  char *res;
  int size;     /* current allocated size of buf */
  int l;
  size = 200;   /* should be large enough to hold an average line from
		 * the shared library without having to realloc() the
		 * buffer */

  buf = (char *)malloc(size);

  res = fgets(buf, size, f);
  if (!res) {          /* end of file */
    free(buf);
    return NULL;
  }
  while (!strchr(buf, '\n')) {
    size *= 2;
    buf = (char *)realloc(buf, size);
    l = strlen(buf);
    res = fgets(buf+l, size-l, f);
    if (!res)          /* end of file */
      break;
  }

  /* strip whitespace from both ends */
  res = strdup(strip(buf));
  free(buf);
  return res;
}

/* Read the config file FN. Values that were given on the command line
   override those in the config file - don't read them. Note: if user
   was specified on the command line, take special care *not* to use
   password and email address from config file. FN must be a non-null
   filename. Return 0. */

int readcfg(char *fn) {
  int other_user = 0;
  char *user=NULL, *pass=NULL, *email=NULL;
  int r;
  struct stat st;
  char *gfn;

  if (getval("user")) { /* user was specified on command line - do not
                           use password or email from config file */
    other_user = 1;
    user = strdup(getval("user"));
    pass = getrealval("pass");
    if (pass)
      pass = strdup(pass);
    email = getval("email");
    if (email)
      email = strdup(email);
  }
  
  /* read the config file - but in a wimpy way that does not overwrite
     existing values. This is because command line values should have
     priority. */
  wp(NULL, "Reading user config file %s...\n", fn);
  r = loadsets(fn, NULL, 0, 0);

  if (r==0) {
    /* success */
  } else if (r==1) {
    wp(NULL, "There were some warnings, please edit your config file.\n");
  } else if (errno == ENOENT) {   /* no such file or directory */
    wp(NULL, "%s: file not found.\n", fn);
  } else {
    wp(NULL, "Error loading config file %s: %s\n", fn, strerror(errno));
  }

  /* read the global config file, if any. Note that this is read
     *last*. I.e., the values given there are the *least*
     authoritative. */

  gfn = getval("globalconfigfile");
  if (!gfn) {
    gfn = GLOBALCONFIGFILE;
  }

  if (gfn) {
    r = stat(gfn, &st);
    if (r==0 && S_ISREG(st.st_mode)) { /* only if file exists and is regular */
      
      wp(NULL, "Reading global config file %s...\n", gfn);
      r = loadsets(gfn, NULL, 0, 0);
      
      if (r==0) {
	/* success */
      } else if (r==1) {
	wp(NULL, "There were some warnings, please edit global config file.\n");
      } else if (errno == ENOENT) {   /* no such file or directory */
	wp(NULL, "%s: file not found.\n", gfn);
      } else {
	wp(NULL, "Error loading global config file %s: %s\n", gfn, strerror(errno));
      }
    }
  }

  /* restore password and email if necessary */
  if (other_user) {
    chset("user", user);
    chset("pass", pass);
    chset("email", email);
    free(user);
    free(pass);
    free(email);
  }
  
  return 0;
}

/* Try to guess reasonable defaults for vital configuration variables.
   Where appropriate, prompt the user. Return -1 if the user refused
   to supply a username or password, else 0. FN must be a non-null
   filename for the config file to be created / updated. If FN is
   null, nothing will be written to a file. Note: if info.daemon is
   set, then we never prompt the user for anything, and supply
   defaults as we can. */

int set_defaults_interactive(char *fn) {
  int changes = 0;
  char *ans;
  char *user, *pass;
  int conn;
  int r;
  struct stat st;
  int userprompt = 0;

  ans = NULL;

  if (!getval("user")) {
    if (!info.daemon) {
      userprompt = 1;
      wp(NULL, "User: ");
      ans = nap_getline(stdin);
    }
    if (!ans || !*ans) {
      wp(NULL, "No user name given\n");
      free(ans);
      return(-1);
    } else {
      chset("user", ans);
      changes = 1;
    }
  }

  free(ans);
  ans = NULL;

  user = getval("user");
  pass = getrealval("pass"); /* note: getval("pass") does not work - it always
				returns "?" */

  /* if password not defined, check the variable pass.<user> */
  if (!pass || !strcmp(pass, "?")) {
    char tmp[10+strlen(user)];
    strcpy(tmp, "pass.");
    strcat(tmp, user);
    pass = getval(tmp);
    if (pass) {
      chset("pass", pass);
    }
  }

  if (!pass || !strcmp(pass,"?")) {
    if (userprompt) {
      wp(NULL, "Password for user %s (optional): ", user);
      ans = getpass(""); /* note: getpass(3) does not allocate its
                            result value, thus we should not free it. */
    }
    if (ans && *ans) {
      chset("pass", ans);
    }
  }
  ans = NULL;
  
  pass = getrealval("pass");
  if (!pass || !strcmp(pass, "?")) {
    char pw[9];
    struct timeval tv;
    int i;
    
    gettimeofday(&tv, NULL);
    srand(tv.tv_usec + 1000000*tv.tv_sec);
    for (i=0; i<8; i++) {
      pw[i] = rand() % 26 + 'a';
    }
    pw[8] = 0;
    chset("pass", pw);
    wp(NULL, "No password given - using random password\n");
  }

  /* if email not defined, check the variable email.<user> */
  if (!getval("email")) {
    char *email;
    char tmp[10+strlen(user)];
    strcpy(tmp, "email.");
    strcat(tmp, user);
    email = getval(tmp);
    if (email) {
      chset("email", email);
    }
  }

  if (!getval("email")) {
    if (userprompt) {
      wp(NULL, "Email for user %s (optional): ", user);
      ans = nap_getline(stdin);
    }
    if (ans && *ans) {
      chset("email", ans);
      changes = 1;
    }
    free(ans);
    ans = NULL;
  }
  
  if (!getval("email")) {
    chset("email", "anon@localhost");
    wp(NULL, "No email given - using anon@localhost\n");
  }

  if (!getval("upload")) {
    if (!info.daemon) {
      wp(NULL, "Please enter a list of directories with files that you want to share.\n");
      wp(NULL, "You can enter several directories, separated by semicolons (';').\n");
      wp(NULL, "Upload path: ");
      ans = nap_getline(stdin);
    }
    if (!ans || !*ans) {
      wp(NULL, "No upload directories given - unable to share files\n");
    } else {
      chset("upload", ans);
      changes = 1;
    }
  }

  free(ans);
  ans = NULL;

  if (!getval("download")) {
    if (!info.daemon) {
      wp(NULL, "Please enter the directory where you want to put downloaded files.\n");
      wp(NULL, "Download directory: ");
      ans = nap_getline(stdin);
    }
    if (!ans || !*ans) {
      wp(NULL, "No download directory given - will use current working directory\n");
    } else {
      chset("download", ans);
      changes = 1;
    }
  }

  free(ans);
  ans = NULL;

  if (!getval("incomplete")) {
    if (!info.daemon) {
      wp(NULL, "Please enter the directory where you want to put incomplete files.\n");
      wp(NULL, "Incomplete directory: ");
      ans = nap_getline(stdin);
    }
    if (!ans || !*ans) {
      wp(NULL, "No incomplete directory given - will use download directory\n");
    } else {
      chset("incomplete", ans);
      changes = 1;
    }
  }

  free(ans);
  ans = NULL;

  if (!getval("dataport")) {
    if (!info.daemon) {
      wp(NULL, "Are you behind a firewall? [y=yes, n=no, u=unsure]: ");
      ans = nap_getline(stdin);
      if (ans && ans[0]=='y') {
	wp(NULL, "Firewalled client - setting dataport to 0\n");
	chset("dataport", "0");
      } else if (ans && ans[0]=='n') {
	wp(NULL, "Using dataport 6699-6799\n");
	chset("dataport", "6699-6799");
      } else {
	wp(NULL, "Okay, I will assume that you are not firewalled. If you\n"
	   "find out later that you are, please set your dataport to 0.\n");
	wp(NULL, "Using dataport 6699-6799\n");
	chset("dataport", "6699-6799");
      }
    } else {
      chset("dataport", "6699-6799");
      wp(NULL, "No dataport given - using %s\n", getval("dataport"));
    }
  }

  free(ans);
  ans = NULL;

  if (!getval("connection")) {
    if (!info.daemon) {
      wp(NULL, 
	 "          Connection | Number\n"
	 "          -------------------\n"
	 "          Unknown    |  0\n"
	 "          14.4       |  1\n"
	 "          28.8       |  2\n"
	 "          33.6       |  3\n"
	 "          56.7       |  4\n"
	 "          64K ISDN   |  5\n"
	 "          128K ISDN  |  6\n"
	 "          Cable      |  7\n"
	 "          DSL        |  8\n"
	 "          T1         |  9\n"
	 "          T3 or >    | 10\n"
	 );
      wp(NULL, "How fast is your internet connection?\n");
      wp(NULL, "Please choose 0--10 from the chart: [4] ");
      ans = nap_getline(stdin);
    }
    if (!ans || !*ans) {
      chset("connection", "4");
      changes = 1;
    } else {
      chset("connection", ans);
    }
  }

  free(ans);
  ans = NULL;

  conn = nvar("connection");
  if (conn < 0 || conn > 10) {
    wp(NULL, "Invalid connection given - using 0\n");
    chset("connection", NULL);
    conn = 0;
  }
  
  if (!getval("maxuploads")) {
    ans = itoa(ups[conn]);
    chset("maxuploads", ans);
    wp(NULL, "Allowing %s simultaneous uploads\n", ans);
    free(ans);
    ans = NULL;
  }

  /* if anything changed, and if non-null fn was given, ask user if he
     wants to save changes. */

  if (fn && !info.daemon) {
    r = stat(fn, &st);  /* r!=0 if config file does not yet exist */
    
    if (r || changes) {
      if (r) {
	wp(NULL, "Create a config file (%s) with these settings? [y] ", fn);
      } else {
	wp(NULL, "Save these settings to your config file (%s)? [y] ", fn);
      }
      ans = nap_getline(stdin);
      if (ans && (!strcmp(ans, "") || !strcasecmp(ans, "y") || !strcasecmp(ans, "yes"))) {
	r = savesets(fn);
	if (r==-1) {
	  wp(NULL, "Error saving config file: %s\n", strerror(errno));
	}
      } else {
	wp(NULL, "Not saving.\n");
      }
      free(ans);
      ans = NULL;
    }
  }

  return 0;
}

/* print to a newly allocated string *str. If *str is non-NULL, free 
   the old content. */
int msprintf(char **str, const char *fmt, ...)
{
  va_list args;
  int r;

  if (*str != NULL) {
    free(*str);
    *str = NULL;
  }

  va_start(args, fmt);
  r = vasprintf(str, fmt, args);
  va_end(args);

  if (r<0) {
    return -1;
  } else {
    /* Note: the info page for libc claims that asprintf (thus
       vasprintf) returns the number of characters *allocated*, but it
       actually seems to return the number of characters *printed*
       (not including the terminating 0). Thus it seems that we could
       return r, but to be safe and unambiguous, we explicitly
       calculate strlen(*str). */

    return strlen(*str);
  }
}

/* count the number of occurences of character t in string buf */
/* not used */
int strcnt(char *buf, char t)
{
  int i,r;
  
  for (i=0,r=0;buf[i];i++)
    if (buf[i] == t)
      r++;
  
  return(r);
}

static struct option longopts[] = {
  {"help",        0, 0, 'h'},
  {"version",     0, 0, 'v'},
  {"daemon",      0, 0, 'q'},
  {"autorestart", 0, 0, 'a'},
  {"build-only",  0, 0, 'B'},
  {"build",       0, 0, 'b'},
  {"nobuild",     0, 0, 'N'},
  {"notitle",     0, 0, 't'},
  {"reconnect",   0, 0, 'r'},
  {"nxterm",      0, 0, 'l'},
  {"transparent", 0, 0, 'T'},
  {"create",      0, 0, 'm'},
  {"noserver",    0, 0, 'n'},
  {"config",      1, 0, 'f'},
  {"server",      1, 0, 's'},
  {"debug",       1, 0, 'd'},
  {"log",         1, 0, 'x'},
  {"logall",      1, 0, 'g'},
  {"user",        1, 0, 'u'},
  {"pass",        1, 0, 'p'},
  {"password",    1, 0, 'p'},
  {"email",       1, 0, 'e'},
  {"upload",      1, 0, 'U'},
  {"download",    1, 0, 'D'},
  {"incomplete",  1, 0, 'I'},
  {"dataport",    1, 0, 'P'},
  {"connection",  1, 0, 'C'},
  {"maxuploads",  1, 0, 'M'},
  {"option",      1, 0, 'o'},
  {0, 0, 0, 0}
};

void phelp(char *nm)
{
  printf("Usage: %s [options]\n", nm);
  printf("Options:\n");
  printf("-h, --help             - print this help message\n");
  printf("-v, --version          - print version info and exit\n");
  printf("-b, --build            - build library of shared files to send to server\n");
  printf("-B, --build-only       - build library and exit\n");
  printf("-N, --nobuild          - do not build library, even if it is out of date\n");
  printf("-m, --create           - create new account on server (obsolete)\n");
  printf("-r, --reconnect        - keep reconnecting until server connection established\n");
  printf("-a, --autorestart      - automatically reconnect when connection to server lost\n");
  printf("-q, --daemon           - run without user interface; file sharing only\n");
  printf("-t, --notitle          - do not display title bar (fixes messed-up displays)\n");
  printf("-l, --nxterm           - try using a terminal which is compatible with most\n"
	 "                         systems (fixes some messed-up displays)\n");
  printf("-T, --transparent      - use terminal's default background instead of black\n");
  printf("-n, --noserver         - start up without connecting to a server\n");
  printf("-f, --config fn        - config file to use (default $HOME/"CONFIGFILE")\n");
  printf("-x, --log fn           - log all transfers to a specific filename\n");
  printf("-g, --logall fn        - log everything to a specific filename\n");
  printf("-s, --server sv:port   - select a specific server (multiple -s opts possible)\n");
  printf("-d, --debug n          - set debug level\n");
  printf("-u, --user str         - specify napster username\n");
  printf("-p, --pass str         - specify user's password\n");
  printf("-e, --email str        - specify user's email address\n");
  printf("-U, --upload dir       - specify upload directory (multiple -U opts possible)\n");
  printf("-D, --download dir     - specify download directory\n");
  printf("-I, --incomplete dir   - specify directory for incomplete files\n");
  printf("-P, --dataport n-m     - specify port(s) to use for incoming upload requests\n");
  printf("-C, --connection n     - specify connection speed number (see README)\n");
  printf("-M, --maxuploads n     - specify maximum number of simultaneous uploads\n");
  printf("-o, --option var=value - set user variable\n");
}

void pversion() {
  printf("nap v"VERSION", a console napster client.\n"
	"Written by Kevin Sullivan. Modified by Peter Selinger and others.\n");
}

void dopts(int argc, char **argv)
{
  int c;
  char *p;
  
  /* defaults */
  info.name = argv[0];
  info.user = NULL;
  info.pass = NULL;
  info.email = NULL;
  info.up = NULL;
  info.down = NULL;
  info.dataport = NULL;
  info.shared_filename = NULL;
  info.logallfile = NULL;
  info.logfile = NULL;
  info.daemon = 0;
  info.nxterm = 0;
  info.autorestart = 0;
  info.serverlist = NULL;
  info.build = 0;
  info.create = 0;
  info.reconnect = 0;
  info.noserver = 0;
  info.notop = 0;
  info.transparent = 0;
  info.bandwidthdown = -1;
  info.bandwidthup = -1;
  info.bandwidthdownconn = -1;
  info.bandwidthupconn = -1;
  info.sflag = 0;

  while ((c = getopt_long(argc, argv, "h?vbBNmrqatlTnf:x:g:s:d:u:p:e:U:D:P:C:M:o:", 
			  longopts, NULL)) != -1)
  {
    switch (c)
    {
    case 'q':
      info.daemon=1;
      break;
    case 'a':
      chset("autorestart", "1");
      break;
    case 'd':
      chset("debug", optarg);
      break;
    case 'b':
      info.build = 1;
      break;
    case 'B':
      info.build = 2;
      break;
    case 'N':
      info.build = -1;
      break;
    case 'm':
      info.create = 1;
      break;
    case 'r':
      info.reconnect = 1;
      break;
    case 'n':
      info.noserver = 1;
    case 'f':
      chset("configfile", optarg);
      break;
    case 't':
      info.notop = 1;
      break;
    case 's':
      if (info.serverlist == NULL) {
	info.serverlist = strdup(optarg);
      } else {
	info.serverlist = (char *)realloc(info.serverlist, strlen(info.serverlist)+strlen(optarg)+2);
	strcat(info.serverlist, ";");
	strcat(info.serverlist, optarg);
      }
      info.sflag = 1;
      break;
    case 'o':
      p = strchr(optarg, '=');
      if (!p) {
	chset(optarg, "1");
      } else {
	*p++ = 0;
	chset(optarg, p);
      }
      break;
    case 'l':
      info.nxterm = 1;
      break;
    case 'T':
      info.transparent = 1;
      break;
    case 'g':
      info.logallfile = strdup(optarg);
      break;
    case 'x':
      info.logfile = strdup(optarg);
      break;
    case '?':
      wp(NULL, "Try --help for more info\n");
      exit(1);
      break;
    case 'h':
      phelp(*argv);
      exit(0);
      break;
    case 'v':
      pversion();
      exit(1);
      break;
    case 'u':
      info.user = strdup(optarg);
      break;
    case 'p':
      info.pass = strdup(optarg);
      break;
    case 'e':
      info.email = strdup(optarg);
      break;
    case 'U':  /* note there can be several -U options, each adding a dir */
      if (!info.up) {
	info.up = strdup(optarg);
	break;
      }
      info.up = realloc(info.up, strlen(info.up)+strlen(optarg)+2);
      if (info.up) {
	strcat(info.up, ";");
	strcat(info.up, optarg);
      }
      break;	   
    case 'D':
      info.down = strdup(optarg);
      break;
    case 'I':
      info.incomplete = strdup(optarg);
      break;
    case 'P':
      info.dataport = strdup(optarg);
      break;
    case 'C':
      chset("connection", optarg);
      break;
    case 'M':
      chset("maxuploads", optarg);
      break;
    default:
      wp(NULL, "Invalid option -- %c\n", c);
      exit(1);
    }
  }

  if (optind < argc) {
    wp(NULL, "%s: unrecognized argument -- %s\n", *argv, argv[optind]);
    wp(NULL, "Try --help for more info\n");
    exit(1);
  }    

  return;
}

/* works like rename(2), except that it can also move a file between
   different filesystems. Note that the latter operation is not done
   atomically. Returns 0 on success, -1 on error with errno set. */
int move_file(const char *oldpath, const char *newpath) {
  int r, c;
  FILE *f1;
  FILE *f2;
  struct stat st;
  struct utimbuf u;

  r = rename(oldpath, newpath);
  if (!r)
    return(0);
  
  if (errno != EXDEV)
    return(r);

  /* errno == EXDEV, so we must copy the file */
  f1 = fopen(oldpath, "r");
  if (!f1) {
    return(-1);
  }

  f2 = fopen(newpath, "w");
  if (!f2) {
    fclose(f1);
    return(-1);
  }

  for (c=fgetc(f1); c!=EOF; c=fgetc(f1)) {
    r = fputc(c, f2);
    if (r==EOF) {
      fclose(f1);
      fclose(f2);
      unlink(newpath);
      return(-1);
    }
  }

  fclose(f1);
  fclose(f2);

  /* done copying the file, now attempt to copy its attributes. We
     ignore errors during this part. */

  r = stat(oldpath, &st);
  if (r) 
    goto remove;

  chmod(newpath, st.st_mode);
  chown(newpath, st.st_uid, st.st_gid);
  u.actime = st.st_atime;
  u.modtime = st.st_mtime;
  utime(newpath, &u);
  
 remove:
  /* now remove the old file */
  unlink(oldpath);

  return(0);
}

/* set up the configuration directory. For version 1.4.4-ps9, move old
   config files to their new locations. In later versions this extra
   step may be omitted. Return 1 if directory was created, else 0.
   Exit nap if the directory does not exist but could not be created.  */

int setup_configdir(void) {
  struct stat st;
  char *configdir, *tmp1, *tmp2;
  int r;

  configdir = home_file(CONFIGDIR);

  /* if the config directory already exists, return. In case there is
     a file .nap/nap.conf (as in older debian distributions), rename it. */
  r = stat(configdir, &st);
  if ((r == 0) && S_ISDIR(st.st_mode)) {
    tmp1 = home_file(CONFIGDIR"/nap.conf");
    tmp2 = home_file(CONFIGFILE);
    move_file(tmp1, tmp2);
    free(tmp1);
    free(tmp2);
    free(configdir);
    return 0;
  }

  /* else, set it up */
  r = mkdir(configdir, 0700);
  if (r==-1) {
    wp(NULL, "Could not create the config directory %s: %s\n", configdir, strerror(errno));
    exit(1);
  }
  free(configdir);

  wp(NULL, "Created config directory %s\n", configdir);

  /* move the most important config files (or rather those with fixed
     names) to their new locations.  Ignore errors (which probably
     mean some files did not exist). */
  tmp1 = home_file(".napconf");
  tmp2 = home_file(CONFIGFILE);
  move_file(tmp1, tmp2);
  free(tmp1);
  free(tmp2);

  tmp1 = home_file(".nap_shared");
  tmp2 = home_file(LIBRARYFILE);
  move_file(tmp1, tmp2);
  free(tmp1);
  free(tmp2);

  tmp1 = home_file(".nap_aliases");
  tmp2 = home_file(ALIASFILE);
  move_file(tmp1, tmp2);
  free(tmp1);
  free(tmp2);

  tmp1 = home_file(".nap_handlers");
  tmp2 = home_file(HANDLERFILE);
  move_file(tmp1, tmp2);
  free(tmp1);
  free(tmp2);

  return 1;
}

/* return the next server from the serverlist, or NULL if there are no
   more. Start from the beginning of the list if serverlist has
   changed since last call, or if NULL was returned by last call */

char *next_server(char *serverlist) {
  static char *last_serverlist = NULL; /* read-only copy of serverlist */
  static char *serverlist_dup = NULL;  /* writable copy of serverlist */
  static char *next_tok = NULL;        /* points into serverlist_dup */
  char *p;

  /* just in case */
  if (!serverlist) {
    return NULL;
  }

  if (last_serverlist && strcmp(serverlist,last_serverlist) != 0) {
    free(last_serverlist);
    last_serverlist = NULL;
  }
  
  if (!last_serverlist) {
    last_serverlist = strdup(serverlist);
    free(serverlist_dup);
    serverlist_dup = strdup(serverlist);
    next_tok = serverlist_dup;
  }

  p = strsep(&next_tok, ";");

  if (p) {
    return p;
  } else {
    free(serverlist_dup);
    serverlist_dup = strdup(serverlist);
    next_tok = serverlist_dup;
    return NULL;
  }
}

/* return the "connection speed", a number between 0-10. For the most
   part, we trust the number given by the user; however, if the user
   has put a serious bandwidth limit on uploads, then we do not allow
   her to claim she has a high connection speed. Note: this is not
   perfect, since the connection speed is only announced to the server
   at connect time, and the bandwidth limit might be changed
   later. But what can we do? */

int connection(void) {
  int c;
  int limit=0;

  c = nvar("connection");
  if (c<0 || c>10) {
    c = 0;
  }
  
  if (info.bandwidthup != 0) {
    limit = info.bandwidthup;
  }
  if (info.bandwidthupconn != 0) {
    if (!limit || info.bandwidthupconn < limit)
      limit = info.bandwidthupconn;
  }
  if (limit <= 0)
    return c;

  while (limit < speeds[c]) 
    c--;

  return c;
}

/* the main procedure: read command line, parse configuration file,
   check and create various things and then enter event loop; afterwards 
   shut down and quit */

int main(int argc, char *argv[])
{
  int s, n, c, r;
  char *srv;
  char *t = NULL;
  FILE *fl;
  char *fn;
  const char *errmsg;
  char *libraryfile, *aliasfile, *handlerfile;
  char *ans, *serial;
  FILE *nfile;
  char *nfilename;

  /* set up some signal handlers (more below) */
  signal(SIGCHLD, SIG_IGN);
  signal(SIGPIPE, SIG_IGN);

  /* initialize main screen's internal state */
  cbuf[0] = 0;

  screen = MAIN_SCREEN;
  
  dopts(argc, argv);
  
  /* create the ~/.nap directory, if it does not already exist. Move
     any old config files to their new locations. */

  setup_configdir();

  /* open log files, if any. Note we do this before reading config file, 
   so that logging starts early. */

  fn = getval("configfile");
  if (!fn)
    fn = home_file(CONFIGFILE);
  else
    fn = strdup(fn);

  readcfg(fn);
  
  /* set defaults and prompt user for values that were left unset by
     command line and config file. Note: if --build-only is given, we
     do not need to generate this information. */
  
  if (info.build != 2) {
    r = set_defaults_interactive(fn);
    if (r==-1) {
      free(fn);
      return(1);
    }
  }

  free(fn);

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

  if (info.build != -1) {
    if (info.build)
      {
	wp(NULL, "Building library...\n");
	if (buildflist(libraryfile, info.up) == -1) {
	  wp(NULL, "There was an error building your library.\n");
	  free(libraryfile);
	  return(1);
	}
      } else if (!up_to_date(libraryfile, info.up))
	{
	  wp(NULL, "Your library is not up-to-date. Rebuilding...\n");
	  if (buildflist(libraryfile, info.up) == -1) {
	    wp(NULL, "There was an error building your library.\n");
	    free(libraryfile);
	    return(1);
	  }
	}
    
    if (info.build == 2)
      return(1);
  }

  fl = fopen(libraryfile, "r");
  if (!fl)
  {
    wp(NULL, "Error reading %s: %s\nTry running \"nap -b\".\n", \
       libraryfile, strerror(errno));
    free(libraryfile);
    return(1);
  }
  fclose(fl);

  /* check for news on the client */
  if (nvar_default("nonews", 0)==0 && !info.daemon) {
    int timeout;

    timeout = nvar_default("newstimeout", NEWSTIMEOUT);

    wp(NULL, "Checking for new releases of nap... ");
    r = checknv(1, timeout, &errmsg, &serial);
    if (r==1 && serial) {
      wp(NULL, "(o)kay, (q)uit, (d)on't prompt again: ");
      ans = nap_getline(stdin);
      if (ans[0]=='q') {
	exit(0);
      } else if (ans[0]=='d') {
	nfilename = home_file(OLDNEWSFILE);
	nfile = fopen(nfilename, "w");
	free(nfilename);
	if (nfile) {
	  fprintf(nfile, "%s\n", serial);
	  fclose(nfile);
	}
      }
      free(ans);
    } else if (r==1) {
      wp(NULL, "Press return to continue.\n");
      ans = nap_getline(stdin);
      free(ans);
    } else if (r==0) {
      wp(NULL, "none found.\n");
    } else if (r==2) {  /* old news, don't prompt */
      /* do nothing */
    } else {
      wp(NULL, "failed.\n");
    }
  }
  
  /* get server list from metaserver */
  if (!info.sflag && nvar_default("nometa", 0)==0) {
    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(NULL, "Reading server list from %s...\n", url);

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

  /* note: we no longer check whether the download directory is
     writable until we actually try to write something to it */
  
  /* before connecting to the server, let's figure out our port number
     for incoming upload requests. We have to find a port that's not
     in use, and we have to communicate the port number to the server,
     so that's why we do this early rather than later. -PS */

  info.port = initfserv(info.dataport);
  if (info.port == -1) {
    if (strchr(info.dataport, '-')) {
      wp(NULL, "Warning: could not open a port in the range %s: %s\n", info.dataport, strerror(errno));
    } else {
      wp(NULL, "Warning: could not open port %s: %s\n", info.dataport, strerror(errno));
    }
    wp(NULL, "Unable to serve incoming upload requests; will proceed as if firewalled\n");
    info.port = 0;
  } else {
    wp(NULL, "Using port %d for incoming client connections\n", info.port);
  }

  if (info.noserver) {
    goto noserver;
  }

  /* connect to a server */

  srv = next_server(info.serverlist);
  if (srv==NULL) {
    wp(NULL, "No server specified\n");
    return(1);
  }

  wp(NULL, "Getting best host...\n");
  for (;;)
  {
    /* if we have reached the end of the server list, go back to beginning, 
       if info.reconnect is set - else give up */
    if (srv==NULL) {
      if (info.reconnect) {
	srv = next_server(info.serverlist);
	sleep(1);
      } else {
	return(1);
      }
    }

    srv = strip(srv);   /* remove whitespace on both ends */
    wp(NULL, "Trying %s\n", srv);
    s = 2;
    while (s == 2)
      s = conn(srv);
    if (s == -1)
    {
      srv = next_server(info.serverlist);
      continue;
    }
  
    c = connection();

    if (info.create)
    {
      wp(NULL, "Creating account...\n");
      n = makeact(s, info.user, info.pass, info.port, c, info.email);
      if (n == -1)
      {
        close(s);
	srv = next_server(info.serverlist);
        continue;
      }
      else 
      {
	info.create = 0;
        break; /* if we just created an account, we are already logged in */
      }
    }
    else
    {
      wp(NULL, "Logging in...\n");
      n = login(s, info.user, info.pass, info.port, c, info.email);
      if (n == -1)
      {
        close(s);
	srv = next_server(info.serverlist);
        continue;
      }
      else 
      {
        break;
      }
    }
  }
  
  /* announce our success */
  {
    struct sockaddr_in dst;
    int frmlen = sizeof(dst);
    if (!getpeername(s, (struct sockaddr *)&dst, &frmlen))
      wp(NULL, "Connected to %s:%i\n", inet_ntoa(dst.sin_addr), ntohs(dst.sin_port));
  }

  addsock(s, "server", S_R, inserv);

  t = glistn(info.user);
  checkhotlist(s, t);
  free(t);
 
  if (nvar_default("savechannels", 0)) {
    char *tmp = NULL, *fn;
    msprintf(&tmp, CHANNELFILE, info.user);
    fn = home_file(tmp);
    free(tmp);

    if (loadchans(s, fn)==-1) {
      wp(wchan, ""RED"* Could not load channels from %s: %s"WHITE"\n", fn, \
	 strerror(errno));
    } 
    drw(wchan);
    free(fn);
  }

  lfiles(s, libraryfile);

  free(libraryfile);

 noserver:  /* we get here if skipped connecting to a server */

  pipe(ipcs);
  
  addsock(ipcs[0], "ipc", S_R, inipc);
  if (!info.daemon)
    addsock(0, "input", S_R, input);
  initwin(info.nxterm);

  signal(SIGUSR1, sigusr1);

  if (!info.daemon) {
    signal(SIGWINCH, tresize);
    signal(SIGINT, sigint);
  }

  aliasfile = home_file(ALIASFILE);
  handlerfile = home_file(HANDLERFILE);
  loadaliases(aliasfile);
  loadhandlers(handlerfile);
  free(aliasfile);
  free(handlerfile);

  wp(wchan, "%s\n", title);
  drw(wchan);

  usleep(400000);

  /* enter the main event loop. */
  sockfunc(wchan, winput);
  
  doquit();
  
  return(1);
}

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

/* Resolve the given string into an IP address in network byte
   order. The string may either be an IP address in dot notation
   ("127.0.0.1"), or a host name ("server.napster.com"). The answer is
   written into *inp. Return non-zero if the address is valid, 0 (with
   h_errno set) if not. */
int resolve(const char *host, struct in_addr *inp)
{
  struct hostent *hst;
  int r;

  r = inet_aton(host, inp);
  if (r) {
    return r;
  }

  hst = gethostbyname(host);
  if (!hst) {
    return 0;
  } else if (hst->h_addrtype != AF_INET || hst->h_length != 4) {
    /* not an IPv4 address??? */
    h_errno = HOST_NOT_FOUND;
    return 0;
  }
  memcpy(&inp->s_addr, hst->h_addr, 4);
  return 1;
}

/* return the formatted IP address of the remote peer for the given
   socket. The returned string is static and constant and will be
   overwritten with the next call, or the next call to ntoa. */
const char *getpeerip(int fd) {
  int r;
  struct sockaddr_in sa;
  socklen_t l = sizeof(sa);

  r = getpeername(fd, (struct sockaddr *)&sa, &l);
  if (r==-1 || l<sizeof(sa) || sa.sin_family != AF_INET)
    return "unknown";
  else 
    return inet_ntoa(sa.sin_addr);
}

/* swap byte order of a (32 bit) word */
unsigned long int swapl(unsigned long int x) {
  return ((x & 0xff) << 24) | ((x & 0xff00) << 8) | ((x & 0xff0000) >> 8) \
    | ((x & 0xff000000) >> 24);
}

/* just like connect(2), except that we time out after t seconds. No
   timeout if t=0. Return value is -1 on error, else 0. If a timeout
   occurred, we set errno to ETIMEDOUT. */

int connect_t(int fd, struct sockaddr *serv_addr, int addlen, int t)
{
  long flags;
  int r;
  fd_set fds;
  struct timeval sec;

  if (t==0) {
    return connect(fd, serv_addr, addlen);
  }

  /* set to non-blocking */
  flags = fcntl(fd, F_GETFL);

  fcntl(fd, F_SETFL, flags | O_NONBLOCK);

  r = connect(fd, serv_addr, addlen);

  /* restore original flags */
  fcntl(fd, F_SETFL, flags);

  if (r == -1 && errno != EINPROGRESS) {
    return -1;
  }

  /* wait up to timeout seconds until this fd becomes writeable */

  FD_ZERO(&fds);
  FD_SET(fd, &fds);
  sec.tv_sec = t;
  sec.tv_usec = 0;
  r = select(fd+1, NULL, &fds, NULL, &sec);
  
  if (r == -1) {  /* error */
    return -1;
  }

  if (r == 0) {   /* timeout */
    errno = ETIMEDOUT;
    return -1;
  }

  return 0;
}

/* make connection to a napster server. As of 1.5.1, we drop support
   for "napster-style" meta-servers, since they no longer seem to
   exist. If "host" contains no ":", then use defines.h:DEFAULT_PORT
   as the port */
int conn(char *host)
{
  int s;
  struct sockaddr_in dst;
  char *buf;
  char *p;
  int r;
  int timeout;
  unsigned short port;

  timeout = nvar_default("connecttimeout", CONNECTTIMEOUT);

  buf = strdup(host);
  if (!buf) {
    wp(wchan, "Error: %s\n", strerror(errno));
    drw(wchan);
    return -1;
  }

  p = strchr(buf, ':');
  if (p) {
    port = atoi(p+1);
    *p = 0;
  } else {
    port = DEFAULT_PORT;
  }
    
  r = resolve(buf, &dst.sin_addr);
  if (!r)
  {
    wp(wchan, "Error resolving host %s: %s\n", buf, hstrerror(h_errno));
    drw(wchan);
    free(buf);
    return(-1);
  }
  dst.sin_port = htons(port);
  dst.sin_family = AF_INET;
  
  s = socket(AF_INET, SOCK_STREAM, 0);
  if (s == -1)
  {
    wp(wchan, "Error creating socket: %s\n", strerror(errno));
    drw(wchan);
    free(buf);
    return(-1);
  }
  
  wp(wchan, "Connecting...\n");
  drw(wchan);

  if (connect_t(s, (struct sockaddr *)&dst, sizeof(dst), timeout) == -1)
  {
    if (!lpbrk) {
      wp(wchan, "Error connecting to %s:%d: %s\n", buf, port, strerror(errno));
      drw(wchan);
    }
    close(s);
    free(buf);
    return(-1);
  }

  setkeepalive(s);
  
  free(buf);
  return(s);
}

int login(int s, char *user, char *pass, int data, int conn, char *email)
{
  char *t1;
  phead_t *rcv;
  int r;
  int timeout;
  char *identity = getval("identity");

  timeout = nvar_default("connecttimeout", CONNECTTIMEOUT);

  if (timeout==0) {
    timeout=-1;
  }

  r = sendpack(s, NAP_LOGIN, "%s %s %i \"%s\" %i", user, pass, data, identity ? identity : IDENTITY, conn);
  if (r<0) {
    wp(wchan, "Error: %s\n", strerror(errno));
    drw(wchan);
    return(-1);
  }

  r = recvpack_t(s, &t1, &rcv, timeout);
  while (r == -2) {
    r = recvpack_t(s, &t1, &rcv, timeout);
  }

  if (r == -1) {
    wp(wchan, "Error while receiving data: %s\n", strerror(errno));
    drw(wchan);
    return(-1);
  } else if (r == -3) {
    wp(wchan, "Error: unexpected end of file from server\n");
    drw(wchan);
    return(-1);
  } else if (r == -4) {
    wp(wchan, "Error: Connection timed out\n");
    drw(wchan);
    return(-1);
  }

  if (rcv->op == NAP_LOGERROR)
  {
    wp(wchan, "Error: %s\n", t1);
    drw(wchan);
    free(t1);
    free(rcv);
    return(-1);
  }

  /* the following works for opennap and slavanap. The "email address"
     they send is of the form anon@servername. Slavanap also sends an
     additional integer. */
  if (strncmp(t1, "anon@", 5)==0) {
    char *p = strchr(t1, ' ');
    if (p) {
      *p = 0;
    }
    wp(wchan, "Server's name appears to be %s\n", t1+5);
    drw(wchan);
  }

  free(t1);
  free(rcv);
  
  return r;
}

int makeact(int s, char *user, char *pass, int data, int conn, char *email)
{
  char *t1=NULL;
  phead_t *rcv;
  int r;
  int timeout;
  char *identity = getval("identity");

  timeout = nvar_default("connecttimeout", CONNECTTIMEOUT);

  if (timeout==0) {
    timeout=-1;
  }

  r = sendpack(s, NAP_MKUSER, user);
  if (r<0) {
    wp(wchan, "Error: %s\n", strerror(errno));
    drw(wchan);
    return(-1);
  }
  
  r = recvpack_t(s, &t1, &rcv, timeout);
  while (r == -2) {
    r = recvpack_t(s, &t1, &rcv, timeout);
  }

  if (r == -1) {
    wp(wchan, "Error while receiving data: %s\n", strerror(errno));
    drw(wchan);
    return(-1);
  } else if (r == -3) {
    wp(wchan, "Error: unexpected end of file from server\n");
    drw(wchan);
    return(-1);
  } else if (r == -4) {
    wp(wchan, "Error: Connection timed out\n");
    drw(wchan);
    return(-1);
  }
  
  if (rcv->op == NAP_UNTK)
  {
    wp(wchan, "Error: Username taken\n");
    drw(wchan);
    free(t1);
    free(rcv);
    return(-1);
  }
  else if (rcv->op == NAP_UNBAD)
  {
    wp(wchan, "Error: Invalid username\n");
    drw(wchan);
    free(t1);
    free(rcv);
    return(-1);
  }
  else if (rcv->op == NAP_UNOK) {
    wp(wchan, "Registered username\n");
    drw(wchan);
  }
  else
  {
    wp(wchan, "Unknown op: 0x%x\n", rcv->op);
    drw(wchan);
    free(t1);
    free(rcv);
    return(-1);
  }
  
  free(t1);
  free(rcv);
  t1 = NULL;
  
  r = sendpack(s, NAP_REG, "%s %s %i \"%s\" %i %s", user, pass, data, identity ? identity : IDENTITY, conn, email);
  if (r<0) {
    wp(wchan, "Error: %s\n", strerror(errno));
    drw(wchan);
    return(-1);
  }

  r = recvpack_t(s, &t1, &rcv, timeout);
  while (r == -2) {
    r = recvpack_t(s, &t1, &rcv, timeout);
  }

  if (r == -1) {
    wp(wchan, "Error while receiving data: %s\n", strerror(errno));
    drw(wchan);
    return(-1);
  } else if (r == -3) {
    wp(wchan, "Error: unexpected end of file from server\n");
    drw(wchan);
    return(-1);
  } else if (r == -4) {
    wp(wchan, "Error: Connection timed out\n");
    drw(wchan);
    return(-1);
  }
  
  if (rcv->op == NAP_LOGERROR)
  {
    wp(wchan, "Error: %s\n", t1);
    drw(wchan);
    free(t1);
    free(rcv);
    return(-1);
  }
  
  /* the following works for opennap and slavanap. The "email address"
     they send is of the form anon@servername. Slavanap also sends an
     additional integer. */
  if (strncmp(t1, "anon@", 5)==0) {
    char *p = strchr(t1, ' ');
    if (p) {
      *p = 0;
    }
    wp(wchan, "Server's name appears to be %s\n", t1+5);
    drw(wchan);
  }

  free(t1);
  free(rcv);
  
  return r;
}

/* calculate the name of the hotlist file */
char *glistn(char *t)
{
  char *r1 = NULL;
  char *r2;
  
  msprintf(&r1, HOTLISTFILE, t);

  r2 = home_file(r1);
  free(r1);

  return(r2);
}

/* send initial hotlist during the login process */
void checkhotlist(int s, char *fn)
{
  FILE *f;
  char *rd;
  hotlist_t *elt;
  
  /* first delete old hlist, if any */
  list_forall_unlink(elt, hlist) {
    free(elt->nm);
    free(elt);
  }

  f = fopen(fn, "r");
  if (!f)
    return;
  
  while ((rd = nap_getline(f)) != NULL)
  {
    if (!strlen(rd)) {
      free(rd);
      break;
    }

    /* create new hotlist entry */
    elt = (hotlist_t *)malloc(sizeof(hotlist_t));
    elt->nm = rd;
    elt->conn = 0;
    elt->on = 0;
    /* add it to the list */
    list_append(hotlist_t, hlist, elt);
    /* and register it with server */
    sendpack(s, NOTIFY_CHECK, "%s", rd);
  }
  fclose(f);
}

/* Get news on the client. Write it to the given fd. Used by the
   command /news, or on startup. This simply looks at a specific web
   page which is assumed to have such news. Forward compatibility is
   important here, thus every line of that page starts with a keyword,
   and we ignore lines whose keyword we don't know. Note: this
   function may be executed in a child process, since it does not
   update any state. Return 1 if there was news, 0 if there was none,
   and -1 on error. Return 2 if there was news, but serial!=NULL and
   we have seen these very news before. In case of -1, the variable
   *errmsg is set to a (static) error message. If fd is 1, do some
   special formatting, write in black and white, and send the output
   through wp() for the log file's benefit. If we return 1 (there is
   new news) and serial!=NULL and the news had a serial identifier,
   that identifier is returned in *serial. */
int checknv(int fd, int timeout, const char **errmsg, char **serial)
{
  FILE *f, *nfile;
  int relevant, news, oldnews;
  char *b, *c;
  char *nfilename;

  if (serial) {
    *serial = NULL;
  }

  f = open_url(NEWSURL, timeout, errmsg);
  if (!f) {
    return -1;
  }

  relevant = 0;
  news = 0;
  oldnews = 0;
  
  /* parse each line of the file */
  while ((b = nap_getline(f)) != NULL) {
    if (*b == 0 || !strncmp(b, "#", 1)) { /* skip comments and blank lines */
      free(b);
      continue;
    }
    if (serial && !strncmp(b, "SERIAL ", 7)) {
      /* check to see if this version of the news is old */
      nfilename = home_file(OLDNEWSFILE);
      nfile = fopen(nfilename, "r");
      free(nfilename);
      if (nfile) {
	c = nap_getline(nfile);
	fclose(nfile);
	if (c && !strcmp(b+7, c)) {
	  oldnews = 1;
	}
	if (c) {
	  free(c);
	}
      }
      if (*serial) {
	free(*serial);
      }
      *serial = strdup(b+7);
      free(b);
      continue;
    }
    if (!strncmp(b, "VERSION ", 8) && !strcmp(b+8, VERSION)) {
      relevant = 1;      /* news for this client */
      free(b);
      continue;
    }
    if (!strncmp(b, "OTHER ", 6) || !strcmp(b, "OTHER")) {
      relevant = 1;
      free(b);
      continue;
    }
    if (relevant && (!strcmp(b, "END") || !strncmp(b, "END ", 4))) {
      free(b);
      break;
    }
    if (relevant && !strncmp(b, "ECHO ", 5)) {
      if (news==0 && fd==1) {
	wp(NULL, "\n**********************************************************************\n");
	wp(NULL, "NEWS:\n");
      }
      if (fd==1) {
	wp(NULL, "%s\n", b+5);
      } else {
	ssock(fd, ""MAGENTA"* %s"WHITE"\n", b+5);
      }
      news = 1;
      free(b);
      continue;
    }
    free(b);
  }
  if (news && fd==1) {
    wp(NULL, "**********************************************************************\n\n");
  }
  fclose(f);

  if (news && !oldnews) {
    return 1;
  }

  if (serial && *serial) {
    free(*serial);
  }

  if (news) {
    return 2;
  }

  return 0;
} 

/* save channels from channel list CL to file FN. Return -1 on i/o
   error with errno set, else 0 */
int savechans(chans_t *cl, char *fn)
{
  chans_t *cur;
  FILE *f;
  
  f = fopen(fn, "w");
  if (!f) {
    return -1;
  }

  for (cur=cl;cur;cur=cur->next)
    if (!cur->q)
      fprintf(f, "%s\n", cur->nm);
  
  fclose(f);
  return 0;
}

/* load channels from file FN and join them. Return -1 on i/o error with
   errno set, else 0. S is server's fd. */
int loadchans(int s, char *fn)
{
  char *line;
  FILE *f;
  
  f = fopen(fn, "r");
  if (!f) {
    return -1;
  }

  while (1)
  {
    line = nap_getline(f);
    if (line == NULL) {
      break;
    }
    if (*line != 0)
      sendpack(s, NAP_JOIN, "%s", line);
    free(line);
  }
  
  fclose(f);
  return 0;
}

chans_t *findchan(chans_t *h, char *chan)
{
  chans_t *cur;

  list_find(cur, h, !strcasecmp(cur->nm, chan));
  if (cur)
    return cur;

  if (chan[0]!='#') {
    list_find(cur, h, cur->nm[0]=='#' && !strcasecmp(cur->nm+1, chan));
  }
  return(cur);
}

chans_t *findquery(chans_t *h, char *chan)
{
  chans_t *cur;

  for (cur=h;;cur=cur->next)
  {
    if (!cur)
      return(NULL);
    if (!strcasecmp(cur->nm, chan) && cur->q == 1)
      return(cur);
  }
}

/* for sending a packet to the napster server, whose file descriptor
   is s. Return -1 on error (with errno set), or else the number of
   bytes written. If s==-1, we print an error message and set errno to
   EINVAL. */
int sendpack(int s, int op, const char *fmt, ...)
{
  char *data;
  char *pack;  /* first four bytes of pack are header, remaining is data */
  int len;
  va_list args;
  int r;
  
  if (s == -1)
  {
    wp(wchan, "%s* Not connected to the server%s\n", RED, WHITE);
    drw(wchan);
    errno = EINVAL;
    return(-1);
  }
  
  if (fmt) {
    data = NULL;
    va_start(args, fmt);
    vasprintf(&data, fmt, args);
    va_end(args);
  } else {
    data = strdup("");
  }
  len = strlen(data);
  pack = (char *)malloc(len+5);
  strcpy(pack+4, data);
  
  /* len and op are sent as little-endian, 16-bit unsigned integers */
  pack[0] = 0xff & len;
  pack[1] = 0xff & (len>>8);
  pack[2] = 0xff & op;
  pack[3] = 0xff & (op>>8);
  
  if (nvar("debug") == 2) {
    wp(wchan, ""DARK GREEN"--> (0x%x=%d) <%s>"WHITE"\n", op, op, quote(pack+4));
    drw(wchan);
  }
  
  r = send(s, pack, len+4, 0);
  free(pack);
  free(data);
    
  return(r);
}

/* write formatted data to file descriptor, and possibly log debugging
   info. Return the number of characters written, or -1 on error with
   errno set. */
int ssock(int s, const char *fmt, ...)
{
  char *data;
  va_list args;
  int r;
  
  data = NULL;

  va_start(args, fmt);
  vasprintf(&data, fmt, args);
  va_end(args);
  
  if (nvar("debug") == 2)
  {
    sock_t *sk = findsockfd(s);
    wp(wchan, ""DARK GREEN"--> [to %d=%s] <%s>"WHITE"\n", s, sk ? sk->socknm : "?", quote(data));
    drw(wchan);
  }

  r = write(s, data, strlen(data));
  
  free(data);

  return(r);
}

/* activate keepalive so we will know when the other side silently
   disappears. (MVB) */
int setkeepalive(int sock)
{
  int on = 1;
  return setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
}

/* receive a packet from the server (at file descriptor s). Return -1
   if there was an error (with errno set), or -3 if there was an
   unexpected end-of-file. Return -2 if only part of the packet could
   be read; in this case, a subsequent call to recvpack will continue
   to read the same packet. Return 1 on success */
int recvpack(int s, char **buf, phead_t **hdr) {
  return recvpack_t(s, buf, hdr, -1);
}

/* like recvpack, except it also has a timeout (in seconds). If t=-1,
   no timeout. If t=0, return immediately if there is no data to read.
   On timeout, return -4. */
int recvpack_t(int s, char **buf, phead_t **hdr, int t)
{
  int r, i;
  fd_set fs;
  struct timeval tv;

  /* the following two static objects, if non-NULL, contain partial
     information that was read at the previous call of recvpack */
  static phead_t *thdr = NULL;
  static unsigned char *tdbuf = NULL;
  static int tpos;  /* how many characters in tdbuf */

  /* if timeout requested, wait at most t seconds until data becomes
     available for reading. */
  if (t>=0) {
    FD_ZERO(&fs);
    FD_SET(s, &fs);
    tv.tv_usec = 0;
    tv.tv_sec = t;
    
    r = select(s+1, &fs, NULL, NULL, &tv);
    if (r==-1) {
      return -1;
    } else if (r==0) { /* timeout */
      return -4;
    }
  }

  if (!thdr)
  {
    unsigned char acc[4];
  
    for (i=0; i<4; i+=r) 
    {
      r = read(s, acc+i, 4-i);  /* note that this might hang */
      if (r <= 0)
      {
        *hdr = NULL;
        *buf = NULL;
        return(r==0 ? -3 : -1);
      }
    }
    thdr = (phead_t *)malloc(sizeof(phead_t));

    /* little-endian, 16 bit unsigned integers */
    thdr->len = acc[0] | (acc[1]<<8);
    thdr->op  = acc[2] | (acc[3]<<8);
  }
  
  if (!thdr->len)
  {
    *hdr = thdr;
    *buf = strdup("");
    thdr = NULL;

    /* print messages with length 0 */
    if (nvar("debug") == 2) {
      wp(wchan, ""DARK GREEN"<-- (0x%x=%d) <>"WHITE"\n", (*hdr)->op, (*hdr)->op);
      drw(wchan);
    }

    return(1);
  }
  
  FD_ZERO(&fs);
  FD_SET(s, &fs);
  tv.tv_usec = 0;
  tv.tv_sec = 0;
  
  if (!select(s+1, &fs, NULL, NULL, &tv))
    return(-2);
  
  if (!tdbuf)
  {
    tdbuf = (char *)malloc(thdr->len+1);
    memset(tdbuf, 0, thdr->len+1);
    tpos = 0;
  }
  
  r = read(s, tdbuf+tpos, thdr->len-tpos);
  if (r <= 0)
  {
    free(tdbuf);
    free(thdr);
    tdbuf = NULL;
    thdr = NULL;
    *buf = NULL;
    *hdr = NULL;
    return(r==0 ? -3 : -1);
  }
  if ((r+tpos) < thdr->len)
  {
    *buf = NULL;
    *hdr = NULL;
    tpos += r;
    return(-2);
  }
  
  *buf = tdbuf;
  *hdr = thdr;
  
  tdbuf = NULL;
  thdr = NULL;
  
  if (nvar("debug") == 2)
  {
    wp(wchan, ""DARK GREEN"<-- (0x%x=%d) <%s>"WHITE"\n", (*hdr)->op, (*hdr)->op, quote(*buf));
    drw(wchan);
  }
  
  return(1);
}

/* read at most 4096 bytes from the given file descriptor. Here, the
   arbitrary limit (4096) is actually desirable, since we want to
   avoid some talkative peer monopolizing our attention. */
int rsock(int s, char **buf)
{
  int r;
  fd_set fs;

  *buf = (char *)malloc(4096+1);
  
  FD_ZERO(&fs);
  FD_SET(s, &fs);
  
  if (select(s+1, &fs, NULL, NULL, NULL) == -1)
  {
    free(*buf);
    *buf = NULL;
    return(-1);
  }
  
  if ((r = read(s, *buf, 4096)) <= 0)
  {
    free(*buf);
    *buf = NULL;
    return(-1);
  }
  (*buf)[r] = 0;  /* make sure it's 0 terminated */
  *buf = (char *)realloc(*buf, r+1);

  if (nvar("debug") == 2)
  {
    sock_t *sk = findsockfd(s); 
    wp(wchan, ""DARK GREEN"<-- [from %d=%s] <%s>"WHITE"\n", s, sk ? sk->socknm : "?", quote(*buf));
    drw(wchan);
  }
  
  return(r);
}

