pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file lock.c
 *
 * \brief Locks for PennMUSH.
 *
 * \verbatim
 *
 * This is the core of Ralph Melton's rewrite of the @lock system.
 * These are some of the underlying assumptions:
 *
 * 1) Locks are checked many more times than they are set, so it is
 * quite worthwhile to spend time when setting locks if it expedites
 * checking locks later.
 *
 * 2) Most possible locks are never used. For example, in the days
 * when there were only basic locks, use locks, and enter locks, in
 * one database of 15000 objects, there were only about 3500 basic
 * locks, 400 enter locks, and 400 use locks.
 * Therefore, it is important to make the case where no lock is present
 * efficient both in time and in memory.
 *
 * 3) It is far more common to have the server itself check for locks
 * than for people to check for locks in MUSHcode. Therefore, it is
 * reasonable to incur a minor slowdown for checking locks in MUSHcode
 * in order to speed up the server's checking.
 *
 * \endverbatim
 */

#include "copyrite.h"
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "conf.h"
#include "externs.h"
#include "boolexp.h"
#include "mushdb.h"
#include "attrib.h"
#include "dbdefs.h"
#include "lock.h"
#include "match.h"
#include "log.h"
#include "flags.h"
#include "dbdefs.h"
#include "mymalloc.h"
#include "strtree.h"
#include "privtab.h"
#include "parse.h"
#include "confmagic.h"


/* If any lock_type ever contains the character '|', reading in locks
 * from the db will break.
 */
const lock_type Basic_Lock = "Basic";	  /**< Name of basic lock */
const lock_type Enter_Lock = "Enter";	  /**< Name of enter lock */
const lock_type Use_Lock = "Use";	  /**< Name of use lock */
const lock_type Zone_Lock = "Zone";	  /**< Name of zone lock */
const lock_type Page_Lock = "Page";	  /**< Name of page lock */
const lock_type Tport_Lock = "Teleport";  /**< Name of teleport lock */
const lock_type Speech_Lock = "Speech";	  /**< Name of speech lock */
const lock_type Listen_Lock = "Listen";	  /**< Name of listen lock */
const lock_type Command_Lock = "Command"; /**< Name of command lock */
const lock_type Parent_Lock = "Parent";	  /**< Name of parent lock */
const lock_type Link_Lock = "Link";	  /**< Name of link lock */
const lock_type Leave_Lock = "Leave";	  /**< Name of leave lock */
const lock_type Drop_Lock = "Drop";	  /**< Name of drop lock */
const lock_type Give_Lock = "Give";	  /**< Name of give lock */
const lock_type Mail_Lock = "Mail";	  /**< Name of mail lock */
const lock_type Follow_Lock = "Follow";	  /**< Name of follow lock */
const lock_type Examine_Lock = "Examine"; /**< Name of examine lock */
const lock_type Chzone_Lock = "Chzone";	  /**< Name of chzone lock */
const lock_type Forward_Lock = "Forward"; /**< Name of forward lock */
const lock_type Control_Lock = "Control"; /**< Name of control lock */
const lock_type Dropto_Lock = "Dropto";	  /**< Name of dropto lock */
const lock_type Destroy_Lock = "Destroy"; /**< Name of destroy lock */
const lock_type Interact_Lock = "Interact"; /**< Name of interaction lock */
/* Define new lock types here. */

