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.
*/

/* configure all options BEFORE including system stuff. */
#include    "config.h"
#include    "mud.h"
#include    "sym.h"
#include    "cmd.h"
#include    "match.h"
#include    "vars.h"

/*
routines to run commands, expand variables, and tokenize. if you modify
this to search different locations for macros, you'll probably want to
look at runargv(), rather than all the string-thrashing code.

if you are adding a new variable or variable type, you will probably
want to look *CAREFULLY* at vlookup.
*/


static int nowhereman;          /* player is nowhere */
static char *actor;             /* real player who started all this */
static int recursion_depth;


/*
copy 's' into the expansion buffer. return nonzero if it don't fit.
*/
static int pastein (char *s, char **bp, size_t * lp)
{
  while (s && *s != '\0') {
    if (--(*lp) <= 0)
      return (1);
    **bp = *s++, (*bp)++;
  }
  return (0);
}




/*
    vlookup is responsible for taking a string and returning
another string that somehow has something to do with the first.
a buffer is provided, in case string space is needed.
*/
static char *vlookup (char *nam, char *who, char *aswho,
  int ac, char *av[], char *buf, size_t bsiz)
{
  if (nam == (char *) 0 || *nam == '\0')
    return ((char *) 0);


  /* locale */
  if (!strcmp (nam, "here"))
    return (ut_loc (aswho));


  /* actor */
  if (!strcmp (nam, "actor"))
    return (run_actor ());


  /* me */
  if (!strcmp (nam, "me"))
    return (aswho);


  /* self */
  if (!strcmp (nam, "self"))
    return (aswho);


  /* subv */
  if (!strcmp (nam, "subv")) {
    char *sp;

    if ((sp = ut_getatt (who, 0, typ_str, nam, (char *) 0)) == (char *) 0)
      return ("it");
    return (sp);
  }

  /* Subv */
  if (!strcmp (nam, "Subv")) {
    char *sp;

    if ((sp = ut_getatt (who, 0, typ_str, nam, (char *) 0)) == (char *) 0)
      return ("It");
    return (sp);
  }


  /* objv */
  if (!strcmp (nam, "objv")) {
    char *sp;

    if ((sp = ut_getatt (who, 0, typ_str, nam, (char *) 0)) == (char *) 0)
      return ("it");
    return (sp);
  }

  /* Objv */
  if (!strcmp (nam, "Objv")) {
    char *sp;

    if ((sp = ut_getatt (who, 0, typ_str, nam, (char *) 0)) == (char *) 0)
      return ("It");
    return (sp);
  }


  /* posv */
  if (!strcmp (nam, "posv")) {
    char *sp;

    if ((sp = ut_getatt (who, 0, typ_str, nam, (char *) 0)) == (char *) 0)
      return ("its");
    return (sp);
  }

  /* Posv */
  if (!strcmp (nam, "Posv")) {
    char *sp;

    if ((sp = ut_getatt (who, 0, typ_str, nam, (char *) 0)) == (char *) 0)
      return ("Its");
    return (sp);
  }


  /* check for $# - param count */
  if (!strcmp (nam, "#"))
    return (itoa (ac, buf, 10));


  /* check for $1 - $ac - individual params */
  if (isdigit (*nam)) {
    int anum;

    if ((anum = atoi (nam)) >= 0 && anum < ac)
      return (av[anum]);
    return ((char *) 0);
  }


  /* check for $* and $+# - concatenated params */
  if (*nam == '*' || *nam == '+') {
    char *xp = buf;
    size_t xs = bsiz;
    char *yp;
    int lo = 0;

    /* if $+#, then figure out the low bound */
    if (*nam != '*') {
      if ((lo = atoi (nam + 1)) < 0 || lo >= ac)
        return ((char *) 0);
    }

    /* now concatenate args */
    for (; lo < ac; lo++) {
      for (yp = av[lo]; *yp != '\0';) {
        if (--xs <= 0)
          return ((char *) 0);
        *xp++ = *yp++;
      }
      if (--xs <= 0)
        return ((char *) 0);
      *xp++ = ' ';
    }

    /* ok. */
    if (xp > buf)
      *(xp - 1) = '\0';
    else
      *buf = '\0';
    return (buf);
  }


  /* and last but not least, we get to the hairy stuff. */
  if (*nam == '@') {
    char ob[MAXOID];

    nam++;

    if (matchlocal (who, nam, ut_loc (who),
        MTCH_UNIQ | MTCH_NONLOC | MTCH_QUIET, ob)
      && matchlocal (who, nam, ut_loc (who), MTCH_FRST | MTCH_QUIET, ob))
      return ((char *) 0);
    (void) strcpy (buf, ob);
    return (buf);
  }


  if (*nam == '!') {
    char ob[MAXOID];
    int avp;

    nam++;

    if (!isdigit (*nam))
      return ((char *) 0);
    if ((avp = atoi (nam)) < 0 || avp >= ac)
      return ((char *) 0);

    if (matchlocal (who, av[avp], ut_loc (who),
        MTCH_UNIQ | MTCH_NONLOC | MTCH_QUIET, ob)
      && matchlocal (who, av[avp], ut_loc (who), MTCH_FRST | MTCH_QUIET, ob))
      return ((char *) 0);

    (void) strcpy (buf, ob);
    return (buf);
  }

  return ((char *) 0);
}




