pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/*
   * The file access.cnf in the game directory will control all 
   access-related directives, replacing lockout.cnf and sites.cnf

   * The format of entries in the file will be:

   wild-host-name    [!]option [!]option [!]option ... # comment

   A wild-host-name is a wildcard pattern to match hostnames with. 
   The wildcard "*" will work like UNIX filename globbing, so
   *.edu will match all sites with names ending in .edu, and
   *.*.*.*.* will match all sites with 4 periods in their name.
   128.32.*.* will match all sites starting with 128.32 (UC Berkeley).
   You can also use user@host to match specific users if you know that
   the host is running ident and you trust its responses (nontrivial).

   The options that can be specified are:
   *CONNECT              Allow connections to non-guest players
   *GUEST                Allow connection to guests
   *CREATE               Allow player creation at login screen
   DEFAULT               All of the above
   NONE                 None of the above
   SUSPECT              Set all players connecting from the site suspect
   REGISTER             Allow players to use the "register" connect command

   Options that are *'d can be prefaced by a !, meaning "Don't allow".

   * The file is parsed line-by-line in order. This makes it possible
   to explicitly allow only certain sites to connect and deny all others,
   or vice versa. Sites can only do the options that are specified
   in the first line they match.

   * If a site is listed in the file with no options at all, it is
   disallowed from any access (treated as !CONNECT, basically)

   * If a site doesn't match any line in the file, it is allowed any
   toggleable access (treated as DEFAULT) but isn't SUSPECT or REGISTER.

   * "make access" produces access.cnf from lockout.cnf/sites.cnf

   * @sitelock'd sites appear after the line "@sitelock" in the file
   Using @sitelock writes out the file.

 */

#include "config.h"
#include "copyrite.h"
#include <stdio.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#include <fcntl.h>
#ifdef I_SYS_TIME
#include <sys/time.h>
#else
#include <time.h>
#endif
#ifdef I_UNISTD
#include <unistd.h>
#endif
#include "externs.h"
#ifdef MEM_CHECK
#include "memcheck.h"
#endif
#include "access.h"
#include "mymalloc.h"
#include "confmagic.h"

/* A linked list data structure to hold the access info */
struct access {
  char host[BUFFER_LEN];
  char comment[BUFFER_LEN];
  int can;
  int cant;
  struct access *next;
};


typedef struct a_acsflag acsflag;
struct a_acsflag {
  const char *name;
  int toggle;			/* Is this a negatable flag? */
  int flag;
};
static acsflag acslist[] =
{
  {"connect", 1, ACS_CONNECT},
  {"create", 1, ACS_CREATE},
  {"guest", 1, ACS_GUEST},
  {"default", 0, ACS_DEFAULT},
  {"register", 0, ACS_REGISTER},
  {"suspect", 0, ACS_SUSPECT},
  {NULL, 0}
};

static struct access *access_top;
extern int reserved;		/* reserved file descriptor */
extern time_t mudtime;
static int add_access_node _((const char *host, const int can, const int cant, const char *comment));
static void free_access_list _((void));

/* Allocate a new node and add to the end of the list. */
static int
add_access_node(host, can, cant, comment)
    const char *host;
    const int can;
    const int cant;
    const char *comment;
{
  struct access *end;
  struct access *tmp;

  tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access");
  if (!tmp)
    return 0;
  tmp->can = can;
  tmp->cant = cant;
  strcpy(tmp->host, host);
  if (comment)
    strcpy(tmp->comment, comment);
  else
    strcpy(tmp->comment, "");
  tmp->next = NULL;

  if (!access_top) {
    /* Add to the beginning */
    access_top = tmp;
  } else {
    end = access_top;
    while (end->next)
      end = end->next;
    end->next = tmp;
  }

  return 1;
}


/* Initialize the linked list and read in the access file.
 * Return 1 if successful, 0 if not
 */
