untermud/DOC/
untermud/DOC/U/
untermud/DOC/U/U-examples/
untermud/DOC/internals/
untermud/DOC/wizard/
untermud/MISC/
untermud/MISC/dbchk/
untermud/RWHO/
untermud/RWHO/rwhod/
/*

    Copyright (C), 1991, Marcus J. Ranum. All rights reserved.

to make:
    cc [-DSETSID -DDAEMON -DSTRCASECMP] -o mwhod mwhod.c

    MUD rwho daemon. This operates with 2 sockets open, one for datagrams
containing update information for the database, the other a stream connection
for users to connect to and download information about what MUDs are up and
who is on.

    mwhods can share their databases between eachother by being defined
as PEERs in the configuration file. The config file specifies a list of MUDs
or servers that we trust and will accept data from, along with passwords to
use when communicating with them. Information is stored about the number of
times a data item has been passed, and the generation count is incremented
each time the datum is re-forwarded. After a certain number of generations,
data will no longer be forwarded.

    The server makes a traverse of its database periodically and expires
all information that it has not received an update for in a given period of
time. This is settable arbitrarily, but should not be too high a value.

    Thanks to John Kennedy for fork() mods to prevent write drops.
*/

#include "os.h"
#ifdef WIN32
#include    "getopt.h"
#endif

extern char *optarg;

/* change this? */
static char connectmsg[] = "Connected to Mud RWHO server...\n";

//FILE    *logfile = stderr;
FILE *logfile = NULL;

/* hear nothing from a MUD for more than this, expire it */
#define MUDTTL          800;
#define PLYTTL          800;

/* name of server, in case we need to propagate */
char *myname = (char *) 0;

/* name of default muds database to load */
#define DEFMUDTAB   "muds.dat"

/* ports to listen at */
#define DGRAMPORT       6888
#define STREAMPORT      6889


/* clean up our tables and propagate them every this many seconds */
#define CLEANUPEVERY        120
int cleantime = CLEANUPEVERY;
int proptime = CLEANUPEVERY;


/* states of user table entries */
#define U_ALIVE     01
#define U_ZOMBIE    02
#define U_KILL      04

#define PNAMSIZ 32
struct uent {
  char uid[PNAMSIZ];            /* user id (must be unique) */
  char uname[PNAMSIZ];          /* user name (arbitrary text) */
  int state;                    /* user state */
  time_t logon;                 /* logon time at remote */
  time_t ttl;                   /* time to live of logon entry */
  time_t upd;                   /* last update on ttl */
  int gen;                      /* number of generations of prop */
  struct uent *next;
};



/* states of mudtable entries */
#define MUD_UP      001
#define MUD_PEER    002
#define MUD_GUEST   004
#define MUD_WIZ     010

struct mudent {
  struct in_addr maddr;         /* remote address */
  short mport;                  /* remote port */
  char *mapw;                   /* remote password */
  char *mnam;                   /* remote MUD name */
  char *txt;                    /* arbitrary text about MUD */
  time_t up;                    /* MUD's uptime */
  int ucnt;                     /* active (?) users */
  time_t ttl;                   /* time to live for MUD entry */
  time_t upd;                   /* last update on ttl */
  int flgs;                     /* flags */
  int gen;                      /* generation to PROPAGATE if peer */
  struct uent *usrs;
  struct mudent *next;
};
struct mudent *mt;
int havepeers = 0;

extern struct mudent *getmudent(char *mud, int cflg);


extern void writepidfile(char *f);
extern void process_stream(SOCKET fd);
extern void process_dgram(struct in_addr *who, char *buf);
extern void mud_free_ulist(struct mudent *m);
extern void mud_add_user(int ac, char *av[]);
extern void mud_zap_user(int ac, char *av[]);
extern void mud_add_entry(int ac, char *av[], int flg);
extern void mud_zap_entry(int ac, char *av[]);
extern void mud_def_newmud(char *buf);
extern void multicast_tables(SOCKET fd);
extern int loadmudtab(char *fil);

static void loaddefmudtab(void);
static void cleandeath(void);
static int stream_mudent(SOCKET fd, struct mudent *m, int *ucnt);
static void lookup_who(SOCKET fd, char *w);
static void lookup_mud(SOCKET fd, char *mud);
static void childclean(void);
static void clean_tables (time_t now);

time_t serverbooted;

int dbgflg = 0;
int logflg = 0;

char *mudtab = DEFMUDTAB;
SOCKET dgramfd;
SOCKET streamfd;

/* load the default MUD table (or reload it) */
static void loaddefmudtab ()
{
  if (dbgflg)
    printf ("reloading mud table\n");
  (void) loadmudtab (mudtab);
}



static void cleandeath ()
{
  (void) shutdown (dgramfd, 2);
  (void) shutdown (streamfd, 2);
  if (dbgflg)
    printf ("shutdown\n");
  WIN32CLEANUP
  exit (0);
}