/*
    iteratively look up the components of a $-variable and when
we reach a final result, paste it in.
*/
int vresolve (char *nam, char *who, char *aswho, int ac, char *av[],
  char **bp, size_t * lp)
{
  Obj *op;
  int wiz;
  char *e = nam;
  char *lkp;
  char *nxt;
  char buf[MUDBUF];

  if (nam == (char *) 0 || *nam == '\0')
    return (0);

  if ((e = strchr (e, '.')) != (char *) 0)
    *e++ = '\0';

  /* resolve first part */
  if ((lkp =
      vlookup (nam, who, aswho, ac, av, buf, sizeof (buf))) == (char *) 0)
    return (0);

  /* happy ending? (usual case) */
  if (e == (char *) 0) {
    return (pastein (lkp, bp, lp));
  }

  /* sigh. */
  wiz = ut_flagged (aswho, var_wiz);

  /* gets tricky. we have '.'s in there. iterate our ass off */
  while (1) {
    char *ap;
    char *atp;

    if ((nxt = e) == (char *) 0 || *nxt == '\0')
      break;

    if ((atp = var_namatch (nxt)) == (char *) 0) {
      atp = nxt;
    }

    if ((e = strchr (e, '.')) != (char *) 0)
      *e++ = '\0';
    if ((op = cache_get (lkp)) == (Obj *) 0)
      return (0);
    if ((ap = objattr (op, atp, (int *) 0)) == (char *) 0)
      return (0);
    if (e != (char *) 0 && *e != '\0' && !attistype (ap, typ_obj))
      return (0);
    if ((ap = attdata (ap)) == (char *) 0)
      return (0);

    /* final insult: can the guy even see it ? */
    if (!wiz && !var_ispublic (atp, who, aswho, lkp) &&
      !ut_isobjown (aswho, lkp))
      return (0);

    lkp = ap;
  }
  return (pastein (lkp, bp, lp));
}