int
read_access_file()
{
  FILE *fp;
  char buf[BUFFER_LEN];
  char *p;
  int can, cant;
  int retval;
  char *comment;

  if (access_top) {
    /* We're reloading the file, so we've got to delete any current 
     * entries
     */
    free_access_list();
  }
  access_top = NULL;
  /* Be sure we have a file descriptor */
#ifndef WIN32
  close(reserved);
#endif
  fp = fopen(ACCESS_FILE, "r");
  if (!fp) {
    do_log(LT_ERR, GOD, GOD, "No %s file found.", ACCESS_FILE);
    retval = 0;
  } else {
    fgets(buf, BUFFER_LEN, fp);
    while (!feof(fp)) {
      /* Strip newline */
      if ((p = strchr(buf, '\n')))
	*p = '\0';
      /* Find beginning of line; ignore blank lines */
      p = buf;
      if (*p && isspace(*p))
	p++;
      if (*p && *p != '#') {
	can = cant = 0;
	comment = NULL;
	/* Is this the @sitelock entry? */
	if (!strncasecmp(p, "@sitelock", 9)) {
	  can = ACS_SITELOCK;
	  buf[9] = '\0';
	} else {
	  if ((comment = strchr(p, '#'))) {
	    *comment++ = '\0';
	    while (*comment && isspace(*comment))
	      comment++;
	  }
	  /* Move past the host name */
	  while (*p && !isspace(*p))
	    p++;
	  if (*p)
	    *p++ = '\0';
	  if (!parse_access_options(p, &can, &cant, NOTHING))
	    /* Nothing listed, so assume we can't do anything! */
	    cant = ACS_DEFAULT;

	}
	if (!add_access_node(buf, can, cant, comment)) {
	  /* Something very bad happened */
	  do_log(LT_ERR, GOD, GOD, "Failed to add access node!");
	  fclose(fp);
	  retval = 0;
	}
      }
      fgets(buf, BUFFER_LEN, fp);
    }
    retval = 1;
    fclose(fp);
  }
#ifndef WIN32
  reserved = open("/dev/null", O_RDWR);
#endif
  return retval;
}

/* Write out the access file from the linked list */
void
write_access_file()
{
  FILE *fp;
  char tmpf[BUFFER_LEN];
  struct access *ap;
  acsflag *c;

  sprintf(tmpf, "%s.tmp", ACCESS_FILE);
  /* Be sure we have a file descriptor */
#ifndef WIN32
  close(reserved);
#endif
  fp = fopen(tmpf, "w");
  if (!fp) {
    do_log(LT_ERR, GOD, GOD, "Unable to open %s.", tmpf);
  } else {
    for (ap = access_top; ap; ap = ap->next) {
      fprintf(fp, "%s ", ap->host);
      switch (ap->can) {
      case ACS_SITELOCK:
	break;
      case ACS_DEFAULT:
	fprintf(fp, "DEFAULT ");
	break;
      default:
	for (c = acslist; c->name; c++)
	  if (ap->can & c->flag)
	    fprintf(fp, "%s ", c->name);
	break;
      }
      switch (ap->cant) {
      case ACS_DEFAULT:
	fprintf(fp, "NONE ");
	break;
      default:
	for (c = acslist; c->name; c++)
	  if (c->toggle && (ap->cant & c->flag))
	    fprintf(fp, "!%s ", c->name);
	break;
      }
      if (ap->comment && *ap->comment)
	fprintf(fp, "# %s\n", ap->comment);
      else
	fprintf(fp, "\n");
    }
    fclose(fp);
#ifdef WIN32
    unlink(ACCESS_FILE);
#endif
    rename(tmpf, ACCESS_FILE);
  }
#ifndef WIN32
  reserved = open("/dev/null", O_RDWR);
#endif
  return;
}

/* Given a hostname and a flag decide if the host can do it.
 * Here's how it works:
 * We run the linked list and take the first match.
 *  (If the hostname is user@host, we try to match both user@host
 *   and just host to each line in the file.)
 * If we make a match, and the line tells us whether the site can/can't
 *   do the action, we're done.
 * Otherwise, we assume that the host can do any toggleable option
 *   (can create, connect, guest), and don't have any special
 *   flags (can't register, isn't suspect)
 */
int
site_can_access(hname, flag)
    const char *hname;
    const int flag;
{
  struct access *ap;
  acsflag *c;
  char *p;

  if (!hname || !*hname)
    return 0;

  if ((p = strchr(hname, '@')))
    p++;
  ap = access_top;
  while (ap) {
    if (!(ap->can & ACS_SITELOCK) && (quick_wild(ap->host, hname) || (p && quick_wild(ap->host, p)))) {
      /* Got one */
      if (ap->cant && ((ap->cant & flag) == flag))
	return 0;
      if (ap->can && (ap->can & flag))
	return 1;
      /* Hmm. We don't know if we can or not, so continue */
      break;
    }
    ap = ap->next;
  }
  /* Flag was neither set nor unset. If the flag was a toggle,
   * then the host can do it. If not, the host can't */
  for (c = acslist; c->name; c++) {
    if (flag & c->flag)
      return c->toggle ? 1 : 0;
  }
  /* Should never reach here, but just in case */
  return 1;
}