/** Table of lock names and permissions */
const lock_list lock_types[] = {
  {"Basic", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Enter", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Use", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Zone", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Page", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Teleport", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Speech", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Listen", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Command", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Parent", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Link", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Leave", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Drop", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Give", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Mail", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Follow", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Examine", TRUE_BOOLEXP, GOD, LF_PRIVATE | LF_OWNER, NULL},
  {"Chzone", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Forward", TRUE_BOOLEXP, GOD, LF_PRIVATE | LF_OWNER, NULL},
  {"Control", TRUE_BOOLEXP, GOD, LF_PRIVATE | LF_OWNER, NULL},
  {"Dropto", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  {"Destroy", TRUE_BOOLEXP, GOD, LF_PRIVATE | LF_OWNER, NULL},
  {"Interact", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
  /* Add new lock types just before this line. */
  {NULL, TRUE_BOOLEXP, GOD, 0, NULL}
};

/** Table of base attributes associated with success and failure of
 * locks. These are the historical ones; we automatically generate
 * such attribute names for those that aren't in this table using
 * <lock>_LOCK`<message>
 */
const LOCKMSGINFO lock_msgs[] = {
  {"Basic", "SUCCESS", "FAILURE"},
  {"Enter", "ENTER", "EFAIL"},
  {"Use", "USE", "UFAIL"},
  {"Leave", "LEAVE", "LFAIL"},
  {NULL, NULL, NULL}
};

/** Table of lock permissions */
PRIV lock_privs[] = {
  {"visual", 'v', LF_VISUAL, LF_VISUAL},
  {"no_inherit", 'i', LF_PRIVATE, LF_PRIVATE},
  {"no_clone", 'c', LF_NOCLONE, LF_NOCLONE},
  {"wizard", 'w', LF_WIZARD, LF_WIZARD},
  /*  {"owner", 'o', LF_OWNER, LF_OWNER}, */
  {"locked", '+', LF_LOCKED, LF_LOCKED},
  {NULL, '\0', 0, 0}
};

StrTree lock_names;  /**< String tree of lock names */

static void free_one_lock_list(lock_list *ll);
static lock_type check_lock_type(dbref player, dbref thing, lock_type name);
static int delete_lock(dbref player, dbref thing, lock_type type);
static int can_write_lock(dbref player, dbref thing, lock_list *lock);
static lock_list *getlockstruct_noparent(dbref thing, lock_type type);

/** Number of locks to store in a page, assuming 4096 byte pages */
#define LOCKS_PER_PAGE 200

static lock_list *free_list = NULL;

static lock_list *next_free_lock(void);
static void free_lock(lock_list *ll);

/** Return a list of lock flag characters.
 * \param ll pointer to a lock.
 * \return string of lock flag characters.
 */
const char *
lock_flags(lock_list *ll)
{
  return privs_to_letters(lock_privs, L_FLAGS(ll));
}

/** List all lock flag characters on a buffer
 * \param buff The buffer
 * \param bp Pointer to a position in the buffer.
 */

void
list_lock_flags(char *buff, char **bp)
{
  int i;
  for (i = 0; lock_privs[i].name; i++) {
    if (lock_privs[i].letter)
      safe_chr(lock_privs[i].letter, buff, bp);
  }
}

/** List all lock flag names on a buffer
 * \param buff The buffer
 * \param bp Pointer to a position in the buffer.
 */

void
list_lock_flags_long(char *buff, char **bp)
{
  int i;
  int first = 1;
  for (i = 0; lock_privs[i].name; i++) {
    if (!first)
      safe_chr(' ', buff, bp);
    first = 0;
    safe_str(lock_privs[i].name, buff, bp);
  }
}

/** Return a list of lock flag names.
 * \param ll pointer to a lock.
 * \return string of lock flag names, space-separated.
 */
const char *
lock_flags_long(lock_list *ll)
{
  return privs_to_string(lock_privs, L_FLAGS(ll));
}


static int
string_to_lockflag(dbref player, char const *p)
{
  int f;
  f = string_to_privs(lock_privs, p, 0);
  if (!f)
    return -1;
  if (!See_All(player) && (f & LF_WIZARD))
    return -1;
  return f;
}

/** Initialize the lock strtree. */
void
init_locks(void)
{
  st_init(&lock_names);
}

static int
can_write_lock(dbref player, dbref thing, lock_list *lock)
{
  if (God(player))
    return 1;
  if (God(thing))
    return 0;
  if (Wizard(player))
    return 1;
  if (L_FLAGS(lock) & LF_WIZARD)
    return 0;
  if (L_FLAGS(lock) & LF_OWNER)
    return player == Owner(thing);
  if (L_FLAGS(lock) & LF_LOCKED)
    return Owner(player) == lock->creator;
  return 1;
}


static lock_list *
next_free_lock(void)
{
  lock_list *ll;

  if (!free_list) {
    size_t n;

    ll = mush_malloc(sizeof(lock_list) * LOCKS_PER_PAGE, "lock_page");

    if (!ll)
      mush_panic("Unable to allocate memory for locks!");

    for (n = 0; n < LOCKS_PER_PAGE - 1; n++) {
      ll[n].type = NULL;
      ll[n].key = TRUE_BOOLEXP;
      ll[n].creator = NOTHING;
      ll[n].flags = 0;
      ll[n].next = &ll[n + 1];
    }

    ll[n].next = NULL;
    ll[n].type = NULL;
    ll[n].key = TRUE_BOOLEXP;
    ll[n].creator = NOTHING;
    ll[n].flags = 0;

    free_list = &ll[0];
  }

  ll = free_list;
  free_list = ll->next;

  ll->type = NULL;
  ll->key = TRUE_BOOLEXP;

  return ll;
}

static void
free_lock(lock_list *ll)
{
  ll->type = NULL;
  ll->key = TRUE_BOOLEXP;
  ll->creator = NOTHING;
  ll->flags = 0;
  ll->next = free_list;
  free_list = ll;
}

/** Given a lock type, find a lock, possibly checking parents.
 * \param thing object on which lock is to be found.
 * \param type type of lock to find.
 * \return pointer to boolexp of lock.
 */
boolexp
getlock(dbref thing, lock_type type)
{
  struct lock_list *ll = getlockstruct(thing, type);
  if (!ll)
    return TRUE_BOOLEXP;
  else
    return L_KEY(ll);
}

/** Given a lock type, find a lock without checking parents. 
 * \param thing object on which lock is to be found.
 * \param type type of lock to find.
 * \return pointer to boolexp of lock.
 */
boolexp
getlock_noparent(dbref thing, lock_type type)
{
  struct lock_list *ll = getlockstruct_noparent(thing, type);
  if (!ll)
    return TRUE_BOOLEXP;
  else
    return L_KEY(ll);
}

lock_list *
getlockstruct(dbref thing, lock_type type)
{
  lock_list *ll;
  dbref p = thing, ancestor = NOTHING;
  int cmp, count = 0, ancestor_in_chain = 0;

  if (GoodObject(thing))
    ancestor = Ancestor_Parent(thing);
  do {
    for (; GoodObject(p); p = Parent(p)) {
      if (count++ > 100)
	return NULL;
      if (p == ancestor)
	ancestor_in_chain = 1;
      ll = Locks(p);
      while (ll && L_TYPE(ll)) {
	cmp = strcasecmp(L_TYPE(ll), type);
	if (cmp == 0)
	  return (p != thing && (ll->flags & LF_PRIVATE)) ? NULL : ll;
	else if (cmp > 0)
	  break;
	ll = ll->next;
      }
    }
    p = ancestor;
  } while (!ancestor_in_chain && !Orphan(thing) && GoodObject(ancestor));
  return NULL;
}

static lock_list *
getlockstruct_noparent(dbref thing, lock_type type)
{
  lock_list *ll = Locks(thing);
  int cmp;

  while (ll && L_TYPE(ll)) {
    cmp = strcasecmp(L_TYPE(ll), type);
    if (cmp == 0)
      return ll;
    else if (cmp > 0)
      break;
    ll = ll->next;
  }
  return NULL;
}


/** Determine if a lock type is one of the standard types or not.
 * \param type type of lock to check.
 * \return canonical lock type or NULL
 */
lock_type
match_lock(lock_type type)
{
  int i;
  for (i = 0; lock_types[i].type != NULL; i++) {
    if (strcasecmp(lock_types[i].type, type) == 0) {
      return lock_types[i].type;
    }
  }
  return NULL;
}

/** Return the proper entry from lock_types, or NULL.
 * \param type of lock to look up.
 * \return lock_types entry for lock.
 */
const lock_list *
get_lockproto(lock_type type)
{
  const lock_list *ll;

  for (ll = lock_types; ll->type; ll++)
    if (strcasecmp(type, ll->type) == 0)
      return ll;

  return NULL;

}

/** Add a lock to an object (primitive).
 * Set the lock type on thing to boolexp.
 * This is a primitive routine, to be called by other routines.
 * It will go somewhat wonky if given a NULL boolexp.
 * It will allocate memory if called with a string that is not already
 * in the lock table.
 * \param player the enactor, for permission checking.
 * \param thing object on which to set the lock.
 * \param type type of lock to set.
 * \param key lock boolexp pointer (should not be NULL!).
 * \param flags lock flags.
 * \retval 0 failure.
 * \retval 1 success.
 */
int
add_lock(dbref player, dbref thing, lock_type type, boolexp key, int flags)
{
  lock_list *ll, **t;
  lock_type real_type = type;

  if (!GoodObject(thing)) {
    return 0;
  }

  ll = getlockstruct_noparent(thing, type);

  if (ll) {
    if (!can_write_lock(player, thing, ll)) {
      free_boolexp(key);
      return 0;
    }
    /* We're replacing an existing lock. */
    free_boolexp(ll->key);
    ll->key = key;
    ll->creator = player;
    if (flags != -1)
      ll->flags = flags;
  } else {
    ll = next_free_lock();
    if (!ll) {
      /* Oh, this sucks */
      do_log(LT_ERR, 0, 0, "Unable to malloc memory for lock_list!");
    } else {
      real_type = st_insert(type, &lock_names);
      ll->type = real_type;
      ll->key = key;
      ll->creator = player;
      if (flags == -1) {
	const lock_list *l2 = get_lockproto(real_type);
	if (l2)
	  ll->flags = l2->flags;
	else
	  ll->flags = 0;
      } else {
	ll->flags = flags;
      }
      if (!can_write_lock(player, thing, ll)) {
	st_delete(real_type, &lock_names);
	free_boolexp(key);
	return 0;
      }
      t = &Locks(thing);
      while (*t && strcasecmp(L_TYPE(*t), L_TYPE(ll)) < 0)
	t = &L_NEXT(*t);
      L_NEXT(ll) = *t;
      *t = ll;
    }
  }
  return 1;
}

/** Add a lock to an object on db load.
 * Set the lock type on thing to boolexp.
 * Used only on db load, when we can't safely test the player's
 * permissions because they're not loaded yet.
 * This is a primitive routine, to be called by other routines.
 * It will go somewhat wonky if given a NULL boolexp.
 * It will allocate memory if called with a string that is not already
 * in the lock table.
 * \param player lock creator.
 * \param thing object on which to set the lock.
 * \param type type of lock to set.
 * \param key lock boolexp pointer (should not be NULL!).
 * \param flags lock flags.
 * \retval 0 failure.
 */
int
add_lock_raw(dbref player, dbref thing, lock_type type, boolexp key, int flags)
{
  lock_list *ll, **t;
  lock_type real_type = type;

  if (!GoodObject(thing)) {
    return 0;
  }

  ll = next_free_lock();
  if (!ll) {
    /* Oh, this sucks */
    do_log(LT_ERR, 0, 0, "Unable to malloc memory for lock_list!");
  } else {
    real_type = st_insert(type, &lock_names);
    ll->type = real_type;
    ll->key = key;
    ll->creator = player;
    if (flags == -1) {
      const lock_list *l2 = get_lockproto(real_type);
      if (l2)
	ll->flags = l2->flags;
      else
	ll->flags = 0;
    } else {
      ll->flags = flags;
    }
    t = &Locks(thing);
    while (*t && strcasecmp(L_TYPE(*t), L_TYPE(ll)) < 0)
      t = &L_NEXT(*t);
    L_NEXT(ll) = *t;
    *t = ll;
  }
  return 1;
}

/* Very primitive. */
static void
free_one_lock_list(lock_list *ll)
{
  if (ll == NULL)
    return;
  free_boolexp(ll->key);
  st_delete(ll->type, &lock_names);
  free_lock(ll);
}

/** Delete a lock from an object (primitive).
 * Another primitive routine.
 * \param player the enactor, for permission checking.
 * \param thing object on which to remove the lock.
 * \param type type of lock to remove.
 */
int
delete_lock(dbref player, dbref thing, lock_type type)
{
  lock_list *ll, **llp;
  if (!GoodObject(thing)) {
    return 0;
  }
  llp = &(Locks(thing));
  while (*llp && strcasecmp((*llp)->type, type) != 0) {
    llp = &((*llp)->next);
  }
  if (*llp != NULL) {
    if (can_write_lock(player, thing, *llp)) {
      ll = *llp;
      *llp = ll->next;
      free_one_lock_list(ll);
      return 1;
    } else
      return 0;
  } else
    return 1;
}

/** Free all locks in a list.
 * Used by the object destruction routines.
 * \param ll pointer to list of locks.
 */
void
free_locks(lock_list *ll)
{
  lock_list *ll2;
  while (ll) {
    ll2 = ll->next;
    free_one_lock_list(ll);
    ll = ll2;
  }
}


/** Check to see that the lock type is a valid type.
 * If it's not in our lock table, it's not valid,
 * unless it begins with 'user:' or an abbreviation thereof,
 * in which case the lock type is the part after the :.
 * As an extra check, we don't allow '|' in lock names because it
 * will confuse our db-reading routines.
 *
 * Might destructively modify name.
 *
 * \param player the enactor, for notification.
 * \param thing object on which to check the lock.
 * \param name name of lock type.
 * \return lock type, or NULL.
 */
static lock_type
check_lock_type(dbref player, dbref thing, lock_type name)
{
  lock_type ll;
  char *sp;
  /* Special-case for basic locks. */
  if (!name || !*name)
    return Basic_Lock;

  /* Normal locks. */
  ll = match_lock(name);
  if (ll != NULL)
    return ll;

  /* If the lock is set, it's allowed, whether it exists normally or not. */
  if (getlock(thing, name) != TRUE_BOOLEXP)
    return name;
  /* Check to see if it's a well-formed user-defined lock. */
  sp = strchr(name, ':');
  if (!sp) {
    notify(player, T("Unknown lock type."));
    return NULL;
  }
  *sp++ = '\0';

  if (!string_prefix("User", name)) {
    notify(player, T("Unknown lock type."));
    return NULL;
  }
  if (strchr(sp, '|')) {
    notify(player, T("The character \'|\' may not be used in lock names."));
    return NULL;
  }
  if (!good_atr_name(sp)) {
    notify(player, T("That is not a valid lock name."));
    return NULL;
  }

  return sp;
}

/** Unlock a lock (user interface).
 * \verbatim
 * This implements @unlock.
 * \endverbatim
 * \param player the enactor.
 * \param name name of object to unlock.
 * \param type type of lock to unlock.
 */
void
do_unlock(dbref player, const char *name, lock_type type)
{
  dbref thing;
  char *sp;
  lock_type real_type;

  /* check for '@unlock <object>/<atr>'  */
  sp = strchr(name, '/');
  if (sp) {
    do_atrlock(player, name, "off");
    return;
  }
  if ((thing = match_controlled(player, name)) != NOTHING) {
    if ((real_type = check_lock_type(player, thing, type)) != NULL) {
      if (getlock(thing, real_type) == TRUE_BOOLEXP) {
	if (!AreQuiet(player, thing))
	  notify_format(player, T("%s(%s) - %s (already) unlocked."),
			Name(thing), unparse_dbref(thing), real_type);
      } else if (delete_lock(player, thing, real_type)) {
	if (!AreQuiet(player, thing))
	  notify_format(player, T("%s(%s) - %s unlocked."), Name(thing),
			unparse_dbref(thing), real_type);
	if (!IsPlayer(thing))
	  ModTime(thing) = mudtime;
      } else
	notify(player, T("Permission denied."));
    }
  }
}

/** Set/lock a lock (user interface).
 * \verbatim
 * This implements @lock.
 * \endverbatim
 * \param player the enactor.
 * \param name name of object to lock.
 * \param keyname key to lock the lock to, as a string.
 * \param type type of lock to lock.
 */
void
do_lock(dbref player, const char *name, const char *keyname, lock_type type)
{
  lock_type real_type;
  dbref thing;
  boolexp key;
  char *sp;

  /* check for '@lock <object>/<atr>'  */
  sp = strchr(name, '/');
  if (sp) {
    do_atrlock(player, name, "on");
    return;
  }
  if (!keyname || !*keyname) {
    do_unlock(player, name, type);
    return;
  }
  switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
  case NOTHING:
    notify(player, T("I don't see what you want to lock!"));
    return;
  case AMBIGUOUS:
    notify(player, T("I don't know which one you want to lock!"));
    return;
  default:
    if (!controls(player, thing)) {
      notify(player, T("You can't lock that!"));
      return;
    }
    if (IsGarbage(thing)) {
      notify(player, T("Why would you want to lock garbage?"));
      return;
    }
    break;
  }

  key = parse_boolexp(player, keyname, type);

  /* do the lock */
  if (key == TRUE_BOOLEXP) {
    notify(player, T("I don't understand that key."));
  } else {
    if ((real_type = check_lock_type(player, thing, type)) != NULL) {
      /* everything ok, do it */
      if (add_lock(player, thing, real_type, key, -1)) {
	if (!AreQuiet(player, thing))
	  notify_format(player, T("%s(%s) - %s locked."), Name(thing),
			unparse_dbref(thing), real_type);
	if (!IsPlayer(thing))
	  ModTime(thing) = mudtime;
      } else {
	notify(player, T("Permission denied."));
	free_boolexp(key);
      }
    } else
      free_boolexp(key);
  }
}

/** Copy the locks from one object to another.
 * \param player the enactor.
 * \param orig the source object.
 * \param clone the destination object.
 */
void
clone_locks(dbref player, dbref orig, dbref clone)
{
  lock_list *ll;
  for (ll = Locks(orig); ll; ll = ll->next) {
    if (!(L_FLAGS(ll) & LF_NOCLONE))
      add_lock(player, clone, L_TYPE(ll), dup_bool(L_KEY(ll)), L_FLAGS(ll));
  }
}


/** Evaluate a lock.
 * Evaluate lock ltype on thing for player.
 * \param player dbref attempting to pass the lock.
 * \param thing object containing the lock.
 * \param ltype type of lock to check.
 * \retval 1 player passes the lock.
 * \retval 0 player does not pass the lock.
 */
int
eval_lock(dbref player, dbref thing, lock_type ltype)
{
  boolexp b = getlock(thing, ltype);
  log_activity(LA_LOCK, thing, unparse_boolexp(player, b, UB_DBREF));
  return eval_boolexp(player, b, thing);
}

/** Active a lock's failure attributes.
 * \param player dbref failing to pass the lock.
 * \param thing object containing the lock.
 * \param ltype type of lock failed.
 * \param def default message if there is no appropriate failure attribute.
 * \param loc location in which action is taking place.
 * \retval 1 some attribute on the object was actually evaluated.
 * \retval 0 no attributes were evaluated (only defaults used).
 */
int
fail_lock(dbref player, dbref thing, lock_type ltype, const char *def,
	  dbref loc)
{
  const LOCKMSGINFO *lm;
  char atr[BUFFER_LEN];
  char oatr[BUFFER_LEN];
  char aatr[BUFFER_LEN];
  char *bp;

  /* Find the lock's failure attribute, if it's there */
  for (lm = lock_msgs; lm->type; lm++) {
    if (!strcmp(lm->type, ltype))
      break;
  }
  if (lm->type) {
    strcpy(atr, lm->failbase);
    bp = oatr;
    safe_format(oatr, &bp, "O%s", lm->failbase);
    *bp = '\0';
    strcpy(aatr, oatr);
    aatr[0] = 'A';
  } else {
    /* Oops, it's not in the table. So we construct them on these lines:
     * <LOCKNAME>_LOCK`<type>FAILURE
     */
    bp = atr;
    safe_format(atr, &bp, "%s_LOCK`FAILURE", ltype);
    *bp = '\0';
    bp = oatr;
    safe_format(oatr, &bp, "%s_LOCK`OFAILURE", ltype);
    *bp = '\0';
    bp = aatr;
    safe_format(aatr, &bp, "%s_LOCK`AFAILURE", ltype);
    *bp = '\0';
  }
  /* Now do the work */
  upcasestr(atr);
  upcasestr(oatr);
  upcasestr(aatr);
  return did_it(player, thing, atr, def, oatr, NULL, aatr, loc);
}


/** Determine if a lock is visual.
 * \param thing object containing the lock.
 * \param ltype type of lock to check.
 * \retval (non-zero) lock is visual.
 * \retval 0 lock is not visual.
 */
int
lock_visual(dbref thing, lock_type ltype)
{
  lock_list *l = getlockstruct(thing, ltype);
  if (l)
    return l->flags & LF_VISUAL;
  else
    return 0;
}

/** Set flags on a lock (user interface).
 * \verbatim
 * This implements @lset.
 * \endverbatim
 * \param player the enactor.
 * \param what string in the form obj/lock.
 * \param flags list of flags to set.
 */
void
do_lset(dbref player, char *what, char *flags)
{
  dbref thing;
  lock_list *l;
  char *lname;
  int flag;
  int unset = 0;

  if ((lname = strchr(what, '/')) == NULL) {
    notify(player, T("No lock name given."));
    return;
  }
  *lname++ = '\0';

  if ((thing = match_controlled(player, what)) == NOTHING)
    return;

  if (*flags == '!') {
    unset = 1;
    flags++;
  }

  if ((flag = string_to_lockflag(player, flags)) < 0) {
    notify(player, T("Unrecognized lock flag."));
    return;
  }

  l = getlockstruct_noparent(thing, lname);
  if (!l || !Can_Read_Lock(player, thing, L_TYPE(l))) {
    notify(player, T("No such lock."));
    return;
  }

  if (!can_write_lock(player, thing, l)) {
    notify(player, T("Permission denied."));
    return;
  }

  if (unset)
    L_FLAGS(l) &= ~flag;
  else
    L_FLAGS(l) |= flag;

  if (!Quiet(player) && !(Quiet(thing) && (Owner(thing) == player)))
    notify_format(player, "%s/%s - %s.", Name(thing), L_TYPE(l),
		  unset ? T("lock flags unset") : T("lock flags set"));
  if (!IsPlayer(thing))
    ModTime(thing) = mudtime;
}

/** Check to see if an object has a good zone lock set.
 * If it doesn't have a lock at all, set one of '=Zone'.
 * \param player The object responsible for having the lock checked.
 * \param zone the object whose lock needs to be checked.
 * \param noisy if 1, notify player of automatic locking
 */
void
check_zone_lock(dbref player, dbref zone, int noisy)
{
  boolexp key = getlock(zone, Zone_Lock);
  if (key == TRUE_BOOLEXP) {
    add_lock(GOD, zone, Zone_Lock, parse_boolexp(zone, "=me", Zone_Lock), -1);
    if (noisy)
      notify_format(player,
		    T
		    ("Unlocked zone %s - automatically zone-locking to itself"),
		    unparse_object(player, zone));
  } else if (eval_lock(Location(player), zone, Zone_Lock)) {
    /* Does #0 and #2 pass it? If so, probably trivial elock */
    if (eval_lock(PLAYER_START, zone, Zone_Lock) &&
	eval_lock(MASTER_ROOM, zone, Zone_Lock)) {
      if (noisy)
	notify_format(player,
		      T("Zone %s really should have a more secure zone-lock."),
		      unparse_object(player, zone));
    } else			/* Probably inexact zone lock */
      notify_format(player,
		    T
		    ("Warning: Zone %s may have loose zone lock. Lock zones to =player, not player"),
		    unparse_object(player, zone));
  }
}