/*
tokenize a command line into an argv. this is complex, but *CAN'T*
use static buffers since it can get called in recursion. this is
egregiously more complicated than I'd like it to be, but all this
string fiddling requires some hefty error checking. at least Saber-C
likes it, even if I don't  ;)
*/
int enargv (char *bp, char **av, int avsiz, char buf[], int bsiz,
  char **retp, char *who, char *aswho, int cac, char *cav[])
{
  char *op;
  int ac = 0;
  int quot = 0;

  /* clean slate */
  av[0] = op = buf;
  *op = '\0';
  av[1] = (char *) 0;

  /* preskip white/junk */
  while (*bp != '\0' && (isspace (*bp) || !isprint (*bp) || *bp == ';'))
    bp++;

  /* this is basically a BIG case statement */
  while (*bp != '\0') {


    /* drop nonprint */
    if (!isprint (*bp)) {
      bp++;
      continue;
    }


    /* set a quotation mark if on is needed */
    if (!quot && (*bp == '\"' || *bp == '\'')) {
      quot = *bp++;
      continue;
    }


    /* accept a word OR a virtual newline */
    if ((isspace (*bp) || *bp == ';') && !quot) {
      if (bsiz-- <= 0) {
        if (retp != (char **) 0)
          *retp = (char *) 0;
        return (-1);
      }
      *op++ = '\0';

      if (++ac >= avsiz) {
        if (retp != (char **) 0)
          *retp = (char *) 0;
        return (-2);
      }
      av[ac] = op;
      av[ac + 1] = (char *) 0;
      *op = '\0';

      while (isspace (*bp))     /* eat whitespace first! */
        bp++;

      /* if semic (virtual line break) return now */
      if (*bp == ';') {
        if (retp != (char **) 0)
          *retp = ++bp;
        return (ac);
      }

      if (*bp == '\0') {
        if (retp != (char **) 0)
          *retp = bp;
        return (ac);
      }

      continue;
    }


    /* end a quote */
    if (quot && *bp == quot) {
      quot = 0;
      bp++;
      continue;
    }


    /* check escapes - do *NOT* permit escaped newlines!!!! */
    if (*bp == '\\') {
      *op++ = *(++bp);

      if (*bp == '\0')
        break;
      bp++;
      continue;
    }


    /* handle '=' to tokenize to end-of-line hack */
    if (*bp == '=') {
      bp++;

      while (isspace (*bp) && *bp != '\0')
        bp++;

      /* if we are currently in mid-arg, terminate */
      if (av[ac] != (char *) 0 && av[ac] != op) {
        *op++ = '\0';
        if (++ac >= avsiz) {
          if (retp != (char **) 0)
            *retp = (char *) 0;
          return (-2);
        }
        av[ac] = op;
      }

      while (*bp != '\0') {
        if (--bsiz < 0) {
          if (retp != (char **) 0)
            *retp = (char *) 0;
          return (-1);
        }
        *op++ = *bp++;
      }
      *op++ = '\0';

      if (++ac >= avsiz) {
        if (retp != (char **) 0)
          *retp = (char *) 0;
        return (-2);
      }
      av[ac] = (char *) 0;
      if (retp != (char **) 0)
        *retp = bp;
      return (ac);
    }


    /* expand $vars */
    if (quot != '\'' && *bp == '$') {
      char vb[MAXVLEN];
      int vl = 0;

      /* transform "$$" -> "$" */
      if (*(++bp) == '$') {
        if (--bsiz < 0) {
          if (retp != (char **) 0)
            *retp = (char *) 0;
          return (-1);
        }
        *op++ = *bp++;
        continue;
      }

      /* handle ${var} format */
      if (*bp == '{') {
        bp++;
        while (*bp != '\0' && *bp != '}' && vl < (int) sizeof (vb) - 2)
          vb[vl++] = *bp++;
        vb[vl] = '\0';
        if (*bp == '}')
          bp++;
        if (vresolve (vb, who, aswho, cac, cav, &op, &bsiz)) {
          if (retp != (char **) 0)
            *retp = (char *) 0;
          return (-1);
        }
        continue;
      }


      /* default format: word or suchlike junk */
      while (*bp != '\0' && vl < (int) sizeof (vb) - 2 &&
        (isalpha (*bp) || isdigit (*bp) ||
          *bp == '.' || *bp == '_' || *bp == '+' || *bp == '*' || *bp == '#'))
        vb[vl++] = *bp++;
      vb[vl] = '\0';
      if (vresolve (vb, who, aswho, cac, cav, &op, &bsiz)) {
        if (retp != (char **) 0)
          *retp = (char *) 0;
        return (-1);
      }
      continue;
    }


    /* default: just copy character */
    if (--bsiz < 0) {
      if (retp != (char **) 0)
        *retp = (char *) 0;
      return (-1);
    }
    *op++ = *bp++;
  }


  /* partially finished word at null */
  if (av[ac] != (char *) 0 && av[ac][0] != '\0') {
    *op = '\0';

    if (ac++ >= avsiz) {
      if (retp != (char **) 0)
        *retp = (char *) 0;
      return (-2);
    }
    av[ac] = (char *) 0;
  }

  if (retp != (char **) 0)
    *retp = bp;
  return (ac);
}