int main (int ac, char **av)
{
  struct sockaddr_in addr;
  struct timeval timo;
  fd_set redy;
  fd_set xcpt;
  time_t lastclean = (time_t) 0;
  time_t lastprop = (time_t) 0;
  time_t now;
  char rbuf[512];
  int red;
  int alen;
  unsigned mydgport = DGRAMPORT;
  unsigned mystport = STREAMPORT;

  time (&serverbooted);

  while ((alen = getopt (ac, av, "S:P:c:p:f:n:dlL:DF:")) != EOF) {
    switch (alen) {
    case 'S':
      mystport = atoi (optarg);
      break;

    case 'P':
      mydgport = atoi (optarg);
      break;

    case 'F':
      writepidfile (optarg);
      break;

    case 'f':
      mudtab = optarg;
      break;

    case 'n':
      myname = optarg;
      break;

    case 'c':
      cleantime = atoi (optarg);
      break;

    case 'p':
      proptime = atoi (optarg);
      break;

    case 'd':
      dbgflg++;
      break;

    case 'l':
      logflg++;
      break;

    case 'L':
      logfile = fopen (optarg, "wb");
      if (logfile == (FILE *) 0)
        perror (optarg);
      break;

    case 'D':
#ifdef  DAEMON
      (void) signal (SIGTERM, SIG_IGN);
      (void) signal (SIGHUP, SIG_IGN);
      (void) signal (SIGCHLD, SIG_IGN);
      (void) signal (SIGALRM, SIG_IGN);
      if (fork ())
        exit (0);
#ifdef  SETSID
      (void) setsid ();
#endif
#else
      fprintf (stderr, "cannot daemonize\n");
#endif
      break;

    default:
      fprintf (stderr, "usage: %s [-d] -f mudlist -n name\n", av[0]);
      exit (1);
    }
  }


  if (loadmudtab (mudtab))
    exit (1);

  (void) signal (SIGFPE, loaddefmudtab);
  (void) signal (SIGINT, cleandeath);
#ifndef WIN32
  (void) signal (SIGPIPE, SIG_IGN);
  (void) signal (SIGCHLD, childclean);
#endif

  if (havepeers && myname == (char *) 0) {
    fprintf (stderr, "cannot have server name unset and propagate\n");
    exit (1);
  }

  WIN32STARTUP
  /* set up datagram service */
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons (mydgport);
  if ((dgramfd = socket (AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
    perror ("socket");
    WIN32CLEANUP
    exit (1);
  }
  if (bind (dgramfd, (struct sockaddr *) &addr, sizeof (addr))) {
    perror ("bind");
    WIN32CLEANUP
    exit (1);
  }
  alen = 1;
  setsockopt (dgramfd, SOL_SOCKET, SO_REUSEADDR, &alen, sizeof (alen));


  /* set up stream service */
  addr.sin_port = htons (mystport);
  if ((streamfd = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
    perror ("socket");
    WIN32CLEANUP
    exit (1);
  }
  if (bind (streamfd, (struct sockaddr *) &addr, sizeof (addr))) {
    perror ("bind");
    WIN32CLEANUP
    exit (1);
  }
  alen = 1;
  setsockopt (streamfd, SOL_SOCKET, SO_REUSEADDR, &alen, sizeof (alen));
  if (listen (streamfd, 5) == SOCKET_ERROR) {
    perror ("listen");
    WIN32CLEANUP
    exit (1);
  }

  timo.tv_sec = cleantime > proptime ? cleantime : proptime;
  timo.tv_usec = 0;

  if (logflg && logfile != (FILE *) 0)
    fprintf (logfile, "server running: port %d/%d\n", mystport, mydgport);
  if (dbgflg)
    printf ("server running: port %d/%d\n", mystport, mydgport);



/* main thang */
looptop:


  /* how often to purge expired entries */
  time (&now);
  if (now - lastclean > cleantime) {
    clean_tables (now);
    lastclean = now;

    if (logfile != (FILE *) 0)
      fflush (logfile);
  }

  /* how often to propagate */
  if (now - lastprop > proptime) {
    if (havepeers)
      multicast_tables (dgramfd);
    lastprop = now;
  }


  FD_ZERO (&redy);
  FD_ZERO (&xcpt);
  FD_SET (streamfd, &redy);
  FD_SET (streamfd, &xcpt);
  FD_SET (dgramfd, &redy);
  FD_SET (dgramfd, &xcpt);


  if ((red = select (streamfd + 1, &redy, (fd_set *) 0, &xcpt, &timo)) == SOCKET_ERROR)
    if (GETERROR != EINTR) {
      perror ("select");
      WIN32CLEANUP
      exit (1);
    }


  /* yawn */
  if (red <= 0)
    goto looptop;


  if (FD_ISSET (dgramfd, &redy)) {
    alen = sizeof (addr);
    red =
      recvfrom (dgramfd, rbuf, sizeof (rbuf), 0, (struct sockaddr *) &addr,
      &alen);
    if (red > 0 && red < (int) sizeof (rbuf)) {
      rbuf[red] = '\0';
      process_dgram (&addr.sin_addr, rbuf);
    }
  }


  if (FD_ISSET (streamfd, &redy)) {
    red = accept (streamfd, (struct sockaddr *) 0, (int *) 0);
    if (red != INVALID_SOCKET)
      process_stream (red);
  }

  if (FD_ISSET (streamfd, &xcpt) || FD_ISSET (dgramfd, &xcpt)) {
    if (logfile != (FILE *) 0)
      fprintf (logfile, "we are betrayed!!\n");
    WIN32CLEANUP
    exit (1);
  }


  goto looptop;
}





/* scan the MUD table */
struct mudent *getmudent (char *mud, int cflg)
{
  struct mudent *ret;

  for (ret = mt; ret != (struct mudent *) 0; ret = ret->next) {
#ifdef  STRCASECMP
    if (ret->mnam != (char *) 0 &&
      ((!cflg && !strcasecmp (ret->mnam, mud)) ||
        (cflg && !strcmp (ret->mnam, mud))))
#else
    if (ret->mnam != (char *) 0 && !strcmp (ret->mnam, mud))
#endif
      return (ret);
  }
  return ((struct mudent *) 0);
}






/* zip through our tables and kill anything with an expired time-to-live */
static void clean_tables (time_t now)
{
  struct mudent *m;
  struct uent *u;
  struct uent *p;
  struct uent *n;

  if (dbgflg)
    printf ("cleaning deadwood out of tables\n");

  for (m = mt; m != (struct mudent *) 0; m = m->next) {
    if (!(m->flgs & MUD_UP))
      continue;

    /* has the whole MUD entry table expired? */
    if (now - m->upd > m->ttl) {
      if (logflg && logfile != (FILE *) 0)
        fprintf (logfile, "dead mud %s\n", m->mnam);
      if (dbgflg)
        printf ("dead %s: now=%d, lastup=%d, ttl=%d\n",
          m->mnam, (int) now, (int) m->upd, (int) m->ttl);
      m->flgs &= ~MUD_UP;
      mud_free_ulist (m);
      continue;
    }

    /* check for expired logins */
    for (p = u = m->usrs; u != (struct uent *) 0; u = n) {
      n = u->next;
      if (now - u->upd > u->ttl || (u->state == U_KILL)) {
        if (logflg && logfile != (FILE *) 0)
          fprintf (logfile, "dead user %s@%s\n", u->uid, m->mnam);
        if (dbgflg)
          printf ("dead user %s@%s\n", u->uid, m->mnam);
        if (u->state == U_ALIVE)
          m->ucnt--;

        if (u == m->usrs)
          p = m->usrs = n;
        else
          p->next = n;
        free (u);
      } else {
        /* Only advance prev if we did NOT nuke */
        p = u;
      }
    }
  }
}





/* propagate out our tables to peer servers, if any */
void multicast_tables (SOCKET fd)
{
  struct sockaddr_in ad;
  struct uent *u;
  struct mudent *m;
  struct mudent *m2;
  char v[512];

  if (dbgflg)
    printf ("propagating tables\n");

  for (m = mt; m != (struct mudent *) 0; m = m->next) {
    if (!(m->flgs & MUD_PEER))
      continue;

    /* setup address */
    ad.sin_port = htons (m->mport);
    ad.sin_family = AF_INET;
    bcopy (&m->maddr, &ad.sin_addr, sizeof (ad.sin_addr));
    if (dbgflg)
      printf ("updating peer %s@%s, port %d\n", m->mnam,
        inet_ntoa (m->maddr), m->mport);


    for (m2 = mt; m2 != (struct mudent *) 0; m2 = m2->next) {

      /* EXCLUDE ! */
      if (m2 == m || m2->gen >= m->gen ||
        m2->flgs & MUD_PEER || !(m2->flgs & MUD_UP))
        continue;

      if (dbgflg)
        printf ("update mud %s->%s\n", m2->mnam, m->mnam);


      /* inform remote of the MUD */
      sprintf (v, "M\t%s\t%s\t%s\t%d\t%d\t%s",
        myname, m->mapw, m2->mnam, (int) m2->up,
        m2->gen + 1, m2->txt != (char *) 0 ? m2->txt : "");

      sendto (fd, v, strlen (v), 0, (struct sockaddr *) &ad, sizeof (ad));


      for (u = m2->usrs; u != (struct uent *) 0; u = u->next) {
        if (u->gen + 1 > m->gen || (u->state == U_KILL))
          continue;

        /*
           if the user is logged out send a byebye
           by propagating the zombie record.
           otherwise send a logged in record.
         */
        if (u->state == U_ZOMBIE) {
          if (dbgflg)
            printf ("deluser %s->%s\n", u->uid, m->mnam);
          sprintf (v, "Z\t%s\t%s\t%s\t%s", myname, m->mapw, m2->mnam, u->uid);
        } else {
          if (dbgflg)
            printf ("send user %s->%s\n", u->uid, m->mnam);
          sprintf (v,
            "A\t%s\t%s\t%s\t%s\t%d\t%d\t%s",
            myname, m->mapw, m2->mnam, u->uid,
            (int) u->logon, u->gen + 1, u->uname);
        }
        sendto (fd, v, strlen (v), 0, (struct sockaddr *) &ad, sizeof (ad));
      }
    }
  }


  /* reap zombies - by marking them all as killable */
  for (m2 = mt; m2 != (struct mudent *) 0; m2 = m2->next)
    for (u = m2->usrs; u != (struct uent *) 0; u = u->next)
      if (u->state == U_ZOMBIE)
        u->state = U_KILL;
}





static int stream_mudent (SOCKET fd, struct mudent *m, int *ucnt)
{
  struct tm *tim;
  struct uent *u;
  char buf[512];
  char ibuf[64];
  int bl;

  if (m->txt == (char *) 0)
    strcpy (ibuf, m->mnam);
  else
    sprintf (ibuf, "%.13s/%.22s", m->mnam, m->txt);

  /* is the MUD running or what? */
  if (!(m->flgs & MUD_UP) && m->up == (time_t) 0) {
    tim = localtime (&serverbooted);
    sprintf (buf,
      "\n%-35.35s     not heard from since %d/%d/%d %2.2d:%2.2d\n",
      ibuf,
      tim->tm_mon, tim->tm_mday, tim->tm_year, tim->tm_hour, tim->tm_min);
  } else {
    tim = localtime (&m->up);
    sprintf (buf,
      "\n%-35.35s %-3d user%c           %s %d/%d/%d %2.2d:%2.2d\n",
      ibuf, m->ucnt, m->ucnt == 1 ? ' ' : 's',
      (m->flgs & MUD_UP) ? "up" : "last up",
      tim->tm_mon + 1, tim->tm_mday, tim->tm_year, tim->tm_hour, tim->tm_min);
  }

  bl = strlen (buf);
  if (send (fd, buf, bl, 0) != bl)
    return (1);

  if (!(m->flgs & MUD_UP))
    return (0);

  if ((u = m->usrs) == (struct uent *) 0 || m->ucnt <= 0)
    return (0);

  sprintf (buf, "%-35.35s %-17.17s      %s\n",
    "--name--", "--id--", "--login--");

  bl = strlen (buf);
  if (send (fd, buf, bl, 0) != bl)
    return (1);

  while (u != (struct uent *) 0) {
    if (u->state != U_ALIVE) {
      u = u->next;
      continue;
    }

    tim = localtime (&u->logon);
    sprintf (buf, "%-35.35s %-17.17s      %2.2d:%2.2d.%2.2d\n",
      u->uname[0] == '\0' ? u->uid : u->uname,
      u->uname[0] == '\0' ? "" : u->uid,
      tim->tm_hour, tim->tm_min, tim->tm_sec);

    bl = strlen (buf);
    if (send (fd, buf, bl, 0) != bl)
      return (1);
    u = u->next;
    (*ucnt)++;
  }
  return (0);
}




static void lookup_who (SOCKET fd, char *w)
{
  struct mudent *m;
  struct uent *u;
  struct tm *tim;
  char buf[512];
  char ibuf[64];
  int bl;
  int got = 0;
  static char nomsg[] = "nobody by that name is logged in\n";

  for (m = mt; m != (struct mudent *) 0; m = m->next)
    for (u = m->usrs; u != (struct uent *) 0; u = u->next) {
#ifdef  STRCASECMP
      if (u->state == U_ALIVE &&
        (!strcasecmp (u->uname, w) || !strcasecmp (u->uid, w))) {
#else
      if (u->state == U_ALIVE &&
        (!strcmp (u->uname, w) || !strcmp (u->uid, w))) {
#endif

        tim = localtime (&u->logon);
        sprintf (ibuf, "%.10s/%.35s", m->mnam,
          u->uname[0] == '\0' ? u->uid : u->uname);

        sprintf (buf,
          "%-35.35s %-17.17s %2.2d:%2.2d.%2.2d\n",
          ibuf, u->uname[0] == '\0' ? "" : u->uid,
          tim->tm_hour, tim->tm_min, tim->tm_sec);

        bl = strlen (buf);
        got++;
        if (send (fd, buf, bl, 0) != bl)
          return;
      }
    }

  if (!got)
    (void) send (fd, nomsg, sizeof (nomsg) - 1, 0);
}




static void lookup_mud (SOCKET fd, char *mud)
{
  struct mudent *m;
  static char nomud[] = "no MUD by that name is running\n";
  int dummy;

  if ((m = getmudent (mud, 0)) == (struct mudent *) 0) {
    (void) send (fd, nomud, sizeof (nomud) - 1, 0);
    return;
  }
  (void) stream_mudent (fd, m, &dummy);
}





/* this is what most users will see, when they connect and ask who is on */
void process_stream (SOCKET fd)
{
  struct mudent *m;
  fd_set redy;
  fd_set xcpt;
  struct timeval timo;
  int reqst = 0;
  char rbuf[512];
  int red;
  int mcnt = 0;
  int ucnt = 0;


#ifdef  DAEMON
  if (fork () == 0) {
#endif
    timo.tv_sec = 0;
    timo.tv_usec = 300;

    FD_ZERO (&redy);
    FD_ZERO (&xcpt);
    FD_SET (fd, &redy);
    FD_SET (fd, &xcpt);

    /* see if there's a request packet */
    if ((red = select (fd + 1, &redy, (fd_set *) 0, &xcpt, &timo)) > 0) {
      if ((red = recv (fd, rbuf, sizeof (rbuf),0)) > 0) {
        char *p;
        if (!strncmp (rbuf, "mud=", 4) || !strncmp (rbuf, "who=", 4))
          reqst = 1;
        p = rbuf;
        while (*p != '\0') {
          if (*p == '\n') {
            *p = '\0';
            break;
          }
          p++;
        }
      }
    }


    if (dbgflg)
      printf ("start user query\n");

    if (send (fd, connectmsg,
        sizeof (connectmsg) - 1, 0) != (sizeof (connectmsg) - 1))
      goto done;

    if (reqst && !strncmp (rbuf, "mud=", 4)) {
      if (dbgflg)
        printf ("lookup_mud %s\n", rbuf);
      lookup_mud (fd, &rbuf[4]);
      goto done;
    }

    if (reqst && !strncmp (rbuf, "who=", 4)) {
      if (dbgflg)
        printf ("lookup_who %s\n", rbuf);
      lookup_who (fd, &rbuf[4]);
      goto done;
    }

    for (m = mt; m != (struct mudent *) 0; m = m->next) {
      if (m->flgs & MUD_PEER)
        continue;
      if (!(m->flgs & MUD_UP)) {
        if (dbgflg)
          printf ("skip down MUD %s\n", m->mnam);
        continue;
      }
      if (stream_mudent (fd, m, &ucnt))
        break;
      if (dbgflg)
        printf ("sent mudent for MUD %s\n", m->mnam);
      mcnt++;
    }

    sprintf (rbuf,
      "\n\n       --- %d muds, %d players known to this server ---\n",
      mcnt, ucnt);
    (void) send (fd, rbuf, strlen (rbuf), 0);

  done:
    if (dbgflg)
      printf ("end user query\n");

#ifdef  DAEMON
    (void) exit (0);
  }
#endif
  closesocket (fd);
}





/* process and deal with a datagram from a remote MUD or server */
void process_dgram (struct in_addr *who, char *buf)
{
  struct mudent *m;
  char *cp;
  char *av[12];
  int ac;


  /* tokenize at tabs */
  av[ac = 0] = cp = buf;
  while (*cp != '\0') {
    if (*cp == '\t') {
      if (ac > 10)
        return;
      *cp++ = '\0';
      av[++ac] = cp;
      av[ac + 1] = (char *) 0;
    } else
      cp++;
  }


  /* minimum req: OP, mudname, password */
  if (ac < 3) {
    if (logflg && logfile != (FILE *) 0)
      fprintf (logfile, "malformed input: %s\n", inet_ntoa (*who));
    if (dbgflg)
      printf ("malformed input from : %s\n", inet_ntoa (*who));
    return;
  }


  /* authenticate */
  for (m = mt; m != (struct mudent *) 0; m = m->next) {
    if (m->flgs & MUD_GUEST)
      continue;
    if (m->mnam != (char *) 0 && !strcmp (m->mnam, av[1])) {
      if (bcmp (who, &m->maddr, sizeof (struct in_addr))) {
        if (logfile != (FILE *) 0)
          fprintf (logfile, "spoof %s from %s\n", av[1], inet_ntoa (*who));
        return;
      }
      break;
    }
  }


  /* unknown MUD */
  if (m == (struct mudent *) 0) {
    if (logfile != (FILE *) 0)
      fprintf (logfile, "probe from %s@%s", av[1], inet_ntoa (*who));
    if (logfile != (FILE *) 0 && ac > 2)
      fprintf (logfile, " (pass=%s)", av[2]);
    if (logfile != (FILE *) 0)
      fprintf (logfile, "\n");
    return;
  }


  /* bogey */
  if (strcmp (m->mapw, av[2])) {
    if (logfile != (FILE *) 0)
      fprintf (logfile, "bad passwd %s from %s@%s\n",
        av[2], av[1], inet_ntoa (*who));
    return;
  }


  /* nice to hear from you again - update remote's ttl */
  time (&m->upd);



  /*
     parameters passed are: operation, mudname, passwd
     since we have already used them by this point, we
     can now shift stuff around when we pass it to the
     actual operands themselves.
   */
  switch (*av[0]) {


    /* remote MUD declares it is alive and booted */
  case 'U':
    mud_add_entry (ac - 2, &av[3], 1);
    break;


    /* remote MUD existence update only (nondestructive) */
  case 'M':
    mud_add_entry (ac - 2, &av[3], 0);
    break;


    /* remote MUD declares it is down and out */
  case 'D':
    mud_zap_entry (ac - 2, &av[3]);
    break;


    /* add a new logged in user */
  case 'A':
    mud_add_user (ac - 2, &av[3]);
    break;


    /* zap an old logged in user */
  case 'Z':
    mud_zap_user (ac - 2, &av[3]);
    break;


  default:
    return;
  }
}




/* read the MUD table of known servers and trusted MUDs */
int loadmudtab (char *fil)
{
  FILE *inf;
  char buf[BUFSIZ];

  if ((inf = fopen (fil, "rb")) == (FILE *) 0) {
    perror (fil);
    return (1);
  }

  while (fgets (buf, sizeof (buf), inf) != (char *) 0) {
    if (*buf == '#' || *buf == '\n')
      continue;
    mud_def_newmud (buf);
  }
  fclose (inf);
  return (0);
}




/* wipe the user list for a MUD */
void mud_free_ulist (struct mudent *m)
{
  struct uent *u;
  struct uent *n;

  if (dbgflg)
    printf ("clearing user list for %s\n", m->mnam);
  u = m->usrs;
  while (u != (struct uent *) 0) {
    n = u->next;
    if (dbgflg)
      printf ("clearing user %s@%s\n", u->uid, m->mnam);
    free (u);
    u = n;
  }
  m->usrs = (struct uent *) 0;
  m->ucnt = 0;
}




/*
add a user entry to a defined MUD if we can
parameters are: mudname username login-time propagation-generation
*/
void mud_add_user (int ac, char *av[])
{
  struct uent *u;
  struct uent *p1;
  struct uent *p2;
  struct mudent *m;
  int tgen;

  m = getmudent (av[0], 1);
  if (ac < 4 || m == (struct mudent *) 0) {
    if (dbgflg && ac > 3 && m == (struct mudent *) 0)
      printf ("unknown mud %s\n", av[0]);
    if (dbgflg && ac < 4)
      printf ("add user: bad arg count %d\n", ac);
    return;
  }

  if ((tgen = atoi (av[3])) > m->gen) {
    if (dbgflg)
      printf ("ignore gen %d %s@%s\n", tgen, av[1], m->mnam);
    return;
  }

  u = m->usrs;
  while (u != (struct uent *) 0) {
    if (!strcmp (u->uid, av[1])) {
      time (&u->upd);
      u->gen = tgen;
      if (u->state != U_ALIVE) {
        u->state = U_ALIVE;
        if (dbgflg)
          printf ("resurrect %s@%s\n", av[1], m->mnam);
        m->ucnt++;
      } else {
        if (dbgflg)
          printf ("refresh %s@%s\n", av[1], m->mnam);
      }

      /* name change? */
      if (ac == 5 && strcmp (u->uname, av[4]))
        strncpy (u->uname, av[4], PNAMSIZ - 2);
      u->uname[PNAMSIZ - 1] = '\0';
      return;
    }
    u = u->next;
  }

  u = (struct uent *) malloc (sizeof (struct uent));
  if (u == (struct uent *) 0)
    return;
  u->ttl = PLYTTL;
  u->upd = m->upd;
  u->state = U_ALIVE;
  u->logon = (time_t) atoi (av[2]);
  u->gen = tgen;
  strncpy (u->uid, av[1], PNAMSIZ - 2);
  u->uid[PNAMSIZ - 1] = '\0';

  u->uname[0] = '\0';
  if (ac == 5)
    strncpy (u->uname, av[4], PNAMSIZ - 2);
  else
    strncpy (u->uname, av[1], PNAMSIZ - 2);
  u->uname[PNAMSIZ - 1] = '\0';

  m->ucnt++;

  if (logflg && logfile != (FILE *) 0)
    fprintf (logfile, "new entry %s@%s\n", av[1], m->mnam);
  if (dbgflg)
    printf ("new entry %s@%s\n", av[1], m->mnam);

  p2 = (struct uent *) 0;
  for (p1 = m->usrs; p1 != (struct uent *) 0; p1 = p1->next) {
#ifdef  STRCASECMP
    if (strcasecmp (p1->uname, u->uname) >= 0)
#else
    if (strcmp (p1->uname, u->uname) >= 0)
#endif
      break;
    p2 = p1;
  }

  if (p2 == (struct uent *) 0) {
    u->next = m->usrs;
    m->usrs = u;
  } else {
    u->next = p2->next;
    p2->next = u;
  }
}





/*
zap a user
parameters are: mudname username
*/
void mud_zap_user (int ac, char *av[])
{
  struct mudent *m;
  struct uent *u;

  m = getmudent (av[0], 1);
  if (ac != 2 || m == (struct mudent *) 0) {
    if (dbgflg && ac == 2 && m == (struct mudent *) 0)
      printf ("unknown mud %s\n", av[0]);
    if (dbgflg && ac != 2)
      printf ("zap user: bad arg count %d\n", ac);
    return;
  }

  u = m->usrs;
  while (u != (struct uent *) 0) {
    if (u->state == U_ALIVE && !strcmp (u->uid, av[1])) {
      m->ucnt--;
      u->state = U_ZOMBIE;
      if (logflg && logfile != (FILE *) 0)
        fprintf (logfile, "logout %s@%s\n", av[1], m->mnam);
      if (dbgflg)
        printf ("logout %s@%s\n", av[1], m->mnam);
    }
    u = u->next;
  }
}





/*
define a new entry for a MUD, or mark one as up and well
parameters are: mudname uptime propagation-generation moretext
*/
void mud_add_entry (int ac, char *av[], int flg)
{
  struct mudent *m;
  int tgen;

  m = getmudent (av[0], 1);
  if (ac < 3 || m == (struct mudent *) 0) {
    struct mudent *m1;
    struct mudent *m2;

    if (ac < 3) {
      if (dbgflg)
        printf ("add entry: bad arg count %d\n", ac);
      return;
    }

    m = (struct mudent *) malloc (sizeof (struct mudent));
    if (m == (struct mudent *) 0) {
      perror ("malloc");
      return;
    }

    m->mapw = (char *) 0;
    m->mnam = malloc ((unsigned) (strlen (av[0]) + 1));
    if (m->mnam == (char *) 0) {
      perror ("malloc");
      return;
    }
    strcpy (m->mnam, av[0]);

    m->txt = (char *) 0;
    m->ucnt = 0;
    m->flgs = MUD_GUEST;
    m->usrs = (struct uent *) 0;
    m->gen = atoi (av[2]);

    m2 = (struct mudent *) 0;
    for (m1 = mt; m1 != (struct mudent *) 0; m1 = m1->next) {
#ifdef  STRCASECMP
      if (strcasecmp (m1->mnam, m->mnam) >= 0)
#else
      if (strcmp (m1->mnam, m->mnam) >= 0)
#endif
        break;
      m2 = m1;
    }

    if (m2 == (struct mudent *) 0) {
      m->next = mt;
      mt = m;
    } else {
      m->next = m2->next;
      m2->next = m;
    }

    if (logflg && logfile != (FILE *) 0)
      fprintf (logfile, "added mud table entry %s\n", m->mnam);
    if (dbgflg)
      printf ("added mud table entry %s\n", m->mnam);
  } else {
    /* do not accept info that is too old */
    if ((tgen = atoi (av[2])) > m->gen) {
      if (dbgflg)
        printf ("ignored gen %d uptime %s\n", tgen, m->mnam);
      return;
    }
  }


  if (ac > 3) {
    /* replace name ? */
    if (m->txt != (char *) 0 && strcmp (m->txt, av[3])) {
      free (m->txt);
      m->txt = (char *) 0;
    }
    if (m->txt == (char *) 0) {
      m->txt = malloc ((unsigned) (strlen (av[3]) + 1));
      if (m->txt == (char *) 0) {
        perror ("malloc");
        return;
      }
      strcpy (m->txt, av[3]);
    }
  }

  m->flgs |= MUD_UP;
  m->ttl = MUDTTL;

  time (&m->upd);
  m->up = (time_t) atoi (av[1]);

  /* reset */
  if (flg)
    mud_free_ulist (m);

  if (dbgflg)
    printf ("mark mud %s up\n", m->mnam);
}





/*
mark an old entry for a MUD as deleted/down
parameters are: mudname
*/
void mud_zap_entry (int ac, char *av[])
{
  struct mudent *m;

  m = getmudent (av[0], 1);
  if (ac != 1 || m == (struct mudent *) 0) {
    if (dbgflg && ac == 1 && m == (struct mudent *) 0)
      printf ("unknown mud %s\n", av[0]);
    if (dbgflg && ac != 1)
      printf ("zap entry: bad arg count %d\n", ac);
    return;
  }
  m->flgs &= ~MUD_UP;
  if (logflg && logfile != (FILE *) 0)
    fprintf (logfile, "mark mud %s down\n", m->mnam);
  if (dbgflg)
    fprintf (logfile, "zapentry mark mud %s down\n", m->mnam);
  mud_free_ulist (m);
}



void mud_def_newmud (char *buf)
{
  struct mudent *mudp;
  struct hostent *hp;
  struct in_addr ad;
  int found = 0;
  short tmport;
  int tmpflg = 0;
  int tmpgen = 0;
  char *cp;
  char *xp;
  char *ho;
  char *nm;
  char *pw;

  cp = buf;

  /* scan out flags */
  while (*cp != '\0' && !isspace (*cp)) {
    switch (*cp) {

    case 'C':                  /* no-op */
      break;

    case 'W':                  /* disused */
      break;

    case 'P':
      tmpflg |= MUD_PEER;
      havepeers++;
      break;

    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      tmpgen = *cp - '0';
      break;

    default:
      fprintf (stderr, "unknown flag %c\n", *cp);
      return;
    }
    cp++;
  }


  /* skip space */
  while (*cp != '\0' && isspace (*cp))
    cp++;


  /* scan out hostname */
  ho = cp;
  while (*cp != '\0' && !isspace (*cp))
    cp++;

  if (*cp == '\0') {
    fprintf (stderr, "malformed mud entry: %s\n", buf);
    return;
  }
  *cp++ = '\0';

  xp = ho;
  while (*xp != '\0' && (*xp == '.' || isdigit (*xp)))
    xp++;


  /* not all digits or dots */
  if (*xp != '\0') {
#ifndef NO_HUGE_RESOLVER_CODE
    if ((hp = gethostbyname (ho)) == (struct hostent *) 0) {
      fprintf (stderr, "unknown host %s\n", ho);
      return;
    }
    bcopy (hp->h_addr, &ad, hp->h_length);
#else
    fprintf (stderr, "must use hardcoded IP octets\n");
#endif
  } else {
#ifdef WIN32
    unsigned long f;
#else
    struct in_addr f;
#endif

#ifdef WIN32
    if ((f = inet_addr (ho)) == INADDR_NONE) {
#else
    if (inet_aton (ho, &f) == 0) {
#endif
      fprintf (stderr, "cannot interpret %s\n", ho);
      return;
    }
    bcopy (&f, &ad, sizeof (f));
  }




  /* skip space */
  while (*cp != '\0' && isspace (*cp))
    cp++;

  /* scan out port # */
  xp = cp;
  while (*cp != '\0' && !isspace (*cp))
    cp++;

  if (*cp == '\0') {
    fprintf (stderr, "missing mud port: %s\n", ho);
    return;
  }
  *cp++ = '\0';
  tmport = atoi (xp);




  /* skip out spaces */
  while (isspace (*cp))
    cp++;

  /* get name */
  nm = cp;
  while (*cp != '\0' && !isspace (*cp))
    cp++;

  if (*cp == '\0' || *nm == '\0') {
    fprintf (stderr, "missing mud name: %s\n", ho);
    return;
  }
  *cp++ = '\0';





  /* skip out spaces */
  while (isspace (*cp))
    cp++;

  /* get password */
  pw = cp;
  while (*cp != '\0' && !isspace (*cp))
    cp++;

  if (*cp == '\0' || *pw == '\0') {
    fprintf (stderr, "missing mud password: %s\n", nm);
    return;
  }
  *cp++ = '\0';

  /* if new entry */
  if ((mudp = getmudent (nm, 0)) == (struct mudent *) 0) {
    mudp = (struct mudent *) malloc (sizeof (struct mudent));
    if (mudp == (struct mudent *) 0) {
      perror ("malloc");
      return;
    }
    if (dbgflg)
      printf ("alloc new table entry for %s\n", nm);
    mudp->usrs = (struct uent *) 0;
    mudp->ucnt = 0;
  } else {
    if (dbgflg)
      printf ("clearing mud table entry %s\n", mudp->mnam);
    found++;
    free (mudp->mapw);
    free (mudp->mnam);
    if (mudp->txt != (char *) 0)
      free (mudp->txt);
  }


  /* misc text */
  while (isspace (*cp))
    cp++;

  mudp->mapw = malloc ((unsigned) (strlen (pw) + 1));
  mudp->mnam = malloc ((unsigned) (strlen (nm) + 1));
  if (mudp->mapw == (char *) 0 || mudp->mnam == (char *) 0) {
    perror ("malloc");
    return;
  }
  strcpy (mudp->mapw, pw);
  strcpy (mudp->mnam, nm);

  mudp->txt = (char *) 0;
  if (*cp != '\0') {
    char *jnkp = cp;

    /* lose the newline */
    while (*jnkp != '\0' && *jnkp != '\n')
      jnkp++;
    if (*jnkp == '\n')
      *jnkp = '\0';

    /* save the text */
    mudp->txt = malloc ((unsigned) (strlen (cp) + 1));
    if (mudp->txt == (char *) 0) {
      perror ("malloc");
      return;
    }
    strcpy (mudp->txt, cp);
  }

  mudp->up = (time_t) 0;
  mudp->flgs = tmpflg;
  mudp->gen = tmpgen;
  mudp->mport = tmport;
  bcopy (&ad, &mudp->maddr, sizeof (ad));

  if (!found) {
    struct mudent *m2;
    struct mudent *m1;

    m2 = (struct mudent *) 0;
    for (m1 = mt; m1 != (struct mudent *) 0; m1 = m1->next) {
#ifdef  STRCASECMP
      if (strcasecmp (m1->mnam, mudp->mnam) >= 0)
#else
      if (strcmp (m1->mnam, mudp->mnam) >= 0)
#endif
        break;
      m2 = m1;
    }

    if (m2 == (struct mudent *) 0) {
      mudp->next = mt;
      mt = mudp;
    } else {
      mudp->next = m2->next;
      m2->next = mudp;
    }
    if (dbgflg)
      printf ("added mud table entry %s\n", mudp->mnam);
  }
}



void writepidfile (char *f)
{
  FILE *pf;
  if ((pf = fopen (f, "wb")) != (FILE *) 0) {
    fprintf (pf, "%d\n", getpid ());
    fclose (pf);
  }
}



static void childclean (void)
{
  (void) wait ((int *) NULL);
}