/**
* \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));
}
}