int run_tokenize (char *bp, char **av, int avsiz, char *tobuf, size_t tobsiz)
{
  return (enargv (bp, av, avsiz, tobuf, tobsiz, (char **) 0, "", "", 0,
      (char **) 0));
}


static int run_execute (char *who, char *aswho, char *macp,
  int ac, char *av[], int *rv)
{
  if (attistype (macp, typ_cmd)) {
    *rv = run (who, aswho, attdata (macp), ac, av, 0);
    return (1);
  }
  if (attistype (macp, typ_u)) {
    parser_setinput (attdata (macp));
    if (parser_compile (who))
      (void) parser_run (who, aswho, ac, av);
    *rv = UERR_NONE;
    return (1);
  }
  return (0);
}



/*
run an argv'd argc'd command line. here's where to insert MUD-local
command-line processing that needs to take place before commands run
*/
static int runargv (char *who, char *aswho, int ac, char *av[])
{
  Cmd *c;
  char ob[MAXOID];
  char *macp;
  int mtyp;
  char *here;

  /* PHASE #1 - check for hard wired call to system function */
  if (av[0][0] == '@') {

    if ((c = cmdlookup (av[0] + 1)) != (Cmd *) 0) {
      if (ac < c->argc || (c->argc != ac && (c->flgs & CM_FIXARG))) {
        say (who, "usage: ", c->usage, "\n", (char *) 0);
        return (1);
      }

      if ((c->flgs & CM_PRIV) && !ut_flagged (aswho, var_wiz)) {
        say (who, "permission denied.\n", (char *) 0);
        return (1);
      }


      if (nowhereman && !(c->flgs & CM_NOWHERE) &&
        !ut_flagged (aswho, var_wiz)) {
        say (who, "You are nowhere. Go away.\n", (char *) 0);
        return (1);
      }
#ifdef  PLAYERONLY
      if ((c->flgs & CM_NOPLY) && !ut_flagged (who, var_isplay)
        && !ut_flagged (aswho, var_wiz))
        return (1);
#endif

      return ((*c->func) (ac, av, who, aswho));
    }

    /* call system macro if one */
    if (!nowhereman && (macp = symlook (av[0] + 1, &mtyp)) != (char *) 0) {
      char *whop = aswho;

      /* handle system setuid macros - DANGER */
      if (mtyp & SFLG_SUID)
        if ((whop = symowner (av[0] + 1)) == (char *) 0)
          whop = aswho;

      if (mtyp & SFLG_CMD)
        return (run (who, whop, macp, ac, av, 0));
      if (mtyp & SFLG_UCMD) {
        parser_setinput (macp);
        if (parser_compile (who))
          (void) parser_run (who, whop, ac, av);
        return (UERR_NONE);
      }
    }

    say (who, "\"", av[0], "\": unknown command.\n", (char *) 0);
    return (1);
  }

  /* folks who are nowhere can't do this */
  if (nowhereman)
    goto nowherejump;

  /* PHASE #2 - look for exit in room */
  here = ut_loc (who);
  if (matchargvexit (here, av, ac, ob) != 0) {
    return (player_go (ac, av, who, here, ob));
  }


  /* PHASE #3 - look for macro attached to caller */
  macp = ut_getatt (who, 1, (char *) 0, av[0], (char *) 0);
  if (macp != (char *) 0 && run_execute (who, who, macp, ac, av, &mtyp))
    return (mtyp);


  /* PHASE #3a - look for macro attached to aswho -- for luck */
  macp = ut_getatt (aswho, 1, (char *) 0, av[0], (char *) 0);
  if (macp != (char *) 0 && run_execute (who, aswho, macp, ac, av, &mtyp))
    return (mtyp);


  /* PHASE #4 - look for macro attached to room */
  macp = ut_getatt (here, 1, (char *) 0, av[0], (char *) 0);
  if (macp != (char *) 0 && run_execute (who, here, macp, ac, av, &mtyp))
    return (mtyp);


#ifdef  COMBAT
  /* PHASE #4.5 - look for macro attached to object in use */
  macp = ut_getatt (who, 0, typ_obj, var_weapon, (char *) 0);
  if (macp != (char *) 0) {
    char *up;

    up = ut_getatt (macp, 1, (char *) 0, av[0], (char *) 0);
    if (up != (char *) 0 && run_execute (who, macp, up, ac, av, &mtyp))
      return (mtyp);
  }
#endif


  /* PHASE #5 - look for macro attached to object in use */
  macp = ut_getatt (who, 0, typ_obj, var_using, (char *) 0);
  if (macp != (char *) 0) {
    char *up;

    up = ut_getatt (macp, 1, (char *) 0, av[0], (char *) 0);
    if (up != (char *) 0 && run_execute (who, macp, up, ac, av, &mtyp))
      return (mtyp);
  }
#ifdef SEARCH_INVENTORY
  /* PHASE #5a - look for a macro on anything the player is carrying */
  macp = ut_getatt (who, 0, typ_list, var_cont, (char *) 0);
  macp = lstnext (macp, ob, sizeof (ob));
  while (macp != (char *) 0) {
    char *listp;
    listp = ut_getatt (ob, 1, (char *) 0, av[0], (char *) 0);
    if (listp != (char *) 0 && run_execute (who, ob, listp, ac, av, &mtyp))
      return (mtyp);
    macp = lstnext (macp, ob, sizeof (ob));
  }
#endif /* SEARCH_INVENTORY */

#ifdef SEARCH_ROOM
  /* PHASE #5b - look for a macro on anything in the room (ugh!) */
  macp = ut_getatt (here, 0, typ_list, var_cont, (char *) 0);
  macp = lstnext (macp, ob, sizeof (ob));
  while (macp != (char *) 0) {
    char *listp;
    listp = ut_getatt (ob, 1, (char *) 0, av[0], (char *) 0);
    if (listp != (char *) 0 && run_execute (who, ob, listp, ac, av, &mtyp))
      return (mtyp);
    macp = lstnext (macp, ob, sizeof (ob));
  }
#endif /* SEARCH_ROOM */

/* skip stuff for folks who are nowhere */
nowherejump:

  /* PHASE #6 - check for non-hardwired call to system functions */
  if ((c = cmdlookup (av[0])) != (Cmd *) 0) {
    if (ac < c->argc || (c->argc != ac && (c->flgs & CM_FIXARG))) {
      say (who, "usage: ", c->usage, "\n", (char *) 0);
      return (1);
    }

    if ((c->flgs & CM_PRIV) && !ut_flagged (aswho, var_wiz)) {
      say (who, "permission denied.\n", (char *) 0);
      return (1);
    }

    if (nowhereman && !(c->flgs & CM_NOWHERE) && !ut_flagged (aswho, var_wiz)) {
      say (who, "You are nowhere. Go away.\n", (char *) 0);
      return (1);
    }
#ifdef  PLAYERONLY
    if ((c->flgs & CM_NOPLY) && !ut_flagged (who, var_isplay)
      && !ut_flagged (aswho, var_wiz))
      return (1);
#endif

    return ((*c->func) (ac, av, who, aswho));
  }

  /* PHASE #7 call system macro if one */
  if (!nowhereman && (macp = symlook (av[0], &mtyp)) != (char *) 0) {
    char *whop = who;

    /* handle system setuid macros - DANGER */
    if (mtyp & SFLG_SUID)
      if ((whop = symowner (av[0])) == (char *) 0)
        whop = aswho;

    if (mtyp & SFLG_CMD)
      return (run (who, whop, macp, ac, av, 0));
    if (mtyp & SFLG_UCMD) {
      parser_setinput (macp);
      if (parser_compile (who))
        (void) parser_run (who, whop, ac, av);
      return (UERR_NONE);
    }
  }


  /* PHASE #8 - barf. */
  say (who, "Huh? What is \"", av[0], "\"?\n", (char *) 0);
  return (1);
}