/* Add an entry to the linked list after the @sitelock entry.
 * If there is no @sitelock entry, add one to the end of the list
 * and then add the entry.
 * Build an appropriate comment based on the player and date
 */
int
add_access_sitelock(player, host, can, cant)
    dbref player;
    const char *host;
    const int can;
    const int cant;
{
  struct access *end;
  struct access *tmp;
  char *date;

  tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access");
  if (!tmp)
    return 0;
  tmp->can = can;
  tmp->cant = cant;
  strcpy(tmp->host, host);
  date = (char *) ctime(&mudtime);
  date[strlen(date) - 1] = '\0';
  sprintf(tmp->comment, "By %s(#%d) on %s", Name(player), player, date);
  tmp->next = NULL;

  if (!access_top) {
    /* Add to the beginning, but first add a sitelock marker */
    if (!add_access_node("@sitelock", ACS_SITELOCK, 0, ""))
      return 0;
    access_top->next = tmp;
  } else {
    end = access_top;
    while (end->next && end->can != ACS_SITELOCK)
      end = end->next;
    /* Now, either we're at the sitelock or the end */
    if (end->can != ACS_SITELOCK) {
      /* We're at the end and there's no sitelock marker. Add one */
      if (!add_access_node("@sitelock", ACS_SITELOCK, 0, ""))
	return 0;
      end = end->next;
    } else {
      /* We're in the middle, so be sure we keep the list linked */
      tmp->next = end->next;
    }
    end->next = tmp;
  }
  return 1;
}

/* Free the entire access list */
static void
free_access_list()
{
  struct access *ap, *next;
  ap = access_top;
  while (ap) {
    next = ap->next;
    mush_free((Malloc_t) ap, "struct_access");
    ap = next;
  }
  access_top = NULL;
}


/* Dump the access list for the player */
void
do_list_access(player)
    dbref player;
{
  struct access *ap;
  acsflag *c;
  char flaglist[BUFFER_LEN];
  char *bp;

  for (ap = access_top; ap; ap = ap->next) {
    if (ap->can != ACS_SITELOCK) {
      bp = flaglist;
      for (c = acslist; c->name; c++) {
	if (c->flag == ACS_DEFAULT)
	  continue;
	if (ap->can & c->flag) {
	  safe_chr(' ', flaglist, &bp);
	  safe_str(c->name, flaglist, &bp);
	}
	if (c->toggle && (ap->cant & c->flag)) {
	  safe_chr(' ', flaglist, &bp);
	  safe_chr('!', flaglist, &bp);
	  safe_str(c->name, flaglist, &bp);
	}
      }
      *bp = '\0';
      notify(player, tprintf("SITE: %-20s   FLAGS:%s", ap->host, flaglist));
      notify(player, tprintf(" COMMENT: %s", ap->comment));
    } else {
      notify(player, "---- @sitelock will add sites immediately below this line ----");
    }

  }
}

/* Parse options and return the appropriate can and cant bits.
 * Return the number of options successfully parsed.
 * This makes a copy of the options string, so it's not modified
 */
int
parse_access_options(opts, can, cant, player)
    const char *opts;
    int *can;
    int *cant;
    dbref player;		/* Who to notify of errors, or NOTHING to write to log */
{
  char myopts[BUFFER_LEN];
  char *p;
  char *w;
  acsflag *c;
  int found, totalfound;

  if (!opts || !*opts)
    return 0;
  strcpy(myopts, opts);
  totalfound = 0;
  p = trim_space_sep(myopts, ' ');
  while ((w = split_token(&p, ' '))) {
    found = 0;
    if (*w == '!') {
      /* Found a negated warning */
      w++;
      for (c = acslist; c->name; c++) {
	if (c->toggle && !strncasecmp(w, c->name, strlen(c->name))) {
	  *cant |= c->flag;
	  found++;
	}
      }
    } else {
      /* None is special */
      if (!strncasecmp(w, "NONE", 4)) {
	*cant = ACS_DEFAULT;
	found++;
      } else {
	for (c = acslist; c->name; c++) {
	  if (!strncasecmp(w, c->name, strlen(c->name))) {
	    *can |= c->flag;
	    found++;
	  }
	}
      }
    }
    /* At this point, we haven't matched any warnings. */
    if (!found) {
      if (GoodObject(player))
	notify(player, tprintf("Unknown access option: %s", w));
      else
	do_log(LT_ERR, GOD, GOD, "Unknown access flag: %s", w);
    } else {
      totalfound += found;
    }
  }
  return totalfound;
}