/*
get a line of commands from 'who' and repeatedly break it into parameters,
or simply detect hotwired commands and call them.
the eb and ab, etc must not be static buffers, as this can be called
recursively.
*/
int run (char *who, char *aswho, char *s, int argc, char *argv[],
  int real_call)
{
  char *bp;
  char *bp2;
  char ab[MUDBUF * 2];          /* buffer to tokenize into */
  char *av[MAXARG];             /* token vector */
  int ac = 0;
  int uers = 0;

  /* some sanity checking and whatnot */
  if (real_call) {
    run_setactor (who);
    recursion_depth = 0;
  } else {
    if (++recursion_depth > MAXRECURSIONS) {
      say (who, "Too many recursions.\n", (char *) 0);
      return (UERR_FATAL);
    }
  }

  /* we set 'bp' as the remaining text pointer here */
  if ((bp = s) == (char *) 0 || *bp == '\0')
    return (UERR_NONE);

  /* Preskip whitespace, obviously */
  while (isspace (*bp))
    bp++;


  /*
     check for hotwired commands, and if there are any, fake up an
     argc, argv, and just run that.
     hotwire say */
  if (*bp == '\"' && !nowhereman) {
    ac = 2;
    av[0] = "say";
    av[1] = bp + 1;
    av[2] = (char *) 0;
    bp = (char *) 0;
    return (runargv (who, aswho, ac, av));
  }

  if (*bp == ':' && !nowhereman) {
    ac = 2;
    av[0] = "do";
    av[1] = bp + 1;
    av[2] = (char *) 0;
    bp = (char *) 0;
    return (runargv (who, aswho, ac, av));
  }

  if (*bp == '!' && !nowhereman) {
    run_setactor (who);
    parser_setinput (bp + 1);
    if (parser_compile (who))
      (void) parser_run (who, aswho, ac, av);   // :WARN: ac uninitialized here. - JAL
    return (UERR_NONE);
  }

  while (bp != (char *) 0 && *bp != '\0') {
    if (real_call)
      run_setactor (who);

    /* tokenize the line and deal with it */
    ac =
      enargv (bp, av, MAXARG, ab, sizeof (ab), &bp2, who, aswho, argc, argv);
    bp = bp2;
    if (ac == 0)
      continue;

    if (ac < 0) {
      switch (ac) {
      case -1:
        say (who, "command input too large.\n", (char *) 0);
        return (UERR_FATAL);

      case -2:
        say (who, "too many tokens in command.\n", (char *) 0);
        return (UERR_FATAL);
      }
      continue;
    }

    uers = runargv (who, aswho, ac, av);
  }
  return (uers);
}





/* return the run_level of the run */
int run_level ()
{
  return (recursion_depth);
}





/* add one to the run level */
int add_run_level ()
{
  return (++recursion_depth);
}





/* return the actor of the run */
char *run_actor ()
{
  return (actor);
}



void run_setactor (char *s)
{
  actor = s;
}




/* run a command line at boot-time.  */
int run_boot (char *l)
{
  Cmd *c;
  char *av[MAXARG];
  char ab[BUFSIZ];
  int ac;

  ac = run_tokenize (l, av, MAXARG, ab, sizeof (ab));
  if (ac == 0)
    return (0);
  if (ac < 0) {
    log_printf ("tokenization error in boot command.\n", (char *) 0);
    return (1);
  }

  if ((c = cmdlookup (av[0])) != (Cmd *) 0) {
    if (ac < c->argc || (c->argc != ac && (c->flgs & CM_FIXARG))) {
      log_printf ("usage: ", c->usage, "\n", (char *) 0);
      return (1);
    }
    return ((*c->func) (ac, av, PRIVUSER, PRIVUSER));
  }

  log_printf ("uknown command: ", av[0], "\n", (char *) 0);
  return (1);
}