]> git.theari.com Git - cobramush.git/commitdiff
Initial import from 0.72 release
authorAri Johnson <iamtheari@gmail.com>
Tue, 5 Sep 2006 00:30:46 +0000 (00:30 +0000)
committerAri Johnson <iamtheari@gmail.com>
Tue, 5 Sep 2006 00:30:46 +0000 (00:30 +0000)
270 files changed:
BUGS [new file with mode: 0644]
CHANGES.174 [new file with mode: 0644]
CHANGES.175 [new file with mode: 0644]
CHANGES.176 [new file with mode: 0644]
CHANGES.177 [new file with mode: 0644]
CHANGES.OLD [new file with mode: 0644]
COPYRITE [new file with mode: 0644]
Configure [new file with mode: 0644]
FAQ [new file with mode: 0644]
I18N [new file with mode: 0644]
INSTALL [new file with mode: 0644]
MANIFEST [new file with mode: 0644]
Makefile.SH [new file with mode: 0644]
Patchlevel [new file with mode: 0644]
README [new file with mode: 0644]
README.SQL [new file with mode: 0644]
README.SSL [new file with mode: 0644]
UPGRADING [new file with mode: 0644]
config_h.SH [new file with mode: 0644]
confmagic.h [new file with mode: 0644]
game/.gitify-empty [new file with mode: 0644]
game/README [new file with mode: 0644]
game/access.README [new file with mode: 0644]
game/aliascnf.dst [new file with mode: 0644]
game/data/.gitify-empty [new file with mode: 0644]
game/data/README [new file with mode: 0644]
game/getdate.README [new file with mode: 0644]
game/getdate.template [new file with mode: 0644]
game/log/.gitify-empty [new file with mode: 0644]
game/log/README [new file with mode: 0644]
game/mushcnf.dst [new file with mode: 0644]
game/namescnf.dst [new file with mode: 0644]
game/restart.dst [new file with mode: 0644]
game/restrictcnf.dst [new file with mode: 0644]
game/save/.gitify-empty [new file with mode: 0644]
game/save/README [new file with mode: 0644]
game/txt/.gitify-empty [new file with mode: 0644]
game/txt/Makefile [new file with mode: 0644]
game/txt/README [new file with mode: 0644]
game/txt/changes.sh [new file with mode: 0644]
game/txt/changes.txt [new file with mode: 0644]
game/txt/compose.sh.SH [new file with mode: 0644]
game/txt/connect.txt [new file with mode: 0644]
game/txt/down.txt [new file with mode: 0644]
game/txt/evt/.gitify-empty [new file with mode: 0644]
game/txt/evt/index.evt [new file with mode: 0644]
game/txt/evt/pennmush.evt [new file with mode: 0644]
game/txt/full.txt [new file with mode: 0644]
game/txt/guest.txt [new file with mode: 0644]
game/txt/hlp/.gitify-empty [new file with mode: 0644]
game/txt/hlp/cobra_attr.hlp [new file with mode: 0644]
game/txt/hlp/cobra_chat.hlp [new file with mode: 0644]
game/txt/hlp/cobra_cmd.hlp [new file with mode: 0644]
game/txt/hlp/cobra_code.hlp [new file with mode: 0644]
game/txt/hlp/cobra_conf.hlp [new file with mode: 0644]
game/txt/hlp/cobra_division.hlp [new file with mode: 0644]
game/txt/hlp/cobra_flag.hlp [new file with mode: 0644]
game/txt/hlp/cobra_func.hlp [new file with mode: 0644]
game/txt/hlp/cobra_mail.hlp [new file with mode: 0644]
game/txt/hlp/cobra_pueb.hlp [new file with mode: 0644]
game/txt/hlp/cobra_vts.hlp [new file with mode: 0644]
game/txt/hlp/cobramush.hlp [new file with mode: 0644]
game/txt/hlp/cobratop.hlp [new file with mode: 0644]
game/txt/hlp/index.hlp [new file with mode: 0644]
game/txt/hlp/pennv174.hlp [new file with mode: 0644]
game/txt/hlp/pennv175.hlp [new file with mode: 0644]
game/txt/hlp/pennv176.hlp [new file with mode: 0644]
game/txt/hlp/pennv177.hlp [new file with mode: 0644]
game/txt/hlp/pennvOLD.hlp [new file with mode: 0644]
game/txt/index-files.pl [new file with mode: 0644]
game/txt/motd.txt [new file with mode: 0644]
game/txt/newuser.txt [new file with mode: 0644]
game/txt/nws/.gitify-empty [new file with mode: 0644]
game/txt/nws/base.nws [new file with mode: 0644]
game/txt/quit.txt [new file with mode: 0644]
game/txt/register.txt [new file with mode: 0644]
hdrs/.gitify-empty [new file with mode: 0644]
hdrs/access.h [new file with mode: 0644]
hdrs/ansi.h [new file with mode: 0644]
hdrs/atr_tab.h [new file with mode: 0644]
hdrs/attrib.h [new file with mode: 0644]
hdrs/boolexp.h [new file with mode: 0644]
hdrs/bufferq.h [new file with mode: 0644]
hdrs/case.h [new file with mode: 0644]
hdrs/chunk.h [new file with mode: 0644]
hdrs/command.h [new file with mode: 0644]
hdrs/compile.h [new file with mode: 0644]
hdrs/conf.h [new file with mode: 0644]
hdrs/copyrite.h [new file with mode: 0644]
hdrs/cron.h [new file with mode: 0644]
hdrs/csrimalloc.h [new file with mode: 0644]
hdrs/dbdefs.h [new file with mode: 0644]
hdrs/dbio.h [new file with mode: 0644]
hdrs/division.h [new file with mode: 0644]
hdrs/extchat.h [new file with mode: 0644]
hdrs/externs.h [new file with mode: 0644]
hdrs/extmail.h [new file with mode: 0644]
hdrs/flags.h [new file with mode: 0644]
hdrs/function.h [new file with mode: 0644]
hdrs/game.h [new file with mode: 0644]
hdrs/getpgsiz.h [new file with mode: 0644]
hdrs/help.h [new file with mode: 0644]
hdrs/htab.h [new file with mode: 0644]
hdrs/ident.h [new file with mode: 0644]
hdrs/intrface.h [new file with mode: 0644]
hdrs/lock.h [new file with mode: 0644]
hdrs/log.h [new file with mode: 0644]
hdrs/malias.h [new file with mode: 0644]
hdrs/match.h [new file with mode: 0644]
hdrs/mushdb.h [new file with mode: 0644]
hdrs/mushtype.h [new file with mode: 0644]
hdrs/mymalloc.h [new file with mode: 0644]
hdrs/mysocket.h [new file with mode: 0644]
hdrs/myssl.h [new file with mode: 0644]
hdrs/oldflags.h [new file with mode: 0644]
hdrs/parse.h [new file with mode: 0644]
hdrs/pcre.h [new file with mode: 0644]
hdrs/privtab.h [new file with mode: 0644]
hdrs/ptab.h [new file with mode: 0644]
hdrs/pueblo.h [new file with mode: 0644]
hdrs/shs.h [new file with mode: 0644]
hdrs/strtree.h [new file with mode: 0644]
hdrs/version.h [new file with mode: 0644]
hints/.gitify-empty [new file with mode: 0644]
hints/a-u-x.sh [new file with mode: 0644]
hints/aix.sh [new file with mode: 0644]
hints/cygwin.sh [new file with mode: 0644]
hints/darwin.sh [new file with mode: 0644]
hints/dec_osf.sh [new file with mode: 0644]
hints/freebsd.sh [new file with mode: 0644]
hints/freebsd_5.sh [new file with mode: 0644]
hints/hpux-gcc.sh [new file with mode: 0644]
hints/hpux.sh [new file with mode: 0644]
hints/irix.sh [new file with mode: 0644]
hints/irix_6.sh [new file with mode: 0644]
hints/linux_2.sh [new file with mode: 0644]
hints/mingw32.sh [new file with mode: 0644]
hints/next.sh [new file with mode: 0644]
hints/openbsd.sh [new file with mode: 0644]
hints/os2.sh [new file with mode: 0644]
hints/solaris_2.sh [new file with mode: 0644]
hints/sunos_4.sh [new file with mode: 0644]
hints/ultrix.sh [new file with mode: 0644]
hints/win32-gcc.sh [new file with mode: 0644]
hints/win32.sh [new file with mode: 0644]
options.h.dist [new file with mode: 0644]
os2/.gitify-empty [new file with mode: 0644]
os2/GCCOPT.CMD [new file with mode: 0644]
os2/Makefile [new file with mode: 0644]
os2/Penn-OS2.htm [new file with mode: 0644]
os2/config.h [new file with mode: 0644]
po/.gitify-empty [new file with mode: 0644]
po/Makefile [new file with mode: 0644]
src/.gitify-empty [new file with mode: 0644]
src/.indent.pro [new file with mode: 0644]
src/Makefile.SH [new file with mode: 0644]
src/SWITCHES [new file with mode: 0644]
src/access.c [new file with mode: 0644]
src/announce.c [new file with mode: 0644]
src/atr_tab.c [new file with mode: 0644]
src/attrib.c [new file with mode: 0644]
src/boolexp.c [new file with mode: 0644]
src/bsd.c [new file with mode: 0644]
src/bufferq.c [new file with mode: 0644]
src/chunk.c [new file with mode: 0644]
src/cmdlocal.dst [new file with mode: 0644]
src/cmds.c [new file with mode: 0644]
src/command.c [new file with mode: 0644]
src/comp_h.c [new file with mode: 0644]
src/comp_w.c [new file with mode: 0644]
src/comp_w8.c [new file with mode: 0644]
src/compress.c [new file with mode: 0644]
src/conf.c [new file with mode: 0644]
src/console.c [new file with mode: 0644]
src/cque.c [new file with mode: 0644]
src/create.c [new file with mode: 0644]
src/cron.c [new file with mode: 0644]
src/csrimalloc.c [new file with mode: 0644]
src/db.c [new file with mode: 0644]
src/destroy.c [new file with mode: 0644]
src/division.c [new file with mode: 0644]
src/extchat.c [new file with mode: 0644]
src/extmail.c [new file with mode: 0644]
src/filecopy.c [new file with mode: 0644]
src/flaglocal.dst [new file with mode: 0644]
src/flags.c [new file with mode: 0644]
src/funcrypt.c [new file with mode: 0644]
src/function.c [new file with mode: 0644]
src/fundb.c [new file with mode: 0644]
src/fundiv.c [new file with mode: 0644]
src/funlist.c [new file with mode: 0644]
src/funlocal.dst [new file with mode: 0644]
src/funmath.c [new file with mode: 0644]
src/funmisc.c [new file with mode: 0644]
src/funstr.c [new file with mode: 0644]
src/funtime.c [new file with mode: 0644]
src/funufun.c [new file with mode: 0644]
src/game.c [new file with mode: 0644]
src/gmalloc.c [new file with mode: 0644]
src/help.c [new file with mode: 0644]
src/htab.c [new file with mode: 0644]
src/ident.c [new file with mode: 0644]
src/info_slave.c [new file with mode: 0644]
src/local.dst [new file with mode: 0644]
src/lock.c [new file with mode: 0644]
src/log.c [new file with mode: 0644]
src/look.c [new file with mode: 0644]
src/malias.c [new file with mode: 0644]
src/match.c [new file with mode: 0644]
src/memcheck.c [new file with mode: 0644]
src/move.c [new file with mode: 0644]
src/mycrypt.c [new file with mode: 0644]
src/mymalloc.c [new file with mode: 0644]
src/mysocket.c [new file with mode: 0644]
src/myssl.c [new file with mode: 0644]
src/notify.c [new file with mode: 0644]
src/parse.c [new file with mode: 0644]
src/pcre.c [new file with mode: 0644]
src/player.c [new file with mode: 0644]
src/plyrlist.c [new file with mode: 0644]
src/portmsg.c [new file with mode: 0644]
src/predicat.c [new file with mode: 0644]
src/privtab.c [new file with mode: 0644]
src/prog.c [new file with mode: 0644]
src/ptab.c [new file with mode: 0644]
src/rob.c [new file with mode: 0644]
src/rplog.c [new file with mode: 0644]
src/services.c [new file with mode: 0644]
src/set.c [new file with mode: 0644]
src/shs.c [new file with mode: 0644]
src/sig.c [new file with mode: 0644]
src/speech.c [new file with mode: 0644]
src/sql.c [new file with mode: 0644]
src/strdup.c [new file with mode: 0644]
src/strtree.c [new file with mode: 0644]
src/strutil.c [new file with mode: 0644]
src/switchinc.c [new file with mode: 0644]
src/tables.c [new file with mode: 0644]
src/timer.c [new file with mode: 0644]
src/unparse.c [new file with mode: 0644]
src/utils.c [new file with mode: 0644]
src/version.c [new file with mode: 0644]
src/warnings.c [new file with mode: 0644]
src/wild.c [new file with mode: 0644]
src/wiz.c [new file with mode: 0644]
utils/.gitify-empty [new file with mode: 0644]
utils/clwrapper.sh [new file with mode: 0644]
utils/customize.pl [new file with mode: 0644]
utils/fixdepend.pl [new file with mode: 0644]
utils/gentables.c [new file with mode: 0644]
utils/ln-dir.sh [new file with mode: 0644]
utils/make_access_cnf.sh [new file with mode: 0644]
utils/mkcmds.sh.SH [new file with mode: 0644]
utils/mkvershlp.pl [new file with mode: 0644]
utils/penn-install [new file with mode: 0644]
utils/update-cnf.pl [new file with mode: 0644]
utils/update.pl [new file with mode: 0644]
win32/.gitify-empty [new file with mode: 0644]
win32/README.mingw [new file with mode: 0644]
win32/README.txt [new file with mode: 0644]
win32/cmds.h [new file with mode: 0644]
win32/cobramush.sln [new file with mode: 0644]
win32/config.h [new file with mode: 0644]
win32/confmagic.h [new file with mode: 0644]
win32/funs.h [new file with mode: 0644]
win32/options.h [new file with mode: 0644]
win32/patches.h [new file with mode: 0644]
win32/pennmush.dsp [new file with mode: 0644]
win32/pennmush.dsw [new file with mode: 0644]
win32/switches.h [new file with mode: 0644]

diff --git a/BUGS b/BUGS
new file mode 100644 (file)
index 0000000..9814dae
--- /dev/null
+++ b/BUGS
@@ -0,0 +1,9 @@
+Bugs that aren't our fault, but might bite people with old compilers:
+
+* Ralph Melton reports that compiling with gcc 2.5.8 under SunOS 4.1.1
+  using -O optimization and forking dumps causes the dump process to
+  crash. Removing -O fixes the problem; so might using a more recent gcc.
+* Javelin has confirmed that compiling with gcc 2.7.2.3 under Linux 2.2.16
+  using -O optimization causes ansi(rh,a) to crash. Removing -O fixes
+  the problem; so might using a more recent gcc
+
diff --git a/CHANGES.174 b/CHANGES.174
new file mode 100644 (file)
index 0000000..fc9f59f
--- /dev/null
@@ -0,0 +1,715 @@
+
+This is the most current changes file for PennMUSH. Please look it
+over; each version contains new things which might significantly affect
+the function of your server.  Changes are reported in reverse
+chronological order (most recent first)
+
+[TN] is Thorvald Natvig, a PennMUSH developer (aka Trivian)
+[TAP] is T. Alexander Popiel, a PennMUSH developer (aka Talek)
+[SW] is Shawn Wagner, a PennMUSH developer (aka Raevnos)
+[LdW] is Luuk de Waard, a former PennMUSH developer (aka Halatir)
+[RLM] is Ralph Melton, a former PennMUSH developer
+[NJG] is Nick Gammon, the primary Win32 porter
+[EEH] is Ervin Hearn III, a Win32 porter (aka Noltar)
+[DW] is Dan Williams, the MacOS porter
+[2.2] refers to code which originated with the TinyMUSH 2.2 developers
+[3] refers to code by (or inspired by) TinyMUSH 3.0
+[Rhost] refers to code by (or inspired by) RhostMUSH
+
+==========================================================================
+
+Version 1.7.4 patchlevel 20                   September 19, 2002
+
+Fixes:
+  * Help semaphores4 typo fixed by Mike Griffiths.
+  * Help cleanup. [TAP]
+  * See_All players now see hidden connected mail senders. Suggested
+    by Philip Mak.
+  * spellnum could introduce spurious spaces. Reported by Intrevis@M*U*S*H.
+  * table() sometimes produced spurious blank rows. Reported by
+    Nymeria@M*U*S*H. This is the first attempt at a fix.
+  * switch() help improved. [SW]
+  * enter <exit> no longer returns Permission denied, but 
+    "I can't see that here", as you can only enter things. 
+    Suggested by Philip Mak.
+  * A one-time, one-pointer memory leak in plyrlist.c fixed
+    by Vadiv@M*U*S*H.
+  * Unbalanced or empty double-quotes gave weird results in page 
+    and whisper. Reported by Vadiv@M*U*S*H. [SW]
+  * @chan/decomp no longer shows names of channels that the enactor
+    can't see. Reported by Nat@SW:ANT.
+  * The lock() and elock() functions now operate properly with
+    user:whatever locks. Reported by Mike Griffiths.
+  * pmatch() will locate hidden players if they're visible to you
+    because they're nearby. Suggested by Julian@M*U*S*H.
+  * regedit and other functions that used $-substitutions for subpatterns
+    could produce weird results in some cases. Reported by Bellemore@M*U*S*H
+
+
+Version 1.7.4 patchlevel 19                   June 14, 2002
+
+Minor changes:
+  * Wizards can now unset the WIZARD flag on any thing, whether they
+    own it or not. Suggested by Cerekk@bDv.
+  * Circular zoning is no longer allowed. Fixes part of a bug reported
+    by Philip Mak. [SW]
+Fixes:
+  * Win32 portability fixes. [EEH]
+  * grep for 'grep' rather than 'egrep' in restart because in grep 2.5.1,
+    egrep is a shell script that exec's grep instead of a link.
+    Fix by Vadiv@M*U*S*H.
+  * The messages for a possessive get used the object's new location
+    instead of its old one. Fixed by Apollo@Restoration.
+  * Attempts by unlinked exits to @link an exit could crash. 
+  * %1 in @areceive was being set to the recipient rather than the giver.
+    Fixed. Report by jubjup@trekmush.org
+  * @uptime fix for Linux 2.4 kernel. [SW]
+  * The @@() function no longer complains about commas. Report by
+    Trispis@M*U*S*H. [TAP]
+  * @search flags=<flaglist> is now smarter about toggles with the same
+    letter and different types. Report by Philip Mak.
+  * English-style matching now ignores the type of object being matched.
+    This fixes a bug with, e.g., @link my 1st object=me reported by
+    Oriens@Alexandria.
+  * bound() now listed in the math functions list. Report by Dandy@M*U*S*H.
+  * Help fix for member() by Cerekk@bDV TrekMUSH
+  * The server can now transparently read mush.cnf (and included) files
+    that have Dos- or Mac-style line-endings. Suggested by Paleran.
+  * Crash bug in @search fixed. Reported by Philip Mak.
+
+Version 1.7.4 patchlevel 18                   May 6, 2002
+
+Minor Changes:
+  * The Mersenne Twister RNG has been backported from 1.7.5 into
+    this patchlevel, as it is not susceptible to some bugs that could
+    cause the RNG to hang the server. Report by Bellemore@M*U*S*H.
+  * Improved detection of info_slave errors. Based on patch by CU5.
+  * Rooms and exits can now be @forced. Suggested by Philip Mak.
+  * Deleting files from the hlp/nws/etc. directories is now sufficient
+    to cause game/txt/Makefile to rebuild the txt files. Patch by
+    Philip Mak.
+  * A see_all player may now use @chan/decompile on any channel.
+    Previously, the ROYALTY flag was required. Suggested by Oriens@Alexandria.
+Fixes:
+  * The QUEUE and semaphore attributes aren't listed in @decompile
+    anymore. Suggested by Oriens@Alexandria. [SW]
+  * Several compiler warnings fixed. [SW]
+  * The LASTSITE and LASTIP attributes are now set locked and wizard by default,
+    to go along with the other connection-tracking attributes. [SW]
+  * Help on-vacation updated. Report by Nymeria@M*U*S*H.
+  * Help for following() function added. Report by Ashen-Shugar@Rhost.
+  * The last line of the access.cnf file sometimes wouldn't get read 
+    properly. [SW]
+  * lnum improperly cached its return values without consideration for
+    changes in separator, so subsequent lnums with different separators
+    broke. Reported by Rhysem and Ambrosia@M*U*S*H. [TAP]
+  * Failing to speak on a channel you can't see no longer reveals the
+    full channel name. Reported by Vadiv@M*U*S*H.
+  * Passing a negative argument to the space() function now returns
+    an error string instead of 8192 blanks. Patch by Myrddin.
+  * Improved messages for following/unfollowing an ambiguous object.
+    Reported by Philip Mak.
+
+
+Version 1.7.4 patchlevel 17                   April 14, 2002
+
+Minor Changes:
+  * The on-vacation flag, if in use, is no longer automatically cleared
+    on connect. Instead, the player is warned on connect (and at each
+    dump) that their flag is set. Suggested by Nymeria@M*U*S*H.
+Fixes:
+  * Improved help for edit(). Suggested by Trispis@M*U*S*H [SW]
+  * List functions with null elements and a null output seperator could
+    trip end-of-buffer checks and stop in the middle of a list. [SW]
+  * valid() was inconsistent in how it handled attribute names with lower-case
+    letters compared to what everything else does. Reported by Philip Mak. [SW]
+  * @open could cause crashes in some unusual contexts. Reported
+    by Dandy@M*U*S*H.
+  * Improved sort()'s autodetection of list types. [SW]
+  * Fixed a problem with sorting dbrefs larger than the highest one in the 
+    db. [SW]
+  * Mac portability fixes. [DW]
+  * Help for @open clarified. Suggested by fil@M*U*S*H.
+  * Help for kill clarified. Suggested by Philip Mak.
+  * Channel titles can no longer contain newlines or beeps. 
+    Report by Nome@M*U*S*H.
+  * soundex behaved badly with extended characters. [SW]
+  * inc() and dec() now behave like the help says, regardless of whether
+    tiny_math is set or not. Their behavior on null strings and strings
+    that do not end in a digit depend on the null_eq_zero setting.
+    Reported by Wayne@PDX.
+  * The panic db file descriptor was never closed after reading a
+    panic dump. [SW]
+  * DOES removed from help attribute list. Suggested Philip Mak.
+  * Under no circumstances should connection to a null-named player
+    be allowed. Suggested by Wayne@PDX.
+  * 'with' no longer allows use of $commands on remote objects you
+    don't control. Report by Nammyung@M*U*S*H.
+
+Version 1.7.4 patchlevel 16                   March 11, 2002
+
+Minor changes:
+  * After using 'register' at the connect screen, the player is
+    no longer disconnected. Suggested by Philip Mak.
+  * 'help mail functions'. Suggested by Trispis@M*U*S*H.
+  * Messages associated with drop, give, and get are now more
+    verbose and provide more information about who did what to whom.
+    Suggested by Philip Mak.
+  * Attrib locks did case-sensitive comparison of values, which is not
+    what you probably want. No longer. Reported by Philip Mak. [SW]
+  * QUEUE and sempahore attributes are now removed (not just emptied)
+    when drained or notified to 0. [TAP]
+Fixes:
+  * Improvements in handling ansi in string functions. [SW]
+  * @clone/preserve didn't preserve when cloning exits. Reported by
+    Bellemore@M*U*S*H. [SW]
+  * A significant bug in the manual notification of timed semaphores has 
+    been corrected. [SW]
+  * Revian@M*U*S*H pointed out that user-defined commands starting with
+    "@" that match the names of standard attributes don't behave as
+    you might expect. This is now documented in help user-defined commands.
+  * Security checks for attribute setting used the owner of the setting 
+    object, not the setting object itself. Report by Howie@New Frontier.
+  * help set() improved based on report by Tareldin@M*U*S*H.
+  * folderstats() did not behave as promised in the help. Now it
+    does. Report by Trispis@M*U*S*H.
+  * Typo in src/log.c fixed by Nathan Schuette.
+  * Improved help for DEBUG. [SW]
+  * Aliased 'help ^' to 'help listening2'. Suggested by Philip Mak.
+  * MacOS portability fixes. [DW]
+  * The sigusr1 handler would reinstall itself as the sighup handler
+    on systems that don't provide persistent signals. 
+    Fixed by Bellemore@M*U*S*H.
+  * &a=b me now properly clears the attribute A=B. Reported by 
+    Trispis@M*U*S*H. In addition, now @tel #1234= produces an error,
+    as it should. [SW]
+  * mail functions can now be called by an object on anything it controls
+    (typically, its owner). Suggested by Trispis@M*U*S*H.
+  * The givee is now correctly passed in %1 to @give/@ogive/@agive,
+    and documented. Reported by Philip Mak.
+  * Added hints for Irix 6.x by Vexon@M*U*S*H.
+  * i18n fix for function invocation limit message.
+  * Clarification in help @alias by Philip Mak.
+  * @set obj=!puppet repeatedly no longer repeats the "not listening"
+    message. Reported by Philip Mak.
+
+
+Version 1.7.4 patchlevel 15                   February 8, 2002
+
+Minor Changes:
+  * @dolist and iter(), where multiple tokens are replaced (## and #@),
+    now do both replacements at once. This is more efficient in several
+    ways and fixes a problem where if the second token gets into the
+    string from a replacement of the first, it gets replaced. (iter(a#@,
+    ##) should return a#@, not a1). [SW]
+  * setunion no longer eats empty list elements. [SW]
+  * The help text for items() is now more descriptive of how it works
+    and differs from words(). Suggested by Zen@SW1.
+  * When you attempt to @chzone an object to a ZMO without a zone-lock,
+    a default zone-lock of "owner of the ZMO" is now set, and the
+    attempt succeeds. Suggested by Linda Antonsson.
+  * In the French message translation files, the word 'drapeau' and
+    'flag' were used interchangeably. I've standardized on 'flag'.
+    Report by Vexon@M*U*S*H.
+Fixes:
+  * Message typo fixed by Bellemore@M*U*S*H.
+  * No more ansified names in logged shutdown messages. Report by
+    Bellemore@M*U*S*H.
+  * Messages when destroying players now take into account the 
+    destroy_possessions and really_safe settings. Suggested by Wayne@PDX.
+  * The parser no longer strips any first layer of braces in, e.g.
+    @switch action clauses, but only when the first character in the
+    clause is a brace. This prevents @sw 1=1, @wait me={@emit 1; @emit 2}
+    from being misparsed and running @emit 2 immediately. Reported by
+    Azratax@Azmush. [TAP]
+
+Version 1.7.4 patchlevel 14                   January 4, 2002
+
+Minor Changes:
+  * The global function invocation limit is now 5 times the per-evaluation
+    function invocation limit, to provide some flexibility in cases
+    where you run a small number of functions that cause a larger
+    number of other functions to be evaluated (e.g., using tel()
+    to move players into rooms with function-laden descriptions). [TAP]
+Fixes:
+  * Mortals are now restricted in which html tags they can generate
+    when pueblo support is enabled. Suggested by BladedThoth.
+  * @sitelock/name !<name> was improperly case-sensitive in its
+    matching. Reported by Linda Antonsson.
+  * Better invocation count checking and aborting on reaching limit.
+    Reported by Ashen-Shugar. [TAP]
+  * Beep characters are ignored when matching object listen patterns.
+    Suggested by Wayne@PDX.
+  * The end-of-dump marker is checked when reading the chat database.
+    Suggested by Bellemore@M*U*S*H. [SW]
+  * @lock obj=attrib:value locks were broken. Reported by Linda
+    Antonsson.
+  * Minor help fixes.
+
+Version 1.7.4 patchlevel 13                   November 30, 2001
+
+Minor changes:
+  * options.h.dist mentions Darwin as well as MacOS X. [DW]
+  * PCRE updated to 3.7. [SW]
+  * When CHAN_NAME_LEN is increased beyond 30, the @chan/list header
+    line is misaligned, and other strange things can happen to
+    @chan/list. Reported by Bladed Thoth
+Fixes:
+  * Crash bug in chat channels reported by BladedThoth.
+
+Version 1.7.4 patchlevel 12                   November 9, 2001
+
+Minor changes:
+  * @dol/delim is now @dol/delimit, for Mux compatibility. [SW]
+  * /preserve switch for @chownall works like @chown's /preserve switch.
+    This changes the default behavior of @chownall, which used to
+    preserve everything, to work like @chown and strip privileged bits.
+    Suggested by Taladan@M*U*S*H.
+Fixes:
+  * Warnings in index-files.pl are no longer shown. Report by Noltar@Korongil
+  * Additional support for ansi in channel names. Ansi is now stripped
+    when sorting channels and when matching names for @chan/decomp and
+    @chan/what.  Reported by Oriens@Alexandria.
+  * Help @decompile clarifies the /flags switch. Suggested by Oriens@Alexandria
+  * Source is indented before diffs are produced.
+  * Typo in help zmr correct by Oriens@Alexandria.
+  * Players disconnecting without QUIT continued to use CPU until fully
+    disconnected. Fixed. Report by Protagonist@M*U*S*H. [SW]
+
+
+Version 1.7.4 patchlevel 11                   October 15, 2001
+
+Minor Changes:
+  * In places like switch() that can match <number, the numbers
+    are now treated as floating point, so they need not be only integers.
+    However, they must be pure numbers; "<3km" will not work.
+  * Tests for channel name matches now disregard ansi. Suggested by Wayne@PDX.
+Fixes:
+  * MacOS linting. [DW]
+  * next() could reveal unfindable players. Reported by Jeffrey@TheHotel. [TAP]
+  * making diffs or a dist now insures that switches.h, etc. are rebuilt
+    for the Mac/Win32 crowd. Reported by many people.
+  * Some warnings discovered with compiling with gcc 3.0.1 fixed. [SW]
+  * Potential crash-or-worse bugs that could be caused by malformed
+    save messages fixed. [SW]
+  * @mail to players with names starting with numbers works correctly now.
+    Report from Mike Wesson. [SW]
+  * Fewer logged warnings from failed convtime()s. [SW]
+  * Help for page now mentions /blind. Reported by Oriens@Alexandria.
+  * Attempting to set an invalid priv on a channel now produces a
+    better message. Reported by Oriens@Alexandria.
+  * Improved message when a Wizard overrides a chan join lock by Wayne@PDX.
+  * Another way to end up inside yourself fixed. Report by Ashen-Shugar. [TAP]
+  * Help default/edefault syntax clarified by Delina@ST:VAAE
+  * Help math functions clarifies 'number'. Suggested by Delina@ST:VAAE
+  * Information on the patches.h header added to the FAQ file. Suggested 
+    by Kahmy. [SW]
+  * Potential crash in @set fixed. Report by Michael Loftis [SW]
+  * The Unfindable flag is checked on all levels of containers, not just
+    the immediate location. Suggested by Oriens. [SW]
+  * NT_TCP fix by Bellemore.
+  * secure() now escapes ^, as the help says it does. Report by Gabriel Matlin.
+  * link_anywhere now lets you actually @link anywhere, instead of just
+    letting variable exits link anywhere. Report by Viila@M*U*S*H.
+  * help home now returns help homes, not help home(). Suggested by 
+    Gary Williams
+
+Version 1.7.4 patchlevel 10                   September 7, 2001
+
+Fixes:
+  * @clone changes in p9 introduced a crash bug. Fixed.
+  * Typo in mushcnf.dst fixed by Noltar@Korongil.
+
+Version 1.7.4 patchlevel 9                    September 4, 2001
+
+Minor changes:
+  * @clone can optionally specify a new name for the clone.
+    Patch by Bellemore@M*U*S*H, inspired by mux.
+  * die() can take a third argument which, if true, will cause it to
+    return the list of individual die rolls rather than the sum.
+  * NT_TCP option moved to options.h.dist, and @config/list compile now
+    reports whether it's on or not. Suggested by Glonk@GlonkMUSH
+  * QUIET flag affects the "Teleported." message as well.
+    Suggested by Glonk@GlonkMUSH.
+  * pos() and strmatch() strip ansi and html markup before matching. [SW]
+  * Slight optimizations for many of the functions that strip markup. [SW] 
+  * chat_strip_quote setting now applies to @*wall and say. Suggested by
+    Glonk@GlonkMUSH. [SW]
+  * @malias/who is now the same as @malias/members. Suggested by
+    Oriens@Alexandria.
+  * Small code change in do_chat_by_name so that find_channel_partial_on
+    can behave as documented. Suggested by Michael Loftis
+Fixes:
+  * p8 broke regeditall when the replacer was null. Fixed.
+    Reported by Nymeria@M*U*S*H.
+  * Some unused variables removed, and pcre.h included in parse.c.
+    Reported by Sidney@M*U*S*H.
+  * index-files.pl produced an uninitialized value warning if a help file
+    had only a single entry (or admin entry). Warning removed.
+    Reported by Nymeria@M*U*S*H.
+  * Fixed to help lstats() to mention stats() as alias. Reported by
+    Glonk@GlonkMUSH.
+  * Help edit() fix by Sash@SW:Uprising.
+  * Improved failure message for @password. Suggested by Mike Wesson. [SW]
+  * alphamin()/alphamax() were stripping markup from what they returned. [SW]
+  * PARANOID flag is now only visible to owners. Reported by 
+    Bellemore@M*U*S*H.
+  * Improved error message when trying to rejoin a channel. [SW]
+  * In Win32 NT_TCP mode, ident lookups are now done and the LASTIP
+    attribute doesn't get corrupted. Patch by Bellemore@M*U*S*H.
+  * @chan/describe now works along with @chan/desc. Suggested by 
+    Trispis@M*U*S*H
+  * 'teach'ing a motion to a follower didn't work right. Reported by
+    Cheetah and Viila@M*U*S*H.
+  * Security bug in follow fixed. Reported by Walker@M*U*S*H.
+  * The &ecirc; and &euml; entities were not correctly returned in
+    Pueblo mode. Fixed by [NG].
+  * Help for trig functions improved. [SW]
+  * Pueblo references no longer give Chaco's (defunct) website.
+    By Noltar@Korongil.
+
+Version 1.7.4 patchlevel 8                    July 22, 2001
+
+Minor changes:
+  * restart is a bit more precise in the "Mush already active" message.
+    Suggested by Lucas Layman.
+  * When a player's creation is refused because creation/registration
+    is globally turned off, show them register.txt instead of down.txt.
+    Patch by Bellemore.
+  * The NOSPOOF flag is now visible only to the player themself. [SW]
+  * regedit can now use backreferences in the replacer. [SW]
+Fixes:
+  * ident lookups were broken on win32. Reported by Bellemore. [SW] 
+  * ident query timeouts could get doubled mistakenly. [SW]
+  * Typo in mushcnf.dst fixed by Noltar@Korongil.
+  * Fix to help puppets2 by TurtleKnee@M*U*S*H.
+  * Help pcreate() added. Report by Eratl@M*U*S*H.
+  * @pcreate messages capitalized by Oriens@Alexandria.
+  * create() used 10 as the default cost, instead of the configured
+    default. Report by 8bitMUSH.
+  * Inactivity timeouts longer than 1 day didn't work. Fixed and
+    efficiency of the check improved. Reported by Bellemore@M*U*S*H.
+  * Null @aliases are no longer allowed. [SW]
+  * Cleanup to ident for situations when the remote host isn't running
+    an ident server. [SW]
+
+Version 1.7.4 patchlevel 7                    July 02, 2001
+
+Major changes:
+  * %r can now evaluate to one character or two, based on a new config 
+    option, newline_one_char, which defaults to being yes. This allows
+    %r to be used as a list delimiter. However, this may
+    break softcode which expects strlen(%r) to be 2, but it's probably
+    smarter to fix the softcode than turn off this option. [sw]
+  * If a command and a standard attribute have the same name, the
+    command takes precedence. So if you have an @attribute named
+    "PEMIT", @pemit me=foo will do the command, not set the attribute. [SW]
+Minor changes:
+  * When someone attempts to create too many attributes on an object,
+    the log indicates who and which object. Suggested by Frob@Battlestar
+    Galactica:TSC.
+  * Buncha tprintfs replaced with notify_formats. [SW] 
+  * New local_connect() and local_disconnect() hooks in local.dst.
+    Suggested by Eratl@M*U*S*H.
+  * lookup_player now deals with player names prefixed with "*",
+    so a bunch of commands like @newpassword will now treat those
+    arguments. Suggested by Glonk@GlonkMUSH.
+  * Make is more verbose about alerting you to changes in the 
+    src/*.dst files.
+  * The message for undestroying someone else's object more closely matches
+    the destroy message. Suggested by Noltar@Korongil.
+  * Server output that used to be tagged with "PRE" for Pueblo is now
+    tagged with "SAMP", because the original Pueblo client did not correctly
+    handle "<BR>\n" in PRE, and the newer clients that are supporting
+    the pueblo protocol, like MUSHclient, do handle it correctly, causing
+    an incompatibility problem. Our workaround is to avoid PRE. 
+    Reported by [NJG].
+  * The WHO list output is tagged <img xch_cmd=html> for Pueblo to get
+    appropriate newline handling. [NJG]
+  * help @mail mentions help @malias. Suggested by Trispis@M*U*S*H.
+  * Matching code now treats players you can't see like disconnected players
+    when matching *player. Reported by Walker@M*U*S*H.
+  * @newpassword now confirms whose password was changed. Suggested by
+    Xyrxwyrth@M*U*S*H.
+  * @chan/who and cwho() now include objects on the channel. Suggested by
+    Glonk@GlonkMUSH.
+  * q-register lookup is slightly faster. [SW]
+  * Floating-point numbers in exponential format (6.02e23) are always
+    accepted, not just when tiny_math is set. [SW]
+  * isint() and isnum() ignore the null_eq_zero option, since they already
+    ignore tiny_math. [SW]
+  * time() and convsecs() take an optional timezone argument that,
+    if 'UTC', makes them act the same way as utctime() and convutcsecs(). 
+    From MUX2. [SW]
+Fixes:
+  * Additional range checking to avoid some bugs reported by Alierak. [SW]
+  * Fix to buglet in @name error with PLAYER_NAME_SPACES reported by
+    Luke@M*U*S*H.
+  * Typo in @name error message fixed by Luke@M*U*S*H.
+  * One could @pcreate players past the hard db limit. Reported by Z@Korongil.
+  * Typos in config_h.SH and options.h.dist fixed by Oriens@Alexandria.
+  * Under some conditions, you could double-join a channel.
+    Reported by Xyrxwyrth@M*U*S*H, investigated by Steven@Champions.
+  * Error message for @chan/desc improved. Reported by Oriens@Alexandria.
+  * Typo in alias.cnf fixed by rodregis@M*U*S*H.
+  * @mvattr sometimes failed to remove the old attrib, when it was a 
+    standard attrib that could be abbreviated (@mvattr a/desc=b).
+    Fixed by Walker@M*U*S*H.
+  * Some english-matching (like 'get 1st foo') would fail. Reported by
+    Mystery8.
+  * Typo in help @verb reported by Greck.
+  * MacOS tweaks. [DW]
+  * Better detection of numbers that are too big. [SW]
+  * Wizards could crash the server by entering objects in their own
+    inventory. Reported by Howie@New Frontiers.
+
+Version 1.7.4 patchlevel 6                    June 11, 2001
+
+Minor changes:
+  * English-style matching has been added to some more commands, 
+    to help with the stricter ambiguity checking (@teleport my 3rd foo=box, 
+    etc.). [SW]
+  * @pemit/list no longer does its useless ## substitution. [SW] 
+  * capstr() and art() skip leading ansi and html. [SW]
+  * table(), alphamin(), alphamax(), comp(), lcstr(), ucstr(), strinsert(), 
+    and delete() are all ansi and html aware. Mixing html and ansi in their 
+    arguments is probably a bad idea, though. [SW]
+  * reverse() and scramble() are ansi-aware, and still will break html, but 
+    in a different way than before. [SW]
+  * foreach() strips ansi and html from the string before doing its things. [SW]
+  * Complete Norwegian translation by Kenneth Aalberg.
+Fixes:
+  * Bug in growing hash tables fixed. [SW] 
+  * Typo in copyright fixed. Reported by Cheetah@M*U*S*H.
+  * Unused variable removed from fun_ansi. Reported by Sidney@M*U*S*H.
+  * Mac portability stuff. [DW]
+  * Wizards could @chown garbage objects. [SW]
+  * Wizards could give to garbage objects. [SW]
+  * Wizards could read God's @mail. [SW]
+  * Eliminated some compiler warnings. [SW]
+  * mid() was quite broken with ansi. right() was less broken. 
+    Both hopefully fixed for good. [SW]
+  * Fixed a problem with the attribute used with foreach() evaluating from 
+    the perspective of the wrong object. [SW]
+  * before(), after(), and wrap() are now classified as string functions
+    in the help. [TAP]
+  * help wildcards now mentions ?. Suggested by cmintrnt@M*U*S*H.
+  * help fixes by Jeff Ferrell.
+  * Problems with wrap() when the text included %r%r (or started with %r)
+    reported by Noltar@Korongil.
+  * If you somehow managed to load a corrupt db with a parent loop,
+    lparent could infinite-loop. Reported by Ashen-Shugar. [TAP]
+
+
+Version 1.7.4 patchlevel 5                    May 25, 2001
+
+Fixes:
+  * Fix to uninitialized variable that could cause ansi to bleed
+    on some systems. Patch by Luke@M*U*S*H
+  * Prototypes for ansi_align and ansi_save added to externs.h. [DW]
+  * FreeBSD hints file updated to get rid of a compiler warning. [SW]
+  * Setting hate_dest to no will not disable @recycle [SW]
+  * switchinc.c updated. [DW]
+
+
+Version 1.7.4 patchlevel 4                    May 13, 2001
+
+Minor changes:
+  * Internally, the /folder switch is now /folders, which prefix-matches
+    to /folder and also lets @mail/folders work as syntactic sugar.
+  * fun_ansi has been rewritten to use less buffer space by consolidating
+    ansi codes. New codes for turning off ansi attributes (like hilite)
+    also added.  Patch by Luke@M*U*S*H.
+  * /silent switch to give suppresses default messages when giving
+    money to players. Suggested by 8BitMUSH.
+  * Old port concentrator code removed. [SW]
+  * On linux, @uptime reads /proc files instead of running 'uptime' [SW]
+  * Code that uses strdup and then adds a MEM_CHECK record for "string"
+    now use a wrapper function that does it automatically. [SW]
+Fixes:
+  * Paging a page-locked player didn't give the appropriate messages.
+    Reported by Steven@Champions.
+  * left, right, and mid are now ansi-aware. Patch by Luke@M*U*S*H.
+  * Help fixes to lexits(), name() (Noltar@Korongil), 1.7.4p3 (Z@Korongil).
+  * win32/cmds.h updated with prototypes for dismiss and desert by
+    Noltar@Korongil. And hdrs/externs.h, too, by [SW].
+  * Memory leak with using alphabetic q-registers in queued commands fixed.
+    Report by Jayvin@Dynamix [SW]
+  * Added hints/openbsd.sh to distribution.
+  * Mac portability linting. [DW]
+  * Several memory leaks in @malias code fixed. [SW]
+
+Version 1.7.4 patchlevel 3                    April 23, 2001
+
+Commands:
+  * unfollow with no args now stops you from following everyone.
+    dismiss command stops people from following you.
+    desert command stops people from following you or leading you.
+    Idea by Oriens@Alexandria. Names suggested by Noltar@Korongil
+Minor changes:
+  * MONITOR announcements of disconnects distinguish hidden disconnects.
+    Suggested by Oriens@Alexandria.
+  * The Uptime field of INFO shows first start time, not last reboot time.
+    Suggested by Trispis@M*U*S*H.
+Fixes:
+  * Exact matches are now preferred over partial matches, and no longer
+    result in ambiguity. Report by Steven Viscido.
+  * Message mentioning INHERIT changed to TRUST by Xyrxwyrth@M*U*S*H.
+  * Distributed register.txt file is now more descriptive. 
+    Suggested by Xyrxwyrth@M*U*S*H.
+  * The ctime(), mtime(), restarttime(), and starttime() functions now 
+    return 2-digit days (01 vs. 1). Reported by Z@Korongil.
+  * @malias output uses the alias token more consistently. Suggested by
+    Kyieren@M*U*S*H.
+  * hints/solaris_2.sh modified a bit.
+  * Mac portability fixes
+  * Options.h clarification suggested by rodregis@M*U*S*H.
+  * Cosmetic bug in @halt fixed. Report by Trispis@M*U*S*H.
+  * Fixed a fencepost error in regedit*() that could generate garbage text.
+    Reported by Vadiv@M*U*S*H
+
+
+Version 1.7.4 patchlevel 2                    March 23, 2001
+
+Major changes:
+  * The object matching routines have been modified. Some things you may
+    notice:
+    * Ambiguous cases are more often reported as such (rather than you
+      getting one of the ambiguous matches arbitrarily).
+    * locate() now returns #-2 as promised. Reported by Jeff Ferrell.
+    * A few functions that used accept player names now require
+      the use of *playername to match the player (e.g. mail(), hidden()).
+      (This is generally more consistent).
+Minor changes:
+  * @tr of a nonexistent attribute now reports that. Report by Z@Korongil.
+  * TEL_OK is an alias for JUMP_OK. Suggested by Kyieren@M*U*S*H.
+  * Added 'help i18n' (aka help translation). Suggested by Kyieren@M*U*S*H.
+  * When you use 'teach' and, as a result, run the command you are teaching,
+    it is treated as if the command were run by a player from the socket --
+    that is, attribute sets are not evaluated. Suggested by Xyrxwyrth@M*U*S*H.
+  * See_All players can see all channels and their members, too.
+    Suggested by Oriens@Alexandria.
+  * When trying to join yourself to a channel, we only check channels
+    you're not on; when trying to leave a channel, we only check channels
+    that you are on. This is handy for disambiguating similar prefixes.
+    Suggested by Oriens@Alexandria.
+  * When you're following a leader and the leader moves, you're told that
+    that you're following them before you attempt to move. Suggested by
+    Oriens@Alexandria.
+  * @stats/table is no longer restricted.
+Fixes:
+  * @grep/iprint produced highlighted strings matching the case you
+    gave, not the case actually found. Reported by Reagan@NF
+  * @search/lsearch by powers could sometimes get you the equivalent
+    flag-bit instead of power-bit. Reported by Reagan@NF
+  * Configure fix.
+  * hpux-gcc hint file now included.
+  * Nested ansi() broke again in p1. Fixed now. Reported by Intrevis@M*U*S*H
+  * Added Configure check for <netdb.h> to help Cygwin builds.
+    Reported by Xyrxwyrth@M*U*S*H.
+  * Help fix or two.
+  * Grammatical correction by Eratl@M*U*S*H in @boot/me error message.
+  * Cosmetics of @mail with folders > 9 improved. Reported by Bellemore@M*U*S*H
+  * One could be disconnected at the connect screen under some conditions
+    for no good reason. Reported by Oriens@Alexandria. [SW]
+  * Compile error when ROYALTY_FLAG not defined patched by Noltar@Korongil.
+  * Fixed infinite loop reported by Xyrxwyrth@M*U*S*H. [SW]
+  * It's no longer posible to connect to a GOING player.
+
+Version 1.7.4 patchlevel 1                    March 17, 2001
+
+Minor changes:
+  * Speedup for repeat() function. [TAP]
+  * Hint for openbsd, which appears to have a broken IPv6 configuration. [SW]
+  * Some OS-dependent defines have been removed.
+  * ansi() now only adds a maximum of 7 codes to the string. [TAP]
+Fixes:
+  * The restrict_command for rob has been removed from restrict.cnf
+    Reported by Kyieren@M*U*S*H.
+  * Help fixes by Kyieren, rodregis, and Luke @ M*U*S*H, Datron@SW2, 
+    and Noltar@Korongil.
+  * stripansi() didn't correctly handle multiple ansi codes in
+    sequence. Reported by CU5@WCX.
+  * Linting for warnings in pcre. [SW]
+  * Configure now sends mailing list subscription stuff to the new 
+    list address.
+  * Updated examples in access.README to use dbrefs.
+  * Updated a reference to the rob command in 'give' errors. Noted by
+    rodregis@M*U*S*H.
+  * median was broken. Reported by Parax@SandsMUSH.
+  * Fixes to update.pl's handling of CHAT_TOKEN_ALIAS and the like.
+    Noted by rodregis@M*U*S*H
+
+Version 1.7.4 patchlevel 0                     March 7, 2001
+
+Major Changes:
+  * This is now the stable minor version. PennMUSH 1.7.2 is no longer
+    supported except to help people transition to this version.
+Commands:
+  * The practically useless 'rob' command has been removed.
+Minor Changes:
+  * A virtually complete French translation joins the Swedish and
+    Hungarian ones! Congratulations to Jean-Michael Amblat and
+    Guillaime Lupin.
+  * The index-files.pl script handles long help topic titles better when
+    creating the index of help entries. [SW]
+  * Config options that can be set with @config/set are now documented in
+    mush help. [SW]
+  * A @config/set of a dbref option now checks dbref for validity. [SW]
+  * An ansi_normal code is added at the end of each channel title.
+  * You can clear attributes that have invalid names. [SW]
+  * stripansi() removes HTML markup as well as ANSI. [SW]
+  * @poll and @doing cannot have ANSI or HTML markup. [SW]
+  * soundex() and soundslike() strip ANSI and HTML. [SW]
+  * The maximum length of attribute names has been limited to 1024 
+    characters. [SW]
+  * Nesting ansi() functions now works better. Patch by Luke@M*U*S*H.
+  * help credits explains [initials] used herein. Suggested by Kyieren@M*U*S*H
+Fixes:
+  * Help fixes by Nymeria, Balerion, Trispis, Vexon (all@M*U*S*H),
+    Jeff Ferrell, and [SW,LdW]
+  * The two-argument forms of regmatch() and regmatchi() were backwards
+    when it came to case-sensitivity. [SW]
+  * @search on zone class did parent instead. Fix by Luke@M*U*S*H.
+  * Use of @mail after @mail/nuke could cause a crash.
+    Reported by Brazil. [SW]
+  * make update handles the include directive correctly. [SW]
+  * The admin WHO output looks better when locations reach 5-digit
+    dbrefs now.
+  * regedit() and regeditall() were case-insenstive. Fixed. [SW]
+  * The code for log() was changed some time back to allow an optional
+    base argument, but the help and function table were never updated. [SW]
+  * owner() could be used to determine if any attribute existed on any
+    object. [SW]
+  * atrlock() has been cleaned up, fixing many bugs. [SW]
+  * Some list functions that evaluate attributes could be used to determine
+    if the attribute existed even if the object doing the function couldn't
+    normally see the attribute. Fixed, and their error messages are now
+    consistant with the other list functions (In other words, no errors, just
+    a null string) [SW]
+  * Idle timeout is now checked every minute rather than at dbck intervals. 
+    Based on a report by Noltar@Korongil.
+  * Cleanup of signed/unsigned casts and signal handlers. [SW,DW]
+  * forbidden_name now does a case-insensitive comparison.
+    Reported by Kyieren@M*U*S*H.
+  * Blank lines at the start of help files are now correctly ignored
+    on Win32 and Mac systems as well as Unix. Report by Nymeria@M*U*S*H.
+  * functions() didn't show @functions. [SW]
+  * Nuked players weren't getting removed from @maliases. [SW]
+  * Database corruption caused by reading a db with over-long attribute
+    names or with attributes starting with quotes fixed. [SW]
+  * Crash bug in @attribute/rename fixed. [SW]
+  * Potential memory leak in help_command fixed. [SW]
+  * Warnings removed. Reported by [NJG]
+  * Windows NT native IO (NT_TCP) stuff should work again. Reported by
+    Bellemore@M*U*S*H. [NJG]
+  * @forwardlist now requires that you control the target, be pemit_all,
+    or pass the target's @lock/forward. Report by Vadiv@M*U*S*H.
+  * unparse_flags didn't handle exit toggles. Report by Draci@Chaotic.
+  * Casting and cleanup to enable compiling with lcc [SW]
+  * A potential problem with regexps with heavy backtracking fixed. [SW]
+  * Memory leaks with @clock fixed. [SW]
+  * Typo in spellnum() "fourty" fixed. Reported by Kyieren@M*U*S*H.
+  * @malias/set didn't work. Reported by Kyieren@M*U*S*H.
+  * Win32 portability fixes. [NJG]
+  * MacOS portability fixes [DW]
diff --git a/CHANGES.175 b/CHANGES.175
new file mode 100644 (file)
index 0000000..3662209
--- /dev/null
@@ -0,0 +1,488 @@
+
+This is the most current changes file for PennMUSH. Please look it
+over; each version contains new things which might significantly affect
+the function of your server.  Changes are reported in reverse
+chronological order (most recent first)
+
+[TN] is Thorvald Natvig, a PennMUSH developer (aka Trivian)
+[TAP] is T. Alexander Popiel, a PennMUSH developer (aka Talek)
+[SW] is Shawn Wagner, a PennMUSH developer (aka Raevnos)
+[LdW] is Luuk de Waard, a PennMUSH developer (aka Halatir)
+[RLM] is Ralph Melton, a former PennMUSH developer
+[NJG] is Nick Gammon, the Win32 porter
+[EEH] is Ervin Hearn III, a Win32 porter (aka Noltar)
+[DW] is Dan Williams, the MacOS porter
+[2.2] refers to code which originated with the TinyMUSH 2.2 developers
+[3] refers to code by (or inspired by) TinyMUSH 3.0
+[Rhost] refers to code by (or inspired by) RhostMUSH
+
+==========================================================================
+
+Version 1.7.5 patchlevel 12                     November 3, 2002
+
+Fixes:
+   * Another bug in wrap() fixed. Reported by Rhysem. [SW]
+   * Bug in @wall fixed. [SW]
+   * Variable renaming to avoid C99 keyword 'conj'. [SW]
+   * Win32 project files for MSVC++ updated by Mark.
+   * Several portability fixes for MS VS.NET's compiler by BladedThoth.
+   * flip() and reverse() didn't mix well. Better now.
+     Reported by Julian. [SW]
+   * Compiling with CHAT_SYSTEM undefined works again. Report by
+     BladedThoth.
+   * bxor() was actually doing a bor(). Reported by Sketch@M*U*S*H. [SW]
+
+
+Version 1.7.5 patchlevel 11                     October 31, 2002
+
+Config:
+   * New mush.cnf option only_ascii_in_names (defaults to yes) prohibits
+     the use of extended characters in names. Games that are running
+     in non-English locales will probably want to set this to no instead. 
+     Suggested by Philip Mak. [SW]
+Commands:
+   * Added @hook/before and @hook/after [SW,3]
+Locks:
+   * You can now use power^<power> and channel^<channel> in locks
+     to test if the enactor has a given power or is on a given channel.
+     Patch by Vadiv@M*U*S*H.
+   * @lock/dropto, if set on a room, can prevent objects from being
+     affected by the room's drop-to. Inspired by Oriens@Alexandria.
+Functions:
+   * The sha1() function computes the SHA-1 cryptographic hash of a string.
+   * A new nosidefx function restriction to allow disabling the side-effect
+     version of a function while still enabling the informational version.
+     For things like name() and parent(). [SW]
+   * @function's report includes more function restrictions in the flags
+     field. [SW]
+Minor changes:
+   * Modularization of code for itemizing lists by Vadiv@M*U*S*H.
+   * If there's no connect.html and you're on an html connection,
+     connect.txt is now better formatted when sent to you. Same for 
+     other cached text files. Suggested by BladedThoth@M*U*S*H.
+   * CRYPT_SYSTEM 1 now behaves like CRYPT_SYSTEM 3 (replacing
+     system-crypt passwords with SHS passwords). Suggested by Vadiv@M*U*S*H.
+   * flag_table is no longer referenced anywhere except when it is used
+     to seed the ptab_flag at startup. A stub "flag_add" function has
+     been added to make life easier for hardcoders. Suggested by
+     Gepht.
+Fixes:
+   * sig.c was broken on systems without sigprocmask. Reported by
+     Arithon@Oracle
+   * Bug with paging disconnected players and @away fixed.
+     Reported by Vadiv@M*U*S*H.
+   * Bashisms that crept into utils/mkcmds.sh has been replaced by
+     more portable alternatives based on Configure's results. 
+     Reported by Jason Newquist.
+   * Trigonometric functions were broken for non-radian degree types.
+     Fixed up.
+   * @decomp <room>/<attrib> didn't properly use 'here' as the name
+     of the object in its output. Reported by Oriens@Alexandria.
+   * Wizards can now modify any lock on anything but God. Reported by
+     Brian Favela.
+   * ex/mortal and ex now produce identical output when a mortal 
+     examines an object owned by someone else. Reported by Philip Mak.
+   * We do a little better about trying to close html and ansi tags
+     in all conditions. Bugs reported by BladedThoth @ M*U*S*H.
+   * whisper/@pemit to a puppet should be relayed to the owner, even if the 
+     owner is in the same room. Discovered thanks to MUSH sound test
+     suite designed by Trispis@M*U*S*H.
+   * The --longest switch in game/txt/Makefile was broken. Report by
+     Nymeria@M*U*S*H
+   * Help fixes by Noltar@Korongil and Intrevis@M*U*S*H
+   * The M_READ extmail bit is now renamed M_MSGREAD, as M_READ conflicts
+     with an included define on Solaris. Report by Jason Newquist.
+   * Setting flags using single characters was not well documented, and
+     didn't respect the character case. Reported by Intrevis@M*U*S*H.
+   * @chown by a Wizard attempted to debit the Wizard's money, rather than
+     that of the new owner of the object, which violated expected conservation
+     of money. Reported by Peter Bengtson.
+   * Several bugs in wrap()'s output fixed. Reported by Balerion@M*U*S*H. [SW]
+
+
+Version 1.7.5 patchlevel 10                     September 19, 2002
+
+Major Changes:
+   * Commands can now be restricted by generic flags or powers.
+     Several mush.cnf options (restricted_building, free_objects,
+     hate_dest, player_locate, cemit_power) are now restrict.cnf
+     restrictions instead. By Vadiv@M*U*S*H.
+Functions:
+   * When a set function (setdiff, etc.) is called with 4 arguments,
+     if the last one isn't a valid sorting category, it's treated as
+     the output separator.  Inspired by Mux [SW]
+   * checkpass(), a wizard function that checks a string against a player's
+     password. Requested by Writh@M*U*S*H.
+   * regedit() and variants can now accept multiple regexps and
+     replacements, in order, like edit(). By Walker@M*U*S*H.
+   * comp() can take a third argument to specify the type of 
+     comparison to make. Suggested by Philip Mak.
+   * The trigonometric functions now take an optional argument to
+     control how the angles they work with are measured to allow them
+     to accept angles in degrees as well as the default radians. [SW,MUX2,Rhost]
+   * Added ctu() for converting between angle measurements. [SW,MUX2,Rhost]
+   * Added atan2(). [SW]
+   * dist2d() and dist3d() can take floating-point numbers. [SW]
+   * Other small cleanups in the math functions. [SW]
+Mail:
+   * The MAIL_SUBJECTS option has been removed. @mail now includes
+     subjects mandatorily. Suggested by Vadiv@M*U*S*H.
+Minor Changes:
+   * When a player @clones an object owned by another player, the
+     clone is now owned by the @cloning player, instead of the original
+     owner. Fixes a quota transfer issue reported by Sparta and
+     Philip Mak.
+   * The flag table is no longer walked with a linear search. Instead,
+     ptab_firstentry and ptab_nextentry are used. Flags no longer need
+     to be added in a particular order or groups in flags.c, and flags
+     added through hardcode should work better. Patch by Vadiv@M*U*S*H
+   * Error message for wrong number of arguments to a function
+     now tells you how many arguments it thinks you gave.
+     Suggested by Philip Mak.
+   * GAGGED players may now perform mail reading and maintenance.
+   * Internal reorganization of signal code. [SW]
+   * Attempts to speak on a channel that you can't speak on or see
+     now fail and command parsing continues. Suggested by Vadiv@M*U*S*H.
+   * The amount of CPU time spent running a queue entry can be limited.
+     This helps reduce the impact of some types of denial-of-service attacks.
+     New mush.cnf directive queue_entry_cpu_time. This currently
+     works only on Unix systems with setitimer. [SW]
+   * Internal rewrite of page/whisper code by Vadiv@M*U*S*H.
+   * Flag set/reset messages now include the name of the target object.
+     Suggested by Kyieren@M*U*S*H.
+   * game/txt/Makefile now includes options to limit the number of
+     news/etc topic aliases that are included in the 'entries' indices
+     generated by index-files.pl. Suggested by Nymeria@M*U*S*H.
+   * Minor inconsistencies in @sweep output punctuation reported by
+     Cmintrnt@M*U*S*H have been fixed.
+   * Added hints/cygwin.sh, tested with cygwin 1.3.12. Added additional
+     cygwin build information to README.
+   * The whisper-pose message is now Player senses: <pose>, with no
+     quotation marks added. This matches all other pose-type messages
+     in the server. Suggested by Philip Mak.
+   * Only escape codes described in the help are allowed in timefmt() [SW]
+Fixes:
+   * Archaic help reference to FORCE_WHITE removed. Noted by Oriens@Alexandria.
+   * Help fixes by Cerekk@bDv TrekMUSH, Julian@M*U*S*H, Letters@M*U*S*H,
+     and Philip Mak.
+   * The wildcard matcher could lag the MUSH under unusual conditions.
+     It's now smarter. Discovered by Sketch@M*U*S*H.
+   * Fixes from 1.7.4p20
+   * Fix a bug with setdiff() not using the output separator correctly. [SW]
+   * convsecs() could attempt to use values larger than 2^31, which could
+     crash Windows. Reported by Bellemore@M*U*S*H.
+   * @config didn't correctly show default flags for objects.
+     Reported by Vadiv@M*U*S*H.
+   * The strcasecoll function was poorly coded, and is now fixed.
+   * Created players who hadn't yet logged in didn't have LASTIP set
+     properly. Reported by Philip Mak.
+
+Version 1.7.5 patchlevel 9                     July 16, 2002
+
+Minor Changes:
+   * /noeval switch added to @wall/@rwall/@wizwall and variants.
+     Suggested by Philip Mak.
+Fixes:
+   * Added a missing space in the @function report for softcoded
+     @functions. [SW]
+   * MUX-style @function foo=obj/attr works right. [SW]
+   * Cleaned up some multiple includes of the same header files. [SW]
+   * Lots of cleanup of old _() macros and similar by Vadiv@M*U*S*H.
+   * Added help for @stats/table. Suggested by Intrevis@M*U*S*H.
+   * Fixes to csrimalloc #ifdefs that broke in last patchlevel. [SW]
+   * A typo that could crash @function on certain operating systems
+     has been fixed. Report by Jeff Heinen.
+   * Improved switch() help. [SW]
+   * Changes in the way switchinc.c is generated, to reduce the number
+     of patches that attempt to patch it due to indentation changes. [SW]
+
+Version 1.7.5 patchlevel 8                     June 26, 2002
+
+Minor Changes:
+  * Added @nspemit and nspemit(). Wizard-only versions of @pemit and
+    pemit() that don't print nospoof information. Suggested by many people,
+    most recently Mike Griffiths and Nymeria@M*U*S*H. [SW]
+  * Help updates. [SW]
+  * Force the pipes to compression program for database reads and saves to be
+    block-buffered. [SW]
+  * @function name=obj/attrib now works, as well as
+    @function name=obj,attrib [TAP]
+  * The AF_PREFIXMATCH flag is no longer shown on attributes it's set
+    on when you examine them.
+Fixes:
+  * A bunch of internal code cleanup, especially around casts. [SW]
+  * The disconnected room check is skipped on GOING rooms. Suggested
+    by Philip Mak.
+  * The dbck check for nameless rooms was only checking disconnected
+    rooms; now it checks all rooms.
+  * hasflag() did not work with single-character flag abbreviations.
+    Report by Mystery8.
+  * The variable named 'template' in src/strutil.c has been renamed
+    to avoid clashes with the C++ reserved word. Suggested by Mac@M*U*S*H.
+  * Improvement to help @filter. Suggested by Philip Mak. [SW]
+  * Files in the patches directory ending in ~ are ignored
+    when patches.h is rebuilt. [SW]
+  * Removed a // comment from strutil.c, as we're still
+    just following the c89 standard, not c99. Report by
+    Vadiv@M*U*S*H. [SW]
+  * make indent now indents the .dst files before the .c ones.
+    Fixes some spurious warnings from later makes. Suggested by
+    Vadiv@M*U*S*H. [SW]
+  * Code cleanup, mostly tprintf() and unneeded header file
+    checking elimination. [SW]
+  * Since a Windows header #defines OPAQUE, which conflicts with a
+    #define for the mush flag of the same name, rename
+    our #define rather than #undefining the Windows one. [SW]
+  * Fixes from 1.7.4p19
+
+
+Version 1.7.5 patchlevel 7                     May 14, 2002
+
+Utilities:
+  * 'make globalinstall' will install executables, scripts, and
+    a game/ directory structure in a global location (/usr/libexec/pennmush
+    by default). Facilitates rpm builds. By Vadiv@M*U*S*H.
+  * The utils/ln-dir.sh script can be used to clone a globalinstall'd
+    pennmush for an individual MUSH/user. In combination, these two
+    are a replacement for 'make customize', especially for mud hosters.
+    By Vadiv@M*U*S*H.
+  * You can now configure options.h settings from the command line
+    using: make DEFINE="OPTION OPTION=value" UNDEFINE="OPTION" update
+    This will mostly be useful for autoinstallers and packaging scripts.
+    Suggested by Vadiv@M*U*S*H.
+Minor Changes:
+  * The default gcc compile flags now include some extra warnings.
+  * The prefix-table code now only aliases down to unique prefixes.
+    This prevents @w from calling @wipe (reported by Philip Mak),
+    and means that you'll need to use alias.cnf to get some of those
+    short aliases. [SW]
+  * Attribute lookups only do prefix-matching on attributes with the
+    AF_PREFIXMATCH flag. Most standard atr_tab.h attributes have this
+    flag, but newly added @attributes won't. Solves a problem with
+    inadvertant prefix-matching of @attribs reported by Sam Knowlton.
+Fixes:
+  * Fixes from 1.7.4p18
+  * @decomp/skipdefaults skips @lsets of default lock flags.
+    Suggested by Oriens@Alexandria. [SW]
+  * Typo in src/bsd.c corrected. Reported by Nymeria@M*U*S*H.
+  * Missing prototype in src/help.c. Reported by Nymeria@M*U*S*H.
+  * A bunch of linting.
+  * Win32 portability fixes. [EEH]
+  * Updated MSVC++ project files for win32. [EEH]
+  * @newpassword = foo would change the password of an arbitrary player.
+    This is now corrected. Report by Oriens@Alexandria.
+
+Version 1.7.5 patchlevel 6                     April 22, 2002
+
+Config:
+  * New attribute_alias config directive, and some default attribute
+    aliases added to alias.cnf. Based on a report from Hilikiradi.
+Functions:
+  * textfile() returns help/news/etc. entries. Suggested by Trispis@M*U*S*H.
+Minor changes:
+  * New @warnings type lock-checks that reports problems with @locks. [SW]
+  * exit-unlinked checks do some sanity checks on variable exits. [SW]
+  * Improved error-checking in evaluation of @locks. [SW]
+  * No more hdrs/warnings.h file. [SW]
+  * New @nameaccent attribute to add accent marks to object
+    names in speech and things like look. Idea from Elendor. [SW]
+  * accent() understands a few more things. [SW]
+  * The accented characters->html entities table and other
+    lookup tables are now in a seperate file, src/tables.c,
+    which can be regenerated if needed by utils/gentables.c [SW]
+  * Improvements in caching of cached text files. [SW]
+Fixes:
+  * Buglet in ansi display of high-bit characters fixed. Report by
+    Trispis@M*U*S*H. [SW]
+  * Improved @clock2 help by Linda Antonsson.
+  * Fixes from 1.7.4p17
+  * A truly perverse database could cause an infinite loop on load. [TAP]
+  * Win32 portability fixes. [NJG, EEH]
+  * The notify code assumed that integers could be directly stored in
+    pointers. This isn't always true. [SW]
+  * Removed some un-used code. [SW]
+  * Fixed some compiler warnings and general code cleanup. [SW]
+  * Changed signal handlers to always use the ANSI/ISO C form (Returning
+    void, basically) [SW]
+  * A null string no longer prefix-matches anything. Report by Prot Diryn
+    and Cheetah@M*U*S*H.
+  * @sitelock/remove could remove entries it shouldn't if you remove the first
+    one after the '@sitelock will add sites...' line. Reported by
+    Ambrosia@M*U*S*H. [SW]
+  * The last line of the access.cnf file sometimes wouldn't get read
+    properly. [SW]
+
+
+Version 1.7.5 patchlevel 5                     March 11, 2002
+
+Commands:
+  * @notify and @drain now accept a new switch /any. [TAP]
+  * Added @remit/list. Suggested by Tareldin@M*U*S*H [SW]
+Minor changes:
+  * We now use the Mersenne Twister pseudo-random number generator,
+    which is better that that available in most C libraries.
+    Moreover, we seed with /dev/urandom, if it's available. [SW]
+  * The 'T' type character (for THING) is now shown when one-character
+    flag lists are displayed. This is more consistent with other types,
+    and makes it harder to confuse #23O (#23, opaque) with #230
+    (#230, no flags). Suggested by Eratl@M*U*S*H.
+  * @lock/use on a parent used to apply to attempts to use $commands on
+    a child. This is no longer necessary, given inheritable locks,
+    so the behavior has been changed. Parents' locks are no longer checked
+    when deciding if a $command inherited from the parent should be run
+    via a child.
+  * New 'call_limit' config option can limit the number of recursive
+    parser calls to control process stack size and avoid crashes
+    on systems with limited stack. Defaults to unlimited, however, because
+    setting this value too small breaks mushcode. Report by Bellemore
+    and BladedThoth @ M*U*S*H.
+Fixes:
+  * Code cleanup - some stuff from 1.7.4 got left in that isn't
+    used in 1.7.5 any more. [SW]
+  * Fixes from 1.7.4p16, notably an important fix for timed semaphores.
+  * Cygwin portability fixes. [NJG]
+  * Updated MSVC++ project files. [EEH]
+
+
+Version 1.7.5 patchlevel 4                     February 15, 2002
+
+Major changes:
+  * The mush recognizes telnet-aware connections. This is
+    neccessary for properly sending them some 8-bit characters. [SW]
+  * Much more support for handling accented characters in the ISO 8859-1
+    character set. See help for accent(), stripaccents(), and NOACCENTS.
+    Inspired by Elendor. [SW]
+  * Things that do first-unique-prefix matching (command, attribute and flag
+    names) now use a more space-efficient data structure than before.
+    This adds two new files, src/ptab.c and hdrs/ptab.h [SW]
+Commands:
+  * @sitelock/remove removes a sitelock entry. [SW]
+Functions:
+  * ord() and chr() functions for converting characters to/from numerical
+    values that represent them. [SW]
+Minor changes:
+  * The useless FORCE_WHITE flag is really, truely, gone. [SW]
+  * Use the new arglens argument to functions in more places. [SW]
+  * capstr() and before() fixes reimplemented using arglens. [SW]
+  * We now use the Mersenne Twister PRNG algorithm. [SW]
+Fixes:
+  * setunion() no longer eats empty list elements. [SW]
+  * Setting an inherited lock on a child could change the parent's lock.
+    Reported by Riverwolf. [SW]
+  * Help fixes. [SW, Nymeria]
+  * Players waiting at the connect screen weren't being disconnected
+    by the idle_timeout.
+  * Detection of cygwin in Configure may be improved.
+  * Fixes from 1.7.4p15.
+
+Version 1.7.5 patchlevel 3                     January 24, 2002
+
+Fixes:
+  * before() was broken in 1.7.5p2. Reported by Sam Knowlton.
+  * capstr() was broken in 1.7.5p2.
+  * Win32 portability fixes by Noltar@Korongil.
+
+Version 1.7.5 patchlevel 2                     January 23, 2002
+
+Major changes:
+  * Implementations for softcode functions get the lengths of their arguments
+    passed to them, and this is taken advantage of in a number of places. [SW]
+Minor changes:
+  * It's harder to get a partial dbref because of end-of-buffer truncation. [SW]
+  * Code cleanup. In particular, safe_str() and friends are no longer
+    macros for a safe_copy_str() or the like, because hardly anything
+    used a different buffer length than BUFFER_LEN, and those places
+    can be handled other ways. [SW]
+Fixes:
+  * Win32 portability fixes by Noltar@Korongil and Eric Koske.
+  * When you have two hidden connections, one idle over the inactivity limit,
+    and the other not, @hide/off on the active connection unhides both,
+    but you also see the Inactivity re-hide message from the other
+    connection. Reported by Trispis.
+  * iname() function actually added to function table so it works.
+    Reported by K. Shirow.
+  * @lock obj=attrib:value locks didn't work properly. Reported by
+    Linda Antonsson.
+  * Fixes from 1.7.4p14.
+
+Version 1.7.5 patchlevel 1                     December 3, 2001
+
+Minor Changes:
+  * PCRE updated to 3.7. [SW]
+  * player_name_len is now runtime configurable. Suggested by
+    Linda Antonsson. [SW]
+  * Any object of any type may be a ZMO, and any object of any type
+    may be zoned to a ZMO of any type. However, searching for
+    $commands has not changed, so $commands on a ZMO are only
+    searched when the ZMO is not a room, and $commands on objects
+    within the ZMO are only searched when the ZMO is a room. [TAP]
+  * @chzoneall nows directly calls @chzone, and @chzone now tells
+    you when it's not changing a zone. [TAP]
+  * The term "Zone Master" (player) has been replaced by "Shared
+    Player" in the help. [TAP]
+  * Many obsolete db formats are no longer readable. hdrs/oldattrib.h
+    and src/convdb.c are no more. [SW]
+  * Code cleanup. [SW]
+Fixes:
+  * Help file for mix updated. Report by Cmintrnt@M*U*S*H
+  * Updated win32 config.h file and other fixes by Noltar@Korongil
+  * WHO wasn't showing unconnected players. Report by Noltar@Korongil. [SW]
+  * Help fixes. [SW]
+
+Version 1.7.5 patchlevel 0                     November 14, 2001
+
+Major Changes:
+  * This is now the development minor version. This first release includes
+    relatively few changes, to make converting to it easier.
+  * Internal changes to the lock system. This requires a new minimal.db,
+    which is now distributed. [SW]
+  * Locale-based string collation throughout.
+  * Only ANSI C compilers are still supported; no more K&R. Files are
+    gradually going to be converted to ANSI C only.
+  * There is now an option to make ZMOs and ZMRs not count for
+    control of objects, only ZMPs. [SW]
+Flags:
+  * The ZONE player flag has been renamed SHARED, to help seperate the
+    ZMP control-only meaning from the command-matching of ZMOs and ZMRs. [SW]
+Commands:
+  * /preserve switch for @link prevents @chowning. Suggested by Vexon@M*U*S*H
+  * Admin WHO and SESSION now includes unconnected descriptors. [SW]
+  * Unconnected descriptors can now be booted. Patch by Bellemore@M*U*S*H.
+  * Unconnected descriptors can now be paged by admin with page/port. [SW]
+Functions:
+  * mix() can take more than 10 lists and of unequal length. [3,SW]
+  * iname() returns the name of an object from inside (honoring nameformat)
+    Idea by Jeffrey@TheHotel.
+  * lplayers() returns a list of players in the location. Handy for
+    room parents. By Vexon@M*U*S*H.
+  * lvplayers(), lvcon(), lvexits() are like lplayers/lcon/lexits, but
+    leave out dark things (and disconnected players). Handy for room
+    parents. By Vexon@M*U*S*H.
+Minor Changes:
+  * munge() now passes its delimiter as %1 to make generic sorting easier. [SW]
+  * Word-based attribute compression is faster than before, for both
+    compression and decompression. [SW]
+  * Windows memory-usage information for wizards is now in @uptime, not
+    @stats [SW]
+  * Word-based attribute compression stats can be viewed on non-Windows
+    mushes as well, by defining COMP_STATS. See externs.h for details. [SW]
+  * Setting of the internal QUEUE and semaphore attributes does not modify
+    an object's last-modified timestamp. [SW]
+  * Speaking on a channel that you're gagging is now treated like
+    speaking on a channel that you're not on. Suggested by rodregis@M*U*S*H
+  * You can use @exitto in place of &DESTINATION to set the destinatino
+    for variable exits, though DESTINATION is checked first. [3]
+  * WATCHER is another name for the MONITOR flag. [3]
+  * max_guest_pennies and guest_paycheck config options. Inspired by [SW]
+  * Lock and unlock messages now show object name and dbref, and tell
+    you if you unlock an already unlocked object. Suggested by Jamie Warren.
+  * A version of portmsg for Win32 is in the win32 directory.
+    Donated by CU5@WCX
+  * Tweaks to info_slave, which now uses readv/writev. [SW]
+  * Lots of code cleanup. [SW]
+  * CHAT_SYSTEM, INFO_SLAVE, and FUNCTION_SIDE_EFFECTS are now #define'd
+    by default. [TAP]
+Fixes:
+  * Indentation fixes [SW]
+  * Fixes up to 1.7.4p12 merged in.
+
diff --git a/CHANGES.176 b/CHANGES.176
new file mode 100644 (file)
index 0000000..56899e5
--- /dev/null
@@ -0,0 +1,353 @@
+
+[TN] is Thorvald Natvig, a PennMUSH developer (aka Trivian)
+[TAP] is T. Alexander Popiel, a PennMUSH developer (aka Talek)
+[SW] is Shawn Wagner, a PennMUSH developer (aka Raevnos)
+[LdW] is Luuk de Waard, a PennMUSH developer (aka Halatir)
+[RLM] is Ralph Melton, a former PennMUSH developer
+[NJG] is Nick Gammon, the Win32 porter
+[EEH] is Ervin Hearn III, a Win32 porter (aka Noltar)
+[DW] is Dan Williams, the MacOS porter
+[2.2] refers to code which originated with the TinyMUSH 2.2 developers
+[3] refers to code by (or inspired by) TinyMUSH 3.0
+[Rhost] refers to code by (or inspired by) RhostMUSH
+
+==========================================================================
+
+Version 1.7.6 patchlevel 16                     April 28, 2004
+
+Fixes:
+   * PCRE updated to 4.5 [SW]
+
+
+Version 1.7.6 patchlevel 15                     January 25, 2004
+
+Fixes:
+   * Improved freebsd hints. [SW]
+   * Channel user memory allocation error corrected.
+
+
+Version 1.7.6 patchlevel 14                     September 23, 2003
+
+Fixes:
+   * Fix to help @search2 by LeeLaLimaLLama@M*U*S*H.
+   * The max file descriptor could get stomped in some cases. [SW]
+   * Powers and toggles on destroyed objects are reset, as they 
+     caused anomalous lsearch/haspower behavior. Report by Mordie@M*U*S*H.
+   * Changing channel privs and loading channels with objects no longer
+     permitted could cause crashes. Report by Septimus@SW RP Forum.
+
+
+Version 1.7.6 patchlevel 13                     August 11, 2003
+
+Fixes:
+   * Calling panic() while in the middle of a panic dump would cause a loop.
+     Reported by [EEH]. [SW] 
+   * Outdated mention of compose.csh removed from compose.sh.SH.
+     Reported by Cheetah@M*U*S*H.
+   * timestring() dealt wrongly with large arguments. Reported by
+     Jules@M*U*S*H. timefmt() had a similar problem, reported by
+     Luke@M*U*S*H.
+   * Better checking of db save failures. [SW]
+
+
+Version 1.7.6 patchlevel 12                     June 23, 2003
+
+Minor changes:
+   * Users no longer see last connection information when they 
+     connect to Guests. Suggested by Jules@M*U*S*H.
+Fixes:
+   * Potential problem with ambigious names in the information functions 
+     fixed. [SW]
+   * The 'chat' config group is no longer displayed when CHAT_SYSTEM
+     isn't defined. Report by Mike Griffiths. [SW]
+   * cygwin install instructions changed to remove obsolete exim
+     version information. Suggested by Cheetah@M*U*S*H.
+   * Objects with user-defined locks had problems with finding built-in locks
+     on the object. Reported by Walker@M*U*S*H. [SW]
+
+
+Version 1.7.6 patchlevel 11                     June 1, 2003
+
+Minor changes:
+   * The restart script now insures that GAMEDIR is a real directory
+     and CONF_FILE exists therein before proceeding. Suggested by
+     Philip Mak.
+   * Attribute flag setting messages are more verbose. Suggested by
+     Mike Griffiths
+   * See_All players may use the ports() function. Suggested by 
+     Mike Griffiths.
+Fixes:
+   * Wizards can no longer @chzone God. Report by Kevin@M*U*S*H.
+   * Help fixes by Mike Griffiths.
+
+Version 1.7.6 patchlevel 10                     May 13, 2003
+
+Minor changes:
+   * PCRE (the regex matching engine we use) is updated to version 4.2. [SW]
+   * @mail/file now unclears the cleared bit when filing @mail.
+     Suggested by Philip Mak.
+Fixes:
+   * @edit is better with editing ansi. Reported by Trispis@M*U*S*H. [SW]
+   * Help file cleanup. [SW]
+   * @warnings about missing FAILURE messages were not correctly 
+     checked, causing false positives. Reported by Cheetah.
+   * Page message no longer ends in a period. Suggested by Time@M*U*S*H.
+   * Help fixes by Intrevis@M*U*S*H.
+   * BASE_ROOM can't be destroyed any more. Suggested by Philip Mak.
+
+
+Version 1.7.6 patchlevel 9                      April 9, 2003
+
+Fixes:
+   * index-files.pl now produces a sensible warning for duplicate
+     help topics, rather than a perl warning. Suggested by Cheetah@M*U*S*H.
+   * Spellnum cosmetic bug with 'seventeen' fixed. Report by Jules@M*U*S*H.
+   * Another wrap() buglet tracked down and fixed. Probably the one
+     reported by Nymeria@M*U*S*H.
+   * Memory leak in flip() and scramble() fixed.
+   * Configure test for /dev/urandom from 1.7.5 got left out by mistake.
+   * Critical overflow bug in command argument parsing fixed.
+
+Version 1.7.6 patchlevel 8                      March 21, 2003
+
+Minor changes:
+   * The CHANGES file has been renamed so that it always refers to
+     a version number, and utils/mkvershelp.pl now generates seperate
+     .hlp files for each CHANGES file. This will prevent patch hunk 
+     failures when two patchlevels of different versions are released
+     and both used to try to modify the same .hlp file.
+   * Channel names are restricted to printable characters, with no
+     leading or trailing spaces. Suggested by Letters@M*U*S*H.
+   * Calling time() with any argument other than 'utc' now generates
+     an error. Report by Time@M*U*S*H.
+Fixes:
+   * Some redundant code cleanup in look_exits suggested by Vadiv@M*U*S*H.
+   * Help file fixes by Ves@M*U*S*H, Jules@M*U*S*H, Cerekk@bDv.
+   * When page_aliases is on, there's a space between the player's
+     name and alias. Suggested by Saturn@M3.
+   * Command-checking for ZMR contents didn't function when a ZMR
+     was used as a player's personal zone. Reported by BlaZe@M*U*S*H.
+   * Default idle_timeout was different in code and mushcnf.dst.
+     Reported by James Bond@M*U*S*H. [SW]
+
+
+Version 1.7.6 patchlevel 7                      February 20, 2003
+
+Fixes:
+   * Some sloppy coding in src/access.c could generate runtime 
+     debugging exceptions. Reported by BladedThoth@M*U*S*H.
+   * wrap() could behave incorrectly when a line was exactly the length
+     of the wrap width and the total input size was larger than 
+     any previously wrapped input. Reported by Liam@Firdeloth.
+   * Extra NUL characters were sent after telnet codes, which 
+     confused Mudnet and maybe some clients. Patch by Alierak.
+
+
+Version 1.7.6 patchlevel 6                      January 23, 2003
+
+Minor changes:
+   * nearby() always works for see_all players. Reported by Sparta.
+   * findable() now requires that executor control either the object
+     or the victim or be see_all. Reported by Sparta.
+Fixes:
+   * POWER^ and CHANNEL^ locks tested the wrong object. [SW]
+   * @grep, @wipe, and @edit now report when no attributes are
+     matched. Suggested by Procyon@M3
+   * Changes to telnet negotiation strings to match those in
+     PennMUSH 1.7.7, which seems to fix some problems with display
+     of connect.txt in some clients. Report by Howie@NewFrontier.
+     Patch by Time@M*U*S*H.
+   * @mail/silent wasn't unless you used /send too. Report by
+     Moe@Chicago.
+   * Wizards could set attributes on garbage objects (which were useless,
+     but may have leaked memory needlessly). Reported by Taz@M*U*S*H.
+   * @chan/hide didn't check for hide permissions properly in some
+     cases. Reported by Tanaku@M*U*S*H.
+   * Better explanation of when regexp matching is case sensitive vs.
+     insensitive. Suggested by Jake@BrazilMUX, Brazil@BrazilMUX, and
+     Vadiv@M*U*S*H.
+
+
+Version 1.7.6 patchlevel 5                      January 7, 2003
+
+Fixes:
+   * 1.7.6p4 broke 'go #dbref', which broke the ability of followers 
+     to follow. Reported by Ellis@M*U*S*H.
+
+
+Version 1.7.6 patchlevel 4                      January 2, 2003
+
+Minor Changes:
+   * English-style matching now applies to exits in the room
+     (so '1st down' can match the first 'down' exit if you're not carrying
+     anything that matches 'down'). New english-style matching adjective
+     'toward' restricts the match to exits (so: 'look toward 1st down').
+Fixes:
+   * Code cleanup to fix several potential buffer overflows.
+   * The wildcard matcher had problems with backslash escapes in
+     some cases, making matching a : in a $command very hard.
+     Reported by Wayne@PDX.
+   * @chzone could cause crashes on some systems.  Reported by Wayne@PDX.
+   * When two exits match, one is no longer chosen at random.
+     Instead, the ambiguity should be reported as an error.
+     Reported by Intrevis@M*U*S*H.
+   * The dbref returned by locate when given the X parameter is
+     no longer random, but the last one found (as per the help).
+   * Serious bug in reading locks from the db on startup corrected.
+   * The profiling timer is turned off duing dumps, as some systems
+     (FreeBSD?) appear to continue to use it and interrupt dumps
+     due to cpu limiting. Reported by Nathan Schuette.
+
+
+Version 1.7.6 patchlevel 3                      December 22, 2002
+
+Minor changes:
+   * call_limit now controls the maximum recursion in process_expression,
+     instead of maximum calls to p_e per command cycle. This still
+     limits stack size, but doesn't get in the way of massive code
+     nearly as much. People using a very high call_limit value should
+     probably lower it significantly. Patch by Philip Mak.
+   * Improved error messages for many database information functions.
+     Notably, several functions that require at least one argument,
+     now complain if they don't get it rather than returning silently.
+     Suggested by Intrevis@M*U*S*H. [SW]
+Fixes:
+   * @warnings are no longer shown on GOING objects. Suggested by
+     Philip Mak.
+   * Help fixes by Intrevis, Time, and Ambrosia@M*U*S*H
+   * Bug in @decomp/skipdefaults reported by Philip Mak. [SW]
+   * Tweaks to utils/mkcmds.sh [SW]
+   * home() on a room acts as described in the help. Reported by
+     Intrevis@M*U*S*H. [SW]
+   * whisper/noisy double-notified the whisperer. Reported by Philip Mak.
+   * Crash bug in @mail fixed. Reported by Titan@OtherSpace.
+
+
+Version 1.7.6 patchlevel 2                      December 17, 2002
+
+Minor changes:
+   * An invalid range argument to @search/lsearch is now coerced
+     to be the lowest or highest dbref, as appropriate. The search
+     range is also now inclusive. And lsearch(<player>) works.
+     Suggested by Philip Mak.
+   * mushcnf.dst now includes a default value for call_limit.
+     Suggested by Philip Mak.
+   * Testing for whether the mush is already running in the
+     restart script has been improved by Philip Mak.
+Internationalization:
+   * Polish translation files (partial) are now being distributed.
+Fixes:
+   * Fix to win32 warnings. [EEH]
+   * Under Win32, a failed accept() call in bsd.c would not be
+     correctly handled. Report by BladedThoth@M*U*S*H.
+   * Help fixes by Luigi@8bitMUSH, Kyieren@M*U*S*H, Intrevis@M*U*S*H.
+   * @map crash bug repoted by Philip Mak, fixed by Walker@M*U*S*H.
+   * Modifiying locks now updates the object's modification time.
+     Reported by Philip Mak.
+
+
+Version 1.7.6 patchlevel 1                      November 26, 2002
+
+Minor changes:
+   * When using @nuke to destroy a SAFE object when really_safe is "no",
+     provide a warning (but schedule destruction anyway). Suggested by
+     Cerekk@bDV.
+Fixes:
+   * VS.NET project file was defaulting to signed instead of unsigned
+     chars, causing crashes. Fixed by BladedThoth@M*U*S*H.
+     Several places where we should have cast things to unsigned to
+     avoid this kind of thing have been fixed. [SW]
+   * The *emit() functions now set the orator correctly.
+     Reported by Philip Mak.
+   * ccom and cplr are cleared after each command execution so they
+     can't be leaked as easily. Suggested by Philip Mak.
+   * Linting.
+   * If God gives the wrong password to @logwipe, provide some feedback.
+     Suggested by Cerekk@bDv.
+   * mkcmds.sh was needlessly rebuilding several autogenerated files.
+   * The rules for flag characters shown in object headers now allows
+     F_INTERNAL flags to be shown (like GOING), just the same as
+     when you get a full flag list on examine. Report by Philip Mak.
+   * Help fixes by Bird@M*U*S*H, Intrevis@M*U*S*H, Philip Mak.
+   * @search type=something would report an error AND match the entire
+     database when something wasn't object, player, exit or room. [SW]
+   * Cosmetic bug in @malias/list fixed. Report by Tanaku@M*U*S*H.
+   * The info_slave now properly obeys the use_dns setting in mush.cnf.
+     This requires a full shutdown to put into effect. Report by
+     BlaZe@M*U*S*H. [SW]
+
+
+Version 1.7.6 patchlevel 0                      November 11, 2002
+
+License:
+ * PennMUSH 1.7.6 and later is now released under the Artistic
+   License. This is an OSI-compliant open source license. See the file
+   COPYRITE for the complete license text.
+
+   Notable changes from the old license:
+   * No restrictions on commercial use
+   * No requirement to inform developers of improvements or submit
+     modifications, though it's still a nice thing to do. Note, however
+     that if you redistribute a modified version of PennMUSH, you MUST
+     include source code.
+
+   The PennMUSH devteam thanks the copyright holders of TinyMUD,
+   TinyMUSH 2.0, TinyMUSH 2.2, and TinyMUSH 3.0 for their assistance
+   in making this possible.
+Documentation:
+   * The README file has been split into README, INSTALL, UPGRADING,
+     and I18N files.
+Minor Changes:
+   * Rooms now hear remits and lemits in them, and can be pemitted
+     to. This behavior now matches that of other MUSH servers.
+   * AUDIBLE objects now propagate sound @remit'd into them.
+     Report by [SW].
+   * Added @lock/destroy to limit who can destroy a DESTROY_OK 
+     object. Suggested by Luigi@8bit.
+   * PARANOID nospoof notification now includes the name of the object's
+     owner as well. Suggested by Philip Mak.
+   * room() no longer notifies the executor of permission errors out of
+     band. It now just returns the error instead, like loc(). Suggested by 
+     Philip Mak.
+   * Creation times are now public information via ctime(); permission to
+     examine is no longer required. This allows objects to use, e.g.,
+     %#@[ctime(%#)] as a historically unique identifier of an enactor.
+     Suggested by Philip Mak.
+   * The reboot.db is now versioned. This will make it possible to
+     @shutdown/reboot across patchlevels that change the reboot.db
+     format (in 1.7.7 and later versions).
+   * Rooms on an @forwardlist now receive the message as a remit,
+     rather than a pemit. Suggested by BladedThoth@M*U*S*H.
+Fixes:
+   * More work on the great table() bug. Looks like a fix. [SW]
+   * Improved VS.NET project files by BladedThoth.
+   * Plugged a memory leak in deleting @locks. [SW]
+   * Fixed @lock-related crash bug reported by Philip Mak. [SW]
+   * General linting.
+   * process_expression ignores the [ in ansi escapes. Reported in the
+     context of #$ by Philip Mak. [SW]
+   * Internal changes to compress(), which now returns an allocated
+     string. Under Huffman compression, it should no longer be possible
+     to overflow a buffer with a pathological compression tree. Initial
+     concern voiced by Eyal Sagi.
+   * Table and ansi didn't play well together. Reported by Ellis@M*U*S*H.
+   * Config file reading should be better on Macs. Patch by Philip Mak.
+   * Obsolete checks for OLD_ANSI are removed. [SW]
+   * Crash bug in @function fixed. Report by Dallimar@Hemlock.
+   * Change to message on failed attribute flag set, to make it more
+     generic to cover all the possible failures. Report by Cerekk@bDv.
+   * Translations to some languages were broken. Fixed now. Report by
+     Sbot@M*U*S*H.
+   * QUEUE is now visible if you control an object, as promised in the
+     help. Reported by Luigi@8bit.
+   * Help fixes by Mortimer@M*U*S*H, Bellemore@M*U*S*H, Hyacinth@8bit,
+     [EEH], BladedThoth@M*U*S*H, Moe@M*U*S*H, Viila@M*U*S*H, Walker@M*U*S*H.
+   * Comment in src/Makefile fixed by Vadiv@M*U*S*H.
+   * A weird crash on @shutdown/reboot, probably attributable to a
+     broken library or system call, is now worked-around. Report by
+     Solarius@SWWF.
+   * Audible objects with @forwardlist set are no longer concealed by
+     the DARK flag.
+   * Win32 project files no longer use the win32/ directory as an include
+     directory, which causes problems. Reported by Gepht.
+
diff --git a/CHANGES.177 b/CHANGES.177
new file mode 100644 (file)
index 0000000..b797a2d
--- /dev/null
@@ -0,0 +1,1155 @@
+
+This is the most current changes file for PennMUSH. Please look it
+over; each version contains new things which might significantly affect
+the function of your server.  Changes are reported in reverse
+chronological order (most recent first)
+
+[TN] is Thorvald Natvig, a PennMUSH developer (aka Trivian)
+[TAP] is T. Alexander Popiel, a PennMUSH developer (aka Talek)
+[SW] is Shawn Wagner, a PennMUSH developer (aka Raevnos)
+[EEH] is Ervin Hearn III, a PennMUSH developer (aka Noltar)
+[LdW] is Luuk de Waard, a former PennMUSH developer (aka Halatir)
+[RLM] is Ralph Melton, a former PennMUSH developer
+[NJG] is Nick Gammon, the Win32 porter
+[DW] is Dan Williams, the MacOS porter
+[2.2] refers to code which originated with the TinyMUSH 2.2 developers
+[3] refers to code by (or inspired by) TinyMUSH 3.0
+[Rhost] refers to code by (or inspired by) RhostMUSH
+
+==========================================================================
+
+Version 1.7.7 patchlevel 32                     May 26, 2004
+
+Major Changes:
+  * SQL support. PennMUSH can now operate as an SQL client and perform
+    queries against an SQL server. Currently only the MySQL server is
+    supported. This adds the @sql command, the sql() and sqlescape()
+    functions, and the Sql_Ok power. See README.SQL for some 
+    additional information.  Mostly based on patches by Hans Engelen.
+  * Creating a leaf attribute automatically creates associated branch
+    attributes if they are not already present. [TAP]
+  * When a $command matches on an object, but the object's use-lock or
+    command-lock prevents the command from being run, the object's
+    COMMAND_LOCK`FAILURE, COMMAND_LOCK`OFAILURE, and COMMAND_LOCK`AFAILURE
+    attributes will be triggered if the $command never successfully 
+    matched, rather than returning a Huh? to the player.
+  * Exits and rooms may now run $commands. Rooms are treated as being
+    located in themselves for purposes of location checks. Exits are
+    treated as being located in their source room. Suggested by [TAP].
+Commands:
+  * 'empty <object>' attempts to get each item in <object> and put
+    it alongside <object> (in <object>'s location).
+  * 'give <object> to <player>' syntax added.
+Minor Changes (user-visible):
+  * @COST attribute is now evaluated, so you can make costs depend
+    on who's paying, a selected item, etc. Suggested by Walker@M*U*S*H.
+    Also, the amount given is passed in as %0, so you can code
+    vendors that accept any amount.
+  * New OBJID^<objid> lock atom.
+  * The server now maintains a rolling log of activity (commands issued,
+    evaluations parsed, and locks evaluated), that is dumped to the log 
+    file on panic, or can be seen by God with @uptime. This aids 
+    debugging code that causes a "clean" panic rather than a crash. 
+    Suggested by Intrevis@M*U*S*H.
+  * When checking a use/command/listen-lock on an object with patterns
+    that get matched, we only check the lock once and cache the result,
+    to prevent multiple lock evaluations if multiple patterns match. [TAP]
+  * @chan/recall now shows nospoof tags for @cemit'd lines.
+    Suggested by Sholevi@M*U*S*H.
+  * SUSPECT flag can now be applied to any type of object.
+    Suggested by Oriens@Alexandria.
+Minor Changes (internals):
+  * fun_escape() and fun_secure() use the same list of special characters, 
+    rather than each having their own copy. [SW]
+  * Buffer queue code used by @chan/buffer and the activity log refactored
+    into src/bufferq.c and hdrs/bufferq.h.
+  * Added mush_panicf(), with printf()-style format and arguments. [SW]
+Fixes: 
+  * @scan correctly shows attributes on parents again. Report by
+    Wayne@PDX.
+  * @shutdown/panic and @shutdown/paranoid work again. [SW]
+  * A panic DB could be created before the database files were actually read,
+    causing problems on the next restart. [SW]
+  * Win32 and Debian installer portability fixes. [EEH]
+  * Code cleanup around errno. [SW]
+  * The locate() function now respects visibility and interactions.
+    Report by Jules@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 31                     May 11, 2004
+
+Minor Changes:
+  * netmush is now started with only a single argument - the path to
+    the configuration file. The error log file (typically game/netmush.log)
+    is now configured in mush.cnf. Suggested by Vadiv@M*U*S*H.
+  * The restart script now bases its decision about whether the mush
+    is already running on the full path to the configuration file,
+    which means you can leave mush.cnf named mush.cnf without fear
+    of restart problems when multiple mushes are using the same
+    host. This also facilitates make update. Suggested by Vadiv@M*U*S*H.
+  * The GAMEDIR environment variable can be passed to 'make update'
+    to cause it to update *.cnf files in directories other than
+    game/ (using the template *.dst files in game/). 
+    E.g.: make GAMEDIR=/home/othermush/game update
+Commands:
+  * @nscemit. Suggested by Mystery8@ST:AW.
+Functions:
+  * nscemit(). Suggested by Mystery8@ST:AW.
+Flags:
+  * New HEAVY admin flag, prevents an object from being teleported
+    by a mortal between two containers they own. Admin without this
+    flag can now be teleported.
+Fixes:
+  * Help fixes by Anri@AkaneaMUSH and Intrevis@M*U*S*H.
+  * mix() now treats empty lists as empty, instead of containing a single
+    null element. Report by Luke@M*U*S*H.
+  * @power messages no longer reference 'flags'. Report by Nymeria@M*U*S*H.
+  * Crash bug with @clone in new power system fixed.
+
+
+Version 1.7.7 patchlevel 30                     May 6, 2004
+
+Major changes:
+  * CHAT_SYSTEM option removed. If you don't want to use the chat system,
+    use restrict.cnf to disable @channel, @chat, etc.
+  * USE_MAILER and MAIL_ALIAS options removed. If you don't want to 
+    use the @mail or @malias systems, use restrict.cnf to disable
+    the associated commands.
+  * QUOTA, EMPTY_ATTRS, and FUNCTION_SIDE_EFFECTS options are now 
+    runtime options, instead of compile-time.
+  * SINGLE_LOGFILE option removed, and log filenames are now 
+    runtime options. You may now give the same name to
+    multiple log files and get a more fine-grained version of the
+    same effect. Based on ideas by Vadiv@M*U*S*H.
+Minor changes:
+  * New IP^ and HOSTNAME^ tests for boolexps. Suggested by Luke@M*U*S*H.
+  * ALLOW_NOSUBJECT option removed. We always use the beginning of the
+    message as the subject if one is not provided.
+  * JURY_OK and UNINSPECTED_FLAG options removed. Use @flag to add
+    flags if you need them. ONLINE_REG and VACATION_FLAG options
+    removed (default to always defined, add or remove with @flag as
+    desired).
+  * MEM_CHECK option removed from options.h; it is now a runtime
+    option in mush.cnf.
+  * @function/restrict can be applied to softcoded @functions, and
+    @function/add can accept a list of restrictions as a fifth argument.
+    Patch by Luke@M*U*S*H.
+  * log_walls run-time configuration option removed. Use the
+    logargs option in restrict.cnf instead.
+Fixes:
+  * Crash bug in anonymous attributes fixed. Report by Intrevis@M*U*S*H.
+  * lplayers() was broken. Report by T'orA@M*U*S*H.
+  * Failing to create a player by providing a bad password now gives
+    a better error. Suggested by [NJG].
+  * Setting/clearing a chan title on a notitles channel works, but
+    reminds you that titles are off. Suggested by Dan@InNomine.
+  * haspower_restricted removed from mushcnf.dst to stop spurious
+    warning on startup. Report by Nymeria@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 29                     April 28, 2004
+
+Major changes:
+  * Anonymous attributes via #lambda. See help anonymous attributes. [SW]
+  * Wizards (other than God) and royalty are no longer treated as No_Pay
+    unless the No_Pay power is explicitly set on them, although they
+    can still give (themselves or others) as many pennies as they wish.
+    This helps stop runaway wizards in the queue (they'll run out of cash
+    like anyone else). To get the old behavior back, @power your admin
+    No_Pay. You probably want to @power any globals that use search(),
+    children(), mail*stats(), etc, No_Pay as well. Suggested by Walker@M*U*S*H.
+  * game/restrict.cnf, alias.cnf, names.cnf are renamed in the tarball and
+    made with make update like mush.cnf. Suggested by Philip Mak. [SW]
+  * @powers now operate under the same code as the @flag system, so God
+    can add and modify powers in the MUSH with @power/add, etc. 
+Commands:
+  * @nsemit, @nsoemit, @nslemit, @nsremit, @nszemit and function forms
+    of the same. Suggested by Cloud@M*U*S*H.
+Functions:
+  * andlpowers(), orlpowers(), andpowers(), orpowers().
+  * align() performs fancy text alignment tricks. Patch by Walker@M*U*S*H.
+  * sent() and recv() show more player descriptor data from SESSION.
+    Suggested by Ricochet@M*U*S*H.
+  * scan() with a single argument assumes the executor's point of
+    view. Suggested by Cheetah@M*U*S*H.
+  * valid() can also check for syntactically correct passwords,
+    command names, and function names.
+Minor changes:
+  * No more CRYPT_SYSTEM in options.h. We try everything and we
+    always reset passwords to SHS. Patch by Vadiv@M*U*S*H.
+  * Wizards and other privileged players can @chan/recall on channels they're
+    not on. Suggested by Trispis@M*U*S*H. [SW]
+  * A separate ip address to bind the SSL port to can now be specified
+    in mush.cnf.
+  * @flag/type allows God to change flag types. Suggested by Renee@ShoreSide.
+  * After each @startup is enqueued (during startup or @restart/all),
+    we immediately run up to 5 queue cycles. This allows, e.g., God's
+    @startup to up to five levels of @dol/@tr/@switch/etc and still have
+    the queued code run ahead of other startups. This requires that you
+    keep God's dbref as #1. Based on comments by Philip Mak and o
+    Trispis@M*U*S*H.
+  * The message after successful @password is now clearer, to avoid
+    confusion in some unusual cases. Report by Kholnuu.
+  * Makefile is no longer set executable. Report by Luke@M*U*S*H.
+  * Server now handles telnet NOP and AYT commands. Suggested by
+    Philip Mak and Liz (author of Muckclient).
+  * announce.c is no longer distributed. portmsg.c cleanup.
+Fixes:
+  * @undestroy on an invalid (garbage, or !going) object now produces
+    an error message. Suggested by T'orA@M*U*S*H.
+  * On shutdowns, all queue deposits are now refunded.
+  * Ansi behavior on second and later lines of text fixed by Walker@M*U*S*H.
+  * Database reading improvements for win32 - ideally, you should now
+    be able to read a database written on win32/unix on either system.
+  * allof() now evaluates its separator argument. [SW]
+  * firstof() doesn't add an extra space before the value it returns. [SW] 
+  * Slackware portability fixes by Dale @ Wolfpaw.net hosting
+  * ]page properly noevals (the right hand side) now. Report by 
+    Cheetah@M*U*S*H.
+  * Partial channel match listings no longer reveal channels the player
+    isn't allowed to see. Report by Taz.
+  * Help fixes by Trispis@M*U*S*H and Tanaku@M*U*S*H.
+  * ssl() and terminfo() don't work on other players unless you're
+    See_All, as promised. Based on patch by Tanaku@M*U*S*H.
+  * lcon, etc. all do an INTERACT_SEE interaction check now.
+    Suggested by Thor@bDv.
+  * Code cleanup. [SW]
+  * Fixes from 1.7.6p16
+
+
+Version 1.7.7 patchlevel 28                     March 12, 2004
+
+Major changes:
+  * You can add customized configuration parameters to set in mush.cnf
+    by adding a couple of new lines into the local_configs() function
+    in local.c. (YOU MUST UPDATE YOUR local.c FROM local.dst IN THIS
+    PATCHLEVEL). Patch by grapenut@M*U*S*H.
+  * Object ids: An object id is the string "#dbref:ctime"
+    where #dbref is the object's dbref and ctime is its creation time
+    in integer format. The %: substitution returns this
+    id for the enactor, and the objid() function returns it for an
+    arbitrary object. Object ids can be used in place of softcode that 
+    stores dbrefs to insure that a recycled dbref isn't used in place
+    of the intended one. The matcher code will also match objects by
+    id any time it's matching by dbref.
+  * @command/add and @command/del. You can add a custom command
+    (which will have the same precedence as a standard server command),
+    and then @hook it to softcode, effectively promoting the precedence
+    of softcoded globals, and letting them take advantage of some
+    command parser settings. Patch by Walker@M*U*S*H.
+Functions:
+  * tr() accepts ranges of characters like a-z instead of having to
+    give each one. [SW]
+  * escape() also escapes parens and commas now. Suggested by Philip Mak. [SW]
+  * time() can now take a time offset or object argument (in the latter
+    case, time offset is read from object's TZ attribute). Patch by
+    Walker@M*U*S*H.
+  * vcross() performs cross products of vectors. [SW]
+  * merge() can now take a list of characters. [SW]
+Minor changes:
+  * You can @set multiple flags at once by giving them as a list.
+    Suggested by Walker@M*U*S*H and others.
+  * Channel names are recognized when surround by <>'s, too. [SW]
+  * 'move' is now a command_alias for 'goto' (in alias.cnf), and not
+    a separate command.
+  * PAGE_LOCK`{,O,A}FAILURE attributes now activated when a page/pemit fails
+    due to the victim's @lock/page. Suggested by Sholevi@M*U*S*H.
+  * Tweaked game message for failing to provide correct password to
+    @password. Suggested by Philip Mak.
+  * New command 'warn_on_missing' (defaults to disabled), aliased to the
+    '[' character. If enabled, players who attempt to write commands
+    starting with functions will get errors. Suggested by [SW] and
+    Cheetah@M*U*S*H.
+  * Renaming something triggers its ONAME and ANAME attributes, if present.
+    The old name is passed as %0; the new as %1. Suggested by Philip Mak.
+  * Owner information on ex/br is reported using the usual object_header()
+    so dbref and flags appear. Suggested by Eratl@M*U*S*H.
+  * Flags that are F_DARK or F_MDARK no longer appear on @flag/list
+    by non-admin. Suggested by Philip Mak.
+  * Warn players who set themselves SHARED with a weak zone lock.
+    Suggested by Philip Mak. [SW]
+  * @halt can now take "here". Suggested by Thor@bDv.
+  * When parsing eqsplit commands, don't evaluate the left side
+    of the equal sign if the command was run with ].
+Fixes:
+  * Fixes to robustify file reading on Windows systems.
+  * The 'nofixed' command restriction works as expected now (previously,
+    you had to use 'nofix').
+  * Exit movements are now translated into explicit GOTO commands,
+    so @hooks and restrictions on GOTO are now applied. Patch by
+    Walker@M*U*S*H.
+  * The AE/ae accent characters can now be produced (accent(a,e)).
+    Patch by Luke@M*U*S*H.
+  * @hook/ignore would double-evaluate arguments. Reported by 
+    Ambrosia@M*U*S*H. [SW]
+  * Mingw error in src/Makefile.SH fixed. Report by Thor@bDv. [SW]
+  * Help fixes by Cerekk@bDv, Mike Griffiths, Steve Varley, Thor@bDv, [SW],
+    Dahan, Jason Stover, and Kyieren@M*U*S*H.
+  * cmdlocal.dst now includes flags.h. By Dahan.
+  * Win32 portability fixes by Dahan, Nathan Baum, [EEH].
+  * utils/mkcmds.sh is now smarter about choosing temp filenames, so
+    parallel make should work. Fixed by Cheetah@M*U*S*H.
+  * The Zone: data in examine could be wrong.
+
+
+Version 1.7.7 patchlevel 27                     January 25, 2004
+
+Minor Changes:
+  * New etimefmt() formatting codes to make it easier to get nice-looking
+    results without 0-valued times. Suggested by ranko_usa@M*U*S*H. [SW]
+  * Autodetect existence of /usr/kerberos/include to make compile 
+    easier for RH9 sufferers.
+  * src/Makefile is now autobuilt from src/Makefile.SH. IF you use
+    local hacks that require src/Makefile, this is likely to be a problem
+    for you. You'll want to start patching Makefile.SH instead.
+  * Fewer warning flags are now provided to the compiler by default.
+    You can set your own warning flags instead by defining the
+    warnings variable in config.over.
+Fixes:
+  * The startups option actually does what it's supposed to now.
+  * Potential DOS in etimefmt fixed. Report by Ashen-Shugar. [SW]
+  * Code cleanup. ok_tag_attribute should work. [SW]
+  * Channels are automatically ungagged only on initial connection
+    (not reconnection, partial disconnection, etc.). Suggested by
+    Mordie@M*U*S*H.
+  * notify() calls during startup would crash. Reported by Mordie@M*U*S*H. [SW]
+  * Fixes from 1.7.6p15.
+
+
+Version 1.7.7 patchlevel 26                     December 15, 2003
+
+Commands:
+  * Add /regexp switch to @switch and @select. Suggested by BladedThoth. [SW]
+  * New /spoof switch to @pemit, @remit, @lemit, @oemit, @emit,
+    causes the message to appear to be generated by the cause, rather
+    than the enactor, which makes globals like $ooc show the right
+    NOSPOOF information (instead of the name of the global command object).
+    Patch by Philip Mak.
+Functions:
+  * hostname(), ipaddr(), and cmds() take a dbref or descriptor number
+    of a connected player and return the hostname, ipaddr, and number
+    of commands executed. Suggested by Sholevi@M*U*S*H and Renee@ShoreSide,
+    code by Sholevi@M*U*S*H.
+  * Add reswitch*() functions. Suggested by BladedThoth. [SW]
+  * insert() can take a negative position argument to insert from
+    the right. Patch by Sholevi@M*U*S*H.
+  * New firstof() and allof() functions return the first true value
+    or all true values from a set of expressions. Patch by Sholevi@M*U*S*H.
+  * tr() works like the Unix utility, translating characters. Patch
+    by Walker@M*U*S*H.
+Attributes:
+  * Attributes may be set DEBUG to cause their evaluation to be
+    debugged selectively. Patch by Pozzo@SWForum.
+  * @desc can no longer be gotten remotely without privileges.
+    To implement this, a new attribute flag NEARBY was added,
+    which prevents visual attributes from being remotely accessed.
+    See new configuration directive 'read_remote_desc' if you prefer
+    the old behavior. Patch by Philip Mak.
+  * @desc on privileged objects can now be evaluated by mortals.
+    To implement this, a new attribute flag PUBLIC was added,
+    which overrides safer_ufun for that attribute. This flag is dangerous
+    and should be avoided unless you are sure you know what you're doing.
+    Patch by Philip Mak.
+Minor Changes:
+  * "+<channel> <text>" complains if <channel> is ambiguous, and
+    displays a list of matching channels. Patch by Luke@M*U*S*H.
+  * Code cleanup around @oemit by Philip Mak.
+  * If an object has an IDESCFORMAT but no IDESCRIBE, interior viewers
+    now see the DESCRIBE formatted by IDESCFORMAT (instead of
+    the DESCRIBE formatted by DESCFORMAT). Suggested by Philip Mak.
+  * Ported to Win32 with the Mingw development environment. 
+    See win32/README.mingw for compile instructions. [EEH]
+  * null() can now take any number of arguments. Patch by Walker@M*U*S*H.
+  * Using @chan/quiet to control the quiet flag on a channel no longer works
+    (Actually, it never did). Use @chan/priv instead. [SW]
+  * The NO_WARN flag now prevents disconnected room warnings, too.
+    Suggested by several people. Patch by Philip Mak.
+  * @sitelock/name !name still unlocks a reserved name, but no longer 
+    removes that name from names.cnf. Suggested by Nymeria. [SW]
+  * Some cleanup of fopen() calls. [SW]
+  * The reference to channel Creator is now Owner in @chan/decomp.
+    Suggested by Howie@NFTrekMUSH. [SW]
+  * The name of the channel being tested is passed as %0 in channel locks.
+    Suggested by Philip Mak. [SW]
+  * help for @chownall mentions the /preserve switch.  Warnings from @chown 
+    and @chownall tell which objects they're for. Suggested by
+    Mordie@M*U*S*H. [SW]
+  * Home wrecking is allowed again. Suggested by Philip Mak.
+Fixes:
+  * Puppets in containers with @listen container=* now hear the
+    outside world correctly. Patch by Philip Mak.
+  * The email sent on player registration now double-quotes the
+    player name in the example connect statement. Suggested by
+    David Kali.
+  * Two rooms should never be nearby() each other. Fix by Philip Mak.
+  * can_look_at() now handles the look/out case, too. Fix by Philip Mak.
+  * Help fixes by Viila@M*U*S*H, MetBoy@M*U*S*H, Cheetah@M*U*S*H.
+  * @flag/alias or @flag/add without giving an alias no longer crashes 
+    the MUSH.  Report by Wildfire.
+  * Correctly retrieve user full name from /etc/passwd fields under
+    Linux in Configure. Record it in config.sh. Reported by Vadiv@M*U*S*H.
+  * Debian package changes. [EEH]
+  * As the help promises, XCH_CMD and SEND attributes don't work
+    for non-Wizards. Really.
+
+
+Version 1.7.7 patchlevel 25                     October 30, 2003
+
+Fixes:
+  * Crash bug in the interaction between parents and attr trees
+    fixed. Report by Walker@M*U*S*H.
+  * Improvements to how locks are decompiled. Built-in locks with the
+    no_inherit flag cleared are handled better.  If the object doing
+    the @decompile is referenced in a lock key, it's decompiled as 'me'
+    rather than by dbref, to make it easier to port between games.
+    Report by Cerekk@bDv. [SW]
+  * The error typically logged by info_slave on a mush crash is worded 
+    better. [SW]
+  * panic() renamed mush_panic() as the Mach kernel now has a public
+    system call panic(). Report by Jeff Ferrell.
+
+
+Version 1.7.7 patchlevel 24                     October 19, 2003
+
+Minor Changes:
+  * The puppet flag can now apply to rooms. Suggested by Philip Mak.
+  * @tel/inside allows priv'd players to teleport into another player's
+    inventory (instead of to their location). Suggested by Philip Mak.
+Fixes:
+  * Startups from a created minimal.db did not properly initialize
+    the objdata htab, so subsequent use of that htab (e.g. adding
+    players to channels) would crash. Report by [LdW].
+  * Help fixes by BladedThoth@M*U*S*H and Philip Mak.
+  * Attempting to clear a branch attribute when it has leaves now
+    gives a better message. Also better message for inability to
+    write an attribute due to tree issues. Patch by Luke@M*U*S*H.
+  * Bug in @debugforwardlist fixed by Luke@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 23                     October 10, 2003
+
+Major Changes:
+  * Forking dumps now work with the chunk memory manager. [TAP]
+Minor Changes:
+  * Chunk allocator improvements in partial read/writes and for
+    Win32. [TAP]
+  * Use the SHA0 algorithm from OpenSSL instead of our own when possible. [SW]
+Functions:
+  * New digest() function for generating checksums via a variety of 
+    algorithms. [SW]
+  * cowner() returns the dbref of a channel's owner. Suggested by
+    Sholevi@M*U*S*H. [SW]
+  * Added baseconv(), for converting numbers from one base to another.
+    Useful for tweaking things like Myrddin's bboard code. Suggested
+    by many people. [SW]
+Fixes:
+  * Remove warnings in set.c and pcre.c. Report by Nymeria@M*U*S*H
+  * @power messages now show the canonical name of the power.
+    Suggested by Cheetah@M*U*S*H.
+  * Adding DARK players to channels produced a duplicated message.
+    Reported by Mystery8@ST:AW.
+  * Players set WIZARD and ROYALTY would get double notification 
+    of some GAME: messages. Fixed now. [SW]
+  * Chatdb corruption possible when admin add players to channels.
+    Reported by several folks, diagnosed by Mordie@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 22                     September 30, 2003
+
+Minor Changes:
+  * When possible, sockets are now set SO_KEEPALIVE and TCP_KEEPIDLE.
+    This may help prevent disconnections of idle players behind
+    poorly designed NAT routers and the like, and make the use of
+    the IDLE command unnecessary. Patch by Philip Mak.
+  * Better failure messages for 'get blah's blah', especially when
+    the container or contained object is ambiguous. Suggested by
+    Philip Mak. 
+Fixes:
+  * Attempting to unset attribute flags actually set them.
+    Report by Ambrosia@M*U*S*H. [SW]
+
+
+Version 1.7.7 patchlevel 21                     September 23, 2003
+
+Major Changes:
+  * Attribute trees. Attributes may now be organized hierarchically
+    like files and directories in a filesystem, using the backtick (`)
+    character as the branch separator. Attribute access restrictions
+    propagate down trees. New wildcard ** is introduced to match
+    all attributes at all tree levels. Suggested by Tabbifli. [TAP]
+Locks:
+  * New framework for performing lock failure activities in hardcode.
+    As a proof-of-concept, the attributes FOLLOW_LOCK`FAILURE,
+    FOLLOW_LOCK`OFAILURE, FOLLOW_LOCK`AFAILURE do what you'd expect
+    when set on a potential leader.  Suggested by Sholevi@M*U*S*H.
+Channels:
+  * New per-channel flags NoTitles, NoNames, and NoCemit do what you'd
+    expect. Set them with @chan/privs. Based on suggestion by 
+    Saturn@M3.
+  * @chan/recall/quiet omits timestamps. Suggested by Vadiv@M*U*S*H.
+Commands:
+  * 'help <wildcard-pattern>' now lists all help topics that match that 
+    pattern.  By popular request. [MUX,SW] 
+  * @flag/letter can be used to change or clear the one-letter alias for a 
+    flag.  Suggested by Nymeria@M*U*S*H. [SW]
+  * @flag/list by God notes disabled flags. Suggested by Nymeria@M*U*S*H. [SW]
+Functions:
+  * rand() now comes in a two-argument (low,high) flavor, and randword()
+    selects a random word from a list. The latter is aliased to
+    pickrand() to match Mux's name. Patch by Luke@M*U*S*H.
+Minor Changes:
+  * Although we're Pueblo 2.50 compliant, go back to sending Pueblo 1.10
+    as the server version until everyone upgrades their clients so
+    they can handle the 2.50 string. Suggested by Shirow.
+  * The locate() function is no longer noisy (no longer notifies
+    the executor in addition to returning a value). Suggested by
+    Mystery8@ST:AW. 
+  * @lock/interact now has a higher priority than other interaction
+    checks, so it will work for Wizards. Suggested by Viila@M*U*S*H.
+  * Tweaks to facilitate a Debian package of PennMUSH. [EEH]
+Fixes:
+  * max descriptor could get stomped in some cases. [SW]
+  * Removed extra struct def in hdrs/mushtype.h. Suggested by Kyle.
+  * Help tweak by Kevin@M*U*S*H.
+  * Fix to locks on players messing up their connection failure counts.
+    Reported by Luke@M*U*S*H.
+  * Fix to @entrances by Luke@M*U*S*H.
+  * Fix to Win32 not handling a missing minimal.db by Luke@M*U*S*H.
+  * The confirmation message for setting/clearing attribute flags would use
+    the flag name given as an argument, not neccessarily the the full name of
+    the flag. Reported by Vadiv@M*U*S*H.  [SW]
+  * Fix a potential memory leak in ident.c [SW]
+
+Version 1.7.7 patchlevel 20                     September 4, 2003
+
+Major Changes:
+  * minimal.db is no more. If you start up the server and there's no
+    db to be found, it creates a new minimal database in memory
+    on the fly. Feel free to delete your coopy of minimal.db. [SW]
+    (In related news, the default OSUCC on #0 in minimal.db is gone. 
+    Suggested by Cheetah@M*U*S*H. So much for the mists.)
+Minor Changes:
+  * SSL connections are now ended before the dump when rebooting,
+    but their descriptor information sticks around to ensure that
+    their @adisconnect is run after the reboot. Based on suggestions
+    by Vadiv@M*U*S*H and [TAP].
+  * When _DEBUG is defined in a win32 build, don't copy pennmush.exe
+    to pennmush_run.exe. Makes debugging easier. Patch by Luke@M*U*S*H.
+  * In pueblo mode, no longer clear past images after each room look.
+    Patch by Konstantine Shirow.
+  * In pueblo mode, we no longer render ansi attributes or colors with
+    HTML tags; we assume the client will correctly handle them as ansi
+    intermixed with Pueblo. This solves a variety of problems.
+    Based on a discussion with Konstantine Shirow.
+  * When matching $commands and ^listens, insure that a matching attribute
+    exists before testing locks. This may break some strange locks using
+    %c, but hopefully won't. Suggested by Cheetah@M*U*S*H.
+  * The @scan command temporarily sets %c to the command to be scanned
+    (without "@scan" prefixed) so that it can detect commands that use
+    %c-matching. Based on ideas from Cheetah and Walker@M*U*S*H.
+  * Added support for Pueblo's md5 checksum. We track it on each
+    descriptor, though we don't do anything with it currently.
+  * Speed-up to fun_merge. Patch by Walker@M*U*S*H.
+  * Internal cleanup of flags in channel_broadcast.
+  * Wizards may @name themselves to names forbidden in names.cnf.
+    Patch by LeeLaLimaLLama@M*U*S*H.
+  * We are now forgiving of stupid non-RFC-compliant telnet clients
+    that send CR only even when they claim to be sending CRLF
+    (Read: MS Windows XP telnet). MS bug reported first by
+    Intrevis@M*U*S*H.
+  * Misleading restrict_command on @clone removed from restrict.cnf,
+    as @dig/@open/@create permissions already control @clone.
+    Suggested by Philip Mak.
+Functions:
+  * lstats() and quota() now allow objects to see stats/quota on other
+    objects they control (e.g., on their owners, perhaps). Suggested
+    by Philip Mak.
+  * hastype() can now test for type GARBAGE. Suggested by Tanaku@M*U*S*H.
+Commands:
+  * The new ']' command prefix causes the rest of the command-line
+    not to be evaluated by the expression parser. Try: ]think [add(1,1)]
+    Inspired by Elendor.
+  * @hook/ignore. [mux]
+  * @hook/override [Rhost, though they don't call it that]
+Attributes:
+  * @debugforwardlist forwards DEBUG output to dbrefs on the list.
+    Suggested by Balerion@M*U*S*H.
+Tests:
+  * A new test harness for developing regression test suites in perl
+    for PennMUSH is now included; few test suites are. If you can figure
+    out how to use this, write some tests for us! (If you can't, don't
+    ask about it, please :)
+Locks:
+  * @lock/interact can prevent other players from transmitting any
+    normal sound to you (that is, you won't hear them speak, pose, 
+    emit, etc., like gagging them in a client). It doesn't control
+    page (use @lock/page) or @mail (use @lock/mail). You still
+    hear 'presence' messages (connects/disconnects/arrivals/leaves)
+    so you can have a sense of who can hear you but you can't hear.
+    Patch by Philip Mak.
+Fixes:
+  * Configure script no longer keeps adding -lssl -lcrypto over and
+    over to the Makefile. Report by Sholevi@M*U*S*H.
+  * Portability fixes for compiling with MSVC5 by Luke@M*U*S*H.
+  * The behavior of spaces inside parentheses that aren't part of a
+    function has been improved. Report by Jason Newquist. [TAP]
+  * txt/*.html files were being improperly escaped before being
+    sent to Pueblo connections. Report by Konstantine Shirow.
+  * mushdb.h includes flags.h, as it relies on constants from there.
+    Suggested by Philip Mak.
+  * Better error reporting on some failure messages in chunk allocator.
+    Patch by Philip Mak.
+  * @config/set with an invalid option now returns an error.
+    Report by Sholevi@M*U*S*H.
+  * Fix to interaction checking in notify_anything that could result
+    in the wrong check being performed. Report by Philip Mak.
+  * Help fixes by LeeLaLimaLLama@M*U*S*H, Sunny@M*U*S*H, Sketch@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 19                     August 19, 2003
+
+Fixes:
+  * When SSL connections are dropped on reboot, other players weren't
+    seeing disconnect messages. Now they are.
+  * Two calls to flaglist_check_long didn't get converted to the new
+    abstraction layer, making flag_broadcast not work right.
+    Report by Luminar@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 18                     August 19, 2003
+
+Major Changes:
+  * The flag handling code has been additionally abstracted to 
+    allow, in a future patchlevel, @powers to be handled in the
+    same way that flags are now.
+Minor Changes:
+  * Wrap the OS-dependant code for making sure the mush always has a free
+    file descriptor available for use in a pair of functions. [SW]
+Fixes:
+  * Linted some ssl-related warnings. Reported by Cheetah@M*U*S*H.
+  * Compile failed in timer.c unless USE_MAILER was defined.
+    Reported by Sunny@M*U*S*H.
+  * Bug allowing players to view internal and mortal_dark attributes
+    introduced in p17 has been fixed. [TAP]
+
+
+Version 1.7.7 patchlevel 17                     August 11, 2003
+
+Major changes:
+  * SSL support, including server and client authentication as options.
+    This should be considered experimental.  New ssl() function returns 1
+    if a player/descriptor is using SSL, 0 otherwise. New file README.SSL
+    documents how to set up SSL. Two things to note:
+    - SSL connections are dropped on @shutdown/reboot, so SSL users may 
+      wish to write client triggers to reconnect. 
+    - Output flushing behaves slightly differently for SSL connections.
+  * The way lock keys are handled internally has been rewritten to allow
+    them to be tracked by the same chunk-management code that handles
+    attributes. [SW]
+  * @mail message texts are now stored with the chunk-management code. [SW]
+  * The new code can no longer read databases created by versions of Penn
+    before 1.7.5p0. If you need to do this, load it in 1.7.6, shutdown,
+    and use that database. [SW]
+Minor changes:
+  * A new minimal.db is distributed (older ones probably won't work,
+    and this one probably won't work with older versions). [TAP]
+  * Contents/exits lists and functions that use first_visible (like
+    lcon, lexits, etc.) are now affected by INTERACT_SEE. Suggested by
+    Prospero@Metro.
+  * @chan/recall now displays the last 10 lines by default. Use
+    @chan/recall <chan>=0 to get the full buffer. Suggested by [TAP].
+  * Various boolexp tweaks. [SW]
+  * @power provides more verbose feedback. Suggested by Mordie@M*U*S*H. [SW]
+  * Additional chunk tweaks, including limiting migrations to one
+    per second. [TAP]
+  * PROFILING #define for use when profiling the code (surprise). This
+    just disables the CPU timer.
+  * When matching $-commands, only call the slower capturing wildcard match
+    function when we already know it succeeds, since it won't most of the time.
+    The faster non-capturing function is checked first to find a match. [SW]
+  * PCRE is initialized once, at startup, rather than testing to see if it has
+    to be done before each place regular expressions are used. [SW]
+  * The TERSE flag now applies to objects, not just players. Suggested
+    by Aur@M*U*S*H.
+  * The terminfo() function now returns SSL status as well.
+  * help-like commands without arguments now use the command name
+    as the argument. E.g. 'news' now looks for topic 'news', instead of
+    'help'. If not found, we fall back on help. Patch by Mike Griffiths.
+  * When an object is zoned to an unlocked ZMO, the ZMO is now autolocked
+    to =me instead of $me, because it's probably less confusing.
+    Suggested by Luke@M*U*S*H.
+  * @name now automatically trims leading and trailing spaces from its
+    newname argument, to avoid confusing people who use edit() to 
+    generate new names and leave in spaces. Report by [TAP], patch by [SW].
+Commands:
+  * New 'logargs' and 'logname' restrictions in restrict.cnf allow
+    per-command logging. Suggested by Saturn @ M3.
+Functions:
+  * The sha1() function has been renamed sha0(), since that's what it
+    really does. [TAP]
+  * trimtiny() and trimpenn() work like trim(), but force the TM or
+    Penn argument style.
+  * New 'logargs' and 'logname' restrictions in restrict.cnf allow
+    per-function logging. Suggested by Saturn @ M3.
+Fixes:
+  * The side-effect version of name() was producing a weird return
+    value. Reported by Saturn@M3.
+  * Problems with restrict_command and restrict_function with multiple
+    restrictions now fixed. Report by Sholevi@M*U*S*H.
+  * stddef.h now included in src/extchat.c. Report by [EEH]. [SW]
+  * INFO output now includes a missing newline. Report by [EEH]. [SW]
+  * help BUILDER updated. Report by Laura Langland-Shula.
+  * down.txt message wasn't being sent. Report by Sholevi@M*U*S*H. [SW]
+  * New Win32 project files that include the chunk code. [EEH]
+  * @chan/what no longer highlights the channel's name, as this is
+    confusing if you use ansified channel names. Suggested by 
+    Oriens@Alexandria.
+  * ex/mortal now operates more correctly. Report by Amy Kou'ai.
+  * Fixes from 1.7.6p13.
+
+Version 1.7.7 patchlevel 16                     June 23, 2003
+
+Commands:
+  * New switch /room for 'with' to run a $command available in a remote
+    location. Suggested by Mike Griffiths. [SW]
+Functions:
+  * Added the terminfo() function, which returns information about a
+    client's name and capabilities for a particular connection. [SW]
+  * New lports() function. Like lwho() but returns port descriptors.
+    For mux2 compatibility. [SW]
+  * Functions that return information about a connected player now treat
+    integer arguments as a port descriptor to look up, rather than using
+    the least-idle connection of a player. To force a player name to
+    be treated as such even when it's a number, prefix it with a * in
+    softcode. For mux2 compatibility. [SW]
+  * Players can use ports() on themselves and use the descriptors
+    they're connected to as arguments to the information functions.
+    For mux2 compatibility. [SW]
+Minor Changes:
+  * Compute various chunk stats (total used, total free space, etc.)
+    on demand instead of keeping running totals. [TAP]
+  * @chan/what displays information about a channel's recall buffer, if any.
+    [SW]
+  * @chan/recall'ed lines are more clearly marked as such. Suggested by
+    Oriens@Alexandria. [SW]
+  * Consolidation of a common idiom used to format times throughout the source
+    into a simple function call. [SW]
+  * The time a @mail was sent was stored, unusually, as a string.
+    No longer. Now it's handled the same way as all other times. [SW]
+Fixes:
+  * Bug in resizing @chan/recall buffers fixed. Reported by Oriens@Alexandria.
+    [SW]
+  * Objects with user-defined locks had problems with finding built-in locks
+    on the object. Reported by Walker@M*U*S*H. [SW]
+  * Unregistered() macro was checking wrong flag name. Report by
+    Matt Kirby.
+  * Help fix by Adu@M*U*S*H.
+  * Potential problem with ambigious names in the information functions fixed.
+
+
+Version 1.7.7 patchlevel 15                     June 1, 2003
+
+Fixes:
+  * Problem with checking command flag masks when the number of
+    flags was an even multiple of 8. Reported by Nymeria and
+    Mordie@M*U*S*H.
+  * Tweak to improve efficiency of ancestor checking code and delint
+    warning reported by Cheetah@M*U*S*H.
+  * SESSION output no longer misaligned with 5-digit dbrefs.
+    Reported by Cheetah@M*U*S*H. [TAP].
+  * Fixes from 1.7.6p11.
+  * game/txt/index-files.pl now uses locale information in the
+    environment to, e.g., correctly lowercase accented characters.
+    Report by Krad@M*U*S*H.
+  * Modified several Makefile.SH targets to prevent Javelin from
+    releasing patches that don't have the autogenerated files
+    up-to-date for Windows folks.
+  * Removed some dependence on typedefs that may or may not be in system
+    header files. [SW]
+  * Patch compiler warnings. [SW,EEH]
+  * Help fixes by Mike Griffiths and Oriens@Alexandria.
+
+
+Version 1.7.7 patchlevel 14                     May 22, 2003
+
+Major changes:
+  * Ancestors: an object can be configured to serve as an 'ultimate
+    parent' of every room, exit, thing, or player, and attributes
+    that are not found on an object or any of its parents may be inherited
+    from the ancestor for that object type.  The ORPHAN flag prevents
+    ancestors from being used on an object.  Patch by Walker@M*U*S*H.
+  * Mail messages now track not only the dbref of the sender but the
+    sender's creation time, so messages from dbrefs that have been
+    nuked and recreated can be distinguished from messages from the
+    original sender. This modifies the maildb and make it not usable
+    with older versions. All existing @mail is grandfathered in, and
+    can't be tracked this way. Suggested by Philip Mak.
+  * New chunk memory allocator can be used to greatly reduce process
+    memory requirements by swapping little-used attribute texts out
+    to disk and caching often-used attribute texts in memory.
+    This is incompatible with forking dumps, so if you use it,
+    you'll do nonforking dumps.  Configurable in mush.cnf, see comments
+    there. [TAP]
+  * Hardcode: new interaction type INTERACT_PRESENCE marks the
+    arrival/departure/connection/disconnection/grows ears/loses ears
+    messages. Many message types that used to be considered auditory
+    (like most @verb-style messages) are now marked as visual instead.
+Functions:
+  * strreplace() works like replace() for strings. [SW]
+  * fraction(), for turning floating-point numbers into fractions. [SW]
+  * root(), for finding roots higher than the square root. [SW]
+Minor changes:
+  * We now use wrapper functions atr_value and safe_atr_value instead of
+    uncompress(AL_STR(...)) or safe_uncompress(AL_STR(...)), so we
+    can do future work with attribute storage cleanly. [TAP]
+  * @*payment attributes now receive the amount of money paid in
+    as %0. Suggested by Sholevi and Time@M*U*S*H. [SW]
+  * Config options that take times now accept a notation describing what kind
+    of units to use. For example, 3600s, 60m, 30m1800s, and 1h all refer to
+    the same period of time. Suggested by Time@M*U*S*H. [SW]
+  * Doxygen commenting of non-static members is essentially complete!
+    Pennhacks see: http://www.pennmush.org/docs/1.7.7/html/
+  * The mail() function no longer matches non-player objects by name.
+  * Several additional messages (locks, parents, etc.) are now quieted by
+    the QUIET flag. Patch by [EEH].
+  * New config option default_home sets the room to send homeless things
+    to. [TAP]
+  * Added visual progress indicators for utils/mkcmds.sh so that slow
+    systems won't think they're hung. Suggested by Cheetah@M*U*S*H.
+Fixes:
+  * Fixes from 1.7.6p10.
+  * The 'i' sort type was not properly implemented. Reported by
+    Noltar and BlaZE@M*U*S*H.
+  * Cleanup of all accesses to ATTR values to use AL_STR() in preparation
+    for future work on attribute storage. [TAP]
+  * The 'any' string for specifying flag types didn't work properly.
+    Reported by Krad@M*U*S*H.
+  * The connect screen may now appear correctly under windows telnet. [SW]
+  * The more efficient channel buffer shifting code now handles
+    pathological cases correctly.
+  * The tag*() functions could leave tags open at the end of full
+    buffers. No longer.
+  * Code cleanup in src/notify.c.
+  * @rejectmotd and @wizmotd set the wrong messages. Report by
+    Konstantine Shirow. [SW]
+  * Using @chan/buffer to resize a recall buffer gives feedback. Reported by
+    Sholevi@M*U*S*H. [SW]
+  * Help fix for grab() by Adu@AbryssMUSH.
+  * You can no longer destroy the base_room (or default_home). Suggested
+    by Philip Mak. [TAP]
+
+Version 1.7.7 patchlevel 13                     April 9, 2003
+
+Major changes:
+  * Interactions (something like "realms" in mux2). Two functions
+    in local.c can now be used to control conditions under which
+    objects can see, hear, or match each other. Possibly useful for
+    implementing umbral worlds, etc. Patch by Vadiv@M*U*S*H.
+Functions:
+  * children(), syntactic sugar for lsearch(all,parent,<dbref>).
+    Suggested by Kyieren@M*U*S*H. Patch by BlaZe@M*U*S*H.
+  * powers() can now take a second argument to set an @power.
+    Suggested by Rob@BtFMUSH.
+Minor changes:
+  * @config/set can now set null strings. Suggested by Cheetah@M*U*S*H.
+  * In restart, set LC_ALL as well as LANG from the given LANG value,
+    in case the user's got an LC_ALL in their shell.
+  * The channel buffer shifting code has gotten much more efficient.
+    Suggested by [TAP].
+  * @function/restrict can accept arguments of the form '!<restriction>'
+    to clear a restriction. Suggested by Saturn@M3.
+  * Most of the asterisk lines between different login message files
+    have been removed. Suggested by Vadiv@M*U*S*H most recently.
+Fixes:
+  * Fixes from 1.7.6p9.
+  * Win32 portability fixes. [EEH]
+  * deny_silent in access.cnf was ignored in several cases, and no
+    longer is. Patch by Cloud@Sci-Fi Chat
+  * Help fixes by Cheetah@M*U*S*H and LeeLaLimaLLama@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 12                     March 21, 2003
+
+Commands:
+  * @channel/buffer creates a buffer for a channel to store the most
+    recent messages broadcast on the channel. @channel/recall can be
+    used to recall them. These are not stored across reboots and should
+    be set up by #1's @startup.
+Functions/Substitutions:
+  * accname() gives the accented name of an object (applying @nameaccent).
+  * %~ gives the accented name of the enactor.
+Minor Changes:
+  * The chat-related commands and functions have been moved out
+    of bsd.c and funmisc.c and into extchat.c. Patch by Vadiv@M*U*S*H.
+  * The notification stuff has been moved out of bsd.c and into a new
+    notify.c file.
+  * @name no longer requires a password for changing player names,
+    and ignores one if given. Suggested by Ambrosia@M*U*S*H (and others).
+  * @hook can not be used on the @password or @newpassword commands.
+  * The dump_complete message is also shown after a forking dump,
+    if one is defined. Suggested by Nathan Schuette.
+  * @lock/leave on a room now prevents people within it from leaving
+    via exits or via @tel. Suggested by Peter Bengtson, patch by
+    BlaZe@M*U*S*H.
+Fixes:
+  * Fixes from 1.7.6p8
+  * Cleanup of a few new db[x] mentions in the source to use dbdefs.h
+    macros. Inspired by Vadiv@M*U*S*H.
+  * @command/restrict didn't work properly for most flags, especially
+    new ones. Reported by Caesar and Sholevi@M*U*S*H.
+  * @pemit/list didn't honor NOSPOOF. Patch by Cheetah@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 11                     February 22, 2003
+
+Commands:
+  * New IDLE command (socket-level) does nothing, and does not update
+    the socket's last_time (so it doesn't change idle times). Useful
+    for people behind lame NAT firewalls that timeout their connections if
+    nothing is sent for 5 minutes. Suggested by Bolt and BladedThoth@M*U*S*H.
+Fixes:
+  * Win32 (and other OS) portability fixes. [EEH]
+  * Fixed the openssl Configure thing again. The right way, this time.
+
+Version 1.7.7 patchlevel 10                     February 22, 2003
+
+Fixes:
+  * Fix to stupid typo in Configure script that breaks on systems
+    without openssl. Argh.
+
+Version 1.7.7 patchlevel 9                      February 20, 2003
+
+Functions:
+  * New function scan() works like @scan. Suggested by Viila@M*U*S*H.
+Flags:
+  * New flag, MISTRUST, prevents an object from controlling anything
+    but itself.
+Configuration:
+  * mush.cnf directives ansi_justify, globals, and global_connects have
+    been removed (they are now always on).
+  * New unconnected_idle_timeout directive in mush.cnf controls
+    timeouts for connections idle at the connect screen.
+  * New max_guests directive in mush.cnf can limit the number of
+    guests allowed to connect at once. Suggested by Sholevi@M*U*SH.
+Minor Changes:
+  * New lflags search class takes a list of flag names.
+  * Improved connection failure messages.
+  * Somewhat more informative message when you @chan/gag,hide,mute
+    all channels at once. Suggested by Tanaku and Kevin@M*U*S*H.
+  * Began commenting files using doxygen.
+  * Internal code cleanup. Mostly converting some magic numbers to
+    #define'd symbols, and some #define'd symbols to enums for better
+    debugging and improved readability. Also some conversion of old
+    K&R style functions. [SW]
+  * sort() and the set functions understand all the same comparison
+    types as comp(). [SW]
+  * Case-sensitive comparison currently isn't always possible, depending
+    on the locale the mush is running on. Help files reflect this. [SW]
+  * @uptime shows the time of the last successful database save, and
+    the time of future events like saves, not just the time until them.
+    Suggested by Cheetah@M*U*S*H. [SW]
+  * Improvements to reporting of failed saves. [SW]
+  * Code cleanup. [SW]
+  * tel() now takes a third argument that makes it function like
+    @tel/silent. Suggested by Cheetah@M*U*S*H. [SW]
+  * @idescformat operates like @descformat for internal descriptions.
+    Suggested by Tanya@M*U*S*H.
+Fixes:
+  * local_startup() was getting run earlier than in the past due to
+    changes in the startup sequence. This has been rectified, so
+    local_startup() again runs after all other initialization (and
+    just before all object startups are triggered). Report by
+    BladedThoth and grapenut@M*U*S*H.
+  * Improved testing for openssl libraries in Configure. The old
+    approach used to cause problems on systems with runtime-only
+    openssl installations without development libraries.
+  * help opaque mentions that opaque blocks look/outside. Suggested
+    by Cheetah@M*U*S*H.
+  * itext() and inum() now generate an error on a null argument,
+    regardless of tiny_math and null_eq_zero settings. Reported by
+    Intrevis@M*U*S*H.
+  * Another fix to the new matcher. Bug report by Kyieren@M*U*S*H.
+  * @flag/alias was broken. Fixed. Reported by Kevin@M*U*S*H.
+
+
+Version 1.7.7 patchlevel 8                      January 27, 2003
+
+Minor Changes:
+  * command_add now expects to receive the flag list and the
+    switch list as strings. Folks who hack into cmdlocal.c should
+    take note and read example in the new cmdlocal.dst
+Fixes:
+  * Players were not created with all the player_flags. In a related
+    bug, checking of command flag restrictions wouldn't work with
+    all flags. Reported by Cory Descoteau.
+  * Flagmasks on commands weren't grown properly when flags were added.
+
+
+Version 1.7.7 patchlevel 7                      January 25, 2003
+
+Fixes:
+  * Crash bug in zone-checking during @dbck fixed.
+
+
+Version 1.7.7 patchlevel 6                      January 23, 2003
+
+Major changes:
+  * Rewrite of the flag system. The new system allows for any number
+    of flags, which may apply to any set of object types (the flags/toggles
+    distinction has been eliminated). Flags without single-character
+    abbreviations are better supported. Flags are stored in the object
+    database, and are referenced in hardcode and the db by the name
+    of the flag rather than bit positions.  Older databases will be
+    automatically converted to the new format on load, but can not be
+    converted back (so make backups!). New flaglocal.dst file for
+    hardcode patch hackers to add custom flags.
+  * Rewrite of the matcher code (src/match.c). Some semantics of the
+    matching have changed: matching adjectives are parsed earlier and
+    restrict the match for greater efficiency; they also behave more
+    close as described in the help with respect to object ordering.
+    In addition, you can now do things by dbref without controlling
+    the object in many cases that previously required control.
+    Provoked by bug reports by Intrevis@M*U*S*H and Philip Mak.
+Commands:
+  * @flag allows God to manipulate flags within the game, including
+    adding new flags. Flags additions/changes are maintained across
+    reboots, so this command does not need to be run at every startup.
+Functions:
+  * lflags(), orlflags(), andlflags() return or test flag lists.
+Minor changes:
+  * New NUMVERSION macro defined in hdrs/version.h that can be tested
+    by hardcode hacks to add code conditional on version.
+  * Much cleanup of @wall. Minimally-user-visible changes:
+    The @rwallemit, @rwallpose, @wallemit, @wallpose, @wizemit and
+    @wizpose commands have been removed. @wall no longer accepts the
+    /wizard, /royalty, and /pose switches, and @rwall and @wizwall accept
+    the /emit switch. Suggested by Vadiv@M*U*S*H, though he'd really
+    rather see @wall removed.
+  * @lock and @unlock now show which type of lock was set/cleared.
+    @lset now specifically notes that lock flags are being changed.
+    Suggested by Tanaku@M*U*S*H.
+Fixes:
+  * @boot/me will no longer boot a connection if it is the sole
+    connection the player, even if it's technically inactive.
+    Suggested by Ambrosia@M*U*S*H.
+  * @boot'ing an active self (by descriptor) crashes the MUSH.
+    Discovered by Ashlynn@ChicagoMUSH.
+  * The thorn and eth characters generated with accent() would
+    convert to 'th' when stripped or viewed under NOACCENT, which
+    could be very confusing in object names. Same for the German sharp
+    s, which converted to 'ss'. Until we can cleverly set up separate
+    tables for object name unparsing, these have been reverted to their
+    old behavior so that stripaccents(accent(X,Y)) should return X for
+    any X and Y. Reported by DeeJee, Intrevis, and Time (@M*U*S*H).
+
+
+Version 1.7.7 patchlevel 5                      January 7, 2003
+
+Fixes:
+  * Fixes from 1.7.6p5.
+
+
+Version 1.7.7 patchlevel 4                      January 2, 2003
+
+Minor Changes:
+  * When room_connects is on, @aconnect and @adisconnect also
+    functions on things when players (dis)connect inside them.
+    Suggested by Philip Mak. [SW]
+  * Parser-enforced argument counts for user-defined @functions,
+    as an option to @function.
+Config:
+  * New mush.cnf option max_global_fns allows increasing the number
+    of @functions allowed without editing source code. If you change
+    this, you should reboot the MUSH or bad things can happen.
+    Suggested by hilikiradi@Dardalani.
+Fixes:
+  * mkcmds.sh doesn't always regenerate every file, only what's
+    needed. Speeds up compiles. Suggested by Philip Mak. [SW]
+  * Fixes from 1.7.6p4.
+
+
+Version 1.7.7 patchlevel 3                      December 25, 2002
+
+Commands:
+  * @sitelock/check <host> tells you which rule, if any, would match.
+Fixes:
+  * The objdata hashtable routines had a serious bug that could
+    cause crashes.
+
+
+Version 1.7.7 patchlevel 2                      December 22, 2002
+
+Major Changes:
+  * The LOCAL_DATA define has been removed along with the pointer
+    in the object structure. The local functions called on creation,
+    destruction, and cloning are now always called. Objects can
+    now store data in a single hashtable using the set_objdata()
+    and get_objdata() functions. As a proof of concept, the transitory
+    channel lists on objects are now stored here, and the "channels"
+    pointer has been removed from the object structure. Design
+    and much of the implementation by Vadiv@M*U*S*H.
+Powers:
+  * can_nspemit power can be used to provide access to @nspemit
+    without a Wizard bit. [SW]
+Functions:
+  * lpos from Mux, TM3. [SW]
+Fixes:
+  * Fix to some gcc-specific macros reported by Peter Bengston and
+    Michael Holbrook. [SW]
+  * Improvements to stripaccents/noaccents conversions. [SW]
+  * Fixes from 1.7.6p3.
+
+
+Version 1.7.7 patchlevel 1                      December 17, 2002
+
+Minor Changes:
+  * ex obj/attrib returns the attribute value even if it's veiled,
+    if a specific (non-wildcard) attribute is given. Suggested by
+    Nhoj@M*U*S*H.
+Fixes:
+  * Win32 portability fixes. [EEH]
+  * Fixes from 1.7.6p2
+
+Version 1.7.7 patchlevel 0                      December 8, 2002
+
+Major Changes:
+  * Clients that understand telnet NAWS (See RFC 1073) can tell the mush
+    what dimensions a given connection's display is.  Added the
+    width() and height() functions, and SCREENWIDTH and SCREENHEIGHT
+    psuedo-commands for getting/setting this information from the mush.
+    This changes the reboot.db format and requires a full shutdown. [SW]
+  * Two new atoms for locks. "#true" in a lock is always evaluated as true
+    (anyone can pass), and "#false" is always evaluated as false (no one
+    can pass). Suggested by Vadiv@M*U*S*H.
+Internationalization:
+  * The pronoun sets are no longer hardcoded. If you're running in a
+    locale other than C or en*, you'll see weird looking pronoun descriptions
+    for things like %s until a translation team translates them to your
+    locale's language. Suggested by Dandy@M*U*S*H.
+Attributes:
+  * @DESCFORMAT can be used to separate description contents from formatting.
+    Suggested by Philip Mak.
+  * VEILED attribute flag causes attribute value not to be displayed
+    on default examine, but otherwise accessible as usual. Good for spammy
+    data attributes. See 'help attribute flags'. Suggested by Cheetah@M*U*S*H.
+Commands:
+  * examine/all shows contents of veiled attributes. Suggested by
+    Intrevis@M*U*S*H.
+Flags:
+  * The FIXED and ROYALTY flags are no longer optional.
+Minor Changes:
+  * Object creation times are no longer optional.
+  * Warnings are no longer a compile-time option; they're turned on.
+    You can stop automatic warnings in mush.cnf, as before.
+  * Cleanup of the telnet-option code in general. [SW]
+  * Consolidation of much of the code for functions that return information
+    about the least-idle connection of a given player. [SW]
+  * The tiny_attrs configuration option has been removed.
+  * Removed a lot of preprocessor checks for conditionally including header
+    files that always succeed because they're standard C headers. [SW]
+  * Removed the Size_t typedef in favor of the standard size_t. [SW]
+  * Some optimization hints for the GCC and VS.NET compilers. [SW]
+  * We try to be more conservative about when we show lines of
+    asterisks around motd-type messages, to avoid showing them when
+    there's no message.
+  * Continued ansi-C-ification of function declarations.
diff --git a/CHANGES.OLD b/CHANGES.OLD
new file mode 100644 (file)
index 0000000..93b2499
--- /dev/null
@@ -0,0 +1,4302 @@
+This is the old changes file for PennMUSH.  Changes are reported in reverse
+chronological order (more recent first)
+
+[TN] is Thorvald Natvig, a PennMUSH developer (aka Trivian)
+[TAP] is T. Alexander Popiel, a PennMUSH developer (aka Talek)
+[SW] is Shawn Wagner, a PennMUSH developer (aka Raevnos)
+[LdW] is Luuk de Waard, a PennMUSH developer (aka Halatir)
+[RLM] is Ralph Melton, a former PennMUSH developer
+[NJG] is Nick Gammon, the Win32 porter
+[DW] is Dan Williams, the MacOS porter
+[2.2] refers to code which originated with the TinyMUSH 2.2 developers
+[3] refers to code by (or inspired by) TinyMUSH 3.0
+[Rhost] refers to code by (or inspired by) RhostMUSH
+
+==========================================================================
+
+Version 1.7.3 patchlevel 14                    January 29, 2001
+
+Major Changes:
+      * Commands and functions can now be aliased to other names
+        in mush.cnf, so you can provide translated command names
+        as well as the english one, etc. Suggested at various times
+        by Krad@M*U*S*H and David@M*U*S*H. New file aliases.cnf
+        in game/ is created to put these command_alias and function_alias
+        directives (as well as reserve_alias). [SW]
+Minor Changes:
+      * smalloc and bigram compression options are no longer supported. [SW]
+      * Internal improvements to @search/lsearch, which are now more
+        consistent with one another. [SW]
+      * mush.cnf options that refer to dbrefs now understand #<number> as 
+        well as just <number>. They are also formatted as dbrefs in @config 
+        and config(). [SW]
+      * Much longer game/names.cnf file contributed by Kyieren@M*U*S*H.
+      * New internal function notify_format() to replace notify+tprintf
+        more safely. [SW]
+      * Tweaks to network activity timeout code. [SW]
+      * @stat and lstats no longer needs to scan the entire database. [SW]
+Commands:
+      * @switch and @select can now take a /notify switch, like @dolist.
+      * Players may @chown by dbref, but they must still be carrying
+        the object if it's a thing. Suggested by Kyieren@M*U*S*H.
+      * @clone can now clone rooms. Suggested by Kyieren@M*U*S*H.
+      * @verb's matching of the <actor> argument has been relaxed so
+        non-privileged objects can use it on remote actors, if the normal
+        permission checks succeed. This makes @verb much more useful for zones
+        and master room objects. [SW]
+Attributes:
+      * @forwardlist [2.2,SW]
+Functions:
+      * itemize() takes a list "a b c" and produces "a, b, and c" and 
+        similar tricks. Also callable as elist() for Rhost compatibility.
+      * ilev() returns the current iter nesting level. [Rhost,SW]
+Fixes:
+      * When indexing help files, ones that aren't in the right format
+        weren't being properly closed. Report by Nammyung@M*U*S*H. [SW]
+      * We're much more forgiving about help files that start with
+        blank lines now.
+      * Help updates by Kyieren@M*U*S*H, [SW]
+      * Games w/o MAIL_ALIAS couldn't load maildbs that had mail aliases.
+        Reported by Nymeria. [SW]
+      * max_pennies had a limit of 10,000 in conf.c, even though it
+        has always defaulted to 100,000. Reported by Intrevis@M*U*S*H.
+      * src/filecopy.c didn't include hdrs/log.h. Fixed by Noltar@Korongil.
+      * MacOS portability fixes. [DW]
+      * vsnprintf used in place of vsprintf everywhere when available. [SW]
+      * Cleanup of @atrlock code. [SW]
+      * '#' without any trailing numbers was treated as #0 when used as a 
+         dbref. [SW]
+      * Added explicit checking for lower-case letters in good_atr_name(). [SW]
+      * Trying to set an attribute with a bad name now gives a better 
+        error message. [SW] 
+      * Fix to potential overflow bug in revwords. [SW]
+      * Fix to potential overflow bug in @grep. [SW]
+      * Configure checks for snprintf. Systems unfortunate enough not to
+        have this (Solaris 2.5.1, for example) are stuck with sprintf.
+        Report by Broncalo@DuneIII.
+      * CHARGES were decremented whenever an A-attribute would be 
+        executed, *even if the attribute wasn't set on the object*.
+        Reported by Kyieren@M*U*S*H.
+
+Version 1.7.3 patchlevel 13                    January 9, 2001
+
+Major Changes:
+      * Semaphores may use attributes other than SEMAPHORE. These 
+        "named semaphores" make it easier to have multiple semaphores
+        waiting on the same object. [SW]
+      * New attribute flag AF_NODUMP prevents attributes from being
+        dumped to the db. It applies to QUEUE, SEMAPHORE, and attributes
+        used as named SEMAPHORES. [SW]
+Commands:
+      * @wait/until can be used to put commands on the wait queue until
+        some absolute time (in secs) is reached. [SW]
+      * @cpattr/noflagcopy copies attribute data without flags.
+        Same for @mvattr. Suggested by Noltar@Korongil.
+Functions:
+      * foreach() takes start and end arguments like TM3/MUX2. [3,SW]
+      * locate() has a new option to force matching the preferred type, 
+        and an undocumented option to eliminate ambiguous matching is now
+        documented. [SW] 
+Minor Changes:
+      * Experimental support for translating 8-bit accented characters to HTML
+        entities for Pueblo. [SW]
+      * Objects without a @lock/zone that are used as zones are noticed and 
+        warned about during dbcks. [SW]
+      * Some people prefer that +channel "foo now cause you to say "foo 
+        on the channel, instead of stripping the initial quote. Now
+        it's a mush.cnf option (chat_strip_quote). Suggested by Dave Milford.
+      * isint() now ignores tiny_math, so isint(foo) will always return 0. [SW] 
+Fixes:
+      * Hopefully fixed a problem with puppets doing look for non-Pueblo
+        players with Pueblo support turned on. The line after the look
+        could appear to come from the puppet even if it didn't. [SW]
+      * If you disabled a command with a one-character token (like say),
+        then running the command with the token ("hi!) would fail, but 
+        wouldn't return the right thing for $command matching. Now it
+        returns the canoncalized raw command (SAY hi!) so you can trap
+        it in softcode. Reported by Mackenzie@SWGT Mush  
+      * General linting [SW]
+      * Help fixes by Eratl@M*U*S*H.
+      * make customize doesn't try to link mkindx any more. And README and
+        os2/Makefile don't refer to it either. By Bobby Bailey (Chil@M*U*S*H).
+      * The internal tprintf() function is safer. [SW]
+      * Crash bug in @edit fixed, and @edit code is cleaned up. [SW] 
+      * max_pennies documented in mush.cnf. Noted by Oriens@Alexandria
+      * cmdlocal.dst now includes cmds.h. Reported by David@M*U*S*H.
+
+
+Version 1.7.3 patchlevel 12                    January 4, 2001
+
+Locks:
+      * A new @lock/chzone, set on a ZMO, controls who may @chzone
+        objects to the ZMO. This can simplify multi-player building
+        using ZMOs. Suggested by David@M*U*S*H
+Commands:
+      * @wcheck/me checks all of a player's objects for them.
+Functions:
+      * cflags() function gets channel flags.
+      * ctitle() gets channel title for an object. Suggested by Luke@M*U*S*H.
+      * strinsert() function does insert() on strings. Suggested by
+        Reagan@M*U*S*H. [SW]
+      * channels() on an object you can't examine now shows you channels 
+        that you have in common with it. Suggested by Trispis@M*U*S*H
+Minor changes:
+      * Runaway objects are now logged, along with the command that
+        pushed them over the edge. Suggested by Trispis@M*U*S*H.
+      * All instances of fprintf(stderr,...) after logging is started
+        are now handled through do_rawlog(LT_ERR,...). By David@M*U*S*H.
+      * @atrchown works more reasonably. [SW]
+      * @lock/link on an unlinked exit restricts who may link it.
+      * Everyone (even mortals) sees object names at the top of
+        their descriptions when they look at them. Suggested by Jeff
+        Ferrell most recently.
+      * New reserve_alias mush.cnf directive makes run-time command
+        alias reservation possible. Suggested by Jeff Ferrell.
+      * Mortals may use @command to get info about commands. Suggested by
+        Chili@M*U*S*H.
+      * Messages logged during help file indexing distinguish indexing
+        of admin topics. Suggested by mith@M*U*S*H
+Fixes:
+      * Help fixes by [SW], Jeff Ferrell, mith@M*U*S*H, Mirth@AtheneuMUSH.
+      * Help update to @cpattr based on behavior noted by Noltar@Korongil.
+      * cwho and @chan/who behave better with hidden (@hide) players.
+        Reported by Reagan@M*U*S*H.
+      * MUSHes with no @functions crashed on @list calls due to a 
+        problem with the hashtable code. Reported by Whiskey. [SW]
+      * A bug in handling of errors under debug in process_expression
+        was reported by Reagan@M*U*S*H. [SW]
+      * Rare memory leak in process_expression fixed. [SW] 
+      * Inconsistencies in the handling of destroyed objects/players
+        and their locations fixed. [SW]
+      * The QUEUE attribute was not properly cleared on restart
+        for non-player objects. It was buggy in other ways, too. [SW]
+      * The AF_SAFE attribute flag couldn't be reset. Reported by
+        Pegasus@StarQuest.
+      * @teleporting into garbage could cause crashes. Reported by Howie
+      * The help_command and ahelp_command directives now look for
+        whitespace generally, not spaces specifically. 
+        Suggested by Krad@M*U*S*H
+      * SIGHUP once again re-reads the config file. [SW]
+      * If an exit @created an object, that object would be inside the exit 
+        until the next dbck. Now it is created in the exit's source room. [SW]
+      * Turning the lowest possible integer number into a string could 
+        result in garbage output. [SW]
+      * Solaris needed arpa/nameser.h before resolv.h in mysocket.c
+
+Version 1.7.3 patchlevel 11                    December 4, 2000
+
+Minor changes:
+      * Added 'comsys' as help alias for 'chat'. Suggested by David@M*U*S*H
+      * New win32 project files and MSVC build instructions by Jenny Lind.
+Fixes:
+      * Improved test for non-broken getdate and getnameinfo.
+      * Loading panic dumps does better on chatdb. By Maverick@M*U*S*H
+      * You may leave a channel even if you're the wrong type
+        to get onto it. Suggested by Cheetah@M*U*S*H.
+      * Typo in the win32 strcasecmp code fixed [NJG]
+      * stddev's algorithm improved. [TAP]
+      * If the go command is restricted, other commands produce spurious
+        errors during the exit-matching phase of command parsing.
+        Reported by Jamie@M*U*S*H
+
+Version 1.7.3 patchlevel 10                    November 20, 2000
+
+Major Changes:
+      * Improved detection of errors saving the game. If any problems 
+        are encountered, the save is aborted, and online admins notified so
+        they can fix the problem before finding out about it too late. [SW] 
+Flags:
+      * The INHERIT flag has been renamed TRUST, which better describes
+        its function. INHERIT remains as an alias.
+Commands:
+      * @chan now takes /ungag, /unhide, and /unmute as well as the
+        usual @chan/gag <channel>=no. By David@M*U*S*H.
+Minor Changes:
+      * money() called on a no_pay player returns the value of
+        MAX_PENNIES to ease softcode checks. Suggested by Oriens@Alexandria.
+      * Removed help and &help entries from the distributed pennmush.nws
+        because people will generally want to override them anyway
+        and we shouldn't introduce problems. Suggested by Jeff Heinen.
+      * safe_str and friends optimize trivial 0 and 1 letter strings. [SW]
+      * A version of word-based compression that's almost 8-bit clean
+        is now included. [SW]
+      * We now use stricmp and friends for strcasecmp and friends on
+        Win32, rather than roll our own. [SW]
+Fixes:
+      * @mail aliases couldn't be used by players who didn't have
+        permissions to see the alias members, which is wrong.
+        Fixed now. Report by Grinna@M*U*S*H.
+      * lnum(1) and lnum(0,0) were broken. Report by Jeff Ferrell 
+      * Help updates. [SW]
+      * @set obj/notanattribute=flag now returns a 'No such attribute' error.
+        Reported by David@M*U*S*H. [SW]
+      * Help file indexing tries to detect files that aren't in the right 
+        format. [SW]
+      * function restrictions were checking the wrong object. [SW] 
+      * objmem and playermem counted eval-locks and atr-locks incorrectly. 
+        Reported by Javin@DynamixMUSH. [SW]
+      * Fixes to win32 NT_TCP stuff. [NJG]
+      * Rare memory leak in do_page fixed by David@M*U*S*H. 
+
+Version 1.7.3 patchlevel 9                    November 20, 2000
+
+Major Changes:
+      * Help files and their associated commands are now defined with
+        the 'help_command' (or 'ahelp_command') directive 
+        in mush.cnf. So you can translate the help to french and
+        add an "aidez-moi" command if you like. [SW]
+      * Help file indexes are now kept in memory and built by the
+        server. The mkindx program is no longer used. [SW]
+      * Added restrict_function and @function/restrict, like the versions 
+        for commands. [SW]
+      * User @functions can now override built-in functions, if you
+        @function/delete the built-in first. You can also @function/restore
+        the built-in back. [SW]
+Attributes:
+      * @[oa]zenter and @[oa]zleave on ZMOs are triggered when someone
+        enters or leaves a given zone. Motion between rooms in the same
+        zone doesn't trigger these. "Zone" is based on the mover's
+        absolute room (outermost container) so that entering and leaving
+        an unzoned object in a zoned room doesn't trigger these either.
+        Suggested by Michael Kurtz.
+Commands:
+      * New /silent switch for @teleport without @[o|a|ox]tport msgs.
+Minor Changes:
+      * Still less allocation involved in outputting text to connections. [SW]
+      * @scan has better output when multiple attribs match. [SW]
+      * Added internal absolute_room function for use by fun_room
+        and move.c and wiz.c, etc.
+      * MEM_CHECK now uses a sorted linked list and is faster. [SW]
+Fixes:
+      * References to kill_timer removed from win32 sections. [SW]
+      * conf.c reports error via logging routines, not fprintf. [SW]
+      * Assorted minor bug and warning fixes. [SW]
+      * Fix to example in help regedit by Amberyl.
+      * @wait and other timing functions were broken under win32.
+        Fixed now. [LdW]
+
+
+Version 1.7.3 patchlevel 8                    November 12, 2000
+
+Major Changes:
+      * Objects' names are stored in a string tree like attributes, so
+        we don't have hundreds of copies of 'West <W>;west;w' and such
+        taking up memory. Attribute name "hash table" is now a strtree. [SW]
+      * We no longer use alarm() to know when to run stuff on the queue. [SW]
+      * @shutdown/reboot is now supported on Windows builds. This is
+        slightly experimental. By Revian.
+Minor Changes:
+      * %qa-%qz now operate as global registers like %q0-%q9. 
+        Suggested by Trispis@M*U*S*H and probably others.
+      * Hashtable lookups are faster (collision chains are sorted) [SW]
+      * @uptime/mortal suppresses the extra process information [SW]
+      * Wizard @uptime prints better process information on linux. [SW]
+      * @listen, @filter and @infilter respect the REGEXP and CASE attribute 
+        flags.  Suggested by Maestro@M*U*S*H [SW]
+      * Having many @waits or semaphores is more efficient, because we
+        sort those queues by time and we stop looking sooner. [SW]
+      * User-defined lock names must follow the same rules as attribute 
+        names. The name part of attrib and eval locks have to also. [SW]
+      * @chan/title's confirmation message is nicer. [SW]
+      * Minor optimizations related to strcpy() and malloc() overhead. [SW]
+      * safe_format uses vsnprintf if your system has it. By Luke@M*U*S*H.
+      * replace_string is safer. By Luke@M*U*S*H.
+Fixes:
+      * iter() is smarter about quitting when the function invocation
+        limit is reached or the buffer is filled. [TAP]
+      * lnum() has been greatly sped up. [TAP]
+      * RWHO references removed from game/mushcnf.dst, game/restart,
+        etc. by mith@M*U*S*H.
+      * Fix to help filter by Revian.
+      * COMPRESSION_TYPE 0 didn't compile. Report by David@M*U*S*H.
+      * Clarification of @lock/teleport in help @elock by Envinyatar@M*U*S*H
+      * Compiling w/o MAIL_ALIASES didn't declare load_maliases.
+        Reported by Envinyatar@M*U*S*H
+      * clone() was stomping %0-%9. Report by Revian.
+      * @dol with an empty list now does the right thing (nothing)
+        instead of running the command once. Report by Linda Naughton [SW]
+      * spname cleanup by mith@M*U*S*H.
+      * Fixed a bug in @function function-name [SW]
+      * make update should finally handle CHAT_TOKEN_ALIAS right.
+        It's also smarter with mush.cnf
+      * isword() on a null string now returns 0. Suggested by Ashen-Shugar.
+      * channels(object) returns a better error when object isn't matched.
+        Suggested by Trispis.
+      * Fix to help rand() to reflect actual error return value. Reported
+        by Philip Mak.
+      * More translated strings noted by Krad.
+      * Problems with encrypt()'s logic fixed by Ashen-Shugar. [Rhost]
+      * Other encrypt() sillyness fixed by Brazil.
+      * Potential crashers around DEBUG and other buffer overruns
+        have been fixed. Report by Tajan and Rimnar. [SW]
+      * notify_anything allocates less memory. [SW]
+      * Fixed mistagged memcheck "restart.descriptor". 
+      * MacOS portability changes. Should build on MacOS X public beta
+        (Darwin) systems nearly straightaway. [DW]
+      * Restart script test for already-running MUSH condensed to one
+        line. Suggested most recently by Cory Albrecht.
+      * Serious crash bug in page fixed. Reported by Revian.
+      * Win32 bugs fixed by Luke@M*U*S*H: problems with dumping
+        compression suffixes, problems with @uptime
+      * MUSH no longer crashes if a player alias is > 32 chars in length.
+        It truncates names that are too long instead. By Luke@M*U*S*H.
+      * We don't check AF_PRIVATE attributes for $/^commands on children.
+        By Luke@M*U*S*H.
+      * Under win32, the MUSH would often start ignoring commands from
+        players after the first 98 per connection. Fixed by Revian.
+        
+Version 1.7.3 patchlevel 7                    October 12, 2000
+
+Functions:
+      * filterbool(), for TM3 compatibility. Like filter(),
+        but tests for any true value, not just 1. [3,SW]
+      * case(), for TM3 compatibility, and caseall().  Like switch() 
+        and switchall(), but does an exact match, not wildcard. [3,SW]
+      * valid() takes more categories. [SW]
+      * localize(), for TM3 compatibility. Like a cross between
+        s() and ulocal(). [3,SW]
+Commands:
+      * @break, for Rhost compatibility. Stops processing the
+        current action list. [Rhost,SW]
+      * @enable and @disable can be used on any boolean config option. [SW]
+      * @function/enable and @function/disable for built-ins
+      * @function function-name reports some information on the function,
+        like @command command-name.
+Flags:
+      * New attribute flag 'safe' prevents accidental modification.
+        Suggested by Stephen Viscido. [TAP]
+Minor Changes:
+      * The daytime config option is no more. Do a few @searches in
+        its honor. [SW]
+      * lsearch() permission is controlled by @search, and entrances()
+        by @entrances, for consistency with other functions that have
+        a corresponding command. [SW]
+      * The number of reboots is tracked, and the restarts() function
+        added, for TM3 compatibility. [3,SW]
+      * Rearranged some compile flags to suit systems that have
+        /usr/local/include/ident.h. By Luiz Claudio Duarte.
+      * More strings marked for translation. By Krad@M*U*S*H
+      * :, [, ], (, and ) are no longer legal in attribute names
+        due to ambiguities and security issues. [TAP,SW]
+      * Ranges in @search, lsearch() and @find can be either #dbrefs or 
+        (for backwards compatibility), integers. Inspired by ElendorMUSH. [SW]
+      * Broadcast messages in bsd.c replaced with GAME prefix.
+        Suggested by Krad@M*U*S*H.
+Fixes:
+      * Fixed a bug in filter() where it only looked
+        at the first character of the evaluated attribute. [SW]
+      * Some more noise from info_slave was removed. [SW]
+      * do_pcreate and do_chownall now live in game.h and not
+        externs.h. Reported by Maestro@M*U*S*H.
+      * Clean-up of atr_add calls in void context. [TAP]
+      * IPv6 buglet fixed. Report by Vexon@M*U*S*H. [SW]
+      * @config/set can no longer be used to set options in the files   
+        and messages categories, as this has icky consequences. [SW]
+      * Fixed a logic bug in letters_to_privs where not all the letters 
+        were being turned into privs properly. Report by Bolt@M*U*S*H [SW]
+      * Fix to isint() so that isint(1a) is 0. [SW]
+      * Added safe_boolean internal function, and fixed a hang bug
+        in edit() reported by Walker@M*U*S*H. [SW]
+      * Fixed problems in panic dumps/reads of maildb and chatdb. [SW]
+      * @edit is a bit more efficient. [SW]
+      * Assorted lock structures are allocated in big chunks like 
+        attribute structures, to save malloc overhead. [SW]
+      * User-defined lock names are stored in the attribute name tree. [SW]
+      * Various help fixes [SW, Javelin]
+      * LASTLOGOUT time now forces two-digit day for convtime niceness. [SW]
+      * Very large malias names or member lists could cause buffer 
+        overruns. [SW]
+      * Buffer overrun fix, fix to str_chr. [TAP]
+      * tprintfs removed from DEBUG output so DEBUG doesn't mess up
+        messages in ^-commands anymore. [TAP]
+
+Version 1.7.3 patchlevel 6                    September 20, 2000
+
+Minor Changes:
+      * Translation files are now archived separately on the ftp site.
+      * A variety of options.h settings have been removed.
+        EXTENDED_ANSI, DUMP_EMPTY_ATTRS, DUMP_LESS_GARBAGE and the *_LOCK 
+        defines are totally gone. [SW]
+        OLD_NEWLINES, DELETE_ATTRS and VISIBLE_EMPTY_ATTRS have been 
+        moved out of options.h as they're special-purpose. [SW]
+      * More common function error messages were made into variables 
+        rather than being hardcoded in as string literals. [SW]
+      * If a player is set HALT, their queued commands will not run.
+      * Speedup in process_expression. [TAP].
+Functions:
+      * The regedit(?:all)?i? family of functions, like perl's s///. [SW]
+      * Case-insenstive versions of regrab() and regraball(). [SW]
+      * etimefmt(), which is to timestring() as timefmt() is to time(). [SW]
+Fixes:
+      * Error messages that were already variables are now translated. [SW]
+      * Fixes to various metaconfig rules. [SW]
+      * Open_Anywhere and Link_Anywhere were sharing the same
+        bitmask. Fixed. [SW]
+      * You can escape : in $command patterns that are being regexp-
+        matched now. [SW]
+      * Rewrites of the regexp functions so that, say, regrab() and
+        regraball() point to the same actual code, using called_as to 
+        know when to stop. They also use PCRE's match optimizing 
+        pcre_study() function when appropriate. [SW]
+      * Buglets in game/restart and game/mushcnf.dst fixed.
+        Reported by Krad and Nymeria at M*U*S*H.
+      * page_aliases directive in mush.cnf works now. Report by Nymeria.
+      * Same for float_precision. Report by Oleo@M*U*S*H.
+      * mushtype.h now included in compress.c. [DW]
+      * Less noise in log/netmush.log from failed ident queries.
+      * More strings marked for translation.
+      * Fixes to problems with @search reported by Oleo@M*U*S*H. 
+      * Weird evaluation of functions in softcoded commands fixed. [TAP]
+      * Cleanup of typos here and there by Padraic@M*U*S*H.
+
+
+Version 1.7.3 patchlevel 5                    September 7, 2000
+
+Minor Changes:
+      * FLOATING_POINT is no longer an option (it's always on). [SW]
+      * EXTENDED_ANSI defaults to enabled. [SW]
+Attributes:
+      * @receive/@oreceive/@areceive triggered on the recipient
+        after a get or give, so you've got access to who caused
+        you to acquire the object and the object's dbref now.
+      * @give/@ogive/@agive triggered on the giver with object's
+        dbref in %0. Suggested by Oriens@Alexandria. 
+Fixes:
+      * Fixes for systems with broken or incomplete IPv6 support. [SW]
+      * Uses of index() changed to strchr() for consistency. [SW]
+      * Much removal of duplicate function prototypes and rearranging
+        of headers.  hdrs/globals.h is now hdrs/case.h. hdrs/intrface.h is 
+        no more, and hdrs/boolexp.h, hdrs/log.h were added. [SW]
+      * @search supports "quoted player names".
+      * We no longer report failed connect to ident servers in the log.
+
+Version 1.7.3 patchlevel 4                    August 8, 2000
+
+Major Changes:
+      * Internationalization:
+        * Support for international time formats via LC_TIME locale [SW]
+        * Support for message translation
+        * Support for locale-sensitive ordering of strings (LC_COLLATE) [SW]
+        To take advantage of the new features, you should have your
+        LANG environment variable set to an appropriate locale 
+        before you 'make install' (which will cause the right message
+        catalog to be compiled), and you should see the section 
+        in game/restart for setting it there (which will actually cause
+        the server to use it).
+      * IPv6 support [SW]
+Commands:
+      * @dolist/delim and @map/delim [SW]
+      * @stats/tables [SW]
+      * SESSION command displays session statistics (experimental) [SW]
+Functions:
+      * uldefault(), like udefault but saves registers like ulocal() [SW]
+      * switchall(), for Tiny compatibility. [SW]
+      * cemit() with an option to act like @cemit/noisy [SW]
+      * vmin() and vmax(), for returning the min and max of each pair in two
+        vectors. [SW]
+      * utctime(), convutcsecs() for UTC/GMT time instead of server-local. [SW]
+      * convtime() uses getdate() if present, along with a variety of templates
+      * that it can accept. [SW]
+      * timefmt() - like the strftime() C function. [SW]
+      * pcreate() side effect function suggested by Adamas and Padraic@M*U*S*H
+      * starttime() now returns the first startup time, and 
+        restarttime() returns the time of the last @shutdown/reboot [SW]
+Minor Changes:
+      * +help is mentioned in help help. Suggested by Trispis@M*U*S*H.
+      * include directive for config files, with an example moving all
+        the restrict_command's to another file. [SW]
+      * make indent runs expand, then indent, because indent doesn't seem to
+        handle tabs very well. [SW]
+      * index-files.pl sorts patchlevels correctly. Patch by Jeff
+        Heinen.
+      * LASTLOGOUT attribute records logout time, like LAST, but not
+        visual. Suggested by Oriens@Alexandria, and others.
+      * Internal cleanup by David@M*U*S*H. New @config category 'messages',
+        no more OBJECT_ENDOWMENT or OBJECT_DEPOSIT macros, etc.
+      * Internal functions safe_integer(), safe_number(), and safe_dbref()
+        to replace safe_str(unparse_FOO(data), buff, bp) calls  [SW]
+      * You can now @trigger an attribute containing a $command or
+        ^listen and it'll work (skipping the $...: or ^...: parts).
+        So you can now do this:
+         &DO_WHO obj=$who *: @pemit %#=[u(who,%0)]
+         &DO_+WHO obj=$+who *: @tr me/do_who=%0
+        (But you can do this much more efficiently with regexp...)
+Fixes:
+      * table() is less CPU-intensive in extreme cases. [SW]
+      * Hopefully, Configure now determines pagesize on FreeBSD.
+        Method suggested by Matt Harris.
+      * CHAT_TOKEN_ALIAS comment clarification by Oleo@M*U*S*H.
+      * pcre regexp engine updated to version 3.4.
+      * Typo in @chan/who fixed by Vexon@M*U*S*H.
+      * @attribute/access won't modify AF_INTERNAL attributes now.
+      * Additional win32 portability fixes. [NJG]
+      * con() was buggy in a bad way. Fixed now.
+      * Configure -d should now work on linux systems that don't have
+        crypt libraries. Reports by mith and Inek@M*U*S*H.
+      * Fix to Z_TEL on things.
+      * Help fix to @lock5 by Datron@SW2.
+Languages:
+      * Swedish and Hungarian translations for most strings are
+        included in this patchlevel.
+
+
+Version 1.7.3 patchlevel 3                    July 12, 2000
+
+Major Changes:
+      * Restrictions to the 7-bit ascii charset have largely been removed
+        except in attribute names, to help international users. [SW]
+      * If available, we now use setlocale to support international
+        charsets (and eventually other conventions, though this should
+        be considered experimental). If you set your LC_CTYPE environment
+        variable to, say, 'fr_FR', french-accented characters should work.
+        Wide (multibyte) charsets are not supported.
+Minor Changes:
+      * Internal cleanup of page/whisper code by David@M*U*S*H.
+      * New mush.cnf directive, page_aliases, for showing alias of
+        paging player. Supported by code by David@M*U*S*H.
+        Requested by many. A contrib version by Flame dates to 1996.
+      * @chat on a non-existant channel returns an error message. [SW]
+      * Two new CRYPT_SYSTEM options. One checks both SHS and crypt(3)
+        for passwords, and saves them back using SHS. The other does 
+        the same for plaintext passwords. These should encourage folks
+        who currently use crypt(3) to make a painless move to SHS. [SW]
+Commands:
+      * @remit can take /silent and /noisy switches now. Suggested by
+        Philip Mak.
+      * @lemit and @emit can take /silent switch. [SW]
+      * @config/set can set configuration parameters at runtime. [SW]
+Functions:
+      * The set algebra functions can be given a sort order for output. [SW]
+Fixes:
+      * CHAT_TOKEN_ALIAS could get defined w/o a character value.
+        Added a better explanation of CHAT_TOKEN_ALIAS in options.h.dist.
+        and fixed utils/update.pl to handle commented defines that take 
+        values a bit better. Report by Nymeria@M*U*S*H.
+      * You can no longer initiate following a disconnected player.
+        Report by Dave@Galactic.
+      * CHANGES for 1.7.1 and 1.7.0 were missing. Back now.
+      * Typo in options.h.dist corrected. Report by Padraic@M*U*S*H.
+      * Small Configure portability improvements.
+      * Better handling of cases where the maildb has messages from
+        dbrefs that are out of range (due to truncating a db to remove
+        corruption, for example). Suggested by Ashen-Shugar.
+      * We now check for sitelocked sites before asking info_slave to
+        do ident lookups.
+      * Many help clarifications. [SW]
+      * linux Configure can use nm to find symbols, finally.
+      * help locate() now includes the z flag.
+
+
+Version 1.7.3 patchlevel 2                    June 3, 2000
+
+Commands:
+      * New @sitelock options to control access to god, wizards, admin
+        by site. [SW]
+      * @force can now take /noeval [SW]
+
+Functions:
+      * squish() can take a second argument to squish non-spaces. [SW]
+      * div(), floordiv(), modulo(), and remainder(), a set of functions
+        jointly adopted by MUSH and MUX servers for compatibility. [TAP]
+      * @@() and null() functions suggested by [LdW].
+
+Minor Changes:
+      * @uptime now shows initial restart time, not just time since
+        last reboot.
+      * Each player now has a limit to the number of @mail messages
+        in their inbox (folder 0), configurable in mush.cnf.
+        Suggested by Edwin@M*U*S*H.
+
+Fixes:
+      * More linting and improved indenting [SW]
+      * PARANOID works right for broadcast messages (like @cemit) now
+        too. Report by Vexon@M*U*S*H.
+      * You can no longer follow what you can't see.
+      * CHAT_TOKEN_ALIAS info appears in options.h now. Report by 
+        Rhysem@M*U*S*H.
+      * Mac portability changes. [DW]
+      * Disconnected players don't follow any more. Suggested by Don Burks.
+      * Various fixes to better resist crashing due to attacks involving
+        overwhelming connections. 
+      * @mail/stats for all was broken. Fixed now.
+      * Clearer message after failed @pemit. Suggested by Eratl@M*U*S*H
+      * Destroyed things stop following/leading. Report by Ashen-Shugar.
+      * follow didn't properly set up the followers as enactors.
+        We no longer short-circuit process_command. Report by Moe@Chicago.
+
+Version 1.7.3 patchlevel 1                    May 18, 2000
+
+Commands:
+      * @oemit now takes a list of players. Adapted from patch by Philip Mak.
+
+Minor Changes:
+      * Reconnecting is less spammy - we don't show motds again
+        to players already connected. Suggested by Trispis@M*U*S*H.
+
+Fixes:
+      * Configure problem that resulted in weird compile failures on
+        bind/accept in src/bsd.c fixed.
+      * Further linting. [SW]
+      * FreeBSD getrlimit problem diagnosed by [SW] is worked around.
+      * Couldn't compile w/o FLOATING_POINTS defined. Fixed.
+      * Fixed a few dependencies in the Makefiles to insure that
+        hdrs/patches.h and hdrs/switches.h are rebuilt properly.
+      * Indentation cleanup.
+      * We now recognize egcs as if it were gcc 2, and set ccflags
+        accordingly.
+      * Increased size of some hash tables for performance. [SW]
+      * Help fixes. [SW]
+      * flags(obj/attrib) behaved badly unless attrib was CAPITALIZED.
+        Fixed now. Reported by Vexon@M*U*S*H.
+
+Version 1.7.3 patchlevel 0                    April 20, 2000
+
+Major Changes:
+      * If you create a 'patches' subdirectory and keep any user-contrib
+        patches you apply in there, and if the patches are properly 
+        formatted, i.e., they include these lines:
+              # Patch name:
+              # Patch version:
+        your MUSH's @version and INFO output will report them.
+        In addition to being helpful for you, this will help the
+        developers when you send us a bug report including your 
+        @version. [TN]
+      * As @cemit doesn't override @chan/gag and allows 
+        NOSPOOF notification, it basically now operates just like
+        @pemit/list (you can protect yourself from spoofing, and you can
+        silence it). Accordingly, the cemit power is no longer 
+        necessary. It's now a runtime option.
+      * @malias (@mailing lists) by Daniel Peters.
+      * Attribute names are now stored in a single string tree,
+        so we don't have thousands of copies of the string
+        "FINGER_NOTE", etc., taking up memory. [TAP]
+      * As a consequence of the attribute name tree, the STARTUP flag 
+        is no longer needed, and will be automatically removed from
+        dbs.
+      * Attributes are now inserted in alphabetical order, which
+        speeds lookup. [TAP]
+      * Panic dumps now dump the maildb and chatdb, appended to the
+        end of PANIC.db. The MUSH handles breaking them up on restart.
+      * New link_anywhere power allows @link'ing to any destination.
+      * Mortals may create VARIABLE exits. At the time the destination 
+        is computed, the exit is check to see if it has permission to
+        link there (i.e., the exit controls the destination or the
+        exit is link_anywhere or the destination is link_ok).
+        To keep old code from breaking, all existing variable exits are 
+        given the link_anywhere power at first db read in this patch.
+        Suggested by David@M*U*S*H.
+      * The follow command is implemented!
+      * Nested iter is now useful. The itext(n) function returns
+        the value of ## for the nth innermost iteraction, where
+        0 is the most inner, and inum(n) does the same for #@. [TN]
+      * New regexp library, pcre, now allows perl 5.005 compatible
+        regular expressions! Suggested by [SW].
+      * Objects are now limited in the number of attributes that may
+        be set on them. This prevents a DoS attack. Suggested by
+        Ashen-Shugar.
+      * Some more english-style matching (look my 2nd box). [TN]
+
+Functions:
+      * config() returns a list of config option names. 
+        config(<option>) returns the value of a config option.
+        (e.g. config(money_singular))
+      * sort() now accepts an output delimiter, a la iter().
+        Suggested by Jason Newquist.
+      * channels() now accepts a delimiter. Suggested by Trispis@M*U*S*H.
+      * money(<integer>) returns the name of the money, either singular
+        or plural, depending on <integer>. Suggested by Trispis@M*U*S*H.
+      * timestring() with a pad flag of 2 forces 2 digit numbers.
+        Suggested by Trispis@M*U*S*H.
+      * fmod() function returns floating point remainder on division.
+        Written by Michael Thole.
+      * brackets() function returns bracket counts for its unparsed
+        argument. Handy for debugging. By Jason Wilcox.
+      * edit() can take multiple find-replace pairs. By Chili@M*U*S*H.
+      * clock() function by Ari@SpaceMUSH and Chili@M*U*S*H.
+      * flags() function can show attribute flags as well. 
+        Suggested by Kami@SW2
+      * mailstats(), mailfstats(), and maildstats() added by Kami@SW2
+      * nattr() (aka attrcnt()) returns number of attributes on 
+        an object. Suggested by Ashen-Shugar.
+      * map() and foreach() now provide the element's position
+        through %1. [LdW]
+      * spellnum() function spells out numbers in words. [LdW]
+      * wrap() for server-based line wrapping. Adapted from code by [LdW]
+      * lmath() function lets you do math on delimited lists, and makes
+        it easy to emulate Tiny's ladd/lsub/etc. [SW]
+      * bitwise math functions. [SW]
+      * mean(), median(), and stddev() functions. [SW]
+      * bound() function for bounding numbers. [SW]
+      * regrab(), regraball(), and regrep() regular expression 
+        versions of grab/graball/grep. [SW]
+      * controls() can now be used if you control either the <obj> or
+        the <victim>. [RLM] suggested this in July 1998, but we were
+        too boneheaded at the time to agree on it.
+
+Commands:
+      * teach <command> shows others in your room the (unparsed)
+        command that you want to demonstrate, and then causes you
+        to execute it. Suggested by Reed Riner.
+      * /preserve switch for @clone and @chown to preserve privbits.
+        By Kurt Fitzner.
+      * rpage and rwho have been removed.
+      * @nameformat on a room allows you to specify how the room's
+        name should be displayed to those inside it when they look.
+      * An optional second token for chat (in addition to +) can
+        be set if you'd like + and = (or whatever) to both work.
+        Patch by Kami@SW2.
+      * @scan returns the matched attribute name as well as object.
+        Suggested by many, including Thi@M*U*S*H.
+      * ; waves is treated as :waves, instead of as ;waves.
+        Suggested by Sandi Fallon, for tiny compatibility.
+      * cv command at connect screen forces a !DARK connect.
+        Suggested by David@M*U*S*H.
+      * with obj=command tries a $command on a specific object. [TN]
+      * @mailsignature finally implemented.
+      * @chan/join and @chan/leave are aliases for @chan/on and @chan/off,
+        respectively. Suggested by [LdW]
+      * @chan/decomp/brief decompiles a channel without listing players.
+
+Flags:
+      * LISTEN_PARENT flag causes the object to inherit ^listens
+        from its parent. By Kurt Fitzner.
+      * Internal ACCESSED flag removed.
+      * PARANOID player toggle replaces the old paranoid_nospoof
+        configuration directive, and allows per-player setting of
+        nospoof format. Suggested by Trispis@M*U*S*H
+
+Minor Changes:
+      * New lock @lock/command controls who may use $commands on an
+        object. @lock/listen controls ^patterns, @lock/use controls both.
+        Patch by Kurt Fitzner.
+      * The max_obj_val and max_wiz_obj_val configuration options
+        have been removed, as they're rarely used. You can change them
+        in hdrs/conf.h (search for ENDOWMENT).
+      * src/connect.c is no longer distributed. It wasn't ever used
+        for anything anyway.
+      * @fixdb command removed.
+      * @config/functions and commands can show listings in lowercase.
+      * match_list changed to try to match player aliases. Allows
+        "look <alias>" for a player in the same room. Reported by Corum.
+      * See_All players can now see/check AF_WIZARD attributes
+        (but AF_MDARK still requires that you be roy/wiz).
+        Suggested by Balazs Gasparin.
+      * VERBOSE PUPPETs relay to their owners even if the owner's
+        in the same room. Dedicated to Julianna@ATS.
+      * You may now @dest objects that you control via a zone,
+        as you could have done so indirectly before anyway.
+        Reported by [LdW]
+      * Sending the MUSH process an INT signal now causes graceful
+        shutdown (not panic dump). Sending a USR2 signal causes
+        a normal dump. As before, HUP causes config reload and
+        TERM causes a panic dump. [TAP]
+      * @chan/list shows your gag status. Suggested by Matt@M*U*S*H
+      * When chatting, we only match partial channel names against
+        channels you're actually on. Suggested by Matt@M*U*S*H
+      * By default you can no longer speak to a channel you're not
+        on. This is configurable per-channel with the new "open"
+        priv. Suggested by Akiko@M*U*S*H.
+      * If you can't go through an exit because it's linked to
+        garbage or its variable destination is invalid, we no longer
+        process the SUCC and DROP message set on the exit.
+      * The Inherit() macro no longer includes a Wizard test -- we
+        don't need it anymore as we protect Wiz objects in controls().
+      * getrandom has been replaced by get_random_long, with a better
+        algorithm and interface. Suggested by Stephen Dennis. [TAP]
+      * Win32 compilers now get the __cdecl hint so they can compile
+        using __fastcall which can greatly increase speed. Patch by
+        Stephen Dennis.
+      * For WIN32, use GetSystemTime instead of timeGetSystemTime.
+        Patch by Stephen Dennis.
+      * For WIN32, use a combination of wsadata.iMaxSockets and
+        options.max_logins to pick a reasonable number of available file
+        descriptors. Patch by Stephen Dennis.
+      * Default dump messages now call it a 'save', not a 'dump',
+        to avoid newbie confusion. Suggested by Vedui.
+      * You're notified if you set an attribute/flag on a quiet object
+        that you don't own. Patch by Kurt Fitzner.
+      * @decomp now comments its "No attributes found" message so as
+        not to break scripts.  Report by Kurt Fitzner.
+      * More Mac tweaking. [DW]
+      * \ and % are no longer valid in attribute names. Suggested by Ali Abdin
+      * Cleanup to logging code. We now try to do almost all of it through
+        log.c functions for better encapsulation. Patch by David@M*U*S*H.
+      * New @lock/examine to restrict who may examine visual objects.
+        Suggested by [LdW]
+      * Examining objects now shows channels they're on, if any.
+        Suggested by Big Spoon@M*U*S*H.
+      * Channel-hidden players are now marked in @chan/who for those
+        who are allowed to see them.
+      * @uptime shows more upcoming events, and shows them to mortals.
+        Suggested by Kyieren@M*U*S*H.
+      * @chzone obj works like @chzone obj=none. Suggested by Mystery8@M*U*S*H
+      * Player creation is now announced to MONITOR players. Suggested
+        by Paul@M*U*S*H.
+      * Poll message kept across @shutdown/reboot. Suggested by [SW].
+      * The military_time directive is removed from mush.cnf. It only
+        affected the way time was shown in @idle/@haven/@away messages
+        anyway. Reported by Angelus@M*U*S*H.
+
+Fixes:
+      * help for lnum() and dig() improved. Leo@ATS TrekMUSH
+      * help for @charges improved. Suggested by Scott Weber
+      * @mvattr a/b=a/b would clear the attribute. No longer.
+        Reported by Octavian@M*U*S*H
+      * type(obj) would log a "WEIRD OBJECT" message if obj was 
+        a garbage object. Reported by RLM. [TAP]
+      * Bug in deciding when to take a penny for queued commands fixed
+        by Stephen Dennis.
+      * Portability fixes for gcc 2.95.2 and other compilers who require
+        that function pointers in prototypes include the function args. 
+        Reported by Gepht.
+      * @chan/decomp should include the channel description, too. 
+        Report by David@M*U*S*H.
+      * Two other ways to be inside an object inside yourself reported by
+        Ashen-Shugar, and one by Rhysem@M*U*S*H.
+      * Small memory leak when doing @cpattr of a standard attribute to a
+        non-standard attribute is fixed.
+      * Help clarification for pemit() suggested by Falor@M*U*S*H.
+      * Help parents and @search3 fixed. Suggested by rodregis@M*U*S*H.
+      * Tport_anything didn't allow teleporting things to exits. 
+        Noted by Vexon@M*U*S*H.
+      * Z_TEL flag works on ZMO's as promised now. Report by [SW].
+      * Potential crash in moveit fixed. Report by Howie@NF TrekMush
+      * @cemit now does the checks that @chat does, in regard to being
+        of the right type, allowed to speak, on the channel, etc.
+        Suggested by Oleo@M*U*S*H.
+      * getstring_noalloc was doing an fgetc into a char variable,
+        instead of an int, so wasn't 8-bit clean. Report by Slava.
+
+Version 1.7.2 patchlevel 35                        January 27, 2001
+
+Fixes:
+      * Fixed a bug in filter introduced in p34. Report by Jason Rhodes.
+      * Help for sort() now indicates that 'n' sorts integers. Report by
+        Dave Milford.
+
+
+Version 1.7.2 patchlevel 34                        October 2, 2000
+
+Fixes:
+      * filter now looks at the whole result, not just the first
+        character, when checking if the filter function returned '1'. [SW]
+      * raw_input and raw_input_at are now unsigned char *, so
+        they build right on HP/UX and similar. Report by Jeff Hildebrand
+
+
+Version 1.7.2 patchlevel 33                        August 17, 2000
+
+Fixes (backported from 1.7.3p4):
+      * Bug in con() patched.
+      * Bug in deciding when to take a penny for queued commands fixed
+        by Stephen Dennis.
+      * Configure portability fixes
+      * Better handling of cases where the maildb has messages from
+        dbrefs that are out of range (due to truncating a db to remove
+        corruption, for example). Suggested by Ashen-Shugar.
+      * Various fixes to better resist crashing due to attacks involving
+        overwhelming connections. 
+      * @mvattr a/b=a/b would clear the attribute. No longer.
+        Reported by Octavian@M*U*S*H
+      * type(obj) would log a "WEIRD OBJECT" message if obj was 
+        a garbage object. Reported by RLM. [TAP]
+      * Small memory leak when doing @cpattr of a standard attribute to a
+        non-standard attribute is fixed.
+      * Tport_anything didn't allow teleporting things to exits. 
+        Noted by Vexon@M*U*S*H.
+      * Z_TEL flag works on ZMO's as promised now. Report by [SW].
+      * Potential crash in moveit fixed. Report by Howie@NF TrekMush
+      * getstring_noalloc was doing an fgetc into a char variable,
+        instead of an int, so wasn't 8-bit clean. Report by Slava.
+
+Version 1.7.2 patchlevel 32                        April 17, 2000
+
+Fixes:
+      * @cpattr from a non-standard attribute to a standard one
+        didn't preserve the AF_STATIC flag, and a subsequent atr_clr
+        could cause a crash.
+
+Version 1.7.2 patchlevel 31                        April 9, 2000
+
+Minor Changes:
+      * The SAY_TOKEN now applies to channels. That is, +public "Hi!
+        will not result in a doubled initial quote any more.
+        Suggested by Tyler Spivey.
+Fixes:
+      * Uninitialized negate_perms in the monitor flag table.
+        Report by Concordia@Beyond the Fire.
+      * Updates to help changes to match CHANGES.
+      * Another way to end up in an object in your inventory has been 
+        fixed. Report by Lensman.
+      * Unused ancient ccflags cruft removed from hints files.
+      * Considerable linting and cleanup. [SW]
+      * MacOS portability improvements. [DW]
+      * You may reset your @alias to itself in different case.
+        Suggested by Bolt.
+
+Version 1.7.2 patchlevel 30                        March 14, 2000
+
+Major Changes:
+      * New US export rules allow us to include shs.c and funcrypt.c
+        in the Penn distribution! Yay!
+      * Code is included in bsd.c for Windows NT users that uses
+        NT's native i/o instead of the bsd socket layer for
+        much improved performance. If you want it, edit src/bsd.c
+        and uncomment the define at the top. [NJG]
+Minor Changes:
+      * New eplayer, eroom, eexit, eobject classes for searches,
+        like Tiny. By Rhysem.
+      * @sitelock/access.cnf can now use regexp patterns. By Raevnos.
+      * The Exits() macro is replaced with Source(), Home(), etc.
+        where sensible. By Maverick@M*U*S*H.
+      * Example of bzip2 compression defines in mushcnf.dst by David@M*U*S*H.
+      * shs.c can now be configured to reverse endianness, so you
+        can more easily use win32 dbs on unix (or vice versa) without
+        password hassles. This is in mush.cnf. [NJG]
+      * JUMP_OK no longer allows anyone to @tel an exit into your room.
+        You must control the destination or have the open_anywhere 
+        power in order to do this now. Report by rodregis.
+Fixes:
+      * Calling do_log with LT_TRACE resulted in no logging. Report by David.
+      * MacOS (and general) portability improvements, suggested by [DW]
+      * help for before(), after() notes case-sensitivity. By Audumla.
+      * hasflag() didn't work with MONITOR. Report by Mystery8.
+      * A little more linting. [NJG]
+      * Fixed help reference to 'global functions'. Report by Falor.
+      * Some gmalloc fixes around missing newlines. Report by Raevnos
+      * Improvements to help switch(). Report by Omozali.
+      * Buffer overflow in @wall fixed. Report by rodregis.
+      * Fixed (I think) the FreeBSD/Linux problem of not finding
+        sigchld/sigcld and similar ilk. Hints for FreeBSD are back.
+      * Crash bug in @link fixed. Report by Howie@New Frontier TrekMUSH
+
+Version 1.7.2 patchlevel 29                        January 23, 2000
+
+Fixes:
+      * src/sig.c didn't include config.h. As a result, some systems
+        with sigaction that didn't keep signal handlers installed
+        (some linuxes) would crash very quickly on the second SIGALRM.
+
+Version 1.7.2 patchlevel 28                        January 14, 2000
+
+Minor Changes:
+      * New 'deny_silent' option for access.cnf sites.
+        Turns off logging of denied connection/creation/guest/register
+        from a site, to prevent logfile spamming by twinks.
+        Reported by Kludge-BC.
+      * TFPREFIX attribute, if set, is used in place of FugueEdit>
+        in @decomp/tf. [SW]
+      * @grep/print no longer requires you to be set ANSI. Suggested 
+        by Philip Mak.
+      * Improved reporting of function invocation limit. [TAP]
+      * /noeval switch added to think command.
+      * Changes to enhance portability to Mac 68k platform and others
+        that need < 32k of local data per function. [DW]
+      * Objects are only considered to be listening if they're
+        connected players, puppets, have an @listen set, or
+        are things/rooms with MONITOR flags. Previously, things/rooms
+        with ^patterns in attributes were considered listeners, even if
+        they weren't MONITOR. Suggested by Luke.
+Fixes:
+      * gmalloc.c updated from 1987 version to 1995 version. By Gepht.
+      * help corrections for shl and shr by Vexon@M*U*S*H.
+      * help corrections for @clock by Krad@M*U*S*H.
+      * RLIMIT_NOFILE bug fixed by Susan Thorne.
+      * Eliminated variables named 'new' to promote easier C++
+        compiles. Suggested by Gepht.
+      * Compiling with CSRI_TRACE works again. [TAP]
+      * signal stuff broken out to src/sig.c to allow link w/info_slave.
+      * strcasecmp and friends prototyped better in externs.h. [DW]
+      * Overzealous test for inherit flag on zoned objects corrected
+        by Nveid.
+      * Clearing an @attribute'd attribute's value on some object
+        and later manipulating the attribute could corrupt the
+        @attribute table in some cases. Fix suggested by Kami@SW2.
+      * Nested pemits could truncate one another. Reported by Alierak.
+      * Channel messages didn't correctly set %#. Reported by Saberwind.
+      * info_slave used ident even if use_ident was off in the 
+        mush.cnf file. Reported by Rhysem.
+
+Version 1.7.2 patchlevel 27                        September 22, 1999
+
+Minor Changes:
+      * Added Raevnos's sitelock/name patch to allow removing names
+        with @sitelock/name !<name> and to fix a display bug.
+      * bsd.c, info_slave.c, and player.c now deal in IP addresses as well
+        as hostnames (which can be spoofed), providing more reliable logging
+        and access control. IP addresses are stored in the LASTIP attrib
+        on players, as per LASTSITE. Suggested by David@M*U*S*H.
+      * Hidden connections are announced as per DARK ones. Suggested 
+        by Julianna@ATS.
+      * New /noisy switch to @cemit prepends <Channel> to message.
+        Suggested by Spork@M*U*S*H.
+Fixes:
+      * help vmul() incorrectly defined the dot product (which vdot() does).
+        Reported by [SW].
+      * Typo fixed in help @set3. Reported by Logain@ATS
+      * Typo fixed in help @emit. Reported by Rhysem@M*U*S*H.
+      * Various help fixes by mith, Big Spoon, and Krad@M*U*S*H.
+      * @function now works for mortals as the help indicates. Report by mith.
+      * @log/wipe should be @logwipe in comments in mushcnf.dst. 
+        Report by Spork@M*U*S*H.
+      * Object names are now limited to 256 characters. Fixes some
+        buffer overflow issues.
+
+Version 1.7.2 patchlevel 26                        July 18, 1999
+
+Minor changes:
+      * @verb didn't save stack args before dealing with the WHAT/OWHAT
+        msgs, as TinyMUSH does. Changed to emulate TinyMUSH.
+        Reported by Angel. [SW]
+Fixes:
+      * The noeval-eqsplit fix cause weirdness with attribute setting by
+        directly connected players when specifying the obj by function.
+        Fixed. Reported by Julienna@ATS.
+      * Wizards couldn't modify atrlock'd attribs without breaking the 
+        lock first. [SW]
+      * @find by Wizards showed all garbage objects. Reported by mith.
+
+
+Version 1.7.2 patchlevel 25                        July 10, 1999
+
+Minor changes:
+      * New 'nofixed' command restriction, by popular demand.
+      * CONFIGURATION messages in netmush.log shortened to CONFIG.
+        Suggested by mith.
+      * Attributes with the Wizard flag can no longer by created/modified
+        by any non-wizard, even that attribute's owner. Reported by
+        Kurt Fitzner.
+      * @pcreate now shows the dbref of created player. Suggested by
+        Oderus.
+      * When you receive an @mail message, you're now told the number.
+        Suggested by Rak@M*U*S*H, among others.
+      * The @toad command has been removed. The security issues it
+        presents, though not unsolvable, aren't worth solving just to
+        provide Wizards with a humiliating alternative to @newpassword.
+Fixes: 
+      * %q0-%q9 were not preserved during evaluation of player-message
+        attributes (DESC, SUCC, DROP, etc.) Reported by Geoff Gerrietts
+      * Added some hints from FreeBSD. Suggested by Lord Argon of mux.net.
+      * Better Configure handling of library paths. 
+      * 'nogagged' wasn't working correctly in restrict_command. Fixed.
+      * @search on rooms sometimes reported a null room. Reported by mith.
+      * Nearly all source files now include conf.h (which includes options.h), 
+        and do so before any other PennMUSH header file except config.h 
+        and copyrite.h.  Suggested by Joel Ricketts.
+      * Fixed a few comparisons of <= db_top. Reported by Kurt Fitzner.
+      * @oemit <obj>=<message> was emitting to the enactor's location,
+        rather than to <obj>'s location, as it should have been. Fixed that
+        and fixed help oemit() which documented this wrong behavior.
+        Reported by Kurt Fitzner.
+      * An 8-bit-unclean construction in bsd.c fixed by Christoper/Chicago.
+      * p/noeval <message> (repaging) eval'd message anyway.
+      * Args to $commands that looked like functions were being eval'd
+        even if not in brackets. Reported by [SW]. [TAP]
+      * @lock/listen could cause weird pose corruption. Reported by
+        David@M*U*S*H. [SW]
+      * Clarification of wiz_noenter in mush.cnf suggested by 
+        Interloper@M*U*S*H.
+      * Bug in orflags/andflags could cause weird results with toggles.
+        Like nospoof players tested positive for 'J'. Reported by Saberwind.
+      * Bug in make customize fixed. Reported by Saberwind.
+      * References to a PASSWORD attribute removed from help. Reported by
+        Saberwind.
+      * Fixed db_top bug in search/lsearch reported by Saberwind.
+      * @halt code was screwy. Reported by Krad@M*U*S*H
+      * Wizards could grant @powers to God. Reported by Saberwind.
+      * delete() with negative position arg could crash. Reported by
+        Ashen-Shugar.
+      * @clone of an exit while inside an object could have unpredictable
+        effects. Reported by Andy@RobotechMUSH
+      * Typo in help aposs() fixed. Reported by Philip Mak.
+      * @hide now defaults to @hide/on. Reported by Saberwind.
+      * ldelete() added help list functions. Reported by Rak@M*U*S*H
+      * @attribute/rename didn't update the attribute's name quite right.
+        Reported by mith.
+      * @clone by a room didn't properly set the cloned object's location.
+        Reported by Philip Mak.
+
+Version 1.7.2 patchlevel 24                        April 5, 1999
+
+Fixes:
+      * @search/lsearch didn't behave right when given an upper range
+        argument of exactly the highest dbref in the db. Reported by
+        [SW].
+      * Unlinked and HOME-linked exits were mishandled during dbcks, 
+        just like variably-linked ones in pl23. Reported by [SW].
+      * Help fixes. [TAP]
+
+        
+Version 1.7.2 patchlevel 23                        April 2, 1999
+
+Fixes:
+      * The NoLeave() macro was misdefined, but also not used (whew).
+        Now it's defined right and used.
+      * Giving a / without a switch to commands caused unpredictable
+        behavior. Fixed. Report by Broncalo@Dune III.
+      * Variable-linked exits were mishandled during dbcks, resulting in
+        them being relinked to their source rooms.
+      * @grep/iprint showed the hilighted matches in the same case as 
+        the pattern was given, rather than the case there were in the
+        attribute. Reported by Philip Mak.
+      * The LAST attribute was set differently when players created and
+        when the later connected. The latter case wasn't appropriately
+        prepending single-digit dates with a 0, which fouls up convtime()
+        calls on LAST. Noted by [SW].
+
+
+Version 1.7.2 patchlevel 22                        March 19, 1999
+
+Minor changes:
+      * More extensive macro cleanup, based on a patch by David@M*U*S*H
+      * Notable for your own code: Inherit() is now Inheritable(),
+        DarkLegal() checks if something's ok to be invisible when it's DARK,
+        Destroyed() is now IsGarbage(), and some other new helpful macros
+        can be found in hdrs/dbdefs.h
+      * Objects now store their creation cost, not their 'value' (which
+        used to be cost/5 - 1, and had relevance for sacrificing, a now
+        obsolete concept). There is no longer a limit on how much you
+        can spend to create an object, and it's all refunded when the
+        object is recycled. Reported by David@M*U*S*H.
+
+Fixes:
+      * Two memory leaks and one unbalanced mem_check fixed. [SW]
+      * @oemit <room>/<object> was broken in many ways. 
+        Reported by [SW].
+      * Help @drop/@odrop/@adrop updated to mention use on exits.
+        Suggested by Stewart Evans.
+      * God using @logwipe and giving the wrong password crashed the MUSH.
+        [SW]
+      * search() was behaving as lsearchr() not lsearch(). Noted by
+        KMoon.
+      * Bad range arguments for @search and lsearch() now give an
+        error message and don't charge the player. [SW].
+      * Help @search3 had a typo. Fixed by Halatir@M*U*S*H.
+      * Restricting the 'goto' command now also restricts movement
+        through exits. Suggested by Christopher Poff.
+      * Objects and rooms now notify their contents when they start/stop 
+        listening. Report by [RLM].
+      * Error in help for @channel referring to @config. Krad@M*U*S*H
+
+
+Version 1.7.2 patchlevel 21                   February 16, 1999
+
+Minor changes:
+      * The restart script now tries to determine its own directory,
+        so it may not require editing to set GAMEDIR any more.
+        Idea by David@M*U*S*H.
+      * Various @find/@search/@entrances commands charged you the
+        FIND_COST even if you didn't have permission to run the command.
+        We don't do that any more. Report by Jonathan Booth.
+      * $command and ^listen pattern matching is now case-insensitive
+        even when the attrib is set REGEXP, unless the attrib is set
+        CASE. In the past, glob matching was case-insensitive and
+        regexp matching was case-sensitive, which cause problems if
+        you tried to regexp match a disabled standard command.
+        Now you've got full flexibility. This may break any current
+        regexp-based $command or ^listen matching that relies on
+        case sensitivity (set those attributes CASE). We now also
+        have a new insensitive regmatch function: regmatchi()
+        Report by Jonathan Booth.
+
+Fixes:
+      * Anyone could @chan/priv channels, even if they didn't pass
+        the modlock. Report by David@M*U*S*H.
+      * DARK disconnects are now shown correctly on chat channels.
+        Really this time. :) Report by Broncalo@Dune III
+      * help for CONNECTED flag updated. Report by matcat@M*U*S*H
+      * Using @kick within a user-defined command could crash the MUSH.
+        Reported by Kludge-BC. [TAP]
+
+
+Version 1.7.2 patchlevel 20                   January 26, 1999
+
+Minor changes:
+      * Many expression replaced with macros by David@M*U*S*H.
+Fixes:
+      * @mail/silent/urgent didn't set the message urgent. 
+        Patch by Halatir@M*U*S*H.
+      * You could get free coins by repeatedly killing your objects.
+        Reported by Max@M*U*S*H. [TAP]
+      * You could rename a channel to a name already in use.
+        Reported by David@M*U*S*H.
+
+
+Version 1.7.2 patchlevel 19                   December 2, 1998
+
+Minor changes:
+      * The main select() polling loop now times out every second,
+        so we will reliably call local_timer() and handle alarms
+        every second. Suggested by [NJG].
+      * 'make' now performs a make in game/txt, assuring that help
+        indices are rebuilt after a patch. Suggested by Broncalo@Dune III.
+
+Fixes:
+      * Crash in using @cpattr with standard attribs fixed.
+        Reported by Atuarre@ATS.
+
+
+Version 1.7.2 patchlevel 18                   November 25, 1998
+
+Minor changes:
+      * Guest players don't receive a paycheck any more. Suggested by
+        Kyieren@M*U*S*H
+      * look_helper() internal function now uses privtabs. As an
+        epiphenomenon, @set obj/attr=locked is now synonymous to
+        @atrlock obj/attr=on. Suggested by [SW].
+
+Fixes:
+      * Win32 compile fixes. [NJG]
+      * DARK disconnects are now shown correctly on chat channels.
+        Report by Broncalo@Dune III
+      * Quiet players no longer see 'Title set.' messages when
+        using @chan/title. Patch by Halatir@M*U*S*H.
+      * @cpattr/@mvattr now copy attribute flags. Report by Jon Booth.
+      * Some compiler warnings fixed by Atuarre.
+      * The 1 and 5 minute dump warning messages weren't being used.
+      * When matching regexp's, later parenthesized subexpressions 
+        weren't correctly assigned to %-vars when earlier ones 
+        were empty. Report by Geoff Gerrietts. [TAP]
+      * Some messages as a result of looking at a room were being
+        placed onto the wrong queue, so remote viewers (@listen *,
+        @pemit/remit to somewhere) would get things out of order.
+        Reported by David@M*U*S*H. 
+      * Help added for functions() and fixed for timestring(). 
+        Report by Geoff Gerrietts.
+      * soundex() misbehavior for very short words fixed.
+        Report by kmoon.
+      * @attribute/access acted as if it were always /retroactive. [SW]
+      * Changes of flags on a standard attribute were lost across
+        restarts. Reported by [SW].
+      * lcstr, ucstr, capstr, and encrypt and decrypt in the 
+        "real" funcrypt.c are now ansi-aware. The former 3 preserve
+        ansi formatting, while the latter two strip it. You must download
+        a new version of funcrypt.c from ftp.pennmush.org (USA/Canada)
+        or export.pennmush.org -- it is not patched herein. 
+        Reported by Ashen-Shugar.
+
+
+Version 1.7.2 patchlevel 17                   November 11, 1998
+
+Minor changes:
+      * Newly created players now have a default uselock of =me.
+      * Number of available file descriptors is printed in startup log.
+        Suggested by Doogie@ATS Trekmush.
+      * The @chat/@cemit commands can no longer be used by gagged players.
+
+Fixes:
+      * Adding functions in funlocal.c shouldn't produce compiler warnings.
+        Patch by Halatir@M*U*S*H
+      * log(0) or ln(0) could crash non-IEEE compliant math libraries.
+        Reported by Drakwil and Talos at SNW.
+      * csrimalloc wouldn't compile with glibc. Fix by Mike Selewski.
+      * Order of checks for number of file descriptors changed to
+        do better on POSIX and hybrid systems like FreeBSD. Suggested by
+        Doogie@ATS Trekmush.
+
+
+Version 1.7.2 patchlevel 16                   October 17, 1998
+
+Fixes:
+      * table() could be used to crash the MUSH. Report by Ashen-Shugar. [TAP]
+      * whisper/list didn't work. Report by Kamala@ATS TrekMUSH, via
+        Mikey@M*U*S*H 
+      * andflags(player,C) checked for the (useless) CHOWN_OK flag
+        rather than the COLOR flag. Its now been special-cased to
+        check COLOR on players. This is a kludge, but probably worth it.
+      * Top of admin WHO now lists 'Loc #' not 'Room #', as that's more
+        accurate. Suggested by Saberwind.
+      * @log/wipe returns as @logwipe. Its absence was reported by 
+        Nveid@M*U*S*H.
+      * Date in hdrs/version.h now y2k compliant.
+
+
+Version 1.7.2 patchlevel 15                   September 7, 1998
+
+Fixes:
+      * @emits weren't propagated through AUDIBLE exits. Report by Nammyung.
+      * Building w/o ROYALTY_FLAG defined works again. Report by Scott Weber.
+      * When matching $ or ^ patterns with the REGEXP attribute flag set,
+        a failed match would then be improperly checked for normal 
+        matching as well. Reported by Jason Rhodes.
+      * Attribute flags weren't listed in @decomp. Reported by
+        Jonathan Booth.
+      * Make customize never got updated to match the new mushcnf/restart
+        system. Now it has. Reported by Manic@FinalFrontier
+      * @edit now works on attribs starting with _. Reported by Jason Rhodes.
+      * Help files should work better on Win32. Reported by Miphon. [TAP]
+
+
+Version 1.7.2 patchlevel 14                   August 4, 1998
+
+Minor Changes:
+      * You may @parent to an object you control via ZMO, even if you
+        don't own it. Patch by Halatir@M*U*S*H
+      * In lsearch() and @search, you may refer to object types
+        in either the singular (ROOM) or plural (ROOMS).
+      * Most chat messages now include the name of the channel. 
+        Suggested by Philip Mak.
+
+Fixes:
+      * Long @chat messages crashed the server. Reported first by Rusty
+        and Siris@M*U*S*H. [TN]
+      * Setting the ZONE flag on a non-zonelocked player should give
+        a warning, and wasn't. Reported by Halatir@M*U*S*H
+      * @@ was parsing its argument. No longer. [RLM]
+      * The @config listing was weird w.r.t. compression. [TN]
+      * @shutdown/reboot could cause a crash if a player had an
+        OUTPUTPREFIX or OUTPUTSUFFIX set.
+      * Hint to linux users about undefining I_MALLOC when using
+        gmalloc. Reported by Kyle Forbes.
+      * @shutdown/reboot now calls local_shutdown(). Reported by Kyle Forbes.
+      * When loading a db in which an object with dbref n has attributes
+        owned by players with dbrefs > n, the attribute ownership was
+        reset to GOD. This should no longer happen unless the owner 
+        really is invalid. Most recently noted by [SW].
+      * Exits that have contents (corrupt!) are fixed up in dbck.
+      * dbck is run whenever the db is loaded.
+      * objeval() help fixed. Reported by Yeechi Chen.
+      * Compiling without ROYALTY_FLAG defined was broken. Reported by
+        Scott Weber.
+      * Sufficiently tricky use of locks could cause a crash due to
+        massive function invocation or recursion. Reported by Atuarre. [TN]
+
+
+Version 1.7.2 patchlevel 13                   July 7, 1998
+
+Minor changes:
+      * @mail/file now shows the folder name of the destination folder
+        as well as its number. Suggested by Julianna@ATS TrekMUSH
+
+Fixes:
+      * Problems with ANSI causing Pueblo to bleed have been identified
+        and fixed!
+      * Bug with cwho() fixed. Report by Tripsis@M*U*S*H. [TAP]
+      * 'make diffs' in Makefile updated to use prcs and to produce
+        diffs for patches without Index: lines which may confuse
+        non-POSIX versions of patch.  
+      * Fixed typo in options.h reported by Kyle Forbes.
+      * Comments in src/services.c and src/filecopy.c are now C-style,
+        not C++ style. Some compilers were puking on these, even though 
+        WIN32 wasn't defined and the preprocessor should've ignored this 
+        stuff. Go figure.
+      * Side-effect functions like pemit() didn't obey the restrictions
+        on the corresponding command (like @pemit), and setting attributes
+        with @set could get around restrictions on ATTRIB_SET. 
+        Reported by Scott Weber. [TAP]
+      * Help for t() and elock() clarified by Octavian@M*U*S*H.
+      * next() could be used on an object in a room that the player
+        didn't control to get the room inventory. Reported by Octavian@M*U*S*H
+      * hint/aux.sh has been renamed hints/a-u-x.sh. This means it won't
+        be properly recommended by Configure on A/UX systems, but Win32
+        programs often puke on files name 'aux.*' because they're braindead,
+        and there are lots more Win32 users than A/UX users. Bummer.
+      * When A was inside B, and @listen B=*, A would hear everything
+        in B's room (good) except B's own speech (bad). Report by 
+        Nemesis @ Beast Wars 2
+      * help exits clarified by Nammyung@M*U*S*H
+      * help comp() clarified by Halatir@M*U*S*H
+      * @squota without a limit now shows the victim's quota in addition
+        to asking what it should be set to. This more closely matches
+        the old behavior of @squota without a limit being treated as
+        @quota. Reported by Matt@M*U*S*H
+      * @chan/what on a nonexistent channel didn't produce any
+        feedback. Reported by Octavian@M*U*S*H.
+      * Typo in help zone master rooms corrected. Report by Matt@M*U*S*H.
+
+
+Version 1.7.2 patchlevel 12                   June 11, 1998
+
+Fixes:
+      * GAGGED players could pose/semipose. Reported by Jorhan@M*U*S*H. [TN]
+      * convsecs() help clarified.
+      * @decomp obj/attr didn't work if you couldn't examine the object
+        even if the attribute was visible. Reported by Jonathan Booth.
+      * make customize had a problem with the way it handled the
+        hlp directory. It now creates a real hlp directory, but makes
+        all the standard hlp files symlinks to the distributed ones.
+        (It used to make the hlp/ directory a symlink, which did bad
+        things with 'txt/Makefile'. Report by Gasparin Balazs.
+      * help control rewritten to clarify the real algorithm.
+        Suggested by [SW].
+      * Configure is more flexible when determining if you're
+        building under cygwin. Reported by Miphon.
+      * zfun() worked, but gave an error message anyway. Fix by Rob@DuneIII
+      * hdrs/regexp.h renamed to hdrs/myregexp.h to avoid conflict
+        with standard regexp.h header file in cygwin.
+      * Roy/see_all players could, under some circumstances, evaluate
+        functions with wiz privileges. Reported by Atuarre. [TAP]
+
+
+Version 1.7.2 patchlevel 11                   May 25, 1998
+
+Changes:
+      * The PennMUSH copyright notice has been changed, as the
+        licensing terms for TinyMUD/TinyMUSH 2.0 have changed,
+        and to update the TinyMUSH 2.2 part of the copyright and
+        the PennMUSH part as well. 'help copyright' now gives the
+        copyright, and it's in COPYRITE and hdrs/copyrite.h.
+        The licensing terms are now shorter, but practically very similar.
+Fixes:
+      * Possible infinite loop (with disk-filling output!) in @dbck
+        with certain types of DB corruption fixed.  Report by RLM and
+        Arathorn@CDI.  [TAP]
+      * @undest intermittent crash-bug fixed. Report by Arathorn. [TAP]
+      * regmatch() crash-bug fixed. Report by Tavoan@ATS. [TAP]
+      * Ansi bleeding problem fixed. Report by Atuarre@ATS. [TAP]
+      * The embedded version of mkindx used by Win32 builds had a bug - 
+        some global variables weren't getting properly reset.
+        Fix by Stephane Thibault.
+      * Help for @oxmove added. Report by Bray Roned@ATS.
+      * isnum() was broken if tinymath was defined. Report by Daniel Peters.
+      * portmsg needed MUSH_IP_ADDR, which is now runtime configured.
+        It no longer does (see comment in portmsg.c if you need this
+        functionality). Report by Daniel Peters.
+      * Help for dig() and lnum() clarified by Andre Leopold.
+
+
+Version 1.7.2 patchlevel 10                   April 24, 1998
+
+Fixes:
+      * MANIFEST updated to reflect the deletion of src/nmalloc.c
+
+
+Version 1.7.2 patchlevel 9                    April 21, 1998
+
+Fixes:
+      * Myopic flag didn't work unless Pueblo support was on. Reported by
+        Rhysem@M*U*S*H.
+      * help debug referred to can_debug, a now-obsolete power.
+        Reported by Rodimus Prime @ TF2005.
+      * @chown to a Zone Master didn't work. The @chown code has been
+        rewritten to be easier to read. Report by Trispis@M*U*S*H.
+      * open_anywhere was mis-listed in help powers2. Report by Trispis.
+      * nmalloc.c is removed, and Win32 compiles should be a bit easier.
+      * Win32 build no longer limited to 64 sockets; 256 instead. [NJG]
+
+
+Version 1.7.2 patchlevel 8                    April 2, 1998
+
+Fixes: 
+      * round() could crash on very big numbers on some systems.
+        Reported by Atuarre@ATS.
+      * Problem with exits getting a contents list in certain 
+        conditions fixed. Reported by Atuarre@ATS. [TAP]
+      * Problems with puppets and Pueblo fixed. Report by Mop-Boy.
+      * mkindx problem with dos text files fixed. [NJG]
+      * Order of include files in htab.c was wrong, caused compilation
+        problems on SCO Openserver. Reported by Flame.
+      * On Win32, the MUSH could quit without flushing its buffers. [NJG]
+      * When inside of an object with @listen *, you didn't see things
+        when the object did a 'look'. Reported by Vedui.
+      * Warnings in rwho.c eliminated. [NJG]
+      * More fooling around with mymalloc.h to help the Win32 compile.
+        Suggested by NJG.
+      * open_everywhere power get left off help powers list at some point.
+        Reported by Steven@Champions.
+      * Error message for joining a non-existant channel was different
+        from that for joining a channel that exists but you can't
+        see. Reported by Octavian@M*U*S*H.
+      * get()'ing an attribute that isn't set now returns an error 
+        message only if the default permissions don't allow you to 
+        get it. This allows get(player/ALIAS) to always work, returning
+        an empty string if the player has no ALIAS. [TAP].
+      * base_room mush.cnf option got left out.
+      * dbck didn't check for disconnected rooms correctly. Noted by
+        Rhysem.
+
+
+Version 1.7.2 patchlevel 7                    March 10, 1998
+
+Fixes:
+      * Typo fixed in pennflag.hlp. Report by Keith Howell.
+      * Bug in mid() that could cause crashes on some OS's when using
+        bad arguments fixed. Report by Yanek@DragonStarMUSH. [TAP]
+      * @wait obj=command fails when tiny_math is defined. Report by Yanek.
+      * With tiny_math defined and tiny_booleans not defined, 
+        strings in booleans weren't being treated as 1. Report by Yanek.
+      * @decompile now shows @powers, too. Report by Jonathan Booth.
+
+
+Version 1.7.2 patchlevel 6                    March 8, 1998
+
+Fixes:
+      * src/mymalloc.c now includes config.h correctly. [NJG]
+      * @zemit would change zones of objects while running.
+        Report by Steve Sanderlin and Vedui.
+      * Minor cosmetic bug in @config/list. Reported by Mike Wesson
+      * @chown'ing an object to a Zone player doesn't reduce the Zone
+        player's quota, but @chown'ing an object back from a Zone player
+        should reduce yours (and didn't). Report by Vedui.
+
+
+Version 1.7.2 patchlevel 5                    March 2, 1998
+
+Fixes:
+      * @cloning an object on a channel could crash the MUSH. Report by
+        Mordak@ATS.
+      * You couldn't add 2 objects with the same name to a channel.
+        Report by Mordak@ATS.
+      * Help for inc() fixed. Brian@M*U*S*H
+      * On Win32, the text file indexes are now properly sorted,
+        and things work ok if you don't have a text file defined. [NJG]
+      * Wiz objects couldn't add players to channels. Report by Mike@M*U*S*H
+      * Compile problem with fork and win32 fixed. [TAP]
+      * You shouldn't get nospoof notification from your own emits. [TN]
+      * You no longer get nospoof notification from every @chat, only
+        from @cemit.
+      * @lock obj=here or @lock obj=exit failed. Report by Luke@M*U*S*H
+      * The MUSH announces where it's sending stderr when it starts up. [NJG]
+      * If there was nothing on the queue, and no user activity,
+        the MUSH could wait as long as 999 seconds before checking to
+        see if it should do anything (like a dump, a shutdown, idling
+        someone out, etc.) This has been changed to 5 seconds.
+        Report by NJG.
+      * Encryption buffer for SHS encryption was too small, could cause
+        password problems. [NJG]
+
+
+Version 1.7.2 patchlevel 4                    February 24, 1998
+
+Fixes:
+      * Fixed a few more compiler warnings under Win32. [NJG]
+      * Output to a non-ANSI, non-COLOR player could cause crashes.
+        Reported by Vedui.
+
+
+Version 1.7.2 patchlevel 3                    February 22, 1998
+
+Fixes
+       * Fixed a slew of compiler warnings under Win32. [NJG]
+       * Jonathan Booth Removed a few #ifdef EVENTS that lingered.
+       * Room names weren't shown bold to people who had ANSI
+         but not COLOR set. Report by Sylvia
+       * Added help alias pueblo() for pueblo. Suggested by Vedui.
+       * Fixed some problems with ansi() and tf reported by Vedui.
+       * @readcache could cause crashes. First report by Mop-Boy.
+       * src/tcl.c is now better located by Configure. [TN]
+
+
+Version 1.7.2 patchlevel 2                    February 19, 1998
+
+Fixes:
+       * Reading of compressed dbs didn't work right. Report by Roger Christie
+       * Fix to help to remove reference to @config/globals. Mordak@ATS.
+
+
+Version 1.7.2 patchlevel 1                    February 18, 1998
+
+Fixes:
+       * max_dbref was limited to 256 by mistake. Report by Rhysem@M*U*S*H
+       * restricted_building in mush.cnf didn't work. Does now.
+         Report by Rhysem@M*U*S*H
+       * memchecks for hash tables didn't work right. Report by [SW]
+
+
+Version 1.7.2 patchlevel 0                    February 9, 1998
+
+Major Changes:
+       * Support for the Pueblo MUD client (http://www.chaco.com/pueblo)
+         which allows the MUSH to send html to the client. This is
+         runtime configurable with @enable/@disable and mush.cnf. [TN]
+       * Regular expression support: the REGEXP attribute flag causes
+         attributes to match $ and ^ patterns using regular expressions.
+         regmatch() matches regular expressions. [2.2]
+       * PennMUSH tarfile now unpacks itself in a pennmush/ directory,
+         by popular request. pennmush/ is now the 'top-level' directory;
+         patches should still be applied from within the pennmush/ directory.
+       * More compile-time options are now run-time options:
+          HASPOWER_RESTRICTED, SAFER_UFUN, DUMP_WARNINGS,
+          INDEX_COMMAND, RULES_COMMAND, HATE_DEST (general command rename?),
+          NOISY_WHISPER, POSSESSIVE_GET POSSGET_ON_DISCONNECTED,
+          REALLY_SAFE, DESTROY_POSSESSIONS, NULL_EQ_ZERO,
+          TINY22_BOOLEANS, TINY_TRIM_ORDER,
+          ADEST_ATTRIB, AMAIL, PLAYER_LISTEN, PLAYER_NOAHEAR,
+          ROOM_CONNECTS, ANSI_NAMES, COMMA_EXIT_LIST, COUNT_ALL,
+          EXITS_CONNECT_ROOMS, WIZWALL_PREFIX, RWALL_PREFIX, WALL_PREFIX,
+          NO_LINK_TO_OBJECT, QUEUE_PER_OWNER, WIZ_NOAENTER, USE_IDENT,
+          MUSH_IP_ADDR, MAILER, ANSI_JUSTIFY, PLAYER_NAME_SPACES,
+          NO_FORK, EVENTS, MILITARY_TIME, LOGIN_LIMIT, IDLE_TIMEOUT,
+          RESTRICTED_BUILDING, FREE_OBJECTS, QUOTA, BUILDING_LIMIT,
+          FLAGS_ON_EXAMINE, EX_PUBLIC_ATTRIBS, TINY_ATTRS, FULL_INVIS,
+          SILENT_PEMIT, PLAYER_LOCATE, DO_GLOBALS, GLOBAL_CONNECTS,
+          PARANOID_NOSPOOF, ACCESS_FILE, NAMES_FILE, OBJECT_COST,
+          EXIT_COST, LINK_COST, ROOM_COST, QUEUE_COST, QUOTA_COST,
+          FIND_COST, PAGE_COST, KILL_BASE_COST, KILL_MIN_COST, KILL_BONUS,
+          QUEUE_LOSS, DBTOP_MAX, MAX_OBJECT_ENDOWMENT,
+          MAX_WIZ_OBJECT_ENDOWMENT, MAX_PENNIES, MAX_DEPTH, MAX_PARENTS,
+          PURGE_INTERVAL, DBCK_INTERVAL.
+       * All the functions that used to be optional are now
+         enabled. The increase in code size is negligible, and
+         the decrase in options is a win. We'll probably add 
+         some way to restrict function use in mush.cnf in the future.
+       * The original MUSH chat system is no longer distributed or
+         supported. The OBJECT_CHAT option is thus obsolete.
+         If you've been using the old system, your db will automatically
+         be converted to the new one, but you will need to recreate
+         your channels and readd players to them.
+       * ADD_NO_COMMAND_FLAG has been removed, as few people need
+         to add NO_COMMAND en masse to all their rooms and players
+         any more. Those that do can use MUSHcode for this.
+       * The definitions of GOD, MAX_ARG, have been moved out of options.h, 
+         because redefining these isn't really an option you want to exercise.
+       * dune.h is no more. Raise a glass to it.
+       * Non-standard attributes that are created by objects are set to 
+         no_command by default. This improves security in many common cases, 
+         but may require your object code to @set the attribute !no_command
+         after it creates it, if the attribute is meant to contain
+         a $command. Attributes set by players themselves (typed directly
+         from a socket) still work as they used to, as do standard
+         attributes (@va-@vz, for example). [TN]
+       * Hash table code has been tightened up. [TN]
+       * New option: tiny_math. Treat strings as 0 in math functions
+         rather than errors. This is handy for Tinymush compatibility,
+         even though it may make real errors harder to find.
+         Suggested by Ashen Shugar.
+
+Minor Changes:
+       * idlesecs() now returns the number of seconds idle for the least
+         idle connection of the player.
+       * conn() now returns number of seconds connected for the longest
+         connected connection of the player.
+       * I wrote the COPYRITE file some time ago, but forgot to include
+         it in the dist. Oops!
+       * UFAIL/OUFAIL/AUFAIL is here. Suggested by Mike Affourtit.
+       * controls() is now ok if you are See_All; you don't need to 
+         actually control the object you're testing. Reported by RLM.
+       * If you're allowing empty attributes, ICLOC is set to ""
+         on newly created players, instead of " ", to permit testing
+         with hasattrval. Suggested by Jonathan Booth. [TN]
+       * Improvements to FPE handling on FreeBSD. By Jason Young.
+       * New switch /noeval for @mail. Suggested by Mop Boy.
+       * The "dbcomp" directive in mush.cnf has been removed.
+         "compress_suffix" has been added. Databases are now specified
+         without compression suffixes.
+       * You are warned if you fail to define an option in the config file.
+         Suggested by TN.
+       * @config can now list config options in logical groups.
+       * mail.c has been removed. extmail.c is used directly instead.
+
+Fixes:
+       * hasattr/get/xget/eval now are less likely to tell a player something
+         they're not privileged to know about the existence of attributes. [TN]
+       * The big_que function has been renamed parse_que. The parse_que
+         function (which just called big_que) is gone.
+       * FAQ updated, as well as other references to pennmush.tinymush.org.
+       * Giving a negative argument to convsecs() would crash Win32.
+         As there's no reason to ever do that, you now can't. 
+         Reported by Javin@DynamixMUSH
+       * dist2d and dist3d now return floating point numbers when
+         FLOATING_POINTS is defined, as they said they would.
+       * help for pi() now refers to it as 3.141593, which is what
+         the function actually produces, due to rounding. Report by Vedui.
+       * help for beep() notes that royalty can use it to. Report by Vedui.
+       * Using a maildb that referred to db#s that didn't exist
+         in the object db (e.g., replacing your object db with minimal.db
+         without removing the maildb) would crash the server.
+         Now the server fixes up invalid messages after loading maildb
+         Report by TN.
+       * Connect screen and other cached text files should now be
+         automatically terminated with CRLF, so windows and other
+         telnet programs will see them correctly. Report by many. [TN]
+       * The separator in iter() is now parsed. Report by Ashen-Shugar.
+       * match(foo,>) always returned 1; similar problems with matchall,
+         grab, graball, strmatch, and elements. Report by Ashen-Shugar.
+       * Objects listening for channel broadcasts with ^<chan> *: didn't
+         work. Now they do.
+       * When you disabled a command in order to override it in MUSHcode,
+         calling the command with switches didn't work. 
+         Reported by Ben Kenobi. Patch by Eyal Sagi and TN.
+       * Calls to cut in utils/mkcmds.sh relied on "-f 2" working like
+         "-f2". On at least Ultrix 4.4, it doesn't. Report by Cwilla.
+       * Link strdup.o to info_slave because some systems need
+         strdup! Report by Cwilla.
+       * Some help file typos cleaned up by Ken Ray.
+       * exit() used to work on non-rooms. Not anymore. Report by
+         Trispis@MUSH101 
+       * All header files now idempotent.
+       * @tel me=home or @tel home now works in all cases where 'home'
+         works. Report by Vedui.
+
+Version 1.7.1 patchlevel 3                    January 12, 1998
+
+Minor Changes:
+       * A file called MANIFEST is now distributed. Configure uses this.
+         Don't mess with it unless you know what you're doing.
+       * An additional note for those upgrading from versions before
+         1.7.0p9, describing an anomaly with player parents and how
+         to handle it has been added to README. Report by Roger Christie.
+
+Fixes:
+       * controls() now requires that the function caller control
+         the object named in the first argument. Suggested by TN.
+       * Player parents were being cleared at every login.
+         Fixed by Jonathan Booth.
+       * effect should be affect in game/txt/newuser.txt. 
+         Reported by Jason Young.
+       * hints file for OS/2 now included. Suggestions by Sylvia.
+       * FNDELAY changed to O_NDELAY in ident.c. Suggested by Sylvia.
+       * Minor change to time_format_1 to make 64bit SGI happier.
+         By Thaddeus Parkinson.
+       * Help for inc()/dec() improved. Suggested by Jonathan Booth.
+
+
+Version 1.7.1 patchlevel 2                    January 5, 1998
+
+Fixes:
+       * References to TinyMUSH in the .txt files are now PennMUSH.
+         Reported by Corum.
+       * It was possible to use @name to create players with the same
+         name. Ick. Reported by Sylvia.
+       * utils/mkcmds.sh now produces a preindented switchinc.c,
+         to match the one that gets diffed in patches. [TAP]
+
+
+Version 1.7.1 patchlevel 1                    December 29, 1997
+
+Fixes:
+        * table() now behaves with ANSI_JUSTIFY. Reported by Jonathan Booth.
+        * ident.c was defining strdup; so was strutil.c. Now only
+          strutil.c does. Reported by Matt Philips. [TAP]
+        * Bug in restart script fixed. [TAP]
+        * shs.h patched to autoconfigure endianness. Reported by TN.
+        * shs.h fixed to be idempotent. [TAP]
+        * shs.h is now distributed with PennMUSH. You still have to get
+          shs.c from export.pennmush.org if you want it.
+        * README now refers to lists.pennmush.org. [TN]
+        * We no longer recommend setting use_dns to no on win32 systems.
+          It seems to work fine as yes.
+
+
+Version 1.7.1 patchlevel 0                    December 21, 1997
+
+Major Changes:
+        * It is no longer necessary to edit src/Makefile when building.
+          RWHO is now integrated, and totally runtime configured in mush.cnf.
+          IDENT is now configured from dune.h. The IDENT/ and RWHO/
+          directories are no more.
+        * The win32 build now requires the gnu-win32 package
+          (available at XXX). It can be built either with MSVC++
+          or the free win32 version of gcc included with gnu-win32.
+          The build is better integrated into the distribution --
+          the win32/ directory is no more. [TAP]
+        * You can now build with one of 3 password-encryption schemes:
+          None, Unix (des) crypt(3), and shs (requires getting shs from
+          export.pennmush.org). This will be useful to folks who're using
+          shs encryption on Win32 platforms and then move their db to
+          a Unix platform -- they can just use shs under Unix and all's well.
+          This is set as CRYPT_SYSTEM in options.h
+        * @log/wipe used to require entering the game account password,
+          but that's non-portable. Instead, a "log_wipe_passwd" is now
+          specified in mush.cnf.
+        * README rewritten [TAP]
+
+Minor Changes:
+        * @lock/drop on a room now controls who can drop things in the room.
+        * "configure" (lower-case 'c'), a wrapper for Configure that used
+          to be included is no longer. Few used it, and it can screw up
+          win32 systems that aren't case-sensitive.
+
+Fixes:
+       * blind_page was written as blindpage in game/mushcnf.dst.
+          Reported by Raevnos@ShadowMist.
+       * udefault() was broken. Reported by John Hutchinson
+        * Some lintwork in csrimalloc.c, to get rid of signed/unsigned
+          warnings and others.
+        * help @list fixed to show the correct @config switches.
+          Reported by Leo@ATS TrekMUSH
+        * @chan/gag now works correctly. Report by Vedui.
+        * Help for remove() updated. Reported by Vedui@Robotech.
+        * hasattr() didn't check if the invoker had permission to read
+          the attribute. Reported by Vedui@Robotech.
+
+
+Version 1.7.0 patchlevel 11                    November 18, 1997
+
+Commands:
+        * page/blind produces a separate page to each person paged,
+          so they can't tell if the page was a multipage. This is the
+          same as the default PennMUSH page behavior (but see Options)
+
+Options:
+        * New mush.cnf option 'blind_page'. If 'yes', page defaults to 
+          page/blind.  If 'no', page defaults to page/list. By popular 
+          request. :)
+        * New dune.h option MUSH_IP_ADDR. Define if your host system has
+          multiple IP addresses to specify which address to listen on
+          for connections. By Bobby Bailey (Chil).
+
+Minor Changes:
+        * All calls to isalnum replaced with isalpha || isdigit, because
+          some linux systems appear to have a broken isalnum!
+        * For some reason, the variable name 'restrict' in fun_lsearch
+          broke compilation on James Cook's system. Gwennie@SNW fixed
+          this by changing the name. Ok, I've changed the name, too,
+          as a preventative measure. *shrug*
+        * When a player can't connect because logins are disabled or
+          the MUSH is full, we no longer (a) show a disconnect message
+          to MONITOR players, (b) purge the player's mail anyway, or
+          (c) show the player quit.txt in addition to full.txt/down.txt.
+          Suggested by John Hutchinson.
+
+Fixes:
+        * When a halted player triggers a $command *:, %0 was including
+          an initial space that wasn't being trimmed. Reported by
+          Jonathan Booth. [TAP]
+        * functions() works right again. Patch by Atuarre@ATS TrekMUSH
+        * look/out allowed looking at any db#. Reported by Lews Therin@DDM
+
+Version 1.7.0 patchlevel 10                    October 30, 1997
+
+Minor Changes:
+        * Players can now have @parents. Inspired by AJ Prowant.
+
+Fixes:
+        * @drain was doing @notify instead. Ick. Reported by Amberyl.
+        * Royalty can @boot, as the help suggests. Reported by Vedui@Robotech
+        * (Hopefully current) copies of hdrs/funs.h and hdrs/cmds.h are
+          kept in the win32/ directory for win32 folks who don't have
+          a Bourne shell and can't run utils/mkcmds.sh.
+
+
+Version 1.7.0 patchlevel 9                    October 16, 1997
+
+Fixes:
+        * @clone could corrupt the db on some machines in some cases. 
+          Report by Jonathan booth. [TAP]
+        * @list attribs now works. Report by Corum@StormWorld.
+
+          
+Version 1.7.0 patchlevel 8                    October 15, 1997
+
+Fixes:
+        * Hopefully the final command_parse fix. 
+
+
+Version 1.7.0 patchlevel 7                    October 15, 1997
+
+Fixes:
+        * The ANSI_JUSTIFY patch got left out somewhere. It's back.
+          Report by John Hutchinson.
+        * +channel and exits are broken in 1.7.0p6 due to our stupidity.
+          Fixed again. Report by John Hutchinson.
+        * @cemit was restricted to Wizards, and should have been restricted
+          to Wiz or Royal. Fixed. Reported by Vedui@Robotech.
+
+
+Version 1.7.0 patchlevel 6                    October 9, 1997
+
+New Functions:
+        * cand(), cor() are short-circuit boolean functions. Suggested by
+          Flame who saw reference to them in a patch to TinyMUX. [TAP]
+        * if() and ifelse() do about what you'd expect. Suggested by
+          a number of people. [TAP]
+
+Minor Changes:
+        * Prototypes for the functions in src/local.c are now in
+          hdrs/externs.h
+        * round(.15,1) = .1 on many systems due to the floating point
+          representation. A tiny kludge around this is now in place.
+          Reported by Flame.
+        * @command/disable say now disables " as well. The same applies
+          to other single-character command forms. If you disable SAY,
+          commands of the form "hi! are rewritten as: say hi! before
+          being passed to the checker for user-defined commands
+          so you need only set up $say *: to catch both. %c will, as always,
+          contain the raw command as entered (i.e. "hi!). Aliases
+          (like 'p' for page) are treated similarly when the aliased
+          command is disabled -- you need only match $page *. [TN]
+        * The Huffman compression algorithm is now 8-bit clean, in 
+          preparation for a future internationalization of PennMUSH
+          (Eh? Dite "help" pour aide. :) In addition, none of the compression
+          algorithms treat the first character of a compressed string
+          specially anymore -- they don't have to. [TAP]
+        * The customize.pl script no longer copies over all the distribution
+          help files from game/txt/hlp. Instead it makes customdir/txt/hlp
+          into a link to game/txt/hlp, which is a good thing when future
+          patches update the help files. Dedicated to Oleo.
+
+Fixes:
+        * @list/attribs showed many duplicate attribute names.
+          Reported by John Hutchinson.
+        * Fixed a mislabeling of allocated memory by htab.c, so mem_check
+          stats will be correct.
+        * @channel/gag now works.
+
+
+Version 1.7.0 patchlevel 5                    October 1, 1997
+
+Minor Changes:
+        * New os2/ subdirectory with information for those looking to
+          build under OS2. Maintained by Sylvia (penn-os2@pennmush.tinymush.org)
+
+Fixes:
+        * Examine/brief could sometimes cause crashes. Reported by
+          Sean Fike. [TN]
+        * cmds.c and command.c didn't do the right #includes for the
+          original chat system. Reported by Magus.
+        * Added help for @shutdown/paranoid. Reported by Sean Fike
+        * @chan/decompile on a nonexistant channel didn't return an
+          error. Reported by Mike Wesson
+
+
+Version 1.7.0 patchlevel 4                    September 19, 1997
+
+New command:
+        * The @shutdown command now takes a /paranoid switch
+          to perform a paranoid dump when shutting down
+          (or rebooting, if given with /reboot as well). Idea by Flame.
+
+New options:
+        * You can define the MAILER option in dune.h to be
+          something other than sendmail if you want to put a
+          wrapper around the mailing program used to send out
+          passwords to players using the 'register' command.
+        * If you define LOCAL_DATA in options.h, you can 
+          use functions in local.c to maintain your own data structures
+          associated with each db object. [TN]
+        * If you define the TINY_TRIM_ORDER option in dune.h,
+          the trim() function takes arguments like TinyMUSH's.
+        * If you define ANSI_JUSTIFY in options.h, the rjust, ljust,
+          and center functions will ignore ansi codes when computing
+          where the string should be placed, so they'll work right for
+          strings with ansi. Wadhah Al-Tailji contributed a patch
+          for this concept. TN wrote this particular implementation.
+
+Fixes:
+        * rnum() didn't find exits properly. Reported by Vedui.
+        * Null channels would get added if the chatdb's channel count
+          got unsync'd with the actual number of channels in the chatdb.
+          Reported by Matt@New England: The Reckoning.
+        * @wcheck/all didn't work. Report by Wolverine@Holodeck1
+        * HPUX needs _XOPEN_SOURCE_EXTENDED in the hints. Report by Angel.
+        * db reading error messages are slightly more verbose.
+          Suggested by Flame.
+        * Mortals examining DARK rooms don't see contents anymore.
+          Report by Jonas Oberg. [TN]
+        * Attribute names may no longer contain the caret (^) character.
+          It's a security risk. Noted by Rob Wilson.
+        * hdrs/funs.h was being appended to, not rebuilt. Noted
+          by John Hutchinson.
+        * setunion fixed again, so that setunion(a a,) correctly
+          returns 'a', not 'a a'. [TN]
+        * Various references to mellers1 updated to pennmush.tinymush.org.
+        * COPYRITE file added to explain a couple of the unclear
+          points in hdrs/copyrite.h and to serve as a pointer.
+        * @chan/rename didnt work. Reported by Jonathan Booth.
+        * Help for entrances() updated by Naomi Novik
+        * Players no longer hear about all the channels they're
+          no longer gagged on when they disconnect. Report by
+          Naomi Novik.
+        * The time noted in the LAST attribute now will always
+          have day numbers 01-31 instead of 1-31, just like time()
+          does. This makes convtime() work better on LAST for some
+          systems. Reported by Valin@PernMUSH.
+        * MUSHcoding a command called $attr * would crash the MUSH.
+          Fixed. Reported by Sam Knowlton. [TN]
+        * Disabling say now disables ", (same for pose, semipose,
+          emit, and chat and their corresponding tokens).
+          Reported by Flame.
+        * The hint for Dec Alpha OSF now indicates that you need to
+          use native malloc. Reported by Sean Fike.
+        * We now check for the assert() macro in Configure. NetBSD may
+          not have it, according to Logan Five.
+        * Doing a LOGOUT after a @shutdown/reboot caused crashes
+          because mail pointers weren't being reset.
+
+
+Version 1.7.0 patchlevel 3                    August 13, 1997
+
+Fixes:
+        * setunion(,list) should work now.
+        * @wall/wizard and @wall/royalty work right. Report by Alan T. [TN]
+
+
+Version 1.7.0 patchlevel 2                    August 12, 1997
+
+Fixes:
+        * Linux 2 is now instructed not to use nm to find objects
+          in libraries since its nm output doesn't seem to be
+          BSD compatible or something.
+        * Compile with CSRI malloc in debugging mode didn't work.
+          Reported by TwinSuns MUSH.
+        * Exits in transparent rooms with COMMA_EXIT_LIST had
+          vanished. They're back! [TN]
+        * More command parser bugs ironed out. [TN]
+        * 'e' is examine, 'b' is brief, 'w' is whisper
+           (unless you reserve them). [TN]
+        * @config said conflicting things about possessive get.
+          Report by Jonas Oberg.
+        * When not using @prefix, audible messages were prefixed
+          with "From <source>" instead of "From <source>,".
+          Fixed. Report by Jonas Oberg.
+
+
+Version 1.7.0 patchlevel 1                    August 7, 1997
+
+Minor Changes:
+        * New file local.c (from local.dst) makes more local hooks
+          available. Some of the hooks in command.c have been moved
+          here, so you may have to mess around a bit if you've already
+          added things in command.c's local_startup or local_shutdown. [TN]
+        * Functions can now be defined and added all within funlocal.c
+          so you don't have to muck with the function.c table. [TN]
+
+Fixes:
+        * restrict_command didn't restrict commands to God correctly.
+          Reported by Jason Newquist.
+        * @command indicates if commands are restricted to God.
+        * When COMMA_EXIT_LIST was defined, rooms with no exits
+          still showed the "Obvious Exits:" message. Report by Michael Rudel.
+        * Partial switch-matching for commands is back.
+          And CMD_T_NEWSTYLE is gone. [TN]
+        * Makefile doesn't clobber existing funlocal.c/cmdlocal.c
+          any more. Report by Jason Young.
+        * inv wasn't working for inventory, and other command aliasing
+          flaws are fixed. Report by Corum. [TN]
+        * MUSH wouldn't compile if PLAYER_LOCATE wasn't defined. Fixed.
+          Report by Alan T.
+        * Some leftover defines removed from game.c and mushcnf.dst
+        * @lock didn't parse right. Fixed. Report by Corum.
+
+
+Version 1.7.0 patchlevel 0                    July 31, 1997
+
+The major goals of this release are to make what used to be
+compile-time options into run-time options, and improve a number
+of internals.
+
+Major Changes:  
+
+        * The hashtable functions from TinyMUSH (htab.c/htab.h) are
+          now used by PennMUSH (with permission), so instead of 
+          every subsystem writing its own hashtable code, they now
+          all use the standard code.
+
+        * The giant switch in game.c has been replaced by hashtabled
+          commands. [TN] 
+          Groovy new features:
+          * @command/enable and @command/disable for any command
+          * Multiple switches (@pemit/noeval/silent) work
+          * The left side of the = is always evaluated before
+            the right (previously, this was compiler-dependent).
+          * Command table is built during compilation, and there's 
+            a standardized command interface, so adding commands
+            is easier.
+          * local.c contains hooks for local routines to run
+            on startup and shutdown, and to use to add new commands.
+
+        * Configuration options which restricted or disabled commands
+          (READ, NO_KILL, ROBBERS, HATE_TOAD, ROY_FIND, HARSH_GUEST,
+          SITELOCK_COMMAND) have been replaced the 'restrict_command'
+          directive in mush.cnf. Check there and be sure things are
+          set the way you want them!
+
+        * Commands may be overridden by completely disabling them,
+          and providing a MUSHcoded version instead.
+            
+        * Ident and DNS lookups are handled by a slave process
+          if possible. [TAP]
+
+        * The help files have been rewritten by Naomi Novik!
+
+        * The MUSH now closes and reopens the log files when it
+          receives a SIGHUP signal. [TAP]
+
+        * @shutdown/reboot will dump the database and restart the
+          MUSH without disconnecting the players. So will a USR1 signal.
+          Good for patching in new source code on the fly. Based on the 
+          patch by Cro@Spaceballs which is itself based on ideas from
+          TinyMUX. This may be nonportable - if it fails on your OS, 
+          pennmush-bugs@pennmush.tinymush.org would like to hear about it. 
+          [TAP]
+
+New commands:
+        * @command (see above)
+        * @list/commands
+        * @attribute, similar to Tinymush (but doesn't save data
+          across shutdowns at this point, so you've got to use it
+          on an @startup!). Also @list/attribs.
+        * @function/delete 
+        * @channel/gag <channel>=<yes|no>, keeps you on the channel
+          (preserving your title, etc.) but silences it so you don't 
+          hear messages. The channel is automatically ungagged if
+          you log out from the MUSH.
+        * @conformat and @exitformat allow custom-formatted Contents:
+          and exit lists. [2.2,TAP]
+
+Minor Changes:
+        * Configure now looks for libbind.a, the BIND 8.1 library
+        * @config shows more of the configuration options
+        * @mail/unfolder <folder> can be used to remove a mailfolder's name.
+          Suggested by Julianna Barrenson.
+        * The default malloc is now CSRImalloc, which is now distributed
+          in a single-file form with the MUSH. 
+        * The MALLOC define in src/Makefile has been removed.
+          Unlike MALLOC_D and MALLOC_O, it didn't do anything.
+        * The "CHANGES" file now contains only the current
+          version's changes. Older changes are in the 
+          "CHANGES.OLD" file.
+        * The attribute flag AF_ODARK is now assumed to be default, 
+          and is thus no longer used or stored. Instead, AF_VISUAL
+          is used to indicate a visual attribute (previously, this
+          was indicated by the absence of AF_ODARK). To note this
+          change in the db, a new DBF constant, DBF_AF_VISUAL,
+          has been defined.
+        * Code for "privilege tables" (like chat channel privs, 
+          attribute flags, etc.) has been centralized into privtab.c
+
+Fixes: 
+        * Locks on zone exits now evaluate with the right enactor.
+          Problem noted by Leonid Korogodsky. [TAP]
+        * Win32 compatibility improvements. [TAP]
+        * Prefer limits.h to values.h. Suggested by Atuarre.
+        * SIGCHLD and SIGCLD now both work. Noted by Naomi Novik
+        * If you idle out and get hidden, only the idle descriptor
+          should get hidden. Noted by Gepht@Hemlock
+        * With DBF_LESS_GARBAGE, garbage objects were loaded with
+          owner NOTHING instead of owner GOD, which could cause
+          crashes in the @mail code. Reported by Harvester@StarWars.
+        * Problems with getrandom() on some systems fixed.
+        * Help for dist3d() clarified. Reported by 
+          Kova'khan@Among the Stars TrekMUSH via Leo at the same MUSH.
+        * You may now use power() for integral powers of negative
+          numbers. Suggested by John Hutchinson.
+        * setunion(,test,|) used to return |test, now returns test.
+          Reported by Ashford @ V MUSH
+        * SAFER_UFUN now blocks non-God eval'ing a u() on God.
+          Reported by MRR@ATS
+        * Building with SunOS cc and COMPRESSION_TYPE 0 works now. 
+          Reported by Jonas Oberg.
+
+Personnel Changes:
+        * Ralph Melton has retired as a member of the PennMUSH 
+          devteam. Alex and Javelin send him best wishes and big
+          thanks for all his work. Replacing Ralph will be
+          Thorvald Natvig. Welcome aboard, TN!
+
+Version 1.6.10 patchlevel 6                  May 11, 1997
+
+Fixes:
+       * inc() and dec() didn't work right with NULL_EQ_ZERO.
+         Fixed. Report by Dennis DeMarco
+
+
+Version 1.6.10 patchlevel 5                 April 29, 1997
+
+New Commands:
+        * @channel/title sets a player's title for a channel.
+          The title appears prepended to their name.
+          I forgot this, and Thorvald Natvig noticed.
+
+Minor Changes:
+        * The inc() and dec() functions can now increment and
+          decrement strings that end in integers. For example:
+          inc(LINE_10) => LINE_11
+          inc(LINE-10) => LINE-9   (incrementing -10)
+          inc(LINE1.9) => LINE1.10 (incrementing the 9)
+        * The LOWER_MATH option has been removed. Everybody
+          gets shr(), shl(), inc(), and dec().
+        * New attributes OIDESCRIBE and AIDESCRIBE do what you'd 
+          expect for the internal descs of objects. Suggested
+          by Stacy Robinson.
+        * @chan/on by a Wizard always succeeds, even if there's a 
+          join lock. Suggested by Mike Wesson.
+        * Enhanced protection against malicious ANSI codes.
+        * @channel/decomp displays locks in a better format.
+          Suggested by Naomi Novik.
+        * Numbers are now checked to be sure they're not so
+          large as to bring down the system, at least in theory.
+          Suggested by Atuarre.
+        * Configure rebuilt under dist-3.0 pl70.
+        * queue_write has been modified to reduce the number of
+          packets sent out on the net; it only sends when needful
+          and lets the usual output loop handle most output.
+          The many packets issue was noted by Doogie.
+        * All references to "parent rooms" have been changed to
+          "zone master rooms" for clarification. Noted by Jonas Oberg.
+
+Fixes: 
+        * Long output should no longer cause NeXT
+          systems to disconnect the user. Fix by Mike Kienenberger
+        * Long output should no longer cause Win32 systems to
+          disconnect, but it will throw out the beginning of 
+          the output. Fix by Hans Engelen.
+        * Bad objects on chat channels are now removed when the
+          chatdb is loaded. Suggested by Dennis De Marco.
+        * help for last() added. Report by Flame.
+        * Objects couldn't be added to object channels. Fixed.
+        * If a site matched in access.cnf, but didn't specify 
+          a certain access rule, a later matching site could. This
+          is bad for 'register', and not what was intended (that
+          a matching site completely controls that site's access).
+          Now fixed. Report by William Browning.
+        * If you're on a channel but don't pass the see-lock,
+          @chan/who returns a better error. Report by Cro.
+        * help for remove() now mentions delimiters. Noted by J. Wilcox.
+        * All tabs in helpfiles replaced with spaces. [TAP]
+        * Loading a db that didn't have garbage objects stored
+          caused a slew of warnings about null names. Should be fixed
+          now. Reported by Atuarre.
+        * FreeBSD hints improved, thanks to Atuarre and Doogie.
+
+
+Version 1.6.10 patchlevel 4                 March 3, 1997
+
+Major Changes:
+        * The disk db can now be dumped without including GARBAGE
+          objects, which may make it somewhat smaller. [TAP]
+
+Minor Changes:
+        * The %? substitution returns the current function invocation
+          count and recursion depth as two space-separated numbers. [TAP]
+        * dig() can take a single argument instead of 3.
+
+Fixes:
+        * set(obj/attr,flag) no longer says "Set" when the object or
+          player is QUIET. Reported by Graywolf.
+        * Search_All players couldn't use @search. Now they can.
+          Reported by Cro.
+        * lnum(2,1) didn't work right, and things were broken with
+          floating point args to lnum. Fixed.
+
+
+Version 1.6.10 patchlevel 3              February 9, 1997
+
+New function:
+        * graball() as per TinyMUSH 2.2. [TAP]
+
+Fixes:
+        * On some systems, make clean would go into an infinite loop
+          if src/CSRI didn't exist. This should now be fixed.
+          Report by Cro@Spaceballs.
+        * Restart script now looks for minimal.db.gz if minimal.db.Z
+          can't be found. This helps people running under Mklinux
+          on Power Macs(!). Report by Jason Newquist.
+        * Fix to matchall(). [TAP]
+        * move.o doesn't compile if FIXED_FLAG isn't defined.
+          Noted by Andy Jalics.
+
+
+
+Version 1.6.10 patchlevel 2              February 2, 1997
+
+Minor Changes:
+        * If forking to do dumps, lower the priority of the dumping 
+          process to keep the parent process more responsive.
+          Based on a patch by Doogie@ATS TrekMUSH.
+
+Fixes:
+        * Code cleanup and fixes to comp_w.c. Problems reported by
+          Mike@TBFF
+        * Portability fixes for alpha-linux systems. Thanks to Roger
+          Chen for facilities to work on this.
+        * Added help for @dump/debug. Report by Flame.
+        * Note added to game/txt/Makefile about braindead Irix 6.2 make
+        * Ident source files now do better #ifdef'ing of Unix include
+          files to help out Win32. 
+        * Configure changes to enhance portability.
+
+
+Version 1.6.10 patchlevel 1              January 25, 1997
+
+
+Commands: 
+        * New command: @dump/debug. Like @dump/paranoid, but
+          it also tries to fix the memory db, so a shutdown/restart
+          may not be necessary. It is never forked. Suggested by Atuarre.
+
+Fixes:
+        * externs.h now declares crypt() as char *, not const char *.
+        * free_access_list's declaration is now K&R compatible again.
+        * repeat() now doesn't work with a null string, which prevents
+          a denial of service attack. Report by Atuarre. [TAP]
+        * Fencepost error in huffman compression code fixed.
+          Report by Mike Wesson. [TAP]
+        * idlesecs() is now referenced in pennfunc.hlp. Report by Flame.
+        * parse() is now referenced in pennfunc.hlp. Report by Sandi Fallon.
+        * lsearch() now gives the correct types in the help.
+          Report by Sandi Fallon.
+        * Halted messages now indicate the object that was halted,
+          even if they are halted as the result of a chown.
+          Report by Sandi Fallon.
+
+Version 1.6.10 patchlevel 0              December 16, 1996
+
+Major Changes:
+        * The attribute matching order has been cleaned up. Code by [TAP]
+          If you do ex obj/attribpattern, and...
+          1. If attribpattern has no wildcards:
+             a. Return attribpattern's value if set, else
+             b. Return the value of an aliased attribute, if any.
+          2. If attribpatern has wildcards:
+             a. Return anything which matches the pattern, and
+                don't bother about aliases.
+
+        Here's a little example:
+                       only DESC set   only DESCRIBE set    both set
+  ex foo/desc          DESC            DESCRIBE             DESC
+  ex foo/desc*         DESC            DESCRIBE             both
+  ex foo/describe      (error)         DESCRIBE             DESCRIBE
+
+
+Minor Changes:
+        * parse() is now an alias for iter() [TAP]
+        * Attribute set/clears report the name of the attribute in the
+          set/cleared message. [TAP]
+        * fun_lattr is now in fundb.c. [TAP]
+        * Improved setq/setr help. [TAP]
+
+Fixes:
+        * Typo in help evaluations corrected. [RLM]
+        * The side-effect version of lock() no longer returns a value.
+          Reported by Corum.
+        * help quota() added. Report by Dennis De Marco.
+        * help INHERIT updated to reflect current control structure.
+          Suggested by Vedui.
+        * vmul() with a separator now returns the vector separated with
+          that separator, as promised. Report by Atuarre@TrekMUSH.
+        * FIXED now overrides STICKY, so you can't set yourself STICKY,
+          get yourself dropped, and go home. Report by Anthony Ivan.
+        * lsearch(all,flags,c) worked, but lsearch(all,flags,Pc) didn't!
+          This is fixed. Report by Flame.
+
+Version 1.6.9 patchlevel 9               November 18, 1996
+
+Fixes:
+        * A Wizard doing @find on a MUSH with garbage crashes the MUSH.
+          Fixed. [TAP]
+        * Fairly major security problem due to a typo in the player-
+          destruction code fixed. Reported by Dennis DeMarco.
+
+
+Version 1.6.9 patchlevel 8               November 10, 1996
+
+Major Changes:
+        * The control system has changed slightly: only wizards
+          control wizobjects and only royalty control royobjects.
+          If a mortal's object gets wizbitted, the mortal will
+          cease to control it. Also, protection is now afforded
+          to players from non-inherit objects.
+
+Fixes:
+        * Setting an @listen on a room caused a crash. Fixed.
+          (Note: @listen on rooms still doesn't work - it's not
+           supposed to -- use LISTENER and ^patterns -- but at
+           least it doesn't crash. :)  Report by Flame@B5.
+        * dune.h.dist now defaults the index and rules indx files
+          to ending in .idx, as they should. Noted by Jason Newquist.
+
+
+
+Version 1.6.9 patchlevel 7               October 30, 1996
+
+Functions:
+        * The help for vmul() suggested it did an elementwise product
+          of 2 vectors, returning a vector. In fact, it was doing a
+          dot product (sum of the elementwise product, which is a scalar).
+          vmul() now does what the help suggests, and vdot() does
+          a dot product.
+
+Fixes:
+        * Bug in comp_w.c (word-based compression) which could cause
+          loss of subjects in @mail has been fixed. [NJG]
+        * Help for "control" made more explicit, and help for "controls()"
+          references "control", not "controls".
+        * @wait 0 now queues its subject immediately, rather than waiting
+          1 second. [TAP]
+        * The "Patchlevel" file is now more grammatical. For Sam Knowlton. :)
+        * Variables named "listen" have been renamed "listener" to
+          remove compiler warnings about shadowing the listen() system call.
+          Reported by Flame@B5
+
+
+Version 1.6.9 patchlevel 6               October 24, 1996
+
+Fixes:
+        * Removed needless calls to tprintf() within do_log() in 
+          access.c
+        * Fixed the variable j in fun_lnum to be the right type
+
+
+Version 1.6.9 patchlevel 5               October 15, 1996
+
+Options:
+        * COMMA_EXIT_LIST causes exit lists to be comma-separated,
+          and include the word "and" before the last exit. [NJG]
+
+Functions:
+        * lnum() with multiple arguments now behaves exactly like
+          Tiny 2.2's lnum().
+
+Minor Changes:
+
+        * @pcreate failure messages distinguish between bad passwords
+          and bad names. Related to a suggestion by Philip Mak.
+
+Fixes:
+        * elements() used to put a leading space in output. Fixed. [RLM]
+        * index(foo|||,|,2,1) now returns nothing, instead of ||,
+          as it should. Fix by Harvester@StarWarsMUSH.
+        * @cpattr a/DESC=b (where a has a DESCRIBE attribute and no DESC
+          attribute) correctly grabbed DESCRIBE from a, but copied it to
+          DESC on b. This is now fixed. [TAP]
+        * Various unused variables and missing prototypes fixed. [NJG]
+        * More win32 fixes. [NJG]
+        * Revised comp_w.c to handle table overflow better. [NJG]
+        * splice() wasn't putting spaces back in between words.
+          Reported by Philip Mak.
+        * Help for aposs() was never added. [MPC]
+        * mkindx doesn't compile on systems without strcasecmp. Fixed.
+          Reported by Stephen Sanderlin.
+        * Configure: -lsocket is used if it's found (also -lcrypt, -lnsl)
+        * Various missing includes fixed
+        * Linux systems weren't doing daylight savings time right.
+          We now always try to use tm_isdst = -1 to get this right.
+
+
+
+Version 1.6.9 patchlevel 4               October 9, 1996
+
+Fixes:
+        * Restart script fix in 1.6.9p3 is buggy. Fixed the fix.
+
+
+Version 1.6.9 patchlevel 3               October 7, 1996
+
+Changes:
+        * @wipe/wipe() of a single attribute (no wildcards) no longer
+          checks the SAFE flag on the object.
+        * Wildcards are now accepted for the attribute name when setting
+          attribute flags.
+
+Fixes:
+        * @succ and &succ could create duplicate success attributes.
+          Fixed so that @succ -> SUCCESS and &succ -> SUCC,
+          and no more duplicates. [TAP]
+        * Help for @purge had disappeared. Back.
+        * Forgot to include restart patch mentioned in 1.6.9p2 [PeaK]
+
+
+Version 1.6.9 patchlevel 2               October 5, 1996
+
+New Compile Options:
+
+        * The behavior of attributes is now configurable; you can
+          emulate attribute setting behavior from 1.6.8 and earlier,
+          use the currently recommended settings, or anywhere 
+          in between. [TAP]
+
+Functions:
+
+        * lnum() now takes an optional second argument, which is the
+          number to start with, e.g. lnum(3,4) => 4 5 6
+          Suggested by [MPC]
+
+Fixes:
+        * @mail/debug clear=<player> now clears all the player's
+          mail, not just their current folder. Fix by
+          Leonid Korogodsky.
+        * look/outside at an ambiguous name crashed. Report by Vedui.
+        * New hints file: linux_2.sh
+        * Help for setq() now included. Report by Flame@Babylon5
+        * Added -w to the ps in restart so the output isn't
+          truncated. [PeaK]
+        * Restart changes to prevent some race conditions under Linux 2.0
+          [Peak]
+
+
+Version 1.6.9 patchlevel 1               September 26, 1996
+
+New Function:
+
+        * setr() is like setq() but returns the string as well.
+          It's identical to [setq(#,string)]%q#. Suggested by Adam Dray.
+
+Fixes:
+
+        * Bug with @clone fixed. Report by Vedui.
+        * Bug with @mail folder 15 fixed. Report by Vedui.
+        * @sitelock/register worked backward. Fixed. Report by [MPC]
+        * rnum() now requires that you can examine the room.
+          By Jason Rhodes, with minor mods.
+        * Better messages when a player or thing is set audible.
+          Suggested by Babylon5@kuwait.net
+        * Configure now uses your email address instead of your name
+          when trying to subscribe to the mailing list; on some systems,
+          it's hard to get a valid name -- getting an email address
+          is usually possible. Suggested by Cro@Spaceballs
+        * Configure no longer adds multiple copies of the same gcc
+          warning flags when you run it again. Reported by Cro@Spaceballs
+
+Version 1.6.9 patchlevel 0               September 16, 1996
+
+Attribute Rewrite [TAP]:
+
+        * There is now a distinction between an empty attribute and
+          a non-existant attribute:
+                @va me          <--- wipes out my VA attribute
+                @va me=         <--- sets my VA attribute to be empty
+          Empty attributes retain their locks and flags; wiped attributes
+          are gone forever.
+        * @set and set() can not wipe out attributes. @wipe and wipe()
+          will.
+        * You can now include ':'s in $-command and ^-command patterns
+          by escaping them with '\'.
+        * Standard attribute names are kept in a string table and
+          memory is not allocated for them. We guesstimate a 3-5%
+          savings in memory use from this change.
+        * objmem() and playermem() are now more accurate.
+        * Internal changes: new attribute flags AF_STATIC, AF_COMMAND,
+          AF_LISTEN; atr_comm_match doesn't look directly at compressed
+          strings any more; restructured some routines (atr_clr to clear
+          attributes, atr_add to set them).
+
+Fixes:
+        
+        * Add help topic SETTING-ATTRIBUTES to explain the above.
+        * Fixed small error with closing a NULL file in access.c [TAP]
+        * Improved help for @destroy
+        * Dashed lines in @mail are now 2 hyphens longer. [MPC]
+        * The Configure hints files weren't properly used when
+          compiling with gcc.
+        * The color flag is now aliased to colour as well. [MPC]
+        * With MAIL_SUBJECTS, the @mail/list could get scrambled
+          for messages from players with long names. Reported by
+          Leto@DuneII.
+        * Small memory leak when access.cnf is reloaded via kill -HUP
+          has been fixed.
+        * Vestige of old TEMPLE code removed from do_drop. Reported
+          by Mike Selewski.
+
+Version 1.6.8 patchlevel 1               September 7, 1996
+
+Fixes:
+        * The mail*() functions were broken due to a typo. [MPC]
+        * The addr field in the descriptor structure has been expanded
+          from 50 characters to 100 characters, because you'd really
+          like to store the entire ident response from encrypting
+          ident daemons.
+        * The MAIL: announcement at login now counts your cleared
+          mail in your mail totals, in case something clears mail for
+          you while you're offline. Suggested by Mike Wesson.
+        * Better string protection for parse_chat which may fix
+          a potential crash when speaking on +channels.
+
+Version 1.6.8 patchlevel 0               September 3, 1996
+
+This is intended as a maintenance release because 1.6.7 has had
+many rapid patches to achieve stability.
+
+Fixes:
+        * "make install" now implies "make all". Corrects a problem
+          with not remaking hdrs/buildinf.h.
+        * README file now shows the utils directory in its directory tree.
+          Reported by Mike@StarWars.
+        * On startup, PennMUSH now logs its version information.
+        * @log/wiz was logging to trace log, not wiz log. Reported by
+          Dean Moore.
+        * @mail system behaved badly if you @shutdown on an empty
+          maildb on some systems. Reported by Mike Selewski.
+        * Detection of getpagesize() system call is now handled by 
+          Configure. Fixes problems on Irix, reported by Mike Selewski. 
+        * Minor typo in Irix hints file fixed.
+        * Stupid misspelling of August in CHANGES-10 fixed.
+
+Version 1.6.7 patchlevel 6               August 31, 1996
+
+Fixes:
+        * The attribute clear fix in 1.6.7p5 is buggy. Now it's really
+          fixed. Report by [MPC].
+
+
+Version 1.6.7 patchlevel 5               August 30, 1996
+
+Fixes:
+        * Sites not listed in the access.cnf file should have been
+          allowed, not denied access. Fixed. Best report by Cwilla@Victory
+        * A little more info in mush.cnf about how player_creation and
+          access.cnf interact
+        * Trying to clear a never-existant attribute got the right
+          error message, but clearing an attribute that had existed,
+          but been already cleared got the "Foo - Set." message.
+          Fixed - atr_add now skips disposed attributes.  Report by [MPC]
+
+
+Version 1.6.7 patchlevel 4               August 28, 1996
+
+Fixes:
+        * Bug in mail functions that caused mail(1:0) to crash
+          has been fixed. Reported by Corum@StormWorld.
+        * Another Win32 fix by Pat Pinatiello.
+
+
+Version 1.6.7 patchlevel 3               August 28, 1996
+
+Fixes:
+        * Configure wasn't setting HAS_SENDMAIL correctly because the
+          updated config_h.SH wasn't included in the diff!
+        * @mail/clear's message about unread mail was screwy.
+
+
+Version 1.6.7 patchlevel 2               August 27, 1996
+
+Fixes:
+        * Forbidden_Site wasn't working quite right
+        * Win32 compatibility improvements suggested by Pat Pinatiello.
+          Hopefully, no further real hacking should be required to 
+          build under Win32 with Visual C++. Pat's instructions included
+          as win32/README.visualc++
+
+
+Version 1.6.7 patchlevel 1               August 27, 1996
+
+Fixes:
+        * #ifdef's and the like were missing which prevent compilation
+          unless HAS_SENDMAIL and CHAT_SYSTEM (>2) were defined.
+          Fixed by [TAP].
+
+
+Version 1.6.7 patchlevel 0               Augest 22, 1996
+
+Major Changes:
+
+        * The lockout.cnf and sites.cnf files are no longer used.
+          Instead, the file game/access.cnf controls all aspects of
+          site-based (or, with ident, possibly user-based) access.
+          Sites can be explicitly allowed or denied the ability to:
+            * Connect to guest players
+            * Connect to non-guest players
+            * Create players
+          Sites can be configured to:
+            * Use email registration (see below)
+            * Set all players from the site SUSPECT
+          See the file game/access.README for file format information.
+          The LOCKOUT and WCREATE defines have been removed from options.h.
+
+        * A new access option, email registration, is available.
+          From the connection screen, the command
+            register <player> <email address>
+          will create the player with a random password, which will be
+          emailed to the address. The email address is stored on the player
+          in the wiz-only attribute REGISTERED_EMAIL. 
+          Obviously, this requires that the system have a way to send email.
+          Ideas in this code came from Jim Cook.
+
+        * @powers are now tabled in flags.c.
+
+        * Nick Gammon's word-based compression algorithm is now
+          COMPRESSION_TYPE 3. This algorithm may be faster than
+          Huffman on the whole, and may provide better compression
+          for large (>1.5Mb of text) databases.
+
+        * @mail message-lists now understand the format [folder#:]msg[-msg]
+          For example, the first 3 messages in folder 1 are 1:1-3.
+          @mail commands that are not given any message list are assumed
+          to apply to all messages in the current folder. You can also
+          do this explicitly by using the word "folder". When you use
+          the word "all", you match all messages in all folders.
+          For example, try: @mail all
+          @mail internals rewritten to increase code reuse.
+
+New Commands:
+
+        * @sitelock has got an additional syntax to support the new
+          access.cnf system.
+
+New Functions:
+
+        * powers() returns the list of powers set on a thing.
+          If HASPOWER_RESTRICTED is defined, you must be able to
+          examine the thing to do this.
+        * mail(), mailtime(), mailstatus(), and mailfrom() now accept
+          this syntax:
+                mail([<player>,] [<folder #>:]<message #>)
+        * cemit() does what you'd expect. Suggested by [MPC].
+
+New Powers:
+
+        * Open_Anywhere power allows the player to open an exit between
+          to any room, even if the player does not control the source
+          or target room.
+
+
+Minor Changes:
+
+        * Previously, a player with the Halt power could use
+          @halt obj=command to effectively @force any object.
+          This form of @halt is now only allowed if you control the
+          object. Bug reported by Flame.
+
+        * When EXTENDED_ANSI is defined, ansi codes are stripped out
+          of strings before checking them against LISTEN and ^patterns.
+          Suggested by Mike Wesson.
+
+        * HAVEN players are no longer notified when they send back
+          an @haven message in response to a page. Page-locked players
+          still are. Suggested by Naomi Novik.
+
+        * @decompile me results in a decompile with "me" as the object name
+          @decompile <player> results in the player's name as the object name
+          @decompile of an exit will use the exit's short name instead of
+          its full name for setting attributes and flags. 
+
+        * Utilty scripts (customize.pl, update.pl, update-cnf.pl, 
+          fixdepend.pl, and make_access_cnf.sh) are now in the utils/
+          subdirectory.
+
+        * Default rwho server is now littlewood.math.okstate.edu,
+          which replaces riemann, which has been turned off.
+
+        * time() and convsecs() now indicate the first 9 days of the
+          month as 01..09, rather than 1-9, which makes convtime()
+          happier when you convert back.
+
+        * You may @tel an exit back into its own room. Added for
+          compatibility with other MUSH flavors. Suggestion by Philip Mak.
+
+        * Dark connections are now broadcast only to MONITOR wizards and
+          royalty. This involved replacing the raw_broadcast and
+          toggle_broadcast functions with a new flag_broadcast function
+          which subsumes them. Suggestion by Philip Mak.
+
+Fixes:
+        * Noted the 256-character limit on channel descriptions in the help.
+        * abs() now deals correctly with floating points.
+        * Win32 compatibility improvements suggested by Pat Pinatiello.
+        * Updates to BUGS, README.Deprecated, and FAQ [TAP]
+        * An ANSI_NORMAL is sent at the end of the Doing message in WHO.
+        * mail() and related functions now accept "me". Bug noted by
+          Mike@TBFF
+        * Help for @squota no longer says that it works like @quota
+          when no limit is given, because it doesn't. 
+        * Bigram compression was not freeing memory that it used
+          for initialization. Fixed, and made faster.
+        * Help for @unlink fixed; you can't pick up unlinked exits. [TAP]
+        * Potential string overflow in new_connection fixed. Idea by [TAP]
+        * Code cleanup: many old sections that were commented out
+          with #ifdef NEVER ... #endif have been removed.
+        * @set obj=!going gave an error message but still reset the flag.
+          Now it should just give the error. Reported by Philip Mak.
+
+Version 1.6.6 patchlevel 0               July 28, 1996
+
+This version involves primarily cleaning up and streamlining code
+in preparation for major internal changes in later releases.
+
+Major Changes:
+        * The following options are now standard and no longer turned
+          on via dune.h/options.h: RALPH_LOCKS, EXTENDED_MAIL, 
+          INHERIT_FLAG. [TAP]
+
+New Commands:
+        * @firstexit command moves an exit to be first in the list of 
+          a room's exits. Based on contrib/topexit.165p3 by Marlek@Earth1996
+          (but note that it requires control over the *room*, not the
+          exit).
+
+New Functions:
+        * poll() returns the current poll. By William Knopes.
+
+Flags:
+        * TEMPLE flag has been removed. This requires adding a new
+          DBF bit. [TAP]
+
+Minor Changes:
+        * Admin no longer automatically pass leave-locks and NO_LEAVE.
+          Suggested by Naomi Novik. [TAP]
+
+Fixes:
+        * Users could cause an infinite loop with @mail/fwd. Fixed.
+        * The fix to pmatch() in 165p4 didn't quite do the job.
+          Alex's does. [TAP]
+        * @mail/stat commands now show correct number of cleared messages.
+          Bug reported by [MPC]
+        * Typo fixed in help for items() [MPC]
+        * help for BUILDER now calls it a power, not a flag.
+
+
+Version 1.6.5 patchlevel 4               July 9, 1996
+
+New Functions:
+        * entrances() works like @entrances, including the cost.
+          Suggested by Julienna@TrekMUSH.
+
+Commands:
+        * whisper/list takes multiple recipients. Suggested by [MPC].
+
+Minor Changes:
+        * A DARK-reconnected message has been added. Idea by [MPC]
+        * LFAIL/OLFAIL/ALFAIL attributes now control the message
+          seen when a player fails to leave an object due to the
+          NO_LEAVE flag or a leave-lock.  By Naomi Novik.
+
+Fixes:
+        * pmatch() on a DARK or hidden player by an unpriv'd player
+          now returns #-1 (can't find the player).
+        * inc(very_big_n) now works exactly like add(1,very_big_n) instead
+          of returning a huge negative number. Same for dec(). 
+          Reported by [MPC].
+        * Fixed spelling of Tinyfugue in help @decompile. [MPC]
+        * Documented the way hasflag() works a bit better to forestall
+          a common confusion: why hasflag(me,r) doesn't check for the
+          royalty flag.
+        * If a player tries to join a channel she's already on but
+          which has since been locked against her, she receives the
+          already-on-channel message now instead of the can't-join
+          message. Suggested by Cwilla@VictoryMUSH.
+        * Compile-time information in @version has been expanded and
+          made much more robust for systems whose make program is
+          broken. We now create the file hdrs/buildinf.h at the
+          beginning of each build, which contains the info.
+        * WHO at the connect screen works correctly now even if 
+          #0 is privileged. Bug reported by Doyce Testerman.
+
+
+Version 1.6.5 patchlevel 3              June 29, 1996
+
+New Command:
+
+        * @shutdown/panic causes a panic dump and shutdown. God-only. [RLM]
+
+Minor Changes:
+
+        * When you destroy a room you don't own, the "wrecking ball"
+          message now includes the name of the owner, like the messages
+          when you destroy objects you don't own. Suggested by
+          Matt Chatterley.
+        * The channels() function, with no arguments, returns the
+          list of all channels (which the player can see).
+
+Fixes:
+        * When you try to get an object that's not in your location,
+          and POSSESSIVE_GET is not defined, you don't receive
+          any message at all. Fixed by Thaddeus Parkinson.
+        * DARK-disconnect messages now appear.
+        * All calls to random changed to getrandom (except the one
+          in getrandom!), and the prototype for random removed from
+          utils.c, where it causes conflicts for at least OSF/1.
+        * portmsg.c extensively hacked to use the autoconfiguration
+          info so you can "make portmsg" on supported systems.
+        * Help for mortal() and @mortal removed. [RLM]
+
+
+Version 1.6.5 patchlevel 2              June 19, 1996
+
+Fixes:
+        * @grep didn't check to be sure you were allowed to see
+          the attributes it searched. Reported by Mike Wesson.
+
+
+Version 1.6.5 patchlevel 1              June 12, 1996
+
+New Functions:
+        * channels(dbref) returns the list of channel names that 
+          dbref is on.
+
+Fixes:
+        * When paging player(s) with spaces in their names, the
+          LASTPAGED attribute stores them with quotes around them,
+          so that repaging will work.
+        * @mail/fwd shows you how many players it successfully
+          forwarded to. 
+        * @chzone here=none produced a spurious message if here was
+          Wiz/roy. Fixed. Reported by Matt Chatterley.
+        * udefault failed to evaluate the arguments it passed to the
+          attribute to be u'd. Reported by PeaK.
+        * Added include of sys/types.h in ident code. Necessary for
+          FreeBSD.
+        * @chf would not return a Huh? Fix by Hemlock MUSH admin.
+        
+Version 1.6.5 patchlevel 0              June 2, 1996
+
+Database:
+        
+        * The "One" character in minimal.db.Z (#1, aka God) is now
+          distributed without a password. This takes care of people
+          who don't have crypt(3) or have a modified one (FreeBSD),
+          since you now log in without a password, and set one,
+          and all is well.
+
+Documentation:
+
+        * README has been extensively updated.
+
+Minor Changes:
+
+        * When paging, LASTPAGED is set to the list of succesfully
+          paged players' names, if any page was successful. This
+          is how page/list always worked; now applies to normal page.
+        * &foo obj = bar used to set FOO to " bar". Similarly, you
+          could use page/noeval (or any speech command with /noeval)
+          to page messages with initial spaces. For TinyMUSH compatibility,
+          this is no longer possible; leading spaces are trimmed now. [TAP]
+        * F_INTERNAL flags (like GOING) are now visible unless they're
+          also F_DARK. [RLM]
+        * When you @decomp obj/atr and atr doesn't exist, you now get
+          a message about it. Suggested by Matt Chatterley.
+        * PREFIX semantics now like INPREFIX (see 1.6.4p2) [TAP]
+
+Fixes:
+
+        * Bug in parse.c which could causes crashes fixed. Reported
+          by Atuarre. [TAP]
+        * On at least 2 systems, the system date was screwed up,
+          which made updating dune.h and options.h still produce
+          files younger than dune.h.dist and options.h.dist.
+          This is not our fault, but we'll fix it anyway - now we
+          touch dune.h.dist and options.h.dist before we update
+          dune.h and options.h. 
+        * @wiping a STARTUP attribute didn't reset the STARTUP flag. [TAP]
+        * Fixed bugs in the freebsd hints.
+        * All of a sudden, linux systems have started barfing on
+          our declaration of signal() in externs.h. All right, if they
+          want to play that way, we now test for this in Configure
+          and don't declare it ourselves if it would break things.
+        * Changed Amberyl's address in the source to lwl@digex.net
+        * Somehow the fix for add(+1,0) got left out of 1.6.4. Back in. 
+        * The ansi() function now produces underscore with "u", as
+          advertised. [TAP]
+        * Warning about discarding const in assignment fixed in game.c.
+
+Version 1.6.4 patchlevel 2              May 14, 1996
+
+Minor Changes:
+        * At game startup, semaphores on all objects are cleared.
+          Prevents objects from having leftover semaphore states after
+          a shutdown. [TAP]
+          INCOMPATIBILITY WARNING! If you have objects which expect
+          to always have sempahore -1 (or something else), be sure
+          that they do "@drain me; @notify me" in their STARTUP
+          (as recommended in Amberyl's MUSH Manual).
+
+Fixes:
+        * @dump/paranoid produced db's with incorrect db flags
+          when CHAT_SYSTEM was 3 or 4. Reported by Matt Chatterley.
+        * Under certain conditions, one could get inside a ZMO using
+          the "go" command. Fixed. [RLM]
+        * A strcpy in fun_match is now a safe_str. [PeaK]
+        * INPREFIX fixes: (1) no space if INPREFIX evals to a null string,
+          (2) message to be prefixed is passed as %0, (3) %1-%9 are
+          temporarily cleared. [TAP]
+
+
+Version 1.6.4 patchlevel 1              May 11, 1996
+
+Fixes:
+        * Forgot to #ifdef MAIL_SUBJECTS around fun_mailsubject.
+          Reported by Mercurial Mink@Protean.
+        * Quirk in @dbck fixed. [RLM]
+        * Possible denial of service attack using repeat() fixed. [RLM/TAP]
+        * Problem with Configure script under certain conditions (esp FreeBSD)
+          producing bad compiler flags fixed. Report by Mike Wilson.
+
+Version 1.6.4 patchlevel 0              May 3, 1996
+
+Major Changes:
+        * eval_boolexp rewritten again. Important features:
+          - If A is locked to @B, evaluating A's lock is identical
+            to evaluating B's lock
+          - To do an indirect lock or an elock(), you must be able to
+            read the lock on the target object (i.e., you must be
+            See_All, control the object, or it must be Visual)
+            (New macros Can_Run_Lock and Can_Read_Lock for this)
+          - As a result, channel locks will work as advertised if
+            you indirect-lock them to VISUAL objects
+        * Changes to handling of plain strings (with no evaluation)
+          resulting in doubling of speed. [TAP]
+        * If you fail the page-lock on a DARK player, you receive their
+          AWAY message rather than their HAVEN message. That is,
+          failing a page-lock on a DARK player is just like paging
+          a DARK HAVEN player.
+        * INHERIT check moved to controls() function
+
+New functions:
+        * mailsubject() returns subject of a given mail message,
+          analogous to mail() and mailfrom(). By Atuarre.
+
+Minor Changes:
+        * Don't go through some major sections of the parser if we've
+          be given PE_NOTHING. Performance boost. [TAP]
+        * Players who are allowed to idle past inactivity limit, and
+          are unfindable, and can hide, are hidden (as before), but
+          only receive the notification once, instead of every
+          hour (or whatever your limit is). Players who are all of the
+          above but can't hide don't get any notification now.
+          Suggested by Gepht@Hemlock
+        * When allocating a new boolexp, set its struct members to
+          null values for better debugging. [RLM]
+        * We no longer save/restore the r-values when evaluating
+          the left side of the equal sign in attribute setting.
+          So @desc %q0 = %q0 now works as expected. Reported by PeaK.
+        * Locks set when players are initially created (lock, elock)
+          are now set to "=me" instead of "me". [PeaK]
+
+Fixes:
+        * When EX_PUBLIC_ATTRIBS wasn't defined, you never saw the
+          DESCRIBE attribute listed when you examined objects. [AKM]
+        * Vacation and Connected were being handled incorrectly
+          internally; the type of object wasn't being checked. [TAP]
+        * Now that Connected is fixed, the call to @doing doesn't have
+          to prefilter for players, so don't. [TAP]
+        * +nn is now considered a number, so add(+20,1) works.
+          Reported by Dennis De Marco.
+        * String boundary problems sometimes when you hit the 
+          function recursion limit could cause crashes. Fixed.
+          Reported by Oliver Stoll. [TAP]
+        * Making on Linux should hopefully now work with -g. [PeaK]
+        * Warning message when @chzoning an inherit player used
+          to be announced when @chzoning *any* player. Fixed. [PeaK]
+
+
+Version 1.6.3 patchlevel 7              April 25, 1996
+
+Fixes:
+        * beep() works without arguments now, as promised. [TAP]
+        * repeat(string,-number) caused an infinite loop. Reported by
+          Atuarre.
+        * The create() function didn't accept a single argument. [RLM]
+        * last(a b c) was returning " c" instead of "c". [TAP]
+        * The Configure script did indeed send email to listproc if
+          you asked to be subscribed to the pennmush mailing list.
+          But it sent it with Precedence: junk, which listproc ignores.
+          Fixed.
+        * %q9 wasn't preserved due to a typo in function.c. Fixed. [PeaK]
+        * SIGHUP now handled synchronously to prevent race conditions.
+          Noted by PeaK.
+
+
+
+Version 1.6.3 patchlevel 6              March 30, 1996
+
+Minor Changes:
+        * A channel's owner now always passes the channel's modlock.
+        * Wizards may now do folderstats(player,folder). Previously
+          on God could, but wizards could see the stats for the player's
+          current folder, and could reset the current folder, so it
+          doesn't make sense to restrict. Report by Cwilla@Victory-MUSH
+        * @function is restricted to see_all players. Suggested by
+          Oderus (Mike Wesson).
+
+Fixes:
+        * @sitelock/ban and @sitelock/register had their effects
+          mixed up. Reported by Matt Chatterley.
+        * Objects that were undestroyed were having the GOING flag
+          removed but not the GOING_TWICE flag. Fixed. [RLM]
+        * element() now works as advertised. [RLM]
+        * Systems without IEEE math now can't do power(x,y) if
+          *either* x or y is greater than 100. Report by InsaneJoseph@WoP
+
+
+
+Version 1.6.3 patchlevel 5              March 12, 1996
+
+New Powers:
+        * The immortal power has been split into 3 powers:
+          no_pay (has unlimited money), no_quota (has unlimited quota),
+          and unkillable (can't be kill'd). For backwards compatibility,
+          @set player=[!]immortal sets/unsets all three. Objects which
+          were immortal before this patch will have all 3 flags set.
+
+Minor Changes:
+        * The DEBUG flag can now be set by any user. The Can_Debug power
+          will automatically be removed from all objects.
+        * The "directive not found" message in loading mush.cnf has been
+          changed to a more comprehensible/less scary "directive in 
+          cnf file ignored" message.
+
+Fixes:
+        * FIXED players can no longer "go home" or "move home"
+        * index-files.pl was producing incorrectly titled index entries. [RLM]
+        * More fixes to elock() and zone locks. Elock() should now work
+          even when RALPH_LOCKS isn't defined. Zone locks are checked
+          on the correct object. [TAP]
+        * @open exit=variable should work correctly now. [TAP]
+
+
+Version 1.6.3 patchlevel 4              February 27, 1996
+
+Fixes:
+        * Complex evaluation locks may be better behaved. [RLM]
+        * ex/debug now shows flag bits in hex, not decimal [RLM]
+        * % failed to quote the following character in some cases.
+          Fixed to work like 1.50. [PeaK]
+        * iter() only allowed a single-character output separator instead of
+          an arbitrary string. Fixed. [PeaK]
+        * Bug in extmail.c which caused crashes on find_exact_starting_point
+          with newly created players may be fixed.
+        * Repaging with page/list uses the correct format.
+          Also, failing to page anyone successfully doesn't clobber
+          LASTPAGED when using page/list.
+        * Dark-connecting admin now trigger connection messages on
+          admin chat channels (unless the channels are quiet)
+        * Help for pos() corrected. 
+        * Things starting with #- (error messages) are now treated as
+          false (0) in boolean functions, as under 1.50. [TAP]
+        * The recursive_member function was inefficient, and potentially
+          buggy, which could allow things to be teleported into things
+          they contained. Fixed.
+        * @drain wasn't lowering QUEUE attrib. Fixed. [RLM]
+        * Zone master help updated to refer to zone locks [RLM]
+        * Memory malloc'd by replace string, though freed, didn't
+          have its mem_check cleared in a few places, which could make
+          it appear that replace_string was leaking. [PeaK]
+
+
+Version 1.6.3 patchlevel 3              February 12, 1996
+
+New option:
+        * If NULL_EQ_ZERO is defined in dune.h, a null string will
+          be considered a zero in a math function. For example,
+          add(,3) will return 3, instead of #-1. 
+
+Minor Changes:
+        * Guests may not join or leave channels. [Mike Wesson, Oderus]
+
+Fixes:
+        * edefault() core dumps fixed.
+        * eval() now behaves correctly. [TAP]
+        * ALL players (inc. Wizards) now subject to BUILDING_LIMIT.
+        * Quota now properly updated on object destruction. [RLM]
+        * Misc. fixes to object destruction, especially in the case
+          of what happens to SAFE objects of destroyed players. [RLM]
+        * Queue was handled strangely for priv'd players. Fixed.
+        * New internal macros NoQuota and NoKill, both the same as
+          NoPay, established in preparation for future splitting of
+          Immortal power into 3 powers.
+        * help @clock2 typo fixed.
+
+
+Version 1.6.3 patchlevel 2              February 7, 1996
+
+[L@E] refers to Lukthil@ElendorMUSH, who did many patches to 1.50
+that I'm just now getting to integrate.
+
+Minor Changes:
+
+        * Wizards are now effectively pemit_all
+        * @dol/notify notifies even if given a null list. [L@E]
+        * See_All players can see quotas with @quota. [L@E]
+        * If we're logging forces, don't just log forces *by* wizards,
+          log forces *of* wizards (and things they own) by anybody. 
+          Idea by L@E.
+        * Removed obsolete code: clear_atr_name
+        * mkindx now sorts index entries alphabetically, so it
+          doesn't matter what order you have get() and get_eval()
+          in your help file - help get will give get().
+          Code by Pavel Kankovsky (PeaK).
+
+Fixes:
+        * #-1 was being considered "true" instead of "false" when
+          TINY22_BOOLEANS was not defined. [RLM]
+        * munge() is better behaved when there are duplicates in the
+          first list, accurately matching them with the second. [L@E]
+        * Emits weren't propagating through audible exits when
+          there was nobody in the room. Fix idea by L@E.
+        * Charges were broken. Fix idea by L@E.
+        * Immortal players could lose coins when their non-immortal
+          objects ran code. Fixed. [L@E]
+        * @edit is less likely to overflow buffers when using ^ and $. [L@E]
+        * @halting objects used to queue them for #0. Fixed. [L@E]
+        * Semaphores weren't removed when an object was halted. Fixed. [L@E]
+        * Possible permissions problem in running STARTUP when objects
+          are chowned fixed. [TAP]
+        * make etags now picks up dune.h and options.h [RLM]
+        * Code consolidation: chowning when an exit is linked is done
+          via a call to chown_object now. [RLM]
+
+
+Version 1.6.3 patchlevel 1              February 4, 1996
+
+
+Minor Changes:
+
+        * NO_LEAVE is now settable by anyone
+        * The index-files.pl script indexes admin news and help separately.
+        * *emit() functions can now have unescaped commas in their last
+          argument (the message). [TAP]
+
+Fixes:
+        * lit() doesn't screw up on braces. [TAP]
+        * @search and lsearch() using eval were broken. [TAP]
+        * @unlock foo worked, and also gave a Huh?. Fixed. [RLM]
+        * When a player was destroyed, their SAFE exits would be
+          destroyed instead of chowned. Fixed. [RLM]
+
+
+Version 1.6.3 patchlevel 0              January 31, 1996
+
+Major Changes:
+
+        * Assume that object A is locked to @B, and object B is
+          locked to canuse/1. It used to be that when player P tried
+          to pass the lock on A, the canuse attribute on *A* was
+          checked, instead of B. Fixing that required that we check
+          to be sure that B is allowed to evaluate the CANUSE attribute
+          on A, which required internal changes to boolexp.c.
+
+Minor Changes:
+
+        * Objects can now send mail as themselves, not their players.
+          Mail from objects is reported as being from an object
+          when read. The format for @mail(/read) has changed somewhat.
+        * page[/list] now stores the last player(s) you paged in a LASTPAGED
+          attribute, so you can re-multipage. The behavior is a little
+          different for page and page/list. Page stores exactly what
+          you typed as the last player/list of players to page; page/list
+          stores only those names for whom the page succeeded.
+
+Fixes:
+
+        * The site of last failed login used to be a visual attribute.
+        * Parser misbehaved on: think ( foo ), removing spaces. [TAP]
+        * @edit and related wasn't stripping leading spaces from the
+          right side of the = sign. Fixed.
+        * Problem with count of unread mail on login with EXTENDED_MAIL
+          fixed.
+        * @channel/rename can now rename a channel to the same name
+          with case changes, without an error.
+        * +channel now matches partial ambiguous channels in a smarter
+          fashion - it looks for a channel you're on.
+        * Better help for @clock
+        * die(0,x) now returns 0. die(negative #,x) returns an error. [TAP]
+        * @chan/decompile can be used by the channel's owner or
+          anyone who passes the modlock, as well as Wizards.
+        * Channel hide locks work right. [RLM]
+        * @chan/decompile now shows the correct commands for locking
+          channels. [RLM]
+
+Version 1.6.2 patchlevel 1              January 28, 1996
+
+Fixes:
+        * get_eval error about # of args fixed.
+
+
+Version 1.6.2 patchlevel 0              January 26, 1996
+
+New Commands:
+
+        * @mortal <command> lets a privileged player run a command
+          as if they were unprivileged. [TAP]
+        * ahelp/anews shows admin help/news topics to admin players. 
+          Admin-only topics are in the same files (help.txt/news.txt),
+          but topic names begin with '&'. For example:
+          & for-all-players
+          ...text...
+          & &admin-only
+          ...text...
+        * page/list <players>=<message> will do a multipage with a less
+          spammy format for the pager.
+
+New Functions:
+
+        * mortal() lets a privileged player evaluate as if there were
+          unprivileged. [TAP]
+
+New Flags:
+
+        * MORTAL, to support @mortal and mortal(). Used internally, and
+          not settable. Can be reset by Wizards, just in case, though. [TAP]
+
+Minor Changes:
+
+        * If you have a channel "admin" and a global +admin (no args),
+          calling +admin will run the global, not give the
+          "What do you want to say to that channel" message, under
+          extended chat.
+        * If a panic db is corrupt, the restart script will move it to
+          save/PANICDB.corrupt instead of removing it.
+
+Fixes:
+        * Problems with compile when HAS_SIGACTION is undefined.
+          Noted by Brian Favela, affects Linux.
+        * Duplicate uselock check in atr_comm_match removed.
+          Noted by Brian Favela.
+        * Looking at an exit showed the full name and aliases, not just
+          the full name. [TAP]
+        * Compiling without EXTENDED_MAIL works right.
+        * Potential buffer overflow problems in look_exits() fixed.
+        * get_eval() really uses a /, not a comma, like the help says.
+        * (Hopefully) last remnants of old parser removed from externs.h
+        * @dol on an empty list now doesn't give the weird "what do you want
+          to do with the list" message. @dol with an empty command does. [RLM]
+
+Version 1.6.1 patchlevel 1              January 23, 1996
+
+Fixes:
+        * v(#), v(!),and v(N) now return the same thing as %#, %!, and %N.
+          [RLM]
+
+Version 1.6.1 patchlevel 0              January 21, 1996
+
+New compile-time options:
+
+        * SAFER_UFUN: when defined, prevents less priv'd objects from
+          evaluating ufuns on more priv'd objects, which helps a
+          potential security problem with side-effect functions.
+          This is more of a stopgap -- control and security will be
+          revamped in a future release -- but is recommended.
+
+New .cnf directives:
+
+        * player_creation enables/disables "create" at login screen.
+          Also @enable/disable creation
+        * use_dns enables/disables IP-to-hostname resolution
+
+New Functions:
+
+        * tel(thing,destination)
+        * oemit([room/]object,messsage)
+        * emit(message)
+        * lemit(message)
+        * remit(object, message)
+        * zemit(zone, message)
+        * timestring(secs,flag) converts seconds to #d #h #m #s
+          format. If flag is 1, the string always includes all time
+          periods (e.g. timestring(301,1) = 0d 0h 5m 1s). If flag is
+          0 or omitted, only the necessary periods are included
+          (e.g. timestring(301,0) = 5m 1s). 
+        * left(string, n) returns the first n characters of the string
+        * right(string, n) returns the last n characters of the string
+        * hasflag() syntax extended to hasflag(obj[/attr],flag) for
+          checking attribute flags as well.
+        * functions() lists all enabled functions
+        * atrlock(obj/attr[,<on|off>])
+        * doing(player) returns a player's @doing if they can be see
+          on the WHO list
+
+Minor Changes:
+
+        * @allquota now has /quiet switch, and works correctly when
+          no limit is given. [NJG]
+        * The restart script is now a lot smarter, and figures things
+          out from mush.cnf. All that needs to be set is the location
+          of the game directory, and if you use make customize, not
+          even that.
+        * @wall/@rwall/@wizwall with a null message fails. (src/speech.c)
+        * SIGHUPs sent to the MUSH are now logged in netmush.log
+        * Common code called when object ownership is changed has
+          been encapsulated into a single function for modularity.
+        * crypt() is now a macro on systems which can't define HAS_CRYPT
+
+Fixes:  
+        * The table() function was incorrectly truncating some
+          list elements. Fixed. Report by Alaric@WoP
+        * The match() function was returning an empty string instead
+          of 0 when it failed to match. Fixed. Report by Alaric@WoP.
+        * help math functions now list the transcendental and other
+          floating point functions
+        * map() and mix() were broken. Fixed. [TAP] (src/funlist.c)
+        * @grep/print is now case-sensitive, like help says. (src/predicat.c)
+        * Refunding money when objects are chowned 
+          was refunding 10 coins, instead of the object deposit. [RLM]
+        * convtime() hadn't been converted to the new parser,
+          and therefore was broken. Fixed. (src/funtime.c)
+        * Compilation warning in src/game.c fixed
+        * Giving players large negative amounts of money was slightly
+          broken and is now fixed. Obscure, eh? [RLM] (src/rob.c)
+        * Misleading comment in options.h.dist about QUEUE_LOSS
+          fixed. [RLM]
+        * Help fixed to indicate that there's no longer a 10 dimension
+          limit on vector functions.
+        * Various missing prototypes corrected.
+        * make customize now correctly sets the restart script
+          executable, and installs links to netmud and mkindx.
+        * Improved Win32 portability [NJG]
+        * Fixes to default() and edefault() [TAP]
+
+Version 1.6.0 patchlevel 5              January 17, 1996
+
+Fixes: 
+        * Extended chat: @chan/decomp shows privs correctly now
+        * Extended chat: When a player is destroyed, channels they own
+          are chowned to God.
+
+Version 1.6.0 patchlevel 4              January 17, 1996
+
+Fixes:
+        * Extended chat: Formerly, non-players could create channels.
+          No longer.
+        * Extended chat: Channel creation cost is now refunded when
+          channel is removed.
+        * Help text for HALT (game/txt/pennflag.hlp) and SUBSTITUTIONS
+          (game/txt/penn.hlp) now notes that HALTed objects do not
+          evaluate functions or substitutions.
+        * Nick Gammon's new email address is in README and win32/README
+
+
+Version 1.6.0 patchlevel 3              January 16, 1996
+
+Fixes:
+        * default, edefault, udefault now work like Tiny 2.2, not eval'ing
+          their arguments.
+        * Many fixes to the extended chat system.
+        * Fixed misspelling of Guest in @config.
+        * @function on a non-existant object no longer crashes. [TAP]
+        * Problems with paranoid dumps not setting the correct dbflags
+          corrected. [TAP]
+        * Problems with EXTENDED_MAIL crashing when using LOGOUT fixed. [RLM]
+        * @warnings for exit-msgs and thing-msgs warned when there was
+          no OFAIL on a locked exit/thing, which is silly. [1.6.0p1]
+        * Started patching the CHANGES file, like I should.
+        * Fixes customize.pl
+        * Fixes update-cnf.pl so that 'make update' won't trash player_flags
+          (and other repeatable directives) in mush.cnf
+        * Fixes game/txt/README
+
+Version 1.6.0 patchlevel 0              January 10, 1996
+
+Major Changes:
+
+        * New function parser with improved speed, sanity. [TAP]
+        * Complete rewrite of destruction code. Undestroying supported
+          for all objects, @adestroy works sanely, SAFE is safer,
+          better consistency checking. [RLM]
+        * Support for 'plural' gender for TinyMUSH compatibility. [2.2]
+        * Most filenames are now 8.3, to support the win32 port
+        * Some options have been moved to the runtime config file
+          (dbcomp). Others have been removed entirely.
+        * 'make update' will update runtime config directives
+        * The chat system has been completely rewritten. Number of 
+          channels is limited only by memory, channels are saved
+          across shutdowns (modifying source to add channels never
+          required), channels can be locked in various ways, 
+          non-wizards can create channels, etc.
+        * New .cnf directives: chat_database (where to store channels),
+          max_player_chans (how many channels can a non-admin create),
+          chan_cost (how much does creating a channel cost)
+        * The CSRI malloc is now supported and suggested. In addition
+          to being extremely memory-efficient, it offers leak detection
+          and profiling features.
+        * The database format now defaults to quoting text, which is
+          less vulnerable to corruption, in particular the loading of
+          attribute locks starting with a number. [1.50-15-04,05 TAP]
+
+New Functions:
+
+        * matchall() [2.2]
+        * default(), edefault(), udefault() [2.2]
+        * aposs() and %a return absolute possessive pronouns [2.2]
+        * elements() [2.2]
+        * mudname(), version(), starttime() [2.2]
+        * stats() is now an alias for lstats() [2.2]
+        * ulocal() [2.2]
+        * search() is now an alias for lsearch() [2.2]
+        * folderstats() returns information about numbers of messages
+          in a given @mail folder
+        * last() is the counterpart to first(), and returns the last
+          item in a list
+        * mailtime(), mailstatus(). Suggested by Alaric@WorldOfPain.
+
+New Commands:
+
+        * @emit can be abbreviated '\\', for TinyMUSH compatibility
+        * The speech commands (say, pose, @[po]emit, whisper, page)
+          can now take a /noeval switch, which prevents them from
+          evaluating the message.
+        * 'semipose' is an alias for pose/nospace
+        * 'INFO' from the connection screen gives version info for
+          the MUSH, for use by automated mudlists and the like.
+        * @sitelock/name adds names to the banned names file.
+        * @enable/@disable guests (and new cnf file directive guest_allow)
+        * @decomp now takes /flags and /attribs switches to only decompile
+          the creation/flags information or the attribute information.
+        * @list command partially implemented for TinyMUSH compatibility.
+
+New Flags:
+
+        * CLOUDY flag on exits provides partial transparency. A CLOUDY
+          TRANSPARENT exit shows only the description of the room beyond, 
+          no contents.  A CLOUDY but not TRANSPARENT exit shows only 
+          the contents, no desc.
+        * FORCE_WHITE flag on an ANSI player forces their output to be
+          reset to white, necessary for some poor ansi terminals which
+          "bleed". [Kalkin]
+
+Minor Changes:
+
+        * @chzone'ing an object to 'none' no longer clears its privbits.
+        * OXMOVE attribute is shown to the room you're leaving.
+        * Setting and resetting the SUSPECT flag is now logged in wizard.log
+        * Various outdated defines have been removed from options.h/dune.h
+        * Objects can now use @mail commands, as if they were their owners.
+
+Fixes:
+
+        * idlesecs() now accepts "me".
+        * "pose" (not ":") used to discard everything after an "=".
+        * The "entries" entries in help.txt, etc, have been fixed a bit.
+        * index(a| |b,|,2,1) now returns a null string instead of "|b"
+        * Various memory leaks [1.50-15-01]
+        * When fork fails, a non-forking dump is done, and the MUSH
+          no longer exits. [1.50-15-02]
+        * Soundex() no longer hangs on non-alphabetical. [1.50-15-06]
+        * dist2d and dist3d are safer on non-IEEE math systems. [1.50-15-03]
+        * mail() now counts messages across all folders
+        * Better matching of del_check to add_check [1.50-15-11]
+        * PennMUSH now compiles correctly without EXTENDED_MAIL
+          [1.50-15-10]
+        * Some fixes to unparse.c to stabilize huffman compression
+          [1.50-15-09,RLM]
+        * Fixes to @boot [1.50-15-08,TAP]
+        * Fixes to variable exit handling by quick-unparse [1.50-15-07,AKM]
+        * Configure now tries to find a full path for uptime(1) so that
+          @uptime will work
+        * Fixes to forbidden_site and forbidden_name when there's no
+          sites/lockout/names file. [NJG]
+        * Backspace handling for really dumb terminals improved [NJG]
+        * When changing the text of an attribute, its flags are no longer
+          reset. [RLM]
+        * When seizing a link, coins weren't refunded if the seize failed.
+
+Version 1.50 patchlevel 14              July 3, 1995
+
+This patchlevel is primarily concerned with bugfixes and patch management.
+
+Maintainer changes:
+  * New file 'Patchlevel' tracks mandatory patches to insure that they 
+    are applied in the proper order
+  * Clearer 'restart' script. [TAP]
+  * The indexing script for help/news/etc now creates the topic 'Entries'
+    instead of 'Index', to prevent a conflict with 'help index()'.
+  * The file hdrs/db.h is now hdrs/mushdb.h, to match Tiny 2.2 and
+    because FreeBSD complains about db.h.
+  * The links hdrs/dune.h and hdrs/options.h were unnecessary and
+    are now removed.
+  * The noforking dump messages have been moved to mush.conf
+
+Commands:
+  * The @set obj1=attrib1:_obj2/attrib2 form no longer works, as
+    it conflicts with general attempts to set attributes that start
+    with underscores. Use @cpattr for copying attributes.
+
+Options:
+  * EXITS_CONNECT_ROOMS, if defined, prevents rooms with at least one
+    exit from being announced to players as "disconnected rooms".
+  * RALPH_DEBUG option allows for improved DEBUG flag output. [RLM]
+  * When MEM_CHECK is defined, it dumps mem_check info to the
+    checkpt.log before each dump.
+  * SAFER_PLAYER_NAMES options has been removed. Player names can't
+    contain those funny characters, period.
+
+Fixes:
+  * Added -D_INCLUDE_AES_SOURCE to hpux hints
+  * stdlib.h is now included in eval.c to get atof. This was breaking
+    linux MUSHes badly.
+  * Linux hints now use BSD signal semantics.
+  * Various attempts to fix possibly unusual bugs in compression.
+  * The internal TRANSPARENT flag bit macro is now TRANSPARENTED,
+    because Solaris defines TRANSPARENT
+  * A bug causing setq'd registers to be incorrectly munged has been fixed.
+  * @chzone warning messages refer to zone-lock, not elock, if RALPH_LOCKS
+    are compiled in.
+  * Examining a player who was on a null channel was reported to crash
+    one MUSH. Some attempts have been made to fix that problem.
+  * The compose.csh script in game/txt has been replaced by 
+    compose.sh, which now deals properly with systems which are missing
+    perl (gasp!)
+  * Configure script hopefully won't die when checking signals on Linux.
+  * Fixes to some strange @mail behavior. [RLM]
+  * CType in compress_h.c is now unsigned long, not long. [RLM]
+  * Fix to converting old maildbs to mail subjects [RLM]
+  * Bigram compression could (rarely) cause $commands or ^patterns
+    to stop functioning.
+  * GoodObject() used to consider db_top to be a valid dbref. It's not.
+  * Recycling of objects was broken in pl13 and is now fixed. [RLM]
+  * mush.conf can now deal with the FIXED flag as a default flag [RLM]
+  * A lot of mem_checks weren't being deleted, especially exec.buff ones.
+  * Defining COMPRESSION_TYPE to be 0 (no compression) now works. [TAP]
+  * Paranoid dumps no longer stomp out tabs and newlines unnecessarily.
+  * Configure now checks for IEEE math compliance, and defines HAS_IEEE_MATH
+    which is used by the code to determine if some math functions need
+    to be limited.
+  * SOLARIS defines have been removed as extraneous.
+
+Version 1.50 patchlevel 13              June 1, 1995
+
+Patchlevel 13 was very much a collaborative effort with Ralph Melton
+and T. Alexander Popiel, terrific MUSHhacks.
+
+Major user changes:
+
+  * Complete rewrite of locks, which allows for many, many new
+    locks, including user-defined locks, with reduced memory usage. [RLM]
+  * @mail can now have a subject.
+
+Major maintainer changes:
+  * The code now contains ANSI prototypes (if Configure ascertains that
+    your compiler likes 'em) for easier debugging.
+  * The help, news, and events entries are now managed in subdirectories
+    of game/txt/, and automatically indexed.
+  * The names.conf file now accepts wildcards
+  * New Makefile target 'update' will propagate your options.h/dune.h
+    settings into an options.h.dist/dune.h.dist template, ask you
+    whether you want to define any newly-introduced options, ask
+    you if you want to remove any obsoleted options, and write out
+    new options.h/dune.h files.
+  * Improvements to the autoconfiguration scripts, which now generate
+    a config.h and confmagic.h file in the top-level directory. These
+    headers tell the code what kinds of features are available.
+    Accordingly, the old hdrs/config.h header file has been renamed 
+    hdrs/conf.h
+  * The 'whisper_loudness' config directive in mush.conf sets the
+    probability that a noisy whisper will be noticed in the room. 
+  * If the standard Huffman attribute compression gives you trouble, you
+    can use the COMPRESSION_TYPE define to use the older bigram
+    compression system, now auto-tuning, or no compression at all.
+  * The TINY22_BOOLEANS option causes the MUSH's boolean functions
+    (and(), or(), not()) to be compatible with TinyMUSH 2.2. In Tiny 2.2,
+    only non-zero integers are "true". In PennMUSH's default, non-zero
+    integers, non-negative db#'s, and strings are "true". You pick.
+  * NO_NUKE_SAFE_PLAYER prevents @nuke from working (for Wizards) on
+    players set SAFE. You have to unSAFE them first.
+
+New functions:
+
+  * MWHO() function is like LWHO() but always evaluates as if the
+    enactor were an unprivileged player. Useful for globals.
+  * ISDAYLIGHT() returns 1 if it's daylight savings time, 0 if not.
+    By Dave Sisson
+  * CWHO() now returns a list of dbrefs, NOT NAMES. Much more flexible.
+  * ITER() now can take a fourth argument, which is the output delimiter.
+    You can have any string shoved between the output elements instead 
+    of a space (for example, a %r).
+  * TABLE() for presenting lists in rows.
+
+New commands:
+
+  * Players can connect with 'ch name password', which is just like
+    'cd', but connects hidden. Activated by defining DARK_CONNECT
+  * @warnings now allows players to exclude warnings by using
+    @warnings me=!warning. [RLM]
+  * @ps/quick now displays only the summary line of @ps for players.
+  * @decomp can take a /tf switch, which makes its output emulate
+    the 'FugueEdit' object (originally by van@TinyTIM) which
+    lets you use tf to edit attributes. Idea by Lord Argon.
+
+Minor changes:
+
+  * The 'news code' and 'news changes' entries are now in help.txt,
+    so you don't need to update your MUSH's news.txt files when you upgrade.
+  * In an ambiguous situation (i.e., @dest foo when you carry
+    foobar and foofoo), @dest will no longer pick one at random. It'll fail.
+  * #0 is now evaluated as TRUE in the context of boolean functions,
+    because it's a valid dbref (applies only #ifndef TINY22_BOOLEANS)
+  * haspower() allows players to see powers on things they control,
+    even with HASPOWER_RESTRICTED defined. [RLM]
+  * O-attributes which evaluate to nothing are no longer displayed. [AKM]
+  * Paranoid dumps no longer smash \r and \n.
+  * @mvattr no longer deletes the original attribute if the copies fail.
+  * Secure now stomps out ^'s
+  * The getrandom() function has been rewritten. [RLM]
+  * It's now a lot harder to have Guest and Wiz/Roy set on the same
+    player. Players shouldn't be able to connect to Wiz/Roy Guests anymore.
+  * HARSH_GUEST is now a lot harsher. Guests pretty much can't modify
+    the database except to lock/unlock things they control.
+  * players who are on a priv'd channel may speak/leave
+    even after they lose their privs. [RLM]
+  * Xenix options removed from options.h.dist, since the Xenix code
+    isn't supported anymore anyway.
+  * The size of MAX_COMMAND_LEN (and therefore all derived buffers)
+    has been doubled.
+  * Doing a 'make' from the src/ directory will now call the top-level
+    makefile. This helps those who use emacs (like Ralph, who came up
+    with this) and M-x compile from the srcdir.
+  * @version shows compilation flags.
+  * Admin WHO no longer wraps lines. Sitenames are truncated as needed. [TAP]
+
+Fixes
+
+  * Repaging a player with a multi-word name works correctly now.
+  * Players with the tport_anything power can teleport FIXED things. 
+  * @map works as documented again.
+  * Paranoid @dumps don't give so many spurious bad attribute warnings.
+  * ZMO elock checking now uses PLAYER_START and MASTER_ROOM instead
+    of #0 and #2. [RLM]
+  * @pemit/list now replaces ## with the target's db#, as the help says.
+  * Subtle bug in exit matching fixed. [RLM]
+  * escape() and secure() no longer parse their arguments.
+  * The asterisk-line separators on the nologin messages have been
+    prettified slightly, at Tigger's suggestion
+  * Some systems didn't deal well with overflowing @doing and @poll's.
+    Dave Sisson patched it.
+  * Error in bsd.c when compiling with DARK_CONNECT but without ROYALTY
+    fixed. (Reported by Suud@Gohs)
+  * hasflag, andflags, and orflags patched to prevent mortals from
+    using them to see mdark flags [RLM]
+  * Type mismatch in warnings.c fixed [RLM]
+  * fun_lcstr() and fun_ucstr() have been changed slightly in order to
+    support unices like MachTen which appear to define tolower() as 
+    a macro meaning (isupper(x) ? _tolower(x) : (x)), and were getting
+    hosed by the tolower(*ap++) call. Bug reported by Adrick.
+  * It was possible to overflow the buffers in do_log by having a 
+    Wizard do @fo me=think lnum(5000), for example. No longer, I hope.
+    Bug report and suggested fix by Adrick.
+  * Removed an old bit of code that broke compiles using original mailer
+  * The restart script is a little smarter about preserving databases.
+  * Fixed a bug that caused ALWAYS_PARANOID to dump core.
+
+Version 1.50 patchlevel 12              March 23, 1995
+
+Major changes:
+
+  * The matching routines in match.c have been rewritten to be
+    reentrant, which fixes some subtle but important bugs
+  * New Makefile target 'customize' for setting up customized
+    per-mush subdirectories for those who run multiple mushes.
+  * An untested DELETE_POWERS define in options.h, which will remove
+    powers from a database, to make it easier to switch to TinyMUSH
+
+New flags/powers:
+
+  * Things with the Pemit_All power can @pemit to HAVEN/ulocked players.
+    Useful for globals.
+
+--- Fixes ---
+  
+  * Previously, passing the elock of a ZoneMaster player allowed 
+    control over *all* the ZM's objects, including the ZM player itself.
+    Players are no longer controllable this way.
+  * Incorporated patch for compiling without CREATION_TIMES defined
+  * Incorporated Ralph Melton's patch to warnings.c to fix core dump
+    with multiple exits warning.
+  * Nobody can @tel rooms any more. New code for @tel'ing exits has
+    been written, however, and players may now @tel exits they control
+    from rooms they control to rooms they control.
+  * Z_TEL bug: players could defeat Z_TEL by entering an object and
+    @tel'ing from there. Reported by Ralph Melton.
+  * Bug in puppet checks in @teleport fixed.
+  * Players in exactly 15 levels of container could defeat NO_TEL. Fixed.
+
+
+Version 1.50 patchlevel 11              March 5, 1995
+
+At Amberyl's suggestion, the "dune" numbering scheme has been 
+abandoned. This is 1.50 pl11, and future versions will be numbered
+accordingly.
+
+Major changes:
+
+  * All objects can now have creation dates. Non-player objects have
+    attribute modification dates as well. Players have number of login
+    failures since last logins tracked instead. Supported by
+    ctime() and mtime() functions to return creation/modification time.
+    (CREATION_TIMES define in dune.h)
+  * The extended @mail system now maintains the maildb sorted by
+    recipient, and each player descriptor has a pointer to where that
+    player's mail begins in the maildb. This is much faster for reading 
+    and listing and clearing mail, only slightly slower when sending.
+
+New Commands:
+
+  * @boot/me: boots a player's idle descriptor (to selfboot hung 
+    connections).
+  * whisper has two switches: silent and noisy. Standard PennMUSH whisper
+    is silent, and is the default unless NOISY_WHISPER is defined.
+  * @grep can take two new switches, /ilist and /iprint, which are just
+    like /list and /print, but case-insensitive.
+
+New Flags:
+
+  * LIGHT (as in TinyMUSH): LIGHT objects/players appear in DARK rooms.
+    In addition, LIGHT exits also show up.
+  * Z_TEL: When set on a Zone Master Room or a room in a zone, prevents
+    players from using @tel from that room to any room that's not in the
+    same zone. Useful for puzzle areas or enforcing some IC constraints.
+
+New Functions:
+
+  * The lsearch() command can take a range of db#'s
+  * ctime() and mtime() functions (if CREATION_TIMES is defined)
+  * grepi() is a case-insensitive grep
+  * hasattr() returns 1 if an object has a given attribute
+  * hasattrp() returns 1 if an object or its parent has a given attribute
+
+Minor Changes:
+
+  * @away/@haven/@idle messages are not sent if they're null
+  * Players always receive feedback when they use @hide
+  * Players who are FIXED are now permitted to @tel objects into their
+    inventory. This makes coding puppets which follow you much easier.
+
+--- Fixes ---
+
+  * examine/mortal now functions more like it should. 
+  * If a ZMO was elocked to an object in #2, players couldn't @chzone
+    things to it. Reported by Melkor@Beleriand. Fixed.
+  * WHO is grammatical when reporting # of connected players. Idea
+    from Kalkin.
+  * The Connected ('c') flag is no longer visible to mortals.
+
+
+Version 1.50 patchlevel dune2              March 5, 1995
+
+DuneSource pl2 changes
+
+Major changes:
+
+  * Extensive warning system for things missing on objects, rooms, etc.
+    @warnings command for players to set the level of warnings they want,
+    @wcheck[/all] command for players to check an object (checks can also run
+    at a God-configurable interval on the whole db), NOWARN flag for objects
+    and players. Idea from Kalkin's adaptation of MUSE code, totally
+    rewritten.
+    
+
+New options (dune.h):
+
+  * GUEST_TEXTFILE option enables a textfile (guest.txt by default)
+    to be shown to Guests who log in. Idea and source code from Kalkin.
+  * PAGELOCK_NOT_USELOCK option causes @lock/use to fail for players,
+    requiring them to type @lock/page. The lock itself has not changed,
+    just the interface, to remind players of its function. By Ralph Melton.
+  * More control over possessive gets with the POSSESSIVE_GET define
+    and the POSSGET_ON_DISCONNECTED define. Possessive get can be
+    disabled, enabled for all but disconnected players, or enabled
+    at all times (the default PennMUSH behavior)
+
+New functions:
+
+  * lit() returns its input totally unparsed, and without spaces compressed.
+  * t() returns the truthvalue of its input, guarranteed to be either
+    1 or 0.
+  * objmem() and playermem() functions, return the memory usage of
+    an object or all of a player's objects. Requires search_all power.
+  * grab(list,pattern[,delimiter]), returns the first element of list
+    which patches the pattern. Patterns are of the match() type.
+    From the TinyMUSH 2.2 code.
+
+Minor Changes:
+
+  * You must actually own an object (not just control it) or be a
+    Wizard in order to set it chown_ok or dest_ok. By Ralph Melton.
+  * You can use #$ in the actions in a switch() function
+    or @switch/@select statement, and it will be replaced by the switch
+    expression before executing or evaluating. This can improve
+    efficiency and save space. For example:
+        switch(complexfunc(%0),Bob,BobResult,#$)
+    is equivalent to:
+        switch(complexfunc(%0),Bob,BobResult,complexfunc(%0))
+    but only requires a single evaluation of complexfunc(%0).
+    Suggested by Kenric@DuneII.
+  * "things" is a synonym for "objects" in @search now. By Ralph Melton
+
+--- Fixes ---
+
+  * #-2 is treated as a "0" (false, like #-1) in functions which need a
+    logical value for it. Previously, it was treated as -2 (true).
+  * @select is now considerably more efficient - it no longer will 
+    evaluate anything after the matched action. The old behavior
+    could well be a bug in the right conditions as well. 
+  * atr_add now rejects null attribute names, so you probably can't set them
+    any more. Suggested by Kalkin, the code's by Mike@StarWars.
+  * Players can reset a DARK flag on themselves, but still can not
+    set themselves DARK.
+  * andflags(me,!Dc) used to return 1 if I am !dark and !connected,
+    instead of !D and connected, as the help states. Fix by Ralph
+    Melton.
+  * Halting an object which is @wait'ing used to fail to decrement
+    the owner's queue attrib. Fixed now. Patch by Ralph Melton.
+  * set_flag uses strupper instead of upcasestr now, which should fix
+    a bug on some systems wherein "cd" command would crash the MUSH and
+    a similar bug wherein players connecting would crash the MUSH if
+    the ON-VACATION flag was in use.
+  * The old channel syntax (@channel foo=command) works again.
+  * The RULES option now works.
+  * The convtime() has been rewritten to work on NeXT's correctly.
+    Previously, its malloc failed and it returned -1.
+  * Systems which have getrlimit, but *don't* have RLIMIT_NOFILE,
+    are now supported. Notably, Aix 2.x and 3.x.
+  * The installation hints for Solaris 2 have been improved.
+    WAIT_TYPE is defined as int, and if NBPC can't be found for
+    getpagesize(), it'll try PAGESIZE instead. Thanks to Kalkin for these.
+  * Installation for AIX has been improved. AIX required inclusion of
+    sys/select.h in the IDENT stuff.
+  * Various rewrites of tests against NOTHING to use the GoodObject()
+    macro instead. Thanks to Ralph Melton for some of these.
+  * Got rid of some warnings when compiling mkindx
+
+Version 1.50 patchlevel dune1              March 5, 1995
+
+DuneSource pl1 changes
+
+Major changes:
+
+  * Whem players attempt to log in to a Guest character that is already
+    in use, the server tries to find one that isn't, and connects the
+    player to that. If it can't, you get the old behavior (two connections
+    to the single Guest).
+  * Extended @mail system is available with many new mail commands.
+  * Dump warnings 5 minutes and 1 minute before non-forking
+    dump, and optional announcement at completion of dump.
+  * Guest players may not set attributes.
+  * Kill command can be disabled.
+  * @aconnect/@adisconnect messages on individual rooms.
+
+Changes to commands:
+
+  * CD command at connection screen allows Wizards to connect to the game
+    DARKly.
+  * @sitelock command for on-the-fly sitelocking by Wizards.
+  * @dump/paranoid can try to fix the db in memory, too.
+  * @decomp/db, produces decomp output using dbref of object instead
+    of its name.
+  * ex/mortal, shows examine output as if player were mortal.
+  * Option to rename @destroy to @recycle, since @dest and @desc are
+    easy to confuse.
+  * @restart command. Combines an @halt with triggering @startup.
+  * @hide now provides feedback when used.
+  * @find may be restricted to royalty, the only ones for whom it might
+    possibly be useful.
+  * @function now lets you know when it updates an @function.
+  * The old (pl8?) @channel <channel>=<function> syntax is back, along
+    with @channel/<function> <channel>, for those who liked it.
+  * @grep can be either case-sensitive or not (the default).
+  * If you don't specify the destination attribute in @cpattr, it
+    assumes you want the same attrib name as the source attrib.
+  * @mvattr, like @cpattr, but removes old attribute.
+  * ANSI players now see their @edit additions in bold.
+  * Rooms and exits can @emit. Rooms @emit into themselves (like @remit),
+    and exits @emit to their source room.
+  * @squota can now be given quota levels as +<quota> or -<quota>, to
+    increase or decrease a player's quota from its current level.
+
+Changes to functions:
+
+  * encrypt() and decrypt() functions.
+  * hidden() function for checking if a player is hidden.
+  * hastype() function for checking if a thing is a given type.
+  * pemit() function sends pemits to a list of objects.
+  * lparent() function, returns object's parent, grandparent, etc.
+  * quota() function, returns a player's quota.
+  * N-dimensional vector arithmetic functions: vadd(), vmul(), vsub(),
+    vmag(), vdim(), vunit()
+  * haspower() can be restricted to admin.
+  * "Lower math" functions: shr(), shl(), inc(), dec()
+  * beep() with no arguments produces a single beep.
+  * pmatch() will now accept *player or #dbref arguments.
+  * lsearchr() function does an lsearch *backwards* through the db.
+
+Changes to flags/powers:
+
+  * Flags can be defined as dark (invisible to all but God), 
+    mdark (invisible to mortals), and odark (invisible to mortals who don't
+    own the object) in flags.c.
+  * @cemit can be granted as a power
+  * The ability to set/unset the DEBUG flag on objects the player controls
+    can be granted as the Can_Debug power. (Idea by Kenric@DuneII)
+  * Optional COLOR flag to control reception of ANSI color apart from
+    ANSI hiliting.
+  * OPAQUE flag on exits. OPAQUE exits in translucent rooms look
+    like normal exits in non-translucent rooms.
+  * FIXED flag prevents @tel and home.
+  * DARK Wizards need not trigger @aenter.
+  * The SUSPECT flag is now abbreviated 's'.
+  * NO_LEAVE flag on objects prevents 'leave' command from working in them.
+    Useful for flying vehicles and the like.
+
+Minor changes:
+
+  * &adestroy attribute triggered when object is dest'd.
+  * Player/room/object names can appear automatically in bold.
+  * Dark and Hidden players are distinguished on the admin WHO list
+  * Dark and Hidden players are indicated on the admin DOING list
+  * Wizards who idle are set hidden, not DARK. Same for royalty.
+  * DARK wizards enter and leave @chat channels silently.
+  * Royalty can now see the Wizard @channel/list
+  * The mortal @channel/list lists only public channels.
+  * @idle/@haven/@away attribs which evaluate to nothing are not
+    sent to the paging player.
+
+--- Fixes ---
+
+  * TRANSPARENT variable exits don't crash the MUSH when looked at.
+  * @channel/delete and @channel/privs take the right number of arguments
+  * @decomp now decompiles flags properly.
+  * When logins are disabled, players may not create characters.
+  * The controls() function is made safer. Defining SAFER_CONTROL prevents
+    anyone but God from controlling God, any non-wizard from controlling
+    anything with a wizbit, and any non-admin from controlling anything with
+    a roybit.
+  * Player names are made safer. Defining SAFER_PLAYER_NAMES prevents the
+    use of [, ], %, and \ in player names.
+  * The strupper() function is made safer.
+  * Mortals can remove the DEBUG flag on objects they own.
+  * The set functions now take delimiters like they should.
+  * Revwords() takes a delimeter, like it should.
+  * @config displays whether function side effects are available correctly.
+    It used to get it backwards.
+  * Some checks against NOTHING have been changed to use the GoodObject()
+    macro in look.c and possibly other places.
+  * It's more difficult for players to enter themselves.
+  * PLAYER_NAME_SPACES works right now, use double-quotes around 
+    multi-word names.
+  * haspower() on unknown powers now returns #-1 NO SUCH POWER instead
+    of a null string.
+
+Version 1.50 patchlevel 10              March 5, 1995
+
+Patchlevel 10 Changes
+
+New additions:
+  - Guest is now a power. Set it on a player to restrict its command set. 
+  - New power Announce, which controls the ability to @wall. 
+  - Global r-registers are now preserved across the queue. 
+  - '#@' now replaces to the list-place for iter() and @dolist. 
+  - Timers now operate on absolute time, rather than game ticks. 
+  - Checks of $commands now obey parent uselocks. 
+  - Variable exit destinations.  When you attempt to go through one of these
+      exits, it evaluates the exit's DESTINATION attribute as a
+      U-function, and uses the result accordingly. 
+  - Rooms can now be set LISTENER. The flag name has been changed to MONITOR
+      (which is what it's called in 2.0), with LISTENER as an alias.
+      (Thus the MONITOR flag on things/rooms and on players mean
+      different things.) 
+  - If the EXTENDED_ANSI option is turned on, it enables the ansi() function,
+      allowing ANSI control codes to be used.  
+  - New wizard command @log can write information to a log file.  
+  - @oemit now takes its target location from the person to exclude, not
+      the person who is doing the @oemit (consistent with 2.0 behavior,
+      and more flexible).  
+  - @ps now follows the 2.0 syntax. Items deleted from the queue are now
+    counted, and the entire thing calls a single generic routine.
+
+Function changes:
+  - More functions take delimiters. The newly-modified ones are:
+      extract(), filter(), first() fold(), iter(), match(), member(), munge(),
+      rest(), revwords(), setdiff(), setinter(), setunion(), shuffle(), 
+      sort(), splice(), wordpos(), words()
+  - If function side effects are configured as enabled, the functions link(),
+      set(), parent(), lock(), clone() and name() can make db changes.
+  - controls() has been tweaked so it returns '#-1 <error message>' on invalid
+      objects, for consistency with other functions and with 2.0.
+  - delete() is now ldelete(). The new delete() function deletes characters
+      from a string. This is a change to be consistent with 2.0.
+  - lcon() is now consistent with the 2.0 convention -- no more partial lists;
+      either you can get the entire list, or you can't get it at all.
+  - locate() has some new parameters: 'c' matches 'carried exits', supported
+      by match_carried_exit(). Exit matching now calls match_exit_internal().
+      'X' allows random choice if the result is ambiguous (#-2).
+  - lsearch() now takes an EVAL class.
+  - objeval() evaluates its first argument.
+  - owner() now can take an object/attribute pair.
+  - sort() autodetects for floating point, and uses qsort.
+  - User-defined functions, such as U(), now return an empty string, rather
+     than #-1 SOME ERROR MESSAGE, because 2.0 behavior is to return an empty
+     string, and, generally, the empty string is easier to handle.
+
+New functions:
+  - New substitution: %qN is equivalent to r(N)  (also twiddled v() slightly)
+  - Findable(). Can <a> locate <b>? Useful for those WHO-list-type globals.
+  - Foreach(). Works basically like MAP(), but on strings.
+  - Haspower(). Like hasflag(), but for powers.
+  - Ports(). Returns the network descriptors a player is connected to.
+  - Rloc(). Returns an object's location at a variable level.
+  - Sortby(). Sorts a list by an arbitrary u-function.
+  - Stripansi(). Strips the ANSI codes from a string.
+
+Important other changes:
+  - Fixed a bug in the checking of ZMO locks in the game's internal controls().
+      The privs parameter to eval_eboolexp() needs to be the ZMO in question,
+      rather than the object itself, in order for attribute locks to work as
+      would seem logical (the object being checked shouldn't even factor into
+      the equation, since by definition zone control is based solely upon
+      the ZMO's enter lock).
+  - Because people seem to want it back, the "pose" command is back, together
+      with a /nospace switch.
+  - TRACE is now an alias for the DEBUG flag.
+  - Player lookups on dbrefs work (i.e, '*#1' is now valid).
+Version 1.50 patchlevel 9
+
+Patchlevel 9 Changes
+
+New additions:
+  - @cemit command emits to a channel.
+  - "Quiet" channels added. They don't show connect/disconnect or joined/left.
+  - '%c' returns the current command.
+  - @dolist/notify queues a '@notify me' after queueing the list commands.
+  - @pemit/list pemits to a list of dbrefs. No more @dolist/@pemits needed!
+
+New functions:
+  - Center(): centers text on a field.
+  - Cwho(): returns the names of players on a channel.
+  - Isdbref(): checks if something's a valid dbref.
+  - Map(): like iter() over a user-defined function.
+  - Mix(): like map(), but takes two lists.
+  - Munge(): combines lists, allowing you to do things like conversion sorts.
+  - Objeval(): evaluates an expression from another object's viewpoint.
+  - Trim(): trims characters at the beginning and/or end of strings.
+Other changes:
+  - Add(), And(), Mul(), and Or() can take up to 100 arguments.
+  - Channel commands are now switch-form.
+  - Debug-flag output is neater and more useful.
+  - Default input line length is 2K.
+  - @grep output format is neater and shorters. 
+  - @link can link already-linked exits.
+  - Locate() can take an 'l' parameter to check object location.
+  - Output line termination is now carriage-return-newline.
+  - Starting quota is now a conf option.
+  - @wipe can take an object-attribute pattern.
+Important bugfixes and modifications:
+  - Controls() now obeys the inherit flag.
+  - Enactor (%#) in idesc evaluates to the player, not to the object.
+  - The escape character is stripped by the parser.
+  - Listening objects trigger enter/leave messages in DARK rooms.
+  - Matching is only done on exits if there is no exact match after trying
+    all other possibilities.
+  - Non-INHERIT things can no longer @parent things which are INHERIT.
+  - Delete(foo%b,1) and Delete(foo,1) now return the same result.
+  - Replace(foo%b,1,Test) and Replace(foo,1,Test) now return the same result.
+  - Taking from an object is now governed by control, as well as ENTER_OK.
+
+Version 1.50 patchlevel 8
+Patchlevel 8 Changes
+Major new features:
+  - Players can now set an @alias.
+New additions:
+  - Player ZONE flag and ZoneMaster control provide "safer" zones.
+  - @hide command hides player from WHO list.
+  - @oemit can take a room dbref, behaving like @remit with an exception.
+  - mortal_dark and wizard attribute flags are settable. 'm' and 'w'.
+  - @pcreate power added.
+  - Channels can be wiped.
+New functions:
+  - Visible(): can one object examine another object or attribute?
+  - Items(), Element(): like Words() and Match(), but for arbitrary separators.
+  - Delete(), Replace(), Insert(): list-manipulation, arbitrary separators.
+  - Orflags(), Andflags(): check multiple flags.
+  - Fullname(): full name of an object.
+  - Many floating point functions.
+Other changes:
+  - Lattr() can take an <obj>/<wildcard> pattern.
+  - @prefix and @inprefix do pronoun substitution.
+  - @search can take an 'eval' parameter.
+  - No second arg to @lock or @link unlocks/unlinks.
+  - Mail() can now, for wizards, give info about other players' mail.
+  - Sort() now 'autodetects' sort type. Nsort() has been removed.
+  - Get_eval() is an alias for U() rather than Eval().
+  - Non-listening objects trigger @aenter and @aleave.
+  - @search, @find, and examine always ignore the MYOPIC flag.
+  - Queue deposits get refunded at startup.
+  - Words() uses ' ' as the delimiter, _not_ any whitespace.
+Major bugfixes:
+  - Many problems with flags have been fixed.
+  - "#123's foo" is no longer matched the same as "#123".
+  - Switch() nesting behaves properly.
+  - Parser bug with '/' in pre-parsed attribute names fixed.
+  - Remove() no longer screws up on things like 'remove(#434 #43 #22,#43)'
+  - Index() and friends no longer screw up on null separators.
+  - Squish() trims leading spaces properly.
+  - Various bugs with setfunctions fixed.
+  - Function recursion bug fixed.
+  - @scan no longer chokes on '='.
+  - no_inherit attribute flag works for real.
+Version 1.50 patchlevel 7
+
+Patchlevel 7 Changes
+Major new features:
+  - Powers system. Individual objects and players can be given the ability
+    to do special things, such as "examine anything", "teleport anywhere",
+    "boot players", etc. The BUILDER and IMMORTAL flags are now powers.
+  - Expanded flag system. Some flags are valid for more types of objects,
+    and flag lookups are quicker. The flag order is now alphabetized,
+    first by "generic" object type, then by specific object type.
+  - User-defined global functions, which behave just like built-in MUSH
+    functions, but are programmed in MUSH in a UFUN() format (i.e. you
+    can call them like any other function, i.e.  '@emit [my_function(foo)]')
+    The "@function" command is used to define and list these functions.
+  - Local variables (registers, numbered 0-9) which are retained for
+    the duration of a single command. Extremely useful for storing long
+    function evaluations, especially if you are programming "switchless".
+    The setq() and r() functions are used to set and retrieve the registers.
+  
+New additions:
+  - "no_clone" attribute flag (do not copy attribute when object is @clone'd)
+  - "@config/functions" lists all functions.
+New functions:
+  - squish() removes leading and trailing spaces from a string, and crunches
+    inter-word spaces down to a single space.
+  - filter() returns members of a list for which a user-def'ed function
+    evalutes to true ("1").
+  - fold() recursively evaluates a user-def'ed function and list.
+  - rjust() and ljust() pad strings with spaces (or another fill character)
+  - nsort() sorts a list of numbers.
+  - shuffle() randomizes order of elements in a list.
+  - scramble() randomizes order of characters in a string.
+  
+Other changes:
+  - cat() can take an arbitrary number of arguments.
+  - conn() and idlesecs() now recognize #dbref and *player. idle() is now
+    an alias for idlesecs().
+  - "Exits:" line in examine is back.
+  - examine on non-existent attribute(s) returns "No matching attributes."
+  - NO_COMMAND doesn't block ^, since you have to set the LISTENER flag anyway.
+  - @config shows a couple more options.
+  - The QUIET flag suppresses "Drained.", "Notified.", and "Halted."
+  - Debug and Verbose output no longer clobber the stack.
+  - switch() and iter() nested within other functions works properly.
+  - Players cannot enter objects remotely via dbref.
+
+Version 1.50 patchlevel 6
+
+Patchlevel 6 Changes
+Major bugfixes:
+  - Eval locks work with get(), ufun(), etc.
+  - Rooms can use @trigger again.
+  
+Changes to the parser:
+  - Spaces are no longer added around equals signs in 'say', 'pose', etc.
+  - The construction '<name of object> <command>' can no longer be used to
+    force a nearby object. '<dbref of object> <command>' still works.
+  - ITER() is much better-behaved; escapes and braces in the second
+    argument are no longer needed. It also now works properly with ufun().
+  - switch() can now take up to 100 arguments. Arguments to it are not
+    evaluated until needed.
+  
+Miscellaneous changes:
+  - Royalty can now set themselves DARK, but this affects _only_ their
+    visibility on the WHO list and related functions; they still appear
+    in the visible contents list of a room, on channel who, etc.
+  - Royalty can set themselves MONITOR.
+  - The TERSE flag suppresses _only_ auto-look messages (so if you type
+    'look here' while TERSE, you will get the description of the room).
+  - VAL() function returns the leading numeric prefix of a string.
+  - INDEX() is an extract()-like function which works for an arbitrary
+    character separator.
+  - WHERE() returns the "true" location of an object.
+  - HOME(), etc. now works on objects you do not control but are VISUAL.
+  - REPEAT() of something zero times returns a blank string, not an error.
+  - Commands done in GOING rooms are no longer considered invalid.
+  - "@parent <object>" by itself unparents an object.
+  - Exits in the same room as you are considered "nearby".
+  - All attribute fetches use the same permission checks. LINK_OK on a
+    zone object no longer allows zfun() to be done on the object if
+    the attribute cannot normally be read.
+  - Attribute flag sets added (@set obj/atr = flag). There are three
+    settable flags, 'visual', 'no_command', and 'no_inherit'. 'Examine'
+    displays these as 'v', '$', and 'i'.
+  - The lcon(), lexits(), con(), exit(), and next() functions now check
+    permissions differently. You can use these functions on a location 
+    that you own, or in a location that you are in and is not DARK.
+    If you cannot check the room, these functions will return #-1 even
+    if you have objects/exits in the room. This behavior is identical
+    to TinyMUSH 2.0's, and provides more privacy.
+  - 'examine' output for objects you don't control is now similar to
+    TinyMUSH 2.0's. The option to examine public attributes by default
+    is configurable.
+
diff --git a/COPYRITE b/COPYRITE
new file mode 100644 (file)
index 0000000..70cf5b3
--- /dev/null
+++ b/COPYRITE
@@ -0,0 +1,171 @@
+/* copyrite.h */
+
+/*
+ * Copyright, License, and Credits for PennMUSH 1.x. Revised October 2002.
+ *
+ * I. Copyrights
+ *
+ * PennMUSH 1.x
+ * Copyright (c) 1995-2002, Alan Schwartz, T. Alexander Popiel, Shawn Wagner
+ * Contact email for Alan Schwartz: <dunemush@pennmush.org>. 
+ * 
+ * Some code used in this server may have been derived from the
+ * TinyMUSH 2.2 source code, with permission. TinyMUSH 2.2 is
+ * Copyright (c) 1994-2002, Jean Marie Diaz, Lydia Leong, and Devin Hooker.
+ *
+ * Some code used in this server may have been directive from TinyMUSH 2.0.
+ * Copyright (c) 1995, Joseph Traub, Glenn Crocker.
+ *
+ * Some code used in this server may have been directive from TinyMUD.
+ * Copyright (c) 1995, David Applegate, James Aspnes, Timothy Freeman
+ * and Bennet Yee.
+ *
+ *------------------------------------------------------------------------*
+ *
+ * II. License
+ *
+ * Because PennMUSH includes parts of multiple works, you must comply
+ * with all of the relevant licenses of those works. The portions derived
+ * from TinyMUD/TinyMUSH 2.0 are licensed under the following terms:
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that: (1) source code distributions
+ *   retain the above copyright notice and this paragraph in its entirety, and
+ *   (2) distributions including binary code include the above copyright 
+ *   notice and this paragraph in its entirety in the documentation or other 
+ *   materials provided with the distribution.  The names of the copyright 
+ *   holders may not be used to endorse or promote products derived from 
+ *   this software without specific prior written permission.
+ * 
+ *   THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+ *   WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ *   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * The portions derived from TinyMUSH 2.2 are used under the Artistic
+ * License. The Artistic License is also the license under which you
+ * are granted permission to copy and modify PennMUSH:
+ *
+ * The Artistic License
+ * 
+ * Preamble
+ * 
+ * The intent of this document is to state the conditions under which a
+ * Package may be copied, such that the Copyright Holder maintains some
+ * semblance of artistic control over the development of the package,
+ * while giving the users of the package the right to use and distribute
+ * the Package in a more-or-less customary fashion, plus the right to make
+ * reasonable modifications.
+ * 
+ * Definitions:
+ * 
+ * "Package" refers to the collection of files distributed by the Copyright
+ * Holder, and derivatives of that collection of files created through
+ * textual modification.
+ * "Standard Version" refers to such a Package if it has not been modified,
+ * or has been modified in accordance with the wishes of the Copyright
+ * Holder.
+ * "Copyright Holder" is whoever is named in the copyright or copyrights
+ * for the package.
+ * "You" is you, if you're thinking about copying or distributing this Package.
+ * "Reasonable copying fee" is whatever you can justify on the basis of media
+ * cost, duplication charges, time of people involved, and so on. (You will
+ * not be required to justify it to the Copyright Holder, but only to the
+ * computing community at large as a market that must bear the fee.)
+ * "Freely Available" means that no fee is charged for the item itself,
+ * though there may be fees involved in handling the item. It also means
+ * that recipients of the item may redistribute it under the same conditions
+ * they received it.
+ * 
+ * 1. You may make and give away verbatim copies of the source form of the
+ * Standard Version of this Package without restriction, provided that
+ * you duplicate all of the original copyright notices and associated
+ * disclaimers.
+ * 
+ * 2. You may apply bug fixes, portability fixes and other modifications
+ * derived from the Public Domain or from the Copyright Holder. A Package
+ * modified in such a way shall still be considered the Standard Version.
+ * 
+ * 3. You may otherwise modify your copy of this Package in any way, provided
+ * that you insert a prominent notice in each changed file stating how and
+ * when you changed that file, and provided that you do at least ONE of
+ * the following:
+ * 
+ *  a) place your modifications in the Public Domain or otherwise make them
+ *  Freely Available, such as by posting said modifications to Usenet or an
+ *  equivalent medium, or placing the modifications on a major archive site
+ *  such as ftp.uu.net, or by allowing the Copyright Holder to include your
+ *  modifications in the Standard Version of the Package.
+ * 
+ *  b) use the modified Package only within your corporation or organization.
+ * 
+ *  c) rename any non-standard executables so the names do not conflict with
+ *  standard executables, which must also be provided, and provide a separate
+ *  manual page for each non-standard executable that clearly documents how
+ *  it differs from the Standard Version.
+ * 
+ *  d) make other distribution arrangements with the Copyright Holder.
+ * 
+ * 4. You may distribute the programs of this Package in object code or
+ * executable form, provided that you do at least ONE of the following:
+ * 
+ *  a) distribute a Standard Version of the executables and library files,
+ *  together with instructions (in the manual page or equivalent) on where
+ *  to get the Standard Version.
+ * 
+ *  b) accompany the distribution with the machine-readable source of the
+ *  Package with your modifications.
+ * 
+ *  c) accompany any non-standard executables with their corresponding
+ *  Standard Version executables, giving the non-standard executables
+ *  non-standard names, and clearly documenting the differences in manual
+ *  pages (or equivalent), together with instructions on where to get the
+ *  Standard Version.
+ * 
+ *  d) make other distribution arrangements with the Copyright Holder.
+ * 
+ * 5. You may charge a reasonable copying fee for any distribution of
+ * this Package. You may charge any fee you choose for support of this
+ * Package. You may not charge a fee for this Package itself. However, you
+ * may distribute this Package in aggregate with other (possibly commercial)
+ * programs as part of a larger (possibly commercial) software distribution
+ * provided that you do not advertise this Package as a product of your own.
+ * 
+ * 6. The scripts and library files supplied as input to or produced as
+ * output from the programs of this Package do not automatically fall under
+ * the copyright of this Package, but belong to whomever generated them,
+ * and may be sold commercially, and may be aggregated with this Package.
+ * 
+ * 7. C or perl subroutines supplied by you and linked into this Package
+ * shall not be considered part of this Package.
+ * 
+ * 8. The name of the Copyright Holder may not be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ * 
+ * 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * The End
+ * 
+ *---------------------------------------------------------------------*
+ *
+ * III. Credits
+ *
+ * Many people have helped develop PennMUSH. In addition to the people
+ * listed above, and the many people noted in the CHANGES file
+ * for suggestions and patches, special mention is due to:
+ *
+ * Past and present PennMUSH development team members:
+ *  T. Alexander Popiel, Ralph Melton, Thorvald Natvig, Luuk de Waard,
+ *  Shawn Wagner
+ * Past and present PennMUSH porters:
+ *  Nick Gammon, Sylvia, Dan Williams, Ervin Hearn III
+ * TinyMUSH 2.2, TinyMUSH 3.0, TinyMUX 2, and RhostMUSH developers
+ * All PennMUSH users who've sent in bug reports and patches
+ * The admin and players of DuneMUSH, Dune II, M*U*S*H, Rio:MdC, 
+ *  and other places Javelin has beta-tested new versions
+ * Lydia Leong (Amberyl), who maintained PennMUSH until 1995, and taught
+ *  Javelin how to be a Wizard and a God of a MUSH.
+ *
+ */
diff --git a/Configure b/Configure
new file mode 100644 (file)
index 0000000..a3caa25
--- /dev/null
+++ b/Configure
@@ -0,0 +1,7307 @@
+#! /bin/sh
+#
+# If these # comments don't work, trim them. Don't worry about any other
+# shell scripts, Configure will trim # comments from them for you.
+#
+# (If you are trying to port this package to a machine without sh,
+# I would suggest you have a look at the prototypical config_h.SH file
+# and edit it to reflect your system. Some packages may include samples
+# of config.h for certain machines, so you might look for one of those.)
+#
+# Yes, you may rip this off to use in other distribution packages. This
+# script belongs to the public domain and cannot be copyrighted.
+#
+# (Note: this Configure script was generated automatically. Rather than
+# working with this copy of Configure, you may wish to get metaconfig.
+# The dist-3.0 package (which contains metaconfig) was posted in
+# comp.sources.misc and is available on CPAN under authors/id/RAM so
+# you may fetch it yourself from your nearest archive site.)
+#
+
+# $Id: Configure,v 1.1.1.1 2004-06-06 20:32:51 ari Exp $
+#
+# Generated on Tue Sep 30 17:54:35 CDT 2003 [metaconfig 3.0 PL70]
+
+cat >/tmp/c1$$ <<EOF
+ARGGGHHHH!!!!!
+
+SCO csh still thinks true is false.  Write to SCO today and tell them that next
+year Configure ought to "rm /bin/csh" unless they fix their blasted shell. :-)
+
+(Actually, Configure ought to just patch csh in place.  Hmm.  Hmmmmm.  All
+we'd have to do is go in and swap the && and || tokens, wherever they are.)
+
+[End of diatribe. We now return you to your regularly scheduled programming...]
+EOF
+cat >/tmp/c2$$ <<EOF
+
+OOPS!  You naughty creature!  You didn't run Configure with sh!
+I will attempt to remedy the situation by running sh for you...
+EOF
+
+true || cat /tmp/c1$$ /tmp/c2$$
+true || exec sh $0 $argv:q
+
+(exit $?0) || cat /tmp/c2$$
+(exit $?0) || exec sh $0 $argv:q
+rm -f /tmp/c1$$ /tmp/c2$$
+
+: compute my invocation name
+me=$0
+case "$0" in
+*/*)
+       me=`echo $0 | sed -e 's!.*/\(.*\)!\1!' 2>/dev/null`
+       test "$me" || me=$0
+       ;;
+esac
+
+: Proper PATH separator
+p_=:
+: On OS/2 this directory should exist if this is not floppy only system :-]
+if test -d c:/.; then
+       : Check for cygwin32 emulation
+       case "x$OSTYPE$OS" in
+       x*msys*Windows*)
+               echo 'Running on Windows? Using the MinGW MSys tools...'
+               ;;
+       x*win32*)
+               echo 'Running on Windows?  Assuming cygwin32 emulation tools...'
+               ;;
+       x*windows*)
+               echo 'Running on Windows?  Assuming cygwin32 emulation tools...'
+               ;;
+       x*cygwin*)
+               echo 'Looks like cygwin32...'
+               ;;
+       x*)
+               p_=\;
+               PATH=`cmd /c "echo %PATH%" | tr '\\\\' / `
+               OS2_SHELL=`cmd /c "echo %OS2_SHELL%" | tr '\\\\' / | tr '[A-Z]' '[a-z]'`
+               ;;
+       esac
+fi
+
+: Proper PATH setting
+paths='/bin /usr/bin /usr/local/bin /usr/ucb /usr/local /usr/lbin'
+paths="$paths /opt/bin /opt/local/bin /opt/local /opt/lbin"
+paths="$paths /usr/5bin /etc /usr/gnu/bin /usr/new /usr/new/bin /usr/nbin"
+paths="$paths /opt/gnu/bin /opt/new /opt/new/bin /opt/nbin"
+paths="$paths /sys5.3/bin /sys5.3/usr/bin /bsd4.3/bin /bsd4.3/usr/ucb"
+paths="$paths /bsd4.3/usr/bin /usr/bsd /bsd43/bin /usr/ccs/bin"
+paths="$paths /etc /usr/lib /usr/ucblib /lib /usr/ccs/lib"
+paths="$paths /sbin /usr/sbin /usr/libexec /gnuwin32/b18/H-i386-cygwin32/bin"
+
+case "x$OSTYPE$OS" in
+       x*msys*Windows*)
+               PATH='/usr/local/bin:/mingw/bin:/bin:/perl/bin'
+               ;;
+       x*)
+               for p in $paths
+               do
+                       case "$p_$PATH$p_" in
+                               *$p_$p$p_*) ;;
+                               *) test -d $p && PATH=$PATH$p_$p ;;
+                       esac                    
+               done
+
+               PATH=.$p_$PATH
+               export PATH
+               ;;
+esac
+
+: shall we be using ksh?
+inksh=''
+needksh=''
+avoidksh=''
+newsh=/bin/ksh
+changesh=''
+if (PATH=.; alias -x) >/dev/null 2>&1; then
+               inksh=true
+fi
+if test -f /hp-ux -a -f /bin/ksh; then
+       needksh='to avoid sh bug in "here document" expansion'
+fi
+if test -d /usr/lpp -a -f /usr/bin/bsh -a -f /usr/bin/uname; then
+       if test X`/usr/bin/uname -v` = X4; then
+               avoidksh="to avoid AIX 4's /bin/sh"
+               newsh=/usr/bin/bsh
+       fi
+fi
+case "$inksh/$needksh" in
+/[a-z]*)
+               unset ENV
+               changesh=true
+               reason="$needksh"
+       ;;
+esac
+case "$inksh/$avoidksh" in
+true/[a-z]*)
+       changesh=true
+       reason="$avoidksh"
+       ;;
+esac
+case "$inksh/$needksh-$avoidksh-" in
+true/--)
+               cat <<EOM
+(I see you are using the Korn shell.  Some ksh's blow up on $me,
+mainly on older exotic systems.  If yours does, try the Bourne shell instead.)
+EOM
+       ;;
+esac
+case "$changesh" in
+true)
+       echo "(Feeding myself to $newsh $reason.)"
+       case "$0" in
+       Configure|*/Configure) exec $newsh $0 "$@";;
+       *) exec $newsh Configure "$@";;
+       esac
+       ;;
+esac
+
+: Configure runs within the UU subdirectory
+test -d UU || mkdir UU
+unset CDPATH
+cd UU && rm -f ./*
+
+d_bsd=''
+d_eunice=''
+d_xenix=''
+eunicefix=''
+Mcc=''
+ar=''
+awk=''
+bash=''
+bison=''
+byacc=''
+cat=''
+chgrp=''
+chmod=''
+chown=''
+comm=''
+compress=''
+cp=''
+cpio=''
+cpp=''
+csh=''
+date=''
+echo=''
+egrep=''
+emacs=''
+expr=''
+find=''
+flex=''
+gcc=''
+grep=''
+gzip=''
+inews=''
+ksh=''
+less=''
+line=''
+lint=''
+ln=''
+lp=''
+lpr=''
+ls=''
+mail=''
+mailx=''
+make=''
+mkdir=''
+more=''
+mv=''
+nroff=''
+perl=''
+pg=''
+pmake=''
+pr=''
+rm=''
+rmail=''
+sed=''
+sendmail=''
+shar=''
+sleep=''
+smail=''
+sort=''
+submit=''
+tail=''
+tar=''
+tbl=''
+test=''
+touch=''
+tr=''
+troff=''
+uname=''
+uniq=''
+uptime=''
+uuname=''
+vi=''
+zcat=''
+zip=''
+hint=''
+myuname=''
+osname=''
+osvers=''
+Author=''
+Date=''
+Header=''
+Id=''
+Locker=''
+Log=''
+RCSfile=''
+Revision=''
+Source=''
+State=''
+archobjs=''
+firstmakefile=''
+cc=''
+gccversion=''
+ccflags=''
+cppflags=''
+ldflags=''
+lkflags=''
+locincpth=''
+optimize=''
+warnings=''
+cdecl=''
+cf_email=''
+cf_name=''
+cf_by=''
+cf_time=''
+contains=''
+cpplast=''
+cppminus=''
+cpprun=''
+cppstdin=''
+d_gettblsz=''
+nofile=''
+tablesize=''
+d_access=''
+d_argsinfp=''
+d_assert=''
+d_attribut=''
+d_bcopy=''
+d_bindtextdomain=''
+d_bzero=''
+d_const=''
+cryptlib=''
+d_crypt=''
+i_crypt=''
+d_force_ipv4=''
+d_fpsetmask=''
+d_fpsetround=''
+i_floatingpoint=''
+d_gaistr=''
+d_getadinf=''
+d_getdate=''
+d_gethbynm2=''
+d_getnminf=''
+d_getpagsz=''
+pagesize=''
+d_getprior=''
+d_gettext=''
+d_gnulibc=''
+d_huge=''
+d_huge_val=''
+d_int_max=''
+d_maxdouble=''
+d_maxint=''
+d_ieee=''
+d_in2p=''
+d_internet=''
+d_ipv6=''
+d_itimer=''
+d_keepsig=''
+d_memcpy=''
+d_memmove=''
+d_memset=''
+d_newstyle=''
+d_open3=''
+d_portable=''
+d_lrand48=''
+d_rand=''
+d_random=''
+d_rename=''
+d_rlimit=''
+d_rusage=''
+d_select=''
+d_sendmail=''
+d_setlocale=''
+d_setpgid=''
+d_bsdsetpgrp=''
+d_setpgrp=''
+d_setprior=''
+d_sigaction=''
+d_sigchld=''
+d_sigcld=''
+d_signalproto=''
+d_sigprocmask=''
+d_snprintf=''
+d_oldsock=''
+d_socket=''
+d_sockpair=''
+sockethdr=''
+socketlib=''
+d_socklen=''
+d_strccmp=''
+d_index=''
+d_strchr=''
+d_strcoll=''
+d_strdup=''
+d_strxfrm=''
+d_sysconf=''
+d_sysctl=''
+d_sysctlbyname=''
+d_tcl=''
+i_tcl=''
+d_textdomain=''
+d_timelocal=''
+d_toupper=''
+d_uptime=''
+d_urandom=''
+d_uwait3=''
+d_uwait=''
+d_voidsig=''
+signal_t=''
+d_volatile=''
+d_vsnprintf=''
+d_waitpid=''
+h_fcntl=''
+h_sysfile=''
+i_arpainet=''
+i_arpanameser=''
+i_errno=''
+i_syserrno=''
+i_fcntl=''
+i_libintl=''
+i_limits=''
+i_locale=''
+i_malloc=''
+i_memory=''
+i_netdb=''
+i_niin=''
+i_sysin=''
+i_nitcp=''
+i_setjmp=''
+i_stddef=''
+i_stdlib=''
+i_string=''
+strings=''
+i_sysfile=''
+d_voidtty=''
+i_bsdioctl=''
+i_sysfilio=''
+i_sysioctl=''
+i_syssockio=''
+i_sysmman=''
+i_syspage=''
+i_sysparam=''
+i_sysresrc=''
+i_sysselct=''
+i_syssock=''
+i_sysstat=''
+i_systypes=''
+i_sysvlimit=''
+i_syswait=''
+i_sgtty=''
+i_termio=''
+i_termios=''
+i_systime=''
+i_systimek=''
+i_time=''
+timeincl=''
+i_unistd=''
+i_values=''
+i_stdarg=''
+i_varargs=''
+i_varhdr=''
+install=''
+installdir=''
+libc=''
+d_mysql=''
+libmysqlclient=''
+glibpth=''
+libpth=''
+loclibpth=''
+plibpth=''
+xlibpth=''
+libs=''
+d_openssl=''
+libcrypto=''
+libssl=''
+lns=''
+mailer=''
+make_set_make=''
+d_mymalloc=''
+freetype=''
+mallocobj=''
+mallocsrc=''
+malloctype=''
+usemymalloc=''
+mydomain=''
+myhostname=''
+phostname=''
+c=''
+n=''
+d_berknames=''
+d_passnames=''
+d_usgnames=''
+nametype=''
+groupcat=''
+hostcat=''
+passcat=''
+package=''
+spackage=''
+pidtype=''
+prototype=''
+sh=''
+sizetype=''
+so=''
+d_keepalive=''
+d_keepidle=''
+sharpbang=''
+shsharp=''
+spitshell=''
+src=''
+startsh=''
+sysman=''
+nm_opt=''
+nm_so_opt=''
+runnm=''
+usenm=''
+incpath=''
+mips=''
+mips_type=''
+usrinc=''
+defvoidused=''
+voidflags=''
+CONFIG=''
+
+define='define'
+undef='undef'
+smallmach='pdp11 i8086 z8000 i80286 iAPX286'
+rmlist=''
+
+: We must find out about Eunice early
+eunicefix=':'
+if test -f /etc/unixtovms; then
+       eunicefix=/etc/unixtovms
+fi
+if test -f /etc/unixtovms.exe; then
+       eunicefix=/etc/unixtovms.exe
+fi
+
+: list of known cpp symbols, sorted alphabetically
+al="AMIX BIT_MSF BSD BSD4_3 BSD_NET2 CMU CRAY DGUX DOLPHIN DPX2"
+al="$al GO32 GOULD_PN HP700 I386 I80960 I960 Lynx M68000 M68K MACH"
+al="$al MIPSEB MIPSEL MSDOS MTXINU MULTIMAX MVS"
+al="$al M_COFF M_I186 M_I286 M_I386 M_I8086 M_I86 M_I86SM"
+al="$al M_SYS3 M_SYS5 M_SYSIII M_SYSV M_UNIX M_XENIX"
+al="$al NeXT OCS88 OSF1 PARISC PC532 PORTAR POSIX"
+al="$al PWB R3000 RES RISC6000 RT Sun386i SVR3 SVR4"
+al="$al SYSTYPE_BSD SYSTYPE_SVR4 SYSTYPE_SYSV Tek4132 Tek4300"
+al="$al UMAXV USGr4 USGr4_2 UTEK UTS UTek UnicomPBB UnicomPBD Utek"
+al="$al VMS Xenix286"
+al="$al _AIX _AIX32 _AIX370 _AM29000 _COFF _CRAY _CX_UX _EPI"
+al="$al _IBMESA _IBMR2 _M88K _M88KBCS_TARGET"
+al="$al _MIPSEB _MIPSEL _M_COFF _M_I86 _M_I86SM _M_SYS3"
+al="$al _M_SYS5 _M_SYSIII _M_SYSV _M_UNIX _M_XENIX _NLS _PGC_ _R3000"
+al="$al _SYSTYPE_BSD _SYSTYPE_BSD43 _SYSTYPE_SVR4"
+al="$al _SYSTYPE_SYSV _SYSV3 _U370 _UNICOS"
+al="$al __386BSD__ __BIG_ENDIAN __BIG_ENDIAN__ __BSD_4_4__"
+al="$al __DGUX__ __DPX2__ __H3050R __H3050RX"
+al="$al __LITTLE_ENDIAN __LITTLE_ENDIAN__ __MACH__"
+al="$al __MIPSEB __MIPSEB__ __MIPSEL __MIPSEL__"
+al="$al __Next__ __OSF1__ __PARAGON__ __PGC__ __PWB __STDC__"
+al="$al __SVR4_2__ __UMAXV__"
+al="$al ____386BSD____ __alpha __alpha__ __amiga"
+al="$al __bsd4_2 __bsd4_2__ __bsdi__ __convex__"
+al="$al __host_mips__"
+al="$al __hp9000s200 __hp9000s300 __hp9000s400 __hp9000s500"
+al="$al __hp9000s500 __hp9000s700 __hp9000s800"
+al="$al __hppa __hpux __hp_osf __i286 __i286__ __i386 __i386__"
+al="$al __i486 __i486__ __i860 __i860__ __ibmesa __ksr1__ __linux__"
+al="$al __m68k __m68k__ __m88100__ __m88k __m88k__"
+al="$al __mc68000 __mc68000__ __mc68020 __mc68020__"
+al="$al __mc68030 __mc68030__ __mc68040 __mc68040__"
+al="$al __mc88100 __mc88100__ __mips __mips__"
+al="$al __motorola__ __osf__ __pa_risc __sparc__ __stdc__"
+al="$al __sun __sun__ __svr3__ __svr4__ __ultrix __ultrix__"
+al="$al __unix __unix__ __uxpm__ __uxps__ __vax __vax__"
+al="$al _host_mips _mips _unix"
+al="$al a29k aegis aix aixpc alliant alpha am29000 amiga ansi ardent"
+al="$al apollo ardent att386 att3b"
+al="$al bsd bsd43 bsd4_2 bsd4_3 bsd4_4 bsdi bull"
+al="$al cadmus clipper concurrent convex cray ctix"
+al="$al dmert encore gcos gcx gimpel gould"
+al="$al hbullx20 hcx host_mips hp200 hp300 hp700 hp800"
+al="$al hp9000 hp9000s300 hp9000s400 hp9000s500"
+al="$al hp9000s700 hp9000s800 hp9k8 hppa hpux"
+al="$al i186 i286 i386 i486 i8086"
+al="$al i80960 i860 iAPX286 ibm ibm032 ibmrt interdata is68k"
+al="$al ksr1 linux luna luna88k m68k m88100 m88k"
+al="$al mc300 mc500 mc68000 mc68010 mc68020 mc68030"
+al="$al mc68040 mc68060 mc68k mc68k32 mc700"
+al="$al mc88000 mc88100 merlin mert mips mvs n16"
+al="$al ncl_el ncl_mr"
+al="$al news1500 news1700 news1800 news1900 news3700"
+al="$al news700 news800 news900 ns16000 ns32000"
+al="$al ns32016 ns32332 ns32k nsc32000 os osf"
+al="$al parisc pc532 pdp11 plexus posix pyr"
+al="$al riscix riscos scs sequent sgi sinix sony sony_news"
+al="$al sonyrisc sparc sparclite spectrum stardent stratos"
+al="$al sun sun3 sun386 svr4 sysV68 sysV88"
+al="$al titan tower tower32 tower32_200 tower32_600 tower32_700"
+al="$al tower32_800 tower32_850 tss u370 u3b u3b2 u3b20 u3b200"
+al="$al u3b20d u3b5 ultrix unix unixpc unos vax venix vms"
+al="$al xenix z8000"
+
+: No trailing extension on UNIX executables
+_exe='' 
+: Extra object files, if any, needed on this platform.
+archobjs=''
+gccversion=''
+: change the next line if compiling for Xenix/286 on Xenix/386
+xlibpth='/usr/lib/386 /lib/386'
+
+: Possible local library directories to search.
+loclibpth="/usr/local/lib /opt/local/lib /usr/gnu/lib"
+loclibpth="$loclibpth /opt/gnu/lib /usr/GNU/lib /opt/GNU/lib"
+
+: general looking path for locating libraries
+glibpth="/shlib /usr/shlib /lib/pa1.1 /usr/lib/large"
+glibpth="$glibpth /lib /usr/lib $xlibpth"
+glibpth="$glibpth /lib/large /usr/lib/small /lib/small"
+glibpth="$glibpth /usr/ccs/lib /usr/ucblib /usr/local/lib"
+
+: Private path used by Configure to find libraries.  Its value
+: is prepended to libpth. This variable takes care of special
+: machines, like the mips.  Usually, it should be empty.
+plibpth=''
+
+large=''
+: full support for void wanted by default
+defvoidused=15
+
+: Possible local include directories to search.
+: Set locincpth to "" in a hint file to defeat local include searches.
+locincpth="/usr/local/include /opt/local/include /usr/gnu/include"
+locincpth="$locincpth /opt/gnu/include /usr/GNU/include /opt/GNU/include"
+locincpth="$locincpth /usr/kerberos/include"
+:
+: no include file wanted by default
+inclwanted=''
+
+: default library list
+libswanted=''
+defvoidused=15
+libswanted='nsl socket m c crypt bind resolv ld dl tcl intl'
+
+: Find the basic shell for Bourne shell scripts
+case "$sh" in
+'')
+       case "$SYSTYPE" in
+       *bsd*|sys5*) xxx="/$SYSTYPE/bin/sh";;
+       *) xxx='/bin/sh';;
+       esac
+       if test -f "$xxx"; then
+               sh="$xxx"
+       else
+               : Build up a list and do a single loop so we can 'break' out.
+               pth=`echo $PATH | sed -e "s/$p_/ /g"`
+               for xxx in sh bash ksh pdksh ash; do
+                       for p in $pth; do
+                               try="$try ${p}/${xxx}"
+                       done
+               done
+               for xxx in $try; do
+                       if test -f "$xxx"; then
+                               sh="$xxx";
+                               break
+                       elif test -f "$xxx.exe"; then
+                               sh="$xxx";
+                               break
+                       fi
+               done
+       fi
+       ;;
+esac
+
+case "$sh" in
+'')    cat <<EOM >&2
+$me:  Fatal Error:  I can't find a Bourne Shell anywhere.  
+
+Usually it's in /bin/sh.  How did you even get this far?
+Please contact me (Javelin) at dunemush@pennmush.org and 
+we'll try to straighten this all out.
+EOM
+       exit 1
+       ;;
+esac
+
+: see if sh knows # comments
+if `$sh -c '#' >/dev/null 2>&1`; then
+       shsharp=true
+       spitshell=cat
+       xcat=/bin/cat
+       test -f $xcat || xcat=/usr/bin/cat
+       echo "#!$xcat" >try
+       $eunicefix try
+       chmod +x try
+       ./try > today
+       if test -s today; then
+               sharpbang='#!'
+       else
+               echo "#! $xcat" > try
+               $eunicefix try
+               chmod +x try
+               ./try > today
+               if test -s today; then
+                       sharpbang='#! '
+               else
+                       sharpbang=': use '
+               fi
+       fi
+else
+       echo " "
+       echo "Your $sh doesn't grok # comments--I will strip them later on."
+       shsharp=false
+       cd ..
+       echo "exec grep -v '^[  ]*#'" >spitshell
+       chmod +x spitshell
+       $eunicefix spitshell
+       spitshell=`pwd`/spitshell
+       cd UU
+       echo "I presume that if # doesn't work, #! won't work either!"
+       sharpbang=': use '
+fi
+rm -f try today
+
+: figure out how to guarantee sh startup
+case "$startsh" in
+'') startsh=${sharpbang}${sh} ;;
+*)
+esac
+cat >try <<EOSS
+$startsh
+set abc
+test "$?abc" != 1
+EOSS
+
+chmod +x try
+$eunicefix try
+if ./try; then
+       : echo "Yup, it does."
+else
+       echo "Hmm... '$startsh' does not guarantee sh startup..."
+       echo "You may have to fix up the shell scripts to make sure $sh runs them."
+fi
+rm -f try
+
+: produce awk script to parse command line options
+cat >options.awk <<'EOF'
+BEGIN {
+       optstr = "dD:eEf:hKOrsSU:V";    # getopt-style specification
+
+       len = length(optstr);
+       for (i = 1; i <= len; i++) {
+               c = substr(optstr, i, 1);
+               if (i < len) a = substr(optstr, i + 1, 1); else a = "";
+               if (a == ":") {
+                       arg[c] = 1;
+                       i++;
+               }
+               opt[c] = 1;
+       }
+}
+{
+       expect = 0;
+       str = $0;
+       if (substr(str, 1, 1) != "-") {
+               printf("'%s'\n", str);
+               next;
+       }
+       len = length($0);
+       for (i = 2; i <= len; i++) {
+               c = substr(str, i, 1);
+               if (!opt[c]) {
+                       printf("-%s\n", substr(str, i));
+                       next;
+               }
+               printf("-%s\n", c);
+               if (arg[c]) {
+                       if (i < len)
+                               printf("'%s'\n", substr(str, i + 1));
+                       else
+                               expect = 1;
+                       next;
+               }
+       }
+}
+END {
+       if (expect)
+               print "?";
+}
+EOF
+
+: process the command line options
+set X `for arg in "$@"; do echo "X$arg"; done |
+       sed -e s/X// | awk -f options.awk`
+eval "set $*"
+shift
+rm -f options.awk
+
+: set up default values
+fastread=''
+reuseval=false
+config_sh=''
+alldone=''
+error=''
+silent=''
+extractsh=''
+override=''
+knowitall=''
+rm -f optdef.sh
+cat >optdef.sh <<EOS
+$startsh
+EOS
+
+
+: option parsing
+while test $# -gt 0; do
+       case "$1" in
+       -d) shift; fastread=yes;;
+       -e) shift; alldone=cont;;
+       -f)
+               shift
+               cd ..
+               if test -r "$1"; then
+                       config_sh="$1"
+               else
+                       echo "$me: cannot read config file $1." >&2
+                       error=true
+               fi
+               cd UU
+               shift;;
+       -h) shift; error=true;;
+       -r) shift; reuseval=true;;
+       -s) shift; silent=true; realsilent=true;;
+       -E) shift; alldone=exit;;
+       -K) shift; knowitall=true;;
+       -O) shift; override=true;;
+       -S) shift; silent=true; extractsh=true;;
+       -D)
+               shift
+               case "$1" in
+               *=)
+                       echo "$me: use '-U symbol=', not '-D symbol='." >&2
+                       echo "$me: ignoring -D $1" >&2
+                       ;;
+               *=*) echo "$1" | \
+                               sed -e "s/'/'\"'\"'/g" -e "s/=\(.*\)/='\1'/" >> optdef.sh;;
+               *) echo "$1='define'" >> optdef.sh;;
+               esac
+               shift
+               ;;
+       -U)
+               shift
+               case "$1" in
+               *=) echo "$1" >> optdef.sh;;
+               *=*)
+                       echo "$me: use '-D symbol=val', not '-U symbol=val'." >&2
+                       echo "$me: ignoring -U $1" >&2
+                       ;;
+               *) echo "$1='undef'" >> optdef.sh;;
+               esac
+               shift
+               ;;
+       -V) echo "$me generated by metaconfig 3.0 PL70." >&2
+               exit 0;;
+       --) break;;
+       -*) echo "$me: unknown option $1" >&2; shift; error=true;;
+       *) break;;
+       esac
+done
+
+case "$error" in
+true)
+       cat >&2 <<EOM
+Usage: $me [-dehrsEKOSV] [-f config.sh] [-D symbol] [-D symbol=value]
+                 [-U symbol] [-U symbol=]
+  -d : use defaults for all answers.
+  -e : go on without questioning past the production of config.sh.
+  -f : specify an alternate default configuration file.
+  -h : print this help message and exit (with an error status).
+  -r : reuse C symbols value if possible (skips costly nm extraction).
+  -s : silent mode, only echoes questions and essential information.
+  -D : define symbol to have some value:
+         -D symbol         symbol gets the value 'define'
+         -D symbol=value   symbol gets the value 'value'
+  -E : stop at the end of questions, after having produced config.sh.
+  -K : do not use unless you know what you are doing.
+  -O : let -D and -U override definitions from loaded configuration file.
+  -S : perform variable substitutions on all .SH files (can mix with -f)
+  -U : undefine symbol:
+         -U symbol    symbol gets the value 'undef'
+         -U symbol=   symbol gets completely empty
+  -V : print version number and exit (with a zero status).
+EOM
+       exit 1
+       ;;
+esac
+
+: Sanity checks
+case "$fastread$alldone" in
+yescont|yesexit) ;;
+*)
+       if test ! -t 0; then
+               echo "Say 'sh Configure', not 'sh <Configure'"
+               exit 1
+       fi
+       ;;
+esac
+
+exec 4>&1
+case "$silent" in
+true) exec 1>/dev/null;;
+esac
+
+: run the defines and the undefines, if any, but leave the file out there...
+touch optdef.sh
+. ./optdef.sh
+
+: set package name
+package=pennmush
+
+: Some greps do not return status, grrr.
+echo "grimblepritz" >grimble
+if grep blurfldyick grimble >/dev/null 2>&1 ; then
+       contains=contains
+elif grep grimblepritz grimble >/dev/null 2>&1 ; then
+       contains=grep
+else
+       contains=contains
+fi
+rm -f grimble
+: the following should work in any shell
+case "$contains" in
+contains*)
+       echo " "
+       echo "AGH!  Grep doesn't return a status.  Attempting remedial action."
+       cat >contains <<'EOSS'
+grep "$1" "$2" >.greptmp && cat .greptmp && test -s .greptmp
+EOSS
+chmod +x contains
+esac
+
+: first determine how to suppress newline on echo command
+echo " "
+echo "Checking echo to see how to suppress newlines..."
+(echo "hi there\c" ; echo " ") >.echotmp
+if $contains c .echotmp >/dev/null 2>&1 ; then
+       echo "...using -n."
+       n='-n'
+       c=''
+else
+       cat <<'EOM'
+...using \c
+EOM
+       n=''
+       c='\c'
+fi
+echo $n "The star should be here-->$c"
+echo '*'
+rm -f .echotmp
+
+: compute the number of columns on the terminal for proper question formatting
+case "$COLUMNS" in
+'') COLUMNS='80';;
+esac
+
+: set up the echo used in my read
+myecho="case \"\$xxxm\" in
+'') echo $n \"\$rp $c\" >&4;;
+*) case \"\$rp\" in
+       '') echo $n \"[\$xxxm] $c\";;
+       *)
+               if test \`echo \"\$rp [\$xxxm]  \" | wc -c\` -ge $COLUMNS; then
+                       echo \"\$rp\" >&4
+                       echo $n \"[\$xxxm] $c\" >&4
+               else
+                       echo $n \"\$rp [\$xxxm] $c\" >&4
+               fi
+               ;;
+       esac;;
+esac"
+
+: now set up to do reads with possible shell escape and default assignment
+cat <<EOSC >myread
+$startsh
+xxxm=\$dflt
+$myecho
+ans='!'
+case "\$fastread" in
+yes) case "\$dflt" in
+       '') ;;
+       *) ans='';
+               case "\$silent-\$rp" in
+               true-) ;;
+               *) echo " " >&4;;
+               esac;;
+       esac;;
+*) case "\$silent" in
+       true) case "\$rp" in
+               '') ans='';;
+               esac;;
+       esac;;
+esac
+while expr "X\$ans" : "X!" >/dev/null; do
+       read answ
+       set x \$xxxm
+       shift
+       aok=''; eval "ans=\\"\$answ\\"" && aok=y
+       case  "\$answ" in
+       "!")
+               sh 1>&4
+               echo " "
+               $myecho
+               ;;
+       !*)
+               set x \`expr "X\$ans" : "X!\(.*\)\$"\`
+               shift
+               sh 1>&4 -c "\$*"
+               echo " "
+               $myecho
+               ;;
+       "\$ans")
+               case "\$ans" in
+               \\&*)
+                       set x \`expr "X\$ans" : "X&\(.*\)\$"\`
+                       shift
+                       case "\$1" in
+                       -d)
+                               fastread=yes
+                               echo "(OK, I'll run with -d after this question.)" >&4
+                               ;;
+                       -*)
+                               echo "*** Sorry, \$1 not supported yet." >&4
+                               ;;
+                       esac
+                       $myecho
+                       ans=!
+                       ;;
+               esac;;
+       *)
+               case "\$aok" in
+               y)
+                       echo "*** Substitution done -- please confirm."
+                       xxxm="\$ans"
+                       ans=\`echo $n "\$ans$c" | tr '\012' ' '\`
+                       xxxm="\$ans"
+                       ans=!
+                       ;;
+               *)
+                       echo "*** Error -- try again."
+                       ans=!
+                       ;;
+               esac
+               $myecho
+               ;;
+       esac
+       case "\$ans\$xxxm\$nostick" in
+       '')
+               ans=!
+               $myecho
+               ;;
+       esac
+done
+case "\$ans" in
+'') ans="\$xxxm";;
+esac
+EOSC
+
+: Find the path to the source tree
+case "$src" in
+'') src=`echo $0 | sed -e 's%/[^/][^/]*$%%'`;;
+esac
+case "$src" in
+'')
+       src=.
+       rsrc=..
+       ;;
+/*) rsrc="$src/..";;
+*) rsrc="../$src";;
+esac
+if test -f $rsrc/Configure && \
+       $contains "^package=$package" $rsrc/Configure >/dev/null 2>&1
+then
+   : found it, so we are ok.
+else
+       rsrc=''
+       for src in . .. ../.. ../../.. ../../../..; do
+               if test -f ../$src/Configure && \
+                       $contains "^package=$package" ../$src/Configure >/dev/null 2>&1
+               then
+                       rsrc=../$src
+                       break
+               fi
+       done
+fi
+case "$rsrc" in
+'')
+       echo " "
+       dflt=
+       rp="Directory where sources for $package are located?"
+       . ./myread
+       src="$ans"
+       rsrc="$src"
+       if test -f $rsrc/Configure && \
+               $contains "^package=$package" $rsrc/Configure >/dev/null 2>&1
+       then
+               echo "Ok, I've found them under $src"
+       else
+               echo "Sorry, I can't seem to be able to locate $package sources." >&4
+               exit 1
+       fi
+       ;;
+../.) ;;
+*)
+       echo " "
+       echo "Sources for $package found in $src" >&4
+       ;;
+esac
+
+: script used to extract .SH files with variable substitutions
+cat >extract <<'EOS'
+CONFIG=true
+echo "Doing variable substitutions on .SH files..."
+if test -f $src/MANIFEST; then
+       set x `awk '{print $1}' <$src/MANIFEST | grep '\.SH'`
+else
+       echo "(Looking for .SH files under the source directory.)"
+       set x `(cd $src; find . -name "*.SH" -print)`
+fi
+shift
+case $# in
+0) set x `(cd $src; echo *.SH)`; shift;;
+esac
+if test ! -f $src/$1; then
+       shift
+fi
+mkdir_p='
+name=$1;
+create="";
+while test $name; do
+       if test ! -d "$name"; then
+               create="$name $create";
+               name=`echo $name | sed -e "s|^[^/]*$||"`;
+               name=`echo $name | sed -e "s|\(.*\)/.*|\1|"`;
+       else
+               name="";
+       fi;
+done;
+for file in $create; do
+       mkdir $file;
+done
+'
+for file in $*; do
+       case "$src" in
+       ".")
+               case "$file" in
+               */*)
+                       dir=`expr X$file : 'X\(.*\)/'`
+                       file=`expr X$file : 'X.*/\(.*\)'`
+                       (cd $dir && . ./$file)
+                       ;;
+               *)
+                       . ./$file
+                       ;;
+               esac
+               ;;
+       *)
+               case "$file" in
+               */*)
+                       dir=`expr X$file : 'X\(.*\)/'`
+                       file=`expr X$file : 'X.*/\(.*\)'`
+                       (set x $dir; shift; eval $mkdir_p)
+                       sh <$src/$dir/$file
+                       ;;
+               *)
+                       sh <$src/$file
+                       ;;
+               esac
+               ;;
+       esac
+done
+if test -f $src/config_h.SH; then
+       if test ! -f config.h; then
+       : oops, they left it out of MANIFEST, probably, so do it anyway.
+       . $src/config_h.SH
+       fi
+fi
+EOS
+
+: extract files and exit if asked to do so
+case "$extractsh" in
+true)
+       case "$realsilent" in
+       true) ;;
+       *) exec 1>&4;;
+       esac
+       case "$config_sh" in
+       '') config_sh='config.sh'; config="$rsrc/config.sh";;
+       /*) config="$config_sh";;
+       *) config="$rsrc/$config_sh";;
+       esac
+       echo " "
+       echo "Fetching answers from $config_sh..."
+       . $config
+       test "$override" && . ./optdef.sh
+       echo " "
+       cd ..
+       . UU/extract
+       rm -rf UU
+       echo "Done."
+       exit 0
+       ;;
+esac
+
+: Eunice requires " " instead of "", can you believe it
+echo " "
+: Here we go...
+echo "Beginning of configuration questions for $package."
+
+trap 'echo " "; test -d ../UU && rm -rf X $rmlist; exit 1' 1 2 3 15
+
+: Now test for existence of everything in MANIFEST
+echo " "
+if test -f $rsrc/MANIFEST; then
+       echo "First let's make sure your kit is complete.  Checking..." >&4
+       awk '$1 !~ /PACK[A-Z]+/ {print $1}' $rsrc/MANIFEST | split -50
+       rm -f missing
+       tmppwd=`pwd`
+       for filelist in x??; do
+               (cd $rsrc; ls `cat $tmppwd/$filelist` >/dev/null 2>>$tmppwd/missing)
+       done
+       if test -s missing; then
+               cat missing >&4
+               cat >&4 <<'EOM'
+
+THIS PACKAGE SEEMS TO BE INCOMPLETE.
+
+You have the option of continuing the configuration process, despite the
+distinct possibility that your kit is damaged, by typing 'y'es.  If you
+do, don't blame me if something goes wrong.  I advise you to type 'n'o
+and contact the author (dunemush@pennmush.org).
+
+EOM
+               echo $n "Continue? [n] $c" >&4
+               read ans
+               case "$ans" in
+               y*)
+                       echo "Continuing..." >&4
+                       rm -f missing
+                       ;;
+               *)
+                       echo "ABORTING..." >&4
+                       kill $$
+                       ;;
+               esac
+       else
+               echo "Looks good..."
+       fi
+else
+       echo "There is no MANIFEST file.  I hope your kit is complete !"
+fi
+rm -f missing x??
+
+: create .config dir to save info across Configure sessions
+test -d ../.config || mkdir ../.config
+cat >../.config/README <<EOF
+This directory created by Configure to save information that should
+persist across sessions for $package.
+
+You may safely delete it if you wish.
+EOF
+
+: general instructions
+needman=true
+firsttime=true
+user=`(logname) 2>/dev/null`
+case "$user" in
+'') user=`whoami 2>&1`;;
+esac
+if $contains "^$user\$" ../.config/instruct >/dev/null 2>&1; then
+       firsttime=false
+       echo " "
+       rp='Would you like to see the instructions?'
+       dflt=n
+       . ./myread
+       case "$ans" in
+       [yY]*) ;;
+       *) needman=false;;
+       esac
+fi
+if $needman; then
+       cat <<EOH
+This installation shell script will examine your system and ask you questions
+to determine how the pennmush package should be installed. If you get
+stuck on a question, you may use a ! shell escape to start a subshell or
+execute a command.  Many of the questions will have default answers in square
+brackets; typing carriage return will give you the default.
+
+On some of the questions which ask for file or directory names you are allowed
+to use the ~name construct to specify the login directory belonging to "name",
+even if you don't have a shell which knows about that.  Questions where this is
+allowed will be marked "(~name ok)".
+
+EOH
+       rp=''
+       dflt='Type carriage return to continue'
+       . ./myread
+       cat <<'EOH'
+
+The prompter used in this script allows you to use shell variables and
+backticks in your answers.  You may use $1, $2, etc...  to refer to the words
+in the default answer, as if the default line was a set of arguments given to a
+script shell.  This means you may also use $* to repeat the whole default line,
+so you do not have to re-type everything to add something to the default.
+
+Everytime there is a substitution, you will have to confirm.  If there is an
+error (e.g. an unmatched backtick), the default answer will remain unchanged
+and you will be prompted again.
+
+If you are in a hurry, you may run 'Configure -d'.  This will bypass nearly all
+the questions and use the computed defaults (or the previous answers if there
+was already a config.sh file). Type 'Configure -h' for a list of options.
+You may also start interactively and then answer '& -d' at any prompt to turn
+on the non-interactive behaviour for the remaining of the execution.
+
+EOH
+       . ./myread
+       cat <<EOH
+
+Much effort has been expended to ensure that this shell script will run on any
+Unix system.  If despite that it blows up on yours, your best bet is to edit
+Configure and run it again.  If you can't run Configure for some reason,
+you'll have to generate a config.sh file by hand.  Whatever problems you
+have, let me (dunemush@pennmush.org) know how I blew it.
+
+This installation script affects things in two ways:
+
+1) it may do direct variable substitutions on some of the files included
+   in this kit.
+2) it builds a config.h file for inclusion in C programs.  You may edit
+   any of these files as the need arises after running this script.
+
+If you make a mistake on a question, there is no easy way to back up to it
+currently.  The easiest thing to do is to edit config.sh and rerun all the SH
+files.  Configure will offer to let you do this before it runs the SH files.
+
+EOH
+       dflt='Type carriage return to continue'
+       . ./myread
+       case "$firsttime" in
+       true) echo $user >>../.config/instruct;;
+       esac
+fi
+
+: find out where common programs are
+echo " "
+echo "Locating common programs..." >&4
+cat <<EOSC >loc
+$startsh
+case \$# in
+0) exit 1;;
+esac
+thing=\$1
+shift
+dflt=\$1
+shift
+for dir in \$*; do
+       case "\$thing" in
+       .)
+       if test -d \$dir/\$thing; then
+               echo \$dir
+               exit 0
+       fi
+       ;;
+       *)
+       for thisthing in \$dir/\$thing; do
+               : just loop through to pick last item
+       done
+       if test -f \$thisthing; then
+               echo \$thisthing
+               exit 0
+       elif test -f \$dir/\$thing.exe; then
+               : on Eunice apparently
+               echo \$dir/\$thing
+               exit 0
+       fi
+       ;;
+       esac
+done
+echo \$dflt
+exit 1
+EOSC
+chmod +x loc
+$eunicefix loc
+loclist="
+awk
+cat
+chmod
+comm
+cp
+echo
+expr
+grep
+make
+mkdir
+rm
+sed
+sort
+touch
+tr
+uniq
+"
+trylist="
+Mcc
+cpp
+date
+ln
+mail
+perl
+rmail
+sendmail
+smail
+test
+uname
+uptime
+"
+pth=`echo $PATH | sed -e "s/$p_/ /g"`
+pth="$pth /lib /usr/lib"
+for file in $loclist; do
+       eval xxx=\$$file
+       case "$xxx" in
+       /*|?:[\\/]*)
+               if test -f "$xxx"; then
+                       : ok
+               else
+                       echo "WARNING: no $xxx -- ignoring your setting for $file." >&4
+                       xxx=`./loc $file $file $pth`
+               fi
+               ;;
+       '') xxx=`./loc $file $file $pth`;;
+       *) xxx=`./loc $xxx $xxx $pth`;;
+       esac
+       eval $file=$xxx
+       eval _$file=$xxx
+       case "$xxx" in
+       /*)
+               echo $file is in $xxx.
+               ;;
+       ?:[\\/]*)
+               echo $file is in $xxx.
+               ;;
+       *)
+               echo "I don't know where '$file' is, and my life depends on it." >&4
+               echo "Go find a public domain implementation or fix your PATH setting!" >&4
+               exit 1
+               ;;
+       esac
+done
+echo " "
+echo "Don't worry if any of the following aren't found..."
+say=offhand
+for file in $trylist; do
+       eval xxx=\$$file
+       case "$xxx" in
+       /*|?:[\\/]*)
+               if test -f "$xxx"; then
+                       : ok
+               else
+                       echo "WARNING: no $xxx -- ignoring your setting for $file." >&4
+                       xxx=`./loc $file $file $pth`
+               fi
+               ;;
+       '') xxx=`./loc $file $file $pth`;;
+       *) xxx=`./loc $xxx $xxx $pth`;;
+       esac
+       eval $file=$xxx
+       eval _$file=$xxx
+       case "$xxx" in
+       /*)
+               echo $file is in $xxx.
+               ;;
+       ?:[\\/]*)
+               echo $file is in $xxx.
+               ;;
+       *)
+               echo "I don't see $file out there, $say."
+               say=either
+               ;;
+       esac
+done
+case "$egrep" in
+egrep)
+       echo "Substituting grep for egrep."
+       egrep=$grep
+       ;;
+esac
+case "$ln" in
+ln)
+       echo "Substituting cp for ln."
+       ln=$cp
+       ;;
+esac
+case "$test" in
+test)
+       echo "Hopefully test is built into your sh."
+       ;;
+*)
+       if `sh -c "PATH= test true" >/dev/null 2>&1`; then
+               echo "Using the test built into your sh."
+               test=test
+               _test=test
+       fi
+       ;;
+esac
+case "$echo" in
+echo)
+       echo "Hopefully echo is built into your sh."
+       ;;
+'') ;;
+*)
+       echo " "
+echo "Checking compatibility between $echo and builtin echo (if any)..." >&4
+       $echo $n "hi there$c" >foo1
+       echo $n "hi there$c" >foo2
+       if cmp foo1 foo2 >/dev/null 2>&1; then
+               echo "They are compatible.  In fact, they may be identical."
+       else
+               case "$n" in
+               '-n') n='' c='\c';;
+               *) n='-n' c='';;
+               esac
+               cat <<FOO
+They are not compatible!  You are probably running ksh on a non-USG system.
+I'll have to use $echo instead of the builtin, since Bourne shell doesn't
+have echo built in and we may have to run some Bourne shell scripts.  That
+means I'll have to use '$n$c' to suppress newlines now.  Life is ridiculous.
+
+FOO
+               $echo $n "The star should be here-->$c"
+               $echo "*"
+       fi
+       $rm -f foo1 foo2
+       ;;
+esac
+
+: determine whether symbolic links are supported
+echo " "
+$touch blurfl
+if $ln -s blurfl sym > /dev/null 2>&1 ; then
+       echo "Symbolic links are supported." >&4
+       lns="$ln -s"
+else
+       echo "Symbolic links are NOT supported." >&4
+       lns="$ln"
+fi
+$rm -f blurfl sym
+
+: see whether [:lower:] and [:upper:] are supported character classes
+echo " "
+up='[A-Z]'
+low='[a-z]'
+case "`echo AbyZ | $tr '[:lower:]' '[:upper:]' 2>/dev/null`" in
+ABYZ)
+       echo "Good, your tr supports [:lower:] and [:upper:] to convert case." >&4
+       up='[:upper:]'
+       low='[:lower:]'
+       ;;
+*)
+       echo "Your tr only supports [a-z] and [A-Z] to convert case." >&4
+       ;;
+esac
+: set up the translation script tr, must be called with ./tr of course
+cat >tr <<EOSC
+$startsh
+case "\$1\$2" in
+'[A-Z][a-z]') exec $tr '$up' '$low';;
+'[a-z][A-Z]') exec $tr '$low' '$up';;
+esac
+exec $tr "\$@"
+EOSC
+chmod +x tr
+$eunicefix tr
+
+: Try to determine whether config.sh was made on this system
+case "$config_sh" in
+'')
+myuname=`( ($uname -a) 2>/dev/null || hostname) 2>&1`
+myuname=`echo $myuname | $sed -e 's/^[^=]*=//' -e 's/\///g' | \
+       ./tr '[A-Z]' '[a-z]' | tr '\012' ' '`
+newmyuname="$myuname"
+dflt=n
+case "$knowitall" in
+'')
+       if test -f ../config.sh; then
+               if $contains myuname= ../config.sh >/dev/null 2>&1; then
+                       eval "`grep myuname= ../config.sh`"
+               fi
+               if test "X$myuname" = "X$newmyuname"; then
+                       dflt=y
+               fi
+       fi
+       ;;
+*) dflt=y;;
+esac
+
+: Get old answers from old config file if Configure was run on the
+: same system, otherwise use the hints.
+hint=default
+cd ..
+if test -f config.sh; then
+       echo " "
+       rp="I see a config.sh file.  Shall I use it to set the defaults?"
+       . UU/myread
+       case "$ans" in
+       n*|N*) echo "OK, I'll ignore it."; mv config.sh config.sh.old;;
+       *)  echo "Fetching default answers from your old config.sh file..." >&4
+               tmp_n="$n"
+               tmp_c="$c"
+               tmp_sh="$sh"
+               . ./config.sh
+               cp config.sh UU
+               n="$tmp_n"
+               c="$tmp_c"
+               : Older versions did not always set $sh.  Catch re-use of such
+               : an old config.sh.
+               case "$sh" in
+               '') sh="$tmp_sh" ;;
+               esac
+               hint=previous
+               ;;
+       esac
+fi
+if test ! -f config.sh; then
+       $cat <<EOM
+
+First time through, eh?  I have some defaults handy for the following systems:
+
+EOM
+       (cd $src/hints; ls -C *.sh) | $sed 's/\.sh/   /g' >&4
+       dflt=''
+       : Half the following guesses are probably wrong... If you have better
+       : tests or hints, please send them to dunemush@pennmush.org
+       : The metaconfig authors would also appreciate a copy...
+       $test -f /irix && osname=irix
+       $test -f /xenix && osname=sco_xenix
+       $test -f /dynix && osname=dynix
+       $test -f /dnix && osname=dnix
+       $test -f /lynx.os && osname=lynxos
+       $test -f /unicos && osname=unicos && osvers=`$uname -r`
+       $test -f /unicosmk.ar && osname=unicosmk && osvers=`$uname -r`
+       $test -f /bin/mips && /bin/mips && osname=mips
+       $test -d /NextApps && set X `hostinfo | grep 'NeXT Mach.*:' | \
+               $sed -e 's/://' -e 's/\./_/'` && osname=next && osvers=$4
+       $test -d /usr/apollo/bin && osname=apollo
+       $test -f /etc/saf/_sactab && osname=svr4
+       $test -d /usr/include/minix && osname=minix
+       if $test -d /MachTen; then
+               osname=machten
+               if $test -x /sbin/version; then
+                       osvers=`/sbin/version | $awk '{print $2}' |
+                       $sed -e 's/[A-Za-z]$//'`
+               elif $test -x /usr/etc/version; then
+                       osvers=`/usr/etc/version | $awk '{print $2}' |
+                       $sed -e 's/[A-Za-z]$//'`
+               else
+                       osvers="$2.$3"
+               fi
+       fi
+       if $test -f $uname; then
+               set X $myuname
+               shift
+
+               case "$5" in
+               fps*) osname=fps ;;
+               mips*)
+                       case "$4" in
+                       umips) osname=umips ;;
+                       *) osname=mips ;;
+                       esac;;
+               [23]100) osname=mips ;;
+               next*) osname=next ;;
+               news*) osname=news ;;
+               i386*)
+                       if $test -f /etc/kconfig; then
+                               osname=isc
+                               if test "$lns" = "ln -s"; then
+                                       osvers=4
+                               elif $contains _SYSV3 /usr/include/stdio.h > /dev/null 2>&1 ; then
+                                       osvers=3
+                               elif $contains _POSIX_SOURCE /usr/include/stdio.h > /dev/null 2>&1 ; then
+                                       osvers=2
+                               fi
+                       fi
+                       ;;
+               esac
+
+               case "$1" in
+               aix) osname=aix
+                       tmp=`( (oslevel) 2>/dev/null || echo "not found") 2>&1`
+                       case "$tmp" in
+                       'not found') osvers="$4"."$3" ;;
+                       '<3240'|'<>3240') osvers=3.2.0 ;;
+                       '=3240'|'>3240'|'<3250'|'<>3250') osvers=3.2.4 ;;
+                       '=3250'|'>3250') osvers=3.2.5 ;;
+                       *) osvers=$tmp;;
+                       esac
+                       ;;
+               *dc.osx) osname=dcosx
+                       osvers="$3"
+                       ;;
+               dnix) osname=dnix
+                       osvers="$3"
+                       ;;
+               domainos) osname=apollo
+                       osvers="$3"
+                       ;;
+               dgux) osname=dgux 
+                       osvers="$3"
+                       ;;
+               dynixptx*) osname=dynixptx
+                       osvers="$3"
+                       ;;
+               freebsd) osname=freebsd 
+                       osvers="$3" ;;
+               genix) osname=genix ;;
+               hp*) osname=hpux 
+                       case "$3" in
+                       *.08.*) osvers=9 ;;
+                       *.09.*) osvers=9 ;;
+                       *.10.*) osvers=10 ;;
+                       *)      osvers="$3" ;;
+                       esac
+                       ;;
+               irix*) osname=irix
+                       case "$3" in
+                       4*) osvers=4 ;;
+                       5*) osvers=5 ;;
+                       6*) osvers=6 ;;
+                       *)      osvers="$3" ;;
+                       esac
+                       ;;
+               linux) osname=linux
+                       case "$3" in
+                       1*) osvers=1 ;;
+                       *)      osvers="$3" ;;
+                       esac
+                       ;;
+               netbsd*) osname=netbsd 
+                       osvers="$3"
+                       ;;
+               bsd386) osname=bsd386
+                       osvers=`$uname -r`
+                       ;;
+               next*) osname=next ;;
+               solaris) osname=solaris
+                       case "$3" in
+                       5*) osvers=`echo $3 | $sed 's/^5/2/g'` ;;
+                       *)      osvers="$3" ;;
+                       esac
+                       ;;
+               sunos) osname=sunos
+                       case "$3" in
+                       5*) osname=solaris
+                               osvers=`echo $3 | $sed 's/^5/2/g'` ;;
+                       *)      osvers="$3" ;;
+                       esac
+                       ;;
+               titanos) osname=titanos
+                       case "$3" in
+                       1*) osvers=1 ;;
+                       2*) osvers=2 ;;
+                       3*) osvers=3 ;;
+                       4*) osvers=4 ;;
+                       *)      osvers="$3" ;;
+                       esac
+                       ;;
+               ultrix) osname=ultrix
+                       osvers="$3"
+                       ;;
+               osf1|mls+)      case "$5" in
+                               alpha)
+                                       osname=dec_osf
+                                       osvers=`echo "$3" | sed 's/^[vt]//'`
+                                       ;;
+                       hp*)    osname=hp_osf1  ;;
+                       mips)   osname=mips_osf1 ;;
+                       esac
+                       ;;
+               uts) osname=uts 
+                       osvers="$3"
+                       ;;
+               qnx) osname=qnx
+                       osvers="$4"
+                       ;;
+               $2) case "$osname" in
+                       *isc*) ;;
+                       *freebsd*) ;;
+                       svr*)
+                               : svr4.x or possibly later
+                               case "svr$3" in 
+                               ${osname}*)
+                                       osname=svr$3
+                                       osvers=$4
+                                       ;;
+                               esac
+                               case "$osname" in
+                               svr4.0)
+                                       : Check for ESIX
+                                       if test -f /stand/boot ; then
+                                               eval `grep '^INITPROG=[a-z/0-9]*$' /stand/boot`
+                                               if test -n "$INITPROG" -a -f "$INITPROG"; then
+                       isesix=`strings -a $INITPROG|grep 'ESIX SYSTEM V/386 Release 4.0'`
+                                                       if test -n "$isesix"; then
+                                                               osname=esix4
+                                                       fi
+                                               fi
+                                       fi
+                                       ;;
+                               esac
+                               ;;
+                       *)      if test -f /etc/systemid; then
+                                       osname=sco
+                                       set `echo $3 | $sed 's/\./ /g'` $4
+                                       if $test -f sco_$1_$2_$3.sh; then
+                                               osvers=$1.$2.$3
+                                       elif $test -f sco_$1_$2.sh; then
+                                               osvers=$1.$2
+                                       elif $test -f sco_$1.sh; then
+                                               osvers=$1
+                                       fi
+                               else
+                                       case "$osname" in
+                                       '') : Still unknown.  Probably a generic Sys V.
+                                               osname="sysv"
+                                               osvers="$3"
+                                               ;;
+                                       esac
+                               fi
+                               ;;
+                       esac
+                       ;;
+               *)      case "$osname" in
+                       '') : Still unknown.  Probably a generic BSD.
+                               osname="$1"
+                               osvers="$3"
+                               ;;
+                       esac
+                       ;;
+               esac
+       else
+               if test -f /vmunix -a -f $src/hints/news_os.sh; then
+                       (what /vmunix | UU/tr '[A-Z]' '[a-z]') > UU/kernel.what 2>&1
+                       if $contains news-os UU/kernel.what >/dev/null 2>&1; then
+                               osname=news_os
+                       fi
+                       $rm -f UU/kernel.what
+               elif test -d c:/.; then
+                       : Check for cygwin32 emulation
+                       case "x$OS" in
+                       xWindows_*)
+                               set X $myuname
+                               osname=win32
+                               osvers="$3"
+                               ;;
+                       x*)
+                               set X $myuname
+                               osname=os2
+                               osvers="$5"
+                               ;;
+                       esac
+               fi
+       fi
+       case "x$osname" in
+       xcygwin32*)
+               osname=win32
+               ;;
+       esac
+       
+       : Now look for a hint file osname_osvers, unless one has been
+       : specified already.
+       case "$hintfile" in
+       ''|' ')
+               file=`echo "${osname}_${osvers}" | $sed -e 's@\.@_@g' -e 's@_$@@'`
+               : Also try without trailing minor version numbers.
+               xfile=`echo $file | $sed -e 's@_[^_]*$@@'`
+               xxfile=`echo $xfile | $sed -e 's@_[^_]*$@@'`
+               xxxfile=`echo $xxfile | $sed -e 's@_[^_]*$@@'`
+               xxxxfile=`echo $xxxfile | $sed -e 's@_[^_]*$@@'`
+               case "$file" in
+               '') dflt=none ;;
+               *)  case "$osvers" in
+                       '') dflt=$file
+                               ;;
+                       *)  if $test -f $src/hints/$file.sh ; then
+                                       dflt=$file
+                               elif $test -f $src/hints/$xfile.sh ; then
+                                       dflt=$xfile
+                               elif $test -f $src/hints/$xxfile.sh ; then
+                                       dflt=$xxfile
+                               elif $test -f $src/hints/$xxxfile.sh ; then
+                                       dflt=$xxxfile
+                               elif $test -f $src/hints/$xxxxfile.sh ; then
+                                       dflt=$xxxxfile
+                               elif $test -f "$src/hints/${osname}.sh" ; then
+                                       dflt="${osname}"
+                               else
+                                       dflt=none
+                               fi
+                               ;;
+                       esac
+                       ;;
+               esac
+               ;;
+       *)
+               dflt=`echo $hintfile | $sed 's/\.sh$//'`
+               ;;
+       esac
+
+       $cat <<EOM
+
+You may give one or more space-separated answers, or "none" if appropriate.
+If your OS version has no hints, DO NOT give a wrong version -- say "none".
+
+EOM
+       rp="Which of these apply, if any?"
+       . UU/myread
+       tans=$ans
+       for file in $tans; do
+               if $test -f $src/hints/$file.sh; then
+                       . $src/hints/$file.sh
+                       $cat $src/hints/$file.sh >> UU/config.sh
+               elif $test X$tans = X -o X$tans = Xnone ; then
+                       : nothing
+               else
+                       : Give one chance to correct a possible typo.
+                       echo "$file.sh does not exist"
+                       dflt=$file
+                       rp="hint to use instead?"
+                       . UU/myread
+                       for file in $ans; do
+                               if $test -f "$src/hints/$file.sh"; then
+                                       . $src/hints/$file.sh
+                                       $cat $src/hints/$file.sh >> UU/config.sh
+                               elif $test X$ans = X -o X$ans = Xnone ; then
+                                       : nothing
+                               else
+                                       echo "$file.sh does not exist -- ignored."
+                               fi
+                       done
+               fi
+       done
+
+       hint=recommended
+       : Remember our hint file for later.
+       if $test -f "$src/hints/$file.sh" ; then
+               hintfile="$file"
+       else
+               hintfile=''
+       fi
+fi
+cd UU
+;;
+*)
+       echo " "
+       echo "Fetching default answers from $config_sh..." >&4
+       tmp_n="$n"
+       tmp_c="$c"
+       cd ..
+       cp $config_sh config.sh 2>/dev/null
+       chmod +w config.sh
+       . ./config.sh
+       cd UU
+       cp ../config.sh .
+       n="$tmp_n"
+       c="$tmp_c"
+       hint=previous
+       ;;
+esac
+test "$override" && . ./optdef.sh
+myuname="$newmyuname"
+
+: Restore computed paths
+for file in $loclist $trylist; do
+       eval $file="\$_$file"
+done
+
+cat << EOM
+
+Configure uses the operating system name and version to set some defaults.
+The default value is probably right if the name rings a bell. Otherwise,
+since spelling matters for me, either accept the default or answer "none"
+to leave it blank.
+
+EOM
+case "$osname" in
+       ''|' ')
+               case "$hintfile" in
+               ''|' '|none) dflt=none ;;
+               *)  dflt=`echo $hintfile | $sed -e 's/\.sh$//' -e 's/_.*$//'` ;;
+               esac
+               ;;
+       *) dflt="$osname" ;;
+esac
+rp="Operating system name?"
+. ./myread
+case "$ans" in
+none)  osname='' ;;
+*) osname=`echo "$ans" | $sed -e 's/[  ][      ]*/_/g' | ./tr '[A-Z]' '[a-z]'`;;
+esac
+: who configured the system
+cf_time=`$date 2>&1`
+cf_by=`(logname) 2>/dev/null`
+case "$cf_by" in
+"")
+       cf_by=`(whoami) 2>/dev/null`
+       case "$cf_by" in
+       "") cf_by=unknown ;;
+       esac ;;
+esac
+
+: determine where manual pages are on this system
+echo " "
+case "$sysman" in
+'') 
+       syspath='/usr/man/man1 /usr/man/mann /usr/man/manl /usr/man/local/man1'
+       syspath="$syspath /usr/man/u_man/man1 /usr/share/man/man1"
+       syspath="$syspath /usr/catman/u_man/man1 /usr/man/l_man/man1"
+       syspath="$syspath /usr/local/man/u_man/man1 /usr/local/man/l_man/man1"
+       syspath="$syspath /usr/man/man.L /local/man/man1 /usr/local/man/man1"
+       sysman=`./loc . /usr/man/man1 $syspath`
+       ;;
+esac
+if $test -d "$sysman"; then
+       echo "System manual is in $sysman." >&4
+else
+       echo "Could not find manual pages in source form." >&4
+fi
+
+: make some quick guesses about what we are up against
+echo " "
+$echo $n "Hmm...  $c"
+echo exit 1 >bsd
+echo exit 1 >usg
+echo exit 1 >v7
+echo exit 1 >osf1
+echo exit 1 >eunice
+echo exit 1 >xenix
+echo exit 1 >venix
+d_bsd="$undef"
+$cat /usr/include/signal.h /usr/include/sys/signal.h >foo 2>/dev/null
+if test -f /osf_boot || $contains 'OSF/1' /usr/include/ctype.h >/dev/null 2>&1
+then
+       echo "Looks kind of like an OSF/1 system, but we'll see..."
+       echo exit 0 >osf1
+elif test `echo abc | tr a-z A-Z` = Abc ; then
+       xxx=`./loc addbib blurfl $pth`
+       if $test -f $xxx; then
+       echo "Looks kind of like a USG system with BSD features, but we'll see..."
+               echo exit 0 >bsd
+               echo exit 0 >usg
+       else
+               if $contains SIGTSTP foo >/dev/null 2>&1 ; then
+                       echo "Looks kind of like an extended USG system, but we'll see..."
+               else
+                       echo "Looks kind of like a USG system, but we'll see..."
+               fi
+               echo exit 0 >usg
+       fi
+elif $contains SIGTSTP foo >/dev/null 2>&1 ; then
+       echo "Looks kind of like a BSD system, but we'll see..."
+       d_bsd="$define"
+       echo exit 0 >bsd
+else
+       echo "Looks kind of like a Version 7 system, but we'll see..."
+       echo exit 0 >v7
+fi
+case "$eunicefix" in
+*unixtovms*)
+       $cat <<'EOI'
+There is, however, a strange, musty smell in the air that reminds me of
+something...hmm...yes...I've got it...there's a VMS nearby, or I'm a Blit.
+EOI
+       echo exit 0 >eunice
+       d_eunice="$define"
+: it so happens the Eunice I know will not run shell scripts in Unix format
+       ;;
+*)
+       echo " "
+       echo "Congratulations.  You aren't running Eunice."
+       d_eunice="$undef"
+       ;;
+esac
+if test -f /xenix; then
+       echo "Actually, this looks more like a XENIX system..."
+       echo exit 0 >xenix
+       d_xenix="$define"
+else
+       echo " "
+       echo "It's not Xenix..."
+       d_xenix="$undef"
+fi
+chmod +x xenix
+$eunicefix xenix
+if test -f /venix; then
+       echo "Actually, this looks more like a VENIX system..."
+       echo exit 0 >venix
+else
+       echo " "
+       if ./xenix; then
+               : null
+       else
+               echo "Nor is it Venix..."
+       fi
+fi
+chmod +x bsd usg v7 osf1 eunice xenix venix
+$eunicefix bsd usg v7 osf1 eunice xenix venix
+$rm -f foo
+
+: see if we need a special compiler
+echo " "
+if ./usg; then
+       case "$cc" in
+       '') case "$Mcc" in
+               /*) dflt='Mcc';;
+               *) case "$large" in
+                       -M*) dflt='cc';;
+                       *)      if $contains '\-M' $sysman/cc.1 >/dev/null 2>&1 ; then
+                                       if $contains '\-M' $sysman/cpp.1 >/dev/null 2>&1; then
+                                               dflt='cc'
+                                       else
+                                               dflt='cc -M'
+                                       fi
+                               else
+                                       dflt='cc'
+                               fi;;
+                       esac;;
+               esac;;
+       *)  dflt="$cc";;
+       esac
+       $cat <<'EOM'
+On some systems the default C compiler will not resolve multiple global
+references that happen to have the same name.  On some such systems the "Mcc"
+command may be used to force these to be resolved.  On other systems a "cc -M"
+command is required.  (Note that the -M flag on other systems indicates a
+memory model to use!) If you have the Gnu C compiler, you might wish to use
+that instead.
+
+EOM
+       rp="What command will force resolution on this system?"
+       . ./myread
+       cc="$ans"
+else
+       case "$cc" in
+       '') dflt=cc;;
+       *) dflt="$cc";;
+       esac
+       rp="Use which C compiler?"
+       . ./myread
+       cc="$ans"
+fi
+echo " "
+echo "Checking for GNU cc in disguise and/or its version number..." >&4
+$cat >gccvers.c <<EOM
+#include <stdio.h>
+int main() {
+#ifdef __GNUC__
+#ifdef __VERSION__
+       printf("%s\n", __VERSION__);
+#else
+       printf("%s\n", "1");
+#endif
+#endif
+       exit(0);
+}
+EOM
+if $cc -o gccvers gccvers.c >/dev/null 2>&1; then
+       gccversion=`./gccvers`
+       case "$gccversion" in
+       '') echo "You are not using GNU cc." ;;
+       *)  echo "You are using GNU cc $gccversion." ;;
+       esac
+else
+       echo " "
+       echo "*** WHOA THERE!!! ***" >&4
+       echo "    Your C compiler \"$cc\" doesn't seem to be working!" >&4
+       case "$knowitall" in
+       '')
+       echo "    You'd better start hunting for one and let me know about it." >&4
+               exit 1
+               ;;
+       esac
+fi
+$rm -f gccvers*
+case "$gccversion" in
+1*) cpp=`./loc gcc-cpp $cpp $pth` ;;
+esac
+
+: decide how portable to be
+case "$d_portable" in
+"$define") dflt=y;;
+*)     dflt=n;;
+esac
+$cat <<'EOH'
+I can set things up so that your shell scripts and binaries are more portable,
+at what may be a noticable cost in performance.  In particular, if you
+ask to be portable, the following happens:
+
+     1) Shell scripts will rely on the PATH variable rather than using
+       the paths derived above.
+     2) ~username interpretations will be done at run time rather than
+       by Configure.
+
+EOH
+rp="Do you expect to run these scripts and binaries on multiple machines?"
+. ./myread
+case "$ans" in
+       y*) d_portable="$define"
+       ;;
+       *)  d_portable="$undef" ;;
+esac
+
+: set up shell script to do ~ expansion
+cat >filexp <<EOSS
+$startsh
+: expand filename
+case "\$1" in
+ ~/*|~)
+       echo \$1 | $sed "s|~|\${HOME-\$LOGDIR}|"
+       ;;
+ ~*)
+       if $test -f /bin/csh; then
+               /bin/csh -f -c "glob \$1"
+               failed=\$?
+               echo ""
+               exit \$failed
+       else
+               name=\`$expr x\$1 : '..\([^/]*\)'\`
+               dir=\`$sed -n -e "/^\${name}:/{s/^[^:]*:[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\).*"'\$'"/\1/" -e p -e q -e '}' </etc/passwd\`
+               if $test ! -d "\$dir"; then
+                       me=\`basename \$0\`
+                       echo "\$me: can't locate home directory for: \$name" >&2
+                       exit 1
+               fi
+               case "\$1" in
+               */*)
+                       echo \$dir/\`$expr x\$1 : '..[^/]*/\(.*\)'\`
+                       ;;
+               *)
+                       echo \$dir
+                       ;;
+               esac
+       fi
+       ;;
+*)
+       echo \$1
+       ;;
+esac
+EOSS
+chmod +x filexp
+$eunicefix filexp
+
+: now set up to get a file name
+cat <<EOS >getfile
+$startsh
+EOS
+cat <<'EOSC' >>getfile
+tilde=''
+fullpath=''
+already=''
+skip=''
+none_ok=''
+exp_file=''
+nopath_ok=''
+orig_rp="$rp"
+orig_dflt="$dflt"
+
+case "$fn" in
+*\(*)
+       expr $fn : '.*(\(.*\)).*' | tr ',' '\012' >getfile.ok
+       fn=`echo $fn | sed 's/(.*)//'`
+       ;;
+esac
+
+case "$fn" in
+*:*)
+       loc_file=`expr $fn : '.*:\(.*\)'`
+       fn=`expr $fn : '\(.*\):.*'`
+       ;;
+esac
+
+case "$fn" in
+*~*) tilde=true;;
+esac
+case "$fn" in
+*/*) fullpath=true;;
+esac
+case "$fn" in
+*+*) skip=true;;
+esac
+case "$fn" in
+*n*) none_ok=true;;
+esac
+case "$fn" in
+*e*) exp_file=true;;
+esac
+case "$fn" in
+*p*) nopath_ok=true;;
+esac
+
+case "$fn" in
+*f*) type='File';;
+*d*) type='Directory';;
+*l*) type='Locate';;
+esac
+
+what="$type"
+case "$what" in
+Locate) what='File';;
+esac
+
+case "$exp_file" in
+'')
+       case "$d_portable" in
+       "$define") ;;
+       *) exp_file=true;;
+       esac
+       ;;
+esac
+
+cd ..
+while test "$type"; do
+       redo=''
+       rp="$orig_rp"
+       dflt="$orig_dflt"
+       case "$tilde" in
+       true) rp="$rp (~name ok)";;
+       esac
+       . UU/myread
+       if test -f UU/getfile.ok && \
+               $contains "^$ans\$" UU/getfile.ok >/dev/null 2>&1
+       then
+               value="$ans"
+               ansexp="$ans"
+               break
+       fi
+       case "$ans" in
+       none)
+               value=''
+               ansexp=''
+               case "$none_ok" in
+               true) type='';;
+               esac
+               ;;
+       *)
+               case "$tilde" in
+               '') value="$ans"
+                       ansexp="$ans";;
+               *)
+                       value=`UU/filexp $ans`
+                       case $? in
+                       0)
+                               if test "$ans" != "$value"; then
+                                       echo "(That expands to $value on this system.)"
+                               fi
+                               ;;
+                       *) value="$ans";;
+                       esac
+                       ansexp="$value"
+                       case "$exp_file" in
+                       '') value="$ans";;
+                       esac
+                       ;;
+               esac
+               case "$fullpath" in
+               true)
+                       case "$ansexp" in
+                       /*) value="$ansexp" ;;
+                       *)
+                               redo=true
+                               case "$already" in
+                               true)
+                               echo "I shall only accept a full path name, as in /bin/ls." >&4
+                               echo "Use a ! shell escape if you wish to check pathnames." >&4
+                                       ;;
+                               *)
+                               echo "Please give a full path name, starting with slash." >&4
+                                       case "$tilde" in
+                                       true)
+                               echo "Note that using ~name is ok provided it expands well." >&4
+                                               already=true
+                                               ;;
+                                       esac
+                               esac
+                               ;;
+                       esac
+                       ;;
+               esac
+               case "$redo" in
+               '')
+                       case "$type" in
+                       File)
+                               if test -f "$ansexp"; then
+                                       type=''
+                               elif test -r "$ansexp" || (test -h "$ansexp") >/dev/null 2>&1
+                               then
+                                       echo "($value is not a plain file, but that's ok.)"
+                                       type=''
+                               fi
+                               ;;
+                       Directory)
+                               if test -d "$ansexp"; then
+                                       type=''
+                               fi
+                               ;;
+                       Locate)
+                               if test -d "$ansexp"; then
+                                       echo "(Looking for $loc_file in directory $value.)"
+                                       value="$value/$loc_file"
+                                       ansexp="$ansexp/$loc_file"
+                               fi
+                               if test -f "$ansexp"; then
+                                       type=''
+                               fi
+                               case "$nopath_ok" in
+                               true)   case "$value" in
+                                       */*) ;;
+                                       *)      echo "Assuming $value will be in people's path."
+                                               type=''
+                                               ;;
+                                       esac
+                                       ;;
+                               esac
+                               ;;
+                       esac
+
+                       case "$skip" in
+                       true) type='';
+                       esac
+
+                       case "$type" in
+                       '') ;;
+                       *)
+                               if test "$fastread" = yes; then
+                                       dflt=y
+                               else
+                                       dflt=n
+                               fi
+                               rp="$what $value doesn't exist.  Use that name anyway?"
+                               . UU/myread
+                               dflt=''
+                               case "$ans" in
+                               y*) type='';;
+                               *) echo " ";;
+                               esac
+                               ;;
+                       esac
+                       ;;
+               esac
+               ;;
+       esac
+done
+cd UU
+ans="$value"
+rp="$orig_rp"
+dflt="$orig_dflt"
+rm -f getfile.ok
+EOSC
+
+: What should the include directory be ?
+echo " "
+$echo $n "Hmm...  $c"
+dflt='/usr/include'
+incpath=''
+mips_type=''
+if $test -f /bin/mips && /bin/mips; then
+       echo "Looks like a MIPS system..."
+       $cat >usr.c <<'EOCP'
+#ifdef SYSTYPE_BSD43
+/bsd43
+#endif
+EOCP
+       if $cc -E usr.c > usr.out && $contains / usr.out >/dev/null 2>&1; then
+               dflt='/bsd43/usr/include'
+               incpath='/bsd43'
+               mips_type='BSD 4.3'
+       else
+               mips_type='System V'
+       fi
+       $rm -f usr.c usr.out
+       echo "and you're compiling with the $mips_type compiler and libraries."
+       xxx_prompt=y
+       echo "exit 0" >mips
+else
+       echo "Doesn't look like a MIPS system."
+       xxx_prompt=n
+       echo "exit 1" >mips
+fi
+chmod +x mips
+$eunicefix mips
+case "$usrinc" in
+'') ;;
+*) dflt="$usrinc";;
+esac
+case "$xxx_prompt" in
+y)     fn=d/
+       echo " "
+       rp='Where are the include files you want to use?'
+       . ./getfile
+       usrinc="$ans"
+       ;;
+*)     usrinc="$dflt"
+       ;;
+esac
+
+: see how we invoke the C preprocessor
+echo " "
+echo "Now, how can we feed standard input to your C preprocessor..." >&4
+cat <<'EOT' >testcpp.c
+#define ABC abc
+#define XYZ xyz
+ABC.XYZ
+EOT
+cd ..
+echo 'cat >.$$.c; '"$cc"' -E ${1+"$@"} .$$.c; rm .$$.c' >cppstdin
+chmod 755 cppstdin
+wrapper=`pwd`/cppstdin
+ok='false'
+cd UU
+
+if $test "X$cppstdin" != "X" && \
+       $cppstdin $cppminus <testcpp.c >testcpp.out 2>&1 && \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1
+then
+       echo "You used to use $cppstdin $cppminus so we'll use that again."
+       case "$cpprun" in
+       '') echo "But let's see if we can live without a wrapper..." ;;
+       *)
+               if $cpprun $cpplast <testcpp.c >testcpp.out 2>&1 && \
+                       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1
+               then
+                       echo "(And we'll use $cpprun $cpplast to preprocess directly.)"
+                       ok='true'
+               else
+                       echo "(However, $cpprun $cpplast does not work, let's see...)"
+               fi
+               ;;
+       esac
+else
+       case "$cppstdin" in
+       '') ;;
+       *)
+               echo "Good old $cppstdin $cppminus does not seem to be of any help..."
+               ;;
+       esac
+fi
+
+if $ok; then
+       : nothing
+elif echo 'Maybe "'"$cc"' -E" will work...'; \
+       $cc -E <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       echo "Yup, it does."
+       x_cpp="$cc -E"
+       x_minus='';
+elif echo 'Nope...maybe "'"$cc"' -E -" will work...'; \
+       $cc -E - <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       echo "Yup, it does."
+       x_cpp="$cc -E"
+       x_minus='-';
+elif echo 'Nope...maybe "'"$cc"' -P" will work...'; \
+       $cc -P <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       echo "Yipee, that works!"
+       x_cpp="$cc -P"
+       x_minus='';
+elif echo 'Nope...maybe "'"$cc"' -P -" will work...'; \
+       $cc -P - <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       echo "At long last!"
+       x_cpp="$cc -P"
+       x_minus='-';
+elif echo 'No such luck, maybe "'$cpp'" will work...'; \
+       $cpp <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       echo "It works!"
+       x_cpp="$cpp"
+       x_minus='';
+elif echo 'Nixed again...maybe "'$cpp' -" will work...'; \
+       $cpp - <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       echo "Hooray, it works!  I was beginning to wonder."
+       x_cpp="$cpp"
+       x_minus='-';
+elif echo 'Uh-uh.  Time to get fancy.  Trying a wrapper...'; \
+       $wrapper <testcpp.c >testcpp.out 2>&1; \
+       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+       x_cpp="$wrapper"
+       x_minus=''
+       echo "Eureka!"
+else
+       dflt=''
+       rp="No dice.  I can't find a C preprocessor.  Name one:"
+       . ./myread
+       x_cpp="$ans"
+       x_minus=''
+       $x_cpp <testcpp.c >testcpp.out 2>&1
+       if $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1 ; then
+               echo "OK, that will do." >&4
+       else
+echo "Sorry, I can't get that to work.  Go find one and rerun Configure." >&4
+               exit 1
+       fi
+fi
+
+case "$ok" in
+false)
+       cppstdin="$x_cpp"
+       cppminus="$x_minus"
+       cpprun="$x_cpp"
+       cpplast="$x_minus"
+       set X $x_cpp
+       shift
+       case "$1" in
+       "$cpp")
+               echo "Perhaps can we force $cc -E using a wrapper..."
+               if $wrapper <testcpp.c >testcpp.out 2>&1; \
+                       $contains 'abc.*xyz' testcpp.out >/dev/null 2>&1
+               then
+                       echo "Yup, we can."
+                       cppstdin="$wrapper"
+                       cppminus='';
+               else
+                       echo "Nope, we'll have to live without it..."
+               fi
+               ;;
+       esac
+       case "$cpprun" in
+       "$wrapper")
+               cpprun=''
+               cpplast=''
+               ;;
+       esac
+       ;;
+esac
+
+case "$cppstdin" in
+"$wrapper") ;;
+*) $rm -f $wrapper;;
+esac
+$rm -f testcpp.c testcpp.out
+
+: Set private lib path
+case "$plibpth" in
+'') if ./mips; then
+               plibpth="$incpath/usr/lib /usr/local/lib /usr/ccs/lib"
+       fi;;
+esac
+case "$libpth" in
+' ') dlist='';;
+'') dlist="$loclibpth $plibpth $glibpth";;
+*) dlist="$libpth";;
+esac
+
+: Now check and see which directories actually exist, avoiding duplicates
+libpth=''
+for xxx in $dlist
+do
+    if $test -d $xxx; then
+               case " $libpth " in
+               *" $xxx "*) ;;
+               *) libpth="$libpth $xxx";;
+               esac
+    fi
+done
+$cat <<'EOM'
+
+Some systems have incompatible or broken versions of libraries.  Among
+the directories listed in the question below, please remove any you
+know not to be holding relevant libraries, and add any that are needed.
+Say "none" for none.
+
+EOM
+case "$libpth" in
+'') dflt='none';;
+*)
+       set X $libpth
+       shift
+       dflt=${1+"$@"}
+       ;;
+esac
+rp="Directories to use for library searches?"
+. ./myread
+case "$ans" in
+none) libpth=' ';;
+*) libpth="$ans";;
+esac
+
+: determine optimize, if desired, or use for debug flag also
+case "$optimize" in
+' ') dflt='none';;
+'') case $gccversion in
+  2*) dflt='-g -O' ;;
+  *egcs*) dflt='-g -O' ;;
+  *) dflt='-g' ;;
+  esac ;;
+*) dflt="$optimize";;
+esac
+$cat <<EOH
+
+Some C compilers have problems with their optimizers.  By default, $package
+compiles with the -O flag to use the optimizer.  Alternately, you might want
+to use the symbolic debugger, which uses the -g flag (on traditional Unix
+systems).  Either flag can be specified here.  To use neither flag, specify
+the word "none".
+
+EOH
+rp="What optimizer/debugger flag should be used?"
+. ./myread
+optimize="$ans"
+case "$optimize" in
+'none') optimize=" ";;
+esac
+
+case "$warnings" in
+' ') dflt='none';;
+'') case $gccversion in
+  2*|egcs*) dflt='-W -Wall -Wno-comment' ;;
+  *) dflt='none' ;;
+  esac ;;
+*) dflt="$warnings";;
+esac
+$cat <<EOH
+
+EOH
+rp="What compiler warning flags should be used?"
+. ./myread
+warnings="$ans"
+case "$warnings" in
+'none') warnings=" ";;
+esac
+
+dflt=''
+: We will not override a previous value, but we might want to
+: augment a hint file
+case "$hint" in
+none|recommended)
+       case "$gccversion" in
+       1*) dflt='-fpcc-struct-return' ;;
+       esac
+       case "$gccversion" in
+       2*) if test -d /etc/conf/kconfig.d &&
+                       $contains _POSIX_VERSION $usrinc/sys/unistd.h >/dev/null 2>&1
+               then
+                       dflt="$dflt -posix"
+               fi
+               ;;
+       esac
+       ;;
+esac
+
+case "$mips_type" in
+*BSD*|'') inclwanted="$locincpth $usrinc";;
+*) inclwanted="$locincpth $inclwanted $usrinc/bsd";;
+esac
+for thisincl in $inclwanted; do
+       if $test -d $thisincl; then
+               if $test x$thisincl != x$usrinc; then
+                       case "$dflt" in
+                       *$thisincl*);;
+                       *) dflt="$dflt -I$thisincl";;
+                       esac
+               fi
+       fi
+done
+
+inctest='if $contains $2 $usrinc/$1 >/dev/null 2>&1; then
+       xxx=true;
+elif $contains $2 $usrinc/sys/$1 >/dev/null 2>&1; then
+       xxx=true;
+else
+       xxx=false;
+fi;
+if $xxx; then
+       case "$dflt" in
+       *$2*);;
+       *) dflt="$dflt -D$2";;
+       esac;
+fi'
+
+if ./osf1; then
+       set signal.h __LANGUAGE_C__; eval $inctest
+else
+       set signal.h LANGUAGE_C; eval $inctest
+fi
+
+case "$hint" in
+none|recommended) dflt="$ccflags $dflt" ;;
+*) dflt="$ccflags";;
+esac
+
+case "$dflt" in
+''|' ') dflt=none;;
+esac
+$cat <<EOH
+
+Your C compiler may want other flags.  For this question you should include
+-I/whatever and -DWHATEVER flags and any other flags used by the C compiler,
+but you should NOT include libraries or ld flags like -lwhatever.  If you
+want $package to honor its debug switch, you should include -DDEBUG here.
+
+To use no flags, specify the word "none".
+
+EOH
+set X $dflt
+shift
+dflt=${1+"$@"}
+rp="Any additional cc flags?"
+. ./myread
+case "$ans" in
+none) ccflags='';;
+*) ccflags="$ans";;
+esac
+
+: the following weeds options from ccflags that are of no interest to cpp
+cppflags="$ccflags"
+case "$gccversion" in
+1*) cppflags="$cppflags -D__GNUC__"
+esac
+case "$mips_type" in
+'');;
+*BSD*) cppflags="$cppflags -DSYSTYPE_BSD43";;
+esac
+case "$cppflags" in
+'');;
+*)
+       echo " "
+       echo "Let me guess what the preprocessor flags are..." >&4
+       set X $cppflags
+       shift
+       cppflags=''
+       $cat >cpp.c <<'EOM'
+#define BLURFL foo
+
+BLURFL xx LFRULB
+EOM
+       previous=''
+       for flag in $*
+       do
+               case "$flag" in
+               -*) ftry="$flag";;
+               *) ftry="$previous $flag";;
+               esac
+               if $cppstdin -DLFRULB=bar $ftry $cppminus <cpp.c \
+                       >cpp1.out 2>/dev/null && \
+                       $cpprun -DLFRULB=bar $ftry $cpplast <cpp.c \
+                       >cpp2.out 2>/dev/null && \
+                       $contains 'foo.*xx.*bar' cpp1.out >/dev/null 2>&1 && \
+                       $contains 'foo.*xx.*bar' cpp2.out >/dev/null 2>&1
+               then
+                       cppflags="$cppflags $ftry"
+                       previous=''
+               else
+                       previous="$flag"
+               fi
+       done
+       set X $cppflags
+       shift
+       cppflags=${1+"$@"}
+       case "$cppflags" in
+       *-*)  echo "They appear to be: $cppflags";;
+       esac
+       $rm -f cpp.c cpp?.out
+       ;;
+esac
+
+: flags used in final linking phase
+case "$ldflags" in
+'') if ./venix; then
+               dflt='-i -z'
+       else
+               dflt=''
+       fi
+       case "$ccflags" in
+       *-posix*) dflt="$dflt -posix" ;;
+       esac
+       ;;
+*) dflt="$ldflags";;
+esac
+
+: Try to guess additional flags to pick up local libraries.
+for thislibdir in $libpth; do
+       case " $loclibpth " in
+       *" $thislibdir "*)
+               case "$dflt " in 
+               *"-L$thislibdir "*) ;;
+               *)  dflt="$dflt -L$thislibdir" ;;
+               esac
+               ;;
+       esac
+done
+
+case "$dflt" in
+'') dflt='none' ;;
+esac
+
+$cat <<EOH
+
+Your C linker may need flags.  For this question you should
+include -L/whatever and any other flags used by the C linker, but you
+should NOT include libraries like -lwhatever.
+
+Make sure you include the appropriate -L/path flags if your C linker
+does not normally search all of the directories you specified above,
+namely
+       $libpth
+To use no flags, specify the word "none".
+
+EOH
+
+rp="Any additional ld flags (NOT including libraries)?"
+. ./myread
+case "$ans" in
+none) ldflags='';;
+*) ldflags="$ans";;
+esac
+rmlist="$rmlist pdp11"
+
+: coherency check
+echo " "
+echo "Checking your choice of C compiler and flags for coherency..." >&4
+set X $cc $optimize $ccflags $ldflags try.c -o try
+shift
+$cat >try.msg <<EOM
+I've tried to compile and run a simple program with:
+
+       $*
+       ./try
+
+and I got the following output:
+
+EOM
+$cat > try.c <<'EOF'
+#include <stdio.h>
+main() { exit(0); }
+EOF
+dflt=y
+if sh -c "$cc $optimize $ccflags try.c -o try $ldflags" >>try.msg 2>&1; then
+       if sh -c './try' >>try.msg 2>&1; then
+               dflt=n
+       else
+               echo "The program compiled OK, but exited with status $?." >>try.msg
+               rp="You have a problem.  Shall I abort Configure"
+               dflt=y
+       fi
+else
+       echo "I can't compile the test program." >>try.msg
+       rp="You have a BIG problem.  Shall I abort Configure"
+       dflt=y
+fi
+case "$dflt" in
+y)
+       $cat try.msg
+       case "$knowitall" in
+       '')
+               echo "(The supplied flags might be incorrect with this C compiler.)"
+               ;;
+       *) dflt=n;;
+       esac
+       echo " "
+       . ./myread
+       case "$ans" in
+       n*|N*) ;;
+       *)      echo "Ok.  Stopping Configure." >&4
+               exit 1
+               ;;
+       esac
+       ;;
+n) echo "OK, that should do.";;
+esac
+$rm -f try try.* core
+
+: Initialize h_fcntl
+h_fcntl=false
+
+: Initialize h_sysfile
+h_sysfile=false
+
+: compute shared library extension
+case "$so" in
+'')
+       if xxx=`./loc libc.sl X $libpth`; $test -f "$xxx"; then
+               dflt='sl'
+       else
+               dflt='so'
+       fi
+       ;;
+*) dflt="$so";;
+esac
+$cat <<EOM
+
+On some systems, shared libraries may be available.  Answer 'none' if
+you want to suppress searching of shared libraries for the remaining
+of this configuration.
+
+EOM
+rp='What is the file extension used for shared libraries?'
+. ./myread
+so="$ans"
+
+: Define several unixisms.
+: Hints files or command line option can be used to override them.
+case "$_a" in
+'') _a='.a';;
+esac
+case "$_o" in
+'') _o='.o';;
+esac
+
+: Looking for optional libraries
+echo " "
+echo "Checking for optional libraries..." >&4
+case "$libs" in
+' '|'') dflt='';;
+*) dflt="$libs";;
+esac
+case "$libswanted" in
+'') libswanted='c_s';;
+esac
+for thislib in $libswanted; do
+       
+
+
+    if $test "$thislib" = "resolv"; then
+      case " $dflt " in
+       *"-lbind "*) echo "We've already found -lbind, so we don't need -lresolv" ;;
+       *)
+
+       if xxx=`./loc lib$thislib.$so.[0-9]'*' X $libpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib (shared)."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc lib$thislib.$so X $libpth` ; $test -f "$xxx"; then
+               echo "Found -l$thislib (shared)."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc lib$thislib$_a X $libpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc $thislib$_a X $libpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc lib${thislib}_s$_a X $libpth`; $test -f "$xxx"; then
+               echo "Found -l${thislib}_s."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l${thislib}_s";;
+               esac
+       elif xxx=`./loc Slib$thislib$_a X $xlibpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       else
+               echo "No -l$thislib."
+       fi
+
+      ;;
+     esac
+    else
+
+       if xxx=`./loc lib$thislib.$so.[0-9]'*' X $libpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib (shared)."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc lib$thislib.$so X $libpth` ; $test -f "$xxx"; then
+               echo "Found -l$thislib (shared)."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc lib$thislib$_a X $libpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc $thislib$_a X $libpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       elif xxx=`./loc lib${thislib}_s$_a X $libpth`; $test -f "$xxx"; then
+               echo "Found -l${thislib}_s."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l${thislib}_s";;
+               esac
+       elif xxx=`./loc Slib$thislib$_a X $xlibpth`; $test -f "$xxx"; then
+               echo "Found -l$thislib."
+               case " $dflt " in
+               *"-l$thislib "*);;
+               *) dflt="$dflt -l$thislib";;
+               esac
+       else
+               echo "No -l$thislib."
+       fi
+
+    fi
+done
+set X $dflt
+shift
+dflt="$*"
+case "$libs" in
+'') dflt="$dflt";;
+*) dflt="$libs";;
+esac
+case "$dflt" in
+' '|'') dflt='none';;
+esac
+
+$cat <<EOM
+Some versions of Unix support shared libraries, which make executables smaller
+but make load time slightly longer.
+
+On some systems, mostly System V Release 3's, the shared library is included
+by putting the option "-lc_s" as the last thing on the cc command line when
+linking.  Other systems use shared libraries by default.  There may be other
+libraries needed to compile $package on your machine as well.  If your system
+needs the "-lc_s" option, include it here.  Include any other special libraries
+here as well.  Say "none" for none.
+EOM
+
+echo " "
+rp="Any additional libraries?"
+. ./myread
+case "$ans" in
+none) libs=' ';;
+*) libs="$ans";;
+esac
+
+: set up the script used to warn in case of inconsistency
+cat <<EOS >whoa
+$startsh
+EOS
+cat <<'EOSC' >>whoa
+dflt=y
+echo " "
+echo "*** WHOA THERE!!! ***" >&4
+echo "    The $hint value for \$$var on this machine was \"$was\"!" >&4
+rp="    Keep the $hint value?"
+. ./myread
+case "$ans" in
+y) td=$was; tu=$was;;
+esac
+EOSC
+
+: function used to set $1 to $val
+setvar='var=$1; eval "was=\$$1"; td=$define; tu=$undef;
+case "$val$was" in
+$define$undef) . ./whoa; eval "$var=\$td";;
+$undef$define) . ./whoa; eval "$var=\$tu";;
+*) eval "$var=$val";;
+esac'
+
+echo " "
+echo "Checking for GNU C Library..." >&4
+cat >gnulibc.c <<EOM
+int
+main()
+{
+       return __libc_main();
+}
+EOM
+if $cc $ccflags $ldflags -o gnulibc gnulibc.c $libs >/dev/null 2>&1 && \
+  ./gnulibc | $contains '^GNU C Library' >/dev/null 2>&1; then
+       val="$define"
+       echo "You are using the GNU C Library"
+else
+       val="$undef"
+       echo "You are not using the GNU C Library"
+fi
+$rm -f gnulibc*
+set d_gnulibc
+eval $setvar
+
+: see if nm is to be used to determine whether a symbol is defined or not
+case "$usenm" in
+'')
+       case "$d_gnulibc" in
+       $define)
+               dflt=n
+               ;;
+       *)
+               dflt=`egrep 'inlibc|csym' ../Configure | wc -l 2>/dev/null`
+               if $test $dflt -gt 20; then
+                       dflt=y
+               else
+                       dflt=n
+               fi
+               ;;
+       esac
+       ;;
+*)
+       case "$usenm" in
+       true) dflt=y;;
+       *) dflt=n;;
+       esac
+       ;;
+esac
+$cat <<EOM
+
+I can use 'nm' to extract the symbols from your C libraries. This is a time
+consuming task which may generate huge output on the disk (up to 3 megabytes)
+but that should make the symbols extraction faster. The alternative is to skip
+the 'nm' extraction part and to compile a small test program instead to
+determine whether each symbol is present. If you have a fast C compiler and/or
+if your 'nm' output cannot be parsed, this may be the best solution.
+If you see a lot of messages about functions NOT found that you know you
+have, re-run Configure and don't let me use 'nm'.
+
+EOM
+rp='Shall I use nm to extract C symbols from the libraries?'
+. ./myread
+case "$ans" in
+n|N) usenm=false;;
+*) usenm=true;;
+esac
+
+runnm=$usenm
+case "$reuseval" in
+true) runnm=false;;
+esac
+
+: nm options which may be necessary
+case "$nm_opt" in
+'') if $test -f /mach_boot; then
+               nm_opt=''
+       elif $test -d /usr/ccs/lib; then
+               nm_opt='-p'
+       elif $test -f /dgux; then
+               nm_opt='-p'
+       else
+               nm_opt=''
+       fi;;
+esac
+
+: nm options which may be necessary for shared libraries but illegal
+: for archive libraries.  Thank you, Linux.
+case "$nm_so_opt" in
+'')    case "$myuname" in
+       *linux*)
+               if nm --help | $grep 'dynamic' > /dev/null 2>&1; then
+                       nm_so_opt='--dynamic'
+               fi
+               ;;
+       esac
+       ;;
+esac
+
+case "$runnm" in
+true)
+: get list of predefined functions in a handy place
+echo " "
+case "$libc" in
+'') libc=unknown
+       case "$libs" in
+       *-lc_s*) libc=`./loc libc_s$_a $libc $libpth`
+       esac
+       ;;
+esac
+libnames='';
+case "$libs" in
+'') ;;
+*)  for thislib in $libs; do
+       case "$thislib" in
+       -lc|-lc_s)
+               : Handle C library specially below.
+               ;;
+       -l*)
+               thislib=`echo $thislib | $sed -e 's/^-l//'`
+               if try=`./loc lib$thislib.$so.'*' X $libpth`; $test -f "$try"; then
+                       :
+               elif try=`./loc lib$thislib.$so X $libpth`; $test -f "$try"; then
+                       :
+               elif try=`./loc lib$thislib$_a X $libpth`; $test -f "$try"; then
+                       :
+               elif try=`./loc $thislib$_a X $libpth`; $test -f "$try"; then
+                       :
+               elif try=`./loc lib$thislib X $libpth`; $test -f "$try"; then
+                       :
+               elif try=`./loc $thislib X $libpth`; $test -f "$try"; then
+                       :
+               elif try=`./loc Slib$thislib$_a X $xlibpth`; $test -f "$try"; then
+                       :
+               else
+                       try=''
+               fi
+               libnames="$libnames $try"
+               ;;
+       *) libnames="$libnames $thislib" ;;
+       esac
+       done
+       ;;
+esac
+xxx=normal
+case "$libc" in
+unknown)
+       set /lib/libc.$so
+       for xxx in $libpth; do
+               $test -r $1 || set $xxx/libc.$so
+               $test -r $1 || \
+                       set `echo blurfl; echo $xxx/libc.$so.[0-9]* | \
+                       tr ' ' '\012' | egrep -v '\.[A-Za-z]*$' | $sed -e '
+                               h
+                               s/[0-9][0-9]*/0000&/g
+                               s/0*\([0-9][0-9][0-9][0-9][0-9]\)/\1/g
+                               G
+                               s/\n/ /' | \
+                        sort | $sed -e 's/^.* //'`
+               eval set \$$#
+       done
+       $test -r $1 || set /usr/ccs/lib/libc.$so
+       $test -r $1 || set /lib/libsys_s$_a
+       ;;
+*)
+       set blurfl
+       ;;
+esac
+if $test -r "$1"; then
+       echo "Your (shared) C library seems to be in $1."
+       libc="$1"
+elif $test -r /lib/libc && $test -r /lib/clib; then
+       echo "Your C library seems to be in both /lib/clib and /lib/libc."
+       xxx=apollo
+       libc='/lib/clib /lib/libc'
+       if $test -r /lib/syslib; then
+               echo "(Your math library is in /lib/syslib.)"
+               libc="$libc /lib/syslib"
+       fi
+elif $test -r "$libc" || (test -h "$libc") >/dev/null 2>&1; then
+       echo "Your C library seems to be in $libc, as you said before."
+elif $test -r $incpath/usr/lib/libc$_a; then
+       libc=$incpath/usr/lib/libc$_a;
+       echo "Your C library seems to be in $libc.  That's fine."
+elif $test -r /lib/libc$_a; then
+       libc=/lib/libc$_a;
+       echo "Your C library seems to be in $libc.  You're normal."
+else
+       if tans=`./loc libc$_a blurfl/dyick $libpth`; $test -r "$tans"; then
+               :
+       elif tans=`./loc libc blurfl/dyick $libpth`; $test -r "$tans"; then
+               libnames="$libnames "`./loc clib blurfl/dyick $libpth`
+       elif tans=`./loc clib blurfl/dyick $libpth`; $test -r "$tans"; then
+               :
+       elif tans=`./loc Slibc$_a blurfl/dyick $xlibpth`; $test -r "$tans"; then
+               :
+       elif tans=`./loc Mlibc$_a blurfl/dyick $xlibpth`; $test -r "$tans"; then
+               :
+       else
+               tans=`./loc Llibc$_a blurfl/dyick $xlibpth`
+       fi
+       if $test -r "$tans"; then
+               echo "Your C library seems to be in $tans, of all places."
+               libc=$tans
+       else
+               libc='blurfl'
+       fi
+fi
+if $test $xxx = apollo -o -r "$libc" || (test -h "$libc") >/dev/null 2>&1; then
+       dflt="$libc"
+       cat <<EOM
+
+If the guess above is wrong (which it might be if you're using a strange
+compiler, or your machine supports multiple models), you can override it here.
+
+EOM
+else
+       dflt=''
+       echo $libpth | tr ' ' '\012' | sort | uniq > libpath
+       cat >&4 <<EOM
+I can't seem to find your C library.  I've looked in the following places:
+
+EOM
+       $sed 's/^/      /' libpath
+       cat <<EOM
+
+None of these seems to contain your C library. I need to get its name...
+
+EOM
+fi
+fn=f
+rp='Where is your C library?'
+. ./getfile
+libc="$ans"
+
+echo " "
+echo $libc $libnames | tr ' ' '\012' | sort | uniq > libnames
+set X `cat libnames`
+shift
+xxx=files
+case $# in 1) xxx=file; esac
+echo "Extracting names from the following $xxx for later perusal:" >&4
+echo " "
+$sed 's/^/     /' libnames >&4
+echo " "
+$echo $n "This may take a while...$c" >&4
+
+for file in $*; do
+       case $file in
+       *$so*) nm $nm_so_opt $nm_opt $file 2>/dev/null;;
+       *) nm $nm_opt $file 2>/dev/null;;
+       esac
+done >libc.tmp
+
+$echo $n ".$c"
+$grep fprintf libc.tmp > libc.ptf
+xscan='eval "<libc.ptf $com >libc.list"; $echo $n ".$c" >&4'
+xrun='eval "<libc.tmp $com >libc.list"; echo "done" >&4'
+xxx='[ADTSIW]'
+if com="$sed -n -e 's/__IO//' -e 's/^.* $xxx  *_[_.]*//p' -e 's/^.* $xxx  *//p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^__*//' -e 's/^\([a-zA-Z_0-9$]*\).*xtern.*/\1/p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e '/|UNDEF/d' -e '/FUNC..GL/s/^.*|__*//p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^.* D __*//p' -e 's/^.* D //p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^_//' -e 's/^\([a-zA-Z_0-9]*\).*xtern.*text.*/\1/p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^.*|FUNC |GLOB .*|//p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$grep '|' | $sed -n -e '/|COMMON/d' -e '/|DATA/d' \
+                               -e '/ file/d' -e 's/^\([^       ]*\).*/\1/p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^.*|FUNC |GLOB .*|//p' -e 's/^.*|FUNC |WEAK .*|//p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^__//' -e '/|Undef/d' -e '/|Proc/s/ .*//p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e '/Def. Text/s/.* \([^ ]*\)\$/\1/p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/^[-0-9a-f ]*_\(.*\)=.*/\1/p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+elif com="$sed -n -e 's/.*\.text n\ \ \ \.//p'";\
+       eval $xscan;\
+       $contains '^fprintf$' libc.list >/dev/null 2>&1; then
+               eval $xrun
+else
+       nm -p $* 2>/dev/null >libc.tmp
+       $grep fprintf libc.tmp > libc.ptf
+       if com="$sed -n -e 's/^.* [ADTSIW]  *_[_.]*//p' -e 's/^.* [ADTSIW] //p'";\
+               eval $xscan; $contains '^fprintf$' libc.list >/dev/null 2>&1
+       then
+               nm_opt='-p'
+               eval $xrun
+       else
+               echo " "
+               echo "nm didn't seem to work right. Trying ar instead..." >&4
+               com=''
+               if ar t $libc > libc.tmp; then
+                       for thisname in $libnames; do
+                               ar t $thisname >>libc.tmp
+                       done
+                       $sed -e "s/\\$_o\$//" < libc.tmp > libc.list
+                       echo "Ok." >&4
+               else
+                       echo "ar didn't seem to work right." >&4
+                       echo "Maybe this is a Cray...trying bld instead..." >&4
+                       if bld t $libc | $sed -e 's/.*\///' -e "s/\\$_o:.*\$//" > libc.list
+                       then
+                               for thisname in $libnames; do
+                                       bld t $libnames | \
+                                       $sed -e 's/.*\///' -e "s/\\$_o:.*\$//" >>libc.list
+                                       ar t $thisname >>libc.tmp
+                               done
+                               echo "Ok." >&4
+                       else
+                               echo "That didn't work either.  Giving up." >&4
+                               exit 1
+                       fi
+               fi
+       fi
+fi
+nm_extract="$com"
+if $test -f /lib/syscalls.exp; then
+       echo " "
+       echo "Also extracting names from /lib/syscalls.exp for good ole AIX..." >&4
+       $sed -n 's/^\([^        ]*\)[   ]*syscall$/\1/p' /lib/syscalls.exp >>libc.list
+fi
+;;
+esac
+$rm -f libnames libpath
+
+: is a C symbol defined?
+csym='tlook=$1;
+case "$3" in
+-v) tf=libc.tmp; tc=""; tdc="";;
+-a) tf=libc.tmp; tc="[0]"; tdc="[]";;
+*) tlook="^$1\$"; tf=libc.list; tc="()"; tdc="()";;
+esac;
+tx=yes;
+case "$reuseval-$4" in
+true-) ;;
+true-*) tx=no; eval "tval=\$$4"; case "$tval" in "") tx=yes;; esac;;
+esac;
+case "$tx" in
+yes)
+       case "$runnm" in
+       true)
+               if $contains $tlook $tf >/dev/null 2>&1;
+               then tval=true;
+               else tval=false;
+               fi;;
+       *)
+               echo "main() { extern short $1$tdc; printf(\"%hd\", $1$tc); }" > t.c;
+               if $cc $ccflags $ldflags -o t t.c $libs >/dev/null 2>&1;
+               then tval=true;
+               else tval=false;
+               fi;
+               $rm -f t t.c;;
+       esac;;
+*)
+       case "$tval" in
+       $define) tval=true;;
+       *) tval=false;;
+       esac;;
+esac;
+eval "$2=$tval"'
+
+: define an is-in-libc? function
+inlibc='echo " "; td=$define; tu=$undef;
+sym=$1; var=$2; eval "was=\$$2";
+tx=yes;
+case "$reuseval$was" in
+true) ;;
+true*) tx=no;;
+esac;
+case "$tx" in
+yes)
+       set $sym tres -f;
+       eval $csym;
+       case "$tres" in
+       true)
+               echo "$sym() found." >&4;
+               case "$was" in $undef) . ./whoa; esac; eval "$var=\$td";;
+       *)
+               echo "$sym() NOT found." >&4;
+               case "$was" in $define) . ./whoa; esac; eval "$var=\$tu";;
+       esac;;
+*)
+       case "$was" in
+       $define) echo "$sym() found." >&4;;
+       *) echo "$sym() NOT found." >&4;;
+       esac;;
+esac'
+
+: determine filename position in cpp output
+echo " "
+echo "Computing filename position in cpp output for #include directives..." >&4
+echo '#include <stdio.h>' > foo.c
+$cat >fieldn <<EOF
+$startsh
+$cppstdin $cppflags $cppminus <foo.c 2>/dev/null | \
+$grep '^[      ]*#.*stdio\.h' | \
+while read cline; do
+       pos=1
+       qline=\`echo "\$cline" | $sed -e 's/\\([][{}#$<>;&()|^*?\\\\]\\)/\\\\\\\\\\1/g'\`
+       eval set \$qline
+       while $test "x\$1" \!= x; do
+               if $test -r "\$1"; then
+                       echo "\$pos"
+                       exit 0
+               fi
+               shift
+               pos=\`expr \$pos + 1\`
+       done
+done
+EOF
+chmod +x fieldn
+fieldn=`./fieldn`
+$rm -f foo.c fieldn
+case $fieldn in
+'') pos='???';;
+1) pos=first;;
+2) pos=second;;
+3) pos=third;;
+*) pos="${fieldn}th";;
+esac
+echo "Your cpp writes the filename in the $pos field of the line."
+
+: locate header file
+$cat >findhdr <<EOF
+$startsh
+wanted=\$1
+name=''
+if test -f $usrinc/\$wanted; then
+       echo "$usrinc/\$wanted"
+       exit 0
+fi
+echo "#include <\$wanted>" > foo\$\$.c
+$cppstdin $cppminus $cppflags < foo\$\$.c 2>/dev/null | \
+$grep "^[      ]*#.*\$wanted" | \
+while read cline; do
+       pos=1
+       qline=\`echo "\$cline" | $sed -e 's/\\([][{}#$<>;&()|^'"'"'*?\\\\]\\)/\\\\\\\\\\1/g'\`
+       eval set \$qline
+        while $test $fieldn -gt \$pos; do
+               shift
+               pos=\`expr \$pos + 1\`
+       done
+       name=\$1
+       case "\$name" in
+       */\$wanted) echo "\$name"; exit 0;;
+       *\\\\\$wanted) echo "\$name"; exit 0;;
+       *) name='';;
+       esac;
+done;
+$rm -f foo\$\$.c;
+case "\$name" in
+'') exit 1;;
+esac
+EOF
+chmod +x findhdr
+
+: access call always available on UNIX
+set access d_access
+eval $inlibc
+
+: locate the flags for 'access()'
+case "$d_access" in
+"$define")
+       echo " "
+       $cat >access.c <<'EOCP'
+#include <sys/types.h>
+#ifdef I_FCNTL
+#include <fcntl.h>
+#endif
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+main() {
+       exit(R_OK);
+}
+EOCP
+       : check sys/file.h first, no particular reason here
+       if $test `./findhdr sys/file.h` && \
+               $cc $cppflags -DI_SYS_FILE access.c -o access >/dev/null 2>&1 ; then
+               h_sysfile=true;
+               echo "<sys/file.h> defines the *_OK access constants." >&4
+       elif $test `./findhdr fcntl.h` && \
+               $cc $cppflags -DI_FCNTL access.c -o access >/dev/null 2>&1 ; then
+               h_fcntl=true;
+               echo "<fcntl.h> defines the *_OK access constants." >&4
+       elif $test `./findhdr unistd.h` && \
+               $cc $cppflags -DI_UNISTD access.c -o access >/dev/null 2>&1 ; then
+               echo "<unistd.h> defines the *_OK access constants." >&4
+       else
+               echo "I can't find the four *_OK access constants--I'll use mine." >&4
+       fi
+       ;;
+esac
+$rm -f access*
+
+: check for ok to use function ptr arguments in prototypes
+echo " "
+$cat >test_argsinfp.c <<'EOCP'
+#include <stdio.h>
+int myfun(int);
+int fun(int (*func)(int)); 
+int fun2(int, int (*prevfun)(int(*func)(int)));
+int fun (int (*func)(int)) { int a = 1; return func(a); }
+int myfun(int x) { return x - 1; }
+
+int main(int argc, char **argv) {
+ exit(fun(myfun));
+} 
+EOCP
+
+if $cc $ccflags $ldflags -o test_argsinfp test_argsinfp.c >/dev/null 2>&1 ; then
+    d_argsinfp='define'
+    echo 'Your compiler prefers arguments in function pointers in prototypes.' >&4
+else
+    d_argsinfp='undef'
+    echo "Your compiler prefers no arguments in function pointers in prototypes." >&4
+fi
+$rm -f test_argsinfp* core
+
+: see if we have the assert macro
+echo " "
+echo "Let's see if I can assert() myself." >&4
+       $cat >d_assert.c <<EOCP
+#include <assert.h>
+#include <stdlib.h>
+
+int main()
+{
+       assert(1);
+}
+EOCP
+
+if $cc $ccflags $ldflags d_assert.c -o d_assert $libs >/dev/null 2>&1; then
+               val="$define"
+               set d_assert; eval $setvar
+               echo "Looks like I can." >&4
+else
+               val="$undef"
+               set d_assert; eval $setvar
+               echo "Nope, I need assertiveness training." >&4
+fi
+$rm -f d_assert*
+
+: Look for GNU-cc style attribute checking
+echo " "
+echo "Checking whether your compiler can handle __attribute__ ..." >&4
+$cat >attrib.c <<'EOCP'
+#include <stdio.h>
+void croak (char* pat,...) __attribute__((format(printf,1,2),noreturn));
+EOCP
+if $cc $optimize $ccflags -c attrib.c >attrib.out 2>&1 ; then
+       if $contains 'warning' attrib.out >/dev/null 2>&1; then
+               echo "Your C compiler doesn't fully support __attribute__."
+               val="$undef"
+       else
+               echo "Your C compiler supports __attribute__."
+               val="$define"
+       fi
+else
+       echo "Your C compiler doesn't seem to understand __attribute__ at all."
+       val="$undef"
+fi
+set d_attribut
+eval $setvar
+$rm -f attrib*
+
+: see if bcopy exists
+set bcopy d_bcopy
+eval $inlibc
+
+: define an alternate in-header-list? function
+inhdr='echo " "; td=$define; tu=$undef; yyy=$@;
+cont=true; xxf="echo \"<\$1> found.\" >&4";
+case $# in 2) xxnf="echo \"<\$1> NOT found.\" >&4";;
+*) xxnf="echo \"<\$1> NOT found, ...\" >&4";;
+esac;
+case $# in 4) instead=instead;; *) instead="at last";; esac;
+while $test "$cont"; do
+       xxx=`./findhdr $1`
+       var=$2; eval "was=\$$2";
+       if $test "$xxx" && $test -r "$xxx";
+       then eval $xxf;
+       eval "case \"\$$var\" in $undef) . ./whoa; esac"; eval "$var=\$td";
+               cont="";
+       else eval $xxnf;
+       eval "case \"\$$var\" in $define) . ./whoa; esac"; eval "$var=\$tu"; fi;
+       set $yyy; shift; shift; yyy=$@;
+       case $# in 0) cont="";;
+       2) xxf="echo \"but I found <\$1> $instead.\" >&4";
+               xxnf="echo \"and I did not find <\$1> either.\" >&4";;
+       *) xxf="echo \"but I found <\$1\> instead.\" >&4";
+               xxnf="echo \"there is no <\$1>, ...\" >&4";;
+       esac;
+done;
+while $test "$yyy";
+do set $yyy; var=$2; eval "was=\$$2";
+       eval "case \"\$$var\" in $define) . ./whoa; esac"; eval "$var=\$tu";
+       set $yyy; shift; shift; yyy=$@;
+done'
+
+: see if libintl.h can be included
+set libintl.h i_libintl
+eval $inhdr
+: check for a new-style definitions
+echo " "
+$cat >test_bindtextdomain.c <<EOCP
+#$i_libintl I_LIBINTL
+#ifdef I_LIBINTL
+#include <libintl.h>
+#endif
+int main(int argc, char **argv) {
+   return (int) bindtextdomain ("", "");
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_bindtextdomain test_bindtextdomain.c $libs >/dev/null 2>&1 ; then
+    d_bindtextdomain='define'
+    echo 'Otima! You seem to have bindtextdomain for translations.' >&4
+else
+    d_bindtextdomain='undef'
+    echo "You don't seem to have bindtextdomain. Sinto muito." >&4
+fi
+$rm -f test_bindtextdomain* core
+
+: see if this is a unistd.h system
+set unistd.h i_unistd
+eval $inhdr
+
+: see if setpgrp exists
+set setpgrp d_setpgrp
+eval $inlibc
+
+case "$d_setpgrp" in
+"$define")
+       echo " "
+       echo "Checking to see which flavor of setpgrp is in use..."
+       $cat >set.c <<EOP
+#$i_unistd I_UNISTD
+#include <sys/types.h>
+#ifdef I_UNISTD
+#  include <unistd.h>
+#endif
+main()
+{
+       if (getuid() == 0) {
+               printf("(I see you are running Configure as super-user...)\n");
+               setuid(1);
+       }
+#ifdef TRY_BSD_PGRP
+       if (-1 == setpgrp(1, 1))
+               exit(0);
+#else
+       if (setpgrp() != -1)
+               exit(0);
+#endif
+       exit(1);
+}
+EOP
+       if $cc -DTRY_BSD_PGRP $ccflags $ldflags -o set set.c $libs >/dev/null 2>&1 && ./set; then
+               echo 'You have to use setpgrp(pid,pgrp) instead of setpgrp().' >&4
+               val="$define"
+       elif $cc $ccflags $ldflags -o set set.c $libs >/dev/null 2>&1 && ./set; then
+               echo 'You have to use setpgrp() instead of setpgrp(pid,pgrp).' >&4
+               val="$undef"
+       else
+               echo "(I can't seem to compile and run the test program.)"
+               if ./usg; then
+                       xxx="a USG one, i.e. you use setpgrp()."
+               else
+                       # SVR4 systems can appear rather BSD-ish.
+                       case "$i_unistd" in
+                       $undef)
+                               xxx="a BSD one, i.e. you use setpgrp(pid,pgrp)."
+                               val="$define"
+                               ;;
+                       $define)
+                               xxx="probably a USG one, i.e. you use setpgrp()."
+                               val="$undef"
+                               ;;
+                       esac
+               fi
+               echo "Assuming your setpgrp is $xxx" >&4
+       fi
+       ;;
+*) val="$undef";;
+esac
+set d_bsdsetpgrp
+eval $setvar
+$rm -f set set.c
+: see if bzero exists
+set bzero d_bzero
+eval $inlibc
+
+: check for const keyword
+echo " "
+echo 'Checking to see if your C compiler knows about "const"...' >&4
+$cat >const.c <<'EOCP'
+typedef struct spug { int drokk; } spug;
+main()
+{
+       const char *foo;
+       const spug y;
+}
+EOCP
+if $cc -c $ccflags const.c >/dev/null 2>&1 ; then
+       val="$define"
+       echo "Yup, it does."
+else
+       val="$undef"
+       echo "Nope, it doesn't."
+fi
+set d_const
+eval $setvar
+
+: see if crypt exists
+echo " "
+if set crypt val -f d_crypt; eval $csym; $val; then
+       echo 'crypt() found.' >&4
+       val="$define"
+       cryptlib=''
+else
+       cryptlib=`./loc Slibcrypt.a "" $xlibpth`
+       if $test -z "$cryptlib"; then
+               cryptlib=`./loc Mlibcrypt.a "" $xlibpth`
+       else
+               cryptlib=-lcrypt
+       fi
+       if $test -z "$cryptlib"; then
+               cryptlib=`./loc Llibcrypt.a "" $xlibpth`
+       else
+               cryptlib=-lcrypt
+       fi
+       if $test -z "$cryptlib"; then
+               cryptlib=`./loc libcrypt.a "" $libpth`
+       else
+               cryptlib=-lcrypt
+       fi
+       if $test -z "$cryptlib"; then
+               echo 'crypt() NOT found!' >&4
+               val="$undef"
+       else
+               val="$define"
+       fi
+fi
+set d_crypt
+eval $setvar
+: see if crypt.h can be included
+set crypt.h i_crypt
+eval $inhdr
+
+: Do nothing by default
+val="$undef"
+set d_force_ipv4; eval $setvar
+
+echo " "
+: see if floatingpoint.h can be included
+set floatingpoint.h i_floatingpoint
+eval $inhdr
+
+: see if fpsetround exists
+$cat >test_fpsetround.c <<'EOCP'
+#$i_floatingpoint I_FLOATINGPOINT
+#ifdef I_FLOATINGPOINT
+#include <floatingpoint.h>
+#endif
+
+int main() {
+       fpsetround(FP_RN);
+}
+EOCP
+
+if ($cc $ccflags $ldflags -o test_fpsetround test_fpsetround.c $libs \
+       && ./test_fpsetround) >/dev/null 2>&1 ; then
+       echo 'fpsetround() is around (and found).' >&4
+       val="$define"
+else
+       echo 'no fpsetround(). No problem.' >&4
+        val="$undef"
+fi
+set d_fpsetround
+eval $setvar
+
+: see if fpsetmask exists
+$cat >test_fpsetmask.c <<'EOCP'
+#$i_floatingpoint I_FLOATINGPOINT
+#ifdef I_FLOATINGPOINT
+#include <floatingpoint.h>
+#endif
+
+int main() {
+       fpsetmask(0L);
+}
+EOCP
+
+if ($cc $ccflags $ldflags -o test_fpsetmask test_fpsetmask.c $libs && \
+       ./test_fpsetmask) >/dev/null 2>&1 ; then
+       echo 'fpsetmask() is up to the task.' >&4
+       val="$define"
+else
+       echo 'no fpsetmask(). No problem.' >&4
+        val="$undef"
+fi
+set d_fpsetmask
+eval $setvar
+
+$rm -f test_fpset* core
+
+: see if gai_strerror exists
+set gai_strerror d_gaistr
+eval $inlibc
+
+: see if getaddrinfo exists
+set getaddrinfo d_getadinf
+eval $inlibc
+
+: see which of string.h or strings.h is needed
+echo " "
+strings=`./findhdr string.h`
+if $test "$strings" && $test -r "$strings"; then
+       echo "Using <string.h> instead of <strings.h>." >&4
+       val="$define"
+else
+       val="$undef"
+       strings=`./findhdr strings.h`
+       if $test "$strings" && $test -r "$strings"; then
+               echo "Using <strings.h> instead of <string.h>." >&4
+       else
+               echo "No string header found -- You'll surely have problems." >&4
+       fi
+fi
+set i_string
+eval $setvar
+case "$i_string" in
+"$undef") strings=`./findhdr strings.h`;;
+*)       strings=`./findhdr string.h`;;
+esac
+
+: see if getdate exists
+set getdate d_getdate
+eval $inlibc
+
+if test $d_getdate = 'define'; then
+$cat >test_getdate.c <<EOCP
+#ifdef I_STRING
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include <ctype.h>
+#ifdef __GNUC__
+/* Required to get the getdate() prototype on glibc. */
+#define __USE_XOPEN_EXTENDED
+#endif
+#include <time.h>
+int main(int argc, char **argv) {
+  int i;
+  i = getdate_err;
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_getdate test_getdate.c $libs >/dev/null 2>&1 ; then
+    d_getdate='define'
+else
+    d_getdate='undef'
+    echo "You may have getdate, but it's broken." >&4
+fi
+$rm -f test_getdate* core
+
+fi
+
+: see if gethostbyname2 exists
+set gethostbyname2 d_gethbynm2
+eval $inlibc
+
+: see if getnameinfo exists - along with constants we use
+set getnameinfo d_getnminf
+eval $inlibc
+
+if test $d_getnminf = 'define'; then
+
+echo " "
+$cat >test_getnminf.c <<EOCP
+#include <netdb.h>
+int main(int argc, char **argv) {
+  int i;
+  i = NI_MAXHOST + NI_MAXSERV + NI_NOFQDN + NI_NUMERICHOST + NI_NAMEREQD;
+  i += NI_NUMERICSERV + NI_DGRAM;
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_getnminf test_getnminf.c $libs >/dev/null 2>&1 ; then
+    d_getnminf='define'
+    echo "You've got getnameinfo and the netdb constants." >&4
+else
+    d_getnminf='undef'
+    echo "You've got getnameinfo but not the NI_* constants! Broken!" >&4
+fi
+$rm -f test_getnminf* core
+
+fi
+: see if sysctl exists
+set sysctl d_sysctl
+eval $inlibc
+: see if sysctlbyname exists
+set sysctlbyname d_sysctlbyname
+eval $inlibc
+
+: see if getpagesize exists
+set getpagesize d_getpagsz
+eval $inlibc
+
+: determine the system page size
+echo " "
+guess=' (OK to guess)'
+case "$pagesize" in
+'')
+    $cat >page.c <<EOP
+#include <stdio.h>
+extern int getpagesize();
+int main()
+{
+    printf("%d\n", getpagesize());
+}
+EOP
+    echo "Computing the granularity of memory management calls..." >&4
+    dflt='4096'
+    case "$d_getpagsz" in
+    "$define")
+        if $cc $ccflags $ldflags page.c -o page $libs >/dev/null 2>&1; then
+            dflt=`./page`
+            guess=''
+        else
+            echo "(I can't seem to compile the test program--guessing)"
+        fi
+        ;;
+    *)
+        if $cc $ccflags $ldflags page.c -o page $libs -lPW >/dev/null 2>&1; then
+            dflt=`./page`
+            guess=''
+            echo "(For your eyes only: I used the getpagesize() from -lPW.)"
+        else
+            if $contains PAGESIZE `./findhdr sys/param.h` >/dev/null 2>&1; then
+                $cat >page.c <<EOP
+#include <sys/param.h>
+#include <stdio.h>
+int main()
+{
+    printf("%d\n", PAGESIZE);
+}
+EOP
+                if $cc $ccflags $ldflags page.c -o page $libs >/dev/null 2>&1; then
+                    dflt=`./page`
+                    guess=''
+                    echo "(Using value of PAGESIZE found in <sys/param.h>.)"
+                else 
+                  if $test "x$d_sysctlbyname" = "x$define"; then
+                    $cat >page.c <<EOP
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <stdio.h>
+int main()
+{
+  int res, pgsize
+  size_t len;
+  len = sizeof(pgsize);
+  res = sysctlbyname("hw.pagesize",&pgsize,&len,NULL,0);
+  if (res < 0)
+    res = sysctlbyname("hw_pagesize",&pgsize,&len,NULL,0);
+  printf("%d\n", res ? -1 : pgsize);
+}
+EOP
+                    if $cc $ccflags $ldflags page.c -o page $libs >/dev/null 2>&1; then
+                      dflt=`./page`
+                      if $test $dflt -gt 0; then
+                       guess=''
+                       echo "(Using value from sysctlbyname)"
+                      else
+                       dflt='4096'
+                      fi
+                    fi
+                  fi
+                fi
+            fi
+        fi
+        ;;
+    esac
+    ;;
+*) dflt="$pagesize"; guess='';;
+esac
+rp="What is the system page size, in bytes$guess?"
+. ./myread
+pagesize=$ans
+$rm -f page.c page
+
+: see if getpriority exists
+set getpriority d_getprior
+eval $inlibc
+
+: see if getdtablesize exists
+echo " "
+case "$d_gettblsz" in
+$define) d_gettblsz="$undef";;
+$undef) d_gettblsz="$define";;
+esac
+if set getdtablesize val -f d_gettblsz; eval $csym; $val; then
+       echo 'getdtablesize() found.' >&4
+       d_gettblsz="$undef"
+       tablesize=''
+else
+       echo 'getdtablesize() NOT found...' >&4
+       if set ulimit val -f; eval $csym; $val; then
+               echo 'Maybe ulimit(4,0) will work...'
+               $cat >nofile.c <<'EOCP'
+#include <stdio.h>
+#ifdef GETPARAM_H
+#include <sys/param.h>
+#endif
+main()
+{
+       printf("%d %d\n",
+#ifdef NOFILE
+               NOFILE,
+#else
+               0,
+#endif
+               ulimit(4,0));
+               exit(0);
+}
+EOCP
+               if $cc $ccflags -DGETPARAM_H nofile.c -o nofile $libs >/dev/null 2>&1 \
+                       || $cc $ccflags nofile.c -o nofile $libs >/dev/null 2>&1 ; then
+                       set `./nofile`
+                       d_gettblsz=$1
+                       d_ulimit4=$2
+                       if $test "$d_ulimit4" -lt 0; then
+                               echo "Your ulimit() call doesn't tell me what I want to know."
+                               echo "We'll just use NOFILE in this case."
+                               nofile=$d_gettblsz
+                               d_gettblsz="$define"
+                               tablesize='NOFILE'
+                       else
+                               if $test "$d_gettblsz" -gt 0; then
+                               echo "Your system defines NOFILE to be $d_gettblsz, and" >&4
+                               else
+                               echo "I had trouble getting NOFILE from your system, but" >&4
+                               fi
+echo "ulimit returns $d_ulimit4 as the number of available file descriptors." >&4
+                               dflt='y';
+                               echo " "
+       rp='Should I use ulimit to get the number of available file descriptors?'
+                               . ./myread
+                               case "$ans" in
+                               y*)
+                                       nofile=$d_ulimit4
+                                       d_gettblsz="$define"
+                                       tablesize='ulimit(4, 0L)'
+                                       echo "Using ulimit(4,0)."
+                                       ;;
+                               *)
+                                       nofile=$d_gettblsz
+                                       d_gettblsz="$define"
+                                       tablesize='NOFILE'
+                                       echo "Using NOFILE."
+                                       ;;
+                               esac
+                       fi
+               else
+                       echo "Strange, I couldn't get my test program to compile."
+                       echo "We'll just use NOFILE in this case."
+                       d_gettblsz="$define"
+                       tablesize='NOFILE'
+                       nofile=''
+               fi
+       else
+               echo 'Using NOFILE instead.'
+               d_gettblsz="$define"
+               tablesize='NOFILE'
+               nofile=''
+       fi
+fi
+$rm -f nofile*
+
+: check for a new-style definitions
+echo " "
+$cat >test_gettext.c <<EOCP
+#$i_libintl I_LIBINTL
+#ifdef I_LIBINTL
+#include <libintl.h>
+#endif
+int main(int argc, char **argv) {
+   return (int) gettext ("");
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_gettext test_gettext.c $libs >/dev/null 2>&1 ; then
+    d_gettext='define'
+    echo 'Voila! You seem to have gettext for translations.' >&4
+else
+    d_gettext='undef'
+    echo "You don't seem to have gettext. Quel dommage." >&4
+fi
+$rm -f test_gettext* core
+
+: see if this is a values.h system
+set values.h i_values
+eval $inhdr
+
+: see if this is a limits.h system
+set limits.h i_limits
+eval $inhdr
+
+: see if we have HUGE, HUGE_VAL, MAXINT, or MAXDOUBLE
+echo " "
+echo "Let's try to figure out a really big double." >&4
+       $cat >d_huge.c <<EOCP
+#include <stdio.h>
+#include <math.h>
+#$i_values I_VALUES
+#$i_limits I_LIMITS
+#ifdef I_LIMITS
+#include <limits.h>
+#else
+#ifdef I_VALUES
+#include <values.h>
+#endif
+#endif
+
+int main()
+{
+       printf("%f\n",HUGE_VAL);
+}
+EOCP
+       if $cc $ccflags $ldflags d_huge.c -o d_huge $libs >/dev/null 2>&1; then
+               val="$define"
+               set d_huge_val; eval $setvar
+               val="$undef"
+               set d_huge; eval $setvar
+               set d_maxdouble; eval $setvar
+               echo "Great. Your system defines HUGE_VAL." >&4
+       else
+               val="$undef"
+               set d_huge_val; eval $setvar
+               echo "Your system doesn't have HUGE_VAL. Maybe HUGE?" >&4
+       $cat >d_huge.c <<EOCP
+#include <stdio.h>
+#include <math.h>
+#$i_values I_VALUES
+#$i_limits I_LIMITS
+#ifdef I_LIMITS
+#include <limits.h>
+#else
+#ifdef I_VALUES
+#include <values.h>
+#endif
+#endif
+int main()
+{
+       printf("%f\n",HUGE);
+}
+EOCP
+
+       if $cc $ccflags $ldflags d_huge.c -o d_huge $libs >/dev/null 2>&1; then
+               val="$define"
+               set d_huge; eval $setvar
+               val="$undef"
+               set d_maxdouble; eval $setvar
+               echo "Good. Your system defines HUGE." >&4
+       else
+               val="$undef"
+               set d_huge; eval $setvar
+               echo "Hmm. Your system doesn't define HUGE. MAX_DOUBLE?" >&4
+               $cat >d_huge.c <<EOCP
+#include <stdio.h>
+#include <math.h>
+#$i_limits I_LIMITS
+#ifdef I_LIMITS
+#include <limits.h>
+#else
+#$i_values I_VALUES
+#ifdef I_VALUES
+#include <values.h>
+#endif
+#endif
+int main()
+{
+       printf("%f\n",MAX_DOUBLE);
+}
+EOCP
+
+               if $cc $ccflags $ldflags d_huge.c -o d_huge $libs >/dev/null 2>&1; then
+                       val="$define"
+                       echo "Ok, you've got MAXDOUBLE." >&4
+               else
+                       val="$undef"
+                       echo "Nope, no MAXDOUBLE either. We'll guess one." >&4
+               fi
+               set d_maxdouble; eval $setvar
+       fi
+fi
+
+
+echo " "
+echo "Let's try to figure out a really big int, too." >&4
+               $cat >d_huge.c <<EOCP
+#include <stdio.h>
+#include <math.h>
+#$i_limits I_LIMITS
+#ifdef I_LIMITS
+#include <limits.h>
+#else
+#$i_values I_VALUES
+#ifdef I_VALUES
+#include <values.h>
+#endif
+#endif
+int main()
+{
+       printf("%d\n",INT_MAX);
+}
+EOCP
+
+if $cc $ccflags $ldflags d_huge.c -o d_huge $libs >/dev/null 2>&1; then
+       val="$define"
+       set d_int_max; eval $setvar
+       val="$undef"
+       set d_maxint; eval $setvar
+       echo "Ok, you've got INT_MAX." >&4
+else
+       val="$undef"
+       echo "No INT_MAX. Maybe MAXINT?" >&4
+       set d_int_max; eval $setvar
+       $cat >d_huge.c <<EOCP
+#include <stdio.h>
+#include <math.h>
+#$i_limits I_LIMITS
+#ifdef I_LIMITS
+#include <limits.h>
+#else
+#$i_values I_VALUES
+#ifdef I_VALUES
+#include <values.h>
+#endif
+#endif
+int main()
+{
+       printf("%d\n",MAXINT);
+}
+EOCP
+       if $cc $ccflags $ldflags d_huge.c -o d_huge $libs >/dev/null 2>&1; then
+               val="$define"
+               echo "Ok, you've got MAXINT." >&4
+               set d_maxint; eval $setvar
+       else
+               val="$undef"
+               echo "No MAXINT. I give up. I'll take a guess." >&4
+               set d_maxint; eval $setvar
+       fi
+fi
+
+$rm -f d_huge*
+
+: check for a safe ieee
+echo " "
+echo "Let's see if your math functions handle errors nicely..." >&4
+$cat >test_ieee.c <<'EOCP'
+#include <stdio.h>
+#include <math.h>
+
+int main() {
+       double x;
+       x = pow(-1,.5);
+        x = pow(10000,10000);
+       printf("define\n");
+       exit(0);
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_ieee test_ieee.c -lm >/dev/null 2>&1 ; then
+    d_ieee=`./test_ieee`
+    if test $d_ieee = define ; then
+       echo "Great! They can." >&4
+    else
+       echo "Nope, they crash and burn." >&4
+       d_ieee='undef'
+    fi
+else
+       $cat <<EOM
+(I can't seem to get my test program to work. We'll play it safe
+ and assume that your math functions don't handle errors nicely.)
+EOM
+    d_ieee='undef'
+fi
+$rm -f test_ieee* core
+
+: see inet_pton exists
+set inet_pton d_in2p
+eval $inlibc
+
+: index or strchr
+echo " "
+if set index val -f; eval $csym; $val; then
+       if set strchr val -f d_strchr; eval $csym; $val; then
+               if $contains strchr "$strings" >/dev/null 2>&1 ; then
+                       val="$define"
+                       vali="$define"
+                       echo "strchr() and index() found." >&4
+               else
+                       val="$undef"
+                       vali="$define"
+                       echo "index() found." >&4
+               fi
+       else
+               val="$undef"
+               vali="$define"
+               echo "index() found." >&4
+       fi
+else
+       if set strchr val -f d_strchr; eval $csym; $val; then
+               val="$define"
+               vali="$undef"
+               echo "strchr() found." >&4
+       else
+               echo "No index() or strchr() found!" >&4
+               val="$undef"
+               vali="$undef"
+       fi
+fi
+set d_strchr; eval $setvar
+val="$vali"
+set d_index; eval $setvar
+
+: check for internet mailer
+dflt=y
+case "$d_internet" in
+"$undef") dflt=n;;
+esac
+cat <<EOM
+Most mailers can deliver mail to addresses of the INTERNET persuasion,
+such as user@host.edu.  Some older mailers, however, require the complete
+path to the destination to be specified in the address.
+
+EOM
+rp="Does your mailer understand INTERNET addresses?"
+. ./myread
+case "$ans" in
+y*) val="$define";;
+*)  val="$undef";;
+esac
+set d_internet
+eval $setvar
+
+: see if sys/types.h has to be included
+set sys/types.h i_systypes
+eval $inhdr
+
+: see if this is a sys/socket.h system
+set sys/socket.h i_syssock
+eval $inhdr
+
+: see if this is a netinet/in.h or sys/in.h system
+set netinet/in.h i_niin sys/in.h i_sysin
+eval $inhdr
+
+: see if we have struct sockaddr_in6.
+echo " "
+$cat >d_ipv6.c <<EOCP
+#$i_systypes I_SYS_TYPES
+#$i_syssock I_SYS_SOCK
+#$i_niin I_NETINET_IN
+#$i_sysin I_SYS_IN
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef I_SYS_SOCK
+#include <sys/socket.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#else
+#ifdef I_SYS_IN
+#include <sys/in.h>
+#endif
+#endif
+#include <stdio.h>
+main()
+{
+       struct sockaddr_in6 test;
+       int foo = AF_INET6;
+       printf("%d\n", foo);
+}
+EOCP
+if $cc $ccflags $ldflags d_ipv6.c -o d_ipv6 $libs >/dev/null 2>&1; then
+       val="$define"
+       set d_ipv6; eval $setvar
+       echo "IPv6 structures found." >&4
+else
+       val="$undef"
+       set d_ipv6; eval $setvar
+       echo "No IPv6 structures found. No problem." >&4
+fi
+: see if setitimer exists
+set setitimer d_itimer
+eval $inlibc
+
+socketlib=''
+sockethdr=''
+: see whether socket exists
+echo " "
+$echo $n "Hmm... $c" >&4
+if set socket val -f d_socket; eval $csym; $val; then
+       echo "Looks like you have Berkeley networking support." >&4
+       d_socket="$define"
+       if set setsockopt val -f; eval $csym; $val; then
+               d_oldsock="$undef"
+       else
+               echo "...but it uses the old 4.1c interface, rather than 4.2" >&4
+               d_oldsock="$define"
+       fi
+else
+       if $contains socklib libc.list >/dev/null 2>&1; then
+               echo "Looks like you have Berkeley networking support." >&4
+               d_socket="$define"
+               : we will have to assume that it supports the 4.2 BSD interface
+               d_oldsock="$undef"
+       else
+               echo "You don't have Berkeley networking in libc$_a..." >&4
+               if test -f /usr/lib/libnet$_a; then
+                       ( (nm $nm_opt /usr/lib/libnet$_a | eval $nm_extract) ||  \
+                       ar t /usr/lib/libnet$_a) 2>/dev/null >> libc.list
+                       if $contains socket libc.list >/dev/null 2>&1; then
+                       echo "...but the Wollongong group seems to have hacked it in." >&4
+                               socketlib="-lnet"
+                               sockethdr="-I/usr/netinclude"
+                               d_socket="$define"
+                               if $contains setsockopt libc.list >/dev/null 2>&1; then
+                                       d_oldsock="$undef"
+                               else
+                                       echo "...using the old 4.1c interface, rather than 4.2" >&4
+                                       d_oldsock="$define"
+                               fi
+                       else
+                               echo "or even in libnet$_a, which is peculiar." >&4
+                               d_socket="$undef"
+                               d_oldsock="$undef"
+                       fi
+               else
+                       echo "or anywhere else I see." >&4
+                       d_socket="$undef"
+                       d_oldsock="$undef"
+               fi
+       fi
+fi
+
+: see if socketpair exists
+set socketpair d_sockpair
+eval $inlibc
+
+: see if this is a netinet/tcp.h system
+set netinet/tcp.h i_nitcp
+eval $inhdr
+
+: see if setsockopt with SO_KEEPALIVE works as advertised
+echo " "
+case "$d_oldsock" in
+"$undef")
+               echo "OK, let's see if SO_KEEPALIVE works as advertised..." >&4
+               $cat > socket.c <<EOP
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#$i_nitcp I_NETINET_TCP
+#ifdef I_NETINET_TCP
+#include <netinet/tcp.h>
+#endif
+#include <netdb.h>
+
+main()
+{
+       int s = socket(AF_INET, SOCK_STREAM, 0);
+       int val = 1;
+       if (s == -1)
+               exit(1);
+       if (-1 == setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)))
+               exit(2);
+#ifdef I_NETINET_TCP
+       val = 1;
+       if (-1 == setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)))
+               exit(3);
+#endif
+       exit(0);
+}
+EOP
+               if $cc $ccflags $sockethdr socket.c -o socket $libs \
+                       $socketlib >/dev/null 2>&1; then
+                       ./socket >/dev/null 2>&1
+                       case $? in
+                       0) echo "Yes, it does!"
+                               val="$define"
+                               val2="$i_nitcp"
+                       ;;
+                       1) $cat <<EOM
+(Something went wrong -- Assuming SO_KEEPALIVE is broken)
+EOM
+                               val="$undef"
+                               val2="$undef"
+                       ;;
+                       2) echo "No, it doesn't.  Don't trust your manuals!!"
+                               val="$undef"
+                               val2="$undef"
+                       ;;
+                       3) echo "It does, but TCP_KEEPIDLE doesn't."
+                               val="$define"
+                               val2="$undef"
+                       ;;
+                       esac
+               else
+                       cat <<EOM
+(I can't compile the test program -- Assuming SO_KEEPALIVE is broken)
+EOM
+                       val="$undef"
+                       val2="$undef"
+               fi
+       ;;
+*) cat <<EOM
+As you have an old socket interface, you can't have heard of SO_KEEPALIVE.
+EOM
+       val="$undef"
+       val2="$undef";;
+esac
+set d_keepalive
+eval $setvar
+val="$val2"
+set d_keepidle
+eval $setvar
+$rm -f socket socket.c
+
+echo " "
+: see if we have sigaction
+if set sigaction val -f d_sigaction; eval $csym; $val; then
+       echo 'sigaction() found.' >&4
+       val="$define"
+else
+       echo 'sigaction NOT found.' >&4
+       val="$undef"
+fi
+$cat > set.c <<'EOP'
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+main()
+{
+    struct sigaction act, oact;
+}
+EOP
+if $cc $ccflags $ldflags -o set set.c $libs >/dev/null 2>&1; then
+       :
+else
+       echo "But you don't seem to have a useable struct sigaction." >&4
+       val="$undef"
+fi
+set d_sigaction; eval $setvar
+$rm -f set set$_o set.c
+
+: see if signals are kept
+val="$undef"
+echo " "
+echo "Checking to see if signal handlers stick around..." >&4
+if test ${d_sigaction} = "$define"; then
+  echo "You've got sigaction, so we can force 'em to." >&4
+  val="$define"
+else
+  $cat >try.c <<'EOCP'
+foo() {}
+
+int main()
+{
+       signal(2, foo);
+       kill(getpid(), 2);
+       kill(getpid(), 2);
+       printf("abc\n");
+}
+EOCP
+  if $cc -o try $ccflags $ldflags try.c >/dev/null 2>&1; then
+       sh -c ./try >try.out 2>/dev/null
+       if $contains abc try.out >/dev/null 2>&1; then
+               echo "Yes, they do."
+               val="$define";
+       else
+               echo "No, they don't."
+       fi
+  else
+       $echo $n "(I can't seem to compile the test program. Assuming $c"
+       if ./bsd; then
+               echo "they do.)"
+               val="$define"
+       else
+               echo "they don't.)"
+       fi
+  fi
+fi
+set d_keepsig
+eval $setvar
+$rm -f try*
+
+: random, lrand48, or rand
+set random d_random
+eval $inlibc
+set lrand48 d_lrand48
+eval $inlibc
+set rand d_rand
+eval $inlibc
+
+: see if memcpy exists
+set memcpy d_memcpy
+eval $inlibc
+set memmove d_memmove
+eval $inlibc
+
+: see if memset exists
+set memset d_memset
+eval $inlibc
+
+: see if we should include -lmysqlclient
+echo " "
+
+d_mysql="$undef"
+
+if $test "x$no_mysql" = "x"; then
+
+  libmysqlclient="-lmysqlclient"
+
+  $cat > test_mysql.c <<EOM
+#include <stdio.h>
+#include <stdlib.h>
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+
+int main(int argc, char **argv) {
+   printf("Your mysql is version %s\n",mysql_get_client_info());
+   exit(0);
+}
+EOM
+
+  if $cc $ccflags $ldflags -o test_mysql test_mysql.c $libs $libmysqlclient >/dev/null 2>&1 ;
+  then
+      echo 'You have mysql...' >&4
+      version=`./test_mysql`
+      if $test $? -eq 0; then
+       echo "$version" >&4
+        d_mysql="$define"
+      else
+        echo "...but my test program didn't run correctly." >&4
+        libmysqlclient=''
+      fi
+  else
+      echo "You don't seem to have mysql." >&4
+      libmysqlclient=''
+  fi
+  $rm -f test_mysql* core
+
+else
+
+  echo "Skipping mysql tests." >&4
+  libmysqlclient=''
+
+fi
+
+: check for a new-style definitions
+echo " "
+$cat >test_newstyle.c <<'EOCP'
+#include <stdio.h>
+int main(int argc, char **argv) {
+       exit(0);
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_newstyle test_newstyle.c >/dev/null 2>&1 ; then
+    d_newstyle='define'
+    echo 'Your compiler accepts new-style function definitions.' >&4
+else
+    d_newstyle='undef'
+    echo "Your compiler DOESN'T accept new-style function definitions." >&4
+fi
+$rm -f test_newstyle* core
+
+: Locate the flags for 'open()'
+echo " "
+$cat >open3.c <<'EOCP'
+#include <sys/types.h>
+#ifdef I_FCNTL
+#include <fcntl.h>
+#endif
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+int main() {
+       if(O_RDONLY);
+#ifdef O_TRUNC
+       exit(0);
+#else
+       exit(1);
+#endif
+}
+EOCP
+: check sys/file.h first to get FREAD on Sun
+if $test "`./findhdr sys/file.h`" && \
+               $cc $ccflags $ldflags "-DI_SYS_FILE" open3.c -o open3 $libs >/dev/null 2>&1 ; then
+       h_sysfile=true;
+       echo "<sys/file.h> defines the O_* constants..." >&4
+       if ./open3; then
+               echo "and you have the 3 argument form of open()." >&4
+               val="$define"
+       else
+               echo "but not the 3 argument form of open().  Oh, well." >&4
+               val="$undef"
+       fi
+elif $test "`./findhdr fcntl.h`" && \
+               $cc $ccflags $ldflags "-DI_FCNTL" open3.c -o open3 $libs >/dev/null 2>&1 ; then
+       h_fcntl=true;
+       echo "<fcntl.h> defines the O_* constants..." >&4
+       if ./open3; then
+               echo "and you have the 3 argument form of open()." >&4
+               val="$define"
+       else
+               echo "but not the 3 argument form of open().  Oh, well." >&4
+               val="$undef"
+       fi
+else
+       val="$undef"
+       echo "I can't find the O_* constant definitions!  You got problems." >&4
+fi
+set d_open3
+eval $setvar
+$rm -f open3*
+
+: see if we should include -lssl and -lcrypto
+echo " "
+if $test -r /usr/lib/libssl$_a || \
+   $test -r /lib/libssl$_a || \
+   $test -r /usr/local/lib/libssl$_a ; then
+       echo "-lssl found." >&4
+       libssl='-lssl'
+else
+       xxx=`./loc libssl$_a x $libpth`
+       case "$xxx" in
+       x)
+               echo "No ssl library found." >&4
+               libssl=''
+               ;;
+       *)
+               echo "SSL library found in $xxx." >&4
+               libssl="$xxx"
+               ;;
+       esac
+fi
+
+if $test -r /usr/lib/libcrypto$_a || \
+   $test -r /lib/libcrypto$_a || \
+   $test -r /usr/local/lib/libcrypto$_a ; then
+       echo "-lcrypto found." >&4
+       libcrypto='-lcrypto'
+else
+       xxx=`./loc libcrypto$_a x $libpth`
+       case "$xxx" in
+       x)
+               echo "No crypto library found." >&4
+               libcrypto=''
+               ;;
+       *)
+               echo "SSL crypto library found in $xxx." >&4
+               libcrypto="$xxx"
+               ;;
+       esac
+fi
+
+d_openssl="$undef"
+case "x$libssl$libcrypto" in
+       x)
+               ;;
+       *)
+$cat > test_openssl.c <<EOM
+#include <stdio.h>
+#include <stdlib.h>
+#include <openssl/opensslv.h>
+int main(int argc, char **argv) {
+   printf("Your openssl is version %lx\n",OPENSSL_VERSION_NUMBER);
+   exit(OPENSSL_VERSION_NUMBER < 0x00906000L);
+}
+EOM
+
+if $cc $ccflags $ldflags -o test_openssl test_openssl.c $libs $libssl $libcrypto >/dev/null 2>&1 ;
+then
+    echo 'You have openssl...' >&4
+    version=`./test_openssl`
+    if $test $? -eq 0; then
+      d_openssl="$define"
+      echo '...and at least version 0.9.6. Great.' >&4
+    else
+      echo '...but not at least version 0.9.6.' >&4
+      libssl=''
+      libcrypto=''
+    fi
+else
+    echo "You don't seem to have openssl." >&4
+    libssl=''
+    libcrypto=''
+fi
+$rm -f test_openssl* core
+
+       ;;      
+esac
+: see if rename exists
+set rename d_rename
+eval $inlibc
+
+: see if getrlimit exists
+set getrlimit d_rlimit
+eval $inlibc
+
+: see if getrusage exists
+set getrusage d_rusage
+eval $inlibc
+
+: see if select exists
+set select d_select
+eval $inlibc
+
+: have we got sendmail?
+echo " "
+echo "Checking to see if we can use sendmail..."
+if $test -f $sendmail; then
+        echo "Looks like sendmail is in $sendmail"
+       val="$define"
+else
+       echo "Nope, out of luck."
+       val="$undef"
+fi
+set d_sendmail
+eval $setvar
+
+: see if setlocale exists
+set setlocale d_setlocale
+eval $inlibc
+
+: see if setpgid exists
+set setpgid d_setpgid
+eval $inlibc
+
+: see if setpriority exists
+set setpriority d_setprior
+eval $inlibc
+
+: see if we have SIGCHLD, SIGCLD, or both
+echo " "
+echo "How should a child signal a parent?" >&4
+$cat >d_sigchld.c <<EOCP
+#include <signal.h>
+#include <stdio.h>
+int main()
+{
+       printf("%d\n",SIGCHLD);
+}
+EOCP
+if $cc $ccflags $ldflags d_sigchld.c -o d_sigchld $libs >/dev/null 2>&1; then
+       val="$define"
+       set d_sigchld; eval $setvar
+       echo "SIGCHLD works."
+else
+       val="$undef"
+       set d_sigchld; eval $setvar
+       echo "SIGCHLD doesn't work."
+fi
+
+$cat >d_sigchld.c <<EOCP
+#include <signal.h>
+#include <stdio.h>
+int main()
+{
+       printf("%d\n",SIGCLD);
+}
+EOCP
+if $cc $ccflags $ldflags d_sigchld.c -o d_sigchld $libs >/dev/null 2>&1; then
+       val="$define"
+       set d_sigcld; eval $setvar
+       echo "SIGCLD works."
+else
+       val="$undef"
+       set d_sigcld; eval $setvar
+       echo "SIGCLD doesn't work."
+fi
+: Cruising for prototypes
+echo " "
+echo "Checking out function prototypes..." >&4
+$cat >prototype.c <<'EOCP'
+main(int argc, char *argv[]) {
+       exit(0);}
+EOCP
+if $cc $ccflags -c prototype.c >prototype.out 2>&1 ; then
+       echo "Your C compiler appears to support function prototypes."
+       val="$define"
+else
+       echo "Your C compiler doesn't seem to understand function prototypes."
+       val="$undef"
+fi
+set prototype
+eval $setvar
+$rm -f prototype*
+
+: see if signal is declared as pointer to function returning int or void
+echo " "
+xxx=`./findhdr signal.h`
+$test "$xxx" && $cppstdin $cppminus $cppflags < $xxx >$$.tmp 2>/dev/null
+if $contains 'int.*\*[         ]*signal' $$.tmp >/dev/null 2>&1 ; then
+       echo "You have int (*signal())() instead of void." >&4
+       val="$undef"
+elif $contains 'void.*\*[      ]*signal' $$.tmp >/dev/null 2>&1 ; then
+       echo "You have void (*signal())() instead of int." >&4
+       val="$define"
+elif $contains 'extern[        ]*[(\*]*signal' $$.tmp >/dev/null 2>&1 ; then
+       echo "You have int (*signal())() instead of void." >&4
+       val="$undef"
+else
+       case "$d_voidsig" in
+       '')
+       echo "I can't determine whether signal handler returns void or int..." >&4
+               dflt=void
+               rp="What type does your signal handler return?"
+               . ./myread
+               case "$ans" in
+               v*) val="$define";;
+               *) val="$undef";;
+               esac;;
+       "$define")
+               echo "As you already told me, signal handler returns void." >&4;;
+       *)
+               echo "As you already told me, signal handler returns int." >&4;;
+       esac
+fi
+set d_voidsig
+eval $setvar
+case "$d_voidsig" in
+"$define") signal_t="void";;
+*) signal_t="int";;
+esac
+$rm -f $$.tmp
+
+: can we prototype signal ourselves
+val="$undef"
+echo " "
+echo "Checking to see if we can declare or prototype signal()..." >&4
+if test ${prototype} = "$define"; then
+  $cat >try.c <<EOCP
+#include <stdio.h>
+#include <signal.h>
+#ifdef signal
+#undef signal
+#endif
+typedef ${signal_t} (*Sigfunc) (int);
+extern Sigfunc signal (int signo, Sigfunc func);
+int main()
+{ printf("no op\n"); }
+EOCP
+else
+  $cat >try.c <<EOCP
+#include <stdio.h>
+#include <signal.h>
+#ifdef signal
+#undef signal
+#endif
+typedef ${signal_t} (*Sigfunc) ();
+extern Sigfunc signal ();
+int main()
+{ printf("no op\n"); }
+EOCP
+fi
+if $cc -o try $ccflags $ldflags try.c >/dev/null 2>&1; then
+       $echo "Looks like we can."
+       val="$define";
+else
+       $echo "Looks like we can't."
+fi
+set d_signalproto
+eval $setvar
+$rm -f try*
+
+echo " "
+: see if we have sigprocmask
+if set sigprocmask val -f d_sigprocmask; eval $csym; $val; then
+       echo 'sigprocmask() found.' >&4
+       val="$define"
+else
+       echo 'sigprocmask NOT found.' >&4
+       val="$undef"
+fi
+$cat > set.c <<'EOP'
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+main()
+{
+  sigset_t mask, oldmask;
+  sigprocmask(SIG_SETMASK, &mask, &oldmask);
+}
+EOP
+if $cc $ccflags $ldflags -o set set.c $libs >/dev/null 2>&1; then
+       :
+else
+       echo "But you don't seem to have a working sigprocmask." >&4
+       val="$undef"
+fi
+set d_sigprocmask; eval $setvar
+$rm -f set set$_o set.c
+
+: see if snprintf exists
+echo " "
+if set snprintf val -f d_snprintf; eval $csym; $val; then
+       echo 'snprintf() found.' >&4
+       val="$define"
+else
+       echo 'snprintf() NOT found.' >&4
+       val="$undef"
+fi
+set d_snprintf
+eval $setvar
+
+: see if we have socklen_t.
+echo " "
+$cat >d_socklen.c <<EOCP
+#$i_systypes I_SYS_TYPES
+#$i_syssock I_SYS_SOCKET
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#include <stdio.h>
+main()
+{
+       socklen_t test = 1;
+       printf("%d\n",test);
+}
+EOCP
+if $cc $ccflags $ldflags d_socklen.c -o d_socklen $libs >/dev/null 2>&1; then
+       val="$define"
+       set d_socklen; eval $setvar
+       echo "socklen_t works." >&4
+else
+       val="$undef"
+       set d_socklen; eval $setvar
+       echo "socklen_t doesn't work. Using int." >&4
+fi
+: see if strcasecmp exists
+set strcasecmp d_strccmp
+eval $inlibc
+
+: see if strcoll exists
+set strcoll d_strcoll
+eval $inlibc
+
+: see if strdup exists
+set strdup d_strdup
+eval $inlibc
+
+: see if strxfrm exists
+set strxfrm d_strxfrm
+eval $inlibc
+
+: see if sysconf exists
+set sysconf d_sysconf
+eval $inlibc
+
+: see if tcl exists
+echo " "
+ok=0
+
+if $test -f src/tcl.c -o -f ../src/tcl.c ; then
+  echo "I'm tickled pink - we have the penn TCL source." >&4
+  echo "Let's see if we have TCL." >&4
+
+: see if tcl.h can be included
+  set tcl.h i_tcl
+  eval $inhdr
+  if $test $i_tcl = "$define"; then
+    echo "We have tcl.h, that's a start." >&4
+    : see if the tcl library was found earlier
+    case $libs in 
+      *tcl*)   val="$define"
+               set d_tcl
+               eval $setvar
+              echo "We have the TCL library, too." >&4
+               ok=1
+          ;;
+      *)       echo "Oops, no TCL library. Check your TCL installation." >&4
+          ;;
+    esac
+  else
+    echo "I can't find tcl.h. Check your TCL installation." >&4
+  fi
+fi
+
+if $test $ok = 0; then
+  nlibs=""
+  for l in $libs; do
+    case $l in 
+      *tcl*) ;;
+      *dl*) ;;
+      *ld*) ;;
+      *) nlibs="$nlibs$l " ;;
+    esac
+  done
+  libs="$nlibs"
+  val="$undef"
+  set d_tcl
+  eval $setvar
+  val="$undef"
+  set i_tcl
+  eval $setvar
+else
+  echo "TCL will be compiled and included." >&4
+fi
+: check for a new-style definitions
+echo " "
+$cat >test_textdomain.c <<EOCP
+#$i_libintl I_LIBINTL
+#ifdef I_LIBINTL
+#include <libintl.h>
+#endif
+int main(int argc, char **argv) {
+   return (int) textdomain ("");
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_textdomain test_textdomain.c $libs >/dev/null 2>&1 ; then
+    d_textdomain='define'
+    echo 'Mabuhay! You seem to have textdomain for translations.' >&4
+else
+    d_textdomain='undef'
+    echo "You don't seem to have textdomain. Sayang." >&4
+fi
+$rm -f test_textdomain* core
+
+: see if timelocal exists
+set timelocal d_timelocal
+eval $inlibc
+
+: check for a safe toupper
+echo " "
+echo "Checking out your toupper()..." >&4
+$cat >test_toupper.c <<'EOCP'
+#include <stdio.h>
+#include <ctype.h>
+
+int main() {
+       char c = 'A';
+       if (c == toupper(c)) {
+         printf("define\n");
+       } else {
+         printf("undef\n");
+        }
+       exit(0);
+}
+EOCP
+
+if $cc $ccflags $ldflags -o test_toupper test_toupper.c >/dev/null 2>&1 ; then
+    d_toupper=`./test_toupper`
+    if test $d_toupper = define ; then
+       echo "It's safe toupper uppers." >&4
+    else
+       echo "We can't toupper uppers." >&4
+    fi
+else
+       $cat <<EOM
+(I can't seem to get my test program to work. We'll play it safe.)
+EOM
+    d_toupper='undef'
+fi
+$rm -f test_toupper* core
+
+: uptime
+echo " "
+if $test -x $uptime ; then
+       val="$define"
+else
+       val="$undef"
+fi
+set d_uptime; eval $setvar
+
+: see if /dev/urandom is present
+echo " "
+if $test -c /dev/urandom ; then
+       val="$define"
+else
+       val="$undef"
+fi
+set d_urandom; eval $setvar
+
+: see if union wait is available
+echo " "
+set X $cppflags
+shift
+flags=''
+also=''
+for f in $*; do
+       case "$f" in
+       *NO_PROTO*) ;;
+       *) flags="$flags $f";;
+       esac
+done
+xxx="`./findhdr sys/wait.h`"
+case "x$xxx" in
+x) xxx=/dev/null
+esac
+$cat "$xxx" | $cppstdin $flags $cppminus >wait.out 2>/dev/null
+if $contains 'union.*wait.*{' wait.out >/dev/null 2>&1 ; then
+       echo "Looks like your <sys/wait.h> knows about 'union wait'..." >&4
+       val="$define"
+       also='also '
+       if $contains 'extern.*wait[     ]*([    ]*int' wait.out >/dev/null 2>&1
+       then
+               echo "But wait() seems to expect an 'int' pointer (POSIX way)." >&4
+               val="$undef"
+               also=''
+       elif $contains 'extern.*wait[   ]*([    ]*union' wait.out >/dev/null 2>&1
+       then
+               echo "And indeed wait() expects an 'union wait' pointer (BSD way)." >&4
+       else
+               echo "So we'll use that for wait()." >&4
+       fi
+else
+       echo "No trace of 'union wait' in <sys/wait.h>..." >&4
+       val="$undef"
+       echo "Your wait() should be happy with a plain 'int' pointer." >&4
+fi
+set d_uwait
+eval $setvar
+$rm -f wait.out
+
+: get C preprocessor symbols handy
+echo " "
+$echo $n "Hmm... $c"
+echo $al | $tr ' ' '\012' >Cppsym.know
+$cat <<EOSS >Cppsym
+$startsh
+case "\$1" in
+-l) list=true
+       shift
+       ;;
+esac
+unknown=''
+case "\$list\$#" in
+1|2)
+       for sym do
+               if $contains "^\$1$" Cppsym.true >/dev/null 2>&1; then
+                       exit 0
+               elif $contains "^\$1$" Cppsym.know >/dev/null 2>&1; then
+                       :
+               else
+                       unknown="\$unknown \$sym"
+               fi
+       done
+       set X \$unknown
+       shift
+       ;;
+esac
+case \$# in
+0) exit 1;;
+esac
+echo \$* | $tr ' ' '\012' | $sed -e 's/\(.*\)/\\
+#ifdef \1\\
+exit 0; _ _ _ _\1\\     \1\\
+#endif\\
+/' >Cppsym\$\$
+echo "exit 1; _ _ _" >>Cppsym\$\$
+$cppstdin $cppminus <Cppsym\$\$ | $grep '^exit [01]; _ _'  >Cppsym2\$\$
+case "\$list" in
+true) $awk 'NF > 5 {print substr(\$6,2,100)}' <Cppsym2\$\$ ;;
+*)
+       sh Cppsym2\$\$
+       status=\$?
+       ;;
+esac
+$rm -f Cppsym\$\$ Cppsym2\$\$
+exit \$status
+EOSS
+chmod +x Cppsym
+$eunicefix Cppsym
+./Cppsym -l $al | $sort | $grep -v '^$' >Cppsym.true
+
+: now check the C compiler for additional symbols
+$cat >ccsym <<EOS
+$startsh
+$cat >tmp.c <<EOF
+extern int foo;
+EOF
+for i in \`$cc -v -c tmp.c 2>&1\`
+do
+       case "\$i" in
+       -D*) echo "\$i" | $sed 's/^-D//';;
+       -A*) $test "$gccversion" && echo "\$i" | $sed 's/^-A\(.*\)(\(.*\))/\1=\2/';;
+       esac
+done
+$rm -f try.c
+EOS
+chmod +x ccsym
+$eunicefix ccsym
+./ccsym | $sort | $uniq >ccsym.raw
+$awk '/\=/ { print $0; next }
+       { print $0"=1" }' ccsym.raw >ccsym.list
+$awk '{ print $0"=1" }' Cppsym.true >ccsym.true
+$comm -13 ccsym.true ccsym.list >ccsym.own
+$comm -12 ccsym.true ccsym.list >ccsym.com
+$comm -23 ccsym.true ccsym.list >ccsym.cpp
+also=''
+symbols='symbols'
+if $test -z ccsym.raw; then
+       echo "Your C compiler doesn't seem to define any symbol!" >&4
+       echo " "
+       echo "However, your C preprocessor defines the following ones:"
+       $cat Cppsym.true
+else
+       if $test -s ccsym.com; then
+               echo "Your C compiler and pre-processor define these symbols:"
+               $sed -e 's/\(.*\)=.*/\1/' ccsym.com
+               also='also '
+               symbols='ones'
+               $test "$silent" || sleep 1
+       fi
+       if $test -s ccsym.cpp; then
+               $test "$also" && echo " "
+               echo "Your C pre-processor ${also}defines the following $symbols:"
+               $sed -e 's/\(.*\)=.*/\1/' ccsym.cpp
+               also='further '
+               $test "$silent" || sleep 1
+       fi
+       if $test -s ccsym.own; then
+               $test "$also" && echo " "
+               echo "Your C compiler ${also}defines the following cpp variables:"
+               $sed -e 's/\(.*\)=1/\1/' ccsym.own
+               $sed -e 's/\(.*\)=.*/\1/' ccsym.own | $uniq >>Cppsym.true
+               $test "$silent" || sleep 1
+       fi
+fi
+$rm -f ccsym*
+
+: see if this is a termio system
+val="$undef"
+val2="$undef"
+val3="$undef"
+if $test "`./findhdr termios.h`"; then
+       set tcsetattr i_termios
+       eval $inlibc
+       val3="$i_termios"
+fi
+echo " "
+case "$val3" in
+"$define") echo "You have POSIX termios.h... good!" >&4;;
+*) if ./Cppsym pyr; then
+               case "`/bin/universe`" in
+               ucb) if $test "`./findhdr sgtty.h`"; then
+                               val2="$define"
+                               echo "<sgtty.h> found." >&4
+                       else
+                               echo "System is pyramid with BSD universe."
+                               echo "<sgtty.h> not found--you could have problems." >&4
+                       fi;;
+               *) if $test "`./findhdr termio.h`"; then
+                               val="$define"
+                               echo "<termio.h> found." >&4
+                       else
+                               echo "System is pyramid with USG universe."
+                               echo "<termio.h> not found--you could have problems." >&4
+                       fi;;
+               esac
+       elif ./usg; then
+               if $test "`./findhdr termio.h`"; then
+                       echo "<termio.h> found." >&4
+                       val="$define"
+               elif $test "`./findhdr sgtty.h`"; then
+                       echo "<sgtty.h> found." >&4
+                       val2="$define"
+               else
+echo "Neither <termio.h> nor <sgtty.h> found--you could have problems." >&4
+               fi
+       else
+               if $test "`./findhdr sgtty.h`"; then
+                       echo "<sgtty.h> found." >&4
+                       val2="$define"
+               elif $test "`./findhdr termio.h`"; then
+                       echo "<termio.h> found." >&4
+                       val="$define"
+               else
+echo "Neither <sgtty.h> nor <termio.h> found--you could have problems." >&4
+               fi
+       fi;;
+esac
+set i_termio; eval $setvar
+val=$val2; set i_sgtty; eval $setvar
+val=$val3; set i_termios; eval $setvar
+
+: see if ioctl defs are in sgtty, termio, sys/filio or sys/ioctl
+set sys/filio.h i_sysfilio
+eval $inhdr
+echo " "
+if $test "`./findhdr sys/ioctl.h`"; then
+       val="$define"
+       echo '<sys/ioctl.h> found.' >&4
+else
+       val="$undef"
+       if $test $i_sysfilio = "$define"; then
+           echo '<sys/ioctl.h> NOT found.' >&4
+       else
+               $test $i_sgtty = "$define" && xxx="sgtty.h"
+               $test $i_termio = "$define" && xxx="termio.h"
+               $test $i_termios = "$define" && xxx="termios.h"
+echo "No <sys/ioctl.h> found, assuming ioctl args are defined in <$xxx>." >&4
+       fi
+fi
+set i_sysioctl
+eval $setvar
+
+: check how to void tty association
+echo " "
+case "$i_sysioctl" in
+"$define") xxx='sys/ioctl.h';;
+esac
+$cat > tcio.c <<EOM
+#include <sys/types.h> /* Just in case */
+#include <$xxx>
+
+int main()
+{
+#ifdef TIOCNOTTY
+       exit(0);
+#else
+       exit(1);
+#endif
+}
+EOM
+if ($cc -o tcio tcio.c && ./tcio) >/dev/null 2>&1; then
+       val="$define"
+       echo "TIOCNOTTY found in <$xxx>." >&4
+       echo "Using ioctl() call on /dev/tty to void tty association." >&4
+else
+       val="$undef"
+       echo "Closing standard file descriptors should void tty association." >&4
+fi
+set d_voidtty
+eval $setvar
+$rm -f tcio tcio.? core
+
+: check for volatile keyword
+echo " "
+echo 'Checking to see if your C compiler knows about "volatile"...' >&4
+$cat >try.c <<'EOCP'
+main()
+{
+       typedef struct _goo_struct goo_struct;
+       goo_struct * volatile goo = ((goo_struct *)0);
+       struct _goo_struct {
+               long long_int;
+               int reg_int;
+               char char_var;
+       };
+       typedef unsigned short foo_t;
+       char *volatile foo;
+       volatile int bar;
+       volatile foo_t blech;
+       foo = foo;
+}
+EOCP
+if $cc -c $ccflags try.c >/dev/null 2>&1 ; then
+       val="$define"
+       echo "Yup, it does."
+else
+       val="$undef"
+       echo "Nope, it doesn't."
+fi
+set d_volatile
+eval $setvar
+$rm -f try.*
+
+: see if vsnprintf exists
+echo " "
+if set vsnprintf val -f d_vsnprintf; eval $csym; $val; then
+       echo 'vsnprintf() found.' >&4
+       val="$define"
+else
+       echo 'vsnprintf() NOT found.' >&4
+       val="$undef"
+fi
+set d_vsnprintf
+eval $setvar
+
+: see if waitpid exists
+set waitpid d_waitpid
+eval $inlibc
+
+: check for a __cdecl use
+echo " "
+$cat >test_cdecl.c <<'EOCP'
+#include <stdio.h>
+#include <stdlib.h>
+#define WIN32_CDECL __cdecl
+
+int WIN32_CDCEL main(int argc, char **argv) {
+       exit(0);
+}
+EOCP
+
+if ($cc $ccflags -o test_cdecl test_cdecl.c && ./test_cdecl) >/dev/null 2>&1 ; then
+    cdecl='__cdecl'
+    echo 'Your compiler likes __cdecl as a keyword.' >&4
+else
+    cdecl=''
+    echo "Your compiler doesn't grok __cdecl - it probably has a brain." >&4
+fi
+$rm -f test_cdecl* core
+
+: check for void type
+echo " "
+echo "Checking to see how well your C compiler groks the void type..." >&4
+echo " "
+$cat >&4 <<EOM
+  Support flag bits are:
+    1: basic void declarations.
+    2: arrays of pointers to functions returning void.
+    4: operations between pointers to and addresses of void functions.
+    8: generic void pointers.
+EOM
+echo " "
+case "$voidflags" in
+'')
+       $cat >try.c <<'EOCP'
+#if TRY & 1
+void sub() {
+#else
+sub() {
+#endif
+       extern void moo();      /* function returning void */
+       void (*goo)();          /* ptr to func returning void */
+#if TRY & 8
+       void *hue;              /* generic ptr */
+#endif
+#if TRY & 2
+       void (*foo[10])();
+#endif
+
+#if TRY & 4
+       if(goo == moo) {
+               exit(0);
+       }
+#endif
+       exit(0);
+}
+main() { sub(); }
+EOCP
+       if $cc $ccflags -c -DTRY=$defvoidused try.c >.out 2>&1 ; then
+               voidflags=$defvoidused
+       echo "It appears to support void to the level $package wants ($defvoidused)."
+               if $contains warning .out >/dev/null 2>&1; then
+                       echo "However, you might get some warnings that look like this:"
+                       $cat .out
+               fi
+       else
+echo "Hmm, your compiler has some difficulty with void. Checking further..." >&4
+               if $cc $ccflags -c -DTRY=1 try.c >/dev/null 2>&1; then
+                       echo "It supports 1..."
+                       if $cc $ccflags -c -DTRY=3 try.c >/dev/null 2>&1; then
+                               echo "It also supports 2..."
+                               if $cc $ccflags -c -DTRY=7 try.c >/dev/null 2>&1; then
+                                       voidflags=7
+                                       echo "And it supports 4 but not 8 definitely."
+                               else
+                                       echo "It doesn't support 4..."
+                                       if $cc $ccflags -c -DTRY=11 try.c >/dev/null 2>&1; then
+                                               voidflags=11
+                                               echo "But it supports 8."
+                                       else
+                                               voidflags=3
+                                               echo "Neither does it support 8."
+                                       fi
+                               fi
+                       else
+                               echo "It does not support 2..."
+                               if $cc $ccflags -c -DTRY=13 try.c >/dev/null 2>&1; then
+                                       voidflags=13
+                                       echo "But it supports 4 and 8."
+                               else
+                                       if $cc $ccflags -c -DTRY=5 try.c >/dev/null 2>&1; then
+                                               voidflags=5
+                                               echo "And it supports 4 but has not heard about 8."
+                                       else
+                                               echo "However it supports 8 but not 4."
+                                       fi
+                               fi
+                       fi
+               else
+                       echo "There is no support at all for void."
+                       voidflags=0
+               fi
+       fi
+esac
+case "$voidflags" in
+"$defvoidused") ;;
+*)
+       dflt="$voidflags";
+       rp="Your void support flags add up to what?"
+       . ./myread
+       voidflags="$ans"
+       ;;
+esac
+$rm -f try.* .out
+
+: see if this is a malloc.h system
+set malloc.h i_malloc
+eval $inhdr
+
+: see if stdlib is available
+set stdlib.h i_stdlib
+eval $inhdr
+
+: compute the return types of malloc and free
+echo " "
+$cat >malloc.c <<END
+#$i_malloc I_MALLOC
+#$i_stdlib I_STDLIB
+#include <stdio.h>
+#include <sys/types.h>
+#ifdef I_MALLOC
+#include <malloc.h>
+#endif
+#ifdef I_STDLIB
+#include <stdlib.h>
+#endif
+#ifdef TRY_MALLOC
+void *malloc();
+#endif
+#ifdef TRY_FREE
+void free();
+#endif
+END
+case "$malloctype" in
+'')
+       if $cc $ccflags -c -DTRY_MALLOC malloc.c >/dev/null 2>&1; then
+               malloctype='void *'
+       else
+               malloctype='char *'
+       fi
+       ;;
+esac
+echo "Your system wants malloc to return '$malloctype', it would seem." >&4
+
+case "$freetype" in
+'')
+       if $cc $ccflags -c -DTRY_FREE malloc.c >/dev/null 2>&1; then
+               freetype='void'
+       else
+               freetype='int'
+       fi
+       ;;
+esac
+echo "Your system uses $freetype free(), it would seem." >&4
+$rm -f malloc.[co]
+: locate a BSD compatible install program
+echo " "
+echo "Looking for a BSD-compatible install program..." >&4
+creatdir=''
+case "$install" in
+'')
+       tryit=''
+       for dir in $pth; do
+               for file in ginstall installbsd scoinst install; do
+                       if $test -f $dir/$file; then
+                               tryit="$tryit $dir/$file"
+                       fi
+               done
+       done
+       $cat >try.c <<EOC
+main()
+{
+       printf("OK\n");
+       exit(0);
+}
+EOC
+       if $cc try.c -o try >/dev/null 2>&1; then
+               cp try try.ns
+               strip try >/dev/null 2>&1
+       else
+               echo "(I can't seem to compile a trivial C program -- bypassing.)"
+               echo "try" >try
+               cp try try.ns
+       fi
+       $cat >tryinst <<EOS
+$startsh
+$rm -rf foo d
+\$1 -d foo/bar
+$mkdir d
+\$1 -c -m 764 try.ns d
+\$1 -c -s -m 642 try.ns d/try
+EOS
+       chmod +x tryinst
+       $eunicefix tryinst
+       dflt=''
+       either=''
+       for prog in $tryit; do
+               $echo $n "Checking $prog... $c"
+               ./tryinst $prog >/dev/null 2>&1
+               if $test -d foo/bar; then
+                       creatdir="$prog -d"
+               fi
+               (ls -l d/try >try.ls; ls -l d/try.ns >tryno.ls) 2>/dev/null
+               if (cmp -s d/try try && cmp -s d/try.ns try.ns && \
+                       $contains 'rwxrw-r--' tryno.ls && \
+                       $contains 'rw-r---w-' try.ls) >/dev/null 2>&1
+               then
+                       dflt="$prog"
+                       echo "ok, that will do."
+                       break
+               fi
+               echo "not good$either."
+               either=' either'
+               $rm -f try*.ls
+       done
+       $rm -rf foo d tryinst try try*.ls try.*
+       case "$dflt" in
+       '')
+               dflt='install'
+               ;;
+       esac
+       ;;
+*) dflt="$install";;
+esac
+$cat <<EOM
+
+I will be requiring a BSD-compatible install program (one that allows
+options like -s to strip executables or -m to specify a file mode) to
+install $package.
+
+If the question below contains a fully qualified default path, then it
+is probably ok. If it is an unqualified name such as 'install', then it
+means I was unable to find out a good install program I could use. If
+you know of one, please tell me about it.
+
+EOM
+fn='/fe~(install)'
+rp='Which install program shall I use?'
+. ./getfile
+install="$ans"
+
+: how can we create nested directories?
+echo " "
+echo "Ok, let's see how we can create nested directories..." >&4
+case "$installdir" in
+'')
+       case "$creatdir" in
+       '')
+               $mkdir -p foo/bar >/dev/null 2>&1
+               if $test -d foo/bar; then
+                       echo "Great, we can build them using 'mkdir -p'."
+                       creatdir='mkdir -p'
+               elif eval "$install -d foo/bar"; $test -d foo/bar; then
+                       creatdir="install -d"
+                       echo "It looks like '$creatdir' will do it for us."
+               fi
+               ;;
+       *)
+               eval "$creatdir foo/bar" >/dev/null 2>&1
+               if $test -d foo/bar; then
+                       echo "Ah! We can use '$creatdir' to do just that."
+               fi
+               ;;
+       esac
+       $rm -rf foo
+       case "$creatdir" in
+       '')
+               echo "Heck! Another ancient system lacking the comfort of modern ones!"
+               echo "We have no choice but to use plain old 'mkdir' -- wish me luck!"
+               installdir=mkdir
+               ;;
+       *) installdir="$creatdir";;
+       esac
+       ;;
+*) echo "As you already told me, '$installdir' should work.";;
+esac
+
+echo " "
+echo "Checking if your $make program sets \$(MAKE)..." >&4
+case "$make_set_make" in
+'')
+       $sed 's/^X //' > testmake.mak << 'EOF'
+Xall:
+X      @echo 'maketemp="$(MAKE)"'
+EOF
+       case "`$make -f testmake.mak 2>/dev/null`" in
+       *maketemp=*) make_set_make='#' ;;
+       *)      make_set_make="MAKE=$make" ;;
+       esac
+       $rm -f testmake.mak
+       ;;
+esac
+case "$make_set_make" in
+'#') echo "Yup, it does.";;
+*) echo "Nope, it doesn't.";;
+esac
+
+: see if we have to deal with yellow pages, now NIS.
+if $test -d /usr/etc/yp || $test -d /etc/yp; then
+       if $test -f /usr/etc/nibindd; then
+               echo " "
+               echo "I'm fairly confident you're on a NeXT."
+               echo " "
+               rp='Do you get the passwd file via NetInfo?'
+               dflt=y
+               case "$passcat" in
+               nidump*) ;;
+               '') ;;
+               *) dflt=n;;
+               esac
+               . ./myread
+               case "$ans" in
+               y*) passcat='nidump passwd .'
+                       ;;
+               *)      echo "You told me, so don't blame me."
+                       case "$passcat" in
+                       nidump*) passcat=''
+                       esac
+                       ;;
+               esac
+               echo " "
+               rp='Do you get the hosts file via NetInfo?'
+               dflt=y
+               case "$hostcat" in
+               nidump*) ;;
+               '') ;;
+               *) dflt=n;;
+               esac
+               . ./myread
+               case "$ans" in
+               y*) hostcat='nidump hosts .';;
+               *)      case "$hostcat" in
+                       nidump*) hostcat='';;
+                       esac
+                       ;;
+               esac
+       fi
+       case "$passcat" in
+       nidump*) ;;
+       *)
+               case "$passcat" in
+               *ypcat*) dflt=y;;
+               '') if $contains '^\+' /etc/passwd >/dev/null 2>&1; then
+                               dflt=y
+                       else
+                               dflt=n
+                       fi;;
+               *) dflt=n;;
+               esac
+               echo " "
+               rp='Are you getting the passwd file via yellow pages?'
+               . ./myread
+               case "$ans" in
+               y*) passcat='ypcat passwd'
+                       ;;
+               *)      passcat='cat /etc/passwd'
+                       ;;
+               esac
+               ;;
+       esac
+       case "$hostcat" in
+       nidump*) ;;
+       *)
+               case "$hostcat" in
+               *ypcat*) dflt=y;;
+               '') if $contains '^\+' /etc/passwd >/dev/null 2>&1; then
+                               dflt=y
+                       else
+                               dflt=n
+                       fi;;
+               *) dflt=n;;
+               esac
+               echo " "
+               rp='Are you getting the hosts file via yellow pages?'
+               . ./myread
+               case "$ans" in
+               y*) hostcat='ypcat hosts';;
+               *) hostcat='cat /etc/hosts';;
+               esac
+               ;;
+       esac
+fi
+case "$hostcat" in
+'') hostcat='cat /etc/hosts';;
+esac
+case "$groupcat" in
+'') groupcat='cat /etc/group';;
+esac
+case "$passcat" in
+'') passcat='cat /etc/passwd';;
+esac
+
+: now get the host name
+echo " "
+echo "Figuring out host name..." >&4
+case "$myhostname" in
+'') cont=true
+       echo 'Maybe "hostname" will work...'
+       if tans=`sh -c hostname 2>&1` ; then
+               myhostname=$tans
+               phostname=hostname
+               cont=''
+       fi
+       ;;
+*) cont='';;
+esac
+if $test "$cont"; then
+       if ./xenix; then
+               echo 'Oh, dear.  Maybe "/etc/systemid" is the key...'
+               if tans=`cat /etc/systemid 2>&1` ; then
+                       myhostname=$tans
+                       phostname='cat /etc/systemid'
+                       echo "Whadyaknow.  Xenix always was a bit strange..."
+                       cont=''
+               fi
+       elif $test -r /etc/systemid; then
+               echo "(What is a non-Xenix system doing with /etc/systemid?)"
+       fi
+fi
+if $test "$cont"; then
+       echo 'No, maybe "uuname -l" will work...'
+       if tans=`sh -c 'uuname -l' 2>&1` ; then
+               myhostname=$tans
+               phostname='uuname -l'
+       else
+               echo 'Strange.  Maybe "uname -n" will work...'
+               if tans=`sh -c 'uname -n' 2>&1` ; then
+                       myhostname=$tans
+                       phostname='uname -n'
+               else
+                       echo 'Oh well, maybe I can mine it out of whoami.h...'
+                       if tans=`sh -c $contains' sysname $usrinc/whoami.h' 2>&1` ; then
+                               myhostname=`echo "$tans" | $sed 's/^.*"\(.*\)"/\1/'`
+                               phostname="sed -n -e '"'/sysname/s/^.*\"\\(.*\\)\"/\1/{'"' -e p -e q -e '}' <$usrinc/whoami.h"
+                       else
+                               case "$myhostname" in
+                               '') echo "Does this machine have an identity crisis or something?"
+                                       phostname='';;
+                               *)
+                                       echo "Well, you said $myhostname before..."
+                                       phostname='echo $myhostname';;
+                               esac
+                       fi
+               fi
+       fi
+fi
+: you do not want to know about this
+set $myhostname
+myhostname=$1
+
+: verify guess
+if $test "$myhostname" ; then
+       dflt=y
+       rp='Your host name appears to be "'$myhostname'".'" Right?"
+       . ./myread
+       case "$ans" in
+       y*) ;;
+       *) myhostname='';;
+       esac
+fi
+
+: bad guess or no guess
+while $test "X$myhostname" = X ; do
+       dflt=''
+       rp="Please type the (one word) name of your host:"
+       . ./myread
+       myhostname="$ans"
+done
+
+: translate upper to lower if necessary
+case "$myhostname" in
+*[A-Z]*)
+       echo "(Normalizing case in your host name)"
+       myhostname=`echo $myhostname | ./tr '[A-Z]' '[a-z]'`
+       ;;
+esac
+
+case "$myhostname" in
+*.*)
+       dflt=`expr "X$myhostname" : "X[^.]*\(\..*\)"`
+       myhostname=`expr "X$myhostname" : "X\([^.]*\)\."`
+       echo "(Trimming domain name from host name--host name is now $myhostname)"
+       ;;
+*) case "$mydomain" in
+       '')
+               {
+                       test "X$hostcat" = "Xypcat hosts" &&
+                       ypmatch "$myhostname" hosts 2>/dev/null |\
+                               $sed -e 's/[     ]*#.*//; s/$/ /' > hosts && \
+                       $test -s hosts
+               } || {
+                       $hostcat | $sed -n -e "s/[       ]*#.*//; s/\$/ /
+                                       /[       ]$myhostname[  . ]/p" > hosts
+               }
+               tmp_re="[       . ]"
+               $test x`$awk "/[0-9].*[  ]$myhostname$tmp_re/ { sum++ }
+                            END { print sum }" hosts` = x1 || tmp_re="[         ]"
+               dflt=.`$awk "/[0-9].*[   ]$myhostname$tmp_re/ {for(i=2; i<=NF;i++) print \\\$i}" \
+                       hosts | $sort | $uniq | \
+                       $sed -n -e "s/$myhostname\.\([-a-zA-Z0-9_.]\)/\1/p"`
+               case `$echo X$dflt` in
+               X*\ *)  echo "(Several hosts in /etc/hosts matched hostname)"
+                       dflt=.
+                       ;;
+               X.) echo "(You do not have fully-qualified names in /etc/hosts)"
+                       ;;
+               esac
+               case "$dflt" in
+               .)
+                       tans=`./loc resolv.conf X /etc /usr/etc`
+                       if $test -f "$tans"; then
+                               echo "(Attempting domain name extraction from $tans)"
+                               dflt=.`$sed -n -e 's/   / /g' \
+                                 -e 's/^search  *\([^ ]*\).*/\1/p' $tans \
+                                 | ./tr '[A-Z]' '[a-z]' 2>/dev/null`
+                               case "$dflt" in
+                               .) dflt=.`$sed -n -e 's/        / /g' \
+                                    -e 's/^domain  *\([^ ]*\).*/\1/p' $tans \
+                                    | ./tr '[A-Z]' '[a-z]' 2>/dev/null`
+                                       ;;
+                               esac
+                       fi
+                       ;;
+               esac
+               case "$dflt" in
+               .) echo "(No help from resolv.conf either -- attempting clever guess)"
+                       dflt=.`sh -c domainname 2>/dev/null`
+                       case "$dflt" in
+                       '') dflt='.';;
+                       .nis.*|.yp.*|.main.*) dflt=`echo $dflt | $sed -e 's/^\.[^.]*//'`;;
+                       esac
+                       ;;
+               esac
+               case "$dflt" in
+               .) echo "(Lost all hope -- silly guess then)"
+                       dflt='.uucp'
+                       ;;
+               esac
+               $rm -f hosts
+               ;;
+       *) dflt="$mydomain";;
+       esac;;
+esac
+echo " "
+rp="What is your domain name?"
+. ./myread
+tans="$ans"
+case "$ans" in
+'') ;;
+.*) ;;
+*) tans=".$tans";;
+esac
+mydomain="$tans"
+
+: translate upper to lower if necessary
+case "$mydomain" in
+*[A-Z]*)
+       echo "(Normalizing case in your domain name)"
+       mydomain=`echo $mydomain | ./tr '[A-Z]' '[a-z]'`
+       ;;
+esac
+
+: a little sanity check here
+case "$phostname" in
+'') ;;
+*)
+       case `$phostname | ./tr '[A-Z]' '[a-z]'` in
+       $myhostname$mydomain|$myhostname) ;;
+       *)
+               case "$phostname" in
+               sed*)
+                       echo "(That doesn't agree with your whoami.h file, by the way.)"
+                       ;;
+               *)
+                       echo "(That doesn't agree with your $phostname command, by the way.)"
+                       ;;
+               esac
+       ;;
+       esac
+       ;;
+esac
+
+: define an is-a-typedef? function
+typedef='type=$1; var=$2; def=$3; shift; shift; shift; inclist=$@;
+case "$inclist" in
+"") inclist="sys/types.h";;
+esac;
+eval "varval=\$$var";
+case "$varval" in
+"")
+       $rm -f temp.c;
+       for inc in $inclist; do
+               echo "#include <$inc>" >>temp.c;
+       done;
+       $cppstdin $cppflags $cppminus < temp.c >temp.E 2>/dev/null;
+       if $contains $type temp.E >/dev/null 2>&1; then
+               eval "$var=\$type";
+       else
+               eval "$var=\$def";
+       fi;
+       $rm -f temp.?;;
+*) eval "$var=\$varval";;
+esac'
+
+: see what type pids are declared as in the kernel
+set pid_t pidtype int stdio.h sys/types.h
+eval $typedef
+dflt="$pidtype"
+echo " "
+rp="What type are process ids on this system declared as?"
+. ./myread
+pidtype="$ans"
+
+: see what type is used for size_t
+set size_t sizetype 'unsigned int' stdio.h sys/types.h
+eval $typedef
+dflt="$sizetype"
+echo " "
+rp="What type is used for the length parameter for string functions?"
+. ./myread
+sizetype="$ans"
+
+: see if arpa/inet.h has to be included
+set arpa/inet.h i_arpainet
+eval $inhdr
+
+: see if arpa/nameser.h has to be included
+set arpa/nameser.h i_arpanameser
+eval $inhdr
+
+: see if errno.h can be included
+set errno.h i_errno
+eval $inhdr
+: see if sys/errno.h can be included
+set sys/errno.h i_syserrno
+eval $inhdr
+: see if this is a sys/file.h system
+val=''
+set sys/file.h val
+eval $inhdr
+
+: do we need to include sys/file.h ?
+case "$val" in
+"$define")
+       echo " "
+       if $h_sysfile; then
+               val="$define"
+               echo "We'll be including <sys/file.h>." >&4
+       else
+               val="$undef"
+               echo "We won't be including <sys/file.h>." >&4
+       fi
+       ;;
+*)
+       h_sysfile=false
+       ;;
+esac
+set i_sysfile
+eval $setvar
+
+: see if fcntl.h is there
+val=''
+set fcntl.h val
+eval $inhdr
+
+: see if we can include fcntl.h
+case "$val" in
+"$define")
+       echo " "
+       if $h_fcntl; then
+               val="$define"
+               echo "We'll be including <fcntl.h>." >&4
+       else
+               val="$undef"
+               if $h_sysfile; then
+       echo "We don't need to include <fcntl.h> if we include <sys/file.h>." >&4
+               else
+                       echo "We won't be including <fcntl.h>." >&4
+               fi
+       fi
+       ;;
+*)
+       h_fcntl=false
+       val="$undef"
+       ;;
+esac
+set i_fcntl
+eval $setvar
+
+: see if locale.h is available
+set locale.h i_locale
+eval $inhdr
+
+: see if memory.h is available.
+val=''
+set memory.h val
+eval $inhdr
+
+: See if it conflicts with string.h
+case "$val" in
+$define)
+       case "$strings" in
+       '') ;;
+       *)
+               $cppstdin $cppflags $cppminus < "$strings" > mem.h
+               if $contains 'memcpy' mem.h >/dev/null 2>&1; then
+                       echo " "
+                       echo "We won't be including <memory.h>."
+                       val="$undef"
+               fi
+               $rm -f mem.h
+               ;;
+       esac
+esac
+set i_memory
+eval $setvar
+
+: see if netdb.h can be included
+set netdb.h i_netdb
+eval $inhdr
+: see if setjmp.h can be included
+set setjmp.h i_setjmp
+eval $inhdr
+: see if stdarg is available
+echo " "
+if $test "`./findhdr stdarg.h`"; then
+       echo "<stdarg.h> found." >&4
+       valstd="$define"
+else
+       echo "<stdarg.h> NOT found." >&4
+       valstd="$undef"
+fi
+
+: see if varags is available
+echo " "
+if $test "`./findhdr varargs.h`"; then
+       echo "<varargs.h> found." >&4
+else
+       echo "<varargs.h> NOT found, but that's ok (I hope)." >&4
+fi
+
+: set up the varargs testing programs
+$cat > varargs.c <<EOP
+#ifdef I_STDARG
+#include <stdarg.h>
+#endif
+#ifdef I_VARARGS
+#include <varargs.h>
+#endif
+
+#ifdef I_STDARG
+int f(char *p, ...)
+#else
+int f(va_alist)
+va_dcl
+#endif
+{
+       va_list ap;
+#ifndef I_STDARG
+       char *p;
+#endif
+#ifdef I_STDARG
+       va_start(ap,p);
+#else
+       va_start(ap);
+       p = va_arg(ap, char *);
+#endif
+       va_end(ap);
+}
+EOP
+$cat > varargs <<EOP
+$startsh
+if $cc -c $ccflags $ldflags -D\$1 varargs.c >/dev/null 2>&1; then
+       echo "true"
+else
+       echo "false"
+fi
+$rm -f varargs$_o
+EOP
+chmod +x varargs
+
+: now check which varargs header should be included
+echo " "
+i_varhdr=''
+case "$valstd" in
+"$define")
+       if `./varargs I_STDARG`; then
+               val='stdarg.h'
+       elif `./varargs I_VARARGS`; then
+               val='varargs.h'
+       fi
+       ;;
+*)
+       if `./varargs I_VARARGS`; then
+               val='varargs.h'
+       fi
+       ;;
+esac
+case "$val" in
+'')
+echo "I could not find the definition for va_dcl... You have problems..." >&4
+       val="$undef"; set i_stdarg; eval $setvar
+       val="$undef"; set i_varargs; eval $setvar
+       ;;
+*) 
+       set i_varhdr
+       eval $setvar
+       case "$i_varhdr" in
+       stdarg.h)
+               val="$define"; set i_stdarg; eval $setvar
+               val="$undef"; set i_varargs; eval $setvar
+               ;;
+       varargs.h)
+               val="$undef"; set i_stdarg; eval $setvar
+               val="$define"; set i_varargs; eval $setvar
+               ;;
+       esac
+       echo "We'll include <$i_varhdr> to get va_dcl definition." >&4;;
+esac
+$rm -f varargs*
+
+: see if stddef is available
+set stddef.h i_stddef
+eval $inhdr
+
+: see if sys/mman.h has to be included
+set sys/mman.h i_sysmman
+eval $inhdr
+
+: see if sys/page.h has to be included
+set sys/page.h i_syspage
+eval $inhdr
+
+: see if this is a sys/param system
+set sys/param.h i_sysparam
+eval $inhdr
+
+: see if sys/resource.h has to be included
+set sys/resource.h i_sysresrc
+eval $inhdr
+
+: see if sys/select.h has to be included
+set sys/select.h i_sysselct
+eval $inhdr
+
+: see if sys/stat.h is available
+set sys/stat.h i_sysstat
+eval $inhdr
+
+: see if we should include time.h, sys/time.h, or both
+echo " "
+echo "Testing to see if we should include <time.h>, <sys/time.h> or both." >&4
+$echo $n "I'm now running the test program...$c"
+$cat >try.c <<'EOCP'
+#include <sys/types.h>
+#ifdef I_TIME
+#include <time.h>
+#endif
+#ifdef I_SYSTIME
+#ifdef SYSTIMEKERNEL
+#define KERNEL
+#endif
+#include <sys/time.h>
+#endif
+#ifdef I_SYSSELECT
+#include <sys/select.h>
+#endif
+main()
+{
+       struct tm foo;
+#ifdef S_TIMEVAL
+       struct timeval bar;
+#endif
+#ifdef S_TIMEZONE
+       struct timezone tzp;
+#endif
+       if (foo.tm_sec == foo.tm_sec)
+               exit(0);
+#ifdef S_TIMEVAL
+       if (bar.tv_sec == bar.tv_sec)
+               exit(0);
+#endif
+       exit(1);
+}
+EOCP
+flags=''
+for s_timezone in '-DS_TIMEZONE' ''; do
+sysselect=''
+for s_timeval in '-DS_TIMEVAL' ''; do
+for i_systimek in '' '-DSYSTIMEKERNEL'; do
+for i_time in '' '-DI_TIME'; do
+for i_systime in '-DI_SYSTIME' ''; do
+       case "$flags" in
+       '') $echo $n ".$c"
+               if $cc $ccflags \
+               $i_time $i_systime $i_systimek $sysselect $s_timeval $s_timezone \
+               try.c -o try >/dev/null 2>&1 ; then
+                       set X $i_time $i_systime $i_systimek $sysselect $s_timeval
+                       shift
+                       flags="$*"
+                       echo " "
+                       $echo $n "Succeeded with $flags$c"
+               fi
+               ;;
+       esac
+done
+done
+done
+done
+done
+timeincl=''
+echo " "
+case "$flags" in
+*SYSTIMEKERNEL*) i_systimek="$define"
+       timeincl=`./findhdr sys/time.h`
+       echo "We'll include <sys/time.h> with KERNEL defined." >&4;;
+*) i_systimek="$undef";;
+esac
+case "$flags" in
+*I_TIME*) i_time="$define"
+       timeincl=`./findhdr time.h`" $timeincl"
+       echo "We'll include <time.h>." >&4;;
+*) i_time="$undef";;
+esac
+case "$flags" in
+*I_SYSTIME*) i_systime="$define"
+       timeincl=`./findhdr sys/time.h`" $timeincl"
+       echo "We'll include <sys/time.h>." >&4;;
+*) i_systime="$undef";;
+esac
+$rm -f try.c try
+
+: see if sys/vlimit.h has to be included
+set sys/vlimit.h i_sysvlimit
+eval $inhdr
+
+: see if this is a syswait system
+set sys/wait.h i_syswait
+eval $inhdr
+
+: determine the name of a reasonable mailer
+case "$mailer" in
+'')
+       if $test -f "$sendmail"; then
+               dflt="$sendmail"
+       elif $test -f "$smail"; then
+               dflt="$smail"
+       elif $test -f "$rmail"; then
+               dflt="$rmail"
+       elif $test -f /bin/mail; then
+               dflt=/bin/mail
+       else
+               dflt=$mail
+       fi
+       ;;
+*)  dflt="$mailer";;
+esac
+$cat <<EOM
+
+Later on, I'm going to offer you the chance to subscribe (or unsubscribe)
+to the $package mailing list. In order to do that, I need to figure out
+how to send mail, your name, and a few other things. 
+
+I need the full pathname of the program used to deliver mail on your system.
+A typical answer would be /usr/lib/sendmail or /bin/rmail, but you may choose
+any other program, as long as it can be fed from standard input and will
+honour any user-supplied headers.
+
+EOM
+fn=f
+rp='Mail transport agent to be used?'
+. ./getfile
+mailer="$ans"
+
+: find out how to find out full name
+case "$d_berknames" in
+"$define")
+       dflt=y;;
+"$undef")
+       dflt=n;;
+*)
+       if ./bsd; then
+               dflt=y
+       elif ./xenix; then
+               dflt=y
+       elif $test "x$osname" = "xlinux"; then
+               dflt=y
+       else
+               dflt=n
+       fi
+       ;;
+esac
+$cat <<'EOM'
+
+Does your /etc/passwd file keep full names in Berkeley/V7 format (name first
+thing after ':' in GCOS field)?  In that case, a typical entry in the password
+file looks like this:
+
+    guest:**paswword**:10:100:Mister Guest User:/usr/users:/bin/sh
+                              ^^^^^^^^^^^^^^^^^
+EOM
+rp="Berkeley/V7 format for full name in /etc/passwd?"
+. ./myread
+case "$ans" in
+y*) d_passnames="$define"
+       d_berknames="$define"
+       d_usgnames="$undef"
+       nametype=bsd
+       ;;
+*)
+       case "$d_usgnames" in
+       "$define") dflt=y;;
+       "$undef") dflt=n;;
+       *)
+               if ./usg; then
+                       dflt=y
+               else
+                       dflt=n
+               fi
+               ;;
+       esac
+$cat <<'EOM'
+
+Does your passwd file keep full names in USG format (name sandwiched between a
+'-' and a '(')?  In that case, a typical entry in the password file looks like
+this:
+
+    guest:**paswword**:10:100:000-Mister Guest User(000):/usr/users:/bin/sh
+                                  ^^^^^^^^^^^^^^^^^
+EOM
+       rp="USG format for full name in /etc/passwd?"
+       . ./myread
+       case "$ans" in
+       n*) echo "Full name will be taken from ~/.fullname"
+               d_passnames="$undef"
+               d_berknames="$undef"
+               d_usgnames="$undef"
+               nametype=other
+               ;;
+       *)
+               d_passnames="$define"
+               d_berknames="$undef"
+               d_usgnames="$define"
+               nametype=usg
+               ;;
+       esac;;
+esac
+
+: figure out their full name
+case "$NAME" in
+'') case "$nametype" in
+       other)
+               fn=`./filexp ~/.fullname`
+               xxx=usg
+               $test -f $fn && xxx=other
+               ;;
+       *)
+               xxx="$nametype"
+               ;;
+       esac
+
+       case "$xxx" in
+       bsd)
+               cf_name=`$passcat | grep "^$cf_by:" | \
+                       sed -e 's/^[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\):.*/\1/' \
+                       -e 's/,.*//'`
+               ;;
+    usg)
+               cf_name=`$passcat | grep "^$cf_by:" | \
+                       sed -e 's/^[^:]*:[^:]*:[^:]*:[^:]*:\([^:]*\):.*/\1/' \
+                       -e 's/[^-]*-\(.*\)(.*)/\1/'`
+               ;;
+       *)
+               cf_name=`$cat $fn`
+               ;;
+       esac
+       ;;
+*)
+       cf_name="$NAME"
+       ;;
+esac
+echo " "
+echo "Pleased to meet you, $cf_name."
+
+$cat <<EOM
+
+I need to get your e-mail address in Internet format if possible, i.e.
+something like user@host.domain. Please answer accurately since I have
+no easy means to double check it. The default value provided below
+is most probably close to the reality but may not be valid from outside
+your organization...
+
+EOM
+cont=x
+while test "$cont"; do
+       case "$cf_email" in
+       '') dflt="$cf_by@$myhostname$mydomain";;
+       *) dflt="$cf_email";;
+       esac
+       rp='What is your e-mail address?'
+       . ./myread
+       cf_email="$ans"
+       case "$cf_email" in
+       *@*.*) cont='' ;;
+       *)
+               rp='Address does not look like an Internet one.  Use it anyway?'
+               case "$fastread" in
+               yes) dflt=y ;;
+               *) dflt=n ;;
+               esac
+               . ./myread
+               case "$ans" in
+               y*) cont='' ;;
+               *) echo " " ;;
+               esac
+               ;;
+       esac
+done
+
+: offer to join the mailing list
+list_request='pennmush-request@pennmush.org'
+list_sub="subscribe"
+list_unsub="unsubscribe"
+list_name="pennmush"
+$cat <<EOM
+
+There is a mailing list for discussion about $package and related issues.
+This is the preferred place to ask questions about the program and discuss
+modifications and additions with the author and other users.  If you are able
+to send mail to the Internet, you are encouraged to subscribe.  You need only
+ever subscribe once, and you can unsubscribe automatically at any time in the
+future.  If you have already subscribed and you wish to unsubscribe now, you
+may do so by answering "unsubscribe".  Answer "subscribe" to subscribe to the
+list.
+
+EOM
+rp="Subscribe to or unsubscribe from the $list_name mailing list?"
+dflt=neither
+. ./myread
+case "$ans" in
+[sS]*) $cat <<EOM
+
+You will be sent a message from the list server to let you know when your
+subscription has been successful and telling you how to submit articles and
+how to unsubscribe again when necessary. You may also unsubscribe by running
+this script again and asking it to do so for you.
+
+EOM
+       echo "Sending mail to subscribe you to the $list_name list..." >&4
+       $mailer $list_request <<EOM >/dev/null 2>&1
+To: $list_request
+Subject: Subscription request by configure
+
+$list_sub
+EOM
+       ;;
+[uU]*) echo "Sending mail to unsubscribe you from the $list_name list..." >&4
+       $mailer $list_request <<EOM >/dev/null 2>&1
+To: $list_request
+Subject: Unsubscription request by configure
+
+$list_unsub
+EOM
+       ;;
+esac
+
+: end of configuration questions
+echo " "
+echo "End of configuration questions."
+echo " "
+
+: back to where it started
+if test -d ../UU; then
+       cd ..
+fi
+
+: configuration may be patched via a 'config.over' file
+if $test -f config.over; then
+       echo " "
+       dflt=y
+       rp='I see a config.over file.  Do you wish to load it?'
+       . UU/myread
+       case "$ans" in
+       n*) echo "OK, I'll ignore it.";;
+       *)      . ./config.over
+               echo "Configuration override changes have been loaded."
+               ;;
+       esac
+fi
+
+: in case they want portability, strip down executable paths
+case "$d_portable" in
+"$define")
+       echo " "
+       echo "Stripping down executable paths..." >&4
+       for file in $loclist $trylist; do
+               eval $file="\$file"
+       done
+       ;;
+esac
+
+: create config.sh file
+echo " "
+echo "Creating config.sh..." >&4
+$spitshell <<EOT >config.sh
+$startsh
+#
+# This file was produced by running the Configure script. It holds all the
+# definitions figured out by Configure. Should you modify one of these values,
+# do not forget to propagate your changes by running "Configure -der". You may
+# instead choose to run each of the .SH files by yourself, or "Configure -S".
+#
+
+# Package name      : $package
+# Source directory  : $src
+# Configuration time: $cf_time
+# Configured by     : $cf_by
+# Target system     : $myuname
+
+Author='$Author'
+Date='$Date'
+Header='$Header'
+Id='$Id'
+Locker='$Locker'
+Log='$Log'
+Mcc='$Mcc'
+RCSfile='$RCSfile'
+Revision='$Revision'
+Source='$Source'
+State='$State'
+ar='$ar'
+archobjs='$archobjs'
+awk='$awk'
+bash='$bash'
+bison='$bison'
+byacc='$byacc'
+c='$c'
+cat='$cat'
+cc='$cc'
+ccflags='$ccflags'
+cdecl='$cdecl'
+cf_by='$cf_by'
+cf_email='$cf_email'
+cf_name='$cf_name'
+cf_time='$cf_time'
+chgrp='$chgrp'
+chmod='$chmod'
+chown='$chown'
+comm='$comm'
+compress='$compress'
+contains='$contains'
+cp='$cp'
+cpio='$cpio'
+cpp='$cpp'
+cppflags='$cppflags'
+cpplast='$cpplast'
+cppminus='$cppminus'
+cpprun='$cpprun'
+cppstdin='$cppstdin'
+cryptlib='$cryptlib'
+csh='$csh'
+d_access='$d_access'
+d_argsinfp='$d_argsinfp'
+d_assert='$d_assert'
+d_attribut='$d_attribut'
+d_bcopy='$d_bcopy'
+d_berknames='$d_berknames'
+d_bindtextdomain='$d_bindtextdomain'
+d_bsd='$d_bsd'
+d_bsdsetpgrp='$d_bsdsetpgrp'
+d_bzero='$d_bzero'
+d_const='$d_const'
+d_crypt='$d_crypt'
+d_eunice='$d_eunice'
+d_force_ipv4='$d_force_ipv4'
+d_fpsetmask='$d_fpsetmask'
+d_fpsetround='$d_fpsetround'
+d_gaistr='$d_gaistr'
+d_getadinf='$d_getadinf'
+d_getdate='$d_getdate'
+d_gethbynm2='$d_gethbynm2'
+d_getnminf='$d_getnminf'
+d_getpagsz='$d_getpagsz'
+d_getprior='$d_getprior'
+d_gettblsz='$d_gettblsz'
+d_gettext='$d_gettext'
+d_gnulibc='$d_gnulibc'
+d_huge='$d_huge'
+d_huge_val='$d_huge_val'
+d_ieee='$d_ieee'
+d_in2p='$d_in2p'
+d_index='$d_index'
+d_int_max='$d_int_max'
+d_internet='$d_internet'
+d_ipv6='$d_ipv6'
+d_itimer='$d_itimer'
+d_keepalive='$d_keepalive'
+d_keepidle='$d_keepidle'
+d_keepsig='$d_keepsig'
+d_lrand48='$d_lrand48'
+d_maxdouble='$d_maxdouble'
+d_maxint='$d_maxint'
+d_memcpy='$d_memcpy'
+d_memmove='$d_memmove'
+d_memset='$d_memset'
+d_mymalloc='$d_mymalloc'
+d_mysql='$d_mysql'
+d_newstyle='$d_newstyle'
+d_oldsock='$d_oldsock'
+d_open3='$d_open3'
+d_openssl='$d_openssl'
+d_passnames='$d_passnames'
+d_portable='$d_portable'
+d_rand='$d_rand'
+d_random='$d_random'
+d_rename='$d_rename'
+d_rlimit='$d_rlimit'
+d_rusage='$d_rusage'
+d_select='$d_select'
+d_sendmail='$d_sendmail'
+d_setlocale='$d_setlocale'
+d_setpgid='$d_setpgid'
+d_setpgrp='$d_setpgrp'
+d_setprior='$d_setprior'
+d_sigaction='$d_sigaction'
+d_sigchld='$d_sigchld'
+d_sigcld='$d_sigcld'
+d_signalproto='$d_signalproto'
+d_sigprocmask='$d_sigprocmask'
+d_snprintf='$d_snprintf'
+d_socket='$d_socket'
+d_socklen='$d_socklen'
+d_sockpair='$d_sockpair'
+d_strccmp='$d_strccmp'
+d_strchr='$d_strchr'
+d_strcoll='$d_strcoll'
+d_strdup='$d_strdup'
+d_strxfrm='$d_strxfrm'
+d_sysconf='$d_sysconf'
+d_sysctl='$d_sysctl'
+d_sysctlbyname='$d_sysctlbyname'
+d_tcl='$d_tcl'
+d_textdomain='$d_textdomain'
+d_timelocal='$d_timelocal'
+d_toupper='$d_toupper'
+d_uptime='$d_uptime'
+d_urandom='$d_urandom'
+d_usgnames='$d_usgnames'
+d_uwait3='$d_uwait3'
+d_uwait='$d_uwait'
+d_voidsig='$d_voidsig'
+d_voidtty='$d_voidtty'
+d_volatile='$d_volatile'
+d_vsnprintf='$d_vsnprintf'
+d_waitpid='$d_waitpid'
+d_xenix='$d_xenix'
+date='$date'
+defvoidused='$defvoidused'
+echo='$echo'
+egrep='$egrep'
+emacs='$emacs'
+eunicefix='$eunicefix'
+expr='$expr'
+find='$find'
+firstmakefile='$firstmakefile'
+flex='$flex'
+freetype='$freetype'
+gcc='$gcc'
+gccversion='$gccversion'
+glibpth='$glibpth'
+grep='$grep'
+groupcat='$groupcat'
+gzip='$gzip'
+h_fcntl='$h_fcntl'
+h_sysfile='$h_sysfile'
+hint='$hint'
+hostcat='$hostcat'
+i_arpainet='$i_arpainet'
+i_arpanameser='$i_arpanameser'
+i_bsdioctl='$i_bsdioctl'
+i_crypt='$i_crypt'
+i_errno='$i_errno'
+i_fcntl='$i_fcntl'
+i_floatingpoint='$i_floatingpoint'
+i_libintl='$i_libintl'
+i_limits='$i_limits'
+i_locale='$i_locale'
+i_malloc='$i_malloc'
+i_memory='$i_memory'
+i_netdb='$i_netdb'
+i_niin='$i_niin'
+i_nitcp='$i_nitcp'
+i_setjmp='$i_setjmp'
+i_sgtty='$i_sgtty'
+i_stdarg='$i_stdarg'
+i_stddef='$i_stddef'
+i_stdlib='$i_stdlib'
+i_string='$i_string'
+i_syserrno='$i_syserrno'
+i_sysfile='$i_sysfile'
+i_sysfilio='$i_sysfilio'
+i_sysin='$i_sysin'
+i_sysioctl='$i_sysioctl'
+i_sysmman='$i_sysmman'
+i_syspage='$i_syspage'
+i_sysparam='$i_sysparam'
+i_sysresrc='$i_sysresrc'
+i_sysselct='$i_sysselct'
+i_syssock='$i_syssock'
+i_syssockio='$i_syssockio'
+i_sysstat='$i_sysstat'
+i_systime='$i_systime'
+i_systimek='$i_systimek'
+i_systypes='$i_systypes'
+i_sysvlimit='$i_sysvlimit'
+i_syswait='$i_syswait'
+i_tcl='$i_tcl'
+i_termio='$i_termio'
+i_termios='$i_termios'
+i_time='$i_time'
+i_unistd='$i_unistd'
+i_values='$i_values'
+i_varargs='$i_varargs'
+i_varhdr='$i_varhdr'
+incpath='$incpath'
+inews='$inews'
+install='$install'
+installdir='$installdir'
+ksh='$ksh'
+ldflags='$ldflags'
+less='$less'
+libc='$libc'
+libcrypto='$libcrypto'
+libmysqlclient='$libmysqlclient'
+libpth='$libpth'
+libs='$libs'
+libssl='$libssl'
+line='$line'
+lint='$lint'
+lkflags='$lkflags'
+ln='$ln'
+lns='$lns'
+locincpth='$locincpth'
+loclibpth='$loclibpth'
+lp='$lp'
+lpr='$lpr'
+ls='$ls'
+mail='$mail'
+mailer='$mailer'
+mailx='$mailx'
+make='$make'
+make_set_make='$make_set_make'
+mallocobj='$mallocobj'
+mallocsrc='$mallocsrc'
+malloctype='$malloctype'
+mips='$mips'
+mips_type='$mips_type'
+mkdir='$mkdir'
+more='$more'
+mv='$mv'
+mydomain='$mydomain'
+myhostname='$myhostname'
+myuname='$myuname'
+n='$n'
+nametype='$nametype'
+nm_opt='$nm_opt'
+nm_so_opt='$nm_so_opt'
+nofile='$nofile'
+nroff='$nroff'
+optimize='$optimize'
+osname='$osname'
+osvers='$osvers'
+package='$package'
+pagesize='$pagesize'
+passcat='$passcat'
+perl='$perl'
+pg='$pg'
+phostname='$phostname'
+pidtype='$pidtype'
+plibpth='$plibpth'
+pmake='$pmake'
+pr='$pr'
+prototype='$prototype'
+rm='$rm'
+rmail='$rmail'
+runnm='$runnm'
+sed='$sed'
+sendmail='$sendmail'
+sh='$sh'
+shar='$shar'
+sharpbang='$sharpbang'
+shsharp='$shsharp'
+signal_t='$signal_t'
+sizetype='$sizetype'
+sleep='$sleep'
+smail='$smail'
+so='$so'
+sockethdr='$sockethdr'
+socketlib='$socketlib'
+sort='$sort'
+spackage='$spackage'
+spitshell='$spitshell'
+src='$src'
+startsh='$startsh'
+strings='$strings'
+submit='$submit'
+sysman='$sysman'
+tablesize='$tablesize'
+tail='$tail'
+tar='$tar'
+tbl='$tbl'
+test='$test'
+timeincl='$timeincl'
+touch='$touch'
+tr='$tr'
+troff='$troff'
+uname='$uname'
+uniq='$uniq'
+uptime='$uptime'
+usemymalloc='$usemymalloc'
+usenm='$usenm'
+usrinc='$usrinc'
+uuname='$uuname'
+vi='$vi'
+voidflags='$voidflags'
+warnings='$warnings'
+xlibpth='$xlibpth'
+zcat='$zcat'
+zip='$zip'
+EOT
+
+: add special variables
+$test -f $src/patchlevel.h && \
+awk '/^#define/ {printf "%s=%s\n",$2,$3}' $src/patchlevel.h >>config.sh
+echo "CONFIG=true" >>config.sh
+
+: propagate old symbols
+if $test -f UU/config.sh; then
+       <UU/config.sh sort | uniq >UU/oldconfig.sh
+       sed -n 's/^\([a-zA-Z_0-9]*\)=.*/\1/p' config.sh config.sh UU/oldconfig.sh |\
+       sort | uniq -u >UU/oldsyms
+       set X `cat UU/oldsyms`
+       shift
+       case $# in
+       0) ;;
+       *)
+               cat <<EOM
+Hmm...You had some extra variables I don't know about...I'll try to keep 'em...
+EOM
+               echo "# Variables propagated from previous config.sh file." >>config.sh
+               for sym in `cat UU/oldsyms`; do
+                       echo "    Propagating $hint variable "'$'"$sym..."
+                       eval 'tmp="$'"${sym}"'"'
+                       echo "$tmp" | \
+                               sed -e "s/'/'\"'\"'/g" -e "s/^/$sym='/" -e "s/$/'/" >>config.sh
+               done
+               ;;
+       esac
+fi
+
+: preserve RCS keywords in files with variable substitution, grrr
+Id='$Id'
+
+: Finish up by extracting the .SH files
+case "$alldone" in
+exit)
+       $rm -rf UU
+       echo "Done."
+       exit 0
+       ;;
+cont)
+       ;;
+'')
+       dflt=''
+       nostick=true
+       $cat <<EOM
+
+If you'd like to make any changes to the config.sh file before I begin
+to configure things, do it as a shell escape now (e.g. !vi config.sh).
+
+EOM
+       rp="Press return or use a shell escape to edit config.sh:"
+       . UU/myread
+       nostick=''
+       case "$ans" in
+       '') ;;
+       *) : in case they cannot read
+               sh 1>&4 -c "$ans";;
+       esac
+       ;;
+esac
+
+: if this fails, just run all the .SH files by hand
+. ./config.sh
+
+echo " "
+exec 1>&4
+. ./UU/extract
+
+if $contains '^depend:' [Mm]akefile >/dev/null 2>&1; then
+       dflt=y
+       case "$silent" in
+       true) ;;
+       *)
+               $cat <<EOM
+
+Now you need to generate make dependencies by running "make depend".
+You might prefer to run it in background: "make depend > makedepend.out &"
+It can take a while, so you might not want to run it right now.
+
+EOM
+               ;;
+       esac
+       rp="Run make depend now?"
+       . UU/myread
+       case "$ans" in
+       y*)
+               make depend && echo "Now you must run a make."
+               ;;
+       *)
+               echo "You must run 'make depend' then 'make'."
+               ;;
+       esac
+elif test -f [Mm]akefile; then
+       echo " "
+       echo "Now you must run a make."
+else
+       echo "Done."
+fi
+
+$rm -f kit*isdone ark*isdone
+$rm -rf UU
+
+: End of Configure
+
diff --git a/FAQ b/FAQ
new file mode 100644 (file)
index 0000000..195a369
--- /dev/null
+++ b/FAQ
@@ -0,0 +1,146 @@
+Frequently asked questions about the PennMUSH Server, post-pl10
+Updated: November 11, 2002
+
+*** There are other FAQs on the web at:
+*** http://www.pennmush.org/cgi-penn/fom/
+*** http://services.pennmush.org/faq.html
+
+0. What's the release history since 1.50pl10?
+1. How do I ask for help with a problem?
+2. How do I report a bug?
+3. How do I request a new feature?
+4. Where can I get more information about admin'ing and hacking MUSH?
+5. Where can I hear about new releases?
+6. Why doesn't %t or space() work right for Pueblo clients?
+7. Compiling with the lcc compiler.
+8. Patch information in @version and INFO
+
+-----------------------------
+
+0. What's the release history since 1.50pl10?
+
+PennMUSH 1.50pl10 is the last patchlevel of PennMUSH developed by Amberyl.
+Amberyl handed over the maintenance, development, and support of
+PennMUSH to Javelin/Paul (Alan Schwartz) after 1.50pl10.
+
+The first two post-pl10 releases were termed the "dune-1" and "dune-2"
+releases (in honor of DuneMUSH, where Alan did most of his development
+work). Amberyl and Javelin agreed that it was silly to start a whole
+new numbering scheme, so the next patchlevel released was pl11.
+
+Javelin, along with the other two PennMUSH developers, T. Alexander
+Popiel and Ralph Melton, made so many internal changes that it was
+time for a new numbering scheme, and PennMUSH was advanced to 1.6.x.
+
+Ralph Melton has since retired, and Thorvald Natvig took his place
+on the devteam. He rewrote the command parser, and PennMUSH was
+advanced to 1.7.0.
+
+Currently, the active development team is Javelin, Talek, and Raevnos.
+The stable version is 1.7.6, and the development version is 1.7.7.
+
+1. How do I ask for help with a problem?
+
+Email to dunemush@pennmush.org
+When asking for help, please be as specific as you can about the
+problem. Include at least the following:
+ - Version of PennMUSH including any official patches you've applied
+ - Host machine brand (Sun, Dec, etc.), model (Sparcstation, etc.)
+ - Operating system version (e.g., Ultrix 4.4)
+ - Compiler used to compile (if a compilation problem)
+ - A description of the problem: what you think it should be doing that
+   it isn't.
+ - If things were working, and you recently changed something and they're
+   not working now, what did you change?
+
+2. How do I report a bug?
+
+Email to pennmush-bugs@pennmush.org
+or visit http://www.pennmush.org/jitterbug/pennmush
+
+Include specific information as described in #3 above. If you know
+what's causing the bug, or how to fix it, or if you have a patch for
+the bug, send it along. If you don't, and the bug caused a crash with
+a core dump, you can send along a stack trace (see #6 if you don't know
+how to do this). 
+
+Bugs are patched as quickly as possible. Patches for bugs are
+emailed to the pennmush@pennmush.org mailing list and to the
+pennmush-announce@pennmush.org mailing list (to subscribe, visit
+http://www.pennmush.org/mailman/listinfo) and are put on the pennmush
+ftp site, in the /pub/PennMUSH/Source directory.
+
+The pennmush-announce list distributes developer announcements
+only; the pennmush list also includes discussion.
+
+3. How do I request a new feature?
+
+Email to pennmush-developers@pennmush.org
+
+No promises, but we try to get back to you about the feasibility
+of suggestions quickly, and implement them as we can.
+
+If I don't think it belongs in the distribution, I'll ask you to contact
+pennhack-volunteers@pennmush.org, a group of volunteer
+hackers who will custom-hack for your MUSH. If you're sure that what
+you need isn't of general interest, you can write to them directly.
+
+4. Where can I get more information about admin'ing and hacking MUSH?
+
+Read Javelin's God for PennMUSH Gods, loads of info about setting up
+a MUSH, hacking source code, daily maintenance, and many tips from
+other Gods!
+
+By WWW: http://www.pennmush.org/~alansz/guide.html
+By ftp: ftp.pennmush.org, /pub/PennMUSH/Guide
+
+5. Where can I hear about new releases?
+
+New releases of the PennMUSH code are announced on the
+PennMUSH and PennMUSH-Announce mailing lists (see above) and
+rec.games.mud.{tiny,admin,announce}
+
+Patches are only announced on the mailing lists, and are put on the
+ftp site.
+
+6. Why doesn't %t or space() work right for Pueblo clients?
+
+Actually, it does. Pueblo is built around an HTML browser. In HTML,
+multiple whitespace is ignored and treated as a single space.  This is
+correct behavior. In HTML, if you really want spaces to count as spaces,
+you must put your text in <PRE>..</PRE> blocks, e.g. tagwrap(PRE,this
+%t has %t tabs %t and    spaces).
+
+7. Compiling with the lcc compiler.
+
+lcc is a freely available C compiler described in the book _A Retargetable
+C Compiler: Design and Implementation_, by C.W. Fraser and D.R. Hanson,
+and is available from http://www.cs.princeton.edu/software/lcc/. A
+precompiled windows version is available. This might be of interest to
+people who want to compile Penn on Windows but don't want to download
+cygwin or another package with a Windows port of gcc. The lcc package
+might be a smaller download, but does lack some tools like sh that
+Configure and restart depend on, so you'll have to find them or try to
+figure out everything in config.h yourself.
+
+PennMUSH can be compiled using lcc, though it produces a lot of spurious
+warnings. After running Configure, make sure that HAS_STRDUP is commented
+out of config.h. lcc uses many of gcc's headers and libraries, so that
+this function might be detected, but it's never recognized by lcc. We'll
+just use our own implementation, as it makes things easier.
+
+8. Patch information in @version and INFO
+
+Files in the pennmush/patches directory are checked to see if they look
+like a patch file, and if so, information from this is included in
+@version and INFO.
+
+So, what makes it look like a patch file? Two lines.
+# Patch name: Whatever
+# Patch version: Whichever
+
+Whatever and Whichever are used in the @version report.
+The file with this patch information is rebuilt when needed by running
+make.
+
+
diff --git a/I18N b/I18N
new file mode 100644 (file)
index 0000000..c600259
--- /dev/null
+++ b/I18N
@@ -0,0 +1,58 @@
+============================================================================
+                         Internationalization in 17.x
+============================================================================
+
+PennMUSH 1.7.3 and later have extensive support for running in non-English
+environments. There are two parts to this. First, what the mush considers
+to be valid characters, what's upper-case, what's lower-case, how they
+are sorted, and so on. The second is translating the messages produced
+by the game into another language. (Helpfiles aren't currently translated.)
+
+Several languages are currently supported to one degree or another.
+If your favorite language isn't (or if you want to increase the
+level of support), and you're fluent in it, you can help out!
+See http://www.pennmush.org/translation.html.
+
+Localization (the process of making a MUSH conform to a given "locale")
+is controlled by the LANG or LC_ALL environment variables, which have
+the form "la_DI", where la is a language code and DI is a dialect. For
+example, "en_US" stands for English, United States dialect. The
+language codes are usually the same as a country's top level domain in
+URLs. Spanish is 'es', Russian is 'ru', and so on.
+
+There are two places where you have to use LANG: when you're
+compiling the MUSH (in order to compile the message translation files)
+and when you start up the MUSH (in order to set the locale for
+the running server).
+
+1. Compiling the MUSH - tips
+
+Files with translated messages are located at
+http://download.pennmush.org/Source/translations/. Get the one for your
+patchlevel and language, put it in your pennmush/po/ directory.
+
+These files have to be compiled to an efficient internal form before
+starting up the mush.  This means you should set your LANG environment
+variable before you compile your server.
+
+To do this using the common bash shell, 'export LANG=en_US', or whatever
+your setting is. If that gives an error, try the tcsh shell form 'setenv
+LANG en_US'. If that still doesn't work, consult the documentation for
+the shell you're using.
+
+The message files are compiled during the "make install" step.
+
+Be sure that you compile with a COMPRESSION_TYPE (in options.h) that
+is 8-bit clean so it will work properly with non-ASCII characters.
+
+2. Starting up the MUSH - tips
+
+The LANG environment variable that controls what language the mush
+uses is normally set in the pennmush/game/restart script. There's an
+example for using French in the script already that can be used as a
+starting point.
+
+Your server account might be set up with a non-english language as the
+default. If so, and you don't set LANG in the restart script, that
+default language will be used.
+
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..89c2f32
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,196 @@
+============================================================================
+                   Installation Guide to PennMUSH 1.7.x
+============================================================================
+This file explains how to install PennMUSH. It comes in three parts:
+  A. Important background
+  B. Installation from source (recommended)
+  C. Installation of precompiled binaries (only for Windows platforms)
+
+If you are upgrading from a previous PennMUSH release, this is
+probably not the file you want to start with. Read the UPGRADING
+file first.
+
+DISCLAIMER: Before attempting to run a MUD of any sort, you should have
+some reasonable knowledge of UNIX and C.  If you do not, it is _strongly_
+suggested that you learn UNIX and C to some reasonable level of competency
+before attempting to set up a MUSH.  (Note that even people using the
+Windows ports are encouraged to know UNIX, because that's the paradigm
+that PennMUSH was built with, and most resources will be written with
+UNIX is mind.)
+
+You may also want to take a look at Javelin's Guide for PennMUSH Gods,
+at http://pennmush.org/~alansz/guide.html
+or by ftp from pennmush.org, /pub/PennMUSH/Guide
+============================================================================
+A. Important background
+
+Here's a quick picture of the organization of the MUSH directory tree.
+The "src" directory contains C source code.  The "hdrs" directory
+contains header files for the source code.  The files used by a running
+MUSH are in the "game" directory, which includes subdirectories "data"
+(current databases), "txt" (text files and directories for building them),
+"log" (log files), and "save" (backup databases).  Finally, the "hints"
+directory is used during the installation process, the "po" directory
+holds translation message files, and the "os2" directory contains files
+of using in building for OS/2.
+
+ pennmush--+-> src
+           +-> hdrs 
+           +-> game ------+-> data 
+           |              |   
+           |              +-> txt -------+-> nws 
+           |              |              +-> evt 
+           |              |              \-> hlp 
+           |              |                  
+           |              +-> log 
+           |              \-> save 
+           +-> hints 
+           +-> os2
+           +-> po
+           +-> utils 
+           \-> win32 
+               
+
+PennMUSH has been tested on a fairly wide variety of machines and
+operating systems including at least:
+
+       NeXT Mach 2.1
+       Sun Sparc SunOS 4.1.x
+       Sun Sparc and i386 Solaris 2.x 
+       DEC Decstation Ultrix 4.x and OSF/1
+       DEC Alpha OSF/1 and Linux
+       SGI Indy Irix 5.x and 6.x
+       HP 9000 series HP-UX 8.x
+       IBM RS/6000 AIX 3.2
+       IBM S/390 Linux
+       Novell Unixware SVR4
+       Linux
+       FreeBSD
+       AT&T SVR4
+       Windows 95/NT cygwin and MSVC++
+       OS/2
+
+There's no real reason why PennMUSH shouldn't compile on any 32-bit
+or better BSD, System V, or POSIX operating system.  Javelin does his
+development on a Linux PC these days.
+
+If you have serious problems, contact Javelin and he will try to help
+you. Email is the best way to get a fast response; in an emergency, you
+can bother him on a MUD, but for code problems, email will probably get
+you a better response.
+
+============================================================================
+
+B. Installation from source
+
+     The quickstart version of the installation is:
+
+1. On win32 only, install proper tools or read win32/README.txt.
+2. sh Configure -d or some variant
+3. create options.h, or make update
+4. make install
+5. possibly make customize
+6. Read game/README and follow those instructions
+
+     Here's the process in detail:
+
+1. If you're running on win32, there are two options known to work:
+   a. Compile with MS VC++ or VS.NET. If you want to do this, read 
+      win32/README.txt and then skip down to step #6 below
+   b. Compile with the Cygwin unix emulation tools (http://www.cygwin.com)
+      In addition to the base cygwin stuff, you'll want the following packages:
+         binutils, gcc, make, patch, perl, exim (the latest *source* code
+         package, not the binary)
+      (gcc 3.2 is recommended.)
+      These are also recommended:
+         gettext, gettext-devel, indent, vim or emacs
+      It is recommended that you install the tools under C:\CYGWIN and
+      that you read the Cygwin FAQ if you get messages about HOME not set.
+
+      Put the pennmush .tar.gz file in C:\CYGWIN\USR\SRC. Don't uncompress
+      it with Winzip or other windows tools!
+
+      VERY IMPORTANT: The rest of the instructions assume that you have
+      started up a bash shell and are running commands under that shell --
+      they won't work right if run directly from a DOS shell.
+
+        cd /usr/src, and unpack the .tar.gz file with:
+             tar xfz pennmush-whatever.tar.gz
+
+2. cd pennmush. On Unix systems:
+       ./Configure -d
+
+   On cygwin systems, try ./Configure -d, but if it fails, try:
+       . Configure
+     (That's a single period, a space, and Configure)
+     When you get to the question about hints files, choose 'cygwin'.
+     For all other questions, the defaults should work.
+
+3. EITHER:
+
+Copy options.h.dist to options.h. Note that these files stay in the
+pennmush directory.
+
+Edit the file. It's liberally commented. 
+
+On Cygwin systems, you should *not* define NT_TCP with cygwin, and you
+should use MALLOC_PACKAGE 0. You may use an COMPRESSION_TYPE you prefer.
+
+Also, cp game/mushcnf.dst to game/mush.cnf and edit. 
+
+On cygwin systems, you probably should not use compressed database,
+so modify that bit.
+
+OR:
+
+Type 'make update', and answer all the questions about which MUSH
+options you want.
+
+You should not need to change any of the other header files.
+
+4. On cygwin systems, add to the CCFLAGS in the Makefile:
+        -I/usr/src/exim-4.20-1/minires
+   (or whatever the latest exim source directory is)
+
+5. Do a "make install". This will build all the necessary files, and
+set up some symbolic links for the restart script.  You will probably
+receive a few compilation warnings, which can generally be ignored.
+
+6. If you plan to run multiple MUSHes, you may want to do a "make
+customize" which will run a script to help set up a separate customized
+game subdirectory for each MUSH (run it once per MUSH you plan to run).
+Files in these subdirectories will already be customized in many ways,
+so what follows may be slightly different. :) This is probably broken.
+
+7. Read game/README and follow those instructions. 
+
+A final thing you may want to think about is compiling announce.c or
+portmsg.c. These are port announcers; if your MUSH ever goes down, you can
+set one up, and a message will be given to a person attempting to connect
+to that port.  Read that file for details. It is not an official MUSH
+piece of code; rather, it is a freely distributable program available
+via anonymous FTP that is included in this code because it happens to
+be fairly useful.  Javelin suggests using portmsg - it appears to be
+more stable.
+
+============================================================================
+
+C. Installation of precompiled binaries (only for Windows platforms)
+
+A pre-built binary is frequently available for win32 users who don't
+want to customize their MUSH server, and don't feel like compiling it
+themselves.  This binary distribution may not contain the src, hdrs,
+hints, or os2 directories and may be missing several key files (like
+Configure) from the pennmush directory.  It does include the options.h
+that it was built with, as an aid to those who decide later that they
+want to customize the server; they are useful as a baseline to work from.
+
+Using the pre-built binary is fairly simple; adjust your configuration
+file as in game/README, then go to the game directory and run
+PennMUSH.exe (you may need to use PennMUSH /run or PennMUSH /start).
+Alternately, if you want the MUSH to automatically start each time you
+turn on your machine, you can install it as a system service by running
+'PennMUSH /install'.  PennMUSH can be removed from service status via
+'PennMUSH /remove'.
+
diff --git a/MANIFEST b/MANIFEST
new file mode 100644 (file)
index 0000000..66a1141
--- /dev/null
+++ b/MANIFEST
@@ -0,0 +1,252 @@
+game/data/README
+game/README
+game/access.README
+game/aliascnf.dst
+game/getdate.README
+game/getdate.template
+game/mushcnf.dst
+game/namescnf.dst
+game/restart.dst
+game/restrictcnf.dst
+game/log/README
+game/save/README
+game/txt/evt/index.evt
+game/txt/evt/pennmush.evt
+game/txt/Makefile
+game/txt/README
+game/txt/changes.sh
+game/txt/changes.txt
+game/txt/compose.sh.SH
+game/txt/connect.txt
+game/txt/down.txt
+game/txt/full.txt
+game/txt/guest.txt
+game/txt/index-files.pl
+game/txt/motd.txt
+game/txt/newuser.txt
+game/txt/quit.txt
+game/txt/register.txt
+game/txt/hlp/cobra_attr.hlp
+game/txt/hlp/cobra_chat.hlp
+game/txt/hlp/cobra_cmd.hlp
+game/txt/hlp/cobra_code.hlp
+game/txt/hlp/cobra_conf.hlp
+game/txt/hlp/cobra_division.hlp
+game/txt/hlp/cobra_flag.hlp
+game/txt/hlp/cobra_func.hlp
+game/txt/hlp/cobra_mail.hlp
+game/txt/hlp/cobra_pueb.hlp
+game/txt/hlp/cobramush.hlp
+game/txt/hlp/cobratop.hlp
+game/txt/hlp/index.hlp
+game/txt/hlp/pennv174.hlp
+game/txt/hlp/pennv175.hlp
+game/txt/hlp/pennv176.hlp
+game/txt/hlp/pennv177.hlp
+game/txt/hlp/pennvOLD.hlp
+game/txt/nws/base.nws
+BUGS
+CHANGES.174
+CHANGES.175
+CHANGES.176
+CHANGES.177
+CHANGES.OLD
+COPYRITE
+Configure
+FAQ
+I18N
+INSTALL
+MANIFEST
+Makefile.SH
+Patchlevel
+README
+README.SQL
+README.SSL
+UPGRADING
+config_h.SH
+confmagic.h
+options.h.dist
+hdrs/access.h
+hdrs/ansi.h
+hdrs/atr_tab.h
+hdrs/attrib.h
+hdrs/boolexp.h
+hdrs/bufferq.h
+hdrs/case.h
+hdrs/chunk.h
+hdrs/command.h
+hdrs/compile.h
+hdrs/conf.h
+hdrs/copyrite.h
+hdrs/cron.h
+hdrs/csrimalloc.h
+hdrs/dbdefs.h
+hdrs/dbio.h
+hdrs/division.h
+hdrs/extchat.h
+hdrs/externs.h
+hdrs/extmail.h
+hdrs/flags.h
+hdrs/function.h
+hdrs/game.h
+hdrs/getpgsiz.h
+hdrs/help.h
+hdrs/htab.h
+hdrs/ident.h
+hdrs/intrface.h
+hdrs/lock.h
+hdrs/log.h
+hdrs/malias.h
+hdrs/match.h
+hdrs/mushdb.h
+hdrs/mushtype.h
+hdrs/mymalloc.h
+hdrs/mysocket.h
+hdrs/myssl.h
+hdrs/oldflags.h
+hdrs/parse.h
+hdrs/pcre.h
+hdrs/privtab.h
+hdrs/ptab.h
+hdrs/pueblo.h
+hdrs/shs.h
+hdrs/strtree.h
+hdrs/version.h
+hints/a-u-x.sh
+hints/aix.sh
+hints/cygwin.sh
+hints/darwin.sh
+hints/dec_osf.sh
+hints/freebsd.sh
+hints/freebsd_5.sh
+hints/hpux-gcc.sh
+hints/hpux.sh
+hints/irix.sh
+hints/irix_6.sh
+hints/linux_2.sh
+hints/mingw32.sh
+hints/next.sh
+hints/openbsd.sh
+hints/os2.sh
+hints/solaris_2.sh
+hints/sunos_4.sh
+hints/ultrix.sh
+hints/win32-gcc.sh
+hints/win32.sh
+os2/GCCOPT.CMD
+os2/Makefile
+os2/Penn-OS2.htm
+os2/config.h
+po/Makefile
+src/Makefile.SH
+src/SWITCHES
+src/access.c
+src/announce.c
+src/atr_tab.c
+src/attrib.c
+src/boolexp.c
+src/bsd.c
+src/bufferq.c
+src/chunk.c
+src/cmdlocal.dst
+src/cmds.c
+src/command.c
+src/comp_h.c
+src/comp_w.c
+src/comp_w8.c
+src/compress.c
+src/conf.c
+src/console.c
+src/cque.c
+src/create.c
+src/cron.c
+src/csrimalloc.c
+src/db.c
+src/destroy.c
+src/division.c
+src/extchat.c
+src/extmail.c
+src/filecopy.c
+src/flaglocal.dst
+src/flags.c
+src/funcrypt.c
+src/function.c
+src/fundb.c
+src/fundiv.c
+src/funlist.c
+src/funlocal.dst
+src/funmath.c
+src/funmisc.c
+src/funstr.c
+src/funtime.c
+src/funufun.c
+src/game.c
+src/gmalloc.c
+src/help.c
+src/htab.c
+src/ident.c
+src/info_slave.c
+src/local.dst
+src/lock.c
+src/log.c
+src/look.c
+src/malias.c
+src/match.c
+src/memcheck.c
+src/move.c
+src/mycrypt.c
+src/mymalloc.c
+src/mysocket.c
+src/myssl.c
+src/notify.c
+src/parse.c
+src/pcre.c
+src/player.c
+src/plyrlist.c
+src/portmsg.c
+src/predicat.c
+src/privtab.c
+src/prog.c
+src/ptab.c
+src/rob.c
+src/rplog.c
+src/services.c
+src/set.c
+src/shs.c
+src/sig.c
+src/speech.c
+src/sql.c
+src/strdup.c
+src/strtree.c
+src/strutil.c
+src/switchinc.c
+src/tables.c
+src/timer.c
+src/unparse.c
+src/utils.c
+src/version.c
+src/warnings.c
+src/wild.c
+src/wiz.c
+utils/clwrapper.sh
+utils/customize.pl
+utils/fixdepend.pl
+utils/gentables.c
+utils/ln-dir.sh
+utils/make_access_cnf.sh
+utils/mkcmds.sh.SH
+utils/mkvershlp.pl
+utils/penn-install
+utils/update-cnf.pl
+utils/update.pl
+win32/README.mingw
+win32/README.txt
+win32/cmds.h
+win32/cobramush.sln
+win32/config.h
+win32/confmagic.h
+win32/funs.h
+win32/options.h
+win32/patches.h
+win32/pennmush.dsp
+win32/pennmush.dsw
diff --git a/Makefile.SH b/Makefile.SH
new file mode 100644 (file)
index 0000000..0bc6908
--- /dev/null
@@ -0,0 +1,328 @@
+case $CONFIG in
+'')
+       if test -f config.sh; then TOP=.;
+       elif test -f ../config.sh; then TOP=..;
+       elif test -f ../../config.sh; then TOP=../..;
+       elif test -f ../../../config.sh; then TOP=../../..;
+       elif test -f ../../../../config.sh; then TOP=../../../..;
+       else
+               echo "Can't find config.sh."; exit 1
+       fi
+       . $TOP/config.sh
+       ;;
+esac
+: This forces SH files to create target in same directory as SH file.
+: This is so that make depend always knows where to find SH derivatives.
+case "$0" in
+*/*) cd `expr X$0 : 'X\(.*\)/'` ;;
+esac
+
+echo "Extracting Makefile (with variable substitutions)"
+
+if test "x$OSTYPE" = "xmsys"; then
+  INSTALL_LINKS="$lns ../src/netmud.exe netmush"
+else
+  INSTALL_LINKS="$lns ../src/netmud netmush; \
+                 $lns ../src/console console; \
+                 $lns ../src/info_slave info_slave"
+fi
+
+: This section of the file will have variable substitutions done on it.
+: Move anything that needs config subs from !NO!SUBS! section to !GROK!THIS!.
+: Protect any dollar signs and backticks that you do not want interpreted
+: by putting a backslash in front.  You may delete these comments.
+$spitshell >Makefile <<!GROK!THIS!
+# Makefile for PennMUSH 
+
+# - System configuration - #
+
+#
+# This section of the file should be automatically configured by
+# the Configure script. If it doesn't work, you might try starting
+# from the Makefile.old that's included instead, and reporting
+# your problem (including this Makefile) to Paul/Javelin,
+# dunemush@pennmush.org
+#
+# If you want to profile the code, add -pg -a -DPROFILING to CCFLAGS
+# and (probably) remove -O
+#
+$make_set_make
+CC=$cc
+CCFLAGS=$optimize -I.. -I../hdrs $ccflags $warnings
+LDFLAGS=$ldflags
+CLIBS=$libs $cryptlib $libssl $libcrypto $libmysqlclient
+INSTALL=$install
+INSTALLDIR=$installdir
+CP=$cp
+CHMOD=$chmod
+INSTALL_LINKS=$INSTALL_LINKS
+!GROK!THIS!
+
+: In the following dollars and backticks do not need the extra backslash.
+$spitshell >>Makefile <<'!NO!SUBS!'
+
+
+# stupid SYS V shell
+SHELL=/bin/sh
+# Where to install with 'make globalinstall'
+GLOBAL_INSTALL=/usr/libexec/pennmush
+
+# Where to install with 'make debianinstall'
+DEB_INSTALL=$(DESTDIR)/usr/lib/pennmush/game
+DEB_BIN=$(DESTDIR)/usr/games
+
+all: config.h options.h autogen game/mush.cnf
+       @echo "Making all in src."
+       (cd src; make all "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" "MAKE=$(MAKE)" \
+       "MAKEFLAGS=$(MAKEFLAGS)")
+       @echo "If the make was successful, use 'make install' to install links."
+
+config.h: Configure
+       @echo "Looks like your Configure has been updated."
+       @echo "Run that first."
+       exit 1
+
+options.h: options.h.dist
+       @echo "Please use 'make update' to update your options.h file from options.h.dist"
+       @echo "You must cp options.h.dist to options.h and edit it."
+       exit 1
+
+autogen: hdrs/cmds.h hdrs/funs.h hdrs/switches.h
+
+hdrs/cmds.h: src/cmds.c src/command.c src/cque.c src/help.c src/set.c src/sql.c Patchlevel
+       (cd utils; sh mkcmds.sh commands)
+
+hdrs/switches.h: src/SWITCHES Patchlevel
+       (cd utils; sh mkcmds.sh switches)
+
+src/switchinc.c: src/SWITCHES Patchlevel
+       (cd utils; sh mkcmds.sh switches)
+
+hdrs/funs.h: src/fun*.c src/bsd.c src/conf.c src/extmail.c src/help.c src/wiz.c src/sql.c Patchlevel
+       (cd utils; sh mkcmds.sh functions)
+
+hdrs/patches.h: patches/*
+       (cd utils; sh mkcmds.sh patches)
+
+install: localized all game/restart
+       -rm -f game/netmush
+       -rm -f game/console
+       -rm -f game/info_slave
+       (cd game; $(INSTALL_LINKS))
+       (cd game/txt; make)
+       @echo "If you plan to run multiple MUSHes, consider running 'make customize'"
+
+netmud: 
+       (cd src; make netmud "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+console:
+       (cd src; make console "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+access:
+       utils/make_access_cnf.sh game
+
+pennmush.pot:
+       (cd src; make ../po/pennmush.pot)
+
+localized:
+       -echo "Localizing for your locale..."
+       -(cd po; make localized)
+
+portmsg:
+       (cd src; make portmsg "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+announce:
+       (cd src; make announce "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+versions: CHANGES*
+       -@rm -rf CHANGES*~ CHANGES*bak
+       @utils/mkvershlp.pl game/txt/hlp CHANGES*
+
+safety:
+       $(CP) src/*.c /var/pennmush-bak/src
+       $(CP) hdrs/*.h /var/pennmush-bak/hdrs
+       $(CP) * /var/pennmush-bak
+
+distdepend: hdrs/funs.h hdrs/cmds.h
+       (cd src; make depend "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+# REQUIRES GNU INDENT! DON'T INDENT WITH ANYTHING ELSE!
+indent:
+       @(cd src; make indent)
+
+protoize:
+       (cd src; make protoize "CCFLAGS=$(CCFLAGS)")
+
+!NO!SUBS!
+
+: This section of the file will have variable substitutions done on it.
+: Move anything that needs config subs from !NO!SUBS! section to !GROK!THIS!.
+: Protect any dollar signs and backticks that you do not want interpreted
+: by putting a backslash in front.  You may delete these comments.
+$spitshell >>Makefile <<!GROK!THIS!
+
+customize: update-conf
+       -@$perl utils/customize.pl
+
+# The default place to find the runtime files is in this directory,
+# but it can be overridden with env variables so people can use
+# other game directories.
+GAMEDIR=game
+
+update-conf: game/mushcnf.dst game/aliascnf.dst game/restrictcnf.dst game/namescnf.dst game/restart.dst
+       -@$touch game/mushcnf.dst
+       -@$perl utils/update-cnf.pl \$(GAMEDIR)/mush.cnf game/mushcnf.dst
+       -@$touch game/aliascnf.dst
+       -@$perl utils/update-cnf.pl \$(GAMEDIR)/alias.cnf game/aliascnf.dst
+       -@$touch game/restrictcnf.dst
+       -@$perl utils/update-cnf.pl \$(GAMEDIR)/restrict.cnf game/restrictcnf.dst
+       -@if [ ! -f \$(GAMEDIR)/names.cnf ]; then \$(CP) game/namescnf.dst \$(GAMEDIR)/names.cnf; fi
+       -@if [ ! -f \$(GAMEDIR)/restart ]; then \$(CP) game/restart.dst \$(GAMEDIR)/restart; chmod 755 \$(GAMEDIR)/restart; fi
+
+\$(GAMEDIR)/alias.cnf: game/aliascnf.dst
+       -@$touch game/aliascnf.dst
+       -@$perl utils/update-cnf.pl \$(GAMEDIR)/alias.cnf game/aliascnf.dst
+
+\$(GAMEDIR)/restrict.cnf: game/restrictcnf.dst
+       -@$touch game/restrictcnf.dst
+       -@$perl utils/update-cnf.pl \$(GAMEDIR)/restrict.cnf game/restrictcnf.dst
+
+\$(GAMEDIR)/names.cnf: game/namescnf.dst
+       if [ ! -f \$(GAMEDIR)/names.cnf ]; then \
+               \$(CP) game/namescnf.dst \$(GAMEDIR)/names.cnf; \
+       fi
+
+\$(GAMEDIR)/restart: game/restart.dst
+       if [ ! -f \$(GAMEDIR)/restart ]; then \
+               \$(CP) game/restart.dst \$(GAMEDIR)/restart ; \
+               chmod 755 \$(GAMEDIR)/restart ; \
+       fi
+
+\$(GAMEDIR)/mush.cnf: game/mushcnf.dst
+       -@$touch game/mushcnf.dst
+       -@$perl utils/update-cnf.pl \$(GAMEDIR)/mush.cnf game/mushcnf.dst
+
+update: update-hdr update-conf
+
+update-hdr:
+       -@$touch options.h.dist
+       -@$perl utils/update.pl options.h options.h.dist
+
+test: netmud
+       (cd test; $perl alltests.pl)
+
+!GROK!THIS!
+
+: In the following dollars and backticks do not need the extra backslash.
+$spitshell >>Makefile <<'!NO!SUBS!'
+
+clean:
+       (cd src; make clean)
+       (cd game; rm -f netmush console info_slave)
+
+distclean: 
+       (cd src; make distclean)
+       (cd hdrs; rm -f *.orig *~ \#* *.rej *.bak funs.h cmds.h)
+       (cd utils; rm -f *.orig *~ \#* *.rej *.bak)
+       (cd game; rm -rf *.log netmush console info_slave *.orig *.rej *~ *.bak mush.cnf)
+       (cd os2; rm -rf *.rej *.orig *~ *.bak)
+       (cd game/txt; make clean)
+
+totallyclean: distclean 
+       (cd hdrs; rm -rf *.rej)
+       (cd src; rm -rf *.rej)
+       -rm -f Makefile
+
+distci: distclean ci-src ci-game
+
+ci-src:
+       -(yes . | ci -l -f -N$(NAME) FAQ* BUGS COPY* CHANGE* READ* MANIFEST \
+         Configure utils/* Makefile.SH Patchlevel config_h.SH confmagic.h \
+         *.dist src/Makefile src/SWITCHES src/*.c src/*.dst \
+         hdrs/* hints/* os2/*)
+
+ci-game:
+       -(yes . | ci -l -f -N$(NAME) game/restart game/mushcnf.dst \
+         game/access.README \
+         game/txt/* game/txt/nws/* game/txt/evt/* game/txt/hlp/* )
+
+diffs:
+       @make indent > /dev/null 2>&1
+       @make versions > /dev/null 2>&1
+       @make touchswitches > /dev/null 2>&1
+       @make autogen > /dev/null 2>&1
+       @(prcs diff -r$(VS) -N pennmush `cat MANIFEST` | grep -v 'Index:')
+
+checkin: versions autogen
+       @prcs checkin
+
+patch: versions
+       @make-patch-header
+       @make diffs
+
+etags: 
+       (cd src; make etags)
+
+ctags:
+       (cd src; make ctags)
+
+dist.tar.Z: distclean dist.tar
+       compress /tmp/dist.tar
+
+dist.tar.gz: distclean dist.tar
+       gzip /tmp/dist.tar
+
+touchswitches:
+       @touch src/SWITCHES
+
+dist.tar: indent distclean versions touchswitches autogen
+       makedist -c pennmush
+       tar -cvf /tmp/dist.tar pennmush
+       -pgp -sb /tmp/dist.tar
+       -rm -rf pennmush
+
+CSRImalloc.tar.Z:
+       (cd src/CSRI; make clean)
+       (tar -cvFFf /tmp/CSRImalloc.tar `cat exclude.tar`)
+       compress /tmp/CSRImalloc.tar
+
+globalinstall: install
+       (cd game/txt; make clean compose.sh)
+       $(INSTALLDIR) $(GLOBAL_INSTALL)
+       $(CP) -R game/* $(GLOBAL_INSTALL)
+       rm -f $(GLOBAL_INSTALL)/netmush $(GLOBAL_INSTALL)/info_slave
+       rm -f $(GLOBAL_INSTALL)/console
+       $(INSTALL) config.sh $(GLOBAL_INSTALL)/config.sh
+       $(INSTALL) src/netmud $(GLOBAL_INSTALL)/netmush
+       $(INSTALL) src/console $(GLOBAL_INSTALL)/console
+       $(INSTALL) src/info_slave utils/ln-dir.sh $(GLOBAL_INSTALL)
+       $(CHMOD) a+rX -R $(GLOBAL_INSTALL)
+       @echo "** Files installed in $(GLOBAL_INSTALL). Feel free to move them."
+       @echo "** You can run $(GLOBAL_INSTALL)/ln-dir.sh to create a user directory,"
+       @echo "** or symlink that to somewhere easier to run. You may wish to strip them."
+
+debianinstall: install
+       (cd game/txt; make clean compose.sh)
+       $(INSTALLDIR) $(DEB_INSTALL)
+       $(INSTALLDIR) $(DEB_BIN)
+       $(CP) -R game/* $(DEB_INSTALL)
+       -rm -f $(DEB_INSTALL)/netmush $(DEB_INSTALL)/info_slave
+       -rm -f $(DEB_INSTALL)/console
+       $(INSTALL) config.sh $(DEB_INSTALL)/config.sh
+       $(INSTALL) src/netmud $(DEB_INSTALL)/netmush
+       $(INSTALL) src/console $(DEB_INSTALL)/console
+       $(INSTALL) src/info_slave $(DEB_INSTALL)/info_slave
+       $(INSTALL) utils/penn-install $(DEB_BIN)/penn-install
+       $(CHMOD) a+rX -R $(DEB_INSTALL)
+       $(CHMOD) a+rX $(DEB_BIN)/penn-install
+       @echo "** Files installed in $(DEB_INSTALL)."
+       @echo "** You can run penn-install to create a user directory."
+
+!NO!SUBS!
+chmod 644 Makefile
diff --git a/Patchlevel b/Patchlevel
new file mode 100644 (file)
index 0000000..b468b81
--- /dev/null
@@ -0,0 +1,2 @@
+Do not edit this file. It is maintained by official CobraMUSH releases.
+This is CobraMUSH 0.72.
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..d225b1e
--- /dev/null
+++ b/README
@@ -0,0 +1,235 @@
+============================================================================
+                   User's Guide to PennMUSH 1.7.x
+============================================================================
+
+Some of this Guide was written by Amberyl, and is used with permission.
+Most of it is by Paul/Javelin.  This most recent version has been
+updated by Alex/Talek and Javelin.
+
+Installation information can be found in the files INSTALL or
+UPGRADING, depending on whether it's a new install or an upgrade.
+The file I18N discusses internationalization.
+
+I.    Introduction and history
+II.   Getting Help, Reporting Bugs
+III.  Getting a .pennmush.org hostname and mailing lists
+IV.   Comments
+
+You may also want to take a look at Javelin's Guide for PennMUSH Gods,
+at http://pennmush.org/~alansz/guide.html
+or by ftp from pennmush.org, /pub/PennMUSH/Guide
+============================================================================
+I. Introduction and history
+
+PennMUSH uses a version-numbering system that includes version
+numbers (like 1.7.2) and patchlevels (like p32), usually written
+together (1.7.2p32). 
+
+Starting with 1.7.2, version numbers that are even (like 1.7.2) are
+stable releases - patchlevels on the latest stable release will only be
+issued to fix serious bugs. Version numbers that are odd (like 1.7.3)
+are development releases - patchlevels on the latest development release
+may include new features as well as bugfixes, and development releases
+may not be as stable as stable releases. On the other hand, some new
+features may *increase* stability without fixing bugs per se, and it's
+quite likely that later patchlevels on the development version will be
+more stable than those on the "stable" version.
+
+PennMUSH is a TinyMUD derivative, and one of the branches along the MUSH
+line. "Vanilla" TinyMUSH, which added the "v" registers and functions to
+the basic TinyMUD building commands, was written by Larry Foard. The code
+was later expanded by Jin, of MicroMUSH. In January of 1991, MicroMUSH
+changed its name to MicroMUSE, and the code there continued to develop
+under the MUSE name. At that same point in time, Moonchilde took the
+last public release of that code and began a series of improvements
+and extensions.
+
+That code was released as PernMUSH, named for the MUSH that Moonchilde
+was running. The last released version of that code was version 1.15,
+at the end of November 1991. PernMUSH itself had switched over to
+TinyMUSH 2.0, which Moonchilde had co-written with Glenn Crocker
+(Wizard of TinyCWRU); there was no longer a reason for Moonchilde to
+maintain this code.
+
+In January of 1992, Amberyl began working on the PernMUSH 1.15 code
+release, for TinyKrynn. She took over the code, which no one was
+supporting, and is continuing to work on extending this code, as well
+as improving its compatibility with TinyMUSH 2.0.  She changed the name
+to PennMUSH (named for her school, the University of Pennsylvania), to
+avoid the confusion that resulted from PernMUSH actually running
+TinyMUSH 2.0.
+
+In January of 1995, Amberyl passed on her mantle to Javelin (aka
+Paul@Dune, Alan Schwartz), who is now the maintainer of the primary
+public distribution in development. He released two patchlevels
+numbered "dune-1" and "dune-2" before releasing PennMUSH 1.50 pl11 and
+later distributions. The numbering scheme changed again with PennMUSH
+1.6.0 (see CHANGES.OLD).
+
+Gradually during the early part of 1995, Alan formed the PennMUSH
+development team with T. Alexander Popiel (Talek) and Ralph Melton.
+The development process became more formalized, with official patches,
+a dedicated bug reporting email address, and better tracking of
+outstanding issues and history.
+
+In August of 1997, Ralph Melton left the PennMUSH development team,
+and Thorvald Natvig joined as a new member.  Many thanks go to Ralph
+who contributed much time, code, and good cheer to PennMUSH.
+
+Javelin, in conjunction with Talek (T. Alexander Popiel) and Thorvald
+Natvig, are the current PennMUSH development team.
+
+A MUSH manual should be available at ftp.digex.net, ftp.math.okstate.edu,
+primerd.prime.com, or from wherever you got this code from. The manual
+should be numbered version 2.007 or higher.
+
+If you are planning on modifying the source code to PennMUSH, you'll
+probably want Javelin's Guide for PennMUSH Gods, which should be
+available where you got this code, or, in hypertext, as
+http://pennmush.org/~alansz/guide.html
+
+      Enjoy!
+
+============================================================================
+
+II.  Getting Help, Reporting Bugs
+
+Here are some guidelines about where and how to report bugs or problems
+or generally look for help.
+
+There are three places one could get help with a problem:
+
+1. pennmush@pennmush.org is the PennMUSH mailing list.
+   To subscribe, visit http://www.pennmush.org/mailman/listinfo/pennmush
+
+   The PennMUSH mailing list should only be used for problems,
+   bugs, suggestions, ideas, discussion, etc. that are OF GENERAL INTEREST.
+   It's often hard to say what's of general interest, but a good
+   rule of thumb is:
+
+     Anything that occurs before the MUSH process is running is
+     *not* of general interest
+
+   That is, don't report problems with downloading PennMUSH, compilation,
+   installation, restarts, or database corruption to the mailing list.
+   These are often system specific.
+
+   (If you don't want to hear these discussions, but do want to be
+   informed of new patches, subscribe to pennmush-announce instead,
+   at http://www.pennmush.org/mailman/listinfo/pennmush-announce)
+
+2. pennmush-bugs@pennmush.org is the bug reporting address 
+   for the PennMUSH developers (suggestions go to pennmush-developers,
+   bugs to pennmush-bugs). This will generally give you the fastest
+   response and is ideal for unusual bugs. A web-based submission
+   form is at http://www.pennmush.org/jitterbug/pennmush
+
+3. dunemush@pennmush.org is Javelin's email address.
+   This is a good place for small suggestions, common-seeming bugs,
+   etc. -- stuff you wouldn't want to bother all the developers with.
+   You will receive a quick response.
+
+When reporting a problem, please always include the following
+information:
+
+1. PennMUSH version number
+2. The type of machine you are using (Sun SparcStation, IBM RS/6000, etc.)
+3. The operating system and version (SunOS 4.1.2, AIX 3.2.4, etc.),
+4. The compiler and compiler version (gcc 2.4.5, SGI cc 2.10, etc. -- the
+   'file' command usually tells you the compiler version, if there's no
+   built-in option like '-v' or '-V' to give it), 
+5. Whether or not you have made any changes to the code.
+
+If the problem resulted in a crash and a core dump, a stack trace of
+the core dump (see the section above) should also be included.
+
+If I need additional stuff (like a log of the Configure or make), I'll
+ask for it, but if you know that it's relevant, you can send it along,
+too.
+
+============================================================================
+
+III. Getting a .pennmush.org hostname and mailing lists
+
+Thanks to donations from the PennMUSH community, Javelin was able to
+register the pennmush.org domain name, and, if you're running a PennMUSH,
+you can have yourmush.pennmush.org assigned as a hostname for your MUSH,
+so players don't need to telnet to obscuresite.obscuredomain.com!
+
+NOTE: A hostname is not the same thing as a site. We don't have accounts
+for you to run your MUSH from. You must already have your MUSH 
+running at someplace.edu or whatever -- we just provide a nice hostname
+that will resolve into your current site's IP address.
+
+How do you get a pennmush.org hostname? Go to 
+http://lists.pennmush.org/pennmush.html, and follow the instructions.
+It may take a day or two before the hostname will work.
+
+Thovald also has volunteered to host mailing lists for MUSHes in
+the pennmush.org domain. Details are on the same web page.
+
+============================================================================
+
+IV. Comments
+IV.a. Amberyl's Comments
+
+These are in the first person.  :)
+I've been working with this code for a year and a quarter now.  I can't
+claim that it's particularly elegant or inspired; all I can say is that
+it works (most of the time), and that I've had fun writing it.  I'm
+also hoping that it's quite readable; the sections I've added or
+revised tend to be quite heavily commented.
+
+A number of people have been contributed a lot, directly and
+indirectly, to PennMUSH; many of them are credited in copyright.h.
+Read the file and embarrass them the next time you see them.  ;)
+
+PennMUSH 1.50 patchlevel 3 contains the promised parser rewrite.  A
+great deal of the code is derived or directly taken from the TinyMUSH
+2.0 parser; credit goes to JT Traub (Moonchilde) and Glenn Crocker
+(Wizard) for writing the thing in the first place. In most cases, the
+1.50 parser should now be functionally identical to the parser in
+TinyMUSH 2.0.9; see the news file for a brief summary of the changes.
+Major differences between the 1.50 and 2.0 parsers are almost certainly
+bugs, and should be reported to me.
+
+I do have a life, though, and academics/job/social stuff take priority.
+Thus, don't get too upset if it takes me a while to add your pet hack.
+:)  I'm generally happy to discuss code and life in general, though, so
+if you see me on a MUSH, feel free to say hi.
+
+       Enjoy your MUSH.
+
+             --  Lydia Leong  (lwl@digex.net)
+                 "Amberyl" just about everywhere
+
+IV.b. Paul/Javelin's Comments
+
+And let me recognize T. Alexander Popiel, Shawn Wagner, Nick Gammon,
+Dan Williams, Ervin Hearn III, Ralph Melton, David Cheatham, and Thorvald
+Natvig, other past and present members of the PennMUSH development or
+porting team.  Working with them is a real pleasure.
+
+I am trying to keep extending the functionality of the server, while
+optimizing and rewriting things wherever possible. I'm always
+interested in improvements or ideas for the code, as well as anything
+you might have done to get it to compile and run on unusual systems.
+
+
+               -- Alan Schwartz (dunemush@pennmush.org)
+                  Javelin at most places
+
+
+IV.c. Alex/Talek's Comments
+
+I would like to thank Ralph, Amberyl, Moonchilde, and all the others
+who went either with us or before us.
+
+PennMUSH is the embodiment of many years of hard work by many people.
+May it never stagnate.
+
+               -- Alex (talek@pennmush.org)
+
diff --git a/README.SQL b/README.SQL
new file mode 100644 (file)
index 0000000..6dcf6aa
--- /dev/null
@@ -0,0 +1,118 @@
+
+                            Use SQL with PennMUSH
+                             Revised: 24 May 2004
+
+
+As of version 1.7.7p32, PennMUSH includes functions and commands that
+can access SQL databases. Currently, the following databases are 
+supported:
+
+  * MySQL
+
+This document explains how to use (or avoid) SQL with PennMUSH, and covers
+the following issues:
+
+   I. Compiling with or without SQL
+   II. Mush configuration overview
+   III. SQL setup tips
+
+I. Compiling withi MySQL
+
+  The Configure script distributed with PennMUSH automatically detects
+  the MySQL client library (libmysqlclient) and attempts to link
+  them into the executable, defining HAS_MYSQL in config.h
+
+  If you want to avoid linking these libraries on systems where they
+  are present, use the '-D no_mysql' switch to Configure.
+
+  If you installed Mysql from a binary package (e.g. rpm or apt),
+  you should be sure that your system also has the development
+  package (usuallyl mysql-dev or mysql-devel).
+
+  If you think you have mysql libraries and header files but Configure
+  isn't finding them, they may be in an unusual location on your system.
+  Creating symbolic links from the header directory to /usr/include/mysql
+  and from the libraries in /usr/lib (or adding the mysql library directory
+  to your /etc/ld.so.conf and running ldconfig) are the easiest ways
+  to fix this.
+
+II. Mush configuration overview
+
+  mush.cnf includes these directives that configure the SQL support:
+
+  sql_platform provides the name of the SQL database server software
+  that will be used for connections. It current takes one of two
+  values: "disabled" (no SQL) or "mysql". If not specified, it 
+  defaults to disabled.
+
+  sql_host gives the name of the host running the SQL server.
+  It defaults to 127.0.0.1, which makes a TCP connection to the 
+  local host. The keyword "localhost" instead makes a domain socket
+  (Unix) or named pipe (Windows) connection.
+
+  sql_database gives the name of the database that contains the 
+  MUSH's tables. This must be specified and there is no default.
+  sql_username provides a username to connect to the SQL server
+  with. If no specified, a null username will be used, which many
+  SQL servers treat as "the user running this (pennmush) process".
+
+  sql_password provides the password for the user. It defaults to
+  no password.
+III. SQL setup tips
+
+  You will have to set up the appropriate database on the SQL server,
+  and username permitted to perform operations in that database, 
+  and a password for that username. This is a platform-specific process.
+
+  A. MySQL platform
+
+    Easiest way is:
+
+    % mysql_setpermission --user root                  REQUIRED
+          --host <mysql host> --port <mysql port>      OPTIONAL, OR:
+          --socket <unix domain socket>                        OPTIONAL
+
+    ######################################################################
+    ## Welcome to the permission setter 1.2 for MySQL.
+    ## made by Luuk de Boer
+    ######################################################################
+    What would you like to do:
+      1. Set password for a user.
+      2. Add a database + user privilege for that database.
+         - user can do all except all admin functions
+      3. Add user privilege for an existing database.
+         - user can do all except all admin functions
+      4. Add user privilege for an existing database.
+         - user can do all except all admin functions + no create/drop
+      5. Add user privilege for an existing database.
+         - user can do only selects (no update/delete/insert etc.)
+      0. exit this program
+   
+    Make your choice [1,2,3,4,5,0]: 2                    <==========
+
+    Which database would you like to add: mush
+    The new database mush will be created
+    
+    What username is to be created: mush                 <==========
+    Username = mush
+    Would you like to set a password for  [y/n]: y       <==========
+    What password do you want to specify for :           <==========
+    Type the password again:                             <==========
+    We now need to know from what host(s) the user will connect.
+    Keep in mind that % means 'from any host' ...
+    The host please: localhost                           <==========
+    Would you like to add another host [yes/no]: no      <==========
+    Okay we keep it with this ...
+    The following host(s) will be used: localhost.
+    ######################################################################
+    
+    That was it ... here is an overview of what you gave to me:
+    The database name       : mush
+    The username            : mush
+    The host(s)             : localhost
+    ######################################################################
+    
+    Are you pretty sure you would like to implement this [yes/no]: yes
+
diff --git a/README.SSL b/README.SSL
new file mode 100644 (file)
index 0000000..dd85125
--- /dev/null
@@ -0,0 +1,292 @@
+                            Use SSL with PennMUSH
+                           Revised: 11 August 2003
+
+
+As of version 1.7.7p17, PennMUSH supports SSL connections when linked
+with the OpenSSL library (http://www.openssl.org). The following
+features are supported:
+
+  * Encrypted sessions using SSLv2, SSLv3, and TLSv1 protocols
+    with ephemeral Diffie-Hellman keying.
+  * Authentication of the server via certificates
+  * Authentication of the clients via certificates
+
+This document explains how to use SSL with PennMUSH, and covers
+the following issues:
+
+   I. An SSL overview
+   II. Compiling with OpenSSL
+   III. Mush configuration overview
+   IV. Installing a server certificate (required)
+   V. Using client certificates for authentication (optional)
+   VI. Legal issues
+
+
+I. An SSL overview
+
+  When an SSL client connects to an SSL server, it performs a 
+  "handshake" that looks something like this:
+
+  Client says hello, offers a menu of cipher options
+  Server says hello, selects a cipher.
+  Server presents its certificate, requests a client certificate
+  Client presents a certificate (or not)
+  Client and server exchange cryptographic session keys
+
+  The server is identified to the client by a certificate, an encoded
+  text that gives the server's name and other attributes and is
+  signed by a certifying authority (CA), like Verisign. The client
+  checks that the signature is by a CA that it trusts, and may perform
+  other validation on the certificate (e.g., checking that the hostname
+  in the certificate matches the hostname it's trying to connect to).
+
+  If the client chooses to present a certificate (or is required to
+  by the server), the server will likewise attempt to validate it
+  against its list of trusted CAs, and may perform other verification.
+
+  Once session keys have been exchanged, the client and server can
+  communicate secure from eavesdropping.
+
+II. Compiling with OpenSSL
+
+  The Configure script distributed with PennMUSH automatically detects
+  the OpenSSL libraries (libssl and libcrypto) and attempts to link
+  them into the executable, defining HAS_OPENSSL in config.h
+
+  You can compile the OpenSSL libraries yourself from source code
+  at http://www.openssl.org.
+
+  OpenSSL can also be compiled on Windows, and you could add its
+  libraries to the PennMUSH project file and link it in that way.
+  Noltar has done this succesfully; it requires compiling both
+  OpenSSL and PennMUSH in /MD (multithread dll) mode.
+
+III. Mush configuration overview
+
+  mush.cnf includes three directives that configure the SSL support:
+
+  ssl_port selects the port number for the MUSH to listen for SSL 
+  connections. Any port number other than the MUSH's ordinary listening
+  port can be chosen (subject, of course, to other system restrictions
+  on choosing port numbers).
+
+  ssl_private_key_file specifies the name of the file (relative to the
+  game/ directory if it's not an absolute path) that contains the
+  MUSH server's certificate and private key. See section IV below.
+
+  ssl_ca_file specifies the name of the file that contains certificates
+  of trusted certificate authorities. OpenSSL distributes a file containing
+  the best known CAs that is suitable for use here.  If you comment this
+  out, client certificate checking will not be performed.
+
+  ssl_require_client_cert is a boolean option that controls whether the
+  MUSH server will require clients to present valid (that is, signed by
+  a CA for which ssl_ca_file holds a certificate) certificates in order
+  to connect. As no mud clients currently do this, you probably want it 
+  off. See section V below.
+
+IV. Installing a server certificate
+
+  SSL support requires that the MUSH present a server certificate (except
+  as discussed below).  You must create a file containing the certificate
+  and the associated private key (stripped of any passphrase protection)
+  and point the ssl_private_key_file directive at this file. This file
+  should only be readable by the MUSH account!
+
+  How do you get such a certificate and private key? Here are the
+  steps you can use with openssl's command-line tool:
+
+  1. Generate a certificate signing request (mymush.csr) and a private key
+     (temp.key). You will be asked to answer several questions. 
+     Be sure the Common Name you request is your MUSH's hostname:
+
+     $ openssl req -new -out mymush.csr -keyout temp.key -passin pass:foobar
+
+  2. Strip the passphrase off of your private key, leaving you 
+     with an unpassworded mymush.key file:
+     $ openssl rsa -in temp.key -out mymush.key -passin pass:foobar
+     $ rm temp.key
+
+  3. Send the certificate signing request to a certifying authority
+     to have it signed. If the CA needs the private key, send the
+     passphrased one. The CA will send you back a certificate
+     which you save to a file (mymush.crt)
+
+  4. Concatenate the certificate with the unpassworded private key and 
+     use this as the ssl_private_key_file:
+
+     $ cat mymush.key >> mymush.crt
+
+  Commercial CAs like Verisign sign certificates for a yearly or two-yearly
+  fee that is probably too steep for most MUSH Gods. Instead of using
+  a commercial CA, you can generate a self-signed certificate by 
+  changing step 1 above to:
+
+  $ openssl req -new -x509 -days 3650 -out mymush.crt -keyout temp.key -passin pass:foobar
+
+  A self-signed certificate is free, but clients that attempt to validate
+  certificates will fail to validate a self-signed certificate unless
+  the user manually installs the certificate in their client and configures
+  it to be trusted. How to do that is beyond the scope of this document,
+  and highly client-dependent.
+
+  Another option is to skip the use of a certificate altogether.
+  If you don't provide an ssl_private_key_file, the server will only
+  accept connections from clients that are willing to use the
+  anonymous Diffie-Hellman cipher; it is unknown which clients
+  are configured to offer this. This provides clients with no 
+  security that they are actually connecting to your server, and
+  exposes them to a man-in-the-middle attack, but requires no
+  work on your part at all.
+
+  Hosting providers or other parties may one day provide CA service
+  to PennMUSHes for free. When they do, you'll have to install those
+  CAs' certificates in your client as trusted in order to have the
+  server's certificate validate, but if a few CAs certify many MUSHes,
+  this is efficient.
+
+
+V. Using client certificates for authentication
+
+  If you provide PennMUSH with a file containing the certificates of
+  trusted CAs (using the ssl_ca_file directive in mush.cnf), it will,
+  by default, request that clients present certificates when they connect.
+  Clients that do not present certificates will still be allowed to 
+  connect (unless ssl_require_client_cert is enabled).
+  Clients that do present certificates must present certificates signed
+  by a trusted CA, or they will be disconnected. Both valid and invalid
+  certificates are logged (to connect.log and netmush.log, respectively).
+
+  If you were really serious about this, you probably would issue your
+  own certs and not allow Verisign, etc. certs. You'd probably want to
+  have the server validate extra attributes on each client cert, which
+  should probably include the player's dbref and creation time. This is
+  left as an exercise for the reader for now.
+
+
+VI. Legal issues
+
+  OpenSSL is used in PennMUSH and may be redistributed with PennMUSH
+  under the following license(s):
+
+  OpenSSL License
+  ---------------
+
+/* ====================================================================
+ * Copyright (c) 1998-2001 The OpenSSL Project.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer. 
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * 3. All advertising materials mentioning features or use of this
+ *    software must display the following acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+ *
+ * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+ *    endorse or promote products derived from this software without
+ *    prior written permission. For written permission, please contact
+ *    openssl-core@openssl.org.
+ *
+ * 5. Products derived from this software may not be called "OpenSSL"
+ *    nor may "OpenSSL" appear in their names without prior written
+ *    permission of the OpenSSL Project.
+ *
+ * 6. Redistributions of any form whatsoever must retain the following
+ *    acknowledgment:
+ *    "This product includes software developed by the OpenSSL Project
+ *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+ * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This product includes cryptographic software written by Eric Young
+ * (eay@cryptsoft.com).  This product includes software written by Tim
+ * Hudson (tjh@cryptsoft.com).
+ *
+ */
+
+ Original SSLeay License
+ -----------------------
+
+/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
+ * All rights reserved.
+ *
+ * This package is an SSL implementation written
+ * by Eric Young (eay@cryptsoft.com).
+ * The implementation was written so as to conform with Netscapes SSL.
+ * 
+ * This library is free for commercial and non-commercial use as long as
+ * the following conditions are aheared to.  The following conditions
+ * apply to all code found in this distribution, be it the RC4, RSA,
+ * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
+ * included with this distribution is covered by the same copyright terms
+ * except that the holder is Tim Hudson (tjh@cryptsoft.com).
+ * 
+ * Copyright remains Eric Young's, and as such any Copyright notices in
+ * the code are not to be removed.
+ * If this package is used in a product, Eric Young should be given attribution
+ * as the author of the parts of the library used.
+ * This can be in the form of a textual message at program startup or
+ * in documentation (online or textual) provided with the package.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *    "This product includes cryptographic software written by
+ *     Eric Young (eay@cryptsoft.com)"
+ *    The word 'cryptographic' can be left out if the rouines from the library
+ *    being used are not cryptographic related :-).
+ * 4. If you include any Windows specific code (or a derivative thereof) from 
+ *    the apps directory (application code) you must include an acknowledgement:
+ *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
+ * 
+ * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * 
+ * The licence and distribution terms for any publically available version or
+ * derivative of this code cannot be changed.  i.e. this code cannot simply be
+ * copied and put under another distribution licence
+ * [including the GNU Public Licence.]
+ */
+
+
+
diff --git a/UPGRADING b/UPGRADING
new file mode 100644 (file)
index 0000000..45c402c
--- /dev/null
+++ b/UPGRADING
@@ -0,0 +1,153 @@
+============================================================================
+                         Upgrading to PennMUSH 1.7.x
+============================================================================
+
+This file explains how to upgrade to a new version of PennMUSH.
+
+There are three basic upgrade situations:
+  A. You're running a stock ("vanilla") PennMUSH server of some
+     version and you want to upgrade to a later version
+  B. You've hacked your server source code a little bit here and there
+     (adding a flag, for example). Hacks to the *local.c files don't
+     count as hacks, as they're easy to handle.
+  C. You've hacked your server source code a lot.
+
+The PennMUSH developers actually only support situation A, but
+we'll give some useful tips for B and C here, too.
+
+DISCLAIMER: It is very wise to always back up your current working
+MUSH directories before you try an upgrade. You were warned.
+
+============================================================================
+
+A. Vanilla upgrade
+
+You have basically two choices here: upgrade with patch files, or
+build a whole new distribution.
+
+A.1. Upgrading with patch files
+
+This is the easiest way to upgrade your source code if you're 
+keeping up with patches as they come out, or if you're upgrading
+patchlevels within a release (e.g., within 1.7.6).
+
+To upgrade with patch files, get all the patch files for higher
+patchlevels than your current version. For example, if you're running
+1.7.6p0 and the latest version is 1.7.6p4, you need patches 1-4.
+
+These files are stored at http://ftp.pennmush.org/Source and usually
+named things like 1.7.6-patch02 (the patch from 1.7.6p1 to 1.7.6p2)
+or, in some cases, 1.7.4p20-1.7.6p0.patch (the patch from 1.7.4p20 to
+1.7.6p0). 
+
+Each patch file contains instructions at the top explaining how to
+apply it. FOLLOW THESE! Don't assume they're all the same.
+
+After you've applied all the patches and followed all the instructions,
+you should be good to go. In most cases, you can simply @shutdown/reboot
+after the final successful compile. If @shutdown/reboot crashes,
+you'll have to restart again.
+
+A.2. Building a new distribution
+
+When you're upgrading across release and no patchlevel is provided
+to make the upgrade (e.g. from 1.7.4p3 to 1.7.7p0), it's often
+easier to simply build a new distribution following the INSTALL
+instructions, but with your old configuration stuff.
+
+Move your older version of PennMUSH in a directory called oldpenn/,
+unpack the new one (it will unpack into pennmush/). 
+
+All of the steps below should be taken before running Configure for the
+new version:
+
+A.2.a. options.h and game/*.cnf
+
+You can copy the options.h file and game/mush.cnf file from your
+old version to the new version. The 'make update' command (run after
+Configure) will compare your files with the newly distributed ones and
+tell you about options that have been added or removed. If you have any
+options defined that the new version doesn't recognize, you'll be asked
+if you want to retain them (which is safe).
+
+If your mush.cnf file is called something else, copy it to mush.cnf in
+pennmush/game anyway, since that's the file that gets updated. Then make
+a link to that file called whatever.cnf if you want to use that.
+
+If you've modified the restart script, you'll have to decide if
+your modified script is still appropriate, or modify the distributed
+game/restart script again as you like it. The latter is encouraged.
+
+You can also copy your old game/access.cnf, game/sitelock.cnf, and
+game/txt/*.txt files into the appropriate locations. You may wish
+to do the same thing for game/restrict.cnf, but you should compare
+it to the new version, as restrictions that may formerly have been
+compiled into the server may now be specified in restrict.cnf instead.
+
+A.2.b. src/*local.c
+
+You should copy local.c, cmdlocal.c, and funlocal.c from oldpenn/src
+to pennmush/src if you want to retain this local code. Of course,
+it may not still work, but it's quite likely that it will. If you
+don't have any such code, you can skip this step.
+
+A.2.c. Databases
+
+This MUSH version should read databases along the main branch of MUSH
+evolution -- TinyMUD, vanilla TinyMUSH up to 2.0, MicroMUSH, and all
+Pern/PennMUSH versions. If you need to convert a TinyMUSH 2.0 database,
+please contact Amberyl, and she'll mail you an extension to 2.0 that
+will dump a 1.50-readable flatfile. You're probably out of luck with
+databases for TinyMUSH 2.2 and later.
+
+Be sure that your options.h settings correctly reflect the type
+of password encryption that was used on your database. The default
+has changed to SHS, so if your db used crypt(3) encryption, be
+sure you set the appropriate definition in options.h.
+
+*** If you are upgrading from 1.7.4 (or earlier) to 1.7.7 (or later),
+*** you must first load your old database under PennMUSH 1.7.6 and
+*** then dump it, and load this converted database under your
+*** target version of PennMUSH. PennMUSH 1.7.7+ can no longer read
+*** 1.7.4 databases.
+
+============================================================================
+
+B. PennMUSH with a few hacks
+
+When you have only a few local hacks outside of the src/*local.c
+files, you can often patch up using the patch file method discussed
+above. Alternatively, you can build a new version and reapply your
+changes.
+
+One small exception is upgrading from a version that used the old flag
+system to one that uses the new flag system (post-1.7.7p5), if you've
+added flags or toggles.  You probably had an #define in hdrs/flags.h
+for your flag's bit value.  This now should be moved to hdrs/oldflags.h;
+you should leave in the table entry in src/flags.c. If you set up a macro
+for testing your flag in hdrs/mushdb.h, you'll need to change it to use
+the has_flag_by_name() function - see the many examples in that file.
+
+If this isn't suitable (you're crossing releases or your hacks are too
+many for this to work cleanly), see below.
+
+============================================================================
+
+C. PennMUSH with a lot of hacks
+
+If you've seriously hacked your server source code, you're on your
+own in terms of keeping up with new patchlevels. Some people apply
+patchfiles and fix the rejected hunks.
+
+A better approach is probably that described in the Guide for Gods,
+and involves creating a set of patches from the distributed old
+version of pennmush (e.g. 1.7.2p32) to your hacked version of pennmush
+(e.g. 1.7.2p32 with hacks), and then applying those patches to the new
+version of PennMUSH (e.g. 1.7.6p0) to create a hacked version thereof. If
+some patch hunks fail, you'll have to apply them manually.
+
+Probably the best approach is to keep all multiple versions of the
+code (old distributed, old hacked, new distributed, new hacked) under
+a source code control system like prcs that can merge changes between
+versions. See the Guide for Gods.
+
diff --git a/config_h.SH b/config_h.SH
new file mode 100644 (file)
index 0000000..b00f17c
--- /dev/null
@@ -0,0 +1,859 @@
+case $CONFIG in
+'')
+       if test -f config.sh; then TOP=.;
+       elif test -f ../config.sh; then TOP=..;
+       elif test -f ../../config.sh; then TOP=../..;
+       elif test -f ../../../config.sh; then TOP=../../..;
+       elif test -f ../../../../config.sh; then TOP=../../../..;
+       else
+               echo "Can't find config.sh."; exit 1
+       fi
+       . $TOP/config.sh
+       ;;
+esac
+case "$0" in
+*/*) cd `expr X$0 : 'X\(.*\)/'` ;;
+esac
+echo "Extracting config.h (with variable substitutions)"
+sed <<!GROK!THIS! >config.h -e 's!^#undef\(.*/\)\*!/\*#define\1 \*!' -e 's!^#un-def!#undef!'
+/*
+ * This file was produced by running the config_h.SH script, which
+ * gets its values from config.sh, which is generally produced by
+ * running Configure.
+ *
+ * Feel free to modify any of this as the need arises.  Note, however,
+ * that running config_h.SH again will wipe out any changes you've made.
+ * For a more permanent change edit config.sh and rerun config_h.SH.
+ *
+ * \$Id: config_h.SH,v 1.1.1.1 2004-06-06 20:32:51 ari Exp $
+ */
+
+/*
+ * Package name      : $package
+ * Source directory  : $src
+ * Configuration time: $cf_time
+ * Configured by     : $cf_by
+ * Target system     : $myuname
+ */
+
+#ifndef _config_h_
+#define _config_h_
+
+/* getdtablesize:
+ *     This catches use of the getdtablesize() subroutine, and remaps it
+ *     to either ulimit(4,0) or NOFILE, if getdtablesize() isn't available.
+ */
+#$d_gettblsz getdtablesize() $tablesize        /**/
+
+/* HAS_BCOPY:
+ *     This symbol is defined if the bcopy() routine is available to
+ *     copy blocks of memory.
+ */
+#$d_bcopy HAS_BCOPY    /**/
+
+/* HAS_BZERO:
+ *     This symbol is defined if the bzero() routine is available to
+ *     set a memory block to 0.
+ */
+#$d_bzero HAS_BZERO    /**/
+
+/* HASCONST:
+ *     This symbol, if defined, indicates that this C compiler knows about
+ *     the const type. There is no need to actually test for that symbol
+ *     within your programs. The mere use of the "const" keyword will
+ *     trigger the necessary tests.
+ */
+#$d_const HASCONST     /**/
+#ifndef HASCONST
+#define const
+#endif
+
+/* HAS_GETPRIORITY:
+ *     This symbol, if defined, indicates that the getpriority routine is
+ *     available to get a process's priority.
+ */
+#$d_getprior HAS_GETPRIORITY           /**/
+
+/* INTERNET:
+ *     This symbol, if defined, indicates that there is a mailer available
+ *     which supports internet-style addresses (user@site.domain).
+ */
+#$d_internet   INTERNET        /**/
+
+/* HAS_ITIMER:
+ *     This symbol, if defined, indicates that the setitimer() routine exists.
+ */
+#$d_itimer HAS_ITIMER  /**/
+
+/* HAS_MEMSET:
+ *     This symbol, if defined, indicates that the memset routine is available
+ *     to set blocks of memory.
+ */
+#$d_memset HAS_MEMSET  /**/
+
+/* HAS_RENAME:
+ *     This symbol, if defined, indicates that the rename routine is available
+ *     to rename files.  Otherwise you should do the unlink(), link(), unlink()
+ *     trick.
+ */
+#$d_rename HAS_RENAME  /**/
+
+/* HAS_GETRUSAGE:
+ *     This symbol, if defined, indicates that the getrusage() routine is
+ *     available to get process statistics with a sub-second accuracy.
+ *     Inclusion of <sys/resource.h> and <sys/time.h> may be necessary.
+ */
+#$d_rusage HAS_GETRUSAGE               /**/
+
+/* HAS_SELECT:
+ *     This symbol, if defined, indicates that the select routine is
+ *     available to select active file descriptors. If the timeout field
+ *     is used, <sys/time.h> may need to be included.
+ */
+#$d_select HAS_SELECT  /**/
+
+/* HAS_SETLOCALE:
+ *     This symbol, if defined, indicates that the setlocale routine is
+ *     available to handle locale-specific ctype implementations.
+ */
+#$d_setlocale HAS_SETLOCALE    /**/
+
+/* HAS_SETPGID:
+ *     This symbol, if defined, indicates that the setpgid(pid, gpid)
+ *     routine is available to set process group ID.
+ */
+#$d_setpgid HAS_SETPGID        /**/
+
+/* HAS_SETPGRP:
+ *     This symbol, if defined, indicates that the setpgrp routine is
+ *     available to set the current process group.
+ */
+/* USE_BSD_SETPGRP:
+ *     This symbol, if defined, indicates that setpgrp needs two
+ *     arguments whereas USG one needs none.  See also HAS_SETPGID
+ *     for a POSIX interface.
+ */
+#$d_setpgrp HAS_SETPGRP                /**/
+#$d_bsdsetpgrp USE_BSD_SETPGRP /**/
+
+/* HAS_SETPRIORITY:
+ *     This symbol, if defined, indicates that the setpriority routine is
+ *     available to set a process's priority.
+ */
+#$d_setprior HAS_SETPRIORITY           /**/
+
+/* HAS_SIGACTION:
+ *     This symbol, if defined, indicates that Vr4's sigaction() routine
+ *     is available.
+ */
+#$d_sigaction HAS_SIGACTION    /**/
+
+/* HAS_SOCKET:
+ *     This symbol, if defined, indicates that the BSD socket interface is
+ *     supported.
+ */
+/* HAS_SOCKETPAIR:
+ *     This symbol, if defined, indicates that the BSD socketpair() call is
+ *     supported.
+ */
+#$d_socket HAS_SOCKET          /**/
+#$d_sockpair HAS_SOCKETPAIR    /**/
+
+/* HAS_STRCASECMP:
+ *     This symbol, if defined, indicates that the strcasecmp() routine is
+ *     available for case-insensitive string compares.
+ */
+#$d_strccmp HAS_STRCASECMP     /**/
+
+/* HAS_STRDUP:
+ *     This symbol, if defined, indicates that the strdup routine is
+ *     available to duplicate strings in memory. Otherwise, roll up
+ *     your own...
+ */
+#$d_strdup HAS_STRDUP          /**/
+
+/* HAS_SYSCONF:
+ *     This symbol, if defined, indicates that sysconf() is available
+ *     to determine system related limits and options.
+ */
+#$d_sysconf HAS_SYSCONF        /**/
+
+/* VOIDSIG:
+ *     This symbol is defined if this system declares "void (*signal(...))()" in
+ *     signal.h.  The old way was to declare it as "int (*signal(...))()".  It
+ *     is up to the package author to declare things correctly based on the
+ *     symbol.
+ */
+/* Signal_t:
+ *     This symbol's value is either "void" or "int", corresponding to the
+ *     appropriate return type of a signal handler.  Thus, you can declare
+ *     a signal handler using "Signal_t (*handler)()", and define the
+ *     handler using "Signal_t handler(sig)".
+ */
+#$d_voidsig VOIDSIG    /**/
+#define Signal_t $signal_t     /* Signal handler's return type */
+
+/* HASVOLATILE:
+ *     This symbol, if defined, indicates that this C compiler knows about
+ *     the volatile declaration.
+ */
+#$d_volatile   HASVOLATILE     /**/
+#ifndef HASVOLATILE
+#define volatile
+#endif
+
+/* HAS_WAITPID:
+ *     This symbol, if defined, indicates that the waitpid routine is
+ *     available to wait for child process.
+ */
+#$d_waitpid HAS_WAITPID        /**/
+
+/* I_ARPA_INET:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <arpa/inet.h> to get inet_addr and friends declarations.
+ */
+#$i_arpainet   I_ARPA_INET             /**/
+
+/* I_FCNTL:
+ *     This manifest constant tells the C program to include <fcntl.h>.
+ */
+#$i_fcntl I_FCNTL      /**/
+
+/* I_LIMITS:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <limits.h> to get definition of symbols like WORD_BIT or
+ *     LONG_MAX, i.e. machine dependant limitations.
+ */
+#$i_limits I_LIMITS            /**/
+
+/* I_LOCALE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <locale.h>.
+ */
+#$i_locale     I_LOCALE                /**/
+
+/* I_MALLOC:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <malloc.h>.
+ */
+#$i_malloc I_MALLOC            /**/
+
+/* I_NETINET_IN:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <netinet/in.h>. Otherwise, you may try <sys/in.h>.
+ */
+/* I_SYS_IN:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/in.h> instead of <netinet/in.h>.
+ */
+#$i_niin I_NETINET_IN  /**/
+#$i_sysin I_SYS_IN             /**/
+
+/* I_STDDEF:
+ *     This symbol, if defined, indicates that <stddef.h> exists and should
+ *     be included.
+ */
+#$i_stddef I_STDDEF    /**/
+
+/* I_STDLIB:
+ *     This symbol, if defined, indicates that <stdlib.h> exists and should
+ *     be included.
+ */
+#$i_stdlib I_STDLIB            /**/
+
+/* I_STRING:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <string.h> (USG systems) instead of <strings.h> (BSD systems).
+ */
+#$i_string I_STRING            /**/
+
+/* I_SYS_FILE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/file.h> to get definition of R_OK and friends.
+ */
+#$i_sysfile I_SYS_FILE         /**/
+
+/* I_SYS_MMAN:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/mman.h>.
+ */
+#$i_sysmman    I_SYS_MMAN              /**/
+
+/* I_SYS_PARAM:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/param.h>.
+ */
+#$i_sysparam I_SYS_PARAM               /**/
+
+/* I_SYS_RESOURCE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/resource.h>.
+ */
+#$i_sysresrc I_SYS_RESOURCE            /**/
+
+/* I_SYS_SELECT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/select.h> in order to get definition of struct timeval.
+ */
+#$i_sysselct I_SYS_SELECT      /**/
+
+/* I_SYS_SOCKET:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/socket.h> before performing socket calls.
+ */
+#$i_syssock I_SYS_SOCKET               /**/
+
+/* I_SYS_STAT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/stat.h>.
+ */
+#$i_sysstat    I_SYS_STAT              /**/
+
+/* I_SYS_TYPES:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/types.h>.
+ */
+#$i_systypes   I_SYS_TYPES             /**/
+
+/* I_SYS_WAIT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/wait.h>.
+ */
+#$i_syswait I_SYS_WAIT /**/
+
+/* I_TIME:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <time.h>.
+ */
+/* I_SYS_TIME:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/time.h>.
+ */
+#$i_time I_TIME                /**/
+#$i_systime I_SYS_TIME         /**/
+
+/* I_UNISTD:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <unistd.h>.
+ */
+#$i_unistd I_UNISTD            /**/
+
+/* I_VALUES:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <values.h> to get definition of symbols like MINFLOAT or
+ *     MAXLONG, i.e. machine dependant limitations.  Probably, you
+ *     should use <limits.h> instead, if it is available.
+ */
+#$i_values I_VALUES            /**/
+
+/* Free_t:
+ *     This variable contains the return type of free().  It is usually
+ * void, but occasionally int.
+ */
+/* Malloc_t:
+ *     This symbol is the type of pointer returned by malloc and realloc.
+ */
+#define Malloc_t $malloctype                   /**/
+#define Free_t $freetype                       /**/
+
+/* MYHOSTNAME:
+ *     This symbol contains name of the host the program is going to run on.
+ *     The domain is not kept with hostname, but must be gotten from MYDOMAIN.
+ *     The dot comes with MYDOMAIN, and need not be supplied by the program.
+ *     If gethostname() or uname() exist, MYHOSTNAME may be ignored. If MYDOMAIN
+ *     is not used, MYHOSTNAME will hold the name derived from PHOSTNAME.
+ */
+#define MYHOSTNAME "$myhostname"               /**/
+
+/* Pid_t:
+ *     This symbol holds the type used to declare process ids in the kernel.
+ *     It can be int, uint, pid_t, etc... It may be necessary to include
+ *     <sys/types.h> to get any typedef'ed information.
+ */
+#define Pid_t $pidtype         /* PID type */
+
+/* CAN_PROTOTYPE:
+ *     If defined, this macro indicates that the C compiler can handle
+ *     function prototypes.
+ */
+/* _:
+ *     This macro is used to declare function parameters for folks who want
+ *     to make declarations with prototypes using a different style than
+ *     the above macros.  Use double parentheses.  For example:
+ *
+ *             int main _((int argc, char *argv[]));
+ */
+#$prototype    CAN_PROTOTYPE   /**/
+#ifdef CAN_PROTOTYPE
+#define        _(args) args
+#else
+#define        _(args) ()
+#endif
+
+/* Size_t:
+ *     This symbol holds the type used to declare length parameters
+ *     for string functions.  It is usually size_t, but may be
+ *     unsigned long, int, etc.  It may be necessary to include
+ *     <sys/types.h> to get any typedef'ed information.
+ */
+#define Size_t $sizetype        /* length paramater for string functions */
+
+/* VOIDFLAGS:
+ *     This symbol indicates how much support of the void type is given by this
+ *     compiler.  What various bits mean:
+ *
+ *         1 = supports declaration of void
+ *         2 = supports arrays of pointers to functions returning void
+ *         4 = supports comparisons between pointers to void functions and
+ *                 addresses of void functions
+ *         8 = suports declaration of generic void pointers
+ *
+ *     The package designer should define VOIDUSED to indicate the requirements
+ *     of the package.  This can be done either by #defining VOIDUSED before
+ *     including config.h, or by defining defvoidused in Myinit.U.  If the
+ *     latter approach is taken, only those flags will be tested.  If the
+ *     level of void support necessary is not present, defines void to int.
+ */
+#ifndef VOIDUSED
+#define VOIDUSED $defvoidused
+#endif
+#define VOIDFLAGS $voidflags
+#if (VOIDFLAGS & VOIDUSED) != VOIDUSED
+#define void int               /* is void to be avoided? */
+#define M_VOID                 /* Xenix strikes again */
+#endif
+
+/* WIN32_CDECL:
+ *     Defined as __cdecl if the compiler can handle that keyword to specify
+ *     C-style argument passing conventions. This allows MS VC++
+ *     on Win32 to use the __fastcall convention for everything else
+ *     and get a performance boost. Any compiler with a brain (read:
+ *     not MS VC) handles this optimization automatically without such a
+ *     kludge. On these systems, this is defined as nothing.
+ */
+#define WIN32_CDECL $cdecl
+
+/* CAN_TAKE_ARGS_IN_FP:
+ *     Defined if the compiler prefers that function pointer parameters
+ *     in prototypes include the function's arguments, rather than 
+ *     nothing (that is, int (*fun)(int) rather than int(*fun)().
+ */
+#$d_argsinfp CAN_TAKE_ARGS_IN_FP /**/
+
+/* HAS_ASSERT:
+ *     If defined, this system has the assert() macro.
+ */
+#$d_assert HAS_ASSERT  /**/
+
+/* HASATTRIBUTE:
+ *     This symbol indicates the C compiler can check for function attributes,
+ *     such as printf formats. This is normally only supported by GNU cc.
+ */
+#$d_attribut HASATTRIBUTE      /**/
+#ifndef HASATTRIBUTE
+#define __attribute__(_arg_)
+#endif
+
+/* HAS_BINDTEXTDOMAIN:
+ *     Defined if bindtextdomain is available().
+ */
+#$d_bindtextdomain HAS_BINDTEXTDOMAIN /**/
+
+/* HAS_CRYPT:
+ *     This symbol, if defined, indicates that the crypt routine is available
+ *     to encrypt passwords and the like.
+ */
+/* I_CRYPT:
+ *     This symbol, if defined, indicates that <crypt.h> can be included.
+ */
+#$d_crypt HAS_CRYPT            /**/
+
+#$i_crypt I_CRYPT              /**/
+
+/* FORCE_IPV4:
+ *     If defined, this system will not use IPv6. Necessary for Openbsd.
+ */
+#$d_force_ipv4 FORCE_IPV4      /**/
+
+/* HAS_FPSETROUND:
+ *     This symbol, if defined, indicates that the crypt routine is available
+ *     to encrypt passwords and the like.
+ */
+/* I_FLOATINGPOINT:
+ *     This symbol, if defined, indicates that <crypt.h> can be included.
+ */
+#$d_fpsetround HAS_FPSETROUND          /**/
+
+#$d_fpsetmask HAS_FPSETMASK            /**/
+
+#$i_floatingpoint I_FLOATINGPOINT              /**/
+
+/* HAS_GAI_STRERROR:
+ *     This symbol, if defined, indicates that getaddrinfo()'s error cores
+ * can be converted to strings for printing.
+ */
+#$d_gaistr HAS_GAI_STRERROR            /**/
+
+/* HAS_GETADDRINFO:
+ *     This symbol, if defined, indicates that the getaddrinfo() routine is
+ *     available to lookup internet addresses in some data base or other.
+ */
+#$d_getadinf HAS_GETADDRINFO           /**/
+
+/* HAS_GETDATE:
+ *     This symbol, if defined, indicates that the getdate() routine is
+ *     available to convert date strings into struct tm's.
+ */
+#$d_getdate HAS_GETDATE                /**/
+
+/* HAS_GETHOSTBYNAME2:
+ *     This symbol, if defined, indicates that the gethostbyname2() 
+ * function is available to resolve hostnames.
+ */
+#$d_gethbynm2 HAS_GETHOSTBYNAME2               /**/
+
+/* HAS_GETNAMEINFO:
+ *     This symbol, if defined, indicates that the getnameinfo() routine is
+ *     available to lookup host names in some data base or other.
+ */
+#$d_getnminf HAS_GETNAMEINFO           /**/
+
+/* HAS_GETPAGESIZE:
+ *     This symbol, if defined, indicates that the getpagesize system call
+ *     is available to get system page size, which is the granularity of
+ *     many memory management calls.
+ */
+/* PAGESIZE_VALUE:
+ *     This symbol holds the size in bytes of a system page (obtained via
+ *     the getpagesize() system call at configuration time or asked to the
+ *     user if the system call is not available).
+ */
+#$d_getpagsz HAS_GETPAGESIZE     /**/
+#define PAGESIZE_VALUE $pagesize /* System page size, in bytes */
+
+/* HAS_GETTEXT:
+ *     Defined if gettext is available().
+ */
+#$d_gettext HAS_GETTEXT /**/
+
+/* HAS_HUGE_VAL:
+ *     If defined, this system has the HUGE_VAL constant. We like this,
+ *     and don't bother defining the other floats below if we find it.
+ */
+/* HAS_HUGE:
+ *     If defined, this system has the HUGE constant. We like this, and
+ *     don't bother defining the other floats below if we find it.
+ */
+/* HAS_INT_MAX:
+ *     If defined, this system has the INT_MAX constant. 
+ */
+/* HAS_MAXINT:
+ *     If defined, this system has the MAXINT constant. 
+ */
+/* HAS_MAXDOUBLE:
+ *     If defined, this system has the MAXDOUBLE constant. 
+ */
+#$d_huge_val HAS_HUGE_VAL      /**/
+
+#$d_huge HAS_HUGE      /**/
+
+#$d_int_max HAS_INT_MAX        /**/
+
+#$d_maxint HAS_MAXINT  /**/
+
+#$d_maxdouble HAS_MAXDOUBLE    /**/
+
+/* HAS_IEEE_MATH:
+ *     Defined if the machine supports IEEE math - that is, can safely
+ *     return NaN or Inf rather than crash on bad math.
+ */
+#$d_ieee HAS_IEEE_MATH /**/
+
+/* HAS_INET_PTON:
+ *     This symbol, if defined, indicates that the inet_pton() and
+ *     inet_ntop() routines are available to convert IP addresses..
+ */
+#$d_in2p HAS_INET_PTON         /**/
+
+/* HAS_IPV6:
+ *     If defined, this system has the sockaddr_in6 struct and AF_INET6.
+ * We can't rely on just AF_INET6 being defined.
+ */
+#$d_ipv6 HAS_IPV6      /**/
+
+/* SIGNALS_KEPT:
+ *     This symbol is defined if signal handlers needn't be reinstated after
+ *     receipt of a signal.
+ */
+#$d_keepsig SIGNALS_KEPT       /**/
+
+/* HAS_MEMCPY:
+ *     This symbol, if defined, indicates that the memcpy routine is available
+ *     to copy blocks of memory. If not, it will be mapped to bcopy
+ *     in confmagic.h
+ */
+/* HAS_MEMMOVE:
+ *     This symbol, if defined, indicates that the memmove routine is available
+ *     to copy blocks of memory. If not, it will be mapped to bcopy
+ */
+#$d_memcpy HAS_MEMCPY  /**/
+
+#$d_memmove HAS_MEMMOVE        /**/
+
+/* CAN_NEWSTYLE:
+ *     Defined if new-style function definitions are allowable.
+ *     If they are, we can avoid some warnings that you get if
+ *     you declare char arguments in a prototype and use old-style
+ *     function definitions, which implicitly promote them to ints.
+ */
+#$d_newstyle CAN_NEWSTYLE /**/
+
+/* HAS_RANDOM:
+ *     Have we got random(), our first choice for number generation?
+ */
+/* HAS_LRAND48:
+ *     Have we got lrand48(), our second choice?
+ */
+/* HAS_RAND:
+ *     Have we got rand(), our last choice?
+ */
+#$d_random HAS_RANDOM  /**/
+#$d_lrand48 HAS_LRAND48        /**/
+#$d_rand HAS_RAND      /**/
+
+/* HAS_GETRLIMIT:
+ *     This symbol, if defined, indicates that the getrlimit() routine is
+ *     available to get resource limits. Probably means setrlimit too.
+ *     Inclusion of <sys/resource.h> and <sys/time.h> may be necessary.
+ */
+#$d_rlimit HAS_GETRLIMIT               /**/
+
+/* SENDMAIL:
+ *     This symbol contains the full pathname to sendmail.
+ */
+/* HAS_SENDMAIL:
+ *     If defined, we have sendmail.
+ */
+#$d_sendmail HAS_SENDMAIL      /**/
+#define SENDMAIL       "$sendmail"
+
+/* HAS_SIGCHLD:
+ *     If defined, this system has the SIGCHLD constant. 
+ */
+/* HAS_SIGCLD:
+ *     If defined, this system has the SIGCLD constant (SysVish SIGCHLD).
+ */
+#$d_sigchld HAS_SIGCHLD        /**/
+
+#$d_sigcld HAS_SIGCLD  /**/
+
+/* CAN_PROTOTYPE_SIGNAL:
+ *     This symbol is defined if we can safely prototype our rewritten
+ *     signal() function as:
+ *     Signal_t(*Sigfunc) _((int));
+ *     extern Sigfunc signal _((int signo, Sigfunc func));
+ */
+#$d_signalproto CAN_PROTOTYPE_SIGNAL   /**/
+
+/* HAS_SIGPROCMASK:
+ *     This symbol, if defined, indicates that POSIX's sigprocmask() routine
+ *     is available.
+ */
+#$d_sigprocmask HAS_SIGPROCMASK        /**/
+
+/* HAS_SNPRINTF:
+ *     This symbol, if defined, indicates that the snprintf routine is 
+ *     available. If not, we use sprintf, which is less safe.
+ */
+#$d_snprintf HAS_SNPRINTF      /**/
+
+/* HAS_SOCKLEN_T:
+ *     If defined, this system has the socklen_t type.
+ */
+#$d_socklen HAS_SOCKLEN_T      /**/
+
+/* HAS_STRCHR:
+ *     This symbol is defined to indicate that the strchr()/strrchr()
+ *     functions are available for string searching. If not, try the
+ *     index()/rindex() pair.
+ */
+/* HAS_INDEX:
+ *     This symbol is defined to indicate that the index()/rindex()
+ *     functions are available for string searching.
+ */
+#$d_strchr HAS_STRCHR  /**/
+#$d_index HAS_INDEX    /**/
+
+/* HAS_STRCOLL:
+ *     This symbol, if defined, indicates that the strcoll routine is
+ *     available to compare strings using collating information.
+ */
+#$d_strcoll HAS_STRCOLL        /**/
+
+/* HAS_STRXFRM:
+ *     This symbol, if defined, indicates that the strxfrm routine is
+ *     available to transform strings using collating information.
+ */
+#$d_strxfrm HAS_STRXFRM        /**/
+
+/* HAS_TCL:
+ *     This symbol, if defined, indicates that the tcl library is available
+ */
+/* I_TCL:
+ *     This symbol, if defined, indicates that <tcl.h> can be included.
+ */
+/* HAS_TCL:
+ *  This symbol, if defined, means we have the tcl library
+ */
+#$d_tcl HAS_TCL                /**/
+
+/* I_TCL:
+ *  This symbol, if defined, means we have the <tcl.h> include file
+ */
+#$i_tcl I_TCL          /**/
+
+/* HAS_TEXTDOMAIN:
+ *     Defined if textdomain is available().
+ */
+#$d_textdomain HAS_TEXTDOMAIN /**/
+
+/* HAS_TIMELOCAL:
+ *     This symbol, if defined, indicates that the timelocal() routine is
+ *     available.
+ */
+#$d_timelocal HAS_TIMELOCAL            /**/
+
+/* HAS_SAFE_TOUPPER:
+ *     Defined if toupper() can operate safely on any ascii character.
+ *     Some systems only allow toupper() on lower-case ascii chars.
+ */
+#$d_toupper HAS_SAFE_TOUPPER /**/
+
+/* UPTIME_PATH:
+ *     This symbol gives the full path to the uptime(1) program if
+ *     it exists on the system. If not, this symbol is undefined.
+ */
+/* HAS_UPTIME:
+ *     This symbol is defined if uptime(1) is available.
+ */
+#$d_uptime HAS_UPTIME  /**/
+#define UPTIME_PATH "$uptime"
+
+/* HAS_DEV_URANDOM:
+ *     This symbol is defined if /dev/urandom is available.
+ */
+#$d_urandom HAS_DEV_URANDOM    /**/
+
+/* UNION_WAIT:
+ *     This symbol if defined indicates to the C program that the argument
+ *     for the wait() system call should be declared as 'union wait status'
+ *     instead of 'int status'. You probably need to include <sys/wait.h>
+ *     in the former case (see I_SYSWAIT).
+ */
+#$d_uwait UNION_WAIT           /**/
+
+/* HAS_VSNPRINTF:
+ *     This symbol, if defined, indicates that the vsnprintf routine is 
+ *     available to printf with a pointer to an argument list.  If not, you
+ *     may need to write your own, probably in terms of _doprnt().
+ */
+#$d_vsnprintf HAS_VSNPRINTF    /**/
+
+/* I_ARPA_NAMESER:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <arpa/nameser.h> to get nameser_addr and friends declarations.
+ */
+#$i_arpanameser        I_ARPA_NAMESER          /**/
+
+/* I_ERRNO:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <errno.h>.
+ */
+/* I_SYS_ERRNO:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <sys/errno.h>.
+ */
+#$i_errno I_ERRNO              /**/
+
+#$i_syserrno I_SYS_ERRNO               /**/
+
+/* I_LIBINTL:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <libintl.h>.
+ */
+#$i_libintl I_LIBINTL          /**/
+
+/* I_MEMORY:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <memory.h>.
+ */
+#$i_memory I_MEMORY            /**/
+
+/* I_NETDB:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <errno.h>.
+ */
+#$i_netdb I_NETDB              /**/
+
+/* I_NETINET_TCP:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <netinet/tcp.h>.
+ */
+#$i_nitcp I_NETINET_TCP        /**/
+
+/* I_SETJMP:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <setjmp.h> and have things work right.
+ */
+#$i_setjmp I_SETJMP            /**/
+
+/* USE_TIOCNOTTY:
+ *     This symbol, if defined indicate to the C program that the ioctl()
+ *     call with TIOCNOTTY should be used to void tty association.
+ *     Otherwise (on USG probably), it is enough to close the standard file
+ *     decriptors and do a setpgrp().
+ */
+#$d_voidtty USE_TIOCNOTTY      /**/
+
+/* I_SYS_PAGE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/page.h>.
+ */
+#$i_syspage I_SYS_PAGE         /**/
+
+/* I_SYS_VLIMIT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/vlimit.h>.
+ */
+#$i_sysvlimit I_SYS_VLIMIT             /**/
+
+/* I_STDARG:
+ *     This symbol, if defined, indicates that <stdarg.h> exists and should
+ *     be included.
+ */
+#$i_stdarg I_STDARG            /**/
+
+/* HAS_MYSQL:
+ *     Defined if mysql client libraries are available.
+ */
+#$d_mysql HAS_MYSQL /**/
+
+/* HAS_OPENSSL:
+ *     Defined if openssl 0.9.6+ is available.
+ */
+#$d_openssl HAS_OPENSSL /**/
+
+/* CAN_KEEPALIVE:
+ *     This symbol if defined indicates to the C program that the SO_KEEPALIVE
+ *     option of setsockopt() will work as advertised in the manual.
+ */
+/* CAN_KEEPIDLE:
+ *     This symbol if defined indicates to the C program that the TCP_KEEPIDLE
+ *     option of setsockopt() will work as advertised in the manual.
+ */
+#$d_keepalive CAN_KEEPALIVE            /**/
+
+#$d_keepidle CAN_KEEPIDLE              /**/
+
+#endif
+!GROK!THIS!
diff --git a/confmagic.h b/confmagic.h
new file mode 100644 (file)
index 0000000..7672196
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * This file was produced by running metaconfig and is intended to be included
+ * after config.h and after all the other needed includes have been dealt with.
+ *
+ * This file may be empty, and should not be edited. Rerun metaconfig instead.
+ * If you wish to get rid of this magic, remove this file and rerun metaconfig
+ * without the -M option.
+ *
+ *  $Id: confmagic.h,v 1.1.1.1 2004-06-06 20:32:51 ari Exp $
+ */
+
+#ifndef _confmagic_h_
+#define _confmagic_h_
+
+#ifndef HAS_BCOPY
+#ifndef bcopy
+#define bcopy(s,d,l) memcpy((d),(s),(l))
+#endif
+#endif
+
+#ifndef HAS_BZERO
+#ifndef bzero
+#define bzero(s,l) memset((s),0,(l))
+#endif
+#endif
+
+/* If your system doesn't have the crypt(3) DES encryption code,
+ * (which isn't exportable from the U.S.), then don't encrypt
+ */
+#ifndef HAS_CRYPT
+#define crypt(s,t) (s)
+#endif
+
+#ifdef HAS_HUGE_VAL
+#define HUGE_DOUBLE    HUGE_VAL
+#else
+#ifdef HAS_HUGE
+#define HUGE_DOUBLE    HUGE
+#else
+#ifdef HAS_MAXDOUBLE
+#define HUGE_DOUBLE    MAXDOUBLE
+#else
+#define HUGE_DOUBLE    2000000000
+#endif
+#endif
+#endif
+#ifdef HAS_INT_MAX
+#define HUGE_INT       INT_MAX
+#else
+#ifdef HAS_MAXINT
+#define HUGE_INT       MAXINT
+#else
+#define HUGE_INT       2000000000
+#endif
+#endif
+
+#ifndef HAS_MEMCPY
+#ifndef memcpy
+#define memcpy(d,s,l) bcopy((s),(d),(l))
+#endif
+#endif
+
+#ifndef HAS_MEMMOVE
+#ifndef memmove
+#define memmove(d,s,l) bcopy((s),(d),(l))
+#endif
+#endif
+
+#ifndef HAS_RANDOM
+#ifndef random
+#ifdef HAS_LRAND48
+#define random lrand48
+#define srandom srand48
+#else
+#ifdef HAS_RAND
+#define random rand
+#define srandom srand
+#endif
+#endif
+#endif
+#endif
+
+/*
+#ifndef HAS_SIGCHLD
+#define SIGCHLD        SIGCLD
+#endif
+
+#ifndef HAS_SIGCLD
+#define SIGCLD SIGCHLD
+#endif
+*/
+
+#ifndef HAS_INDEX
+#ifndef index
+#define index strchr
+#endif
+#endif
+
+#ifndef HAS_STRCHR
+#ifndef strchr
+#define strchr index
+#endif
+#endif
+
+#ifndef HAS_INDEX
+#ifndef rindex
+#define rindex strrchr
+#endif
+#endif
+
+#ifndef HAS_STRCHR
+#ifndef strrchr
+#define strrchr rindex
+#endif
+#endif
+
+#ifndef HAS_STRCOLL
+#undef strcoll
+#define strcoll strcmp
+#endif
+
+#if !defined(WIN32) && !defined(HAS_STRXFRM)
+#define strncoll strncmp
+#define strncasecoll strncasecmp
+#define strcasecoll strcasecmp
+#endif
+
+#endif
diff --git a/game/.gitify-empty b/game/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/README b/game/README
new file mode 100644 (file)
index 0000000..2251d7c
--- /dev/null
@@ -0,0 +1,127 @@
+             Run-time Installation and Configuration of PennMUSH
+
+This document assumes that you've successfully compiled and installed
+PennMUSH as described in the main PennMUSH README file.
+
+The next step is to create your configuration file. In the game directory
+is a file called "mush.cnf". If you don't have mush.cnf, but you have
+mushcnf.dst, you can copy mushcnf.dst to mush.cnf.  This file is a list
+of all runtime configuration options with their default settting. Change
+them as you see fit.  IMPORTANT: do not _delete_ any parameters. They
+all need to be there.
+
+WIN32:
+  Under win32 using the Microsoft compiler, ignore the restart script.
+  In the configuration file, turn off disk database compression; it is
+  not supported.  Then go to the game directory and run PennMUSH.exe.
+  Poof, you're done.
+
+UNIX:
+  Edit the restart script. Change GAMEDIR to the path to the directory
+  containing mush.cnf. Read about the optional settings in that file.
+  The restart script is written for sh, and assumes a fairly standard
+  Berkeley UNIX setup. If you're on a HP-UX or SysV machine, for example,
+  you may need to change the restart script a bit (the ps options,
+  for example). Then run it.
+
+You should now be ready to start the game.  This distribution can
+general a minimal database - a God character, starting room, and
+master room.  The server will generate this database if it doesn't
+find another database to load.
+
+If you're starting with the minimal database, the god character "One"
+has no password, so you can log in without one. Of course, you should
+immediately set one (via @newpasswd).  options.h has the Master Room as
+#2 by default; in the minimal database, this room is created for you.
+
+Now you should be set -- all you have to do now is customize the
+.txt files in the game directory.
+
+The logfiles in the "log" directory generally contain useful
+information. You will probably want to read your error logfile (defined
+in mush.cnf) every time, since errors and other important messages get
+printed to that logfile.
+
+============================================================================
+
+                            Game Trouble-shooting
+
+If you ever run into trouble, the your first reaction should ALWAYS be
+to back up your database. indb.Z.old is the file that the MUSH saves
+indb.Z to when the game, restarted, indb.Z is the file that the MUSH
+loaded at startup, and outdb.Z is the file to which the MUSH is
+currently dumping the database.
+
+You can tell if a dump is (theoretically) complete by doing a
+"zcat <database file name> | tail -10".  The last line should read
+"***END OF DUMP***". If it doesn't, your database has been truncated
+for some reason. Check the logfile. Possible causes include a full
+process table, a full disk partition, or running out of disk quota.
+
+Occasionally the dump process may dump core. This is caused by some
+sort of corruption in an attribute, normally. You can tell if the dump
+process has died by looking in your data directory; you will see
+something like "outdb.Z.#5#". Wait a few moments and check on the file
+again. If it has grown, then the game is in the process of a normal
+dump. If it hasn't, and there's a core file, then something has gone
+wrong. You should definitely shout a warning that building is not being
+saved.
+
+To attempt to fix the problem, do a @dbck to take care of any possible
+minor weirdness in the database, then try doing a "@dump/paranoid", and
+reading the checkpoint logfile (default is log/checkpt.log). This is
+slow, but it will write out an uncorrupted database, and tell you what
+it fixed. Back up that database and indb.Z, then figure out what you're
+going to do next: you can take the game down with a kill -9, or attempt
+to manually fix the problem by either @destroying the offending object,
+or attempting to reset the attributes on the object that are causing a
+problem.  If "@dump/paranoid" dies, you are more or less out of luck.
+
+The game may crash from time to time. It will generate a core file,
+usually; if you don't limit the coredumpsize or strip the executable,
+you should be able to get some useful information out of it, using a
+debugger. Javelin is interested in stack traces. You can do a stack
+trace in the following manner: Go into the directory where you keep
+your source code, and type
+       <name of debugger> netmud ../game/core
+If you don't call your executable "netmud", substitute in whatever 
+you do call it.
+
+You are looking for variables set to bizarre values - attempts to
+access objects that aren't there, attempts to use pointers which point
+to nothing, and the like.
+
+If you are using the "adb" debugger (don't do this unless you really
+have absolutely nothing else available), you will see nothing. It's
+loaded and ready, though. Type "$c". This will print out a list of the
+functions it called. Type "$q" to quit. You can't really get much more
+useful information out of adb.
+
+If you are using the "dbx" debugger, type "where" to see the stack
+trace. You can move through it using "up" and "down", and see exactly
+what the sequence of calls was. You can also use "print <variable
+name>" to see the value of a variable at the time the game crashed.
+The "gdb" debugger is similar to "dbx"; with that, you can abbreviate
+"print" as "p".
+
+Javelin appreciates news of any bugs found, and any patches that have
+been written to deal with them. He is also interested in any extensions
+that people make to the code, and requests that ones that are of more
+than just local interest be sent to him for inclusion in the next
+release of this code.
+
+One important thing to remember is, if the MUSH refuses to start, there
+is probably a good reason. Check the MUSH log, and the core file, if
+there is one. Make sure to back up your database before attempting to
+restart -- remember that every time it restarts, it overwrites
+indb.Z.old. If you restart three times and somehow manage to trash your
+database each time (for example, a full process table zero'ing out your
+files), you won't have a backup to restart from, unless you've backed
+up your database before trying!
+
+You can also find helpful tips in Javelin's Guide for Gods,
+which is available on the WWW as
+       http://pennmush.org/~alansz/guide.html
+and by ftp from pennmush.org as 
+       /pub/DuneMUSH/Guide/guide-single.txt
+
diff --git a/game/access.README b/game/access.README
new file mode 100644 (file)
index 0000000..a3dc346
--- /dev/null
@@ -0,0 +1,189 @@
+      Everything you ever wanted to know about access.cnf and more
+
+
+The file "access.cnf" in the game/ subdirectory controls access to the
+MUSH. It's used to restrict which sites can conect to players or guests,
+create players, or register players by email. It can also flag a site
+as suspect; all players who connect from suspect sites have their
+SUSPECT flag set.
+
+This file replaces the older lockout.cnf and sites.cnf file; 
+typing 'make access' will create a new access.cnf file from your
+lockout.cnf and sites.cnf files.
+
+FILE SYNTAX
+
+The syntax of the file is simple. Each line gives information about
+a host or host-pattern:
+
+[user@]host [dbref] [options] [# comment]
+
+host - the only required file, this is a hostname or a wildcard pattern
+       to match. Examples: 
+         berkeley.edu  - matches hostname berkeley.edu
+        *.berkeley.edu - matches hostname <anystring>.berkeley.edu
+         *berkeley.edu  - matches either of the above
+         *              - matches all hosts
+user@ - if the host supports ident, and you trust the ident response,
+        and you're sure that the link is fast enough that you'll always
+        get an ident response in time, you can match for specific
+        users. Example: johnq@netcom.com
+dbref   - The dbref of a character to restrict the rule too.
+          (Only makes sense for connect rules). Leave it out
+          or use '-2' to match all characters. Leave out the '#'
+          in the dbref.
+options - A space-separated list of options which apply to connections
+          from the host. Described in detail below.
+comment - an optional comment
+
+Everything in the file is separate by a single space - don't use tabs.
+
+The file is read line-by-line, and the first match is used. This
+means that the order in which hosts are listed is very important.
+
+Also, since both hostnames and IPs are checked, some rules must take
+both into account.
+
+There is one special line in the file, which looks like this:
+
+@sitelock
+
+This line indicates where @sitelock'd sites will be inserted in
+the file. Hosts listed after this line can have their access
+options superseded by using @sitelock on-line. Hosts listed before
+this line can not have their access options overriden by @sitelock.
+If the line doesn't appear in the file, it will be added to the end
+of the file at startup.
+
+
+READING AND WRITING THE FILE
+
+The access.cnf file is read and cached at startup, and whenever the MUSH 
+receives a HUP signal.
+
+The access.cnf file is written back to disk whenever @sitelock is used.
+
+
+OPTIONS
+
+The following options are available for each host in the file:
+
+create - People connecting from this host may 'create' players.
+!create        - People connecting from this host may NOT 'create' players.
+connect - People may connect to their existing non-guest players.
+!connect - People may NOT connect to their existing non-guest players.
+guest  - People may connect to guest players from this host.
+!guest - People may NOT connect to guest players from this host.
+none    - shorthand for: !create !connect !guest
+default - shorthand for: create connect guest
+!god    - God cannot connect from this host.
+!wizard - Wizards cannot connect from this host.
+!admin  - Wizards and Royalty cannot connect from this host.
+register - People may use the 'register' command from this host.
+suspect        - All players connected to from this host will be set SUSPECT
+deny_silent - Don't log failed create/connect/guest/register attempts
+regexp - Use regexp match rather than glob matching for the pattern
+
+If no options are given, the host is treated as if option "none"
+were used. If at least one option is listed, it's assumed that
+hosts can do anything (create, connect, guest) that they are
+not prohibited from.
+
+
+EXAMPLE SCENARIOS
+
+Here are some typical ways you might want to set up your file:
+
+1. Totally ban specific sites, allow all others
+
+*badsite.com -2 none
+*.twink.edu -2 none
+
+This will totally lock out those sites (like lockout.cnf)
+
+
+2. Allow specific sites and no others. Note that you must list both
+   hostname-matching patterns and ip address-matching patterns, because
+   if either fails to match a rule that allows connection, the connection
+   will be refused. This is true in general when writing positive rules.
+
+*.berkeley.edu -2 default
+128.32.* -2 default
+* -2 none
+
+People may connect from .berkeley.edu (128.32.) sites only.
+
+
+3. Allow connection but not creation from some sites
+
+*.twink.edu -2 !create
+
+This is equivalent to the former function of sites.cnf
+
+
+4. Allow connection but not creation or guest-connection from some sites
+
+*.twink.edu -2 !guest !create
+
+
+5. Require that a given site use the 'register' command to register
+   players by email. 
+
+*.twink.edu -2 !create register
+
+Using !create prevents people from using the usual create command.
+Adding register allows them to uset the register command.
+
+
+6. Disable creation from twink.edu sites, and don't let Wizards 
+   override this rule with @sitelock
+
+*.twink.edu -2 !create
+@sitelock
+
+Because the rule appears above "@sitelock", and @sitelock rules appear
+below "@sitelock", the rule will always be checked before any 
+@sitelock rules.
+
+
+7. Disable creation from twink.edu sites, but allow Wizards to
+   later override this rule with @sitelock
+
+@sitelock
+*.twink.edu -2 !create
+
+Because the rule appears below "@sitelock", new @sitelock rules
+(which will be added immediately following "@sitelock") will precede
+it, and will be checked first.
+
+8. God can only be connected to from one specific account on the
+   server, and nowhere else. Wizards cannot override it. This requires
+   you to connect to 'localhost <port>' from a given account on the
+   same server the mush runs on. If the server doesn't support ident,
+   remove 'username@' so that anyone on the server can connect. 
+
+username@localhost 1 connect 
+username@127.0.0.1 1 connect
+* -2 !god
+@sitelock
+
+9. A complex example:
+
+  a) Allow anybody from localhost.berkeley.edu complete access
+  b) Force people from *.twink.edu to use registration, and set their
+     players SUSPECT
+  c) Completely ban *badsite.com, and don't log attempts to connect
+  d) Don't allow jerk@netcom.com to connect to Guests
+  e) Allow people from somesite.org to connect to Guests only.
+  f) Allow @sitelock to override c-e above
+
+localhost.berkeley.edu -2 default
+127.0.0.1 -2 default
+*.twink.edu -2 !create register suspect
+@sitelock
+*badsite.com -2 none deny_silent
+jerk@netcom.com -2 !guest
+somesite.org -2 !connect !create guest
+
+
+
diff --git a/game/aliascnf.dst b/game/aliascnf.dst
new file mode 100644 (file)
index 0000000..8ef5d67
--- /dev/null
@@ -0,0 +1,68 @@
+# Command aliases that you'd like reserved.
+# Normally, commands are autoaliased to their unique prefixes,
+# so n -> news
+# However, you might prefer to have n not treated as a command alias
+# because you think of it as short for 'north' and want to use a 
+# master room exit to catch it. In that case, you'd uncomment
+# lines like this:
+#reserve_alias e
+#reserve_alias w
+#reserve_alias s
+#reserve_alias n
+#reserve_alias nw
+#reserve_alias ne
+#reserve_alias sw
+#reserve_alias se
+#reserve_alias u
+#reserve_alias d
+
+# We add a dummy here to make updating easier
+reserve_alias dummy_exit_alias
+
+# You can also alias commands to other names.
+# command_alias EXISTING_COMMAND ALIAS
+# For example, a french-language game do the following:
+# command_alias think pensent
+# Some standard aliases:
+command_alias @edit @gedit
+# Alias read to look?
+#command_alias look read 
+
+command_alias look l
+command_alias inventory i
+command_alias @switch @sw
+command_alias page p
+command_alias whisper w
+command_alias goto move
+command_alias brief b
+
+# As well as commands, functions can be aliased. Same syntax,
+# just function_alias instead of command_alias. Note that many
+# functions use the same internal function, doing slightly different
+# things depending on the name it was called by. The hasattr*() functions
+# are an example of these. Aliases of this type of function may not work
+# right.
+# Some standard aliases:
+function_alias lsearch search
+function_alias soundslike soundlike
+function_alias lstats stats
+function_alias trunc val
+function_alias nattr attrcnt
+function_alias iter parse
+function_alias modulo mod
+function_alias modulo modulus
+function_alias randword pickrand
+
+# For rhost compatibility
+function_alias textfile dynhelp
+
+# And so can attributes!
+attribute_alias describe desc
+attribute_alias idescribe idesc
+attribute_alias success succ
+attribute_alias osuccess osucc
+attribute_alias asuccess asucc
+attribute_alias failure fail
+attribute_alias ofailure ofail
+attribute_alias afailure afail
+
diff --git a/game/data/.gitify-empty b/game/data/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/data/README b/game/data/README
new file mode 100644 (file)
index 0000000..42f363b
--- /dev/null
@@ -0,0 +1 @@
+Database files are usually kept here.
diff --git a/game/getdate.README b/game/getdate.README
new file mode 100644 (file)
index 0000000..8529a4f
--- /dev/null
@@ -0,0 +1,14 @@
+The getdate.template file is used by the extended CONVTIME() (See
+@config compile for it's status) to match many date and time
+templates.
+
+The format is one template per line, using the same codes as
+TIMEFMT(), except with %-signs instead of $-signs. Plain text is
+matched exactly. The MUSH does not need to be rebooted after editing
+getdaily.template.
+
+DO NOT use '%c', '%x' or '%X', as they might crash the mush on a linux
+box, and possibly others.
+
+For more details on the format of the file, see your system
+documentation on the getdate() and strptime() C functions.
diff --git a/game/getdate.template b/game/getdate.template
new file mode 100644 (file)
index 0000000..2e3e278
--- /dev/null
@@ -0,0 +1,13 @@
+%a %b %d %H:%M:%S %Y
+%a %b %d %H:%M:%S
+%b %d %H:%M:%S %Y
+%b %d %H:%M:%S
+%B %d %H:%M:%S
+%B %d %H:%M:%S %Y
+%b %d %Y
+%b %d
+%B %d %Y
+%B %d
+%D
+%m/%d/%Y
+%m/%d/%y %H:%M:%S
diff --git a/game/log/.gitify-empty b/game/log/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/log/README b/game/log/README
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/game/mushcnf.dst b/game/mushcnf.dst
new file mode 100644 (file)
index 0000000..9457fae
--- /dev/null
@@ -0,0 +1,844 @@
+# configuration file for PennMUSH
+#
+# The directives in this file control the behavior of your MUSH
+# If you change any of them while your MUSH is running, you can
+# cause the MUSH to re-read this file by sending it a SIGHUP
+# signal. Typically, one does this by using 'ps' to determine the
+# MUSH's process id#, and then issuing a: kill -1 pid#
+# command.
+
+####
+#### ESSENTIALS
+####
+
+# Name of your MUSH. Please change this.
+mud_name       CobraMUSH       
+
+# The port it's running on. See also ssl_port, later.
+port   4201
+
+# Should we listen only on a specific IP address? If your host has
+# multiple IP addresses, put the ip address to listen on here
+# Example: ip_addr 128.32.243.78
+# Otherwise, just leave it blank.
+ip_addr        
+
+# Should the MUSH attempt to resolve IP numbers to hostnames?
+# If yes, you'll see hostnames on the wizard WHO. If no, IP numbers.
+# No makes sense if you're running PennMUSH at home and don't have
+# a DNS server you can access. MacOS 7/8/9 should use 'no'
+# Changing this while using info_slave requires a @shutdown/reboot
+# to make it take effect.
+use_dns        yes
+
+# Databases
+# These are, respectively, where to read a database, where to
+# write a database, where to put a panic dump (performed if
+# the MUSH determines it's going to crash, where to put MUSH mail,
+# and where to keep information about the chat system.
+# Filenames are relative to the game/ directory.
+#
+# Do NOT put compression suffixes at the end of the files.
+# That's handled below
+#
+input_database data/indb
+output_database        data/outdb
+crash_database data/PANIC.db
+mail_database  data/maildb
+chat_database  data/chatdb
+flag_database   data/flagdb
+
+# Database compression
+# When your databases are dumped, they can be dumped in a compressed
+# format to save disk space, or uncompressed for speed.
+# To use a compression program, you must know the name of the
+# program that compresses, the name of the program that uncompresses,
+# and the suffix that the compression program adds.
+#
+# Most people can just use one of the following:
+#
+# Use these 3 lines for no compression. Required on win32 and MacOS 7/8/9.
+#compress_program
+#uncompress_program
+#compress_suffix
+#
+# Use these 3 lines for gzip compression
+#compress_program gzip
+#uncompress_program gunzip
+#compress_suffix .gz
+#
+# Use these 3 lines for Unix compress compression
+#compress_program compress
+#uncompress_program uncompress
+#compress_suffix .Z
+#
+# Use these 3 lines for bzip2 compression
+#compress_program bzip2
+#uncompress_program bunzip2
+#compress_suffix .bz2
+#
+compress_program       gzip
+uncompress_program     gunzip
+compress_suffix        .gz
+
+# Room where new players are created.
+player_start   0
+
+# Room where new guests begin their brief lives.
+guest_start    0
+
+# The master room. Exits here are global, as are commands on
+# objects here. This only is used if globals is set to "yes".
+master_room    2
+
+# SQL Master Room. Room that holds objects for sql command queries.
+# -1 means we're not using it.
+sql_mroom      -1
+
+# The base room. Any room that can be reached from this room
+# through exits is considered a 'connected room'.
+# See also: exits_connect_rooms
+base_room      0
+
+# The ancestor room. This dbref serves as an 'ultimate parent' to
+# all rooms that aren't set ORPHAN. Set to -1 to disable.
+ancestor_room  -1
+
+# The ancestor exit. This dbref serves as an 'ultimate parent' to
+# all exits that aren't set ORPHAN. Set to -1 to disable.
+ancestor_exit  -1
+
+# The ancestor thing. This dbref serves as an 'ultimate parent' to
+# all things that aren't set ORPHAN. Set to -1 to disable.
+ancestor_thing -1
+
+# The ancestor player. This dbref serves as an 'ultimate parent' to
+# all players that aren't set ORPHAN. Set to -1 to disable.
+ancestor_player        -1
+
+# The default home.  This is the room used when an object becomes
+# homeless (usually due to its home getting destroyed).  It's also
+# the place where things get moved to if their location gets
+# unsalvageably corrupted.
+default_home   0
+
+# What's the filename of the @sitelock file, that controls
+# who can connect and who can't
+access_file    access.cnf    
+
+# Where are the names you want to ban players from
+# creation/rename? A good idea to start with are swear words,
+# and features names like 'Luke', 'Merlin', 'Gandalf', 'Picard',
+# 'Lessa', 'Dracula', 'Hercules', 'Scooby', 'Nancy Drew', etc,
+# depending on what type of MUSH you are running
+names_file     names.cnf
+  
+###
+### Attribute (chunk) cache
+###
+### PennMUSH can swap rarely-referenced attribute text out to a disk
+### file, and cache often-used attribute text in memory. This 
+### can result in substantial (typically 30-50%) savings
+### in process memory use, at the cost of a very small performance hit.
+### You can control the size of memory cache (or set it so large
+### that nothing is ever swapped to disk), as well as several less
+### important parameters here. 
+###
+
+# The file to store the attribute data in, when not in memory.
+# This is relative to the game/ directory.
+chunk_swap_file        data/chunkswap
+
+# The amount of memory allowed for the attribute cache, in bytes.
+# The actual amount used will be a multiple of 64K slightly less
+# the specified amount.  You must give it at least 132000 bytes.
+# If you want to use an 'infinite' cache, try setting this
+# to 2000000000; you'll lose the memory benefits, but you'll still
+# gain some locality benefits and overhead savings.
+chunk_cache_memory     1000000
+
+# The number of attributes that may be moved at one time, once per
+# second.  The higher the value, the faster memory gets defragmented,
+# but at a greater CPU cost.
+chunk_migrate  50
+
+###
+### SSL support
+###
+
+# The port to listen on for SSL connections. This must be an unused
+# port other than the standard connection port. To disable SSL
+# connections, leave this set to 0.
+ssl_port       0
+
+# The ip address to bind to for the SSL port, if one is specified. 
+# If your host has multiple IP addresses, put the ip address to 
+# listen on here. Otherwise, leave it blank to listen on all
+# addresses if SSL is in use.
+ssl_ip_addr
+
+# The file containing the MUSH server's certificate and private key,
+# concatenated together, and with no password on the private key.
+# Obviously, this file should only be readable by the MUSH account
+# owner. If this is commented out, the server will not present a
+# certificate, so clients that attempt to authenticate the server
+# will fail.
+ssl_private_key_file   server-key.crt
+
+# The file containing one or more certificates of certifying authorities
+# that the server should trust to certify clients who connect and
+# present certificates. A standard bundle of these is distributed
+# with openssl as 'ca-bundle.crt'. If commented out, the server will
+# not attempt client authentication.
+ssl_ca_file    /usr/share/ssl/certs/ca-bundle.crt
+
+# Are clients *required* to present a valid certificate in order to
+# make an SSL connection?
+ssl_require_client_cert        no
+
+###
+### Limits, costs, and other constants
+###
+
+# name of the monetary units
+money_singular Penny
+money_plural   Pennies
+
+# Should there be a limit on how long players can be idle?
+# If you want one, set idle_timeout to the # number of MINUTES 
+# a player may idle before getting disconnected.
+# If you don't want a timeout, set it to 0.
+idle_timeout   0m
+
+# Should there be a limit on how long connections at the connect screen
+# (without an associated player) can be idle?
+# If you want one, set unconnected_idle_timeout to the # number of MINUTES 
+# a connection may idle before getting disconnected.
+# If you don't want a timeout, set it to 0.
+unconnected_idle_timeout       5m
+
+
+# how many seconds till we consider someone idled?
+idle_time      30m
+
+# Should there be a limit on the number of logins the MUSH
+# can accept? If your operating system has a limited number of
+# file descriptors per process, you should set this to 
+# that number - 8. If not, or if you like to live dangerously,
+# set this to 0.
+max_logins     120
+
+# Should there be a limit on the number of concurrent guest logins the MUSH
+# will allow? This option can take 3 values:
+# 0 = no limit, any number of guests. Logins beyond the number of established
+#     guest characters will result in multiple players being logged into the
+#     same guest character.
+# -1 = limited to the number of guests in the database. To allow more guests
+#      to log in, create more guest characters.
+# Any other number = the number of guest connections allowed at once.
+max_guests     0
+
+# How much MUSH money do players get when they're created?
+starting_money 150
+
+# How much MUSH money do non-guest players get each day they log in?
+paycheck       50
+
+# How much MUSH money do guests get each day they log in?
+guest_paycheck 0
+
+# What's the most money anyone but guests can have?
+max_pennies    100000
+
+# What's the most money guests can have?
+max_guest_pennies      1000
+
+# If quotas are enforced, how much players get by default
+starting_quota 20
+
+# number of commands a player can have queued. Prevents runaway machines
+# from getting out of hand.
+player_queue_limit     100
+
+# the number of commands run from the queue when there is no net activity
+queue_chunk    3
+
+# the number of commands run from the queue when there is net activity
+active_queue_chunk     1
+
+# the maximum level of recursion allowed in functions
+function_recursion_limit       50
+
+# the maximum number of functions that can be invoked
+function_invocation_limit      5000
+
+# the maximum depth we're allowed to recursively call the parser
+# for a single expression. This limits how much the stack size can increase,
+# which could be useful if your host limits your stack (it will prevent
+# a crash). The higher your allowed stack size limit, the larger the
+# mush process can grow, and the higher this can be set. Generally
+# speaking, you won't ever see more than 8192 recursions, so that's
+# probably an upper limit, but most sane code shouldn't need more
+# than a couple thousand. Setting it to '0' means unlimited.
+call_limit     100
+
+# The maximum number of milliseconds of CPU time that a single queue entry
+# is allowed to use before aborting. Setting this to a low number will
+# help prevent many malicious attacks, as well as accidently bad code,
+# from lagging the game. Setting it to 0 means unlimited, and is a bad 
+# idea. Remember there are 1000 milliseconds in a second.
+queue_entry_cpu_time   1500
+
+# How many channels total can be created?
+# This doesn't allocate memory, it just sets a maximum.
+max_channels   200
+
+# How many channels can each non-admin create? Set this to some number
+# higher than zero to allow mortals to create channels.
+max_player_chans       0
+
+# What's the maximum number of levels of parenting allowed
+max_parents    10
+
+# What's the max chain length of indirect locks allowed?
+max_depth      10
+
+# How many @functions can we have? If you change this without # doing a
+# shutdown (or shutdown/reboot) immediately thereafter, you'll very likely
+# crash your MUSH, so don't do that.
+max_global_fns 80
+
+# How much does it cost a mortal to create a channel?
+chan_cost      1000
+
+# How likely should it be that noisy whispers are noticed by other
+# players in the room? (Others see: John whispers to Mary.)
+# Use a number from 0 to 100
+whisper_loudness       100
+
+# the highest allowable dbref -- you can't build more than this
+# many objects. if you don't want such a limit, leave this set
+# to 0.
+max_dbref      0
+
+# The maximum number of attributes per object. This prevents
+# denial-of-service attacks involving creating an infinite number
+# of attributes on an object. This value is probably enough.
+max_attrs_per_obj      2048
+
+# The maximum number of mail messages in each player's inbox.
+# Encourages people to clean up their inbox, discourages 
+# mailspammers.
+mail_limit     300
+
+# If you kill someone, you can spend up to 100 coins; the chance
+# of killing them is the number of coins you spend. What's the
+# minimum number of coins that must be spent, and the default
+# number if no number is given?
+kill_min_cost  10
+kill_default_cost      10
+
+# If you kill someone, they get paid off. The payoff is this
+# percentage of the amount you spent to kill them. The usual is 50%.
+kill_bonus     50
+
+# How much to various commands cost:
+find_cost      100
+page_cost      0
+
+# How many objects are equal to 1 quota, if quotas are used
+quota_cost     1
+
+# How much deposit is required to queue a command?
+queue_cost     10
+
+# One out of how many commands that are queued will cost the
+# player a coin? Setting this to 1 causes a coin to be lost with
+# every command. Setting it to 0 disables coin loss for queued
+# commands (and is a very bad idea).
+queue_loss     63
+
+# What does it cost to build various things?
+room_cost      10
+object_cost    10
+link_cost      1
+exit_cost      1
+
+# How often should we run a purge to remove destroyed objects? (seconds)
+purge_interval 10m1s
+
+# How often should we run a dbck to check db consistency? (seconds)
+dbck_interval  9m59s
+
+# If you've got USE_WARNINGS defined, set this to the number
+# of seconds between MUSH-wide topology
+# warning checks. Default is 3600 (1 hour). If you set this to 0,
+# timed MUSH-wide checks will be disabled.
+warn_interval  1h
+
+# If compiled with FLOATING_POINTS support, this controls the
+# decimal precision of numbers. Default is 6 digits after the
+# decimal point.
+float_precision        6
+
+# The password that must be given to do an @logwipe. You must also
+# be God, of course. CHANGE THIS.
+log_wipe_passwd        zap!
+
+# The maximum length of player names. Lowering this won't change
+# current player names. This number should be one greater than the
+# actual maximum length you want.
+player_name_len        16
+
+# Limit the number of objects players can own.
+use_quota yes
+
+###
+### Dump stuff
+###
+
+# How often should the database be dumped, in seconds?
+# 3600 is once an hour, and probably the most frequent you'd ever want.
+# On a large MUSH, consider at most once every 3-6 hours.
+# This cannot be a multiple of any of the timer.c parameters
+# (so keep it an even number of hours).
+dump_interval  1h
+
+# should I fork a concurrent process to do database dumps?
+# If I do, your memory requirements will double during the dump.
+# If I don't, the MUSH will pause while it dumps.
+# If you're low on memory, don't do this.
+# If you're on Win32, don't do this; fork() is not defined.
+forking_dump   yes
+
+# If you're not forking, you get a bunch of messages that you
+# can set to warn players when the dump is 5 minutes away,
+# 1 minute away, in progress, and finished. You can 
+# leave messages you don't want blank, but don't comment
+# them out or remove them from the file or you'll get the
+# default messages.
+dump_warning_5min      GAME: Database save in 5 minutes.
+dump_warning_1min      GAME: Database save in 1 minute.
+dump_message   GAME: Saving database. Game may freeze for a few moments.
+dump_complete  
+
+
+### 
+### Filenames
+###
+
+# Text files shown on connection, as message of the day,
+# as wizard message of the day, on quit, to newly created players,
+# when logins are disabled, when player creation is disabled,
+# and when a guest logs in.
+connect_file   txt/connect.txt
+motd_file      txt/motd.txt
+quit_file      txt/quit.txt
+newuser_file   txt/newuser.txt
+down_file      txt/down.txt
+register_create_file   txt/register.txt
+guest_file     txt/guest.txt
+full_file      txt/full.txt
+
+# The equivalent files in html, shown to Pueblo clients.
+connect_html_file      txt/connect.html
+motd_html_file txt/motd.html
+quit_html_file txt/quit.html
+newuser_html_file      txt/newuser.html
+down_html_file txt/down.html
+register_create_html_file      txt/register.html
+guest_html_file        txt/guest.html
+full_html_file txt/full.html
+
+# The big text files. New ones can be added by setting up
+# a new subdirectory of game/txt as described in game/txt/README,
+# and adding a new help_command line below, or uncommenting one of
+# the normal file entries.
+
+#help_command events txt/events.txt
+#help_command index txt/index.txt
+#help_command rules txt/rules.txt
+#help_command +help txt/plushelp.txt
+
+help_command   help txt/help.txt
+help_command   news txt/news.txt
+help_command   changes txt/changes.txt
+
+ahelp_command  ahelp txt/help.txt
+ahelp_command  anews txt/news.txt
+restrict_command       ahelp admin
+restrict_command       anews admin
+
+
+### Config directive for IDENT.
+### If you want to do ident (RFC1143) lookups, set use_ident to "yes"
+### and select an ident_timeout to determine how long the MUSH
+### should wait for a response, in seconds. If you're using
+### INFO_SLAVE (in options.h), this is how long the info_slave waits.
+# Changing this while using info_slave requires a @shutdown/reboot
+# to make it take effect.
+use_ident      yes
+ident_timeout  5s
+
+###
+### Logging
+###
+### When selecting log files, you may assign multiple logs to the
+### same filename. If you don't assign a filename to a log,
+### messages are written on stderr instead (usually redirected to
+### log/netmush.log). Probably unwise to change these in a running
+### MUSH.
+###
+### When selecting log files, you may assign multiple logs to the
+### same filename. If you don't assign a filename to a log,
+### messages are written on stderr instead (usually redirected to
+### log/netmush.log). Probably unwise to change these in a running
+### MUSH.
+###
+
+# Filename to log important messages (startups, errors, shutdowns)
+error_log  log/netmush.log
+
+# Filename to log connections to
+connect_log log/connect.log
+
+# Filename to log wizard commands to
+wizard_log log/wizard.log
+
+# Filename to log dump checkpoint messages to
+checkpt_log log/checkpt.log
+
+# Filename to log debugging trace messages to
+trace_log log/trace.log
+
+# Filename to log commands by SUSPECT players to
+command_log log/command.log
+
+# log all commands. Makes big, big command.log files. Use only for
+# debugging, generally.
+log_commands   no
+
+# log forces done by wizards
+log_forces     yes
+
+###
+### Logins
+###
+
+# Support the pueblo MUSH client and allow html to be sent to it
+pueblo no
+
+# allow non-wizard/royalty logins
+logins yes
+
+# allow guest logins
+guests yes
+
+# allow players to create/register characters at the login screen
+# If you turn this off, neither "create" nor "register" will work.
+# Use access.cnf if you want to disable creation for specific sites
+# or disable creation but enable registration for everyone.
+player_creation        yes
+
+# If you use the shs password system, and your database was created
+# on a little-endian hardware architecture (such as an intel PC),
+# set this to 'yes'. If your database was created on a big-endian
+# hardware architecture (most non-intel systems), set this to 'no'.
+# If you port a db with shs passwords between systems, and the
+# passwords don't work, try changing this setting.
+reverse_shs    yes
+
+# trigger @aconnect/@adisconnect in a connecting player's location
+# if the location is a room or thing?
+room_connects  no
+
+
+###
+### SQL connectivity
+###
+
+# What SQL server platform should we use? Options include:
+# mysql, disabled (the default)
+sql_platform disabled
+
+# What's the SQL hostname? Use '127.0.0.1' for a TCP connection
+# to the local host, and 'localhost' for a domain socket connection.
+sql_host 127.0.0.1
+
+# What's the SQL database? You have to set this to a database that
+# you create.
+sql_database mush
+
+# What username to access the database?
+sql_username mush
+
+# What password for that user? Change this!
+sql_password mush
+
+
+###
+### Options affecting commands and functions
+### (See also restrict_command to restrict command use)
+###
+
+# The old daytime directive is better suited to restrict_command
+# or @command/disable.
+
+# prevent objects from evaluating ufuns on more privileged objects. [++]
+safer_ufun     yes
+
+# allow functions that have side effects? (e.g. dig(), etc.)
+function_side_effects yes
+
+# default whisper to whisper/noisy instead of whisper/silent
+noisy_whisper  no
+
+# is possessive get (get players's object) allowed?
+possessive_get yes
+
+# what if the player is disconnected?
+possessive_get_d       no
+
+# SAFE absolutely prevents destruction, even with @nuke
+really_safe    yes
+
+# With this turned on, ZMRs and ZMOs are not included in control
+# checking. Only Zone Master Players are. The other zone types are
+# still used for command-matching, just not for control purposes.
+# Highly recomended.
+zone_control_zmp_only  yes
+
+# When a player is nuked, his SAFE objects are @chowned to God.
+# If this is set to "yes", his non-safe objects are destroyed
+# If this is set to "no", they are chowned to God
+destroy_possessions    yes
+
+# Can we @link to an object?
+link_to_object yes
+
+# Keep queue limits on a per-owner rather than per-object basis?
+# That is, is an object runaway when its owner's total queue is too
+# high, rather than when the object's queue is too high?
+owner_queues   no
+
+# If this is yes, DARK wizards do not trigger AENTER/ALEAVE when they move.
+# If it's no, they are just like anybody else.
+# We don't have wizards.. Should we make this be something else?
+# wiz_noaenter no
+
+# should say/pose by a DARK wizard be anonymous ('Someone says...')?
+full_invis     no
+
+###
+### TinyMUSH compatibility
+###
+
+# Should we treat a missing number as 0 in things like add(3,)?
+# For TinyMUSH compatibility, the answer is 'no'.
+null_eq_zero   no
+
+# In PennMUSH, strings and db#s larger than #0 have traditionally
+# been considered true (1) in boolean functions like and(), or(), etc.
+# In TinyMUSH, strings and db#s evaluate as false (0)
+# Should we emulate TinyMUSH?
+tiny_booleans  no
+
+# TinyMUSH's trim function is: 
+#   trim(<string> [,<trim style> [,<trim character>]])
+# PennMUSH's trim function has been:
+#    trim(<string>[,<character to trim>[,<trim style>]])
+# Should we emulate TinyMUSH? [+ for new MUSHes]
+tiny_trim_fun  yes
+
+# In Tiny, strings used in math expressions evaluate to 0,
+# so eq(asdfa,0) = 1, gt(asdf,0) = 0, etc.
+# In Penn, using strings where numbers should be is traditionally an
+# error (returning #-1 ARGUMENT MUST BE NUMBER or similar)
+# Do you want the TinyMUSH behavior?
+tiny_math      no
+
+# should @pemit default to @pemit/silent, like TinyMUSH?
+silent_pemit   no
+
+###
+### Attributes
+###
+
+# enable the adestroy attribute, triggered when an object is nuked?
+adestroy       no
+
+# enable the amail attribute for admin, triggered when they receive
+# @mail? This does no mail loop checking. [-]
+amail  no
+
+# does @listen work on players?
+player_listen  yes
+
+# does @ahear work on players? If player_listen is yes, and
+# player_ahear is no, sound outside the player can be heard by her
+# inventory, but her @ahear isn't executed.
+player_ahear   yes
+
+# Should we trigger the @startup attribute on startup?
+# You always want to, unless you need to disable some
+# malicious code on an @startup. This does not affect the
+# @restart command, which will work even if this is "no".
+startups       yes
+
+# Can @desc attributes be accessed remotely by everyone?
+# Historically, this was allowed, but it makes it difficult to
+# have hidden objects with secrets in their @desc if players
+# can remotely get the @desc. If set to "no", you must be
+# able to look at something to retrieve its @desc.
+read_remote_desc       no
+
+###
+### Cosmetic stuff
+###
+
+# show room/object/player names in bold for ansi players?
+ansi_names     yes
+
+# show exit lists with commas (a, b, and c)?
+comma_exit_list        no
+
+# count hidden players when WHO reports total connected?
+count_all      no
+
+# are rooms with any exits considered connected, and thus not required
+# to have the FLOATING flag set on them?
+exits_connect_rooms    no
+
+# Prefixes for various broadcast messages
+wall_prefix    Announcement:
+
+# can players have names with spaces in them?
+player_name_spaces     no
+
+# show expanded flag name list when you examine an object?
+flags_on_examine       yes
+
+# show visual attributes when you examine an object you don't own?
+# (like examine/full in TinyMUSH)
+ex_public_attribs      yes
+
+# What should page a b c = message do?
+# If blind_page is yes, page defaults to page/blind (a,b,c each get separate
+# pages with no evidence that it was a multipage). If blind_page is no,
+# page defaults to page/list (a single page goes to a,b,c and they can
+# see that they all received it.
+blind_page     yes
+
+# Should we show the pager's alias, in parentheses, after their name?
+# That is, should recipients see:
+# Javelin (Jav) paged: ...
+page_aliases   yes
+
+# Should +whatever "hi! strip the initial quote and produce <whatever> X says,
+# "hi!", or not (producing <whatever> X says, ""hi!"). This also affects
+# the @*wall and say commands.
+chat_strip_quote       yes
+
+
+# Should strlen(%r) be 1 or 2? Turning this option on will allow %r to be
+# used as a list delimiter, but might break formatting softcode that depends
+# on its length being 2.
+newline_one_char       yes
+
+
+# Should object names be restricted to just ascii characters, or can
+# non-ascii (Accented letters, for example) be used as well if the
+# mush'es locale permits? Not reccomended except for
+# non-english-language games, as people with tend to have problems
+# typing in those extra characters. @nameaccent is the preferred way to
+# get fancy names.
+only_ascii_in_names    yes
+
+###
+### Default flags for newly created stuff
+### To get multiple flags, you may repeat these directives:
+###   player_flags flag1
+###   player_flags flag2
+### As of 1.7.7p6, you may also stack them on one directive:
+###   player_flags flag1 flag2
+###
+
+# -- Default flags for exits
+
+# Uncommenting this will cause the exit default to be DARK (like in TinyMUD):
+#   no exits show up on the "Obvious exits" list.
+# exit_flags dark
+
+# Uncommenting this will cause all exits to be TRANSPARENT by default
+#   (if you look through them, you will see the description of the next room)
+# exit_flags transparent
+
+# -- Default flags for rooms
+
+# Uncommenting this will cause all rooms to be TRANSPARENT by default.
+#   Each obvious exit in a transparent room is displayed on a line by
+#   itself, in the format:
+#   <Exit name> leads to <Destination name>
+#   instead of having all exits strung out in one row.
+# room_flags transparent
+
+# Objects which are NO_COMMAND will not be checked for $commands. 
+#   Making this a default may speed up your server somewhat. This is
+#   definitely a good idea for rooms and players, and, depending on
+#   the compostion of your database, probably a good idea for things.
+room_flags     no_command
+
+# -- Default flags for players
+
+# Players who are ENTER_OK can be given stuff.
+player_flags   enter_ok
+
+# Players who are ANSI see attribute names hilighted.
+#player_flags  ansi
+
+# See the explanation for rooms and no_command.
+player_flags   no_command
+
+
+# Players who are INHERIT allow all their objects to control them.
+# player_flags inherit
+
+
+
+# -- Default flags for things
+
+# For example, you can't see through OPAQUE things.
+# thing_flags opaque
+
+# See the explanation for rooms and no_command.
+thing_flags    no_command
+
+# -- Default flags for newly created channels
+#
+# For example, you might want:
+# channel_flags player quiet open hide_ok
+channel_flags  player
+
+###
+### Reserved command names, and command and function aliases are in
+### alias.cnf
+###
+include        alias.cnf
+
+###
+### Restrictions on command usage are in restrict.cnf
+###
+include        restrict.cnf
+
+# You should probably keep this defined, security issues may arrise from
+# this being set no.
+twinchecks      yes 
+
+# Define a powerless object in the database
+# (this needs to be set if your using twinchecks)
+powerless       0
+
+mem_check 0
+dark_noaenter yes
+empty_attrs  yes
+announce_connects yes
diff --git a/game/namescnf.dst b/game/namescnf.dst
new file mode 100644 (file)
index 0000000..4ac1a0e
--- /dev/null
@@ -0,0 +1,63 @@
+<name>
+all
+ass
+asshole
+balls
+bitch
+boobs
+breast
+butt
+chink
+clit
+clitoris
+cock
+cum
+cunt
+cybersex
+dago
+damn
+dickhead
+dildo
+fag
+faggot
+feces
+fob
+fuck
+fudgepacker
+here
+homo
+honkey
+jap
+jew
+kike
+me
+name
+nigger
+nigga
+nipple
+nutsack
+ovaries
+pecker
+penis
+piss
+pubic
+pussy
+queer
+scrotum
+semen
+shit
+slut
+sperm
+spic
+tinysex
+tit
+titties
+titty
+tribble
+twat
+vagina
+vibrator
+weiner
+wetback
+whore
+wop
diff --git a/game/restart.dst b/game/restart.dst
new file mode 100644 (file)
index 0000000..061d25d
--- /dev/null
@@ -0,0 +1,195 @@
+#!/bin/sh
+#
+
+# usage: restart
+
+#-- options
+
+# If this doesn't work, you can set GAMEDIR to the directory this
+# script lives in by hand.
+GAMEDIR=`which $0 | sed 's/\/[^\/]*$//'`
+
+# The config file
+CONF_FILE=mush.cnf
+
+# The error log file
+LOG=log/netmush.log
+
+# Uncomment the line below to attempt to allow crashes to produce
+# core dumps. If you're getting crashes, this is the best way
+# to debug them.
+#ulimit -c unlimited
+
+
+# Uncomment the line below to attempt to allow crashes to produce
+# core dumps. If you're getting crashes, this is the best way
+# to debug them.
+#ulimit -c unlimited
+
+if [ ! -d $GAMEDIR ]; then
+  echo "GAMEDIR doesn't appear to be a directory. It's: $GAMEDIR"
+  exit 1
+fi
+
+cd $GAMEDIR
+echo Running from `pwd`
+
+if [ ! -f $CONF_FILE ]; then
+  echo "CONF_FILE doesn't exist. It's: $CONF_FILE"
+  echo "Create $CONF_FILE from $GAMEDIR/mushcnf.dst"
+  exit 1
+fi
+
+# Internationalization stuff
+# Set LANG here to get international character sets and, if someone's
+# done it, translation of messages.
+# Vaild locales are usually <lang_code>_<COUNTRY CODE>
+# Example (uncomment to use):
+#LANG=fr_FR
+
+# Time zone stuff
+# If you want your MUSH to run in a different timezone than the one
+# you're in, you need to identify the target time zone file in 
+# /usr/share/zoneinfo or /usr/lib/zoneinfo. Then uncomment the next
+# two lines and set TZ to the desired timezone file, as shown, with
+# an initial colon:
+#TZ=:EST5EDT
+#export TZ
+
+
+# The config file. Best to keep this as is. If you must change
+# the name, make it a link to mush.cnf.
+CONF_FILE=mush.cnf
+
+#######################################################################
+
+if [ -z $GAMEDIR ]; then
+  echo "You must set GAMEDIR in the restart script."
+  exit 1
+fi
+
+if [ ! -d $GAMEDIR ]; then
+  echo "GAMEDIR doesn't appear to be a directory. It's: $GAMEDIR"
+  exit 1
+fi
+
+cd $GAMEDIR
+echo Running from `pwd`
+
+if [ ! -f $CONF_FILE ]; then
+  echo "CONF_FILE doesn't exist. It's: $CONF_FILE"
+  echo "Create $CONF_FILE from $GAMEDIR/mushcnf.dst"
+  exit 1
+fi
+
+
+# The config file. Best to keep this as is. If you must change
+# the name, make it a link to mush.cnf.
+CONF_FILE=mush.cnf
+
+#######################################################################
+
+if [ -z $GAMEDIR ]; then
+  echo "You must set GAMEDIR in the restart script."
+  exit 1
+fi
+
+if [ ! -d $GAMEDIR ]; then
+  echo "GAMEDIR doesn't appear to be a directory. It's: $GAMEDIR"
+  exit 1
+fi
+
+cd $GAMEDIR
+echo Running from `pwd`
+
+if [ ! -f $CONF_FILE ]; then
+  echo "CONF_FILE doesn't exist. It's: $CONF_FILE"
+  echo "Create $CONF_FILE from $GAMEDIR/mushcnf.dst"
+  exit 1
+fi
+
+# If netmush isn't here, they probably didn't make install
+# In any case, we'd better not proceed.
+if [ ! -e netmush ]; then
+  echo "I don't see $GAMEDIR/netmush. Did you remember to make install?"
+  exit 1
+fi 
+
+#
+# Read the cnf file and set some variables.
+#
+INDB=`egrep "^input_database" $CONF_FILE | sed "s/.*[  ][      ]*.*\/\(.*\)/\1/"`
+OUTDB=`egrep "^output_database" $CONF_FILE | sed "s/.*[        ][      ]*.*\/\(.*\)/\1/"`
+PANICDB=`egrep "^crash_database" $CONF_FILE | sed "s/.*[       ][      ]*.*\/\(.*\)/\1/"`
+PANICDIR=`egrep "^crash_database" $CONF_FILE | sed "s/.*[      ][      ]*\(.*\)\/.*/\1/"`
+COMPRESSOR="cat"
+SUFFIX=""
+
+# Find out what the compression program is, if any
+egrep -s "^compress_program[   ]*[A-Za-z0-9]" $CONF_FILE
+nocompress=$?
+if [ $nocompress -eq 0 ]; then
+    COMPRESSOR=`egrep "^compress_program" $CONF_FILE | sed "s/[^       ]*[     ]*\(.*\)/\1/"`
+    SUFFIX=`egrep "^compress_suffix" $CONF_FILE | sed "s/[^    ]*[     ]*\(.*\)/\1/"`
+fi
+  
+
+#-- start up everything
+
+# Prevent double-starting things. You may need to provide a pathname for
+#  some of the commands. System V flavors need "ps -f" instead of "ps uwx".
+mush=`ps uwwx | grep " $GAMEDIR/$CONF_FILE" | grep -v grep | wc -l`
+
+
+if [ $mush -gt 0 ]; then
+  echo Mush already active or some other process is using $GAMEDIR/$CONF_FILE.
+  exit 0
+fi
+
+echo Building text file indexes.
+(cd txt; make)
+
+echo Restarting Mush.
+
+if [ -r $PANICDIR/$PANICDB ]; then
+   end="`tail -1 $PANICDIR/$PANICDB`"
+   if [ "$end" = "***END OF DUMP***" ]; then
+      echo "Recovering PANIC dump."
+      cat $PANICDIR/$PANICDB | $COMPRESSOR > data/$OUTDB$SUFFIX
+      rm $PANICDIR/$PANICDB
+      echo "PANIC dump successfully recovered."
+   else
+      mv $PANICDIR/$PANICDB save/$PANICDB.corrupt
+      echo "Warning: PANIC dump corrupt. Using older db."
+   fi
+fi
+
+rm -f log/*.log
+
+if [ -r data/$OUTDB$SUFFIX ]; then
+   rm -f save/$INDB$SUFFIX.old
+   mv -f data/$INDB$SUFFIX save/$INDB$SUFFIX.old
+   mv data/$OUTDB$SUFFIX data/$INDB$SUFFIX
+else
+   echo "No $OUTDB$SUFFIX found."
+   if [ -r data/$INDB$SUFFIX ]; then
+      echo "Using $INDB$SUFFIX."
+   else
+      echo "No $INDB$SUFFIX found."
+      if [ -r save/$INDB$SUFFIX.old ]; then
+        echo "Using save/$INDB$SUFFIX.old."
+        cp save/$INDB$SUFFIX.old data/$INDB$SUFFIX
+      else
+       echo "No database found. Mush will start with a minimal world."
+     fi
+   fi
+fi
+
+if [ -r reboot.db ]; then
+  rm -f reboot.db
+fi
+
+DATEMSK="${GAMEDIR}/getdate.template"
+export DATEMSK
+
+LC_ALL=$LANG LANG=$LANG ./netmush $GAMEDIR/$CONF_FILE &
diff --git a/game/restrictcnf.dst b/game/restrictcnf.dst
new file mode 100644 (file)
index 0000000..1cfbaa9
--- /dev/null
@@ -0,0 +1,93 @@
+#
+# Commands to restrict
+# Syntax: restrict_command <command> <restriction> [" <error message>]
+#         restrict_function <function> <restriction>
+# <restriction> is a space separated list that may include:
+#      nobody          Totally disable the command
+#      nogagged        Gagged players can't use it
+#      nofixed         Fixed players can't use it
+#      noguest         Guests can't use it
+#      noplayer        Player objects can't use it (things, rooms, exits may. Command only)
+#      admin           Must be roy or wiz to use it
+#      wizard          Must be wiz to use it
+#      god             Must be god to use it
+#      logname         When func/cmd is used, log its name and user
+#      logargs         When func/cmd is used, log its name, args, and user
+#       <flag>         Any flag that must be present to use it (Command only)
+#       <power>                Any power that must be present to use it (Command only)
+#      !<restriction>  The opposite of a restriction (Command only).
+#       nosidefx        The side-effect version of a function won't work (Function only).
+#
+# If <error message> is given to a restrict_command, it is sent to the player
+# instead of a more generic, typically useless error message.
+#
+# Command restrictions typically also apply to side-effect functions that
+# emulate the command.
+
+# Don't let guests mess with the database
+# (This replaces the HARSH_GUEST compile-time define)
+# The "ATTRIB_SET" command controls the setting of attributes with
+#  @attr obj=value or &attr obj=value
+restrict_command @set noguest
+restrict_command ATTRIB_SET noguest
+restrict_command @chown noguest
+restrict_command @chzone noguest
+restrict_command @cpattr noguest
+restrict_command @mvattr noguest
+restrict_command @edit noguest
+restrict_command @gedit noguest
+restrict_command @parent noguest
+restrict_command @wipe noguest
+restrict_command @unlink noguest
+restrict_command @link noguest
+restrict_command @lock noguest
+restrict_command @unlock noguest
+restrict_command @create noguest
+restrict_command @dig noguest
+restrict_command @open noguest
+
+# @power is traditionally logged
+restrict_command @power logargs
+
+# Some additional protection against spam attacks by guests
+restrict_command @dolist noguest
+restrict_function repeat noguest
+restrict_function iter noguest
+restrict_function map noguest
+restrict_function fold noguest
+
+# Don't allow kill (slay still works)
+#restrict_command kill nobody
+
+# Uncomment to allow only builders to use certain commands
+#lock_command  @open power^builder "You need to be a builder to do that.
+#lock_command  @dig power^builder "You need to be a builder to do that.
+#lock_command    @create power^builder "You need to be a builder to do that.
+
+# Used to be player_locate
+#restrict_command @whereis nobody
+
+# Used to be hate_dest
+#restrict_command @destroy noplayer " Use @recycle instead
+
+# Used to be cemit_power
+#restrict_command @cemit admin cemit " You can't @cemit without cemit @power
+
+# Turn off ansi().
+#restrict_function ansi nobody
+
+# And some of the more dangerous side-effect functions.
+#restrict_function set nobody
+#restrict_function wipe nobody
+#restrict_function create nobody
+#restrict_function clone nobody
+#restrict_function tel nobody
+
+# If you turn this on, players who try to use functions in place
+# of commands (e.g. $foo: [pemit(%#,blah)] will get error messages.
+# Disabled by default for backward compatibility.
+restrict_command warn_on_missing nobody
+
+# We add a dummy here to make updating easier
+restrict_function lstats noguest
+
diff --git a/game/save/.gitify-empty b/game/save/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/save/README b/game/save/README
new file mode 100644 (file)
index 0000000..42f363b
--- /dev/null
@@ -0,0 +1 @@
+Database files are usually kept here.
diff --git a/game/txt/.gitify-empty b/game/txt/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/txt/Makefile b/game/txt/Makefile
new file mode 100644 (file)
index 0000000..6ec3e5a
--- /dev/null
@@ -0,0 +1,51 @@
+#
+# This makefile only rebuilds the text files we need it
+#
+# By default we build help, news, and events.
+# To build rules.txt:
+#      add rules.txt to the TXT line
+# Do the same to build index.txt (but add index.txt)
+
+TXT=help.txt news.txt events.txt
+
+# INDEX_FLAGS can be set to one or more of:
+#  --first     Insert the first entry alias in the index
+#  --longest   Insert the longest entry alias in the index
+# If left blank, all aliases are indexed. This is the default behavior.
+# (By default, this variable is not used in making the help index,
+# but if you want it to be, you can figure out how from the examples
+# below.)
+INDEX_FLAGS=
+
+all: $(TXT)
+
+help.txt: hlp/*.hlp hlp compose.sh
+       ./compose.sh hlp
+       mv hlp.txt help.txt
+       chmod 660 help.txt
+
+news.txt: nws/*.nws nws compose.sh
+       ./compose.sh nws $(INDEX_FLAGS)
+       mv nws.txt news.txt
+       chmod 660 news.txt
+
+events.txt: evt/*.evt evt compose.sh
+       ./compose.sh evt $(INDEX_FLAGS)
+       mv evt.txt events.txt
+       chmod 660 events.txt
+
+rules.txt: rules/*.rules rules compose.sh
+       ./compose.sh rules $(INDEX_FLAGS)
+       chmod 660 events.txt
+
+index.txt: index/*.index index compose.sh
+       ./compose.sh index $(INDEX_FLAGS)
+       chmod 660 *.txt
+clean:
+       -rm -f $(IDX) $(TXT)
+       -rm -f compose.sh
+       -rm -f hlp/*.orig hlp/*.rej hlp/\#* hlp/*~
+
+compose.sh: compose.sh.SH
+       sh compose.sh.SH
+
diff --git a/game/txt/README b/game/txt/README
new file mode 100644 (file)
index 0000000..d616288
--- /dev/null
@@ -0,0 +1,36 @@
+Your local help/news/events/etc files can now be kept separate
+from those distributed with PennMUSH, and can now be managed as
+a set of files rather than a single large file.
+
+Here's the details:
+
+1. The source files for help.txt, news.txt, and events.txt are
+   kept in directories called hlp, nws, and evt respectively.
+
+2. Files in those directories which end in .<directoryname> are
+   considered to be part of the text. That is, files in hlp/
+   which end in .hlp, and files in nws/ which end in .nws
+   will be merged into the final help.txt, news.txt, or whatever.
+
+3. When the MUSH is restarted, a 'make' is run in this directory.
+   For if a file in hlp/ is newer than help.txt, it calls
+   compose.sh to rebuild help.txt. If you've got perl on your system,
+   compose.sh will also call index-files.pl to make a 'help index'
+   entry which lists all your help entries.
+
+So, if you want to add your own news entries, make a file called
+nws/local.nws and put 'em there. Or maybe organize it into parts:
+nws/theme.nws, nws/code.nws, etc.
+
+Files distributed with PennMUSH always begin with "penn", so don't
+start your files with that.
+
+You can also add files for "rules" and "index" commands if you
+compiled them in. The easiest way to do add rules files is
+to:
+    a) create a directory here called "rules"
+    b) put your rules files there, each ending in .rules
+    c) edit game/txt/Makefile and add rules.txt to the TXT line
+For index files, do exactly the same process, but replace all references
+to 'rules' above with 'index'.
+
diff --git a/game/txt/changes.sh b/game/txt/changes.sh
new file mode 100644 (file)
index 0000000..9d85f5e
--- /dev/null
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# First get our entries line number and cut 'em up
+lineat=`grep -n "& Entries" changes.txt | sed "s/\([0-9]\+\):& Entries/\1/"`
+line=`expr $lineat - 1`
+
+cat changes.txt | head -n $line > changes.new
+
+# Next create entries file
+
+cat changes.new | perl index-files.pl > changes.idx
+
+# And combine the two
+cat changes.idx >> changes.new
+
+# Then we clean up our mess
+mv changes.new changes.txt
+rm changes.idx
diff --git a/game/txt/changes.txt b/game/txt/changes.txt
new file mode 100644 (file)
index 0000000..a4d0ca6
--- /dev/null
@@ -0,0 +1,657 @@
+& help
+& 0.72
+CobraMUSH Version 0.72
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (See 'changes entries' for a list of all versions.)
+
+  Attributes:
+     * New 'AAHEAR' and 'AMHEAR' attribute flags, when set
+       on a ^listen attribute, cause it to behave like
+       @aahear or @amhear instead of like @ahear. [AEJ]
+  Locks:
+     * New DBREFLIST^<attrib> lock key checks if the enactor is listed
+       in the attribute by dbref or objid [AEJ]
+  Flags:
+     * LIGHT now overrides DARK consistently [AEJ]
+     * TRACK_MONEY flag on players causes the player to be
+       informed when his objects spend money [AEJ]
+  Powers:
+     * Pueblo_Send allows you to send Pueblo tags [AEJ]
+     * Many_Attribs allows an object to have more than the configured
+       maximum number of attributes [AEJ]
+     * Nuke is now used to limit who may destroy players. [RLB]
+     * CQuota Power Renamed to SetQuotas. [RLB]
+  Commands:
+     * @program now warns owner on attempting to use prompts within
+       the command. [RLB]
+     * lock_command to offers an optional fail message in the configuration
+       directive similar to restrict_command. [RLB]
+     * @list/powers works to list powers again. [RLB]
+     * @config/powers works to list powers again. [RLB]
+     * Command switches can now be individually locked
+       using the SWITCH^ lock key. [RLB]
+     * Wildcards can now be used in listing powers via:
+       @power/list, @list/powers, & @config/powers. [RLB]
+     * @newpassword can now be used to set passwords on
+       division objects. [RLB]
+     * @SD command added which allows switching between 
+       divisions. [RLB]
+     * @edit/check will display the results of an edit without
+       actually modifying the attribute [AEJ]
+     * @chan/recall now takes a <start line> parameter [AEJ]
+     * UNIMPLEMENTED_COMMAND allows hooks to disabled commands [AEJ]
+     * @power DEBUG switch removed. Due to its reason existed for 
+       original power system development. [RLB]
+  Functions:
+     * PrimaryDivision() Added to reference the primary division of a player
+       no matter where they are @sd'd to. [RLB]
+  Channels:
+     * Channels now ignore interaction rules by default, unless the
+       'interact' channel flag is set on them [AEJ]
+     * Default permissions for new channels are set by the channel_flags
+       configuration directive [AEJ]
+  Attributes:
+     * @SDIN, @OSDIN, @ASDIN added to be processed upon using @sd to 
+       switch into a division. [RLB]
+     * @SDOUT, @OSDOUT, @ASDOUT added to be processed upon exiting a division
+       when using @sd. [RLB]
+  Mail:
+     * @mailfilter attribute filters new mail into a folder if it returns
+       a folder name or number [AEJ]
+     * @mailforwardlist forwards mail to the list of dbrefs specified in
+       it, if you pass the MailForward lock on the target [AEJ]
+  Major changes:
+     * Chat.db now begins with +F for CobraMUSH databases; the reader
+       checks for +V to do a PennMUSH chat.db conversion [AEJ]
+     * New executable: 'console' will run the full CobraMUSH minus
+       networking, interacting with the user on stdin/stdout [AEJ]
+  Minor changes:
+     * All verbs, and generally anything that utilizes queue_base_attribute()
+       now acknowledges powinherit attribute flag.
+     * @program now saves Q registers through out the program.
+     * VTS removed from distribution. [RLB]
+     * LFAIL/OLFAIL/ALFAIL messages are activated whenever a @lock/leave
+       is checked and failed, not just when a leave command fails (so they
+       work for failed attempts to leave via exits and @tel) [AEJ]
+     * adjust_divisions() internal function, now only adjusts divisions
+       of a player's objects that were of their division that is being
+       changed. [RLB]
+     * Added conversion table for chat.db channel flags [AEJ]
+     * %u evaluates to the evaluated $command [AEJ]
+     * %i[0-9] give the same as itext(0)-itext(9) [AEJ]
+     * Rooms are considered connected in @dbck if they are connected to
+       the base room -or- to a room that is set FLOATING [AEJ]
+     * @invformat no longer receives the penny count as %0 and
+       its former %1 is now %0, the list of inventory items [AEJ]
+     * @conformat and @invformat now receive a |-separated list of names
+       as %1 [AEJ]
+     * Moved chat and mail function help from cobra_func.hlp to
+       cobra_chat.hlp and cobra_mail.hlp [AEJ]
+     * Removed vestiges of the previous attempt at a new chat system [AEJ]
+  Fixes:
+     * All fixes and additions from 0.71 maintenance release
+       patches.
+     * PennMUSH version numbers are no longer reported. [AEJ]
+     * cflags() now reports the N (nonames), T (notitles), and
+       C (nocemit) flags for channels [AEJ]
+     * If A can't hear from B (due to interactions), then A's puppet
+       would send a null line when it heard B; now the puppet doesn't
+       send at all [AEJ]
+     * Bug in reading dbs with no chatdb present [AEJ]
+     * playermem() and objectmem() now return #-1 NO MATCH consistently [AEJ]
+     * @list/list() now work with flags and powers [AEJ]
+     * @attribute NAME and @attribute @NAME now both show information about
+       attributes and @attribute/delete @NAME now also works [AEJ]
+     * Setting a password ending in a % is now possible [AEJ]
+     * @attribute did not check for valid attribute names before
+       adding them [AEJ]
+     * access.cnf checks now check for ipv6 addresses, too [AEJ]
+     * more consistent use of #defined telnet codes [AEJ]
+     * Renamed CONFGROUP to COBRA_CONFGROUP, just in case that interferes
+       with SSL the way that CONF did [AEJ]
+     * Fixed crash bug in pcreate() [AEJ]
+     * Fixed crash bug in setunion() [AEJ]
+     * Defunct internal function reverse_exits() existed to no use, for what 
+       reverse() served. [RLB]
+  Helpfiles:
+     * %q0-9 and %qa-z are now raliased to help r() [RLB]
+     * Help for grab() now references graball() [AEJ]
+     * Help for substitutions{2,3,4} aliased to %2, %3, and %4 [AEJ]
+     * Help for ulocal() clarified [AEJ]
+     * Help for strreplace() touched up [AEJ]
+
+& 0.71p3
+CobraMUSH Version 0.71p3
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (See 'changes entries' for a list of all versions.)
+
+  Version 0.71p3 is a bugfix/maintenance release of version 0.71.
+  It is the third bugfix/maintenance release in that series.
+
+  Bugfixes:
+     * Fedora Core systems caused crashes within @chown and
+       @powergroup. Reported by Apollo@New Republic. [RLB]
+     * CobraMUSH Configuration type 'CONF' conflicts with openssl,
+       CONF type.  Reported by lice. [RLB]
+     * pgpower(powergroup,max) was not returning max powers. [RLB]
+     * Game would crash upon a @pcreate in a newly created 
+       database before a @shutdown/reboot
+
+& 0.71p2
+CobraMUSH Version 0.71p2
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (See 'changes entries' for a list of all versions.)
+
+  Version 0.71p2 is a bugfix/maintenance release of version 0.71.
+  It is the second bugfix/maintenance release in that series.
+
+  Distribution changes:
+     * MANIFEST updated to point to restart.dst instead of restart
+     * 'make install' will generate game/restart, as will 'make update'
+
+  Helpfile Changes:
+     * Grammar and spelling corrections in 'help @powergroup2'
+     * Defunct know system existed in helpfiles, removed.
+     * @program helpfile clarified
+     * Example added to help @prompt
+
+  Bugfixes:
+     * adjust_divisions() could crash when adjusting powergroups
+     * @su remembers the exit path across a @shutdown/reboot
+     * Warnings are now issued if you @parent an object to something
+       whose owner is different and which is not set AUTH_PARENT
+
+& 0.71p1
+CobraMUSH Version 0.71p1
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (See 'changes entries' for a list of all versions.)
+
+  Version 0.71p1 is a bugfix/maintenance release of version 0.71.
+
+  Helpfiles:
+     * @cron helpfile being cleaned up for corrections to actual
+       behavior. [RLB]
+  Bugfixes:
+     * Multiple powergroups can now be added to an object, and
+       powergroups are added by way of an insertion sort so that
+       they are kept in alphanumeric order on each object.
+     * Cleaned up removal of powergroups from objects.
+     * Invalid cron specs could crash the game.  [RLB]
+
+& 0.71
+CobraMUSH Version 0.71
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (changes entries for list all versions.)
+
+  Minor Change:
+     * Version numbering changed, '-' will no longer preceed
+       maintenance patches, they will be preceeded by p.
+       This will allow easier migration of CobraMUSH to
+       debian packages.
+     * Began using vim folding on certain source files
+     * Jeremy Ritter's MUSHCron is now being used in replacement
+       of @hourly & @daily.  However hourly & daily,  still work 
+       as default global crontab entries.
+     * MUSH now auto-detects your connection for what type of
+       @prompt your connection can handle.  'WeirdPrompt's flag
+       no longer used as an affect of this.
+     * Global Attribute Locks are now possible as well as a catchall
+       attribute for any non-standard attribute.
+  Commands:
+     * @program now accepts the the format of @program obj=atr
+       to default to the executing object the attribute  is on.
+     * @program no longer handles a prompt, @prompt must be used
+       instead.
+     * @attribute/lock, @attribute/write, added todo apart of global
+       attribuet locking.
+  Configuration:
+     * Guest prefix moved to a config file option
+     * Option to use roman numeral numbering of guests 
+       given.
+     * Default Player Powergroup configuration option added.
+  Functions:
+     * program() no longer handles prompts, prompt() must be used.
+     * arabic2roman() converts arabic numbers to roman numerals.
+     * roman2arabic() converts roman numeral characters to
+       arabic characters.
+     * pghaspower(), and pgpowers() functions added.
+     * powergroups() with no arguments now gives the equivalent of
+       @powergroups/list
+  Helpfiles:
+     * documented @prompt, prompt(), pghaspower(), and pgpowers()
+     * modified @program, and powergroups() helpfiles for new syntaxes
+  Fixes:
+     * ooref overwrite bug in process_expressoin
+     * Deleting a powergroup that exists on a player would
+       crash the game.
+     * Newly created databases would create the master division
+       twice.
+     * Attributes weren't being cloned correctly.
+     * Deleting powers when a powergroup had no powers would crash
+       the game.
+
+& 0.70-3
+CobraMUSH Version 0.70-3
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (changes entries for list all versions.)
+
+  Release Date: Fri Nov 04 15:41:54 2005
+
+  Minor Changes:
+     * Patchlevel file in rootlevel CobraMUSH directory
+       changed to reflect CobraMUSH version and maintenance
+       release information.
+     * Created changes.sh for easy compilation of changes
+       index file for development purposes.
+  Helpfile Changes:
+     * WeirdPrompts found undocumented, documented in
+       'help flag list', 'help @program', and created
+       WeirdPrompts helpfile entry.
+     * AUnidle referenced from 'help attributes' now.
+  Fixes:
+     * free_object made unstatic to not allow certain third-party
+       packages work with CobraMUSH.
+
+& 0.70-2
+CobraMUSH Version 0.70-2
+  This is a list of changes in CobraMUSH that could be of 
+  interest to the user.
+  (changes entries for list all versions.)
+
+  Release Date: Tue Oct 25 20:42:05 2005
+
+  Fixes:
+    * Clean up on left over #ifdefs
+    * Helpfile grammatical, clarifications, and general helpfile
+      updates
+    * Chat Token Alias wasn't defaulty defined to '=', which
+      broke make update.
+    * Reverted VTS back to a usable stage.
+
+& 0.70-1
+CobraMUSH Version 0.70-1
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (changes entries for list of all versions.)
+
+  Release Date: Mon Oct 10 20:51:00 2005
+
+  Fixes:
+     * Master Division Couldn't keep powers or a level through
+       a @dbck.
+     * @shutdown was required localhost, fixed to require
+       Site power
+
+& 0.70
+CobraMUSH Version 0.7
+  This is a list of changes in CobraMUSH that could be of 
+  interest to the user.
+  (changes entries for list of all versions.)
+
+  Release Date: Sun Oct 09 17:24:10 2005
+
+  Major Changes:
+       * Power System rewritten to allow for database
+         dumpable type powers.
+       * PowerLevels removed
+       * PowerGroups Introduced
+  Minor Changes:
+       * @AtrLock Rewrite - Read & Write locks
+       * Seperate database now used for flags
+       * @command restrict permissions based on lock
+         boolexps*
+       * Added lock_command config parameter to config
+         loading.
+       * Fixed cobramush to properely handle win32 
+         support
+       * Know System Removed from source
+       * look <exit> is now recursively searched upwards
+         through the parent tree to find an exit to 
+        look at just like going through an exit is.
+       * Added some extra @dbck'ing checks. 
+       * When destroying a division, all sub-divisions
+         now get passed up the divtree while everything
+        else in the division itself gets thrown to 
+        No division.
+       * Sub-Divisions on a division object now listed
+         under 'Sub-Divisions' in an examine or brief
+        of the object instead of combining with
+        Contents.
+       * Hacked Thorvald Natvig's VTS from '98 in using
+         the VTS #define to enable or disable it.
+  Attribute Changes:
+       * Added INVFORMAT attribute to specify a custom
+         inventory report.
+  Command Changes:
+       * @empower now sets multiple powers at a time
+       * @powergroup and @power command introduced as part of 
+         the new db power system
+       * @su commands introduced
+  Function Changes:
+       * ldivisions() function added to list all divisions
+         within an object.
+       * Power Related Functions Added: powergroups() and  
+         haspowergroup() added.
+  Fixes:
+       * Global Ufuns weren't default the Originating
+         Object Reference Right.
+& 0.67
+CobraMUSH Version 0.67
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (changes entries for list of all versions.)
+
+  Functions:
+      * Idle_Average(<player>) Reports players Average
+        Idle time for their current session.
+  Attributes:
+      * LASTACTIVITY - Keeps track of players last session
+        activity
+  Fixes:
+      * lsearch eplayer, erooms, ethings, was bringing up
+        every object in the database.  Fixed to only bring
+        up corresponding types. [AEJ]
+
+& 0.66b
+CobraMUSH Version 0.66 
+  This is a list of changes in CobraMUSH that could be of
+  interest to the user.
+  (changes entries for list of all versions.)
+
+  Minor Changes:
+      * Creation & Implementation of SWMP
+        Sql Web<->MUSH Protocol
+      * Sql_Mroom config addition
+      * TwinCheck Optimizations
+      * Inherit Behavior changed
+      * Modified: block kept on non players objects now.
+        Shows what the last modification on an object was.
+       Only owner or Director can see this.
+  Commands:
+      * Customizeable huh message code
+      * @ZCLONE - Copy a whole zone with the touch
+                  of a button.
+  Functions:
+      * Idle_Average(<player>) Reports players Average
+        Idle time for their current session.
+  Attributes:
+      * LASTACTIVITY - Keeps track of players last session
+        activity
+  Fixes:
+      * Fixed Inheritable Controls bug
+      * Flag Setting fixes 
+      * Help Updates
+      * @function updates now update function flags
+  RP Systems:
+      * Must have a valid TrueRace set before you can go IC now
+      * Combat RPLog System added. 
+      * Paralyzed/Blind flags added
+  PennMUSH Incorporations: (view with changes)
+      * pennincorp.1 
+
+& pennincorp.1
+      * Ported everything from penn patch 1.7.7p39 & p40
+        except:
+           - buy command and assosciated parts
+          - simplified @version code.
+          - @ps/all label patch
+          - INFO Command and PennMUSH stuff
+      * @assert command
+      * @break/@assert default nosuccess action list
+      * Penn Related Fixes upto 1.8.0p6 
+
+& 0.65b
+CobraMUSH Version 0.65b
+ This is a list of changes in CobraMUSH that could be
+ of interest to the user.
+    (changes entries for list of all versions.)
+ Minor Changes:
+   * mortal WHO now shows Empire Abbreviation for players.
+   * Servercode is now also aimed at supporting a RP environment.
+   * New Queue Signal System inspired from Rhost Thaw & Freeze System.
+   * Helpfile updates
+   * Compile Warning Optimization 
+ Flags:
+   * MUTE flag added, settable on a player or room. On player MUTES
+     them individually, on a room it mutes everyone in the room.
+ Powers:
+   * RPTEL power now exists, which is required to move someone
+     in RPMODE.
+ Functions:
+   * new wait(), trigger(), and signal() functions
+   * version() reported pennmush version info when
+     it should show cobra specific version info.
+ Fixes: 
+   * Guest description wasn't being set when created, now it is.
+   * powerlevel setting bug fixed
+   * program prompting finally fixed.
+      - Fixes reported from pennmush changes files -
+   * @command/delete of a non-existing command is crash worthy.
+   * Crash bug in is_objid crashes with no string argument.
+   * string sub parser buffer overflow bug fixed.
+   * pennies overflow integer fixed.
+   * replace_string2 overflow fixed
+& 0.6b
+CobraMUSH Version 0.6b
+ This is a list of changes in CobraMUSH that could be 
+ of interest to the user.
+    (changes entries for list of all versions.)
+ PennMUSH Update:
+   * Updated to base penn version 1.7.7-p32
+ Minor Changes:
+   * pow_inherit attribute flag now supports @CONFORMAT
+     and @EXITFORMAT code. (help pow_inherit added)
+   * PrivCheck improvements
+   * Know System Improvements
+ Flags:
+   * AUTH_PARENT flag added.  Special authorization for parents
+     in use with twinchecks to override twincheck attribute
+     queue behavior off the parent.
+   * RPMODE Flag has been incorporated as a central function
+     in CobraMUSH. Players now need RPCHAT power to chat while
+     in RPMODE, & RPEMIT power to emit while in RPMODE. And
+     paging is restricted unles one of the parties is level 24
+     or greater.
+ Commands:
+   * Hidden players no longer appear connected when paged by
+     someone that can't see them.
+   * @ATRCHOWN is now restricted to God if in secondary queue
+     or owner in primary.
+ Functions:
+   * Global functions can now be made to be executed ulocal style
+     via throwing ulocal in the restriction field of @function
+ Locks:
+   * DIVISION boolexp in locks had the '+' key added to lock
+     to a division and & all its children.
+ Fixes:
+   * @sweep showed objects that had commands & were set halt
+     or no_command as listening for commands.. Fixed
+   * @search me required search:lte. Fixed, you can now
+     search yourself.
+   * Security Hole that allowed people forcing another object
+     with a power they don't have to perform 'X' task, has been
+     resolved. As well as $-commands set by someone on someone
+     else to perform 'X' task that they can't do has been resolved.
+        New config options exist:
+               twinchecks  <bool> (enable this type of check or not)
+               powerless   <dbref> (a powerless object at level: 1(1))
+   * @DEBUGFORWARDLIST wouldn't forward to someone if they
+     were higher prived than them, even if attribute set by
+     a guy that was prived as such.  Now checks old check 
+     in combination with attribute ownership.
+   * ok_tag_attribute was breaking with new gcc
+   * div_t struct was changed to div_tab to allow for stdlib.h
+     include
+   * PrivWho didn't work correctly.  Works again.
+& 0.5b
+& kv0.0.5b
+CobraMUSH Version 0.5b
+  This is a list of changes in CobraMUSH that could be
+  of interest to the user.
+    (changes entries for list of all versions.)
+  Minor Changes:
+    * New auto-guest system implemented.  'help guests'
+    * Incorporated Division, Level, PowerLevel, & Power checks to the 
+      lock boolexp parser.
+    * Added optional Know System. 'help know system'
+    * God level now bypasses command restrictions
+    * Documented full list of powers under 'help powers'
+    * Documented @program
+    * Re-defined CMD_T's bit locations.
+    * Removed wizmotd.txt from MANIFEST
+    * Pass_Locks power changed to enable passing locks of all types
+      except Use-Lock.
+    * Changed Version Scheme.
+  Flags:
+    * Level 28 with privileged power can now set themselves Dark.
+  Powers:
+    * Broken Announce up into 3 different powers. Previously Announce
+      had 3 bits, but was only a self-checking power so it was pointless
+      to be of that magnitude, so it was split up as should be.
+      - Announce
+      - EAnnounce
+      - DAnnounce
+  Functions:
+    * Added kname(), check_know() as part of the know system
+    * name() behavior changes when ran on a object or room that
+      has the 'ICFUNC's flag
+    * Added break() to escape iter() loops.
+    * Added loctree() to feed a complete string tree list of an object's location.
+  Fixes:
+    * Minimal DB creation didn't set master division object's Home & Location
+      correctly.  Causing a crash when a subdivision was made.
+    * Channel Admin channel setting didn't work correctly. Fixed.
+
+& kv0.0.4b 
+CobraMUSH Version 0.0.4b
+  This is a list of changes in CobraMUSH that could be 
+  of interest to the user.
+    (changes entries for list of all versions.)
+
+  Minor Changes:
+    * Mods to how powers info is accessed for future plans of DB dumpabe
+      powers. 
+    * Added @AUNIDLE
+    * Added pow_inherit attribute flag.
+    * Re-Implemented @hourly
+    * Re-Implemented Pass_Locks power. Passes Locktypes: Basic, Enter, Page
+      Tport, Speech, Parent, Leave, Give, Mail, Follow, Forward_lock,
+      and Dropto.
+    * Added a divisions dbck check
+  Standard PennMUSH Incorporation:
+    * Added Can_NsPemit power
+  Fixes:
+    * Restricting Commands by flags was broken.  Rewrote the code. 
+    * Fixed a possible problem in @parent.
+    * If no 3rd argument was supplied to powerlevel() the game would crash.
+      Third argument defaults to 0 now if not supplied.
+    * @channel's switches got disabled somehow...  re-enabled them. 
+    * Sometimes master division object's would get set to themselves
+      sending the game in loops.  Fixed origin of error & added
+      auto-detection of this bug & fix.
+    * @mail/stat, dstat, & fstat wasn't working properely with no argument 
+      supplied.  Works now. 
+    * Privilege power didn't allow setting of privilege flags.  Fixed Problem.
+
+& kv0.0.3b
+CobraMUSH Version 0.0.3b
+This is a list of changes in CobraMUSH that could be 
+of interest to the user.
+ (changes entries for list of all versions.)
+
+  Major Changes:
+       * Ported kickass code to 1.7.7p16 from 1.7.5p6 [AEJ]
+       * Patched to 1.7.7p26 [RLB]
+       * Completed Wiz/Roy compatability.
+           - Wiz/Roy now marker flags to designated powerlevels
+       * DB Conversion improved.
+           - Powers now converted to new powers
+           - Wiz/Roy converted over properely
+           - Master Division auto created & all except unregistered flagged players assigned
+           - All Wiz, Roy, Guest, Unregistered, & Normal player levels set accordingly.
+  Minor Changes:
+       * Completed Wiz/Roy compataibility.
+           - Wiz/Roy re-implemented as marker flags 
+           - Wiz Powerlevel & Level are both 29(default)
+           - Roy Powerlevel is 25 & Level is 28(default)
+       * DB Creation modified to create a division format database.
+       * @showlog taken out for redesign
+       * The following taken out temporarily due to drastic
+         upgrade in base penn version.
+           - AutoCreated Guests
+           - @hourly
+           - Pass_Locks Power
+& kv0.0.2a
+CobraMUSH Version 0.0.2a
+This is a list of changes in CobraMUSH that could be
+of interest to the user.  
+
+(changes entries for list of all versions.)
+  Minor Changes:
+  * DB Byte code reworked to allow the max-num of powerz to be upped on the fly
+  * Much of the division code rewritten to be easier to understand & more efficient
+               -Commands-
+  * @division broken up into @powerlevel @empower @level & @division is now just
+    used for setting & creating divisions.
+  * Platform base name changed to CobraMUSH
+ Fixes:
+  * command power checking fixed.. Previously it checked AND powers, changed it to be
+    OR powers as was intended.
+
+& kv0.0.1a
+KickAss Version 0.0.1a
+Major changes:
+  * Division system - Replaced Old Power System with DivPower System
+                   - Use dpowers.cnf for powerlevel definitions
+  * Replaced Old WizRoy Priv System with Div/Level/Power Priv System
+Commands:
+  * @division main interface to division system
+  * @showlog implemented to view logs in MUSH
+  * Many commands usability has changed with div/powerz system
+  * @program Added
+  * @Hourly Added [AEJ]
+Functions:
+  * Added division functions divscope(), level(), powerlevel(), division(),
+    hasdivpower() updiv(), downdiv(), indiv(), powover()
+  * Added cansee() function used for checking if a player can examine another
+    player
+  * haspower() now aliased to hasdivpower() to maintain compatibility
+  * baseconv() added [AEJ]
+Minor changes:
+  * Guests redid: - Guest Level replaces Guest Power
+                 - Guests AutoCreated at login now 
+  * Unregistered level replaces Unregistered Flag
+  * Division Object Type added
+  * Forcing of your own objects not logged
+  * Unkillable power ripped out along with kill command
+  * WizMOTD Removed along with @wizwall @rwall & friends
+  * Added AriSpace Option [AEJ]
+  * '%z' Ansi substitution
+See 'help 1.7.5p4' for penn based off of.
+
+
+
+
+
+
+
+& Entries
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ 0.5b                      0.65b                     0.66b                    
+ 0.67                      0.6b                      0.70                     
+ 0.70-1                    0.70-2                    0.70-3                   
+ 0.71                      0.71p1                    0.71p2                   
+ 0.71p3                    0.72                      help                     
+ kv0.0.1a                  kv0.0.2a                  kv0.0.3b                 
+ kv0.0.4b                  kv0.0.5b                  pennincorp.1             
+
+& &Entries
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
diff --git a/game/txt/compose.sh.SH b/game/txt/compose.sh.SH
new file mode 100644 (file)
index 0000000..31c8453
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+case $CONFIG in
+'')
+       if test -f config.sh; then TOP=.;
+       elif test -f ../config.sh; then TOP=..;
+       elif test -f ../../config.sh; then TOP=../..;
+       elif test -f ../../../config.sh; then TOP=../../..;
+       elif test -f ../../../../config.sh; then TOP=../../../..;
+       else
+               echo "Can't find config.sh."; exit 1
+       fi
+       . $TOP/config.sh
+       ;;
+esac
+: This forces SH files to create target in same directory as SH file.
+: This is so that make depend always knows where to find SH derivatives.
+case "$0" in
+*/*) cd `expr X$0 : 'X\(.*\)/'` ;;
+esac
+echo "Extracting compose.sh (with variable substitutions)"
+: This section of the file will have variable substitutions done on it.
+: Move anything that needs config subs from !NO!SUBS! section to !GROK!THIS!.
+: Protect any dollar signs and backticks that you do not want interpreted
+: by putting a backslash in front.  You may delete these comments.
+$spitshell >compose.sh <<!GROK!THIS!
+#!/bin/sh
+#
+# compose.sh: a shell script for putting together help.txt, etc.
+#
+# Usage: compose.sh <directory>
+# Example: compose.sh help
+#
+# This script calls index-files.pl
+# 
+# By Alan Schwartz (Javelin/Paul)
+#
+
+# These come from Configure
+perl=${perl-none}
+test=$test
+cat=$cat
+rm=$rm
+echo=$echo
+
+!GROK!THIS!
+
+: In the following dollars and backticks do not need the extra backslash.
+$spitshell >>compose.sh <<'!NO!SUBS!'
+# This process can eat CPU, so uncomment if you want to be nice
+#/etc/renice +4 $$
+
+# What subdirectories should we be processing?
+dir=$1
+if $test ! -d $dir; then
+  $echo "Usage: compose.sh <directory>"
+  exit 0
+fi
+
+index_args=$2
+
+# Ok, let's do 'em:
+  cd $dir
+
+  # Remove the old index
+  $rm -f index.$dir
+
+  # Build a new index, and tack it on.
+  $echo Building index for $dir...
+  if test -f $perl; then
+    $cat *.$dir | tee ../$dir.txt | $perl ../index-files.pl $index_args > index.$dir
+    $cat index.$dir >> ../$dir.txt
+  else
+    $cat *.$dir > ../$dir.txt
+  fi
+  cd ..
+
+$echo Done.
+$echo Remember to use @readcache if the mush is currently running.
+!NO!SUBS!
+chmod 755 compose.sh
+$eunicefix compose.sh
diff --git a/game/txt/connect.txt b/game/txt/connect.txt
new file mode 100644 (file)
index 0000000..56a840c
--- /dev/null
@@ -0,0 +1,12 @@
+<This is where you announce that they've connected to your MUSH>
+<It's a good idea to include the version/patchlevel of MUSH you're running>
+<It's a good idea to include an email address for questions about the MUSH>
+----------------------- CobraMUSH-v0.72 -------------------------------------
+Use create <name> <password> to create a character.
+Use connect <name> <password> to connect to your existing character.
+Use 'ch <name> <pass>' to connect hidden, and cd to connect DARK (admin)
+Use QUIT to logout.
+Use the WHO command to find out who is online currently.
+-----------------------------------------------------------------------------
+Yell at your local god to personalize this file!
+
diff --git a/game/txt/down.txt b/game/txt/down.txt
new file mode 100644 (file)
index 0000000..4ccf008
--- /dev/null
@@ -0,0 +1,6 @@
+***************************************************************************
+There has been an emergency which necessitated the disabling of non-wizard
+logins.  Please be patient, and you will be re-allowed to login as soon
+as the emergency has been controlled.  Thank you.
+***************************************************************************
+
diff --git a/game/txt/evt/.gitify-empty b/game/txt/evt/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/txt/evt/index.evt b/game/txt/evt/index.evt
new file mode 100644 (file)
index 0000000..f98f7b7
--- /dev/null
@@ -0,0 +1,7 @@
+
+& Entries
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+& &Entries
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
diff --git a/game/txt/evt/pennmush.evt b/game/txt/evt/pennmush.evt
new file mode 100644 (file)
index 0000000..3dca389
--- /dev/null
@@ -0,0 +1,16 @@
+& help
+===========================================================================
+                           CobraMUSH Events
+===========================================================================
+  
+No topics written yet.
+  
+   
+   
+  
+  
+  
+===========================================================================
+
diff --git a/game/txt/full.txt b/game/txt/full.txt
new file mode 100644 (file)
index 0000000..4d0c40b
--- /dev/null
@@ -0,0 +1,2 @@
+The maximum number of players this game can support are currently
+logged in. Please try back in a few minutes.
diff --git a/game/txt/guest.txt b/game/txt/guest.txt
new file mode 100644 (file)
index 0000000..0921dbd
--- /dev/null
@@ -0,0 +1,3 @@
+Yell at your local God to personalize this file, if GUEST_TEXTFILE
+is defined and it's being shown to Guests who connect!
+
diff --git a/game/txt/hlp/.gitify-empty b/game/txt/hlp/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/txt/hlp/cobra_attr.hlp b/game/txt/hlp/cobra_attr.hlp
new file mode 100644 (file)
index 0000000..3558c62
--- /dev/null
@@ -0,0 +1,95 @@
+& ATTRIBUTE TREES
+& ATTR TREES
+Attributes can be arranged in a hierarchical tree; these are called
+"attribute trees", and a conceptually similar to the way that
+files and directories/folders are organized on computer filesystems.
+Attribute trees can be used to reduce spam when examining and to
+provide organized control over permissions for related attributes.
+
+Attribute trees use the backtick (`) character to separate their
+components (much as filesystems use / or \). For example, the
+following attribute name would be a couple levels down in its tree:
+
+       CHAR`SKILLS`PHYSICAL
+
+Attribute names may not start or end with the backtick, and may not
+contain two backticks in a row.
+
+All attributes are either branch attributes or leaf attributes.
+A branch attribute is an attribute that has other branches or leaves
+beneath it; a leaf attribute is one that does not. Any attribute may
+act as a branch. If you try to create an unsupported leaf, branch
+attributes will be created as needed to support it.
+
+See help attribute trees2 for more information and examples.
+
+& ATTRIBUTE TREES2
+& ATTR TREES2
+Attribute trees provide two immediate benefits. First, they reduce
+spam when examining objects. The usual * and ? wildcards for attributes
+do not match the ` character; the new ** wildcard does. Some
+examples of using examine:
+   examine obj              displays top-level attributes (plus object header)
+   examine obj/*            displays top-level attributes
+   examine obj/BRANCH`      displays only attributes immediately under BRANCH
+   examine obj/BRANCH`*     displays only attributes immediately under BRANCH
+   examine obj/BRANCH`**    displays entire tree under BRANCH
+   examine obj/**           displays all attributes of object
+
+The same principles apply to lattr(). @decompile obj is a special case,
+and displays all attributes.
+
+Branch attributes will be displayed with a ` in the attribute flags
+on examine. 
+
+See help attribute trees3 for more information and examples.
+
+& ATTRIBUTE TREES3
+& ATTR TREES3
+The second benefit of attributes trees is convenient access control.
+Attribute flags that restrict attribute access or execution
+(no_inherit, no_command, mortal_dark, wizard) propagate down
+attribute trees, so if a branch is set mortal_dark, mortals can
+not read any of its leaves or subbranches either.
+
+Attribute flags that grant access (e.g. visual) do NOT propagate down
+trees.
+
+These properties make attribute trees ideal for data attributes:
+  &DATA bank = Data for each depositor is stored here, by dbref
+  @set bank/DATA = no_command
+  &DATA`#30 bank = $2000 savings:$1000 loan @ 5%
+  ...
+
+They're also handy for things like character attributes:
+  @attribute/access CHAR = wizard mortal_dark no_clone no_inherit
+  &CHAR #30 = Character data
+  &CHAR`SKILLS #30 = coding:3 documentation:1 obfuscation:5
+  ...
+
+See help attribute trees4 for information about @parent and attribute trees.
+& ATTRIBUTE TREES4
+& ATTR TREES4
+Attribute trees interact with @parent in several ways.
+
+As usual, children inherit attributes from their parent unless the
+child has its own overriding attribute. However, children that wish
+to override a leaf attribute must also have their own (overriding)
+copy of all branches leading to that leaf. This means that when you do:
+
+  &BRANCH parent = a branch
+  &BRANCH`LEAF parent = a leaf
+  &BRANCH`LEAF child = a new leaf
+
+In this case, a new BRANCH attribute will be created on the child,
+so '-[get(child/BRANCH)]-' will return '--'. This may not be what
+you actually want.
+
+If a branch on the parent is set no_inherit, it will not be inherited,
+regardless of any other flags that may be present. If a branch is
+inherited, the child object can not loosen any access restrictions to
+inherited attributes that are set by the parent (although it may loosen
+access restrictions to its own attributes on the same branch). The child
+object may impose stricter restrictions, however, and these may prevent
+access to inherited parent data.
+
diff --git a/game/txt/hlp/cobra_chat.hlp b/game/txt/hlp/cobra_chat.hlp
new file mode 100644 (file)
index 0000000..d24e492
--- /dev/null
@@ -0,0 +1,305 @@
+& CHAT
+& comsys
+  CHAT SYSTEM
+
+  The MUSH has a built-in chat system with many different channels.
+  These channels vary from MUSH to MUSH; ask at your local site or
+  use @channel/list to see which ones are available.
+
+  You can talk to many people on the MUSH via the chat system, without
+  needing to be in the same room as them. Use the "@channel" command
+  to join, leave, or check who is on a channel, and use the "@chat"
+  or "+" command to communicate.
+
+  If you examine yourself, you will see a list of channels that you are
+  currently listening to. Some channels are restricted to wizards or
+  administrators only. See the following help topics for details:
+    @chat, @channel, @cemit, @clock, cwho()
+
+& +
+& @chat
+  @chat <channel> = <message>
+  +<channel> <message>
+
+  This tells everyone on <channel> your <message>. You can prepend
+  <message> with ':' or ';' to pose instead of talk. This command can
+  also be formatted:  +<channel> <message>
+  You do not need to type the complete name of the channel, only as
+  many letters as needed to make it distinct from another channel
+  that you're also on.
+
+  Note: if you use the '+' form of this command, and you do not
+  use the name of a known channel, your command will be processed
+  as normal, preventing user-defined commands like "+last" from
+  being clobbered by the chat system.
+
+  See also: chat
+& @cemit
+  @cemit[/noisy][/noeval] <channel>=<message>
+
+  This command allows <message> to be directly broadcasted to the
+  players on <channel>. No channel-prefix is prepended unless the
+  /noisy switch is given. If the /noeval switch is given, the <message>
+  is not evaluated. This command is intended for use in writing
+  extended chat systems.
+
+  See also: chat
+& @channel
+  @channel/list [<channel-prefix>]
+  @channel/what [<channel-prefix>]
+  @channel/on <channel>[=<player>]
+  @channel/off <channel>[=<player>]
+
+  The basic form of this command allows you to see the available
+  channels, and join or leave a channel. You do not need to type the
+  complete name of the channel, only as many letters as needed to make
+  it distinct from other channels.
+
+  Wizards may add and remove other players from channels by providing
+  a player name as a second argument.
+
+  Channels may be restricted in who can join them and/or speak on
+  them. @channel/list will show you the channel's name, number of users,
+  number of message since last restart, access information, and your
+  status. See "help channel-list" for an explanation of how to read
+  the listing.
+
+  @channel/what will show you the channel's name, access information,
+  and a description of the channel's purpose.
+
+  More commands are provided in "help @channel2".  See also: chat
+& @channel2
+  @channel/who <channel>
+  @channel/hide <channel> = <yes|no>
+  @channel/title <channel> = <string>
+
+  The @channel/who command shows you who is currently on a channel,
+  if you are permitted to see it.
+
+  Some channels allow their users to hide from the @channel/who list.
+  If you're on such a channel and are permitted to hide, you can
+  use @channel/hide <channel>=yes to hide yourself, and
+  @channel/hide <channel>=no to reappear.
+
+  @channel/title lets you set a title to appear before your name
+  when you speak on the channel. If you leave the channel, your
+  title is cleared; use @channel/gag instead (see help @channel3).
+
+  See "help @channel3" for more.
+& @channel3
+  @channel/mute <channel> = <yes|no>
+  @channel/gag <channel> = <yes|no>
+  @channel/recall <channel> [ = <lines>[,<start line>] ]
+
+  Some channels broadcast messages when players connect or disconnect from
+  the MUSH. If you don't want to hear those messages, use @channel/mute
+  <channel>=yes. To resume hearing the messages, use @channel/mute
+  <channel>=no or @channel/unmute <channel>. Leave out <channel>
+  to mute or unmute all channels.
+
+  If you want to remain on a channel but not receive any messages
+  on the channel, use @channel/gag <channel>=yes. To resume hearing,
+  use @channel/gag <channel>=no (or @channel/ungag <channel>). When
+  you disconnect, the channel will be automatically ungagged for you.
+  Leave out <channel> to gag or ungag all channels.  If the channel does
+  not have the "open" priv, you can not speak on it while you are gagged.
+
+  @channel/recall shows you the most recent messages on the channel;
+  the number of messages depends on how the channel is configured, but
+  can be limited by specifying <lines> to show and a <start line> to start
+  display from. You must be on a channel to recall from it.
+
+  See "help @channel4" for more.
+& @channel4
+  @channel/add <channel> [= <priv>]
+  @channel/delete <channel>
+  @channel/desc <channel> = <desc>
+  @channel/rename <channel> = <new name>
+
+  @channel/add creates a new channel. On some MUSHes, any player
+  can create a new channel, though there will be a cost associated
+  with creation (see @config chat). Possible <priv> specifications:
+  * "player" - players may use the channel
+  * "object" - objects may use the channel
+  * "admin" - only royalty/wizards/chat_privs may use the channel
+  * "wizard" - only wizards may use the channel
+  * "quiet" - channel will not show connection messages
+  * "open" - you may speak even if you aren't listening to the channel
+  * "hide_ok" - you may hide from the channel who list.
+  * "notitles" - chantitles are not displayed in channel messages.
+  * "nonames" - player names are not displayed in channel messages.
+  * "nocemit" - @cemit is prohibited on the channel.
+  * "interact" - Interaction rules (defined in local.c) are applied to
+    the channel
+  Specifications may be combined, space-separated. Default is determined
+  by the 'channel_flags' @config option, or 'player' if not set.
+
+  @channel/delete removes a channel. You must own it or be Wizard.
+  @channel/desc sets the channel's description, shown on @channel/what.
+    Descriptions are limited to 256 characters.
+  @channel/rename is used to rename a channel.
+
+  See "help @channel5" for more. See also "help @clock".
+& @channel5
+  @channel/priv <channel> = <new priv level>
+  @channel/wipe <channel>
+  @channel/buffer <channel> = <lines>
+  @channel/decompile[/brief] <channel>
+  @channel/chown <channel> = <new owner>
+
+  The "priv" switch changes the channel's access privileges. Use !<priv>
+    to reset a privilege.
+  The "wipe" switch clears a channel of players without deleting it.
+  The "buffer" switch sets the maximum number of full-length lines that
+  the channel will buffer for @chan/recall. Many more shorter lines may
+  actually be buffered. Setting it to 0 turns off buffering.
+
+  The "decompile" and "chown" switches can only be used by Wizards.
+  @channel/decompile produces a decompile of matching channels. If the
+  /brief switch is included, players on the channel aren't listed.
+  @channel/chown allows a Wizard to change the owner of a channel.
+
+& channel-list
+Here's the legend for reading the @channel/list output:
+
+Channel Name               Num Users Num Msgs  Access Locks     Status  Buf
+Sample                             1        0 [DPOWQHo jsmvh*] [On  QH]   4
+                                               ||||||| ||||||   |   ||    |
+Channel is DISABLED----------------------------/|||||| ||||||   |   ||    |
+Channel allows PLAYERS--------------------------/||||| ||||||   |   ||    |
+Channel allows OBJECTS---------------------------/|||| ||||||   |   ||    |
+Channel is Wizard-only (W) or Admin-only (A)------/||| ||||||   |   ||    |
+Channel is QUIET-----------------------------------/|| ||||||   |   ||    |
+Channel is HIDE_OK----------------------------------/| ||||||   |   ||    |
+Channel is OPEN (non-members can speak on it)--------/ ||||||   |   ||    |
+Channel has @clock/join set----------------------------||||||   |   ||    |
+Channel has @clock/speak set----------------------------/||||   |   ||    |
+Channel has @clock/mod set-------------------------------/|||   |   ||    |
+Channel has @clock/see set--------------------------------/||   |   ||    |
+Channel has @clock/hide set--------------------------------/|   |   ||    |
+Player is the owner of the channel--------------------------/   |   ||    |
+Player is currently on/off/gagging the channel------------------/   ||    |
+If on, player has the channel muted---------------------------------/|    |
+If on, player is hiding on the channel-------------------------------/    |
+Size of the channel buffer in full-length lines---------------------------/
+& @clock
+  @clock/join <channel> [= <key>]
+  @clock/speak <channel> [= <key>]
+  @clock/see <channel> [= <key>]
+  @clock/hide <channel> [= <key>]
+  @clock/mod <channel> [= <key>]
+
+  The @clock command modifies the a lock on a chat channel if the
+  extended chat system is in use. If no key is specified, the
+  lock is unlocked. Evaluation locks will not work with @clock. 
+  See help @clock2 for information on using indirect locks.
+
+  The "join" lock restricts who can join the channel.
+  The "speak" lock restricts who can speak to the channel.
+  The "see" lock restricts who can see the channel on @channel/list
+  The "hide" lock restricts @channel/hide if the channel is hide_ok.
+  The "mod" lock restricts who can modify the channel. If you pass the
+  mod lock on the channel, you can do anything short of deleting it.
+
+  When new channels are added, the mod lock is set to the creator/owner,
+  and all other locks are unlocked.
+
+  See help @clock2 for how to use indirect locks to lock a channel to
+  anything.
+
+& @clock2
+If user-defined locks are available, you can use indirect @clocks
+to lock a channel to a lock of any type (including evaluation locks)
+on a VISUAL object. This channel can only be joined by an UNFINDABLE player:
+
+  >@clock/join unfindchannel=@#10
+  >@lock/user:ChanJoinLock #10=isunfind/1
+  >&isunfind #10=[hasflag(%#,unfindable)]
+  >@set #10 = VISUAL
+
+@clock                  Corresponding default user: lock for object
+join                    ChanJoinLock
+speak                   ChanSpeakLock
+see                     ChanSeeLock
+hide                    ChanHideLock
+mod                     ChanModLock
+
+You can lock multiple channels to the same object by specfiying a
+specific indirect lock instead of the default one:
+
+  >@clock/join onechannel=@#10/onechanneljoin
+  >@clock/join anotherchannel=@#10/anotherchanneljoin
+  >@lock/user:onechanneljoin #10 = 1
+  >@lock/user:anotherchanneljoin #10 = isunfind/1
+& COWNER()
+  cowner(<channel>)
+
+  Returns the dbref of the owner of a channel.
+& CTITLE()
+  ctitle(<channel>,<object>)
+
+  Returns <objects> @chan/title on <channel>. You must either
+  be able to examine the object, or it must visible be on a channel
+  which you are allowed to join.
+& CWHO()
+  cwho(<channel>)
+  This returns a list of connected dbrefs who are on <channel>. 
+  When used by mortals, hidden/DARK players do not appear on the list.
+& CEMIT()
+  cemit(<channel>, <message>[, <noisy>])
+
+  Sends a message to all players listening to the given chat channel.
+  See help @cemit for details.
+
+  If the third argument is a true value, the channel name will be
+  prepended to the message, behaving like @cemit/noisy.
+& CFLAGS()
+  cflags(<channel>)
+  cflags(<channel>,<object>)
+
+  With one argument, cflags() returns a list of flags set on the
+  given channel, represented as a string of characters. See
+  'help channel-list' for the list of flags (they appear in the
+  "Access" column). You must be able to see the channel to use this
+  function.
+
+  With two arguments, cflags() returns a list of flags for that
+  object on that channel, currently a string consisting of zero
+  or more of "G" (gagging), "Q" (muted), and "H" (hidden).
+  You must be able to see that channel and to examine the object
+  to use this function. If the object is not on the channel, an
+  error is returned.
+& CHANNELS()
+  channels([<delimiter>])
+  channels(<object>)
+  channels(<object>[,<delimiter>])
+
+  With no arguments, channels() returns the list of all channel names
+  which are visible to the player. With two arguments, returns the list
+  of channel names to which the object is listening, delimited by
+  <delimiter>.
+
+  With one argument, the behavior is ambiguous. If the argument
+  matches an object, returns the list of names to which the object
+  is listening, space-delimited. If not, it's treated as a no-argument
+  case with a delimiter.
+
+  If you don't have permission to examine the object, you only see 
+  those channels to which the object belong for which you have 
+  permission to join (or are joined to).
+& CLOCK()
+  clock(<channel>[/<locktype>][, <new lock>])
+
+  With one argument, returns the value of a lock on a channel, if you
+  own the channel or are See_All.  If no locktype is given, "JOIN" 
+  is assumed.
+  With two arguments, sets the lock if you would be able to do so via
+  @clock.
+
+  See also: @clock
+& Channel functions
+  Channel functions work with the channel system.
+
+  cemit()       cflags()      channels()    clock()      cowner()
+  ctitle()      cwho()
diff --git a/game/txt/hlp/cobra_cmd.hlp b/game/txt/hlp/cobra_cmd.hlp
new file mode 100644 (file)
index 0000000..02de52f
--- /dev/null
@@ -0,0 +1,4058 @@
+& COMMANDS
+Help is available for the following MUSH commands:
+  ahelp          anews          brief          DOING          drop
+  examine        enter          events         follow         get            
+  give           go             index          leave          
+  LOGOUT         look           move           news           page           
+  pose           QUIT           read           rules          say
+  score          take           teach          think          
+  unfollow       use            whisper        WHO            with
+  "              :              ;              +              ]
+  In addition to these, there are several types of '@' commands. @-commands 
+  are usually commands which have permanent effects on the MUSH (such as
+  creating a new object). Here are the help topics on @-commands:
+  @-ATTRIBUTES   @-BUILDING     @-GENERAL      @-Director
+& @-ATTRIBUTES
+These '@' commands set standard message/action sets on objects. Each comes
+in 3 versions: @<whatever>, @o<whatever>, and @a<whatever>. Only the
+@<whatever> version is listed below, but help is available for each:
+  @describe      @drop          @efail         @enter
+  @failure       @follow        @give          @idescribe     @leave
+  @lfail         @move          @payment       @receive       @success
+  @tport         @ufail         @unfollow      @use           @zenter        
+  @zleave
+
+These '@' command set other standard attributes on objects that don't
+follow the pattern above:
+
+  @aahear        @aclone        @aconnect      @adisconnect   @amhear
+  @away          @charges       @cost          @conformat     @descformat
+  @ealias        @exitformat    @filter        @forwardlist   @haven         
+  @idescformat   @idle          @infilter      @inprefix      @lalias        
+  @listen        @nameformat    @oxenter       @oxleave       @oxmove        
+  @oxtport       @prefix        @runout        @sex           @startup       
+
+See also: ATTRIBUTES, NON-STANDARD ATTRIBUTES
+& @-BUILDING
+These '@' commands are building-related (they create or modify objects):
+  @atrlock       @atrchown      @chown         @chzone        @clone         
+  @cpattr        @create        @destroy       @dig           @elock         
+  @eunlock       @firstexit     @link          @lock          @mvattr        
+  @name          @nuke          @open          @parent        @recycle       
+  @set           @undestroy     @ulock         @unlink        @unlock        
+  @uunlock       @wipe
+  
+& @-GENERAL
+These '@' commands are general utility and programming commands:
+
+  @@             @alias         @break         @channel       @chat
+  @cemit         @command       @config        @decompile     @doing
+  @dolist        @drain         @edit          @emit          @entrances
+  @find          @force         @function      @gedit         @grep
+  @halt          @lemit         @listmotd      @mail          @map
+  @notify        @nsemit        @nslemit       @nsoemit       @nspemit
+  @nsremit       @nszemit       @oemit         @password      @pemit
+  @ps            @remit         @restart       @scan          @search
+  @select        @stats         @sweep         @switch        @teleport
+  @trigger       @verb          @version       @wait          @whereis
+  @zemit
+
+& @-Director
+These '@' commands are only usable by Directors or privileged players:
+  @allhalt       @allquota      @boot          @chownall      @chzoneall
+  @comment       @dbck          @disable       @dump          @empower
+  @enable       @flag          @hide          @hook          @kick          
+  @log           @motd          @newpassword   @pcreate       @poll          
+  @poor          @power         @powergroup    @purge         @quota 
+  @readcache     @rejectmotd    @shutdown      @sitelock      @sql           
+  @squota        @su           @uptime        @wall          cd             
+  ch             cv
+& ]
+  The "]" command-prefix instructs the MUSH that the rest of the command
+  input that follows should not be evaluated. Here's an example:
+
+  > say [add(1,1)]
+  You say, "2"
+
+  > say \[add(1,1)\]
+  You say, "[add(1,1)]"
+
+  > ]say [add(1,1)]
+  You say, "[add(1,1)]"
+
+  > ]"[add(1,1)]
+  You say, "[add(1,1)]"
+  
+  This can be used to pass unevaluated MUSHcode to softcoded commands
+  without having to escape every special character, or to help objects
+  set attributes to contain unevaluated code.
+
+& @@
+  The "@@" command is a special kind of command; it signals the start
+  of a comment. The comment lasts until a semi-colon is found, just
+  like other MUSH programming statements terminate with a semi-colon.
+  It cannot be put into the middle of a statement, like
+  @va me = $testing:@emit Test. @@ Just a test @@; @vb me=Testing.
+  That will result in the object emitting "Test. @@ Just a test. @@"
+  The correct usage is to make the comment a statement by itself:
+  @va me = $testing:@emit Test.; @@ Just a test @@; @vb me=Testing.
+  It is not necessary to use a closing '@@', but doing so makes the
+  comment stand out much more clearly. A space between the first
+  '@@' and the word following it is necessary.
+& @aahear
+  @aahear <object> = <action list>
+
+  An aahear on an object is activated whenever the object's listen pattern 
+  is matched by anything done/said by either anything else in the room OR 
+  the object itself. In contrast, an ahear on an object cannot be activated 
+  by anything the object itself does.
+
+See also: @listen, @ahear, @amhear, LISTENING
+& @aclone
+  @aclone <object> = <action list>
+
+  Sets the actions to be taken whenever <object> is @cloned. This command 
+  can be useful for notifying the owner of a vending machine or parent 
+  object when someone uses the machine.
+
+  Please note that there is no @oclone, and that @clone is always a
+  command, not an attribute.
+
+See also: @clone, @create, ACTION LISTS
+& @aconnect
+  @aconnect <object> = <action list>
+
+  Sets the actions to be taken by a player right after connecting to
+  the game.
+
+  Example: @aconnect me = :stretches luxuriously, as if waking from a nap.
+
+  Note that long and spammy @aconnect messages, whether in your room or
+  on a channel, are frequently found annoying by other players.
+
+  The player's location, and zone object/objects in the zone parent
+  room of the location, as well as objects in the master room, are
+  also checked for an @aconnect. If one is found, it will be executed
+  when a player connects in that location or zone (or, in the case of
+  the master room, anywhere).
+
+See also: @adisconnect, ACTION LISTS
+
+& @adescribe
+  @adescribe <object> = <action list>
+  
+  Sets the actions to be taken when  <object> is looked at. 
+  A common use of this command is:
+
+    @adesc me=think %N just looked at you.
+
+  which will inform you whenever someone looks at you. %N will be 
+  replaced by the name of the person who looked. While it is possible to
+  set a message to be broadcasted to everyone in the area when someone
+  looks at you, this is strongly discouraged, as many people find it 
+  annoying.
+
+See also: @describe, @aidescribe, look, SUBSTITUTION, ACTION LISTS
+& @adestroy
+  @adestroy <object> = <action list>
+
+  Sets the actions to be taken by the object when it is destroyed
+  (via @rec or @nuke). This can only be set by a Director.
+  If @adestroy doesn't work, try &adestroy. :)
+& @adisconnect
+  @adisconnect <object> = <action list>
+
+  Sets the actions to be taken by a player right after disconnecting
+  from the game.
+
+  Example: @adisconnect me = home
+
+  The player's location, and zone object/objects in the zone parent
+  room of the location, as well as objects in the master room, are
+  checked for an @adisconnect. If one is found, it will be executed
+  when a player disconnects in that location or zone (or, in the case of
+  the master room, anywhere).
+
+See also: @aconnect, ACTION LISTS
+& @adrop
+  @adrop <object> = <action list>
+
+  Sets the actions to be taken when <object> is dropped. 
+  If <object> is an exit, sets an action to be done after a player
+  has passed through the exit.
+
+See also: @odrop, @drop, drop, ACTION LISTS
+& @aefail
+  @aefail <object> = <action list>
+
+  Sets the actions to be taken by the object when someone fails to enter
+  it.
+
+See also: enter, FAILURE, ACTION LISTS
+& @aenter
+  @aenter <object> = <action list>
+
+  Sets the actions taken by the object whenever someone enters it.
+
+See also: @enter, @oenter, enter, ACTION LISTS
+& @aufail
+& @oufail
+& @ufail
+  @ufail <object> = <message>
+  @oufail <object> = <message>
+  @aufail <object> = <action>
+
+  Sets message shown to a player who fails to use an object via
+  the 'use' command (@ufail), message shown to others in the room
+  (@oufail), and action for the object to take (@aufail).
+
+  Note that these attributes are @ufail, NOT @ufailure, for
+  TinyMUSH compatibility.
+
+& @afailure
+  @afailure <object> = <action list>
+
+  Sets the actions to be taken on failure to pass the @lock on an
+  object. For players and things, this means failure to pick them up
+  with get/take. For exits, this means failure to go through an exit.
+  
+  May be abbreviated @afail. 
+
+See also: @fail, @ofail, get, EXITS, @lock, ACTION LISTS
+& @afollow
+  @afollow <object> = <action list>
+
+  Sets the actions to be taken after someone or something begins 
+  following the object. 
+
+See also: follow, unfollow, followers(), @follow, @ofollow, ACTION LISTS
+& @aunfollow
+  @aunfollow <object> = <action list>
+
+  Sets the actions to be taken after someone or something stops 
+  following the object. 
+
+See also: follow, unfollow, followers(), @unfollow, @ounfollow, ACTION LISTS
+& @ahear
+  @ahear <object> = <actions>
+
+  Sets the actions to be taken after the object's @listen is matched. 
+  Note that @ahear ignores any messages that the object itself creates, 
+  so it can only be triggered by other things. If you want the object to
+  be able to trigger itself, try using @amhear or @aahear.
+  
+See also: @aahear, @amhear, @listen, ACTION LISTS, LISTENING
+& @aleave
+  @aleave <object> = <action list>
+
+  Sets the actions to be taken whenever someone leaves the object.
+
+See also: leave, @leave, @oleave, ACTION LISTS
+& @alfail
+  @alfail <object> = <action list>
+
+  Sets the actions to be taken whenever someone tries to leave the 
+  object and fails (because the object is set NO_LEAVE or because
+  the person fails the @lock/leave on the object). 
+
+See also: leave, @lfail, @olfail, ACTION LISTS
+& @alias
+  @alias <player>=<alias>
+  @alias is a special attribute. When a player sets an @alias, he is
+  effectively giving himself a secondary name; he can be paged by his
+  @alias, and matched with *<alias>, and all other game functions which
+  look up player names will also accept the alias. The attribute is
+  visible to all players.
+  
+  Aliases cannot be longer than the limit allowed for player names, 
+  cannot contain spaces, and must be unique -- no other player may
+  have the same alias or name as any other player's alias or name.
+
+  @alias has no effect on non-players.
+& @allhalt
+  @allhalt
+
+  This command halts all objects in the game in an effort to free up 
+  the queue. Director-only command.
+  
+  This command is equivalent to "@halt/all".
+
+See also: QUEUE, @ps
+& @allquota
+  @allquota[/quiet] [<limit>]
+
+  This is a God level command that is only available if the quota
+  system is being used.  It displays the current max and owned objects
+  of every player (unless the /quiet switch is used) and resets their 
+  quota left to the new limit minus the current number owned if a limit 
+  is given.
+
+& @amhear
+  @amhear <object> = <action list>
+
+  Sets the actions to be taken whenever the @listen on the object is
+  matched by something that the object itself does. It will not react
+  if anyone else in the area does anything that matches the @listen
+  pattern. If you want other objects to be able to set off the action
+  list, try using @ahear or @aahear.
+
+See also: @listen, @ahear, @aahear, LISTENING, ACTION LISTS
+
+& @amove
+  @amove <object> = <action list>
+
+  This is the action to be taken whenever an object moves.
+
+See also: @move, @omove, @oxmove, ACTION LISTS, go
+
+& @apayment
+  @apayment <object> = <action list>
+
+  Sets the actions to be taken after a player gives an object some
+  amount of pennies. The amount paid is passed in as %0.
+
+See also: give, @pay, @opay, @cost, MONEY, ACTION LISTS
+
+& @atport
+  @atport <object>=<action list>
+
+  Sets the list of actions that <object> will perform when it is
+  teleported. These actions are done after <object> has arrived
+  in its new location.
+
+See also: @tel, ACTION LISTS
+
+& @atrchown
+  @atrchown <object>/<attribute> = <new_owner>.
+
+  Like @chown except it changes the control of an attribute from one person
+  to another. You may only @atrchown attributes that you can normally set,
+  and unless you're a Director, you can only @atrchown attributes to yourself.
+
+  Only God May use this command.
+
+See also: @atrlock, ATTRIBUTES, NON-STANDARD ATTRIBUTES
+
+& @atrlock
+  @atrlock[/read/write] <object>/<attribute> = <key>.
+
+  Locks an attribute's read or write lock to a specified 
+  'lock key'.
+
+  TODO: Detailed Explanations on Read & Write Locks.
+
+
+See also: @atrchown, ATTRIBUTES, NON-STANDARD ATTRIBUTES, LOCKING
+
+& @asuccess
+  @asuccess <object> = <action list>
+
+  Sets the actions to be taken when the @lock of an object is passed.
+  For things/players, this means picking them up. For exits, this means
+  going through them. 
+
+See also: @success, @osuccess, get, @lock, EXITS, ACTION LISTS
+& @attribute
+  @attribute <attrib>
+  @attribute/access[/retroactive] <attrib> = <flag list>
+  @attribute/delete <attrib>
+  @attribute/rename <attrib> = <new name>
+
+  @attribute is a Director-only command which modify's the MUSH's
+  table of standard attributes (use @list/attribs to list them).
+  A standard attribute is one that can be set with @<attrib> instead
+  of &<attrib>, and which, when set, has a predefined set of
+  initial permissions.
+
+  *** Unlike TinyMUSH, changes to the PennMUSH attribute table
+  *** are not saved across shutdowns. Use these commands from
+  *** God's @startup to insure they are run at each startup.
+
+  Used without switches, @attribute shows info about a standard attrib.
+
+  @attribute/access adds a new standard attribute into the table,
+  associating it with the given space-separated list of flags.
+  See 'help @set' for possible flags.
+  If the /retroactive switch is added, the flags are set on every copy
+  of the attribute that already exists in the database.
+
+  @attribute/delete removes a standard attribute from the table.
+  @attribute/rename renames a standard attribute. 
+
+& @ASDOUT
+  @asdout <object> = <action list>
+  
+   Sets the actions to be taken when a player succesfully exits a
+   division by use of the @sd command.
+
+  See also: @sd, @sdout, @sdin, ACTION LISTS
+
+& @ASDIN
+  @asdin <object> = <action list>
+  
+   Sets the actions to be taken when a player succesfully enters a
+   division by use of the @sd command.
+
+  See also: @sd, @sdin, @sdout, ACTION LISTS
+
+& @ause
+  @ause <object> = <action list>
+
+  Sets the actions to be taken when an object is successfully "used".
+  
+See also: use, @use, ACTION LISTS, @charges, @runout
+& @away
+  @away <player> = <message>
+
+  This message is sent to a player who tries to page you when you are
+  not connected, if it evaluates to something non-null.
+
+  Example: @away me=I'm not here, please send me @mail instead.
+
+& @boot
+  @boot <player>
+  @boot/port <descriptor number>
+  @boot/me
+
+  Disconnects the player from the game. 
+
+  The /port switch takes a descriptor number instead (the "Port" number
+  in WHO for Directors).
+
+  The /me switch boots any single descriptor for the same player which
+  has been idle for at least 1 minute. Players can use this command
+  to terminate hung connections.
+
+  Only admin and those with the "boot" power can @boot other players. 
+
+& @ASSERT
+& @BREAK
+  @break  <boolean>[=<command list>]
+  @assert <boolean>[=<command list>]
+
+  @break stops the execution of further commands in the current action
+  list if <boolean> is a true value. It doesn't affect new queue entries
+  made by previous commands in the action list. Very useful to people who
+  don't like @switch. If <command> is given, it is executed instead of
+  the rest of the commands in the current queue.
+
+  @assert does the inverse: stopping execution if <boolean> evaluse to false.
+
+  Examples:
+  > @va obj=$testme *:@pemit %#=Before break;@break %0;@pemit %#=After break
+  > testme 0
+  Before break
+  After break
+  > testme 1
+  Before break
+
+  > @force me={@switch 1=1, think Third; think First; @break 1; think Second}
+  First
+  Third
+  (The @switch is run, which queues 'think Third', think First is
+   run, displaying 'First', command execution is broken (so we never
+   think Second, and then the queued 'think Third' is run, displaying
+   Third. If you figured that out, you have a very good understanding
+   of the CobraMUSH queue. :)
+
+  See also: ACTION LISTS, QUEUE, BOOLEAN VALUES
+
+& @charges
+  @charges <object> = <integer>
+
+  Allows you to limit the number of times an object can be "used". 
+  The "charges" attribute will be decreased by one each time the
+  object's @ause is triggered, and once it reaches zero, the object 
+  cannot be used anymore.
+
+See also: use, @runout, @ause
+& @chown
+  @chown[/preserve] <object>=<player>
+
+  Changes the ownership of <object> to <player>. You can chown things,
+  rooms, or exits. To chown a thing, you have to be carrying it. 
+  If you do not own an object, you can only chown it if it is CHOWN_OK.
+  If you're not a Director, you can only @chown objects to yourself or
+  to a Zone Master whose zone-lock you pass.
+
+  Normally, @chown'ing an object clears privileged flags and powers,
+  and sets the object halt.  Directors may use @chown/preserve to chown
+  an object, preserving these privileges and not setting the object
+  halt. Doing this to an active object with queued commands is not
+  recommended, and may have strange and insecure effects.
+
+  Examples:
+    (for a room)    @chown here=me
+    (for an object) @chown box=Soundwave
+  
+  Players can't be @chowned; they always own themselves.
+
+See also: CHOWN_OK, Zone Masters
+& @chownall
+  @chownall[/preserve] <player> [= <target_player>]
+
+  Transfers ownership of all the objects that the player owns to 
+  <target_player>. If the target player is not included, then all 
+  the objects are chowned to the person executing the command. 
+  This is a Director-only command. The /preserve switch keeps privileged
+  flags and powers instead of clearing them, and doesn't halt them.
+
+See also: @chown
+& @chzone
+  @chzone <object>=<zone master> 
+  @chzone <object>=none
+
+  The first form of this command changes the ZONE of <object> to
+  <zone master>. This puts the object on that zone and may (if the
+  zone_control_zmp_only option is off) allow anyone who passes the
+  zone-lock of the zone master to make changes to the object. Any
+  kind of object can be @chzoned, and any kind of object can be used
+  as the zone master.
+
+  The second form of this command resets the zone of <object> to *NOTHING*.
+  Anyone can reset the zone of an object s/he owns.
+
+  If a player is @chzoned, anything s/he makes afterwards will start out
+  with that Zone, but none of the objects that s/he presently owns will
+  have their Zone changed. Players can @chzone themselves to a master if
+  they own it. Otherwise, only Directors can @chzone players.
+
+(continued in help @chzone2)
+& @chzone2
+
+  To see the Zone of an object, you can use either 'brief' or 'examine'
+  to examine it. The Zone is listed on the same line as the Owner of
+  the object.
+
+  Players can @chzone objects they own if they own the zone master or
+  if they pass its @lock/chzone.  Directors can @chzone objects to any
+  zone master as long as the object has a zone-lock.
+
+  Whenever an object besides a player is @chzoned to a zone master, the
+  Director, ROYALTY, and TRUST flags will be reset, as will all @power's
+  (for security purposes). For similar reasons, it is strongly recommended
+  that you do not @chzone admin- or Director-owned objects to any zone
+  that less privileged players have access to.
+
+See also: ZONES, @chzoneall
+& @chzoneall
+  @chzoneall <player>=<zone master>
+
+  Changes the zone of all objects owned by <player> to <zone master>. 
+  If <zone object> is "none", the zone is reset to NOTHING. Only Directors 
+  may use this command.
+  
+See also: @chzone, ZONES
+& @clone
+  @clone <object, room, or exit>[=<new name>]
+  @clone/preserve <object, room, or exit>[=<new name>]
+
+  For objects, creates an exact duplicate of it and puts it in the
+  current room. For exits, it creates an exact duplicate of that
+  exit, except the clone's source is the current room rather than
+  whatever the original exit's source was. For rooms, creates an
+  exact duplicate of the room, without contents or exits. The name
+  of the duplicate object can be provided; it defaults to the same
+  as the name of the original object.
+
+  The thing to be cloned must be controlled by the @cloning player.
+  The clone will be owned by the @cloning player.
+
+  If creation times are enabled, a clone will have a different creation
+  time than the object it was cloned from, but will have the same
+  modification time, to make tracking revisions of code easier.
+
+  A Director may use @clone/preserve, which has the effect of preserving
+  all the bits, powers, and warnings of the original object.
+
+  To clone a room and all its exits, use code like:
+    @tel [setq(0,%l)][clone(here)]; @dol lexits(%q0)=@clone ##
+
+See also: @create
+& @command
+  @command <command>
+  @command/<switch> <command>
+
+  @command provides information about and controls the availability 
+  of other commands.
+
+  With no switches, @command shows all sorts of interesting information
+  about how a command is parsed.
+
+  Switches include:
+  /disable   : Disable the <command>
+  /off       : Synonym for /disable
+  /enable    : Enable the <command>
+  /on        : Synonym for /enable
+  /quiet     : Don't make noisy output when doing one of the above
+  /add       : Creates a useless command. @hook/override it.
+  /delete    : Deletes a command added by @command/add
+
+  See HELP RESTRICT for more.
+(continued in help @command2)  
+& @command2
+  @command/add and @command/delete are powerful tools in that they let
+  you write $-commands which may or may not parse their arguments, and
+  have the precedence of a built-in command, being checked before
+  $-commands on objects. Only God may use these switches.
+
+  Additional switches are used in @command that only apply when used
+  with @command/add. These switches are:
+
+  /noeval    : The command does not evaluate arguments passed to it.
+  /eqsplit   : The parser parses leftside and rightside
+  /lsargs    : Comma-separated arguments on the left side are parsed.
+  /rsargs    : In conjunction with eqsplit, the right-side arguments,
+               comma-separated, are parsed individually and passed
+               to the $-command in @hook/override.
+
+  Any command added without the /noeval switch is provided with the
+  /noeval switch itself, so if you @command/add foo, then foo's arguments
+  are parsed by default, but you can call foo/noeval. Note: the $-command
+  needs to make allowances for the /noeval switch in it's matching.
+
+(examples in help @command3)
+& @command3
+  Examples:
+
+  > @create Dining Machine
+  > &eat dining=$eat *:@remit %L=%N takes a bite of %0.
+  > @command/add/noeval eat
+  > @hook/override eat=dining machine,eat
+  > eat meat loaf
+  Walker takes a bite of meat loaf.
+  > eat randword(apple tomato pear)
+  Walker takes a bite of randword(apple tomato pear)
+
+  > &drink dining=$^drink(/noeval)? (.*)$:@remit %L=%N drinks %1.
+  > @set dining/drink=regexp
+  > @command/add drink
+  > @hook/override drink=dining machine,drink
+  > drink reverse(tea)
+  Walker drinks aet.
+  > drink/noeval reverse(tea)
+  Walker drinks reverse(tea).
+
+& @comment
+  @comment <object> = <comment>
+
+  This is a Director-only command which sets a COMMENT attribute on
+  <object>. The comment can only be seen by other Directors and royalty.
+& @config
+  @config/functions
+  @config/commands
+  @config/attribs
+  @config/flags
+  @config/list[/lowercase] [<option|option-type>]
+  @config/set option=value
+  
+  This command lists the MUSH configuration parameters, indicating what
+  special things are enabled, and the cost of certain commands.
+  Switches include:
+
+  /functions  --   Lists all functions.
+  /commands   --   Lists all commands.
+  /flags      --   Lists all flags.
+  /attribs    --   Lists all standard attributes.
+  /list       --   Lists the value of a particular <option> or <option-type>
+                   if given one; lists the option-types if not.
+  /set        --   Director only, changes parameters from the mush. See
+                   help @config parameters for available ones.
+& @conformat
+  @conformat <object> [=<format>]
+
+  Replaces the usual "Contents:" or "Carrying:" format when an object
+  is looked at, by a player-specified contents format. This is evaluated
+  as if it were a description or other similar message on the room.
+  The objects that the looker would normally be able to see is passed
+  as a dbref list in %0; all contents can be acquired through 'lcon(me)'.
+  A |-delimited list of all the unparsed object names is passed in
+  %1 (so iter(%1,%i0,|,%r) produces the standard list)
+  One could change the format to 'Contents: Object1 Object2 Object3'
+  through '@conformat here = Contents: [iter(%0,name(##))]',
+  for example. More complex things are, obviously, possible.
+  See also: @exitformat, @nameformat, @descformat
+& @descformat
+  @descformat <object> [=<text>]
+
+  Replaces the usual description of the object when it is looked at
+  by player-specified text. The evaluated DESCRIBE attribute is
+  passed as %0; the unevaluated DESCRIBE can be acquired through
+  v(DESCRIBE).
+
+  This is useful for things like room parents that enforce a consistent
+  "look" for each room's @desc.
+
+  See also: @exitformat, @nameformat, @conformat, @idescformat
+& @idescformat
+  @idescformat <object> [=<text>]
+
+  Replaces the usual description of the object when it is looked at from
+  within by player-specified text. The evaluated IDESCRIBE attribute
+  is passed as %0; the unevaluated IDESCRIBE can be acquired through
+  v(IDESCRIBE).
+
+  This is useful for things like object parents that enforce a consistent
+  "look" for each object's @idesc.
+
+  See also: @exitformat, @nameformat, @conformat, @descformat
+& @nameaccent
+  @nameaccent <object> [=<accent template>]
+
+  When this attribute holds an accent template that has the same
+  length as the object's name, it is used to change the object's name
+  in some situations (How it shows up in speech, look, and a few other
+  commands). This allows for accented names without having to use the 
+  accented characters directly in a name, which can make it harder for
+  people to type.
+
+  If a container has both a @nameaccent and a @nameformat, the
+  @nameformat is used.
+
+  See also: accent(), @nameformat
+& @nameformat
+  @nameformat <object> [=<format>]
+
+  Customizes the usual display of the object's name to people who
+  are inside it and 'look'. It is evaluated as if it were a description
+  or similar message on the room. The room's dbref is passed as %0.
+
+  @nameformat is not used when people outside the object look at it.
+
+  Example: Show the room's zone after its name.
+  @nameformat here = [name(%0)] [if(isdbref(zone(%0)),<[name(zone(%0))]>)]
+
+  See also: @exitformat, @conformat, @descformat
+& @cost
+  @cost <object> = <amount> 
+
+  This sets the number of pennies that need to be given to an object to
+  trigger its @pay/@opay/@apay attributes. It is evaluated, so you
+  may use functions as well as simple integers; the amount given by
+  the player is passed as %0.
+
+  Example:
+    @cost exit-machine=10
+    @apay exit-machine=@open %N-exit 
+    @pay exit-machine=Your exit has been created.  
+    give exit-machine=10
+    > Your exit has been created.
+    (The exit will also have been opened by the machine.)
+
+    @cost charity=%0
+    @pay charity=Thanks for your donation of %0.
+
+See also: give, MONEY, @pay, money()
+& @cpattr
+& @mvattr
+  @cpattr[/noflagcopy] <obj>/<attr> = <obj1>[/<attr1>] [,<obj2>/<attr2>,...]
+  @mvattr[/noflagcopy] <obj>/<attr> = <obj1>[/<attr1>] [,<obj2>/<attr2>,...]
+  
+  @cpattr copies an attribute from one object to another, or several 
+  others. @mvattr does the same thing, except it also removes the original
+  attribute from the original object. Attribute flags ARE copied as well,
+  unless you use the /noflagcopy switch, which you probably want if you're
+  copying from a non-standard attribute to a standard one.
+
+  For example:
+    @cpattr box/test = box/test1, cube/random, tribble/describe
+
+  would check the object "box" for an attribute named TEST and then
+  copy it to the attributes TEST1 on "box", RANDOM on the object named
+  "cube", and DESCRIBE on the object named "tribble".
+
+  If you leave out the destination attribute, the attribute is copied
+  to one of the same name on the new object. For example:
+    @cpattr box/test=cube
+  would copy the TEST attribute from "box" to TEST on "cube".
+
+See also: ATTRIBUTES, NON-STANDARD ATTRIBUTES
+& @create
+  @create <name> [=<cost>]
+
+  Creates a thing with the specified name. Creating an object costs
+  a certain amount of MUSH money, which usually defaults to 10 pennies.
+  You can specify a higher cost if you wish, but not a lower one.
+  This cost is refunded if you @destroy/@recycle the object.
+
+  Once you have created an object, you can use it as a PUPPET, to store
+  USER-DEFINED COMMANDS, or just as a prop. Some MUSHes choose to limit 
+  the number of objects that players can create by setting a QUOTA.
+  See also: give, @quota, MONEY
+& @dbck
+  @dbck
+  This is a Director only command.  It forces the database to perform a
+  series of internal cleanup and consistency checks that normally run 
+  approximately every 10 minutes:
+
+  1. For every object, make sure its location, home, next, contents,
+     parent, and zone fields are valid objects.
+  2. Check for disconnected rooms that aren't set FLOATING
+  3. For every exit, player, or thing, make sure there is exactly one
+     way to reach it from a room by following the contents fields of
+     non-exits, the next fields of non-rooms, and the exits fields of
+     rooms.
+  4. For every thing or player, make sure that it is in the contents
+     list of its location. Make sure every exit is in the exits list
+     of its location.
+  5. Check that objects being used as zones have a @lock/zone.
+
+  @dbck no longer performs an @purge. The results of @dbck are written
+  to the game's error log, and not reported to the Director.
+
+& @decompile
+  @decompile[</switch>] <object>[/<attribute-pattern>]
+  @decompile/tf <object>/<attrib>
+
+  This command produces a list of the commands that you would have to
+  enter in order to recreate <object>. Useful for either copying objects
+  from one MUSH to another, or for making logs of important objects to
+  protect against an accidental @nuke or a crash.
+
+  You can either @decompile an entire object, or just certain parts of it.
+  To @decompile just a few attributes, for example, you could type:
+
+      @decompile <object>/<attribute name>
+
+  for each attribute. You can also use wildcards in <attribute name> to
+  @decompile a list of similarly-named attributes.
+
+(continued in help @decompile2)
+& @decompile2
+  @decompile takes five switches: /db, /flags, /attribs, /tf, /skipdefault
+  Multiple switches may be usefully combined (other than /tf).
+
+  @decompile/db
+    This command makes @decompile use the object's DBREF # instead of its
+    name, which is useful for editing code off-MUSH.
+  @decompile/flags
+    Only the code to @create the object and set flags/powers/locks is printed.
+  @decompile/attribs
+    Only the code to set the object's attributes is printed.
+  @decompile/skipdefault
+    Don't output commands to set attribute flags if those flags are the
+    defaults for that attribute on that MUSH.
+
+(continued in help @decompile3)
+& @decompile3
+
+  @decompile/tf <object>/<attribute>
+
+  The /tf switch is useful only for users of the popular "TinyFugue" 
+  client program (available from ftp.tcp.com in the directory
+  /pub/muds/Clients/tinyfugue). If you do have this program, this
+  switch is invaluable for editing code online, because it will grab the 
+  code to set that attribute and put it into your buffer.
+
+  To use @dec/tf, first type this command into TinyFugue:
+
+    /def -ag -mglob -p100 -t"FugueEdit > *" fe = /grab %-2
+
+  (you can also put this into your .tfrc so it will automatically
+  be entered every time you start TinyFugue (tf).) This command works
+  just like the 'FugueEdit' object originally created by van@TinyTIM.
+
+  You can use a string other than "FugueEdit > " by setting your
+  TFPREFIX attribute. This is probably a good idea.
+See also: CLIENTS, ATTRIBUTES, WILDCARDS, MUSHCODE
+& @describe
+  @describe <object> [=<description>]
+
+  This command sets the description of the object, which will be seen 
+  whenever something looks at the object with the command 'look <object>'.
+  Every object should have a description, even if just a short one 
+  describing its purpose. If the [=<description>] is left out, the desc.
+  on the object will be reset to nothing. When you look at something that
+  has no desc., you are told, "You see nothing special."
+
+  @describe can be abbreviated as @desc
+
+See also: look, @adescribe
+& @destroy
+& @recycle
+& @nuke
+& @undestroy
+& @unrecycle
+  @destroy[/override] <object>  OR   @recycle[/override] <object>
+  @undestroy <object>           OR   @unrecycle <object>
+  @nuke <object>
+
+  NOTE: @destroy and @recycle are the same command - some MUSHes
+  choose to use @recycle instead of @destroy to avoid the typo of
+  '@dest obj' for '@desc obj'. Others use @destroy. @nuke is an alias
+  for @dest/override.
+
+  Recycles <object> and returns the player's investment (the cost of
+  @create). You should always @destroy objects you no longer need;
+  this keeps the database down to a manageable size.  When an object
+  is @destroyed, it is set GOING, its queue is cleared and any
+  semaphores are drained, and its ADESTROY attribute is immediately
+  triggered. 
+
+(continued in help @destroy2)
+& @destroy2
+
+  GOING objects are actually removed from the db approximately every
+  10-20 minutes. Until then, you may use the @undestroy/@unrecycle
+  command to abort the destruction sequence. If you @undestroy an
+  object, its STARTUP attribute is triggered. If you @destroy a
+  GOING object, it is purged immediately.
+
+  Directors may @nuke players. If the DESTROY_POSSESSION config option
+  is set (see @config), destroying a player destroys all non-SAFE
+  things the player owns, and @chowns SAFE things to God. If not,
+  destroying a player @chowns all their objects to God.
+
+See also: SAFE, GOING, DESTROY_OK  
+& @dig
+  @dig[/teleport] <room name>[=<exit name>;<alias>*,<exit name>;<alias>*]
+  
+  This command is used to create a new room, possibly with exits linking
+  it to the room you are standing in. This command normally costs 10 pennies,
+  although some MUSHes may have different amounts or may restrict this 
+  command to those with the builder @power.
+  
+  @dig/teleport will automatically @tel the digger to the new room.
+
+  If you use the long form of this command, an exit to the new room
+  from the one you are standing in and an exit back from the new room
+  will both be automatically @open'ed and @link'ed. You may have as 
+  many or as few exit aliases for an exit as you like, separated in the
+  command by semicolons. The exit leading to the new room is separated
+  from the exit leading back by a single comma.
+
+(continued in help @dig2)
+& @dig2
+  Examples: 
+  @dig Kitchen
+    This command will create a new room named 'Kitchen'. You will be informed
+    what the dbref # of this room is.
+
+  @dig Kitchen = Kitchen <N>;n;north;kitchen;k
+    This will create the room as above and also open an exit leading
+    to it named "Kitchen <N>;n;north;kitchen;k". It will appear in the 
+    list of Obvious exits as just "Kitchen <N>", but you will be able to
+    go through it by typing any of the following: n, k, north, kitchen
+    It will NOT create an exit coming back from the Kitchen room.
+
+  @dig Kitchen = Kitchen <N>;n;north;kitchen;k,Out <S>;s;south;out;o
+    This will do just the same as the above, except it will also create
+    an exit named "Out <S>;s;south;out;o" coming back from the kitchen
+    to whatever room you are currently in.
+    
+See also: @open, @link, EXITS, @create
+& @doing
+  @doing <message>
+  @doing/header <message>
+
+  This command sets a short message that shows up in the WHO/DOING listing
+  next to your name. @doing/header <message> works exactly like @poll and
+  can only be used by Directors or those with the poll power.
+
+See also: @poll, WHO, DOING
+& @dolist
+  @dolist[/notify][/delimit] [<delim>] <list> = <action>
+  
+  @dolist executes the <action> for each element in <list>. If <list> is a
+  function, it will be evaluated to obtain the necessary list to use. It
+  may be any space-separated list of strings, which can be object numbers,
+  attributes, or arbitrary words. If the /delimit switch is given, the first
+  character of the list is used as the separator, instead of space.
+  
+  <action> is a command or list of commands enclosed in braces { }
+  and is performed once for every item in <list>. The special symbol "##"
+  is replaced by the corresponding item from <list>.  The special symbol
+  "#@" is replaced by the position of that item in the list.
+  If the /notify switch is used, a "@notify me" is queued after all the
+  list commands. This is useful for object synchronization with semaphores.
+   
+  Example: @dolist [lcon(here)] = "[name(##)]
+    would cause you to say the name of all objects in the room.
+& @drain
+  @drain[/any][/all] <object>[/<attribute>][=<number>]
+  
+  This command discards commands waiting on a semaphore without
+  executing them.
+
+  If the /any switch is given, then all semaphores associated with
+  <object> are @drained.  Otherwise, the only the specified semaphore
+  attribute (or SEMAPHORE if no attribute is specified) is @drained.
+
+  If the /all switch is given, then all queue entries associated
+  with the selected semaphore(s) are discarded, and the semaphore
+  attribute(s) are cleared.  Otherwise, only the indicated <number>
+  of queue entries are discarded.  If no <number> is given, then
+  the /all switch is assumed.
+
+  You may not specify both the /any switch and a specific attribute.
+  Similarly, you may not specify both the /all switch and a number.
+
+  See also the help for: SEMAPHORES, @notify, @wait
+& @drop
+  @drop <object> [=<message>]. <object> can be a thing, player, exit,
+  or room, specified as <name> or #<number> or 'me' or 'here'. Sets 
+  the drop message for <object>. The message is displayed when a 
+  player drops <object>. Without  a message argument, it clears the 
+  message. 
+
+  On an exit, this message is sent to a player after they pass through
+  the exit.
+
+  See also: drop, @odrop, @adrop.
+& @dump
+  @dump [/paranoid] [check interval]
+  This is a Director only command that saves a copy of the current memory
+  database out to a save file.  This preempts the normal regular dumping
+  that the mud performs on its own.
+  If the /paranoid switch is used, the game performs additional consistency
+  checking which corrects possible data corruption. If a check interval
+  is specified, the game writes confirmation of the dump to the checkpoint
+  log file every <interval> objects. If no interval is specified, it is
+  taken to be the size of the database, divided by 5.
+  This switch should ONLY be used if a normal @dump is not being done
+  correctly. Paranoid dumps should generally only be done by Directors with 
+  access to the account on which the MUSH is running, since others will
+  not have access to the checkpoint log file.
+& @ealias
+  @ealias <object> = <enter alias>
+
+  This allows a player to type the enter alias instead of "enter <object>"
+  If you have a chair, you could "@ealias chair = sit down" and then just
+  type "sit down" instead of "enter chair" - using the object name is
+  not necessary. Note that the enter alias is checked after normal exits.
+  Like an exit, it may have a semi-colon separated list of words,
+  i.e. sit down;sit;sit on chair
+& @edit
+& @gedit
+  @edit[/first][/check] <object>/<pattern> = <search>,<replace> 
+  @edit[/check] <object>/<pattern> = $,<string to append>
+  @edit[/check] <object>/<pattern> = ^,<string to prepend>
+
+  This is useful when you don't want to have to retype those obnoxiously 
+  long descriptions just to make one little change.  Instead, search and 
+  replace via @edit.  
+  
+  <pattern> is a pattern, optionally containing wildcards, for the attribute 
+  names you wish to edit. Only attributes already defined on <object> may be 
+  edited. <search> and <replace> are two strings. It's also possible to use 
+  "$" and "^" to signal appending and prepending text, respectively.
+  
+  If the text contains commas, percent signs, or similar special characters,
+  it usually must be enclosed in curly braces.
+
+  If the /first switch is used, only the first occurrence of <search>
+  is replaced.
+
+  If the /check switch is used, the editing is not actually done, but the
+  results are shown to you (with changes highlighted) as if a normal @edit
+  was performed.
+
+  See also ATTRIBUTES, edit()
+& @efail
+  @efail <object> = <message>
+  This is the message shown to the player who fails to enter the object.
+& @elock
+  @elock <object> = <key>
+  Enter-locks an object, restricting who is allowed to enter it. Special
+  lock types are supported (see "help @lock" for details). Only objects
+  which are ENTER_OK may be entered, regardless of the key.
+  Rooms don't use ENTER_OK or @elock; they use @lock/teleport
+  instead. Only people who pass the room's teleport lock, are Directors
+  or royalty, or control the room, will be allowed to @teleport into the
+  room. (Note that this is different from NO_TEL, which prevents people
+  from teleporting out of a room).  The teleport lock is evaluated even
+  if the room is JUMP_OK - in other words, if you are trying to teleport
+  into a room you don't control, the room must be JUMP_OK, and you must
+  pass the teleport lock.
+
+  Note that the enter lock of an object or room being used as a Zone
+  Master Object determines control of that zone. Please note that if
+  you're using a room as a ZMO (i.e. as a zone master room), only the
+  controllers of that zone will be able to teleport into that room
+  (which is a good thing for security).
+& @emit
+& \  
+  @emit[/<switch>] [<message>]
+  \<message>
+
+  This sends <message> to every person in the current room. However, no
+  identifier marking it as being sent by you is shown.  For example,
+  @emit foo would show 'foo' to every object in the room. 
+
+  The /room switch makes this command equivalent to "@lemit". 
+  The /silent switch in combination with /room suppresses the
+   @lemit confirmation. It has no effect without /room.
+  The /noeval switch prevents the MUSH from evaluating the message.
+  The /spoof switch causes nospoof notifications to show the enactor's
+    dbref instead of the executor's dbref, and requires control over
+    the enactor or the Can_nspemit power.
+
+  @emit can be abbreviated "\"
+
+  See also @pemit, @remit, @oemit, @lemit, NOSPOOF and SPOOFING.
+& @enable
+& @disable
+  @enable <parameter>
+  @disable <parameter>
+  
+  These are Director commands that allow certain parameters of the game to
+  be changed at runtime. The values of these parameters are listed by the
+  "@config" command. Common parameters and their effects are as follows:
+  
+  logins       --  When logins are disabled, only Directors and royalty may
+                   log into the game. Mortals attempting to log in will be
+                   given the down text, as well as the @rejectmotd.
+  guests       --  When guests are disabled, players can not log in as
+                   guest characters.
+  pueblo       --  Turns on HTML output for the Pueblo client.
+  log_commands --  When this is enabled, all commands are logged.
+  log_huhs     --  When this is enabled, all commands that produce a "Huh?"
+                   are logged.
+  log_forces   --  When this is enabled, @forces done by Directors are logged.
+  log_walls    --  When this is enabled, @walls are logged.
+
+  Actually, any boolean (Yes/No) runtime option can be enabled or
+  disabled with these commands, as a shortcut for @config/set.
+
+  @enable <option> is the same thing as @config/set <option>=yes
+  @disable <option> is the same thing as @config/set <option>=no
+& @zenter
+& @ozenter
+& @azenter
+  @zenter <ZMO> = <message>
+  @ozenter <ZMO> = <message>
+  @azenter <ZMO> = <action>
+  
+  These attributes set the message shown to a player who enters a room in
+  a new zone (@zenter), the message shown to others in the room in the
+  new zone when the player enters (@ozenter), and the action triggered
+  by the entry (@azenter).
+
+  Entry into a new zone is said to occur when a player goes from 
+  a room not in the zone to a room in the zone. "Room" in this context
+  means the player's absolute room (outermost container), so entering
+  and leaving unzoned objects within a zoned room doesn't trigger these.
+
+  Zone entry is assumed to occur before room entry, so these are
+  triggered before the room's @[oa]enter.
+
+See also: @zleave, @ozleave, @azleave, ZONES
+& @zleave
+& @ozleave
+& @azleave
+  @zleave <ZMO> = <message>
+  @ozleave <ZMO> = <message>
+  @azleave <ZMO> = <action>
+  
+  These attributes set the message shown to a player who leaves a room in
+  a zone (@zleave), the message shown to others in the room in the
+  old zone when the player leaves (@ozleave), and the action triggered
+  by the leave-taking (@azleave).
+
+  Leaving a zone is said to occur when a player goes from a room in
+  the zone to a room not in the zone. "Room" in this context means the
+  player's absolute room (outermost container), so entering and leaving
+  unzoned objects within a zoned room doesn't trigger these.
+
+  Zone leaving is assumed to occur after room leaving, so these are
+  triggered after the room's @[oa]leave.
+
+See also: @zenter, @ozenter, @azenter, ZONES
+& @enter
+  @enter <object> = <message>
+
+  This sets the message that is displayed to anyone entering the 
+  object. Works just as @success on an exit does.
+
+  Example:
+    @enter Chair = You sit down on the comfy chair.
+    enter chair
+    > You sit down on the comfy chair.
+
+See also: @oenter, @oxenter, enter
+& @entrances
+  @entrances[/<switch>] <object> [=<begin>,<end>]
+  
+  This command will show you all exits linked to the object you use the
+  command on, as well as where the exit originates.  This command is
+  computationally expensive and costs the same as @find. You can limit
+  the range of the dbrefs searched by specifying <begin> and <end>.
+  
+  It takes four switches:
+  
+  /exits       show only exits linked to <object>
+  /things      show only things which have their homes in <object>
+  /players     show only players who have their homes in <object>
+  /rooms       show only rooms which have a drop-to of <object>
+& @eunlock
+  @eunlock <object>
+
+  Enter-unlocks an object, in a fashion similar to @unlock. For anyone
+  to be able to enter an object, it must be both @eunlocked and ENTER_OK.
+  You can also simply type 
+    @lock/enter <object> 
+  to unlock an object.
+
+See also: @lock, locktypes, @elock
+& @exitformat
+  @exitformat <object> [=<format>].
+
+  Replaces the usual "Obvious Exits:" format when an object is looked
+  at, by a player-specified exits format. This is evaluated as if it
+  were a description or similar message on the room.  The objects that
+  the looker would normally be able to see is passed as a dbref list
+  in %0; all exits can be acquired through 'lexits(me)'.
+  One could change the format to 'Exits: Exit1 Exit2 Exit3' through
+  '@exitformat here = Exits: [iter(%0,name(##))]', for example.
+
+  See also: TRANSPARENT, @conformat, @nameformat, @descformat
+& @failure
+  @failure <object> [=<message>]
+
+  Sets the message shown to someone who fails to pass the object's
+  basic @lock. For a thing or player, this would be when someone tries
+  to pick the object up and fails. For an exit, this occurs when someone
+  tries to go through the exit and fails.
+
+  If [=<message>] is omitted, any currently set message will be reset.
+
+  Can be abbreviated @fail. It is recommended that you put an @fail message
+  on any locked object.
+
+See also: FAILURE, @ofailure, @afailure
+& @firstexit
+  @firstexit <name of exit>
+
+  Makes the named exit the first in the listing of Obvious Exits in a
+  room. You must control the room in order to use this command.
+
+See also: EXITS, @open, @link
+& @filter
+  @filter <object> = <pattern 1>, <pattern 2>, <pattern 3>, ...
+  This attribute is meant to be used in conjunction with the AUDIBLE
+  flag. The @filter of an object is a comma-separated list of wildcard
+  patterns (like @switch patterns). Any messages which match one of the 
+  patterns is suppressed and not propagated through the AUDIBLE object
+  with the @filter set. (Note: @filter on rooms has no effect!)
+  Please note that you should NOT use curly brackets {} in an @filter
+  pattern. If you want to filter a message containing a comma, you
+  need to put a \ before it. Also note that AUDIBLE exits will not
+  take effect unless the room they lead from is also set AUDIBLE.
+
+  See 'help @filter2' for more.
+& @filter2
+ If the @filter attribute is flagged REGEXP, the patterns are considered
+ regular expressions, not wildcards. The CASE attribute flag is also
+ respected. 
+
+Example: 
+  An exit leads from the room where Director is standing to another
+  room where the puppet "Wiztoy" is standing. The exit is set AUDIBLE.
+  
+    @prefix exit=From inside,
+    :tests.
+    > Director tests.
+    Wiztoy> From inside, Director tests.
+    @filter exit=* jumps.,* tests.
+    :jumps.
+    > Director jumps.
+    :tests.
+    > Director tests.
+  (Wiztoy doesn't hear anything, because it is filtered out.)
+    :tests again.
+    > Director tests again.
+    Wiztoy> From inside, Director tests again.
+
+See also: AUDIBLE, @infilter, LISTENING
+& @find
+  @find [<name>] [=<begin>,<end>]
+
+  Displays the name and number of every room, thing, or
+  player that you control whose name matches <name>. Because the
+  command is computationally expensive, this costs 100 pennies.
+  You can limit the range of the @find by specifying <begin> and
+  <end>, where these are the first and last dbrefs to examine.
+
+  Some MUSHes may choose to restrict @find only to Royalty. Even if 
+  not, it is recommended that you use @search instead, as it can be
+  limited more effectively.
+
+See also: @search
+& @follow
+  @follow <object> = <message>
+
+  Sets the message shown to someone after they begin following 
+  the object (using the 'follow' command). 
+
+See also: follow, unfollow, followers(), @ofollow, @afollow
+& @FORWARDLIST
+  @forwardlist <object> [=<list of dbrefs>]
+
+  When an object set AUDIBLE hears sound that passes its @filter, its
+  @forwardlist attribute is checked, and if present, the sound (Prefixed
+  by the object's @prefix attribute) is forwarded to all the dbrefs in the
+  @forwardlist, like puppets do to their owners.
+
+  The @forwardlist must be a space-seperated list of dbrefs. 
+  In order to forward to an object, you must either control it,
+  have the pemit_all power, or, if it has a @lock/forward set,  pass 
+  its @lock/forward.  (If you want to allow all objects you own to
+  forward to you, regardless of whether or not they control you, use
+  @lock/forward me=$me)
+
+See also: @filter, @prefix, AUDIBLE, PUPPET
+& @unfollow
+  @unfollow <object> = <message>
+
+  Sets the message shown to someone after they stop following 
+  the object (using the 'unfollow' command). 
+
+See also: follow, unfollow, followers(), @ounfollow, @aunfollow
+& @force
+  @force[/noeval] <object>=<command>
+
+  This forces the object to perform the command just as if it had
+  typed it in itself. Only Directors may @force other players. You
+  can @force any object which you own or control. Useful for
+  manipulating puppets. If <object> is not in the same room as you,
+  you can instead use the object's dbref number. 
+
+  @force can be shortened to @fo or @for, or abbreviated as:
+    <dbref #> <command>
+
+(continued in help @force2)
+& @force2
+
+  Normally, the command is parsed for function evaluation and 
+  substitutions twice - once when @force is run, once when
+  the forcee runs the command. @force/noeval will only
+  evaluate the command when the forcee runs it.
+
+Example:
+  @create Lackey
+  > Created: Object #103 
+  @fo Lackey = go east
+  > Lackey goes east.
+  > Lackey has left.
+  @fo #103 = page Cyclonus = Hi there!
+  > Lackey pages: Hi there!
+  #103 page Cyclonus = Whee
+  > Lackey pages: Whee
+
+See also: PUPPET, DBREF 
+& @flag
+  @flag <flag name>
+  @flag/list [<flag name pattern>]
+  @flag/add <flag name>=[<letter>], [<type(s)>], [<setperms>], [<unsetperms>]
+  @flag/delete <flag name>
+  @flag/alias <flag name>=<alias>
+  @flag/letter <flag name>[=<letter>]
+  @flag/restrict <flag name>=[<setperms>], [<unsetperms>]
+  @flag/type <flag name>=<type(s)>
+  @flag/enable <flag name>
+  @flag/disable <flagname>
+
+  This command manipulates the list of flags in the database.
+  When given a flag name as an argument, the command displays information
+  about the flag, including aliases and permissions. @flag/list
+  lists names of enabled flags, and may be given a wildcarded pattern
+  to restrict which names it will show.
+
+  All other switches to this command are restricted to God:
+    /disable disables a flag, making it invisible and unusable
+    /enable re-enables a disabled flag
+    /alias adds a new alias for an existing flag
+    /letter changes or removes a single-letter alias for an existing flag.
+    /restrict changes flag permissions (see help @flag2)
+    /type changes flag type(s) (see help @flag2)
+    /delete deletes a flag completely, removing it from all objects
+      in the database and the removing it permanently from the 
+      flag table. It requires the exact flag name or alias to be used.
+      Be very very careful with this. 
+
+  See also: help flags. See help @flag2 for information on @flag/add
+& @flag2
+  @flag/add is used to add a new flag with the given name. Arguments
+  other than the flag name are optional:
+
+  <letter> gives the flag's one-letter abbreviation, which must
+    not conflict with the one-letter abbreviation of another flag that
+    could be applied to the same object type(s). It defaults to none, which 
+    means it won't appear in a list of flag characters but can still be 
+    tested for with hasflag(), andlflags(), and orlflags(). 
+  <type> specifies the space-separated list of types to which the flag
+    applies, and may be 'any' or one or more of 'room', 'thing', 'player',
+    or 'exit'. It defaults to 'any'.
+  <setperms> specifies the space-separated list of permissions for who can
+    set and/or see the flag. See 'help flag permissions' for details.
+    It defaults to 'any'
+  <unsetperms> specifies the space-separated list of permissions for who
+    can clear the flag on an object they control. It defaults to 
+    whatever <setperms> is given, or 'any'.
+
+  Flags added with @flag/add are saved with the database when it
+  is dumped, and do not need to be re-added at startup. They are
+  treated exactly as any other flag in the server.
+& flag permissions
+  The following permissions can be used when specifying whether
+  <actor> may set or clear a flag on an <object> they control:
+
+   trusted     <actor> must pass a TRUST check (see help TRUST)
+   royalty     <actor> must be ROYALTY or Director
+   Director      <actor> must be Director
+   god         <actor> must be God (#1)
+
+  The following permissions can be used to specify whether <looker>
+  can see the flag on an <object>, and are given along with the
+  <setperms> in @flag/add. By default, anyone can see the flag:
+
+   dark        <actor> must be Only God (#1) to see the flag on objects.
+   mdark       <actor> must be Director or ROYALTY 
+   odark       <actor> must own the <object> (or be Director or ROYALTY)
+
+& @function
+  @function [<function name>]
+  @function <name>=<obj>,<attrib>[,<min args>, <max args>[,<restrictions>]]
+    or @function <function name>=<object>/<attribute>
+  @function/<switch> <function name>
+  
+  When used without any arguments, this command lists all global
+  user-defined functions. For Directors and others with the Functions
+  power, it also lists the dbref number and attribute corresponding
+  to the listed functions.
+
+  When used with a function name, it displays some information
+  about how that function is parsed, and how many arguments it
+  takes.
+  
+  <switch> can be one of:
+  /disable, to disable a built in function.
+  /enable, to re-enable it.
+  /delete, to remove a user-defined function.
+
+  Otherwise, this command defines a global function with the name
+  <function name>, which evaluates to <attribute> on <object>.
+  <object> can be anything that the player using the @function command
+  controls. <function name> must be 30 characters or less.
+
+(continued in help @function2)
+& @function2
+  A function defined using @function works just like any of the normal
+  MUSH functions, from the user's perspective. The functions are
+  executed by the object, with its powers. 
+  Functions defined via @function should follow the format used by
+  UFUN() - %0 is the first argument passed, %1 is the second argument
+  passed, and so forth. Option third and fourth arguments to @function
+  can be used to set a parser-enforced number of arguments for the function.
+  
+  Example:
+  
+    > &WORD_CONCAT #10=%0 %1
+    > say "[ufun(#10/word_concat,foo,bar)]
+    You say, "foo bar"
+  
+    > @function word_concat = #10, word_concat
+    > say "[word_concat(foo,bar)]
+    You say, "foo bar"
+(continued in help @function3)
+& @function3
+  Global user-defined functions are not automatically loaded when the
+  game is restarted. In order to avoid objects which attempt to use
+  functions that have not been loaded, a @startup containing @function
+  commands should be set on a Director object with as low a dbref number 
+  as possible; object #1 (generally God) is suggested for this use.
+
+  For example, if you have one object that stores all your global
+  functions, you could set the following command (the object is #100
+  in the example):
+
+    @startup #1=@dol lattr(#100)=@function ##=#100,##
+
+  And then store each function as an attribute of the same name on 
+  object #100.
+(continued in help @function4)
+& @function4
+ Normally, built in functions cannot be overriden by @functions.
+ However, if a built-in function is deleted with @function/delete,
+ you can then make a @function with the same name. 
+ @function/restore will delete the @function and turn the built in
+ version back on.
+
+ For example:
+  @function/delete ansi
+  &ansi_fun #1234=%0
+  @function ansi=#1234, ansi_fun, 2, 2, noguest
+
+ will create a new version of ansi() that doesn't do any
+ colorization, and that requires it to be called with exactly
+ 2 arguments, like the built-in version. It will be restricted to
+ non-guest players.
+
+See also: RESTRICT, FUNCTIONS, @startup
+& @grep
+  @grep[/<switch>] <object>[/<attrs>]=<pattern>
+  
+  This command searches attributes in an object for <pattern>. It takes
+  four switches:
+  @grep/list
+    Lists the names of the attributes on the object containing <pattern>.
+  @grep/ilist
+    Same as above, but is case-insensitive when trying to match <pattern>.
+  @grep/print
+    Prints out all the attributes, highlighting the pattern itself in 
+    boldface, if you're ANSI.
+  @grep/iprint
+    Same as above, case-insensitive.
+
+  When used without a switch, @grep defaults to @grep/list.
+(continued in help @grep2)
+& @grep2
+
+  You must be able to see attributes on <object> (i.e. you must control
+  the object, it must be VISUAL, or you must be a Director or Royalty).
+  <attrs> is an optional wildcard pattern specifying attributes to
+  match (much like @edit). If <attrs> is not provided, all attributes
+  are assumed (just as if you had provided "*").
+   
+  <pattern> is not treated as a wildcard pattern, so you can grep for
+  patterns containing '*', '?', etc.  Also, <pattern> is NOT PARSED,
+  so '[v(0)]' and the like can be searched for.
+& @halt
+  @halt <object> [=<new_command>] 
+  @halt/all
+  The @halt command removes all queued actions for <object>. 
+  If <new command> is specified, that new command is placed
+  in the queue for the object instead. If no new command is
+  specified, the object is set HALT.
+
+  If <object> is a player, it clears the queue for the player and
+  and all of his objects. You can use "@halt me" to clear your own
+  queue without setting yourself HALT. Only Directors can @halt 
+  other players.
+  
+  Note that halting an object does NOT affect any objects waiting
+  on it as a semaphore.
+
+  @halt/all is a synonym for @allhalt and is a Director-only command.
+
+See also: HALT, QUEUE, @ps
+& @haven
+  @haven <player> = <message>
+
+  This message is sent to a player whose pages you are refusing, either
+  through use of the HAVEN flag or through the use of a page lock, if
+  it evaluates to something non-null.
+
+See also: HAVEN, page, @lock, @away
+& @hide
+  @hide[/<switch>]
+  
+  This command enables a royalty, Director, or player with the Hide
+  power to disappear from the WHO list for mortals. "@hide/yes" or "@hide/on"
+  hides the player, "@hide/no" or "@hide/off" unhides the player.
+  Hidden players are marked as "(Hide)" in the privileged WHO listing.
+  Mortals can not use the CONN(), IDLE(), or LWHO() functions to find 
+  hidden players.
+
+& @idescribe
+& @oidescribe
+& @aidescribe
+  @idescribe <object> [=<description>]
+  @oidescribe <object> [=<message>]
+  @aidescribe <object> [=<action list>]
+
+  @idescribe command sets the internal description for an object, which is 
+  shown to anyone who enters or looks while inside the object. Meaningless 
+  for rooms and exits. May be abbreviated to @idesc.
+
+  @oidescribe sets the message seen by others when someone looks at the 
+  idesc, and @aidescribe sets the action the object will take (just as 
+  with @desc, @odesc, @adesc). 
+
+  If there is no IDESCRIBE set for an object, those who enter or look 
+  inside it will see its @desc, and no o-message or action will be
+  available. If you want to use @aidescribe without @idescribe, set 
+  @idescribe to a blank string.
+
+  If [=<description>] or [=<message>] omitted, the attribute will be 
+  reset.
+  
+See also: enter, @enter, ENTER_OK, @describe, look, @idescformat
+& @hook
+  @hook/<switch> <command>[=<object>, <attribute>]
+
+  @hook tells the command parser to evaluate given attributes at certain points
+  in command evaluation. The possible points, indicated by the proper switch:
+
+  @hook/ignore: The attribute is evaluated before the built-in command is run.
+                If it returns a false value, the command is skipped
+                (the input is still matched against softcoded commands)
+  @hook/override: The object/attribute is matched for a $command,
+                and if it matches, it is run instead of the built-in command,
+                but with the precedence of the built-in command (thus
+                overriding not only the built-in command but any local
+                $commands that might match). If the match fails, normal
+                built-in command processing continues. Note that all locks
+                and flags on the object (HALT, etc.) still apply.
+  @hook/before: The attribute is evaluated before the built-in command is run.
+  @hook/after: The attribute is evaluated after the built-in command is run.
+
+  In all cases, %# is the dbref of the object doing the command, and all
+  hooks share the same set of q-registers. With /before and /after, 
+  the results of the evaluated attribute is thrown away like it was
+  wrapped in a call of null(). Also, in cases where a command and function
+  do the same thing (e.g., @pemit and pemit()), only the command gets
+  the hooks.
+
+  Leaving out the object and attribute clears an existing hook. Directors can
+  see existing hooks with @command.
+
+  See HELP @HOOK2 for an example.
+& @hook2
+  An example of @hook:
+
+  > &top_line #3=pemit(%#, What follows is the results of a look)
+  > &bottom_line #3=pemit(%#, You're done looking.)
+  > @hook/before look=#3, top_line
+  > @hook/after look=#3, bottom_line
+  > look
+  What follows is the results of a look
+  Room Zero
+  You are in Room Zero. It's very dark here.
+  You're done looking.
+& @idle
+  @idle <player> = <message>
+
+  This message is sent in return to every page which successfully reaches
+  you if it evaluates non-null. It is useful if you are idle for long 
+  periods of time and wish to inform people where you are, or if you
+  are in a meeting and cannot quickly return pages.
+
+  Clever example: 
+
+   @idle me=[switch(gt(idlesecs(me),120),1,I'm idle. Use @mail,)]
+
+  Players paging me will only see the "I'm idle" message if I've been
+  idle for over 2 minutes (120 seconds).
+& @infilter
+  @infilter <object> = <pattern 1>, <pattern 2>, <pattern 3>, ...
+  
+  @infilter is meant to be used on objects that have an @listen of "*"
+  (ie, objects that listen to everything, which is commonly used for 
+  things like chairs so that someone inside the object can hear 
+  everything said/done outside it). @infilter filters out any messages
+  that match one of the patterns and prevents those inside the object
+  from hearing them. It does not stop the @ahear of the listening object
+  from being triggered by things that match the @listen.
+  For an explanation of infilter patterns, see the help for "@filter".
+
+See also: @filter, @listen, AUDIBLE, LISTENING
+& @inprefix
+  @inprefix <object> = <message>
+  
+  @inprefix is intended for use with objects with a @listen of "*".
+  It prepends the <message> string to any message propagated to the
+  contents of <object> from the outside. If there is no @inprefix,
+  no string is prepended to the output.
+  
+  Example:
+  
+  [ First, @create Vehicle and Test (objects #103 and #104) and drop them ]
+    > @inprefix Vehicle = From outside,
+    Vehicle - Set.
+    > enter Vehicle
+    Vehicle(#103)
+    > @force #104=:bounces.
+    From outside, Test bounces.
+
+See also: @prefix, @listen, @infilter
+& @kick
+  @kick <number>
+  
+  This Director-only command forces the immediate execution of <number>
+  items from the queue. Rarely useful. If your MUSH is lagging badly,
+  chances are high that it stems from network problems. Check the queue
+  before using this command.
+
+See also: @ps, QUEUE
+& @lalias
+  @lalias <object> = <leave alias>
+
+  Sets the leave alias(es) of an object that is ENTER_OK. Setting a leave 
+  alias allows people to type the leave alias instead of the "leave"
+  command in order to get out of the object. Meaningless for exits and
+  rooms.
+
+  You can set multiple leave aliases on an object by separating them with
+  semi-colons.
+  Example:
+      @lalias chair=stand up;stand;get up;rise
+
+See also: @ealias, leave, @leave, @lfail
+& @leave
+  @leave <object> = <message>
+  
+  Sets the message that is displayed to anyone leaving <object>. 
+  
+See also: leave, @lfail
+& @lemit
+  @lemit[/<switch>] <message>
+
+  Emits a message to the outermost container object. For example, if you
+  are carrying a bird, and are inside a vehicle which is in room #10, and
+  you force the bird to @lemit "Cheep", everyone in room #10 will hear
+  "Cheep". This command is the same as "@emit/room". 
+
+  The /silent switch suppresses the normal confirmation message.
+  The /noeval switch prevents <message> from being evaluated.
+  The /spoof switch causes nospoof notifications to show the enactor's
+    dbref instead of the executor's dbref, and requires control over
+    the enactor or the Can_nspemit power.
+& @lfail
+  @lfail <object> = <message>
+
+  This is the message shown to the player who fails to leave the object,
+  either because the object is set NO_LEAVE or because the person leaving
+  fails the leave-lock.
+
+See also: leave, @leave, NO_LEAVE, locktypes
+& @list
+  @list/<switch>[/lowercase]
+  @list[/lowercase] <switch>
+
+  The @list command lists useful MUSH information.
+
+  Switches include:
+  /motd      : Alias for @listmotd, shows current messages of the day
+  /functions : Alias for @config/functions, shows all functions
+  /commands  : Alias for @config/commands, shows all commands
+  /attribs   : Alias for @config/attribs, shows all standard attribs
+  /locks     : Shows all global (non-user) locks
+  /flags     : Alias for @flag/list, shows all flags
+  /powers    : Alias for @powers/list, shows all powers
+  /lowercase : Show whatever you're showing in lowercase, rather than upper
+& @listmotd
+  @listmotd
+
+  This command lists the current MOTD (message of the day) for the MUSH.
+  If used by a Director or admin, it will also include the Director, full, and
+  down MOTDs to the user. Same as @list/motd.
+
+See also: @list
+& @link
+  @link[/preserve] <object>=<dbref | here | home | variable>
+
+  Links <object> to either a room or a thing. If a thing or player is
+  linked, that sets the object's HOME. If a room is linked, that sets 
+  the object's drop-to room, which is where objects that are dropped
+  in the room will be sent to.
+
+  Most often, @link is used to link or relink an exit to a destination 
+  room. In order to link an exit to a room, you must either own or
+  control the room, OR the room must be set LINK_OK. If the exit is
+  currently unlinked, and you pass its @lock/link, you may link it even
+  if you do not own it. In this case, the exit will be @chowned to you
+  (and set HALT).  Linking an exit may have a cost (usually 1 penny.)
+  The Director-only /preserve switch can be used to link without @chowning
+  and HALTing the exit.
+
+  If the destination of an exit is "variable", its destination is
+  determined at the time of travel by the attribute DESTINATION on the
+  exit, which is evaluated like a U()-function. You must have permission
+  to link to whatever the DESTINATION evaluates to in order for the exit
+  to work. If there's no DESTINATION attribute, the EXITTO attribute
+  is also tried.
+
+  If the destination is "home", those who travel through the exit will
+  be sent to their homes.
+
+  LINK_OK objects can also be used as semaphores, and any object can be
+  @parented to them.
+
+See also: EXITS, @open, @dig, DROP-TO, HOME
+& @listen
+  @listen <object> = <string>
+
+  Sets the object's listen pattern to <string>, which can have wildcards.
+  Whenever something the object hears matches the pattern, the object's
+  ahear/amhear/aahear attribute will be triggered. In addition, anything 
+  inside the object will hear it as well.
+
+For example:
+  @listen Chair = *  
+    Since the wildcard (*) matches anything, anyone inside the object will
+    hear anything said outside it.
+  @listen Butler = * has arrived.
+  @ahear Butler = :walks over to the new arrival and takes %p coat.
+  > Cyclonus has arrived.
+  > Butler walks over to the new arrival and takes his coat.
+    In this case, the listen pattern is met whenever someone 'arrives' in
+    the room, and then the object does whatever is inside its @ahear 
+    attribute.
+
+(continued in help @listen2)
+& @listen2
+  An object "hears" anything that another player standing in the same room
+  would hear. For example, if you type in a command, the object does NOT
+  hear it. If the command has a result that people in the room hear, the
+  object will hear it.
+
+For example:
+  @listen Recorder = @emit *
+  @ahear Recorder = :records {%0}
+  @emit Whee!
+  > Whee!
+    In this example, the Recorder's listen-pattern is NOT matched, because
+    it doesn't hear the '@emit Whee!', it only hears the 'Whee!' part, which
+    doesn't match.
+
+  @listen Recorder = Cyclonus says, "*"
+  "Whee!
+  > Cyclonus says, "Whee!"
+  > Recorder records: Whee!
+
+See also: LISTENING, @ahear, @amhear, @aahear
+& NEW LOCKS
+  In PennMUSH 1.7.5, several new features have been added to locks.
+
+  Locks can now be inherited off of parents, just like attributes.
+  By default, locks are set no_inherit, but this flag can be cleared.
+
+  There are now lock flags including ones to control inheritance,
+  copying in a @clone, who can set them, and so on. Details are
+  in HELP @LSET.
+  
+  Indirect lock keys (@#1234) can now refer to other lock names on
+  objects, not just a lock of the same name. See HELP @LOCK4.
+
+  There is a new lock key for testing flags and object types. 
+  See HELP @LOCK9 for more information.
+& LOCKING
+& LOCKS
+& @lock
+  @lock[/<switch>] <object>=<key> 
+  
+  This command "locks" the object, specifying (by the key) who or what can
+  do certain things with the object. There are many different types of locks,
+  all of which are described in "help locktypes" and which are designated by
+  the switch. The "basic" lock determines, for players and objects, who can
+  pick them up. For exits, it determines who can go through the exit.
+  All other locks can be set the same way as the basic lock.
+
+  Whenever you "pass" the basic lock, you succeed in doing something with
+  the object. This triggers the @success/@osuccess/@asuccess
+  messages and actions. If you fail to pass the basic lock, you trigger
+  the @failure/@ofailure/@afailure messages and actions. Other locktypes
+  may also have such success/failure messages.
+
+  You can specify <object> and <key> as either the name of an object in
+  the immediate area, a DBREF number, "me", or "here". 
+
+  Many new features have recently been added to locks. See HELP NEW LOCKS
+  for details.
+
+(continued in help @lock2)  
+& @lock2
+  You can lock an object in several different ways. The simplest lock is to
+  lock it to one other thing:
+   @lock My Toy = = me
+     This locks the object "My Toy" to you and you alone. It is recommended
+     that you @lock me == me in order to prevent anyone else from picking
+     you up. The two = signs are NOT a typo!
+
+  You can lock an object -against- one other object as well, using the '!' 
+  symbol:
+    @lock Shared Toy = !Vector Sigma
+      This locks the object "Shared Toy" to everyone -except- Vector Sigma.
+      Everyone except Vector will be able to pick up the object.
+
+(continued in help @lock3)
+& @lock3
+  You can lock an object to something that has to be carried:
+    @lock Door = +Secret Door Key
+      This locks the exit "Door" to someone carrying the object "Secret Door
+      Key". Anyone carrying that object will be able to go through the exit.
+
+  You can lock an object to -either- an object or to someone carrying the
+  object with:
+    @lock Disneyworld Entrance = Child
+      This locks the exit "Disneyworld Entrance" to either the object 
+      "Child" -or- to someone carrying the object "Child". (OK, so it's
+      a weird example.)
+  
+  You can lock an object to a specific player by using a *:
+    @lock Note == *Jazz
+      Only the player "Jazz" will be able to pick up the Note object.
+  
+(continued in help @lock4)
+& @lock4
+  An "owner" lock allows you to lock something to anything owned by the
+  same player:
+    @lock Box = $My Toy
+      This locks "Box" to anything owned by the owner of "My Toy" (since
+      players own themselves, that includes the owner as well).
+
+  An "indirect" lock allows you to lock something to the same thing as
+  another object (very useful in setting channel locks; see help @clock):
+    @lock Second Puppet = @First Puppet
+      This locks the object "Second Puppet" to whatever the object 
+      "First Puppet" is locked to. Normally, the lock type that is checked
+      is the same as the lock on the first. You can specify a different
+      lock type with @object/LOCKNAME. For example:
+      @lock Second Puppet = @First Puppet/Use
+      Second Puppet's basic lock now checks First Puppet's use lock.
+
+  There are also some more complex locks called attribute and evaluation
+  locks. In addition, you can make complex locks (combining several types
+  or reversing them).
+
+(continued in help @lock5)
+& @lock5
+ATTRIBUTE LOCKS
+  You can lock an object to an attribute on the person trying to pass 
+  the lock (as long as the object can "see" that attribute):
+
+    @lock <object>=<attribute>:<value>
+
+  <value> can contain wildcards (*), greater than (>) or less than (<)
+  symbols.
+
+  For example:
+    @lock Men's Room = sex:m*
+      This would lock the exit "Men's Room" to anyone with a SEX attribute
+      starting with the letter "m". 
+    @lock A-F = icname:<g
+      This would lock the exit "A-F" to anyone with a ICNAME attribute
+      starting with a letter "less than" the letter "g". This assumes
+      that ICNAME is visual or the object with the lock can see it.
+
+(continued in help @lock6)
+& @lock6
+COMPLEX LOCKS
+  You can combine or reverse locks very easily using the following 
+  BOOLEAN symbols and parentheses () to group them:
+    &      - "and"
+    |      - "or"
+
+  For example:
+   @lock My Toy = =me | !*Chromia | +Toy Box Key
+      This locks "My Toy" to me, -against- the player Chromia, or to anyone 
+      carrying the object "Toy Box Key". 
+   @lock My Toy = *Marissa & +Toy Box Key
+      This locks "My Toy" to the player Marissa, who needs to be carrying
+      the object "Toy Box Key".
+   @lock My Toy= *Chromia | ( *Marissa & +Toy Box Key )
+      This locks it to Chromia, OR to Marissa if she is carrying the Key.
+
+(continued in help @lock7)
+& @lock7
+EVALUATION LOCKS
+  An evaluation lock is set using this format:
+
+    @lock <object>=<attribute>/<value>
+
+  The difference between this and an attribute lock is that the <attribute>
+  is taken from <object> rather than from the person trying to pass the
+  lock. When someone tries, <attribute> is evaluated, and the result is 
+  compared to <value>. If it matches, then the person passes the lock.
+
+  The person trying to pass the lock is %# and <object> is %! when the
+  evaluation takes place. The evaluation is done with the powers of
+  <object>. If you try to do something (like [get(%#/<attribute>)]) and
+  <object> doesn't have permission to do that, the person will automatically
+  fail to pass the lock.
+
+(continued in help @lock8)
+& @lock8
+
+  Example: 
+    @lock Stage = ispuppet/1
+    &ispuppet Stage = hasflag(%#, PUPPET)
+      This locks the object "Stage" to puppets only.
+
+      Whenever someone tries to pick up the object, the attribute 
+      "ispuppet" will be checked, substituting in the person's DBREF number
+      for %#. 
+      [hasflag(<dbref>, PUPPET)] will come out to 1 if the object with 
+      <dbref> has the "PUPPET" flag. Otherwise, it will come out to be 0. 
+      Since the value in the @lock is 1, only objects with the PUPPET flag
+      will be able to pass this lock.
+
+(continued in help @lock9)
+& @lock9
+  You can also test for set flags, powers, or object types in a lock directly,
+  without using an evaluation lock, with these formats:
+
+    @lock <object>=flag^<flag>
+    @lock <object>=type^<type>
+    @lock <object>=power^<power>
+
+  These locks act like the object the lock is on does a hasflag(%#, <flag>),
+  hastype(%#, <type>), or haspower(%#, <power>) succeeding if the flag is set.
+
+  You can test for channel membership with:
+
+    @lock <object>=channel^<channel>
+
+  For example,
+    @lock/use Admin Commands=flag^Director|flag^royalty
+
+  You can test for an object id, instead of a dbref, with:
+
+    @lock <object>=objid^<object id>
+
+See also: locktypes, @clock, objid()
+& locktypes
+& locklist
+& lock types
+& lock list
+  Your MUSH will almost certainly support these standard lock types:
+
+  @lock/basic           Who can pick up the player/object, or go through
+                        the exit.
+  @lock/enter           Who can enter the player/object (aka @elock)
+  @lock/teleport        Who can teleport to the room
+  @lock/use             Who can use the object (aka @ulock)
+  @lock/page            Who can page/@pemit the player
+  @lock/zone            Who can control objects on this zone
+  @lock/parent          Who can @parent something to this object/room
+  @lock/link            Who can @link something to this object/room
+                        or who can @link this unlinked exit.
+  @lock/mail            Who can @mail the player
+  @lock/user:<name>     User-defined. No built-in function of this lock,
+                        but users can test it with elock()
+
+  See 'help locktypes2' for more
+& lock types2
+& locktypes2
+  More standard lock types:
+
+  @lock/speech          Who can speak/pose/emit in this room
+  @lock/listen          Who can trigger my @ahear/^-pattern actions
+  @lock/command         Who can trigger my $-pattern commands
+  @lock/leave           Who can leave this object (or room, via exits/@tel)
+  @lock/drop            Who can drop this object or in this room
+  @lock/give            Who can give this object
+  @lock/follow          Who can follow this object
+  @lock/examine         Who can examine this object if it's VISUAL
+  @lock/chzone          Who can @chzone to this object if it's a ZMO
+  @lock/forward         Who can @forwardlist a message to this object 
+  @lock/control                Who can control this object (only if set)
+  @lock/dropto         Who can trigger this container's drop-to.
+  @lock/destroy                Who can destroy this object if it's DESTROY_OK
+  @lock/interact        Who can send sound (say/pose/emit/etc) to this object
+
+  See also: @lock, @lset, @clock, FAILURE
+& @lset
+  @lset <object>/<lock type>=[!]<flag>
+
+  This commands sets or clears flags on locks.
+  Valid flags include:
+  
+  visual     (v)     This lock can be seen even if the object it's on
+                     isn't visual.
+  no_inherit (i)     This lock isn't inherited off of parents. All locks
+                     are set no_inherit by default.
+  no_clone   (c)     This lock isn't copied by @clone.
+  Director     (w)     This lock can only be set by Directors.
+  locked     (+)     This lock can only be set by the owner of the lock.
+
+& @log
+  @log[/<switch>] <message>
+
+  This Director-only command puts <message> in a log file, tagged with
+  the time and object executing the command.  The available switches
+  are /check, /cmd, /conn, /err, /trace, and /wiz, specifying which
+  file to log to.  /cmd is default.
+
+  See also: @logwipe
+& @logwipe
+  @logwipe/<switch> <password>
+
+  This God-only command erases one of the MUSH logs. Available switches
+  are /check, /cmd, /conn, /trace, and /wiz. God must give the 
+  log wipe password from the MUSH's configuration file to use this
+  command.
+
+  See also: @log
+& @map
+  @map[/delim] [<delim>] <list> = <function or pattern>
+  This command takes a space-separated list of words, and performs
+  pronoun/pattern substitution on each word, returning a list - 
+  "mapping" the function onto each item in the list. It returns the
+  list in a MAPLIST attribute, automatically set on the object executing
+  the @map. The set is always performed before any actions further
+  actions are executed. 
+  
+  If the /delim switch is given, the first character of the list is
+  used as the delimiter, instead of space.
+
+  Brackets are generally necessary in order to make the function
+  substitutions evaluate correctly.
+  
+  @map is an obsolete command provided for backwards compatibility;
+  the ITER() or MAP() functions should probably be used instead.
+  See "help @map2" for examples of @map.
+& @map2
+  Examples of @map:
+    @map foobar baz blech=[strlen(##)]
+      Returns "6 3 5" in MAPLIST.
+    @map testing map-test=[strlen(before(##, est)]
+      Returns "1 5" in MAPLIST
+    @map Joe Bob Ann=I know ##.
+      Returns "I know Joe. I know Bob. I know Ann." in MAPLIST
+  >  @va Object=$test * * *:@map %0 %1 %2=[strlen(##)];
+           @dolist [v(maplist)] = say ##
+  Object - Set.
+  >  test aaa bb c
+  Object says, "3"
+  Object says, "2"
+  Object says, "1"
+& @motd
+  @motd [/<switch>] [<message>].  
+  The default for this command (and with the /connect) switch, is a
+  Director only command that will set a temporary message that 
+  will be shown to players when they connect. This MOTD is cleared
+  every time the MUSH restarts.
+
+  Note that @motd by itself clears the message. Use @motd/list or
+  @listmotd to see the current messages.
+  Other switches:
+  /Director : sets the message for Directors (like @wizmotd)
+  /down   : sets the logins-off message (like @rejectmotd)
+  /full   : sets the max-players-logged-in message
+  /list   : list the MOTDs (like @listmotd, can be used by anyone)
+& @move
+  @move <object> = <movement message>
+
+  This sets the message that is shown to <object> whenever it moves.
+  Example:
+       @move me=You moved! You are now in the room: [name(loc(me))].
+
+See also: go, @omove, @oxmove, @amove
+& @name
+  @name <object>=<new name>
+
+  Changes the name of <object>, which can be a thing, player, exit,
+  or room. You can refer to object by name, DBREF number, or as "me"
+  or "here".
+
+  When changing a player's name on games that support multi-word player
+  names, the name should be enclosed in quotes:
+    @name me = "James Bond"
+
+  Changing the name of <object> will cause object to execute its
+  ONAME and ANAME. The old name will be passed at %0 to these;
+  the new name will be passed as %1.
+& @newpassword
+  @newpassword <player> = <password>
+
+  This Director-only command changes <player>'s password. If <player>
+  is logged in, s/he will be informed that the password was changed.
+
+See also: @password
+& @notify
+  @notify[/any][/all] <object>[/<attribute>][=<number>]
+  
+  This command notifies a semaphore, allowing commands queued for
+  that semaphore to be executed.
+
+  If the /any switch is given, then all semaphores associated with
+  <object> are @notified.  Otherwise, only the specified semaphore
+  attribute (or SEMAPHORE if no attribute is specified) is @notified.
+
+  If the /all switch is given, then all queue entries associated
+  with the selected semaphore(s) are executed.  Otherwise, only the
+  first <number> of queue entries are run.  If no <number> is given,
+  then only one queue entry is run.
+
+  If the /all switch was not used, and there were not enough queue
+  entries waiting to satisfy the requested <number> (or 1, if no
+  number was given), then subsequent @waits will not block until
+  the requested <number> have been run.
+  
+  You may not specify both the /any switch and a specific attribute.
+  Similarly, you may not specify both the /all switch and a number.
+
+See also: SEMAPHORES, @drain, @wait
+& @nspemit
+& @nsemit
+& @nslemit
+& @nsremit
+& @nszemit
+& @nsoemit
+  @nsemit[/<switch>] [<message>]
+  @nslemit[/<switch>] <message>
+  @nspemit[/switches] <object>=<message>
+  @nsremit[/switches] <object> = <message>.
+  @nszemit <zone> = <message>
+
+  These Director-only commands work like @emit, @lemit, @pemit, @remit,
+  and @zemit, respectively, but will not include nospoof information.
+  They are meant to be used by commands in the master room where the
+  nospoof information is just useless noise. They take all switches
+  of their respective commands.
+
+See also: @emit, @lemit, @pemit, @remit, @zemit, nsemit(), nslemit(),
+          nspemit(), nsremit(), nszemit()
+
+& @odescribe
+  @odescribe <object> [=<message>]
+
+  This sets the message that will be shown to others whenever anyone
+  looks at <object>. The name of the person looking will be added to
+  the beginning of the message. Please note that @odescs are often found
+  annoying. 
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: look, @describe, @idescribe
+& @odrop
+  @odrop <object> [=<message>]
+
+  This sets the message that will be shown to others whenever anyone drops
+  <object>. The name of the person dropping the object will be added to the
+  beginning of the message automatically.
+
+  If set on an exit, the @odrop is shown in the exit's destination room
+  when someone goes through the exit.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: drop, @drop, @adrop
+& @oefail
+  @oefail <object> [= <message>]
+
+  This sets the message that will be show to others whenever anyone tries
+  to enter <object> and fails, usually because they fail the enter-lock.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: enter, @efail, @aefail, @lock
+& @oemit
+  @oemit[/<switch>] [<room>/]<object> [<object>...] = <message>
+  This command shows <message> to everyone in the location of <object>
+  EXCEPT <object>. The object can be specified by name if in your current
+  location, or by DBREF number. A list of objects can be given, in
+  which case the message is shown in all locations to all except those
+  objects.
+  
+  If a room is specified (usually via dbref), this command shows
+  <message> to everyone in <room> except for <object> (which may be
+  a list, as above). In this case, object(s) are matched with reference to
+  <room>. Therefore, if you want to send a message to everything but an
+  object called "spy" in #100, you can simply use "@oemit #100/spy=Test";
+  you don't need to know the dbref of "spy".
+
+  The /noeval switch prevents the MUSH from evaluating the message.
+  The /spoof switch causes nospoof notifications to show the enactor's
+    dbref instead of the executor's dbref, and requires control over
+    the enactor or the Can_nspemit power.
+See also: @emit, @pemit, NOSPOOF and SPOOFING.
+& @oenter
+  @oenter <object> [= <message>]
+
+  This sets the message that will be show to others whenever anyone
+  enters the object. It will be seen by those already -inside- the
+  object and will automatically add the name of the person entering
+  at the beginning. If you wish to set a message to be seen by those
+  -outside- the object, use @oxenter.
+
+See also: enter, @enter, @oxenter
+& @ofailure
+  @ofailure <object> [=<message>]
+
+  This sets the message that will be show to others whenever anyone
+  fails to pass <object>'s basic lock. For players and things, this means
+  failing to pick them up. For exits, this means failing to go through 
+  the exit. It automatically prefixes the name of the player.
+
+Example:
+  @ofail Locked Door=tries to open the door, but it seems to be locked.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: FAILURE, @lock, @failure, @afailure
+& @ofollow
+  @ofollow <object> [= <message>]
+
+  Sets the message shown to others in the room after someone
+  begins following the object (using the 'follow' command). 
+  Similar to @omove or @osucc, this command prepends the name
+  of the person doing the following.
+
+  If the =<message> part is omitted, the message will be reset.
+
+Ex: @ofollow me=falls into step behind Cyclonus.
+
+See also: follow, unfollow, followers(), @follow, @afollow
+& @ounfollow
+  @ounfollow <object> [= <message>]
+
+  Sets the message shown to others in the room after someone
+  stops following the object (using the 'unfollow' command). 
+  Similar to @omove or @osucc, this command prepends the name
+  of the person doing the unfollowing.
+
+  If the =<message> part is omitted, the message will be reset.
+
+Ex: @ounfollow me=stops following [name(me)].
+
+See also: follow, unfollow, followers(), @unfollow, @aunfollow
+& @oleave
+  @oleave <object> [= <message>]
+  
+  This sets the message that will be displayed to the people inside
+  <object> whenever someone leaves <object>. If you want to set a 
+  message to be displayed to people outside the object, use @oxleave.
+  This command automatically inserts the name of the person leaving
+  at the start of the message.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: leave, @leave, @aleave, @oxleave
+& @olfail
+  @olfail <object> [= <message>]
+  
+  This sets the message that will be shown to others whenever someone
+  tries to leave <object> and fails (usually if the person fails the
+  leave-lock, or if <object> is set NO_LEAVE). It automatically inserts
+  the name of the person trying to leave at the start of the message.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: leave, @lfail, @alfail, @lock
+& @omove
+  @omove <object> [= <message>]
+
+  Sets the message that will be shown to others whenever <object> moves.
+  It is shown in the destination room or object and automatically inserts
+  the name of the <object> at the beginning. Please note that long @omoves
+  are frequently found annoying.
+
+Example:
+ @omove me=stalks into the room wearing the latest in malevolent expressions.
+  
+  If the =<message> part is omitted, the message will be reset.
+
+See also: go, @move, @amove, @oxmove
+& @oxmove
+  @oxmove <object> [= <message>]
+
+  Sets the message shown to others in the source room whenever <object> 
+  moves elsewhere. <object>'s name is automatically inserted at the
+  beginning of this message. Note that long @oxmoves are annoying.
+
+ Example:
+  @oxmove me=stalks away, glaring.
+
+  If the =<message> is omitted, the message is cleared.
+  
+See also: go, @move, @omove, @amove
+& @opayment
+  @opayment <object> [=<message>]
+
+  Sets the message that will be show to others whenever someone "pays"
+  <object> (by giving the object a certain amount of MUSH money). The
+  name of the person giving the money will be automatically inserted at
+  the beginning of the message.
+  
+  If the =<message> part is omitted, the message will be reset.
+
+Ex: @opay Vending Machine=sticks a quarter in the vending machine.
+
+See also: give, @cost, @payment, @apayment, MONEY
+& @open
+  @open <exit name>
+  @open <exit name>;<exit aliases>=<destination>
+  @open <exit name>;<exit aliases>=<destination>,<exit name>;<exit aliases>
+
+  This command opens an exit in the room you are standing in with the 
+  specified name. You can then use the @link command to set the exit's
+  destination, or you can set it automatically by using the DBREF of a
+  destination, which can be a room or object. (Note that you CANNOT open
+  exits from objects.) If you also include the second exit name, an exit
+  from the destination room will be opened back to the room you are in.
+
+  NOTE: you can have as many exit aliases as you like by adding more,
+  separated by semicolons. An exit alias allows you to type that instead of 
+  the full exit name to go through the exit. Only the exit name appears in
+  the list of Obvious Exits in a room.
+
+Ex: @open Up;u;climb = #255, Down;dow;do;d;fall
+
+See also: EXITS, @link, @dig
+& @osuccess
+  @osuccess <object> [=<message>]
+
+  Sets the message that is shown to others whenever someone passes the 
+  object's Basic lock. For players and things, this means picking them up.
+  For exits, this means going through the exit, and the osuccess message
+  is shown to those in the room the player has just left. This message 
+  automatically inserts the name of the person at the beginning.
+
+  @osucc's are very useful for allowing people to follow someone around
+  if the name of the exit is included in the message. It is recommended 
+  that you put @osuccs on all exits and all takeable objects.
+
+Ex: @osucc North=heads north into the catacombs.
+  
+  If the =<message> part is omitted, the message will be reset.
+
+See also: take, @success, @asuccess, @lock, FAILURE
+& @otport
+  @otport <object> [=<message>]
+
+  Sets the <message>, which will be prefixed by <object>'s name,
+  that will be shown to the others in the room that the <object>
+  is teleported to. If you want to set a message that will be shown
+  to the room that the object is teleported OUT of, use @oxtport.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: @teleport, @oxtport
+
+& @ouse
+  @ouse <object> [=<message>]
+
+  Sets the message that will be shown to others whenever someone successfully
+  uses <object>. The name of the person using the object will automatically
+  be inserted at the beginning.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: use, @use, @ause, @charges, @runout
+& @oxenter
+  @oxenter <object> [= <message>]
+
+  Sets the message that will be shown to others outside the object whenever
+  someone enters the object. The name of the person entering will be
+  inserted at the beginning of the message automatically. If you want
+  to show a message to everyone inside the object, use @oenter.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: enter, @oenter, leave
+& @oxleave
+  @oxleave <object> [= <message>]
+
+  This message is shown to everyone in the room that a person enters
+  when doing a 'leave' command.  This will be shown in addition to the
+  enter messages of the room, not instead of.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: leave, @leave, @oleave, @aleave
+& @oxtport
+  @oxtport <object> [=<message>]
+  
+  Sets the <message>, which will be prefixed by <object>'s name,
+  that will be shown to those in the room that the object has
+  left via @teleport. If you want to show a message to the room the
+  object has arrived in, use @otport.
+
+  If the =<message> part is omitted, the message will be reset.
+
+See also: @teleport, @otport, @atport
+& @parent
+  @parent <object> = <parent>
+  This command sets the parent of <object> to <parent>. <parent> may be
+  an object of any type; <object> can be anything but a player. The
+  player must control <object>. <parent> must be controlled by the
+  player or LINK_OK.
+  If <parent> is "none" or blank, the object is unparented.
+
+See also: PARENTS, parent(), lparent()
+& @password
+  @password <old password>=<new password>
+
+  This changes your password. Please note that passwords ARE case-sensitive.
+  
+& @payment
+  @payment <object> [=<message>]
+
+  Sets the message that is shown to anyone who pays the object, by giving
+  it a certain amount of MUSH money. If =<message> is omitted, the message
+  is reset to nothing. May be abbreviated @pay. 
+
+See also: give, @apay, @opay, and @cost.
+& @receive
+& @oreceive
+& @areceive
+  @receive <recipient> [=<message>]
+  @oreceive <recipient> [=<message>]
+  @areceive <recipient> [=<message>]
+
+  @receive sets the message that is shown to the recipient who 
+  acquires an object by 'get'ing it or having it 'give'n to them.
+  @oreceive is a message shown to others in the recipient's location,
+  and @areceive is an action run by the recipient. If not set,
+  the recipient gets a default message ("Jane gave you A Headache").
+  
+  %0 will be set to the dbref of the object received.
+  %1 will be set to the dbref of the giver if a 'give' was performed.
+
+See also: give
+& @give
+& @ogive
+& @agive
+  @give <giver> [=<message>]
+  @ogive <giver> [=<message>]
+  @agive <giver> [=<message>]
+
+  @give sets the message that is shown to the giver when giving an object
+  to someone else.  @ogive is a message shown to others in the giver's
+  location, and @agive is an action run by the giver. If not set,
+  the giver gets a default message.
+
+  %0 will be set to the dbref of the object given.
+  %1 will be set to the dbref of the recipient.
+
+See also: give
+& @pcreate
+  @pcreate <name> = <password>
+
+  This Director-only command creates a player with the given name and
+  password.
+& @pemit
+  @pemit[/<switch>] <object> = <message>
+  
+  The basic form of this command sends <message> to <object> directly.
+  It is very similar in its effects to @emit except only one object
+  will see the message. You may @pemit to objects in the same room,
+  objects you are carrying, and to objects that are carrying you, 
+  or @pemit remotely, using #<object> or *<player name>.
+  The /list switch to this command allows you to @pemit a message to
+  a list:  @pemit/list <object 1> [<object 2> <object N>] = <message>
+  There can be any number of objects in the list. The objects must be
+  specified by dbref number. You will not get back a "confirmation"
+  message for the /list form of this command. 
+
+(continued in help @pemit2)
+& @pemit2
+  The @pemit command can take the following additional switches:
+    /contents  -- equivalent to @remit.
+    /silent    -- does not tell the @pemit'ing object a confirmation message.
+    /noisy     -- tells the @pemit'ing object a confirmation message.
+    /noeval    -- the message will not be evaluated for substitutions 
+    /spoof     -- the enactor's dbref will be used for nospoof notifications
+                  instead of the executor's dbref. Requires control
+                  over enactor or Can_nspemit power. 
+
+  Note that page-lock and the HAVEN flag will block @pemits as well, 
+  except from Directors or those with the pemit_all power.
+
+See also @emit, @oemit, @remit, NOSPOOF, and SPOOFING.
+& @poll
+  @poll <poll question>
+  This Director-only command sets the "poll" - the Doing question. If
+  "@poll" is used by itself, the question is reset to the default
+  string "Doing". It can also be used by those with the poll @power.
+
+See also: @doing, WHO, DOING
+& @poor
+  @poor <value>.
+  This is a Director only command.  It sets every player's money supply to
+  value.
+
+See also: MONEY
+
+& @prefix
+  @prefix <object> = <message>
+  This attribute is meant to be used in conjunction with the AUDIBLE
+  flag. The @prefix of the object is prepended to messages propagated
+  via AUDIBLE.   Pronoun substitution is done on @prefix messages.
+  
+  For example, if you have an audible exit "Outside" leading from a room 
+  Garden to a room Street, with @prefix  "From the garden nearby,"  if
+  Joe does a ":waves to everyone." from the Garden, the people at Street
+  will see the message,   "From the garden nearby, Joe waves to everyone."
+
+See also: @inprefix, AUDIBLE, @listen
+& @ps
+  @ps[/<switch>] [*<player>]
+  
+  @ps is a useful command for MUSHers.  It lists all commands currently on
+  your 'to be executed' queue, thus allowing you to identify infinite (or
+  unnecessary) loops with-out putting in says or poses. It gives a count of
+  the total commands in each of the queues (Player, Object, Wait, and
+  Semaphore), displayed in the format:
+      <Number of your queued commands> / <Total number of queued commands>.
+      
+  @ps with no arguments will show you your own queue. Directors may specify
+  the /all switch, and see the full queue. They may also specify a player.
+  @ps/summary just displays the queue totals for the whole queue.
+  @ps/quick displays the queue totals for just your queue.
+& @purge
+  @purge is a Director only command that calls the internal purge routine to 
+  advance the clock of each object scheduled to be destroyed, and destroy 
+  those things whose time is up. The internal purge routine is normally
+  run automatically approximately every 10 minutes. 
+
+  The @purge command should almost never need to be performed
+  manually. If you do use it manually, you may want to use it twice in
+  a row to make sure that everything marked GOING is actually
+  destroyed.
+
+See also: @dbck
+& @quota
+  @quota [/<switch>] [<victim>]
+
+  This command is only available if the quota system is enabled.
+  It reports the victim's owned objects and the maximum number of objects 
+  s/he may own. You must be a Director to see another player's quota.
+
+  The /set and /all switches are equivalent to @squota and @allquota,
+  respectively.
+
+  See Also: @squota
+
+& @readcache
+  @readcache
+  
+  This admin-only command reads special text files into a cache and
+  rebuilds the help and news indices. This must be done every time the
+  text files (connect text, help files, etc.) are changed while the
+  game is running. It does not need to be used after changing the
+  names.cnf file of bad player names.
+  A site admin can achieve the same effect by sending the MUSH process
+  a kill -1 or kill -HUP.
+& @rejectmotd
+  @rejectmotd <message>
+
+  This is a admin only command that will set a short (non-longterm) message 
+  that will be shown to players that try to connect when logins are disabled. 
+  This is the "Down MOTD" in the @listmotd listing. The siteadmin can set a
+  more permanent message for this by editing the file "down.txt".
+  
+See also: @motd, @list, @listmotd
+& @remit
+  @remit[/switches] <object> = <message>.
+
+  Sends the message to all contents of <object>, which can be a room,
+  thing, or player. The message is also sent to the <object> itself.
+  (The TinyMUSH equivalent is @pemit/contents).
+
+  The /silent switch stops the remitter from getting feedback if they're
+   in a different location than the target.
+  The /noisy switch always gives feedback to the remitter if they are
+   not in the target location. Without /silent or /noisy, the silent_pemit
+   config option is used to determine noisiness.
+  The /list switch will send the message to the contents of multiple
+   objects at the same time. The <OBJECT> argument is treated as a
+   space-separated list of targets.
+  The /spoof switch causes nospoof notifications to show the enactor's
+   dbref instead of the executor's dbref, and requires control over
+   the enactor or the Can_nspemit power.
+
+See also: @emit, @pemit, @oemit, SPOOFING, NOSPOOF and CONTROL.
+& @restart
+  @restart <object> or @restart/all
+  
+  This command halts <object> (as described in @halt), and then triggers
+  the STARTUP attribute on the object, if set. If <object> is a player,
+  it affects the player and all of their objects. Players can use
+  @restart me to restart their own objects. The /all switch halts
+  all objects (see @allhalt) and restarts them, and can only be used
+  by a Director.
+
+
+See also: @halt, @startup
+& @runout 
+  @runout <object> = <action list> 
+
+  Sets the actions to be taken when the charges of the object reaches
+  zero. 
+
+See also: @charges, use, ACTION LISTS
+& @rwall
+  @rwall[/emit] <message>
+
+  Only Directors and royalty may use this command. It broadcasts a
+  message to all connected Directors and royals. If the /emit switch
+  is given, it's done as a prefixed emit. Otherwise, it acts like
+  a @channel.
+
+See also: @wall, @wizwall
+& @scan
+  @scan[/<switch>] <command>
+  
+  @scan gives you a list of all objects containing $commands (user-defined
+  commands) which could match <command>. If given no switches, it checks
+  you, your possessions, your location, objects in your location, the
+  zone/zone master room of your location, your zone, and objects in the 
+  master room. It does NOT stop when it gets a match, but rather, finds all 
+  possible matches. It also tells how many commands on each object were
+  matched, and what attributes they are in. It does NOT scan objects
+  that you do not control and are not set VISUAL.
+  
+  This command can take four switches:
+     /room     --   just matches on your location and objects in it.
+     /self     --   just matches on you and anything you're carrying.
+     /zone     --   just matches on zones of your location and yourself.
+     /globals  --   just matches on objects in the master room.
+& @search
+  @search [<player>] [<class>=<restriction>] [,<begin>,<end>]
+  
+  This command searches the database and lists objects which meet user
+  specified search criteria.  You can limit the scope of the search by
+  specifying <begin> and <end> as the first and last dbrefs to search.
+  
+  If a <player> argument is supplied, only objects owned by that player
+  will be listed. If a <class> argument is supplied only objects of a
+  certain class will  be listed. Possible <class>es include TYPE, NAME,
+  ZONE, PARENT, EXITS, OBJECTS (Or THINGS), ROOMS, PLAYERS, FLAGS, LFLAGS,
+  POWERS, EVAL, EPLAYER, EROOM, EEXIT, and EOBJECT (Or ETHING).
+
+  If <class>=TYPE, possible <restriction>s include OBJECT (Or THING), ROOM,
+  EXIT, PLAYER. This shows all objects of the specified type.
+  
+  If <class>=NAME, only objects whose name begin with the string <restriction>
+  will be listed. If <class>=ZONE, only objects in the zone <restriction>
+  will be listed. If <class>=PARENT, only children of parent <restriction>
+  will be listed. For ZONE and PARENT, <restriction> must be specified as a
+  dbref number.
+  
+  'help @search2' for more.
+& @search2
+  If <class>=EXITS, OBJECTS, ROOMS, or PLAYERS, only objects of that type
+  will be listed.
+
+  If <class>=FLAGS or LFLAGS, only objects with the list of flags
+  specified by <restriction> will be listed. For FLAGS, flags to match
+  should be given as a string of single flag letters, with appropriate
+  case. For LFLAGS, flags to match should be given as a space-separated
+  list of flag names.
+
+  If <class>=POWERS, only objects with the given power are listed. Only
+  one power may be specified.
+  
+  If <class>=EVAL, only objects for which <restriction> evaluates to a
+  true boolean value will be listed. The token '##' in <restriction>, which
+  is a function, is replaced by each dbref sequentially. Classes EPLAYER,
+  EROOM, EEXIT, and EOBJECT work like EVAL but are restricted to a single type.
+  
+  See "help @search3" for more.
+& @search3
+  For the class TYPE=PLAYER, and for  PLAYER=<player-name>, anyone may
+  obtain information on any player.  In all other cases, only Directors may
+  obtain information about other players. This is computationally
+  expensive, costing 100 pennies. It is generally faster than @find.
+  
+  Examples:  
+    @search flags=Wc      <-- search for connected Directors.
+    @search type=room     <-- list all rooms owned by me.
+    @search zone=#50      <-- list all objects belong to zone #50.
+    @search Joe eval=1,100,200   <-- list objects from #100-#200 owned by Joe.
+    @search eval=gt(money(##),10)     <-- list all objects owned by me  
+                                          worth more than 10 coins.
+  
+  If <class>=POWERS, only objects with the given power are listed. Only
+  one power may be specified.
+  
+  If <class>=EVAL, only objects for which <restriction> evaluates to a
+  true boolean value will be listed. The token '##' in <restriction>, which
+  is a function, is replaced by each dbref sequentially. Classes EPLAYER,
+  EROOM, EEXIT, and EOBJECT work like EVAL but are restricted to a single type.
+& @select
+  @select <string>=<expr1>,<action1>[,<exprN>,<actionN>]...[,<default>]
+  This is similar to @switch, except it only executes the action
+  associated with the first expression which matches <string> - the
+  targets are mutually exclusive. If no target is matched, the default
+  actions are executed. This is equivalent to "@switch/first".
+
+  Example:
+    &FOO thing = $foo *:@select %0=*a*,:acks,*b*,:bars,*c*,:cheeps,:glurps
+    foo abc
+    > thing acks
+    foo xxx
+    > thing glurps
+
+  The string "#$" in <action>'s will be expanded to the evaluated result
+  of <string> before it is acted on.
+
+  If the /notify switch is given, a "@notify me" is queued after 
+  the selected <action>. This is useful for synchronization with semaphores.
+
+  If the /regexp switch is given, the expressions are case-insensitive regular
+  expressions.
+
+See also: @switch, switch()
+& @set
+  @set <object>=[!]<flag> [[!]<flag> ...]
+  @<pre-defined attribute> <object>=<value>
+  @set <object>=<attribute>:<value>
+  @set <object>/<attribute>=[!]<atrflag>
+  
+  The first form sets (or unsets) flag(s) on <object>. See 'help flags'.
+    Ex: @set me=VISUAL
+  Flags may be specified by full name (recommended) or by flag character.
+  Flags are set or reset in the order supplied.
+
+  The second form sets a pre-defined attribute on <object>
+    Ex: @fail Heavy Box=You can't pick that up.
+
+  The third form sets an arbitrary attribute with <value> on <object>.
+  You can also do this with &<attribute> <object>=<value>
+    Ex: @set Test Object=random:This is a random attribute.
+        &random Test Object=This is a random attribute.
+  
+  The fourth form sets (or unsets) an attribute flag on the specified
+  attribute. See 'help attribute flags'.
+& attribute flags
+  Attribute flags are set on an object's attributes using @set, or applied
+  to attributes globally using @attrib. Their names (and, when applicable,
+  the character used in examine as shorthand for the flag) include:
+  no_command ($)    Attribute will not be checked for '$' commands and
+                    '^' listen patterns. 
+  visual (v)        Attribute can be seen by anyone via get(), eval(),
+                    ufun(), zfun(), and similar functions. 
+  no_inherit (i)    Attribute will not be inherited by the children of
+                    this object. 
+  no_clone (c)      Attribute will not be copied if the object is @clone'd.
+  regexp (R)        Match $-commands and ^-listens using regular expressions.
+                    See 'help regexps'.
+  case (C)          Match $-commands and ^-listens case sensitively.
+  safe (S)          Attribute may not be modified, without unsetting this flag.
+  mortal_dark (m)   Attribute cannot be seen by mortals. This flag can only 
+                    be set by royalty and Directors.  "hidden" is a synonym.
+  Director (w)      Attribute can only be set by Directors. 
+                    This flag can only be set by royalty and Directors.
+  veiled (V)        Attribute value will not be shown on default examine,
+                    but is still otherwise accessible (for spammy attribs).
+  debug (b)         Show debug output when this attribute is evaluated.
+  nearby (n)        Even if this attribute is visual, it can only be
+                    retrieved if you're co-located with the object.
+  pow_inherit(t)    Executes code from object it's on. Preserve %@ for object it 
+                   is called off of.  USE WITH CAUTION!        
+  public (p)        This attribute can be evaluated by any object, even
+                    if safer_ufun is in use. DANGEROUS! AVOID!
+  aahear (A)        ^-listens on this attribute match like @aahear
+  amhear (M)        ^-listens on this attribute match like @amhear
+  prefixmatch       When a user attempts to set an attribute using @<attrib>,
+                    this attribute will be matched down to its unique
+                    prefixes. This flag is primarily used internally.
+  `                 This attribute is a branch. See: help ATTRIBUTE TREES
+
+& @sex
+  @sex <player> = <gender>  
+
+  You can use this command to set yourself or any of your objects to be
+  male, female, neuter, or plural. The SEX attribute is used for pronoun 
+  substitution by the MUSH, and anything not recognizable will be treated
+  as neuter. 
+
+  @sex me = Male
+  @sex me = Female
+  @sex me = Woman
+  @sex me = They
+  @sex me = Plural
+  @sex me = No thank you (silly, but possible)
+
+See also: GENDER, SUBSTITUTION
+& @shutdown
+  @shutdown[/panic][/reboot][/paranoid]
+
+  @shutdown shuts down the game. It may only be used by Directors and
+  must be typed in full.
+
+  @shutdown/panic performs a panic shutdown of the game, using a seperate
+  database file, not the normal one. It may only be used by God.
+
+  @shutdown/reboot restarts the game without disconnecting the users.
+
+  If the /paranoid switch is added, the shutdown dump will be a paranoid
+  dump (see @dump).
+& @sitelock
+  @sitelock
+  @sitelock/name <name>
+  @sitelock <host-pattern> = <options>[,<name>]
+  @sitelock[/<ban|register>] <host-pattern>
+  @sitelock/check <host>
+  @sitelock/remove <string>
+
+  The @sitelock command adds rules to the access.cnf file, controlling
+  a host's level of access to the MUSH, or adds banned player names to
+  the names.cnf file. Only Directors may use @sitelock.
+
+  @sitelock without arguments lists all sites in access.cnf.
+  Rules are processed in the order listed, and the first matching
+  rule is applied. @sitelock/check tells you which rule will match
+  for a given <host>.
+
+  @sitelock/name adds a name to the list of banned player names.
+  Use !<name> to remove a name from the list.
+
+  @sitelock <host-pattern> = <options>[,<name>] controls the access options
+  for hosts which match <host-pattern>, which may include wildcard
+  characters "*" and "?". See help @sitelock2 for the list of options,
+  and help @sitelock3 for an explanation about the name argument.
+
+  For backward compatibility, @sitelock/ban is shorthand for
+  setting options "!connect !create !guest", and @sitelock/register
+  is shorthand for options "!create".
+
+& @sitelock2
+
+  Sitelock allow/deny options:
+   connect   --  allow this site to connect to non-guest players
+   !connect  --  don't allow this site to connect to non-guest players
+   guest     --  allow this site to connect to guest players
+   !guest    --  don't allow this site to connect to guest players
+   create    --  allow this site to create players
+   !create   --  don't allow this site to create players
+   default   --  allow any of the above
+   none      --  don't allow any of the above
+   !god      --  God can't connect from this site.
+   !Director   --  Directors can't connect from this site.
+   !admin    --  Directors and Royalty can't connect from this site.
+
+  Allow/deny options not set are assumed to be allowed.
+
+  Sitelock special options:
+   register    -- allow this site to use 'register <name> <email>'
+                  at the connection screen to register players.
+                  Players will be emailed their character's password.
+                  This should be used with !create to be effective.
+   suspect     -- set all players who connect from this site SUSPECT.
+   deny_silent -- don't log failed access attempts from this site.
+   regexp      -- Treat the hostname pattern as a regular expression
+                  instead of a wildcard pattern.
+& @sitelock3
+ If you specify a character name after the options, the options
+ are only checked if the host pattern matches, AND the character
+ being checked for connect support matches the one you gave.
+ Use it only with connect and !connect options, since they're
+ the only ones where an existing character is used.
+
+ For example, to disallow anyone from connecting to 'Twink' from
+ one domain, but to allow connections to the character from others,
+ use something like:
+
+ @sitelock *.somesite.com=!connect,Twink
+
+ If you want to disallow connections to a character from anywhere,
+ use @newpassword or @sitelock *=!connect,Twink.
+
+  @sitelock/remove will delete entries that were added with @sitelock
+  if their host-pattern matches <string> exactly.
+& @sql
+  @sql <query>
+
+  This command issues and SQL query if the MUSH supports SQL and
+  can connect to an SQL server. You must be Director or have the
+  Sql_Ok power to use @sql.
+
+  Generally, the sql() function is more useful for coding, as it
+  delimits its return values, but @sql is handy for INSERT-type
+  queries and quick checks. If you pass arbitrary data to @sql,
+  be sure you call sqlescape() on it (see the example in help sql()).
+
+  Example: @sql SHOW TABLES
+
+  See also: sql(), sqlescape()
+& @squota
+  @squota <victim> [= [+|-] <amount>]
+
+  This is a command that is available only if the quota system is enabled.
+  To use this command you need be able to use the 'SetQuotas' power over
+  the <victim> object.  The command reports the victim's owned objects, and
+  sets the maximum number of objects allowed in a division or allowed for a
+  player object.  If no limit is specified, this shows current quota, and 
+  reminds you to set a new one.Using + or - add or remove quota limit from 
+  the player or division relatively to the current quota set on the division
+  or player object.
+
+
+  See Also: @quota
+
+& @startup
+  @startup <object> = <action list>
+
+  Sets the list of actions on <object> that will happen whenever the MUSH
+  is restarted. This lets you start up objects that need to be running 
+  continuously. It is also useful for use with @function. 
+  
+See also: @function, ACTION LISTS
+& @stats
+  @stats [<player>]
+  @stats/table
+  @stats/chunks
+  @stats/regions
+  @stats/paging
+
+  In its first form, display the number of objects in the game broken
+  down by object types.  Directors can supply a player name to count only
+  objects owned by that player.
+
+  In its second form, display statistics on internal tables.
+
+  In the remaining forms, display statistics or histograms about the
+  chunk (attribute) memory system.
+& @success
+  @success <object> [=<message>]. 
+
+  Sets the message that is shown to someone who successfully passes
+  the basic lock of <object>. For things and players, this means picking
+  them up. For exits, this means going through the exit.
+
+  Ex.: @succ Box=You pick up the box.
+       @succ Door=You walk through the door.
+
+See also: @osuccess, @asuccess
+& @sweep
+  @sweep [connected | here | inventory | exits ]
+  @sweep gives you a list of all objects/players that are listening in
+  the room you are currently in, as well as the objects you are
+  carrying. Most objects only listen for a particular string or
+  phrase, so they normally do not pose a problem if you need privacy.
+  You will have to be careful of players and puppets since they will
+  hear everything you say and do. (And might post the same to r.g.m!)
+  AUDIBLE exits are also shown on an ordinary sweep, if the room is
+  also AUDIBLE. (Audible exits aren't active unless the room is audible).
+  The four command options can also be used as switches (i.e., you
+  can use "@sweep/connected" instead of "@sweep connected"). 
+  If the connected flag is given, only connected players and puppets
+  owned by connected players will be shown in the @sweep.
+  The "here" and "inventory" flags check only your location or
+  inventory, respectively. "exits" only checks for AUDIBLE exits.
+& @switch
+  @switch [/<switch>] <string> = <expr1>, <action1> [,<exprN>, 
+                                 <actionN>]... [,<default>]
+  For those of you familiar with programming, this command acts like
+  if/then/else or switch/case. It compares <string> against whatever
+  each <expr> evaluates to. If <string> and <expr> match, the action list
+  associated with that <expr> is carried out. If no match is found, the
+  <default> action list is carried out. 
+
+  The string "#$" in <action>'s will be expanded to the evaluated result
+  of <string> before it is acted on.
+  
+  @switch/all   will carry out the action lists for -all- of the 
+                expressions that match <string>. This is the default.
+  @switch/first will carry out the action list for only the -first-
+                expression that matches <string>. This is the same as
+                @select, and it is less computationally expensive than 
+                /all in many cases.
+  @switch/notify will cause a "@notify me" to be queued after 
+                the selected <action>. 
+  @switch/regexp will cause the expressions to be matched as case-insensitive
+                 regular expressions.
+
+(continued in help @switch2)
+& @switch2
+Examples: 
+    &SWITCH_EX thing = $foo *:@switch %0=*a*,:acks,*b*,:bars,:glurps
+    foo abc
+    > thing acks
+    > thing bars
+    foo xxx
+    > thing glurps
+
+    &SWITCH_EX thing = $foo *:@switch/first %0=*a*,:acks,*b*,:bars,:glurps
+    foo abc
+    > thing acks
+
+    &SWITCH_EX thing = $test:@switch hasflag(%#,PUPPET)=1,"Puppet!,"Not Puppet!
+    test
+    > thing says, "Not Puppet!"
+See also: switch wildcards, @select, switch()
+& @teleport
+  @teleport[/silent][/inside] [<object>=] <room>. 
+
+  Teleports <object> to <room>.  <object> must be a thing; if you do not
+  supply a thing, the object is assumed to be yourself. The destination
+  must be either JUMP_OK or controlled by you, and you must either
+  control <object> or <object>'s current location. Also, the destination,
+  if a room, cannot be teleport-locked against <object>. Mortals cannot
+  teleport Royalty or Directors. If the target room has a drop-to, <object>
+  will go to the drop-to room instead. 
+
+  Privileged players who teleport a player to another player send them
+  to the location of the target, unless the /inside switch is used,
+  in which case they are sent to the inventory of the target.
+
+  Teleportation from a room can be stopped by setting the NO_TEL flag.
+  Royalty and Directors can _always_ teleport to any location, regardless
+  of NO_TEL or teleport locks.
+
+  Teleportation triggers the @oxtport/@tport/@otport/@atport attributes,
+  unless <room> is an exit or the /silent switch is given.
+
+  As a special case, using "home" as the <room> teleports the object
+  to its home.
+
+  See also: JUMP_OK, NO_TEL, @oxtport, @tport, @otport, @atport, @lock
+& @tport
+  @tport <object> [=<message>]
+
+  Sets the <message> shown to <object> when <object> is teleported.
+& @trigger
+  @trigger <object>/<attribute> [=<value 0>,<val. 1>,...,<val 9>]
+
+  @trigger can be used to set off commands stored in an attribute on 
+  an object. It can also pass values to that attribute on the stack 
+  as %0 - %9.
+
+  Example:
+    &GREET me=POSE waves hi.
+    @tr me/GREET
+    > Cyclonus waves hi.
+
+    &GREET me=POSE waves to %0! ; say Hi there, %1.
+    @trig me/GREET = Gears, Arcee
+    > Cyclonus waves to Gears.
+    > You say, "Hi there, Arcee."
+
+(continued in help @trigger2)
+& @trigger2
+  @trigger is very useful for splitting up large commands and for making
+  them neater, but it does cause a time delay in execution, because the 
+  commands are put into the queue a second later. For very commonly-used
+  globals that you want to execute quickly, you should probably avoid using 
+  @trigger. However, in most cases, the time saved by cramming everything 
+  into one attribute is outweighed by the time spent debugging.
+
+& @ulock
+  @ulock <object> = <key>
+
+  This is an abbreviation of @lock/use.
+
+  This lock determines who is allowed to "use" the object or to set off
+  any of the $-commands on the object. 
+
+  Example: if I want everyone but Bob to be able to use my toy, I would
+  "@ulock toy=!*Bob". If I want only Bob to be able to use it, I would
+  "@ulock toy=*Bob".
+
+See also: @lock, locktypes
+& @uptime
+  @uptime[/mortal]
+  
+  This command, for mortals, gives the time until the next database dump.
+  For Directors, it also gives the system uptime (just as if 'uptime' had
+  been typed at the shell prompt) and process statistics, some of which
+  are explained in the next help entry.
+  Directors can use the /mortal switch to avoid seeing the extra process
+  statistics.
+
+  Continued in HELP @UPTIME2
+& @UPTIME2
+  While the exact statistics displayed depends on the operating system
+  of the game's server, typical things might include the process ID,
+  the machine page size, the maximum resident set size utilized (in K),
+  "integral" memory (in K x seconds-of-execution), the number of page 
+  faults ("hard" ones require I/O activity, "soft" ones do not), the
+  number of times the process was "swapped" out of main memory, the
+  number of times the process had to perform disk I/O, the number of
+  network packets sent and received, the number of context switches,
+  and the number of signals delivered to the process.
+
+  Under Linux, memory usage is split into a number of different categories
+  including shared libraries, resident set size, stack size, and some 
+  other figures. Also under linux, more information on signals is printed.
+& @unlink
+  @unlink <exit>
+  @unlink <room>
+
+  The first form of this command unlinks an exit from its destination
+  room. Unlinked exits may be picked up and dropped elsewhere or relinked
+  by anyone else. (Note that relinking an unlinked exit will @chown it to
+  you if you do not already own it.)
+
+  The second form removes the DROP-TO on the room.
+
+See also: @link, DROP-TO
+& @unlock
+  @unlock[/<switch>] <object>
+
+  Removes the lock on <object>. It can take as many switches as @lock can. 
+
+See also: @lock, locktypes
+& @sdin
+  @sdin <division> [=<message>]
+
+  Sets the message that is displayed to someone when they successfully
+  use @sdin to switch into <division>. If =<message> is omitted, the 
+  message is reset to nothing.
+
+  See Also: @sd, @asdin, @sdout
+
+& @sdout
+  @sdout <division> [=<message>]
+
+  Sets the message that is displayed to someone when they successfully
+  leave <division> using @sd.  If =<message> is omitted, the message 
+  is reset to nothing.
+
+  See Also: @sd, @asdout, @sdin
+
+& @use
+  @use <object> [=<message>]
+
+  Sets the message that is displayed to someone who successfully uses 
+  <object>. If =<message> is omitted, the message is reset to nothing.
+
+See also: use, @ouse, @ause, @charges, @runout
+& @uunlock
+  @uunlock <object> = <key>
+
+  Un-use-locks the object. See also: @lock, @ulock
+& @version
+  @version
+  Tells the player the name of the MUSH, which version of the code is 
+  currently running on the system, when it was compiled, and when
+  the last restart was.
+& @verb
+  @verb <victim>=<actor>,<what>,<whatd>,<owhat>,<owhatd>,<awhat>,<args>
+  
+  This command provides a way to do user-defined verbs with associated
+  @attr/@oattr/@aattr groups. Invoking it does the following:
+  
+  <actor> sees the contents of <victim>'s <what> attribute, or
+    <whatd> if <victim> doesn't have a <what>.
+  Everyone in the same room as <actor> sees the contents of
+    <victim>'s <owhat> attribute, with <actor>'s name prepended,
+    or <owhatd>, also with <actor>'s name prepended, if <victim>
+    doesn't have an <owhat>.
+  <victim> executes the contents of his <awhat> attribute.
+  
+  By supplying up to ten <args>, you may pass those values on
+  the stack (i.e. %0, %1, %2, etc. up through %9).
+  
+  See "help @verb2" for more.
+  
+& @verb2  
+  In order to use this command, at least one of the following criterion
+  must apply:
+    1. The object which did the @verb is a Director.
+    2. The object which did the @verb controls both <actor> and <victim>
+    3. The thing which triggered the @verb (such as through a $command on
+       the object which did the @verb) must be <actor>, AND the object
+       which did the @verb must be either privileged or control <victim>
+       or <victim> must be VISUAL.
+  
+  See "help @verb3" for examples.
+  
+& @verb3
+  Example:
+  
+  &VERB_EXAMPLE Test Object=$test:@verb me=%#,TEST,You just tested.,OTEST,
+       just tested the example.,ATEST,%N
+  test
+  > You just tested.
+  > [others see] Cyclonus just tested the example.
+
+  &TEST Test Object=You have just tested this object!
+  &ATEST Test Object=@emit %0 has failed!
+  &OTEST Test Object=tests test object.
+  test
+  > You have just tested this object!
+  > [others see] Cyclonus tests test object.
+  > Cyclonus has failed!
+
+  Another example follows in "help @verb4"
+& @verb4
+  In order to make this into a global command that anyone can use, we
+  need to put it on a Director object in the Master Room. 
+
+  &DO_TEST Global=$test *:@select locate(%#,%0)=#-1,
+                  {@pemit %#=I don't see that here.},
+                  {@verb locate(%#,%0,n)=%#,
+                   TEST,You test [capstr(%0)].,
+                   OTEST,tests [capstr(%0)].,
+                   ATEST}
+  &TEST Example=You test this fun example.
+  &ATEST Example=POSE has been tested!
+  test example
+  > You test this fun example.
+  > [others see] You test Example.
+  > Example has been tested!
+
+See also: USER-DEFINED COMMANDS, STACK, @trigger, @select
+& @wait
+  @wait[/until] <time> = <command_list>
+  @wait <object> = <command_list>
+  @wait[/until] <object>/<time> = <command_list>
+  
+  The basic form of this command puts the command list (a semicolon-separated 
+  list of commands) into the wait queue to execute in <time> seconds. If the
+  /until switch is given, the time is taken to be an absolute value in seconds,
+  not an offset.
+  
+  The second form sets up a semaphore wait on <object>. The enactor will
+  execute <command_list> when <object> is @notified.
+  
+  The third form combines the first two: the enactor will execute
+  <command_list> when <object> is @notified or when <time> passes,
+  whichever happens first.
+  More forms that support semaphores on arbitrary attributes are described in
+  help @wait2
+  See also the help for: SEMAPHORES, @drain, @notify
+& @wait2
+  Normally, a semaphore wait depends on the SEMAPHORE attribute of the object
+  in question. However, it is useful to be able to use other attributes as
+  semaphores, so one object can be used as the blocker for multiple different
+  things at once. Possible attribute names aren't completely arbitrary. See
+  HELP SEMAPHORES5 for details.
+
+  The syntax for these are:
+  @wait <object>/<attribute> = <command list>
+  @wait[/until] <object>/<attribute>/<time> = <command list>
+
+  You cannot do a non-timed semaphore on an attribute with a numeric name,
+  as that is taken as a timeout instead.
+& @wall
+  @wall[/<switch>] <message>
+
+  Only Directors can use this command, which allows the player to shout
+  or pose a message to every player connected. It must be typed in full.
+  It can also take the following switches
+  /emit    : emit a prefixed message to all.
+  /noeval  : Don't evaluate the message.
+
+  You can also use @wall :<pose> to @wallpose.
+
+See also: @wizwall, @rwall
+& @warnings
+  @warnings <object|me>=<warning list>
+
+  If the building warning system is enabled, this command will set
+  the types of warnings which should be reported on an object or
+  to a player. You must control the object to use this command.
+
+  When an object is checked for warnings (via @wcheck by the owner, or
+  automatically), only warnings which are set to be reported on the
+  object will be reported. If no warnings are set on the object, the
+  owner's warning settings will be used. When admin use @wcheck to
+  check non-owned objects, their personal warnings are always used.
+
+  For a list of warnings, see 'help warnings list'
+  See also 'help @wcheck' and 'help NO_WARN'
+
+  For examples, see 'help @warnings2'
+
+& @warnings2
+
+  Example 1: Normal building situations
+  Most people will simply want to leave their @warnings set to "normal"
+  and their objects' @warnings set to "none". They will then receive 
+  normal warnings for all their objects.
+
+  Example 2: Warning-lover
+  People who find warnings very helpful (like heavy builders) may want
+  to set their personal @warnings to "extra" or "all", and keep their
+  objects' warnings at "none". If a specific object should be treated 
+  less strictly, set that object's @warnings differently. If an object
+  shouldn't be warned on at all, set the NO_WARN flag on the object.
+
+(continued in help @warnings3)
+& @warnings3
+  Example 3: Warning-hater
+  People who prefer not to be warned except for specific object may
+  set their personal @warnings to "none" and set the @warnings on
+  those objects to appropriate levels.
+
+  Example 4: I need some peace!
+  Players who @set themselves NO_WARN will receive no warnings ever
+  until they unset the flag.
+
+& @wcheck
+  @wcheck <object>
+  @wcheck/all 
+  @wcheck/me
+
+  This command is enabled if the building warning system is enabled.
+
+  The first form of the command performs warning checks on a specific
+  object. The player must own the object or be see_all. When the owner
+  runs the command, the @warnings of the object are used to determine
+  which warnings to give. If the object has no @warning's set, the
+  @warnings of the owner are used. When a non-owner runs the command,
+  the @warnings of the non-owner are used.
+
+  The second form of the command runs @wcheck on every object in the
+  database and informs connected owners of warnings. It is usually
+  automatically run by the MUSH at intervals. Only Directors may use
+  @wcheck/all.
+
+  The third runs it on all objects the player owns that aren't set NO_WARN.
+
+See also: @warnings, WARNINGS, NO_WARN
+& @whereis
+  @whereis <player>
+
+  If <player> is not set UNFINDABLE, this command will tell you where
+  the player is. It will also inform the player that you attempted to
+  locate their position, and whether you succeeded or not.
+
+  To avoid being found this way, just do: @set me=UNFINDABLE
+
+  Ex: @whereis  Moonchilde
+
+See also: UNFINDABLE, loc()
+& @wipe
+  @wipe <object>[/<attribute pattern>]
+  
+  This command clears attributes from <object>, with the exception of
+  attributes changeable only by Directors, and attributes not controlled by
+  the object's owner (i.e. locked attributes owned by someone else).
+  Only God may use @wipe to clear wiz-changeable-only attributes.
+  The SAFE flag protects objects from @wipe.
+  If no <pattern> is given, this gets rid of all the attributes, with
+  exceptions as given above. If <pattern> is given, it gets rid of
+  all attributes which match that pattern. Note that the restrictions
+  above still apply.
+& @wizwall
+  @wizwall[/emit] <message>
+
+  This wiz-only command works similarly to @rwall or @wall, sending
+  a message in either say, pose, or emit format to all Directors who
+  are currently logged in. 
+
+See also: @wall, @rwall
+& @wizmotd
+  @wizmotd <message>
+
+  This is a Director only command that will set a short temporary message
+  that will be shown to all Directors when they connect. It is listed in
+  @listmotd as the Wiz MOTD. A more permanent message can be set by
+  the siteadmin by editing the file "wiz.txt".
+& @zemit
+  @zemit <zone> = <message>
+
+  Emits a message to all rooms in <zone>. You must have control in that
+  zone in order to use this command. Because it is computationally
+  expensive, it costs 100 pennies.
+& ahelp
+  ahelp [<topic>]
+
+  Shows the current admin help for the MUSH. Only ROYALTY and DirectorS
+  can use this command.
+
+See also: anews
+& anews
+  anews [<topic>]
+
+  Shows the current admin news for the MUSH. Only ROYALTY and DirectorS
+  can use this command.
+
+See also: ahelp
+& brief
+  brief <object> 
+
+  This command works like an abbreviated version of examine. It does not
+  print out all the attributes on the object. 
+
+See also: examine
+& cd
+& ch
+& cv
+  cd <name> <password>
+  ch <name> <password>
+  cv <name> <password>
+
+  Not really a MUSH command, but a command available at the connect
+  screen for Directors. If enabled, Directors who use 'cd' instead of
+  'connect' to connect will be set DARK as they connect, and will not
+  show arrival messages. Their arrival will, however, be broadcast
+  to other admin, but not to MONITOR-flagged mortals.
+
+  ch is a variant of cd that allows Royalty to connect @hidden.
+  cv is a variant that forces a non-dark, non-hidden connection.
+
+See also: DARK, @hide
+& DOING
+  DOING
+
+  This command displays the list of players currently connected to the
+  MUSH. For mortals, it is identical to WHO. For Directors, it displays
+  the WHO in the format mortals see. The Director WHO shows location and
+  host, but does not show @doing messages. DOING shows @doing messages
+  but not location or host.
+
+See also: WHO, @poll, @doing
+& OUTPUTPREFIX
+& OUTPUTSUFFIX
+  OUTPUTPREFIX <string>
+  OUTPUTSUFFIX <string>
+
+  Sets your output prefix or suffix. These strings will be shown
+  before and after the output of any command that you initiate,
+  respectively. They are primarily useful for bots and the like.
+
+& IDLE
+  IDLE
+
+  This command does nothing. It does not reset a connection's
+  idle time. It is useful for people who are connecting from behind
+  a NAT gateway with a short fixed timeout; if you're in this situation,
+  have your client send the IDLE command every minute or so, and the 
+  NAT connection won't time out (but you won't appear, to other players,
+  to be active).
+
+& SESSION
+  SESSION
+
+  This admin-only form of WHO includes information on how much text
+  has been received and sent to the connections. It has three fields:
+
+  Sent, which is the number of characters sent TO the mush from that
+  connection. Recv, the number of characters sent FROM the mush to that
+  connection, and Pend, the number of characters still waiting to be sent
+  from the mush to the connection.
+
+See also: WHO, DOING
+& teach
+  teach <command>
+
+  Shows <command> (unparsed) to others in your room and then causes
+  you to execute <command> as if you'd typed it in directly from 
+  the socket (even if you're an object). Useful for helping newbies:
+
+  > say To do a pose, use :<action>
+  You say "To do a pose, use :<action>"
+  > teach :waves hello.
+  Javelin types --> :waves hello.
+  Javelin waves hello.
+
+  > teach "[sort(c b a)]
+  Javelin types --> "[sort(c b a)]
+  Javelin says, "a b c"
+
+& drop 
+  drop <object>
+
+  Drops <object>, if you are presently carrying it. If the room the object
+  is dropped in has a DROP-TO set, the object may automatically be sent
+  to another location. Some MUSHes may have @lock/drop enabled, which 
+  allows you to set who is permitted to drop the object.
+
+  Note that as of 1.6.6, the TEMPLE flag and SACRIFICING have been 
+  disabled.
+
+See also: STICKY, DROP-TO
+& enter
+  enter <object>
+
+  Used to enter a thing or player. You can only enter an object if 
+  you own it or if it is set ENTER_OK. You must also pass the enter-lock,
+  if it is set. Entering an object triggers is @enter/@oenter/@oxenter
+  messages and its @aenter actions. If you fail the enter-lock, the
+  object's @efail/@oefail/@aefail messages and actions are triggered.
+
+  Insides of objects are best used for vehicles, or storage spaces
+  when you don't have a home. You can describe the interior of an
+  object differently from its exterior by using @idescribe.  
+
+See: @enter, @oenter, @oxenter, @aenter, leave, @lock, @idesc, INTERIORS
+& events
+& rules
+  events [<topic>]
+  rules [<topic>]
+
+  These commands, like news, work the same way as the help command, except
+  that the information provided in them is specific to this particular
+  MUSH. Not all MUSHes will have both or either of these commands enabled.
+
+See also: news, help
+& examine
+  examine[/<switch>] <object>[/<attribute>] 
+  
+  Displays all available information about <object>.  <object> may be an 
+  object, 'me' or 'here'. You must control the object to examine it.  If 
+  you do not own the object, or it is not visible, you will just see the 
+  name of the object's owner.  May be abbreviated 'ex <object>'.  If the 
+  attribute parameter is given, you will only see that attribute (good 
+  for looking at code). You can also wild-card match on attributes. 
+  The * wildcard matches any number of characters except a backtick (`).
+  The ? wildcard matches a single character except a backtick (`).
+  The ** wildcard matches any number of characters, including backticks.
+  For example. to see all the attributes that began with a 'v' you could do 
+  ex <object>/v**
+  
+  The /brief switch is equivalent to the 'brief' command.
+  The /debug switch is Director-only and shows raw values for certa
+    in fields in an object. 
+  The /mortal switch shows an object as if you were a mortal other than
+    the object's owner and is primarily useful to admins. This switch
+    ignores the object's VISUAL flag (but not its attribute flags)
+  The /all switch shows the values of VEILED attributes.
+
+  See also: ATTRIBUTE TREES
+& follow
+  follow <object>
+
+  If you pass the object's follow lock, you begin following it. As the
+  object moves around (except if it @teleports away or goes home), you
+  will automatically move around with it, so long as you pass all the
+  locks and enter/leave locks on the exits and things the object moves
+  through. This doesn't prevent you from going somewhere else on your
+  own.
+
+See also: unfollow, dismiss, desert, followers(), following(), 
+          @follow, @ofollow, @afollow
+& dismiss
+  dismiss <object>
+  dismiss
+
+  The dismiss command stops <object> from following you. If no object
+  is given, it stops everyone from following you.
+
+See also: follow, unfollow, desert, followers()
+& desert
+  desert <object>
+  desert
+  The desert command stops <object> from following you and stops you
+  from following <object>. That is, it's shorthand for 'unfollow <object>'
+  and 'dismiss <object>'. If no object is given, it stops everyone from
+  following or leading you.
+
+See also: follow, unfollow, dismiss, followers(), following()
+& empty
+  empty <object>
+
+  The empty command attempts to move all the contents of <object>
+  to <object>'s location. You must either be holding <object>
+  (in which case the command is like getting <object>'s <item>
+  for each item) or be in the same location as <object> (in which
+  case the command is like getting <object>'s <item> and dropping it).
+
+  The empty command assumes that all <object>'s items pass through the
+  hands of the player running the command. Therefore, the same kinds of
+  locks and messages that are applied in a possessive get (and, possibly,
+  a drop) are applied to each item in <object>. It is therefore possible
+  to fail to empty an object for many reasons, even when you could do so
+  using "extraphysical" methods (teleporting items, forcing the object
+  to drop them, or forcing the items to leave the object.)
+
+See also: get, drop
+& get
+& take
+  get <object>
+  take <object>
+
+  You pick up object, if you pass the object's @lock. You can also do 
+  get <thing>'s <object>, which will fail if either thing is not ENTER_OK 
+  or the object is locked against you. Some MUSHes choose to disable the 
+  ability to take an object in someone else's inventory.
+  
+See also: @lock, ENTER_OK, give, drop, @success, inventory
+& @abuy
+  @abuy <object> = <action list>
+
+  Sets the actions to be taken after a player buys an item
+  from PRICELIST. The item purchased is passed in as %0,
+  and the amount paid as %1
+
+  See also: buy, @buy, @obuy, @pricelist, MONEY, ACTION LISTS
+& @buy
+  @buy <object> [=<message>]
+
+  Sets the message that is shown to anyone who buys something from
+  the object, using the 'buy' command. The item purchased is passed
+  in as %0, and the amount paid as %1.
+
+  See also: buy, @abuy, @obuy, @pricelist, MONEY
+& @obuy
+  @obuy <object> [=<message>]
+
+  Sets the message that will be show to others whenever someone buys
+  an item from the object's PRICELIST using the 'buy' command. The
+  name of the person giving the money will be automatically inserted at
+  the beginning of the message. The item purchased is passed in as %0,
+  and the amount paid as %1.
+
+  See also: buy, @abuy, @obuy, @pricelist, MONEY
+& @pricelist
+  @pricelist <object>=<item1>:<price1>[,<price2>][ <item2>:...]
+
+  The PRICELIST attribute is a space-delimited list of item names
+  and prices that are checked when the 'buy' command is run.
+  
+  An item name may have '_'s where the player would use a space in
+  the name.
+
+  A price is either a number (20), a range of numbers (10-30), or
+  a minimum number (10+).
+
+  ex:
+    @PRICELIST vendor=mansion:1000+ large_house:100-200 house:20,30,50
+
+  See also: buy, @abuy, @buy, @obuy, MONEY
+& buy
+  buy <item>[ from <vendor>][ for <cost>]
+
+  When you try buying an item, PRICELIST attributes on nearby objects
+  (or <vendor> if given) will be checked for matching item:costs. If
+  <cost> is given, the first item that matches that cost will be purchased.
+  Otherwise, the first matching item that you can afford will be purchased.
+
+  If the pricelist match contains a list of prices,
+  ITEM:30,20,10, the first one you can afford will be the resulting price.
+
+  ex:
+  > &PRICELIST vendor=coke:20 pepsi:20
+  > &drink.coke vendor=You enjoy a delicious coke.
+  > &drink.pepsi vendor=It tastes like a funny coke.
+  > @BUY vendor=u(drink.%0)
+  > buy coke
+  You enjoy a delicious coke.
+
+  See also: @ABUY, @BUY, @PRICELIST
+& give
+  give[/silent] <player>=<number>
+  give[/silent] <number> to <player>
+  give <player>=<object>
+  give <object> to <player>
+
+  Gives player the specified <number> of pennies or <object>. You can't give 
+  someone pennies if their new total would be greater than 100000 pennies. 
+  (No reason to get greedy).  The /silent switch suppresses the default 
+  message indicating how many pennies were given. Directors may "give"
+  a negative number of pennies.
+
+  You may also give players objects, but the other player must be set
+  ENTER_OK in order to receive something you give.
+
+  Giving money to an object gives the money to the object's owner. Some
+  MUSHes may have @lock/give enabled, which determines who is allowed
+  to give an object.
+
+See also: @pay, @cost, @lock, inventory, @receive, @give
+& go
+& goto
+& move
+  go[to] <direction> 
+  go[to] home
+  move <direction>
+  move home
+
+  Goes in the specified direction. <Direction> can be the name or alias of
+  an exit in your area, the enter alias of an object in your area, or
+  the leave alias of the object you are in. You do not need to use the
+  word 'go' or 'move', in fact -- simply typing the direction will have the
+  same effect.
+
+  'go home' is a special command that returns you to your home room/object.
+  Some MUSHes may disable this command.
+
+See also: HOME, @link, @ealias, @lalias, EXITS
+& index
+  index
+
+  A file similar to news, often indexing the news and events files.
+  May not be available on some MUSHes.
+& INFO
+  INFO
+
+  This command returns some information about the MUSH you are on, such
+  as its version number, time of last restart, number of players currently
+  connected, and size of database. It can be issued from the connect
+  screen.
+& inventory
+  inventory
+
+  Lists what you are carrying. Can be abbreviated by just 'i', or 'inv'.
+  It also tells you how much MUSH money you have. If you are not set
+  OPAQUE, others will also be able to see what is in your inventory by
+  looking at you.
+
+  Note that on some MUSHes it is possible to take things that are in 
+  someone else's inventory. To be safe, @lock any objects that you do
+  not want to lose.
+  
+See also: score, take, drop, OPAQUE, @lock
+
+& leave
+  leave
+
+  The command leave allows you to exit an object you have enter'ed 
+  into. When you leave an object, its @leave/@oleave/@oxleave messages
+  are triggered, and its @aleave actions are triggered. 
+
+  The NO_LEAVE flag may be enabled on some MUSHes. Objects set with this
+  flag cannot be left. @lock/leave may also be enabled on some MUSHes,
+  which allows you to set who can leave the object. If you fail to
+  leave, the object's @lfail/@olfail/@alfail messages/actions will be
+  triggered.
+
+See also: enter, @leave, @lfail, @lock, INTERIORS 
+& LOGOUT
+  LOGOUT
+
+  LOGOUT is similar to QUIT, but instead of disconnecting you from the
+  game completely, it merely disconnects you from your current
+  character and returns you to the opening welcome screen. This is
+  useful if you want to disconnect and then reconnect to another
+  character. Unlike most commands, it is case-sensitive and must be
+  typed in all caps.
+& look
+& read
+  look [<object>]
+  look <person>'s <object>
+  look/outside
+
+  Displays the description of <object>, or the room you're in if you don't 
+  name a specific object.  You can specify object as <name> or #<dbref> or 
+  'me' or 'here'. On some MUSHes, 'read' may work the same as 'look'. The 
+  second form of this command allows you to look at objects held by other 
+  people, if the person is not OPAQUE.
+    
+  Look can take one switch, "outside". look/outside allows you to
+  look at objects outside your current location, as long as your
+  immediate location is not a room, and is not OPAQUE.
+
+(continued in help look2)
+& look2
+  If you look at an object that is not set OPAQUE, you will see any non-DARK
+  items in its inventory. You can look at DARK items in your location if 
+  you know what their name is by typing 'look <object>', but they will
+  not show up in the list of contents.
+
+  When you type 'look' alone, you look at your current location. For a room,
+  this normally shows you the room's description, the list of contents, and
+  any obvious exits from the room. For an object, it only shows you the
+  interior description (@idescribe).
+  
+  If a room is set DARK, when you look you will not see any of the exits
+  or contents of the room, unless they are set LIGHT. 
+
+  'look' may be abbreviated 'l'.
+  
+See also: OPAQUE, FLAGS, @describe, @adescribe, @odescribe, DARK, LIGHT
+& news
+  news [<topic>]
+
+  The news system works just like the help system. Many MUSHes use it to
+  provide standard information on the rules, theme, and customized
+  commands of the particular MUSH. It is highly recommended that you 
+  read it regularly.
+& page 
+  page[/<switch>] [<player-list>=]<message>.
+
+  This command sends a message to a player or list of players. If
+  the player's name contains spaces, surround it with double-quotes.
+  If you have already paged someone since connecting, just typing:
+
+    page <message>
+
+  will send the message to the last person paged. You cannot page a 
+  player if they are set HAVEN or if you do not pass their @lock/page.
+  In the latter case, the player's PAGE_LOCK`FAILURE, PAGE_LOCK`OFAILURE,
+  and PAGE_LOCK`AFAILURE attributes will be activated if set.
+
+  Examples:
+    > page airwolf=hi there!
+    You paged Airwolf with 'hi there!'.
+    > page see, I don't have to retype the name.
+    You paged Airwolf with 'see, I don't have to retype the name.'.
+    > page "John Lennon" Ringo=Paul's fine!
+
+(continued in help page2)
+& page2
+  Page will attempt a partial match on the name, checking both for an
+  @alias and to see if the name matches someone connected. If the first
+  character of <message> is a : or a ;, it will send the page in pose
+  format.  
+
+  Objects may page players, but not vice versa. If an object pages
+  a NOSPOOF player, that player will see the object's number in
+  square brackets, in front of the message, in a fashion similar to
+  the way NOSPOOF flags emits.
+
+  Page takes five switches: /list, /blind, /noeval, /override, and /port. 
+  The /list and /blind switches provide two different ways of handling
+  pages to lists of players. With /list, each paged player sees the
+  list of who was paged, and the paging player is notified only once.
+  With /blind, it's as if each player was individually paged. 
+  The default list behavior is set by the blind_page @config option.
+  The /noeval switch prevents the MUSH from evaluating the message.
+  The /override switch is admin-only, and overrides pagelocks and HAVEN.
+  The /port switch is admin-only, and will page a port descriptor directly, 
+  including connections that have not yet logged into a player.
+  
+See also: @lock, @alias, pose, :, ;, HAVEN, NOSPOOF, FLAGS
+& :
+& ;
+& pose
+& semipose
+  pose[/noeval] <action pose>
+  :<action pose>
+
+  pose[/nospace] <action pose>
+  semipose[/noeval] <action pose>
+  ;<action pose>
+  Displays your name followed by the statement you posed.  May be abbreviated
+  by the ':' symbol. Normally, a space is placed between your name and the
+  action you are posing. If you do not wish to have a space inserted, use
+  the /nospace switch, the 'semipose' command, or the ';' symbol and don't
+  add a space between the ; and the <action pose>.
+  The /noeval switch prevents the pose from being evaluated by
+  the parser.
+
+(continued in help pose2)
+& pose2
+  Examples:
+    If you are player Bob, and you type in ':laughs out loud.' or
+    'pose laughs out loud.' everybody in the room will see:
+    "Bob laughs out loud." 
+  
+    If you are player Bob, and you type in ';'s smiling today.' or
+    'pose/nospace 's smiling today.' everybody in the room will see:
+    "Bob's smiling today."
+& "
+& say
+  say <message>
+  say/noeval <message>
+  "<message>
+
+  Says <message> out loud. The message will be enclosed in double-quotes.
+  A single double-quote is the abbreviation for this common command.
+
+  Say takes one switch, /noeval. The /noeval switch prevents any evaluation 
+  of the message, and is handy when you want to say things that use special 
+  characters like % or []'s.
+
+  See also: whisper, pose
+& score
+  score
+  
+  Displays how many pennies you are carrying.  Helpful to see if
+  any machines are looping. If they are, your pennies will be being 
+  rapidly drained. MUSH money may also be used for other purposes in
+  the game.
+  
+See also: LOOPING, @ps, QUEUE, MONEY
+
+& think
+  think <message>
+
+  You can use this command to send a private message to yourself. Pronoun
+  substitution is performed. This is essentially equivalent to doing a
+  "@pemit me=<message>", but with "think", there's no prepended text.
+  One possible use: @adesc me=think %N just looked at you.
+& QUIT    
+  QUIT
+
+  Log out and leave the game. Must be in all capitals.  
+& unfollow
+  unfollow
+  unfollow <object>
+
+  This command stops you from following an object that you were formerly
+  following. If no object is given, you stop following everyone you
+  were following.
+
+See also: follow, dismiss, desert, followers(), @follow, @ofollow, @afollow
+& use
+  use <object>
+
+  Attempts to use an object, triggering its @use/@ouse/@ause attributes.
+  The person using the object must pass its uselock; no inheritance check
+  is necessary. This is may be done remotely, by using a dbref number;
+  it provides an easy way for non-TRUST objects to perform commands on
+  TRUST objects.
+
+  If the person fails to pass the object's uselock, the object's
+  @ufail/@oufail/@aufail attributes are triggered.
+
+See also: @use, @ouse, @ause, @charges, @runout
+& whisper 
+  whisper <player>=<message>
+  whisper/silent <player>=<message>
+  whisper/noisy <player>=<message>
+  whisper/noeval <player>=<message>
+  whisper/list <players>=<message>
+
+  Whispers the message to the named person, if they are in the same
+  room as you. No one will see the message you whisper.  You can also
+  whisper to things you are carrying, or to things that are carrying
+  you. whisper <player>=:<pose> also works, in a fashion similar to
+  page-pose.
+
+  In a noisy whisper, other players in the room may be told who you whispered
+  to (but not what you whispered): Polgara whispers to Javelin.
+  The probability that a noisy whisper will be heard aloud is configurable.
+  In a silent whisper, other players will not know that you whispered.
+  The default whisper may be configured either silent or noisy (check @config)
+
+(continued in help whisper2)
+& whisper2
+  The /noeval switch prevents any evaluation of the message, and
+  is handy when you want to say things that use special characters
+  like % or []'s.
+
+  The /list switch lets you whisper to many people at once. Multiple
+  persons should be space-separated, and names with spaces should be
+  enclosed in double-quotes.
+& WHO
+  WHO   
+
+  Displays a list of players currently connected to the MUSH.
+  The WHO tells you how long a player has been on and how long they
+  have been inactive. Unlike most commands, it is case sensitive.
+
+See also: @doing, @poll, DOING
+& with
+  with[/room] <obj>=<command>
+
+  Attempts to run a user-defined command on a specific object.
+  If the /room switch is given, <obj> must be a room, and its contents
+  are checked for commands as if it was a master room.
+
+  See 'help USER-DEFINED COMMANDS'.
diff --git a/game/txt/hlp/cobra_code.hlp b/game/txt/hlp/cobra_code.hlp
new file mode 100644 (file)
index 0000000..b42de47
--- /dev/null
@@ -0,0 +1,197 @@
+& code
+& contact
+PennMUSH is developed by a team of developers whose names are
+listed in 'help changes'. It is maintained by Javelin, aka
+Alan Schwartz.  Suggestions, comments, and bug reports are welcome:
+
+Report bugs at:               pennmush-bugs@pennmush.org
+Comments/suggestions to:      pennmush-developers@pennmush.org
+Reach Javelin at:             dunemush@pennmush.org
+
+For information about downloading PennMUSH, see 'help download'.
+For information about changes in versions of the code, see 'help changes'.
+
+& download
+The latest version of this MUSH code is available at
+http://download..pennmush.org/Source.  It will be called something like
+pennmush-1.7.6p0.tar.Z, depending on the version number. You
+will also find files of translations of server messages into various
+languages in Source/translations/<version>. See help i18n
+for more.
+
+Also on that site is the MUSH manual, in /Manuals. It should be called
+mushman.2.008.tar.Z or something similar; you should attempt to get at
+least version 2.007.  Also on that site is Javelin's Guide for PennMUSH
+Gods, in /pub/PennMUSH/Guide. A better way to read it is on the Web at
+http://www.pennmush.org/~alansz/guide.html
+
+& i18n
+& internationalization
+& locale
+& translation
+Internationalization support in PennMUSH includes:
+* Support for (8-bit) locale-based character sets, including
+  translation of iso-8859-1 accented characters to html entities for
+  Pueblo, the accent() and stripaccents() functions, and the NOACCENTS
+  flag.
+* Support for locale-based date/time formats
+* Support for locale-based message sets for translations of server
+  messages. There are active translation teams (and you can join!)
+  and several languages have practically complete translation files
+  available from the PennMUSH ftp site.
+* Some support for locale-based string collation
+* The ability to alias command and function names, so you can generate
+  a set of translated commands/functions.
+
+Most of these features get enabled by setting an appropriate environment
+variable in the PennMUSH restart script.
+
+& copyright
+& copyrite
+& license
+Copyright, License, and Credits for PennMUSH 1.x. Revised October 2002.
+
+I. Copyrights
+
+PennMUSH 1.x
+Copyright (c) 1995-2002, Alan Schwartz, T. Alexander Popiel, Shawn Wagner
+Contact email for Alan Schwartz: <dunemush@pennmush.org>. 
+
+Some code used in this server may have been derived from the
+TinyMUSH 2.2 source code, with permission. TinyMUSH 2.2 is
+Copyright (c) 1994-2002, Jean Marie Diaz, Lydia Leong, and Devin Hooker.
+
+Some code used in this server may have been directive from TinyMUSH 2.0.
+Copyright (c) 1995, Joseph Traub, Glenn Crocker.
+
+Some code used in this server may have been directive from TinyMUD.
+Copyright (c) 1995, David Applegate, James Aspnes, Timothy Freeman
+and Bennet Yee.
+
+ *------------------------------------------------------------------------*
+
+II. License
+
+Because PennMUSH includes parts of multiple works, you must comply
+with all of the relevant licenses of those works. The portions derived
+from TinyMUD/TinyMUSH 2.0 are licensed under the following terms:
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that: (1) source code distributions
+  retain the above copyright notice and this paragraph in its entirety, and
+  (2) distributions including binary code include the above copyright 
+  notice and this paragraph in its entirety in the documentation or other 
+  materials provided with the distribution.  The names of the copyright 
+  holders may not be used to endorse or promote products derived from 
+  this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+  WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+The portions derived from TinyMUSH 2.2 are used under the Artistic
+License. The Artistic License is also the license under which you
+are granted permission to copy and modify PennMUSH:
+
+The Artistic License
+
+Preamble
+
+The intent of this document is to state the conditions under which a
+Package may be copied, such that the Copyright Holder maintains some
+semblance of artistic control over the development of the package,
+while giving the users of the package the right to use and distribute
+the Package in a more-or-less customary fashion, plus the right to make
+reasonable modifications.
+
+Definitions:
+
+"Package" refers to the collection of files distributed by the Copyright
+Holder, and derivatives of that collection of files created through
+textual modification.
+"Standard Version" refers to such a Package if it has not been modified,
+or has been modified in accordance with the wishes of the Copyright
+Holder.
+"Copyright Holder" is whoever is named in the copyright or copyrights
+for the package.
+"You" is you, if you're thinking about copying or distributing this Package.
+"Reasonable copying fee" is whatever you can justify on the basis of media
+cost, duplication charges, time of people involved, and so on. (You will
+not be required to justify it to the Copyright Holder, but only to the
+computing community at large as a market that must bear the fee.)
+"Freely Available" means that no fee is charged for the item itself,
+though there may be fees involved in handling the item. It also means
+that recipients of the item may redistribute it under the same conditions
+they received it.
+
+1. You may make and give away verbatim copies of the source form of the
+Standard Version of this Package without restriction, provided that
+you duplicate all of the original copyright notices and associated
+disclaimers.
+
+2. You may apply bug fixes, portability fixes and other modifications
+derived from the Public Domain or from the Copyright Holder. A Package
+modified in such a way shall still be considered the Standard Version.
+
+3. You may otherwise modify your copy of this Package in any way, provided
+that you insert a prominent notice in each changed file stating how and
+when you changed that file, and provided that you do at least ONE of
+the following:
+
+ a) place your modifications in the Public Domain or otherwise make them
+ Freely Available, such as by posting said modifications to Usenet or an
+ equivalent medium, or placing the modifications on a major archive site
+ such as ftp.uu.net, or by allowing the Copyright Holder to include your
+ modifications in the Standard Version of the Package.
+
+ b) use the modified Package only within your corporation or organization.
+
+ c) rename any non-standard executables so the names do not conflict with
+ standard executables, which must also be provided, and provide a separate
+ manual page for each non-standard executable that clearly documents how
+ it differs from the Standard Version.
+
+ d) make other distribution arrangements with the Copyright Holder.
+
+4. You may distribute the programs of this Package in object code or
+executable form, provided that you do at least ONE of the following:
+
+ a) distribute a Standard Version of the executables and library files,
+ together with instructions (in the manual page or equivalent) on where
+ to get the Standard Version.
+
+ b) accompany the distribution with the machine-readable source of the
+ Package with your modifications.
+
+ c) accompany any non-standard executables with their corresponding
+ Standard Version executables, giving the non-standard executables
+ non-standard names, and clearly documenting the differences in manual
+ pages (or equivalent), together with instructions on where to get the
+ Standard Version.
+
+ d) make other distribution arrangements with the Copyright Holder.
+
+5. You may charge a reasonable copying fee for any distribution of
+this Package. You may charge any fee you choose for support of this
+Package. You may not charge a fee for this Package itself. However, you
+may distribute this Package in aggregate with other (possibly commercial)
+programs as part of a larger (possibly commercial) software distribution
+provided that you do not advertise this Package as a product of your own.
+
+6. The scripts and library files supplied as input to or produced as
+output from the programs of this Package do not automatically fall under
+the copyright of this Package, but belong to whomever generated them,
+and may be sold commercially, and may be aggregated with this Package.
+
+7. C or perl subroutines supplied by you and linked into this Package
+shall not be considered part of this Package.
+
+8. The name of the Copyright Holder may not be used to endorse or
+promote products derived from this software without specific prior
+written permission.
+
+9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+
+The End
diff --git a/game/txt/hlp/cobra_conf.hlp b/game/txt/hlp/cobra_conf.hlp
new file mode 100644 (file)
index 0000000..abf43eb
--- /dev/null
@@ -0,0 +1,230 @@
+& @config parameters
+ Many of the mush's run-time options can be set from the game by
+ wizards, using @config/set <option>=<new value>. Those that can be set
+ with visible changes are listed below, grouped by category. See help
+ @config <category> for details on each.
+
+  Attribs      Chat       Cmds        Cosmetic      Costs
+  Db           Dump       Funcs       Limits        Log
+  Net          Tiny
+
+ The categories and groups are the same as those used by @config/list.
+ Values must be of the listed type for each option. They include:
+ <number>, <dbref>, <boolean> (Yes/No), <time>, or <string>.
+& @config attribs
+ These options control some attribute behavior.
+
+  adestroy=<boolean>: Is the @adestroy attribute used?
+  amail=<boolean>: Is the @amail attribute used?
+  player_listen=<boolean>: Is @listen checked on players?
+  player_ahear=<boolean>: Is @ahear triggered on players?
+  room_connects=<boolean>: Are @aconnect and @adisconnect triggered on rooms?
+  global_connects=<boolean>: Are @aconnect and @adisconnect triggered on
+   objects in the master room?
+  read_remote_desc=<boolean>: Can anyone remotely retrieve @descs?
+& @config chat
+ These options control chat system settings.
+
+  chan_cost=<number>: How many pennies a channel costs to create.
+  max_channels=<number>: How many channels can exist total.
+  max_player_channels=>number>: How many channels can each non-admin
+   player create? If 0, mortals cannot create channels.
+& @config cmds
+ These options affect command behavior.
+
+  globals=<boolean>: Is the master room checked for commands and exits?
+  noisy_whisper=<boolean>: Does whisper default to whisper/noisy?
+  possessive_get=<boolean>: Does "get container's object" work?
+  possessive_get_d=<boolean>: Does it work on disconnected players?
+  link_to_object=<boolean>: Can exits have objects as their destination?
+  owner_queues=<boolean>: Are command queues kept per-owner, or per-object?
+  restricted_building=<boolean>: Does it take the builder @power to build?
+  free_objects=<boolean>: If restricted_building is on, can @create be used
+   without the builder @power?
+  full_invis=<boolean>: Should say by a dark player show up as
+   'Someone says,'?
+  wiz_noaenter=<boolean>: If yes, dark players don't trigger @aenters.
+  player_locate=<boolean>: Can mortals find the locations of remote players?
+  cemit_power=<boolean>: Should the cemit @power control @cemit?
+  really_safe=<boolean>: Does SAFE prevent @nuking?
+  hate_dest=<boolean>: Does @destroy work?
+  destroy_possessions=<boolean>: When a player is destroyed, are their objects
+   as well?
+& @config cosmetic
+ These are cosmetic options of various sorts.
+
+  money_singular=<string>: What is one penny called?
+  money_plural=<string>: What are many pennies called?
+  player_name_spaces=<boolean>: Can player names have spaces in them?
+  ansi_names=<boolean>: Are names in look hilighted?
+  ansi_justify=<boolean>: Do ljust() and rjust() take ansi codes into
+   account?
+  float_precision=<numbers>: How many digits after the decimal point in
+   floating point numbers are kept when formatting the result of a
+   floating point function?
+  comma_exit_list=<boolean>: Do exits show up like North, East, and West
+   or as North East West?
+  count_all=<boolean>: Does the count of connected players in WHO include
+   hidden connections for mortals?
+  blind_page=<boolean>: Does page default to page/blind?
+
+Continued in help @config cosmetic2
+& @config cosmetic2
+ More cosmetic options.
+
+  page_aliases=<boolean>: Are aliases included in page listings?
+   For example, Foo(F) pages: Blah
+  flags_on_examine=<boolean>: Are flag names included when examining
+   objects?
+  ex_public_attribs=<boolean>: Show visual attributes when examining objects
+   you don't control?
+  wizwall_prefix=<string>: Prefix for @wizwall messages.
+  rwall_prefix=<string>: Prefix for @rwall messages.
+  wall_prefix=<string>: Prefix for @wall messages.
+  announce_connects=<boolean>: Should (dis)connects be announced to 
+    non-MONITOR players and to channels?
+  chat_strip_quote=<boolean>:  Does +chan "foo strip the "?
+  newline_one_char=<boolean>: Is strlen(%r) equal to 1?
+  only_ascii_in_names=<boolean>: Names are ascii-only or are extended
+   characters permitted?
+& @config costs
+ These options control how many pennies various things cost.
+
+  object_cost=<number>: How many pennies it costs to create an object.
+  exit_cost=<number>: How many pennies it costs to create an exit.
+  link_cost=<number>: How many pennies it costs to use @link.
+  room_cost=<number>: How many pennies it costs to @dig a room.
+  queue_cost=<number>: How many pennies it costs to queue a command.
+   Refunded when the command executes.
+  quota_cost=<number>: How much @quota goes down by for each object.
+  find_cost=<number>: How many pennies it costs to use @search, @find,
+   @entrances, and their function versions.
+  page_cost=<number>: How many pennies it costs to use page.  
+& @config db
+ These are database options.
+
+ player_start=<dbref>: What room newly created players are in.
+ master_room=<dbref>: The location of the master room.
+ ancestor_room=<dbref>: If set to a good object, this is considered a global
+  parent for all rooms. If -1 or a nonexistant object, then disabled.
+ ancestor_exit=<dbref>: As ancestor_room for exits.
+ ancestor_thing=<dbref>: As ancestor_room for things.
+ ancestor_player=<dbref>: As ancestor_room for players.
+ base_room=<dbref>: The starting room used to determine if other rooms
+  are disconnected.
+ default_home=<dbref>: The room to send things to when they're homeless.
+ exits_connect_rooms=<boolean>: Is a room with any exit at all in not
+  considered disconnected for FLOATING checks?
+& @config dump
+ These options affect database saves and other periodic checks.
+
+ The ones that take times will accept the time as either a
+ plain number, or a number with a suffix 's' for seconds or 'm' for
+ minutes or 'h' for hours.
+
+  forking_dump=<boolean>: Does the game clone itself and save in the
+   copy, or just pause while the save happens?
+  dump_message=<string>: Notification message for a database save.
+  dump_complete=<string>: Notification message for the end of a save.
+  dump_warning_1min=<string>: Notification one minute before a save.
+  dump_warning_5min=<string>: Notification five minutes before a save.
+  dump_interval=<time>: Seconds between database saves.
+  warn_interval=<time>: Seconds between automatic @wchecks.
+  purge_interval=<time>: Seconds between automatic @purges.
+  dbck_interval=<time>: Seconds between automatic @dbcks.
+& @config funcs
+ These options affect the behavior of some functions.
+
+ haspower_restricted=<boolean>: Is haspower() available only to objects
+  with see_all?
+ safer_ufun=<boolean>: Are objects stopped from evaluting attributes on
+  objects with more privileges than themselves?
+& @config limits
+ Limits and other constants.
+
+ The ones that take times will accept the time as either a
+ plain number, or a number with a suffix 's' for seconds or 'm' for
+ minutes or 'h' for hours.
+
+  max_dbref=<dbref>: The highest dbref an object can have. If 0,
+   there is no limit on database size.
+  max_attrs_per_obj=<number>: The maximum attributes an object can have.
+  max_logins=<number>: The maximum number of connected players.
+  max_guests=<number>: The maximum number of connected guests. If 0, 
+   no limit. If -1, limited by the number of guest players in the db.
+  idle_timeout=<time>: The number of minutes a connection can be idle
+   before getting booted. 0 means no limit.
+  unconnected_idle_timeout=<time>: The number of minutes a connection can be 
+   sitting at the login screen before getting booted. 0 means no limit.
+
+Continued in help @config limits2
+& @config limits2
+ Limits and constants, continued.
+
+  whisper_loudness=<number>: The percentage chance of a whisper/noisy
+   being heard.
+  starting_quota=<number>: How much quota new players get.
+  starting_money=<number>: How many pennies new players get.
+  paycheck=<number>: How many pennies players get each day they log on.
+  max_pennies=<number>: The maximum pennies an object can have.
+  mail_limit=<number>: How many @mail messages someone can have.
+  max_depth=<number>: How deep can @parent chains can go.
+  player_queue_limit=<number>: The number of commands a player can have
+   queued at once.
+  queue_loss=<number>: One in <number> times, queuing a command will cost
+   an extra penny that doesn't get refunded.
+  queue_chunk=<number>: How many queued commands get executed in a row when
+   there's no network activity pending.
+
+Continued in help @config limits3
+& @config limits3
+ Limits and constants, continued.
+
+  active_queue_chunk=<number>: How many queued commands get executed in a
+   row when there is network activity pending.
+  function_recursion_limit=<number>: The depth to which softcode functions
+   can call more functions.
+  function_invocation_limit=<number>: The maximum number of softcode
+   functions that can be called in one command.
+  guest_paycheck=<number>: How many pennies guests get each day.
+  max_guest_pennies=<number>: The maximum pennies a guest can have.
+  player_name_len=<number>: The maximum length of a player name.
+  queue_entry_cpu_time=<number>: The maximum number of milliseconds a
+   queue entry can take to run.
+  use_quota=<boolean>: Controls if quotas are used to limit the number
+   of objects a player can own.
+& @config log
+ These options affect logging.
+
+  log_commands=<boolean>: Are all commands logged?
+  log_huhs=<boolean>: Are commands that produce Huh? messages logged?
+  log_forces=<boolean>: Are @forces of wizard objects logged?
+  log_walls=<boolean>: Are @walls logged?
+& @config net
+ Networking and connection-related options.
+  mud_name=<string>: The name of the mush for mudname() and @version and
+   the like.
+  use_dns=<boolean>: Are IP addresses resolved into hostnames?
+  use_ident=<boolean>: Is ident information looked up for connections?
+  ident_timeout=<time>: How many seconds does the mush wait before an ident
+    request fails?
+  logins=<boolean>: Are mortal logins enabled?
+  player_creation=<boolean>: Can CREATE be used from the login screen?
+  guests=<boolean>: Are guest logins allowed?
+  pueblo=<boolean>: Is Pueblo support turned on?
+  sql_platform=<string>: What kind of SQL server are we using? 
+                         ("mysql" or "disabled")
+  sql_host=<string>: What is the hostname or ip address of the SQL server
+& @config tiny
+ Options that help control compability with TinyMUSH servers.
+
+  null_eq_zero=<boolean>: Is a null string where a number is expected
+   considered a 0?
+  tiny_booleans=<boolean>: Use Tiny-style boolean values where only
+   non-zero numbers are true.
+  tiny_trim_func=<boolean>: Are the second and third arguments to trim()
+   reversed?
+  tiny_math=<boolean>: Is a string where a number is expected considered
+   a 0?
+  silent_pemit=<boolean>: Does @pemit default to @pemit/silent?
diff --git a/game/txt/hlp/cobra_division.hlp b/game/txt/hlp/cobra_division.hlp
new file mode 100644 (file)
index 0000000..e034fe5
--- /dev/null
@@ -0,0 +1,751 @@
+& division
+& division system
+CobraMUSH Division System
+------------------------------------------------------
+       The Division system in use works much like zone trees in that it allows 
+for commands to be placed on the division object and those commands are 
+accessible to people attached to the division (or parent divisions). Depending 
+on how your division tree is setup a great deal of control can be maintained 
+over who can do what to what. Below we'll use a sample division tree and 
+briefly run through it.
+
+                           Master Division
+                                  |
+                       Master Builder Division
+                                  |
+              ____________________ ________________________
+             /                    |                        \
+        Empire 1              Empire 2                 Empire 3
+            |                     |                        |
+     ------- -------           ---^---          ------- ---^--- -------
+   /        |       \         /       \        /       /       \       \
+ Branch1 Branch2 Branch3   Branch1 Branch2  Branch1 Branch2 Branch3 Branch4
+
+       Above you see a standard setup of the division system used to represent 
+various empires and organizations within them. The primary reason for this 
+setup is security. Through the Division System powers, class levels and power 
+levels grant complete control. With this control you also gain the ability 
+to grant division leaders and empire heads and people in similiar positions the 
+powers they need to best perform their duties. 
+       For example we have Player1 who is attached to Branch1 of Empire1. This 
+player regardless of the powers given to him wouldnt be able to affect anyone 
+outside of Branch1. However following that example we have Player2 who is the 
+EmpireDirector of Empire1. With the powers granted to an empire leader Player2 
+will be able to examine, modify and in many other ways help to insure that 
+Empire1 is run as smoothly as possible.
+
+See Also:
+  POWERS LIST                             DIVISION COMMANDS
+  DIVISION CREDITS                        DIVISION FUNCTIONS
+  DIVISION TUTORIAL      
+
+& DIVISION TUTORIAL
+Your Division and You: Setting it up 101
+
+Ok, you've got a brand new division and you're wondering what you should do with
+it? Well you arent the first so here is a simple step by step guide to handling
+the Division System. This guide here assumes that you are using the SCed
+interface written by Jamie.
+
+<1> First off we create the division object (if you havent already done so or
+    been given one). We use the following command to do this
+
+@div/create Example Division
+
+<2> Next we want to define the max class level and max power level that will
+    be available within this division. Note, you can not set these values
+    above your current class level and power level. We do this with the next
+    set of commands
+
+@level Example Division=29
+@powerlevel Example Division=29
+
+
+NOTE: Powerlevels are obsolete refer to help @powergroup 
+
+Continued in 'help division tutorial2'
+
+& DIVISION TUTORIAL2
+
+<3> In step three we now decide what we want our classes to be called within
+    our division. You'll know via the @reclass/list command that 29 default
+    classes are already defined. This however doesnt mean you cant make up
+    your own. In fact one of the most powerfull and flexible features of
+    the Division System the option of custom classes for ANY division.
+
+ &class_list Example Division=Head_Cheese Top_Dog Sailor Wanderer Guestie
+   You'll note that I didnt use any spaces except between classes. At the
+   time of this writting the division system does not support class names
+   with spaces in them.
+
+<4> Now that you have your first class list defined we move onto the next
+    step and that's to define the level for each class. This step is very
+    important in that it defines half of the power system. For example if
+    a class level 15 guy has the Modify power then he'll be able to modify
+    anyone of lower class within his division scope. The format for setting
+    the class levels is very simple and I'll repeat the class_list just so
+    you can see the pattern.
+
+ &class_list Example Division=Head_Cheese Top_Dog Sailor Wanderer Guestie
+ &class_level Example Division=29           25     15       5       1
+
+You dont have to have them spaced out like I do there, but one space must
+exist between each of your rank numbers. I just spaced it out so you could
+see that each must correspond to the class name in the same order.
+
+Continued in 'help division tutorial3'
+
+& DIVISION TUTORIAL3
+
+<5> Lastly we set the power_level attribute on the division object. In most
+    cases this number will shadow the class_level attribute, but it's ok
+    if it varies. To see info on what powers are availabe to what power
+    level look at @powerlevel/info <#>
+
+ &class_list Example Division=Head_Cheese Top_Dog Sailor Wanderer Guestie
+ &class_level Example Division=29           25      15      5        1
+ &power_level Example Division=29           25      15      5        1
+
+NOTE: powerlevels are obsolete as of this version of CobraMUSH
+
+& powers list
+& DIVISION POWERS
+These are a list of all the default powers supplied with the CobraMUSH
+division system and their behavior definitions.
+
+(See for Yescode Definitions: help power yescodes)
+
+Parenthesis Special Behavior definitions:
+S - Only used over self
+B - only checks LT
+L - only checks LTE
+LS - If used over a division & they don't have division power they're fucked.
+     otherwise. works like a normal power.
+# - how many places of power. 1, 2(usually only yes, & lte checking), 
+    3 - full Yes/LTE/LT range power
+
+ANNOUNCE(S1)   -  Power to use @Announce
+ATTACH(2)      -  Power to set divisions
+BCREATE(B1)    -  Power to @BCreate & @newpass builders LT
+BUILDERS(S1)   -  Ability to use builder commands
+CAN_NSPEMIT(S1)        -  Power to use NoSpoof Pemit
+CEMIT(S1)      -  Power to @cemit
+CHAT(S1)       -  Chat Admin
+CHOWN(3)       -  Ability to @chown other peoples objects without CHOWN_OK
+COMBAT(3)      -  Power for an optional SCed combat system.
+
+(Continued in Powers2)
+
+& POWERS LIST2
+& powers2
+& DIVISION POWERS2
+DANNOUNCE(S1)  -  Ability to use @Dannounce
+DIVISION(S1)   -  Power to create & maintain Division objects.
+EANNOUNCE(S1)  -  Power to use @Eannounce 
+EMPIRE(S1)     -  Power used to do empire specific things with divisions.
+EMPOWER(L1)    -  Power to empower.
+GFUNCS(S1)     -  Ability to add functions to the global register.
+HALT(3)                -  Ability to @halt other players objects.
+IDLE(S1)       -  Power to idle to the end of the world.
+JOIN(2)                -  Power to Join other players.
+LEVEL(LS2)     -  Power to change a players class level.
+LINK(3)                -  Can @link an exit to anyplace
+LOGIN(S1)      -  Not subject to login restrictions.
+MAILADMIN(3)   -  Can use mail administration commands/functions.
+MANY_ATTRIBS(S) -  Ability to have unlimited attributes on an object.
+MODIFY(3)      -  Power to modify objects not belonging to you.
+NEWPASS(3)     -  Can use @newpassword command.
+NOPAY(S1)      -  Doesn't need money for anything.
+NOQUOTA(S1)    -  Unlimited quota.
+NUKE(3)                -  Ability to Nuke players
+OPEN(3)                -  Can @open a link from any room.
+PASS_LOCKS(3)  -  Can pass most locks.
+PCREATE(S1)    -  Can use @pcreate command.
+PEMIT(3)       -  Can @pemit to most anyone.
+POLL(S1)       -  @poll'er 
+PRIVILEGE(S1)  -  help 'privilege'
+(Continued in Powers3)         
+
+& POWERS LIST3
+& powers3 
+& Division Powers3
+PRIVWHO(S1)    -  Ability to get privileged who.
+PROGRAM(3)     -  Able to put another player in a @program.
+PROGLOCK(3)    -  Power to lock another player in a @program.
+PUEBLO_SEND(S)  - Ability to send XCH_SEND and SEND Pueblo tags.
+QUOTA(3)       -  Can use @quota command on other players.
+QUEUE(S1)      -  Can queue things computationaly expensive.
+REMOTE(3)      -  Ability to do things remotely.
+SEARCH(3)      -  Ability to @search other players.
+SEE_ALL(3)     -  Power to examine objects not owned by you.
+SEE_QUEUE(3)   -  Can see other players queue. 
+SETQUOTAS(3)    -  Ability to use @SQuota on Divisions and Players.
+SITE(S1)       -  Can use game-site specific commands such as @dump, @shutdown, etc.. 
+@SU(3)         -  Allows using '@su' to switch user to another player without password.
+SUMMON(2)      -  Power to summon others.
+TEL_THING(3)   -  Power to teleport anything within your class and division scope
+TEL_PLACE(3)   -  Power to teleport to anywhere within your class and division scope
+
+& PRIVILEGE
+Usually used in combination with admin or director level.  This power implments 
+some of the more 'priviliged' aspects of a command that we don't really need to
+make a power for every single small aspect.  Though in other cases a command is
+completely blocked off by this, or in other cases, you have to have this power & 
+another power to do something..  It is a very broad capability power, & should be
+given only to people you consider 'privileged' cause its effects vary in many cases.
+
+& DIVISION COMMANDS
+For help on these commands however do not include the S- in the
+command reference.
+
+Builtin Commands:
+
+   @DIVISION        @EMPOWER           @LEVEL
+   @POWER            @POWERGROUP     
+   
+
+Softcoded Extension Commands:  (JW Cobra extensions)
+
+   @ANNOUNCE         @DANNOUNCE         @EANNOUNCE
+   JOIN              SUMMON             
+
+& S-@ANNOUNCE
+& @ANNOUNCE
+@announce <message>
+
+        Announces a message to all connected players provided you have the 
+        announce power.
+
+& S-@DANNOUNCE
+& @DANNOUNCE
+@dannounce <message>
+
+        Announces a message to all connected players within your division 
+        provided you have the announce division power.
+
+& S-JOIN
+& JOIN
+join <player>
+
+        Teleports you to player's location provided you have the join power 
+        over them.
+
+& S-SUMMON
+& SUMMON
+summon <player>
+
+        Teleports <player> to your location provided you have the summon 
+        power over them.
+
+
+& @power
+ Syntaxes:
+           @power <power name>
+          @power <object> = <power name>
+           @power/add <power name>=<type>
+          @power/alias <power name>=<power alias>
+          @power/delete <power name>
+          @power/list
+
+   This command manipulates and views information about the in game powers.
+   With no switches, 2 versions can be given.
+       1) Supplying just the power name argument will view information about
+          the specified power.
+                                or
+       2) Suppling the left argument as a database object, and the right argument
+          as a power name is an alias to a typical use of the @empower command.
+   
+    Switches           Description:
+       /add            Add <power Name> as a specified type power 
+       /alias          Alias <power name> with <power alias>
+       /delete         Delete specified <power name>
+       /list           List all powers
+
+  See Also: Division System, power types, @empower
+
+& power types
+  In the CobraMUSH Power System, an individual power may take up multiple bits.
+  While on the other hand, others don't need to, so an arrangement of power types
+  has been made for the CobraMUSH Power System as follow.
+
+   Self: 
+            A simple self checking '1 bit' power, this power can only be used over
+            yourself in a powercheck.
+
+   LevChk:  
+            A standard CobraMUSH LevChecking Power, has '3 bits' and is used
+           in all yescode instances of Yes, YesLTE, and YesLT.
+
+   LevChk_LTE:
+            A Full Yes '1 bit' power, can be used over other players but only
+           acts like the YesCode instance of 'YesLTE'
+
+   LevSet:
+            A special check for level setting.  If the power is being used over a 
+           division player must have 'Division' power as well.  A '3 bit' full yes
+           power just like LevChk.
+
+   BCreate:
+            A Full Yes '1 bit' power, can be used over other players.  Only works
+           in the 'YesLTE' sense however does a combination check making sure 
+           player has 'Builder' power over player as well.
+
+  See Also: Division System, power yescodes, powover()
+
+& power yescodes
+  In setting powers, and reading powers.  You may notice a variance in 'codes'
+  so to say for a powers level.  In CobraMUSH these are called 'Power YesCodes'
+
+  Yes Levels in their correspondance to each other in the columns are as 
+  follow.
+
+        Yes             YesLTE           YesLT            No
+       Yes             LTE              LT               No
+       3               2                1                0
+
+  YesCode Defintions:
+      Yes                    The  power can be used on anyone.
+      YesLTE                 Thes power can be used on anyone equal or lower than
+                             the 'object' level enacting object.
+      YesLT                  The power can be used on anyone lower than the level
+                             of the enacting object.
+      No                     Power does not exist
+
+   All yescodes abide by the normal division 'DivScope' rules.  
+
+  See Also: Division System, Power Types, @empower, DivScope
+  
+& DivScope
+  DivScope of an object is defined as an object that is in your division on the
+  division tree of below it (Reference help division system for tree).
+
+  For Example If your 'Empire 2' on the division tree, Empire 2, Branch1 & Branch2
+  are in your divscope.  Nothing above you, nor aside of you is in your divscope.
+  However Branch1 and Branch2 and anything below them are.  If your in the Master
+  Division, everything follows under your divscope.
+
+  See Also: Division System, divscope(), power yescodes, power types
+
+& @POWERGROUP
+   Syntaxes:
+             @PowerGroup <Power Group>
+            @PowerGroup <Power Group> = <Power>
+            @PowerGroup <Object> = [!]<Power Group> ...
+            @PowerGroup/Raw <Object> = <Power Group>
+            @PowerGroup/Add <Power Group>
+            @PowerGroup/Delete <Power Group>
+            @PowerGroup/Auto <Power Group> = <Power Set List>
+            @PowerGroup/Max <Power Group> = < Power Set List>
+            @PowerGroup/List
+
+    In order to use this command you must possess the 'PowerGroup' Power.
+
+    This command views and manipulates the ingame powergroup system in coordination
+    with the power system.
+
+    With Just the <Power Group> argument it views the PowerGroup info.
+    If <Power Group> is given with a second argument being the power, its an alias
+    to the /max switch.  
+    
+    Otherwise if the first argument is an object and the second is  a PowerGroup Name,
+    the command assumes you are trying to set the powergroup on an object.  In order
+    to set a powergroup on an object you Must:
+
+        A) Be able to Use 'PowerGroup' power over <Object>
+                      and
+        B) Possess the 'Max' powers of such said powergroup.
+
+    The '!' character signifying the not token, is used to reset a powergroup and
+    multiple powergroups may be set/unset at a time providing a space inbetween the
+    powergroup names.
+
+ (Continued in @powergroup2)
+
+& @powergroup2 
+   
+  An as well may only receive a powergroup if the following conditions
+  are met.
+
+  1) If the powergroup is default for the object type, the object may receive it.
+  2) If the object has no division, and it is not the default powergroup being given.
+     They may not receive any powergroup.
+  3) The division the object is a member of has the specified powergroup.
+     The object may receive the powergroup.
+  4) The division the object is a member of can receive all 'Max' powers of specified
+     powergroup.  The object may receive the powergroup
+  
+  (Continued in @powergroup3)
+
+& @powergroup3
+
+  Switches                 Description
+    /Raw                   Sets powergroup in a 'raw manner' without automatically
+                           giving.
+
+    /Add                   Adds a named powergroup
+
+    /Delete                Delete a power group, only God can Do this
+
+    /Auto                  Sets a power on or off on the automatic power list. If power 
+                           does not exist on max power list, it automatically adds it. 
+                          (For Syntax See: @empower)
+
+    /Max                   Sets a power on or off on the max power list of a powergroup.
+                           If your removing a power from the max power list and it exists
+                          on the auto power list, it is automatically removed.
+                          (For Syntax see: @empower)
+
+    /List                  List all powergroups available to set
+  
+ See Also: Division System, powergroups(), @empower, power yescodes
+
+
+& @EMPOWER
+  Syntax:  @Empower <Object> = [!]<power name>:<yescode> ...
+
+    This command attemts to grant or take away <Power Name> from <Object>.  
+    Multiple powers may be set and/or removed at the same time.  The 'NOT'
+    or '!' token given before the powername specifies whether or not you 
+    are taking away the power.  In order to give or take away a specified 
+    power from an object you must first maintain that power yourself, and 
+    be able to use the 'Empower' power over <Object>
+
+  Example:
+    > @empower *bob=See_All:YesLt
+      -> Bob(#123) : See_All(YesLTE).
+
+  See Also: Division System, power yescodes
+
+& @LEVEL
+  Syntax: @level <object>=<level>
+
+    Attempts to Set Object's level to specified level.  Levels
+    exist 1-30, 1 being a power signifying Guest Player where
+    which in that instance the object will be restricted to 
+    normal guest restrictions.  
+    
+    If 'Level 1' or the 'Guest Level' is set on a player, then the
+    player will be destroyed automatically upon disconnection.
+
+    You Must be able to use the 'Level' power over <Object> in
+    order to use this command.
+
+  Example:
+    > @level *Bob=25
+      -> Level Set to: 25
+
+  See Also: Division System, Level()
+
+& @DIVISION
+  Syntaxes: 
+          @division <object>=<division>
+         @division/create <Object>
+     
+    Without specifying a switch the command Attempts to set 
+    <object>'s division to <division>.  The user of this command 
+    must have 'Attach' power over <object> and <Division> must be 
+    in their divscope. 
+
+    When specifying the /Create switch, the command will  create
+    a new division upon the division tree you belong in.
+
+  Example:
+   > @division *bob=#3
+     -> Division for Bob(#123) set to Master Division(#3D).
+   > @division/create Bob's Division
+     -> Division created: Bob's Division(#93D)  Parent division: Master Division(#3D)
+
+  See Also: Division System, Division()
+
+& S-@RECLASS
+& @RECLASS
+@reclass <player>=<class>
+
+        Sets <player>'s class to <class> provided that the enact has the 
+       reclass power and that <player> is within the enactor's division scope. 
+       I'am not listing classes here since they vary from division to division.
+
+EXAMPLE: @reclass *bob=Captain
+
+@reclass/list [division]
+
+        Returns a list of all valid classes within your division. If none 
+        are set for your division it returns classes for the next higher
+        division above yours. If the options [division] argument is used 
+        it will return the class list for that division, provided you have 
+        the needed level and power to see the list.
+
+EXAMPLES: @reclass/list
+EXAMPLES: @reclass/list #1234
+
+& DIVISION FUNCTIONS
+divscope()     level()         indivall()      division()      
+hasdivpower()  updiv()         downdiv()       indiv()         
+powover()       empire()       powers()        powergroups()
+haspowergroup() pgpowers()      pghaspower()
+
+
+& haspowergroup()
+  Syntax: haspowergroup(<object>, <powergroup>)
+
+  Tests if <object> has <powergroup> or not.  Returns 1 on a valid
+  success and 0 if not.
+
+  Example:
+    >  think haspowergroup(*Nveid, Wizard)
+        -> 1
+
+  See Also: powergroups(), DIVISION FUNCTIONS
+
+& powergroups()
+  Syntaxes: powergroups(<object>)
+            powergroups()
+
+  With no arguments given this function will return all powergroups
+  avaiable for executing object to set similar to @powergroups/list.
+
+  If an object is specified, a string listing of all powergroups set
+  on the object will be returned.
+
+  Examples:
+     > think powergroups(*Nveid)
+         -> Wizard
+
+     > think powergroups()
+         -> Admin Builder Director Player 
+
+
+  See Also: haspowergroup(), pghaspower(), pgpowers(), DIVISION FUNCTIONS
+
+& empire()
+  Syntax: empire(<object>)
+     
+     Returns teh database number of <object>'s empire.  If <object> is not in a valid
+     empire the function will return #-1 INVALID DIVISION.
+
+
+   See Also: Division Functions, Division(), Empire Objects
+
+& Empire Objects
+
+   Empire Objects are normal division objects, with the 'empire object flagged. The 
+   main use, is to particularly mark an object as the 'toplevel' division for a 
+   specified division.  These objects are referenced by any of their child divisions
+   using the empire() function.
+
+   See Also: division system, empire()
+
+& powers()
+  powers(<obj>)
+        Returns a space sperated list of all powers that <obj> has along with
+        the number corresponding to that power's permission. 3=YES, 2=YESLTE,
+        and 1=YESLT.
+
+   Example:
+     > think powers(*Nveid)
+        -> @SU:2 Announce:3 Attach:3 BCreate:3 Boot:3 Builder:3 Can_NsPemit:3 Cemit:3
+
+   See Also: haspower(), hasdivpower(), DIVISION FUNCTIONS
+
+& divscope()
+  Syntax: divscope(<obj1>[,<obj2>])
+
+         Returns a 1 if <obj1> is within the division scope of the enactor, 
+         0 if not. If <obj2> is used it returns a 1 if <obj2> is within <obj1>'s 
+         division scope and returns a 0 if not.
+
+  Example: 
+    > say [divscope(*Bob)]
+      -> You say, "0"
+
+  This example here assumes that bob is not within your division scope. Meaning
+  if you go by the tree example used in 'help division' then bob would not be
+  in a sub-division of the one the enactor is in.
+
+  Example:
+     > say [divscope(*bob, *jamie)]
+        -> You say, "1"
+
+  This example assumes that jane is either in a division directly below bob's
+  or another sub-division of bob's and is therefore within bob's division scope 
+  of influence.
+
+  See Also: Division Functions, DivScope
+
+& division()
+  Syntax: division(<object>)
+
+    Returns the database number of a given <object>'s assosciated division.
+
+  Example:
+    > say [division(%#)]
+      -> You say, "#1234"
+
+  This example shows the function return the dbref number of the enactor's
+  division.
+
+  See Also: Division Functions, Substitutions
+
+& level()
+  Syntax: level(<object> [,<level>])
+
+        Returns the level of <object> specified.  If the optional 2nd 
+       argument is given the object's level will attempt to change to
+       the level requested and return what to what level you have the
+       ability to change the <object> to.
+
+  Example:
+    > say [level(%#)]
+      -> You say, "25"
+  
+  This example shows the funciton return of the class level of the speaker.    
+
+    > think [level(#1,29)]
+      -> 30
+
+   This example shows the attempt to change God's level from someone who
+   can't.  So it returns the level God is at after the attempt.  In a 
+   successful attempt it will return the new level the Object is set.
+
+
+  See Also: Division Functions, level(), Substituions
+
+& haspower()
+  Syntax: haspower(<object>, <power name>)
+
+    Returns 1 if the object has the named power, and 0 if it does not.
+    If the object does not exist, #-1 will be returned.
+
+    You may or may not have to be able to examine the object to use this.
+
+  See Also: Division Functions, hasdivpower() 
+
+& hasdivpower()
+  Syntax: hasdivpower(<object>, <power name> [,<yes/yeslte/yeslt>])
+
+   Returns appropriately what level <object> has <power name> at.
+       3 - Has Power at Full 'Yes' Value
+       2 - Has Power at 'YesLTE' value
+       1 - Has Power at 'YesLT' value
+
+   If the optional third argument is given, it will test the object 
+   wether or not they have the power at the given power level. 
+   A simple 1 will return if they have the power at that level, and 
+   a 0 will return if not.
+
+   Example:
+     > think hasdivpower(*Nveid, See_All)
+        -> 3
+     > think hasdivpower(*Nveid, See_All, 3)
+        -> 1
+
+   * Both return true for Nveid has 'See_All', only the second one
+     makes sure Nveid has 'See_All' at full yes.
+     
+  See Also: Division Functions, powers(), haspower()
+
+& pghaspower()
+  Syntax: pghaspower(<powergroup>, <auto/max>, <power name> [,<yescode>])
+
+   Returns appropriately what level <powergroup> has <power name> at.
+       3 - Has Power at Full 'Yes' Value
+       2 - Has Power at 'YesLTE' value
+       1 - Has Power at 'YesLT' value
+
+   If the optional fourth argument is given, it will test the powergroup 
+   wether or not they have the power at the given power yescode. 
+
+   A simple 1 will return if they have the power at that level, and 
+   a 0 will return if not.
+
+   Example:
+     > think pghaspower(Director, Max, See_All)
+        -> 3
+     > think pghaspower(Director, Maxl, See_All,  3)
+        -> 1
+
+   * Both return true for director Powergroup  has 'See_All', only the 
+     second one makes sure the Director Powergroup has 'See_All' at 
+     full yes.
+     
+  See Also: Division Functions, pgpowers(), @powergroup, power yescodes
+
+& pgpowers()
+  Syntax: pgpowers(<powergroup>, <auto/max>)
+
+   Returns a space seperated list of powers existing within a powergroup, on
+   either its auto or max powerfield.
+
+   Example:
+     > think pgpowers(Player,Max)
+       -> RPChat:3 RPEmit:3
+
+   See Also: pghaspower(), @powergroup, DIVISION FUNCTIONS
+
+
+
+& updiv()
+  Syntax: updiv(<object>)
+         Returns a space seperated list of divisions above <obj>.
+
+  See Also: Division Functions, downdiv()
+
+& indiv()
+  Syntax: indiv(<division>[,<type>])
+
+         Returns a space seperated list of all players within <division>.
+         <Type> limits search to specified Object type, if no type is
+         given the search defaults to player type.
+
+  See Also: indivall()
+
+& indivall()
+  Syntax: indivall( <division> [,<type> ] )
+
+      Returns a space seperated list of all objects within a specified
+      <division> and all  its subdivisions of a specified <type>
+      object.  If no <type> given is given, the search defaults its
+      search to the player object type.
+
+      Example:
+        > think indivall(#3)
+          -> #1 #4 #5 #7
+       > think indivall(#3, room)
+          -> #0 #2 #8 #9 #10 #60 #64 #69 #76 #82 #87
+
+       * The first Search returns all players in the #3 Master Division entirely
+         The second search returns all rooms in the #3 Master division entirely.
+
+  See Also: Division Functions, indiv()
+
+& downdiv()
+  Syntax: downdiv(<division>)
+
+     Returns a space seperated list of divisions below <division> (within 
+     <divison>'s range of power).  
+
+  Example:
+     > think downdiv(#8)
+        -> #8 #152 #192 #193 #1234 #1501
+
+  See Also: Division Functions, updiv()
+
+& powover()
+  Syntax: powover(<object1>, <object2>, <power name>)
+
+       Checks to see if <Object1> has Power over <Object2> with specified 
+       <power name>.
+
+  Example:
+    > think powover(*Nveid, *Jamie, See_All)
+       -> 1
+
+  See Also: Division Functions
+
+& DIVISION CREDITS
+  Originally based off TOS TrekMUSE Division System coded by Macgyver@tos.net
+  System in use Designed by Jamie and Nveid
+
+  HardCode (C) Implementations by Nveid[RLB]
+  SoftCode (MUSHCode) Implementations by Jamie[JLW] and Nveid[RLB]
diff --git a/game/txt/hlp/cobra_flag.hlp b/game/txt/hlp/cobra_flag.hlp
new file mode 100644 (file)
index 0000000..85776d2
--- /dev/null
@@ -0,0 +1,717 @@
+& FLAGS
+  @set <object>=[!]<flag name>
+
+  Flags give objects certain abilities or qualities.  Such as the
+  Dark flag, makes someone invisible to everyone else.
+
+  Some flags can only be set on certain types of objects, such as
+  just players or just rooms. Other flags, like VISUAL, can be set
+  on any type of object (player, room, exit, thing).
+
+  To un-set a flag, use the exclamation point (!) before the flag
+  name. For help on any particular flag, type: help <flag name>
+
+  A descriptive list of flags is available in: help flag list
+  A complete list of flags is available through: @flag/list
+
+(continued in help flags2)
+& FLAGS2
+  You can see the list of flags set on an object in several ways.
+
+  1.  If you are allowed to examine the object. The flags are listed in
+  expanded word format on the line just below the object's name, after
+  the word "Flags:".
+  2.  Flag abbreviations are also visible after the object's name in the
+  room description (if the object is not set OPAQUE and you are not set
+  MYOPIC).
+  3.  The flags() function will also return the object's list of flag
+  abbreviations.
+
+  Note: ROOM, PLAYER, EXIT, and GARBAGE are special flags which
+  indicate the TYPE of an object. The absence of these four special flags
+  indicates the object is of type THING.
+
+  See also: examine, flags(), hasflag(), orflags(), andflags(),
+  orlflags(), andlflags(), types of objects, type(), hastype(), @flag
+& FLAG LIST
+& FLAGS LIST
+Flag  Title           Flag  Title              Flag  Title
+-----------------------------------------------------------------------
+  A - Abode, Ansi       C - Chown_Ok, Color     D - Dark
+  E - Exit (type)       F - Floating, Fixed     G - Going
+  H - Haven             I - Trust/Inherit       J - Jump_Ok, Judge
+  L - Link_Ok           M - Monitor             N - No_Tel, No_Leave
+  O - Opaque            P - Player (type)       Q - Quiet
+  R - Room (type)       S - Sticky              T - Thing (type)
+  U - Unfindable        V - Visual              X - Safe              
+  Z - Shared, Z_Tel     
+  
+  a - Audible           b - Debug               c - Connected
+  d - Destroy_Ok        e - Enter_Ok            g - Gagged
+  h - Halt              i - Orphan              j - Jury_Ok             
+  l - Light             m - Myopic, Mistrust    n - No_Command          
+  o - On-Vacation       p - Puppet, Paranoid    s - Suspect           
+  t - Transparent       u - Uninspected         v - Verbose   
+  w - No_Warn           x - Terse, Cloudy       ? - Unregistered      
+  ^ - Listen_Parent     ~ - Noaccents           " - NoSpoof
+
+ Additional Flags:
+  Auth_Parent           WeirdPrompts
+-----------------------------------------------------------------------
+Some flags may not be enabled on some MUSHes. @flag/list will show
+which are available.
+
+& AUTH_PARENT
+ Flag:  AUTH_PARENT (notype)
+
+ Set on an object to declare it as an authorized parent.  Special
+ permission by checking attribute ownership for execution of 
+ certain tasks are ignored.  This flag is settable only by players
+ with the privilege power.
+
+& ABODE
+ Flag:  ABODE  (rooms)
+
+  If a ROOM has the ABODE flag set, any PLAYER can set his home there,
+  and can set the homes of THINGs there.  It does not mean that a
+  player can open an exit to that room, only that they can set their
+  home there.  This flag should not be set unless you want to make the
+  room a public 'living area'.
+
+  To make a room your home, type '@link me=here' while standing in the
+  room.
+
+  See also: @link
+& ANSI
+  Flag:  ANSI  (players)
+  
+  When set on a player, this flag bold-hilites the names and owners
+  of attributes when the player "examines" an object. This makes it
+  much easier to pick out where attributes begin and end, when examining
+  complex objects. You must be using a terminal which supports ANSI
+  control codes in order for this to work. Additionally, some MUSHes
+  may not support ANSI. Check @config to see.
+  
+  See also the COLOR flag. If COLOR is not set, and ANSI is, you will
+  see vt100 ANSI codes, but not color ANSI codes.
+
+  See also: COLOR, ansi(), @config
+& AUDIBLE
+  Flag:  AUDIBLE  (all types)
+
+  Exits that are AUDIBLE propagate sound to their destinations. In
+  other words, any message - emit, say, or pose - that is heard in the
+  source room of the exit is passed on to the contents of the exit's
+  destination room. The message is prepended with the exit's @prefix
+  attribute; if there is no @prefix, the default is used:
+  
+  "From <name of the exit's source room>,"
+  
+  Messages matching a certain pattern may be filtered out by using
+  @filter on an exit; read 'help @filter' for more. If the object 
+  has a @forwardlist, sounds that pass the @filter are sent on to
+  the objects in the @forwardlist.
+  
+  In order for exits in a room to propagate sound, the room must also
+  be set AUDIBLE. If the room is audible, exits that are audible show 
+  up on a @sweep, even if they are set DARK.
+   
+  See "help AUDIBLE2" for more.
+& AUDIBLE2
+  This flag is also valid for things. If an object is set AUDIBLE,
+  any messages which originate from its contents will be broadcasted
+  to the outside world. This makes it very simple to program vehicles.
+  Like AUDIBLE on exits, the message is prepended with the thing's
+  @prefix attribute, and messages matching certain patterns may be
+  filtered with @filter. If there is no @prefix, the message will be
+  prepended with "From <name of AUDIBLE object>,"
+  The AUDIBLE object does not receive its own propagated messages.
+  The AUDIBLE flag allows most "emitters" (objects that listen for
+  messages and broadcast them to other rooms) to be eliminated. The
+  message is propagated only to the next room and no farther, so 
+  there is no danger of looping.
+& TRACK_MONEY
+  Flag: TRACK_MONEY (players)
+
+  By setting the TRACK_MONEY flag, a player can determine which
+  objects maybe using their money. TRACK_MONEY reports all charges
+  to a player and their objects except the queue deposit.
+
+  > @set me=TRACK_MONEY
+  > give Javelin=50
+  You give 50 pennies to Javelin.
+  GAME: Walker spent 50 pennies!
+  > @create foo
+  GAME: Walker spent 10 pennies!
+  Created: Object #345.
+  > @for foo=@search
+  GAME: foo(#345) spent 100 pennies!
+  (search results)
+  > <a whole buncha commands>
+  GAME: Object Walker(#123) lost a Penny to queue loss.
+
+  See also: no_pay
+& BUILDER
+  BUILDER 
+
+  Builder is both a flag & a power.  It's a flag to mark someone as a
+  designated builder, and a power to actually grant the building powers
+  to someone.
+
+  See also: powers list, flags, @empower, @dig, @open, @create
+& CHOWN_OK
+  Flag:  CHOWN_OK  (things, rooms, exits)
+
+  You can set this flag on an object you own to allow other players 
+  to transfer ownership of the object to themselves (using @chown).
+  You must be carrying the thing, or in the room in order to set the
+  flag, unless you use the DBREF number.
+
+  See also: @chown
+& CLOUDY
+  Flag:  CLOUDY (exits)
+
+  If this flag is set on a (non-TRANSPARENT) exit, when a player looks
+  at the exit they will see the contents of the destination room
+  following the exit's description.
+
+  If the flag is set on a TRANSPARENT exit, when a player looks at the 
+  exit they will see only the description of the destination room
+  following the exit's description, and will not see contents.
+& COLOR
+  Flag:  COLOR  (players)
+  
+  When set on a player, this flag allows the player to see ANSI color.
+  The ANSI flag must also be set.
+
+  See also: ANSI, ansi()
+& CONNECTED
+  Flag:  CONNECTED  (players)
+
+  This internal flag applies only to players and it shows if the 
+  player is connected or not. Only admin can see this flag. No one
+  can set or reset this flag. 
+
+  Mortal code can't use hasflag(<x>,connected) to test if a player is 
+  connected. Consider using conn(), lwho(), or mwho() instead.
+
+  See also: conn(), lwho(), mwho()
+& DARK
+  Flag:  DARK  (all types)
+
+  If a room is DARK, then no items are shown when a person 
+  'looks' there. If a thing is DARK, then "look" does not list that 
+  object in the room's Contents:, and if an exit is DARK, it doesn't
+  show up in the Obvious Exits: list.  Puppets and audible objects
+  with @forwardlist are not concealed by this flag.  Note that players,
+  puppets, and other "hearing" objects still trigger enter/leave messages
+  when in DARK areas.  There is a config option for "full invisibility":
+  players and objects that are dark will be slightly disguised in
+  speech and poses.  Such actions by these objects will show as being
+  from Someone or Something depending on whether it was an object or
+  director type player.
+
+  See 'help DARK2' for more.
+
+& DARK2
+  Players who can hide from the WHO list should use @hide/on and
+  @hide/off to control this, not the DARK flag. While any player can
+  turn off their DARK flag, only Directors can set their DARK flag.
+
+  Directors who are DARK "disappear" completely -- they are not on the WHO
+  list, do not announce connects and disconnects, etc.
+& DEBUG
+  Flag: DEBUG  (all types)
+  
+  The DEBUG flag is used for debugging MUSHcode. It is meant to be used
+  in conjunction with the VERBOSE flag. If an object is set DEBUG, all
+  parser evaluation results will be shown to the object's owner and to 
+  any dbrefs in the object's DEBUGFORWARDLIST, in the format:
+  #dbref! <string to evaluate> :
+  #dbref!  recursive evaluation of functions in string
+  #dbref! <string to evaluate> => <evaluated string>
+
+  Note that verbose output is "#obj]" - debug output is "#obj!".
+  
+  Because the parser does recursive evaluations, you will see successive
+  messages evaluating specific parts of an expression. This enables you
+  to pinpoint exactly which evaluation is going wrong.
+
+  To add a target to an object's DEBUGFORWARDLIST, the object must
+  pass the target's @lock/forward, or control the target.
+  
+  See "help DEBUG2" for more.
+& DEBUG2
+  Objects run under this flag are computationally expensive.  Avoid
+  leaving it set on objects. It can also generate huge amounts of spam
+  from the output.
+  
+  Create test, and set it DEBUG.
+  
+  > @va test=$wc *:"String %0 has [strlen(%0)] letters and [words(%0)] words.
+  > wc This is my test string
+  #14! String %0 has [strlen(%0)] letters and [words(%0)] words. :
+  #14!  strlen(%0) :
+  #14!   %0 => This is my test string
+  #14!  strlen(%0) => 22
+  #14!  words(%0) :
+  #14!   %0 => This is my test string
+  #14!  words(%0) => 5
+  #14! String %0 has [strlen(%0)] letters and [words(%0)] words. =>
+    String This is my test string has 22 letters and 5 words.
+  Test says, "String This is my test string has 22 letters and 5 words."
+& DESTROY_OK
+  Flag:  DESTROY_OK  (things)
+
+  The DESTROY_OK flag allows anyone to @destroy it. This is good for
+  "temporary" objects like "Can of Cola". If the object has a
+  @lock/destroy set, a player who doesn't control the object
+  must pass this lock in order to destroy it.
+
+  DESTROY_OK takes precedence over SAFE.
+
+  See also: @destroy
+& ENTER_OK
+  Flag:  ENTER_OK  (all types)
+  If an object or person is ENTER_OK, other players may 
+  enter the object or person by using 'enter <object/person>.
+  Only objects which are ENTER_OK may be entered, regardless of the
+  enter lock. Players must also have the ENTER_OK flag set if they wish
+  to be able to receive  things given to them by other players via the 
+  command 'give <player> = <object>'.  
+
+  This flag has no effect on rooms.
+
+  See also: enter, leave, give, @lock
+& FIXED
+  Flag: FIXED (players)
+  
+  When this flag is set on a player, it prevents them or any of their
+  objects from using the @tel or home command. The only exception is
+  that a player's objects are permitted to @tel themselves to the
+  player's inventory.
+& FLOATING
+  Flag:  FLOATING (rooms)
+
+  If a room is set floating, you will not be notified every 10 
+  minutes or so that you have a disconnected room.
+  A disconnected room may mean (depending on how the MUSH is
+  configured) a room that can't be reached from room #0, or
+  a room that can't be reached from room #0 and has no exits.
+& GAGGED
+  Flag: GAGGED (players)
+
+  When set on a player, it disables him from doing anything 
+  except moving and looking.  He cannot talk, page, build, pose, get 
+  or drop objects. Normally used as a penalty for those who break
+  MUSH rules.
+
+  Only Directors can set this flag.
+& GOING
+  Flag:  GOING  (all types)
+
+  Used internally for the @destroy command, it is set on things that
+  are scheduled to be destroyed. To prevent a GOING object from being
+  destroyed, use the @undestroy (or @unrecycle) command. You can no
+  longer @set the object !GOING.
+
+& HALT
+  Flag:  HALT   (all types)
+
+  While this flag is set, the object cannot perform any mush 
+  actions, listen, be triggered, evaluate functions or substitutions,
+  etc.
+
+  See also: @halt, @restart
+& HAVEN
+  Flag:  HAVEN (players, rooms)
+
+  If a player is set HAVEN, she cannot be paged and anyone paging them
+  will be sent a brief notification. You can also set a longer @HAVEN
+  message if you wish. You may prefer to use @lock/page to block out
+  only specific individuals.
+
+  See also: @haven
+
+& TRUST
+& INHERIT
+  Flag:  INHERIT  (rooms, objects, exits, divisions)
+  Inherits all powers and privileges from an object's owner that is set 
+  'Inheritable'.  Objects however do not Inherit the 'Privlege' power,
+  nor the Level of their owner.
+
+  See Also: Inheritable
+
+& INHERITABLE
+  Flag: INHERITABLE (players)
+
+  When Inheritable is set on a player, all objects owned by that player
+  inherit its powers and privileges.  Objects owned by the player however 
+  do no Inheri the 'Privilege' power nor the Level of the Player.
+
+  See Also: Inherit
+
+& JURY_OK
+  Flags: JUDGE and JURY_OK (players)
+  These flags may be used by the MUSH to support some form of Judged
+  RP system. Or they may not be compiled in. See @config for more
+  information.
+& JUMP_OK
+& TEL_OK
+  Flag: JUMP_OK (or TEL_OK) (rooms)
+
+  When a room is set JUMP_OK, then that room can be teleported into
+  by anyone. See @teleport.
+& LIGHT
+  Flag:  LIGHT (all types)
+
+  Objects, players, and exits which have the LIGHT flag set on them
+  (and are not also set DARK) appear in the contents of DARK rooms.
+
+  See also: DARK
+& LINK_OK
+  Flag: LINK_OK  (rooms, things)
+
+  If a room or object is LINK_OK, anyone can link exits to it (but 
+  still not from it). Also, LINK_OK overrides the TRUST protection
+  against @trigger (although not @force or @set). This also allows 
+  others to @parent their objects to the thing set LINK_OK.
+
+  See @link, TRUST, @parent, PARENTS
+& MONITOR
+  Flag:  MONITOR  (players)
+  
+  When set on a player, this flag notifies that player when anyone connects
+  to or disconnects from the MUSH. It is valid only for players, and must be
+  set by a director.
+
+  Flag:  MONITOR  (things, rooms)
+  When set on a thing or room, this flag activates the ^ listen patterns on
+  the object. Objects which have ^ listen patterns but are not set MONITOR
+  do not check those patterns.
+& MORTAL
+  The MORTAL flag is no longer available in PennMUSH. Please see help
+  changes for more information.
+& MYOPIC
+  Flag:  MYOPIC  (players)
+
+  Myopic is a flag which suppresses the printing of an object's dbref
+  number and abbreviated list of flags when it is looked at. It makes
+  the world appear like you don't control any of it, even if you have
+  see_all or modify power of it. It's useful if you don't like to see 
+  object numbers.  This flag is only valid for players; objects belonging 
+  to MYOPIC players are automatically considered to be MYOPIC.
+
+  See also: DBREF
+& MISTRUST
+  Flag:  MISTRUST  (things, rooms, exits)
+
+  Mistrust prevents an object from controlling anything but
+  itself. This will also usually prevent it from examining anything else
+  non-VISUAL owned by the same player. It also prevents the object
+  from benefitting from its owner's no_pay, no_kill, and no_quota
+  powers, if any.
+
+  This flag can be used when you wish a single player to retain ownership
+  of objects that other players will use to run arbitrary commands,
+  and don't want those objects to be able to affect your objects.
+
+
+  See also: control
+& NOACCENTS
+  Flag: NOACCENTS  (players)
+
+  This flag causes all accented characters to be converted to non-accented
+  before being sent to a connection. See HELP STRIPACCENTS() for caveats.
+
+  See also: i18n, accent(), stripaccents()
+& NO_COMMAND
+  Flag:  NO_COMMAND  (all types)
+  
+  The NO_COMMAND flag disables the checking of $-commands on an object.
+  Most MUSHes will be configured to automatically set this flag on rooms
+  and players. The server runs faster when fewer objects are checked for
+  $-commands; thus, any object which does not have $-commands on it should
+  be set NO_COMMAND. Many MUSHes choose to have all objects initially set
+  NO_COMMAND at creation. The flag has no effect on exits.
+
+  See also: USER-DEFINED COMMANDS
+& NO_LEAVE
+& NOLEAVE
+  Flag: NO_LEAVE (objects)
+  
+  When this flag is set on an object, players can not "leave" it. 
+  Attempts to leave the object will trigger its @LFAIL, @OLFAIL,
+  and @ALFAIL, if set.
+
+  See also: leave
+& NO_TEL
+  Flag:  NO_TEL  (rooms)
+
+  The NO_TEL flag prevents objects in a room from being @teleported;
+  mortals in the room cannot use @teleport, nor can other objects 
+  @teleport them out. This flag is checked on the "absolute room" of an
+  object; thus, if you are in a container in a room which is NO_TEL,
+  you cannot use @teleport from that container. There is no way to
+  get out of a NO_TEL room except by exiting in some "normal" manner,
+  or by going "home". Puzzle rooms, prisons, and similar locations would
+  probably benefit from this flag.
+& NO_WARN
+& NOWARN
+  Flag: NO_WARN  (all types)
+
+  This flag is enabled with the MUSH building warning system.
+
+  When this flag is set on an object, its owner will not receive
+  any building warnings from that object. When it is set on a player,
+  that player will not receive any building warnings at all.
+
+  See also 'help warnings', 'help @warnings', and 'help @wcheck'
+
+& NOSPOOF
+  Flag: NOSPOOF  (players)
+
+  If a player is set NOSPOOF, @emits of all sorts will be tagged with the
+  name of the person/object making them. This prevents spoofing and lets
+  you see where such messages originated. Objects belonging to NOSPOOF 
+  players are automatically considered NOSPOOF. 
+  
+  Note that NOSPOOF output can be spammy and that the output format of
+  NOSPOOF can mess up @listen and ^ patterns, giving unexpected results.
+
+  Sample output:
+    @set me=nospoof
+    > Flag set.
+    @pemit me=Testing
+    > [Cyclonus->Cyclonus] Testing
+
+  See PARANOID, SPOOFING, @emit, @pemit, @remit, and @oemit.
+& PARANOID
+  Flag: PARANOID (players)
+
+  Used in conjunction with the NOSPOOF flag. If a player is set
+  PARANOID and NOSPOOF, @emits of all sorts are tagged with the
+  name and dbref of the person/object making them and the name of
+  the object's owner, if the emit was not from a player.
+
+  See NOSPOOF, SPOOFING, @emit, @pemit, @remit, and @oemit.
+& ON-VACATION
+  Flag: ON-VACATION (players)
+
+  This flag may be used by the MUSH to allow players to indicate when 
+  they have left for vacation, to prevent themselves from being purged
+  for inactivity. Its usefulness depends on game policy.
+
+  You will be notified (periodically and on connect) if you leave this flag 
+  set, to remind you to unset it when you return from vacation.
+& OPAQUE
+  Flag:  OPAQUE  (all types)
+
+  When set on yourself, it prevents other players from seeing what you are
+  carrying in your inventory. This applies to everyone and everything,
+  even players with modify or see_all powers, or to stuff that you own. 
+  It works the same way on objects. This flag also prevents people inside 
+  an object from using look/outside.
+
+  When set on an exit in a TRANSPARENT room, the exit is displayed
+  as if the room weren't TRANSPARENT.
+
+  Meaningless for rooms.
+
+  See also: TRANSPARENT, look
+& ORPHAN
+  Flag:  ORPHAN   (all types)
+
+  The ORPHAN flag severs the connection between an object and its
+  type ancestor, and prevents attributes from being
+  retrieved from the ancestor. It has no effect on the object's 
+  true @parents, only on its use of the ancestor.
+
+  See also: @parent, ancestors
+& PLAYER
+  Flag:  PLAYER  (player)
+
+  The PLAYER flag identifies you as a player. This flag cannot
+  be reset by any player, not even the 'God' player.  It is used by hardcode 
+  to identify your commands, check for validity of commands or locks, etc.
+  You can just pretend it isn't there. 
+& PUPPET
+  Flag:  PUPPET  (things)
+  
+  Setting this flag on a thing turns it into a sort of extension of its
+  owner, making it grow eyes and ears. It then relays everything sees
+  and hears back to its owner, unless the owner is in the same room.
+  (If you set a puppet VERBOSE as well, it will relay even when the
+  owner is in the same room).
+
+See: @force, PUPPETS
+& QUIET
+  Flag:  QUIET  (all types)
+
+  This flag when set on yourself prevents you from hearing 
+  the 'set', 'triggered', 'Teleported.', and several other messages 
+  from any objects you own.  When set on an object, only that object 
+  will not relay its messages.
+& ROOM
+  Flag:  ROOM  (rooms)
+
+  This flag is automatically set on rooms when you @dig a new
+  room. It cannot be changed.
+& ROYALTY
+  Flag:  ROYALTY  (all types)
+
+  This flag is obsolete with the CobraMUSH Division system, some games
+  may or may not have a use for it, depending on wether or not they're
+  using the penn compatibility features. 
+
+& SAFE
+  Flag:  SAFE  (all types)
+
+  The SAFE flag protects objects from destruction. If the REALLY_SAFE
+  option was set when the MUSH was compiled (see @config), the only
+  way to destroy an object set SAFE is to explicitly reset the SAFE
+  flag and then @dest it. If the REALLY_SAFE option is not set,
+  @destroy/override (or @nuke) will override the SAFE flag and destroy
+  the object.
+& STICKY
+  Flag:  STICKY  (all types)
+
+  If a thing or player is STICKY, it goes home when dropped (See HOMES).
+  It also goes home when an object carrying it teleports or goes home,
+  unless the object controls it.
+  If a room is STICKY, its drop-to is delayed until the last person leaves 
+  (See DROP-TOs). This flag is meaningless for exits.
+& SUSPECT
+  Flag:  SUSPECT  (all types)
+
+  This flag is only settable by directors. Players with this flag have
+  their connects, disconnects, name changes all connected directors. 
+  Actions by any object with this flag are also logged to the MUSH log files.
+& TEMPLE
+  The TEMPLE flag is no longer available in PennMUSH. Please see help
+  changes for more information.
+& TERSE
+  Flag:  TERSE  (players, things)
+
+  When an object is set TERSE, it does not see the descriptions or
+  success/failure messages in rooms. This is a useful flag if you're
+  on a slow connection or you're moving through a familiar area and
+  don't want to see tons of text. 
+
+  When a player is TERSE, all of their objects are considered to be TERSE.
+& TRANSPARENT
+  Flag:  TRANSPARENT  (all types)
+
+  If this flag is set on a room, it will display exits in "long" format.
+  Instead of putting all the exits on one line under "Obvious exits:"
+  it prints each exit on a line by itself, in the format:
+  <Exit Name> leads to <Exit Destination>.
+  Thus, you might have:
+     Obvious exits:
+     South leads to Joe's Room.
+     East leads to City Park.
+  instead of
+     Obvious exits:
+     South  East
+
+  Exits set OPAQUE are still shown in the short format, so you can mix
+  the two.
+
+(continued in help transparent2)
+& TRANSPARENT2
+
+  If this flag is set on an exit, when a player looks at the exit they
+  will see the description and contents of the destination room following
+  the exit's description.  The exit list and succ/fail messages of the
+  room will NOT be displayed. See also CLOUDY.
+
+  See also: CLOUDY, OPAQUE, EXITS, @exitformat
+& UNFINDABLE
+  Flag:  UNFINDABLE  (all types)
+  
+  If a player is set UNFINDABLE, he cannot be found by the @whereis 
+  command. You also cannot use loc(), locate(), and similar functions
+  to find his location, unless you have the see_all power or equivalent.
+  
+  If a room is set UNFINDABLE, you cannot locate any of its contents
+  via any means (@whereis, the loc() function, etc.) unless you are
+  see_all or equivalent.
+  
+  If a player who can @hide and idle is set UNFINDABLE, and he is idle past 
+  the allowable maximum idle time, he will be hidden automatically.
+& UNINSPECTED 
+  Flag: UNINSPECTED (rooms)
+
+  This flag may be used by the MUSH to indicate rooms which have not been
+  inspected by the Building Council, Administration, etc.
+& UNREGISTERED
+  Flag: UNREGISTERED (players)
+  
+  This flag may be used by the MUSH to support on-line registration.
+  The only restriction on UNREGISTERED players is that they may not
+  be granted @powers.
+& VERBOSE
+  Flag:  VERBOSE  (all types)
+
+  An object set VERBOSE echoes the commands it executes to its owner
+  before executing them. This differs from the PUPPET flag in that the
+  owner sees the command itself, rather than the output from the command.
+  This flag is extremely useful in debugging, especially if used in
+  conjunction with the PUPPET flag. VERBOSE output follows the format
+  "#<object>] <command>". Something like "#1300] @trigger me/test" is a
+  good example of typical VERBOSE output.
+
+  See also: PUPPET, DEBUG
+& VISUAL 
+  Flag:  VISUAL  (all types)
+
+  When this flag is set on an object, it allows any other player to
+  examine it and see all the object's attributes as if they owned it.
+  It does not enable them to make changes to the object. Very useful
+  for getting help with code.
+
+  See also: examine, brief
+& WIZARD
+  Flag:  WIZARD    (all types)
+
+  This flag may or may not do anything on the game your on, considering
+  the CobraMUSH Division system has made this flag obsolete.  However
+  some places have the flag in sake of compatibility.
+
+  See also: ROYALTY
+& Z_TEL
+  Flag:  Z_TEL  (things, rooms)
+
+  The Z_TEL flag, when set on a zoned room or on the ZMO of a room, 
+  prevents objects in the room from being @teleported out of the
+  zone - that is, objects can only be @teleported to a room which
+  is zoned to the same ZMO. Setting this flag on the ZMO affects all
+  rooms zoned to it. Like NO_TEL, the "home" command will still work.
+  This flag is intended for use in puzzle rooms and IC areas.
+
+  See also: ZONES, ZONE MASTERS, @chzone, ZONE MASTER ROOMS
+& SHARED
+& ZONE
+  Flag:  SHARED   (players) 
+
+  The SHARED flag is used to designate a player as a Zone Master.
+  Objects owned by a Zone Master are controlled by anyone who passes the 
+  player's zone lock.  
+
+  See also: ZONE MASTERS
+& LISTEN_PARENT
+  Flag:  LISTEN_PARENT (things, rooms)
+  
+  When set on a thing or room which also has the MONITOR flag set, this
+  flag causes ^ listen patterns to be checked on the object's parents as
+  well as on the object.
+
+  See also: MONITOR, LISTENING
+
diff --git a/game/txt/hlp/cobra_func.hlp b/game/txt/hlp/cobra_func.hlp
new file mode 100644 (file)
index 0000000..27aab98
--- /dev/null
@@ -0,0 +1,4190 @@
+& FUNCTIONS
+  Functions are specialized commands used to manipulate strings and
+  other input. Function take the general form:  [FUNCTION(<input>)]
+    
+  The brackets are used to delimit and force evaluation of the function 
+  (or nested functions). The brackets can also be used to group functions 
+  for the purposes of string concatenation. In general, more than one pair 
+  of brackets is not required, but liberal use of them makes code easier to 
+  read. You can nest an arbitrary number of brackets.
+
+  Examples:
+      > say [first(rest(This is a nice day))]
+      You say, "is"
+      > @va me=This is a 
+      Wizard - Set.
+      > @vb me=nice day
+      Wizard - Set.
+      > say [first([rest([v(va)] [v(vb)])])]
+      You say, "is"
+  
+  See "help FUNCTIONS2" for more.
+& FUNCTIONS2
+  
+  A list of available built-in functions can be obtained via the command
+  "@config/functions". In the help text, the list is under the topic
+  "FUNCTION LIST".
+  
+  In addition to these built-in functions are MUSH-defined "global user
+  functions."  These are defined by objects with the "GFuncs" power, via 
+  the "@function" command. To the user, they act just like the built-in g
+  ame functions. For details on global user functions, see "help @function".
+  
+  See also: MUSHCODE
+& FUNCTION LIST
+  Several major variants of functions are available. The help topics
+  are listed below, together with a quick summary of the function type
+  and some examples of that type of function.
+  Attribute functions: attribute-related manipulations (GET, UFUN)
+  Bitwise functions: Manipulation of individual bits of numbers (SHL, BOR)
+  Boolean functions:  produce 0 or 1 (false or true) answers  (OR, AND)
+  Channel functions: Get information about channels (CTITLE, CWHO)
+  Communication functions: Send messages to objects (PEMIT, OEMIT)
+  Connection functions: Get information about a player's connection (CONN)
+  Dbref functions: return dbref info related to objects (LOC, LEXITS)
+  Html functions: output html tags for Pueblo-compatible clients
+  Information functions:  find out something about objects (FLAGS, MONEY)
+  List functions:  manipulate lists (REVWORDS, FIRST)
+  Mail functions: manipulate @mail (MAIL, FOLDERSTATS)
+  Math functions:  number manipulation, generic or integers only (ADD, DIV)
+  Regular expression functions: Regular expressions (REGMATCH, REGEDIT)
+  SQL functions: Access SQL databases (SQL, SQLESCAPE)
+  String functions:  string manipulation (ESCAPE, FLIP)
+  Time functions: Formatting and display of time (TIME, CONVSECS)
+  Utility functions: general utilities (ISINT, COMP)
+  
+  The command "@config/functions" lists all of the game's built-in functions.
+  The command "@function" lists all of the game's custom global functions
+    defined via the @function command.
+  
+& Attribute functions
+  All these functions access attributes on an object.
+  
+  aposs()       default()     edefault()    eval()        filter()
+  filterbool()  fold()        foreach()     get()         grep()
+  grepi()       lattr()       nattr()       obj()         poss()        
+  regrep()      regrepi()     subj()        udefault()    ufun()        
+  uldefault()   ulocal()      v-function    xget()        zfun()
+
+  See also: ATTRIBUTES, NON-STANDARD ATTRIBUTES
+& Bitwise functions
+  These functions treat integers as a sequence of binary bits (Either 0
+  or 1) and manipulate them.
+
+  For example, 2 is represented as '0010' and 4 as '0100'. If these two
+  numbers are bitwise-or'ed together with BOR(), the result is 6, or
+  (In binary) '0110'. These functions are useful for storing small
+  lists of toggle (Yes/No) options efficiently.
+
+  baseconv()    band()        bnand()       bnot()        bor()
+  bxor()        shl()         shr()
+
+& Boolean functions
+  Boolean functions all return 0 or 1 as an answer.
+  Your MUSH may be configured to use traditional PennMUSH booleans,
+  in which case non-zero numbers, non-negative db#'s, and strings
+  are all considered "true" when passed to these functions.
+  Alternatively, your MUSH may be using TinyMUSH 2.2 booleans,
+  in which case only non-zero numbers are "true".
+  
+  and()         cand()        cor()         eq()          gt()
+  gte()         lt()          lte()         nand()        neq()
+  nor()         not()         or()          t()           xor()
+    
+  See also: BOOLEAN VALUES, @config
+& Communication functions
+  Communication functions are side-effect functions that send a message
+  to an object or objects.
+
+  cemit()       emit()        lemit()       nsemit()      nslemit()
+  nsoemit()     nspemit()     nsremit()     nszemit()     oemit()
+  pemit()       remit()       zemit() 
+& Connection functions
+  Connection functions return information about the connections open 
+  on a game, or about specific connections.
+
+  cmds()        conn()        doing()       height()      hostname()
+  hidden()      idle()        ipaddr()      lports()      lwho()        
+  mwho()        ports()       pueblo()      recv()        sent()
+  ssl()         terminfo()    width()       idle_average() 
+& Dbref functions
+  Dbref functions return a dbref or list of dbrefs related to some value
+  on an object.
+  
+  children()    con()         entrances()   exit()        followers()   
+  following()   home()        lcon()        lexits()      loc()         
+  locate()      lparent()     lplayers()    lsearch()     lvcon()       
+  lvexits()     lvplayers()   next()        num()         owner()       
+  parent()      pmatch()      rloc()        rnum()        room()        
+  where()       zone()
+  
+  See also: DBREF
+& Information functions
+  Information functions return values related to objects or the game.
+  andflags()    andlflags()   config()      controls()    ctime()
+  elock()       findable()    flags()       fullname()    hasattr()
+  hasattrp()    hasflag()     haspower()    hastype()     iname()       
+  lflags()      lock()        lstats()      money()       mtime()
+  mudname()     name()        nattr()       nearby()      objid()
+  objmem()      orflags()     orlflags()    playermem()   poll()        
+  powers()      quota()       restarts()    type()        version()     
+  visible()     
+
+& List functions
+  List functions take at least one list of elements and return transformed
+  lists or one or more members of those lists. Most of these functions
+  can take an arbitrary <delimiter> argument to specify what delimits
+  list elements; if none is provided, a space is used by default.
+
+  element()     elements()    extract()     first()       grab()
+  graball()     index()       insert()      itemize()     items()
+  iter()        last()        ldelete()     map()         match()
+  matchall()    member()      mix()         munge()       remove()
+  replace()     rest()        revwords()    setdiff()     setinter()
+  setunion()    shuffle()     sort()        sortby()      splice()
+  step()        table()       wordpos()     words()
+    
+  See also: LISTS
+& Math functions
+  Math functions take one or more floating point numbers and return 
+  a numeric value.
+  abs()         acos()        add()         asin()        atan()
+  atan2()       bound()       ceil()        cos()         ctu()
+  dist2d()      dist3d()      e()           exp()         fdiv()
+  floor()       fmod()        fraction()    ln()          lmath()       
+  log()         max()         mean()        median()      min()         
+  mul()         pi()          power()       root()        round()
+  sign()        sin()         sqrt()        stddev()      sub()         
+  tan()         trunc()       val()
+  These functions operate only on integers (if passed floating point
+  numbers, they will return an error or misbehave):
+  dec()         div()         floordiv()    inc()         mod()
+  remainder()
+
+  These functions operate on n-dimensional vectors. A vector
+  is a delimiter-separated list of numbers (space-separated, by default):
+  vadd()        vcross()      vdim()        vdot()        vmag()        
+  vmax()        vmin()        vmul()        vsub()        vunit()
+& Regular expression functions
+  These functions take a regular expression (regexp, or re) and match
+  it against assorted things.
+  
+  regedit()     regeditall() regeditalli()  regediti()    regmatch()
+  regmatchi()   regrab()     regraball()    regraballi()  regrabi()
+  regrep()      regrepi()    reswitch()     reswitchall() reswitchalli()
+  reswitchi()
+  See also: string functions, regexp
+& SQL functions
+  These functions perform queries or other operations on an SQL
+  database to which the MUSH is connected, if SQL support is
+  available and enabled.
+
+  sql()         sqlescape()
+
+& String functions
+  String functions take at least one string and return a transformed
+  string, parts of a string, or a value related to the string(s).
+  
+  accent()      after()       alphamin()    alphamax()    art()
+  before()      brackets()    capstr()      case()        caseall()
+  cat()         center()      comp()        chr()         decrypt()
+  delete()      digest()      edit()        encrypt()     escape()
+  if()          ifelse()      lcstr()       left()        lit()
+  ljust()       merge()       mid()         ord()         pos()
+  regedit()     lpos()        regmatch()    repeat()      reverse()
+  right()       rjust()       scramble()    secure()      sha0()
+  space()       spellnum()    squish()      strcat()      strinsert()
+  stripaccents()stripansi()   strlen()      strmatch()    strreplace()
+  switch()      trim()        ucstr()       wrap()
+  See also: STRINGS
+& Time functions
+  These functions return times or format times.
+  
+  convsecs()    convutcsecs() convtime()    ctime()       etimefmt()
+  isdaylight()  mtime()       restarttime() secs()        starttime()
+  time()        timefmt()     timestring()  utctime()   
+
+& Utility functions
+  These functions don't quite fit into any other category.
+  
+  allof()       ansi()        atrlock()     beep()        break()      
+  checkpass()   clone()       create()      die()         dig()         
+  firstof()    functions()   isdbref()     isint()       isnum()       
+  isword()     localize()    link()        list()        lnum()        
+  null()       objeval()     open()        pcreate()     r-function    
+  rand()        s-function    scan()        set()         setq()        
+  setr()        soundex()     soundslike()  tel()         textfile()    
+  valid()       wipe()        @@()
+
+& @@()
+& NULL()
+  @@(<expression>)
+  null(<expression>[,<expression>,...])
+
+  The @@() function does nothing and returns nothing. It could be
+  used for commenting, perhaps. It does not evaluate its argument.
+
+  The null() function is similar, but does evaluate its argument(s),
+  so side-effects can occur within a null(). Useful for eating the
+  output of functions when you don't use that output.
+& ABS()
+  abs(<number>)
+  Returns the absolute value of a number. i.e. ABS(-4) returns 4;
+  ABS(2) returns 2, etc.
+& ACCENT()
+  accent(<string>, <template>)
+
+  The accent() function will return <string>, with characters in it
+  possibly changed to accented ones according to <template>. Both
+  arguments must be the same size.
+
+  Whether or not the resulting string is actually displayed correctly
+  is client-dependent. Some OSes uses different character sets than
+  the one assumes (ISO 8859-1), and some clients strip these 8-bit
+  characters.
+
+  See HELP ACCENT2 for a description of the template argument.
+
+  See also: stripaccents(), NOACCENTS
+& ACCENT2
+  For each character in <string>, the corresponding character of
+  <template> is checked according to the table below, and a replacement
+  done. If either the current <string> or <template> characters aren't
+  in the table, the <string> character is passed through unchanged.
+
+  Accent                         Template   String
+  Name       Description         Character  Character
+  --------------------------------------------------------------
+  grave      Backward slant      `          A,E,I,O,U,a,e,i,o,u
+             above letter
+  acute      Forward slant       '          A,E,I,O,U,Y,a,e,i,o,u,y
+             above letter
+  tilde      Wavy line above     ~          A,N,O,a,n,o
+             letter
+  circumflex carat above         ^          A,E,I,O,U,a,e,i,o,u
+             letter
+  umlaut     Two dots above      :          A,E,I,O,U,,a,e,i,o,u,y
+  diaeresis  letter
+  ring       Small circle above  o          A,a
+             letter
+  cedilla    Small tail below    ,          C,c
+             letter
+
+  See HELP ACCENT3 for more
+& ACCENT3
+  These are non-accent special characters, mostly punctuation and
+  non-roman letters.
+
+                      Template   String
+  Description         Character  Character
+  --------------------------------------------------------------
+  Upside-down ?       u          ?
+  Upside-down !       u          !
+  << quote mark       "          <
+  >> quote mark       "          >
+  German sharp s      B          s
+  Capital thorn       |          P
+  Lower-case thorn    |          p
+  Capital eth         -          D
+  Lower-case eth      &          o
+  
+  See HELP ACCENT4 for examples
+& ACCENT4
+  Some examples of accent() and their expected outputs:
+
+  > think accent(Aule, ---:)
+  Aul(e-with-diaeresis)
+
+  > think accent(The Nina was a ship, The Ni~a was a ship) 
+  The Ni(n-with-~)a was a ship
+
+  > think accent(Khazad ai-menu!, Khaz^d ai-m^nu!)
+  Khaz(a-with-^)d ai-m(e-with-^)nu!
+& ACOS()
+  acos(<cosine>[, <angle type>])
+  Returns the angle that has the given <cosine> (arc-cosine), with the
+  angle expressed in the given angle type, or radians by default.
+
+  See HELP CTU() for more on the angle type.
+& ADD()
+  add(<number>,<number>,...)
+
+  Returns the sum of some numbers. 
+& AFTER()
+  after(<string1>, <string2>)
+
+  Returns the portion of <string1> that occurs after <string2>.
+  If <string2> isn't in <string1>, the function returns a null string.
+  This is case-sensitive.
+  Examples:
+   > think after(foo bar baz,bar)
+    baz
+   > think after(foo bar baz,ba)
+   r baz
+
+& ALIGN()
+  align(<widths>,<col1>,..,<coln>[,<filler>[,<colsep>[,<rowsep>])
+
+  Creates columns of text, each column designated by <col1..coln>.
+  Each column is individually wrapped inside its own column, allowing
+  for easy creation of book pages, newsletters, or the like.
+
+  <widths> is a space-separated list of column widths. '10 10 10' for
+  the widths argument specifies that there are 3 columns, each 10
+  spaces wide. You can further modify this by prefixing the number
+  with '<', '-' or '>'. A < before a number causes the field to be
+  left-aligned. A '-' causes it to be centered, and '>' makes it
+  right-aligned. No prefix defaults to left-aligned. A '.' after the
+  number implies the column is to be repeated for as long as there is
+  a non-repeating column.
+
+  <filler> is a single character that, if given, is the character used
+  to fill empty columns and remaining spaces. <colsep>, if given, is
+  inserted between every column, on every row. <rowsep>, if given, is
+  inserted between every line. By default, <filler> and <colsep> are
+  a space, and <rowsep> is a newline.
+
+  Continued in HELP ALIGN2
+& ALIGN2
+  Examples:
+  
+    > &line me=align(<5 10 20,\([left(xget(%0,sex),1)]\),name(%0),name(%L))
+    > th iter(lwho(),u(line,##))
+      (M) Walker     Tree
+      (F) Jane Doe   Nowhere
+
+    > &haiku me = Alignment function,%rIt justifies your writing,%rBut the
+                  words still suck.%rLuke
+
+    > th [align(5 -40 5,,[repeat(-,40)]%r[u(haiku)]%r[repeat(-,40)],,%b,+)
+
+         +----------------------------------------+
+         +          Alignment function,           +     
+         +       It justifies your writing,       +     
+         +       But the words still suck.        +     
+         +                  Luke                  +
+         +----------------------------------------+
+
+& ALLOF()
+  allof(<expr1>[, ...,<exprN>], <osep>)
+
+  Evaluates every expression argument (including side-effects) and returns
+  the results of those which are true, in a list separated by osep.  The
+  output separator argument is required and must be a single character,
+  or can be left empty, in which case a space will be used to separate the
+  results by default.
+
+  The meaning of true or false depends on configuration options as
+  explained in the 'BOOLEAN VALUES' help topics.
+
+  > &s me=Bats are similar to Rats which are afraid of Cats
+  > say allof(grab(v(s),rats),grab(v(s),mats),grab(v(s),bats),)
+  You say, "Rats Bats"
+
+  > say allof(#-1,#101,#2970,,#-3,0,#319,null(This Doesn't Count),|)
+  You say, "#101|#2970|#319"
+
+  See also: firstof(), BOOLEAN VALUES
+& ALPHAMAX()
+  alphamax(<word1>, <word2>, <word3>, ...)
+
+  Takes any number of arguments, and returns the word which is
+  lexicographically biggest. I.e., which word would be last in 
+  alphabetical order.
+
+& ALPHAMIN()
+  alphamin(<word1>, <word2>, <word3>, ...)
+
+  Takes any number of arguments, and returns the word which is
+  lexicographically smallest: the word that would be first in
+  alphabetical order.
+
+& AND()
+& CAND()
+  and(<boolean value 1>,<boolean value 2>[, ... , <boolean value N>])
+  cand(<boolean value 1>,<boolean value 2>[, ... , <boolean value N>])
+  Takes boolean values, and returns 1 if all of them are equivalent
+  to true(1).  and() always evaluates all arguments (including side
+  effects), while cand() stops evaluation after the first argument
+  that evaluates to false.
+
+  See also: BOOLEAN VALUES, or(), xor(), not()
+& ANDFLAGS()
+  andflags(<object>,<string of flag letters>)
+
+  This function returns 1 if <object> has all the flags in a specified
+  string, and 0 if it does not. The string is specified with a single
+  letter standing for each flag, like the output of the FLAGS()
+  function. A '!'  preceding a flag letter means "not flag".
+
+  Thus, ANDFLAGS(me,WD) would return 1 if I was set WIZARD and DARK.
+  ANDFLAGS(me,W!Dc) would return 1 if I was set WIZARD, not DARK,
+  and CONNECTED.
+
+  If a letter does not correspond to any flag, <object> doesn't have it,
+  so the function returns 0. There can be an arbitrary number of flags. Do
+  not put spaces between flag letters.
+& ANDLFLAGS()
+  andlflags(<object>,<list of flags>)
+
+  This function returns 1 if <object> has all the flags in a specified
+  list, and 0 if it does not. The list is a space-separated list of
+  flag names.  A '!' preceding a flag name means "not flag".
+
+  Thus, ANDLFLAGS(me,haven dark) would return 1 if I was set HAVEN
+  and DARK.  ANDFLAGS(me,haven !Dark connected) would return 1 if I
+  was set HAVEN, not DARK, and CONNECTED.
+
+  If a name does not correspond to any flag, <object> doesn't have it,
+  so the function returns 0. There can be an arbitrary number of flags.
+& ANSI()
+  ansi(<codes>,<string>)
+  This allows you to highlight a string using ANSI terminal effects.
+  The codes are:
+        f - flash                       F - not flash
+        h - hilite                      H - not hilite
+        u - underscore                  U - not underscore
+        i - inverse                     I - not inverse
+        n - normal                      
+        x - black foreground            X - black background
+        r - red foreground              R - red background
+        g - green foreground            G - green background
+        y - yellow foreground           Y - yellow background
+        b - blue foreground             B - blue background
+        m - magenta foreground          M - magenta background
+        c - cyan foreground             C - cyan background
+        w - white foreground            W - white background
+  For example, "ansi(fc, Test)" would hilight "Test" in flashing cyan.
+  See also: ANSI, COLOR 
+& APOSS()
+  aposs(<object>)
+
+  Returns the absolute possessive pronoun - his/hers/its/theirs -
+  for an object.
+& ART()
+  art(<string>)
+
+  This function returns the proper article, "a" or "an", based on whether
+  or not <string> begins with a vowel.
+& ASIN()
+  asin(<sine>[, <angle type>])
+  Returns the angle with the given <sine> (arc-sine), with the angle
+  expressed in the given angle type, or radians by default.
+
+  See HELP CTU() for more on the angle type.
+& ATAN()
+& ATAN2()
+  atan(<tangent>[, <angle type>])
+  atan2(<number>, <number>[<, <angle type>]) 
+
+  Returns the angle with the given <tangent> (arc-tangent), with the
+  angle expressed in the given angle type, or radians by default.
+
+  atan2(y, x) is like atan(fdiv(y, x)), except x can be 0, and the
+  signs of both arguments are used in determining the sign of the
+  result. It is useful in converting between cartesian and polar
+  coordinates.
+
+  See HELP CTU() for more on the angle type.
+& ATRLOCK()
+  atrlock(<object>/<attrib>[, <on|off>])
+
+  When given a single object/attribute pair as an argument, returns 1
+  if the attribute is locked, 0 if unlocked, and #-1 if the attribute
+  doesn't exist or can't be read by the function's caller.
+
+  When given a second argument of "on" (or "off"), attempts to lock
+  (unlock) the specified attribute, as per @atrlock.
+& BAND()
+  band(<integer>,<integers>,...)
+
+  Does a bitwise AND of all its arguments, returning the result
+  (A number with only the bits set in every argument set in it).
+& BASECONV()
+  baseconv(<number>, <from base>, <to base>)
+
+  Converts <number>, which is in base <from base> into base <to base>.
+  The bases can be between 2 (binary) and 36. 
+& BEEP()
+  beep([<number>])
+  Sends <number> "alert" bell characters. <number> must be in the range
+  1 to 5, or, if unspecified, defaults to 1.
+
+  This function may only be used by 'Admin' type players.
+& BEFORE()
+  before(<string1>, <string2>)
+  Returns the portion of <string1> that occurs before <string2>.
+  If <string2> isn't in <string1>, <string1> is returned.
+  This is case-sensitive.
+  Examples:
+   > think before(foo bar baz,bar)
+   foo
+   > think before(foo bar baz,r)
+   foo ba
+
+& BRACKETS()
+  brackets([<string>])
+
+  Returns a count of the number of left and right square brackets, 
+  parentheses, and curly braces in the string, in that order, as a
+  space-separated list of numbers. This is useful for finding missing 
+  or extra brackets in MUSH code. 
+
+  Example:
+  > @desc me=This is [ansi(h,a test)] of the { brackets() function.
+  > think brackets(v(desc))
+  1 1 2 2 1 0
+& BNAND()
+  bnand(<integer>, <integer>)
+
+  Returns its first argument with every bit that was set in the second
+  argument cleared.
+& BNOT()
+  bnot(<integer>)
+
+  Returns the bitwise complement of its argument. Every bit set in it
+  is cleared, and every clear bit is set.
+& BOR()
+  bor(<integer>, <integer>, ...)
+
+  Does a bitwise OR of all its arguments, returning the result.
+  (A number with a bit set if that bit appears in any of its arguments).
+& BOUND()
+  bound(<number>, <lower bound>, <higher bound>)
+
+  bound() returns <number> if it is between <lower bound> and
+  <higher bound>. If it's lower than the lower bound, the lower
+  bound is returned. If it's higher than the higher bound,
+  the higher bound is returned.
+  
+  See also: ceil(), floor(), round(), trunc()
+& BXOR()
+  bxor(<integer>, <integer>,...)
+
+  Does a bitwise XOR of all its arguments, returning the result.
+  (A number with a bit set if it's set in only one of its arguments).
+& CAPSTR()
+  capstr(<string>)
+  
+  Returns <string> with the first character capitalized.
+  Example: capstr(foo bar baz) returns "Foo bar baz"
+& CAT()
+  cat(<string1>,<string2>[,<string3>,<string4>,...])
+
+  cat() concatenates strings, separating each string by a space.
+  So "[cat(one, two)]" will return 'one two'.
+& CEIL()
+  ceil(<number>)
+  Returns the least integral value greater than or equal to <number>.
+
+  See also: floor(), bound(), round(), trunc()
+& CENTER()
+  center(<string>,<width>[,<fill>])
+  This function will center <string> within a field <width> characters wide,
+  using <fill> characters for padding on either end of the string for
+  centering. If <fill> is not specified, a space will be used.
+  Example:
+    > say center(X,5,-)
+    You say, "--X--"
+    > say center(.NEAT.,15)
+    You say, "    .NEAT.      "
+
+& CHECKPASS()
+  checkpass(<player>,<string>)
+
+  Returns 1 if <string> matches the player's password otherwise 0.
+  If <player> has no password, this function will always return 1.
+  <player> should be specified as a dbref or *<name>.
+
+  This function is restricted to Director marked objects.
+
+& CHR()
+& ORD()
+  chr(<number>)
+  ord(<character>)
+
+  ord() returns the numerical value of the given character.
+  chr() returns the character with the given numerical value.
+
+  Examples:
+  > think ord(A)
+  65
+  > think chr(65)
+  A
+& CLONE()
+  clone(<object>)
+  This function clones <object>, and returns the dbref number of the clone.
+
+  This is a side-effect function and may not be enabled on some MUSHes.
+& CMDS()
+  cmds(<player|descriptor>)
+
+  Returns the number of commands issued by a player during this
+  connection as indicated by WHO. 
+
+  The caller can use the function on himself, but using on any other
+  player requires privileged power such as Wizard, Royalty or SEE_ALL.
+
+  See also: Connection Functions
+& SENT()
+  sent(<player|descriptor>)
+
+  Returns the number of characters sent by a player during this
+  connection as indicated by SESSION. 
+
+  The caller can use the function on himself, but using on any other
+  player requires privileged power such as Wizard, Royalty or SEE_ALL.
+
+  See also: Connection Functions
+& RECV()
+  recv(<player|descriptor>)
+
+  Returns the number of characters received by a player during this
+  connection as indicated by SESSION. 
+
+  The caller can use the function on himself, but using on any other
+  player requires privileged power such as Wizard, Royalty or SEE_ALL.
+
+  See also: Connection Functions
+& COMP()
+  comp(<value1>, <value2>[, <type>])
+
+  Comp compares two values.  It returns 0 if they are the same, -1 if
+  value1 is less than/precedes alphabetically value2, and 1 
+  otherwise.
+
+  By default the comparison is a case-sensitive lexicographic (string)
+  comparison. By giving the optional <type>, the comparison can
+  be specified:
+      <type>            Comparison
+        A               Maybe case-sensitive lexicographic (default)
+        I               Always case-insensitive lexicographic
+        D               Dbrefs of valid objects
+        N               Integers
+        F               Floating point numbers
+
+  Whether or not the a sort type is case-sensitive or not depends
+  on the particular mush and its environment.
+& CON()
+  con(<object>)
+
+  Returns the dbref of the first object in a container.
+
+  You can get the complete contents of any container you may examine,
+  regardless of whether or not objects are dark.  You can get the
+  partial contents (obeying DARK/LIGHT/etc.) of your current location
+  or the enactor (%#).  You CANNOT get the contents of anything else,
+  regardless of whether or not you have objects in it.
+
+  See also: lcon(), next()
+& CONFIG()
+  config()
+  config(<option>)
+
+  With no arguments, this function returns a list of config option names.
+  Given a config option name, this function returns its value.
+  Boolean configuration options will return values of "Yes" or "No".
+
+  Ex: config(money_singular) => "Penny"
+
+& CONN()
+  conn(<player|descriptor>)
+  This function returns the number of seconds a player has been connected.
+  <player name> must be the full name of a player, or a player's dbref.
+  Players who are not connected have a conn value of "-1", as do dark
+  directors, when conn() is used on them by a non-priv'ed player.
+  See also: CONNECTED
+& CONTROLS()
+  controls(<object>,<victim>)
+  
+  This function returns 1 if <object> controls <victim>, or 0, if
+  it does not. If one of the objects does not exist, it will return
+  #-1 ARGN NOT FOUND (where N is the argument which is the invalid
+  object). You must control <object> or <victim>, or have the See_All
+  power, to use this function.
+
+  See also: CONTROL
+  
+& CONVSECS()
+& CONVUTCSECS()
+  convsecs(<seconds>[, <zone>])
+  convutcsecs(<seconds>) 
+
+  This function converts seconds to a time string, based on how many
+  seconds the number is after Jan 1, 1970 UTC. Because it's based on
+  UTC, but returns local time, convsecs(0) is not going to be
+  "Thu Jan 1 00:00:00 1970" unless you're in the UTC (GMT) timezone.
+  convutcsecs() and convsecs() with a second argument of 'utc' return
+  the time based on UTC time instead of the server's local time.
+  
+  Example:
+  > say [secs()]
+  You say, "709395750"
+  > say [convsecs(709395750)]
+  You say, "Wed Jun 24 10:22:54 1992"
+  > say [convutcsecs(709395750)]
+  You say, "Wed Jun 24 14:22:30 1992"
+  See also: convtime(), time()
+& CONVTIME()
+  convtime(<time string>)
+
+  This functions converts a time string (in the local time zone) to the
+  number of seconds since Jan 1, 1970 GMT. A time string is of the
+  format: Ddd MMM DD HH:MM:SS YYYY where Ddd is the day of the week,
+  MMM is the month, DD is the day of the month, HH is the hour in
+  24-hour time, MM is the minutes, SS is the seconds, and YYYY is the
+  year.  If you supply an incorrectly formatted string, it will return
+  -1.
+
+  If the extended convtime() is supported (See @config compile), more
+  formats for the date are enabled, including ones missing the day
+  of week and year, and a 'Month Day Year' format.
+
+  Example:
+  > say [time()]
+  You say, "Wed Jun 24 10:22:54 1992"
+  > say [convtime(Wed Jun 24 10:22:54 1992)]
+  You say, "709395774"
+
+  See also: convsecs(), time()
+& COS()
+  cos(<angle>[, <angle type>])
+  Returns the cosine of <angle>. Angle must be in the given angle
+  type, or radians by default. 
+
+  Example:
+  > say cos(90, d)
+  You say, "0" 
+  > say cos(1.570796)
+  You say, "0"
+
+  See HELP CTU() for more on the angle type.
+& PCREATE()
+  pcreate(<name>,<password>)
+
+  Creates a player with a given name and password. Wizard-only.
+
+  See also: @pcreate
+& CREATE()
+  create(<object>, <cost>)
+  This function creates an object with name <object> for <cost> pennies,
+  and returns the dbref number of the created object.
+
+  This is a side-effect function and may not be enabled on some MUSHes.
+& CTIME()
+  ctime(<object>)
+  If creation times are enabled, this function will return the 
+  date and time that the object was created, if the player is
+  able to examine the object.
+& CTU()
+  ctu(<angle>, <from>, <to>)
+
+  Converts between the different ways to measure angles.
+  <from> controls what the angle is treated as, and <to> what form
+  it is turned into. They can be 'd' for degrees, or 'r' for radians.
+  There is also a third way to measure angle, 'g' for gradians, but it's not
+  used often and is only included for completeness.
+
+  As a refresher, there are 180 degrees in pi radians in 200 gradians.
+
+  Example:
+  > say 90 degrees is [ctu(90, d, r)] radians
+  You say, "90 degrees is 1.570796 radians"
+& DEC()
+  dec(<integer>)
+  dec(<string-ending-in-integer>)
+
+  Dec returns the integer minus 1. If given a string that ends in an integer,
+  it decrements only the final integer portion. That is:
+
+  > think dec(3)
+  2
+  > think dec(hi3)
+  hi2
+  > think dec(1.3.3)
+  1.3.2
+  > think dec(1.3)
+  1.2
+
+  Note especially the last example, which will trip you up if you use
+  floating point numbers with dec() and expect it to work like sub().
+  See also: inc()
+& DECRYPT()
+  decrypt(<string>, <password>)
+
+  Decrypts a string encrypted with the encrypt() function, if given
+  the string and the same password.
+
+& DEFAULT()
+  Function:  default([<obj>/]<attr>,<default case>)
+  This function returns the value of <obj>/<attr>, as if retrieved via
+  the get() function, if the attribute exists and is readable by you.
+  Otherwise, it evaluates the default case, and returns that.
+  Note that the default case is only evaluated if the attribute does
+  not exist or cannot be read. Note further than an empty attribute
+  counts as an existing attribute.
+  This is useful for code that needs to return the value of an attribute,
+  or an error message or default case, if that attribute does not exist.
+  Examples:
+    > &TEST me=apple orange banana
+    > say default(me/Test, No fruits!)
+    You say "apple orange banana"
+    > &TEST ME
+    > say default(me/Test, No fruits!)
+    You say "No fruits!"
+  See also: get(), eval(), ufun(), edefault(), udefault(), uldefault()
+& DELETE()
+  delete(<string>, <first>, <len>)
+  Return a modified <string>, with <len> characters starting after the
+  character at position <first> deleted. In other words, it copies <first>
+  characters, skips <len> characters>, and then copies the remainder of
+  the string.
+  Example:
+    > say [delete(abcdefgh, 3, 2)]
+    You say, "abcfgh"
+& DIE()
+  die(<number of times to roll die>, <number of sides on die>[, <show>])
+  This function simulates rolling dice. It "rolls" a die with a given
+  number of sides, a certain number of times, and sums the results.
+  For example, DIE(2, 6) would roll "2d6" - two six-sided dice,
+  generating a result in the range 2-12. The maximum number of
+  dice this function will roll in a single call is 20.
+  If a third argument is given and it's a true value, the result will
+  be a space-seperated list of the individual rolls rather than their
+  sum.
+
+  Example:
+  > think die(3, 6)
+  6
+  > think die(3, 6, 1)
+  5 2 1  
+& DIG()
+  dig(<name> [, <exit to> [, <exit from>]])
+  This function digs a room called <name>, and optionally opens and links
+  <exit to> and <exit from>, like the normal @dig command. It returns
+  the dbref number of the new room.
+
+  This is a side-effect function and may not be enabled on some MUSHes.
+& DIGEST()
+  digest(<algorithm>, <string>)
+
+  Returns a checksum (Hash, digest, etc.) of <string> using the given
+  <algorithm>. If the mush is compiled with SSL support (See @config
+  compile), <algorithm> can be one of:
+
+  md2 md4 md5 sha sha1 dss1 mdc2 ripemd160
+
+  Without SSL, only the sha algorithm is enabled. In both cases, sha returns
+  the same thing as the sha0() function.
+
+  See also: sha0()
+& DIST2D()
+  dist2d(x1, y1, x2, y2)
+
+  Returns the distance between two points in the Cartesian
+  plane that have coordinates (x1, y1) and (x2, y2). 
+& DIST3D()
+  dist3d(x1, y1, z1, x2, y2, z2)
+
+  Returns the distance between two points in space, with
+  coordinates (x1, y1, z1) and (x2, y2, z2).
+& DIV()
+& FLOORDIV()
+  div(<number>,<number>)
+  floordiv(<number>,<number>)
+  Div returns the integer part of the quotient of the first number
+  divided by the second number.  Floordiv returns the largest integer
+  less than or equal to the quotient of the first number divided by
+  the second.  For positive numbers, these are the same thing, but
+  for negative numbers they may be different:
+
+   div(13,4)          ==>  3       and     floordiv(13,4)     ==>  3
+   div(-13,4)         ==>  -3      but     floordiv(-13,4)    ==>  -4
+   div(13,-4)         ==>  -3      but     floordiv(13,-4)    ==>  -4
+   div(-13,-4)        ==>  3       and     floordiv(-13,-4)   ==>  3
+
+  Note that add(mul(div(%0,%1),%1),remainder(%0,%1)) always yields %0,
+  and add(mul(floordiv(%0,%1),%1),modulo(%0,%1)) also always yields %0.
+
+  See also: MODULO
+& DOING()
+  doing(<player|descriptor>)
+
+  Given the name of a connected player, returns that player's @doing
+  string if they can be seen on the WHO list.
+
+  See also: @poll, @doing, poll()
+& E()
+  e()
+  Returns the value of "e"  (2.71828182845904523536, rounded to the
+  game's float_precision setting).
+& EDEFAULT()
+  Function:  edefault([<obj>/]<attr>,<default case>)
+  This function returns the evaluated value of <obj>/<attr>, as if
+  retrieved via the get_eval() function, if the attribute exists and
+  is readable by you. Otherwise, it evaluates the default case, and
+  returns that. The default case is only evaluated if the attribute
+  does not exist or cannot be read.
+  Example:
+    > &TEST me=You have lost [rand(10)] marbles.
+    > say edefault(me/Test,You have no marbles.)
+    You say "You have lost 6 marbles."
+    > &TEST me
+    > say edefault(me/Test,You have no marbles.)
+    You say "You have no marbles."
+  
+  See also: get(), eval(), ufun(), default(), udefault()
+& EDIT()
+  edit(<string>, <search>, <replace>[, <search2>, <replace2> ...])
+  This functions in a similar way to the @edit command; instead of
+  taking an attribute from an object, it takes an arbitrary string.
+  It searches the string for <search> and replaces with <replace>,
+  then repeats the process if additional search-replace pairs are
+  given.
+
+  If <search> is a caret (^), <replace> is prepended.
+  If <search> is a dollar sign ($), <replace> is appended.
+  If <search> is an empty string, <replace> is inserted between
+  every character, and before the first and after the last.
+  If <replace> is an empty string, <search> is deleted from the string.
+
+  Example:
+  > say [edit(this is a test,^,I think%b,$,.,a test,an exam)]
+  You say "I think this is an exam." 
+
+  edit() can not replace a literal single ^ or $. Use regedit() for that.
+  
+  See also: @edit, regedit()
+& ELEMENT()
+  element(<list>,<item>,<single-character separator>)
+  
+  This returns the position of <item> in <list>, where <list>'s items
+  are separated by <separator>. A wildcard match is done, so this 
+  function behaves much like MATCH(), except its separator argument
+  is required, not optional.
+  
+  Example:
+    > say [element(this|is|a|test|string,is,|)]
+    You say, "2"
+
+  See also: match(), grab()
+& ELEMENTS()
+  elements(<list of words>,<list of numbers>[,<delim>][, <osep>])
+  This function returns the words in <list of words> that are in the
+  positions specified by <list of numbers>. Optionally, a list delimiter
+  other than a space can be used.
+  Examples:
+    > say elements(Foo Ack Beep Moo Zot,2 4)
+    You say "Ack Moo"
+    > say elements(Foof|Ack|Beep|Moo,3 1,|)
+    You say "Beep|Foof"
+
+  See also: extract(), index(), grab()
+& ELOCK()
+  elock(<object>[/<locktype>], <victim>)
+
+  elock() returns 1 if the <victim> would pass the lock on <object>,
+  and 0 if it would fail. You must control <object>.
+  
+  You can also provide a switch after <object> if you wish to check
+  something other than the basic lock. This can be used to test
+  user-defined locks. elock() can take as many switches as @lock.
+
+  For example:
+      @lock/drop Dancing Slippers=#0
+      think elock(Dancing Slippers/drop, Princess)
+      > 0
+  
+  See also: @lock, locktypes
+& EMIT()
+  emit(<message>)
+  nsemit(<message>)
+
+  Sends a message to the room, as per @emit.
+
+  nsemit() is a privileged variation that works like @nsemit.
+
+& ENCRYPT()
+  encrypt(<string>, <password>) 
+
+  Returns an encrypted string produced by a simple password-based
+  encrypted algorithm. Good passwords are long passwords.
+  This is not high-security encryption.  See also decrypt().
+& ENTRANCES()
+  entrances([<object> [,<type> [,<begin> [,<end>]]]])
+
+  With no arguments, the entrances() function returns a list of all
+  exits, things, players, and rooms linked to your location, like
+  @entrances. You can specify an object other than your current location
+  with <object>. You can limit the type of objects found by specifying
+  <type> as follows:
+        a        all (default)
+        e        exits
+        t        things
+        p        players
+        r        rooms
+  You can also limit the range of the dbrefs searched by giving <begin>
+  and <end>.
+
+  This function is computationally expensive and costs the same as
+  @find. You must control the object in order to perform entrances()
+  on it.         
+& EQ()
+  eq(<number1>,<number2>)
+
+  Takes two numbers, and returns 1 if they are equal, 0 otherwise.
+  
+  See also: neq()
+& ESCAPE()
+  escape(<string>)
+  The ESCAPE() function "escapes out" potentially "dangerous" characters,
+  preventing function evaluation in the next pass of the parser. It 
+  returns <string> after adding the escape character ('\') at the 
+  beginning of the string, and before the following characters:
+  %  ;  [  ]  {  }  \ ( ) ,
+  
+  This function prevents strings entered by players from causing side 
+  effects, such as performing an unintended GET() of an attribute. It
+  is only needed when the resulting string will be passed through @force
+  or used as an attribute for an object (like the description of a mail
+  message object).  Since the function preserves the original string, 
+  it is, in most cases, a better choice than SECURE().
+  
+& EVAL()
+& GET_EVAL()
+  eval(<object>, <attribute>)
+  get_eval(<object>/<attribute>)
+  Eval() works the same way as xget(), except that it performs %-substitutions
+  and function evaluation on the attribute before returning the value. eval()
+  does not modify the stack (%0-%9), so the attribute being evaled sees the
+  same values for them that the calling code does. Unless you need this behavior,
+  it is better to use u() instead, which hides the caller's stack.
+
+    Example:
+      &TEST me=%B%B%B-[name(me)]
+      think xget(me,test)
+      > %B%B%B-[name(me)]
+      think eval(me,test)
+      >    -Shalott
+
+  Get_eval() does the same thing, except it uses the format of get() instead
+  of xget() -- using a slash rather than a comma to separate the object from
+  the attribute. It is included for TinyMUSH 2.x compatibility.
+  
+  See also: get(), u(), xget()
+& EXIT()
+  exit(<object>)
+
+  Returns the dbref of the first exit in a room.
+
+  You can get the complete exit list of any room you may examine,
+  regardless of whether or not exits are dark.  You can get the partial
+  exit list (obeying DARK/LIGHT/etc.) of your current location or the
+  enactor (%#).  You CANNOT get the exit list of anything else,
+  regardless of whether or not you have objects in it.
+
+  See also: lexits(), next()
+& EXP()
+  exp(<number>)
+  Returns e to the power of <number>.
+& EXTRACT()
+  extract(<list>,<first>,<length>[,<delimiter>])
+
+  This function returns <length> elements of a list, counting
+  from the <first> element.
+
+  For example:
+    think extract(This is a test string,3,2)
+    > a test
+
+  See also: index(), elements(), grab()
+& FDIV()
+  fdiv(<numerator>,<denominator>)
+  Returns the quotient of the two numbers. Note that the DIV() and MOD()
+  functions cannot be used on floating point numbers.
+& FILTER()
+& FILTERBOOL()
+  filter([<obj>/]<attr>, <list>[,<delimiter>][,<osep>])
+  filterbool([<obj>]/<attr>, <list>[, <delimiter>][,<osep>])  
+
+  This function returns the elements of <list> for which a user-defined
+  function evaluates to "1", or to a boolean true value if filterbool()
+  is used. That function is specified by the first argument (just as
+  with the ufun() function), and the element of the list being tested is
+  passed to that user-defined function as %0.
+  
+  Thus, "filter(obj/attr, x1 x2 x3)" is nearly equivalent to
+  "iter(x1 x2 x3, switch(ufun(obj/attr, ##),1,##,))"
+  though the iter version may have extra blank spaces.
+  
+  Example:
+  
+    > &IS_ODD test=[mod(%0,2)]
+    > say [filter(test/is_odd, 1 2 3 4 5 6)]
+    You say, "1 3 5"
+
+  See also: anonymous attributes
+& FINDABLE()
+  findable(<object>,<victim>)
+  This function returns 1 if <object> can locate <victim>, or 0, if
+  it cannot. If one of the objects does not exist, it will return
+  #-1 ARGN NOT FOUND (where N is the argument which is the invalid
+  object).
+& FIRST()
+  first(<list>[,<delimiter>])
+
+  Returns the first element of a list.
+
+  See also: rest(), last()
+& FIRSTOF()
+  firstof(<expr1>, <expr2>[, ... ,<exprN>])
+
+  Returns the first evaluated expression that is true.  If no arguments
+  are true, then the last argument, <exprN>, is returned as the default
+  expression, whether it is true or false.  
+
+  The meaning of true or false is dependent on configuration options
+  as explained in the 'BOOLEAN VALUES' help topics.
+
+  This function does evaluate each argument while testing, including
+  side-effects, stopping (short-circuits) when the true expression is
+  found.
+
+  > say firstof(0,2)
+  You say, "2"
+  > say firstof(10,11,0)
+  You say, "10"
+  > say firstof(grab(the cat,mommy),grab(in the hat,daddy),#-1 Error)
+  You say, "#-1 Error"
+  > say firstof(get(%#/royal cheese),#-1 This Has No Meaning,0,)
+  You say, ""
+
+  See also: allof(), BOOLEAN VALUES
+& FLAGS()
+  flags(<object>)
+  flags(<object>/<attribute>)
+  flags()
+
+  Flags returns a string consisting of the flags attached to the 
+  object or the attribute on the object. The string is a single word
+  made up of all the appropriate flag letters.
+
+  Given no arguments, this function returns a string consisting of
+  all the flag letters of all the flags the server knows. Note that
+  some flags may not have flag letters, and multiple flags may have
+  the same letter (and will appear twice).
+
+  See also: lflags()
+& LFLAGS()
+  lflags(<object>)
+  lflags(<object>/<attribute>)
+  lflags()
+
+  Lflags returns a space-separated list consisting of the names of flags
+  attached to the object or the attribute on the object.
+
+  Given no arguments, this function returns a space-separated list
+  of all flag names know to the server.
+
+  See also: flags()
+& FLIP()
+& REVERSE()
+  flip(<string>)
+  reverse(<string>)
+
+  This function reverses a string. For example, "flip(foo bar baz)"
+  returns "zab rab oof".
+& FLOOR()
+  floor(<number>)
+  Returns the greatest integral value less than or equal to <number>.
+
+  See also: ceil(), bound(), round(), trunc()
+& FMOD()
+  fmod(<number>,<divisor>)
+
+  Identical to mod() but may take floating point arguments. The return
+  value is the (possibly floating point) smallest positive remainder
+  left after subtracting the largest number of multiples of <divisor>
+  from <number>.
+
+  Example:
+  > think fmod(6.1,2.5)
+  1.1
+& FOLD()
+  fold([<obj>/]<attr>, <list>[, <base case>][,<delimiter>])
+  
+  This function "folds" a list through a user-defined function, specified
+  by the first argument to fold(), which is analogous to ufun()'s first
+  argument.
+  
+  If no base case is provided, fold() passes the first element of <list>
+  as %0, and the second element of <list> as %1, to the user-defined
+  function. The user-defined function is then called again, with the
+  result of the first evaluation being %0, and the next (third) element
+  of the list as %1. This is repeated until all the elements of the 
+  list have been used.
+  
+  If a base case is provided, it is passed as %0, and the first element
+  of list is passed as %1, to the user-defined function. The process for
+  the no-base-case fold() is then used.
+  
+  See 'help FOLD2' for examples.
+& FOLD2
+  Examples:
+  
+    > &REP_NUM test=%0[repeat(%1,%1)]
+    > say [fold(test/rep_num,1 2 3 4 5)]
+    You say, "122333444455555"
+    > say [fold(test/rep_num,1 2 3 4 5,List:)]
+    You say, "List:122333444455555"
+  
+    > &ADD_NUMS test=add(%0,%1)
+    > say [fold(test/add_nums,1 2 3 4 5)]
+    You say, "15"
+
+  See also: anonymous attributes
+& FOLDERSTATS()
+  folderstats()
+  folderstats(folder#)
+  folderstats(player)
+  folderstats(player,folder#)
+
+  FOLDERSTATS() returns the number of read, unread, and cleared messages
+  in a specific folder, or, if none is given, the player's current
+  folder. Only Wizards may use forms which get other players' mail
+  information.
+& FOLLOWERS()
+  followers(<object>)
+
+  Returns the list of things and players following object. You must
+  control object.
+& FOLLOWING()
+  following(<object>)
+
+  Returns the list of things and players that the object is following.
+  You must control object.
+& FOREACH()
+  foreach([<object>/]<attribute>,<string>[,<start>[,<end>]])
+  Maps a function onto a string.
+  Each character in <string> has the user-defined function of the first
+  argument performed on it; the character is passed to the function as
+  %0, and its position in the string as %1 (the first character has 
+  position 0). The results are concatenated. If a start character is given,
+  everything before that character is copied without passing it to the
+  function, and everything after it until the end of the string or an end
+  character is passed to the function. Anything left after the end character
+  is also copied unevaluated. The start and end characters themselves are 
+  not copied.
+Continued in HELP FOREACH2
+& FOREACH2
+  Examples:
+    > &add_one me=[add(%0,1)]
+    > say [foreach(add_one, 54321)]
+    You say, "65432"
+    > say [foreach(add_one, This is #0# number, #, #)]
+    You say, "This is 1 number"
+
+    > &is_alphanum me=[regmatch(%0, \[\[:alnum:\]\])]%b
+    > say [foreach(is_alphanum,jt1o+)]
+    You say, "1 1 1 1 0 "
+
+  See also: anonymous attributes
+& ACCNAME()
+  accname(<object>)
+
+  accname() returns the name of object <object>, applying the object's
+  @nameaccent, if any.
+
+  Related functions: NAME(), FULLNAME(), INAME()
+& FRACTION()
+  fraction(<number>)
+
+  This function returns a fraction representing the floating-point <number>.
+  Since not all numbers can be expressed as a fraction, dividing the
+  numerator by the denominator of the results will not always return the
+  original <number>, but something close to it.  
+
+  Examples:
+  > think fraction(.75)
+  3/4
+  > think fraction(pi())
+  348987/111086
+  > think fraction(2)
+  2
+& FULLNAME()
+  fullname(<object>)
+
+  Fullname() returns the name of object <object>. It is identical to
+  name() except that for exits, fullname() returns the complete
+  exit name, including all aliases.
+  
+  >"[fullname(south)]
+  You say, "South;sout;sou;so;s"
+
+  Related functions: NAME(), ACCNAME(), INAME()
+& FUNCTIONS()
+  functions()
+
+  Returns a space-separated list of the names of all function.
+& GET()
+  get(<object>/<attribute>)
+
+  The get() function returns the string stored in an object's attribute. 
+  You may get the attributes of objects you control, the attributes you
+  own on other objects, and publicly accessible attributes.
+  
+& GRAB()
+& REGRAB()
+& REGRABI()
+  grab(<list>, <pattern>[, <delimiter>])
+  regrab(<list>, <regexp>[, <delimiter>])
+  regrabi(<list>, <regexp>[, <delimiter>])
+  
+  This function returns the first word in list which matches the pattern.
+  For grab(), the pattern is specified as in match(); i.e., it 
+  can contain wildcards. For regrab(), the pattern is a regular expression.
+  regrabi() is case-insensitive.
+  
+  Basically, this is a much more efficient way to do:
+  extract(list, match(list, pattern, delimiter), 1, delimiter) or
+  the regular expression variation thereof.
+
+  See also: match(), extract(), element(), elements(), index(), regmatch(),
+            graball()
+& GRABALL()
+& REGRABALL()
+& REGRABALLI()
+  graball(<list>,<pattern>[,<delim>][,<output seperator])
+  regraball(<list>,<regexp>[,<delim>][,<output seperator])
+  regraballi(<list>,<regexp>[,<delim>][,<output seperator])
+  
+  These functions work identically to the grab() and regrab()/regrabi()
+  functions, save that they return all matches, not just the first: They 
+  return all words in the <list> which match <pattern>. 
+  If none match, an empty string is returned.
+  
+  Examples:
+
+  > say graball(This is a test of a test,test)
+  You say "test test"
+  > say graball(This is testing a test,tes*)
+  You say "testing test"
+  > say regraball(This is testing a test,s$)
+  You say "This is"
+  
+  See also: match(), matchall(), grab(), regmatch()
+& GREP()
+& REGREP()
+  grep(<object>,<attrs>,<pattern>)
+  regrep(<object>,<attrs>,<regexp>)
+  grepi(<object>,<attrs>,<pattern>)
+  regrepi(<object>,<attrs>,<regexp>)
+  
+  These functions return a list of attributes on <object> containing
+  <pattern> (or matching <regexp>).  <attrs> is a wildcard pattern for
+  attribute names to search.
+
+  The list returned is similar to that returned by @grep/list
+  <object>/<attrs>=<pattern>
+
+  Parsing _does_ occur before this function is invoked. Therefore,
+  "special" characters will need to be escaped out.  In grep(), 
+  <pattern> is NOT wildcard matched.
+
+  grep()/regrep() are case-sensitive. grepi()/regrepi() are case-insensitive.
+& GT()
+  gt(<num>,<num>)
+
+  Takes two numbers, and returns 1 if and only if the first is greater
+  than the second, and 0 otherwise.
+& GTE()
+  gte(<num>,<num>)
+
+  Takes two numbers, and returns 1 if and only if the first is greater
+  than or equal to the second, and 0 otherwise.
+& HASATTR()
+& HASATTRP()
+& HASATTRVAL()
+& HASATTRPVAL()
+  hasattr(<object>, <attribute name>)
+  hasattrp(<object>, <attribute name>)
+  hasattrval(<object>, <attribute name>)    
+  hasattrpval(<object>, <attribute name>)   
+  
+  The hasattr() functions check to see if an object has an attribute.
+  They return #-1 if the object does not exist or the attribute can't
+  be examined by the player. Otherwise, they return 1 if the attribute
+  is present and 0 if it is not.
+
+  hasattr() returns 1 if the object has the attribute, 0 if it doesn't.
+  
+  hasattrp() also checks for attributes inherited from parent objects.
+
+  hasattrval() returns 1 if the attribute exists and isn't empty.
+  
+  hasattrpval() is hasattrval() but checks parents.
+  
+& HASFLAG()
+  hasflag(<object>[/<attrib>], <flag name>)
+  Returns 1 if the object has the named flag, and 0 if it does not.
+  If the object does not exist, #-1 will be returned. You do not have
+  to control the object.
+  Example: hasflag(me, opaque) will return "1" or "0".
+
+  Unlike orflags() and andflags(), hasflag uses the *flag name*, not
+  the single character abbreviation. Many flag names have shorter
+  abbreviations which will also work (W for Wizard, roy for royalty).
+  
+  The "flags" ROOM, EXIT, and PLAYER are actually types. If you want
+  to check if an object "has" one of these flags, you must use the
+  HASTYPE() function.
+
+  If an attribute is given, checks to see if the attribute has the 
+  given attribute flag. See help attribute flags for attribute flag names.
+
+  See also: orlflags(), andlflags(), orflags(), andflags()
+& HASTYPE()
+  hastype(<object>, <type>)
+
+  Returns 1 if the object is of the named type, otherwise 0.
+  Valid types are: ROOM, EXIT, PLAYER, THING, GARBAGE.
+  If an invalid type is given, #-1 NO SUCH TYPE is returned.
+& HIDDEN()
+  hidden(<player|descriptor>)
+
+  Returns 1 if the player is hidden, otherwise 0.
+  Can only be called by someone privileged to see hidden players.
+  If you're not, #-1 is returned.
+& HOME()
+  home(<object>)
+  Returns the object's 'home'.  This is the home for a player or thing,
+  the drop-to of a room, or source of an exit.
+& HOST()
+& HOSTNAME()
+  host(<player|descriptor>)
+  hostname(<player|descriptor>)
+
+  Returns the hostname of a player as indicated by WHO.  This may
+  be more reliable that get(<player>/lastsite) if the player has
+  multple connections from different locations, and the function
+  is called with a descriptor argument.
+
+  The caller can use the function on himself, but using on any other
+  player requires privileged power such as Wizard, Royalty or SEE_ALL.
+
+  See also: Connection Functions, ipaddr(), ports(), lports()
+& IDLE()
+& IDLESECS()
+  idle(<player|descriptor>)
+  idlesecs(<player|descriptor>)
+  This function returns the number of seconds a player has been idle,
+  much as WHO does. <player name> must be the full name of a player, or
+  a player's dbref. Players who are not connected have an idlesecs of "-1",
+  as do dark directors, when idle() is used on them by a non-priv'ed player.
+
+& IF()
+& IFELSE()
+  if(<condition>, <true expression>[, <false expression>])
+  ifelse(<condition>, <true expression>, <false expression>)
+
+  These functions evaluate the <condition> and return <true expression>
+  if the <condition> is true, or <false expression> (if provided) if the
+  <condition> is false.
+
+  See also:  BOOLEAN VALUES, switch()
+& INAME()
+  iname(<object>)
+
+  iname() returns the name of object <object>, as it would appear if
+  you were inside it. It is identical to name() except that if the
+  object has a NAMEFORMAT or NAMEACCENT attribute, it is used.
+
+  You must be see_all, control <object>, or be inside it to use this
+  function.
+
+  See also: @nameformat, name(), fullname()
+& INC()
+  inc(<integer>)
+  inc(<string-ending-in-integer>)
+
+  Inc returns the integer plus 1. If given a string that ends in an integer,
+  it increments only the final integer portion. That is:
+
+  > think inc(3)
+  4
+  > think inc(hi3)
+  hi4
+  > think inc(1.3.3)
+  1.3.4
+  > think inc(1.3)
+  1.4
+
+  Note especially the last example, which will trip you up if you use
+  floating point numbers with inc() and expect it to work like add().
+  See also: dec()
+& INDEX()
+  index(<list>,<character>,<first>,<length>)
+  
+  This function is similar to EXTRACT(), except that it requires a 
+  separator argument, while EXTRACT() uses a space if a separator
+  isn't given. The function returns <length> items starting from 
+  the <first> position. Trailing spaces are trimmed. The comma cannot
+  be used as the <character> separator unless it's escaped with a \. 
+  
+  Examples:
+  
+    > say [index(Cup of Tea | Mug of Beer | Glass of Wine, |, 2, 1)]
+    You say, "Mug of Beer"
+
+    > say [index(%rtoy boat^%rblue tribble^%rcute doll^%rred ball,^,2,2)]
+    You say, "
+    blue tribble^
+    cute doll"
+
+  See also: extract(), elements(), grab()
+& INSERT()
+  insert(<list>,<position>,<new item>[,<single-character separator>])
+
+  If <position> is a positive integer, this inserts <new item> BEFORE
+  the item at <position> from the left in <list>.
+
+  That means that <new item> then becomes the <position>th element of
+  <list>. If a separator is not given, a space is assumed. Null items are 
+  counted when determining position, as in 'items()'.
+
+  If <position> is a negative integer, this inserts <new item> AFTER
+  the item at the absolute value of <position> from the RIGHT in <list>.
+
+  This is the same as reversing the list before inserting <new item>, 
+  and then reversing it again into correct order.  For example, when 
+  <position> is -1, <new item> will be the last in the list; when
+  <position> is -2, <new item> will be the second item from the right,
+  and so on.
+  
+  Examples:
+    > say [insert(This is a string,4,test)]
+    You say, "This is a test string"
+    > say [insert(one|three|four,2,two,|)]
+    You say, "one|two|three|four"
+    > say [insert(meep bleep gleep,-3,GOOP)]  
+    You say, "meep GOOP bleep gleep"
+
+& ISDAYLIGHT()
+  isdaylight()
+
+  Returns 1 if it's daylight savings time in the MUSH time zone,
+  otherwise 0.
+& ISDBREF()
+  isdbref(<string>)
+  This function returns 1 if the string is a valid object dbref, and
+  0 if the string is not a valid object dbref.
+  See also: DBREFS
+& ISINT()
+  isint(<string>)
+
+  Returns 1 if its argument is an integer, and 0 otherwise. Integers can
+  begin with a '+' or '-' sign, but the rest of the string must be digits.
+
+  See also: isnum()
+& ISNUM()
+  isnum(<string>)
+
+  This function returns 1 if <string> is a number, and 0 if it is not.
+  Numbers can begin with a '-' sign (for negatives), but the rest of
+  the characters in the string must be digits, and an optional decimal
+  point.
+
+  See also: isint()
+& ISWORD()
+  isword(<string>)
+  
+  This function returns 1 if every character in <string> is a letter,
+  or 0, if any character isn't a letter.  Case does not matter.
+    
+& ITEMS()
+  items(<list>,<single-character separator>)
+
+  items() counts the number of items in a list using an arbitrary
+  (required) separator. Null items are counted, so:
+
+        items(X|X,|)     => 2     (2 X items)
+        items(X||X,|)    => 3     (2 X items and 1 null item)
+        items(X X,%b)    => 2     (2 X items)
+        items(X%b%bX,%b) => 3     (2 X items and 1 null item)
+        items(,|)        => 1     (a single null item)
+
+   Another way to think about this is that items() counts the number
+   of delimiters in the string, and adds 1.
+
+& ITEMIZE()
+& ELIST()
+  itemize(<list>[,<delim>[,<conjunction>[,<punctuation>]]])
+  elist(<list>[,<conjunction> [,<delim> [,<output delim> [,<punctuation>]]]])
+
+  These functions take the elements of <list> (separated by <delim> or
+  a space by default), and:
+   If there's just one, return it.
+   If there's two, return <e1> <conjunction> <e2>
+   If there's more than two, return <e1><punc> <e2><punc> ... <conj> <en>
+   
+  The default <conjunction> is "and", default punctuation is ","
+  Examples:
+    > say [itemize(eggs)] * [itemize(eggs bacon)]
+    You say, "eggs * eggs and bacon" 
+    > say [itemize(eggs bacon spam)]
+    You say, "eggs, bacon, and spam"
+    > say [itemize(eggs bacon spam, ,&,;)]
+    You say, "eggs; bacon; & spam"
+& ITER()
+& PARSE()
+  iter(<list>,<pattern>[,<delimiter> [,<output separator>]])
+  parse(<list>,<pattern>[,<delimiter> [,<output separator>]])
+  
+  This works in a manner very similar to @map, except that it returns
+  a string directly.  <list> is a space-separated list of words, and
+  <pattern> is what will be "mapped" onto each element of the list,
+  with the token "##" being replaced successively by the next word in
+  the list, and the token "#@" being replaced by the word's position
+  in the list (see also help itext() and help inum()).  The result is
+  concatenated and returned as a space separated list.  This is similar
+  to @dolist, but the results are made into a list rather than executed.
+
+  The list may be <delimiter>-separated.
+
+  By default, the return list will be space-separated. However,
+  by including the output separator (which requires explicitly
+  including the delimiter), you can have it separated by any string.
+
+Continued in HELP ITER2
+& ITER2   
+  parse() is a synonym for iter(). If you nest iters, ## and #@ refer
+  to the first iter(). See 'help ITEXT()' and 'help INUM()' for how to
+  retrieve their values for any nested iter. See 'help MAP()' for a
+  similar function. 
+
+  Note that ## and #@ are replaced before evaluation, so the word will
+  be evaluated, which can be a problem when iter()ing on an untrusted
+  list.  iter-with-itext or map() should be preferred to iter-with-##
+  whenever you're iterating over user-provided values.
+
+  > say [iter(This is a test string., [strlen(##)])]
+  You say, "4 2 1 4 7"
+  
+  > say [iter(lnum(5), mul(add(##,##),2))]
+  You say, "0 4 8 12 16"
+  
+  > say [iter(lexits(here), [name(##)] (owned by [name(owner(##))]))]
+  You say, "South (owned by Claudia) North (owned by Roy)"
+  
+  > &STRLEN_FN me=[strlen(%0)]
+  > say [iter(This is a test string., [u(STRLEN_FN, ##)])]
+  You say, "4 2 1 4 7"
+
+ This example could be replaced by the use of map() like so:
+  > say [map(strlen_fun, This is a test string)]
+
+  > say [iter(lnum(3), ##, ,%r)]
+  You say, "0
+  1
+  2"
+& ILEV()
+& ITEXT()
+& INUM()
+  ilev()
+  itext(<n>)
+  inum(<n>)
+
+  These functions, when called within an iter(), return the equivalent
+  of ## (itext) or #@ (inum), with reference to the nth more outermost
+  iter(), where n=0 refers to the current iter(), n=1 to an iter()
+  in which the current iter() is nested, etc. ilev() will return the
+  current nesting depth, or -1 if it is outside an iter(). Thus,
+  itext(ilev()) will return the ## of the outermost iter().
+
+  > say [iter(red blue green,iter(fish shoe, #@:##))]
+  You say, "1:red 1:red 2:blue 2:blue 3:green 3:green"
+  
+  > say [iter(red blue green,iter(fish shoe, [inum(ilev())]:[itext(1)]))]
+  You say, "1:red 1:red 2:blue 2:blue 3:green 3:green"
+
+  > say [iter(red blue green,iter(fish shoe, [inum(0)]:[itext(0)]))]
+  You say, "1:fish 2:shoe 1:fish 2:shoe 1:fish 2:shoe"
+  
+  > say [iter(red blue green,iter(fish shoe, [itext(1)]:[itext(0)]))]
+  You say, "red:fish red:shoe blue:fish blue:shoe green:fish green:shoe"
+& IPADDR()
+  ipaddr(<player|descriptor>)
+
+  Returns the IP address of the connected player or descriptor.  This
+  may be more reliable that get(<player>/lastip) if the player has
+  multple connections from different locations, and the function
+  is called with a descriptor argument.
+
+  The caller can use the function on himself, but using on any other
+  player requires privileged power such as Wizard, Royalty or SEE_ALL.
+
+  See also: Connection Functions, hostname(), ports(), lports()
+& LAST()
+  last(<list>[,<delimiter>])
+  
+  Returns the last element of a list.
+
+  See also: first(), rest()
+& LATTR()
+  lattr(<object>[/<attribute pattern>])
+  Returns a space-separated list of the attribute names on the object.
+  You must either be a Wizard or Royalty, own the object, have the
+  See_All power, or have the object set VISUAL in order to use this 
+  function on the object.
+  
+  If a wildcarded attribute pattern is provided, only attribute names
+  matching that pattern will be returned. lattr() uses the same
+  wildcards as examine (?, *, **).
+
+  See also: nattr(), examine
+& NATTR()
+& ATTRCNT()
+  nattr(<object>)
+  attrcnt(<object>) 
+
+  This function (known by two names) returns the number of attributes
+  on the object. You must have permission to examine the object
+  in order to use this function, but its count may include attributes
+  that are not visible to you.  This function is considerably faster
+  than words(lattr()) and doesn't suffer from buffer length constraints.
+  It's designed primarily for statistical purposes.
+
+  See also: lattr()
+& LCON()
+  lcon(<object>)
+
+  Returns a list of the dbrefs of contents in a container.
+
+  You can get the complete contents of any container you may examine,
+  regardless of whether or not objects are dark.  You can get the
+  partial contents (obeying DARK/LIGHT/etc.) of your current location
+  or the enactor (%#).  You CANNOT get the contents of anything else,
+  regardless of whether or not you have objects in it.
+
+  See also: lexits(), lplayers(), con(), next(), lvcon()
+& LCSTR()
+  lcstr(<string>)
+
+  Returns <string> with all letters converted to lowercase.
+  Example: lcstr(Foo BAR bAz) returns "foo bar baz"
+& LDELETE()
+  Ldelete(<list>,<position>[,<single-character separator>])
+  
+  This deletes the item at <position> in the list. If a separator
+  character is not given, a space is assumed. Null items are
+  counted, as in 'items()'.
+  
+  Examples:
+    > say [ldelete(This is a long test string,4)]
+    You say, "This is a test string"
+    > say [ldelete(lemon|orange|apple,2,|)]
+    You say, "lemon|apple"
+& LEFT()
+  left(<string>, <length>)
+
+  Returns the first <length> characters from string.
+
+& LEMIT()
+  lemit(<message>)
+  nslemit(<message>)
+
+  Sends a message to the outermost room, as per @lemit.
+
+  nslemit() is a privileged variation that works like @nslemit.
+
+& LEXITS()
+  lexits(<object>)
+
+  Returns a list of the dbrefs of exits in a room.
+
+  You can get the complete exit list of any room you may examine,
+  regardless of whether or not exits are dark.  You can get the partial
+  exit list (obeying DARK/LIGHT/etc.) of your current location or the
+  enactor (%#).  You CANNOT get the exit list of anything else,
+  regardless of whether or not you have objects in it.
+
+  See also: lcon(), exit(), next(), lvexits()
+& LJUST()
+  ljust(<string>,<length>[,<fill>])
+  
+  This function pads a string with trailing characters ("left-justifies")
+  so it is <length> long. If <string> is longer than <length>, the <string> 
+  is returned; it is not truncated. If <fill> is not specified, a space
+  is used.
+  
+  Examples:
+  
+    > say [ljust(foo,6)]
+    You say, "foo   "
+  
+    > say %r0[ljust(foo,6,-)]7%r01234567
+    You say, "
+    0foo---7
+    01234567"
+& LINK()
+  link(<name>, <destination>)
+  This function links object <name> to <destination>. While normally
+  used on exits, it has all of the other capabilities of @link as well.
+  It returns nothing.
+
+  This is a side-effect function and may not be enabled on some MUSHes.
+& LIST()
+  list(<option>)
+  This function takes the same arguments as the @list command, and returns
+  the same things. 
+& LIT()
+  lit(<string>)
+
+  This function returns the string literally - without even squishing
+  spaces, and without evaluating *anything*. This can be useful for
+  writing ASCII maps with spaces or whatever.
+
+  It can be a bit tricky to get a literal string with spaces into an
+  attrib, however, since spaces are usually squished in setting an
+  attribute. This example illustrates how to make it work:
+
+    > @va me=$test: think {[lit(near       far)]}
+    Set.
+    > ex me/va
+    VA [#1]: $test: think {[lit(near       far)]}
+    > test
+    near       far
+
+  Leaving out the {}'s will not work in the above.
+& LMATH()
+  lmath(<op>, <list>[, <delim>])
+
+  This function performs generic math operations on <list>, returning
+  the result. Each element of the list is treated as one argument to
+  an operation, so that lmath(<op>, 1 2 3) is equivalent to <op>(1, 2, 3).
+  Using @function, one can easily write ladd, lsub, etc as per TinyMUSH.
+
+  Supported <op>'s are: add and band bor bxor div fdiv max mean 
+  median min modulo mul nand nor or remainder stddev sub xor
+
+  Example:
+  >think lmath(add, 1|2|3, |)
+  6
+  >think lmath(max, 1 2 3)
+  3
+  >&FUN_FACTORIAL me=[lmath(mul,lnum(1,%0))]
+  >think u(fun_factorial,5)
+  120
+& LN()
+  ln(<number>)
+  Returns the natural log of <number>.
+& LNUM()
+  lnum(<number>)
+  lnum(<start number>,<end number>[,<output separator>])
+
+  With one argument, lnum returns a list of numbers, from 0 to <number - 1>. 
+  For example, lnum(4) returns the list "0 1 2 3". This is useful for 
+  creating loops.
+  With two arguments, the numbers range from the first to the second
+  argument. For example, lnum(1,4) => 1 2 3 4
+  With three arguments, the output is separated by the separator
+  given in the third argument. lnum(1,4,|) => 1|2|3|4
+
+& LOC()
+  loc(<object>)
+  
+  Loc returns the dbref of the location that object is at.  The 
+  object has to either be yours or be in the same room as you to 
+  work. The location of an exit is its destination (the source of 
+  an exit is its home). The location of a room is its drop-to
+  (if one is not set, then the location is #-1).
+& LOCALIZE()
+  localize(<code>)
+
+  Localize() saves the q-registers, evaluates its argument, and restores
+  the registers afterwards. It has much the same relation to s() that
+  ulocal() does to u(), except localize()'s argument is only evaluated
+  once, instead of twice like s()'s. Useful in @functions or to wrap around
+  fragments of code too small to go into another attribute.
+
+  Example:
+  > say [setr(0, Outside)]-[s(\[setr(0, Inside)\])]-%q0
+  You say, "Outside-Inside-Inside"
+  > say [setr(0, Outside)]-[localize(setr(0, Inside))]-%q0
+  You say, "Outside-Inside-Outside"
+
+  See also: setq(), setr(), r(), ulocal(), uldefault(), s()
+& LOCATE()
+  locate(<looker>, <name>, <parameters>)
+  This function attempts to find the number called <name> relative to
+  <looker>. You must control <looker> or have the See_All power. 
+  This is a bit like the NUM() function, but with a wider, controllable 
+  "range". 
+  You can control the preferred type of the match with:
+    E   -   Exits
+    L   -   Unlocked exits preferred over locked exits
+    N   -   No type (this is the default)
+    P   -   Players
+    R   -   Rooms
+    T   -   Things
+    F   -   Return #-1 if what's found is of a different type than the
+            preferred one.
+    X   -   Never return #-2. Use the last dbref found if the match is  
+            ambiguous.
+  If you specify more than one type, the last one will be preferred. Unless
+  you specify an F option, if an object of a different type is found and
+  none of the preferred type are, the found object will be returned.
+  (Read "help locate2" for more.)
+& LOCATE2
+  You can control where to look with:
+    a   -   Absolute match (look for #<object>)
+    c   -   Exits carried by <looker>
+    e   -   Exits in <looker>'s location
+    h   -   "here"  (the location of <looker>)
+    i   -   Inventory of <looker>
+    l   -   Location (container) of <looker>
+    m   -   "me"  (<looker> itself)
+    n   -   Neighbors (other objects in same location as <looker>)
+    p   -   Player names prefixed by '*'
+    z   -   English-style matching (my 2nd book) of <name>
+    *   -   All of the above (try a complete match)
+Just string all the parameters together, without separating them by
+spaces, i.e.  LOCATE(#100, Test, Tn)  would check #100's neighbors
+for an object named "Test", preferring a thing over other types.
+& LOCK()
+  lock(<object>[/<locktype>][, <new value>])
+
+  lock() returns the text string equivalent of the lock on an object that
+  you control. You can also provide a locktype (e.g. "enter", "use", etc.)
+  switch after the object, if you want to check something other than the
+  regular lock.  If a new value is specified, it will attempt to change
+  the lock before reporting it.
+
+  This is a side-effect function and may not be enabled on some MUSHes.
+  See also: @lock, locktypes
+& LOG()
+  log(<number>[, <base>])
+  Returns the logarithm (base 10, or the given base) of <number>.
+& LPARENT()
+  lparent(<object>)
+  
+  This function returns a list consisting of the object's db# (as per
+  num()), the db# of its parent, grandparent, greatgrandparent, etc.
+  The list will not, however, show parents of objects which the player
+  is not privileged to examine.
+& LPLAYERS()
+  lplayers(<object>)
+
+  This function returns the dbrefs of all players, connected or not, in 
+  <object>. DARK directors aren't listed to mortals or those without the
+  see_all power. You must be in <object> or control it to use this
+  function.
+
+  See also: lvplayers(), lcon()
+& LPOS()
+  lpos(<string>, <character>)
+
+  This function returns a list of the positions where <character> occupies
+  in <string>, with the first character of the string being 0. Note that
+  this differs from the pos() function, but is consistent with other string
+  functions like mid() and delete().
+
+  If <character> is a null argument, space is used.
+  If <character> is not found anywhere in <string>, an empty list is 
+  returned.
+
+  Example:
+  > say lpos(a-bc-def-g, -)
+  You say, "1 4 8"
+
+  See also: pos()
+& LSEARCH()
+& SEARCH()
+& LSEARCHR()
+& CHILDREN()
+  lsearch(<player>[, <class>[, <restriction>[, <low>[, <high>]]]])
+  lsearchr(<player>[, <class>[, <restriction>[, <low>[, <high>]]]])
+  children(<object>)
+  This function is similar to the @search command, except it returns
+  just a list of dbref numbers. It is computationally expensive, and
+  costs 100 pennies to perform. The function must have at least three
+  arguments.  Wizards can specify "all" or <player> for the <player>
+  field; mortals must use "me". If you do not want to restrict
+  something, use "none" for <class> and/or <restriction>. <low> and
+  <high> restrict the search to that range of db#'s and are specified as
+  dbrefs ("#2") with the #.
+
+  The possible <class>es and <restriction>s are the same as those accepted
+  by @search. See 'help @search' for information about them.
+
+  children() is exactly the same as lsearch(<me|all>,parent,<object>),
+  using "all" for See_All/Search_All players  and "me" for others.
+    
+  See 'help lsearch2' for more details.
+& LSEARCH2
+& SEARCH2 
+   
+  If <class> is one of the eval ones (EVAL, EEXITS, EROOMS, EOBJECTS or
+  EPLAYERS), note that any brackets, percent signs, or other special
+  characters should be escaped, as the code in <restriction> will be
+  evaluated twice - Once as an argument to lsearch(), and then again for
+  each object looked at in the search.
+
+  <class> can be 'NONE' to make lsearch() act like a @search without a class.
+
+  lsearchr() is like an lsearch() run through revwords(). Results are returned
+  from highest dbref to lowest.
+  search() is an alias for lsearch().
+  Examples:
+  
+  lsearch(all, flags, gc)                   <-- lists all connected gagged players. 
+  lsearch(me, type, room)                  <-- lists all rooms owned by me.
+  lsearch(me, type, room, 100, 200)        <-- same, but only w/db# 100-200
+  lsearch(all, eplayer, \[eq(money(##),100)\]) <-- lists all players with 100
+                                                   coins.
+& LSTATS()
+& STATS()
+  lstats(<player>)
+  stats(<player>)
+  This function returns the breakdown of objects in the database, in
+  a format similar to "@stats". If <player> is "all", a breakdown is
+  done for the entire database. Otherwise, the breakdown is returned
+  for that particular player. Only directors can LSTATS() other players.
+  The list returned is in the format:
+  <Total objects> <Rooms> <Exits> <Things> <Players> <Garbage>
+
+  stats() is an alias for lstats().
+& LT()
+  lt(<num>,<num>)
+
+  Takes two numbers, and returns 1 if and only if the first is less
+  than the second, and 0 otherwise.
+& LTE()
+  lte(<num>,<num>)
+
+  Takes two numbers, and returns 1 if and only if the first is less
+  than or equal to the second, and 0 otherwise.
+& LVCON()
+  lvcon(<object>)
+
+  This function returns the dbrefs of all objects that are inside <object>
+  and visible (non-dark). You must be in <object> or control it to use this
+  function.
+
+  See also: lcon()
+& LVEXITS()
+  lvexits(<room>)
+
+  This function returns the dbrefs of all visible (non-dark) exits from
+  <room>. You must be in the room or control it to use this function.
+
+  See also: lexits()
+& LVPLAYERS()
+  lvplayers(<object>)
+
+  This function returns the dbrefs of all connected and non-dark players
+  in an object. You must be in the object or control it to use this 
+  function.
+& LWHO()
+  lwho()
+
+  This returns a list of the dbref numbers for all currently-connected
+  players. When mortals use this function, the dbref numbers of DARK
+  directors or royalty do NOT appear on the dbref list.
+
+  If lwho() is given an argument, and used by an object that can see
+  DARK and Hidden players, lwho() returns the output of lwho() from
+  <viewer>'s point of view.
+
+  See also: mwho()
+& MAP()
+  map([<object>/]<attribute>,<list>[,<delim>][, <osep>])
+  
+  Maps a function onto a list.
+  This function works much like ITER(). Each element of <list> has
+  the user-defined function of the first argument performed on it;
+  the element is passed to the function as %0, and its position
+  in <list> as %1. <delim> is used as the element delimiter; 
+  if it is not specified, a space is used. The resulting output is
+  delimited by <osep>, if given, or else by the delimiter
+  Examples:
+  
+    > &times_two me=[mul(%0,2)]
+    > say [map(times_two, 5 4 3 2 1)]
+    You say, "10 8 6 4 2"
+    > say [map(times_two,1;2;3;4;5,;)]
+    You say, "2;4;6;8;10"
+
+  See also: anonymous attributes
+& MATCH()
+  match(<list>, <pattern>[, <delimiter>])
+
+  This function tests if the pattern matches an element of the list.
+  The pattern can contain the wildcards * and  ?.  ? matches to any
+  one character, while * matches to any number of characters
+  including none.  So s?x would match to sex or six, but not to socx,
+  but s*x would match to all of them. If no match is found, 0 is
+  returned. If a match is found, it returns the number of the element
+  of the list that matched.  
+
+  This attempts to match to a list element, not to an entire string.
+  To match an entire string  (for example, to match "red blue green"
+  to "*bl*"), use the strmatch() function.
+
+  See also: element(), grab()
+& MATCHALL()
+  Function: matchall(<list>,<pattern>[,<delim>[,<osep>]])
+  This function works identically to the match() function, save that it
+  returns all matches, not just the first: It returns the index numbers of
+  all elements in <list> which match <pattern>. If none match, an empty
+  string is returned.  The resulting output is delimited by <osep>, if
+  given, or else by the delimiter.
+
+  Examples:
+  > say matchall(This is a test of a test,test)
+  You say "4 7"
+  > say matchall(This is testing a test,tes*)
+  You say "3 5"
+  See also: match(), strmatch(), graball()
+& MAX()
+  max(<num1>, <num2>, ..., ...)
+
+  This function returns the largest number in its list of arguments.
+  It can take any number of arguments.
+& MEAN()
+  mean(<number>,...)
+
+  Returns the mean (arithmetic average) of its arguments.
+
+  See also: median(), stddev()
+& MEDIAN()
+  median(<number>,...)
+
+  Returns the median (the middlemost numerically) of its arguments.
+
+  See also: mean(), stddev()
+& MEMBER()
+  member(<list>,<word>[,<delimiter>])
+
+  Takes a list and a word, and returns the position of <word>
+  if <word> is a word in <list>.  A word is defined as a string which
+  has no interior spaces.  So '  hello  ' would be one word, while
+  'hello there' would be two.  See LISTS
+
+  member() is case-sensitive and requires an exact match. For wild
+  card patterns, use match().
+& MERGE()
+  merge(<string1>, <string2>, <characters>)
+  
+  This function merges <string1> and <string2>, depending on <characters>.
+  If a character in <string1> is the same as one in <characters>, it is
+  replaced by the character in the corresponding position in <string2>.  The
+  two strings must be of the same length.
+  
+  Example:
+    > say [merge(AB--EF,abcdef,-)]
+    You say, "ABcdEF"
+  Spaces need to be treated specially. An empty argument is considered to
+  equal a space, for <characters>.
+  
+  Example:
+    > say [merge(AB[space(2)]EF,abcdef,)]
+    You say, "ABcdEF"
+
+  See also: TR()  
+& MID()
+  mid(<string>, <first>, <length>)
+
+  Mid returns a segment of the string, the <length> characters to the
+  right of the <first> character.  Note that the first character in a
+  string is numbered zero, and not one.
+& MIN()
+  min(<num1>, <num2>, ..., ...)
+
+  This function returns the smallest number in its list of arguments.
+  It can take any number of arguments.
+& MIX()
+  mix([<object>/]<attribute>,<list 1>,<list 2>[,...,<list n>],[<delim>])
+  This function is similar to MAP(), except that it takes the elements
+  of two or more lists, one by one, and passes them to the user-defined
+  function as %0, %1, up to %9, respectively, for elements of <list 1> to
+  <list 10>. If the lists are of different sizes, the shorter ones are padded
+  with empty elements. <delim> is used to separate elements; if it is not
+  specified, it defaults to a space. If using more than 2 lists, the last
+  argument must be a delimiter.
+
+  See HELP MIX2 for examples
+& MIX2  
+  Examples of mix():
+  > &add_nums me=[add(%0, %1)]
+  > say [mix(add_nums,1 2 3 4 5, 2 4 6 8 10)]
+  You say, "3 6 9 12 15"
+  > &lengths me=[strlen(%0)] and [strlen(%1)].
+  > say [mix(lengths, some random, words)]
+  You say, "4 and 5. 6 and 0."
+  > &add_nums me=[add(%0, %1, %2)]
+  > say [mix(add_nums, 1:1:1, 2:2:2, 3:3:3, :)]
+  You say, "6:6:6"
+
+  See also: anonymous attributes
+& MOD()
+& MODULO()
+& MODULUS()
+& REMAINDER()
+  mod(<number>,<number>)
+  modulo(<number>,<number>)
+  modulus(<number>,<number>)
+  remainder(<number>,<number>)
+
+  Remainder returns the remainder of the integer division of the first
+  number by the second.  Modulo returns the modulo of the two numbers.
+  For positive numbers, these are the same, but they may be different
+  for negative numbers:
+
+     modulo(13,4)       ==>  1      and     remainder(13,4)    ==>  1
+     modulo(-13,4)      ==>  3      but     remainder(-13,4)   ==>  -1
+     modulo(13,-4)      ==>  -3     but     remainder(13,-4)   ==>  1
+     modulo(-13,-4)     ==>  -1     and     remainder(-13,-4)  ==>  -1 
+  Remainder result always has the same sign as the first argument. 
+  Modulo result always has the same sign as the second argument.
+
+  Mod and modulus are just aliases for modulo.
+
+  See also: DIV
+& MONEY()
+  money(<object>)
+  money(<integer>)
+
+  The first form returns the amount of money <object> has.
+  The second form returns the name for a given amount
+  of money, appropriately inflected as singular or plural.
+
+  > say [money(Javelin)]
+  You say, "150"
+  > say [money(1)]
+  You say, "Penny"
+
+  > say [money(2)]
+  You say, "Pennies"
+
+  > &counter me=$count *: @emit %0 [money(%0)]
+  > count 2
+  2 Pennies
+
+& MTIME()
+  mtime(<object>)
+
+  If creation times are enabled, this function will return the 
+  date and time that one of the object's attributes was last
+  added, deleted, or modified. Only things, rooms, and exits have
+  modification times.
+& MUDNAME()
+  Function: mudname()
+  Returns the name of the MUD.  This is usually (but not necessarily) the name
+  that appears in the various mud lists, and is the name that the mud is
+  listed under in reports from any inter-mush bots like mudnet that it's
+  connected to.
+  Example:
+    > say mudname()
+    You say "TestMUSH"
+
+& MUL()
+  mul(<number>,<number>,...)
+
+  Returns the product of some numbers.
+& MUNGE()
+  munge([<object>/]<attribute>,<list 1>,<list 2>[,<delimiter>[,<osep>]])
+  This function takes two lists of equal length. It passes the entirety of
+  <list 1> to the user-defined function as %0, and the delimiter as %1.
+  Then, this resulting list is matched with elements in <list 2>, and
+  the rearranged <list 2> is returned. This is useful for doing things
+  like sorting a list, and then returning the corresponding elements in
+  the other list. If a resulting element from the user-defined function
+  doesn't match an element in the original <list 1>, a corresponding
+  element from <list 2> does not appear in the final result.
+
+  See HELP MUNGE2 for examples.
+& MUNGE2
+  For example: Consider attribute PLACES, which contains "Fort Benden Ista",
+  and another attribute DBREFS contains the dbrefs of the main JUMP_OK
+  location of these areas, "#20 #9000 #5000".  We want to return a list of
+  dbrefs, corresponding to the names of the places sorted alphabetically. The
+  places sorted this way would be "Benden Fort Ista", so we want the final
+  list to be "#9000 #20 #5000". The functions, using munge(), are simple:
+  
+  > &sort me=[sort(%0)]
+  > say [munge(sort,v(places),v(dbrefs))]
+  You say, "#9000 #20 #5000"
+  See HELP MUNGE3 for another example
+& MUNGE3
+  Another common task that munge() is well suited for is sorting a list
+  of dbrefs of players by order of connection. This example reuses the 
+  &sort attribute from the previous one, but unlike the other example,
+  it builds the list to sort on out of the list to return. 
+  
+  > &faction_members me=#3 #12 #234
+  > say [munge(sort,iter(v(faction_members),conn(##)),v(faction_members))]
+  You say, "#12 #234 #3"
+
+  See also: anonymous attributes
+& MWHO()
+  mwho()
+
+  This returns a list of the dbref numbers for all current-connected,
+  non-hidden players. It's exactly the same as lwho() used by a
+  mortal, and is suitable for use on privileged global objects who
+  need an unprivileged who-list.
+& NAME()
+  name(<object>[,<new name>])
+  name(<player>[,<new name> <password>])
+  Name returns the name of object <object>. For exits, name returns
+  the displayed name of the exit.
+  If function side effects are allowed, this function, given two arguments,
+  acts just like @name <object>=<new name>. Consequently, if renaming
+  a player, you must use the player's password or be God.
+
+  name() with no arguments currently returns nothing. This should be an
+  error, but enough old code has been written that expects this behavior
+  that it will continue to do this for the time being. Don't rely on it.
+
+  Related functions: FULLNAME(), ACCNAME(), INAME() 
+& NAND()
+  nand(<boolean>, <boolean>,...)
+
+  Returns 1 if at least one of its arguments is false, 0 if all are
+  true. Equivalent to not(and()), but more efficient.
+& NEARBY()
+  nearby(<object 1>, <object 2>)
+
+  Returns 1 if object 1 is "nearby" object 2. "Nearby" is defined as:
+  object 1 is in the same location as object 2, or,
+  object 1 is being carried by object 2, or,
+  object 1 is carrying object 2.
+  You must control at least one of the objects.
+& NEQ()
+  neq(<num1>,<num2>)
+
+  Basically the same as [not(eq(<num1>,<num2>))].
+
+  See also: eq(), not()
+& NEXT()
+  next(<object>)
+
+  If object is an exit in a room, then next() will return the next 
+  non exit in the list of exits for that room.  If object is a
+  thing or a player, then next will return the next object in the
+  contents list that the object is in.  Otherwise, it returns a
+  '#-1' string.  '#-1' is also used to denote that there are no
+  more exits/things/players in the container.
+
+  You can get the complete contents of any container you may examine,
+  regardless of whether or not objects are dark.  You can get the
+  partial contents (obeying DARK/LIGHT/etc.) of your current location
+  or the enactor (%#).  You CANNOT get the contents of anything else,
+  regardless of whether or not you have objects in it.  These rules
+  apply to exits, as well.
+
+  See also: lcon(), lexits(), con(), exit()
+& NOR()
+  nor(<boolean>, <boolean>,...)
+
+  Returns 1 if all its arguments are false, 0 if one is true.
+  Equivalent to not(or()), but more efficient.
+
+  See also: and(), or(), xor(), not()
+& NOT()
+  not(<boolean value>)
+
+  Takes a boolean value, and returns its inverse.  
+  I.E. if the input is equivalent to true(1), it returns a 0, and if 
+  the input is equivalent to false(0), it returns a 1.
+
+  The definition of truth and falsehood depends on configuration settings;
+  see help BOOLEAN VALUES for details.
+  See also: and(), or(), nor(), xor()
+
+& NUM()
+  num(<object>)
+
+  Returns the dbref number of the object, which must be in the same 
+  room as the object executing num.
+& OBJ()
+  obj(<object>)
+
+  Returns the objective pronoun - him/her/it - for an object.
+& OBJEVAL()
+  objeval(<object>, <expression>)
+  Allows you to evaluate <expression> from the viewpoint of <object>.
+  If side-effect functions are enabled, you must control <object>;
+  if not, you must either control <object> or have the see_all power.
+  If <object> does not exist or you don't meet one
+  of the criterion, the function evaluates with your privileges.
+  This function is useful for securing objects which need to evaluate
+  attributes on things owned by others.
+  
+& OBJID()
+  objid(<object>)
+
+  This function returns the object id, a value which uniquely identifies
+  it for the life of the MUSH. The object id is the object's dbref,
+  a colon character, and the object's creation time, in seconds since
+  the epoch.
+
+  The object id can be used nearly anywhere the dbref can, and ensures
+  that if an object's dbref is recycled, the new object won't be mistaken
+  for the old object.
+
+  The substitution %: returns the object id of the enacting object.
+
+& OBJMEM()
+  objmem(<object>)
+
+  This function returns the amount of memory, in bytes, being used
+  by the object. It can only be used by players with Search powers.
+
+  See also: playermem()
+
+& OEMIT()
+  oemit([<room>/]<object> [<object> ...],<message>)
+  nsoemit([<room>/]<object> [<object> ...],<message>)
+
+  Sends <message> to all objects in <room> (default is the location 
+  of <object>(s)) except <object>(s), as per @oemit.
+
+  nsoemit() is a privileged variation that works like @nsoemit.
+
+& OPEN()
+  open(<exit name>, <room>)
+  This function opens an exit called <exit name> and links it to
+  <room>, which must be a dbref number. It returns the dbref number
+  of the new exit.
+& OR()
+& COR()
+  or(<boolean value 1>,<boolean value 2>[, ... , <boolean value N>])
+  cor(<boolean value 1>,<boolean value 2>[, ... , <boolean value N>])
+  Takes boolean values, and returns a 1 if at least one of the inputs 
+  is equivalent to true(1).  or() always evaluates all arguments
+  (including side effects), while cor() stops evaluation after the
+  first argument that evaluates to true.
+
+  See also: BOOLEAN VALUES, and()
+& ORFLAGS()
+  orflags(<object>,<string of flag characters>)
+  
+  This function returns 1 if <object> has at least one of the flags in
+  a specified string, and 0 if it does not. The string is specified with
+  a single letter standing for each flag, like the output of the FLAGS()
+  function. A '!' preceding a flag letter means "not flag".
+
+  Thus, ORFLAGS(me,Wr) would return 1 if I am set WIZARD or ROYALTY.
+  ORFLAGS(me,D!c) would return 1 if I am DARK or not CONNECTED.
+
+  If a letter does not correspond to any flag, <object> doesn't have it,
+  so it is simply ignored. There can be an arbitrary number of flags. Do
+  not put spaces between flag letters.
+& ORLFLAGS()
+  orlflags(<object>,<list of flag names>)
+
+  This function returns 1 if <object> has at least one of the flags in
+  a specified list, and 0 if it does not. The list is a space-separated 
+  list of flag names.  A '!' preceding a flag name means "not flag".
+
+  Thus, ORLFLAGS(me,wizard royalty) would return 1 if I am set 
+  WIZARD or ROYALTY.  ORLFLAGS(me,dark !connected)  would return 1 if I am 
+  DARK or not CONNECTED.
+
+  If a name does not correspond to any flag, <object> doesn't have it,
+  so it is simply ignored. There can be an arbitrary number of flags.
+& OWNER()
+  owner(<object>[/<attribute>])
+  Given just an object, it returns the owner of the object.
+  Given an object/attribute pair, it returns the owner of that attribute.
+& PARENT()
+  parent(<object>[, <new parent>])
+  This function returns the dbref number of an object's parent. You
+  must be able to examine the object to do this. 
+  If you specify a second argument, it tries to re-parent the object.
+  In this case, you must control the object.
+& PEMIT()
+  pemit(<object list>,<message>)
+  nspemit(<object list>,<message>)
+
+  This function will send each object on the list a message, as per
+  the @pemit/list command. It returns nothing. It respects page-locks and
+  HAVEN flags on players.
+
+  nspemit() is a privileged variation that works like @nspemit/list.
+  
+& PI()
+  pi()
+  
+  Returns the value of "pi" (3.14159265358979323846264338327, rounded
+  to the game's float_precision setting).
+& PLAYERMEM()
+  playermem(<player>)
+
+  This function returns the amount of memory, in bytes, being used
+  by everything owned by the player. It can only be used by players
+  with Search powers.
+
+  See also: objmem()
+
+& PMATCH()
+  pmatch(<string>)
+  Given the partial name of a player, it returns that player's dbref
+  number. This partial name completion works similarly to the partial
+  name completion of the "page" command - i.e. it first attempts to match
+  the normal names of all players (connected or not), and if that fails,
+  it tries to match the partial names of connected players visible to
+  the enactor. If no player is matched, it returns "#-1". If more than
+  one match is possible for a partial name, it returns "#-2".
+
+  Pmatch() will also accept *<player> or #<db#>. If given a non-player
+  dbref #, pmatch() will return #-1.
+& POLL()
+  poll()
+
+  This function returns the current @poll. 
+
+  See also: @poll, doing(), @doing 
+& LPORTS()
+& PORTS()
+  lports()
+  ports(<player name>)
+  These function returns the list of descriptors ("ports") that are used by
+  connected players. lports() returns all ports, in the same order as
+  lwho() returns dbrefs, and ports() returns those a specific player
+  is connected to, from most recent to least recent. Only players who
+  are See_All or privileged may use these functions; in other cases,
+  lports() returns #-1, and ports() an empty list. As an exception,
+  players can use ports() on themselves.
+
+  These port numbers also appear in the privileged WHO, and can be used
+  with @boot/port, page/port, and the functions that return information
+  about a connection to make them use a specific connection rather than the
+  least-idle one when a player has multiple connections open. Players can
+  get information about their own connections. See_all is needed to use
+  them to get information about other people's ports.
+& POS()
+  pos(<string1>,<string2>)
+
+  This function returns the position that string1 begins in string2,
+  with the first position being 1.  
+  If string1 is not in string2, then it returns #-1.
+  
+& POSS()
+  poss(<object>)
+
+  Returns the possessive pronoun - his/her/its - for an object.
+& POWER()
+  power(<number>,<exponent>)
+  Returns <number> to the power of <exponent>.
+
+  See also: root()
+
+& QUOTA()
+  quota(<player>)  
+  
+  Returns the player's quota, the maximum number of objects they can
+  create, if quotas are in effect. Returns 99999 for players with
+  unlimited quotas, so it's safe to use in numerical comparisons.
+
+& %q0
+& %q1
+& %q2
+& %q3
+& %q4
+& %q5
+& %q6
+& %q7
+& %q8
+& %q9
+& %qa
+& %qb
+& %qc
+& %qd
+& %qe
+& %qf
+& %qg
+& %qh
+& %qi
+& %qj
+& %qk
+& %ql
+& %qm
+& %qn
+& %qo
+& %qp
+& %qq
+& %qr
+& %qs
+& %qt
+& %qu
+& %qv
+& %qw
+& %qx
+& %qy
+& %qz
+& R()
+& R-FUNCTION
+  r(<register>)
+  
+  The r() function is used to access "local registers", and returns
+  the contents of the specified register. There are 36 such registers,
+  numbered 0 through 9, and A through Z.
+  The '%qN' percent-substitution can also be used to access these local
+  registers, where N is register <register> needed.
+  
+  See "help SETQ()" for details about registers.
+
+& RAND()
+  rand(<num>)
+  rand(<min>, <max>)
+  
+  Return a random number.
+  
+  The first form returns an integer between 0 and <num>-1, inclusive.
+  The second returns an integer between <min> and <max>, inclusive.
+
+  If called with an invalid argument, rand() returns an error message
+  beginning with #-1.
+& RANDWORD()
+& PICKRAND()
+  randword(<list>[, <delim>])
+  
+  Returns a randomly selected element from <list>. <delim> is the list
+  delimiter: if not specified, whitespace delimits the list.
+  
+  pickrand() may be an alias for randword() on some servers.
+& REGEDIT()
+& REGEDITALL()
+& REGEDITI()
+& REGEDITALLI()
+  regedit(<string>, <regexp>, <replacement>[, <regexp2>, <replace2> ...])
+  regediti(<string>, <regexp>, <replacement>[, <regexp2>, <replace2> ...])
+  regeditall(<string>, <regexp>, <replacement>[, <regexp2>, <replace2> ...])
+  regeditalli(<string>, <regexp>, <replacement>[, <regexp2>, <replace2> ...])
+
+  These functions are a version of edit() that uses regular expressions.
+  The part of <string> that matches the <regexp> is replaced by the
+  evaluated <replacement>, with $<number> in <replacement> expanded to the
+  corresponding matching sub-expression of <regexp>, with $0 the entire
+  matched section. regedit() only replaces the first match.
+  regeditall() replaces all matches. The versions ending in i are
+  case insensitive. The <replacement> argument is evaluated once for
+  each match, allowing for more complex transformations than is
+  possible with straight replacement.
+
+  Example:
+  > say regedit(this test is the best string, (.)est, $1rash)
+  You say "this trash is the best string"
+  > say regeditall(this test is the best string, (.)est, [capstr($1)]rash)
+  You say "this Trash is the Brash string"
+
+  See also: edit(), regmatch()
+& REGMATCH()
+& REGMATCHI()
+  (Help text from TinyMUSH 2.2.4, with permission)
+  regmatch(<string>,<regexp>[,<register list>])
+  regmatchi(<string>,<regexp>[,<register list>])
+  This function matches the regular expression <regexp> against the
+  entirety of <string>, returning 1 if it matches and 0 if it does not.
+  regmatchi() does the same thing, but case-insensitively.
+  If <register list> is specified, there is a side-effect: any
+  parenthesized substrings within the regular expression will be set
+  into the specified local registers, in the order they were specified
+  in the list. <register list> can be a list of one through nine numbers.
+  If the specified register is -1, the substring is not copied into a
+  register. Under regmatchi, case of the substring may be modified.
+  For example, in regmatch( cookies=30 , (.+)=(\[0-9\]*) )
+  (note use of escaping for MUSH parser), then the 0th substring
+  matched is 'cookies=30', the 1st substring is 'cookies', and the 2nd
+  substring is '30'. If <register list> is '0 3 5', then %q0 will become
+  "cookies=30", %q3 will become "cookies", and %q5 will become "30".
+  If <register list> was '0 -1 5', then the "cookies" substring would
+  simply be discarded.
+  See 'help regexp syntax' for an explanation of regular expressions.
+
+  See also: regrab()
+& REMIT()
+  remit(<object>, <message>)
+  nsremit(<object>, <message>)
+
+  Sends a message to the contents of <object>, as per @remit.
+
+  nsremit() is a privileged variation that works like @nsremit.
+& REMOVE()
+  remove(<list>,<word>[,<delimiter>])
+
+  Remove takes a list and a word, and returns the list, with the
+  first occurrence of the word deleted from it.  
+
+  A word is defined as a string which contains no interior spaces (or
+  <delimiter>'s if <delimiter> is used).  If the word is not in the
+  list, then the list is returned. It is case-sensitive.
+
+  To remove all occurrences of a word from a string, consider
+  using edit().
+
+& REPEAT()
+  repeat(<string>,<number>)
+  
+  This function simply repeats <string>, <number> times.  No spaces are
+  inserted between each repetition.
+  
+  Example:
+    > say [repeat(Test, 5)]
+    You say, "TestTestTestTestTest"
+  
+& REPLACE()
+  replace(<list>,<position>,<new item>[,<single-character separator>])
+  
+  This replaces the item at <position> of <list> with <new item>.
+  If no separator is given, a space is assumed. Null items are 
+  counted when determining position, as in 'items()'.
+  
+  Examples:
+    > say [replace(Turn north at the junction,2,south)]
+    You say, "Turn south at the junction"
+    > say [replace(blue|red|green|yellow,3,white,|)]
+    You say, "blue|red|white|yellow"
+  
+& REST()
+  rest(<list>[,<delimiter>])
+
+  Returns a list minus its first element.
+
+  See also: first(), last()
+& REVWORDS()
+  revwords(<list of words>[,<delimiter>][, <output seperator>])
+  This function reverses the order of words in a list.
+  Example:
+    > say revwords(foo bar baz eep)
+    You say, "eep baz bar foo"
+  
+& RIGHT()
+  right(<string>, <length>)
+
+  Returns the <length> rightmost characters from string.
+
+& RJUST()
+  rjust(<string>,<length>[,<fill>])
+  
+  This function pads a string with leading characters ("right-justifies")
+  so it is <length> long. If <string> is longer than <length>, the <string>
+  is returned; it is not truncated. If <fill> is not specified, a space
+  is used.
+  Examples:
+  
+    > say -[rjust(foo,6)]-
+    You say, "-   foo-"
+  
+    > say %r0[rjust(foo,6,-)]%r01234567
+    You say, "
+    0---foo7
+    01234567"
+& RLOC()
+  rloc(<object>, <levels>) 
+  
+  This function may be used to the get the location of an object's location
+  (and on through the levels of locations), substituting for repeated nested
+  loc() calls. <levels> indicates the number of loc()-equivalent calls to
+  make; i.e., loc(loc(<object>)) is equivalent to rloc(<object>,2).
+  rloc(<object>,0) is equivalent to num(<object>), and rloc(<object>,1) is
+  equivalent to loc(<object>).
+  
+  If rloc() encounters a room, the dbref of that room is returned. If rloc()
+  encounters an exit, the dbref of that exit's destination is returned.
+  It can also return the locations of controlled or nearby objects, or of
+  findable players.
+  Related functions:  LOC(), WHERE(), ROOM()
+& RNUM()
+  rnum(<room number>, <object>)
+  This function returns the dbref number of an object (player, thing, or
+  exit). The object must be in the specified room. This function is
+  essentially identical to NUM(), except it matches things in the
+  specified room rather than the room that you are in. The RNUM()
+  function is meant to be used in conjunction with Master Room objects.
+
+& ROOM()
+  room(<object>)
+
+  Returns the "absolute" location of an object. This is always a room;
+  it is the container of all other containers of the object. The
+  "absolute" location of an object is the place @lemit messages are
+  sent to and NO_TEL status determined.
+  You must control the object, have see_all power over the object, or 
+  be near the object in order for this function to work. The exception 
+  to this are players; if <object> is a player, the ROOM() function may 
+  be used to find the player's absolute location if the player is not
+  set UNFINDABLE.
+& ROOT()
+  root(<number>, <n>)
+
+  Returns the n-th root of <number>. The 2nd root is the square root,
+  the 3rd the cube root, and so on.
+
+  Example:
+  > think root(27, 3)
+  3
+  > think power(3, 3)
+  27
+
+  See also: sqrt(), power()
+& ROUND()
+  round(<number>,<places>)
+  Rounds <number> to <places> decimal places. <places> must be between
+  0 and 6.
+  See also: ceil(), floor(), bound(), trunc()
+& S()
+& S-FUNCTION
+  s(string)
+
+  This function performs evaluation on a string and returns that
+  string.  It should be considered extremely dangerous to use on a
+  string that you don't have complete control over (i.e., on user
+  input).  As usual, %n is the name, %s the subjective pronoun, %o the
+  objective, and %p the possessive.  Functions are evaluated.
+  It is important to note that the pronoun is that of the triggering object.
+
+  So, if the ve of an object were: "[s(This is %n)], and I were to 
+  type @trigger <object>/ve, it would return "This is <myname>", but 
+  if vf were @trigger me/ve, then triggering the vf makes the ve 
+  return "This is <object>"
+
+& SCAN()
+  scan(<object>, <command>)
+  scan(<command>)
+
+  This function works like @scan, and returns a space-separated list of 
+  dbref/attribute pairs containing $commands that would be triggered if
+  <command> were run by <object>. You must control <object> or be
+  See_All to use this function.
+
+  If no <object> is specified, this function works like @scan run
+  by the function's executor.
+
+& SCRAMBLE()
+  scramble(<string>)
+
+  This function scrambles a string, returning a random permutation of its
+  characters. For example, "[scramble(abcdef)]" might return "cfaedb".
+  Note that this function does not pay any attention to spaces or other
+  special characters; it will scramble these characters just like normal
+  characters.
+
+& SECS()
+  secs()
+
+  This function takes no arguments, and returns the number of elapsed
+  seconds since midnight, January 1, 1970 UTC. UTC is the base time zone,
+  formerly GMT. This is a good way of synchronizing things that must
+  run at a certain time.
+& SECURE()
+  secure(<string>)
+
+  This function returns <string> with all "dangerous" characters replaced
+  by spaces. Dangerous characters are ( ) [ ] { } $ % , ^ and ; This
+  can make output slightly ugly, but it's a good way of preventing other
+  people from doing nasty things with your objects.
+
+  See also: ESCAPE()
+
+& SET()
+  set(<object>, <flag>)
+  set(<object>/<attribute>, <attribute flag>)
+  set(<object>, <attribute>:<value>)
+  This function is equivalent to @set, and can be used to switch
+  flags, set attributes, and many other things.  The two arguments
+  to the function are the same as the arguments that would appear
+  on either side of the '=' in @set. This function returns nothing.
+  Note that you can't clear an attribute with set(), though
+  you can make it an empty attribute. Use wipe() to clear attributes.
+
+& SETDIFF()
+  setdiff(<list1>, <list2>[, <delimiter>][, <sort type>][, <osep>])
+  This function returns the difference of two sets -- i.e., the
+  elements in <list1> that aren't in <list2>. The list that is
+  returned is sorted. Normally, alphabetic sorting is done. You can
+  change this with the fourth argument, which takes the same form as
+  sort()'s second. If used with exactly four arguments where the fourth
+  is not a sort type, it's treated instead as the output separator.
+
+  Example:
+    > say setdiff(foo baz gleep bar, bar moof gleep)
+    You say, "baz foo"
+& SETINTER()
+  setinter(<list1>, <list2>[, <delimiter>][, <sort type>][,<osep>])
+  This function returns the intersection of two sets -- i.e., the
+  elements that are in both <list1> and <list2>. The list that is
+  returned is sorted. Normally, alphabetic sorting is done. You can
+  change this with the fourth argument, which takes the same form as
+  sort()'s second. If used with exactly four arguments where the fourth
+  is not a sort type, it's treated instead as the output separator.
+  Example:
+    > say setinter(foo baz gleep bar, bar moof gleep)
+   You say, "bar gleep"
+
+& SETQ()
+& SETR()
+  setq(<register>,<string>)
+  setr(<register>,<string>)
+  
+  The setq() and setr() functions are used to copy strings into local
+  registers.  setq() returns a null string; it is a purely "side effect"
+  function.  setr() returns the value stored.
+  
+  There are thirty-six local registers, numbered 0 through 9 and A through Z.
+  They are cleared at the start of each new queue cycle (i.e. whenever
+  a new command is evaluated). They are most useful for storing
+  complex function evaluations which are used repeatedly within a
+  single command.
+  Registers set via setq() or setr() can be accessed via the r() function, 
+  or via the %qN percent-substitution.
+  
+  See "help SETQ2" for examples of its use.
+  
+& SETQ2
+  
+  The setq() function is probably best used at the start of the string
+  being manipulated, such as in the following example:
+  
+    &TEST object=[strlen(%0)]
+    &CMD object=$test *:"[setq(0,u(TEST,%0))]Test. %0 has length [r(0)].
+    test Foo
+    > Object says, "Test. Foo has length 3."
+  
+  In this case, it is a waste to use setq(), since we only use the function
+  result once, but if TEST was a complex function being used multiple times
+  within the same command, it would be much more efficient to use the local
+  register, since TEST would then only be evaluated once.
+  
+  setq() can thus be used to improve the readability of MUSH code, as well
+  as to cut down the amount of time needed to do complex evaluations.
+  See "help SETQ3" for scoping rules of setq().
+  
+& SETQ3
+  The registers set by setq() can be used in later commands in the same
+  thread.  That is, the registers are set to null on all $-commands,
+  ^-commands, A-attribute triggers, etc., but are then retained from
+  that point forward through the execution of all your code.  Code
+  branches like @wait and @switch retain the register values from the
+  time of the branch, so the code:
+    
+  say setr(a,foo); @wait 0=say %qa; say setr(a,bar)
+    
+  produces the following when executed by an object:
+  
+  Object says "foo"
+  Object says "bar"
+  Object says "foo"
+
+& SETUNION()
+  setunion(<list1>, <list2>[, <delimiter>][, <sort type>][, <osep>])
+  This function returns the union of two sets -- i.e., all the
+  elements of both <list1> and <list2>, minus any duplicate
+  elements. Think of it as CAT() without words duplicated.  The list
+  returned is sorted. Normally, alphabetic sorting is done. You can
+  change this with the fourth argument, which takes the same form as
+  sort()'s second. If used with exactly four arguments where the fourth
+  is not a sort type, it's treated instead as the output separator.
+  Example:
+    > say setunion(foo baz gleep bar, bar moof gleep)
+    You say, "bar baz foo gleep moof"
+    > say setunion(1.1 1.0, 1.000)
+    You say, "1.0 1.000 1.1"
+    > say setunion(1.1 1.0, 1.000, %b, f)
+    You say, "1.0 1.1"
+& SHA0()
+  sha0(<string>)
+
+  Returns the SHA cryptographic hash of the string. See RFC 3174
+  for more information.
+& SHL()
+  shl(<number>,<count>)
+
+  Performs a leftwards bit-shift on <number>, shifting it <count> times.
+  This is equivalent to mul(<number>,power(2,<count>), but much faster.
+& SHR()
+  shr(<number>,<count>)
+
+  Performs a rightwards bit-shift on <number>, shifting it <count> times.
+  This is equivalent to div(<number>,power(2,<count>), but much faster.
+& SHUFFLE()
+  shuffle(<list>>[,<delimiter>][,<osep>])
+  
+  This function shuffles the order of the items of a list, returning a
+  random permutation of its elements. "[shuffle(foo bar baz gleep)]" 
+  might evaluate to "baz foo gleep bar".
+  
+& SIGN()
+  sign(<number>)
+
+  Essentially returns the sign of a number -- 0 if the number is 0,
+  1 if the number is positive, and -1 if the number is negative.
+  Thus, SIGN(-4) is -1, SIGN(2) is 1, and SIGN(0) is 0.
+& SIN()
+  sin(<angle>[, <angle type>)  
+  Returns the sine of <angle>, which should be expressed in the
+  given angle type, or radians by default.
+
+  See HELP CTU() for more on the angle type.
+& SORT()
+  sort(<word1> <word2> ...[,<sort type>][,<delimiter>][,<output sep>])
+  
+  This sorts a list of words. If no second argument is given, it will
+  try to detect the type of sort it should do. If all the words are
+  numbers, it will sort them in order of smallest to largest. If all
+  the words are dbrefs, it will sort them in order of smallest to
+  largest. Otherwise, it will perform a lexicographic sort.
+  
+  The following letters as a second argument specify a certain sort:
+  a:  Sort lexicographically (Maybe case-sensitive).
+  i:  Sort lexicographically (Always case-insensitive).
+  d:  Sort dbrefs.
+  n:  Sort integer numbers.
+  f:  Sort decimal numbers.
+
+  Whether or not the a sort type is case-sensitive or not depends
+  on the particular mush and its environment.
+  
+  The optional third argument gives the list's delimiter character.
+  If not present, <delimiter> defaults to a space.
+  The optional fourth argument gives a string that will delimit
+  the resulting list; it defaults to <delimiter>. 
+
+& SORTBY()
+  sortby([<obj>/]<attrib>,<list>[,<delimiter>][, <output seperator>])
+  This sorts an arbitrary list according to the u-function <obj>/<attrib>.
+  This u-function should compare two arbitrary elements, %0 and %1, and
+  return zero (equal), a negative integer (element 1 is less than element 2)
+  or a positive integer (element 1 is greater than element 2).
+  A simple example, which imitates a normal alphabetic sort:
+    > &ALPHASORT test=[comp(%0,%1)]
+    > say [sortby(test/ALPHASORT,foo bar baz)]
+    You say, "bar baz foo"
+  A slightly more complicated sort. #1 is "God", #2 is "Amby", "#3" is "Bob":
+    > &NAMESORT me=[comp(name(%0),name(%1))]
+    > say [sortby(NAMESORT,#1 #2 #3)]
+    You say, "#2 #3 #1"
+  Warning: the function invocation limit applies to this function. If
+  this limit is exceeded, the function will fail _silently_. List and
+  function sizes should be kept reasonable.
+
+  See also: anonymous attributes
+& SOUNDEX()
+  soundex(<word>)
+
+  The soundex function returns the soundex pattern for a word.
+  A soundex pattern represents the sound of the word, and similar
+  sounding words should have the same soundex pattern. Soundex patterns
+  consist of an uppercase letter and 3 digits.
+  > think soundex(foobar)
+  F160
+
+  For details of how the algorithm works, see help soundex2
+
+  See also: soundslike()
+& SOUNDEX2
+  Here's how the soundex algorithm works:
+  1. The first letter of the soundex code is the first letter of
+     the word (exception: words starting with PH get a soundex
+     starting with F)
+  2. Each remaining letter is converted to a number:
+      vowels, h, w, y ---------> 0
+      b, p, f, v --------------> 1
+      c, g, j, k, q, s, x, z --> 2
+      d, t --------------------> 3
+      l -----------------------> 4
+      m, n --------------------> 5
+      r -----------------------> 6
+     At this stage, "foobar" is "F00106" 
+  3. Strings of the same number are condensed. "F0106"
+  4. All 0's are removed, because vowels are much less important
+     than consonants in distinguishing words. "F16"
+  5. The string is padded with 0's or truncated to 4 characters. "F160"
+  That's it. It's not foolproof (enough = "E520", enuf = "E510") but
+  it works pretty well. :)
+& SOUNDLIKE()
+& SOUNDSLIKE()
+  soundslike(<word>,<word>)
+  soundlike(<word>,<word>)
+
+  The soundslike function returns 1 if the two words have the same
+  soundex code (see help soundex() for information), which means, 
+  in general, if they sound alike. For example:
+  
+  > think soundslike(robin,robbyn)
+  1
+  > think soundslike(robin,roebuck)
+  0
+
+& SPACE()
+  space(<number>)
+  Prints <number> number of spaces. Useful for times when you want to
+  be able to use lots of spaces to separate things. For example,
+  "a[space(5)]b  would print, "Amberyl says, "a     b"".
+& SPELLNUM()
+  spellnum(<number>)
+
+  Given a number, return its written-out representation in words.
+& SPLICE()
+  splice(<list1>, <list2>, <word>[, <delimiter>])
+  
+  This function splices <list1> and <list2> together. <list1> and <list2>
+  are space-separated lists of words
+  
+  If a word in <list1> is the same as <word>, it is replaced by the word
+  in the corresponding position in <list2>.  Both lists must have the
+  same number of words.
+  
+  Example:
+    > say [splice(foo bar baz,eek moof gleep,bar)]
+    You say, "foo moof baz"
+  
+& SQL()
+  sql(<query string>,[<row delimiter>[,<field delimiter>])
+
+  Performs an SQL query if the MUSH is configured to connect to an 
+  SQL database server. This function requires a WIZARD flag or
+  the Sql_Ok power.
+
+  By default, SELECT queries will return their data space-separated.
+  Usually, it's more useful to specify a character to delimit 
+  rows returned (and sometimes another character to delimit the
+  fields/columns returned, if they may contain spaces).
+
+  <query string> is evaluated, so it's useful to either read it from
+  another attribute with u() or use lit() to protect commas. If 
+  you will be interpolating user-provided values into the query,
+  be careful to escape them with sqlescape(), like this:
+
+     &SEL_GETID obj = SELECT id FROM mytable WHERE name = '[sqlescape(%0)]'
+     &DOIT obj = $do *: ... [setq(0,u(SEL_GETID,%0))] ...
+
+  See also: sqlescape(), @sql
+
+& SQLESCAPE()
+  sqlescape(<string>)
+
+  This function performs SQL-server-implemented escaping of strings.
+  It's important to escape arbitrary data before passing it to the
+  sql() function or @sql command to prevent SQL injection attacks.
+
+  Example:
+    > think sqlescape(You don't say)
+    You don\'t say
+
+  When used in an SQL query, the results of an sqlescape() function
+  should be enclosed in single quotes.
+
+  You must be a WIZARD or have the Sql_Ok power to use this function.
+
+  See also: sql(), @sql
+
+& SQRT()
+  sqrt(<number>)
+  Returns the square root of <number>. <number> cannot be negative.
+
+  See also: root()
+& SQUISH()
+  squish(<string>[, <character>])
+  
+  This function removes the leading and trailing <character>s from a string,
+  and condenses all inter-word <character>s to a single <character>. If no
+  character is given, uses space.
+  
+  Example:
+  
+    > say [squish(  foo bar  baz blech   eek )]
+    You say, "foo bar baz blech eek"
+    > say [squish(||a|| b|c|d, |)]
+    You say, a| b|c|d
+
+& STARTTIME()
+  Function: starttime()
+  Returns a string containing the time the MUSH first started up (not 
+  including @shutdown/reboots).  The time is in the same format that the 
+  TIME() function returns.
+  Example:
+    > say starttime()
+    You say "Sat Dec  7 00:09:13 1991
+
+  See also: convtime(), restarttime(), restarts()
+& RESTARTTIME()
+  restarttime()
+
+  Returns a string which is the time the MUSH last rebooted. The time
+  is in the same format as the TIME() function returns.
+
+  See also: convtime(), starttime()
+& RESTARTS()
+  restarts()
+
+  Returns the number of times the server has been rebooted with
+  @shutdown/reboot since the last full startup.
+& SSL()
+  ssl(<player|descriptor>)
+
+  This function returns 1 if the player is using an SSL connection,
+  and 0 otherwise. If SSL is disabled, it always returns 0.
+  Players can check the SSL status of their own connection.
+  The See_All power is required to check other connections. 
+
+  See also: terminfo()
+& STEP()
+  step([<obj>/]<attr>, <list>, <step>[, <delim>, <outsep>])
+
+  This function is similar to map(), except you can pass up to
+  10 elements of the list at a time, in %0 to %9. <step> must
+  be between 1 and 10, with a step of 1 equivalent to map().
+  If the elements of the list can't be split up evenly, the last
+  evaluation pads the missing values with empty values. 
+  If no output separator is given, the delimiter (Default is a
+  space) is used.
+
+  Continued in step2
+& STEP2
+  Example:
+  > &foo me=%0 - %1 - %2%r
+  > think step(foo, 1 2 3 4 5, 3)
+  1 - 2 - 3
+  4 - 5 -
+
+  See also: map(), iter(), anonymous attributes
+& STDDEV()
+  stddev(<number>,...)
+
+  Returns the sample standard deviation of its arguments.
+
+  See also: mean(), median()
+& STRCAT()
+  strcat(<string1>, <string2>)
+  Concatenates two strings together, with no space between them.
+  For example, strcat(foo bar,baz blech) will return the string
+  "foo barbaz blech".
+& STRINSERT()
+  strinsert(<string>, <position>, <insert>)
+
+  This function returns <string>, with <insert> added before <position>
+  in <string>. Note that the first character in a string is numbered 0,
+  not 1. 
+
+  Example:
+  > think strinsert(barbaz, 0, foo)
+  foobarbaz
+  > think strinsert(Myname, 2, %b)
+  My name   
+& STRIPACCENTS()
+  stripaccents(<string>)
+
+  Returns the string with accented characters converted to non-accented.
+  As with the accent() function, this assumes the ISO 8859-1 character set.
+& STRIPANSI()
+  stripansi(<string>)
+
+  Returns the string with all ansi and HTML codes removed.
+& STRLEN()
+  strlen(<string>)
+
+  Returns the length of the string (The number of characters in it).
+& STRMATCH()
+  strmatch(<string>, <pattern>)
+  
+  This function is matches <pattern> against the entire <string>.
+  It returns 1 if it matches and 0 if it doesn't. It is not
+  case-sensitive, and <pattern> may contain wildcards.
+  strmatch(Foo bar baz,*Baz) will return 1.
+  strmatch(Foo bar baz,*Foo) will return 0.
+  strmatch(Foo bar baz,*o*a*) will return 1.
+& STRREPLACE()
+  strreplace(<string>, <start>, <length>, <text>)
+
+  Returns string with the <length> characters starting at <start> replaced
+  by <text>. As with other string functions, the first character is at
+  position 0.
+
+  Example:
+  > think strreplace(Fix teh typo, 4, 3, the)
+  Fix the typo
+& SUB()
+  sub(<num>, <num>)
+  Sub() subtracts the second number from the first.
+& SUBJ()
+  subj(<object>)
+
+  Returns the subjective pronoun - he/she/it - for an object.
+& RESWITCH()
+& RESWITCHI()
+& RESWITCHALL()
+& RESWITCHALLI()
+  reswitch(<string>, <re1>, <list1>, [<reN>, <listN>], ... [<default>])
+  reswitchall(<string>, <re1>, <list1>, [<reN>, <listN>], ... [<default>])
+  reswitchi(<string>, <re1>, <list1>, [<reN>, <listN>], ... [<default>])
+  reswitchalli(<string>, <re1>, <list1>, [<reN>, <listN>], ... [<default>])
+
+  These functions are just like switch() except they compare <string>
+  against a series of regular expressions, not wildcard patterns. reswitch()
+  is case-sensitive, reswitchi() is case-insensitive. The reswitchall versions
+  evaluate every corresponding <list>, not just the first that matches a regexp.
+
+  See also: switch() 
+& SWITCH()
+& SWITCHALL()
+& CASE()
+& CASEALL()
+  switch(<string>, <expr1>, <list1>, [<exprN>, <listN>], ...[<default>])
+  switchall(<string>, <expr1>, <list1>, [<exprN>, <listN>], ...[<default>])
+  case(<string>, <expr1>, <list1>, [<exprN>, <listN>], ...[<default>])
+  caseall(<string>, <expr1>, <list1>, [<exprN>, <listN>], ...[<default>])
+
+  These functions match <string> against the <expr>essions, returning the
+  corresponding <list>. If nothing is matched, the <default> is returned.
+  Only the first matching expression counts (like @switch/first), and
+  <list>s that are not returned are not evaluated.
+
+  Wildcard patterns are allowed in switch() and switchall(). case() and
+  caseall() do a case-sensitive exact match, like member() or comp().
+
+  If the string "#$" appears in the <list> to be evaluated, it will be
+  replaced with the evaluated value of <string> /before/ evaluation of
+  <list>. This is not done in case() and caseall(), for TinyMUSH 3
+  compatibility.
+  switchall() and caseall() will return all the <lists> with matching
+  <expr>ssions, without spaces between them, so they match similarly to
+  @switch, while switch() and case() match more like @switch/first.
+
+  See HELP SWITCH WILDCARDS for more, and HELP SWITCH2 for examples
+& SWITCH2
+  Examples of switch() and related functions:
+    > say switch(test, *a*, foo, *b*, bar, *t*, neat, baz)
+    You say, "neat"
+    > say switchall(ack, *a*, foo, *b*, bar, *c*, neat, baz)
+    You say, "fooneat"
+    > say switch(moof, *a*, foo, *b*, bar, *t*, neat, baz)
+    You say, "baz"
+    > say switch(moof, *a*, foo, *b*, bar, *t*, neat, #$)
+    You say, "moof"
+    > say case(moof, *f, foo, moof, bar, baz)
+    You say, "bar"
+& SWITCH WILDCARDS
+  @switch, @select, switch() and switchall() normally do wildcard
+  matching between their first argument and the <expr>ession
+  arguments, with the normal * and ? special characters. However, if
+  one of the <expr>essions starts with < or >, a less than or greater
+  than check is done instead of wildcard matching for that pair.
+
+  switch(X, >Y, A, B) returns A if X is greater than Y,
+  and B if X is less than or equal to Y.
+  
+  switch(X, <Y, A, B) returns A if X is less than Y,
+  and B if X is greater than or equal to Y.
+
+  If X and Y are numbers, the test is like using gt() or lt(). gte()
+  and lte() can be simulated by using Y'=Y-1 and Y'=Y+1.
+
+  If X and Y are non-numeric strings, the result of comp(X,Y) is used
+  to determine which string is alphabetically before (Less than) the other.
+
+  If you need to have a leading < or > that's treated like a normal
+  character in a wildcard match, use \\< or \\> (The \\ will turn into
+  \ when the argument is evaluated, and then that single \ will stop
+  the greater/less than check).
+
+  See also: HELP WILDCARDS
+& T()
+  t(<expression>)
+
+  Returns a 0 if the expression is false, and 1 otherwise. 
+  The definition of truth and falsehood depends on configuration settings;
+  see help BOOLEAN VALUES for details.
+
+& TABLE()
+  table(<list>,<field width>,<line length>,<delimiter>,<output separator>) 
+
+  This function returns the elements of <list> in a tabular format.
+  All other parameters are optional.
+  <field width> specifies how wide each table entry is allowed to be.
+  It defaults to 10 characters
+  <line length> is how wide a table row can be. Default is 78 chars.
+  <delimiter> is the delimiter used in <list>. Default is white space.
+  <output separator> is a single character to be used between entries
+  in the table. Default is a single space.
+  Examples:
+  > think table(a b areallylongone d)
+  a          b          areallylon d
+
+  > think table(the quick brown fox,10,25, ,|)
+  the       |quick
+  brown     |fox
+    
+& TAN()
+  tan(<angle>[, <angle type>])
+  Returns the tangent of <angle>, which should be expressed in the
+  given angle type, or radians by default.
+
+  See HELP CTU() for more on the angle type.
+& TEL()
+  tel(<object>,<destination>[,<silent>[,<inside>]])
+
+  This function will teleport <object> to <destination>, exactly as
+  @tel <object>=<destination>. <silent> is an optional boolean that,
+  if true, makes the function act like @tel/silent. <inside>
+  is an optional boolean that, if true, makes the function act like
+  @tel/inside (some value for <silent> must also be specified).
+
+  See also: @tel
+& TERMINFO()
+  terminfo(<player|descriptor>)
+
+  Returns a list with at least one element - the type of client used
+  by the player, or "unknown" if the client being used doesn't support
+  being asked to identify itself using RFC 1091. 
+  
+  Other elements in the list describe client capabilities, and
+  currently include:
+  pueblo           present if the client is in Pueblo mode.
+  telnet           present if the client understands the telnet protocol.
+  ssl              present if the client is using an SSL/TLS connection.
+
+  Other fields may be added in the future, if, for example, MXP support
+  is ever added.
+
+  Players can use terminfo() on their own connections. Using it on
+  other players is restricted to see_all objects.  
+& TEXTFILE()
+& dynhelp()
+  textfile(<type>,<entry>)
+
+  textfile() returns the text of entries from cached text files (such as
+  "help", "news", "events", etc.) All whitespace and newlines are included,
+  so you may want to edit %r's and squish the result if you plan to use
+  the text as a list of words rather than a display.
+
+  Examples: 
+  > say textfile(help,tel\()
+  You say, "  tel(<object>,<destination>)
+
+    This function will teleport <object> to <destination>, exactly as
+    @tel <object>=<destination>.
+
+    See also: @tel
+  "
+& TIME()
+& UTCTIME()
+  time([utc])
+  utctime()
+  time(<timezone>)
+  time(<object>)
+
+  time() gives you the current time on the MUSH.
+  WARNING!  This is the time on the machine that the mush is running
+  on, and not where you are.
+
+  utctime() and time(utc) give the same time in UTC (Aka GMT), not the
+  server's local timezone.
+
+  If a timezone (-24 to +24) is given, it adds that many hours to UTC
+  to return the correct timezone. Timezone may contain decimals (-1.5)
+
+  If <object> is given, and is a valid object containing an attribute
+  TZ, it modifies the resulting time according to said timezone. time()
+  on a player will always return a time, and if TZ is not a number between
+  -24 and +24 inclusive, the time returned is UTC.
+
+  See also: timefmt(), timestring(), convsecs(), convtime()
+& ETIMEFMT()
+  etimefmt(<format>[, <secs>])
+
+  This function is similar to timestring() - it formats an elapsed time
+  into days, hours, minutes and seconds. However, its formatting is
+  much more versatile than timestring(), as well as being more complex.
+
+  Escape codes in <format> are replaced by the proper values, and other
+  characters are left unchanged.
+
+  A list of all codes is in HELP ETIMEFMT2
+
+  Examples:
+  > say etimefmt(I have been connected for $2H:$2M., conn(%#))
+  You say, "I have been connected for 01:32."
+  > think etimefmt($2mm $2ss, 500) - [timestring(500)]
+   8m 20s -  8m 20s
+
+  See also: timestring(), timefmt()
+& ETIMEFMT2
+  etimefmt()'s escape codes are similar to timefmt()'s.
+  The time is broken up into days, hours, minutes, and seconds, and
+  each value replaces the matching code.
+
+  $s - The number of seconds.    $h - The number of hours.
+  $S - The number of seconds,    $H - The number of hours,
+       left-padded with 0.            left-padded with 0.
+  $m - The number of minutes.    $d - The number of days.
+  $M - The number of minutes,    $D - The number of days,
+       left-padded with 0.            left-padded with 0.
+  $$ - A literal $.
+
+  You can also put a number between the $ and letter to specify
+  a minimum width for the expanded code. The capital letter codes
+  are the same as the lower case codes if you don't provide a width.
+  An 'x' before the code (But after any number) will automatically add
+  a d, h, m, or s suffix to the time, and a 'z' will not display anything
+  if the field's value is 0. x and z can be combined.
+
+  Continued in HELP ETIMEFMT3
+& ETIMEFMT3
+  Some examples:
+
+  > think etimefmt($2h:$2M, 3700)
+   1:01
+  > think etimefmt(You have $m minutes and $s seconds to go, 78)
+  You have 1 minutes and 18 seconds to go
+  > think squish(etimefmt(Connected for $zxd $xzh $zxm $xzs, conn(me)))
+  Connected for 5h 24m 45s
+& TIMEFMT()
+  timefmt(<format>[, <secs>])
+
+  This function takes a format and a time in seconds (Or the current time)
+  and returns the format with escape sequences in it expanded to the
+  proper values based on the time, relative to the host the server is
+  on.
+
+  A list of all codes is in HELP TIMEFMT2
+
+  Example:
+  > think timefmt($A\, the $dth day of $B.)
+  Monday, the 17th day of July.
+& TIMEFMT2
+  All escape codes start with a $. To get a literal $, use $$.
+  Invalid codes will return #-1 INVALID ESCAPE CODE. Other text will be
+  passed through unchanged.
+
+  $a - Abbreviated weekday name  $p - AM/PM  ($P may also work)
+  $A - Full weekday name         $S - Seconds after the minute
+  $b - Abbreviated month name    $U - Week of the year from 1rst Sunday
+  $B - Full month name           $w - Day of the week. 0 = Sunday
+  $c - Date and time             $W - Week of the year from 1rst Monday
+  $d - Day of the month          $x - Date
+  $H - Hour of the 24-hour day   $X - Time
+  $I - Hour of the 12-hour day   $y - Two-digit year
+  $j - Day of the year           $Y - Four-digit year
+  $m - Month of the year         $Z - Time zone
+  $M - Minutes after the hour    $$ - $ character.
+& TIMESTRING()
+  timestring(<seconds>[,<pad flag>])
+
+  The timestring function takes a number of seconds as input and
+  returns the amount of time formatted into days, hours, minutes, and
+  seconds. If <pad flag> is 1, all time periods will be used even
+  if the number of seconds is less than a day, hour, or minute.
+  If <pad flag> is 2, all numbers will be 2 digits long.
+
+  Example:
+  > say [timestring(301)]
+  You say, " 5m  1s"
+  > say [timestring(301,1)]
+  You say, "0d  0h  5m  1s"
+  > say [timestring(301,2)]
+  You say, "00d 00h 05m 01s"
+
+& TR()
+  tr(<string>,<find>,<replace>)
+
+  This function translates every character in <string> that exists in
+  <find> to the character at an identical position in <replace>. Ranges of
+  characters seperated by -'s are accepted. <find> and <replace> must be the
+  same length after expansion of ranges. If a character exists more than
+  once in <find>, only the last instance will be counted. The example
+  below is the common ROT-13 algorithm for lower case strings, demonstrated
+  with every letter explicitly listed, and with the equivalent but briefer 
+  character ranges. Literal -'s can be in <find> and <replace> if they are the
+  first or last characters in the arguments.
+
+   Examples:
+      > say tr(hello,abcdefghijklmnopqrstuvwxyz,nopqrstuvwxyzabcdefghijklm)
+       You say, "uryyb"
+      > say tr(uryyb, a-z, n-za-m)
+       You say, "hello"
+
+  See also: MERGE()
+& TRIM()
+  trim(<string>[,<character to trim>][,<trim style>])
+  trimpenn(<string>[,<character to trim>][,<trim style>])
+  trimtiny(<string>[,<trim style>][,<character to trim>])
+  This function trims leading and trailing characters from a string.
+  The character trimmed is normally a space; if a second argument is
+  provided, however, that character will be used instead.
+  
+  If no trim style is specified, characters are trimmed from both the
+  left and right sides of the string. If the 'l' trim style is specified,
+  characters are only trimmed from the left side. If the 'r' trim style
+  is specified, characters are only trimmed from the right side. If you
+  specify a trim style, you must also explicitly specify the character
+  to trim, since the trim style must be the third argument to the function.
+
+  If the tiny_trim_fun config option is "yes", the character and style
+  arguments are reversed. Use trimpenn() or trimtiny() if you want to
+  specify a particular argument sequence no matter how the option is set.
+   Examples:
+      > say [trim(   foo bar baz   eek  )]
+       You say, "foo bar baz   eek"
+      > say [trim(***BLAM***,*)]
+       You say, "BLAM"
+      > say [trim(-----> WOW---,-,r)]
+       You say, "-----> WOW"
+
+& TRUNC()
+& VAL()
+  trunc(<string>)
+  val(<string>)
+  
+  This function truncates floating point numbers to integers. It can
+  also be used to return the leading numeric prefix of a string, or
+  "0" if there isn't one. For example, "val(101Dalmations)"  => 101.
+  
+  See also: ceil(), floor(), bound(), round()
+& TYPE()
+  type(<object>)
+
+  This function returns the type of an object - PLAYER, THING, EXIT,
+  or ROOM. See "help types of objects" for more.
+& U()
+& UFUN()
+  u([<object>/]<user function name>, <arg 0>, <arg 1>, ...)
+  ufun([<object>/]<user function name>, <arg 0>, <arg1>, ...)
+  This allows you to create your own functions and evaluate them.
+  <user function name> is the attribute that contains the desired
+  user-defined function. Supplying <object> is optional; if you
+  do not, the attribute will be read off the object that is
+  evaluating the UFUN().
+  
+  <arg 0>, <arg 1>, ... are the arguments that get passed to the
+  user function as v(0), v(1), etc. (as in @trigger).  You can pass
+  up to 10 arguments (v(0) through v(9)); extra arguments will be
+  evaluated but not accessible (since v(10) refers to an attribute,
+  not another argument).
+  
+  This function is also known as U()  (alias for TinyMUSH compatibility).
+
+  See "help UFUN2" for more.
+  
+& U2
+& UFUN2
+  Example:
+  
+  > @va Object=$test *:"[ufun(testfun, v(0))]; @emit [v(0)]
+  > &testfun object=[strlen(v(0))] [ucstr(v(0))]
+  > test string
+  Object says, "6 STRING"
+  string
+  
+  A user-defined function may be as complex as you want it to be,
+  subject to limits on recursion depth, number of function invocations,
+  or cpu time that may be configured in the MUSH.
+  If the evaluation order doesn't quite seem right, adding escapes
+  or breaking up the expression will probably help.
+    
+& UCSTR()
+  ucstr(<string>)
+
+  Returns <string> with all letters converted to uppercase.
+  Example: ucstr(Foo BAR baz) returns "FOO BAR BAZ"
+& UDEFAULT()
+  Function:  udefault([<obj>/]<attr>,<default case>[,<arg>]...)
+  This function returns the value of the user-defined function
+  as defined by <attr> (or <obj>/<attr>), as if retrieved via
+  the u() function, with <args>, if the attribute exists and is
+  readable by you.
+  Otherwise, it evaluates the default case, and returns that. The
+  default case is only evaluated if the attribute does not exist
+  or cannot be read.
+  Examples:
+    > &TEST me=[center(%0,5,*)]
+    > say udefault(Test,-- BOOM --,ACK)
+    You say "*ACK*"
+    > &TEST me
+    > say udefault(me/Test,-- BOOM --,ACK)
+    You say "-- BOOM --"
+  See also: get(), eval(), ufun(), uldefault(), default(), edefault()
+& ULDEFAULT()
+  uldefault([<obj>/]<attr>,<default case>[,<arg>]...)
+
+  Just like UDEFAULT(), but it preserves registers like ULOCAL().
+
+  See also: u(), udefault(), ulocal(), setq()
+& ULOCAL()
+  Function:  ulocal([<obj>/]<attr>[,<arg>]...)
+  The ulocal() function is almost identical to u() in function:  it
+  evaluates an attribute, either from the object performing the function,
+  or another object that you control or has the same owner as you, passing
+  in arguments and returning the result. When evaluating the fetched
+  attribute, %# refers to the original enactor and not the 'calling' object;
+  'me' refers to the object that supplied the attribute.
+
+  However, unlike the u() function, the evaluated attribute receives
+  only a temporary copy of the global registers r(0)-r(9) and
+  r(A)-r(Z) (%q0-%q9, %qa-%qz).  This means that functions "below" the
+  level of the ulocal() can reset global registers for temporary
+  calculations, without needing to worry about "clobbering" the original
+  values (which are restored when ulocal() returns).
+  This makes ulocal() particularly useful for global or shared code which
+  calls arbitrary u() functions, where global register values need to be
+  preserved from accidental user clobbering.
+  See "help ulocal2" for examples.
+& ULOCAL2
+  Example of ulocal():
+    > &FRUIT me=apples bananas oranges pears
+    > &SUB-FUNCTION me=[setq(0,v(FRUIT))][extract(%q0,match(%q0,%0),1)]
+    > &TOP-FUNCTION me=[setq(0,are delicious!)][ulocal(SUB-FUNCTION,%0)] %q0
+    > say u(TOP-FUNCTION,b*)
+    You say "bananas are delicious!"
+  If SUB-FUNCTION had been called with u() instead of ulocal():
+    > &TOP-FUNCTION me=[setq(0,are delicious!)][u(SUB-FUNCTION,%0)] %q0
+    > say u(TOP-FUNCTION,b*)
+    You say "bananas apples bananas oranges pears"
+  In this second example, in SUB-FUNCTION, %q0 was set to "apples bananas
+  oranges pears", so that when the u() "returned" and TOP-FUNCTION evaluated
+  %q0, this is what was printed. In the first example, ulocal() reset the
+  value of %q0 to its original "are delicious!"
+  See also: u(), setq(), r()
+& V()
+& V-FUNCTION
+  V(<name of attribute>)
+  V(<variable name>)
+
+  The first form of this function works just like get(me/<attribute name>).
+  It is faster and more efficient than get(), however, and so it's better
+  to use v() when you are getting attributes off an object or its parents.
+
+  The second form of this function provides a different way of getting the
+  results of %-substitutions like %#, %N, %0, etc. Simply take the variable
+  name (whatever follows the % symbol) and put it inside the v() function:
+
+       v(N) is equivalent to %N
+       v(!) is equivalent to %!
+       v(3) is equivalent to %3
+
+  See also: SUBSTITUTIONS, get(), ATTRIBUTES
+
+& VADD()
+  vadd(<vector>,<vector>[,<delimiter>])
+
+  Returns the sum of two vectors. A vector is a list of numbers
+  separated by spaces or a delimiter.
+
+  > think vadd(1 2 3,4 5 6)
+  5 7 9
+  > think vadd(0|0|0,1|2|3,|)
+  1|2|3
+& VALID()
+  valid(<category>,<string>)
+
+  The valid() function checks to see if <string> is a valid member of
+  <category>, and returns 1 if it is, 0 if not, and #-1 if an
+  invalid category is used.
+  
+  The categories are:
+   name        Test for a valid object name.
+   attrname    Test for a valid attribute name.
+   playername  Test for a valid player name that can be set with
+                @name or @alias.
+   password    Test for a valid password.
+   command     Test for a valid command name for @command/add
+   function    Test for a valid function name for @function
+
+  > think valid(name,Foobar)
+  1 
+  > think valid(attrname,Foo bar)
+  0
+& VCROSS()
+  vcross(<vector>, <vector>[, <delimiter>])
+
+  Returns the 3-dimensional vector that is the cross product of its
+  3-dimensional argument vectors. The cross product is defined as:
+   
+   x = Ay * Bz - By * Az
+   y = Az * Bx - Bz * Ax
+   z = Ax * By - Bx * Ay
+
+  > think vcross(4 5 6, 7 8 9)
+  -3 6 -3
+& VDIM()
+  vdim(<vector>[,<delimiter>])
+
+  Returns the dimensionality of a vector.
+
+  > think vdim(1 2 3 4)
+  4
+& VDOT()
+  vdot(<vector>,<vector>[,<delimiter>])
+  
+  Returns the dot product of two vectors. A dot product is the sum
+  of the products of the corresponding elements of the two
+  vectors, e.g. vdot(a b c,d e f) = ad + be + cf.
+  The vectors must be of the same length.
+  
+  > think vdot(1 2 3,2 3 4)
+  20
+& VMIN()
+  vmin(<vector>, <vector>[, <delimiter>])
+
+  Returns a new vector made out of the minimums of each
+  corresponding pair of numbers from the two vectors.
+  The vectors must be of the same length.
+
+  > think vmin(1 2 3, 4 1 2)
+  1 1 2
+& VMAX()
+  vmax(<vector>, <vector>[, <delimiter>])
+
+  Returns a new vector made out of the maximums of each
+  corresponding pair of numbers from the two vectors.
+  The vectors must be of the same length.
+
+  > think vmax(1 2 3, 4 1 2)
+  4 2 3
+
+& VERSION()
+  Function: version()
+  Returns a string which contains various version information for the MUSH
+  you're on.
+  Example:
+     > say version()
+     You say "PennMUSH version 1.6.0 patchlevel 0 [1/10/96]"
+
+& VISIBLE()
+  visible(<object>,<victim>[/<attribute>])
+  
+  If no attribute name is provided, this function returns 1 if 
+  <object> can examine <victim>, or 0, if it cannot. If an
+  attribute name is given, the function returns 1 if <object>
+  can see the attribute <attribute> on <victim>, or 0, if it
+  cannot.
+  
+  If <object>, <victim>, or <attribute> is invalid, the function
+  returns 0.
+& VMAG()
+  vmag(<vector>[,<delimiter>]
+
+  Returns the magnitude of a vector, using a Euclidean distance metric.
+  That is, for vector a b c d, returns sqrt(a^2+b^2+c^2+d^2).
+
+  > think vmag(3 4) 
+  5
+& VMUL()
+  vmul(<vector|number>,<vector|number>[,<delimiter>])
+
+  Returns the result of either multiplying a vector by a number,
+  or the element-wise product of two vectors. The element-wise product
+  of a b c by w x z is aw bx cz
+
+  > think vmul(1 2 3,2)
+  2 4 6
+  > think vmul(1 2 3,2 3 4)
+  2 6 12
+& VSUB()
+  vsub(<vector>,<vector>[,<delimiter>])
+
+  Returns the difference between two vectors.
+
+  > think vsub(3 4 5,3 2 1)
+  0 2 4
+& VUNIT()
+  vunit(<vector>[,<delimiter>]
+
+  Returns the unit vector (a vector of magnitude 1), which points
+  in the same direction as the given vector.
+
+  > think vunit(2 0 0)
+  1 0 0
+  > think vmul(vunit(5 6 7),vmag(5 6 7))
+  5 6 7
+& WIDTH()
+& HEIGHT()
+& SCREENWIDTH
+& SCREENHEIGHT
+  width(<player|descriptor>)
+  height(<player|descriptor>)
+
+  These two functions return the screen width and height for a connected
+  player. If the player's client is capable of doing so, it will let the
+  mush know what the correct sizes are on connection and when the client
+  is resized. The defaults are 78 for width, and 24 for height, the
+  normal minimal values. These can be changed with the special
+  SCREENWIDTH and SCREENHEIGHT commands, both of which take a number as
+  their sole argument, and set the appropriate field.
+
+  If used on something that's not a player, the functions return the
+  default values.
+
+  The intent of these functions is allow softcode that does formatting
+  to be able to produce a display that can make full use of any given
+  screen size.
+& WHERE()
+  where(<object>)
+  
+  This function returns the "true" location of an object. This is
+  the standard location (i.e. where the object is) for things and
+  players, the source room for exits, and #-1 for rooms.
+  
+  In other words, the "true" location of an object is where it is
+  linked into the database. For example, an exit appears in the 
+  room of its "home", not its "location" (the LOC() function on an
+  exit will return the latter). A room's "real" location is always
+  Nothing (the LOC() function will return its drop-to).
+  
+& WIPE() 
+  wipe(<obj>/<attribute-pattern>)
+
+  This function is equivalent to @wipe. It returns nothing. 
+  
+& WORDPOS()
+  wordpos(<list>, <number>[, <delimiter>])
+
+  Returns the number of the word within <list> where the <number>th
+  character falls. Characters and words are numbered starting with 1,
+  and spaces between words are treated as belonging to the word that
+  follows them. If <number> is not within the string, #-1 is returned.
+  Example: wordpos(foo bar baz, 5) returns "2"
+& WORDS()
+  words(<list>[,<delimiter>])
+
+  words() returns the number of elements in <list>.
+& WRAP()
+  wrap(<string>, <width>[, <first line width>[, <line separator>])
+
+  This function takes <string> and splits it into lines containing
+  no more than <width> characters each. If <first line width> is
+  given, the first line may have a different width.  If <line separator>
+  is given, it is inserted between each line; by default the 
+  separator is a newline.
+
+  Examples:
+  @desc here=[wrap(Wrapped paragraph,72)]
+  @desc here=[wrap([space(4)]Indented paragraph,72)]
+  @desc here=[iter(wrap(Hanging indent,72,76,|),
+                   [switch(#@,>1,space(4))]##,|,%r)]
+
+& XGET()
+  xget(<object>, <attribute>)
+  
+  This function is identical to get() in purpose, but a comma instead of
+  a slash separates object and attribute. There is no real advantage to
+  using this instead of get(). Please see "help get()" for more details
+  on the use of this function.
+  
+& XOR()
+  xor(<boolean value>,<boolean value>)
+
+  Takes two booleans, and returns a 1 if one, and only one of the two
+  inputs is equivalent to true(1).  See BOOLEAN VALUES.
+
+  See also: and(), or(), not(), nor()
+& ZEMIT()
+  zemit(<zone>, <message>)
+  nszemit(<zone>, <message>)
+
+  Sends a message to everything zoned to <zone>, as per @zemit.
+  Costs apply.
+
+  nszemit() is a privileged variation that works like @nszemit.
+
+& ZFUN()
+  zfun(<user function name>, <arg 0>, <arg1>, ... <arg8>)
+  This is essentially identical to UFUN(), but the attribute corresponding
+  to the user function name is read from the ZMO of the object instead
+  of from the object itself. In order to read the attribute from the ZMO,
+  one of the following criteria must be met:
+  1. The object is set WIZARD or ROYALTY.
+  2. The object controls the ZMO.
+  3. The object's owner owns the attribute on the ZMO.
+  4. The ZMO is set VISUAL.
+  5. The attribute being checked is set VISUAL.
+  See the help for UFUN() for more details on user-defined functions.
+& ZONE()
+  zone(<object>[, <new zone>])
+  Returns the object's 'zone'. This is the dbref of the master object
+  which defines the zone.  If the second argument is specified, the
+  function tries to change the zone on the object before reporting it.
+
+  See also: ZONES
diff --git a/game/txt/hlp/cobra_mail.hlp b/game/txt/hlp/cobra_mail.hlp
new file mode 100644 (file)
index 0000000..e97951b
--- /dev/null
@@ -0,0 +1,309 @@
+& mail
+& @mail
+  @mail[/<switches>] [<msg-list> [= <target>]]
+  @mail[/<switches>] <player-list> = [<subject>/]<message>
+  @mail invokes the built-in MUSH mailer, which allows players to send
+  and receive mail. Pronoun/function substitution is performed on
+  any messages you may try to send.  
+
+  A <msg-list> is one of the following:
+        A single msg # (ex: 3)
+        A message range (ex: 2-5, -7, 3-)
+        A folder number and message number/range (ex: 0:3, 1:2-5, 2:-7)
+        A sender (ex: *paul)
+        An age of mail in days (ex: ~3 (exactly 3), <2, >1)
+           "days" here means 24-hour periods from the current time.
+        One of the following: "read", "unread", "cleared", "tagged",
+        "urgent", "all" (all messages in all folders), "folder" (all
+        messages in current folder)
+  A <player-list> is a space-separated list of recipients, which may be:
+        Player names
+        Player dbref #'s
+        Message #'s, in which case you send to the sender of that message.
+        An alias name (see help @malias)
+
+  See also the following topics:    mail-sending   mail-reading   
+      mail-folders   mail-forward   mail-other     mail-admin
+      @malias
+& mail-reading
+
+  @mail <msg #>
+  @mail/read <msg-list>
+        This displays messages which match the msg# or msg-list from
+        your current folder.
+  
+  @mail
+  @mail <msg-list, but not a single msg #> 
+  @mail/list <msg-list>
+        This gives a brief list of all mail in the current folder,
+        with sender name, time sent, and message status.
+        The status field is a set of characters (ex: NC-UF+) which mean:
+                N = New (unread) message
+                C = Cleared message
+                U = Urgent message
+                F = Forwarded message
+                + = Tagged message
+        The opposites of these (read messages, etc.) are indicated with a
+        '-' in the status field in that position.
+        
+& mail-sending 
+  @mail[/switch] <player-list> = [<subject>]/<msg>
+        This sends the message <msg> to all players in <player-list>.
+        If no subject is given, the message subject is the beginning
+        of the message itself.
+        All function substitutions are valid in <msg> including mail(#) which
+        will allow you to forward mail you have received to other users.
+        The following switches are available:
+                /send   - same as no switch
+                /urgent - mail is marked as "Urgent"
+                /silent - no notification to sender that mail was sent
+                /nosig  - no mail signature
+        If you have an @mailsignature attribute set on yourself, its
+        contents will be evaluated and appended to the message unless
+        the /nosig switch is given.
+  @mail/fwd <msg-list> = <player-list>
+        This sends a copy of all the messages in <msg-list> to
+        all the players in <player-list>. The copy will appear to have
+        been sent by you (not the original sender), and its status will
+        be "Forwarded".
+
+& mail-other
+  @mail/clear [<msg-list> | all]
+  @mail/unclear [<msg-list> | all]
+        These commands mark mail in the current folder as cleared or uncleared.
+        Mail marked for clearing is deleted when you disconnect, or
+        if you use @mail/purge. If no msg-list is specified, all
+        mail in your current folder is cleared. If "all" is given instead
+        of a msg-list, all mail in *all* folders is cleared/uncleared.
+  
+  @mail/purge
+        Actually deletes all messages marked for clearing with @mail/clear.
+        This is done automatically when you log out.
+
+  @mail/tag [<msg-list> | all>]
+  @mail/untag [<msg-list> | all>]
+        These commands tag or untag mail in the current folder.
+        Tagged mail can be later acted on en masse by using "tagged" as
+        the msg-list for other commands (which does *not* untag them
+        afterward). If no msg-list is specified, all messages in the
+        current folder are tagged/untagged. If "all" is given as the
+        msg-list, all mail in *all* folders is tagged/untagged.
+        (Ex: To clear all mail from Paul and Chani, @mail/tag *paul,
+        @mail/tag *chani, @mail/clear tagged, @mail/untag all).
+& mail-folders
+  The MUSH mail system allows each player 16 folders, numbered from
+  0 to 15. Mail can only be in 1 folder at a time. Folder 0 is
+  the "inbox" where new mail is received. Most @mail commands
+  operate on only the current folder.
+
+  @mail/folder
+        This commands lists all folders which contain mail, telling
+        how many messages are in each, and what the current folder is.
+
+  @mail/folder <folder#|foldername>
+        This command sets your current folder to <folder#>.
+
+  @mail/folder <folder#> = <foldername>
+        This command gives <folder#> a name. 
+
+  @mail/unfolder <folder#|foldername>
+        This command removes a folder's name
+
+  @mail/file <msg-list>=<folder#>
+        This command moves all messages in msg-list from the current
+        folder to a new folder, <folder#>.
+
+  See also: @mailfilter
+& @mailfilter
+& mailfilter
+  The @mailfilter attribute specifies automatic filing of incoming
+  @mail messages into folders. When an @mail message is received,
+  the contents of @mailfilter are evaluated, with the following
+  arguments passed:
+     %0     dbref of message sender
+     %1     message subject
+     %2     message body
+     %3     message status flags (a string containing U, F, and/or R,
+            for urgent, forwarded, and/or reply, respectively)
+
+  If @mailfilter evaluates to a folder name or number, the message
+  will be filed into that folder. If @mailfilter evaluates to a null
+  string, the message remains in the incoming folder.
+
+  Example: Filter urgent messages into folder 1
+  > @mailfilter me=if(strmatch(%3,*U*),1)
+
+  See also: mail-folders
+& mail-admin
+  
+  The @mail command can also take the following switches:
+  
+    @mail/stats [<player>]    --  Basic mail statistics.
+    @mail/dstats [<player>]   --  Also provides read/unread count.
+    @mail/fstats [<player>]   --  Does all that, plus gives space usage.
+  
+    @mail/debug <action>[=<player>]
+    @mail/nuke
+  
+  Only wizards may stats players other than themselves. The mail statistics
+  commands are computationally expensive and cost the same as @find.
+  
+  The /debug switch does sanity checking on the mail database, and may only
+  be used by a wizard. "@mail/debug sanity" just does the check; the command
+  "@mail/debug clear=<player name or dbref number>" wipes mail for an object.
+  "@mail/debug fix" attempts to repair problems noted in the sanity check.
+
+  The /nuke switch destroys the post office, erasing all @mail everywhere.  
+  It may only be used by God.
+  
+& @malias
+@malias [<alias>]
+
+The @malias command is used to create, view, and manipulate @mail
+aliases, or lists. An alias is a shorthand way of specifying a list
+of players for @mail. Aliases begin with the '+' (plus) prefix,
+and represent a list of dbrefs; aliases may not include other aliases.
+
+@malias with no arguments lists aliases available for your use,
+and is equivalent to @malias/list
+
+@malias with a single argument (the name of an alias) lists the
+members of that alias, if you're allowed to see them. Other forms of
+the same command are @malias/members <alias> or @malias/who <alias>
+
+See help @malias2 for more
+
+& @malias2
+@malias[/create] <alias>=<player list>
+@malias/desc <alias>=<Description>
+@malias/rename <alias>=<newalias>
+@malias/destroy <alias>
+
+The first form above creates a new alias for the given list of players.
+@malias/desc sets the alias's description, which is shown when aliases
+are listed.
+@malias/rename renames an alias.
+@malias/destroy destroys the alias completely.
+
+See help @malias3 for more.
+& @malias3
+@malias/set <alias>=<player list>
+@malias/add <alias>=<player list>
+@malias/remove <alias>=<player list>
+
+@malias/set resets the list of players on the alias to <player list>.
+@malias/add adds players to the alias. Note that the same player
+may be on an alias multiple times.
+@malias/remove removes players from the alias. If a player is on the
+alias more than once, a single remove will remove only one instance
+of that player.
+
+See help @malias4 for more.
+& @malias4
+@malias/use <alias>=<perm list>
+@malias/see <alias>=<perm list>
+
+@malias/use controls who may use an alias. Players who may use an
+alias will see it in their @malias list, and can @mail to the
+alias.
+@malias/see controls who may list the members of an alias.
+
+An empty permission list allows any player. The permission list
+may also be a space-separated list of one or more of "owner",
+"members" (of the alias), and "admin".
+
+By default, the owner and alias members may see and use the alias,
+but only the owner may list the members.  Note that admin may always
+list aliases and their members, regardless of these settings, but are
+treated like anyone else when trying to @mail with an alias.
+
+See help @malias5 for more.
+& @malias5
+@malias/all
+@malias/stat
+@malias/chown <alias>=<player>
+@malias/nuke
+
+@malias/all is an admin-only command that lists all aliases in the MUSH.
+@malias/stat is an admin-only command that displays statistics about the
+number of aliases and members of aliases in use.
+@malias/chown is a wizard-only command that changes the owner of an alias.
+@malias/nuke is a God-only command that destroys all aliases.
+& Mail functions
+  Mail functions work with @mail.
+
+  folderstats() mail()        maildstats()  mailfrom()    mailfstats()
+  mailstats()   mailstatus()  mailsubject() mailtime()
+  
+& MAIL()
+  mail()
+  mail(<player name>)
+  mail([<folder #>:]<mail message #>)
+  mail(<player>, [<folder #>:]<mail message #>)
+
+  Without arguments, mail() returns the number of messages in
+  all the player's mail folders. With a player name argument,
+  mail() returns the number of read, unread, and cleared messages
+  <player> has in all folders. Only Wizards can use this on other players.
+
+  When given numeric arguments, mail() returns the text of the
+  corresponding message in the current folder. The message number
+  may also be prefaced by the folder number and a colon, to indicate
+  a message in a different folder.
+
+  Example: 
+  > think mail(3:2)
+  (text of the second message in the player's third folder)
+  
+& MAILFROM()
+& MAILTIME()
+& MAILSTATUS()
+& MAILSUBJECT()
+  mailfrom([<player>,] [<folder #>:]<mail message #>)
+  mailtime([<player>,] [<folder #>:]<mail message #>)
+  mailstatus([<player>,] [<folder #>:]<mail message #>)
+  mailsubject([<player>,] [<folder #>:]<mail message #>)
+  mailfrom() returns the dbref number of the sender of a mail message.
+  mailtime() is similar, but returns the time the mail was sent.
+  mailsubject() is similar, but returns the subject of the message.
+  mailstatus() returns the mail's status characters (as per
+  @mail/list)
+
+& MAILSTATS()
+& MAILDSTATS()
+& MAILFSTATS()
+  mailstats([<player>])
+  maildstats([<player>])
+  mailfstats([<player>])
+
+  mail*stats() functions return data like @mail/*stats does. You
+  either must use this on yourself, or you must have the 'MailAdmin'
+  Power with the target player in your control scope. The information 
+  will be joined together as a space separated list of numbers.
+
+  Example:
+  > think mailstats(One)
+  <# sent> <# received>
+  > think mailfstats(One)
+  <# sent> <# sent unread> <# sent cleared> <# sent bytes> <# received>
+  <# received unread> <# received cleared> <# received bytes>
+& mail-forward
+& @mailforwardlist
+  @mailforwardlist me = <space-separated list of dbrefs or objids>
+  @lock/mailforward me= = <lock>
+
+  By setting a @mailforwardlist attribute, a player can direct that
+  @mail they receive should be delivered to the specified list
+  of dbrefs of other players. The list may include the player's own
+  dbref, in which case the player will receive a copy of the message,
+  or omit it, in which case the message will be delivered to those listed
+  but the player will not receive a copy.
+
+  To deliver messages to other players this way, you must control them
+  (i.e. you're delivering to yourself or you're  a wizard) or
+  pass their @lock/mailforward. An empty @lock/mailforward disallows
+  forwarding to you, and is the default.
+
diff --git a/game/txt/hlp/cobra_pueb.hlp b/game/txt/hlp/cobra_pueb.hlp
new file mode 100644 (file)
index 0000000..2375c66
--- /dev/null
@@ -0,0 +1,131 @@
+& PUEBLO
+& PUEBLO()
+Pueblo is a client made by Chaco (a now defunct company).
+It attempts to mix HTML with MUSH, and does a decent job at it.
+There are other clients (notably MUSHclient) that also offer Pueblo 
+features.  If compiled into the MUSH, PennMUSH offers support for
+the enhanced features of Pueblo.
+
+PennMUSH will automatically detect a Pueblo client (rather, the 
+client will announce itself and PennMUSH will detect that), and
+set up that connection for Pueblo use. 
+
+The PUEBLO() function returns 1 for players connected with Pueblo, 
+0 for players with other clients, and #-1 NOT CONNECTED for
+players who aren't connected. It uses the most recently active
+connection if a player is multiply logged in.
+
+For more information, see 'Pueblo features', 'HTML' and
+'HTML Functions'. 
+
+& PUEBLO FEATURES
+PennMUSH makes the following enhancements visible to Pueblo users, if
+Pueblo support has been enabled in the server (check @config):
+
+- Object/Room names are highlighted
+- Support for VRML graphics
+- Unordered list for contents and transparent exits
+- Contents and links have links (Click an exit to walk through it)
+- Object lists (like the ones found in 'examine') have links
+- Conversion of ANSI sequences to <FONT> tags.
+
+See 'HTML', 'HTML Functions' and '@VRML_URL' for more help.
+
+& @VRML_URL
+& VRML
+@VRML_URL Object=<URL>
+
+This provides an object (usually a room) with a VRML world. When a 
+Pueblo-user enters this object, the VRML World listed in @VRML_URL
+will be loaded.
+
+Example:
+@VRML_URL here=http://www.pennmush.org/pennmush.vrml
+
+To learn about the VRML Format, have a look at the Pueblo Help, which
+mentions several good sites for learning.
+
+See also 'HTML'.
+
+& HTML
+Hyper Text Markup Language (http://www.w3.org)
+
+The only HTML implementation supported by the MUSH is the one
+supported by Pueblo (See 'Pueblo'). To utilize HTML, use
+one of the MUSH HTML Functions. (See 'HTML Functions').
+
+HTML tags are stripped when sent to non-HTML capable players.
+
+See 'HTML Functions'.
+& HTML FUNCTIONS
+HTML Functions are used to output HTML tags to HTML capable
+users. These tags will be stripped by the system for anything
+non-HTML related. These functions will not be available if
+the server is compiled without Pueblo support (check @config).
+
+html()
+tag()
+endtag()
+tagwrap()
+
+Examples:
+  [html(A HREF="http://www.pennmush.org")]PennMUSH[html(/A)]
+  [tag(A,HREF="http://www.pennmush.org")]PennMUSH[endtag(A)]
+  [tagwrap(A,HREF="http://www.pennmush.org",PennMUSH)]
+Each of these produces the HTML output:
+  <A HREF="http://www.pennmush.org">PennMUSH</A>
+
+Mortals are restricted in the tags they may use. Most standard HTML
+tags are ok; protocol-specific tags like SEND and XCH_CMD can only be
+sent by Wizards. In addition, the html() function is Wizard-only.
+& HTML()
+Function: html(<string>)
+
+This wizard-only function will output string as a HTML Tag.
+
+Example:
+  think [html(B)]
+
+Will output (in HTML):
+  <B>
+
+Non-wizards should see the tag(), endtag(), and tagwrap() functions.
+& TAG()
+Function: tag(<name>,[param1[,param2...]])
+
+This will output the tag 'name' with values from it's parameters.
+
+Example:
+ [tag(IMG,SRC=http://www.pennmush.org/someimage.jpg,ALIGN=LEFT,WIDTH=300)]
+
+Will output (in HTML):
+ <IMG SRC=http://www.pennmush.org/someimage.jpg ALIGN=LEFT WIDTH=300>
+
+& ENDTAG()
+Function: endtag(<name>)
+
+This will output an end tag 'name'.
+
+Example:
+ [endtag(IMG)]
+
+Will output (in HTML):
+ </IMG>
+
+& TAGWRAP()
+Function: tagwrap(<tag>[,<parameters>],<string>)
+
+This will output 'tag' with parameters, followed by 'string', and then
+a closing tag for 'tag'. 
+
+Example:
+ [tagwrap(A,HREF=http://lists.pennmush.org,PennMUSH Lists)]
+Will output (in HTML):
+ <A HREF=http://lists.pennmush.org>PennMUSH Lists</A>
+
+A particularly important use of this function is tagwrap(PRE,<string>).
+Because Pueblo works like an html browser, spaces and tabs are compressed
+to a single space. If you have code (a +who function, for example) that
+relies on exact spacing, surround its output with a tagwrap(PRE,...)
+so that Pueblo will render it as "preformatted" text.
+
diff --git a/game/txt/hlp/cobra_vts.hlp b/game/txt/hlp/cobra_vts.hlp
new file mode 100644 (file)
index 0000000..241424d
--- /dev/null
@@ -0,0 +1,145 @@
+
+& VTS
+  The Virtual Thing System (VTS) is designed to handle things which are 
+  required in any great amount (basically, any item which would otherwise 
+  be @cloned multiple times). All Virtual Things (vings) are instances of 
+  a Master Virtual Thing (VTM), and may be countable, or non-countable; the
+  former will stack in inventory lists (for instance, "You have twelve vings"
+  rather than "ving" repeated twelve times), while the latter behave as per
+  normal objects in this respect.
+
+  The following flags can be set on VTMs:
+  VTM       -  Makes the Thing a VTM.
+  VTCOUNT   -  Makes vings using this VTM countable
+
+  These can be (re)set via the @vts command.
+
+See also:  @vts, VTS functions, @vtplural, VTREF
+
+& VTREF
+  Any Ving in a given location can be referred to by its VTREF, which is 
+  returned by the token %&.
+
+See also: VTS
+
+& VTM
+  This flag is used to denote a Thing as being a VTS Master Thing (VTM), and 
+  can be set with the @vts command.
+
+See also: VTS, @vts
+
+& VTCOUNT
+  This flag is set on a VTM in order to make all the Vings which are connected 
+  to that VTM 'countable'. Countable Vings will stack in inventory lists, 
+  giving 'You have twelve vings' instead of 'ving' being listed twelve times.
+  It can be set via the @vts command.
+
+See also: VTS, @vts
+
+& VTS functions
+  The following functions exist within the VTS:
+
+  vtattr()         vtcount()          vtcreate()
+  vtdestroy()      vtlcon()           vtloc()
+  vtlocate()       vtmaster()         vtname()
+  vttel()          vtref()
+
+& VTATTR()
+  vtattr(<ving>, <attrib>[, <value>])
+
+  This can only be used on non-countable vings.
+
+  Without <value> specified, this returns the value of the local 
+  attribute <attrib> on <ving>.
+  When <value> is given, <attrib> is set to <value>.  
+
+& VTLOC()
+  vtloc(<ving>)
+
+  This function returns the dbref of <ving>'s location.
+
+& VTCOUNT()
+  vtcount(<ving>)
+
+  This returns the count of <ving>, in other words, the number of Vings which
+  are represented by that object.
+  For non-countable vings it returns 0.
+
+& VTCREATE()
+  vtcreate(<master>, <location>[, <number>])
+
+  This creates a new ving, using <master> as its VTM, at <location>, and if
+  the optional third argument is passed, with count <number>. The count 
+  denotes how many objects are represented by this Ving, for instance, a
+  coin Ving with a count of 10 would represent ten coins.
+
+See also:  vtdestroy()
+
+& VTDESTROY
+  vtdestroy(<vtref>)
+
+  This destroys the Ving with the corresponding vtref.
+
+See also:  vtcreate(), vtref()
+
+& VTLCON()
+  vtlcon(<object>)
+
+  This returns a list of the vtrefs of all Vings within <object>.
+
+& VTLOCATE()
+  vtlocate(<looker>, <name>[, <parameters>])
+
+  This function will attempt to find the Ving <name> relative to <looker>, 
+  modifying the search based upon the <parameters> which are specified (if 
+  any).
+
+  Valid parameters are:
+    r   -   Match by vtref
+    i   -   Match those vings in inventory
+    l   -   Match all vings at the same location
+    o   -   Match other vings in above location
+
+  This function returns the vtref of the matched Ving.
+
+& VTMASTER()
+  vtmaster(<ving>)
+
+  This returns the VTM of <ving>.
+
+& VTNAME()
+  vtname(<ving>)
+
+  This returns the name of <ving>, including the count value, if <ving> is 
+  countable.
+
+See also:  @vtplural
+& VTTEL()
+  vttel(<ving>, <destination>[, <number>])
+
+  This teleports <number> of <ving> to <destination>. By default, <number> is
+  equal to 1, and the function will return the new vtref.
+
+& VTREF()
+  vtref(<master>, <location>[, <which one>])
+
+  This returns the vtref of Vings at <location> with VTM <master>, and 
+  optionally <which one>, which is a number specifying which one you want,
+  in case you don't want the first one.
+
+& @VTPLURAL
+  @vtplural <master> = <plural>
+
+  Sets the VTPLURAL attribute of a VTM.
+
+See also: VTS
+
+& @VTS
+  @vts/set/wipe <thing> = <normal|count|none>
+  @vts/wipe <thing>
+
+  Only wizards may use this command. It sets/unsets the VTS and/or 
+  VTCOUNT flag and wipes out any instances of the given VTM that may exist.
+
+See also:  VTS
diff --git a/game/txt/hlp/cobramush.hlp b/game/txt/hlp/cobramush.hlp
new file mode 100644 (file)
index 0000000..6dce1e3
--- /dev/null
@@ -0,0 +1,443 @@
+& cobramush
+& kickass
+CobraMUSH main topics
+---------------------------------------------
+AGENDA         CREDITS
+DIVISION
+
+FYI: CobraMUSH changes is under the 'changes' 
+command NOT 'help changes'.
+
+& pow_inherit
+pow_inherit(t) attribute flag
+Use with caution, setting this on a object used as
+an object parent will allow children to execute this
+attribute with powers of the parent object.
+
+Currently only works with the following:
+       $-commands
+       ^-Listen Patterns
+       @CONFORMAT
+       @EXITFORMAT
+POW_INHERIT attribute flag code will execute as if
+it is running off the exact object that holds the attribute
+the object that actually called it will be referenced via %@
+%! -> The Object that actually has the code.
+%@ -> Object that is parented to the object that has the code.
+
+This is a dangerous way of coding, know what your doing before
+using this method.
+
+& wait()
+wait(<time>,<functions>)
+wait(<object>,<functions>)
+wait(<object>/<time>,<functions>)
+
+  1st argument is evaluated.
+  2nd argument is evaluated upon execution.
+
+  The basic form of this function puts the function list into the wait
+  queue to execute in <time> seconds.
+
+  The second form puts the the functions into the semaphore wait queue on
+  <object>. The executor will execute <functions> when <object> is notified.
+
+  The third form combines the first two: the enactor will execute
+  <functions> when <object> is notified or when <time> passes,
+  whichever happens first.
+
+  Any form you do returns a 'QID' to be used in a signal().
+
+  More forms that support semaphores on arbitrary attributes are described in
+  help @wait2
+
+  See also the help for: SEMAPHORES, @drain, @notify, signal()
+
+& trigger()
+Trigger(<object>/<attribute>,<val1>,<val2>,...,<val10>)
+
+  Works just like @trigger that fills in those fields.
+
+  See also the help for: @trigger
+
+& signal()
+  signal(<queue id>, <signal>[,<wait arg with time signal>])
+
+  Signals a specific QID directly in the MUSH queue.
+  All signals require you control the executor of the QID, except
+  Kill signal also takes halt power of the executor.
+
+  Defined Queue Signals:
+    KILL       - Kills a Queue ID out of the queue.
+    FREEZE     - Freezes a Countdown QID in the MUSH Queue.
+    CONTINUE   - Continue contdown of a QID in the MUSH Queue.
+    TIME       - Re-Adjust time of a QID in the MUSH Queue.
+    QUERY_T    - Query The Amount of time left on a qid wait process.
+
+  Returns 1 if signal goes through, otherwise returns #-1 Error Message
+
+& @AUNIDLE
+Actions to be performed when a player un-idles after a given period of time.
+First the player's @AUNIDLE will be executed & if there is a @AUNIDLE
+on their division object that will be executed as well.
+
+& AGENDA
+This is the agenda for the CobraMUSH developers
+
+Items that might be of interest:
+
+* Write new chat system to be more modifiable & customizable
+  as well as work more inline with the DivSystem.
+* Move function restricting to a lock based simpler to commands
+* Rewrite @wall
+* Sharing of BBS/Mail/Chat systems with a web interface.
+* Implement MUD eXtension Protocol
+           
+& @LASTACTIVITY
+  Game attribute, upon disconnect writes session activity stats for
+  player.
+
+  Format: 
+   <OnTime> <Total IdleTime for Session>  <Times_Unidled> <Commands Enacted for Session>
+
+  See Also: Idle_Total(), Idle_Average(), Unidle_Times()
+
+& Idle_Average()
+ Syntax: Idle_Average(<Player>)
+ Returns average idle time for players current session.
+
+ See Also: @LASTACTIVITY
+
+& Idle_Total()
+ Syntax: Idle_Total(<Player>)
+
+ Returns total time spent idle for current session.
+
+ See Also: Idle_Average(), Idle_Time()
+
+& UnIdle_Times()
+
+ Syntax: Unidle_Times(<player>)
+
+ Returns the Unidle_Times counter for player
+
+
+& @su
+  Syntax: @su <player name>
+
+     This command will place you into player name given under
+     the following conditions:
+       1) You have '@SU' power over the player name given
+                        or
+       2) You will be placed into a prompt and required to give
+          a password if you do not have @SU power over player.
+
+     To exit an '@su session' simply type LOGOUT and you will
+     be placed back into the previous character you were logged
+     into.
+
+    Example:
+      > @su Jimmy
+        -> Password:
+      > JimmyBuffetPass
+        -> ----Login Success Screen---
+
+     All @SU attempts successes & failed are logged.
+
+  See Also: @SD
+
+& @sd
+  Syntax: @sd <division>
+          @sd/logout
+
+  This command allows the enacting player to temporarily switch
+  into divisions under the following conditions:
+  1) You control the selected division your trying to switch into.
+  2) The division your attempting to switch into is in your secondary
+     divisions list.
+                            or
+  3) You will be placed into a prompt and rquired to give a special
+     password to enter the division.
+
+  To exit an '@sd session' simply type @sd/logout, and you will backtrace
+  through divisions you switched into.
+
+    Example:
+      > @sd #3
+        -> Password:
+      > JimmyBuffetPass
+        -> You have switched into Division: Master Division(#3D)
+
+  See Also: @sdin, @sdout, @asdout, @asdin, @SU
+
+& @program
+ Syntaxes:
+   @program[/lock] <player> = <program_object>/<program_attribute>
+   @programm[/lock] <player> = <program_attribute>
+   @program/quit <player>
+
+   This command gives the ability to place users into MUSHcoded 
+   '@programs'.  When a player is placed in a MUSH '@program', all
+   commands they feed in directly bypass commands, builtin-in or 
+   softcoded, and feed user-input to MUSHCode in a given attribute
+   specified.
+
+   Using this command requires the enacting object to have the 'PROGRAM'
+   power over the target player.  In order to use the lock switch, which
+   will prevent the user from escaping commands out of the program, also
+   requires the additional 'PROGLOCK' power over the target player.
+
+   (Continued in @program2)
+
+& @program2
+   Programs may be initiated directly on a player as such.
+  
+   Example:
+     &MyProgramAttribute MyProgramObject=@switch 
+                                             %0=Y,{@pe %#=Yes?;
+                                                   @prog/quit %#
+                                                  }
+                                                 ,{@pe %#=No?;
+                                                   @prog/quit %#
+                                                  }
+     @program *BillyBob = MyProgramObject/MyProgramAttribute
+
+     BillyBob Types:
+     >Y
+     Yes?
+   
+   (Continued in @program3)
+
+& @program3
+  Programs are normally done on the object holding the program itself
+  however, initiated through a user-defined command.  Using the same
+  MUSHCode Attribute holding the program take the following example.
+
+  Example:
+    @va MyProgramObject=$start-program:@program %#=MyProgramAttribute
+    >start-program
+    
+  The user will then see a lone '>' on the next line, and if they use 
+  a client that handles programs correctly as tinyfugue it will appear
+  in their input window.
+
+  Customized prompts may given with a program as well using the 
+  '@prompt' command explained in its own helpfile.
+
+  When in a program, that is not started using /lock, a user may escape
+  commands outside of the program using the '|' character.
+
+  Example:
+    > |WHO
+    -> ** User see's the 'WHO' **
+
+   See Also: quitprog(), program(), prompt(), @prompt
+       
+& program()
+ Syntax: 
+   program(<player>,<program_object>,<program_attribute>[,<proglock toggle>])
+
+This function will put a player in a @program as so long as the object
+that is enacting command has the 'PROGRAM' power & can use it over the
+target player.
+
+Locking a Program on a player requires that 'PROGLOCK' power over the 'target'
+player.  Locking a program on a player will in change lock a player into not
+being able to escape the program on their own will & require the program to
+or another object capable of releasing the player from the program.
+
+See Also: quitprog(), prompt() @program
+
+& prompt()
+ Syntax: prompt(<player>[,<prompt>])
+
+ A special version of pemit() that can only be used on players in a 
+ @program.  When used on players using clients that support the 
+ 'GOAHEAD' command, the <prompt> text will appear in their text 
+ input area.
+
+ Without the optional <prompt> argument a standard  hilited ansi '>'
+ symbol will appear to the user as the prompt.
+ See Also: @prompt, @program, pemit(), program(), quitprog()
+
+& @prompt
+ Syntaxes: @prompt <player>=<prompt>
+           @prompt <player>
+
+  A special version of @pemit that can only be used on players in a 
+  @program.  When used on players using clients that support the 
+  'GOAHEAD' command, the <prompt> text will appear in their text 
+  input area. 
+
+  When using the second verison of the command without the <prompt> text
+  area, a standard hilited ansi '>' symbol will be prompted to the user.
+
+  Example:
+    @program %#=MyProgram
+    @prompt %#=MyPrompt>
+
+  The user than see's 'MyPrompt>' on their screen, or in their input
+  window for the @program they are placed in.
+
+  See Also: @program, @pemit, program(), quitprog(),prompt()
+      
+& quitprog()
+Syntax: quitprog(<player>)
+
+  Will attempt to take a player out of a @program.
+
+  See Also: program(), @program
+
+& changes
+Type 'changes' for CobraMUSH Changes
+Type 'help pennmush changes' for pennmush version changes
+
+& guests
+& multi-guests
+The multi-guest system, is an a system that allows the 
+automatic creation of guest characters without manual
+creation by the admin.  The max number of multi guests
+is set in the mush.cnf file under max_guests.  If set to
+0, upto 100 guests will be allowed.  Guest characters are
+defined as being level 1 players.  As so, all level 1 
+players will be set for destruction at next purge cycle.
+
+
+& ICFUNCS
+  TODO
+
+& break()
+Syntax: break(<number>)
+Break out of an iter.
+
+For nested iters, increment the number break out more.
+Default agument is 0.
+
+Example:
+iter(1 2 3,##[if(eq(##,2),break())])
+       -> 1 2
+iter(1 2 3,iter(5 6 7,if(eq(itext(0),6),break(0))##)
+       -> 1  2   3
+iter(1 2 3,iter(5 6 7,if(eq(itext(0),6),break(1))##)
+       -> 1 
+
+
+& kickass credits
+& CREDITS
+  Maintainer: Nveid[RLB]
+  Developers: Ari[AJ], Jamie [JLW]
+  Derived From PennMUSH v1.7.7p26
+
+  For original PennMUSH credits type help pennmush credits.
+
+  Big Thanks goto to the PennMUSH  development team for giving
+  us a Stable Server to work off of.
+
+See also: help code, help license, help pennmush credits
+
+& @ZCLONE
+ Syntax: @zclone <zone>
+
+ Clones all rooms within a zone, and all exits inside those rooms.
+ The zone that is being cloned must have the privileged flag
+ 'ZCLONE_OK' set to allow it to be cloned.
+
+ The DBREF_LIST attribute on the Zone Master Object that is being
+ cloned may contain a list of dbrefs that correspond exits and
+ rooms within a zone.  When the new ZMO is created all dbrefs in
+ that list will be translated to the newly created exit/rooms
+ dbrefs.
+
+ The AZCLONE attribute on the Zone Master Object, after being
+ cloned on the newly created ZMO will be triggered to perform
+ any initialization procedures that maybe needed after the
+ @zcloning process.
+
+ See Also: ZCLONE_OK
+
+& ZCLONE_OK
+   Flag: ZCLONE_OK       (Thing)
+
+   This flag will allow any builder on the game to copy a
+   specified zone master object and all its rooms and exits
+   within that zone. Only privilege powered players may
+   set or unset this flag.
+
+& @CRON
+  Syntaxes:
+            @cron 
+           @cron/add[/<type>] <cronjob>=<cronspec> <object>/<attribute>
+           @cron/set <cronjob>=[!]<flag>
+           @cron/list [<cronjob>]
+           @cron/delete <cronjob>
+
+  Synposis: Manages the ingame MUSHCron system.
+
+  In order to use this command you must possess the 'Cron' Power.
+
+  This command alone with no switches will display all the @cron specs
+  you control.
+
+  @cron/add creates or alters the job named <cronjob>.  The <type> given
+  specified whether the attribute holds functions or commands.  If 
+  <object> is "global" then the cronjob will execute the job on all 
+  objects the 'executor' that adds the cronjob controls.   
+
+  @cron/set sets or unsets a flag on <cronjob>. Valid flags are:
+    COMMAND
+    FUNCTION  (not enabled as of this writing)
+    HALT
+
+  A cronjob may only be of the 'COMMAND' type as now for executing, 
+  functions are disabled.  If the Halt status is set on a @cron job
+  then the job will not be processed.
+
+  @cron/list will show information about the cronjobs. If you specify
+  <cronjob> you will see more information about the cronspec for that job.
+
+  @cron/delete will remove <cronjob> from the crontab.
+
+  See Also: @cron2
+
+& @CRON2
+  2 Default global cronjobs are included, being an 'Hourly' and
+  a 'Daily'
+
+  Daily spec triggering the 'Daily' attribute at the beginning of
+  each day and the Hourly spec triggering the 'Hourly' Attribute
+  at the beginning of each Hour.
+
+
+  See Also: @cron3
+
+& @CRON3
+  A cronspec describes at which time a cronjob will be executed.
+  The fields of a valid cronspec are as follows:
+
+    <minutes> <hours> <days of month> <months> <days of week>
+      0-59     0-23        0-30         0-11         0-6
+                                      Jan-Dec      Sun-Mon
+
+  Each field may contain a range of numbers, e.g. 2-5, or a * to indicate
+  0 through the maximum for that field.
+
+  A skip value can be used to select values from a range. It is specified
+  as /<skip>, e.g. 0-23/2 would execute at 0, 2, 4, etc. until 22.
+
+  You can specify a list of values in each field, seperated with a comma.
+  e.g. 0,2,4,6,10-14,20-30/5.
+
+  For the months and days of the week you may specify the english names
+  or 3 letter abbreviations. January and Sunday are 0.
+
+  * * * * *                     Every minute of every day
+  30 * * * *                    Run every half hour.
+  */30 * * * *                  Run every 30 minutes.
+  15 6,18 * * *                 6:15 am and pm
+  20 4 20 April *               Do something once a year
+  */2 * * * *                   Do it every 2 minutes
+
+  Examples:
+   @cron/add/command global_trigger_monday=0 0 * * Monday global/TRG_MONDAY
diff --git a/game/txt/hlp/cobratop.hlp b/game/txt/hlp/cobratop.hlp
new file mode 100644 (file)
index 0000000..5a9df99
--- /dev/null
@@ -0,0 +1,1936 @@
+& help
+===================================================================
+                    CobraMUSH Help Facility
+===================================================================
+ If your new to MUSHing, and for better understanding of the help
+ facility type 'help newbie'
+
+ Administrative Information pertaining cobramush may be found under
+ the 'ahelp' facility.
+
+ These two tables index the primary MUSH help files. 
+ ______________________ ___________________________________________
+   Help Topic            Description
+ ---------------------- -------------------------------------------
+  Informative Topics    Topics of General Knowledge to the user
+  MUSHCode Topics       Topics relating to MUSHCode in general
+  attribute list        List of MUSH Attributes
+  commands              List of MUSH Commands
+  flag list             List of MUSH Flags
+  function list         Categorized MUSH Function list
+    
+
+          Type 'help entries' for index of all help files.
+  
+& newbie
+  
+  If you are new to MUSHing, the help files may seem confusing. Most of
+  them are written in a specific style, however, and once you understand
+  it the files are extremely helpful.
+
+  The first line of a help file on a command or function will normally be
+  the syntax of the command. "Syntax" means the way the command needs to
+  be typed in. In the help files, when the syntax of a command is described,
+  square brackets [] mean that that part of the command is optional and
+  doesn't have to be typed in. Also, pointy brackets <> mean that that part
+  of the command needs to be replaced with a specific piece of information.
+  
+  You should not type the [] or <> brackets when entering a command.
+  
+(continued in help newbie2 -- type 'help newbie2' without the single quotes)
+  
+& newbie2
+
+  For example, the syntax of the help command is:
+  
+  help [<topic>]
+  
+  What this means is that to get help, you would type first the word "help" and
+  then you could optionally type the name of a more specific topic in order
+  to get help on that topic. Just typing "help" will work too (that's why the
+  <topic> part is optional).
+  
+  Some common commands that you should look at help for are:
+  
+    look   say    go    page    pose    take     give    home
+  
+  Just type help <command> for help. Example: help page
+  
+(continued in help newbie3)
+& newbie3
+
+  There is help available on every standard MUSH command. If you see a command
+  or someone mentions one to you that you want to know more about, try just
+  typing: help <command name> -- that will most likely bring up the help
+  file on it.
+  
+  Please note that just because there is help available on a command does
+  not necessarily mean that the command can be used on this MUSH. The
+  siteadmin of the MUSH can choose to turn off some commands. If there's
+  something that you would like available, and it isn't, please ask a wizard
+  why not.
+  
+  It is also highly recommended that any new player read the MUSH manual,
+  written by Amberyl. It is available by anonymous FTP from:
+     ftp.pennmush.org
+  in the directory:
+     /pub/PennMUSH/Manuals
+
+
+& informative topics
+These are topics that are of general knowledge which informative
+qualities may benefit many users, new and advanced alike.
+
+AGENDA                    ANCESTORS                 ATTRIB-OWNERSHIP
+CHAT                      CLIENTS                   CONTROL
+COPYRIGHT                 COSTS                     CREDITS
+DIVISION SYSTEM           DROP-TO                   FLAGS
+FUNCTIONS                 GENDER                    GLOBALS
+HERE                      HOMES                     LINKING
+MASTER ROOM               ME                        MONEY
+PREDESIGNATED LEVELS      QUEUE                     SETTING-ATTRIBUTES
+SPOOFING                  SWITCHES                  WARNINGS
+ZONE MASTER ROOMS         ZONE MASTERS              ZONES
+
+& topicszq
+Help is available on the following topics:
+
+  ACTION LISTS             ANCESTORS                ANONYMOUS ATTRIBUTES
+  ATTRIB-OWNERSHIP         ATTRIBUTES               BEING KILLED
+  BOOLEAN VALUES           CHAT                     CLIENTS
+  CONTROL                  COPYRIGHT                COSTS
+  CREDITS                  DBREFS                   DIVISION 
+  DROP-TO                  ENACTOR                  EVALUATION               
+  EXECUTOR                 EXITS                    FAILURE 
+  FLAGS                    FUNCTIONS                GENDER   
+  GLOBALS                  HERE                     HOMES
+  INTERIORS                LINKING                  LISTENING
+  LISTS                    LOOPING                  MASTER ROOM              
+
+  (continued in help topics2)
+& topics2
+  MATCHING                 ME                       MONEY
+  MUSHCODE                 NON-STANDARD ATTRIBUTES  PARENTS 
+  POWERS                   PUPPETS                  QUEUE  
+  REGEXPS                  REGISTERS                SEMAPHORES  
+  SETTING-ATTRIBUTES       SPOOFING                 STACK 
+  STRINGS                  SUBSTITUTIONS            SUCCESS   
+  SWITCHES                 TYPES OF OBJECTS         USER-DEFINED COMMANDS    
+  VERBS                    WARNINGS                 WILDCARDS 
+  ZONE MASTER ROOMS        ZONE MASTERS             ZONES  
+  
+Type "help <topic name>" for help.
+& ACTION LISTS
+  Action lists are simply lists of actions that are all executed at once.
+  You can have an action list in a user-defined command, in one of the
+  a-attributes, or in many other commands.
+
+  Actions in an action list are separated by semicolons. Each action is 
+  simply a separate MUSH command. If part of the action (such as the text
+  in an @emit, for example) contains a semi-colon or comma, you may need
+  to enclose that part in curly braces {}. You can also nest action lists
+  inside each other by enclosing each action list in braces {}.
+
+  Substitution will be performed on the contents of action lists before
+  they are executed.
+
+(continued in help action2)
+& ACTION2
+  Example 1:
+    > @asuccess Gift = @pemit %#={The box pops open; surprise!} ; 
+        @name me=New Toy ; @desc me={A shiny new toy, just for %N!}
+    > take gift
+    The box pops open; surprise!
+    > look new toy
+    New Toy
+    A shiny new toy, just for Cyclonus!
+            
+  Example 2:
+    > &TEST me=$test:@emit {Testing; testing; one, two.} ; 
+        @dolist 1 2 3={think {Test ##, success.} }
+    > test
+    Testing; testing; one, two.
+    Test 1, success.
+    Test 2, success.
+    Test 3, success.
+
+  See also: ATTRIBUTES, SUBSTITUTION, @asuccess, @dolist
+& ANCESTORS
+  ANCESTORS
+
+  Objects can inherit attributes from other objects through the
+  use of parents. An object's parent, its parent's parent, its 
+  parent's parent's parent, etc. constitute the object's "parent chain"
+  and lookups work the way up the chain until an inheritance occurs.
+
+  Ancestors are "virtual parents" that are assumed to be last on every
+  parent chain. There is one ancestor for each object type (room, exit,
+  thing, player), and @config lists the dbref of each ancestor object
+  (@config ancestor_room, etc.) Under normal circumstances, if an attribute
+  can't be retrieve from an object or any of its explicit parents,
+  the attribute will be looked on on the appropriate ancestor.
+  The ORPHAN flag may be set on an object to cause lookups on that 
+  object to ignore ancestors (like the pre-ancestor behavior).
+
+  Ancestors may themselves have parent chains, but these are (obviously)
+  not virtually terminated by ancestors.
+
+  Note that the choice of which ancestor to look up is based on the 
+  type of the *child* object, as is the check of the ORPHAN flag.
+
+  See also: PARENTS, ORPHAN
+& ANONYMOUS ATTRIBUTES
+& LAMBDA
+& #LAMBDA
+  In many cases where a function expects a object/attribute pair
+  that refers to an attribute to evaluate, you can use the form
+
+  #lambda/<code>
+
+  instead, and the code will be treated as an attribute's body.
+  The code will normally be parsed twice, so special characters 
+  should be escaped where needed.
+
+  These anonymous attributes should be used for short and simple
+  pieces of code. Anything long or complicated should go in an
+  actual attribute, for readability and maintainability.
+
+  See HELP ANONYMOUS2 for examples.
+& ANONYMOUS2
+  A typical usage of anonymous attributes would be to convert
+  a list of dbrefs to names, as so:
+
+  >say map(#lambda/name(\%0), #3 #12 #23)
+  You say, "Joe Robert Sally"
+
+  Because the code is parsed twice, you can actually build parts of
+  it in place, which is very convenient. Consider this implementation
+  of a lattrval function, which is like lattr() but it only returns
+  non-empty attributes:
+
+  &lattrval me=
+   filter(#lambda/hasattrval([secure(before(%0, /))], \%0), lattr(%0))
+  
+  The first time '#lambda/hasattrval([secure(before(%0, /))], \%0)' is
+  parsed in a call like 'u(lattrval, #1234)', it is turned into
+  '#lambda/hasattrval(#1234, %0)', thus avoiding the need for a setq() or
+  the like to store the top-level %0 for use in a real attribute called by
+  filter(). However, this can lead to problems with evaluating un-trusted
+  code. Use secure() or escape() where neccessary.
+
+  See HELP ANONYMOUS3 for another example.
+& ANONYMOUS3
+  
+  You can also use lit() to avoid having the code evaluated twice, if
+  needed. For example, this code, which returns all unlinked exits in
+  a room:
+
+  &lunlinked me=filter(lit(#lambda/strmatch(loc(%0), #-1)), lexits(%0))
+
+  This approach is useful both for security in making it harder to evaluate
+  a string that shouldn't be, and for making the code look nicer by not
+  having to escape percent signs, brackets, and other special
+  characters. However, it also makes it harder to build the code string on
+  the fly. Use what's most appropriate.
+     
+  See HELP ANONYMOUS4 for a list of functions that support anonymous
+  attributes.
+& ANONYMOUS4
+  The following functions support anonymous attributes:
+  
+  filter()    filterbool()   fold()      foreach()   map()
+  mix()       munge()        sortby()    step() 
+& ATTRIB-OWNERSHIP
+  ATTRIBUTE OWNERSHIP
+  
+  The latest person to set an attribute on an object is the owner
+  of that attribute. If you lock an attribute, using the @atrlock command,
+  only the person who owns the attribute will be able to alter the
+  attribute. This allows you to create standard commands on objects and
+  then @chown them to others without letting them alter them. 
+
+  Attribute ownership is NOT changed when the object itself is @chown'ed.
+  To change attribute ownership, you must use the @atrchown command.
+
+  You must control an object in order to set attributes on it.
+
+  See also: @atrlock, @atrchown, ATTRIBUTES
+& ATTRIBUTES
+& ATTRIBUTES LIST
+& ATTRIBUTE LIST
+  Attributes with (*) after them are special, cannot be set by players,
+  and may only be visible to wizards or admin. For those attributes, there
+  is no @-command, so you can just type 'help <attribute name>' for help.
+  For all other attributes, type 'help @<attribute name>' for help.
+
+Standard Attributes: (see @list/attribs for the complete list)
+  AAHEAR        ACLONE        ACONNECT      ADEATH        ADESCRIBE
+  ADISCONNECT   ADROP         AEFAIL        AENTER        AFAILURE
+  AHEAR         ALEAVE        ALFAIL        AMHEAR        AMOVE
+  APAYMENT      ASUCCESS      AUNIDLE       AWAY          CHARGES 
+  COST          DEATH         DESCRIBE      DROP          EALIAS 
+  EFAIL         ENTER         FAILURE       FORWARDLIST   HAVEN   
+  IDESCRIBE     IDLE          LALIAS        LAST (*)      LASTACTIVITY
+  LASTIP (*)    LASTLOGOUT(*) LASTSITE (*)  LEAVE         LFAIL  
+  LISTEN        MOVE          ODEATH        ODESCRIBE     ODROP    
+  OEFAIL        OENTER        OFAILURE      OLEAVE        OLFAIL  
+  OMOVE         OPAYMENT      OSUCCESS      OXENTER       OXLEAVE 
+  OXMOVE        PAYMENT       QUEUE (*)     RQUOTA (*)    RUNOUT    
+  SEX           STARTUP       SUCCESS       TFPREFIX
+
+(continued in help attributes2)
+& ATTRIBUTES2
+  An attribute is part of the code on an object that makes it unique. An
+  attribute can contain any sort of text -- from a single word, to a long
+  paragraph, to a piece of MUSHcode. Some attributes are standard in 
+  PennMUSH. That means that their effects are pre-set. 
+
+  Standard attributes can be set using one of the following commands:
+    @<attribute name> <object>=<content>
+    @set <object>=<attribute name>:<content>
+    &<attribute name> <object>=<content>
+
+  It is also possible to have non-standard attributes, which can be named 
+  anything you like. Please see help NON-STANDARD ATTRIBUTES for more 
+  information on those. 
+
+(continued in help attributes3)
+& ATTRIBUTES3
+  Any attribute name can be shortened, but a shorter forms run the risk
+  of conflicting with other attribute names.  This could result in you
+  setting an unwanted attribute. 
+
+  For example:
+    @adesc me=think %N looks at you.
+  will set your ADESCRIBE attribute just as
+    @adescribe me=think %N looks at you.
+  would.
+
+  To see the attributes that are set on you or on any of the objects you own,
+  you should use the "examine" command. See 'help examine'.
+  
+(continued in help attributes4)
+& ATTRIBUTES4
+  Attributes can be owned by someone other than the object they are set on.
+  This allows the person to change the content of just that attribute while 
+  not the rest of the object. Attributes can also be locked, which prevents
+  them from being changed by anyone.
+
+  In addition to the standard attributes with pre-set effects, there are
+  some special attributes that date from the days before you could set
+  non-standard attributes with any name you wanted. These are the 
+  attributes VA-VZ, WA-WZ, XA-XZ. These attributes have no pre-set effects,
+  and were just to allow players to store any text or MUSHcode that they
+  wished in those attributes. Now that non-standard attributes are available,
+  it is highly recommended that you instead use them, since you can use
+  longer and descriptive names for attributes, which makes it much easier
+  to examine and work on objects.
+
+  See also: ATTRIB-OWNERSHIP, @set, examine, @atrchown, @atrlock, hasattr()
+    get(), v(), NON-STANDARD ATTRIBUTES, SETTING-ATTRIBUTES, ATTRIBUTE TREES
+
+& BOOLEAN VALUES 
+
+  A boolean variable, for those of you not familiar with programming, 
+  is a variable that is either true or false. Normally, a value of
+  1 is considered "true" and a value of 0 is considered "false". Many
+  MUSH functions return either 1 if they are true or 0 if false.
+  For example, the hasflag() function tests to see if an object has
+  a certain flag set on it. If
+        hasflag(<object>,<flag name>) 
+  is true (the object has the flag), it will return 1. If it is false, 
+  it will return 0. 
+
+  Other functions expect to operate on boolean values. What they
+  consider "true" or "false", however, depends on the setting of
+  the "tiny_booleans" config option (@config tiny will show this).  
+
+(continued in help boolean2)
+& BOOLEAN2 
+  If tiny_booleans is...
+  no                       FALSE: null string, 0, any negative db
+                           TRUE:  everything else
+  yes                      TRUE:  numbers other than 0
+                                  strings beginning with numbers other than 0
+                           FALSE: everything else 
+  
+  Or, put another way:
+  Value                 tiny_booleans=no        tiny_booleans=yes  Gotcha
+  0                     FALSE                   FALSE
+  non-zero number       TRUE                    TRUE 
+  #<non-negative>       TRUE                    FALSE               *
+  #<negative>           FALSE                   FALSE                
+  null string           FALSE                   FALSE
+  0<non-numbers..>      TRUE                    FALSE               *
+  <non-numbers...>      TRUE                    FALSE               *
+
+(continued in help boolean3)
+& BOOLEAN3
+
+  Examples (assuming tiny_booleans is "no"):     
+    not(foo) = 0  
+    not(<null string>) = 1
+    not(-66) = 0
+    not(0) = 1
+    not(#-1) = 1
+    not(#12) = 0
+  And so on...
+  (note: These rules only apply when a function expects a Boolean
+  value, not for strings that expect other values.)
+
+  See also: BOOLEAN FUNCTIONS, not(), t()
+& CLIENTS
+  Clients are special software programs that you can use to connect to 
+  MUSHes. They are usually much nicer to use than raw telnet and give you
+  many additional features, such as larger text buffers (so you can type
+  more), backscroll, history of previous commands, macros, and so on. 
+
+  Here is a list of common clients and the anonymous ftp sites at which
+  you can get them. To find out how to anonymous ftp, ask your system 
+  administrator. Please note that the below sites are subject to change.
+  The below are listed solely for your information and possible benefit. 
+  The developers of PennMUSH have nothing to do with the clients. 
+
+  OPERATING                   FTP or WWW SITE/
+  SYSTEM          CLIENT      DIRECTORY
+  -----------------------------------------------------------------------
+  UNIX            Tinyfugue   tf.tcp.com
+                              /pub/tinyfugue
+  WINDOWS 32-bit  MUSHClient  ftp.pennmush.org
+                              /pub/PennMUSH/Win32Binaries
+                  SimpleMU    http://simplemu.onlineroleplay.com
+  MACINTOSH       MUDDweller  http://www.shareware.com (search for Muddweller)
+
+& CONTROL
+  Controlling an object basically means that you have the power to change
+  the object's characteristics such as flags and attributes. It may also
+  mean that you have the ability to destroy it.
+
+  Here are the conditions under which object O controls victim V:
+  1. If V is God, O must be God
+  2. If O is Mistrust, and V is not O.  O will not control V.
+  3. If V is Privilege powered, O must 'GTE Level of V', O must have 
+     Privilege Power, and CanModify power over V.
+  4. O is Inherit and Owner is Inheritable then O inherits powers
+     of Owner but not Level to control V with.
+  5. If V has Privilege power, through self or Inheriting, O does not 
+     control V.
+  6. If O owns V and and has 'GTE Level than V', O controls V.
+  7. If O has Modify power over V, O controls V.
+  8. If V is on a zone, and isn't a player and isn't TRUST,
+     O controls V if O passes the zone-lock of the zone.
+  9. If V is owned by a SHARED player, and V isn't a player and isn't set
+     TRUST, O controls V if O passes the zone-lock of the SHARED player.
+
+  Step 8 is skipped if config(zone_control_zmp_only) is on.
+  There's also one special case: anyone can @link an unlinked exit
+  (at which point the exit is @chowned to the linker).
+
+  See also: controls(), TRUST, MISTRUST, ZONES, SHARED PLAYERS
+& COSTS
+  These are usually:
+
+          page: 0 pennies
+          @dig: 10 pennies
+          @create: 10 pennies (or more)
+          @find: 100 pennies
+          @search: 100 pennies
+          @entrances: 100 pennies
+          @link: 1 penny (if you  didn't already own it,
+                          +1 to the previous owner).
+          @open: 1 penny (2 pennies if linked at  the same time)
+  
+  Type '@config costs' to get the costs for the particular MUSH you are on.
+
+  See also: MONEY, money(), score
+& PENNMUSH CREDITS
+  Maintainer: Javelin
+  Developers: Talek [TAP], Raevnos [SW], Ervin Hearn III [EEH]
+  Porters: Nick Gammon [NJG] (win32), Dan Williams [DW] (MacOS), 
+           Sylvia (OS/2)
+  Former developers: Rhyanna [RLM], Trivian [TN], Halatir [LdW]
+  The original TinyMUSH 1.0 code was written by Lawrence Foard, and was
+  based upon James Aspnes' TinyMUD server. Since then, the code has been
+  modified by the programmers of MicroMUSE (then MicroMUSH), and Joseph
+  Traub (Moonchilde of PernMUSH).  From January 1992 to January 1995,
+  Lydia Leong (Amberyl of PernMUSH / Polgara of Belgariad) maintained
+  the code currently known as PennMUSH 1.50.  From January 1995 on, Alan
+  Schwartz (Paul of DuneMUSH / Javelin elsewhere) has been maintaining
+  this code, along with a development team.
+
+  Big thanks to the developers of TinyMUSH 2.0, 2.2 [2.2], 3.0 [3], Mux2,
+  and Rhost [Rhost] servers, as well as to the players of Belgariad MUSH,
+  DuneMUSH, and M*U*S*H, and everyone else using this server!
+
+  See also: help code, help license
+& DATABASE
+& DBREFS
+& DBREF NUMBER
+& DBREF #
+  You will find the term "dbref" or "dbref number" used frequently in these
+  help files and in MUSHcode. It is an abbreviation of "database reference
+  number".
+  
+  The database is the part of MUSH that stores all the information about 
+  this particular MUSH. Players, things, rooms, and exits are all objects
+  in the database. Each object in the database has a unique dbref number
+  that is set when the object is created. You can use the dbref number to
+  refer to an object that is not in your current location, and it is 
+  especially important for global code.
+
+  Using DBREFs is also faster than using names, even if the object is
+  in your location. This is because whenever you try to do something with
+  an object (such as look at it, take it, etc.), the MUSH first has to
+  locate the object. Since the dbref is unique, it can immediately find
+  the object rather than checking through all the contents of your area
+  to see if one matches the name.
+
+(continued in help dbref2)
+& DBREF2
+  
+  If you own or control an object, you will see its dbref number listed
+  right after its name when you look at it (unless you are set MYOPIC).
+
+  Example:
+    > look me
+    Cyclonus(#3PWenAMc)
+    A very short desc.
+
+  The dbref number is indicated by the number/pound sign (#). Cyclonus's
+  dbref is #3. The letters following the dbref are the abbreviations of
+  the flags set on the object. NOTE: the abbreviation of the OPAQUE
+  flag is 'O' (o), which looks like '0' (zero) on some clients. Make sure 
+  you have the right number before using it in your code!
+
+  See also: MYOPIC, OPAQUE, MUSHCODE
+& DROP-TOS
+
+  When you use the @link command on a room, it sets another room or
+  object as the DROP-TO location. By default, any non-STICKY object that
+  someone drops in the room will automatically be transported to the
+  drop-to location, rather than staying in the room. Any STICKY object
+  droped in the room will go to its home.
+
+  If the room is set STICKY, objects dropped in the room will stay there
+  until the last player leaves/disconnects, at which point they will be
+  transported as described above.
+
+  If the room has a @lock/dropto set on it, only objects that pass the
+  lock will be transported (either immediately or when the last player
+  leaves if the room is STICKY). This can be used to prevent the dropto
+  from acting on, say, objects containing connected players.
+
+  Drop-tos are useful for keeping rooms uncluttered. 
+
+  See also: @link, STICKY, LINK_OK, @lock
+& %#
+& %N
+& %~
+& %:
+& ENACTOR
+  The enactor is the object that does something (enacts something :).
+  This is an important concept in MUSH, because the way many commands
+  work will depend on who enters the command (ie, who the enactor is).
+  Any type of object can be an enactor. 
+
+  There are five %-substitutions that involve the enactor:
+    %# = the enactor's dbref
+    %N = the enactor's name, first letter capitalized
+    %n = the enactor's name, first letter as-is
+    %~ = the enactor's accented name
+    %: = the enactor's unique identifier, like objid(%#)
+
+  If, for example, you have an @osucc on an object that includes the
+  %n symbol, whenever someone picks up the object, that %n will be 
+  replaced with the name of the enactor (the person who typed 'get <object>'
+  in this case). 
+  
+  See also: EXECUTOR, SUBSTITUTION, DBREF
+& EVALUATION ORDER
+  Whenever some text is entered by an object or thing, the MUSH program
+  attempts to match it against a valid game command in the following 
+  order of possible commands:
+
+    Special game commands: WHO, QUIT, etc.
+    "home" command
+    Single-token commands: ", :, ;, +
+    Exits in the room
+    @-commands
+    Regular game commands: get, inventory, etc.
+    Enter aliases
+    Leave aliases
+    User-defined commands on nearby objects. All such $commands are matched
+      and executed.
+    If there are no user-defined commands nearby:
+      If the zone of the player's location is a zone master room,
+        Zone master room exits
+        Zone master room user-defined commands
+      Else
+        User-defined commands on the zone of the player's location
+
+(continued in help evaluation2)
+& EVALUATION2
+    If still nothing is matched:
+      User-defined commands on the player's personal zone
+    If nothing, including zone commands, has been matched:
+      Global exits
+      Global user-defined commands: all $commands in the Master Room are
+        matched. Local commands are always checked first and ALWAYS negate
+        global commands.
+
+  Because local commands overrule global commands, you can easily prevent
+  a global command from working in a specific room by setting a copy of
+  the global command in that room. Alternatively, if a global command is
+  oddly not working in a room, you should check for copies of the command
+  word in the room (using @scan).
+& %!
+& EXECUTOR
+  The executor of a command is the object actually carrying out the command.
+  This differs from the enactor, because the enactor is the object that sets
+  off the command. In some cases, the enactor and the executor will be the
+  same. There is a %-substitution, %!, that is replaced by the dbref # of
+  the executor of the command.
+
+  For example:
+    @emit %N is the enactor and %! is the executor!
+    > Cyclonus is the enactor and #6 is the executor!
+    @create Box
+    > Created: Object #10
+    &DO_EMIT box=$emit:@emit %N is the enactor and %! is the executor!
+    emit
+    > Cyclonus is the enactor and #10 is the executor!
+
+  In the first case, Cyclonus directly entered the command and was therefore
+  both the enactor and the executor. In the second, Cyclonus set off the 
+  command on the box, so Cyclonus was still the enactor, but the box was
+  the object that was actually doing the @emit, and was thus the executor.
+
+  See also: ENACTOR, SUBSTITUTION
+& EXITS
+  An exit is a one-way link that takes you from its source room to its 
+  destination room. To open an exit from a room, you must control that room.
+  To open an exit to a room, you must either control the room or it must be 
+  set LINK_OK. If an exit is set DARK is will not show up in the list of 
+  obvious exits in a room.
+
+  If an exit is set TRANSPARENT, someone who looks at the exit will also
+  see the description and contents of the destination room. If an exit is 
+  set CLOUDY, someone who looks at the exit will also see the contents of 
+  the room beyond, but not its description. If an exits is set -both-
+  CLOUDY and TRANSPARENT, the description but not the contents will be seen.
+   
+  If you have code on an exit (In an @asuccess or the like), note that
+  [loc(exit)] is the exit's destination, and [home(exit)] is the exit's
+  starting point. If an exit @emit's something, it will be heard in the
+  source room.
+   
+(continued in exits2)
+& EXITS2
+  You can create an exit that sends those who go through it to their homes
+  by typing '@link <EXIT>=home'.
+
+  Starting with PennMUSH version 1.50p10, exits can have more than one 
+  destination. To make an exit with a variable destination, open the exit 
+  (using @open),  then type '@link <EXIT>=variable'. Finally, add an 
+  attribute named 'DESTINATION' to the exit (&destination <EXIT>), which 
+  will be evaluated for the dbref # of the destination room when the exit 
+  is used. 
+   
+  For example:
+  @open South <S>;s;south
+  @link s=variable
+  &destination s=[switch(rand(3),0,#100,1,#101,2,#102)]
+   
+  This exit would take you to either room #100, #101, or #102 depending on 
+  the random number. 
+
+  Anyone can create variable exits, but the destinations must be to places
+  that the exit can normally @link to.
+
+  See also: @link, @open, link_ok, CLOUDY, TRANSPARENT, @firstexit
+& FAILURE
+  FAILURE  
+
+  A "failure" usually occurs when you try to do something that is 
+  governed by an @lock and you don't pass the lock. If you try to
+  take a player or thing, and you don't pass their @lock, you will
+  set off their @fail/@ofail/@afail attributes. If you try to go
+  through an exit, and you don't pass its @lock, you will similarly
+  set off its @fail/@ofail/@afail. Other failure sets include:
+
+  Failing to enter an object (@efail, @oefail, @aefail)
+  Failing to leave an object (@lfail, @olfail, @alfail)
+  Failing to use an object (@ufail, @oufail, @aufail)
+  Other failures (&<lock>`FAILURE, &<lock>`OFAILURE, &<lock>`AFAILURE)
+    where the <lock> can be: FOLLOW_LOCK, PAGE_LOCK
+
+  Many other things can also be locked -- see @lock and locktypes for 
+  more information. However, there are failure messages at this time
+  only for the above.
+
+  See also: @lock, @fail, @efail, @lfail
+& GENDER
+& SEX
+  Gender on a MUSH is entirely up to you. You can set yourself (or any
+  of your objects) to be male, female, neuter, or plural. If whatever
+  is in the SEX attribute is not recognizable, the MUSH will assume 
+  the object is neuter. Setting a gender attribute will enable 
+  pronoun substitution by the MUSH. The SEX attribute is visual to
+  anyone who wants to see it. 
+
+  See also: @sex, SUBSTITUTION
+& GLOBALS
+& GLOBAL COMMANDS
+  A command is "global" if it can be used anywhere in the world of the
+  MUSH. The standard MUSH commands are all global, so this term is 
+  usually used to refer to user-defined commands on objects in the
+  Master Room of the MUSH. Global commands very greatly from MUSH to
+  MUSH, but you can usually find MUSH-specific help on them by
+  typing "+help". 
+
+  See also: MASTER ROOM, USER-DEFINED COMMANDS, EVALUATION
+& HERE
+  The word 'here' refers to the room you are in. For example,
+  to rename the room  you're in (if you control it), you could enter 
+  "@name here= <new name>". 
+& HOMES
+& HOME
+  Every thing or player has a home, which is usually the room where
+  it was created. You can reset your home or the home of any object
+  you own with the @link command: @link <me|object>=<location>. You
+  must also control <location>, unless that location (room or thing)
+  is set ABODE or LINK_OK.
+
+  When a player types 'home', s/he is sent back to the home room. When 
+  a thing with the STICKY flag set on it is dropped, it also goes to 
+  its home location. Note that if the FIXED flag is set on a player, 
+  he/she cannot use the 'home' command.
+
+  You can create an exit that sends players home by doing:
+        @link <exit name>=home
+  You can set the drop-to in a room to home by doing:
+        @link <room dbref or "here">=home
+
+  See also: DROP-TOS, @link, STICKY, LINK_OK, FIXED, EXITS
+& INTERIORS
+  Here's a quick description of how to make things that can be entered:
+        
+  @create Car
+  @desc Car=A shiny red car.
+  @idesc car=You are sitting inside a luxurious sportscar.
+  @set Car=enter_ok
+  @oxleave car=climbs out of the car.   { The 'ox' messages are shown to 
+  @oxenter car=climbs into the car.     { those OUTSIDE the object.
+  @oenter car=joins you inside the car. { The 'o' messages are shown to 
+  @oleave car=gets out of the car.      { those INSIDE the object
+  @enter car=You get into the car.      { The plain messages are shown to 
+  @leave car=You get out of the car.    { the one entering or leaving 
+       
+(continued in help interiors2)
+& INTERIORS2
+  Now, if you want people inside to be able to hear and communicate with 
+  the outside, you also need to do the following.
+   
+  @set car=audible  (lets people outside hear what's being said in the car.
+  @listen car=*     (lets people inside hear what's being said outside.
+  @prefix car=From inside the car,
+  @inprefix car=From outside,
+  @filter car=* has arrived.,* has left.,joins you inside the car.,
+    gets out of the car.
+  @infilter car=* has arrived.,* has left.,* climbs out of the car.,
+    * climbs into the car.
+
+  (The filters will keep people on the outside from seeing the 'o'
+  messages and people on the inside from seeing the 'ox' messages which
+  is a good thing.)
+
+  See also: enter, leave, @prefix, @filter, AUDIBLE, @listen
+& LAST & LASTLOGOUT
+  LAST and LASTLOGOUT
+
+  These attributes show the last times you connected and disconnected from
+  the MUSH.
+& LASTSITE
+& LASTIP 
+  LASTSITE and LASTIP
+
+  The LASTSITE attribute gives the name of the site you last connected from.
+  The LASTIP attribute gives the IP address you last connected from.
+  Mortals cannot set them.
+& LINKING  
+
+  You can link to a room if you control it, or if it is set 
+  LINK_OK or ABODE. Being able to link means you can set the homes of
+  objects or yourself to that  room if it is set ABODE, and can set 
+  the destination of exits to that room if it is LINK_OK.
+
+  See also: LINK_OK, ABODE, @link
+& LISTENING
+  
+  There are two basic ways to trigger action on the MUSH. The basic way
+  is to type in commands such as 'look' or '@emit'. These commands are not
+  seen or heard by other players, although the results of the commands may
+  be.
+
+  The other way is to "listen" for something said/emitted in your hearing.
+  There are two ways to listen for something in a room. The easiest way
+  is to use a combination of @listen and @ahear/@aahear/@amhear. 
+
+  For example:
+    > @listen Welcome Mat=* has arrived.
+    > @ahear Welcome Mat="Welcome, %N!
+    Breaker has arrived.
+    Welcome Mat says, "Welcome, Breaker!"
+
+(continued in help listening2)
+& ^
+& LISTENING2
+  If you need an object to "listen" for more than one pattern, you can
+  also use ^-patterns.  These work similar to user-defined commands, 
+  using ^ instead of $. An object must be set MONITOR to have ^-patterns
+  activated.
+
+  Syntax:  &<attribute> <object> = ^<pattern>:<action list>
+
+  For example:
+  > @set Welcome Mat = MONITOR
+  > &greet Welcome Mat = ^* has arrived.:"Welcome, %N!
+  > &goodbye Welcome Mat = ^* has left.:POSE says as %N leaves, "Bye!"
+  Grimlock has arrived.
+  Welcome Mat says, "Welcome, Grimlock!"
+  Grimlock has left.
+  Welcome Mat says as Grimlock leaves, "Bye!"
+
+  Such attributes can also be @triggered as if the ^<pattern>:
+  did not exist.
+
+(continued in help listening3)
+& LISTENING3
+  By default, ^-patterns work like @ahear. To have them work like
+  @amhear, set the AMHEAR attribute flag on the attribute; to have
+  them work like @aahear, set the AAHEAR attribute flag on the attribute
+  (Note that the triggering object is whatever happens to be %#, so, for
+  example, when you @set an object MONITOR, you are %# with regard to the
+  "Object is now listening" message, and this message can be picked up
+  with an ^pattern.)
+
+  Additionally, unlike $-commands, @listen and ^-patterns are NOT
+  inherited via @parent, unless the LISTEN_PARENT flag is set on the
+  listener.
+
+  Listen patterns are checked after the object's normal @listen attribute.
+
+  See also: @listen, @ahear, @amhear, @aahear, MONITOR, 
+          USER-DEFINED COMMANDS, LISTEN_PARENT
+
+& LISTS
+  The word "list" is used in the help files to refer to a string that
+  is a series of smaller strings separated by one or more spaces. A list
+  can also have its elements separated by some other kind of character --
+  the separating character is called the "delimiter". 
+  For example, the following are all lists:
+
+    #6 #10 #14 #12
+    Rumble|Krystal|Bobatron|Rodimus Prime   ('|' is the delimiter here)
+    foo bar whee blarg 
+    -eek- .boing. yawp #5 7
+  
+  Lots of MUSHCode depends on lists and manipulating them. Normally, a list
+  is made up of similar items (so the fourth list in the example is NOT a 
+  typical one).
+
+  See also: STRINGS, List Functions
+& LOOPING
+  Looping in an object can have its good parts and its bad parts.
+  The good part is when you activate part of a program multiple times
+  to exhaustively perform an operation.  This can be done like this:
+
+    &PART1 object=<action list> ; @trigger me/PART2
+    &PART2 object= @select <test for being done>=<false>,@trigger me/PART1
+
+  Looping can be a problem when it goes on without stopping.  The @ps
+  command can be used to see if you are looping.  Beware!  A looping
+  machine that isn't @halt'd will drain your pennies while you are away
+  from the mush!
+
+  See also: @ps, HALT, COSTS, @trigger
+& MASTER ROOM
+  
+  The Master Room enables global commands and exits. Exits in the Master
+  Room may be used from any location on the MUSH. All objects left in the
+  Master Room are checked for user-defined $commands. Those $commands are
+  considered global, meaning that they can be used anywhere on the MUSH. 
+  Normally, only wizards will have access to the Master Room; if you have
+  a global command that you would like to see enabled for the MUSH, speak
+  to a wizard.
+  
+  See also: EVALUATION, GLOBAL COMMANDS
+& ME
+  The word 'me' refers to yourself. Some things to do when 
+  starting out: 
+  1) give  yourself a description:      @desc me=<description>
+  2) check your desc.:                  look me
+  3) lock yourself:                     @lock me==me
+  4) set your gender:                   @sex me=<male|female|neuter|plural>
+
+  See also: help newbie, help @lock, help @describe, help @sex
+& MONEY
+  The MUSH has a built-in money system, which gives a starting amount
+  of money to new players and hands out a daily allowance thereafter.
+  MUSH money (the default name is "pennies", but this may be different
+  depending on the particular MUSH) is spent on some MUSH commands
+  that are computationally expensive or alter the database. In 
+  addition, every time you "queue" a command, it costs you a certain
+  amount of money -- this prevents looping from getting out of control,
+  since when all your money is spent, you can't queue any more commands.
+
+  The money system can also be used on player-created objects by giving 
+  them @cost/@payment/@opayment/@apayment attributes. When someone then
+  pays the object by giving it the right number of pennies, the attributes
+  are triggered.
+
+  See also: COSTS, give, @cost, @pay, @opay, @apay
+
+& MUSHCode Topics
+& MUSHCODE
+& SOFTCODE
+  MUSHcode is the programming language available within the MUSH itself
+  with which you can create user-defined commands and macros.  It is 
+  sometimes called "softcode" to distinguish it from "hardcode", which is 
+  the language that the source code for the MUSH server is written 
+  in. (Incidentally, hardcode is written in the C programming language.)
+  
+  At its most basic, writing MUSHcode is just stringing together a series
+  of commands that you would otherwise just type in one at a time.  You
+  can store MUSHcode in attributes on any type of object you own or control
+  (including yourself!).  The series of commands can be triggered by using 
+  a user-defined command or by using @trigger.
+
+   MUSHCode Help Topics 
+  -----------------------
+  ACTION LISTS           ANONYMOUS ATTRIBUTES   ATTRIBUTES
+  BOOLEAN VALUES         DBREFS                 ENACTOR
+  EVALUATION             EXECUTOR               EXITS
+  FAILURE                HERE                   INTERIORS
+  LISTENING              LISTS                  LOOPING
+  MATCHING               ME                     NON-STANDARD ATTRIBUTE
+  PARENTS                PUPPETS                REGEXPS
+  REGISTERS              SEMAPHORES             STACK
+  STRINGS                SUBSTITUTION           SUBSTITUTIONS
+  SUCCESS                TYPES OF OBJECTS       USER-DEFINED COMMANDS
+  VERBS                  WILDCARDS
+     (continued in MUSHCode2)
+
+& MUSHCode2
+  If you would like to learn more about mushcoding and how to create macros
+  for yourself, the following help files may be useful.  However, the best
+  way to learn is by obtaining a copy of Amberyl's MUSH manual and following
+  the examples described there.  The manual is available by anonymous FTP
+  from: ftp.pennmush.org in the directory /pub/PennMUSH/Manuals
+
+
+
+& NON-STANDARD ATTRIBUTES
+  While there are many standard attributes in MUSH, objects can also have
+  an unlimited number of attributes, with any name you wish to use. In the
+  past, you were limited to attributes named VA-VZ, WA-WZ, XA-XZ; these
+  are still available as standard attributes. However, it is strongly
+  recommended that you use non-standard attributes and meaningful names
+  in order to make maintaining your MUSHCode easier.
+
+  To set a non-standard attribute, you can use these formats:
+      &<attribute name> <obj> = <value>  OR
+      @_<attribute_name> <obj> = <value> OR
+      @set <obj> = <attribute_name>:<value>
+
+  You can get the value of attributes using the functions v(), get(), and
+  xget(). You can evaluate attributes using u(), eval(), and get_eval().
+  All attributes can be used in attribute locks and can be 'owned' 
+  independent of object ownership. 
+  
+  See also: ATTRIBUTES, ATTRIB-OWNERSHIP, Attribute Functions, 
+     ATTRIBUTE TREES
+& PARENT
+& PARENTS
+& OBJECT PARENTS
+  
+  Objects may have "parent" objects, from which they can inherit attributes.
+  Once an object is given a parent, it may use the attributes on the parent
+  just as if the attributes were on the object itself, including checking for
+  $commands. Use the @parent command to change the parent of an object.
+
+  Objects may have multiple levels of parents - thus, if #100 is the
+  parent of #101, which is the parent of #102, object #102 checks itself,
+  #101, and #100 for attributes. Attributes are checked on the object
+  itself first, followed by its parent, followed by that parent's parent,
+  and so forth. There is a (configurable) maximum number of ancestors
+  an object may have; the default is 10.
+   
+(continued in help parents2) 
+& PARENTS2
+  Note that the only properties inherited are attributes. In particular,
+  flags and exits are NOT inherited from the parent object. Also, commands
+  which walk the attribute list (such as "examine", the LATTR() function,
+  the HASATTR() function, @set, and @edit) only affect attributes that are 
+  on the object itself.
+  There are some limitations to the use of @parent. The most important is
+  that ^-pattern checking is not done on the parent of an object, unless the
+  object is set LISTEN_PARENT. For the purposes of automated game checks, 
+  the following attributes are not inherited: CHARGES, EALIAS, LALIAS, LAST, 
+  LASTSITE, LISTEN, QUEUE, RQUOTA, SEMAPHORE, and STARTUP. 
+    
+  The attributes inherited from the parent are treated just like its
+  own attributes by the child. Thus, when a $-command or @trigger is
+  executed, "me", for example, refers to the child, not the parent,
+  and the $-command's associated actions are performed by the child.
+
+(continued in help parents3)
+& PARENTS3
+  Attributes with $-commands _are_ inherited from the parent and
+  previous generations. Conflicts are resolved not by the $-command 
+  name, but by the attribute name. If two attributes are in "conflict", 
+  the child's attribute is used.
+
+  For example:
+
+  > &TEST #10=$test:@emit I'm the parent
+  > &TEST #11=$check:@emit I'm the child
+  > @parent #11=#10
+  > test
+  (nothing happens)
+  > check
+  I'm the child
+
+(continued in help parents4)
+& PARENTS4
+  If a parent has the same $-command name in a different attribute, however,
+  BOTH the parent and child commands will execute:
+
+(continued from previous example)
+  > &CHECK #10=$check:@emit No, I'm the parent!
+
+  > check
+  I'm the child
+  No, I'm the parent!
+  @parent is most useful when several objects use common attributes.
+  It is slightly faster to have $commands on the child object which
+  in turn @trigger or otherwise retrieve attributes inherited from
+  the parent object, rather than having the $commands checked on the
+  parent object.
+
+(continued in help parents5)
+& PARENTS5
+  Parent-object $-command checking is at its most efficient when there
+  are few or no attributes on the child. Also, each additional level
+  of parents further reduces efficiency.
+  If you are "mass-marketing" your objects, you can create blank copies, 
+  and @parent those copies to a template object. You can then customize 
+  necessary attributes on the copy. When a buyer @chowns his copy, the 
+  parent does not change, so unless you're putting data into the parent 
+  that you want to make impossible to read, it's safe to allow the
+  purchasers of your object to @chown their copy.
+
+  See also: @parent, $-COMMANDS, ATTRIBUTES
+
+& PUPPETS
+  A thing is turned into a puppet by setting the PUPPET flag on it.
+  A puppet object is an extension of its owner and relays everything
+  it sees and hears to its owner, except if it is in the same room as
+  the owner (a puppet with the VERBOSE flag will relay even if it's in
+  the same room). Things relayed by the puppet will be prefixed by the 
+  name of the puppet.
+
+  Puppets are useful for keeping track of what is going on in two
+  rooms at once, as extensions of a player (such as a pet, for example),
+  or for testing code. 
+
+  You can control your puppets using the @force command. It is important
+  to remember the DBREF numbers of your puppets so you can control them
+  even if they are not in the same room with you. You can also have
+  your puppets follow you by using the 'follow' command.
+
+(example in help puppets2)
+& PUPPETS2
+  An example of a puppet:
+
+  > @create Punch
+  Created: Object #18.
+  > drop Punch
+  Dropped.
+  > @set punch=puppet
+  Punch is now listening.
+  Flag set.
+  > @force punch=go north
+  Punch has left.
+  Punch> The Finishing Place
+  Punch> 
+  Punch> Obvious exits:
+  Punch> Door <S>  
+  #18 :waves hello
+  Punch> Punch waves hello
+
+  See also: PUPPET, @force, DBREF
+& QUEUE
+  QUEUE
+
+  The queue is the waiting line for commands to be executed by the MUSH.
+  Each time you enter a command, it goes into the queue and stays there
+  until its turn comes up, at which time the MUSH processes the command
+  and you see the results. The MUSH can execute several commands every
+  second, so normally you see results right away. However, if there are
+  too many commands in the queue, there may be a delay, called lag. The
+  more common cause of lag, however, is network delays between you and
+  the MUSH.
+
+  The QUEUE attribute is only visible to objects that control you 
+  (wizards, you, and your objects) or unless you are VISUAL.  It 
+  tracks how many active commands you have in the queue.
+
+  See also: @ps, LOOPING
+& REGEXP
+& REGEXPS
+  (This help text is largely from TinyMUSH 2.2.4, with permission)
+
+  The majority of matching in MUSH is done with wildcard ("globbing")
+  patterns. There is a second type of matching, using regular expressions,
+  that is available in certain circumstances.
+  For attributes that are $-commands or ^-listen-patterns, setting that
+  attribute "regexp" (with '@set <object>/<attribute>=regexp') causes
+  patterns to be matched using regular expressions rather than
+  globbing. In addition, the function regmatch() performs regular
+  expression matching.
+  In a regular expression match, the substring of the string which matched
+  the regexp pattern is %0; %1 through %9 are the substrings of the string
+  which matched parenthesized expressions within the regexp pattern. 
+  Continued in 'help regexps2'.
+& REGEXPS2
+  Regular expressions are extremely useful when you want to enforce
+  a data type. For example, if you have a command where you want a
+  player to enter a string and a number ('+setnum <player>=<number>',
+  for example), you might do it like this:
+  &DO_NUM Command Object=$^\+setnum (.+)=([0-9]+)$: @va me=Data: %1 = %2
+  @set Command Object/DO_NUM = regexp
+  Then, '+setnum cookies=30' would set VA to "Data: cookies = 30".
+  This eliminates your having to check to see if the player entered
+  a number, since the regular expression matches only numbers.
+  Furthermore, the '+' guarantees that there needs to be at least
+  one character there, so a player can't enter '+setnum cookies='
+  or '+setnum =10' or similarly malformed input.
+  The '+' sign in the command has to be escaped out, or it is taken as
+  a regexp token. Furthermore, the pattern-match has to be anchored
+  with ^ and $, or something like 'try +setnum cookies=30 now' would
+  also match. 
+  Regular expression syntax is explained in 'help regexp syntax'.
+& REGEXP SYNTAX
+  PennMUSH uses PCRE for its regular expression engine.  PCRE is an
+  open source library of functions to support regular expressions whose
+  syntax and semantics are as close as possible to those of the Perl
+  5 language.  The text below is excerpted from its man page. PCRE
+  was written by Philip Hazel <ph10@cam.ac.uk>, and is Copyright (c)
+  1997-1999 University of Cambridge, England. You can find it at
+     ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/
+
+  (Note that in PennMUSH, if the regular expression is in an eval'd
+  context (like an argument to regmatch), you'll have to do a lot
+  of escaping to make things work right. One way to escape an argument
+  like %0 is: regeditall(%0,\\W,\\$0) or similar).
+
+  Regular expression matching in PennMUSH can be used on user-defined
+  command or listen patterns. In this usage, regular expressions
+  are matched case-insensitively unless the attribute has the CASE flag 
+  set. Regular expressions can also be matched in MUSHcode using
+  regmatch(), regrab(), regedit, etc. function families, which usually
+  come in case-sensitive and case-insensitive versions.
+ (Cont'd in help regexp syntax2)
+& regexp syntax2
+  A regular expression is a pattern that is matched against a subject
+  string from left to right. Most characters stand for themselves in a
+  pattern, and match the corresponding characters in the subject. 
+
+  There are two different sets of meta-characters: those  that are
+  recognized anywhere in the pattern except within square brackets,
+  and those that are recognized in square  brackets.  Outside square
+  brackets, the meta-characters are as follows:
+
+       \      general escape character with several uses
+       ^      assert start of subject
+       $      assert end of subject
+       .      match any character except newline
+       [      start character class definition
+       |      start of alternative branch ("or")
+       (      start subpattern
+       )      end subpattern
+       ?      0 or 1 quantifier (after a unit to quantify)
+              or, minimal match (after a quantifier)
+              or,  extends the meaning of ( after a ( 
+       *      0 or more quantifier
+       +      1 or more quantifier
+
+  (Cont'd in help regexp syntax3)
+& regexp syntax3
+  Part of a pattern that is in square  brackets  is  called  a
+  "character class". It matches any character listed in the class.
+  In a character class, the only metacharacters are:
+
+       \      general escape character
+       ^      negate the class, if the first character in the class
+       -      indicates character range (e.g. A-Z, 0-4)
+   [:NAME:]   A symbol for a group of characters that can vary 
+              according to the language the mush is using.
+              See 'help regexp classes' for more information.
+       ]      terminates the character class
+
+  A backslash will escape most metacharacters, and can turn
+  some normal characters into generic character types:
+
+       \d     any decimal digit
+       \D     any character that is not a decimal digit
+       \s     any whitespace character
+       \S     any character that is not a whitespace character
+       \w     any "word" character (letter, digit, or underscore)
+       \W     any "non-word" character
+  (Cont'd in help regexp syntax4)
+& regexp syntax4
+  A backlash can also two useful assertions -- conditions that
+  must be met at a particular point in a match:
+
+       \b     word boundary
+       \B     not a word boundary
+
+  A word boundary is a position in the  subject  string  where the
+  current character and the previous character do not both match \w or
+  \W (i.e. one matches \w and  the  other  matches \W),  or the start
+  or end of the string if the first or last character matches \w,
+  respectively.
+
+  (Cont'd in help regexp syntax5)
+& regexp syntax5
+  Quantifiers specify repetition of characters. Three are available:
+       *    match 0 or more of whatever came before
+       +    match 1 or more of whatever came before
+       ?    match 0 or 1 of whatever came before
+
+  (In theory, you can match m-n of whatever came before with {m,n},
+  but the MUSH parser makes it difficult to use {}'s in functions unless
+  you store the regex pattern in an attribute and use v() to fetch it)
+
+  Quantifiers are usually greedy -- they match as much as possible.
+  Adding a ? after a quantifier causes it to match as little as
+  possible instead. 
+
+  (Cont'd in help regexp syntax6)
+& regexp syntax6
+  Outside a character class, a backslash followed by  a  digit greater
+  than  0  (and  possibly  further  digits) is a back reference to a
+  capturing subpattern  earlier  (i.e.  to  its left)  in  the  pattern,
+  provided there have been that many previous capturing left parentheses.
+  A back reference matches whatever actually matched the  capturing
+  subpattern in the current subject string, rather than anything matching
+  the subpattern itself. So the pattern
+
+    (sens|respons)e and \1ibility
+
+  matches "sense and sensibility" and "response and  responsibility",  but
+  not  "sense  and  responsibility". 
+
+  (Cont'd in help regexp syntax7)
+& regexp syntax7
+  An assertion is  a  test  on  the  characters  following  or
+  preceding  the current matching point that does not actually consume
+  any characters.  There  are  two kinds:  those that look ahead of the
+  current position in the subject string, and those that look behind it.
+
+  An assertion subpattern is matched in the normal way, except that  it
+  does not cause the current matching position to be changed. Lookahead
+  assertions start with  (?=  for  positive assertions and (?! for
+  negative assertions. For example, Lookbehind assertions start with
+  (?<=  for  positive  assertions and (?<! for negative assertions.
+
+  Assertion subpatterns are not capturing subpatterns, and may not
+  be  repeated,  because  it makes no sense to assert the same thing
+  several times. If an assertion contains capturing subpatterns within it,
+  these are always counted for the purposes of numbering the capturing
+  subpatterns  in  the  whole pattern.
+
+  (Cont'd in help regexp syntax8)
+& regexp syntax8
+
+  PCRE's engine can also do conditional subpattern matching,
+  embedded comments in regexps, and a bunch of other things.
+  See a regexp book for details.
+& REGEXP CLASSES
+  In a character class, you can use a number of additional keywords 
+  that match certain types of characters. The keywords are enclosed in
+  [: and :], within the character class, so the whole thing looks like
+  [[:NAME:]].
+
+  These keywords can be mixed with other things in the character class,
+  like [ab[:digit:]], which will match 'a, 'b', or a digit. [:^NAME:]
+  reverses the meaning of NAME - it expands to everything but characters
+  that would match [:NAME:].
+
+  Some recognized NAMEs:
+   digit, for numbers. [[:digit:]] is the same as \d.
+    [[:^digit:]] is the same as \D.
+   alpha, for letters. alnum, for numbers and letters.
+   lower, for lower-case letters. upper, for upper-case letters.
+   word, for word characters. [[:word:]] is the same as \w.
+    [[:^word:]] is the same as \W.
+   space, for whitespace characters. [[:space:]] is the same as \s.
+    [[:^space:]] is the same as \S.
+
+Continued in 'help regexp classes2'
+& REGEXP CLASSES2
+ These keywords (Or the corresponding \codes) should be used instead of
+ explicit ranges where possible to improve portability. For example,
+ [A-Za-z] and [[:alpha:]] are not the same thing in languages with accented
+ characters. There are other keywords, but the ones listed are the most
+ useful.
+
+  Examples:
+  > say regmatch(foo_bar, lit(^[[:word:]]+$))
+  You say "1"
+  > say regmatch(foo bar, lit(^[[:word:]]+$))
+  You say "0"  
+
+& REGEXP EXAMPLES
+  Topic: REGEXP EXAMPLES
+  The regexp pattern '.' is equivalent to the wildcard '?'; it matches
+  one and only one of an arbitrary character.
+  The regexp pattern '.+' is equivalent to the wildcard '*'; it matches
+  one or more arbitrary characters. To match zero or more arbitrary
+  characters, the regexp pattern is '.*'.
+  To match a string of numbers, use:       [0-9]+    or \d+
+  To match a string of letters only, use:  [A-Za-z]+ or \w+
+  See 'help regexp syntax' for a more detailed explanation.
+
+& REGISTERS
+  A register is essentially a little reserved piece of computer memory
+  that can hold some variable information that you want to pass on to
+  another command.  There are ten registers on the MUSH available via
+  %-substitution (%0 - %9) and thirty-six setq registers available via
+  %q- substitution (%q0 - %q9 and %qA - %qZ). 
+
+  The basic registers are filled with information that matches the 
+  wildcard pattern of the command trigger. (Before you say "Huh?", here's
+  an example.)
+
+  &COMMAND me=$command *+*:@emit %0 is in register 0 and %1 is in register 1.
+  > command whee+blert foo
+  whee is in register 0 and blert foo is in register 1.
+
+(continued in help registers2)
+& REGISTERS2
+  As you can see from the above example, the command trigger had two wildcards
+  separated by a "+" sign. When the user types in the command with some words
+  taking the place of the wildcards, the first register (register 0) is filled
+  with whatever part of the command replaces the first wildcard (in this case,
+  "whee") and the second register is filled with whatever replaces the second
+  ("blert foo").  
+
+  They can also be filled with information that is passed by an @trigger 
+  command:
+
+  &SOMECODE me=@emit %0 is in register 0 and %1 is in register 1.
+  @trigger me/somecode=whee,foo bar
+  > whee is in register 0 and foo bar is in register 1.
+
+  The registers can also be accessed using the V-function (v(0) through v(9)).
+
+  Please see help setq() for more information about the setq registers.
+
+  See also: SUBSTITUTIONS, @trigger, USER-DEFINED COMMANDS, setq()
+& RQUOTA
+  RQUOTA
+
+  This attribute tracks remaining building quota if it is implemented.  
+  It is settable in-game only by a wizard, and is only visible to wizards.
+
+  See also: @quota, @squota
+& SEMAPHORES
+  The most complicated thing about semaphores is their name. Before you try
+  to use semaphores, you should first be familiar with the "@wait" command.
+  If you are, then you know that normally, you type:
+
+    @wait <number of seconds>=<action> 
+
+  and the action takes place after that number of seconds has passed. With
+  a semaphore, you instead type:
+
+    @wait <object>=<action>
+    @wait <object>/<number of seconds before timeout>=<action>
+
+  and the action takes place after the object has been "notified" that its
+  time for it to happen. You can also set a timeout -- if the object hasn't
+  been notified by the time that number of seconds has passed, the action
+  will take place. Any object (player, thing, exit, room) that you control 
+  or that is set LINK_OK can be used to wait actions on.
+
+(continued in help semaphores2)
+& SEMAPHORES2
+
+  An object is notified using the "@notify" command.  When you type "@wait
+  <object>=<action>", you are adding one to the SEMAPHORE attribute on the
+  object. When you type "@notify <object>", you are decreasing the SEMAPHORE
+  attribute on the object by one. Whenever the attribute decreases, one of
+  the actions waiting on the object takes place. The actions occur in the
+  order they were added. 
+
+  You can make the semaphore attribute of an object negative by @notify-ing
+  it more times than things have been @wait-ed on it. If you do so, anything
+  @wait-ed on the object will add one to the SEMAPHORE attribute and the
+  action will take place immediately. You can also make all the actions
+  waiting on an object take place right away by using "@notify/all", or 
+  wipe all the commands out and clear the SEMAPHORE attribute by using
+  "@drain". Please note that all SEMAPHORE attributes are cleared out 
+  whenever the MUSH is restarted.
+  Semaphores can be used to make sure that events occur in the right order,
+  or to make sure that two players can't use the same object at the same
+  time. 
+
+(continued in help semaphores3)
+& SEMAPHORES3
+
+  It's important to remember that the actions will be carried out NOT by
+  the object that they are being @waited on, but by whichever object
+  entered the @wait.
+
+  Examples:
+  
+  > @wait semaphore=:tests.
+  > @notify semaphore
+  Wizard tests.
+  
+  > @wait timer/30=:waits 30 seconds.
+  [ 30 seconds passes. ]
+  Wizard waits 30 seconds.
+  See also: @wait, @drain, @notify
+(continued in help semaphores4)
+& SEMAPHORES4
+  Semaphores can be used to enforce mutual exclusion - to prevent
+  the same object from being used simultaneously by two players.
+  The basic strategy is to ensure that the object always has a
+  SEMAPHORE of -1, to enclose commands in an @wait, and to
+  conclude the set of commands with an @notify me:
+
+  > &doit obj = $doit: @wait me={&doer me = %N; @tr me/report}
+  > &report obj = "[v(doer)] did it!; @notify me
+  > @startup obj = @drain me; @notify me
+  > @notify obj
+  > ex obj/SEMAPHORE
+  SEMAPHORE [#1ic+]: -1
+  > doit
+  obj says "Talek did it!
+  > ex obj/SEMAPHORE
+  SEMAPHORE [#1ic+]: -1
+
+  If a second player types doit as well, the second player's command
+  is put on the semaphore queue and not run until the @notify me at
+  the end of the REPORT attribute. Note the STARTUP attribute -
+  because semaphores are cleared when the MUSH starts up, you must
+  insure that the object gets @notify'd once when it starts up.
+(Continued in help semaphores5)
+& SEMAPHORES5
+ Normally, semaphores use the SEMAPHORE attribute. However, other
+ attributes can be used, as long as they follow a few simple rules:
+ If the attribute is already set, it has to have the same owner (God)
+ and flags as the SEMAPHORE attribute would (typically no_inherit, no_clone,
+ and locked - see help @set and @atrlock), and have a numeric or empty
+ value. If it's not set, it can't be one of the built in attributes
+ (See @list attribs) unless, naturally, it is SEMAPHORE.
+
+ See the help on @wait, @notify and @drain for details, but, briefly,
+ you can use named semaphores with <object>/<attribute> where you would
+ normally just use <object> in those commands. This means you can't have
+ a un-timed semaphore on an attribute with a numeric name.
+
+(Continued in help semaphores6)
+& SEMAPHORES6
+ An example:
+  > @wait me/semtest=think blah
+  > ex me/semtest 
+  SEMTEST [#1ic+]: 1
+  > @ps
+  ...
+  Semaphore Queue:
+  [#8/SEMTEST]Raevnos(#8P):think blah
+  ...
+  > @notify me/semtest
+  Notified.
+  blah
+
+ This allows you to use one object to control many different things -- for 
+ example, fights in a turn-based combat sytem.
+& SETTING-ATTRIBUTES
+  
+  Standard attributes are set using @<attrib> <obj>=<value>
+  Nonstandard attributes are set using &<attrib> <obj>=<value> 
+  Attributes may also be set using @set <obj>=<attrib>:<value>
+  
+  Attributes are cleared using @<attrib> <obj> or &<attrib> <obj>
+  or with @wipe.
+  
+  Note that there is a difference between clearing an attribute
+  and setting an attribute to a null value:
+    @va me       <--- wipes out my VA attribute
+    @va me=      <--- sets my VA attribute to be empty
+  
+  Empty attributes retain their flags and atrlock status. Wiped attributes
+  are gone forever.
+  
+  See also ATTRIBUTES, NON-STANDARD ATTRIBUTES, @set, @wipe
+& SPOOFING
+  Spoofing is the act of making other characters think that a person
+  said or did something that they did not.  This is very easy to
+  accomplish, and has some good effects, which is why it is allowed.
+  However, abusing it is very twinkish and will most likely get you in
+  hot water with your wizards. Note that if you are being spoofed and
+  want to know who is doing it, you can set yourself NOSPOOF and you will
+  be notified who is making the @emits.
+
+  See also: @emit, @pemit, @remit, @oemit, and NOSPOOF.
+
+& STACK
+  For those unfamiliar with the term stack, it refers to a programming
+  data structure that follows a LIFO (Last-In-First-Out) principle. The
+  stack in MUSH holds the ten REGISTERS, which can be accessed via the 
+  V-function (v(0) through v(9)) or via %-substitution (%0 through %9).
+  
+  See also: REGISTERS
+& STRINGS
+  A string is simply a bunch of characters.  A word is a string that begins
+  and ends with the space character.  A sentence is a string made up of 
+  smaller substrings that are words.  Please note that a "word" or "sentence"
+  in this technical sense does not have to make sense in English (or in any
+  other language, for that matter). As far as mush functions and commands
+  are concerned, this is a perfectly good sentence:
+
+        Foozle 09blert bar baz foo.
+
+  See also: string functions
+& %
+& SUBSTITUTIONS
+  The % symbol is used in MUSH commands to indicate a substitution -- some
+  other character(s) or words are substituted for whatever follows the % 
+  symbol. Some common substitutions are:
+
+     %B = a single space (just like [space(1)])
+     %R = a blank line
+     %T = A tab. Note that this may not look right on some screens.
+     %# = dbref of the ENACTOR (object that set off the command)
+     %N = the ENACTOR's name, first letter capitalized
+     %n = the ENACTOR's name, first letter as-is
+     %~ = the ENACTOR's accented name
+     %: = the enactor's unique identifier, like objid(%#)
+
+(continued in help SUBSTITUTIONS2)
+& SUBSTITUTIONS2
+& %2
+  If the ENACTOR's gender is set, you can use these substitutions to get the
+  right pronoun for him/her:
+     %s = subjective pronoun: he, she, it, they
+     %o = objective pronoun: him, her, it, them
+     %p = possessive pronoun: his, her, its, their
+     %a = absolute possessive: his, hers, its, theirs.
+
+    Case makes a difference: %S will return He, She, It, They. If you need
+    an actual % symbol, use %% to get it.
+
+  Some attributes can be retrieved via substitutions:
+     %va-%vz = the contents of the object's VA-VZ attributes, respectively
+     %wa-%wz, %xa-%xz = as above, for WA-WZ and XA-XZ
+
+(continued in help substitutions3)
+& SUBSTITUTIONS3
+& %3
+  Other substitutions:
+     %0-%9   = the contents of the REGISTERS 0-9, respectively
+     %@ = the caller's dbref number. Initially same as %#, changes when 
+          something like a U-FUNCTION is called.
+     %! = the dbref number of the object the command is on.
+     %L = the dbref of the ENACTOR's location.
+     %c = text of the last command, _before_ evaluation.
+     %? = The current function invocation and depth counts.
+    %qN = the equivalent of r(N), a register set by a setq() function.
+(continued in help substitutions4)
+& SUBSTITUTIONS4
+& %4
+  Example: 
+
+  @sex me=male
+  @drop box=%N just dropped %p box.
+  drop box
+  > Cyclonus just dropped his box.
+ Let's say that Cyclonus's dbref number is #10 and the box's dbref
+ number is #11. The dbref # of the room Cyclonus is standing in is
+ #13.  When Cyclonus dropped the box above, these were the values of
+ the following %-variables:
+    
+  %N = Cyclonus
+  %# = #10
+  %@ = #10
+  %! = #11
+  %L = #13
+
+  See also: EVALUATION, ENACTOR, EXECUTOR, DBREFS, v()
+& SUCCESS
+  A "success" normally occurs when you attempt to do something that is
+  restricted by an @lock and you pass the @lock. (Note that if no lock
+  is set, you automatically pass it.) For example, the "basic" lock
+  restricts who can pick up a player/thing or who can go through an
+  exit. Whenever you successfully do either of these things, you will
+  set off the basic success messages on the object whose lock you have
+  just successfully passed.
+
+  Many other actions can also be locked -- see @lock and locktypes for 
+  more information. Many of these actions have standard attributes that
+  you can set messages in for when someone succeeds.
+
+  See also: FAILURE, @lock, VERBS, ATTRIBUTES, @success, @asuccess, @osuccess
+& SWITCHES
+  SWITCHES
+  Commands can have "switches" which modify the behavior of the
+  command. Switches are attached after the end of a command.
+  For example, most people are familiar with the command
+
+    @lock me=me
+
+  The "enter" switch to @lock allows you to lock who can enter:
+
+    @lock/enter me=me
+
+  A command may have multiple switches:
+
+    @pemit/noeval/silent me=Hi!
+
+  Help on the switches available for a command is available in the help
+  file for that command.
+  (If you are looking for information on @switch, see help @switch instead.)
+& TYPES OF OBJECTS
+  
+  Everything on a MUSH is an object in the MUSH database. There are four
+  types of objects: players, rooms, exits, things. The first three are
+  separated from each other by being set with a special FLAG: Player,
+  Room, Exit. Any object that doesn't have one of these flags is a thing.
+
+  Unique Characteristics
+  PLAYERS
+    Can own other objects and can be connected to. Can receive @mail.
+    Can move around, speak/pose/emit, enter MUSH commands, enter global
+    commands. You can have $-commands and ^-patterns on a player.
+    Players can be carried, can carry other objects, and can follow.
+  ROOMS
+    Fixed container objects, linked together by exits. Cannot move.
+    Rooms can @emit and enter MUSH commands, but they cannot execute
+    global commands. You can have $-commands and ^-patterns on a room.
+    
+(continued in help TYPES2)
+& TYPES2
+  EXITS
+    Objects that link rooms and things together. Cannot move, but can
+    be @teleport-ed to a new location. Exits can @emit and enter MUSH
+    commands, but they cannot execute global commands. You can NOT 
+    have $-commands and ^-patterns on exits. Exits can lead TO things,
+    but they can only lead FROM rooms. 
+  THINGS
+    Can move around, speak/pose/emit, enter MUSH commands, enter global
+    commands. Can send @mail as themselves. You can have $-commands and 
+    ^-patterns on things. Things can carry, be carried, and can follow.
+
+  See also: EXITS, USER-DEFINED COMMANDS, LISTENING, GLOBALS
+& $-COMMANDS
+& MACROS
+& USER-DEFINED COMMANDS
+  User-defined commands can be created by setting $-commands on players, 
+  things, and rooms. Exits cannot have $-commands. To set a $-command:
+
+    &<attribute> <object>=$<command name>:<action list>
+  
+  Whenever someone in the same room as the object types the command
+  name, the action list is carried out by the object, as long as:
+
+  - the person typing the command passes the object's @lock/use
+    and @lock/command
+  - the object is not set NO_COMMAND or HALT
+
+  Such attributes can also be @triggered as if the $<command name>:
+  did not exist.
+
+  It is recommended that <command name> not begin with "@", as the
+  command parser treats @ specially and may cause your command to fail
+  if the name might also match an attribute name. Conventionally,
+  global commands are often named with the "+" prefix.
+  
+(continued in help user-defined2)
+& $-COMMANDS2
+& MACROS2
+& USER-DEFINED2
+  Any number of *'s and ?'s may be in the command name. A * matches
+  any number of characters, and a ? matches any single character.  When
+  the actions are executed, the values on the stack in %0-%9 are the
+  portions of what the user types that match the first ten *'s or ?'s.
+  You can also match a regular expression rather than wildcards.
+  See 'help regexps' for details.
+
+  For example, to make a 'wave' command, you could do the following:
+    &DO_WAVE me=$wave *:pose {waves to %0.}
+  You could then type:
+        > wave Guest
+        Rhyanna waves to Guest.
+
+  If a command would match, but the enactor can't pass the lock, the
+  object may define generic failure behavior by setting the 
+  COMMAND_LOCK`FAILURE, COMMAND_LOCK`OFAILURE, and/or COMMAND_LOCK`AFAILURE
+  attributes. These are triggered on all objects with matching commands
+  and failing locks, but only if no command successfully matched,
+  and take the place of the usual "Huh?" message.
+
+  *BE SURE TO @LOCK/USE ME==ME IF YOU SET MACROS ON YOURSELF!*
+
+  See also: STACK, SUBSTITUTIONS, @lock
+& VERBS
+  For most verbs there are three forms: Verb (what the Enactor sees),
+  Overb (what others in the area see) and Averb (the action to be 
+  taken when the event happens). Example: @Drop, @Odrop and @Adrop
+& WARNINGS
+
+  If the building warning system has been enabled in the source code,
+  players may receive regular warnings about potential building problems
+  on objects that they own, and will be able to check individual objects
+  for warnings.
+
+  For more information, see the following help topics:
+    @warnings        @wcheck         NO_WARN         warnings list
+    
+& WARNINGS LIST
+  The building warning system, if enabled, supports the following
+  types of warnings:
+
+  exit-unlinked         Warn on unlinked exits
+  exit-oneway           Warn on exits with no return exit
+  exit-multiple         Warn on multiple exits from A to B
+  exit-msgs             Warn on missing succ/osucc/odrop/fail
+  exit-desc             Warn on missing description
+  room-desc             Warn on missing description
+  thing-msgs            Warn on missing succ/osucc/odrop/fail
+  thing-desc            Warn on missing description
+  my-desc               Warn on missing player description
+  lock-checks           Warn on @lock problems
+
+(continued in help warnings list2)
+& WARNINGS LIST2
+  These warnings combine the functionality of multiple warnings above:
+
+  serious               exit-unlinked, thing-desc, room-desc, my-desc,
+                        lock-checks
+  normal                serious, exit-oneway, exit-multiple, exit-msgs
+  extra                 normal, thing-msgs
+  all                   all of the above
+
+  The warning "none" indicates no warnings.
+  You can exclude warnings from a larger list by using !<warning>
+  after the larger list. For example: @warnings me=all !exit-oneway
+& WILDCARDS
+  PennMUSH has two standard wildcards in user-defined commands:
+  an asterisk (*) matches any string, and a question mark (?) matches
+  a single character.  For example, let's say that you want a command
+  called "supercalifragalisticexpealidocious" (don't ask me why), but you
+  don't want to force people to type the whole thing to trigger the command.
+  You could use a wildcard in the command trigger to match substrings of it:
+
+  &TOO_LONG_CMD object=$supercali*:@emit whee
+  super
+  > (nothing happens)
+  supercali
+  > whee
+  supercalifra
+  > whee
+  supercalifragalisticexpealidocious
+  > whee
+  supercalifoobert
+  > whee
+
+  A backslash (\) can be used to escape * and ? if you want to match 
+  a literal asterisk or question mark.
+  See also: USER-DEFINED COMMANDS, REGEXP
+& ZONE MASTER ROOMS
+& ZMR
+  
+  Zone master rooms are a subset of zones. If a room is used as a zone
+  master, it is a zone master room (ZMR). ZMRs are like local "master"
+  rooms. Exits in the ZMR are global to that zone, and $commands on
+  objects in the ZMR are global to that zone ($commands on the ZMR itself,
+  like $commands on the master room, are ignored). If a ZMR is a player's
+  personal zone, objects in the ZMR are checked for commands that the 
+  player can use anywhere (but exits are not checked unless the player
+  is in a zoned room).
+
+  Zone master rooms are only defined if globals are used.  Zone master
+  rooms are best used for very large zones which have a lot of global
+  exits, or for zones with restricted commands that can go on a separate
+  use-locked object from general ones.
+
+  See also: ZONES, MASTER ROOM, EVALUATION
+& ZONE MASTERS
+& ZMP
+& SHARED PLAYERS
+  SHARED PLAYERS
+  Shared players are player objects which are used to mediate shared
+  control of objects. A shared player is an object of type PLAYER which
+  has the SHARED flag set. They are created like ordinary players, and
+  can connect, build, etc.  The only difference is that objects owned by
+  a shared player are controlled by anything that passes the @lock/zone
+  of the shared player. 
+  
+  Anyone who passes the @lock/zone of the shared player can @chown objects
+  to it. This, however, does not refund the original creator's money or
+  quota, as does normal.
+
+  Shared players used to be known as Zone Masters.  The term was changed
+  to emphasize the fact that they are not related to zone master objects,
+  which are used to affect search order for user-defined commands.
+
+  Continued in HELP SHARED PLAYERS2
+& SHARED PLAYERS2
+  Some suggested uses of shared players:
+  
+    1. If you are working on a building project with several people, it
+       may be useful to create a shared player and @lock/zone it to all of
+       you.  That way, all of the players working on the project will be
+       able to modify the building, as long as the shared player owns all
+       the objects being built.
+    
+    2. If local wizards are desired, a shared player may be created and zone
+       locked to the local wizards. Players building within that zone should
+       be @chowning to the shared player, or logged in as it while creating
+       objects. The local wizard will then be able to control anything within
+       that domain as long as the object in question is owned by the shared
+       player.
+& ZONES  
+& ZONE OBJECTS
+& ZONE MASTER OBJECTS
+& ZMO
+  Zones are areas of the MUSH that can have the same user-defined commands
+  without having to @parent every object in the zone or make the commands
+  mush-wide globals.
+
+  The default zone is NOTHING. Any building done by a player defaults 
+  to belonging to the same zone that the player belongs to.
+  Every zone is defined by a Zone Master Object (ZMO). The ZMO is an
+  ordinary MUSH object owned by some player. A wizard may change the
+  zone of an object or player to a ZMO.
+  
+  If the ZMO is a room, it is called a "zone master room." Most of the
+  statements about ZMOs also apply to zone master rooms; for details,
+  see the help topic ZONE MASTER ROOMS.
+  
+  See "help ZONES2" for more.
+& ZONES2
+  $commands on a ZMO are treated as global within that zone.
+  The game attempts to match $commands for the ZMO of the player's
+  location, as well as $commands for the player's own zone.
+
+  If you want restricted global commands defined over only a small area,
+  you can define that area to be part of a zone, and place the desired
+  $commands upon the ZMO. If you want players to be able to use special
+  commands for a culture they belong to, the $commands should go on the
+  ZMO, and the players @chzoned to it so they can use the commands
+  anywhere.
+
+  See also: @chzone, SHARED PLAYERS
+& matching
+  Matching is the process the MUSH uses to determine which object you
+  mean when you try to do something with an object. Different commands
+  do matching in different ways, but most will allow you to specify
+  an object as:
+    * its dbref (#7)
+    * its full name (Box of chocolates)
+    * part of any word in its name, if nothing else shares that part (Box)
+    * me (yourself)
+    * here (the room you're in)
+  Usually, you can also qualify an object with an adjective to help
+  the MUSH determine which object you mean.  Adjectives include:
+    * my <obj> - an object you're carrying
+    * this <obj> - an object in your location (also: this here <obj>)
+    * toward <exit> - an exit in your location
+    * 1st, 2nd, etc. <obj> - one of a set of objects with the same names.
+      Objects are ordered in the order in which they're listed in your
+      inventory, room contents, and exit list (in that order). If there
+      aren't enough objects, this will fail.
+   You may combine some adjectives (my 1st box, this here 2nd box).
+& &HELP
+This is the AHELP index.
+& RESTRICT
+
+ Commands and functions can have their permission levels controlled
+ in the mush config files, or by wizards from the game via @command
+ and @function.
+ In the config file, the syntax is:
+  restrict_command command-name restriction [" <error message>]
+  restrict_function function-name restriction
+
+ From the game:
+  @command/restrict command-name=restriction [" <error message>]
+  @function/restrict function-name=restriction
+For commands, if <error message> is given, that message is sent to the
+player who runs it instead of a generic, unhelpful error message.
+
+(Continued in restrict2)
+& RESTRICT2
+ <restriction> can be one of the following:
+   god        Command or function is usuable only by God.
+   wizard     Usable only by wizards.
+   admin      Usable only by Wiz/Roy.
+   nogagged   Usable only by non-GAGGED objects.
+   nofixed    Usable only by non-FIXED objects.
+   noguest    Usable only by non-guest @powered objects.
+   nobody     Nothing can use it. Same as the /disable switch
+              to @command or @function.
+   noparse    Function arguments are not evaluated. Only applies
+              to @functions.
+
+ Commands can also use the 'noplayer' restriction, which stops
+ player objects from using the command, as well as any generic <flag>
+ or any <power>.
+
+(Continued in restrict3)
+& RESTRICT3
+ In cases where there are a function and command that do the same thing
+ (Like pemit() and @pemit), the command's restrictions are also checked
+ when the function is called, so restricting @pemit also restricts
+ pemit(). However, a function's restrictions are not checked when a
+ command is called, to allow disabling side-effect functions.
+
+ Some functions (Like name()) have non-side-effect and side-effect versions
+ depending on how many arguments they're called with. The side-effect
+ version can be disabled while keeping the safe non-side-effect form with
+ the 'nosidefx' restriction. This can also be used to disable pure side-effect
+ functions.
+
diff --git a/game/txt/hlp/index.hlp b/game/txt/hlp/index.hlp
new file mode 100644 (file)
index 0000000..4c7a395
--- /dev/null
@@ -0,0 +1,588 @@
+
+& Entries
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ "                         #lambda                   $-commands               
+ $-commands2               %                         %!                       
+ %#                        %:                        %n                       
+ %~                        +                         1.50p6                   
+ 1.50p7                    1.50p8                    1.50p9                   
+ 1.50p10                   1.50p11                   1.50p12                  
+ 1.50p13                   1.50p14                   1.50pdune1               
+ 1.50pdune2                1.6.0p0                   1.6.0p3                  
+ 1.6.0p4                   1.6.0p5                   1.6.1p0                  
+ 1.6.1p1                   1.6.10p0                  1.6.10p1                 
+ 1.6.10p2                  1.6.10p3                  1.6.10p4                 
+ 1.6.10p5                  1.6.10p6                  1.6.2p0                  
+ 1.6.2p1                   1.6.3p0                   1.6.3p1                  
+ 1.6.3p2                   1.6.3p3                   1.6.3p4                  
+ 1.6.3p5                   1.6.3p6                   1.6.3p7                  
+ 1.6.4p0                   1.6.4p1                   1.6.4p2                  
+
+For more, see Entries-2
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-2
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ 1.6.5p0                   1.6.5p1                   1.6.5p2                  
+ 1.6.5p3                   1.6.5p4                   1.6.6p0                  
+ 1.6.7p0                   1.6.7p1                   1.6.7p2                  
+ 1.6.7p3                   1.6.7p4                   1.6.7p5                  
+ 1.6.7p6                   1.6.8p0                   1.6.8p1                  
+ 1.6.9p0                   1.6.9p1                   1.6.9p2                  
+ 1.6.9p3                   1.6.9p4                   1.6.9p5                  
+ 1.6.9p6                   1.6.9p7                   1.6.9p8                  
+ 1.6.9p9                   1.7.0p0                   1.7.0p1                  
+ 1.7.0p2                   1.7.0p3                   1.7.0p4                  
+ 1.7.0p5                   1.7.0p6                   1.7.0p7                  
+ 1.7.0p8                   1.7.0p9                   1.7.0p10                 
+ 1.7.0p11                  1.7.1p0                   1.7.1p1                  
+ 1.7.1p2                   1.7.1p3                   1.7.2p0                  
+ 1.7.2p1                   1.7.2p2                   1.7.2p3                  
+ 1.7.2p4                   1.7.2p5                   1.7.2p6                  
+
+For more, see Entries-3
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-3
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ 1.7.2p7                   1.7.2p8                   1.7.2p9                  
+ 1.7.2p10                  1.7.2p11                  1.7.2p12                 
+ 1.7.2p13                  1.7.2p14                  1.7.2p15                 
+ 1.7.2p16                  1.7.2p17                  1.7.2p18                 
+ 1.7.2p19                  1.7.2p20                  1.7.2p21                 
+ 1.7.2p22                  1.7.2p23                  1.7.2p24                 
+ 1.7.2p25                  1.7.2p26                  1.7.2p27                 
+ 1.7.2p28                  1.7.2p29                  1.7.2p30                 
+ 1.7.2p31                  1.7.2p32                  1.7.2p33                 
+ 1.7.2p34                  1.7.2p35                  1.7.3p0                  
+ 1.7.3p1                   1.7.3p2                   1.7.3p3                  
+ 1.7.3p4                   1.7.3p5                   1.7.3p6                  
+ 1.7.3p7                   1.7.3p8                   1.7.3p9                  
+ 1.7.3p10                  1.7.3p11                  1.7.3p12                 
+ 1.7.3p13                  1.7.3p14                  1.7.4p0                  
+ 1.7.4p1                   1.7.4p2                   1.7.4p3                  
+
+For more, see Entries-4
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-4
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ 1.7.4p4                   1.7.4p5                   1.7.4p6                  
+ 1.7.4p7                   1.7.4p8                   1.7.4p9                  
+ 1.7.4p10                  1.7.4p11                  1.7.4p12                 
+ 1.7.4p13                  1.7.4p14                  1.7.4p15                 
+ 1.7.4p16                  1.7.4p17                  1.7.4p18                 
+ 1.7.4p19                  1.7.4p20                  1.7.5p0                  
+ 1.7.5p1                   1.7.5p2                   1.7.5p3                  
+ 1.7.5p4                   1.7.5p5                   1.7.5p6                  
+ 1.7.5p7                   1.7.5p8                   1.7.5p9                  
+ 1.7.5p10                  1.7.5p11                  1.7.5p12                 
+ 1.7.6p0                   1.7.6p1                   1.7.6p2                  
+ 1.7.6p3                   1.7.6p4                   1.7.6p5                  
+ 1.7.6p6                   1.7.6p7                   1.7.6p8                  
+ 1.7.6p9                   1.7.6p10                  1.7.6p11                 
+ 1.7.6p12                  1.7.6p13                  1.7.6p14                 
+ 1.7.6p15                  1.7.6p16                  1.7.7p0                  
+
+For more, see Entries-5
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-5
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ 1.7.7p1                   1.7.7p2                   1.7.7p3                  
+ 1.7.7p4                   1.7.7p5                   1.7.7p6                  
+ 1.7.7p7                   1.7.7p8                   1.7.7p9                  
+ 1.7.7p10                  1.7.7p11                  1.7.7p12                 
+ 1.7.7p13                  1.7.7p14                  1.7.7p15                 
+ 1.7.7p16                  1.7.7p17                  1.7.7p18                 
+ 1.7.7p19                  1.7.7p20                  1.7.7p21                 
+ 1.7.7p22                  1.7.7p23                  1.7.7p24                 
+ 1.7.7p25                  1.7.7p26                  1.7.7p26                 
+ 1.7.7p27                  1.7.7p28                  1.7.7p29                 
+ 1.7.7p30                  1.7.7p31                  :                        
+ ;                         @-attributes              @-building               
+ @-director                @-general                 @@                       
+ @@()                      @aahear                   @abuy                    
+ @aclone                   @aconnect                 @adescribe               
+ @adestroy                 @adisconnect              @adrop                   
+
+For more, see Entries-6
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-6
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ @aefail                   @aenter                   @afailure                
+ @afollow                  @agive                    @ahear                   
+ @aidescribe               @aleave                   @alfail                  
+ @alias                    @allhalt                  @allquota                
+ @amhear                   @amove                    @announce                
+ @apayment                 @areceive                 @asuccess                
+ @atport                   @atrchown                 @atrlock                 
+ @attribute                @aufail                   @aunfollow               
+ @aunidle                  @ause                     @away                    
+ @azenter                  @azleave                  @boot                    
+ @break                    @buy                      @cemit                   
+ @channel                  @channel2                 @channel3                
+ @channel4                 @channel5                 @charges                 
+ @chat                     @chown                    @chownall                
+ @chzone                   @chzone2                  @chzoneall               
+ @clock                    @clock2                   @clone                   
+
+For more, see Entries-7
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-7
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ @command                  @command2                 @command3                
+ @comment                  @config                   @config attribs          
+ @config chat              @config cmds              @config cosmetic         
+ @config cosmetic2         @config costs             @config db               
+ @config dump              @config funcs             @config limits           
+ @config limits2           @config limits3           @config log              
+ @config net               @config parameters        @config tiny             
+ @conformat                @cost                     @cpattr                  
+ @create                   @dannounce                @dbck                    
+ @decompile                @decompile2               @decompile3              
+ @descformat               @describe                 @destroy                 
+ @destroy2                 @dig                      @dig2                    
+ @disable                  @division                 @doing                   
+ @dolist                   @drain                    @drop                    
+ @dump                     @ealias                   @edit                    
+ @efail                    @elock                    @emit                    
+
+For more, see Entries-8
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-8
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ @empower                  @enable                   @enter                   
+ @entrances                @eunlock                  @exitformat              
+ @failure                  @filter                   @filter2                 
+ @find                     @firstexit                @flag                    
+ @flag2                    @follow                   @force                   
+ @force2                   @forwardlist              @function                
+ @function2                @function3                @function4               
+ @gedit                    @give                     @grep2                   
+ @grep                     @halt                     @haven                   
+ @hide                     @hook                     @hook2                   
+ @idescformat              @idescribe                @idle                    
+ @infilter                 @inprefix                 @kick                    
+ @lalias                   @lastactivity             @leave                   
+ @lemit                    @level                    @lfail                   
+ @link                     @list                     @listen                  
+ @listen2                  @listmotd                 @lock                    
+
+For more, see Entries-9
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-9
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ @lock2                    @lock3                    @lock4                   
+ @lock5                    @lock6                    @lock7                   
+ @lock8                    @lock9                    @log                     
+ @logwipe                  @lset                     @map2                    
+ @mail                     @malias                   @malias2                 
+ @malias3                  @malias4                  @malias5                 
+ @map                      @motd                     @move                    
+ @mvattr                   @name                     @nameaccent              
+ @nameformat               @newpassword              @notify                  
+ @nsemit                   @nslemit                  @nsoemit                 
+ @nspemit                  @nsremit                  @nszemit                 
+ @nuke                     @obuy                     @odescribe               
+ @odrop                    @oefail                   @oemit                   
+ @oenter                   @ofailure                 @ofollow                 
+ @ogive                    @oidescribe               @oleave                  
+ @olfail                   @omove                    @opayment                
+
+For more, see Entries-10
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-10
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ @open                     @oreceive                 @osuccess                
+ @otport                   @oufail                   @ounfollow               
+ @ouse                     @oxenter                  @oxleave                 
+ @oxmove                   @oxtport                  @ozenter                 
+ @ozleave                  @parent                   @password                
+ @payment                  @pcreate                  @pemit                   
+ @pemit2                   @poll                     @poor                    
+ @power                    @powergroup2              @powergroup              
+ @prefix                   @pricelist                @program                 
+ @ps                       @purge                    @quota                   
+ @readcache                @receive                  @reclass                 
+ @recycle                  @rejectmotd               @remit                   
+ @restart                  @runout                   @rwall                   
+ @scan                     @search                   @search2                 
+ @search3                  @select                   @set                     
+ @sex                      @shutdown                 @sitelock                
+
+For more, see Entries-11
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-11
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ @sitelock2                @sitelock3                @sql                     
+ @squota                   @startup                  @stats                   
+ @su                       @success                  @sweep                   
+ @switch                   @switch2                  @teleport                
+ @tport                    @trigger                  @trigger2                
+ @ufail                    @ulock                    @undestroy               
+ @unfollow                 @unlink                   @unlock                  
+ @unrecycle                @uptime                   @uptime2                 
+ @use                      @uunlock                  @verb                    
+ @verb2                    @verb3                    @verb4                   
+ @version                  @vrml_url                 @wait                    
+ @wait2                    @wall                     @warnings                
+ @warnings2                @warnings3                @wcheck                  
+ @whereis                  @wipe                     @wizmotd                 
+ @wizwall                  @zemit                    @zenter                  
+ @zleave                   \                         ]                        
+
+For more, see Entries-12
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-12
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ ^                         abode                     abs()                    
+ accent()                  accent2                   accent3                  
+ accent4                   accname()                 acos()                   
+ action lists              action2                   add()                    
+ after()                   agenda                    ahelp                    
+ align()                   align2                    allof()                  
+ alphamax()                alphamin()                ancestors                
+ and()                     andflags()                andlflags()              
+ anews                     anonymous attributes      anonymous2               
+ anonymous3                anonymous4                ansi                     
+ ansi()                    aposs()                   art()                    
+ asin()                    atan()                    atan2()                  
+ atrlock()                 attr trees                attr trees2              
+ attr trees3               attr trees4               attrcnt()                
+ attrib-ownership          attribute flags           attribute functions      
+ attribute list            attribute trees           attribute trees2         
+
+For more, see Entries-13
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-13
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ attribute trees3          attribute trees4          attributes               
+ attributes list           attributes2               attributes3              
+ attributes4               audible                   audible2                 
+ band()                    baseconv()                beep()                   
+ before()                  bitwise functions         bnand()                  
+ bnot()                    boolean functions         boolean values           
+ boolean2                  boolean3                  bor()                    
+ bound()                   brackets()                break()                  
+ brief                     builder                   buy                      
+ bxor()                    cand()                    capstr()                 
+ case()                    caseall()                 cat()                    
+ cd                        ceil()                    cemit()                  
+ center()                  cflags()                  ch                       
+ changes                   channel functions         channel-list             
+ channels()                chat                      checkpass()              
+ children()                chown_ok                  chr()                    
+
+For more, see Entries-14
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-14
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ clients                   clock()                   clone()                  
+ cloudy                    cmds()                    cobramush                
+ code                      color                     commands                 
+ communication functions   comp()                    comsys                   
+ con()                     config()                  conn()                   
+ connected                 connection functions      contact                  
+ control                   controls()                convsecs()               
+ convtime()                convutcsecs()             copyright                
+ copyrite                  cor()                     cos()                    
+ costs                     cowner()                  create()                 
+ credits                   ctime()                   ctitle()                 
+ ctu()                     cv                        cwho()                   
+ dark                      dark2                     database                 
+ dbref #                   dbref functions           dbref number             
+ dbref2                    dbrefs                    debug                    
+ debug2                    dec()                     decrypt()                
+
+For more, see Entries-15
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-15
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ default()                 delete()                  desert                   
+ destroy_ok                die()                     dig()                    
+ digest()                  dismiss                   dist2d()                 
+ dist3d()                  div()                     division                 
+ division commands         division credits          division functions       
+ division powers           division powers2          division powers3         
+ division system           division tutorial         division tutorial2       
+ division tutorial3        division()                divscope                 
+ divscope()                doing                     doing()                  
+ downdiv()                 download                  drop                     
+ drop-tos                  dynhelp()                 e()                      
+ edefault()                edit()                    element()                
+ elements()                elist()                   elock()                  
+ emit()                    empire objects            empire()                 
+ empty                     enactor                   encrypt()                
+ endtag()                  enter                     enter_ok                 
+
+For more, see Entries-16
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-16
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ entrances()               eq()                      escape()                 
+ etimefmt()                etimefmt2                 etimefmt3                
+ eval()                    evaluation order          evaluation2              
+ events                    examine                   executor                 
+ exit()                    exits                     exits2                   
+ exp()                     extract()                 failure                  
+ fdiv()                    filter()                  filterbool()             
+ findable()                first()                   firstof()                
+ fixed                     flag list                 flag permissions         
+ flags                     flags list                flags()                  
+ flags2                    flip()                    floating                 
+ floor()                   floordiv()                fmod()                   
+ fold()                    fold2                     folderstats()            
+ follow                    followers()               following()              
+ foreach()                 foreach2                  fraction()               
+ fullname()                function list             functions                
+
+For more, see Entries-17
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-17
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ functions()               functions2                gagged                   
+ gender                    get                       get()                    
+ get_eval()                give                      global commands          
+ globals                   go                        going                    
+ goto                      grab()                    graball()                
+ grep()                    gt()                      gte()                    
+ guests                    halt                      hasattr()                
+ hasattrp()                hasattrpval()             hasattrval()             
+ hasdivpower()             hasflag()                 haspower()               
+ haspowergroup()           hastype()                 haven                    
+ height()                  help                      here                     
+ hidden()                  home                      home()                   
+ homes                     host()                    hostname()               
+ html                      html functions            html()                   
+ i18n                      icfuncs                   idle                     
+ idle()                    idle_average()            idle_total()             
+
+For more, see Entries-18
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-18
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ idlesecs()                if()                      ifelse()                 
+ ilev()                    iname()                   inc()                    
+ index                     index()                   indiv()                  
+ indivall()                info                      information functions    
+ informative topics        inherit                   inherit2                 
+ insert()                  interiors                 interiors2               
+ internationalization      inum()                    inventory                
+ ipaddr()                  isdaylight()              isdbref()                
+ isint()                   isnum()                   isword()                 
+ itemize()                 items()                   iter()                   
+ iter2                     itext()                   join                     
+ judge                     jump_ok                   jury_ok                  
+ kickass                   kickass credits           know                     
+ know administration       know functions            know system              
+ lambda                    last & lastlogout         last()                   
+ lastip                    lastsite                  lattr()                  
+
+For more, see Entries-19
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-19
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ lcon()                    lcstr()                   ldelete()                
+ leave                     left()                    lemit()                  
+ level()                   lexits()                  lflags()                 
+ license                   light                     link()                   
+ link_ok                   linking                   list functions           
+ list()                    listen_parent             listening                
+ listening2                listening3                lists                    
+ lit()                     ljust()                   lmath()                  
+ ln()                      lnum()                    loc()                    
+ locale                    localize()                locate()                 
+ locate2                   lock list                 lock types               
+ lock types2               lock()                    locking                  
+ locklist                  locks                     locktypes                
+ locktypes2                log()                     logout                   
+ look                      look2                     looping                  
+ lparent()                 lplayers()                lports()                 
+
+For more, see Entries-20
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-20
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ lpos()                    lsearch()                 lsearch2                 
+ lsearchr()                lstats()                  lt()                     
+ lte()                     lvcon()                   lvexits()                
+ lvplayers()               lwho()                    macros                   
+ macros2                   mail                      mail functions           
+ mail()                    mail-admin                mail-folders             
+ mail-other                mail-reading              mail-sending             
+ maildstats()              mailfrom()                mailfstats()             
+ mailstats()               mailstatus()              mailsubject()            
+ mailtime()                map()                     master room              
+ match()                   matchall()                matching                 
+ math functions            max()                     me                       
+ mean()                    median()                  member()                 
+ merge()                   mid()                     min()                    
+ mistrust                  mix()                     mix2                     
+ mod()                     modulo()                  modulus()                
+
+For more, see Entries-21
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-21
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ money                     money()                   monitor                  
+ mortal                    move                      mtime()                  
+ mudname()                 mul()                     multi-guests             
+ munge()                   munge2                    munge3                   
+ mushcode                  mushcode topics           mushcode2                
+ mwho()                    myopic                    name()                   
+ nand()                    nattr()                   nearby()                 
+ neq()                     new locks                 newbie                   
+ newbie2                   newbie3                   news                     
+ next()                    no_command                no_leave                 
+ no_tel                    no_warn                   noaccents                
+ noleave                   non-standard attributes   nor()                    
+ nospoof                   not()                     nowarn                   
+ null()                    num()                     obj()                    
+ object parents            objeval()                 objid()                  
+ objmem()                  oemit()                   on-vacation              
+
+For more, see Entries-22
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-22
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ opaque                    open()                    or()                     
+ ord()                     orflags()                 orlflags()               
+ orphan                    outputprefix              outputsuffix             
+ owner()                   page                      page2                    
+ paranoid                  parent                    parent()                 
+ parents                   parents2                  parents3                 
+ parents4                  parents5                  parse()                  
+ patchlevels               pcreate()                 pemit()                  
+ pennmush changes          pennmush credits          pi()                     
+ pickrand()                player                    playermem()              
+ pmatch()                  poll()                    ports()                  
+ pos()                     pose                      pose2                    
+ poss()                    pow_inherit               power types              
+ power yescodes            power()                   powergroups()            
+ powers list               powers list2              powers list3             
+ powers()                  powers2                   powers3                  
+
+For more, see Entries-23
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-23
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ powover()                 privilege                 program()                
+ pueblo                    pueblo features           pueblo()                 
+ puppet                    puppets                   puppets2                 
+ queue                     quiet                     quit                     
+ quitprog()                quota()                   r()                      
+ r-function                rand()                    randword()               
+ read                      recv()                    regedit()                
+ regeditall()              regeditalli()             regediti()               
+ regexp                    regexp classes            regexp classes2          
+ regexp examples           regexp syntax             regexp syntax2           
+ regexp syntax3            regexp syntax4            regexp syntax5           
+ regexp syntax6            regexp syntax7            regexp syntax8           
+ regexps                   regexps2                  registers                
+ registers2                regmatch()                regmatchi()              
+ regrab()                  regraball()               regraballi()             
+ regrabi()                 regrep()                                           
+
+For more, see Entries-24
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-24
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ regular expression functions                        remainder()              
+ remit()                   remove()                  repeat()                 
+ replace()                 rest()                    restarts()               
+ restarttime()             restrict                  restrict2                
+ restrict3                 reswitch()                reswitchall()            
+ reswitchalli()            reswitchi()               reverse()                
+ revwords()                right()                   rjust()                  
+ rloc()                    rnum()                    room                     
+ room()                    root()                    round()                  
+ royalty                   rquota                    rules                    
+ s()                       s-@announce               s-@dannounce             
+ s-@reclass                s-function                s-join                   
+ s-summon                  safe                      say                      
+ scan()                    score                     scramble()               
+ screenheight              screenwidth               search()                 
+ search2                   secs()                    secure()                 
+
+For more, see Entries-25
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-25
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ semaphores                semaphores2               semaphores3              
+ semaphores4               semaphores5               semaphores6              
+ semipose                  sent()                    session                  
+ set()                     setdiff()                 setinter()               
+ setq()                    setq2                     setq3                    
+ setr()                    setting-attributes        setunion()               
+ sex                       sha0()                    shared                   
+ shared players            shared players2           shl()                    
+ shr()                     shuffle()                 sign()                   
+ signal()                  sin()                     softcode                 
+ sort()                    sortby()                  soundex()                
+ soundex2                  soundlike()               soundslike()             
+ space()                   spellnum()                splice()                 
+ spoofing                  sql functions             sql()                    
+ sqlescape()               sqrt()                    squish()                 
+ ssl()                     stack                     starttime()              
+
+For more, see Entries-26
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-26
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ stats()                   stddev()                  step2                    
+ step()                    sticky                    strcat()                 
+ string functions          strings                   strinsert()              
+ stripaccents()            stripansi()               strlen()                 
+ strmatch()                strreplace()              sub()                    
+ subj()                    substitutions             substitutions2           
+ substitutions3            substitutions4            success                  
+ summon                    suspect                   switch wildcards         
+ switch()                  switch2                   switchall()              
+ switches                  t()                       table()                  
+ tag()                     tagwrap()                 take                     
+ tan()                     teach                     tel()                    
+ tel_ok                    temple                    terminfo()               
+ terse                     textfile()                think                    
+ time functions            time()                    timefmt()                
+ timefmt2                  timestring()              topics2                  
+
+For more, see Entries-27
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-27
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ topicszq                  tr()                      translation              
+ transparent               transparent2              trigger()                
+ trim()                    trunc()                   trust                    
+ trust2                    type()                    types of objects         
+ types2                    u()                       u2                       
+ ucstr()                   udefault()                ufun()                   
+ ufun2                     uldefault()               ulocal()                 
+ ulocal2                   unfindable                unfollow                 
+ unidle_times()            uninspected               unregistered             
+ updiv()                   use                       user-defined commands    
+ user-defined2             utctime()                 utility functions        
+ v()                       v-function                vadd()                   
+ val()                     valid()                   vcross()                 
+ vdim()                    vdot()                    verbose                  
+ verbs                     version()                 visible()                
+ visual                    vmag()                    vmax()                   
+
+For more, see Entries-28
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+& Entries-28
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ vmin()                    vmul()                    vrml                     
+ vsub()                    vunit()                   wait()                   
+ warnings                  warnings list             warnings list2           
+ where()                   whisper                   whisper2                 
+ who                       width()                   wildcards                
+ wipe()                    with                      wizard                   
+ wordpos()                 words()                   wrap()                   
+ xget()                    xor()                     z_tel                    
+ zemit()                   zfun()                    zmo                      
+ zmp                       zmr                       zone                     
+ zone master objects       zone master rooms         zone masters             
+ zone objects              zone()                    zones                    
+ zones2                                                                       
+
+& &Entries
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+ &help                                                                        
+-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
diff --git a/game/txt/hlp/pennv174.hlp b/game/txt/hlp/pennv174.hlp
new file mode 100644 (file)
index 0000000..2a79a6a
--- /dev/null
@@ -0,0 +1,716 @@
+& 1.7.4p20
+Version 1.7.4 patchlevel 20                   September 19, 2002
+
+Fixes:
+  * Help semaphores4 typo fixed by Mike Griffiths.
+  * Help cleanup. [TAP]
+  * See_All players now see hidden connected mail senders. Suggested
+    by Philip Mak.
+  * spellnum could introduce spurious spaces. Reported by Intrevis@M*U*S*H.
+  * table() sometimes produced spurious blank rows. Reported by
+    Nymeria@M*U*S*H. This is the first attempt at a fix.
+  * switch() help improved. [SW]
+  * enter <exit> no longer returns Permission denied, but 
+    "I can't see that here", as you can only enter things. 
+    Suggested by Philip Mak.
+  * A one-time, one-pointer memory leak in plyrlist.c fixed
+    by Vadiv@M*U*S*H.
+  * Unbalanced or empty double-quotes gave weird results in page 
+    and whisper. Reported by Vadiv@M*U*S*H. [SW]
+  * @chan/decomp no longer shows names of channels that the enactor
+    can't see. Reported by Nat@SW:ANT.
+  * The lock() and elock() functions now operate properly with
+    user:whatever locks. Reported by Mike Griffiths.
+  * pmatch() will locate hidden players if they're visible to you
+    because they're nearby. Suggested by Julian@M*U*S*H.
+  * regedit and other functions that used $-substitutions for subpatterns
+    could produce weird results in some cases. Reported by Bellemore@M*U*S*H
+
+
+& 1.7.4p19
+Version 1.7.4 patchlevel 19                   June 14, 2002
+
+Minor changes:
+  * Wizards can now unset the WIZARD flag on any thing, whether they
+    own it or not. Suggested by Cerekk@bDv.
+  * Circular zoning is no longer allowed. Fixes part of a bug reported
+    by Philip Mak. [SW]
+Fixes:
+  * Win32 portability fixes. [EEH]
+  * grep for 'grep' rather than 'egrep' in restart because in grep 2.5.1,
+    egrep is a shell script that exec's grep instead of a link.
+    Fix by Vadiv@M*U*S*H.
+  * The messages for a possessive get used the object's new location
+    instead of its old one. Fixed by Apollo@Restoration.
+  * Attempts by unlinked exits to @link an exit could crash. 
+  * %1 in @areceive was being set to the recipient rather than the giver.
+    Fixed. Report by jubjup@trekmush.org
+  * @uptime fix for Linux 2.4 kernel. [SW]
+  * The @@() function no longer complains about commas. Report by
+    Trispis@M*U*S*H. [TAP]
+  * @search flags=<flaglist> is now smarter about toggles with the same
+    letter and different types. Report by Philip Mak.
+  * English-style matching now ignores the type of object being matched.
+    This fixes a bug with, e.g., @link my 1st object=me reported by
+    Oriens@Alexandria.
+  * bound() now listed in the math functions list. Report by Dandy@M*U*S*H.
+  * Help fix for member() by Cerekk@bDV TrekMUSH
+  * The server can now transparently read mush.cnf (and included) files
+    that have Dos- or Mac-style line-endings. Suggested by Paleran.
+  * Crash bug in @search fixed. Reported by Philip Mak.
+
+& 1.7.4p18
+Version 1.7.4 patchlevel 18                   May 6, 2002
+
+Minor Changes:
+  * The Mersenne Twister RNG has been backported from 1.7.5 into
+    this patchlevel, as it is not susceptible to some bugs that could
+    cause the RNG to hang the server. Report by Bellemore@M*U*S*H.
+  * Improved detection of info_slave errors. Based on patch by CU5.
+  * Rooms and exits can now be @forced. Suggested by Philip Mak.
+  * Deleting files from the hlp/nws/etc. directories is now sufficient
+    to cause game/txt/Makefile to rebuild the txt files. Patch by
+    Philip Mak.
+  * A see_all player may now use @chan/decompile on any channel.
+    Previously, the ROYALTY flag was required. Suggested by Oriens@Alexandria.
+Fixes:
+  * The QUEUE and semaphore attributes aren't listed in @decompile
+    anymore. Suggested by Oriens@Alexandria. [SW]
+  * Several compiler warnings fixed. [SW]
+  * The LASTSITE and LASTIP attributes are now set locked and wizard by default,
+    to go along with the other connection-tracking attributes. [SW]
+  * Help on-vacation updated. Report by Nymeria@M*U*S*H.
+  * Help for following() function added. Report by Ashen-Shugar@Rhost.
+  * The last line of the access.cnf file sometimes wouldn't get read 
+    properly. [SW]
+  * lnum improperly cached its return values without consideration for
+    changes in separator, so subsequent lnums with different separators
+    broke. Reported by Rhysem and Ambrosia@M*U*S*H. [TAP]
+  * Failing to speak on a channel you can't see no longer reveals the
+    full channel name. Reported by Vadiv@M*U*S*H.
+  * Passing a negative argument to the space() function now returns
+    an error string instead of 8192 blanks. Patch by Myrddin.
+  * Improved messages for following/unfollowing an ambiguous object.
+    Reported by Philip Mak.
+
+
+& 1.7.4p17
+Version 1.7.4 patchlevel 17                   April 14, 2002
+
+Minor Changes:
+  * The on-vacation flag, if in use, is no longer automatically cleared
+    on connect. Instead, the player is warned on connect (and at each
+    dump) that their flag is set. Suggested by Nymeria@M*U*S*H.
+Fixes:
+  * Improved help for edit(). Suggested by Trispis@M*U*S*H [SW]
+  * List functions with null elements and a null output seperator could
+    trip end-of-buffer checks and stop in the middle of a list. [SW]
+  * valid() was inconsistent in how it handled attribute names with lower-case
+    letters compared to what everything else does. Reported by Philip Mak. [SW]
+  * @open could cause crashes in some unusual contexts. Reported
+    by Dandy@M*U*S*H.
+  * Improved sort()'s autodetection of list types. [SW]
+  * Fixed a problem with sorting dbrefs larger than the highest one in the 
+    db. [SW]
+  * Mac portability fixes. [DW]
+  * Help for @open clarified. Suggested by fil@M*U*S*H.
+  * Help for kill clarified. Suggested by Philip Mak.
+  * Channel titles can no longer contain newlines or beeps. 
+    Report by Nome@M*U*S*H.
+  * soundex behaved badly with extended characters. [SW]
+  * inc() and dec() now behave like the help says, regardless of whether
+    tiny_math is set or not. Their behavior on null strings and strings
+    that do not end in a digit depend on the null_eq_zero setting.
+    Reported by Wayne@PDX.
+  * The panic db file descriptor was never closed after reading a
+    panic dump. [SW]
+  * DOES removed from help attribute list. Suggested Philip Mak.
+  * Under no circumstances should connection to a null-named player
+    be allowed. Suggested by Wayne@PDX.
+  * 'with' no longer allows use of $commands on remote objects you
+    don't control. Report by Nammyung@M*U*S*H.
+
+& 1.7.4p16
+Version 1.7.4 patchlevel 16                   March 11, 2002
+
+Minor changes:
+  * After using 'register' at the connect screen, the player is
+    no longer disconnected. Suggested by Philip Mak.
+  * 'help mail functions'. Suggested by Trispis@M*U*S*H.
+  * Messages associated with drop, give, and get are now more
+    verbose and provide more information about who did what to whom.
+    Suggested by Philip Mak.
+  * Attrib locks did case-sensitive comparison of values, which is not
+    what you probably want. No longer. Reported by Philip Mak. [SW]
+  * QUEUE and sempahore attributes are now removed (not just emptied)
+    when drained or notified to 0. [TAP]
+Fixes:
+  * Improvements in handling ansi in string functions. [SW]
+  * @clone/preserve didn't preserve when cloning exits. Reported by
+    Bellemore@M*U*S*H. [SW]
+  * A significant bug in the manual notification of timed semaphores has 
+    been corrected. [SW]
+  * Revian@M*U*S*H pointed out that user-defined commands starting with
+    "@" that match the names of standard attributes don't behave as
+    you might expect. This is now documented in help user-defined commands.
+  * Security checks for attribute setting used the owner of the setting 
+    object, not the setting object itself. Report by Howie@New Frontier.
+  * help set() improved based on report by Tareldin@M*U*S*H.
+  * folderstats() did not behave as promised in the help. Now it
+    does. Report by Trispis@M*U*S*H.
+  * Typo in src/log.c fixed by Nathan Schuette.
+  * Improved help for DEBUG. [SW]
+  * Aliased 'help ^' to 'help listening2'. Suggested by Philip Mak.
+  * MacOS portability fixes. [DW]
+  * The sigusr1 handler would reinstall itself as the sighup handler
+    on systems that don't provide persistent signals. 
+    Fixed by Bellemore@M*U*S*H.
+  * &a=b me now properly clears the attribute A=B. Reported by 
+    Trispis@M*U*S*H. In addition, now @tel #1234= produces an error,
+    as it should. [SW]
+  * mail functions can now be called by an object on anything it controls
+    (typically, its owner). Suggested by Trispis@M*U*S*H.
+  * The givee is now correctly passed in %1 to @give/@ogive/@agive,
+    and documented. Reported by Philip Mak.
+  * Added hints for Irix 6.x by Vexon@M*U*S*H.
+  * i18n fix for function invocation limit message.
+  * Clarification in help @alias by Philip Mak.
+  * @set obj=!puppet repeatedly no longer repeats the "not listening"
+    message. Reported by Philip Mak.
+
+
+& 1.7.4p15
+Version 1.7.4 patchlevel 15                   February 8, 2002
+
+Minor Changes:
+  * @dolist and iter(), where multiple tokens are replaced (## and #@),
+    now do both replacements at once. This is more efficient in several
+    ways and fixes a problem where if the second token gets into the
+    string from a replacement of the first, it gets replaced. (iter(a#@,
+    ##) should return a#@, not a1). [SW]
+  * setunion no longer eats empty list elements. [SW]
+  * The help text for items() is now more descriptive of how it works
+    and differs from words(). Suggested by Zen@SW1.
+  * When you attempt to @chzone an object to a ZMO without a zone-lock,
+    a default zone-lock of "owner of the ZMO" is now set, and the
+    attempt succeeds. Suggested by Linda Antonsson.
+  * In the French message translation files, the word 'drapeau' and
+    'flag' were used interchangeably. I've standardized on 'flag'.
+    Report by Vexon@M*U*S*H.
+Fixes:
+  * Message typo fixed by Bellemore@M*U*S*H.
+  * No more ansified names in logged shutdown messages. Report by
+    Bellemore@M*U*S*H.
+  * Messages when destroying players now take into account the 
+    destroy_possessions and really_safe settings. Suggested by Wayne@PDX.
+  * The parser no longer strips any first layer of braces in, e.g.
+    @switch action clauses, but only when the first character in the
+    clause is a brace. This prevents @sw 1=1, @wait me={@emit 1; @emit 2}
+    from being misparsed and running @emit 2 immediately. Reported by
+    Azratax@Azmush. [TAP]
+
+& 1.7.4p14
+Version 1.7.4 patchlevel 14                   January 4, 2002
+
+Minor Changes:
+  * The global function invocation limit is now 5 times the per-evaluation
+    function invocation limit, to provide some flexibility in cases
+    where you run a small number of functions that cause a larger
+    number of other functions to be evaluated (e.g., using tel()
+    to move players into rooms with function-laden descriptions). [TAP]
+Fixes:
+  * Mortals are now restricted in which html tags they can generate
+    when pueblo support is enabled. Suggested by BladedThoth.
+  * @sitelock/name !<name> was improperly case-sensitive in its
+    matching. Reported by Linda Antonsson.
+  * Better invocation count checking and aborting on reaching limit.
+    Reported by Ashen-Shugar. [TAP]
+  * Beep characters are ignored when matching object listen patterns.
+    Suggested by Wayne@PDX.
+  * The end-of-dump marker is checked when reading the chat database.
+    Suggested by Bellemore@M*U*S*H. [SW]
+  * @lock obj=attrib:value locks were broken. Reported by Linda
+    Antonsson.
+  * Minor help fixes.
+
+& 1.7.4p13
+Version 1.7.4 patchlevel 13                   November 30, 2001
+
+Minor changes:
+  * options.h.dist mentions Darwin as well as MacOS X. [DW]
+  * PCRE updated to 3.7. [SW]
+  * When CHAN_NAME_LEN is increased beyond 30, the @chan/list header
+    line is misaligned, and other strange things can happen to
+    @chan/list. Reported by Bladed Thoth
+Fixes:
+  * Crash bug in chat channels reported by BladedThoth.
+
+& 1.7.4p12
+Version 1.7.4 patchlevel 12                   November 9, 2001
+
+Minor changes:
+  * @dol/delim is now @dol/delimit, for Mux compatibility. [SW]
+  * /preserve switch for @chownall works like @chown's /preserve switch.
+    This changes the default behavior of @chownall, which used to
+    preserve everything, to work like @chown and strip privileged bits.
+    Suggested by Taladan@M*U*S*H.
+Fixes:
+  * Warnings in index-files.pl are no longer shown. Report by Noltar@Korongil
+  * Additional support for ansi in channel names. Ansi is now stripped
+    when sorting channels and when matching names for @chan/decomp and
+    @chan/what.  Reported by Oriens@Alexandria.
+  * Help @decompile clarifies the /flags switch. Suggested by Oriens@Alexandria
+  * Source is indented before diffs are produced.
+  * Typo in help zmr correct by Oriens@Alexandria.
+  * Players disconnecting without QUIT continued to use CPU until fully
+    disconnected. Fixed. Report by Protagonist@M*U*S*H. [SW]
+
+
+& 1.7.4p11
+Version 1.7.4 patchlevel 11                   October 15, 2001
+
+Minor Changes:
+  * In places like switch() that can match <number, the numbers
+    are now treated as floating point, so they need not be only integers.
+    However, they must be pure numbers; "<3km" will not work.
+  * Tests for channel name matches now disregard ansi. Suggested by Wayne@PDX.
+Fixes:
+  * MacOS linting. [DW]
+  * next() could reveal unfindable players. Reported by Jeffrey@TheHotel. [TAP]
+  * making diffs or a dist now insures that switches.h, etc. are rebuilt
+    for the Mac/Win32 crowd. Reported by many people.
+  * Some warnings discovered with compiling with gcc 3.0.1 fixed. [SW]
+  * Potential crash-or-worse bugs that could be caused by malformed
+    save messages fixed. [SW]
+  * @mail to players with names starting with numbers works correctly now.
+    Report from Mike Wesson. [SW]
+  * Fewer logged warnings from failed convtime()s. [SW]
+  * Help for page now mentions /blind. Reported by Oriens@Alexandria.
+  * Attempting to set an invalid priv on a channel now produces a
+    better message. Reported by Oriens@Alexandria.
+  * Improved message when a Wizard overrides a chan join lock by Wayne@PDX.
+  * Another way to end up inside yourself fixed. Report by Ashen-Shugar. [TAP]
+  * Help default/edefault syntax clarified by Delina@ST:VAAE
+  * Help math functions clarifies 'number'. Suggested by Delina@ST:VAAE
+  * Information on the patches.h header added to the FAQ file. Suggested 
+    by Kahmy. [SW]
+  * Potential crash in @set fixed. Report by Michael Loftis [SW]
+  * The Unfindable flag is checked on all levels of containers, not just
+    the immediate location. Suggested by Oriens. [SW]
+  * NT_TCP fix by Bellemore.
+  * secure() now escapes ^, as the help says it does. Report by Gabriel Matlin.
+  * link_anywhere now lets you actually @link anywhere, instead of just
+    letting variable exits link anywhere. Report by Viila@M*U*S*H.
+  * help home now returns help homes, not help home(). Suggested by 
+    Gary Williams
+
+& 1.7.4p10
+Version 1.7.4 patchlevel 10                   September 7, 2001
+
+Fixes:
+  * @clone changes in p9 introduced a crash bug. Fixed.
+  * Typo in mushcnf.dst fixed by Noltar@Korongil.
+
+& 1.7.4p9
+Version 1.7.4 patchlevel 9                    September 4, 2001
+
+Minor changes:
+  * @clone can optionally specify a new name for the clone.
+    Patch by Bellemore@M*U*S*H, inspired by mux.
+  * die() can take a third argument which, if true, will cause it to
+    return the list of individual die rolls rather than the sum.
+  * NT_TCP option moved to options.h.dist, and @config/list compile now
+    reports whether it's on or not. Suggested by Glonk@GlonkMUSH
+  * QUIET flag affects the "Teleported." message as well.
+    Suggested by Glonk@GlonkMUSH.
+  * pos() and strmatch() strip ansi and html markup before matching. [SW]
+  * Slight optimizations for many of the functions that strip markup. [SW] 
+  * chat_strip_quote setting now applies to @*wall and say. Suggested by
+    Glonk@GlonkMUSH. [SW]
+  * @malias/who is now the same as @malias/members. Suggested by
+    Oriens@Alexandria.
+  * Small code change in do_chat_by_name so that find_channel_partial_on
+    can behave as documented. Suggested by Michael Loftis
+Fixes:
+  * p8 broke regeditall when the replacer was null. Fixed.
+    Reported by Nymeria@M*U*S*H.
+  * Some unused variables removed, and pcre.h included in parse.c.
+    Reported by Sidney@M*U*S*H.
+  * index-files.pl produced an uninitialized value warning if a help file
+    had only a single entry (or admin entry). Warning removed.
+    Reported by Nymeria@M*U*S*H.
+  * Fixed to help lstats() to mention stats() as alias. Reported by
+    Glonk@GlonkMUSH.
+  * Help edit() fix by Sash@SW:Uprising.
+  * Improved failure message for @password. Suggested by Mike Wesson. [SW]
+  * alphamin()/alphamax() were stripping markup from what they returned. [SW]
+  * PARANOID flag is now only visible to owners. Reported by 
+    Bellemore@M*U*S*H.
+  * Improved error message when trying to rejoin a channel. [SW]
+  * In Win32 NT_TCP mode, ident lookups are now done and the LASTIP
+    attribute doesn't get corrupted. Patch by Bellemore@M*U*S*H.
+  * @chan/describe now works along with @chan/desc. Suggested by 
+    Trispis@M*U*S*H
+  * 'teach'ing a motion to a follower didn't work right. Reported by
+    Cheetah and Viila@M*U*S*H.
+  * Security bug in follow fixed. Reported by Walker@M*U*S*H.
+  * The &ecirc; and &euml; entities were not correctly returned in
+    Pueblo mode. Fixed by [NG].
+  * Help for trig functions improved. [SW]
+  * Pueblo references no longer give Chaco's (defunct) website.
+    By Noltar@Korongil.
+
+& 1.7.4p8
+Version 1.7.4 patchlevel 8                    July 22, 2001
+
+Minor changes:
+  * restart is a bit more precise in the "Mush already active" message.
+    Suggested by Lucas Layman.
+  * When a player's creation is refused because creation/registration
+    is globally turned off, show them register.txt instead of down.txt.
+    Patch by Bellemore.
+  * The NOSPOOF flag is now visible only to the player themself. [SW]
+  * regedit can now use backreferences in the replacer. [SW]
+Fixes:
+  * ident lookups were broken on win32. Reported by Bellemore. [SW] 
+  * ident query timeouts could get doubled mistakenly. [SW]
+  * Typo in mushcnf.dst fixed by Noltar@Korongil.
+  * Fix to help puppets2 by TurtleKnee@M*U*S*H.
+  * Help pcreate() added. Report by Eratl@M*U*S*H.
+  * @pcreate messages capitalized by Oriens@Alexandria.
+  * create() used 10 as the default cost, instead of the configured
+    default. Report by 8bitMUSH.
+  * Inactivity timeouts longer than 1 day didn't work. Fixed and
+    efficiency of the check improved. Reported by Bellemore@M*U*S*H.
+  * Null @aliases are no longer allowed. [SW]
+  * Cleanup to ident for situations when the remote host isn't running
+    an ident server. [SW]
+
+& 1.7.4p7
+Version 1.7.4 patchlevel 7                    July 02, 2001
+
+Major changes:
+  * %r can now evaluate to one character or two, based on a new config 
+    option, newline_one_char, which defaults to being yes. This allows
+    %r to be used as a list delimiter. However, this may
+    break softcode which expects strlen(%r) to be 2, but it's probably
+    smarter to fix the softcode than turn off this option. [sw]
+  * If a command and a standard attribute have the same name, the
+    command takes precedence. So if you have an @attribute named
+    "PEMIT", @pemit me=foo will do the command, not set the attribute. [SW]
+Minor changes:
+  * When someone attempts to create too many attributes on an object,
+    the log indicates who and which object. Suggested by Frob@Battlestar
+    Galactica:TSC.
+  * Buncha tprintfs replaced with notify_formats. [SW] 
+  * New local_connect() and local_disconnect() hooks in local.dst.
+    Suggested by Eratl@M*U*S*H.
+  * lookup_player now deals with player names prefixed with "*",
+    so a bunch of commands like @newpassword will now treat those
+    arguments. Suggested by Glonk@GlonkMUSH.
+  * Make is more verbose about alerting you to changes in the 
+    src/*.dst files.
+  * The message for undestroying someone else's object more closely matches
+    the destroy message. Suggested by Noltar@Korongil.
+  * Server output that used to be tagged with "PRE" for Pueblo is now
+    tagged with "SAMP", because the original Pueblo client did not correctly
+    handle "<BR>\n" in PRE, and the newer clients that are supporting
+    the pueblo protocol, like MUSHclient, do handle it correctly, causing
+    an incompatibility problem. Our workaround is to avoid PRE. 
+    Reported by [NJG].
+  * The WHO list output is tagged <img xch_cmd=html> for Pueblo to get
+    appropriate newline handling. [NJG]
+  * help @mail mentions help @malias. Suggested by Trispis@M*U*S*H.
+  * Matching code now treats players you can't see like disconnected players
+    when matching *player. Reported by Walker@M*U*S*H.
+  * @newpassword now confirms whose password was changed. Suggested by
+    Xyrxwyrth@M*U*S*H.
+  * @chan/who and cwho() now include objects on the channel. Suggested by
+    Glonk@GlonkMUSH.
+  * q-register lookup is slightly faster. [SW]
+  * Floating-point numbers in exponential format (6.02e23) are always
+    accepted, not just when tiny_math is set. [SW]
+  * isint() and isnum() ignore the null_eq_zero option, since they already
+    ignore tiny_math. [SW]
+  * time() and convsecs() take an optional timezone argument that,
+    if 'UTC', makes them act the same way as utctime() and convutcsecs(). 
+    From MUX2. [SW]
+Fixes:
+  * Additional range checking to avoid some bugs reported by Alierak. [SW]
+  * Fix to buglet in @name error with PLAYER_NAME_SPACES reported by
+    Luke@M*U*S*H.
+  * Typo in @name error message fixed by Luke@M*U*S*H.
+  * One could @pcreate players past the hard db limit. Reported by Z@Korongil.
+  * Typos in config_h.SH and options.h.dist fixed by Oriens@Alexandria.
+  * Under some conditions, you could double-join a channel.
+    Reported by Xyrxwyrth@M*U*S*H, investigated by Steven@Champions.
+  * Error message for @chan/desc improved. Reported by Oriens@Alexandria.
+  * Typo in alias.cnf fixed by rodregis@M*U*S*H.
+  * @mvattr sometimes failed to remove the old attrib, when it was a 
+    standard attrib that could be abbreviated (@mvattr a/desc=b).
+    Fixed by Walker@M*U*S*H.
+  * Some english-matching (like 'get 1st foo') would fail. Reported by
+    Mystery8.
+  * Typo in help @verb reported by Greck.
+  * MacOS tweaks. [DW]
+  * Better detection of numbers that are too big. [SW]
+  * Wizards could crash the server by entering objects in their own
+    inventory. Reported by Howie@New Frontiers.
+
+& 1.7.4p6
+Version 1.7.4 patchlevel 6                    June 11, 2001
+
+Minor changes:
+  * English-style matching has been added to some more commands, 
+    to help with the stricter ambiguity checking (@teleport my 3rd foo=box, 
+    etc.). [SW]
+  * @pemit/list no longer does its useless ## substitution. [SW] 
+  * capstr() and art() skip leading ansi and html. [SW]
+  * table(), alphamin(), alphamax(), comp(), lcstr(), ucstr(), strinsert(), 
+    and delete() are all ansi and html aware. Mixing html and ansi in their 
+    arguments is probably a bad idea, though. [SW]
+  * reverse() and scramble() are ansi-aware, and still will break html, but 
+    in a different way than before. [SW]
+  * foreach() strips ansi and html from the string before doing its things. [SW]
+  * Complete Norwegian translation by Kenneth Aalberg.
+Fixes:
+  * Bug in growing hash tables fixed. [SW] 
+  * Typo in copyright fixed. Reported by Cheetah@M*U*S*H.
+  * Unused variable removed from fun_ansi. Reported by Sidney@M*U*S*H.
+  * Mac portability stuff. [DW]
+  * Wizards could @chown garbage objects. [SW]
+  * Wizards could give to garbage objects. [SW]
+  * Wizards could read God's @mail. [SW]
+  * Eliminated some compiler warnings. [SW]
+  * mid() was quite broken with ansi. right() was less broken. 
+    Both hopefully fixed for good. [SW]
+  * Fixed a problem with the attribute used with foreach() evaluating from 
+    the perspective of the wrong object. [SW]
+  * before(), after(), and wrap() are now classified as string functions
+    in the help. [TAP]
+  * help wildcards now mentions ?. Suggested by cmintrnt@M*U*S*H.
+  * help fixes by Jeff Ferrell.
+  * Problems with wrap() when the text included %r%r (or started with %r)
+    reported by Noltar@Korongil.
+  * If you somehow managed to load a corrupt db with a parent loop,
+    lparent could infinite-loop. Reported by Ashen-Shugar. [TAP]
+
+
+& 1.7.4p5
+Version 1.7.4 patchlevel 5                    May 25, 2001
+
+Fixes:
+  * Fix to uninitialized variable that could cause ansi to bleed
+    on some systems. Patch by Luke@M*U*S*H
+  * Prototypes for ansi_align and ansi_save added to externs.h. [DW]
+  * FreeBSD hints file updated to get rid of a compiler warning. [SW]
+  * Setting hate_dest to no will not disable @recycle [SW]
+  * switchinc.c updated. [DW]
+
+
+& 1.7.4p4
+Version 1.7.4 patchlevel 4                    May 13, 2001
+
+Minor changes:
+  * Internally, the /folder switch is now /folders, which prefix-matches
+    to /folder and also lets @mail/folders work as syntactic sugar.
+  * fun_ansi has been rewritten to use less buffer space by consolidating
+    ansi codes. New codes for turning off ansi attributes (like hilite)
+    also added.  Patch by Luke@M*U*S*H.
+  * /silent switch to give suppresses default messages when giving
+    money to players. Suggested by 8BitMUSH.
+  * Old port concentrator code removed. [SW]
+  * On linux, @uptime reads /proc files instead of running 'uptime' [SW]
+  * Code that uses strdup and then adds a MEM_CHECK record for "string"
+    now use a wrapper function that does it automatically. [SW]
+Fixes:
+  * Paging a page-locked player didn't give the appropriate messages.
+    Reported by Steven@Champions.
+  * left, right, and mid are now ansi-aware. Patch by Luke@M*U*S*H.
+  * Help fixes to lexits(), name() (Noltar@Korongil), 1.7.4p3 (Z@Korongil).
+  * win32/cmds.h updated with prototypes for dismiss and desert by
+    Noltar@Korongil. And hdrs/externs.h, too, by [SW].
+  * Memory leak with using alphabetic q-registers in queued commands fixed.
+    Report by Jayvin@Dynamix [SW]
+  * Added hints/openbsd.sh to distribution.
+  * Mac portability linting. [DW]
+  * Several memory leaks in @malias code fixed. [SW]
+
+& 1.7.4p3
+Version 1.7.4 patchlevel 3                    April 23, 2001
+
+Commands:
+  * unfollow with no args now stops you from following everyone.
+    dismiss command stops people from following you.
+    desert command stops people from following you or leading you.
+    Idea by Oriens@Alexandria. Names suggested by Noltar@Korongil
+Minor changes:
+  * MONITOR announcements of disconnects distinguish hidden disconnects.
+    Suggested by Oriens@Alexandria.
+  * The Uptime field of INFO shows first start time, not last reboot time.
+    Suggested by Trispis@M*U*S*H.
+Fixes:
+  * Exact matches are now preferred over partial matches, and no longer
+    result in ambiguity. Report by Steven Viscido.
+  * Message mentioning INHERIT changed to TRUST by Xyrxwyrth@M*U*S*H.
+  * Distributed register.txt file is now more descriptive. 
+    Suggested by Xyrxwyrth@M*U*S*H.
+  * The ctime(), mtime(), restarttime(), and starttime() functions now 
+    return 2-digit days (01 vs. 1). Reported by Z@Korongil.
+  * @malias output uses the alias token more consistently. Suggested by
+    Kyieren@M*U*S*H.
+  * hints/solaris_2.sh modified a bit.
+  * Mac portability fixes
+  * Options.h clarification suggested by rodregis@M*U*S*H.
+  * Cosmetic bug in @halt fixed. Report by Trispis@M*U*S*H.
+  * Fixed a fencepost error in regedit*() that could generate garbage text.
+    Reported by Vadiv@M*U*S*H
+
+
+& 1.7.4p2
+Version 1.7.4 patchlevel 2                    March 23, 2001
+
+Major changes:
+  * The object matching routines have been modified. Some things you may
+    notice:
+    * Ambiguous cases are more often reported as such (rather than you
+      getting one of the ambiguous matches arbitrarily).
+    * locate() now returns #-2 as promised. Reported by Jeff Ferrell.
+    * A few functions that used accept player names now require
+      the use of *playername to match the player (e.g. mail(), hidden()).
+      (This is generally more consistent).
+Minor changes:
+  * @tr of a nonexistent attribute now reports that. Report by Z@Korongil.
+  * TEL_OK is an alias for JUMP_OK. Suggested by Kyieren@M*U*S*H.
+  * Added 'help i18n' (aka help translation). Suggested by Kyieren@M*U*S*H.
+  * When you use 'teach' and, as a result, run the command you are teaching,
+    it is treated as if the command were run by a player from the socket --
+    that is, attribute sets are not evaluated. Suggested by Xyrxwyrth@M*U*S*H.
+  * See_All players can see all channels and their members, too.
+    Suggested by Oriens@Alexandria.
+  * When trying to join yourself to a channel, we only check channels
+    you're not on; when trying to leave a channel, we only check channels
+    that you are on. This is handy for disambiguating similar prefixes.
+    Suggested by Oriens@Alexandria.
+  * When you're following a leader and the leader moves, you're told that
+    that you're following them before you attempt to move. Suggested by
+    Oriens@Alexandria.
+  * @stats/table is no longer restricted.
+Fixes:
+  * @grep/iprint produced highlighted strings matching the case you
+    gave, not the case actually found. Reported by Reagan@NF
+  * @search/lsearch by powers could sometimes get you the equivalent
+    flag-bit instead of power-bit. Reported by Reagan@NF
+  * Configure fix.
+  * hpux-gcc hint file now included.
+  * Nested ansi() broke again in p1. Fixed now. Reported by Intrevis@M*U*S*H
+  * Added Configure check for <netdb.h> to help Cygwin builds.
+    Reported by Xyrxwyrth@M*U*S*H.
+  * Help fix or two.
+  * Grammatical correction by Eratl@M*U*S*H in @boot/me error message.
+  * Cosmetics of @mail with folders > 9 improved. Reported by Bellemore@M*U*S*H
+  * One could be disconnected at the connect screen under some conditions
+    for no good reason. Reported by Oriens@Alexandria. [SW]
+  * Compile error when ROYALTY_FLAG not defined patched by Noltar@Korongil.
+  * Fixed infinite loop reported by Xyrxwyrth@M*U*S*H. [SW]
+  * It's no longer posible to connect to a GOING player.
+
+& 1.7.4p1
+Version 1.7.4 patchlevel 1                    March 17, 2001
+
+Minor changes:
+  * Speedup for repeat() function. [TAP]
+  * Hint for openbsd, which appears to have a broken IPv6 configuration. [SW]
+  * Some OS-dependent defines have been removed.
+  * ansi() now only adds a maximum of 7 codes to the string. [TAP]
+Fixes:
+  * The restrict_command for rob has been removed from restrict.cnf
+    Reported by Kyieren@M*U*S*H.
+  * Help fixes by Kyieren, rodregis, and Luke @ M*U*S*H, Datron@SW2, 
+    and Noltar@Korongil.
+  * stripansi() didn't correctly handle multiple ansi codes in
+    sequence. Reported by CU5@WCX.
+  * Linting for warnings in pcre. [SW]
+  * Configure now sends mailing list subscription stuff to the new 
+    list address.
+  * Updated examples in access.README to use dbrefs.
+  * Updated a reference to the rob command in 'give' errors. Noted by
+    rodregis@M*U*S*H.
+  * median was broken. Reported by Parax@SandsMUSH.
+  * Fixes to update.pl's handling of CHAT_TOKEN_ALIAS and the like.
+    Noted by rodregis@M*U*S*H
+
+& 1.7.4p0
+Version 1.7.4 patchlevel 0                     March 7, 2001
+
+Major Changes:
+  * This is now the stable minor version. PennMUSH 1.7.2 is no longer
+    supported except to help people transition to this version.
+Commands:
+  * The practically useless 'rob' command has been removed.
+Minor Changes:
+  * A virtually complete French translation joins the Swedish and
+    Hungarian ones! Congratulations to Jean-Michael Amblat and
+    Guillaime Lupin.
+  * The index-files.pl script handles long help topic titles better when
+    creating the index of help entries. [SW]
+  * Config options that can be set with @config/set are now documented in
+    mush help. [SW]
+  * A @config/set of a dbref option now checks dbref for validity. [SW]
+  * An ansi_normal code is added at the end of each channel title.
+  * You can clear attributes that have invalid names. [SW]
+  * stripansi() removes HTML markup as well as ANSI. [SW]
+  * @poll and @doing cannot have ANSI or HTML markup. [SW]
+  * soundex() and soundslike() strip ANSI and HTML. [SW]
+  * The maximum length of attribute names has been limited to 1024 
+    characters. [SW]
+  * Nesting ansi() functions now works better. Patch by Luke@M*U*S*H.
+  * help credits explains [initials] used herein. Suggested by Kyieren@M*U*S*H
+Fixes:
+  * Help fixes by Nymeria, Balerion, Trispis, Vexon (all@M*U*S*H),
+    Jeff Ferrell, and [SW,LdW]
+  * The two-argument forms of regmatch() and regmatchi() were backwards
+    when it came to case-sensitivity. [SW]
+  * @search on zone class did parent instead. Fix by Luke@M*U*S*H.
+  * Use of @mail after @mail/nuke could cause a crash.
+    Reported by Brazil. [SW]
+  * make update handles the include directive correctly. [SW]
+  * The admin WHO output looks better when locations reach 5-digit
+    dbrefs now.
+  * regedit() and regeditall() were case-insenstive. Fixed. [SW]
+  * The code for log() was changed some time back to allow an optional
+    base argument, but the help and function table were never updated. [SW]
+  * owner() could be used to determine if any attribute existed on any
+    object. [SW]
+  * atrlock() has been cleaned up, fixing many bugs. [SW]
+  * Some list functions that evaluate attributes could be used to determine
+    if the attribute existed even if the object doing the function couldn't
+    normally see the attribute. Fixed, and their error messages are now
+    consistant with the other list functions (In other words, no errors, just
+    a null string) [SW]
+  * Idle timeout is now checked every minute rather than at dbck intervals. 
+    Based on a report by Noltar@Korongil.
+  * Cleanup of signed/unsigned casts and signal handlers. [SW,DW]
+  * forbidden_name now does a case-insensitive comparison.
+    Reported by Kyieren@M*U*S*H.
+  * Blank lines at the start of help files are now correctly ignored
+    on Win32 and Mac systems as well as Unix. Report by Nymeria@M*U*S*H.
+  * functions() didn't show @functions. [SW]
+  * Nuked players weren't getting removed from @maliases. [SW]
+  * Database corruption caused by reading a db with over-long attribute
+    names or with attributes starting with quotes fixed. [SW]
+  * Crash bug in @attribute/rename fixed. [SW]
+  * Potential memory leak in help_command fixed. [SW]
+  * Warnings removed. Reported by [NJG]
+  * Windows NT native IO (NT_TCP) stuff should work again. Reported by
+    Bellemore@M*U*S*H. [NJG]
+  * @forwardlist now requires that you control the target, be pemit_all,
+    or pass the target's @lock/forward. Report by Vadiv@M*U*S*H.
+  * unparse_flags didn't handle exit toggles. Report by Draci@Chaotic.
+  * Casting and cleanup to enable compiling with lcc [SW]
+  * A potential problem with regexps with heavy backtracking fixed. [SW]
+  * Memory leaks with @clock fixed. [SW]
+  * Typo in spellnum() "fourty" fixed. Reported by Kyieren@M*U*S*H.
+  * @malias/set didn't work. Reported by Kyieren@M*U*S*H.
+  * Win32 portability fixes. [NJG]
+  * MacOS portability fixes [DW]
diff --git a/game/txt/hlp/pennv175.hlp b/game/txt/hlp/pennv175.hlp
new file mode 100644 (file)
index 0000000..bb3183c
--- /dev/null
@@ -0,0 +1,481 @@
+& 1.7.5p12
+Version 1.7.5 patchlevel 12                     November 3, 2002
+
+Fixes:
+   * Another bug in wrap() fixed. Reported by Rhysem. [SW]
+   * Bug in @wall fixed. [SW]
+   * Variable renaming to avoid C99 keyword 'conj'. [SW]
+   * Win32 project files for MSVC++ updated by Mark.
+   * Several portability fixes for MS VS.NET's compiler by BladedThoth.
+   * flip() and reverse() didn't mix well. Better now.
+     Reported by Julian. [SW]
+   * Compiling with CHAT_SYSTEM undefined works again. Report by
+     BladedThoth.
+   * bxor() was actually doing a bor(). Reported by Sketch@M*U*S*H. [SW]
+
+
+& 1.7.5p11
+Version 1.7.5 patchlevel 11                     October 31, 2002
+
+Config:
+   * New mush.cnf option only_ascii_in_names (defaults to yes) prohibits
+     the use of extended characters in names. Games that are running
+     in non-English locales will probably want to set this to no instead. 
+     Suggested by Philip Mak. [SW]
+Commands:
+   * Added @hook/before and @hook/after [SW,3]
+Locks:
+   * You can now use power^<power> and channel^<channel> in locks
+     to test if the enactor has a given power or is on a given channel.
+     Patch by Vadiv@M*U*S*H.
+   * @lock/dropto, if set on a room, can prevent objects from being
+     affected by the room's drop-to. Inspired by Oriens@Alexandria.
+Functions:
+   * The sha1() function computes the SHA-1 cryptographic hash of a string.
+   * A new nosidefx function restriction to allow disabling the side-effect
+     version of a function while still enabling the informational version.
+     For things like name() and parent(). [SW]
+   * @function's report includes more function restrictions in the flags
+     field. [SW]
+Minor changes:
+   * Modularization of code for itemizing lists by Vadiv@M*U*S*H.
+   * If there's no connect.html and you're on an html connection,
+     connect.txt is now better formatted when sent to you. Same for 
+     other cached text files. Suggested by BladedThoth@M*U*S*H.
+   * CRYPT_SYSTEM 1 now behaves like CRYPT_SYSTEM 3 (replacing
+     system-crypt passwords with SHS passwords). Suggested by Vadiv@M*U*S*H.
+   * flag_table is no longer referenced anywhere except when it is used
+     to seed the ptab_flag at startup. A stub "flag_add" function has
+     been added to make life easier for hardcoders. Suggested by
+     Gepht.
+Fixes:
+   * sig.c was broken on systems without sigprocmask. Reported by
+     Arithon@Oracle
+   * Bug with paging disconnected players and @away fixed.
+     Reported by Vadiv@M*U*S*H.
+   * Bashisms that crept into utils/mkcmds.sh has been replaced by
+     more portable alternatives based on Configure's results. 
+     Reported by Jason Newquist.
+   * Trigonometric functions were broken for non-radian degree types.
+     Fixed up.
+   * @decomp <room>/<attrib> didn't properly use 'here' as the name
+     of the object in its output. Reported by Oriens@Alexandria.
+   * Wizards can now modify any lock on anything but God. Reported by
+     Brian Favela.
+   * ex/mortal and ex now produce identical output when a mortal 
+     examines an object owned by someone else. Reported by Philip Mak.
+   * We do a little better about trying to close html and ansi tags
+     in all conditions. Bugs reported by BladedThoth @ M*U*S*H.
+   * whisper/@pemit to a puppet should be relayed to the owner, even if the 
+     owner is in the same room. Discovered thanks to MUSH sound test
+     suite designed by Trispis@M*U*S*H.
+   * The --longest switch in game/txt/Makefile was broken. Report by
+     Nymeria@M*U*S*H
+   * Help fixes by Noltar@Korongil and Intrevis@M*U*S*H
+   * The M_READ extmail bit is now renamed M_MSGREAD, as M_READ conflicts
+     with an included define on Solaris. Report by Jason Newquist.
+   * Setting flags using single characters was not well documented, and
+     didn't respect the character case. Reported by Intrevis@M*U*S*H.
+   * @chown by a Wizard attempted to debit the Wizard's money, rather than
+     that of the new owner of the object, which violated expected conservation
+     of money. Reported by Peter Bengtson.
+   * Several bugs in wrap()'s output fixed. Reported by Balerion@M*U*S*H. [SW]
+
+
+& 1.7.5p10
+Version 1.7.5 patchlevel 10                     September 19, 2002
+
+Major Changes:
+   * Commands can now be restricted by generic flags or powers.
+     Several mush.cnf options (restricted_building, free_objects,
+     hate_dest, player_locate, cemit_power) are now restrict.cnf
+     restrictions instead. By Vadiv@M*U*S*H.
+Functions:
+   * When a set function (setdiff, etc.) is called with 4 arguments,
+     if the last one isn't a valid sorting category, it's treated as
+     the output separator.  Inspired by Mux [SW]
+   * checkpass(), a wizard function that checks a string against a player's
+     password. Requested by Writh@M*U*S*H.
+   * regedit() and variants can now accept multiple regexps and
+     replacements, in order, like edit(). By Walker@M*U*S*H.
+   * comp() can take a third argument to specify the type of 
+     comparison to make. Suggested by Philip Mak.
+   * The trigonometric functions now take an optional argument to
+     control how the angles they work with are measured to allow them
+     to accept angles in degrees as well as the default radians. [SW,MUX2,Rhost]
+   * Added ctu() for converting between angle measurements. [SW,MUX2,Rhost]
+   * Added atan2(). [SW]
+   * dist2d() and dist3d() can take floating-point numbers. [SW]
+   * Other small cleanups in the math functions. [SW]
+Mail:
+   * The MAIL_SUBJECTS option has been removed. @mail now includes
+     subjects mandatorily. Suggested by Vadiv@M*U*S*H.
+Minor Changes:
+   * When a player @clones an object owned by another player, the
+     clone is now owned by the @cloning player, instead of the original
+     owner. Fixes a quota transfer issue reported by Sparta and
+     Philip Mak.
+   * The flag table is no longer walked with a linear search. Instead,
+     ptab_firstentry and ptab_nextentry are used. Flags no longer need
+     to be added in a particular order or groups in flags.c, and flags
+     added through hardcode should work better. Patch by Vadiv@M*U*S*H
+   * Error message for wrong number of arguments to a function
+     now tells you how many arguments it thinks you gave.
+     Suggested by Philip Mak.
+   * GAGGED players may now perform mail reading and maintenance.
+   * Internal reorganization of signal code. [SW]
+   * Attempts to speak on a channel that you can't speak on or see
+     now fail and command parsing continues. Suggested by Vadiv@M*U*S*H.
+   * The amount of CPU time spent running a queue entry can be limited.
+     This helps reduce the impact of some types of denial-of-service attacks.
+     New mush.cnf directive queue_entry_cpu_time. This currently
+     works only on Unix systems with setitimer. [SW]
+   * Internal rewrite of page/whisper code by Vadiv@M*U*S*H.
+   * Flag set/reset messages now include the name of the target object.
+     Suggested by Kyieren@M*U*S*H.
+   * game/txt/Makefile now includes options to limit the number of
+     news/etc topic aliases that are included in the 'entries' indices
+     generated by index-files.pl. Suggested by Nymeria@M*U*S*H.
+   * Minor inconsistencies in @sweep output punctuation reported by
+     Cmintrnt@M*U*S*H have been fixed.
+   * Added hints/cygwin.sh, tested with cygwin 1.3.12. Added additional
+     cygwin build information to README.
+   * The whisper-pose message is now Player senses: <pose>, with no
+     quotation marks added. This matches all other pose-type messages
+     in the server. Suggested by Philip Mak.
+   * Only escape codes described in the help are allowed in timefmt() [SW]
+Fixes:
+   * Archaic help reference to FORCE_WHITE removed. Noted by Oriens@Alexandria.
+   * Help fixes by Cerekk@bDv TrekMUSH, Julian@M*U*S*H, Letters@M*U*S*H,
+     and Philip Mak.
+   * The wildcard matcher could lag the MUSH under unusual conditions.
+     It's now smarter. Discovered by Sketch@M*U*S*H.
+   * Fixes from 1.7.4p20
+   * Fix a bug with setdiff() not using the output separator correctly. [SW]
+   * convsecs() could attempt to use values larger than 2^31, which could
+     crash Windows. Reported by Bellemore@M*U*S*H.
+   * @config didn't correctly show default flags for objects.
+     Reported by Vadiv@M*U*S*H.
+   * The strcasecoll function was poorly coded, and is now fixed.
+   * Created players who hadn't yet logged in didn't have LASTIP set
+     properly. Reported by Philip Mak.
+
+& 1.7.5p9
+Version 1.7.5 patchlevel 9                     July 16, 2002
+
+Minor Changes:
+   * /noeval switch added to @wall/@rwall/@wizwall and variants.
+     Suggested by Philip Mak.
+Fixes:
+   * Added a missing space in the @function report for softcoded
+     @functions. [SW]
+   * MUX-style @function foo=obj/attr works right. [SW]
+   * Cleaned up some multiple includes of the same header files. [SW]
+   * Lots of cleanup of old _() macros and similar by Vadiv@M*U*S*H.
+   * Added help for @stats/table. Suggested by Intrevis@M*U*S*H.
+   * Fixes to csrimalloc #ifdefs that broke in last patchlevel. [SW]
+   * A typo that could crash @function on certain operating systems
+     has been fixed. Report by Jeff Heinen.
+   * Improved switch() help. [SW]
+   * Changes in the way switchinc.c is generated, to reduce the number
+     of patches that attempt to patch it due to indentation changes. [SW]
+
+& 1.7.5p8
+Version 1.7.5 patchlevel 8                     June 26, 2002
+
+Minor Changes:
+  * Added @nspemit and nspemit(). Wizard-only versions of @pemit and
+    pemit() that don't print nospoof information. Suggested by many people,
+    most recently Mike Griffiths and Nymeria@M*U*S*H. [SW]
+  * Help updates. [SW]
+  * Force the pipes to compression program for database reads and saves to be
+    block-buffered. [SW]
+  * @function name=obj/attrib now works, as well as
+    @function name=obj,attrib [TAP]
+  * The AF_PREFIXMATCH flag is no longer shown on attributes it's set
+    on when you examine them.
+Fixes:
+  * A bunch of internal code cleanup, especially around casts. [SW]
+  * The disconnected room check is skipped on GOING rooms. Suggested
+    by Philip Mak.
+  * The dbck check for nameless rooms was only checking disconnected
+    rooms; now it checks all rooms.
+  * hasflag() did not work with single-character flag abbreviations.
+    Report by Mystery8.
+  * The variable named 'template' in src/strutil.c has been renamed
+    to avoid clashes with the C++ reserved word. Suggested by Mac@M*U*S*H.
+  * Improvement to help @filter. Suggested by Philip Mak. [SW]
+  * Files in the patches directory ending in ~ are ignored
+    when patches.h is rebuilt. [SW]
+  * Removed a // comment from strutil.c, as we're still
+    just following the c89 standard, not c99. Report by
+    Vadiv@M*U*S*H. [SW]
+  * make indent now indents the .dst files before the .c ones.
+    Fixes some spurious warnings from later makes. Suggested by
+    Vadiv@M*U*S*H. [SW]
+  * Code cleanup, mostly tprintf() and unneeded header file
+    checking elimination. [SW]
+  * Since a Windows header #defines OPAQUE, which conflicts with a
+    #define for the mush flag of the same name, rename
+    our #define rather than #undefining the Windows one. [SW]
+  * Fixes from 1.7.4p19
+
+
+& 1.7.5p7
+Version 1.7.5 patchlevel 7                     May 14, 2002
+
+Utilities:
+  * 'make globalinstall' will install executables, scripts, and
+    a game/ directory structure in a global location (/usr/libexec/pennmush
+    by default). Facilitates rpm builds. By Vadiv@M*U*S*H.
+  * The utils/ln-dir.sh script can be used to clone a globalinstall'd
+    pennmush for an individual MUSH/user. In combination, these two
+    are a replacement for 'make customize', especially for mud hosters.
+    By Vadiv@M*U*S*H.
+  * You can now configure options.h settings from the command line
+    using: make DEFINE="OPTION OPTION=value" UNDEFINE="OPTION" update
+    This will mostly be useful for autoinstallers and packaging scripts.
+    Suggested by Vadiv@M*U*S*H.
+Minor Changes:
+  * The default gcc compile flags now include some extra warnings.
+  * The prefix-table code now only aliases down to unique prefixes.
+    This prevents @w from calling @wipe (reported by Philip Mak),
+    and means that you'll need to use alias.cnf to get some of those
+    short aliases. [SW]
+  * Attribute lookups only do prefix-matching on attributes with the
+    AF_PREFIXMATCH flag. Most standard atr_tab.h attributes have this
+    flag, but newly added @attributes won't. Solves a problem with
+    inadvertant prefix-matching of @attribs reported by Sam Knowlton.
+Fixes:
+  * Fixes from 1.7.4p18
+  * @decomp/skipdefaults skips @lsets of default lock flags.
+    Suggested by Oriens@Alexandria. [SW]
+  * Typo in src/bsd.c corrected. Reported by Nymeria@M*U*S*H.
+  * Missing prototype in src/help.c. Reported by Nymeria@M*U*S*H.
+  * A bunch of linting.
+  * Win32 portability fixes. [EEH]
+  * Updated MSVC++ project files for win32. [EEH]
+  * @newpassword = foo would change the password of an arbitrary player.
+    This is now corrected. Report by Oriens@Alexandria.
+
+& 1.7.5p6
+Version 1.7.5 patchlevel 6                     April 22, 2002
+
+Config:
+  * New attribute_alias config directive, and some default attribute
+    aliases added to alias.cnf. Based on a report from Hilikiradi.
+Functions:
+  * textfile() returns help/news/etc. entries. Suggested by Trispis@M*U*S*H.
+Minor changes:
+  * New @warnings type lock-checks that reports problems with @locks. [SW]
+  * exit-unlinked checks do some sanity checks on variable exits. [SW]
+  * Improved error-checking in evaluation of @locks. [SW]
+  * No more hdrs/warnings.h file. [SW]
+  * New @nameaccent attribute to add accent marks to object
+    names in speech and things like look. Idea from Elendor. [SW]
+  * accent() understands a few more things. [SW]
+  * The accented characters->html entities table and other
+    lookup tables are now in a seperate file, src/tables.c,
+    which can be regenerated if needed by utils/gentables.c [SW]
+  * Improvements in caching of cached text files. [SW]
+Fixes:
+  * Buglet in ansi display of high-bit characters fixed. Report by
+    Trispis@M*U*S*H. [SW]
+  * Improved @clock2 help by Linda Antonsson.
+  * Fixes from 1.7.4p17
+  * A truly perverse database could cause an infinite loop on load. [TAP]
+  * Win32 portability fixes. [NJG, EEH]
+  * The notify code assumed that integers could be directly stored in
+    pointers. This isn't always true. [SW]
+  * Removed some un-used code. [SW]
+  * Fixed some compiler warnings and general code cleanup. [SW]
+  * Changed signal handlers to always use the ANSI/ISO C form (Returning
+    void, basically) [SW]
+  * A null string no longer prefix-matches anything. Report by Prot Diryn
+    and Cheetah@M*U*S*H.
+  * @sitelock/remove could remove entries it shouldn't if you remove the first
+    one after the '@sitelock will add sites...' line. Reported by
+    Ambrosia@M*U*S*H. [SW]
+  * The last line of the access.cnf file sometimes wouldn't get read
+    properly. [SW]
+
+
+& 1.7.5p5
+Version 1.7.5 patchlevel 5                     March 11, 2002
+
+Commands:
+  * @notify and @drain now accept a new switch /any. [TAP]
+  * Added @remit/list. Suggested by Tareldin@M*U*S*H [SW]
+Minor changes:
+  * We now use the Mersenne Twister pseudo-random number generator,
+    which is better that that available in most C libraries.
+    Moreover, we seed with /dev/urandom, if it's available. [SW]
+  * The 'T' type character (for THING) is now shown when one-character
+    flag lists are displayed. This is more consistent with other types,
+    and makes it harder to confuse #23O (#23, opaque) with #230
+    (#230, no flags). Suggested by Eratl@M*U*S*H.
+  * @lock/use on a parent used to apply to attempts to use $commands on
+    a child. This is no longer necessary, given inheritable locks,
+    so the behavior has been changed. Parents' locks are no longer checked
+    when deciding if a $command inherited from the parent should be run
+    via a child.
+  * New 'call_limit' config option can limit the number of recursive
+    parser calls to control process stack size and avoid crashes
+    on systems with limited stack. Defaults to unlimited, however, because
+    setting this value too small breaks mushcode. Report by Bellemore
+    and BladedThoth @ M*U*S*H.
+Fixes:
+  * Code cleanup - some stuff from 1.7.4 got left in that isn't
+    used in 1.7.5 any more. [SW]
+  * Fixes from 1.7.4p16, notably an important fix for timed semaphores.
+  * Cygwin portability fixes. [NJG]
+  * Updated MSVC++ project files. [EEH]
+
+
+& 1.7.5p4
+Version 1.7.5 patchlevel 4                     February 15, 2002
+
+Major changes:
+  * The mush recognizes telnet-aware connections. This is
+    neccessary for properly sending them some 8-bit characters. [SW]
+  * Much more support for handling accented characters in the ISO 8859-1
+    character set. See help for accent(), stripaccents(), and NOACCENTS.
+    Inspired by Elendor. [SW]
+  * Things that do first-unique-prefix matching (command, attribute and flag
+    names) now use a more space-efficient data structure than before.
+    This adds two new files, src/ptab.c and hdrs/ptab.h [SW]
+Commands:
+  * @sitelock/remove removes a sitelock entry. [SW]
+Functions:
+  * ord() and chr() functions for converting characters to/from numerical
+    values that represent them. [SW]
+Minor changes:
+  * The useless FORCE_WHITE flag is really, truely, gone. [SW]
+  * Use the new arglens argument to functions in more places. [SW]
+  * capstr() and before() fixes reimplemented using arglens. [SW]
+  * We now use the Mersenne Twister PRNG algorithm. [SW]
+Fixes:
+  * setunion() no longer eats empty list elements. [SW]
+  * Setting an inherited lock on a child could change the parent's lock.
+    Reported by Riverwolf. [SW]
+  * Help fixes. [SW, Nymeria]
+  * Players waiting at the connect screen weren't being disconnected
+    by the idle_timeout.
+  * Detection of cygwin in Configure may be improved.
+  * Fixes from 1.7.4p15.
+
+& 1.7.5p3
+Version 1.7.5 patchlevel 3                     January 24, 2002
+
+Fixes:
+  * before() was broken in 1.7.5p2. Reported by Sam Knowlton.
+  * capstr() was broken in 1.7.5p2.
+  * Win32 portability fixes by Noltar@Korongil.
+
+& 1.7.5p2
+Version 1.7.5 patchlevel 2                     January 23, 2002
+
+Major changes:
+  * Implementations for softcode functions get the lengths of their arguments
+    passed to them, and this is taken advantage of in a number of places. [SW]
+Minor changes:
+  * It's harder to get a partial dbref because of end-of-buffer truncation. [SW]
+  * Code cleanup. In particular, safe_str() and friends are no longer
+    macros for a safe_copy_str() or the like, because hardly anything
+    used a different buffer length than BUFFER_LEN, and those places
+    can be handled other ways. [SW]
+Fixes:
+  * Win32 portability fixes by Noltar@Korongil and Eric Koske.
+  * When you have two hidden connections, one idle over the inactivity limit,
+    and the other not, @hide/off on the active connection unhides both,
+    but you also see the Inactivity re-hide message from the other
+    connection. Reported by Trispis.
+  * iname() function actually added to function table so it works.
+    Reported by K. Shirow.
+  * @lock obj=attrib:value locks didn't work properly. Reported by
+    Linda Antonsson.
+  * Fixes from 1.7.4p14.
+
+& 1.7.5p1
+Version 1.7.5 patchlevel 1                     December 3, 2001
+
+Minor Changes:
+  * PCRE updated to 3.7. [SW]
+  * player_name_len is now runtime configurable. Suggested by
+    Linda Antonsson. [SW]
+  * Any object of any type may be a ZMO, and any object of any type
+    may be zoned to a ZMO of any type. However, searching for
+    $commands has not changed, so $commands on a ZMO are only
+    searched when the ZMO is not a room, and $commands on objects
+    within the ZMO are only searched when the ZMO is a room. [TAP]
+  * @chzoneall nows directly calls @chzone, and @chzone now tells
+    you when it's not changing a zone. [TAP]
+  * The term "Zone Master" (player) has been replaced by "Shared
+    Player" in the help. [TAP]
+  * Many obsolete db formats are no longer readable. hdrs/oldattrib.h
+    and src/convdb.c are no more. [SW]
+  * Code cleanup. [SW]
+Fixes:
+  * Help file for mix updated. Report by Cmintrnt@M*U*S*H
+  * Updated win32 config.h file and other fixes by Noltar@Korongil
+  * WHO wasn't showing unconnected players. Report by Noltar@Korongil. [SW]
+  * Help fixes. [SW]
+
+& 1.7.5p0
+Version 1.7.5 patchlevel 0                     November 14, 2001
+
+Major Changes:
+  * This is now the development minor version. This first release includes
+    relatively few changes, to make converting to it easier.
+  * Internal changes to the lock system. This requires a new minimal.db,
+    which is now distributed. [SW]
+  * Locale-based string collation throughout.
+  * Only ANSI C compilers are still supported; no more K&R. Files are
+    gradually going to be converted to ANSI C only.
+  * There is now an option to make ZMOs and ZMRs not count for
+    control of objects, only ZMPs. [SW]
+Flags:
+  * The ZONE player flag has been renamed SHARED, to help seperate the
+    ZMP control-only meaning from the command-matching of ZMOs and ZMRs. [SW]
+Commands:
+  * /preserve switch for @link prevents @chowning. Suggested by Vexon@M*U*S*H
+  * Admin WHO and SESSION now includes unconnected descriptors. [SW]
+  * Unconnected descriptors can now be booted. Patch by Bellemore@M*U*S*H.
+  * Unconnected descriptors can now be paged by admin with page/port. [SW]
+Functions:
+  * mix() can take more than 10 lists and of unequal length. [3,SW]
+  * iname() returns the name of an object from inside (honoring nameformat)
+    Idea by Jeffrey@TheHotel.
+  * lplayers() returns a list of players in the location. Handy for
+    room parents. By Vexon@M*U*S*H.
+  * lvplayers(), lvcon(), lvexits() are like lplayers/lcon/lexits, but
+    leave out dark things (and disconnected players). Handy for room
+    parents. By Vexon@M*U*S*H.
+Minor Changes:
+  * munge() now passes its delimiter as %1 to make generic sorting easier. [SW]
+  * Word-based attribute compression is faster than before, for both
+    compression and decompression. [SW]
+  * Windows memory-usage information for wizards is now in @uptime, not
+    @stats [SW]
+  * Word-based attribute compression stats can be viewed on non-Windows
+    mushes as well, by defining COMP_STATS. See externs.h for details. [SW]
+  * Setting of the internal QUEUE and semaphore attributes does not modify
+    an object's last-modified timestamp. [SW]
+  * Speaking on a channel that you're gagging is now treated like
+    speaking on a channel that you're not on. Suggested by rodregis@M*U*S*H
+  * You can use @exitto in place of &DESTINATION to set the destinatino
+    for variable exits, though DESTINATION is checked first. [3]
+  * WATCHER is another name for the MONITOR flag. [3]
+  * max_guest_pennies and guest_paycheck config options. Inspired by [SW]
+  * Lock and unlock messages now show object name and dbref, and tell
+    you if you unlock an already unlocked object. Suggested by Jamie Warren.
+  * A version of portmsg for Win32 is in the win32 directory.
+    Donated by CU5@WCX
+  * Tweaks to info_slave, which now uses readv/writev. [SW]
+  * Lots of code cleanup. [SW]
+  * CHAT_SYSTEM, INFO_SLAVE, and FUNCTION_SIDE_EFFECTS are now #define'd
+    by default. [TAP]
+Fixes:
+  * Indentation fixes [SW]
+  * Fixes up to 1.7.4p12 merged in.
+
diff --git a/game/txt/hlp/pennv176.hlp b/game/txt/hlp/pennv176.hlp
new file mode 100644 (file)
index 0000000..4cb7351
--- /dev/null
@@ -0,0 +1,355 @@
+& 1.7.6p16
+Version 1.7.6 patchlevel 16                     April 28, 2004
+
+Fixes:
+   * PCRE updated to 4.5 [SW]
+
+
+& 1.7.6p15
+Version 1.7.6 patchlevel 15                     January 25, 2004
+
+Fixes:
+   * Improved freebsd hints. [SW]
+   * Channel user memory allocation error corrected.
+
+
+& 1.7.6p14
+Version 1.7.6 patchlevel 14                     September 23, 2003
+
+Fixes:
+   * Fix to help @search2 by LeeLaLimaLLama@M*U*S*H.
+   * The max file descriptor could get stomped in some cases. [SW]
+   * Powers and toggles on destroyed objects are reset, as they 
+     caused anomalous lsearch/haspower behavior. Report by Mordie@M*U*S*H.
+   * Changing channel privs and loading channels with objects no longer
+     permitted could cause crashes. Report by Septimus@SW RP Forum.
+
+
+& 1.7.6p13
+Version 1.7.6 patchlevel 13                     August 11, 2003
+
+Fixes:
+   * Calling panic() while in the middle of a panic dump would cause a loop.
+     Reported by [EEH]. [SW] 
+   * Outdated mention of compose.csh removed from compose.sh.SH.
+     Reported by Cheetah@M*U*S*H.
+   * timestring() dealt wrongly with large arguments. Reported by
+     Jules@M*U*S*H. timefmt() had a similar problem, reported by
+     Luke@M*U*S*H.
+   * Better checking of db save failures. [SW]
+
+
+& 1.7.6p12
+Version 1.7.6 patchlevel 12                     June 23, 2003
+
+Minor changes:
+   * Users no longer see last connection information when they 
+     connect to Guests. Suggested by Jules@M*U*S*H.
+Fixes:
+   * Potential problem with ambigious names in the information functions 
+     fixed. [SW]
+   * The 'chat' config group is no longer displayed when CHAT_SYSTEM
+     isn't defined. Report by Mike Griffiths. [SW]
+   * cygwin install instructions changed to remove obsolete exim
+     version information. Suggested by Cheetah@M*U*S*H.
+   * Objects with user-defined locks had problems with finding built-in locks
+     on the object. Reported by Walker@M*U*S*H. [SW]
+
+
+& 1.7.6p11
+Version 1.7.6 patchlevel 11                     June 1, 2003
+
+Minor changes:
+   * The restart script now insures that GAMEDIR is a real directory
+     and CONF_FILE exists therein before proceeding. Suggested by
+     Philip Mak.
+   * Attribute flag setting messages are more verbose. Suggested by
+     Mike Griffiths
+   * See_All players may use the ports() function. Suggested by 
+     Mike Griffiths.
+Fixes:
+   * Wizards can no longer @chzone God. Report by Kevin@M*U*S*H.
+   * Help fixes by Mike Griffiths.
+
+& 1.7.6p10
+Version 1.7.6 patchlevel 10                     May 13, 2003
+
+Minor changes:
+   * PCRE (the regex matching engine we use) is updated to version 4.2. [SW]
+   * @mail/file now unclears the cleared bit when filing @mail.
+     Suggested by Philip Mak.
+Fixes:
+   * @edit is better with editing ansi. Reported by Trispis@M*U*S*H. [SW]
+   * Help file cleanup. [SW]
+   * @warnings about missing FAILURE messages were not correctly 
+     checked, causing false positives. Reported by Cheetah.
+   * Page message no longer ends in a period. Suggested by Time@M*U*S*H.
+   * Help fixes by Intrevis@M*U*S*H.
+   * BASE_ROOM can't be destroyed any more. Suggested by Philip Mak.
+
+
+& 1.7.6p9
+Version 1.7.6 patchlevel 9                      April 9, 2003
+
+Fixes:
+   * index-files.pl now produces a sensible warning for duplicate
+     help topics, rather than a perl warning. Suggested by Cheetah@M*U*S*H.
+   * Spellnum cosmetic bug with 'seventeen' fixed. Report by Jules@M*U*S*H.
+   * Another wrap() buglet tracked down and fixed. Probably the one
+     reported by Nymeria@M*U*S*H.
+   * Memory leak in flip() and scramble() fixed.
+   * Configure test for /dev/urandom from 1.7.5 got left out by mistake.
+   * Critical overflow bug in command argument parsing fixed.
+
+& 1.7.6p8
+Version 1.7.6 patchlevel 8                      March 21, 2003
+
+Minor changes:
+   * The CHANGES file has been renamed so that it always refers to
+     a version number, and utils/mkvershelp.pl now generates seperate
+     .hlp files for each CHANGES file. This will prevent patch hunk 
+     failures when two patchlevels of different versions are released
+     and both used to try to modify the same .hlp file.
+   * Channel names are restricted to printable characters, with no
+     leading or trailing spaces. Suggested by Letters@M*U*S*H.
+   * Calling time() with any argument other than 'utc' now generates
+     an error. Report by Time@M*U*S*H.
+Fixes:
+   * Some redundant code cleanup in look_exits suggested by Vadiv@M*U*S*H.
+   * Help file fixes by Ves@M*U*S*H, Jules@M*U*S*H, Cerekk@bDv.
+   * When page_aliases is on, there's a space between the player's
+     name and alias. Suggested by Saturn@M3.
+   * Command-checking for ZMR contents didn't function when a ZMR
+     was used as a player's personal zone. Reported by BlaZe@M*U*S*H.
+   * Default idle_timeout was different in code and mushcnf.dst.
+     Reported by James Bond@M*U*S*H. [SW]
+
+
+& 1.7.6p7
+Version 1.7.6 patchlevel 7                      February 20, 2003
+
+Fixes:
+   * Some sloppy coding in src/access.c could generate runtime 
+     debugging exceptions. Reported by BladedThoth@M*U*S*H.
+   * wrap() could behave incorrectly when a line was exactly the length
+     of the wrap width and the total input size was larger than 
+     any previously wrapped input. Reported by Liam@Firdeloth.
+   * Extra NUL characters were sent after telnet codes, which 
+     confused Mudnet and maybe some clients. Patch by Alierak.
+
+
+& 1.7.6p6
+Version 1.7.6 patchlevel 6                      January 23, 2003
+
+Minor changes:
+   * nearby() always works for see_all players. Reported by Sparta.
+   * findable() now requires that executor control either the object
+     or the victim or be see_all. Reported by Sparta.
+Fixes:
+   * POWER^ and CHANNEL^ locks tested the wrong object. [SW]
+   * @grep, @wipe, and @edit now report when no attributes are
+     matched. Suggested by Procyon@M3
+   * Changes to telnet negotiation strings to match those in
+     PennMUSH 1.7.7, which seems to fix some problems with display
+     of connect.txt in some clients. Report by Howie@NewFrontier.
+     Patch by Time@M*U*S*H.
+   * @mail/silent wasn't unless you used /send too. Report by
+     Moe@Chicago.
+   * Wizards could set attributes on garbage objects (which were useless,
+     but may have leaked memory needlessly). Reported by Taz@M*U*S*H.
+   * @chan/hide didn't check for hide permissions properly in some
+     cases. Reported by Tanaku@M*U*S*H.
+   * Better explanation of when regexp matching is case sensitive vs.
+     insensitive. Suggested by Jake@BrazilMUX, Brazil@BrazilMUX, and
+     Vadiv@M*U*S*H.
+
+
+& 1.7.6p5
+Version 1.7.6 patchlevel 5                      January 7, 2003
+
+Fixes:
+   * 1.7.6p4 broke 'go #dbref', which broke the ability of followers 
+     to follow. Reported by Ellis@M*U*S*H.
+
+
+& 1.7.6p4
+Version 1.7.6 patchlevel 4                      January 2, 2003
+
+Minor Changes:
+   * English-style matching now applies to exits in the room
+     (so '1st down' can match the first 'down' exit if you're not carrying
+     anything that matches 'down'). New english-style matching adjective
+     'toward' restricts the match to exits (so: 'look toward 1st down').
+Fixes:
+   * Code cleanup to fix several potential buffer overflows.
+   * The wildcard matcher had problems with backslash escapes in
+     some cases, making matching a : in a $command very hard.
+     Reported by Wayne@PDX.
+   * @chzone could cause crashes on some systems.  Reported by Wayne@PDX.
+   * When two exits match, one is no longer chosen at random.
+     Instead, the ambiguity should be reported as an error.
+     Reported by Intrevis@M*U*S*H.
+   * The dbref returned by locate when given the X parameter is
+     no longer random, but the last one found (as per the help).
+   * Serious bug in reading locks from the db on startup corrected.
+   * The profiling timer is turned off duing dumps, as some systems
+     (FreeBSD?) appear to continue to use it and interrupt dumps
+     due to cpu limiting. Reported by Nathan Schuette.
+
+
+& 1.7.6p3
+Version 1.7.6 patchlevel 3                      December 22, 2002
+
+Minor changes:
+   * call_limit now controls the maximum recursion in process_expression,
+     instead of maximum calls to p_e per command cycle. This still
+     limits stack size, but doesn't get in the way of massive code
+     nearly as much. People using a very high call_limit value should
+     probably lower it significantly. Patch by Philip Mak.
+   * Improved error messages for many database information functions.
+     Notably, several functions that require at least one argument,
+     now complain if they don't get it rather than returning silently.
+     Suggested by Intrevis@M*U*S*H. [SW]
+Fixes:
+   * @warnings are no longer shown on GOING objects. Suggested by
+     Philip Mak.
+   * Help fixes by Intrevis, Time, and Ambrosia@M*U*S*H
+   * Bug in @decomp/skipdefaults reported by Philip Mak. [SW]
+   * Tweaks to utils/mkcmds.sh [SW]
+   * home() on a room acts as described in the help. Reported by
+     Intrevis@M*U*S*H. [SW]
+   * whisper/noisy double-notified the whisperer. Reported by Philip Mak.
+   * Crash bug in @mail fixed. Reported by Titan@OtherSpace.
+
+
+& 1.7.6p2
+Version 1.7.6 patchlevel 2                      December 17, 2002
+
+Minor changes:
+   * An invalid range argument to @search/lsearch is now coerced
+     to be the lowest or highest dbref, as appropriate. The search
+     range is also now inclusive. And lsearch(<player>) works.
+     Suggested by Philip Mak.
+   * mushcnf.dst now includes a default value for call_limit.
+     Suggested by Philip Mak.
+   * Testing for whether the mush is already running in the
+     restart script has been improved by Philip Mak.
+Internationalization:
+   * Polish translation files (partial) are now being distributed.
+Fixes:
+   * Fix to win32 warnings. [EEH]
+   * Under Win32, a failed accept() call in bsd.c would not be
+     correctly handled. Report by BladedThoth@M*U*S*H.
+   * Help fixes by Luigi@8bitMUSH, Kyieren@M*U*S*H, Intrevis@M*U*S*H.
+   * @map crash bug repoted by Philip Mak, fixed by Walker@M*U*S*H.
+   * Modifiying locks now updates the object's modification time.
+     Reported by Philip Mak.
+
+
+& 1.7.6p1
+Version 1.7.6 patchlevel 1                      November 26, 2002
+
+Minor changes:
+   * When using @nuke to destroy a SAFE object when really_safe is "no",
+     provide a warning (but schedule destruction anyway). Suggested by
+     Cerekk@bDV.
+Fixes:
+   * VS.NET project file was defaulting to signed instead of unsigned
+     chars, causing crashes. Fixed by BladedThoth@M*U*S*H.
+     Several places where we should have cast things to unsigned to
+     avoid this kind of thing have been fixed. [SW]
+   * The *emit() functions now set the orator correctly.
+     Reported by Philip Mak.
+   * ccom and cplr are cleared after each command execution so they
+     can't be leaked as easily. Suggested by Philip Mak.
+   * Linting.
+   * If God gives the wrong password to @logwipe, provide some feedback.
+     Suggested by Cerekk@bDv.
+   * mkcmds.sh was needlessly rebuilding several autogenerated files.
+   * The rules for flag characters shown in object headers now allows
+     F_INTERNAL flags to be shown (like GOING), just the same as
+     when you get a full flag list on examine. Report by Philip Mak.
+   * Help fixes by Bird@M*U*S*H, Intrevis@M*U*S*H, Philip Mak.
+   * @search type=something would report an error AND match the entire
+     database when something wasn't object, player, exit or room. [SW]
+   * Cosmetic bug in @malias/list fixed. Report by Tanaku@M*U*S*H.
+   * The info_slave now properly obeys the use_dns setting in mush.cnf.
+     This requires a full shutdown to put into effect. Report by
+     BlaZe@M*U*S*H. [SW]
+
+
+& 1.7.6p0
+Version 1.7.6 patchlevel 0                      November 11, 2002
+
+License:
+ * PennMUSH 1.7.6 and later is now released under the Artistic
+   License. This is an OSI-compliant open source license. See the file
+   COPYRITE for the complete license text.
+
+   Notable changes from the old license:
+   * No restrictions on commercial use
+   * No requirement to inform developers of improvements or submit
+     modifications, though it's still a nice thing to do. Note, however
+     that if you redistribute a modified version of PennMUSH, you MUST
+     include source code.
+
+   The PennMUSH devteam thanks the copyright holders of TinyMUD,
+   TinyMUSH 2.0, TinyMUSH 2.2, and TinyMUSH 3.0 for their assistance
+   in making this possible.
+Documentation:
+   * The README file has been split into README, INSTALL, UPGRADING,
+     and I18N files.
+Minor Changes:
+   * Rooms now hear remits and lemits in them, and can be pemitted
+     to. This behavior now matches that of other MUSH servers.
+   * AUDIBLE objects now propagate sound @remit'd into them.
+     Report by [SW].
+   * Added @lock/destroy to limit who can destroy a DESTROY_OK 
+     object. Suggested by Luigi@8bit.
+   * PARANOID nospoof notification now includes the name of the object's
+     owner as well. Suggested by Philip Mak.
+   * room() no longer notifies the executor of permission errors out of
+     band. It now just returns the error instead, like loc(). Suggested by 
+     Philip Mak.
+   * Creation times are now public information via ctime(); permission to
+     examine is no longer required. This allows objects to use, e.g.,
+     %#@[ctime(%#)] as a historically unique identifier of an enactor.
+     Suggested by Philip Mak.
+   * The reboot.db is now versioned. This will make it possible to
+     @shutdown/reboot across patchlevels that change the reboot.db
+     format (in 1.7.7 and later versions).
+   * Rooms on an @forwardlist now receive the message as a remit,
+     rather than a pemit. Suggested by BladedThoth@M*U*S*H.
+Fixes:
+   * More work on the great table() bug. Looks like a fix. [SW]
+   * Improved VS.NET project files by BladedThoth.
+   * Plugged a memory leak in deleting @locks. [SW]
+   * Fixed @lock-related crash bug reported by Philip Mak. [SW]
+   * General linting.
+   * process_expression ignores the [ in ansi escapes. Reported in the
+     context of #$ by Philip Mak. [SW]
+   * Internal changes to compress(), which now returns an allocated
+     string. Under Huffman compression, it should no longer be possible
+     to overflow a buffer with a pathological compression tree. Initial
+     concern voiced by Eyal Sagi.
+   * Table and ansi didn't play well together. Reported by Ellis@M*U*S*H.
+   * Config file reading should be better on Macs. Patch by Philip Mak.
+   * Obsolete checks for OLD_ANSI are removed. [SW]
+   * Crash bug in @function fixed. Report by Dallimar@Hemlock.
+   * Change to message on failed attribute flag set, to make it more
+     generic to cover all the possible failures. Report by Cerekk@bDv.
+   * Translations to some languages were broken. Fixed now. Report by
+     Sbot@M*U*S*H.
+   * QUEUE is now visible if you control an object, as promised in the
+     help. Reported by Luigi@8bit.
+   * Help fixes by Mortimer@M*U*S*H, Bellemore@M*U*S*H, Hyacinth@8bit,
+     [EEH], BladedThoth@M*U*S*H, Moe@M*U*S*H, Viila@M*U*S*H, Walker@M*U*S*H.
+   * Comment in src/Makefile fixed by Vadiv@M*U*S*H.
+   * A weird crash on @shutdown/reboot, probably attributable to a
+     broken library or system call, is now worked-around. Report by
+     Solarius@SWWF.
+   * Audible objects with @forwardlist set are no longer concealed by
+     the DARK flag.
+   * Win32 project files no longer use the win32/ directory as an include
+     directory, which causes problems. Reported by Gepht.
+
diff --git a/game/txt/hlp/pennv177.hlp b/game/txt/hlp/pennv177.hlp
new file mode 100644 (file)
index 0000000..929d8f8
--- /dev/null
@@ -0,0 +1,1180 @@
+& pennmush changes
+& 1.7.7p26
+This is a list of changes in this patchlevel which are probably of
+interest to players. More information about new commands and functions
+can probably be gotten via 'help <name of whatever>'. 'help credits'
+lists the [initials] of developers and porters that are used in the list 
+of changes.
+
+Information about changes in prior releases can be found under
+help topics named for each release (e.g. 'help 1.7.2p30').
+A list of the patchlevels associated with each release can
+be read in 'help patchlevels'.
+
+Version 1.7.7 patchlevel 32                     May 26, 2004
+
+Major Changes:
+  * SQL support. PennMUSH can now operate as an SQL client and perform
+    queries against an SQL server. Currently only the MySQL server is
+    supported. This adds the @sql command, the sql() and sqlescape()
+    functions, and the Sql_Ok power. See README.SQL for some 
+    additional information.  Mostly based on patches by Hans Engelen.
+  * Creating a leaf attribute automatically creates associated branch
+    attributes if they are not already present. [TAP]
+  * When a $command matches on an object, but the object's use-lock or
+    command-lock prevents the command from being run, the object's
+    COMMAND_LOCK`FAILURE, COMMAND_LOCK`OFAILURE, and COMMAND_LOCK`AFAILURE
+    attributes will be triggered if the $command never successfully 
+    matched, rather than returning a Huh? to the player.
+  * Exits and rooms may now run $commands. Rooms are treated as being
+    located in themselves for purposes of location checks. Exits are
+    treated as being located in their source room. Suggested by [TAP].
+Commands:
+  * 'empty <object>' attempts to get each item in <object> and put
+    it alongside <object> (in <object>'s location).
+  * 'give <object> to <player>' syntax added.
+Minor Changes (user-visible):
+  * @COST attribute is now evaluated, so you can make costs depend
+    on who's paying, a selected item, etc. Suggested by Walker@M*U*S*H.
+    Also, the amount given is passed in as %0, so you can code
+    vendors that accept any amount.
+  * New OBJID^<objid> lock atom.
+  * The server now maintains a rolling log of activity (commands issued,
+    evaluations parsed, and locks evaluated), that is dumped to the log 
+    file on panic, or can be seen by God with @uptime. This aids 
+    debugging code that causes a "clean" panic rather than a crash. 
+    Suggested by Intrevis@M*U*S*H.
+  * When checking a use/command/listen-lock on an object with patterns
+    that get matched, we only check the lock once and cache the result,
+    to prevent multiple lock evaluations if multiple patterns match. [TAP]
+  * @chan/recall now shows nospoof tags for @cemit'd lines.
+    Suggested by Sholevi@M*U*S*H.
+  * SUSPECT flag can now be applied to any type of object.
+    Suggested by Oriens@Alexandria.
+Minor Changes (internals):
+  * fun_escape() and fun_secure() use the same list of special characters, 
+    rather than each having their own copy. [SW]
+  * Buffer queue code used by @chan/buffer and the activity log refactored
+    into src/bufferq.c and hdrs/bufferq.h.
+  * Added mush_panicf(), with printf()-style format and arguments. [SW]
+Fixes: 
+  * @scan correctly shows attributes on parents again. Report by
+    Wayne@PDX.
+  * @shutdown/panic and @shutdown/paranoid work again. [SW]
+  * A panic DB could be created before the database files were actually read,
+    causing problems on the next restart. [SW]
+  * Win32 and Debian installer portability fixes. [EEH]
+  * Code cleanup around errno. [SW]
+  * The locate() function now respects visibility and interactions.
+    Report by Jules@M*U*S*H.
+
+
+& 1.7.7p31
+Version 1.7.7 patchlevel 31                     May 11, 2004
+
+Minor Changes:
+  * netmush is now started with only a single argument - the path to
+    the configuration file. The error log file (typically game/netmush.log)
+    is now configured in mush.cnf. Suggested by Vadiv@M*U*S*H.
+  * The restart script now bases its decision about whether the mush
+    is already running on the full path to the configuration file,
+    which means you can leave mush.cnf named mush.cnf without fear
+    of restart problems when multiple mushes are using the same
+    host. This also facilitates make update. Suggested by Vadiv@M*U*S*H.
+  * The GAMEDIR environment variable can be passed to 'make update'
+    to cause it to update *.cnf files in directories other than
+    game/ (using the template *.dst files in game/). 
+    E.g.: make GAMEDIR=/home/othermush/game update
+Commands:
+  * @nscemit. Suggested by Mystery8@ST:AW.
+Functions:
+  * nscemit(). Suggested by Mystery8@ST:AW.
+Flags:
+  * New HEAVY admin flag, prevents an object from being teleported
+    by a mortal between two containers they own. Admin without this
+    flag can now be teleported.
+Fixes:
+  * Help fixes by Anri@AkaneaMUSH and Intrevis@M*U*S*H.
+  * mix() now treats empty lists as empty, instead of containing a single
+    null element. Report by Luke@M*U*S*H.
+  * @power messages no longer reference 'flags'. Report by Nymeria@M*U*S*H.
+  * Crash bug with @clone in new power system fixed.
+
+
+& 1.7.7p30
+Version 1.7.7 patchlevel 30                     May 6, 2004
+
+Major changes:
+  * CHAT_SYSTEM option removed. If you don't want to use the chat system,
+    use restrict.cnf to disable @channel, @chat, etc.
+  * USE_MAILER and MAIL_ALIAS options removed. If you don't want to 
+    use the @mail or @malias systems, use restrict.cnf to disable
+    the associated commands.
+  * QUOTA, EMPTY_ATTRS, and FUNCTION_SIDE_EFFECTS options are now 
+    runtime options, instead of compile-time.
+  * SINGLE_LOGFILE option removed, and log filenames are now 
+    runtime options. You may now give the same name to
+    multiple log files and get a more fine-grained version of the
+    same effect. Based on ideas by Vadiv@M*U*S*H.
+Minor changes:
+  * New IP^ and HOSTNAME^ tests for boolexps. Suggested by Luke@M*U*S*H.
+  * ALLOW_NOSUBJECT option removed. We always use the beginning of the
+    message as the subject if one is not provided.
+  * JURY_OK and UNINSPECTED_FLAG options removed. Use @flag to add
+    flags if you need them. ONLINE_REG and VACATION_FLAG options
+    removed (default to always defined, add or remove with @flag as
+    desired).
+  * MEM_CHECK option removed from options.h; it is now a runtime
+    option in mush.cnf.
+  * @function/restrict can be applied to softcoded @functions, and
+    @function/add can accept a list of restrictions as a fifth argument.
+    Patch by Luke@M*U*S*H.
+  * log_walls run-time configuration option removed. Use the
+    logargs option in restrict.cnf instead.
+Fixes:
+  * Crash bug in anonymous attributes fixed. Report by Intrevis@M*U*S*H.
+  * lplayers() was broken. Report by T'orA@M*U*S*H.
+  * Failing to create a player by providing a bad password now gives
+    a better error. Suggested by [NJG].
+  * Setting/clearing a chan title on a notitles channel works, but
+    reminds you that titles are off. Suggested by Dan@InNomine.
+  * haspower_restricted removed from mushcnf.dst to stop spurious
+    warning on startup. Report by Nymeria@M*U*S*H.
+
+
+& 1.7.7p29
+Version 1.7.7 patchlevel 29                     April 28, 2004
+
+Major changes:
+  * Anonymous attributes via #lambda. See help anonymous attributes. [SW]
+  * Wizards (other than God) and royalty are no longer treated as No_Pay
+    unless the No_Pay power is explicitly set on them, although they
+    can still give (themselves or others) as many pennies as they wish.
+    This helps stop runaway wizards in the queue (they'll run out of cash
+    like anyone else). To get the old behavior back, @power your admin
+    No_Pay. You probably want to @power any globals that use search(),
+    children(), mail*stats(), etc, No_Pay as well. Suggested by Walker@M*U*S*H.
+  * game/restrict.cnf, alias.cnf, names.cnf are renamed in the tarball and
+    made with make update like mush.cnf. Suggested by Philip Mak. [SW]
+  * @powers now operate under the same code as the @flag system, so God
+    can add and modify powers in the MUSH with @power/add, etc. 
+Commands:
+  * @nsemit, @nsoemit, @nslemit, @nsremit, @nszemit and function forms
+    of the same. Suggested by Cloud@M*U*S*H.
+Functions:
+  * andlpowers(), orlpowers(), andpowers(), orpowers().
+  * align() performs fancy text alignment tricks. Patch by Walker@M*U*S*H.
+  * sent() and recv() show more player descriptor data from SESSION.
+    Suggested by Ricochet@M*U*S*H.
+  * scan() with a single argument assumes the executor's point of
+    view. Suggested by Cheetah@M*U*S*H.
+  * valid() can also check for syntactically correct passwords,
+    command names, and function names.
+Minor changes:
+  * No more CRYPT_SYSTEM in options.h. We try everything and we
+    always reset passwords to SHS. Patch by Vadiv@M*U*S*H.
+  * Wizards and other privileged players can @chan/recall on channels they're
+    not on. Suggested by Trispis@M*U*S*H. [SW]
+  * A separate ip address to bind the SSL port to can now be specified
+    in mush.cnf.
+  * @flag/type allows God to change flag types. Suggested by Renee@ShoreSide.
+  * After each @startup is enqueued (during startup or @restart/all),
+    we immediately run up to 5 queue cycles. This allows, e.g., God's
+    @startup to up to five levels of @dol/@tr/@switch/etc and still have
+    the queued code run ahead of other startups. This requires that you
+    keep God's dbref as #1. Based on comments by Philip Mak and o
+    Trispis@M*U*S*H.
+  * The message after successful @password is now clearer, to avoid
+    confusion in some unusual cases. Report by Kholnuu.
+  * Makefile is no longer set executable. Report by Luke@M*U*S*H.
+  * Server now handles telnet NOP and AYT commands. Suggested by
+    Philip Mak and Liz (author of Muckclient).
+  * announce.c is no longer distributed. portmsg.c cleanup.
+Fixes:
+  * @undestroy on an invalid (garbage, or !going) object now produces
+    an error message. Suggested by T'orA@M*U*S*H.
+  * On shutdowns, all queue deposits are now refunded.
+  * Ansi behavior on second and later lines of text fixed by Walker@M*U*S*H.
+  * Database reading improvements for win32 - ideally, you should now
+    be able to read a database written on win32/unix on either system.
+  * allof() now evaluates its separator argument. [SW]
+  * firstof() doesn't add an extra space before the value it returns. [SW] 
+  * Slackware portability fixes by Dale @ Wolfpaw.net hosting
+  * ]page properly noevals (the right hand side) now. Report by 
+    Cheetah@M*U*S*H.
+  * Partial channel match listings no longer reveal channels the player
+    isn't allowed to see. Report by Taz.
+  * Help fixes by Trispis@M*U*S*H and Tanaku@M*U*S*H.
+  * ssl() and terminfo() don't work on other players unless you're
+    See_All, as promised. Based on patch by Tanaku@M*U*S*H.
+  * lcon, etc. all do an INTERACT_SEE interaction check now.
+    Suggested by Thor@bDv.
+  * Code cleanup. [SW]
+  * Fixes from 1.7.6p16
+
+
+& 1.7.7p28
+Version 1.7.7 patchlevel 28                     March 12, 2004
+
+Major changes:
+  * You can add customized configuration parameters to set in mush.cnf
+    by adding a couple of new lines into the local_configs() function
+    in local.c. (YOU MUST UPDATE YOUR local.c FROM local.dst IN THIS
+    PATCHLEVEL). Patch by grapenut@M*U*S*H.
+  * Object ids: An object id is the string "#dbref:ctime"
+    where #dbref is the object's dbref and ctime is its creation time
+    in integer format. The %: substitution returns this
+    id for the enactor, and the objid() function returns it for an
+    arbitrary object. Object ids can be used in place of softcode that 
+    stores dbrefs to insure that a recycled dbref isn't used in place
+    of the intended one. The matcher code will also match objects by
+    id any time it's matching by dbref.
+  * @command/add and @command/del. You can add a custom command
+    (which will have the same precedence as a standard server command),
+    and then @hook it to softcode, effectively promoting the precedence
+    of softcoded globals, and letting them take advantage of some
+    command parser settings. Patch by Walker@M*U*S*H.
+Functions:
+  * tr() accepts ranges of characters like a-z instead of having to
+    give each one. [SW]
+  * escape() also escapes parens and commas now. Suggested by Philip Mak. [SW]
+  * time() can now take a time offset or object argument (in the latter
+    case, time offset is read from object's TZ attribute). Patch by
+    Walker@M*U*S*H.
+  * vcross() performs cross products of vectors. [SW]
+  * merge() can now take a list of characters. [SW]
+Minor changes:
+  * You can @set multiple flags at once by giving them as a list.
+    Suggested by Walker@M*U*S*H and others.
+  * Channel names are recognized when surround by <>'s, too. [SW]
+  * 'move' is now a command_alias for 'goto' (in alias.cnf), and not
+    a separate command.
+  * PAGE_LOCK`{,O,A}FAILURE attributes now activated when a page/pemit fails
+    due to the victim's @lock/page. Suggested by Sholevi@M*U*S*H.
+  * Tweaked game message for failing to provide correct password to
+    @password. Suggested by Philip Mak.
+  * New command 'warn_on_missing' (defaults to disabled), aliased to the
+    '[' character. If enabled, players who attempt to write commands
+    starting with functions will get errors. Suggested by [SW] and
+    Cheetah@M*U*S*H.
+  * Renaming something triggers its ONAME and ANAME attributes, if present.
+    The old name is passed as %0; the new as %1. Suggested by Philip Mak.
+  * Owner information on ex/br is reported using the usual object_header()
+    so dbref and flags appear. Suggested by Eratl@M*U*S*H.
+  * Flags that are F_DARK or F_MDARK no longer appear on @flag/list
+    by non-admin. Suggested by Philip Mak.
+  * Warn players who set themselves SHARED with a weak zone lock.
+    Suggested by Philip Mak. [SW]
+  * @halt can now take "here". Suggested by Thor@bDv.
+  * When parsing eqsplit commands, don't evaluate the left side
+    of the equal sign if the command was run with ].
+Fixes:
+  * Fixes to robustify file reading on Windows systems.
+  * The 'nofixed' command restriction works as expected now (previously,
+    you had to use 'nofix').
+  * Exit movements are now translated into explicit GOTO commands,
+    so @hooks and restrictions on GOTO are now applied. Patch by
+    Walker@M*U*S*H.
+  * The AE/ae accent characters can now be produced (accent(a,e)).
+    Patch by Luke@M*U*S*H.
+  * @hook/ignore would double-evaluate arguments. Reported by 
+    Ambrosia@M*U*S*H. [SW]
+  * Mingw error in src/Makefile.SH fixed. Report by Thor@bDv. [SW]
+  * Help fixes by Cerekk@bDv, Mike Griffiths, Steve Varley, Thor@bDv, [SW],
+    Dahan, Jason Stover, and Kyieren@M*U*S*H.
+  * cmdlocal.dst now includes flags.h. By Dahan.
+  * Win32 portability fixes by Dahan, Nathan Baum, [EEH].
+  * utils/mkcmds.sh is now smarter about choosing temp filenames, so
+    parallel make should work. Fixed by Cheetah@M*U*S*H.
+  * The Zone: data in examine could be wrong.
+
+
+& 1.7.7p27
+Version 1.7.7 patchlevel 27                     January 25, 2004
+
+Minor Changes:
+  * New etimefmt() formatting codes to make it easier to get nice-looking
+    results without 0-valued times. Suggested by ranko_usa@M*U*S*H. [SW]
+  * Autodetect existence of /usr/kerberos/include to make compile 
+    easier for RH9 sufferers.
+  * src/Makefile is now autobuilt from src/Makefile.SH. IF you use
+    local hacks that require src/Makefile, this is likely to be a problem
+    for you. You'll want to start patching Makefile.SH instead.
+  * Fewer warning flags are now provided to the compiler by default.
+    You can set your own warning flags instead by defining the
+    warnings variable in config.over.
+Fixes:
+  * The startups option actually does what it's supposed to now.
+  * Potential DOS in etimefmt fixed. Report by Ashen-Shugar. [SW]
+  * Code cleanup. ok_tag_attribute should work. [SW]
+  * Channels are automatically ungagged only on initial connection
+    (not reconnection, partial disconnection, etc.). Suggested by
+    Mordie@M*U*S*H.
+  * notify() calls during startup would crash. Reported by Mordie@M*U*S*H. [SW]
+  * Fixes from 1.7.6p15.
+
+
+& 1.7.7p26
+Version 1.7.7 patchlevel 26                     December 15, 2003
+
+Commands:
+  * Add /regexp switch to @switch and @select. Suggested by BladedThoth. [SW]
+  * New /spoof switch to @pemit, @remit, @lemit, @oemit, @emit,
+    causes the message to appear to be generated by the cause, rather
+    than the enactor, which makes globals like $ooc show the right
+    NOSPOOF information (instead of the name of the global command object).
+    Patch by Philip Mak.
+Functions:
+  * hostname(), ipaddr(), and cmds() take a dbref or descriptor number
+    of a connected player and return the hostname, ipaddr, and number
+    of commands executed. Suggested by Sholevi@M*U*S*H and Renee@ShoreSide,
+    code by Sholevi@M*U*S*H.
+  * Add reswitch*() functions. Suggested by BladedThoth. [SW]
+  * insert() can take a negative position argument to insert from
+    the right. Patch by Sholevi@M*U*S*H.
+  * New firstof() and allof() functions return the first true value
+    or all true values from a set of expressions. Patch by Sholevi@M*U*S*H.
+  * tr() works like the Unix utility, translating characters. Patch
+    by Walker@M*U*S*H.
+Attributes:
+  * Attributes may be set DEBUG to cause their evaluation to be
+    debugged selectively. Patch by Pozzo@SWForum.
+  * @desc can no longer be gotten remotely without privileges.
+    To implement this, a new attribute flag NEARBY was added,
+    which prevents visual attributes from being remotely accessed.
+    See new configuration directive 'read_remote_desc' if you prefer
+    the old behavior. Patch by Philip Mak.
+  * @desc on privileged objects can now be evaluated by mortals.
+    To implement this, a new attribute flag PUBLIC was added,
+    which overrides safer_ufun for that attribute. This flag is dangerous
+    and should be avoided unless you are sure you know what you're doing.
+    Patch by Philip Mak.
+Minor Changes:
+  * "+<channel> <text>" complains if <channel> is ambiguous, and
+    displays a list of matching channels. Patch by Luke@M*U*S*H.
+  * Code cleanup around @oemit by Philip Mak.
+  * If an object has an IDESCFORMAT but no IDESCRIBE, interior viewers
+    now see the DESCRIBE formatted by IDESCFORMAT (instead of
+    the DESCRIBE formatted by DESCFORMAT). Suggested by Philip Mak.
+  * Ported to Win32 with the Mingw development environment. 
+    See win32/README.mingw for compile instructions. [EEH]
+  * null() can now take any number of arguments. Patch by Walker@M*U*S*H.
+  * Using @chan/quiet to control the quiet flag on a channel no longer works
+    (Actually, it never did). Use @chan/priv instead. [SW]
+  * The NO_WARN flag now prevents disconnected room warnings, too.
+    Suggested by several people. Patch by Philip Mak.
+  * @sitelock/name !name still unlocks a reserved name, but no longer 
+    removes that name from names.cnf. Suggested by Nymeria. [SW]
+  * Some cleanup of fopen() calls. [SW]
+  * The reference to channel Creator is now Owner in @chan/decomp.
+    Suggested by Howie@NFTrekMUSH. [SW]
+  * The name of the channel being tested is passed as %0 in channel locks.
+    Suggested by Philip Mak. [SW]
+  * help for @chownall mentions the /preserve switch.  Warnings from @chown 
+    and @chownall tell which objects they're for. Suggested by
+    Mordie@M*U*S*H. [SW]
+  * Home wrecking is allowed again. Suggested by Philip Mak.
+Fixes:
+  * Puppets in containers with @listen container=* now hear the
+    outside world correctly. Patch by Philip Mak.
+  * The email sent on player registration now double-quotes the
+    player name in the example connect statement. Suggested by
+    David Kali.
+  * Two rooms should never be nearby() each other. Fix by Philip Mak.
+  * can_look_at() now handles the look/out case, too. Fix by Philip Mak.
+  * Help fixes by Viila@M*U*S*H, MetBoy@M*U*S*H, Cheetah@M*U*S*H.
+  * @flag/alias or @flag/add without giving an alias no longer crashes 
+    the MUSH.  Report by Wildfire.
+  * Correctly retrieve user full name from /etc/passwd fields under
+    Linux in Configure. Record it in config.sh. Reported by Vadiv@M*U*S*H.
+  * Debian package changes. [EEH]
+  * As the help promises, XCH_CMD and SEND attributes don't work
+    for non-Wizards. Really.
+
+
+& 1.7.7p25
+Version 1.7.7 patchlevel 25                     October 30, 2003
+
+Fixes:
+  * Crash bug in the interaction between parents and attr trees
+    fixed. Report by Walker@M*U*S*H.
+  * Improvements to how locks are decompiled. Built-in locks with the
+    no_inherit flag cleared are handled better.  If the object doing
+    the @decompile is referenced in a lock key, it's decompiled as 'me'
+    rather than by dbref, to make it easier to port between games.
+    Report by Cerekk@bDv. [SW]
+  * The error typically logged by info_slave on a mush crash is worded 
+    better. [SW]
+  * panic() renamed mush_panic() as the Mach kernel now has a public
+    system call panic(). Report by Jeff Ferrell.
+
+
+& 1.7.7p24
+Version 1.7.7 patchlevel 24                     October 19, 2003
+
+Minor Changes:
+  * The puppet flag can now apply to rooms. Suggested by Philip Mak.
+  * @tel/inside allows priv'd players to teleport into another player's
+    inventory (instead of to their location). Suggested by Philip Mak.
+Fixes:
+  * Startups from a created minimal.db did not properly initialize
+    the objdata htab, so subsequent use of that htab (e.g. adding
+    players to channels) would crash. Report by [LdW].
+  * Help fixes by BladedThoth@M*U*S*H and Philip Mak.
+  * Attempting to clear a branch attribute when it has leaves now
+    gives a better message. Also better message for inability to
+    write an attribute due to tree issues. Patch by Luke@M*U*S*H.
+  * Bug in @debugforwardlist fixed by Luke@M*U*S*H.
+
+
+& 1.7.7p23
+Version 1.7.7 patchlevel 23                     October 10, 2003
+
+Major Changes:
+  * Forking dumps now work with the chunk memory manager. [TAP]
+Minor Changes:
+  * Chunk allocator improvements in partial read/writes and for
+    Win32. [TAP]
+  * Use the SHA0 algorithm from OpenSSL instead of our own when possible. [SW]
+Functions:
+  * New digest() function for generating checksums via a variety of 
+    algorithms. [SW]
+  * cowner() returns the dbref of a channel's owner. Suggested by
+    Sholevi@M*U*S*H. [SW]
+  * Added baseconv(), for converting numbers from one base to another.
+    Useful for tweaking things like Myrddin's bboard code. Suggested
+    by many people. [SW]
+Fixes:
+  * Remove warnings in set.c and pcre.c. Report by Nymeria@M*U*S*H
+  * @power messages now show the canonical name of the power.
+    Suggested by Cheetah@M*U*S*H.
+  * Adding DARK players to channels produced a duplicated message.
+    Reported by Mystery8@ST:AW.
+  * Players set WIZARD and ROYALTY would get double notification 
+    of some GAME: messages. Fixed now. [SW]
+  * Chatdb corruption possible when admin add players to channels.
+    Reported by several folks, diagnosed by Mordie@M*U*S*H.
+
+
+& 1.7.7p22
+Version 1.7.7 patchlevel 22                     September 30, 2003
+
+Minor Changes:
+  * When possible, sockets are now set SO_KEEPALIVE and TCP_KEEPIDLE.
+    This may help prevent disconnections of idle players behind
+    poorly designed NAT routers and the like, and make the use of
+    the IDLE command unnecessary. Patch by Philip Mak.
+  * Better failure messages for 'get blah's blah', especially when
+    the container or contained object is ambiguous. Suggested by
+    Philip Mak. 
+Fixes:
+  * Attempting to unset attribute flags actually set them.
+    Report by Ambrosia@M*U*S*H. [SW]
+
+
+& 1.7.7p21
+Version 1.7.7 patchlevel 21                     September 23, 2003
+
+Major Changes:
+  * Attribute trees. Attributes may now be organized hierarchically
+    like files and directories in a filesystem, using the backtick (`)
+    character as the branch separator. Attribute access restrictions
+    propagate down trees. New wildcard ** is introduced to match
+    all attributes at all tree levels. Suggested by Tabbifli. [TAP]
+Locks:
+  * New framework for performing lock failure activities in hardcode.
+    As a proof-of-concept, the attributes FOLLOW_LOCK`FAILURE,
+    FOLLOW_LOCK`OFAILURE, FOLLOW_LOCK`AFAILURE do what you'd expect
+    when set on a potential leader.  Suggested by Sholevi@M*U*S*H.
+Channels:
+  * New per-channel flags NoTitles, NoNames, and NoCemit do what you'd
+    expect. Set them with @chan/privs. Based on suggestion by 
+    Saturn@M3.
+  * @chan/recall/quiet omits timestamps. Suggested by Vadiv@M*U*S*H.
+Commands:
+  * 'help <wildcard-pattern>' now lists all help topics that match that 
+    pattern.  By popular request. [MUX,SW] 
+  * @flag/letter can be used to change or clear the one-letter alias for a 
+    flag.  Suggested by Nymeria@M*U*S*H. [SW]
+  * @flag/list by God notes disabled flags. Suggested by Nymeria@M*U*S*H. [SW]
+Functions:
+  * rand() now comes in a two-argument (low,high) flavor, and randword()
+    selects a random word from a list. The latter is aliased to
+    pickrand() to match Mux's name. Patch by Luke@M*U*S*H.
+Minor Changes:
+  * Although we're Pueblo 2.50 compliant, go back to sending Pueblo 1.10
+    as the server version until everyone upgrades their clients so
+    they can handle the 2.50 string. Suggested by Shirow.
+  * The locate() function is no longer noisy (no longer notifies
+    the executor in addition to returning a value). Suggested by
+    Mystery8@ST:AW. 
+  * @lock/interact now has a higher priority than other interaction
+    checks, so it will work for Wizards. Suggested by Viila@M*U*S*H.
+  * Tweaks to facilitate a Debian package of PennMUSH. [EEH]
+Fixes:
+  * max descriptor could get stomped in some cases. [SW]
+  * Removed extra struct def in hdrs/mushtype.h. Suggested by Kyle.
+  * Help tweak by Kevin@M*U*S*H.
+  * Fix to locks on players messing up their connection failure counts.
+    Reported by Luke@M*U*S*H.
+  * Fix to @entrances by Luke@M*U*S*H.
+  * Fix to Win32 not handling a missing minimal.db by Luke@M*U*S*H.
+  * The confirmation message for setting/clearing attribute flags would use
+    the flag name given as an argument, not neccessarily the the full name of
+    the flag. Reported by Vadiv@M*U*S*H.  [SW]
+  * Fix a potential memory leak in ident.c [SW]
+
+& 1.7.7p20
+Version 1.7.7 patchlevel 20                     September 4, 2003
+
+Major Changes:
+  * minimal.db is no more. If you start up the server and there's no
+    db to be found, it creates a new minimal database in memory
+    on the fly. Feel free to delete your coopy of minimal.db. [SW]
+    (In related news, the default OSUCC on #0 in minimal.db is gone. 
+    Suggested by Cheetah@M*U*S*H. So much for the mists.)
+Minor Changes:
+  * SSL connections are now ended before the dump when rebooting,
+    but their descriptor information sticks around to ensure that
+    their @adisconnect is run after the reboot. Based on suggestions
+    by Vadiv@M*U*S*H and [TAP].
+  * When _DEBUG is defined in a win32 build, don't copy pennmush.exe
+    to pennmush_run.exe. Makes debugging easier. Patch by Luke@M*U*S*H.
+  * In pueblo mode, no longer clear past images after each room look.
+    Patch by Konstantine Shirow.
+  * In pueblo mode, we no longer render ansi attributes or colors with
+    HTML tags; we assume the client will correctly handle them as ansi
+    intermixed with Pueblo. This solves a variety of problems.
+    Based on a discussion with Konstantine Shirow.
+  * When matching $commands and ^listens, insure that a matching attribute
+    exists before testing locks. This may break some strange locks using
+    %c, but hopefully won't. Suggested by Cheetah@M*U*S*H.
+  * The @scan command temporarily sets %c to the command to be scanned
+    (without "@scan" prefixed) so that it can detect commands that use
+    %c-matching. Based on ideas from Cheetah and Walker@M*U*S*H.
+  * Added support for Pueblo's md5 checksum. We track it on each
+    descriptor, though we don't do anything with it currently.
+  * Speed-up to fun_merge. Patch by Walker@M*U*S*H.
+  * Internal cleanup of flags in channel_broadcast.
+  * Wizards may @name themselves to names forbidden in names.cnf.
+    Patch by LeeLaLimaLLama@M*U*S*H.
+  * We are now forgiving of stupid non-RFC-compliant telnet clients
+    that send CR only even when they claim to be sending CRLF
+    (Read: MS Windows XP telnet). MS bug reported first by
+    Intrevis@M*U*S*H.
+  * Misleading restrict_command on @clone removed from restrict.cnf,
+    as @dig/@open/@create permissions already control @clone.
+    Suggested by Philip Mak.
+Functions:
+  * lstats() and quota() now allow objects to see stats/quota on other
+    objects they control (e.g., on their owners, perhaps). Suggested
+    by Philip Mak.
+  * hastype() can now test for type GARBAGE. Suggested by Tanaku@M*U*S*H.
+Commands:
+  * The new ']' command prefix causes the rest of the command-line
+    not to be evaluated by the expression parser. Try: ]think [add(1,1)]
+    Inspired by Elendor.
+  * @hook/ignore. [mux]
+  * @hook/override [Rhost, though they don't call it that]
+Attributes:
+  * @debugforwardlist forwards DEBUG output to dbrefs on the list.
+    Suggested by Balerion@M*U*S*H.
+Tests:
+  * A new test harness for developing regression test suites in perl
+    for PennMUSH is now included; few test suites are. If you can figure
+    out how to use this, write some tests for us! (If you can't, don't
+    ask about it, please :)
+Locks:
+  * @lock/interact can prevent other players from transmitting any
+    normal sound to you (that is, you won't hear them speak, pose, 
+    emit, etc., like gagging them in a client). It doesn't control
+    page (use @lock/page) or @mail (use @lock/mail). You still
+    hear 'presence' messages (connects/disconnects/arrivals/leaves)
+    so you can have a sense of who can hear you but you can't hear.
+    Patch by Philip Mak.
+Fixes:
+  * Configure script no longer keeps adding -lssl -lcrypto over and
+    over to the Makefile. Report by Sholevi@M*U*S*H.
+  * Portability fixes for compiling with MSVC5 by Luke@M*U*S*H.
+  * The behavior of spaces inside parentheses that aren't part of a
+    function has been improved. Report by Jason Newquist. [TAP]
+  * txt/*.html files were being improperly escaped before being
+    sent to Pueblo connections. Report by Konstantine Shirow.
+  * mushdb.h includes flags.h, as it relies on constants from there.
+    Suggested by Philip Mak.
+  * Better error reporting on some failure messages in chunk allocator.
+    Patch by Philip Mak.
+  * @config/set with an invalid option now returns an error.
+    Report by Sholevi@M*U*S*H.
+  * Fix to interaction checking in notify_anything that could result
+    in the wrong check being performed. Report by Philip Mak.
+  * Help fixes by LeeLaLimaLLama@M*U*S*H, Sunny@M*U*S*H, Sketch@M*U*S*H.
+
+
+& 1.7.7p19
+Version 1.7.7 patchlevel 19                     August 19, 2003
+
+Fixes:
+  * When SSL connections are dropped on reboot, other players weren't
+    seeing disconnect messages. Now they are.
+  * Two calls to flaglist_check_long didn't get converted to the new
+    abstraction layer, making flag_broadcast not work right.
+    Report by Luminar@M*U*S*H.
+
+
+& 1.7.7p18
+Version 1.7.7 patchlevel 18                     August 19, 2003
+
+Major Changes:
+  * The flag handling code has been additionally abstracted to 
+    allow, in a future patchlevel, @powers to be handled in the
+    same way that flags are now.
+Minor Changes:
+  * Wrap the OS-dependant code for making sure the mush always has a free
+    file descriptor available for use in a pair of functions. [SW]
+Fixes:
+  * Linted some ssl-related warnings. Reported by Cheetah@M*U*S*H.
+  * Compile failed in timer.c unless USE_MAILER was defined.
+    Reported by Sunny@M*U*S*H.
+  * Bug allowing players to view internal and mortal_dark attributes
+    introduced in p17 has been fixed. [TAP]
+
+
+& 1.7.7p17
+Version 1.7.7 patchlevel 17                     August 11, 2003
+
+Major changes:
+  * SSL support, including server and client authentication as options.
+    This should be considered experimental.  New ssl() function returns 1
+    if a player/descriptor is using SSL, 0 otherwise. New file README.SSL
+    documents how to set up SSL. Two things to note:
+    - SSL connections are dropped on @shutdown/reboot, so SSL users may 
+      wish to write client triggers to reconnect. 
+    - Output flushing behaves slightly differently for SSL connections.
+  * The way lock keys are handled internally has been rewritten to allow
+    them to be tracked by the same chunk-management code that handles
+    attributes. [SW]
+  * @mail message texts are now stored with the chunk-management code. [SW]
+  * The new code can no longer read databases created by versions of Penn
+    before 1.7.5p0. If you need to do this, load it in 1.7.6, shutdown,
+    and use that database. [SW]
+Minor changes:
+  * A new minimal.db is distributed (older ones probably won't work,
+    and this one probably won't work with older versions). [TAP]
+  * Contents/exits lists and functions that use first_visible (like
+    lcon, lexits, etc.) are now affected by INTERACT_SEE. Suggested by
+    Prospero@Metro.
+  * @chan/recall now displays the last 10 lines by default. Use
+    @chan/recall <chan>=0 to get the full buffer. Suggested by [TAP].
+  * Various boolexp tweaks. [SW]
+  * @power provides more verbose feedback. Suggested by Mordie@M*U*S*H. [SW]
+  * Additional chunk tweaks, including limiting migrations to one
+    per second. [TAP]
+  * PROFILING #define for use when profiling the code (surprise). This
+    just disables the CPU timer.
+  * When matching $-commands, only call the slower capturing wildcard match
+    function when we already know it succeeds, since it won't most of the time.
+    The faster non-capturing function is checked first to find a match. [SW]
+  * PCRE is initialized once, at startup, rather than testing to see if it has
+    to be done before each place regular expressions are used. [SW]
+  * The TERSE flag now applies to objects, not just players. Suggested
+    by Aur@M*U*S*H.
+  * The terminfo() function now returns SSL status as well.
+  * help-like commands without arguments now use the command name
+    as the argument. E.g. 'news' now looks for topic 'news', instead of
+    'help'. If not found, we fall back on help. Patch by Mike Griffiths.
+  * When an object is zoned to an unlocked ZMO, the ZMO is now autolocked
+    to =me instead of $me, because it's probably less confusing.
+    Suggested by Luke@M*U*S*H.
+  * @name now automatically trims leading and trailing spaces from its
+    newname argument, to avoid confusing people who use edit() to 
+    generate new names and leave in spaces. Report by [TAP], patch by [SW].
+Commands:
+  * New 'logargs' and 'logname' restrictions in restrict.cnf allow
+    per-command logging. Suggested by Saturn @ M3.
+Functions:
+  * The sha1() function has been renamed sha0(), since that's what it
+    really does. [TAP]
+  * trimtiny() and trimpenn() work like trim(), but force the TM or
+    Penn argument style.
+  * New 'logargs' and 'logname' restrictions in restrict.cnf allow
+    per-function logging. Suggested by Saturn @ M3.
+Fixes:
+  * The side-effect version of name() was producing a weird return
+    value. Reported by Saturn@M3.
+  * Problems with restrict_command and restrict_function with multiple
+    restrictions now fixed. Report by Sholevi@M*U*S*H.
+  * stddef.h now included in src/extchat.c. Report by [EEH]. [SW]
+  * INFO output now includes a missing newline. Report by [EEH]. [SW]
+  * help BUILDER updated. Report by Laura Langland-Shula.
+  * down.txt message wasn't being sent. Report by Sholevi@M*U*S*H. [SW]
+  * New Win32 project files that include the chunk code. [EEH]
+  * @chan/what no longer highlights the channel's name, as this is
+    confusing if you use ansified channel names. Suggested by 
+    Oriens@Alexandria.
+  * ex/mortal now operates more correctly. Report by Amy Kou'ai.
+  * Fixes from 1.7.6p13.
+
+& 1.7.7p16
+Version 1.7.7 patchlevel 16                     June 23, 2003
+
+Commands:
+  * New switch /room for 'with' to run a $command available in a remote
+    location. Suggested by Mike Griffiths. [SW]
+Functions:
+  * Added the terminfo() function, which returns information about a
+    client's name and capabilities for a particular connection. [SW]
+  * New lports() function. Like lwho() but returns port descriptors.
+    For mux2 compatibility. [SW]
+  * Functions that return information about a connected player now treat
+    integer arguments as a port descriptor to look up, rather than using
+    the least-idle connection of a player. To force a player name to
+    be treated as such even when it's a number, prefix it with a * in
+    softcode. For mux2 compatibility. [SW]
+  * Players can use ports() on themselves and use the descriptors
+    they're connected to as arguments to the information functions.
+    For mux2 compatibility. [SW]
+Minor Changes:
+  * Compute various chunk stats (total used, total free space, etc.)
+    on demand instead of keeping running totals. [TAP]
+  * @chan/what displays information about a channel's recall buffer, if any.
+    [SW]
+  * @chan/recall'ed lines are more clearly marked as such. Suggested by
+    Oriens@Alexandria. [SW]
+  * Consolidation of a common idiom used to format times throughout the source
+    into a simple function call. [SW]
+  * The time a @mail was sent was stored, unusually, as a string.
+    No longer. Now it's handled the same way as all other times. [SW]
+Fixes:
+  * Bug in resizing @chan/recall buffers fixed. Reported by Oriens@Alexandria.
+    [SW]
+  * Objects with user-defined locks had problems with finding built-in locks
+    on the object. Reported by Walker@M*U*S*H. [SW]
+  * Unregistered() macro was checking wrong flag name. Report by
+    Matt Kirby.
+  * Help fix by Adu@M*U*S*H.
+  * Potential problem with ambigious names in the information functions fixed.
+
+
+& 1.7.7p15
+Version 1.7.7 patchlevel 15                     June 1, 2003
+
+Fixes:
+  * Problem with checking command flag masks when the number of
+    flags was an even multiple of 8. Reported by Nymeria and
+    Mordie@M*U*S*H.
+  * Tweak to improve efficiency of ancestor checking code and delint
+    warning reported by Cheetah@M*U*S*H.
+  * SESSION output no longer misaligned with 5-digit dbrefs.
+    Reported by Cheetah@M*U*S*H. [TAP].
+  * Fixes from 1.7.6p11.
+  * game/txt/index-files.pl now uses locale information in the
+    environment to, e.g., correctly lowercase accented characters.
+    Report by Krad@M*U*S*H.
+  * Modified several Makefile.SH targets to prevent Javelin from
+    releasing patches that don't have the autogenerated files
+    up-to-date for Windows folks.
+  * Removed some dependence on typedefs that may or may not be in system
+    header files. [SW]
+  * Patch compiler warnings. [SW,EEH]
+  * Help fixes by Mike Griffiths and Oriens@Alexandria.
+
+
+& 1.7.7p14
+Version 1.7.7 patchlevel 14                     May 22, 2003
+
+Major changes:
+  * Ancestors: an object can be configured to serve as an 'ultimate
+    parent' of every room, exit, thing, or player, and attributes
+    that are not found on an object or any of its parents may be inherited
+    from the ancestor for that object type.  The ORPHAN flag prevents
+    ancestors from being used on an object.  Patch by Walker@M*U*S*H.
+  * Mail messages now track not only the dbref of the sender but the
+    sender's creation time, so messages from dbrefs that have been
+    nuked and recreated can be distinguished from messages from the
+    original sender. This modifies the maildb and make it not usable
+    with older versions. All existing @mail is grandfathered in, and
+    can't be tracked this way. Suggested by Philip Mak.
+  * New chunk memory allocator can be used to greatly reduce process
+    memory requirements by swapping little-used attribute texts out
+    to disk and caching often-used attribute texts in memory.
+    This is incompatible with forking dumps, so if you use it,
+    you'll do nonforking dumps.  Configurable in mush.cnf, see comments
+    there. [TAP]
+  * Hardcode: new interaction type INTERACT_PRESENCE marks the
+    arrival/departure/connection/disconnection/grows ears/loses ears
+    messages. Many message types that used to be considered auditory
+    (like most @verb-style messages) are now marked as visual instead.
+Functions:
+  * strreplace() works like replace() for strings. [SW]
+  * fraction(), for turning floating-point numbers into fractions. [SW]
+  * root(), for finding roots higher than the square root. [SW]
+Minor changes:
+  * We now use wrapper functions atr_value and safe_atr_value instead of
+    uncompress(AL_STR(...)) or safe_uncompress(AL_STR(...)), so we
+    can do future work with attribute storage cleanly. [TAP]
+  * @*payment attributes now receive the amount of money paid in
+    as %0. Suggested by Sholevi and Time@M*U*S*H. [SW]
+  * Config options that take times now accept a notation describing what kind
+    of units to use. For example, 3600s, 60m, 30m1800s, and 1h all refer to
+    the same period of time. Suggested by Time@M*U*S*H. [SW]
+  * Doxygen commenting of non-static members is essentially complete!
+    Pennhacks see: http://www.pennmush.org/docs/1.7.7/html/
+  * The mail() function no longer matches non-player objects by name.
+  * Several additional messages (locks, parents, etc.) are now quieted by
+    the QUIET flag. Patch by [EEH].
+  * New config option default_home sets the room to send homeless things
+    to. [TAP]
+  * Added visual progress indicators for utils/mkcmds.sh so that slow
+    systems won't think they're hung. Suggested by Cheetah@M*U*S*H.
+Fixes:
+  * Fixes from 1.7.6p10.
+  * The 'i' sort type was not properly implemented. Reported by
+    Noltar and BlaZE@M*U*S*H.
+  * Cleanup of all accesses to ATTR values to use AL_STR() in preparation
+    for future work on attribute storage. [TAP]
+  * The 'any' string for specifying flag types didn't work properly.
+    Reported by Krad@M*U*S*H.
+  * The connect screen may now appear correctly under windows telnet. [SW]
+  * The more efficient channel buffer shifting code now handles
+    pathological cases correctly.
+  * The tag*() functions could leave tags open at the end of full
+    buffers. No longer.
+  * Code cleanup in src/notify.c.
+  * @rejectmotd and @wizmotd set the wrong messages. Report by
+    Konstantine Shirow. [SW]
+  * Using @chan/buffer to resize a recall buffer gives feedback. Reported by
+    Sholevi@M*U*S*H. [SW]
+  * Help fix for grab() by Adu@AbryssMUSH.
+  * You can no longer destroy the base_room (or default_home). Suggested
+    by Philip Mak. [TAP]
+
+& 1.7.7p13
+Version 1.7.7 patchlevel 13                     April 9, 2003
+
+Major changes:
+  * Interactions (something like "realms" in mux2). Two functions
+    in local.c can now be used to control conditions under which
+    objects can see, hear, or match each other. Possibly useful for
+    implementing umbral worlds, etc. Patch by Vadiv@M*U*S*H.
+Functions:
+  * children(), syntactic sugar for lsearch(all,parent,<dbref>).
+    Suggested by Kyieren@M*U*S*H. Patch by BlaZe@M*U*S*H.
+  * powers() can now take a second argument to set an @power.
+    Suggested by Rob@BtFMUSH.
+Minor changes:
+  * @config/set can now set null strings. Suggested by Cheetah@M*U*S*H.
+  * In restart, set LC_ALL as well as LANG from the given LANG value,
+    in case the user's got an LC_ALL in their shell.
+  * The channel buffer shifting code has gotten much more efficient.
+    Suggested by [TAP].
+  * @function/restrict can accept arguments of the form '!<restriction>'
+    to clear a restriction. Suggested by Saturn@M3.
+  * Most of the asterisk lines between different login message files
+    have been removed. Suggested by Vadiv@M*U*S*H most recently.
+Fixes:
+  * Fixes from 1.7.6p9.
+  * Win32 portability fixes. [EEH]
+  * deny_silent in access.cnf was ignored in several cases, and no
+    longer is. Patch by Cloud@Sci-Fi Chat
+  * Help fixes by Cheetah@M*U*S*H and LeeLaLimaLLama@M*U*S*H.
+
+
+& 1.7.7p12
+Version 1.7.7 patchlevel 12                     March 21, 2003
+
+Commands:
+  * @channel/buffer creates a buffer for a channel to store the most
+    recent messages broadcast on the channel. @channel/recall can be
+    used to recall them. These are not stored across reboots and should
+    be set up by #1's @startup.
+Functions/Substitutions:
+  * accname() gives the accented name of an object (applying @nameaccent).
+  * %~ gives the accented name of the enactor.
+Minor Changes:
+  * The chat-related commands and functions have been moved out
+    of bsd.c and funmisc.c and into extchat.c. Patch by Vadiv@M*U*S*H.
+  * The notification stuff has been moved out of bsd.c and into a new
+    notify.c file.
+  * @name no longer requires a password for changing player names,
+    and ignores one if given. Suggested by Ambrosia@M*U*S*H (and others).
+  * @hook can not be used on the @password or @newpassword commands.
+  * The dump_complete message is also shown after a forking dump,
+    if one is defined. Suggested by Nathan Schuette.
+  * @lock/leave on a room now prevents people within it from leaving
+    via exits or via @tel. Suggested by Peter Bengtson, patch by
+    BlaZe@M*U*S*H.
+Fixes:
+  * Fixes from 1.7.6p8
+  * Cleanup of a few new db[x] mentions in the source to use dbdefs.h
+    macros. Inspired by Vadiv@M*U*S*H.
+  * @command/restrict didn't work properly for most flags, especially
+    new ones. Reported by Caesar and Sholevi@M*U*S*H.
+  * @pemit/list didn't honor NOSPOOF. Patch by Cheetah@M*U*S*H.
+
+
+& 1.7.7p11
+Version 1.7.7 patchlevel 11                     February 22, 2003
+
+Commands:
+  * New IDLE command (socket-level) does nothing, and does not update
+    the socket's last_time (so it doesn't change idle times). Useful
+    for people behind lame NAT firewalls that timeout their connections if
+    nothing is sent for 5 minutes. Suggested by Bolt and BladedThoth@M*U*S*H.
+Fixes:
+  * Win32 (and other OS) portability fixes. [EEH]
+  * Fixed the openssl Configure thing again. The right way, this time.
+
+& 1.7.7p10
+Version 1.7.7 patchlevel 10                     February 22, 2003
+
+Fixes:
+  * Fix to stupid typo in Configure script that breaks on systems
+    without openssl. Argh.
+
+& 1.7.7p9
+Version 1.7.7 patchlevel 9                      February 20, 2003
+
+Functions:
+  * New function scan() works like @scan. Suggested by Viila@M*U*S*H.
+Flags:
+  * New flag, MISTRUST, prevents an object from controlling anything
+    but itself.
+Configuration:
+  * mush.cnf directives ansi_justify, globals, and global_connects have
+    been removed (they are now always on).
+  * New unconnected_idle_timeout directive in mush.cnf controls
+    timeouts for connections idle at the connect screen.
+  * New max_guests directive in mush.cnf can limit the number of
+    guests allowed to connect at once. Suggested by Sholevi@M*U*SH.
+Minor Changes:
+  * New lflags search class takes a list of flag names.
+  * Improved connection failure messages.
+  * Somewhat more informative message when you @chan/gag,hide,mute
+    all channels at once. Suggested by Tanaku and Kevin@M*U*S*H.
+  * Began commenting files using doxygen.
+  * Internal code cleanup. Mostly converting some magic numbers to
+    #define'd symbols, and some #define'd symbols to enums for better
+    debugging and improved readability. Also some conversion of old
+    K&R style functions. [SW]
+  * sort() and the set functions understand all the same comparison
+    types as comp(). [SW]
+  * Case-sensitive comparison currently isn't always possible, depending
+    on the locale the mush is running on. Help files reflect this. [SW]
+  * @uptime shows the time of the last successful database save, and
+    the time of future events like saves, not just the time until them.
+    Suggested by Cheetah@M*U*S*H. [SW]
+  * Improvements to reporting of failed saves. [SW]
+  * Code cleanup. [SW]
+  * tel() now takes a third argument that makes it function like
+    @tel/silent. Suggested by Cheetah@M*U*S*H. [SW]
+  * @idescformat operates like @descformat for internal descriptions.
+    Suggested by Tanya@M*U*S*H.
+Fixes:
+  * local_startup() was getting run earlier than in the past due to
+    changes in the startup sequence. This has been rectified, so
+    local_startup() again runs after all other initialization (and
+    just before all object startups are triggered). Report by
+    BladedThoth and grapenut@M*U*S*H.
+  * Improved testing for openssl libraries in Configure. The old
+    approach used to cause problems on systems with runtime-only
+    openssl installations without development libraries.
+  * help opaque mentions that opaque blocks look/outside. Suggested
+    by Cheetah@M*U*S*H.
+  * itext() and inum() now generate an error on a null argument,
+    regardless of tiny_math and null_eq_zero settings. Reported by
+    Intrevis@M*U*S*H.
+  * Another fix to the new matcher. Bug report by Kyieren@M*U*S*H.
+  * @flag/alias was broken. Fixed. Reported by Kevin@M*U*S*H.
+
+
+& 1.7.7p8
+Version 1.7.7 patchlevel 8                      January 27, 2003
+
+Minor Changes:
+  * command_add now expects to receive the flag list and the
+    switch list as strings. Folks who hack into cmdlocal.c should
+    take note and read example in the new cmdlocal.dst
+Fixes:
+  * Players were not created with all the player_flags. In a related
+    bug, checking of command flag restrictions wouldn't work with
+    all flags. Reported by Cory Descoteau.
+  * Flagmasks on commands weren't grown properly when flags were added.
+
+
+& 1.7.7p7
+Version 1.7.7 patchlevel 7                      January 25, 2003
+
+Fixes:
+  * Crash bug in zone-checking during @dbck fixed.
+
+
+& 1.7.7p6
+Version 1.7.7 patchlevel 6                      January 23, 2003
+
+Major changes:
+  * Rewrite of the flag system. The new system allows for any number
+    of flags, which may apply to any set of object types (the flags/toggles
+    distinction has been eliminated). Flags without single-character
+    abbreviations are better supported. Flags are stored in the object
+    database, and are referenced in hardcode and the db by the name
+    of the flag rather than bit positions.  Older databases will be
+    automatically converted to the new format on load, but can not be
+    converted back (so make backups!). New flaglocal.dst file for
+    hardcode patch hackers to add custom flags.
+  * Rewrite of the matcher code (src/match.c). Some semantics of the
+    matching have changed: matching adjectives are parsed earlier and
+    restrict the match for greater efficiency; they also behave more
+    close as described in the help with respect to object ordering.
+    In addition, you can now do things by dbref without controlling
+    the object in many cases that previously required control.
+    Provoked by bug reports by Intrevis@M*U*S*H and Philip Mak.
+Commands:
+  * @flag allows God to manipulate flags within the game, including
+    adding new flags. Flags additions/changes are maintained across
+    reboots, so this command does not need to be run at every startup.
+Functions:
+  * lflags(), orlflags(), andlflags() return or test flag lists.
+Minor changes:
+  * New NUMVERSION macro defined in hdrs/version.h that can be tested
+    by hardcode hacks to add code conditional on version.
+  * Much cleanup of @wall. Minimally-user-visible changes:
+    The @rwallemit, @rwallpose, @wallemit, @wallpose, @wizemit and
+    @wizpose commands have been removed. @wall no longer accepts the
+    /wizard, /royalty, and /pose switches, and @rwall and @wizwall accept
+    the /emit switch. Suggested by Vadiv@M*U*S*H, though he'd really
+    rather see @wall removed.
+  * @lock and @unlock now show which type of lock was set/cleared.
+    @lset now specifically notes that lock flags are being changed.
+    Suggested by Tanaku@M*U*S*H.
+Fixes:
+  * @boot/me will no longer boot a connection if it is the sole
+    connection the player, even if it's technically inactive.
+    Suggested by Ambrosia@M*U*S*H.
+  * @boot'ing an active self (by descriptor) crashes the MUSH.
+    Discovered by Ashlynn@ChicagoMUSH.
+  * The thorn and eth characters generated with accent() would
+    convert to 'th' when stripped or viewed under NOACCENT, which
+    could be very confusing in object names. Same for the German sharp
+    s, which converted to 'ss'. Until we can cleverly set up separate
+    tables for object name unparsing, these have been reverted to their
+    old behavior so that stripaccents(accent(X,Y)) should return X for
+    any X and Y. Reported by DeeJee, Intrevis, and Time (@M*U*S*H).
+
+
+& 1.7.7p5
+Version 1.7.7 patchlevel 5                      January 7, 2003
+
+Fixes:
+  * Fixes from 1.7.6p5.
+
+
+& 1.7.7p4
+Version 1.7.7 patchlevel 4                      January 2, 2003
+
+Minor Changes:
+  * When room_connects is on, @aconnect and @adisconnect also
+    functions on things when players (dis)connect inside them.
+    Suggested by Philip Mak. [SW]
+  * Parser-enforced argument counts for user-defined @functions,
+    as an option to @function.
+Config:
+  * New mush.cnf option max_global_fns allows increasing the number
+    of @functions allowed without editing source code. If you change
+    this, you should reboot the MUSH or bad things can happen.
+    Suggested by hilikiradi@Dardalani.
+Fixes:
+  * mkcmds.sh doesn't always regenerate every file, only what's
+    needed. Speeds up compiles. Suggested by Philip Mak. [SW]
+  * Fixes from 1.7.6p4.
+
+
+& 1.7.7p3
+Version 1.7.7 patchlevel 3                      December 25, 2002
+
+Commands:
+  * @sitelock/check <host> tells you which rule, if any, would match.
+Fixes:
+  * The objdata hashtable routines had a serious bug that could
+    cause crashes.
+
+
+& 1.7.7p2
+Version 1.7.7 patchlevel 2                      December 22, 2002
+
+Major Changes:
+  * The LOCAL_DATA define has been removed along with the pointer
+    in the object structure. The local functions called on creation,
+    destruction, and cloning are now always called. Objects can
+    now store data in a single hashtable using the set_objdata()
+    and get_objdata() functions. As a proof of concept, the transitory
+    channel lists on objects are now stored here, and the "channels"
+    pointer has been removed from the object structure. Design
+    and much of the implementation by Vadiv@M*U*S*H.
+Powers:
+  * can_nspemit power can be used to provide access to @nspemit
+    without a Wizard bit. [SW]
+Functions:
+  * lpos from Mux, TM3. [SW]
+Fixes:
+  * Fix to some gcc-specific macros reported by Peter Bengston and
+    Michael Holbrook. [SW]
+  * Improvements to stripaccents/noaccents conversions. [SW]
+  * Fixes from 1.7.6p3.
+
+
+& 1.7.7p1
+Version 1.7.7 patchlevel 1                      December 17, 2002
+
+Minor Changes:
+  * ex obj/attrib returns the attribute value even if it's veiled,
+    if a specific (non-wildcard) attribute is given. Suggested by
+    Nhoj@M*U*S*H.
+Fixes:
+  * Win32 portability fixes. [EEH]
+  * Fixes from 1.7.6p2
+
+& 1.7.7p0
+Version 1.7.7 patchlevel 0                      December 8, 2002
+
+Major Changes:
+  * Clients that understand telnet NAWS (See RFC 1073) can tell the mush
+    what dimensions a given connection's display is.  Added the
+    width() and height() functions, and SCREENWIDTH and SCREENHEIGHT
+    psuedo-commands for getting/setting this information from the mush.
+    This changes the reboot.db format and requires a full shutdown. [SW]
+  * Two new atoms for locks. "#true" in a lock is always evaluated as true
+    (anyone can pass), and "#false" is always evaluated as false (no one
+    can pass). Suggested by Vadiv@M*U*S*H.
+Internationalization:
+  * The pronoun sets are no longer hardcoded. If you're running in a
+    locale other than C or en*, you'll see weird looking pronoun descriptions
+    for things like %s until a translation team translates them to your
+    locale's language. Suggested by Dandy@M*U*S*H.
+Attributes:
+  * @DESCFORMAT can be used to separate description contents from formatting.
+    Suggested by Philip Mak.
+  * VEILED attribute flag causes attribute value not to be displayed
+    on default examine, but otherwise accessible as usual. Good for spammy
+    data attributes. See 'help attribute flags'. Suggested by Cheetah@M*U*S*H.
+Commands:
+  * examine/all shows contents of veiled attributes. Suggested by
+    Intrevis@M*U*S*H.
+Flags:
+  * The FIXED and ROYALTY flags are no longer optional.
+Minor Changes:
+  * Object creation times are no longer optional.
+  * Warnings are no longer a compile-time option; they're turned on.
+    You can stop automatic warnings in mush.cnf, as before.
+  * Cleanup of the telnet-option code in general. [SW]
+  * Consolidation of much of the code for functions that return information
+    about the least-idle connection of a given player. [SW]
+  * The tiny_attrs configuration option has been removed.
+  * Removed a lot of preprocessor checks for conditionally including header
+    files that always succeed because they're standard C headers. [SW]
+  * Removed the Size_t typedef in favor of the standard size_t. [SW]
+  * Some optimization hints for the GCC and VS.NET compilers. [SW]
+  * We try to be more conservative about when we show lines of
+    asterisks around motd-type messages, to avoid showing them when
+    there's no message.
+  * Continued ansi-C-ification of function declarations.
diff --git a/game/txt/hlp/pennvOLD.hlp b/game/txt/hlp/pennvOLD.hlp
new file mode 100644 (file)
index 0000000..8bd9371
--- /dev/null
@@ -0,0 +1,4442 @@
+& 1.7.3p14
+Version 1.7.3 patchlevel 14                    January 29, 2001
+
+Major Changes:
+      * Commands and functions can now be aliased to other names
+        in mush.cnf, so you can provide translated command names
+        as well as the english one, etc. Suggested at various times
+        by Krad@M*U*S*H and David@M*U*S*H. New file aliases.cnf
+        in game/ is created to put these command_alias and function_alias
+        directives (as well as reserve_alias). [SW]
+Minor Changes:
+      * smalloc and bigram compression options are no longer supported. [SW]
+      * Internal improvements to @search/lsearch, which are now more
+        consistent with one another. [SW]
+      * mush.cnf options that refer to dbrefs now understand #<number> as 
+        well as just <number>. They are also formatted as dbrefs in @config 
+        and config(). [SW]
+      * Much longer game/names.cnf file contributed by Kyieren@M*U*S*H.
+      * New internal function notify_format() to replace notify+tprintf
+        more safely. [SW]
+      * Tweaks to network activity timeout code. [SW]
+      * @stat and lstats no longer needs to scan the entire database. [SW]
+Commands:
+      * @switch and @select can now take a /notify switch, like @dolist.
+      * Players may @chown by dbref, but they must still be carrying
+        the object if it's a thing. Suggested by Kyieren@M*U*S*H.
+      * @clone can now clone rooms. Suggested by Kyieren@M*U*S*H.
+      * @verb's matching of the <actor> argument has been relaxed so
+        non-privileged objects can use it on remote actors, if the normal
+        permission checks succeed. This makes @verb much more useful for zones
+        and master room objects. [SW]
+Attributes:
+      * @forwardlist [2.2,SW]
+Functions:
+      * itemize() takes a list "a b c" and produces "a, b, and c" and 
+        similar tricks. Also callable as elist() for Rhost compatibility.
+      * ilev() returns the current iter nesting level. [Rhost,SW]
+Fixes:
+      * When indexing help files, ones that aren't in the right format
+        weren't being properly closed. Report by Nammyung@M*U*S*H. [SW]
+      * We're much more forgiving about help files that start with
+        blank lines now.
+      * Help updates by Kyieren@M*U*S*H, [SW]
+      * Games w/o MAIL_ALIAS couldn't load maildbs that had mail aliases.
+        Reported by Nymeria. [SW]
+      * max_pennies had a limit of 10,000 in conf.c, even though it
+        has always defaulted to 100,000. Reported by Intrevis@M*U*S*H.
+      * src/filecopy.c didn't include hdrs/log.h. Fixed by Noltar@Korongil.
+      * MacOS portability fixes. [DW]
+      * vsnprintf used in place of vsprintf everywhere when available. [SW]
+      * Cleanup of @atrlock code. [SW]
+      * '#' without any trailing numbers was treated as #0 when used as a 
+         dbref. [SW]
+      * Added explicit checking for lower-case letters in good_atr_name(). [SW]
+      * Trying to set an attribute with a bad name now gives a better 
+        error message. [SW] 
+      * Fix to potential overflow bug in revwords. [SW]
+      * Fix to potential overflow bug in @grep. [SW]
+      * Configure checks for snprintf. Systems unfortunate enough not to
+        have this (Solaris 2.5.1, for example) are stuck with sprintf.
+        Report by Broncalo@DuneIII.
+      * CHARGES were decremented whenever an A-attribute would be 
+        executed, *even if the attribute wasn't set on the object*.
+        Reported by Kyieren@M*U*S*H.
+
+& 1.7.3p13
+Version 1.7.3 patchlevel 13                    January 9, 2001
+
+Major Changes:
+      * Semaphores may use attributes other than SEMAPHORE. These 
+        "named semaphores" make it easier to have multiple semaphores
+        waiting on the same object. [SW]
+      * New attribute flag AF_NODUMP prevents attributes from being
+        dumped to the db. It applies to QUEUE, SEMAPHORE, and attributes
+        used as named SEMAPHORES. [SW]
+Commands:
+      * @wait/until can be used to put commands on the wait queue until
+        some absolute time (in secs) is reached. [SW]
+      * @cpattr/noflagcopy copies attribute data without flags.
+        Same for @mvattr. Suggested by Noltar@Korongil.
+Functions:
+      * foreach() takes start and end arguments like TM3/MUX2. [3,SW]
+      * locate() has a new option to force matching the preferred type, 
+        and an undocumented option to eliminate ambiguous matching is now
+        documented. [SW] 
+Minor Changes:
+      * Experimental support for translating 8-bit accented characters to HTML
+        entities for Pueblo. [SW]
+      * Objects without a @lock/zone that are used as zones are noticed and 
+        warned about during dbcks. [SW]
+      * Some people prefer that +channel "foo now cause you to say "foo 
+        on the channel, instead of stripping the initial quote. Now
+        it's a mush.cnf option (chat_strip_quote). Suggested by Dave Milford.
+      * isint() now ignores tiny_math, so isint(foo) will always return 0. [SW] 
+Fixes:
+      * Hopefully fixed a problem with puppets doing look for non-Pueblo
+        players with Pueblo support turned on. The line after the look
+        could appear to come from the puppet even if it didn't. [SW]
+      * If you disabled a command with a one-character token (like say),
+        then running the command with the token ("hi!) would fail, but 
+        wouldn't return the right thing for $command matching. Now it
+        returns the canoncalized raw command (SAY hi!) so you can trap
+        it in softcode. Reported by Mackenzie@SWGT Mush  
+      * General linting [SW]
+      * Help fixes by Eratl@M*U*S*H.
+      * make customize doesn't try to link mkindx any more. And README and
+        os2/Makefile don't refer to it either. By Bobby Bailey (Chil@M*U*S*H).
+      * The internal tprintf() function is safer. [SW]
+      * Crash bug in @edit fixed, and @edit code is cleaned up. [SW] 
+      * max_pennies documented in mush.cnf. Noted by Oriens@Alexandria
+      * cmdlocal.dst now includes cmds.h. Reported by David@M*U*S*H.
+
+
+& 1.7.3p12
+Version 1.7.3 patchlevel 12                    January 4, 2001
+
+Locks:
+      * A new @lock/chzone, set on a ZMO, controls who may @chzone
+        objects to the ZMO. This can simplify multi-player building
+        using ZMOs. Suggested by David@M*U*S*H
+Commands:
+      * @wcheck/me checks all of a player's objects for them.
+Functions:
+      * cflags() function gets channel flags.
+      * ctitle() gets channel title for an object. Suggested by Luke@M*U*S*H.
+      * strinsert() function does insert() on strings. Suggested by
+        Reagan@M*U*S*H. [SW]
+      * channels() on an object you can't examine now shows you channels 
+        that you have in common with it. Suggested by Trispis@M*U*S*H
+Minor changes:
+      * Runaway objects are now logged, along with the command that
+        pushed them over the edge. Suggested by Trispis@M*U*S*H.
+      * All instances of fprintf(stderr,...) after logging is started
+        are now handled through do_rawlog(LT_ERR,...). By David@M*U*S*H.
+      * @atrchown works more reasonably. [SW]
+      * @lock/link on an unlinked exit restricts who may link it.
+      * Everyone (even mortals) sees object names at the top of
+        their descriptions when they look at them. Suggested by Jeff
+        Ferrell most recently.
+      * New reserve_alias mush.cnf directive makes run-time command
+        alias reservation possible. Suggested by Jeff Ferrell.
+      * Mortals may use @command to get info about commands. Suggested by
+        Chili@M*U*S*H.
+      * Messages logged during help file indexing distinguish indexing
+        of admin topics. Suggested by mith@M*U*S*H
+Fixes:
+      * Help fixes by [SW], Jeff Ferrell, mith@M*U*S*H, Mirth@AtheneuMUSH.
+      * Help update to @cpattr based on behavior noted by Noltar@Korongil.
+      * cwho and @chan/who behave better with hidden (@hide) players.
+        Reported by Reagan@M*U*S*H.
+      * MUSHes with no @functions crashed on @list calls due to a 
+        problem with the hashtable code. Reported by Whiskey. [SW]
+      * A bug in handling of errors under debug in process_expression
+        was reported by Reagan@M*U*S*H. [SW]
+      * Rare memory leak in process_expression fixed. [SW] 
+      * Inconsistencies in the handling of destroyed objects/players
+        and their locations fixed. [SW]
+      * The QUEUE attribute was not properly cleared on restart
+        for non-player objects. It was buggy in other ways, too. [SW]
+      * The AF_SAFE attribute flag couldn't be reset. Reported by
+        Pegasus@StarQuest.
+      * @teleporting into garbage could cause crashes. Reported by Howie
+      * The help_command and ahelp_command directives now look for
+        whitespace generally, not spaces specifically. 
+        Suggested by Krad@M*U*S*H
+      * SIGHUP once again re-reads the config file. [SW]
+      * If an exit @created an object, that object would be inside the exit 
+        until the next dbck. Now it is created in the exit's source room. [SW]
+      * Turning the lowest possible integer number into a string could 
+        result in garbage output. [SW]
+      * Solaris needed arpa/nameser.h before resolv.h in mysocket.c
+
+& 1.7.3p11
+Version 1.7.3 patchlevel 11                    December 4, 2000
+
+Minor changes:
+      * Added 'comsys' as help alias for 'chat'. Suggested by David@M*U*S*H
+      * New win32 project files and MSVC build instructions by Jenny Lind.
+Fixes:
+      * Improved test for non-broken getdate and getnameinfo.
+      * Loading panic dumps does better on chatdb. By Maverick@M*U*S*H
+      * You may leave a channel even if you're the wrong type
+        to get onto it. Suggested by Cheetah@M*U*S*H.
+      * Typo in the win32 strcasecmp code fixed [NJG]
+      * stddev's algorithm improved. [TAP]
+      * If the go command is restricted, other commands produce spurious
+        errors during the exit-matching phase of command parsing.
+        Reported by Jamie@M*U*S*H
+
+& 1.7.3p10
+Version 1.7.3 patchlevel 10                    November 20, 2000
+
+Major Changes:
+      * Improved detection of errors saving the game. If any problems 
+        are encountered, the save is aborted, and online admins notified so
+        they can fix the problem before finding out about it too late. [SW] 
+Flags:
+      * The INHERIT flag has been renamed TRUST, which better describes
+        its function. INHERIT remains as an alias.
+Commands:
+      * @chan now takes /ungag, /unhide, and /unmute as well as the
+        usual @chan/gag <channel>=no. By David@M*U*S*H.
+Minor Changes:
+      * money() called on a no_pay player returns the value of
+        MAX_PENNIES to ease softcode checks. Suggested by Oriens@Alexandria.
+      * Removed help and &help entries from the distributed pennmush.nws
+        because people will generally want to override them anyway
+        and we shouldn't introduce problems. Suggested by Jeff Heinen.
+      * safe_str and friends optimize trivial 0 and 1 letter strings. [SW]
+      * A version of word-based compression that's almost 8-bit clean
+        is now included. [SW]
+      * We now use stricmp and friends for strcasecmp and friends on
+        Win32, rather than roll our own. [SW]
+Fixes:
+      * @mail aliases couldn't be used by players who didn't have
+        permissions to see the alias members, which is wrong.
+        Fixed now. Report by Grinna@M*U*S*H.
+      * lnum(1) and lnum(0,0) were broken. Report by Jeff Ferrell 
+      * Help updates. [SW]
+      * @set obj/notanattribute=flag now returns a 'No such attribute' error.
+        Reported by David@M*U*S*H. [SW]
+      * Help file indexing tries to detect files that aren't in the right 
+        format. [SW]
+      * function restrictions were checking the wrong object. [SW] 
+      * objmem and playermem counted eval-locks and atr-locks incorrectly. 
+        Reported by Javin@DynamixMUSH. [SW]
+      * Fixes to win32 NT_TCP stuff. [NJG]
+      * Rare memory leak in do_page fixed by David@M*U*S*H. 
+
+& 1.7.3p9
+Version 1.7.3 patchlevel 9                    November 20, 2000
+
+Major Changes:
+      * Help files and their associated commands are now defined with
+        the 'help_command' (or 'ahelp_command') directive 
+        in mush.cnf. So you can translate the help to french and
+        add an "aidez-moi" command if you like. [SW]
+      * Help file indexes are now kept in memory and built by the
+        server. The mkindx program is no longer used. [SW]
+      * Added restrict_function and @function/restrict, like the versions 
+        for commands. [SW]
+      * User @functions can now override built-in functions, if you
+        @function/delete the built-in first. You can also @function/restore
+        the built-in back. [SW]
+Attributes:
+      * @[oa]zenter and @[oa]zleave on ZMOs are triggered when someone
+        enters or leaves a given zone. Motion between rooms in the same
+        zone doesn't trigger these. "Zone" is based on the mover's
+        absolute room (outermost container) so that entering and leaving
+        an unzoned object in a zoned room doesn't trigger these either.
+        Suggested by Michael Kurtz.
+Commands:
+      * New /silent switch for @teleport without @[o|a|ox]tport msgs.
+Minor Changes:
+      * Still less allocation involved in outputting text to connections. [SW]
+      * @scan has better output when multiple attribs match. [SW]
+      * Added internal absolute_room function for use by fun_room
+        and move.c and wiz.c, etc.
+      * MEM_CHECK now uses a sorted linked list and is faster. [SW]
+Fixes:
+      * References to kill_timer removed from win32 sections. [SW]
+      * conf.c reports error via logging routines, not fprintf. [SW]
+      * Assorted minor bug and warning fixes. [SW]
+      * Fix to example in help regedit by Amberyl.
+      * @wait and other timing functions were broken under win32.
+        Fixed now. [LdW]
+
+
+& 1.7.3p8
+Version 1.7.3 patchlevel 8                    November 12, 2000
+
+Major Changes:
+      * Objects' names are stored in a string tree like attributes, so
+        we don't have hundreds of copies of 'West <W>;west;w' and such
+        taking up memory. Attribute name "hash table" is now a strtree. [SW]
+      * We no longer use alarm() to know when to run stuff on the queue. [SW]
+      * @shutdown/reboot is now supported on Windows builds. This is
+        slightly experimental. By Revian.
+Minor Changes:
+      * %qa-%qz now operate as global registers like %q0-%q9. 
+        Suggested by Trispis@M*U*S*H and probably others.
+      * Hashtable lookups are faster (collision chains are sorted) [SW]
+      * @uptime/mortal suppresses the extra process information [SW]
+      * Wizard @uptime prints better process information on linux. [SW]
+      * @listen, @filter and @infilter respect the REGEXP and CASE attribute 
+        flags.  Suggested by Maestro@M*U*S*H [SW]
+      * Having many @waits or semaphores is more efficient, because we
+        sort those queues by time and we stop looking sooner. [SW]
+      * User-defined lock names must follow the same rules as attribute 
+        names. The name part of attrib and eval locks have to also. [SW]
+      * @chan/title's confirmation message is nicer. [SW]
+      * Minor optimizations related to strcpy() and malloc() overhead. [SW]
+      * safe_format uses vsnprintf if your system has it. By Luke@M*U*S*H.
+      * replace_string is safer. By Luke@M*U*S*H.
+Fixes:
+      * iter() is smarter about quitting when the function invocation
+        limit is reached or the buffer is filled. [TAP]
+      * lnum() has been greatly sped up. [TAP]
+      * RWHO references removed from game/mushcnf.dst, game/restart,
+        etc. by mith@M*U*S*H.
+      * Fix to help filter by Revian.
+      * COMPRESSION_TYPE 0 didn't compile. Report by David@M*U*S*H.
+      * Clarification of @lock/teleport in help @elock by Envinyatar@M*U*S*H
+      * Compiling w/o MAIL_ALIASES didn't declare load_maliases.
+        Reported by Envinyatar@M*U*S*H
+      * clone() was stomping %0-%9. Report by Revian.
+      * @dol with an empty list now does the right thing (nothing)
+        instead of running the command once. Report by Linda Naughton [SW]
+      * spname cleanup by mith@M*U*S*H.
+      * Fixed a bug in @function function-name [SW]
+      * make update should finally handle CHAT_TOKEN_ALIAS right.
+        It's also smarter with mush.cnf
+      * isword() on a null string now returns 0. Suggested by Ashen-Shugar.
+      * channels(object) returns a better error when object isn't matched.
+        Suggested by Trispis.
+      * Fix to help rand() to reflect actual error return value. Reported
+        by Philip Mak.
+      * More translated strings noted by Krad.
+      * Problems with encrypt()'s logic fixed by Ashen-Shugar. [Rhost]
+      * Other encrypt() sillyness fixed by Brazil.
+      * Potential crashers around DEBUG and other buffer overruns
+        have been fixed. Report by Tajan and Rimnar. [SW]
+      * notify_anything allocates less memory. [SW]
+      * Fixed mistagged memcheck "restart.descriptor". 
+      * MacOS portability changes. Should build on MacOS X public beta
+        (Darwin) systems nearly straightaway. [DW]
+      * Restart script test for already-running MUSH condensed to one
+        line. Suggested most recently by Cory Albrecht.
+      * Serious crash bug in page fixed. Reported by Revian.
+      * Win32 bugs fixed by Luke@M*U*S*H: problems with dumping
+        compression suffixes, problems with @uptime
+      * MUSH no longer crashes if a player alias is > 32 chars in length.
+        It truncates names that are too long instead. By Luke@M*U*S*H.
+      * We don't check AF_PRIVATE attributes for $/^commands on children.
+        By Luke@M*U*S*H.
+      * Under win32, the MUSH would often start ignoring commands from
+        players after the first 98 per connection. Fixed by Revian.
+        
+& 1.7.3p7
+Version 1.7.3 patchlevel 7                    October 12, 2000
+
+Functions:
+      * filterbool(), for TM3 compatibility. Like filter(),
+        but tests for any true value, not just 1. [3,SW]
+      * case(), for TM3 compatibility, and caseall().  Like switch() 
+        and switchall(), but does an exact match, not wildcard. [3,SW]
+      * valid() takes more categories. [SW]
+      * localize(), for TM3 compatibility. Like a cross between
+        s() and ulocal(). [3,SW]
+Commands:
+      * @break, for Rhost compatibility. Stops processing the
+        current action list. [Rhost,SW]
+      * @enable and @disable can be used on any boolean config option. [SW]
+      * @function/enable and @function/disable for built-ins
+      * @function function-name reports some information on the function,
+        like @command command-name.
+Flags:
+      * New attribute flag 'safe' prevents accidental modification.
+        Suggested by Stephen Viscido. [TAP]
+Minor Changes:
+      * The daytime config option is no more. Do a few @searches in
+        its honor. [SW]
+      * lsearch() permission is controlled by @search, and entrances()
+        by @entrances, for consistency with other functions that have
+        a corresponding command. [SW]
+      * The number of reboots is tracked, and the restarts() function
+        added, for TM3 compatibility. [3,SW]
+      * Rearranged some compile flags to suit systems that have
+        /usr/local/include/ident.h. By Luiz Claudio Duarte.
+      * More strings marked for translation. By Krad@M*U*S*H
+      * :, [, ], (, and ) are no longer legal in attribute names
+        due to ambiguities and security issues. [TAP,SW]
+      * Ranges in @search, lsearch() and @find can be either #dbrefs or 
+        (for backwards compatibility), integers. Inspired by ElendorMUSH. [SW]
+      * Broadcast messages in bsd.c replaced with GAME prefix.
+        Suggested by Krad@M*U*S*H.
+Fixes:
+      * Fixed a bug in filter() where it only looked
+        at the first character of the evaluated attribute. [SW]
+      * Some more noise from info_slave was removed. [SW]
+      * do_pcreate and do_chownall now live in game.h and not
+        externs.h. Reported by Maestro@M*U*S*H.
+      * Clean-up of atr_add calls in void context. [TAP]
+      * IPv6 buglet fixed. Report by Vexon@M*U*S*H. [SW]
+      * @config/set can no longer be used to set options in the files   
+        and messages categories, as this has icky consequences. [SW]
+      * Fixed a logic bug in letters_to_privs where not all the letters 
+        were being turned into privs properly. Report by Bolt@M*U*S*H [SW]
+      * Fix to isint() so that isint(1a) is 0. [SW]
+      * Added safe_boolean internal function, and fixed a hang bug
+        in edit() reported by Walker@M*U*S*H. [SW]
+      * Fixed problems in panic dumps/reads of maildb and chatdb. [SW]
+      * @edit is a bit more efficient. [SW]
+      * Assorted lock structures are allocated in big chunks like 
+        attribute structures, to save malloc overhead. [SW]
+      * User-defined lock names are stored in the attribute name tree. [SW]
+      * Various help fixes [SW, Javelin]
+      * LASTLOGOUT time now forces two-digit day for convtime niceness. [SW]
+      * Very large malias names or member lists could cause buffer 
+        overruns. [SW]
+      * Buffer overrun fix, fix to str_chr. [TAP]
+      * tprintfs removed from DEBUG output so DEBUG doesn't mess up
+        messages in ^-commands anymore. [TAP]
+
+& 1.7.3p6
+Version 1.7.3 patchlevel 6                    September 20, 2000
+
+Minor Changes:
+      * Translation files are now archived separately on the ftp site.
+      * A variety of options.h settings have been removed.
+        EXTENDED_ANSI, DUMP_EMPTY_ATTRS, DUMP_LESS_GARBAGE and the *_LOCK 
+        defines are totally gone. [SW]
+        OLD_NEWLINES, DELETE_ATTRS and VISIBLE_EMPTY_ATTRS have been 
+        moved out of options.h as they're special-purpose. [SW]
+      * More common function error messages were made into variables 
+        rather than being hardcoded in as string literals. [SW]
+      * If a player is set HALT, their queued commands will not run.
+      * Speedup in process_expression. [TAP].
+Functions:
+      * The regedit(?:all)?i? family of functions, like perl's s///. [SW]
+      * Case-insenstive versions of regrab() and regraball(). [SW]
+      * etimefmt(), which is to timestring() as timefmt() is to time(). [SW]
+Fixes:
+      * Error messages that were already variables are now translated. [SW]
+      * Fixes to various metaconfig rules. [SW]
+      * Open_Anywhere and Link_Anywhere were sharing the same
+        bitmask. Fixed. [SW]
+      * You can escape : in $command patterns that are being regexp-
+        matched now. [SW]
+      * Rewrites of the regexp functions so that, say, regrab() and
+        regraball() point to the same actual code, using called_as to 
+        know when to stop. They also use PCRE's match optimizing 
+        pcre_study() function when appropriate. [SW]
+      * Buglets in game/restart and game/mushcnf.dst fixed.
+        Reported by Krad and Nymeria at M*U*S*H.
+      * page_aliases directive in mush.cnf works now. Report by Nymeria.
+      * Same for float_precision. Report by Oleo@M*U*S*H.
+      * mushtype.h now included in compress.c. [DW]
+      * Less noise in log/netmush.log from failed ident queries.
+      * More strings marked for translation.
+      * Fixes to problems with @search reported by Oleo@M*U*S*H. 
+      * Weird evaluation of functions in softcoded commands fixed. [TAP]
+      * Cleanup of typos here and there by Padraic@M*U*S*H.
+
+
+& 1.7.3p5
+Version 1.7.3 patchlevel 5                    September 7, 2000
+
+Minor Changes:
+      * FLOATING_POINT is no longer an option (it's always on). [SW]
+      * EXTENDED_ANSI defaults to enabled. [SW]
+Attributes:
+      * @receive/@oreceive/@areceive triggered on the recipient
+        after a get or give, so you've got access to who caused
+        you to acquire the object and the object's dbref now.
+      * @give/@ogive/@agive triggered on the giver with object's
+        dbref in %0. Suggested by Oriens@Alexandria. 
+Fixes:
+      * Fixes for systems with broken or incomplete IPv6 support. [SW]
+      * Uses of index() changed to strchr() for consistency. [SW]
+      * Much removal of duplicate function prototypes and rearranging
+        of headers.  hdrs/globals.h is now hdrs/case.h. hdrs/intrface.h is 
+        no more, and hdrs/boolexp.h, hdrs/log.h were added. [SW]
+      * @search supports "quoted player names".
+      * We no longer report failed connect to ident servers in the log.
+
+& 1.7.3p4
+Version 1.7.3 patchlevel 4                    August 8, 2000
+
+Major Changes:
+      * Internationalization:
+        * Support for international time formats via LC_TIME locale [SW]
+        * Support for message translation
+        * Support for locale-sensitive ordering of strings (LC_COLLATE) [SW]
+        To take advantage of the new features, you should have your
+        LANG environment variable set to an appropriate locale 
+        before you 'make install' (which will cause the right message
+        catalog to be compiled), and you should see the section 
+        in game/restart for setting it there (which will actually cause
+        the server to use it).
+      * IPv6 support [SW]
+Commands:
+      * @dolist/delim and @map/delim [SW]
+      * @stats/tables [SW]
+      * SESSION command displays session statistics (experimental) [SW]
+Functions:
+      * uldefault(), like udefault but saves registers like ulocal() [SW]
+      * switchall(), for Tiny compatibility. [SW]
+      * cemit() with an option to act like @cemit/noisy [SW]
+      * vmin() and vmax(), for returning the min and max of each pair in two
+        vectors. [SW]
+      * utctime(), convutcsecs() for UTC/GMT time instead of server-local. [SW]
+      * convtime() uses getdate() if present, along with a variety of templates
+      * that it can accept. [SW]
+      * timefmt() - like the strftime() C function. [SW]
+      * pcreate() side effect function suggested by Adamas and Padraic@M*U*S*H
+      * starttime() now returns the first startup time, and 
+        restarttime() returns the time of the last @shutdown/reboot [SW]
+Minor Changes:
+      * +help is mentioned in help help. Suggested by Trispis@M*U*S*H.
+      * include directive for config files, with an example moving all
+        the restrict_command's to another file. [SW]
+      * make indent runs expand, then indent, because indent doesn't seem to
+        handle tabs very well. [SW]
+      * index-files.pl sorts patchlevels correctly. Patch by Jeff
+        Heinen.
+      * LASTLOGOUT attribute records logout time, like LAST, but not
+        visual. Suggested by Oriens@Alexandria, and others.
+      * Internal cleanup by David@M*U*S*H. New @config category 'messages',
+        no more OBJECT_ENDOWMENT or OBJECT_DEPOSIT macros, etc.
+      * Internal functions safe_integer(), safe_number(), and safe_dbref()
+        to replace safe_str(unparse_FOO(data), buff, bp) calls  [SW]
+      * You can now @trigger an attribute containing a $command or
+        ^listen and it'll work (skipping the $...: or ^...: parts).
+        So you can now do this:
+         &DO_WHO obj=$who *: @pemit %#=[u(who,%0)]
+         &DO_+WHO obj=$+who *: @tr me/do_who=%0
+        (But you can do this much more efficiently with regexp...)
+Fixes:
+      * table() is less CPU-intensive in extreme cases. [SW]
+      * Hopefully, Configure now determines pagesize on FreeBSD.
+        Method suggested by Matt Harris.
+      * CHAT_TOKEN_ALIAS comment clarification by Oleo@M*U*S*H.
+      * pcre regexp engine updated to version 3.4.
+      * Typo in @chan/who fixed by Vexon@M*U*S*H.
+      * @attribute/access won't modify AF_INTERNAL attributes now.
+      * Additional win32 portability fixes. [NJG]
+      * con() was buggy in a bad way. Fixed now.
+      * Configure -d should now work on linux systems that don't have
+        crypt libraries. Reports by mith and Inek@M*U*S*H.
+      * Fix to Z_TEL on things.
+      * Help fix to @lock5 by Datron@SW2.
+Languages:
+      * Swedish and Hungarian translations for most strings are
+        included in this patchlevel.
+
+
+& 1.7.3p3
+Version 1.7.3 patchlevel 3                    July 12, 2000
+
+Major Changes:
+      * Restrictions to the 7-bit ascii charset have largely been removed
+        except in attribute names, to help international users. [SW]
+      * If available, we now use setlocale to support international
+        charsets (and eventually other conventions, though this should
+        be considered experimental). If you set your LC_CTYPE environment
+        variable to, say, 'fr_FR', french-accented characters should work.
+        Wide (multibyte) charsets are not supported.
+Minor Changes:
+      * Internal cleanup of page/whisper code by David@M*U*S*H.
+      * New mush.cnf directive, page_aliases, for showing alias of
+        paging player. Supported by code by David@M*U*S*H.
+        Requested by many. A contrib version by Flame dates to 1996.
+      * @chat on a non-existant channel returns an error message. [SW]
+      * Two new CRYPT_SYSTEM options. One checks both SHS and crypt(3)
+        for passwords, and saves them back using SHS. The other does 
+        the same for plaintext passwords. These should encourage folks
+        who currently use crypt(3) to make a painless move to SHS. [SW]
+Commands:
+      * @remit can take /silent and /noisy switches now. Suggested by
+        Philip Mak.
+      * @lemit and @emit can take /silent switch. [SW]
+      * @config/set can set configuration parameters at runtime. [SW]
+Functions:
+      * The set algebra functions can be given a sort order for output. [SW]
+Fixes:
+      * CHAT_TOKEN_ALIAS could get defined w/o a character value.
+        Added a better explanation of CHAT_TOKEN_ALIAS in options.h.dist.
+        and fixed utils/update.pl to handle commented defines that take 
+        values a bit better. Report by Nymeria@M*U*S*H.
+      * You can no longer initiate following a disconnected player.
+        Report by Dave@Galactic.
+      * CHANGES for 1.7.1 and 1.7.0 were missing. Back now.
+      * Typo in options.h.dist corrected. Report by Padraic@M*U*S*H.
+      * Small Configure portability improvements.
+      * Better handling of cases where the maildb has messages from
+        dbrefs that are out of range (due to truncating a db to remove
+        corruption, for example). Suggested by Ashen-Shugar.
+      * We now check for sitelocked sites before asking info_slave to
+        do ident lookups.
+      * Many help clarifications. [SW]
+      * linux Configure can use nm to find symbols, finally.
+      * help locate() now includes the z flag.
+
+
+& 1.7.3p2
+Version 1.7.3 patchlevel 2                    June 3, 2000
+
+Commands:
+      * New @sitelock options to control access to god, wizards, admin
+        by site. [SW]
+      * @force can now take /noeval [SW]
+
+Functions:
+      * squish() can take a second argument to squish non-spaces. [SW]
+      * div(), floordiv(), modulo(), and remainder(), a set of functions
+        jointly adopted by MUSH and MUX servers for compatibility. [TAP]
+      * @@() and null() functions suggested by [LdW].
+
+Minor Changes:
+      * @uptime now shows initial restart time, not just time since
+        last reboot.
+      * Each player now has a limit to the number of @mail messages
+        in their inbox (folder 0), configurable in mush.cnf.
+        Suggested by Edwin@M*U*S*H.
+
+Fixes:
+      * More linting and improved indenting [SW]
+      * PARANOID works right for broadcast messages (like @cemit) now
+        too. Report by Vexon@M*U*S*H.
+      * You can no longer follow what you can't see.
+      * CHAT_TOKEN_ALIAS info appears in options.h now. Report by 
+        Rhysem@M*U*S*H.
+      * Mac portability changes. [DW]
+      * Disconnected players don't follow any more. Suggested by Don Burks.
+      * Various fixes to better resist crashing due to attacks involving
+        overwhelming connections. 
+      * @mail/stats for all was broken. Fixed now.
+      * Clearer message after failed @pemit. Suggested by Eratl@M*U*S*H
+      * Destroyed things stop following/leading. Report by Ashen-Shugar.
+      * follow didn't properly set up the followers as enactors.
+        We no longer short-circuit process_command. Report by Moe@Chicago.
+
+& 1.7.3p1
+Version 1.7.3 patchlevel 1                    May 18, 2000
+
+Commands:
+      * @oemit now takes a list of players. Adapted from patch by Philip Mak.
+
+Minor Changes:
+      * Reconnecting is less spammy - we don't show motds again
+        to players already connected. Suggested by Trispis@M*U*S*H.
+
+Fixes:
+      * Configure problem that resulted in weird compile failures on
+        bind/accept in src/bsd.c fixed.
+      * Further linting. [SW]
+      * FreeBSD getrlimit problem diagnosed by [SW] is worked around.
+      * Couldn't compile w/o FLOATING_POINTS defined. Fixed.
+      * Fixed a few dependencies in the Makefiles to insure that
+        hdrs/patches.h and hdrs/switches.h are rebuilt properly.
+      * Indentation cleanup.
+      * We now recognize egcs as if it were gcc 2, and set ccflags
+        accordingly.
+      * Increased size of some hash tables for performance. [SW]
+      * Help fixes. [SW]
+      * flags(obj/attrib) behaved badly unless attrib was CAPITALIZED.
+        Fixed now. Reported by Vexon@M*U*S*H.
+
+& 1.7.3p0
+Version 1.7.3 patchlevel 0                    April 20, 2000
+
+Major Changes:
+      * If you create a 'patches' subdirectory and keep any user-contrib
+        patches you apply in there, and if the patches are properly 
+        formatted, i.e., they include these lines:
+              # Patch name:
+              # Patch version:
+        your MUSH's @version and INFO output will report them.
+        In addition to being helpful for you, this will help the
+        developers when you send us a bug report including your 
+        @version. [TN]
+      * As @cemit doesn't override @chan/gag and allows 
+        NOSPOOF notification, it basically now operates just like
+        @pemit/list (you can protect yourself from spoofing, and you can
+        silence it). Accordingly, the cemit power is no longer 
+        necessary. It's now a runtime option.
+      * @malias (@mailing lists) by Daniel Peters.
+      * Attribute names are now stored in a single string tree,
+        so we don't have thousands of copies of the string
+        "FINGER_NOTE", etc., taking up memory. [TAP]
+      * As a consequence of the attribute name tree, the STARTUP flag 
+        is no longer needed, and will be automatically removed from
+        dbs.
+      * Attributes are now inserted in alphabetical order, which
+        speeds lookup. [TAP]
+      * Panic dumps now dump the maildb and chatdb, appended to the
+        end of PANIC.db. The MUSH handles breaking them up on restart.
+      * New link_anywhere power allows @link'ing to any destination.
+      * Mortals may create VARIABLE exits. At the time the destination 
+        is computed, the exit is check to see if it has permission to
+        link there (i.e., the exit controls the destination or the
+        exit is link_anywhere or the destination is link_ok).
+        To keep old code from breaking, all existing variable exits are 
+        given the link_anywhere power at first db read in this patch.
+        Suggested by David@M*U*S*H.
+      * The follow command is implemented!
+      * Nested iter is now useful. The itext(n) function returns
+        the value of ## for the nth innermost iteraction, where
+        0 is the most inner, and inum(n) does the same for #@. [TN]
+      * New regexp library, pcre, now allows perl 5.005 compatible
+        regular expressions! Suggested by [SW].
+      * Objects are now limited in the number of attributes that may
+        be set on them. This prevents a DoS attack. Suggested by
+        Ashen-Shugar.
+      * Some more english-style matching (look my 2nd box). [TN]
+
+Functions:
+      * config() returns a list of config option names. 
+        config(<option>) returns the value of a config option.
+        (e.g. config(money_singular))
+      * sort() now accepts an output delimiter, a la iter().
+        Suggested by Jason Newquist.
+      * channels() now accepts a delimiter. Suggested by Trispis@M*U*S*H.
+      * money(<integer>) returns the name of the money, either singular
+        or plural, depending on <integer>. Suggested by Trispis@M*U*S*H.
+      * timestring() with a pad flag of 2 forces 2 digit numbers.
+        Suggested by Trispis@M*U*S*H.
+      * fmod() function returns floating point remainder on division.
+        Written by Michael Thole.
+      * brackets() function returns bracket counts for its unparsed
+        argument. Handy for debugging. By Jason Wilcox.
+      * edit() can take multiple find-replace pairs. By Chili@M*U*S*H.
+      * clock() function by Ari@SpaceMUSH and Chili@M*U*S*H.
+      * flags() function can show attribute flags as well. 
+        Suggested by Kami@SW2
+      * mailstats(), mailfstats(), and maildstats() added by Kami@SW2
+      * nattr() (aka attrcnt()) returns number of attributes on 
+        an object. Suggested by Ashen-Shugar.
+      * map() and foreach() now provide the element's position
+        through %1. [LdW]
+      * spellnum() function spells out numbers in words. [LdW]
+      * wrap() for server-based line wrapping. Adapted from code by [LdW]
+      * lmath() function lets you do math on delimited lists, and makes
+        it easy to emulate Tiny's ladd/lsub/etc. [SW]
+      * bitwise math functions. [SW]
+      * mean(), median(), and stddev() functions. [SW]
+      * bound() function for bounding numbers. [SW]
+      * regrab(), regraball(), and regrep() regular expression 
+        versions of grab/graball/grep. [SW]
+      * controls() can now be used if you control either the <obj> or
+        the <victim>. [RLM] suggested this in July 1998, but we were
+        too boneheaded at the time to agree on it.
+
+Commands:
+      * teach <command> shows others in your room the (unparsed)
+        command that you want to demonstrate, and then causes you
+        to execute it. Suggested by Reed Riner.
+      * /preserve switch for @clone and @chown to preserve privbits.
+        By Kurt Fitzner.
+      * rpage and rwho have been removed.
+      * @nameformat on a room allows you to specify how the room's
+        name should be displayed to those inside it when they look.
+      * An optional second token for chat (in addition to +) can
+        be set if you'd like + and = (or whatever) to both work.
+        Patch by Kami@SW2.
+      * @scan returns the matched attribute name as well as object.
+        Suggested by many, including Thi@M*U*S*H.
+      * ; waves is treated as :waves, instead of as ;waves.
+        Suggested by Sandi Fallon, for tiny compatibility.
+      * cv command at connect screen forces a !DARK connect.
+        Suggested by David@M*U*S*H.
+      * with obj=command tries a $command on a specific object. [TN]
+      * @mailsignature finally implemented.
+      * @chan/join and @chan/leave are aliases for @chan/on and @chan/off,
+        respectively. Suggested by [LdW]
+      * @chan/decomp/brief decompiles a channel without listing players.
+
+Flags:
+      * LISTEN_PARENT flag causes the object to inherit ^listens
+        from its parent. By Kurt Fitzner.
+      * Internal ACCESSED flag removed.
+      * PARANOID player toggle replaces the old paranoid_nospoof
+        configuration directive, and allows per-player setting of
+        nospoof format. Suggested by Trispis@M*U*S*H
+
+Minor Changes:
+      * New lock @lock/command controls who may use $commands on an
+        object. @lock/listen controls ^patterns, @lock/use controls both.
+        Patch by Kurt Fitzner.
+      * The max_obj_val and max_wiz_obj_val configuration options
+        have been removed, as they're rarely used. You can change them
+        in hdrs/conf.h (search for ENDOWMENT).
+      * src/connect.c is no longer distributed. It wasn't ever used
+        for anything anyway.
+      * @fixdb command removed.
+      * @config/functions and commands can show listings in lowercase.
+      * match_list changed to try to match player aliases. Allows
+        "look <alias>" for a player in the same room. Reported by Corum.
+      * See_All players can now see/check AF_WIZARD attributes
+        (but AF_MDARK still requires that you be roy/wiz).
+        Suggested by Balazs Gasparin.
+      * VERBOSE PUPPETs relay to their owners even if the owner's
+        in the same room. Dedicated to Julianna@ATS.
+      * You may now @dest objects that you control via a zone,
+        as you could have done so indirectly before anyway.
+        Reported by [LdW]
+      * Sending the MUSH process an INT signal now causes graceful
+        shutdown (not panic dump). Sending a USR2 signal causes
+        a normal dump. As before, HUP causes config reload and
+        TERM causes a panic dump. [TAP]
+      * @chan/list shows your gag status. Suggested by Matt@M*U*S*H
+      * When chatting, we only match partial channel names against
+        channels you're actually on. Suggested by Matt@M*U*S*H
+      * By default you can no longer speak to a channel you're not
+        on. This is configurable per-channel with the new "open"
+        priv. Suggested by Akiko@M*U*S*H.
+      * If you can't go through an exit because it's linked to
+        garbage or its variable destination is invalid, we no longer
+        process the SUCC and DROP message set on the exit.
+      * The Inherit() macro no longer includes a Wizard test -- we
+        don't need it anymore as we protect Wiz objects in controls().
+      * getrandom has been replaced by get_random_long, with a better
+        algorithm and interface. Suggested by Stephen Dennis. [TAP]
+      * Win32 compilers now get the __cdecl hint so they can compile
+        using __fastcall which can greatly increase speed. Patch by
+        Stephen Dennis.
+      * For WIN32, use GetSystemTime instead of timeGetSystemTime.
+        Patch by Stephen Dennis.
+      * For WIN32, use a combination of wsadata.iMaxSockets and
+        options.max_logins to pick a reasonable number of available file
+        descriptors. Patch by Stephen Dennis.
+      * Default dump messages now call it a 'save', not a 'dump',
+        to avoid newbie confusion. Suggested by Vedui.
+      * You're notified if you set an attribute/flag on a quiet object
+        that you don't own. Patch by Kurt Fitzner.
+      * @decomp now comments its "No attributes found" message so as
+        not to break scripts.  Report by Kurt Fitzner.
+      * More Mac tweaking. [DW]
+      * \ and % are no longer valid in attribute names. Suggested by Ali Abdin
+      * Cleanup to logging code. We now try to do almost all of it through
+        log.c functions for better encapsulation. Patch by David@M*U*S*H.
+      * New @lock/examine to restrict who may examine visual objects.
+        Suggested by [LdW]
+      * Examining objects now shows channels they're on, if any.
+        Suggested by Big Spoon@M*U*S*H.
+      * Channel-hidden players are now marked in @chan/who for those
+        who are allowed to see them.
+      * @uptime shows more upcoming events, and shows them to mortals.
+        Suggested by Kyieren@M*U*S*H.
+      * @chzone obj works like @chzone obj=none. Suggested by Mystery8@M*U*S*H
+      * Player creation is now announced to MONITOR players. Suggested
+        by Paul@M*U*S*H.
+      * Poll message kept across @shutdown/reboot. Suggested by [SW].
+      * The military_time directive is removed from mush.cnf. It only
+        affected the way time was shown in @idle/@haven/@away messages
+        anyway. Reported by Angelus@M*U*S*H.
+
+Fixes:
+      * help for lnum() and dig() improved. Leo@ATS TrekMUSH
+      * help for @charges improved. Suggested by Scott Weber
+      * @mvattr a/b=a/b would clear the attribute. No longer.
+        Reported by Octavian@M*U*S*H
+      * type(obj) would log a "WEIRD OBJECT" message if obj was 
+        a garbage object. Reported by RLM. [TAP]
+      * Bug in deciding when to take a penny for queued commands fixed
+        by Stephen Dennis.
+      * Portability fixes for gcc 2.95.2 and other compilers who require
+        that function pointers in prototypes include the function args. 
+        Reported by Gepht.
+      * @chan/decomp should include the channel description, too. 
+        Report by David@M*U*S*H.
+      * Two other ways to be inside an object inside yourself reported by
+        Ashen-Shugar, and one by Rhysem@M*U*S*H.
+      * Small memory leak when doing @cpattr of a standard attribute to a
+        non-standard attribute is fixed.
+      * Help clarification for pemit() suggested by Falor@M*U*S*H.
+      * Help parents and @search3 fixed. Suggested by rodregis@M*U*S*H.
+      * Tport_anything didn't allow teleporting things to exits. 
+        Noted by Vexon@M*U*S*H.
+      * Z_TEL flag works on ZMO's as promised now. Report by [SW].
+      * Potential crash in moveit fixed. Report by Howie@NF TrekMush
+      * @cemit now does the checks that @chat does, in regard to being
+        of the right type, allowed to speak, on the channel, etc.
+        Suggested by Oleo@M*U*S*H.
+      * getstring_noalloc was doing an fgetc into a char variable,
+        instead of an int, so wasn't 8-bit clean. Report by Slava.
+
+& 1.7.2p35
+Version 1.7.2 patchlevel 35                        January 27, 2001
+
+Fixes:
+      * Fixed a bug in filter introduced in p34. Report by Jason Rhodes.
+      * Help for sort() now indicates that 'n' sorts integers. Report by
+        Dave Milford.
+
+
+& 1.7.2p34
+Version 1.7.2 patchlevel 34                        October 2, 2000
+
+Fixes:
+      * filter now looks at the whole result, not just the first
+        character, when checking if the filter function returned '1'. [SW]
+      * raw_input and raw_input_at are now unsigned char *, so
+        they build right on HP/UX and similar. Report by Jeff Hildebrand
+
+
+& 1.7.2p33
+Version 1.7.2 patchlevel 33                        August 17, 2000
+
+Fixes (backported from 1.7.3p4):
+      * Bug in con() patched.
+      * Bug in deciding when to take a penny for queued commands fixed
+        by Stephen Dennis.
+      * Configure portability fixes
+      * Better handling of cases where the maildb has messages from
+        dbrefs that are out of range (due to truncating a db to remove
+        corruption, for example). Suggested by Ashen-Shugar.
+      * Various fixes to better resist crashing due to attacks involving
+        overwhelming connections. 
+      * @mvattr a/b=a/b would clear the attribute. No longer.
+        Reported by Octavian@M*U*S*H
+      * type(obj) would log a "WEIRD OBJECT" message if obj was 
+        a garbage object. Reported by RLM. [TAP]
+      * Small memory leak when doing @cpattr of a standard attribute to a
+        non-standard attribute is fixed.
+      * Tport_anything didn't allow teleporting things to exits. 
+        Noted by Vexon@M*U*S*H.
+      * Z_TEL flag works on ZMO's as promised now. Report by [SW].
+      * Potential crash in moveit fixed. Report by Howie@NF TrekMush
+      * getstring_noalloc was doing an fgetc into a char variable,
+        instead of an int, so wasn't 8-bit clean. Report by Slava.
+
+& 1.7.2p32
+Version 1.7.2 patchlevel 32                        April 17, 2000
+
+Fixes:
+      * @cpattr from a non-standard attribute to a standard one
+        didn't preserve the AF_STATIC flag, and a subsequent atr_clr
+        could cause a crash.
+
+& 1.7.2p31
+Version 1.7.2 patchlevel 31                        April 9, 2000
+
+Minor Changes:
+      * The SAY_TOKEN now applies to channels. That is, +public "Hi!
+        will not result in a doubled initial quote any more.
+        Suggested by Tyler Spivey.
+Fixes:
+      * Uninitialized negate_perms in the monitor flag table.
+        Report by Concordia@Beyond the Fire.
+      * Updates to help changes to match CHANGES.
+      * Another way to end up in an object in your inventory has been 
+        fixed. Report by Lensman.
+      * Unused ancient ccflags cruft removed from hints files.
+      * Considerable linting and cleanup. [SW]
+      * MacOS portability improvements. [DW]
+      * You may reset your @alias to itself in different case.
+        Suggested by Bolt.
+
+& 1.7.2p30
+Version 1.7.2 patchlevel 30                        March 14, 2000
+
+Major Changes:
+      * New US export rules allow us to include shs.c and funcrypt.c
+        in the Penn distribution! Yay!
+      * Code is included in bsd.c for Windows NT users that uses
+        NT's native i/o instead of the bsd socket layer for
+        much improved performance. If you want it, edit src/bsd.c
+        and uncomment the define at the top. [NJG]
+Minor Changes:
+      * New eplayer, eroom, eexit, eobject classes for searches,
+        like Tiny. By Rhysem.
+      * @sitelock/access.cnf can now use regexp patterns. By Raevnos.
+      * The Exits() macro is replaced with Source(), Home(), etc.
+        where sensible. By Maverick@M*U*S*H.
+      * Example of bzip2 compression defines in mushcnf.dst by David@M*U*S*H.
+      * shs.c can now be configured to reverse endianness, so you
+        can more easily use win32 dbs on unix (or vice versa) without
+        password hassles. This is in mush.cnf. [NJG]
+      * JUMP_OK no longer allows anyone to @tel an exit into your room.
+        You must control the destination or have the open_anywhere 
+        power in order to do this now. Report by rodregis.
+Fixes:
+      * Calling do_log with LT_TRACE resulted in no logging. Report by David.
+      * MacOS (and general) portability improvements, suggested by [DW]
+      * help for before(), after() notes case-sensitivity. By Audumla.
+      * hasflag() didn't work with MONITOR. Report by Mystery8.
+      * A little more linting. [NJG]
+      * Fixed help reference to 'global functions'. Report by Falor.
+      * Some gmalloc fixes around missing newlines. Report by Raevnos
+      * Improvements to help switch(). Report by Omozali.
+      * Buffer overflow in @wall fixed. Report by rodregis.
+      * Fixed (I think) the FreeBSD/Linux problem of not finding
+        sigchld/sigcld and similar ilk. Hints for FreeBSD are back.
+      * Crash bug in @link fixed. Report by Howie@New Frontier TrekMUSH
+
+& 1.7.2p29
+Version 1.7.2 patchlevel 29                        January 23, 2000
+
+Fixes:
+      * src/sig.c didn't include config.h. As a result, some systems
+        with sigaction that didn't keep signal handlers installed
+        (some linuxes) would crash very quickly on the second SIGALRM.
+
+& 1.7.2p28
+Version 1.7.2 patchlevel 28                        January 14, 2000
+
+Minor Changes:
+      * New 'deny_silent' option for access.cnf sites.
+        Turns off logging of denied connection/creation/guest/register
+        from a site, to prevent logfile spamming by twinks.
+        Reported by Kludge-BC.
+      * TFPREFIX attribute, if set, is used in place of FugueEdit>
+        in @decomp/tf. [SW]
+      * @grep/print no longer requires you to be set ANSI. Suggested 
+        by Philip Mak.
+      * Improved reporting of function invocation limit. [TAP]
+      * /noeval switch added to think command.
+      * Changes to enhance portability to Mac 68k platform and others
+        that need < 32k of local data per function. [DW]
+      * Objects are only considered to be listening if they're
+        connected players, puppets, have an @listen set, or
+        are things/rooms with MONITOR flags. Previously, things/rooms
+        with ^patterns in attributes were considered listeners, even if
+        they weren't MONITOR. Suggested by Luke.
+Fixes:
+      * gmalloc.c updated from 1987 version to 1995 version. By Gepht.
+      * help corrections for shl and shr by Vexon@M*U*S*H.
+      * help corrections for @clock by Krad@M*U*S*H.
+      * RLIMIT_NOFILE bug fixed by Susan Thorne.
+      * Eliminated variables named 'new' to promote easier C++
+        compiles. Suggested by Gepht.
+      * Compiling with CSRI_TRACE works again. [TAP]
+      * signal stuff broken out to src/sig.c to allow link w/info_slave.
+      * strcasecmp and friends prototyped better in externs.h. [DW]
+      * Overzealous test for inherit flag on zoned objects corrected
+        by Nveid.
+      * Clearing an @attribute'd attribute's value on some object
+        and later manipulating the attribute could corrupt the
+        @attribute table in some cases. Fix suggested by Kami@SW2.
+      * Nested pemits could truncate one another. Reported by Alierak.
+      * Channel messages didn't correctly set %#. Reported by Saberwind.
+      * info_slave used ident even if use_ident was off in the 
+        mush.cnf file. Reported by Rhysem.
+
+& 1.7.2p27
+Version 1.7.2 patchlevel 27                        September 22, 1999
+
+Minor Changes:
+      * Added Raevnos's sitelock/name patch to allow removing names
+        with @sitelock/name !<name> and to fix a display bug.
+      * bsd.c, info_slave.c, and player.c now deal in IP addresses as well
+        as hostnames (which can be spoofed), providing more reliable logging
+        and access control. IP addresses are stored in the LASTIP attrib
+        on players, as per LASTSITE. Suggested by David@M*U*S*H.
+      * Hidden connections are announced as per DARK ones. Suggested 
+        by Julianna@ATS.
+      * New /noisy switch to @cemit prepends <Channel> to message.
+        Suggested by Spork@M*U*S*H.
+Fixes:
+      * help vmul() incorrectly defined the dot product (which vdot() does).
+        Reported by [SW].
+      * Typo fixed in help @set3. Reported by Logain@ATS
+      * Typo fixed in help @emit. Reported by Rhysem@M*U*S*H.
+      * Various help fixes by mith, Big Spoon, and Krad@M*U*S*H.
+      * @function now works for mortals as the help indicates. Report by mith.
+      * @log/wipe should be @logwipe in comments in mushcnf.dst. 
+        Report by Spork@M*U*S*H.
+      * Object names are now limited to 256 characters. Fixes some
+        buffer overflow issues.
+
+& 1.7.2p26
+Version 1.7.2 patchlevel 26                        July 18, 1999
+
+Minor changes:
+      * @verb didn't save stack args before dealing with the WHAT/OWHAT
+        msgs, as TinyMUSH does. Changed to emulate TinyMUSH.
+        Reported by Angel. [SW]
+Fixes:
+      * The noeval-eqsplit fix cause weirdness with attribute setting by
+        directly connected players when specifying the obj by function.
+        Fixed. Reported by Julienna@ATS.
+      * Wizards couldn't modify atrlock'd attribs without breaking the 
+        lock first. [SW]
+      * @find by Wizards showed all garbage objects. Reported by mith.
+
+
+& 1.7.2p25
+Version 1.7.2 patchlevel 25                        July 10, 1999
+
+Minor changes:
+      * New 'nofixed' command restriction, by popular demand.
+      * CONFIGURATION messages in netmush.log shortened to CONFIG.
+        Suggested by mith.
+      * Attributes with the Wizard flag can no longer by created/modified
+        by any non-wizard, even that attribute's owner. Reported by
+        Kurt Fitzner.
+      * @pcreate now shows the dbref of created player. Suggested by
+        Oderus.
+      * When you receive an @mail message, you're now told the number.
+        Suggested by Rak@M*U*S*H, among others.
+      * The @toad command has been removed. The security issues it
+        presents, though not unsolvable, aren't worth solving just to
+        provide Wizards with a humiliating alternative to @newpassword.
+Fixes: 
+      * %q0-%q9 were not preserved during evaluation of player-message
+        attributes (DESC, SUCC, DROP, etc.) Reported by Geoff Gerrietts
+      * Added some hints from FreeBSD. Suggested by Lord Argon of mux.net.
+      * Better Configure handling of library paths. 
+      * 'nogagged' wasn't working correctly in restrict_command. Fixed.
+      * @search on rooms sometimes reported a null room. Reported by mith.
+      * Nearly all source files now include conf.h (which includes options.h), 
+        and do so before any other PennMUSH header file except config.h 
+        and copyrite.h.  Suggested by Joel Ricketts.
+      * Fixed a few comparisons of <= db_top. Reported by Kurt Fitzner.
+      * @oemit <obj>=<message> was emitting to the enactor's location,
+        rather than to <obj>'s location, as it should have been. Fixed that
+        and fixed help oemit() which documented this wrong behavior.
+        Reported by Kurt Fitzner.
+      * An 8-bit-unclean construction in bsd.c fixed by Christoper/Chicago.
+      * p/noeval <message> (repaging) eval'd message anyway.
+      * Args to $commands that looked like functions were being eval'd
+        even if not in brackets. Reported by [SW]. [TAP]
+      * @lock/listen could cause weird pose corruption. Reported by
+        David@M*U*S*H. [SW]
+      * Clarification of wiz_noenter in mush.cnf suggested by 
+        Interloper@M*U*S*H.
+      * Bug in orflags/andflags could cause weird results with toggles.
+        Like nospoof players tested positive for 'J'. Reported by Saberwind.
+      * Bug in make customize fixed. Reported by Saberwind.
+      * References to a PASSWORD attribute removed from help. Reported by
+        Saberwind.
+      * Fixed db_top bug in search/lsearch reported by Saberwind.
+      * @halt code was screwy. Reported by Krad@M*U*S*H
+      * Wizards could grant @powers to God. Reported by Saberwind.
+      * delete() with negative position arg could crash. Reported by
+        Ashen-Shugar.
+      * @clone of an exit while inside an object could have unpredictable
+        effects. Reported by Andy@RobotechMUSH
+      * Typo in help aposs() fixed. Reported by Philip Mak.
+      * @hide now defaults to @hide/on. Reported by Saberwind.
+      * ldelete() added help list functions. Reported by Rak@M*U*S*H
+      * @attribute/rename didn't update the attribute's name quite right.
+        Reported by mith.
+      * @clone by a room didn't properly set the cloned object's location.
+        Reported by Philip Mak.
+
+& 1.7.2p24
+Version 1.7.2 patchlevel 24                        April 5, 1999
+
+Fixes:
+      * @search/lsearch didn't behave right when given an upper range
+        argument of exactly the highest dbref in the db. Reported by
+        [SW].
+      * Unlinked and HOME-linked exits were mishandled during dbcks, 
+        just like variably-linked ones in pl23. Reported by [SW].
+      * Help fixes. [TAP]
+
+        
+& 1.7.2p23
+Version 1.7.2 patchlevel 23                        April 2, 1999
+
+Fixes:
+      * The NoLeave() macro was misdefined, but also not used (whew).
+        Now it's defined right and used.
+      * Giving a / without a switch to commands caused unpredictable
+        behavior. Fixed. Report by Broncalo@Dune III.
+      * Variable-linked exits were mishandled during dbcks, resulting in
+        them being relinked to their source rooms.
+      * @grep/iprint showed the hilighted matches in the same case as 
+        the pattern was given, rather than the case there were in the
+        attribute. Reported by Philip Mak.
+      * The LAST attribute was set differently when players created and
+        when the later connected. The latter case wasn't appropriately
+        prepending single-digit dates with a 0, which fouls up convtime()
+        calls on LAST. Noted by [SW].
+
+
+& 1.7.2p22
+Version 1.7.2 patchlevel 22                        March 19, 1999
+
+Minor changes:
+      * More extensive macro cleanup, based on a patch by David@M*U*S*H
+      * Notable for your own code: Inherit() is now Inheritable(),
+        DarkLegal() checks if something's ok to be invisible when it's DARK,
+        Destroyed() is now IsGarbage(), and some other new helpful macros
+        can be found in hdrs/dbdefs.h
+      * Objects now store their creation cost, not their 'value' (which
+        used to be cost/5 - 1, and had relevance for sacrificing, a now
+        obsolete concept). There is no longer a limit on how much you
+        can spend to create an object, and it's all refunded when the
+        object is recycled. Reported by David@M*U*S*H.
+
+Fixes:
+      * Two memory leaks and one unbalanced mem_check fixed. [SW]
+      * @oemit <room>/<object> was broken in many ways. 
+        Reported by [SW].
+      * Help @drop/@odrop/@adrop updated to mention use on exits.
+        Suggested by Stewart Evans.
+      * God using @logwipe and giving the wrong password crashed the MUSH.
+        [SW]
+      * search() was behaving as lsearchr() not lsearch(). Noted by
+        KMoon.
+      * Bad range arguments for @search and lsearch() now give an
+        error message and don't charge the player. [SW].
+      * Help @search3 had a typo. Fixed by Halatir@M*U*S*H.
+      * Restricting the 'goto' command now also restricts movement
+        through exits. Suggested by Christopher Poff.
+      * Objects and rooms now notify their contents when they start/stop 
+        listening. Report by [RLM].
+      * Error in help for @channel referring to @config. Krad@M*U*S*H
+
+
+& 1.7.2p21
+Version 1.7.2 patchlevel 21                   February 16, 1999
+
+Minor changes:
+      * The restart script now tries to determine its own directory,
+        so it may not require editing to set GAMEDIR any more.
+        Idea by David@M*U*S*H.
+      * Various @find/@search/@entrances commands charged you the
+        FIND_COST even if you didn't have permission to run the command.
+        We don't do that any more. Report by Jonathan Booth.
+      * $command and ^listen pattern matching is now case-insensitive
+        even when the attrib is set REGEXP, unless the attrib is set
+        CASE. In the past, glob matching was case-insensitive and
+        regexp matching was case-sensitive, which cause problems if
+        you tried to regexp match a disabled standard command.
+        Now you've got full flexibility. This may break any current
+        regexp-based $command or ^listen matching that relies on
+        case sensitivity (set those attributes CASE). We now also
+        have a new insensitive regmatch function: regmatchi()
+        Report by Jonathan Booth.
+
+Fixes:
+      * Anyone could @chan/priv channels, even if they didn't pass
+        the modlock. Report by David@M*U*S*H.
+      * DARK disconnects are now shown correctly on chat channels.
+        Really this time. :) Report by Broncalo@Dune III
+      * help for CONNECTED flag updated. Report by matcat@M*U*S*H
+      * Using @kick within a user-defined command could crash the MUSH.
+        Reported by Kludge-BC. [TAP]
+
+
+& 1.7.2p20
+Version 1.7.2 patchlevel 20                   January 26, 1999
+
+Minor changes:
+      * Many expression replaced with macros by David@M*U*S*H.
+Fixes:
+      * @mail/silent/urgent didn't set the message urgent. 
+        Patch by Halatir@M*U*S*H.
+      * You could get free coins by repeatedly killing your objects.
+        Reported by Max@M*U*S*H. [TAP]
+      * You could rename a channel to a name already in use.
+        Reported by David@M*U*S*H.
+
+
+& 1.7.2p19
+Version 1.7.2 patchlevel 19                   December 2, 1998
+
+Minor changes:
+      * The main select() polling loop now times out every second,
+        so we will reliably call local_timer() and handle alarms
+        every second. Suggested by [NJG].
+      * 'make' now performs a make in game/txt, assuring that help
+        indices are rebuilt after a patch. Suggested by Broncalo@Dune III.
+
+Fixes:
+      * Crash in using @cpattr with standard attribs fixed.
+        Reported by Atuarre@ATS.
+
+
+& 1.7.2p18
+Version 1.7.2 patchlevel 18                   November 25, 1998
+
+Minor changes:
+      * Guest players don't receive a paycheck any more. Suggested by
+        Kyieren@M*U*S*H
+      * look_helper() internal function now uses privtabs. As an
+        epiphenomenon, @set obj/attr=locked is now synonymous to
+        @atrlock obj/attr=on. Suggested by [SW].
+
+Fixes:
+      * Win32 compile fixes. [NJG]
+      * DARK disconnects are now shown correctly on chat channels.
+        Report by Broncalo@Dune III
+      * Quiet players no longer see 'Title set.' messages when
+        using @chan/title. Patch by Halatir@M*U*S*H.
+      * @cpattr/@mvattr now copy attribute flags. Report by Jon Booth.
+      * Some compiler warnings fixed by Atuarre.
+      * The 1 and 5 minute dump warning messages weren't being used.
+      * When matching regexp's, later parenthesized subexpressions 
+        weren't correctly assigned to %-vars when earlier ones 
+        were empty. Report by Geoff Gerrietts. [TAP]
+      * Some messages as a result of looking at a room were being
+        placed onto the wrong queue, so remote viewers (@listen *,
+        @pemit/remit to somewhere) would get things out of order.
+        Reported by David@M*U*S*H. 
+      * Help added for functions() and fixed for timestring(). 
+        Report by Geoff Gerrietts.
+      * soundex() misbehavior for very short words fixed.
+        Report by kmoon.
+      * @attribute/access acted as if it were always /retroactive. [SW]
+      * Changes of flags on a standard attribute were lost across
+        restarts. Reported by [SW].
+      * lcstr, ucstr, capstr, and encrypt and decrypt in the 
+        "real" funcrypt.c are now ansi-aware. The former 3 preserve
+        ansi formatting, while the latter two strip it. You must download
+        a new version of funcrypt.c from ftp.pennmush.org (USA/Canada)
+        or export.pennmush.org -- it is not patched herein. 
+        Reported by Ashen-Shugar.
+
+
+& 1.7.2p17
+Version 1.7.2 patchlevel 17                   November 11, 1998
+
+Minor changes:
+      * Newly created players now have a default uselock of =me.
+      * Number of available file descriptors is printed in startup log.
+        Suggested by Doogie@ATS Trekmush.
+      * The @chat/@cemit commands can no longer be used by gagged players.
+
+Fixes:
+      * Adding functions in funlocal.c shouldn't produce compiler warnings.
+        Patch by Halatir@M*U*S*H
+      * log(0) or ln(0) could crash non-IEEE compliant math libraries.
+        Reported by Drakwil and Talos at SNW.
+      * csrimalloc wouldn't compile with glibc. Fix by Mike Selewski.
+      * Order of checks for number of file descriptors changed to
+        do better on POSIX and hybrid systems like FreeBSD. Suggested by
+        Doogie@ATS Trekmush.
+
+
+& 1.7.2p16
+Version 1.7.2 patchlevel 16                   October 17, 1998
+
+Fixes:
+      * table() could be used to crash the MUSH. Report by Ashen-Shugar. [TAP]
+      * whisper/list didn't work. Report by Kamala@ATS TrekMUSH, via
+        Mikey@M*U*S*H 
+      * andflags(player,C) checked for the (useless) CHOWN_OK flag
+        rather than the COLOR flag. Its now been special-cased to
+        check COLOR on players. This is a kludge, but probably worth it.
+      * Top of admin WHO now lists 'Loc #' not 'Room #', as that's more
+        accurate. Suggested by Saberwind.
+      * @log/wipe returns as @logwipe. Its absence was reported by 
+        Nveid@M*U*S*H.
+      * Date in hdrs/version.h now y2k compliant.
+
+
+& 1.7.2p15
+Version 1.7.2 patchlevel 15                   September 7, 1998
+
+Fixes:
+      * @emits weren't propagated through AUDIBLE exits. Report by Nammyung.
+      * Building w/o ROYALTY_FLAG defined works again. Report by Scott Weber.
+      * When matching $ or ^ patterns with the REGEXP attribute flag set,
+        a failed match would then be improperly checked for normal 
+        matching as well. Reported by Jason Rhodes.
+      * Attribute flags weren't listed in @decomp. Reported by
+        Jonathan Booth.
+      * Make customize never got updated to match the new mushcnf/restart
+        system. Now it has. Reported by Manic@FinalFrontier
+      * @edit now works on attribs starting with _. Reported by Jason Rhodes.
+      * Help files should work better on Win32. Reported by Miphon. [TAP]
+
+
+& 1.7.2p14
+Version 1.7.2 patchlevel 14                   August 4, 1998
+
+Minor Changes:
+      * You may @parent to an object you control via ZMO, even if you
+        don't own it. Patch by Halatir@M*U*S*H
+      * In lsearch() and @search, you may refer to object types
+        in either the singular (ROOM) or plural (ROOMS).
+      * Most chat messages now include the name of the channel. 
+        Suggested by Philip Mak.
+
+Fixes:
+      * Long @chat messages crashed the server. Reported first by Rusty
+        and Siris@M*U*S*H. [TN]
+      * Setting the ZONE flag on a non-zonelocked player should give
+        a warning, and wasn't. Reported by Halatir@M*U*S*H
+      * @@ was parsing its argument. No longer. [RLM]
+      * The @config listing was weird w.r.t. compression. [TN]
+      * @shutdown/reboot could cause a crash if a player had an
+        OUTPUTPREFIX or OUTPUTSUFFIX set.
+      * Hint to linux users about undefining I_MALLOC when using
+        gmalloc. Reported by Kyle Forbes.
+      * @shutdown/reboot now calls local_shutdown(). Reported by Kyle Forbes.
+      * When loading a db in which an object with dbref n has attributes
+        owned by players with dbrefs > n, the attribute ownership was
+        reset to GOD. This should no longer happen unless the owner 
+        really is invalid. Most recently noted by [SW].
+      * Exits that have contents (corrupt!) are fixed up in dbck.
+      * dbck is run whenever the db is loaded.
+      * objeval() help fixed. Reported by Yeechi Chen.
+      * Compiling without ROYALTY_FLAG defined was broken. Reported by
+        Scott Weber.
+      * Sufficiently tricky use of locks could cause a crash due to
+        massive function invocation or recursion. Reported by Atuarre. [TN]
+
+
+& 1.7.2p13
+Version 1.7.2 patchlevel 13                   July 7, 1998
+
+Minor changes:
+      * @mail/file now shows the folder name of the destination folder
+        as well as its number. Suggested by Julianna@ATS TrekMUSH
+
+Fixes:
+      * Problems with ANSI causing Pueblo to bleed have been identified
+        and fixed!
+      * Bug with cwho() fixed. Report by Tripsis@M*U*S*H. [TAP]
+      * 'make diffs' in Makefile updated to use prcs and to produce
+        diffs for patches without Index: lines which may confuse
+        non-POSIX versions of patch.  
+      * Fixed typo in options.h reported by Kyle Forbes.
+      * Comments in src/services.c and src/filecopy.c are now C-style,
+        not C++ style. Some compilers were puking on these, even though 
+        WIN32 wasn't defined and the preprocessor should've ignored this 
+        stuff. Go figure.
+      * Side-effect functions like pemit() didn't obey the restrictions
+        on the corresponding command (like @pemit), and setting attributes
+        with @set could get around restrictions on ATTRIB_SET. 
+        Reported by Scott Weber. [TAP]
+      * Help for t() and elock() clarified by Octavian@M*U*S*H.
+      * next() could be used on an object in a room that the player
+        didn't control to get the room inventory. Reported by Octavian@M*U*S*H
+      * hint/aux.sh has been renamed hints/a-u-x.sh. This means it won't
+        be properly recommended by Configure on A/UX systems, but Win32
+        programs often puke on files name 'aux.*' because they're braindead,
+        and there are lots more Win32 users than A/UX users. Bummer.
+      * When A was inside B, and @listen B=*, A would hear everything
+        in B's room (good) except B's own speech (bad). Report by 
+        Nemesis @ Beast Wars 2
+      * help exits clarified by Nammyung@M*U*S*H
+      * help comp() clarified by Halatir@M*U*S*H
+      * @squota without a limit now shows the victim's quota in addition
+        to asking what it should be set to. This more closely matches
+        the old behavior of @squota without a limit being treated as
+        @quota. Reported by Matt@M*U*S*H
+      * @chan/what on a nonexistent channel didn't produce any
+        feedback. Reported by Octavian@M*U*S*H.
+      * Typo in help zone master rooms corrected. Report by Matt@M*U*S*H.
+
+
+& 1.7.2p12
+Version 1.7.2 patchlevel 12                   June 11, 1998
+
+Fixes:
+      * GAGGED players could pose/semipose. Reported by Jorhan@M*U*S*H. [TN]
+      * convsecs() help clarified.
+      * @decomp obj/attr didn't work if you couldn't examine the object
+        even if the attribute was visible. Reported by Jonathan Booth.
+      * make customize had a problem with the way it handled the
+        hlp directory. It now creates a real hlp directory, but makes
+        all the standard hlp files symlinks to the distributed ones.
+        (It used to make the hlp/ directory a symlink, which did bad
+        things with 'txt/Makefile'. Report by Gasparin Balazs.
+      * help control rewritten to clarify the real algorithm.
+        Suggested by [SW].
+      * Configure is more flexible when determining if you're
+        building under cygwin. Reported by Miphon.
+      * zfun() worked, but gave an error message anyway. Fix by Rob@DuneIII
+      * hdrs/regexp.h renamed to hdrs/myregexp.h to avoid conflict
+        with standard regexp.h header file in cygwin.
+      * Roy/see_all players could, under some circumstances, evaluate
+        functions with wiz privileges. Reported by Atuarre. [TAP]
+
+
+& 1.7.2p11
+Version 1.7.2 patchlevel 11                   May 25, 1998
+
+Changes:
+      * The PennMUSH copyright notice has been changed, as the
+        licensing terms for TinyMUD/TinyMUSH 2.0 have changed,
+        and to update the TinyMUSH 2.2 part of the copyright and
+        the PennMUSH part as well. 'help copyright' now gives the
+        copyright, and it's in COPYRITE and hdrs/copyrite.h.
+        The licensing terms are now shorter, but practically very similar.
+Fixes:
+      * Possible infinite loop (with disk-filling output!) in @dbck
+        with certain types of DB corruption fixed.  Report by RLM and
+        Arathorn@CDI.  [TAP]
+      * @undest intermittent crash-bug fixed. Report by Arathorn. [TAP]
+      * regmatch() crash-bug fixed. Report by Tavoan@ATS. [TAP]
+      * Ansi bleeding problem fixed. Report by Atuarre@ATS. [TAP]
+      * The embedded version of mkindx used by Win32 builds had a bug - 
+        some global variables weren't getting properly reset.
+        Fix by Stephane Thibault.
+      * Help for @oxmove added. Report by Bray Roned@ATS.
+      * isnum() was broken if tinymath was defined. Report by Daniel Peters.
+      * portmsg needed MUSH_IP_ADDR, which is now runtime configured.
+        It no longer does (see comment in portmsg.c if you need this
+        functionality). Report by Daniel Peters.
+      * Help for dig() and lnum() clarified by Andre Leopold.
+
+
+& 1.7.2p10
+Version 1.7.2 patchlevel 10                   April 24, 1998
+
+Fixes:
+      * MANIFEST updated to reflect the deletion of src/nmalloc.c
+
+
+& 1.7.2p9
+Version 1.7.2 patchlevel 9                    April 21, 1998
+
+Fixes:
+      * Myopic flag didn't work unless Pueblo support was on. Reported by
+        Rhysem@M*U*S*H.
+      * help debug referred to can_debug, a now-obsolete power.
+        Reported by Rodimus Prime @ TF2005.
+      * @chown to a Zone Master didn't work. The @chown code has been
+        rewritten to be easier to read. Report by Trispis@M*U*S*H.
+      * open_anywhere was mis-listed in help powers2. Report by Trispis.
+      * nmalloc.c is removed, and Win32 compiles should be a bit easier.
+      * Win32 build no longer limited to 64 sockets; 256 instead. [NJG]
+
+
+& 1.7.2p8
+Version 1.7.2 patchlevel 8                    April 2, 1998
+
+Fixes: 
+      * round() could crash on very big numbers on some systems.
+        Reported by Atuarre@ATS.
+      * Problem with exits getting a contents list in certain 
+        conditions fixed. Reported by Atuarre@ATS. [TAP]
+      * Problems with puppets and Pueblo fixed. Report by Mop-Boy.
+      * mkindx problem with dos text files fixed. [NJG]
+      * Order of include files in htab.c was wrong, caused compilation
+        problems on SCO Openserver. Reported by Flame.
+      * On Win32, the MUSH could quit without flushing its buffers. [NJG]
+      * When inside of an object with @listen *, you didn't see things
+        when the object did a 'look'. Reported by Vedui.
+      * Warnings in rwho.c eliminated. [NJG]
+      * More fooling around with mymalloc.h to help the Win32 compile.
+        Suggested by NJG.
+      * open_everywhere power get left off help powers list at some point.
+        Reported by Steven@Champions.
+      * Error message for joining a non-existant channel was different
+        from that for joining a channel that exists but you can't
+        see. Reported by Octavian@M*U*S*H.
+      * get()'ing an attribute that isn't set now returns an error 
+        message only if the default permissions don't allow you to 
+        get it. This allows get(player/ALIAS) to always work, returning
+        an empty string if the player has no ALIAS. [TAP].
+      * base_room mush.cnf option got left out.
+      * dbck didn't check for disconnected rooms correctly. Noted by
+        Rhysem.
+
+
+& 1.7.2p7
+Version 1.7.2 patchlevel 7                    March 10, 1998
+
+Fixes:
+      * Typo fixed in pennflag.hlp. Report by Keith Howell.
+      * Bug in mid() that could cause crashes on some OS's when using
+        bad arguments fixed. Report by Yanek@DragonStarMUSH. [TAP]
+      * @wait obj=command fails when tiny_math is defined. Report by Yanek.
+      * With tiny_math defined and tiny_booleans not defined, 
+        strings in booleans weren't being treated as 1. Report by Yanek.
+      * @decompile now shows @powers, too. Report by Jonathan Booth.
+
+
+& 1.7.2p6
+Version 1.7.2 patchlevel 6                    March 8, 1998
+
+Fixes:
+      * src/mymalloc.c now includes config.h correctly. [NJG]
+      * @zemit would change zones of objects while running.
+        Report by Steve Sanderlin and Vedui.
+      * Minor cosmetic bug in @config/list. Reported by Mike Wesson
+      * @chown'ing an object to a Zone player doesn't reduce the Zone
+        player's quota, but @chown'ing an object back from a Zone player
+        should reduce yours (and didn't). Report by Vedui.
+
+
+& 1.7.2p5
+Version 1.7.2 patchlevel 5                    March 2, 1998
+
+Fixes:
+      * @cloning an object on a channel could crash the MUSH. Report by
+        Mordak@ATS.
+      * You couldn't add 2 objects with the same name to a channel.
+        Report by Mordak@ATS.
+      * Help for inc() fixed. Brian@M*U*S*H
+      * On Win32, the text file indexes are now properly sorted,
+        and things work ok if you don't have a text file defined. [NJG]
+      * Wiz objects couldn't add players to channels. Report by Mike@M*U*S*H
+      * Compile problem with fork and win32 fixed. [TAP]
+      * You shouldn't get nospoof notification from your own emits. [TN]
+      * You no longer get nospoof notification from every @chat, only
+        from @cemit.
+      * @lock obj=here or @lock obj=exit failed. Report by Luke@M*U*S*H
+      * The MUSH announces where it's sending stderr when it starts up. [NJG]
+      * If there was nothing on the queue, and no user activity,
+        the MUSH could wait as long as 999 seconds before checking to
+        see if it should do anything (like a dump, a shutdown, idling
+        someone out, etc.) This has been changed to 5 seconds.
+        Report by NJG.
+      * Encryption buffer for SHS encryption was too small, could cause
+        password problems. [NJG]
+
+
+& 1.7.2p4
+Version 1.7.2 patchlevel 4                    February 24, 1998
+
+Fixes:
+      * Fixed a few more compiler warnings under Win32. [NJG]
+      * Output to a non-ANSI, non-COLOR player could cause crashes.
+        Reported by Vedui.
+
+
+& 1.7.2p3
+Version 1.7.2 patchlevel 3                    February 22, 1998
+
+Fixes
+       * Fixed a slew of compiler warnings under Win32. [NJG]
+       * Jonathan Booth Removed a few #ifdef EVENTS that lingered.
+       * Room names weren't shown bold to people who had ANSI
+         but not COLOR set. Report by Sylvia
+       * Added help alias pueblo() for pueblo. Suggested by Vedui.
+       * Fixed some problems with ansi() and tf reported by Vedui.
+       * @readcache could cause crashes. First report by Mop-Boy.
+       * src/tcl.c is now better located by Configure. [TN]
+
+
+& 1.7.2p2
+Version 1.7.2 patchlevel 2                    February 19, 1998
+
+Fixes:
+       * Reading of compressed dbs didn't work right. Report by Roger Christie
+       * Fix to help to remove reference to @config/globals. Mordak@ATS.
+
+
+& 1.7.2p1
+Version 1.7.2 patchlevel 1                    February 18, 1998
+
+Fixes:
+       * max_dbref was limited to 256 by mistake. Report by Rhysem@M*U*S*H
+       * restricted_building in mush.cnf didn't work. Does now.
+         Report by Rhysem@M*U*S*H
+       * memchecks for hash tables didn't work right. Report by [SW]
+
+
+& 1.7.2p0
+Version 1.7.2 patchlevel 0                    February 9, 1998
+
+Major Changes:
+       * Support for the Pueblo MUD client (http://www.chaco.com/pueblo)
+         which allows the MUSH to send html to the client. This is
+         runtime configurable with @enable/@disable and mush.cnf. [TN]
+       * Regular expression support: the REGEXP attribute flag causes
+         attributes to match $ and ^ patterns using regular expressions.
+         regmatch() matches regular expressions. [2.2]
+       * PennMUSH tarfile now unpacks itself in a pennmush/ directory,
+         by popular request. pennmush/ is now the 'top-level' directory;
+         patches should still be applied from within the pennmush/ directory.
+       * More compile-time options are now run-time options:
+          HASPOWER_RESTRICTED, SAFER_UFUN, DUMP_WARNINGS,
+          INDEX_COMMAND, RULES_COMMAND, HATE_DEST (general command rename?),
+          NOISY_WHISPER, POSSESSIVE_GET POSSGET_ON_DISCONNECTED,
+          REALLY_SAFE, DESTROY_POSSESSIONS, NULL_EQ_ZERO,
+          TINY22_BOOLEANS, TINY_TRIM_ORDER,
+          ADEST_ATTRIB, AMAIL, PLAYER_LISTEN, PLAYER_NOAHEAR,
+          ROOM_CONNECTS, ANSI_NAMES, COMMA_EXIT_LIST, COUNT_ALL,
+          EXITS_CONNECT_ROOMS, WIZWALL_PREFIX, RWALL_PREFIX, WALL_PREFIX,
+          NO_LINK_TO_OBJECT, QUEUE_PER_OWNER, WIZ_NOAENTER, USE_IDENT,
+          MUSH_IP_ADDR, MAILER, ANSI_JUSTIFY, PLAYER_NAME_SPACES,
+          NO_FORK, EVENTS, MILITARY_TIME, LOGIN_LIMIT, IDLE_TIMEOUT,
+          RESTRICTED_BUILDING, FREE_OBJECTS, QUOTA, BUILDING_LIMIT,
+          FLAGS_ON_EXAMINE, EX_PUBLIC_ATTRIBS, TINY_ATTRS, FULL_INVIS,
+          SILENT_PEMIT, PLAYER_LOCATE, DO_GLOBALS, GLOBAL_CONNECTS,
+          PARANOID_NOSPOOF, ACCESS_FILE, NAMES_FILE, OBJECT_COST,
+          EXIT_COST, LINK_COST, ROOM_COST, QUEUE_COST, QUOTA_COST,
+          FIND_COST, PAGE_COST, KILL_BASE_COST, KILL_MIN_COST, KILL_BONUS,
+          QUEUE_LOSS, DBTOP_MAX, MAX_OBJECT_ENDOWMENT,
+          MAX_WIZ_OBJECT_ENDOWMENT, MAX_PENNIES, MAX_DEPTH, MAX_PARENTS,
+          PURGE_INTERVAL, DBCK_INTERVAL.
+       * All the functions that used to be optional are now
+         enabled. The increase in code size is negligible, and
+         the decrase in options is a win. We'll probably add 
+         some way to restrict function use in mush.cnf in the future.
+       * The original MUSH chat system is no longer distributed or
+         supported. The OBJECT_CHAT option is thus obsolete.
+         If you've been using the old system, your db will automatically
+         be converted to the new one, but you will need to recreate
+         your channels and readd players to them.
+       * ADD_NO_COMMAND_FLAG has been removed, as few people need
+         to add NO_COMMAND en masse to all their rooms and players
+         any more. Those that do can use MUSHcode for this.
+       * The definitions of GOD, MAX_ARG, have been moved out of options.h, 
+         because redefining these isn't really an option you want to exercise.
+       * dune.h is no more. Raise a glass to it.
+       * Non-standard attributes that are created by objects are set to 
+         no_command by default. This improves security in many common cases, 
+         but may require your object code to @set the attribute !no_command
+         after it creates it, if the attribute is meant to contain
+         a $command. Attributes set by players themselves (typed directly
+         from a socket) still work as they used to, as do standard
+         attributes (@va-@vz, for example). [TN]
+       * Hash table code has been tightened up. [TN]
+       * New option: tiny_math. Treat strings as 0 in math functions
+         rather than errors. This is handy for Tinymush compatibility,
+         even though it may make real errors harder to find.
+         Suggested by Ashen Shugar.
+
+Minor Changes:
+       * idlesecs() now returns the number of seconds idle for the least
+         idle connection of the player.
+       * conn() now returns number of seconds connected for the longest
+         connected connection of the player.
+       * I wrote the COPYRITE file some time ago, but forgot to include
+         it in the dist. Oops!
+       * UFAIL/OUFAIL/AUFAIL is here. Suggested by Mike Affourtit.
+       * controls() is now ok if you are See_All; you don't need to 
+         actually control the object you're testing. Reported by RLM.
+       * If you're allowing empty attributes, ICLOC is set to ""
+         on newly created players, instead of " ", to permit testing
+         with hasattrval. Suggested by Jonathan Booth. [TN]
+       * Improvements to FPE handling on FreeBSD. By Jason Young.
+       * New switch /noeval for @mail. Suggested by Mop Boy.
+       * The "dbcomp" directive in mush.cnf has been removed.
+         "compress_suffix" has been added. Databases are now specified
+         without compression suffixes.
+       * You are warned if you fail to define an option in the config file.
+         Suggested by TN.
+       * @config can now list config options in logical groups.
+       * mail.c has been removed. extmail.c is used directly instead.
+
+Fixes:
+       * hasattr/get/xget/eval now are less likely to tell a player something
+         they're not privileged to know about the existence of attributes. [TN]
+       * The big_que function has been renamed parse_que. The parse_que
+         function (which just called big_que) is gone.
+       * FAQ updated, as well as other references to pennmush.tinymush.org.
+       * Giving a negative argument to convsecs() would crash Win32.
+         As there's no reason to ever do that, you now can't. 
+         Reported by Javin@DynamixMUSH
+       * dist2d and dist3d now return floating point numbers when
+         FLOATING_POINTS is defined, as they said they would.
+       * help for pi() now refers to it as 3.141593, which is what
+         the function actually produces, due to rounding. Report by Vedui.
+       * help for beep() notes that royalty can use it to. Report by Vedui.
+       * Using a maildb that referred to db#s that didn't exist
+         in the object db (e.g., replacing your object db with minimal.db
+         without removing the maildb) would crash the server.
+         Now the server fixes up invalid messages after loading maildb
+         Report by TN.
+       * Connect screen and other cached text files should now be
+         automatically terminated with CRLF, so windows and other
+         telnet programs will see them correctly. Report by many. [TN]
+       * The separator in iter() is now parsed. Report by Ashen-Shugar.
+       * match(foo,>) always returned 1; similar problems with matchall,
+         grab, graball, strmatch, and elements. Report by Ashen-Shugar.
+       * Objects listening for channel broadcasts with ^<chan> *: didn't
+         work. Now they do.
+       * When you disabled a command in order to override it in MUSHcode,
+         calling the command with switches didn't work. 
+         Reported by Ben Kenobi. Patch by Eyal Sagi and TN.
+       * Calls to cut in utils/mkcmds.sh relied on "-f 2" working like
+         "-f2". On at least Ultrix 4.4, it doesn't. Report by Cwilla.
+       * Link strdup.o to info_slave because some systems need
+         strdup! Report by Cwilla.
+       * Some help file typos cleaned up by Ken Ray.
+       * exit() used to work on non-rooms. Not anymore. Report by
+         Trispis@MUSH101 
+       * All header files now idempotent.
+       * @tel me=home or @tel home now works in all cases where 'home'
+         works. Report by Vedui.
+
+& 1.7.1p3
+Version 1.7.1 patchlevel 3                    January 12, 1998
+
+Minor Changes:
+       * A file called MANIFEST is now distributed. Configure uses this.
+         Don't mess with it unless you know what you're doing.
+       * An additional note for those upgrading from versions before
+         1.7.0p9, describing an anomaly with player parents and how
+         to handle it has been added to README. Report by Roger Christie.
+
+Fixes:
+       * controls() now requires that the function caller control
+         the object named in the first argument. Suggested by TN.
+       * Player parents were being cleared at every login.
+         Fixed by Jonathan Booth.
+       * effect should be affect in game/txt/newuser.txt. 
+         Reported by Jason Young.
+       * hints file for OS/2 now included. Suggestions by Sylvia.
+       * FNDELAY changed to O_NDELAY in ident.c. Suggested by Sylvia.
+       * Minor change to time_format_1 to make 64bit SGI happier.
+         By Thaddeus Parkinson.
+       * Help for inc()/dec() improved. Suggested by Jonathan Booth.
+
+
+& 1.7.1p2
+Version 1.7.1 patchlevel 2                    January 5, 1998
+
+Fixes:
+       * References to TinyMUSH in the .txt files are now PennMUSH.
+         Reported by Corum.
+       * It was possible to use @name to create players with the same
+         name. Ick. Reported by Sylvia.
+       * utils/mkcmds.sh now produces a preindented switchinc.c,
+         to match the one that gets diffed in patches. [TAP]
+
+
+& 1.7.1p1
+Version 1.7.1 patchlevel 1                    December 29, 1997
+
+Fixes:
+        * table() now behaves with ANSI_JUSTIFY. Reported by Jonathan Booth.
+        * ident.c was defining strdup; so was strutil.c. Now only
+          strutil.c does. Reported by Matt Philips. [TAP]
+        * Bug in restart script fixed. [TAP]
+        * shs.h patched to autoconfigure endianness. Reported by TN.
+        * shs.h fixed to be idempotent. [TAP]
+        * shs.h is now distributed with PennMUSH. You still have to get
+          shs.c from export.pennmush.org if you want it.
+        * README now refers to lists.pennmush.org. [TN]
+        * We no longer recommend setting use_dns to no on win32 systems.
+          It seems to work fine as yes.
+
+
+& 1.7.1p0
+Version 1.7.1 patchlevel 0                    December 21, 1997
+
+Major Changes:
+        * It is no longer necessary to edit src/Makefile when building.
+          RWHO is now integrated, and totally runtime configured in mush.cnf.
+          IDENT is now configured from dune.h. The IDENT/ and RWHO/
+          directories are no more.
+        * The win32 build now requires the gnu-win32 package
+          (available at XXX). It can be built either with MSVC++
+          or the free win32 version of gcc included with gnu-win32.
+          The build is better integrated into the distribution --
+          the win32/ directory is no more. [TAP]
+        * You can now build with one of 3 password-encryption schemes:
+          None, Unix (des) crypt(3), and shs (requires getting shs from
+          export.pennmush.org). This will be useful to folks who're using
+          shs encryption on Win32 platforms and then move their db to
+          a Unix platform -- they can just use shs under Unix and all's well.
+          This is set as CRYPT_SYSTEM in options.h
+        * @log/wipe used to require entering the game account password,
+          but that's non-portable. Instead, a "log_wipe_passwd" is now
+          specified in mush.cnf.
+        * README rewritten [TAP]
+
+Minor Changes:
+        * @lock/drop on a room now controls who can drop things in the room.
+        * "configure" (lower-case 'c'), a wrapper for Configure that used
+          to be included is no longer. Few used it, and it can screw up
+          win32 systems that aren't case-sensitive.
+
+Fixes:
+       * blind_page was written as blindpage in game/mushcnf.dst.
+          Reported by Raevnos@ShadowMist.
+       * udefault() was broken. Reported by John Hutchinson
+        * Some lintwork in csrimalloc.c, to get rid of signed/unsigned
+          warnings and others.
+        * help @list fixed to show the correct @config switches.
+          Reported by Leo@ATS TrekMUSH
+        * @chan/gag now works correctly. Report by Vedui.
+        * Help for remove() updated. Reported by Vedui@Robotech.
+        * hasattr() didn't check if the invoker had permission to read
+          the attribute. Reported by Vedui@Robotech.
+
+
+& 1.7.0p11
+Version 1.7.0 patchlevel 11                    November 18, 1997
+
+Commands:
+        * page/blind produces a separate page to each person paged,
+          so they can't tell if the page was a multipage. This is the
+          same as the default PennMUSH page behavior (but see Options)
+
+Options:
+        * New mush.cnf option 'blind_page'. If 'yes', page defaults to 
+          page/blind.  If 'no', page defaults to page/list. By popular 
+          request. :)
+        * New dune.h option MUSH_IP_ADDR. Define if your host system has
+          multiple IP addresses to specify which address to listen on
+          for connections. By Bobby Bailey (Chil).
+
+Minor Changes:
+        * All calls to isalnum replaced with isalpha || isdigit, because
+          some linux systems appear to have a broken isalnum!
+        * For some reason, the variable name 'restrict' in fun_lsearch
+          broke compilation on James Cook's system. Gwennie@SNW fixed
+          this by changing the name. Ok, I've changed the name, too,
+          as a preventative measure. *shrug*
+        * When a player can't connect because logins are disabled or
+          the MUSH is full, we no longer (a) show a disconnect message
+          to MONITOR players, (b) purge the player's mail anyway, or
+          (c) show the player quit.txt in addition to full.txt/down.txt.
+          Suggested by John Hutchinson.
+
+Fixes:
+        * When a halted player triggers a $command *:, %0 was including
+          an initial space that wasn't being trimmed. Reported by
+          Jonathan Booth. [TAP]
+        * functions() works right again. Patch by Atuarre@ATS TrekMUSH
+        * look/out allowed looking at any db#. Reported by Lews Therin@DDM
+
+& 1.7.0p10
+Version 1.7.0 patchlevel 10                    October 30, 1997
+
+Minor Changes:
+        * Players can now have @parents. Inspired by AJ Prowant.
+
+Fixes:
+        * @drain was doing @notify instead. Ick. Reported by Amberyl.
+        * Royalty can @boot, as the help suggests. Reported by Vedui@Robotech
+        * (Hopefully current) copies of hdrs/funs.h and hdrs/cmds.h are
+          kept in the win32/ directory for win32 folks who don't have
+          a Bourne shell and can't run utils/mkcmds.sh.
+
+
+& 1.7.0p9
+Version 1.7.0 patchlevel 9                    October 16, 1997
+
+Fixes:
+        * @clone could corrupt the db on some machines in some cases. 
+          Report by Jonathan booth. [TAP]
+        * @list attribs now works. Report by Corum@StormWorld.
+
+          
+& 1.7.0p8
+Version 1.7.0 patchlevel 8                    October 15, 1997
+
+Fixes:
+        * Hopefully the final command_parse fix. 
+
+
+& 1.7.0p7
+Version 1.7.0 patchlevel 7                    October 15, 1997
+
+Fixes:
+        * The ANSI_JUSTIFY patch got left out somewhere. It's back.
+          Report by John Hutchinson.
+        * +channel and exits are broken in 1.7.0p6 due to our stupidity.
+          Fixed again. Report by John Hutchinson.
+        * @cemit was restricted to Wizards, and should have been restricted
+          to Wiz or Royal. Fixed. Reported by Vedui@Robotech.
+
+
+& 1.7.0p6
+Version 1.7.0 patchlevel 6                    October 9, 1997
+
+New Functions:
+        * cand(), cor() are short-circuit boolean functions. Suggested by
+          Flame who saw reference to them in a patch to TinyMUX. [TAP]
+        * if() and ifelse() do about what you'd expect. Suggested by
+          a number of people. [TAP]
+
+Minor Changes:
+        * Prototypes for the functions in src/local.c are now in
+          hdrs/externs.h
+        * round(.15,1) = .1 on many systems due to the floating point
+          representation. A tiny kludge around this is now in place.
+          Reported by Flame.
+        * @command/disable say now disables " as well. The same applies
+          to other single-character command forms. If you disable SAY,
+          commands of the form "hi! are rewritten as: say hi! before
+          being passed to the checker for user-defined commands
+          so you need only set up $say *: to catch both. %c will, as always,
+          contain the raw command as entered (i.e. "hi!). Aliases
+          (like 'p' for page) are treated similarly when the aliased
+          command is disabled -- you need only match $page *. [TN]
+        * The Huffman compression algorithm is now 8-bit clean, in 
+          preparation for a future internationalization of PennMUSH
+          (Eh? Dite "help" pour aide. :) In addition, none of the compression
+          algorithms treat the first character of a compressed string
+          specially anymore -- they don't have to. [TAP]
+        * The customize.pl script no longer copies over all the distribution
+          help files from game/txt/hlp. Instead it makes customdir/txt/hlp
+          into a link to game/txt/hlp, which is a good thing when future
+          patches update the help files. Dedicated to Oleo.
+
+Fixes:
+        * @list/attribs showed many duplicate attribute names.
+          Reported by John Hutchinson.
+        * Fixed a mislabeling of allocated memory by htab.c, so mem_check
+          stats will be correct.
+        * @channel/gag now works.
+
+
+& 1.7.0p5
+Version 1.7.0 patchlevel 5                    October 1, 1997
+
+Minor Changes:
+        * New os2/ subdirectory with information for those looking to
+          build under OS2. Maintained by Sylvia (penn-os2@pennmush.tinymush.org)
+
+Fixes:
+        * Examine/brief could sometimes cause crashes. Reported by
+          Sean Fike. [TN]
+        * cmds.c and command.c didn't do the right #includes for the
+          original chat system. Reported by Magus.
+        * Added help for @shutdown/paranoid. Reported by Sean Fike
+        * @chan/decompile on a nonexistant channel didn't return an
+          error. Reported by Mike Wesson
+
+
+& 1.7.0p4
+Version 1.7.0 patchlevel 4                    September 19, 1997
+
+New command:
+        * The @shutdown command now takes a /paranoid switch
+          to perform a paranoid dump when shutting down
+          (or rebooting, if given with /reboot as well). Idea by Flame.
+
+New options:
+        * You can define the MAILER option in dune.h to be
+          something other than sendmail if you want to put a
+          wrapper around the mailing program used to send out
+          passwords to players using the 'register' command.
+        * If you define LOCAL_DATA in options.h, you can 
+          use functions in local.c to maintain your own data structures
+          associated with each db object. [TN]
+        * If you define the TINY_TRIM_ORDER option in dune.h,
+          the trim() function takes arguments like TinyMUSH's.
+        * If you define ANSI_JUSTIFY in options.h, the rjust, ljust,
+          and center functions will ignore ansi codes when computing
+          where the string should be placed, so they'll work right for
+          strings with ansi. Wadhah Al-Tailji contributed a patch
+          for this concept. TN wrote this particular implementation.
+
+Fixes:
+        * rnum() didn't find exits properly. Reported by Vedui.
+        * Null channels would get added if the chatdb's channel count
+          got unsync'd with the actual number of channels in the chatdb.
+          Reported by Matt@New England: The Reckoning.
+        * @wcheck/all didn't work. Report by Wolverine@Holodeck1
+        * HPUX needs _XOPEN_SOURCE_EXTENDED in the hints. Report by Angel.
+        * db reading error messages are slightly more verbose.
+          Suggested by Flame.
+        * Mortals examining DARK rooms don't see contents anymore.
+          Report by Jonas Oberg. [TN]
+        * Attribute names may no longer contain the caret (^) character.
+          It's a security risk. Noted by Rob Wilson.
+        * hdrs/funs.h was being appended to, not rebuilt. Noted
+          by John Hutchinson.
+        * setunion fixed again, so that setunion(a a,) correctly
+          returns 'a', not 'a a'. [TN]
+        * Various references to mellers1 updated to pennmush.tinymush.org.
+        * COPYRITE file added to explain a couple of the unclear
+          points in hdrs/copyrite.h and to serve as a pointer.
+        * @chan/rename didnt work. Reported by Jonathan Booth.
+        * Help for entrances() updated by Naomi Novik
+        * Players no longer hear about all the channels they're
+          no longer gagged on when they disconnect. Report by
+          Naomi Novik.
+        * The time noted in the LAST attribute now will always
+          have day numbers 01-31 instead of 1-31, just like time()
+          does. This makes convtime() work better on LAST for some
+          systems. Reported by Valin@PernMUSH.
+        * MUSHcoding a command called $attr * would crash the MUSH.
+          Fixed. Reported by Sam Knowlton. [TN]
+        * Disabling say now disables ", (same for pose, semipose,
+          emit, and chat and their corresponding tokens).
+          Reported by Flame.
+        * The hint for Dec Alpha OSF now indicates that you need to
+          use native malloc. Reported by Sean Fike.
+        * We now check for the assert() macro in Configure. NetBSD may
+          not have it, according to Logan Five.
+        * Doing a LOGOUT after a @shutdown/reboot caused crashes
+          because mail pointers weren't being reset.
+
+
+& 1.7.0p3
+Version 1.7.0 patchlevel 3                    August 13, 1997
+
+Fixes:
+        * setunion(,list) should work now.
+        * @wall/wizard and @wall/royalty work right. Report by Alan T. [TN]
+
+
+& 1.7.0p2
+Version 1.7.0 patchlevel 2                    August 12, 1997
+
+Fixes:
+        * Linux 2 is now instructed not to use nm to find objects
+          in libraries since its nm output doesn't seem to be
+          BSD compatible or something.
+        * Compile with CSRI malloc in debugging mode didn't work.
+          Reported by TwinSuns MUSH.
+        * Exits in transparent rooms with COMMA_EXIT_LIST had
+          vanished. They're back! [TN]
+        * More command parser bugs ironed out. [TN]
+        * 'e' is examine, 'b' is brief, 'w' is whisper
+           (unless you reserve them). [TN]
+        * @config said conflicting things about possessive get.
+          Report by Jonas Oberg.
+        * When not using @prefix, audible messages were prefixed
+          with "From <source>" instead of "From <source>,".
+          Fixed. Report by Jonas Oberg.
+
+
+& 1.7.0p1
+Version 1.7.0 patchlevel 1                    August 7, 1997
+
+Minor Changes:
+        * New file local.c (from local.dst) makes more local hooks
+          available. Some of the hooks in command.c have been moved
+          here, so you may have to mess around a bit if you've already
+          added things in command.c's local_startup or local_shutdown. [TN]
+        * Functions can now be defined and added all within funlocal.c
+          so you don't have to muck with the function.c table. [TN]
+
+Fixes:
+        * restrict_command didn't restrict commands to God correctly.
+          Reported by Jason Newquist.
+        * @command indicates if commands are restricted to God.
+        * When COMMA_EXIT_LIST was defined, rooms with no exits
+          still showed the "Obvious Exits:" message. Report by Michael Rudel.
+        * Partial switch-matching for commands is back.
+          And CMD_T_NEWSTYLE is gone. [TN]
+        * Makefile doesn't clobber existing funlocal.c/cmdlocal.c
+          any more. Report by Jason Young.
+        * inv wasn't working for inventory, and other command aliasing
+          flaws are fixed. Report by Corum. [TN]
+        * MUSH wouldn't compile if PLAYER_LOCATE wasn't defined. Fixed.
+          Report by Alan T.
+        * Some leftover defines removed from game.c and mushcnf.dst
+        * @lock didn't parse right. Fixed. Report by Corum.
+
+
+& 1.7.0p0
+Version 1.7.0 patchlevel 0                    July 31, 1997
+
+The major goals of this release are to make what used to be
+compile-time options into run-time options, and improve a number
+of internals.
+
+Major Changes:  
+
+        * The hashtable functions from TinyMUSH (htab.c/htab.h) are
+          now used by PennMUSH (with permission), so instead of 
+          every subsystem writing its own hashtable code, they now
+          all use the standard code.
+
+        * The giant switch in game.c has been replaced by hashtabled
+          commands. [TN] 
+          Groovy new features:
+          * @command/enable and @command/disable for any command
+          * Multiple switches (@pemit/noeval/silent) work
+          * The left side of the = is always evaluated before
+            the right (previously, this was compiler-dependent).
+          * Command table is built during compilation, and there's 
+            a standardized command interface, so adding commands
+            is easier.
+          * local.c contains hooks for local routines to run
+            on startup and shutdown, and to use to add new commands.
+
+        * Configuration options which restricted or disabled commands
+          (READ, NO_KILL, ROBBERS, HATE_TOAD, ROY_FIND, HARSH_GUEST,
+          SITELOCK_COMMAND) have been replaced the 'restrict_command'
+          directive in mush.cnf. Check there and be sure things are
+          set the way you want them!
+
+        * Commands may be overridden by completely disabling them,
+          and providing a MUSHcoded version instead.
+            
+        * Ident and DNS lookups are handled by a slave process
+          if possible. [TAP]
+
+        * The help files have been rewritten by Naomi Novik!
+
+        * The MUSH now closes and reopens the log files when it
+          receives a SIGHUP signal. [TAP]
+
+        * @shutdown/reboot will dump the database and restart the
+          MUSH without disconnecting the players. So will a USR1 signal.
+          Good for patching in new source code on the fly. Based on the 
+          patch by Cro@Spaceballs which is itself based on ideas from
+          TinyMUX. This may be nonportable - if it fails on your OS, 
+          pennmush-bugs@pennmush.tinymush.org would like to hear about it. 
+          [TAP]
+
+New commands:
+        * @command (see above)
+        * @list/commands
+        * @attribute, similar to Tinymush (but doesn't save data
+          across shutdowns at this point, so you've got to use it
+          on an @startup!). Also @list/attribs.
+        * @function/delete 
+        * @channel/gag <channel>=<yes|no>, keeps you on the channel
+          (preserving your title, etc.) but silences it so you don't 
+          hear messages. The channel is automatically ungagged if
+          you log out from the MUSH.
+        * @conformat and @exitformat allow custom-formatted Contents:
+          and exit lists. [2.2,TAP]
+
+Minor Changes:
+        * Configure now looks for libbind.a, the BIND 8.1 library
+        * @config shows more of the configuration options
+        * @mail/unfolder <folder> can be used to remove a mailfolder's name.
+          Suggested by Julianna Barrenson.
+        * The default malloc is now CSRImalloc, which is now distributed
+          in a single-file form with the MUSH. 
+        * The MALLOC define in src/Makefile has been removed.
+          Unlike MALLOC_D and MALLOC_O, it didn't do anything.
+        * The "CHANGES" file now contains only the current
+          version's changes. Older changes are in the 
+          "CHANGES.OLD" file.
+        * The attribute flag AF_ODARK is now assumed to be default, 
+          and is thus no longer used or stored. Instead, AF_VISUAL
+          is used to indicate a visual attribute (previously, this
+          was indicated by the absence of AF_ODARK). To note this
+          change in the db, a new DBF constant, DBF_AF_VISUAL,
+          has been defined.
+        * Code for "privilege tables" (like chat channel privs, 
+          attribute flags, etc.) has been centralized into privtab.c
+
+Fixes: 
+        * Locks on zone exits now evaluate with the right enactor.
+          Problem noted by Leonid Korogodsky. [TAP]
+        * Win32 compatibility improvements. [TAP]
+        * Prefer limits.h to values.h. Suggested by Atuarre.
+        * SIGCHLD and SIGCLD now both work. Noted by Naomi Novik
+        * If you idle out and get hidden, only the idle descriptor
+          should get hidden. Noted by Gepht@Hemlock
+        * With DBF_LESS_GARBAGE, garbage objects were loaded with
+          owner NOTHING instead of owner GOD, which could cause
+          crashes in the @mail code. Reported by Harvester@StarWars.
+        * Problems with getrandom() on some systems fixed.
+        * Help for dist3d() clarified. Reported by 
+          Kova'khan@Among the Stars TrekMUSH via Leo at the same MUSH.
+        * You may now use power() for integral powers of negative
+          numbers. Suggested by John Hutchinson.
+        * setunion(,test,|) used to return |test, now returns test.
+          Reported by Ashford @ V MUSH
+        * SAFER_UFUN now blocks non-God eval'ing a u() on God.
+          Reported by MRR@ATS
+        * Building with SunOS cc and COMPRESSION_TYPE 0 works now. 
+          Reported by Jonas Oberg.
+
+Personnel Changes:
+        * Ralph Melton has retired as a member of the PennMUSH 
+          devteam. Alex and Javelin send him best wishes and big
+          thanks for all his work. Replacing Ralph will be
+          Thorvald Natvig. Welcome aboard, TN!
+
+& 1.6.10p6
+Version 1.6.10 patchlevel 6                  May 11, 1997
+
+Fixes:
+       * inc() and dec() didn't work right with NULL_EQ_ZERO.
+         Fixed. Report by Dennis DeMarco
+
+
+& 1.6.10p5
+Version 1.6.10 patchlevel 5                 April 29, 1997
+
+New Commands:
+        * @channel/title sets a player's title for a channel.
+          The title appears prepended to their name.
+          I forgot this, and Thorvald Natvig noticed.
+
+Minor Changes:
+        * The inc() and dec() functions can now increment and
+          decrement strings that end in integers. For example:
+          inc(LINE_10) => LINE_11
+          inc(LINE-10) => LINE-9   (incrementing -10)
+          inc(LINE1.9) => LINE1.10 (incrementing the 9)
+        * The LOWER_MATH option has been removed. Everybody
+          gets shr(), shl(), inc(), and dec().
+        * New attributes OIDESCRIBE and AIDESCRIBE do what you'd 
+          expect for the internal descs of objects. Suggested
+          by Stacy Robinson.
+        * @chan/on by a Wizard always succeeds, even if there's a 
+          join lock. Suggested by Mike Wesson.
+        * Enhanced protection against malicious ANSI codes.
+        * @channel/decomp displays locks in a better format.
+          Suggested by Naomi Novik.
+        * Numbers are now checked to be sure they're not so
+          large as to bring down the system, at least in theory.
+          Suggested by Atuarre.
+        * Configure rebuilt under dist-3.0 pl70.
+        * queue_write has been modified to reduce the number of
+          packets sent out on the net; it only sends when needful
+          and lets the usual output loop handle most output.
+          The many packets issue was noted by Doogie.
+        * All references to "parent rooms" have been changed to
+          "zone master rooms" for clarification. Noted by Jonas Oberg.
+
+Fixes: 
+        * Long output should no longer cause NeXT
+          systems to disconnect the user. Fix by Mike Kienenberger
+        * Long output should no longer cause Win32 systems to
+          disconnect, but it will throw out the beginning of 
+          the output. Fix by Hans Engelen.
+        * Bad objects on chat channels are now removed when the
+          chatdb is loaded. Suggested by Dennis De Marco.
+        * help for last() added. Report by Flame.
+        * Objects couldn't be added to object channels. Fixed.
+        * If a site matched in access.cnf, but didn't specify 
+          a certain access rule, a later matching site could. This
+          is bad for 'register', and not what was intended (that
+          a matching site completely controls that site's access).
+          Now fixed. Report by William Browning.
+        * If you're on a channel but don't pass the see-lock,
+          @chan/who returns a better error. Report by Cro.
+        * help for remove() now mentions delimiters. Noted by J. Wilcox.
+        * All tabs in helpfiles replaced with spaces. [TAP]
+        * Loading a db that didn't have garbage objects stored
+          caused a slew of warnings about null names. Should be fixed
+          now. Reported by Atuarre.
+        * FreeBSD hints improved, thanks to Atuarre and Doogie.
+
+
+& 1.6.10p4
+Version 1.6.10 patchlevel 4                 March 3, 1997
+
+Major Changes:
+        * The disk db can now be dumped without including GARBAGE
+          objects, which may make it somewhat smaller. [TAP]
+
+Minor Changes:
+        * The %? substitution returns the current function invocation
+          count and recursion depth as two space-separated numbers. [TAP]
+        * dig() can take a single argument instead of 3.
+
+Fixes:
+        * set(obj/attr,flag) no longer says "Set" when the object or
+          player is QUIET. Reported by Graywolf.
+        * Search_All players couldn't use @search. Now they can.
+          Reported by Cro.
+        * lnum(2,1) didn't work right, and things were broken with
+          floating point args to lnum. Fixed.
+
+
+& 1.6.10p3
+Version 1.6.10 patchlevel 3              February 9, 1997
+
+New function:
+        * graball() as per TinyMUSH 2.2. [TAP]
+
+Fixes:
+        * On some systems, make clean would go into an infinite loop
+          if src/CSRI didn't exist. This should now be fixed.
+          Report by Cro@Spaceballs.
+        * Restart script now looks for minimal.db.gz if minimal.db.Z
+          can't be found. This helps people running under Mklinux
+          on Power Macs(!). Report by Jason Newquist.
+        * Fix to matchall(). [TAP]
+        * move.o doesn't compile if FIXED_FLAG isn't defined.
+          Noted by Andy Jalics.
+
+
+
+& 1.6.10p2
+Version 1.6.10 patchlevel 2              February 2, 1997
+
+Minor Changes:
+        * If forking to do dumps, lower the priority of the dumping 
+          process to keep the parent process more responsive.
+          Based on a patch by Doogie@ATS TrekMUSH.
+
+Fixes:
+        * Code cleanup and fixes to comp_w.c. Problems reported by
+          Mike@TBFF
+        * Portability fixes for alpha-linux systems. Thanks to Roger
+          Chen for facilities to work on this.
+        * Added help for @dump/debug. Report by Flame.
+        * Note added to game/txt/Makefile about braindead Irix 6.2 make
+        * Ident source files now do better #ifdef'ing of Unix include
+          files to help out Win32. 
+        * Configure changes to enhance portability.
+
+
+& 1.6.10p1
+Version 1.6.10 patchlevel 1              January 25, 1997
+
+
+Commands: 
+        * New command: @dump/debug. Like @dump/paranoid, but
+          it also tries to fix the memory db, so a shutdown/restart
+          may not be necessary. It is never forked. Suggested by Atuarre.
+
+Fixes:
+        * externs.h now declares crypt() as char *, not const char *.
+        * free_access_list's declaration is now K&R compatible again.
+        * repeat() now doesn't work with a null string, which prevents
+          a denial of service attack. Report by Atuarre. [TAP]
+        * Fencepost error in huffman compression code fixed.
+          Report by Mike Wesson. [TAP]
+        * idlesecs() is now referenced in pennfunc.hlp. Report by Flame.
+        * parse() is now referenced in pennfunc.hlp. Report by Sandi Fallon.
+        * lsearch() now gives the correct types in the help.
+          Report by Sandi Fallon.
+        * Halted messages now indicate the object that was halted,
+          even if they are halted as the result of a chown.
+          Report by Sandi Fallon.
+
+& 1.6.10p0
+Version 1.6.10 patchlevel 0              December 16, 1996
+
+Major Changes:
+        * The attribute matching order has been cleaned up. Code by [TAP]
+          If you do ex obj/attribpattern, and...
+          1. If attribpattern has no wildcards:
+             a. Return attribpattern's value if set, else
+             b. Return the value of an aliased attribute, if any.
+          2. If attribpatern has wildcards:
+             a. Return anything which matches the pattern, and
+                don't bother about aliases.
+
+        Here's a little example:
+                       only DESC set   only DESCRIBE set    both set
+  ex foo/desc          DESC            DESCRIBE             DESC
+  ex foo/desc*         DESC            DESCRIBE             both
+  ex foo/describe      (error)         DESCRIBE             DESCRIBE
+
+
+Minor Changes:
+        * parse() is now an alias for iter() [TAP]
+        * Attribute set/clears report the name of the attribute in the
+          set/cleared message. [TAP]
+        * fun_lattr is now in fundb.c. [TAP]
+        * Improved setq/setr help. [TAP]
+
+Fixes:
+        * Typo in help evaluations corrected. [RLM]
+        * The side-effect version of lock() no longer returns a value.
+          Reported by Corum.
+        * help quota() added. Report by Dennis De Marco.
+        * help INHERIT updated to reflect current control structure.
+          Suggested by Vedui.
+        * vmul() with a separator now returns the vector separated with
+          that separator, as promised. Report by Atuarre@TrekMUSH.
+        * FIXED now overrides STICKY, so you can't set yourself STICKY,
+          get yourself dropped, and go home. Report by Anthony Ivan.
+        * lsearch(all,flags,c) worked, but lsearch(all,flags,Pc) didn't!
+          This is fixed. Report by Flame.
+
+& 1.6.9p9
+Version 1.6.9 patchlevel 9               November 18, 1996
+
+Fixes:
+        * A Wizard doing @find on a MUSH with garbage crashes the MUSH.
+          Fixed. [TAP]
+        * Fairly major security problem due to a typo in the player-
+          destruction code fixed. Reported by Dennis DeMarco.
+
+
+& 1.6.9p8
+Version 1.6.9 patchlevel 8               November 10, 1996
+
+Major Changes:
+        * The control system has changed slightly: only wizards
+          control wizobjects and only royalty control royobjects.
+          If a mortal's object gets wizbitted, the mortal will
+          cease to control it. Also, protection is now afforded
+          to players from non-inherit objects.
+
+Fixes:
+        * Setting an @listen on a room caused a crash. Fixed.
+          (Note: @listen on rooms still doesn't work - it's not
+           supposed to -- use LISTENER and ^patterns -- but at
+           least it doesn't crash. :)  Report by Flame@B5.
+        * dune.h.dist now defaults the index and rules indx files
+          to ending in .idx, as they should. Noted by Jason Newquist.
+
+
+
+& 1.6.9p7
+Version 1.6.9 patchlevel 7               October 30, 1996
+
+Functions:
+        * The help for vmul() suggested it did an elementwise product
+          of 2 vectors, returning a vector. In fact, it was doing a
+          dot product (sum of the elementwise product, which is a scalar).
+          vmul() now does what the help suggests, and vdot() does
+          a dot product.
+
+Fixes:
+        * Bug in comp_w.c (word-based compression) which could cause
+          loss of subjects in @mail has been fixed. [NJG]
+        * Help for "control" made more explicit, and help for "controls()"
+          references "control", not "controls".
+        * @wait 0 now queues its subject immediately, rather than waiting
+          1 second. [TAP]
+        * The "Patchlevel" file is now more grammatical. For Sam Knowlton. :)
+        * Variables named "listen" have been renamed "listener" to
+          remove compiler warnings about shadowing the listen() system call.
+          Reported by Flame@B5
+
+
+& 1.6.9p6
+Version 1.6.9 patchlevel 6               October 24, 1996
+
+Fixes:
+        * Removed needless calls to tprintf() within do_log() in 
+          access.c
+        * Fixed the variable j in fun_lnum to be the right type
+
+
+& 1.6.9p5
+Version 1.6.9 patchlevel 5               October 15, 1996
+
+Options:
+        * COMMA_EXIT_LIST causes exit lists to be comma-separated,
+          and include the word "and" before the last exit. [NJG]
+
+Functions:
+        * lnum() with multiple arguments now behaves exactly like
+          Tiny 2.2's lnum().
+
+Minor Changes:
+
+        * @pcreate failure messages distinguish between bad passwords
+          and bad names. Related to a suggestion by Philip Mak.
+
+Fixes:
+        * elements() used to put a leading space in output. Fixed. [RLM]
+        * index(foo|||,|,2,1) now returns nothing, instead of ||,
+          as it should. Fix by Harvester@StarWarsMUSH.
+        * @cpattr a/DESC=b (where a has a DESCRIBE attribute and no DESC
+          attribute) correctly grabbed DESCRIBE from a, but copied it to
+          DESC on b. This is now fixed. [TAP]
+        * Various unused variables and missing prototypes fixed. [NJG]
+        * More win32 fixes. [NJG]
+        * Revised comp_w.c to handle table overflow better. [NJG]
+        * splice() wasn't putting spaces back in between words.
+          Reported by Philip Mak.
+        * Help for aposs() was never added. [MPC]
+        * mkindx doesn't compile on systems without strcasecmp. Fixed.
+          Reported by Stephen Sanderlin.
+        * Configure: -lsocket is used if it's found (also -lcrypt, -lnsl)
+        * Various missing includes fixed
+        * Linux systems weren't doing daylight savings time right.
+          We now always try to use tm_isdst = -1 to get this right.
+
+
+
+& 1.6.9p4
+Version 1.6.9 patchlevel 4               October 9, 1996
+
+Fixes:
+        * Restart script fix in 1.6.9p3 is buggy. Fixed the fix.
+
+
+& 1.6.9p3
+Version 1.6.9 patchlevel 3               October 7, 1996
+
+Changes:
+        * @wipe/wipe() of a single attribute (no wildcards) no longer
+          checks the SAFE flag on the object.
+        * Wildcards are now accepted for the attribute name when setting
+          attribute flags.
+
+Fixes:
+        * @succ and &succ could create duplicate success attributes.
+          Fixed so that @succ -> SUCCESS and &succ -> SUCC,
+          and no more duplicates. [TAP]
+        * Help for @purge had disappeared. Back.
+        * Forgot to include restart patch mentioned in 1.6.9p2 [PeaK]
+
+
+& 1.6.9p2
+Version 1.6.9 patchlevel 2               October 5, 1996
+
+New Compile Options:
+
+        * The behavior of attributes is now configurable; you can
+          emulate attribute setting behavior from 1.6.8 and earlier,
+          use the currently recommended settings, or anywhere 
+          in between. [TAP]
+
+Functions:
+
+        * lnum() now takes an optional second argument, which is the
+          number to start with, e.g. lnum(3,4) => 4 5 6
+          Suggested by [MPC]
+
+Fixes:
+        * @mail/debug clear=<player> now clears all the player's
+          mail, not just their current folder. Fix by
+          Leonid Korogodsky.
+        * look/outside at an ambiguous name crashed. Report by Vedui.
+        * New hints file: linux_2.sh
+        * Help for setq() now included. Report by Flame@Babylon5
+        * Added -w to the ps in restart so the output isn't
+          truncated. [PeaK]
+        * Restart changes to prevent some race conditions under Linux 2.0
+          [Peak]
+
+
+& 1.6.9p1
+Version 1.6.9 patchlevel 1               September 26, 1996
+
+New Function:
+
+        * setr() is like setq() but returns the string as well.
+          It's identical to [setq(#,string)]%q#. Suggested by Adam Dray.
+
+Fixes:
+
+        * Bug with @clone fixed. Report by Vedui.
+        * Bug with @mail folder 15 fixed. Report by Vedui.
+        * @sitelock/register worked backward. Fixed. Report by [MPC]
+        * rnum() now requires that you can examine the room.
+          By Jason Rhodes, with minor mods.
+        * Better messages when a player or thing is set audible.
+          Suggested by Babylon5@kuwait.net
+        * Configure now uses your email address instead of your name
+          when trying to subscribe to the mailing list; on some systems,
+          it's hard to get a valid name -- getting an email address
+          is usually possible. Suggested by Cro@Spaceballs
+        * Configure no longer adds multiple copies of the same gcc
+          warning flags when you run it again. Reported by Cro@Spaceballs
+
+& 1.6.9p0
+Version 1.6.9 patchlevel 0               September 16, 1996
+
+Attribute Rewrite [TAP]:
+
+        * There is now a distinction between an empty attribute and
+          a non-existant attribute:
+                @va me          <--- wipes out my VA attribute
+                @va me=         <--- sets my VA attribute to be empty
+          Empty attributes retain their locks and flags; wiped attributes
+          are gone forever.
+        * @set and set() can not wipe out attributes. @wipe and wipe()
+          will.
+        * You can now include ':'s in $-command and ^-command patterns
+          by escaping them with '\'.
+        * Standard attribute names are kept in a string table and
+          memory is not allocated for them. We guesstimate a 3-5%
+          savings in memory use from this change.
+        * objmem() and playermem() are now more accurate.
+        * Internal changes: new attribute flags AF_STATIC, AF_COMMAND,
+          AF_LISTEN; atr_comm_match doesn't look directly at compressed
+          strings any more; restructured some routines (atr_clr to clear
+          attributes, atr_add to set them).
+
+Fixes:
+        
+        * Add help topic SETTING-ATTRIBUTES to explain the above.
+        * Fixed small error with closing a NULL file in access.c [TAP]
+        * Improved help for @destroy
+        * Dashed lines in @mail are now 2 hyphens longer. [MPC]
+        * The Configure hints files weren't properly used when
+          compiling with gcc.
+        * The color flag is now aliased to colour as well. [MPC]
+        * With MAIL_SUBJECTS, the @mail/list could get scrambled
+          for messages from players with long names. Reported by
+          Leto@DuneII.
+        * Small memory leak when access.cnf is reloaded via kill -HUP
+          has been fixed.
+        * Vestige of old TEMPLE code removed from do_drop. Reported
+          by Mike Selewski.
+
+& 1.6.8p1
+Version 1.6.8 patchlevel 1               September 7, 1996
+
+Fixes:
+        * The mail*() functions were broken due to a typo. [MPC]
+        * The addr field in the descriptor structure has been expanded
+          from 50 characters to 100 characters, because you'd really
+          like to store the entire ident response from encrypting
+          ident daemons.
+        * The MAIL: announcement at login now counts your cleared
+          mail in your mail totals, in case something clears mail for
+          you while you're offline. Suggested by Mike Wesson.
+        * Better string protection for parse_chat which may fix
+          a potential crash when speaking on +channels.
+
+& 1.6.8p0
+Version 1.6.8 patchlevel 0               September 3, 1996
+
+This is intended as a maintenance release because 1.6.7 has had
+many rapid patches to achieve stability.
+
+Fixes:
+        * "make install" now implies "make all". Corrects a problem
+          with not remaking hdrs/buildinf.h.
+        * README file now shows the utils directory in its directory tree.
+          Reported by Mike@StarWars.
+        * On startup, PennMUSH now logs its version information.
+        * @log/wiz was logging to trace log, not wiz log. Reported by
+          Dean Moore.
+        * @mail system behaved badly if you @shutdown on an empty
+          maildb on some systems. Reported by Mike Selewski.
+        * Detection of getpagesize() system call is now handled by 
+          Configure. Fixes problems on Irix, reported by Mike Selewski. 
+        * Minor typo in Irix hints file fixed.
+        * Stupid misspelling of August in CHANGES-10 fixed.
+
+& 1.6.7p6
+Version 1.6.7 patchlevel 6               August 31, 1996
+
+Fixes:
+        * The attribute clear fix in 1.6.7p5 is buggy. Now it's really
+          fixed. Report by [MPC].
+
+
+& 1.6.7p5
+Version 1.6.7 patchlevel 5               August 30, 1996
+
+Fixes:
+        * Sites not listed in the access.cnf file should have been
+          allowed, not denied access. Fixed. Best report by Cwilla@Victory
+        * A little more info in mush.cnf about how player_creation and
+          access.cnf interact
+        * Trying to clear a never-existant attribute got the right
+          error message, but clearing an attribute that had existed,
+          but been already cleared got the "Foo - Set." message.
+          Fixed - atr_add now skips disposed attributes.  Report by [MPC]
+
+
+& 1.6.7p4
+Version 1.6.7 patchlevel 4               August 28, 1996
+
+Fixes:
+        * Bug in mail functions that caused mail(1:0) to crash
+          has been fixed. Reported by Corum@StormWorld.
+        * Another Win32 fix by Pat Pinatiello.
+
+
+& 1.6.7p3
+Version 1.6.7 patchlevel 3               August 28, 1996
+
+Fixes:
+        * Configure wasn't setting HAS_SENDMAIL correctly because the
+          updated config_h.SH wasn't included in the diff!
+        * @mail/clear's message about unread mail was screwy.
+
+
+& 1.6.7p2
+Version 1.6.7 patchlevel 2               August 27, 1996
+
+Fixes:
+        * Forbidden_Site wasn't working quite right
+        * Win32 compatibility improvements suggested by Pat Pinatiello.
+          Hopefully, no further real hacking should be required to 
+          build under Win32 with Visual C++. Pat's instructions included
+          as win32/README.visualc++
+
+
+& 1.6.7p1
+Version 1.6.7 patchlevel 1               August 27, 1996
+
+Fixes:
+        * #ifdef's and the like were missing which prevent compilation
+          unless HAS_SENDMAIL and CHAT_SYSTEM (>2) were defined.
+          Fixed by [TAP].
+
+
+& 1.6.7p0
+Version 1.6.7 patchlevel 0               Augest 22, 1996
+
+Major Changes:
+
+        * The lockout.cnf and sites.cnf files are no longer used.
+          Instead, the file game/access.cnf controls all aspects of
+          site-based (or, with ident, possibly user-based) access.
+          Sites can be explicitly allowed or denied the ability to:
+            * Connect to guest players
+            * Connect to non-guest players
+            * Create players
+          Sites can be configured to:
+            * Use email registration (see below)
+            * Set all players from the site SUSPECT
+          See the file game/access.README for file format information.
+          The LOCKOUT and WCREATE defines have been removed from options.h.
+
+        * A new access option, email registration, is available.
+          From the connection screen, the command
+            register <player> <email address>
+          will create the player with a random password, which will be
+          emailed to the address. The email address is stored on the player
+          in the wiz-only attribute REGISTERED_EMAIL. 
+          Obviously, this requires that the system have a way to send email.
+          Ideas in this code came from Jim Cook.
+
+        * @powers are now tabled in flags.c.
+
+        * Nick Gammon's word-based compression algorithm is now
+          COMPRESSION_TYPE 3. This algorithm may be faster than
+          Huffman on the whole, and may provide better compression
+          for large (>1.5Mb of text) databases.
+
+        * @mail message-lists now understand the format [folder#:]msg[-msg]
+          For example, the first 3 messages in folder 1 are 1:1-3.
+          @mail commands that are not given any message list are assumed
+          to apply to all messages in the current folder. You can also
+          do this explicitly by using the word "folder". When you use
+          the word "all", you match all messages in all folders.
+          For example, try: @mail all
+          @mail internals rewritten to increase code reuse.
+
+New Commands:
+
+        * @sitelock has got an additional syntax to support the new
+          access.cnf system.
+
+New Functions:
+
+        * powers() returns the list of powers set on a thing.
+          If HASPOWER_RESTRICTED is defined, you must be able to
+          examine the thing to do this.
+        * mail(), mailtime(), mailstatus(), and mailfrom() now accept
+          this syntax:
+                mail([<player>,] [<folder #>:]<message #>)
+        * cemit() does what you'd expect. Suggested by [MPC].
+
+New Powers:
+
+        * Open_Anywhere power allows the player to open an exit between
+          to any room, even if the player does not control the source
+          or target room.
+
+
+Minor Changes:
+
+        * Previously, a player with the Halt power could use
+          @halt obj=command to effectively @force any object.
+          This form of @halt is now only allowed if you control the
+          object. Bug reported by Flame.
+
+        * When EXTENDED_ANSI is defined, ansi codes are stripped out
+          of strings before checking them against LISTEN and ^patterns.
+          Suggested by Mike Wesson.
+
+        * HAVEN players are no longer notified when they send back
+          an @haven message in response to a page. Page-locked players
+          still are. Suggested by Naomi Novik.
+
+        * @decompile me results in a decompile with "me" as the object name
+          @decompile <player> results in the player's name as the object name
+          @decompile of an exit will use the exit's short name instead of
+          its full name for setting attributes and flags. 
+
+        * Utilty scripts (customize.pl, update.pl, update-cnf.pl, 
+          fixdepend.pl, and make_access_cnf.sh) are now in the utils/
+          subdirectory.
+
+        * Default rwho server is now littlewood.math.okstate.edu,
+          which replaces riemann, which has been turned off.
+
+        * time() and convsecs() now indicate the first 9 days of the
+          month as 01..09, rather than 1-9, which makes convtime()
+          happier when you convert back.
+
+        * You may @tel an exit back into its own room. Added for
+          compatibility with other MUSH flavors. Suggestion by Philip Mak.
+
+        * Dark connections are now broadcast only to MONITOR wizards and
+          royalty. This involved replacing the raw_broadcast and
+          toggle_broadcast functions with a new flag_broadcast function
+          which subsumes them. Suggestion by Philip Mak.
+
+Fixes:
+        * Noted the 256-character limit on channel descriptions in the help.
+        * abs() now deals correctly with floating points.
+        * Win32 compatibility improvements suggested by Pat Pinatiello.
+        * Updates to BUGS, README.Deprecated, and FAQ [TAP]
+        * An ANSI_NORMAL is sent at the end of the Doing message in WHO.
+        * mail() and related functions now accept "me". Bug noted by
+          Mike@TBFF
+        * Help for @squota no longer says that it works like @quota
+          when no limit is given, because it doesn't. 
+        * Bigram compression was not freeing memory that it used
+          for initialization. Fixed, and made faster.
+        * Help for @unlink fixed; you can't pick up unlinked exits. [TAP]
+        * Potential string overflow in new_connection fixed. Idea by [TAP]
+        * Code cleanup: many old sections that were commented out
+          with #ifdef NEVER ... #endif have been removed.
+        * @set obj=!going gave an error message but still reset the flag.
+          Now it should just give the error. Reported by Philip Mak.
+
+& 1.6.6p0
+Version 1.6.6 patchlevel 0               July 28, 1996
+
+This version involves primarily cleaning up and streamlining code
+in preparation for major internal changes in later releases.
+
+Major Changes:
+        * The following options are now standard and no longer turned
+          on via dune.h/options.h: RALPH_LOCKS, EXTENDED_MAIL, 
+          INHERIT_FLAG. [TAP]
+
+New Commands:
+        * @firstexit command moves an exit to be first in the list of 
+          a room's exits. Based on contrib/topexit.165p3 by Marlek@Earth1996
+          (but note that it requires control over the *room*, not the
+          exit).
+
+New Functions:
+        * poll() returns the current poll. By William Knopes.
+
+Flags:
+        * TEMPLE flag has been removed. This requires adding a new
+          DBF bit. [TAP]
+
+Minor Changes:
+        * Admin no longer automatically pass leave-locks and NO_LEAVE.
+          Suggested by Naomi Novik. [TAP]
+
+Fixes:
+        * Users could cause an infinite loop with @mail/fwd. Fixed.
+        * The fix to pmatch() in 165p4 didn't quite do the job.
+          Alex's does. [TAP]
+        * @mail/stat commands now show correct number of cleared messages.
+          Bug reported by [MPC]
+        * Typo fixed in help for items() [MPC]
+        * help for BUILDER now calls it a power, not a flag.
+
+
+& 1.6.5p4
+Version 1.6.5 patchlevel 4               July 9, 1996
+
+New Functions:
+        * entrances() works like @entrances, including the cost.
+          Suggested by Julienna@TrekMUSH.
+
+Commands:
+        * whisper/list takes multiple recipients. Suggested by [MPC].
+
+Minor Changes:
+        * A DARK-reconnected message has been added. Idea by [MPC]
+        * LFAIL/OLFAIL/ALFAIL attributes now control the message
+          seen when a player fails to leave an object due to the
+          NO_LEAVE flag or a leave-lock.  By Naomi Novik.
+
+Fixes:
+        * pmatch() on a DARK or hidden player by an unpriv'd player
+          now returns #-1 (can't find the player).
+        * inc(very_big_n) now works exactly like add(1,very_big_n) instead
+          of returning a huge negative number. Same for dec(). 
+          Reported by [MPC].
+        * Fixed spelling of Tinyfugue in help @decompile. [MPC]
+        * Documented the way hasflag() works a bit better to forestall
+          a common confusion: why hasflag(me,r) doesn't check for the
+          royalty flag.
+        * If a player tries to join a channel she's already on but
+          which has since been locked against her, she receives the
+          already-on-channel message now instead of the can't-join
+          message. Suggested by Cwilla@VictoryMUSH.
+        * Compile-time information in @version has been expanded and
+          made much more robust for systems whose make program is
+          broken. We now create the file hdrs/buildinf.h at the
+          beginning of each build, which contains the info.
+        * WHO at the connect screen works correctly now even if 
+          #0 is privileged. Bug reported by Doyce Testerman.
+
+
+& 1.6.5p3
+Version 1.6.5 patchlevel 3              June 29, 1996
+
+New Command:
+
+        * @shutdown/panic causes a panic dump and shutdown. God-only. [RLM]
+
+Minor Changes:
+
+        * When you destroy a room you don't own, the "wrecking ball"
+          message now includes the name of the owner, like the messages
+          when you destroy objects you don't own. Suggested by
+          Matt Chatterley.
+        * The channels() function, with no arguments, returns the
+          list of all channels (which the player can see).
+
+Fixes:
+        * When you try to get an object that's not in your location,
+          and POSSESSIVE_GET is not defined, you don't receive
+          any message at all. Fixed by Thaddeus Parkinson.
+        * DARK-disconnect messages now appear.
+        * All calls to random changed to getrandom (except the one
+          in getrandom!), and the prototype for random removed from
+          utils.c, where it causes conflicts for at least OSF/1.
+        * portmsg.c extensively hacked to use the autoconfiguration
+          info so you can "make portmsg" on supported systems.
+        * Help for mortal() and @mortal removed. [RLM]
+
+
+& 1.6.5p2
+Version 1.6.5 patchlevel 2              June 19, 1996
+
+Fixes:
+        * @grep didn't check to be sure you were allowed to see
+          the attributes it searched. Reported by Mike Wesson.
+
+
+& 1.6.5p1
+Version 1.6.5 patchlevel 1              June 12, 1996
+
+New Functions:
+        * channels(dbref) returns the list of channel names that 
+          dbref is on.
+
+Fixes:
+        * When paging player(s) with spaces in their names, the
+          LASTPAGED attribute stores them with quotes around them,
+          so that repaging will work.
+        * @mail/fwd shows you how many players it successfully
+          forwarded to. 
+        * @chzone here=none produced a spurious message if here was
+          Wiz/roy. Fixed. Reported by Matt Chatterley.
+        * udefault failed to evaluate the arguments it passed to the
+          attribute to be u'd. Reported by PeaK.
+        * Added include of sys/types.h in ident code. Necessary for
+          FreeBSD.
+        * @chf would not return a Huh? Fix by Hemlock MUSH admin.
+        
+& 1.6.5p0
+Version 1.6.5 patchlevel 0              June 2, 1996
+
+Database:
+        
+        * The "One" character in minimal.db.Z (#1, aka God) is now
+          distributed without a password. This takes care of people
+          who don't have crypt(3) or have a modified one (FreeBSD),
+          since you now log in without a password, and set one,
+          and all is well.
+
+Documentation:
+
+        * README has been extensively updated.
+
+Minor Changes:
+
+        * When paging, LASTPAGED is set to the list of succesfully
+          paged players' names, if any page was successful. This
+          is how page/list always worked; now applies to normal page.
+        * &foo obj = bar used to set FOO to " bar". Similarly, you
+          could use page/noeval (or any speech command with /noeval)
+          to page messages with initial spaces. For TinyMUSH compatibility,
+          this is no longer possible; leading spaces are trimmed now. [TAP]
+        * F_INTERNAL flags (like GOING) are now visible unless they're
+          also F_DARK. [RLM]
+        * When you @decomp obj/atr and atr doesn't exist, you now get
+          a message about it. Suggested by Matt Chatterley.
+        * PREFIX semantics now like INPREFIX (see 1.6.4p2) [TAP]
+
+Fixes:
+
+        * Bug in parse.c which could causes crashes fixed. Reported
+          by Atuarre. [TAP]
+        * On at least 2 systems, the system date was screwed up,
+          which made updating dune.h and options.h still produce
+          files younger than dune.h.dist and options.h.dist.
+          This is not our fault, but we'll fix it anyway - now we
+          touch dune.h.dist and options.h.dist before we update
+          dune.h and options.h. 
+        * @wiping a STARTUP attribute didn't reset the STARTUP flag. [TAP]
+        * Fixed bugs in the freebsd hints.
+        * All of a sudden, linux systems have started barfing on
+          our declaration of signal() in externs.h. All right, if they
+          want to play that way, we now test for this in Configure
+          and don't declare it ourselves if it would break things.
+        * Changed Amberyl's address in the source to lwl@digex.net
+        * Somehow the fix for add(+1,0) got left out of 1.6.4. Back in. 
+        * The ansi() function now produces underscore with "u", as
+          advertised. [TAP]
+        * Warning about discarding const in assignment fixed in game.c.
+
+& 1.6.4p2
+Version 1.6.4 patchlevel 2              May 14, 1996
+
+Minor Changes:
+        * At game startup, semaphores on all objects are cleared.
+          Prevents objects from having leftover semaphore states after
+          a shutdown. [TAP]
+          INCOMPATIBILITY WARNING! If you have objects which expect
+          to always have sempahore -1 (or something else), be sure
+          that they do "@drain me; @notify me" in their STARTUP
+          (as recommended in Amberyl's MUSH Manual).
+
+Fixes:
+        * @dump/paranoid produced db's with incorrect db flags
+          when CHAT_SYSTEM was 3 or 4. Reported by Matt Chatterley.
+        * Under certain conditions, one could get inside a ZMO using
+          the "go" command. Fixed. [RLM]
+        * A strcpy in fun_match is now a safe_str. [PeaK]
+        * INPREFIX fixes: (1) no space if INPREFIX evals to a null string,
+          (2) message to be prefixed is passed as %0, (3) %1-%9 are
+          temporarily cleared. [TAP]
+
+
+& 1.6.4p1
+Version 1.6.4 patchlevel 1              May 11, 1996
+
+Fixes:
+        * Forgot to #ifdef MAIL_SUBJECTS around fun_mailsubject.
+          Reported by Mercurial Mink@Protean.
+        * Quirk in @dbck fixed. [RLM]
+        * Possible denial of service attack using repeat() fixed. [RLM/TAP]
+        * Problem with Configure script under certain conditions (esp FreeBSD)
+          producing bad compiler flags fixed. Report by Mike Wilson.
+
+& 1.6.4p0
+Version 1.6.4 patchlevel 0              May 3, 1996
+
+Major Changes:
+        * eval_boolexp rewritten again. Important features:
+          - If A is locked to @B, evaluating A's lock is identical
+            to evaluating B's lock
+          - To do an indirect lock or an elock(), you must be able to
+            read the lock on the target object (i.e., you must be
+            See_All, control the object, or it must be Visual)
+            (New macros Can_Run_Lock and Can_Read_Lock for this)
+          - As a result, channel locks will work as advertised if
+            you indirect-lock them to VISUAL objects
+        * Changes to handling of plain strings (with no evaluation)
+          resulting in doubling of speed. [TAP]
+        * If you fail the page-lock on a DARK player, you receive their
+          AWAY message rather than their HAVEN message. That is,
+          failing a page-lock on a DARK player is just like paging
+          a DARK HAVEN player.
+        * INHERIT check moved to controls() function
+
+New functions:
+        * mailsubject() returns subject of a given mail message,
+          analogous to mail() and mailfrom(). By Atuarre.
+
+Minor Changes:
+        * Don't go through some major sections of the parser if we've
+          be given PE_NOTHING. Performance boost. [TAP]
+        * Players who are allowed to idle past inactivity limit, and
+          are unfindable, and can hide, are hidden (as before), but
+          only receive the notification once, instead of every
+          hour (or whatever your limit is). Players who are all of the
+          above but can't hide don't get any notification now.
+          Suggested by Gepht@Hemlock
+        * When allocating a new boolexp, set its struct members to
+          null values for better debugging. [RLM]
+        * We no longer save/restore the r-values when evaluating
+          the left side of the equal sign in attribute setting.
+          So @desc %q0 = %q0 now works as expected. Reported by PeaK.
+        * Locks set when players are initially created (lock, elock)
+          are now set to "=me" instead of "me". [PeaK]
+
+Fixes:
+        * When EX_PUBLIC_ATTRIBS wasn't defined, you never saw the
+          DESCRIBE attribute listed when you examined objects. [AKM]
+        * Vacation and Connected were being handled incorrectly
+          internally; the type of object wasn't being checked. [TAP]
+        * Now that Connected is fixed, the call to @doing doesn't have
+          to prefilter for players, so don't. [TAP]
+        * +nn is now considered a number, so add(+20,1) works.
+          Reported by Dennis De Marco.
+        * String boundary problems sometimes when you hit the 
+          function recursion limit could cause crashes. Fixed.
+          Reported by Oliver Stoll. [TAP]
+        * Making on Linux should hopefully now work with -g. [PeaK]
+        * Warning message when @chzoning an inherit player used
+          to be announced when @chzoning *any* player. Fixed. [PeaK]
+
+
+& 1.6.3p7
+Version 1.6.3 patchlevel 7              April 25, 1996
+
+Fixes:
+        * beep() works without arguments now, as promised. [TAP]
+        * repeat(string,-number) caused an infinite loop. Reported by
+          Atuarre.
+        * The create() function didn't accept a single argument. [RLM]
+        * last(a b c) was returning " c" instead of "c". [TAP]
+        * The Configure script did indeed send email to listproc if
+          you asked to be subscribed to the pennmush mailing list.
+          But it sent it with Precedence: junk, which listproc ignores.
+          Fixed.
+        * %q9 wasn't preserved due to a typo in function.c. Fixed. [PeaK]
+        * SIGHUP now handled synchronously to prevent race conditions.
+          Noted by PeaK.
+
+
+
+& 1.6.3p6
+Version 1.6.3 patchlevel 6              March 30, 1996
+
+Minor Changes:
+        * A channel's owner now always passes the channel's modlock.
+        * Wizards may now do folderstats(player,folder). Previously
+          on God could, but wizards could see the stats for the player's
+          current folder, and could reset the current folder, so it
+          doesn't make sense to restrict. Report by Cwilla@Victory-MUSH
+        * @function is restricted to see_all players. Suggested by
+          Oderus (Mike Wesson).
+
+Fixes:
+        * @sitelock/ban and @sitelock/register had their effects
+          mixed up. Reported by Matt Chatterley.
+        * Objects that were undestroyed were having the GOING flag
+          removed but not the GOING_TWICE flag. Fixed. [RLM]
+        * element() now works as advertised. [RLM]
+        * Systems without IEEE math now can't do power(x,y) if
+          *either* x or y is greater than 100. Report by InsaneJoseph@WoP
+
+
+
+& 1.6.3p5
+Version 1.6.3 patchlevel 5              March 12, 1996
+
+New Powers:
+        * The immortal power has been split into 3 powers:
+          no_pay (has unlimited money), no_quota (has unlimited quota),
+          and unkillable (can't be kill'd). For backwards compatibility,
+          @set player=[!]immortal sets/unsets all three. Objects which
+          were immortal before this patch will have all 3 flags set.
+
+Minor Changes:
+        * The DEBUG flag can now be set by any user. The Can_Debug power
+          will automatically be removed from all objects.
+        * The "directive not found" message in loading mush.cnf has been
+          changed to a more comprehensible/less scary "directive in 
+          cnf file ignored" message.
+
+Fixes:
+        * FIXED players can no longer "go home" or "move home"
+        * index-files.pl was producing incorrectly titled index entries. [RLM]
+        * More fixes to elock() and zone locks. Elock() should now work
+          even when RALPH_LOCKS isn't defined. Zone locks are checked
+          on the correct object. [TAP]
+        * @open exit=variable should work correctly now. [TAP]
+
+
+& 1.6.3p4
+Version 1.6.3 patchlevel 4              February 27, 1996
+
+Fixes:
+        * Complex evaluation locks may be better behaved. [RLM]
+        * ex/debug now shows flag bits in hex, not decimal [RLM]
+        * % failed to quote the following character in some cases.
+          Fixed to work like 1.50. [PeaK]
+        * iter() only allowed a single-character output separator instead of
+          an arbitrary string. Fixed. [PeaK]
+        * Bug in extmail.c which caused crashes on find_exact_starting_point
+          with newly created players may be fixed.
+        * Repaging with page/list uses the correct format.
+          Also, failing to page anyone successfully doesn't clobber
+          LASTPAGED when using page/list.
+        * Dark-connecting admin now trigger connection messages on
+          admin chat channels (unless the channels are quiet)
+        * Help for pos() corrected. 
+        * Things starting with #- (error messages) are now treated as
+          false (0) in boolean functions, as under 1.50. [TAP]
+        * The recursive_member function was inefficient, and potentially
+          buggy, which could allow things to be teleported into things
+          they contained. Fixed.
+        * @drain wasn't lowering QUEUE attrib. Fixed. [RLM]
+        * Zone master help updated to refer to zone locks [RLM]
+        * Memory malloc'd by replace string, though freed, didn't
+          have its mem_check cleared in a few places, which could make
+          it appear that replace_string was leaking. [PeaK]
+
+
+& 1.6.3p3
+Version 1.6.3 patchlevel 3              February 12, 1996
+
+New option:
+        * If NULL_EQ_ZERO is defined in dune.h, a null string will
+          be considered a zero in a math function. For example,
+          add(,3) will return 3, instead of #-1. 
+
+Minor Changes:
+        * Guests may not join or leave channels. [Mike Wesson, Oderus]
+
+Fixes:
+        * edefault() core dumps fixed.
+        * eval() now behaves correctly. [TAP]
+        * ALL players (inc. Wizards) now subject to BUILDING_LIMIT.
+        * Quota now properly updated on object destruction. [RLM]
+        * Misc. fixes to object destruction, especially in the case
+          of what happens to SAFE objects of destroyed players. [RLM]
+        * Queue was handled strangely for priv'd players. Fixed.
+        * New internal macros NoQuota and NoKill, both the same as
+          NoPay, established in preparation for future splitting of
+          Immortal power into 3 powers.
+        * help @clock2 typo fixed.
+
+
+& 1.6.3p2
+Version 1.6.3 patchlevel 2              February 7, 1996
+
+[L@E] refers to Lukthil@ElendorMUSH, who did many patches to 1.50
+that I'm just now getting to integrate.
+
+Minor Changes:
+
+        * Wizards are now effectively pemit_all
+        * @dol/notify notifies even if given a null list. [L@E]
+        * See_All players can see quotas with @quota. [L@E]
+        * If we're logging forces, don't just log forces *by* wizards,
+          log forces *of* wizards (and things they own) by anybody. 
+          Idea by L@E.
+        * Removed obsolete code: clear_atr_name
+        * mkindx now sorts index entries alphabetically, so it
+          doesn't matter what order you have get() and get_eval()
+          in your help file - help get will give get().
+          Code by Pavel Kankovsky (PeaK).
+
+Fixes:
+        * #-1 was being considered "true" instead of "false" when
+          TINY22_BOOLEANS was not defined. [RLM]
+        * munge() is better behaved when there are duplicates in the
+          first list, accurately matching them with the second. [L@E]
+        * Emits weren't propagating through audible exits when
+          there was nobody in the room. Fix idea by L@E.
+        * Charges were broken. Fix idea by L@E.
+        * Immortal players could lose coins when their non-immortal
+          objects ran code. Fixed. [L@E]
+        * @edit is less likely to overflow buffers when using ^ and $. [L@E]
+        * @halting objects used to queue them for #0. Fixed. [L@E]
+        * Semaphores weren't removed when an object was halted. Fixed. [L@E]
+        * Possible permissions problem in running STARTUP when objects
+          are chowned fixed. [TAP]
+        * make etags now picks up dune.h and options.h [RLM]
+        * Code consolidation: chowning when an exit is linked is done
+          via a call to chown_object now. [RLM]
+
+
+& 1.6.3p1
+Version 1.6.3 patchlevel 1              February 4, 1996
+
+
+Minor Changes:
+
+        * NO_LEAVE is now settable by anyone
+        * The index-files.pl script indexes admin news and help separately.
+        * *emit() functions can now have unescaped commas in their last
+          argument (the message). [TAP]
+
+Fixes:
+        * lit() doesn't screw up on braces. [TAP]
+        * @search and lsearch() using eval were broken. [TAP]
+        * @unlock foo worked, and also gave a Huh?. Fixed. [RLM]
+        * When a player was destroyed, their SAFE exits would be
+          destroyed instead of chowned. Fixed. [RLM]
+
+
+& 1.6.3p0
+Version 1.6.3 patchlevel 0              January 31, 1996
+
+Major Changes:
+
+        * Assume that object A is locked to @B, and object B is
+          locked to canuse/1. It used to be that when player P tried
+          to pass the lock on A, the canuse attribute on *A* was
+          checked, instead of B. Fixing that required that we check
+          to be sure that B is allowed to evaluate the CANUSE attribute
+          on A, which required internal changes to boolexp.c.
+
+Minor Changes:
+
+        * Objects can now send mail as themselves, not their players.
+          Mail from objects is reported as being from an object
+          when read. The format for @mail(/read) has changed somewhat.
+        * page[/list] now stores the last player(s) you paged in a LASTPAGED
+          attribute, so you can re-multipage. The behavior is a little
+          different for page and page/list. Page stores exactly what
+          you typed as the last player/list of players to page; page/list
+          stores only those names for whom the page succeeded.
+
+Fixes:
+
+        * The site of last failed login used to be a visual attribute.
+        * Parser misbehaved on: think ( foo ), removing spaces. [TAP]
+        * @edit and related wasn't stripping leading spaces from the
+          right side of the = sign. Fixed.
+        * Problem with count of unread mail on login with EXTENDED_MAIL
+          fixed.
+        * @channel/rename can now rename a channel to the same name
+          with case changes, without an error.
+        * +channel now matches partial ambiguous channels in a smarter
+          fashion - it looks for a channel you're on.
+        * Better help for @clock
+        * die(0,x) now returns 0. die(negative #,x) returns an error. [TAP]
+        * @chan/decompile can be used by the channel's owner or
+          anyone who passes the modlock, as well as Wizards.
+        * Channel hide locks work right. [RLM]
+        * @chan/decompile now shows the correct commands for locking
+          channels. [RLM]
+
+& 1.6.2p1
+Version 1.6.2 patchlevel 1              January 28, 1996
+
+Fixes:
+        * get_eval error about # of args fixed.
+
+
+& 1.6.2p0
+Version 1.6.2 patchlevel 0              January 26, 1996
+
+New Commands:
+
+        * @mortal <command> lets a privileged player run a command
+          as if they were unprivileged. [TAP]
+        * ahelp/anews shows admin help/news topics to admin players. 
+          Admin-only topics are in the same files (help.txt/news.txt),
+          but topic names begin with '&'. For example:
+          & for-all-players
+          ...text...
+          & &admin-only
+          ...text...
+        * page/list <players>=<message> will do a multipage with a less
+          spammy format for the pager.
+
+New Functions:
+
+        * mortal() lets a privileged player evaluate as if there were
+          unprivileged. [TAP]
+
+New Flags:
+
+        * MORTAL, to support @mortal and mortal(). Used internally, and
+          not settable. Can be reset by Wizards, just in case, though. [TAP]
+
+Minor Changes:
+
+        * If you have a channel "admin" and a global +admin (no args),
+          calling +admin will run the global, not give the
+          "What do you want to say to that channel" message, under
+          extended chat.
+        * If a panic db is corrupt, the restart script will move it to
+          save/PANICDB.corrupt instead of removing it.
+
+Fixes:
+        * Problems with compile when HAS_SIGACTION is undefined.
+          Noted by Brian Favela, affects Linux.
+        * Duplicate uselock check in atr_comm_match removed.
+          Noted by Brian Favela.
+        * Looking at an exit showed the full name and aliases, not just
+          the full name. [TAP]
+        * Compiling without EXTENDED_MAIL works right.
+        * Potential buffer overflow problems in look_exits() fixed.
+        * get_eval() really uses a /, not a comma, like the help says.
+        * (Hopefully) last remnants of old parser removed from externs.h
+        * @dol on an empty list now doesn't give the weird "what do you want
+          to do with the list" message. @dol with an empty command does. [RLM]
+
+& 1.6.1p1
+Version 1.6.1 patchlevel 1              January 23, 1996
+
+Fixes:
+        * v(#), v(!),and v(N) now return the same thing as %#, %!, and %N.
+          [RLM]
+
+& 1.6.1p0
+Version 1.6.1 patchlevel 0              January 21, 1996
+
+New compile-time options:
+
+        * SAFER_UFUN: when defined, prevents less priv'd objects from
+          evaluating ufuns on more priv'd objects, which helps a
+          potential security problem with side-effect functions.
+          This is more of a stopgap -- control and security will be
+          revamped in a future release -- but is recommended.
+
+New .cnf directives:
+
+        * player_creation enables/disables "create" at login screen.
+          Also @enable/disable creation
+        * use_dns enables/disables IP-to-hostname resolution
+
+New Functions:
+
+        * tel(thing,destination)
+        * oemit([room/]object,messsage)
+        * emit(message)
+        * lemit(message)
+        * remit(object, message)
+        * zemit(zone, message)
+        * timestring(secs,flag) converts seconds to #d #h #m #s
+          format. If flag is 1, the string always includes all time
+          periods (e.g. timestring(301,1) = 0d 0h 5m 1s). If flag is
+          0 or omitted, only the necessary periods are included
+          (e.g. timestring(301,0) = 5m 1s). 
+        * left(string, n) returns the first n characters of the string
+        * right(string, n) returns the last n characters of the string
+        * hasflag() syntax extended to hasflag(obj[/attr],flag) for
+          checking attribute flags as well.
+        * functions() lists all enabled functions
+        * atrlock(obj/attr[,<on|off>])
+        * doing(player) returns a player's @doing if they can be see
+          on the WHO list
+
+Minor Changes:
+
+        * @allquota now has /quiet switch, and works correctly when
+          no limit is given. [NJG]
+        * The restart script is now a lot smarter, and figures things
+          out from mush.cnf. All that needs to be set is the location
+          of the game directory, and if you use make customize, not
+          even that.
+        * @wall/@rwall/@wizwall with a null message fails. (src/speech.c)
+        * SIGHUPs sent to the MUSH are now logged in netmush.log
+        * Common code called when object ownership is changed has
+          been encapsulated into a single function for modularity.
+        * crypt() is now a macro on systems which can't define HAS_CRYPT
+
+Fixes:  
+        * The table() function was incorrectly truncating some
+          list elements. Fixed. Report by Alaric@WoP
+        * The match() function was returning an empty string instead
+          of 0 when it failed to match. Fixed. Report by Alaric@WoP.
+        * help math functions now list the transcendental and other
+          floating point functions
+        * map() and mix() were broken. Fixed. [TAP] (src/funlist.c)
+        * @grep/print is now case-sensitive, like help says. (src/predicat.c)
+        * Refunding money when objects are chowned 
+          was refunding 10 coins, instead of the object deposit. [RLM]
+        * convtime() hadn't been converted to the new parser,
+          and therefore was broken. Fixed. (src/funtime.c)
+        * Compilation warning in src/game.c fixed
+        * Giving players large negative amounts of money was slightly
+          broken and is now fixed. Obscure, eh? [RLM] (src/rob.c)
+        * Misleading comment in options.h.dist about QUEUE_LOSS
+          fixed. [RLM]
+        * Help fixed to indicate that there's no longer a 10 dimension
+          limit on vector functions.
+        * Various missing prototypes corrected.
+        * make customize now correctly sets the restart script
+          executable, and installs links to netmud and mkindx.
+        * Improved Win32 portability [NJG]
+        * Fixes to default() and edefault() [TAP]
+
+& 1.6.0p5
+Version 1.6.0 patchlevel 5              January 17, 1996
+
+Fixes: 
+        * Extended chat: @chan/decomp shows privs correctly now
+        * Extended chat: When a player is destroyed, channels they own
+          are chowned to God.
+
+& 1.6.0p4
+Version 1.6.0 patchlevel 4              January 17, 1996
+
+Fixes:
+        * Extended chat: Formerly, non-players could create channels.
+          No longer.
+        * Extended chat: Channel creation cost is now refunded when
+          channel is removed.
+        * Help text for HALT (game/txt/pennflag.hlp) and SUBSTITUTIONS
+          (game/txt/penn.hlp) now notes that HALTed objects do not
+          evaluate functions or substitutions.
+        * Nick Gammon's new email address is in README and win32/README
+
+
+& 1.6.0p3
+Version 1.6.0 patchlevel 3              January 16, 1996
+
+Fixes:
+        * default, edefault, udefault now work like Tiny 2.2, not eval'ing
+          their arguments.
+        * Many fixes to the extended chat system.
+        * Fixed misspelling of Guest in @config.
+        * @function on a non-existant object no longer crashes. [TAP]
+        * Problems with paranoid dumps not setting the correct dbflags
+          corrected. [TAP]
+        * Problems with EXTENDED_MAIL crashing when using LOGOUT fixed. [RLM]
+        * @warnings for exit-msgs and thing-msgs warned when there was
+          no OFAIL on a locked exit/thing, which is silly. [1.6.0p1]
+        * Started patching the CHANGES file, like I should.
+        * Fixes customize.pl
+        * Fixes update-cnf.pl so that 'make update' won't trash player_flags
+          (and other repeatable directives) in mush.cnf
+        * Fixes game/txt/README
+
+& 1.6.0p0
+Version 1.6.0 patchlevel 0              January 10, 1996
+
+Major Changes:
+
+        * New function parser with improved speed, sanity. [TAP]
+        * Complete rewrite of destruction code. Undestroying supported
+          for all objects, @adestroy works sanely, SAFE is safer,
+          better consistency checking. [RLM]
+        * Support for 'plural' gender for TinyMUSH compatibility. [2.2]
+        * Most filenames are now 8.3, to support the win32 port
+        * Some options have been moved to the runtime config file
+          (dbcomp). Others have been removed entirely.
+        * 'make update' will update runtime config directives
+        * The chat system has been completely rewritten. Number of 
+          channels is limited only by memory, channels are saved
+          across shutdowns (modifying source to add channels never
+          required), channels can be locked in various ways, 
+          non-wizards can create channels, etc.
+        * New .cnf directives: chat_database (where to store channels),
+          max_player_chans (how many channels can a non-admin create),
+          chan_cost (how much does creating a channel cost)
+        * The CSRI malloc is now supported and suggested. In addition
+          to being extremely memory-efficient, it offers leak detection
+          and profiling features.
+        * The database format now defaults to quoting text, which is
+          less vulnerable to corruption, in particular the loading of
+          attribute locks starting with a number. [1.50-15-04,05 TAP]
+
+New Functions:
+
+        * matchall() [2.2]
+        * default(), edefault(), udefault() [2.2]
+        * aposs() and %a return absolute possessive pronouns [2.2]
+        * elements() [2.2]
+        * mudname(), version(), starttime() [2.2]
+        * stats() is now an alias for lstats() [2.2]
+        * ulocal() [2.2]
+        * search() is now an alias for lsearch() [2.2]
+        * folderstats() returns information about numbers of messages
+          in a given @mail folder
+        * last() is the counterpart to first(), and returns the last
+          item in a list
+        * mailtime(), mailstatus(). Suggested by Alaric@WorldOfPain.
+
+New Commands:
+
+        * @emit can be abbreviated '\\', for TinyMUSH compatibility
+        * The speech commands (say, pose, @[po]emit, whisper, page)
+          can now take a /noeval switch, which prevents them from
+          evaluating the message.
+        * 'semipose' is an alias for pose/nospace
+        * 'INFO' from the connection screen gives version info for
+          the MUSH, for use by automated mudlists and the like.
+        * @sitelock/name adds names to the banned names file.
+        * @enable/@disable guests (and new cnf file directive guest_allow)
+        * @decomp now takes /flags and /attribs switches to only decompile
+          the creation/flags information or the attribute information.
+        * @list command partially implemented for TinyMUSH compatibility.
+
+New Flags:
+
+        * CLOUDY flag on exits provides partial transparency. A CLOUDY
+          TRANSPARENT exit shows only the description of the room beyond, 
+          no contents.  A CLOUDY but not TRANSPARENT exit shows only 
+          the contents, no desc.
+        * FORCE_WHITE flag on an ANSI player forces their output to be
+          reset to white, necessary for some poor ansi terminals which
+          "bleed". [Kalkin]
+
+Minor Changes:
+
+        * @chzone'ing an object to 'none' no longer clears its privbits.
+        * OXMOVE attribute is shown to the room you're leaving.
+        * Setting and resetting the SUSPECT flag is now logged in wizard.log
+        * Various outdated defines have been removed from options.h/dune.h
+        * Objects can now use @mail commands, as if they were their owners.
+
+Fixes:
+
+        * idlesecs() now accepts "me".
+        * "pose" (not ":") used to discard everything after an "=".
+        * The "entries" entries in help.txt, etc, have been fixed a bit.
+        * index(a| |b,|,2,1) now returns a null string instead of "|b"
+        * Various memory leaks [1.50-15-01]
+        * When fork fails, a non-forking dump is done, and the MUSH
+          no longer exits. [1.50-15-02]
+        * Soundex() no longer hangs on non-alphabetical. [1.50-15-06]
+        * dist2d and dist3d are safer on non-IEEE math systems. [1.50-15-03]
+        * mail() now counts messages across all folders
+        * Better matching of del_check to add_check [1.50-15-11]
+        * PennMUSH now compiles correctly without EXTENDED_MAIL
+          [1.50-15-10]
+        * Some fixes to unparse.c to stabilize huffman compression
+          [1.50-15-09,RLM]
+        * Fixes to @boot [1.50-15-08,TAP]
+        * Fixes to variable exit handling by quick-unparse [1.50-15-07,AKM]
+        * Configure now tries to find a full path for uptime(1) so that
+          @uptime will work
+        * Fixes to forbidden_site and forbidden_name when there's no
+          sites/lockout/names file. [NJG]
+        * Backspace handling for really dumb terminals improved [NJG]
+        * When changing the text of an attribute, its flags are no longer
+          reset. [RLM]
+        * When seizing a link, coins weren't refunded if the seize failed.
+
+& 1.50p14
+Version 1.50 patchlevel 14              July 3, 1995
+
+This patchlevel is primarily concerned with bugfixes and patch management.
+
+Maintainer changes:
+  * New file 'Patchlevel' tracks mandatory patches to insure that they 
+    are applied in the proper order
+  * Clearer 'restart' script. [TAP]
+  * The indexing script for help/news/etc now creates the topic 'Entries'
+    instead of 'Index', to prevent a conflict with 'help index()'.
+  * The file hdrs/db.h is now hdrs/mushdb.h, to match Tiny 2.2 and
+    because FreeBSD complains about db.h.
+  * The links hdrs/dune.h and hdrs/options.h were unnecessary and
+    are now removed.
+  * The noforking dump messages have been moved to mush.conf
+
+Commands:
+  * The @set obj1=attrib1:_obj2/attrib2 form no longer works, as
+    it conflicts with general attempts to set attributes that start
+    with underscores. Use @cpattr for copying attributes.
+
+Options:
+  * EXITS_CONNECT_ROOMS, if defined, prevents rooms with at least one
+    exit from being announced to players as "disconnected rooms".
+  * RALPH_DEBUG option allows for improved DEBUG flag output. [RLM]
+  * When MEM_CHECK is defined, it dumps mem_check info to the
+    checkpt.log before each dump.
+  * SAFER_PLAYER_NAMES options has been removed. Player names can't
+    contain those funny characters, period.
+
+Fixes:
+  * Added -D_INCLUDE_AES_SOURCE to hpux hints
+  * stdlib.h is now included in eval.c to get atof. This was breaking
+    linux MUSHes badly.
+  * Linux hints now use BSD signal semantics.
+  * Various attempts to fix possibly unusual bugs in compression.
+  * The internal TRANSPARENT flag bit macro is now TRANSPARENTED,
+    because Solaris defines TRANSPARENT
+  * A bug causing setq'd registers to be incorrectly munged has been fixed.
+  * @chzone warning messages refer to zone-lock, not elock, if RALPH_LOCKS
+    are compiled in.
+  * Examining a player who was on a null channel was reported to crash
+    one MUSH. Some attempts have been made to fix that problem.
+  * The compose.csh script in game/txt has been replaced by 
+    compose.sh, which now deals properly with systems which are missing
+    perl (gasp!)
+  * Configure script hopefully won't die when checking signals on Linux.
+  * Fixes to some strange @mail behavior. [RLM]
+  * CType in compress_h.c is now unsigned long, not long. [RLM]
+  * Fix to converting old maildbs to mail subjects [RLM]
+  * Bigram compression could (rarely) cause $commands or ^patterns
+    to stop functioning.
+  * GoodObject() used to consider db_top to be a valid dbref. It's not.
+  * Recycling of objects was broken in pl13 and is now fixed. [RLM]
+  * mush.conf can now deal with the FIXED flag as a default flag [RLM]
+  * A lot of mem_checks weren't being deleted, especially exec.buff ones.
+  * Defining COMPRESSION_TYPE to be 0 (no compression) now works. [TAP]
+  * Paranoid dumps no longer stomp out tabs and newlines unnecessarily.
+  * Configure now checks for IEEE math compliance, and defines HAS_IEEE_MATH
+    which is used by the code to determine if some math functions need
+    to be limited.
+  * SOLARIS defines have been removed as extraneous.
+
+& 1.50p13
+Version 1.50 patchlevel 13              June 1, 1995
+
+Patchlevel 13 was very much a collaborative effort with Ralph Melton
+and T. Alexander Popiel, terrific MUSHhacks.
+
+Major user changes:
+
+  * Complete rewrite of locks, which allows for many, many new
+    locks, including user-defined locks, with reduced memory usage. [RLM]
+  * @mail can now have a subject.
+
+Major maintainer changes:
+  * The code now contains ANSI prototypes (if Configure ascertains that
+    your compiler likes 'em) for easier debugging.
+  * The help, news, and events entries are now managed in subdirectories
+    of game/txt/, and automatically indexed.
+  * The names.conf file now accepts wildcards
+  * New Makefile target 'update' will propagate your options.h/dune.h
+    settings into an options.h.dist/dune.h.dist template, ask you
+    whether you want to define any newly-introduced options, ask
+    you if you want to remove any obsoleted options, and write out
+    new options.h/dune.h files.
+  * Improvements to the autoconfiguration scripts, which now generate
+    a config.h and confmagic.h file in the top-level directory. These
+    headers tell the code what kinds of features are available.
+    Accordingly, the old hdrs/config.h header file has been renamed 
+    hdrs/conf.h
+  * The 'whisper_loudness' config directive in mush.conf sets the
+    probability that a noisy whisper will be noticed in the room. 
+  * If the standard Huffman attribute compression gives you trouble, you
+    can use the COMPRESSION_TYPE define to use the older bigram
+    compression system, now auto-tuning, or no compression at all.
+  * The TINY22_BOOLEANS option causes the MUSH's boolean functions
+    (and(), or(), not()) to be compatible with TinyMUSH 2.2. In Tiny 2.2,
+    only non-zero integers are "true". In PennMUSH's default, non-zero
+    integers, non-negative db#'s, and strings are "true". You pick.
+  * NO_NUKE_SAFE_PLAYER prevents @nuke from working (for Wizards) on
+    players set SAFE. You have to unSAFE them first.
+
+New functions:
+
+  * MWHO() function is like LWHO() but always evaluates as if the
+    enactor were an unprivileged player. Useful for globals.
+  * ISDAYLIGHT() returns 1 if it's daylight savings time, 0 if not.
+    By Dave Sisson
+  * CWHO() now returns a list of dbrefs, NOT NAMES. Much more flexible.
+  * ITER() now can take a fourth argument, which is the output delimiter.
+    You can have any string shoved between the output elements instead 
+    of a space (for example, a %r).
+  * TABLE() for presenting lists in rows.
+
+New commands:
+
+  * Players can connect with 'ch name password', which is just like
+    'cd', but connects hidden. Activated by defining DARK_CONNECT
+  * @warnings now allows players to exclude warnings by using
+    @warnings me=!warning. [RLM]
+  * @ps/quick now displays only the summary line of @ps for players.
+  * @decomp can take a /tf switch, which makes its output emulate
+    the 'FugueEdit' object (originally by van@TinyTIM) which
+    lets you use tf to edit attributes. Idea by Lord Argon.
+
+Minor changes:
+
+  * The 'news code' and 'news changes' entries are now in help.txt,
+    so you don't need to update your MUSH's news.txt files when you upgrade.
+  * In an ambiguous situation (i.e., @dest foo when you carry
+    foobar and foofoo), @dest will no longer pick one at random. It'll fail.
+  * #0 is now evaluated as TRUE in the context of boolean functions,
+    because it's a valid dbref (applies only #ifndef TINY22_BOOLEANS)
+  * haspower() allows players to see powers on things they control,
+    even with HASPOWER_RESTRICTED defined. [RLM]
+  * O-attributes which evaluate to nothing are no longer displayed. [AKM]
+  * Paranoid dumps no longer smash \r and \n.
+  * @mvattr no longer deletes the original attribute if the copies fail.
+  * Secure now stomps out ^'s
+  * The getrandom() function has been rewritten. [RLM]
+  * It's now a lot harder to have Guest and Wiz/Roy set on the same
+    player. Players shouldn't be able to connect to Wiz/Roy Guests anymore.
+  * HARSH_GUEST is now a lot harsher. Guests pretty much can't modify
+    the database except to lock/unlock things they control.
+  * players who are on a priv'd channel may speak/leave
+    even after they lose their privs. [RLM]
+  * Xenix options removed from options.h.dist, since the Xenix code
+    isn't supported anymore anyway.
+  * The size of MAX_COMMAND_LEN (and therefore all derived buffers)
+    has been doubled.
+  * Doing a 'make' from the src/ directory will now call the top-level
+    makefile. This helps those who use emacs (like Ralph, who came up
+    with this) and M-x compile from the srcdir.
+  * @version shows compilation flags.
+  * Admin WHO no longer wraps lines. Sitenames are truncated as needed. [TAP]
+
+Fixes
+
+  * Repaging a player with a multi-word name works correctly now.
+  * Players with the tport_anything power can teleport FIXED things. 
+  * @map works as documented again.
+  * Paranoid @dumps don't give so many spurious bad attribute warnings.
+  * ZMO elock checking now uses PLAYER_START and MASTER_ROOM instead
+    of #0 and #2. [RLM]
+  * @pemit/list now replaces ## with the target's db#, as the help says.
+  * Subtle bug in exit matching fixed. [RLM]
+  * escape() and secure() no longer parse their arguments.
+  * The asterisk-line separators on the nologin messages have been
+    prettified slightly, at Tigger's suggestion
+  * Some systems didn't deal well with overflowing @doing and @poll's.
+    Dave Sisson patched it.
+  * Error in bsd.c when compiling with DARK_CONNECT but without ROYALTY
+    fixed. (Reported by Suud@Gohs)
+  * hasflag, andflags, and orflags patched to prevent mortals from
+    using them to see mdark flags [RLM]
+  * Type mismatch in warnings.c fixed [RLM]
+  * fun_lcstr() and fun_ucstr() have been changed slightly in order to
+    support unices like MachTen which appear to define tolower() as 
+    a macro meaning (isupper(x) ? _tolower(x) : (x)), and were getting
+    hosed by the tolower(*ap++) call. Bug reported by Adrick.
+  * It was possible to overflow the buffers in do_log by having a 
+    Wizard do @fo me=think lnum(5000), for example. No longer, I hope.
+    Bug report and suggested fix by Adrick.
+  * Removed an old bit of code that broke compiles using original mailer
+  * The restart script is a little smarter about preserving databases.
+  * Fixed a bug that caused ALWAYS_PARANOID to dump core.
+
+& 1.50p12
+Version 1.50 patchlevel 12              March 23, 1995
+
+Major changes:
+
+  * The matching routines in match.c have been rewritten to be
+    reentrant, which fixes some subtle but important bugs
+  * New Makefile target 'customize' for setting up customized
+    per-mush subdirectories for those who run multiple mushes.
+  * An untested DELETE_POWERS define in options.h, which will remove
+    powers from a database, to make it easier to switch to TinyMUSH
+
+New flags/powers:
+
+  * Things with the Pemit_All power can @pemit to HAVEN/ulocked players.
+    Useful for globals.
+
+--- Fixes ---
+  
+  * Previously, passing the elock of a ZoneMaster player allowed 
+    control over *all* the ZM's objects, including the ZM player itself.
+    Players are no longer controllable this way.
+  * Incorporated patch for compiling without CREATION_TIMES defined
+  * Incorporated Ralph Melton's patch to warnings.c to fix core dump
+    with multiple exits warning.
+  * Nobody can @tel rooms any more. New code for @tel'ing exits has
+    been written, however, and players may now @tel exits they control
+    from rooms they control to rooms they control.
+  * Z_TEL bug: players could defeat Z_TEL by entering an object and
+    @tel'ing from there. Reported by Ralph Melton.
+  * Bug in puppet checks in @teleport fixed.
+  * Players in exactly 15 levels of container could defeat NO_TEL. Fixed.
+
+
+& 1.50p11
+Version 1.50 patchlevel 11              March 5, 1995
+
+At Amberyl's suggestion, the "dune" numbering scheme has been 
+abandoned. This is 1.50 pl11, and future versions will be numbered
+accordingly.
+
+Major changes:
+
+  * All objects can now have creation dates. Non-player objects have
+    attribute modification dates as well. Players have number of login
+    failures since last logins tracked instead. Supported by
+    ctime() and mtime() functions to return creation/modification time.
+    (CREATION_TIMES define in dune.h)
+  * The extended @mail system now maintains the maildb sorted by
+    recipient, and each player descriptor has a pointer to where that
+    player's mail begins in the maildb. This is much faster for reading 
+    and listing and clearing mail, only slightly slower when sending.
+
+New Commands:
+
+  * @boot/me: boots a player's idle descriptor (to selfboot hung 
+    connections).
+  * whisper has two switches: silent and noisy. Standard PennMUSH whisper
+    is silent, and is the default unless NOISY_WHISPER is defined.
+  * @grep can take two new switches, /ilist and /iprint, which are just
+    like /list and /print, but case-insensitive.
+
+New Flags:
+
+  * LIGHT (as in TinyMUSH): LIGHT objects/players appear in DARK rooms.
+    In addition, LIGHT exits also show up.
+  * Z_TEL: When set on a Zone Master Room or a room in a zone, prevents
+    players from using @tel from that room to any room that's not in the
+    same zone. Useful for puzzle areas or enforcing some IC constraints.
+
+New Functions:
+
+  * The lsearch() command can take a range of db#'s
+  * ctime() and mtime() functions (if CREATION_TIMES is defined)
+  * grepi() is a case-insensitive grep
+  * hasattr() returns 1 if an object has a given attribute
+  * hasattrp() returns 1 if an object or its parent has a given attribute
+
+Minor Changes:
+
+  * @away/@haven/@idle messages are not sent if they're null
+  * Players always receive feedback when they use @hide
+  * Players who are FIXED are now permitted to @tel objects into their
+    inventory. This makes coding puppets which follow you much easier.
+
+--- Fixes ---
+
+  * examine/mortal now functions more like it should. 
+  * If a ZMO was elocked to an object in #2, players couldn't @chzone
+    things to it. Reported by Melkor@Beleriand. Fixed.
+  * WHO is grammatical when reporting # of connected players. Idea
+    from Kalkin.
+  * The Connected ('c') flag is no longer visible to mortals.
+
+
+& 1.50pdune2
+Version 1.50 patchlevel dune2              March 5, 1995
+
+DuneSource pl2 changes
+
+Major changes:
+
+  * Extensive warning system for things missing on objects, rooms, etc.
+    @warnings command for players to set the level of warnings they want,
+    @wcheck[/all] command for players to check an object (checks can also run
+    at a God-configurable interval on the whole db), NOWARN flag for objects
+    and players. Idea from Kalkin's adaptation of MUSE code, totally
+    rewritten.
+    
+
+New options (dune.h):
+
+  * GUEST_TEXTFILE option enables a textfile (guest.txt by default)
+    to be shown to Guests who log in. Idea and source code from Kalkin.
+  * PAGELOCK_NOT_USELOCK option causes @lock/use to fail for players,
+    requiring them to type @lock/page. The lock itself has not changed,
+    just the interface, to remind players of its function. By Ralph Melton.
+  * More control over possessive gets with the POSSESSIVE_GET define
+    and the POSSGET_ON_DISCONNECTED define. Possessive get can be
+    disabled, enabled for all but disconnected players, or enabled
+    at all times (the default PennMUSH behavior)
+
+New functions:
+
+  * lit() returns its input totally unparsed, and without spaces compressed.
+  * t() returns the truthvalue of its input, guarranteed to be either
+    1 or 0.
+  * objmem() and playermem() functions, return the memory usage of
+    an object or all of a player's objects. Requires search_all power.
+  * grab(list,pattern[,delimiter]), returns the first element of list
+    which patches the pattern. Patterns are of the match() type.
+    From the TinyMUSH 2.2 code.
+
+Minor Changes:
+
+  * You must actually own an object (not just control it) or be a
+    Wizard in order to set it chown_ok or dest_ok. By Ralph Melton.
+  * You can use #$ in the actions in a switch() function
+    or @switch/@select statement, and it will be replaced by the switch
+    expression before executing or evaluating. This can improve
+    efficiency and save space. For example:
+        switch(complexfunc(%0),Bob,BobResult,#$)
+    is equivalent to:
+        switch(complexfunc(%0),Bob,BobResult,complexfunc(%0))
+    but only requires a single evaluation of complexfunc(%0).
+    Suggested by Kenric@DuneII.
+  * "things" is a synonym for "objects" in @search now. By Ralph Melton
+
+--- Fixes ---
+
+  * #-2 is treated as a "0" (false, like #-1) in functions which need a
+    logical value for it. Previously, it was treated as -2 (true).
+  * @select is now considerably more efficient - it no longer will 
+    evaluate anything after the matched action. The old behavior
+    could well be a bug in the right conditions as well. 
+  * atr_add now rejects null attribute names, so you probably can't set them
+    any more. Suggested by Kalkin, the code's by Mike@StarWars.
+  * Players can reset a DARK flag on themselves, but still can not
+    set themselves DARK.
+  * andflags(me,!Dc) used to return 1 if I am !dark and !connected,
+    instead of !D and connected, as the help states. Fix by Ralph
+    Melton.
+  * Halting an object which is @wait'ing used to fail to decrement
+    the owner's queue attrib. Fixed now. Patch by Ralph Melton.
+  * set_flag uses strupper instead of upcasestr now, which should fix
+    a bug on some systems wherein "cd" command would crash the MUSH and
+    a similar bug wherein players connecting would crash the MUSH if
+    the ON-VACATION flag was in use.
+  * The old channel syntax (@channel foo=command) works again.
+  * The RULES option now works.
+  * The convtime() has been rewritten to work on NeXT's correctly.
+    Previously, its malloc failed and it returned -1.
+  * Systems which have getrlimit, but *don't* have RLIMIT_NOFILE,
+    are now supported. Notably, Aix 2.x and 3.x.
+  * The installation hints for Solaris 2 have been improved.
+    WAIT_TYPE is defined as int, and if NBPC can't be found for
+    getpagesize(), it'll try PAGESIZE instead. Thanks to Kalkin for these.
+  * Installation for AIX has been improved. AIX required inclusion of
+    sys/select.h in the IDENT stuff.
+  * Various rewrites of tests against NOTHING to use the GoodObject()
+    macro instead. Thanks to Ralph Melton for some of these.
+  * Got rid of some warnings when compiling mkindx
+
+& 1.50pdune1
+Version 1.50 patchlevel dune1              March 5, 1995
+
+DuneSource pl1 changes
+
+Major changes:
+
+  * Whem players attempt to log in to a Guest character that is already
+    in use, the server tries to find one that isn't, and connects the
+    player to that. If it can't, you get the old behavior (two connections
+    to the single Guest).
+  * Extended @mail system is available with many new mail commands.
+  * Dump warnings 5 minutes and 1 minute before non-forking
+    dump, and optional announcement at completion of dump.
+  * Guest players may not set attributes.
+  * Kill command can be disabled.
+  * @aconnect/@adisconnect messages on individual rooms.
+
+Changes to commands:
+
+  * CD command at connection screen allows Wizards to connect to the game
+    DARKly.
+  * @sitelock command for on-the-fly sitelocking by Wizards.
+  * @dump/paranoid can try to fix the db in memory, too.
+  * @decomp/db, produces decomp output using dbref of object instead
+    of its name.
+  * ex/mortal, shows examine output as if player were mortal.
+  * Option to rename @destroy to @recycle, since @dest and @desc are
+    easy to confuse.
+  * @restart command. Combines an @halt with triggering @startup.
+  * @hide now provides feedback when used.
+  * @find may be restricted to royalty, the only ones for whom it might
+    possibly be useful.
+  * @function now lets you know when it updates an @function.
+  * The old (pl8?) @channel <channel>=<function> syntax is back, along
+    with @channel/<function> <channel>, for those who liked it.
+  * @grep can be either case-sensitive or not (the default).
+  * If you don't specify the destination attribute in @cpattr, it
+    assumes you want the same attrib name as the source attrib.
+  * @mvattr, like @cpattr, but removes old attribute.
+  * ANSI players now see their @edit additions in bold.
+  * Rooms and exits can @emit. Rooms @emit into themselves (like @remit),
+    and exits @emit to their source room.
+  * @squota can now be given quota levels as +<quota> or -<quota>, to
+    increase or decrease a player's quota from its current level.
+
+Changes to functions:
+
+  * encrypt() and decrypt() functions.
+  * hidden() function for checking if a player is hidden.
+  * hastype() function for checking if a thing is a given type.
+  * pemit() function sends pemits to a list of objects.
+  * lparent() function, returns object's parent, grandparent, etc.
+  * quota() function, returns a player's quota.
+  * N-dimensional vector arithmetic functions: vadd(), vmul(), vsub(),
+    vmag(), vdim(), vunit()
+  * haspower() can be restricted to admin.
+  * "Lower math" functions: shr(), shl(), inc(), dec()
+  * beep() with no arguments produces a single beep.
+  * pmatch() will now accept *player or #dbref arguments.
+  * lsearchr() function does an lsearch *backwards* through the db.
+
+Changes to flags/powers:
+
+  * Flags can be defined as dark (invisible to all but God), 
+    mdark (invisible to mortals), and odark (invisible to mortals who don't
+    own the object) in flags.c.
+  * @cemit can be granted as a power
+  * The ability to set/unset the DEBUG flag on objects the player controls
+    can be granted as the Can_Debug power. (Idea by Kenric@DuneII)
+  * Optional COLOR flag to control reception of ANSI color apart from
+    ANSI hiliting.
+  * OPAQUE flag on exits. OPAQUE exits in translucent rooms look
+    like normal exits in non-translucent rooms.
+  * FIXED flag prevents @tel and home.
+  * DARK Wizards need not trigger @aenter.
+  * The SUSPECT flag is now abbreviated 's'.
+  * NO_LEAVE flag on objects prevents 'leave' command from working in them.
+    Useful for flying vehicles and the like.
+
+Minor changes:
+
+  * &adestroy attribute triggered when object is dest'd.
+  * Player/room/object names can appear automatically in bold.
+  * Dark and Hidden players are distinguished on the admin WHO list
+  * Dark and Hidden players are indicated on the admin DOING list
+  * Wizards who idle are set hidden, not DARK. Same for royalty.
+  * DARK wizards enter and leave @chat channels silently.
+  * Royalty can now see the Wizard @channel/list
+  * The mortal @channel/list lists only public channels.
+  * @idle/@haven/@away attribs which evaluate to nothing are not
+    sent to the paging player.
+
+--- Fixes ---
+
+  * TRANSPARENT variable exits don't crash the MUSH when looked at.
+  * @channel/delete and @channel/privs take the right number of arguments
+  * @decomp now decompiles flags properly.
+  * When logins are disabled, players may not create characters.
+  * The controls() function is made safer. Defining SAFER_CONTROL prevents
+    anyone but God from controlling God, any non-wizard from controlling
+    anything with a wizbit, and any non-admin from controlling anything with
+    a roybit.
+  * Player names are made safer. Defining SAFER_PLAYER_NAMES prevents the
+    use of [, ], %, and \ in player names.
+  * The strupper() function is made safer.
+  * Mortals can remove the DEBUG flag on objects they own.
+  * The set functions now take delimiters like they should.
+  * Revwords() takes a delimeter, like it should.
+  * @config displays whether function side effects are available correctly.
+    It used to get it backwards.
+  * Some checks against NOTHING have been changed to use the GoodObject()
+    macro in look.c and possibly other places.
+  * It's more difficult for players to enter themselves.
+  * PLAYER_NAME_SPACES works right now, use double-quotes around 
+    multi-word names.
+  * haspower() on unknown powers now returns #-1 NO SUCH POWER instead
+    of a null string.
+
+& 1.50p10
+Version 1.50 patchlevel 10              March 5, 1995
+
+Patchlevel 10 Changes
+
+New additions:
+  - Guest is now a power. Set it on a player to restrict its command set. 
+  - New power Announce, which controls the ability to @wall. 
+  - Global r-registers are now preserved across the queue. 
+  - '#@' now replaces to the list-place for iter() and @dolist. 
+  - Timers now operate on absolute time, rather than game ticks. 
+  - Checks of $commands now obey parent uselocks. 
+  - Variable exit destinations.  When you attempt to go through one of these
+      exits, it evaluates the exit's DESTINATION attribute as a
+      U-function, and uses the result accordingly. 
+  - Rooms can now be set LISTENER. The flag name has been changed to MONITOR
+      (which is what it's called in 2.0), with LISTENER as an alias.
+      (Thus the MONITOR flag on things/rooms and on players mean
+      different things.) 
+  - If the EXTENDED_ANSI option is turned on, it enables the ansi() function,
+      allowing ANSI control codes to be used.  
+  - New wizard command @log can write information to a log file.  
+  - @oemit now takes its target location from the person to exclude, not
+      the person who is doing the @oemit (consistent with 2.0 behavior,
+      and more flexible).  
+  - @ps now follows the 2.0 syntax. Items deleted from the queue are now
+    counted, and the entire thing calls a single generic routine.
+
+Function changes:
+  - More functions take delimiters. The newly-modified ones are:
+      extract(), filter(), first() fold(), iter(), match(), member(), munge(),
+      rest(), revwords(), setdiff(), setinter(), setunion(), shuffle(), 
+      sort(), splice(), wordpos(), words()
+  - If function side effects are configured as enabled, the functions link(),
+      set(), parent(), lock(), clone() and name() can make db changes.
+  - controls() has been tweaked so it returns '#-1 <error message>' on invalid
+      objects, for consistency with other functions and with 2.0.
+  - delete() is now ldelete(). The new delete() function deletes characters
+      from a string. This is a change to be consistent with 2.0.
+  - lcon() is now consistent with the 2.0 convention -- no more partial lists;
+      either you can get the entire list, or you can't get it at all.
+  - locate() has some new parameters: 'c' matches 'carried exits', supported
+      by match_carried_exit(). Exit matching now calls match_exit_internal().
+      'X' allows random choice if the result is ambiguous (#-2).
+  - lsearch() now takes an EVAL class.
+  - objeval() evaluates its first argument.
+  - owner() now can take an object/attribute pair.
+  - sort() autodetects for floating point, and uses qsort.
+  - User-defined functions, such as U(), now return an empty string, rather
+     than #-1 SOME ERROR MESSAGE, because 2.0 behavior is to return an empty
+     string, and, generally, the empty string is easier to handle.
+
+New functions:
+  - New substitution: %qN is equivalent to r(N)  (also twiddled v() slightly)
+  - Findable(). Can <a> locate <b>? Useful for those WHO-list-type globals.
+  - Foreach(). Works basically like MAP(), but on strings.
+  - Haspower(). Like hasflag(), but for powers.
+  - Ports(). Returns the network descriptors a player is connected to.
+  - Rloc(). Returns an object's location at a variable level.
+  - Sortby(). Sorts a list by an arbitrary u-function.
+  - Stripansi(). Strips the ANSI codes from a string.
+
+Important other changes:
+  - Fixed a bug in the checking of ZMO locks in the game's internal controls().
+      The privs parameter to eval_eboolexp() needs to be the ZMO in question,
+      rather than the object itself, in order for attribute locks to work as
+      would seem logical (the object being checked shouldn't even factor into
+      the equation, since by definition zone control is based solely upon
+      the ZMO's enter lock).
+  - Because people seem to want it back, the "pose" command is back, together
+      with a /nospace switch.
+  - TRACE is now an alias for the DEBUG flag.
+  - Player lookups on dbrefs work (i.e, '*#1' is now valid).
+& 1.50p9
+Version 1.50 patchlevel 9
+
+Patchlevel 9 Changes
+
+New additions:
+  - @cemit command emits to a channel.
+  - "Quiet" channels added. They don't show connect/disconnect or joined/left.
+  - '%c' returns the current command.
+  - @dolist/notify queues a '@notify me' after queueing the list commands.
+  - @pemit/list pemits to a list of dbrefs. No more @dolist/@pemits needed!
+
+New functions:
+  - Center(): centers text on a field.
+  - Cwho(): returns the names of players on a channel.
+  - Isdbref(): checks if something's a valid dbref.
+  - Map(): like iter() over a user-defined function.
+  - Mix(): like map(), but takes two lists.
+  - Munge(): combines lists, allowing you to do things like conversion sorts.
+  - Objeval(): evaluates an expression from another object's viewpoint.
+  - Trim(): trims characters at the beginning and/or end of strings.
+Other changes:
+  - Add(), And(), Mul(), and Or() can take up to 100 arguments.
+  - Channel commands are now switch-form.
+  - Debug-flag output is neater and more useful.
+  - Default input line length is 2K.
+  - @grep output format is neater and shorters. 
+  - @link can link already-linked exits.
+  - Locate() can take an 'l' parameter to check object location.
+  - Output line termination is now carriage-return-newline.
+  - Starting quota is now a conf option.
+  - @wipe can take an object-attribute pattern.
+Important bugfixes and modifications:
+  - Controls() now obeys the inherit flag.
+  - Enactor (%#) in idesc evaluates to the player, not to the object.
+  - The escape character is stripped by the parser.
+  - Listening objects trigger enter/leave messages in DARK rooms.
+  - Matching is only done on exits if there is no exact match after trying
+    all other possibilities.
+  - Non-INHERIT things can no longer @parent things which are INHERIT.
+  - Delete(foo%b,1) and Delete(foo,1) now return the same result.
+  - Replace(foo%b,1,Test) and Replace(foo,1,Test) now return the same result.
+  - Taking from an object is now governed by control, as well as ENTER_OK.
+
+& 1.50p8
+Version 1.50 patchlevel 8
+Patchlevel 8 Changes
+Major new features:
+  - Players can now set an @alias.
+New additions:
+  - Player ZONE flag and ZoneMaster control provide "safer" zones.
+  - @hide command hides player from WHO list.
+  - @oemit can take a room dbref, behaving like @remit with an exception.
+  - mortal_dark and wizard attribute flags are settable. 'm' and 'w'.
+  - @pcreate power added.
+  - Channels can be wiped.
+New functions:
+  - Visible(): can one object examine another object or attribute?
+  - Items(), Element(): like Words() and Match(), but for arbitrary separators.
+  - Delete(), Replace(), Insert(): list-manipulation, arbitrary separators.
+  - Orflags(), Andflags(): check multiple flags.
+  - Fullname(): full name of an object.
+  - Many floating point functions.
+Other changes:
+  - Lattr() can take an <obj>/<wildcard> pattern.
+  - @prefix and @inprefix do pronoun substitution.
+  - @search can take an 'eval' parameter.
+  - No second arg to @lock or @link unlocks/unlinks.
+  - Mail() can now, for wizards, give info about other players' mail.
+  - Sort() now 'autodetects' sort type. Nsort() has been removed.
+  - Get_eval() is an alias for U() rather than Eval().
+  - Non-listening objects trigger @aenter and @aleave.
+  - @search, @find, and examine always ignore the MYOPIC flag.
+  - Queue deposits get refunded at startup.
+  - Words() uses ' ' as the delimiter, _not_ any whitespace.
+Major bugfixes:
+  - Many problems with flags have been fixed.
+  - "#123's foo" is no longer matched the same as "#123".
+  - Switch() nesting behaves properly.
+  - Parser bug with '/' in pre-parsed attribute names fixed.
+  - Remove() no longer screws up on things like 'remove(#434 #43 #22,#43)'
+  - Index() and friends no longer screw up on null separators.
+  - Squish() trims leading spaces properly.
+  - Various bugs with setfunctions fixed.
+  - Function recursion bug fixed.
+  - @scan no longer chokes on '='.
+  - no_inherit attribute flag works for real.
+& 1.50p7
+Version 1.50 patchlevel 7
+
+Patchlevel 7 Changes
+Major new features:
+  - Powers system. Individual objects and players can be given the ability
+    to do special things, such as "examine anything", "teleport anywhere",
+    "boot players", etc. The BUILDER and IMMORTAL flags are now powers.
+  - Expanded flag system. Some flags are valid for more types of objects,
+    and flag lookups are quicker. The flag order is now alphabetized,
+    first by "generic" object type, then by specific object type.
+  - User-defined global functions, which behave just like built-in MUSH
+    functions, but are programmed in MUSH in a UFUN() format (i.e. you
+    can call them like any other function, i.e.  '@emit [my_function(foo)]')
+    The "@function" command is used to define and list these functions.
+  - Local variables (registers, numbered 0-9) which are retained for
+    the duration of a single command. Extremely useful for storing long
+    function evaluations, especially if you are programming "switchless".
+    The setq() and r() functions are used to set and retrieve the registers.
+  
+New additions:
+  - "no_clone" attribute flag (do not copy attribute when object is @clone'd)
+  - "@config/functions" lists all functions.
+New functions:
+  - squish() removes leading and trailing spaces from a string, and crunches
+    inter-word spaces down to a single space.
+  - filter() returns members of a list for which a user-def'ed function
+    evalutes to true ("1").
+  - fold() recursively evaluates a user-def'ed function and list.
+  - rjust() and ljust() pad strings with spaces (or another fill character)
+  - nsort() sorts a list of numbers.
+  - shuffle() randomizes order of elements in a list.
+  - scramble() randomizes order of characters in a string.
+  
+Other changes:
+  - cat() can take an arbitrary number of arguments.
+  - conn() and idlesecs() now recognize #dbref and *player. idle() is now
+    an alias for idlesecs().
+  - "Exits:" line in examine is back.
+  - examine on non-existent attribute(s) returns "No matching attributes."
+  - NO_COMMAND doesn't block ^, since you have to set the LISTENER flag anyway.
+  - @config shows a couple more options.
+  - The QUIET flag suppresses "Drained.", "Notified.", and "Halted."
+  - Debug and Verbose output no longer clobber the stack.
+  - switch() and iter() nested within other functions works properly.
+  - Players cannot enter objects remotely via dbref.
+
+& 1.50p6
+Version 1.50 patchlevel 6
+
+Patchlevel 6 Changes
+Major bugfixes:
+  - Eval locks work with get(), ufun(), etc.
+  - Rooms can use @trigger again.
+  
+Changes to the parser:
+  - Spaces are no longer added around equals signs in 'say', 'pose', etc.
+  - The construction '<name of object> <command>' can no longer be used to
+    force a nearby object. '<dbref of object> <command>' still works.
+  - ITER() is much better-behaved; escapes and braces in the second
+    argument are no longer needed. It also now works properly with ufun().
+  - switch() can now take up to 100 arguments. Arguments to it are not
+    evaluated until needed.
+  
+Miscellaneous changes:
+  - Royalty can now set themselves DARK, but this affects _only_ their
+    visibility on the WHO list and related functions; they still appear
+    in the visible contents list of a room, on channel who, etc.
+  - Royalty can set themselves MONITOR.
+  - The TERSE flag suppresses _only_ auto-look messages (so if you type
+    'look here' while TERSE, you will get the description of the room).
+  - VAL() function returns the leading numeric prefix of a string.
+  - INDEX() is an extract()-like function which works for an arbitrary
+    character separator.
+  - WHERE() returns the "true" location of an object.
+  - HOME(), etc. now works on objects you do not control but are VISUAL.
+  - REPEAT() of something zero times returns a blank string, not an error.
+  - Commands done in GOING rooms are no longer considered invalid.
+  - "@parent <object>" by itself unparents an object.
+  - Exits in the same room as you are considered "nearby".
+  - All attribute fetches use the same permission checks. LINK_OK on a
+    zone object no longer allows zfun() to be done on the object if
+    the attribute cannot normally be read.
+  - Attribute flag sets added (@set obj/atr = flag). There are three
+    settable flags, 'visual', 'no_command', and 'no_inherit'. 'Examine'
+    displays these as 'v', '$', and 'i'.
+  - The lcon(), lexits(), con(), exit(), and next() functions now check
+    permissions differently. You can use these functions on a location 
+    that you own, or in a location that you are in and is not DARK.
+    If you cannot check the room, these functions will return #-1 even
+    if you have objects/exits in the room. This behavior is identical
+    to TinyMUSH 2.0's, and provides more privacy.
+  - 'examine' output for objects you don't control is now similar to
+    TinyMUSH 2.0's. The option to examine public attributes by default
+    is configurable.
+
+& patchlevels
+For information on a specific patchlevel of one of the versions listed,
+type 'help <version>p<patchlevel>'. For example, 'help 1.7.2p3'
+
+1.7.7: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+       19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
+1.7.6: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+1.7.5: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
+1.7.4: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+       19, 20
+1.7.3: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
+1.7.2: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+       19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35
+1.7.1: 0, 1, 2, 3
+1.7.0: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+1.6.10: 0, 1, 2, 3, 4, 5, 6
+1.6.9: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
+1.6.8: 0, 1
+1.6.7: 0, 1, 2, 3, 4, 5, 6
+1.6.6: 0
+1.6.5: 0, 1, 2, 3, 4
+1.6.4: 0, 1, 2
+1.6.3: 0, 1, 2, 3, 4, 5, 6, 7
+1.6.2: 0, 1
+1.6.1: 0, 1
+1.6.0: 0, 3, 4, 5
+1.50: dune2, dune1, 6, 7, 8, 9, 10, 11, 12, 13, 14
diff --git a/game/txt/index-files.pl b/game/txt/index-files.pl
new file mode 100644 (file)
index 0000000..6be151e
--- /dev/null
@@ -0,0 +1,139 @@
+#!/usr/bin/perl
+#
+# index-files.pl - make an & index topic for events/news/help
+#
+# Called by compose-tricky
+#
+# Take a MUSH help.txt format file on the stdin, and write a
+# "& entries" entry or entries.
+# Lines with entries to be indexed start with &'s.
+# Write the resulting entries to the stdout, also in help.txt format,
+# in columns and paged.
+# Idea by Schmidt@Dune, perl version by Alan Schwartz (Javelin/Paul)
+# with mods by Jeff Heinen. Modified further by Raevnos.
+#
+# Usage: index-files.pl [options] < news.txt > nws/index.nws
+#
+require 5; # Sorry, Talek.
+use strict;
+use Getopt::Long;
+use locale;
+my (@entries, @aentries);
+
+# Have we got any options?
+my($first,$longest) = ("","");
+exit 1 unless GetOptions ('first' => \$first, 'longest' => \$longest);
+
+# Collect all the topic names
+my @set = ();
+my @aset = ();
+while (<STDIN>) {
+  chomp;
+  next if /^& &?Entries/o; # Skip index entries.
+  if ((@set or @aset) and $_ !~ /&\s+\S/) {
+    # We've got a set of entries now. Choose which to add.
+    if ($first) {
+       # Add the first one
+       push(@entries,$set[0]) if $set[0];
+       push(@aentries,$aset[0]) if $aset[0];
+    }
+    if ($longest) {
+       # Add the longest one
+       @set = sort { length($b) <=> length($a) } @set;
+       @aset = sort { length($b) <=> length($a) } @aset;
+       push(@entries,$set[0]) if $set[0];
+       push(@aentries,$aset[0]) if $aset[0];
+    }
+    if (!$first and !$longest) {
+       # Add all
+       push(@entries,@set) if @set;
+       push(@aentries,@aset) if @aset;
+    }
+    @set = (); @aset = ();
+  }
+  push @aset,$1 if /^&\s+(&.*)/o; # Catch admin-help entries
+  push @set, $1 if /^&\s+([^&].*)/o; # Catch normal entries.
+}
+# If anything's left in @set/@aset now, someone's screwed up
+
+# Make 'em all lower-case and sort 'em.
+@entries = map { lc $_ } @entries;
+@aentries = map { lc $_ } @aentries;
+
+sub withnumchecking;
+my @sorted = ($#entries > 0) ? (sort withnumchecking @entries) : @entries;
+my @asorted = ($#entries > 0) ? (sort withnumchecking @aentries) : @aentries;
+
+my $maxlines = 14;
+my $maxlen = 25;
+my $separator ="-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n";
+my $three_items = " %-25.25s %-25.25s %-25.25s\n"; # Format for three entries
+my $bigone = " %-51.51s %-25.25s\n"; # Format for two entries, long first.
+my $bigtwo = " %-25.25s %-51.51s\n"; # Format for two entries, long second.
+my $admin = 0;
+my $index;
+
+foreach $index (\@sorted, \@asorted) { 
+  my $i = 0;
+  my $title = $admin ? "&Entries" : "Entries";
+  $admin++;
+  my $titlecount = 1;
+  my $header = 0;
+  my ($entry1, $entry2, $entry3);
+  print "\n& $title\n", $separator;
+  while (@$index) {
+    if (${$index}[0] eq "help") {
+       shift @$index;
+       next;
+     }
+    if ($header) {
+      print "& $title-$titlecount\n", $separator;
+      $header = 0;
+    }
+    $entry1 = shift @$index;
+    $entry2 = shift @$index;
+    $entry2 = "" if !defined $entry2;
+    if (length($entry1) > $maxlen) {
+      if (length($entry2) > $maxlen) {
+        printf " %-76.76s\n", $entry1;
+        unshift @$index, $entry2;
+      } else {
+        printf $bigone, $entry1, $entry2;
+      }
+    } else {
+      if (length($entry2) > $maxlen) {
+        printf $bigtwo, $entry1, $entry2;
+      } else {
+        $entry3 = shift @$index;
+        $entry3 = "" if !defined $entry3;
+        if (length($entry3) > $maxlen) {
+          unshift @$index, $entry3;
+          $entry3 ="";
+        }
+        printf $three_items, $entry1, $entry2, $entry3;
+      }
+    }
+   if ($i++ > $maxlines)  {
+     $titlecount++;
+     print "\nFor more, see $title-$titlecount\n", $separator;
+     $header = 1;
+     $i = 0;
+   }
+ }
+}
+print $separator;
+
+sub withnumchecking {
+  my($firsta, $firstb) = ($a,$b);
+  my($lasta, $lastb);
+  ($firsta, $lasta) = ($1,$2) if $a =~ /(.+)p(\d+)/o;
+  ($firstb, $lastb) = ($1,$2) if $b =~ /(.+)p(\d+)/o;
+  my $res = $firsta cmp $firstb;
+  return $res if $res;
+  if (!defined($lasta)) {
+    warn "Possible duplicate help topic: $a\n";
+    return $res;
+  }
+  return $lasta <=> $lastb;
+}
+
diff --git a/game/txt/motd.txt b/game/txt/motd.txt
new file mode 100644 (file)
index 0000000..e6e176b
--- /dev/null
@@ -0,0 +1,4 @@
+Welcome to CobraMUSH!
+--------------------------------------------------------------------------
+Yell at your god to personalize this file
+
diff --git a/game/txt/newuser.txt b/game/txt/newuser.txt
new file mode 100644 (file)
index 0000000..eb4c66b
--- /dev/null
@@ -0,0 +1,13 @@
+-------------------------------------------------------------------------
+Congratulations on your newly created character. CobraMUSH welcomes you.
+For more information about this game, type 'news' or 'help' to get help.
+
+Please read news for updates on program changes and other things that
+affect your life here at CobraMUSH.  If you ask for help on a topic that is
+in the help, you will be told to read help and news first to see if you can
+figure out what you might be doing.  If you then still need help, the wizards
+will be more than happy to assist you.
+
+Yell at your god to personalize this file!
+-------------------------------------------------------------------------
+
diff --git a/game/txt/nws/.gitify-empty b/game/txt/nws/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/game/txt/nws/base.nws b/game/txt/nws/base.nws
new file mode 100644 (file)
index 0000000..d5c4cd0
--- /dev/null
@@ -0,0 +1,3 @@
+& help
+CobraMUSH News Facility
+-----------------------------
diff --git a/game/txt/quit.txt b/game/txt/quit.txt
new file mode 100644 (file)
index 0000000..951d42e
--- /dev/null
@@ -0,0 +1,6 @@
+Thank you for visiting.
+
+Please return soon.
+
+*********** D I S C O N N E C T E D ***********
+
diff --git a/game/txt/register.txt b/game/txt/register.txt
new file mode 100644 (file)
index 0000000..0d93a40
--- /dev/null
@@ -0,0 +1,6 @@
+This message is shown when a user tries to create a player or register
+a player from a site that doesn't allow creation or registration,
+respectively, or if creation/registration is disabled entirely.
+Alas. You'll probably have to send email to the person who runs this
+MUSH if you want a character.
+
diff --git a/hdrs/.gitify-empty b/hdrs/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/hdrs/access.h b/hdrs/access.h
new file mode 100644 (file)
index 0000000..11637ec
--- /dev/null
@@ -0,0 +1,61 @@
+#ifndef __ACCESS_H
+#define __ACCESS_H
+
+/** Access information for a host-pattern.
+ * This structure holds access information for a given host-pattern.
+ * It's organized into a linked list of access rules.
+ */
+struct access {
+  char host[BUFFER_LEN];       /**< The host pattern */
+  char comment[BUFFER_LEN];    /**< A comment about the rule */
+  dbref who;                   /**< Who created this rule if sitelock used */
+  int can;                     /**< Bitflags of what the host can do */
+  int cant;                    /**< Bitflags of what the host can't do */
+  struct access *next;         /**< Pointer to next rule in the list */
+};
+
+
+/* These flags are can/can't - a site may or may not be allowed to do them */
+#define ACS_CONNECT     0x1    /* Connect to non-guests */
+#define ACS_CREATE      0x2    /* Create new players */
+#define ACS_GUEST       0x4    /* Connect to guests */
+#define ACS_REGISTER    0x8    /* Site can use the 'register' command */
+/* These flags are set in the 'can' bit, but they mark special processing */
+#define ACS_SITELOCK    0x10   /* Marker for where to insert @sitelock */
+#define ACS_SUSPECT     0x20   /* All players from this site get SUSPECT */
+#define ACS_DENY_SILENT 0x40   /* Don't log failed attempts */
+#define ACS_REGEXP      0x80   /* Treat the host pattern as a regexp */
+
+#define ACS_GOD         0x100  /* God can connect from this site */
+#define ACS_DIRECTOR    0x200  /* Directors can connect from this site */
+#define ACS_ADMIN       0x400  /* Admins can connect from this site */
+
+/* This is the usual default access */
+#define ACS_DEFAULT             (ACS_CONNECT|ACS_CREATE|ACS_GUEST)
+
+/* Usefile macros */
+
+#define Site_Can_Connect(hname, who)  site_can_access(hname,ACS_CONNECT, who)
+#define Site_Can_Create(hname)  site_can_access(hname,ACS_CREATE, AMBIGUOUS)
+#define Site_Can_Guest(hname)  site_can_access(hname,ACS_GUEST, AMBIGUOUS)
+#define Site_Can_Register(hname)  site_can_access(hname,ACS_REGISTER, AMBIGUOUS)
+#define Deny_Silent_Site(hname, who) site_can_access(hname,ACS_DENY_SILENT, who)
+#define Suspect_Site(hname, who)  site_can_access(hname,ACS_SUSPECT, who)
+#define Forbidden_Site(hname)  (!site_can_access(hname,ACS_DEFAULT, AMBIGUOUS))
+
+/* Public functions */
+int read_access_file(void);
+void write_access_file(void);
+int site_can_access(const char *hname, int flag, dbref who);
+struct access *site_check_access(const char *hname, dbref who, int *rulenum);
+int format_access(struct access *ap, int rulenum,
+                 dbref who
+                 __attribute__ ((__unused__)), char *buff, char **bp);
+int add_access_sitelock(dbref player, const char *host, dbref who, int can,
+                       int cant);
+int remove_access_sitelock(const char *pattern);
+void do_list_access(dbref player);
+int parse_access_options
+  (const char *opts, dbref *who, int *can, int *cant, dbref player);
+
+#endif                         /* __ACCESS_H */
diff --git a/hdrs/ansi.h b/hdrs/ansi.h
new file mode 100644 (file)
index 0000000..848e859
--- /dev/null
@@ -0,0 +1,69 @@
+/* ansi.h */
+
+/* ANSI control codes for various neat-o terminal effects
+
+ * Some older versions of Ultrix don't appear to be able to
+ * handle these escape sequences. If lowercase 'a's are being
+ * stripped from @doings, and/or the output of the ANSI flag
+ * is screwed up, you have the Ultrix problem.
+ *
+ * To fix the ANSI problem, try replacing the '\x1B' with '\033'.
+ * To fix the problem with 'a's, replace all occurrences of '\a'
+ * in the code with '\07'.
+ *
+ */
+
+#ifndef __ANSI_H
+#define __ANSI_H
+
+#define ANSI_BLACK_V    (30)
+#define ANSI_RED_V      (31)
+#define ANSI_GREEN_V    (32)
+#define ANSI_YELLOW_V   (33)
+#define ANSI_BLUE_V     (34)
+#define ANSI_MAGENTA_V  (35)
+#define ANSI_CYAN_V     (36)
+#define ANSI_WHITE_V    (37)
+
+#define BEEP_CHAR     '\a'
+#define ESC_CHAR      '\x1B'
+
+#define ANSI_BEGIN   "\x1B["
+
+#define ANSI_NORMAL   "\x1B[0m"
+
+#define ANSI_HILITE   "\x1B[1m"
+#define ANSI_INVERSE  "\x1B[7m"
+#define ANSI_BLINK    "\x1B[5m"
+#define ANSI_UNDERSCORE "\x1B[4m"
+
+#define ANSI_INV_BLINK         "\x1B[7;5m"
+#define ANSI_INV_HILITE        "\x1B[1;7m"
+#define ANSI_BLINK_HILITE      "\x1B[1;5m"
+#define ANSI_INV_BLINK_HILITE  "\x1B[1;5;7m"
+
+/* Foreground colors */
+
+#define ANSI_BLACK      "\x1B[30m"
+#define ANSI_RED        "\x1B[31m"
+#define ANSI_GREEN      "\x1B[32m"
+#define ANSI_YELLOW     "\x1B[33m"
+#define ANSI_BLUE       "\x1B[34m"
+#define ANSI_MAGENTA    "\x1B[35m"
+#define ANSI_CYAN       "\x1B[36m"
+#define ANSI_WHITE      "\x1B[37m"
+
+/* Background colors */
+
+#define ANSI_BBLACK     "\x1B[40m"
+#define ANSI_BRED       "\x1B[41m"
+#define ANSI_BGREEN     "\x1B[42m"
+#define ANSI_BYELLOW    "\x1B[43m"
+#define ANSI_BBLUE      "\x1B[44m"
+#define ANSI_BMAGENTA   "\x1B[45m"
+#define ANSI_BCYAN      "\x1B[46m"
+#define ANSI_BWHITE     "\x1B[47m"
+
+#define ANSI_END        "m"
+
+#endif                         /* __ANSI_H */
diff --git a/hdrs/atr_tab.h b/hdrs/atr_tab.h
new file mode 100644 (file)
index 0000000..66c8cba
--- /dev/null
@@ -0,0 +1,260 @@
+/* atr_tab.h */
+
+#ifndef __ATR_TAB_H
+#define __ATR_TAB_H
+
+#include "attrib.h"
+
+/* attribute list */
+ATTR attr[] = {
+  {"AAHEAR", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ACLONE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ACONNECT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ADESCRIBE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ADESTROY", AF_PRIVILEGE | AF_PRIVATE | AF_NOCOPY | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ADISCONNECT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ADROP", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AEFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AFAILURE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AFOLLOW", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AGIVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AHEAR", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AIDESCRIBE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ALEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ALFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ALIAS", AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+#ifdef USE_MAILER
+  {"AMAIL", AF_PRIVILEGE | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+#endif
+  {"AMHEAR", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AMOVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ANAME", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"APAYMENT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ARECEIVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ASDIN", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ASDOUT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ASUCCESS", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ATPORT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AUFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AUNFOLLOW", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AUNIDLE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL}, 
+  {"AUSE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AWAY", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AZENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"AZLEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"CHARGES", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"COMMENT", AF_NOPROG | AF_MDARK | AF_PRIVILEGE | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"CONFORMAT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"COST", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"DAILY", AF_NOPROG, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"DEBUGFORWARDLIST", AF_NOPROG | AF_PRIVATE | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"DESCFORMAT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"DESCRIBE", AF_PUBLIC | AF_NEARBY | AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"DROP", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"EALIAS", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"EFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"EXITFORMAT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"EXITTO", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"FAILURE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"FILTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"FOLLOW", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+
+  {"FOLLOWING", AF_NOPROG | AF_PRIVATE | AF_PRIVILEGE | AF_NOCOPY,
+   NULL_CHUNK_REFERENCE, 0 | AF_PREFIXMATCH, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"FOLLOWERS", AF_NOPROG | AF_PRIVATE | AF_PRIVILEGE | AF_NOCOPY,
+   NULL_CHUNK_REFERENCE, 0 | AF_PREFIXMATCH, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"FORWARDLIST", AF_NOPROG | AF_PRIVATE | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"GIVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"HAVEN", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"HOURLY", AF_NOPROG, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"IDESCFORMAT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"IDESCRIBE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"IDLE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"IDLE_TIMEOUT", AF_NOPROG | AF_MDARK | AF_PRIVILEGE, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"INFILTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"INPREFIX", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"LALIAS", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"LAST", AF_PRIVILEGE | AF_VISUAL | AF_LOCKED | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"LASTACTIVITY",  AF_PRIVILEGE | AF_LOCKED | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"LASTIP", AF_PRIVILEGE | AF_LOCKED | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0,
+    TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"LASTFAILED", AF_PRIVILEGE | AF_LOCKED | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"LASTLOGOUT", AF_PRIVILEGE | AF_LOCKED | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"LASTPAGED", AF_PRIVILEGE | AF_LOCKED | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"LASTSITE", AF_PRIVILEGE | AF_LOCKED | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"LEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"LFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"LISTEN", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+#ifdef USE_MAILER
+  {"MAILCURF", AF_PRIVILEGE | AF_NOPROG | AF_LOCKED | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"MAILFILTERS", AF_PRIVILEGE | AF_NOPROG | AF_LOCKED | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"MAILFOLDERS", AF_PRIVILEGE | AF_NOPROG | AF_LOCKED | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"MAILSIGNATURE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+#endif /* USE_MAILER */
+  {"MOVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"NAMEACCENT", AF_NOPROG | AF_VISUAL | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"NAMEFORMAT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ODESCRIBE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ODROP", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OEFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OFAILURE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OFOLLOW", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OGIVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OIDESCRIBE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OLEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OLFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OMOVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ONAME", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OPAYMENT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ORECEIVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OXMOVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OSUCCESS", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OTPORT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OUFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OUNFOLLOW", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OUSE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OXENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OXLEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OXTPORT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OZENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"OZLEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"PAYMENT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"PRICELIST", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"PREFIX", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"RECEIVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"QUEUE", AF_PRIVATE | AF_NOCOPY | AF_NODUMP,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"REGISTERED_EMAIL",
+   AF_LOCKED | AF_PRIVATE | AF_NOCOPY, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"RQUOTA", AF_MDARK | AF_LOCKED, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"RUNOUT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"SEMAPHORE", AF_LOCKED | AF_PRIVATE | AF_NOCOPY | AF_NODUMP,
+   NULL_CHUNK_REFERENCE,
+   0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"SEX", AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP,
+   NULL},
+  {"STARTUP", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"SUCCESS", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"TFPREFIX", AF_NOPROG | AF_NOCOPY | AF_PRIVATE | AF_PREFIXMATCH,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"TPORT", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"UFAIL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"UNFOLLOW", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"USE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VA", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VB", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VC", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VD", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VE", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VF", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VG", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VH", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VI", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VJ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VK", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VL", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VM", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VN", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VO", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VP", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VQ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VR", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VRML_URL", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VS", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VT", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VU", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VV", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VW", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VX", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VY", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"VZ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WA", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WB", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WC", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WD", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WE", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WF", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WG", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WH", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WI", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WJ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WK", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WL", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WM", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WN", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WO", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WP", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WQ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WR", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WS", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WT", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WU", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WV", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WW", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WX", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WY", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"WZ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XA", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XB", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XC", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XD", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XE", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XF", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XG", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XH", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XI", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XJ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XK", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XL", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XM", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XN", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XO", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XP", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XQ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XR", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XS", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XT", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XU", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XV", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XW", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XX", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XY", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XZ", 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XYXXY", AF_INTERNAL | AF_NOPROG | AF_LOCKED,
+   NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XY_PROGINFO", AF_INTERNAL | AF_NOPROG | AF_LOCKED, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XY_PROGPROMPT", AF_INTERNAL | AF_NOPROG | AF_LOCKED, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XY_PROGENV", AF_INTERNAL | AF_NOPROG | AF_LOCKED, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"XYXX_DIVRCD", AF_INTERNAL | AF_NOPROG | AF_LOCKED, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ZENTER", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {"ZLEAVE", AF_NOPROG | AF_PREFIXMATCH, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL},
+  {NULL, 0, NULL_CHUNK_REFERENCE, 0, TRUE_BOOLEXP, TRUE_BOOLEXP, NULL}
+};
+
+#endif                         /* __ATR_TAB_H */
diff --git a/hdrs/attrib.h b/hdrs/attrib.h
new file mode 100644 (file)
index 0000000..8d4c693
--- /dev/null
@@ -0,0 +1,133 @@
+#ifndef _ATTRIB_H
+#define _ATTRIB_H
+
+#include "mushtype.h"
+#include "boolexp.h"
+#include "chunk.h"
+#include "command.h"
+
+/** An attribute on an object.
+ * This structure represents an attribute set on an object.
+ * Attributes form a linked list on an object, sorted alphabetically.
+ */
+
+struct attr {
+  char const *name;            /**< Name of attribute */
+  int flags;                   /**< Attribute flags */
+  chunk_reference_t data;      /**< The attribute's value, compressed */
+  dbref creator;               /**< The attribute's creator's dbref */
+  boolexp write_lock;          /**< Attribute lock set */
+  boolexp read_lock;           /**< Attribute read lock */
+  ATTR *next;                  /**< Pointer to next attribute in list */
+};
+
+struct aget_oi {
+       ATTR *attribute_part;
+       dbref found_on;
+};
+
+
+/* Stuff that's actually in atr_tab.c */
+extern ATTR *aname_hash_lookup(const char *name);
+extern int alias_attribute(const char *atr, const char *alias);
+extern void do_attribute_access
+  (dbref player, char *name, char *perms, int retroactive);
+extern void do_attribute_delete(dbref player, char *name, char def);
+extern void do_attribute_rename(dbref player, char *old, char *newname);
+extern void do_attribute_info(dbref player, char *name);
+extern void do_list_attribs(dbref player, int lc);
+extern char *list_attribs(void);
+extern void attr_init_postconfig(void);
+extern void do_attribute_lock(dbref player, char *name, char *lock, switch_mask swi);
+
+/* From attrib.c */
+extern int good_atr_name(char const *s);
+extern ATTR *atr_match(char const *string);
+extern ATTR *atr_sub_branch(ATTR *branch);
+extern void atr_new_add(dbref thing, char const *RESTRICT atr,
+                       char const *RESTRICT s, dbref player, int flags,
+                       unsigned char derefs, boolexp wlock, boolexp rlock);
+extern int atr_add(dbref thing, char const *RESTRICT atr,
+                  char const *RESTRICT s, dbref player, int flags);
+extern int atr_clr(dbref thing, char const *atr, dbref player);
+extern ATTR *atr_get(dbref thing, char const *atr);
+extern ATTR *atr_get_noparent(dbref thing, char const *atr);
+typedef int (*aig_func) (dbref, dbref, dbref, const char *, ATTR *, void *);
+extern int atr_iter_get(dbref player, dbref thing, char const *name,
+                       int mortal, aig_func func, void *args);
+extern ATTR *atr_complete_match(dbref player, char const *atr, dbref privs);
+extern void atr_free(dbref thing);
+extern void atr_cpy(dbref dest, dbref source);
+extern char const *convert_atr(int oldatr);
+extern int atr_comm_match(dbref thing, dbref player, int type, int end,
+                         char const *str, int just_match, char *atrname,
+                         char **abp, dbref *errobj);
+extern int atr_comm_divmatch(dbref thing, dbref player, int type, int end,
+                            char const *str, int just_match, char *atrname,
+                            char **abp, dbref *errobj);
+extern int one_comm_match(dbref thing, dbref player, const char *atr,
+                         const char *str);
+extern int do_set_atr(dbref thing, char const *RESTRICT atr,
+                     char const *RESTRICT s, dbref player, int flags);
+extern void do_atrlock(dbref player, char const *arg1, char const *arg2, char write_lock);
+extern void do_atrchown(dbref player, char const *arg1, char const *arg2);
+extern int string_to_atrflag(dbref player, const char *p);
+extern const char *atrflag_to_string(int mask);
+extern void init_atr_name_tree(void);
+
+extern int can_read_attr_internal(dbref player, dbref obj, ATTR *attr);
+extern int can_write_attr_internal(dbref player, dbref obj, ATTR *attr,
+                                  int safe);
+extern unsigned const char *atr_get_compressed_data(ATTR *atr);
+extern char *atr_value(ATTR *atr);
+extern char *
+safe_atr_value(ATTR *atr)
+  __attribute_malloc__;
+
+
+/* possible attribute flags */
+#define AF_ODARK        0x1    /* OBSOLETE! Leave here but don't use */
+#define AF_INTERNAL     0x2    /* no one can see it or set it */
+#define AF_PRIVILEGE    0x4    /* Only privileged players can change it */
+#define AF_NUKED        0x8    /* OBSOLETE! Leave here but don't use */
+#define AF_LOCKED       0x10   /* Only creator of attrib can change it. */
+#define AF_NOPROG       0x20   /* won't be searched for $ commands. */
+#define AF_MDARK        0x40   /* Only admins can see it */
+#define AF_PRIVATE      0x80   /* Children don't inherit it */
+#define AF_NOCOPY       0x100  /* atr_cpy (for @clone) doesn't copy it */
+#define AF_VISUAL       0x200  /* Everyone can see this attribute */
+#define AF_REGEXP       0x400  /* Match $/^ patterns using regexps */
+#define AF_CASE         0x800  /* Match $/^ patterns case-sensitive */
+#define AF_SAFE         0x1000 /* This attribute may not be modified */
+#define AF_STATIC       0x10000        /* OBSOLETE! Leave here but don't use */
+#define AF_COMMAND      0x20000        /* INTERNAL: value starts with $ */
+#define AF_LISTEN       0x40000        /* INTERNAL: value starts with ^ */
+#define AF_NODUMP       0x80000        /* INTERNAL: attribute is not saved */
+#define AF_LISTED       0x100000       /* INTERNAL: Used in @list attribs */
+#define AF_PREFIXMATCH  0x200000       /* Subject to prefix-matching */
+#define AF_VEILED       0x400000       /* On ex, show presence, not value */
+#define AF_DEBUG       0x800000  /* Show debug when evaluated */
+#define AF_NEARBY      0x1000000 /* Override AF_VISUAL if remote */
+#define AF_PUBLIC      0x2000000 /* Override SAFER_UFUN */
+#define AF_ANON                0x4000000 /* INTERNAL: Attribute doesn't exist in the database */
+#define AF_POWINHERIT  0x8000000       /* Execute with powers of object it's on */
+#define AF_MHEAR       0x20000000    /* ^-listens can be triggered by %! */
+#define AF_AHEAR       0x40000000    /* ^-listens can be triggered by anyone */
+
+/* external predefined attributes. */
+    extern ATTR attr[];
+
+/* external @wipe indicator (changes atr_clr() behaviour) */
+    extern int we_are_wiping;
+
+#define AL_ATTR(alist)          (alist)
+#define AL_NAME(alist)          ((alist)->name)
+#define AL_STR(alist)           (atr_get_compressed_data((alist)))
+#define AL_NEXT(alist)          ((alist)->next)
+#define AL_CREATOR(alist)       ((alist)->creator)
+#define AL_FLAGS(alist)         ((alist)->flags)
+#define AL_DEREFS(alist)        ((alist)->data?chunk_derefs((alist)->data):0)
+#define AL_WLock(alist)                ((alist)->write_lock)
+#define AL_RLock(alist)                ((alist)->read_lock)
+
+#endif                         /* __ATTRIB_H */
diff --git a/hdrs/boolexp.h b/hdrs/boolexp.h
new file mode 100644 (file)
index 0000000..8eb32de
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef BOOLEXP_H
+#define BOOLEXP_H
+#include "copyrite.h"
+#include "chunk.h"
+
+typedef chunk_reference_t boolexp;
+
+/* tokens for locks */
+#define NOT_TOKEN '!'
+#define AND_TOKEN '&'
+#define OR_TOKEN '|'
+#define AT_TOKEN '@'
+#define IN_TOKEN '+'
+#define IS_TOKEN '='
+#define OWNER_TOKEN '$'
+
+enum { TRUE_BOOLEXP = NULL_CHUNK_REFERENCE };
+
+/* From boolexp.c */
+extern boolexp dup_bool(boolexp b);
+extern int sizeof_boolexp(boolexp b);
+extern int eval_boolexp(dbref player, boolexp b, dbref target, unsigned char *switches);
+extern boolexp parse_boolexp(dbref player, const char *buf, lock_type ltype);
+extern boolexp parse_boolexp_d(dbref player, const char *buf, lock_type ltype,
+                              int derefs);
+extern void free_boolexp(boolexp b);
+boolexp getboolexp(FILE * f, const char *ltype);
+void putboolexp(FILE * f, boolexp b);
+enum u_b_f {
+  UB_ALL, /**< Use names of objects */
+  UB_DBREF, /**< Use dbrefs */
+  UB_MEREF /**< Use dbrefs or "me" if the object is the player arg
+              from unparse_boolexp.() For @decompile. */
+};
+extern char *unparse_boolexp(dbref player, boolexp b, enum u_b_f flag);
+#endif                         /* BOOLEXP_H */
diff --git a/hdrs/bufferq.h b/hdrs/bufferq.h
new file mode 100644 (file)
index 0000000..7c1e6ce
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef _BUFFERQ_H_
+#define _BUFFERQ_H_
+/**
+ * \file bufferq.h
+ *
+ * \brief Headers for managing queues of buffers, a handy data structure.
+ *
+ *
+ */
+
+
+#define BUFFERQLINEOVERHEAD     (2*sizeof(int)+sizeof(time_t)+sizeof(dbref))
+
+typedef struct bufferq BUFFERQ;
+
+struct bufferq {
+  char *buffer;                /**< Pointer to start of buffer */
+  char *buffer_end;    /**< Pointer to insertion point in buffer */
+  int buffer_size;     /**< Size allocated to buffer, in bytes */
+  int num_buffered;    /**< Number of strings in the buffer */
+  char last_string[BUFFER_LEN];        /**< Cache of last string inserted */
+  char last_type;      /**< Cache of type of last string inserted */
+};
+
+#define BufferQSize(b) ((b)->buffer_size)
+#define BufferQNum(b) ((b)->num_buffered)
+#define BufferQLast(b) ((b)->last_string)
+#define BufferQLastType(b) ((b)->last_type)
+
+extern BUFFERQ *allocate_bufferq(int lines);
+extern BUFFERQ *reallocate_bufferq(BUFFERQ * bq, int lines);
+extern void free_bufferq(BUFFERQ * bq);
+extern void add_to_bufferq(BUFFERQ * bq, int type, dbref player,
+                          const char *msg);
+extern char *iter_bufferq(BUFFERQ * bq, char **p, dbref *player, int *type,
+                         time_t * timestamp);
+extern int bufferq_lines(BUFFERQ * bq);
+extern int isempty_bufferq(BUFFERQ * bq);
+#endif
diff --git a/hdrs/case.h b/hdrs/case.h
new file mode 100644 (file)
index 0000000..e995c2c
--- /dev/null
@@ -0,0 +1,13 @@
+#ifndef CASE_H
+#define CASE_H
+#include <ctype.h>
+#include "config.h"
+
+#ifdef HAS_SAFE_TOUPPER
+#define DOWNCASE(x)     tolower((unsigned char)x)
+#define UPCASE(x)       toupper((unsigned char)x)
+#else
+#define DOWNCASE(x) (isupper((unsigned char)x) ? tolower((unsigned char)x) : (x))
+#define UPCASE(x)   (islower((unsigned char)x) ? toupper((unsigned char)x) : (x))
+#endif
+#endif                         /* CASE_H */
diff --git a/hdrs/chunk.h b/hdrs/chunk.h
new file mode 100644 (file)
index 0000000..25fa8cf
--- /dev/null
@@ -0,0 +1,35 @@
+/* This must be first, otherwise dbref will be undefined */
+#include "mushtype.h"
+#ifndef _CHUNK_H_
+#define _CHUNK_H_
+
+#undef LOG_CHUNK_STATS
+
+typedef unsigned short u_int_16;
+typedef unsigned int u_int_32;
+
+typedef u_int_32 chunk_reference_t;
+#define NULL_CHUNK_REFERENCE 0
+
+chunk_reference_t chunk_create(unsigned char const *data, u_int_16 len,
+                              unsigned char derefs);
+void chunk_delete(chunk_reference_t reference);
+u_int_16 chunk_fetch(chunk_reference_t reference,
+                    unsigned char *buffer, u_int_16 buffer_len);
+u_int_16 chunk_len(chunk_reference_t reference);
+unsigned char chunk_derefs(chunk_reference_t reference);
+void chunk_migration(int count, chunk_reference_t ** references);
+int chunk_num_swapped(void);
+void chunk_init(void);
+enum chunk_stats_type { CSTATS_SUMMARY, CSTATS_REGIONG, CSTATS_PAGINGG,
+  CSTATS_FREESPACEG, CSTATS_REGION, CSTATS_PAGING
+};
+void chunk_stats(dbref player, enum chunk_stats_type which);
+void chunk_new_period(void);
+
+int chunk_fork_file(void);
+void chunk_fork_parent(void);
+void chunk_fork_child(void);
+void chunk_fork_done(void);
+
+#endif                         /* _CHUNK_H_ */
diff --git a/hdrs/command.h b/hdrs/command.h
new file mode 100644 (file)
index 0000000..643a205
--- /dev/null
@@ -0,0 +1,229 @@
+#ifndef __COMMAND_H
+#define __COMMAND_H
+
+#include "boolexp.h"
+#include "division.h"
+
+typedef unsigned char switch_mask[NUM_SWITCH_BYTES];
+#define SW_SET(m,n)     (m[(n) >> 3] |= (1 << ((n) & 0x7)))
+#define SW_CLR(m,n)     (m[(n) >> 3] &= ~(1 << ((n) & 0x7)))
+#define SW_ISSET(m,n)   (m[(n) >> 3] & (1 << ((n) & 0x7)))
+#define SW_ZERO(m)      memset(m, 0, NUM_SWITCH_BYTES)
+
+/* These are type restrictors */
+#define CMD_T_ROOM      0x80000000
+#define CMD_T_THING     0x40000000
+#define CMD_T_EXIT      0x20000000
+#define CMD_T_PLAYER    0x10000000
+#define CMD_T_ANY       0xF4000000
+#define CMD_T_GOD       0x08000000
+#define CMD_T_DIVISION 0x04000000
+
+/* Any unknown or undefined switches will be passed in switches, instead of causing error */
+#define CMD_T_SWITCHES  0x02000000
+
+/* Command is disabled, set with @command */
+#define CMD_T_DISABLED  0x01000000
+
+/* Command will fail if object is gagged */
+#define CMD_T_NOGAGGED  0x00800000
+
+/* Command will fail if object is a guest */
+#define CMD_T_NOGUEST   0x00400000
+
+
+/* Command will fail if object is fixed */
+#define CMD_T_NOFIXED   0x00200000
+
+#define CMD_T_NORPMODE 0x00002000
+
+/* INTERNAL : Command is listed in @list commands */
+#define CMD_T_LISTED    0x00080000
+
+/* INTERNAL : Command is an internal command, and shouldn't be matched
+ * or aliased
+ */
+#define CMD_T_INTERNAL  0x00040000
+
+/* Logging for commands */
+#define CMD_T_LOGNAME   0x00020000
+#define CMD_T_LOGARGS   0x00010000
+
+/* Split arguments at =, but don't abort if there's no = */
+#define CMD_T_EQSPLIT    0x0001
+
+/* Split into argv[] at ,s */
+#define CMD_T_ARGS       0x0010
+
+/* Split at spaces instead of commas. CMD_T_ARGS MUST also be defined */
+#define CMD_T_ARG_SPACE  0x0020
+
+/* Do NOT parse arguments */
+#define CMD_T_NOPARSE    0x0040
+
+#define CMD_T_LS_ARGS    CMD_T_ARGS
+#define CMD_T_LS_SPACE   CMD_T_ARG_SPACE
+#define CMD_T_LS_NOPARSE CMD_T_NOPARSE
+#define CMD_T_RS_ARGS    CMD_T_ARGS<<4
+#define CMD_T_RS_SPACE   CMD_T_ARG_SPACE<<4
+#define CMD_T_RS_NOPARSE CMD_T_NOPARSE<<4
+
+/** COMMAND prototype.
+ * \verbatim
+   Passed arguments:
+   executor : Object issuing command.
+   sw : switch_mask, check with the SW_ macros.
+   raw : *FULL* unparsed, untouched command.
+   switches : Any unhandled switches, or NULL if none.
+   args_raw : Full argument, untouched. null-string if none.
+   arg_left : Left-side arguments, unparsed if CMD_T_NOPARSE.
+   args_left : Parsed arguments, if CMD_T_ARGS is defined.
+   args_right : Parsed right-side arguments, if CMD_T_RSARGS is defined.
+
+   Note that if you don't specify EQSPLIT, left is still the data you want. If you define EQSPLIT,
+   there are also right_XX values.
+
+   Special case:
+   If the NOEVAL switch is given, AND EQSPLIT is defined, the right-side will not be parsed.
+   If NOEVAL is givean the EQSPLIT isn't defined, the left-side won't be parsed.
+ * \endverbatim
+ */
+
+#define COMMAND(command_name) \
+void command_name (COMMAND_INFO *cmd, dbref player, dbref cause, \
+ switch_mask sw,char *raw, char *switches, char *args_raw, \
+                  char *arg_left, char *args_left[MAX_ARG], \
+                  char *arg_right, char *args_right[MAX_ARG], int fromport); \
+void command_name(COMMAND_INFO *cmd __attribute__ ((__unused__)), \
+                  dbref player __attribute__ ((__unused__)), \
+                  dbref cause __attribute__ ((__unused__)), \
+                  switch_mask sw __attribute__ ((__unused__)), \
+                  char *raw __attribute__ ((__unused__)), \
+                  char *switches __attribute__ ((__unused__)), \
+                  char *args_raw __attribute__ ((__unused__)), \
+                  char *arg_left __attribute__ ((__unused__)), \
+                  char *args_left[MAX_ARG] __attribute__ ((__unused__)), \
+                  char *arg_right __attribute__ ((__unused__)), \
+                  char *args_right[MAX_ARG] __attribute__ ((__unused__)), \
+                 int fromport)
+
+/** Common command prototype macro */
+#define COMMAND_PROTO(command_name) \
+void command_name (COMMAND_INFO *cmd, dbref player, dbref cause, switch_mask sw,char *raw, char *switches, char *args_raw, \
+                  char *arg_left, char *args_left[MAX_ARG], \
+                  char *arg_right, char *args_right[MAX_ARG], int fromport)
+
+typedef struct command_info COMMAND_INFO;
+typedef void (*command_func) (COMMAND_INFO *, dbref, dbref, switch_mask, char *,
+                             char *, char *, char *, char *[MAX_ARG], char *,
+                             char *[MAX_ARG], int fromport);
+
+/** A hook specification.
+ */
+struct hook_data {
+  dbref obj;           /**< Object where the hook attribute is stored. */
+  char *attrname;      /**< Attribute name of the hook attribute */
+};
+
+/** A command.
+ * This structure represents a command in the table of available commands.
+ */
+struct command_info {
+  const char *name;    /**< Canonical name of the command */
+  const char *restrict_message;        /**< Message sent when command is restricted */
+  command_func func;   /**< Function to call when command is run */
+  unsigned int type;   /**< Types of objects that can use the command */
+  switch_mask sw;      /**< Bitflags of switches this command can take */
+  boolexp lock;
+  /** Hooks on this command.
+   */
+  struct {
+    struct hook_data before;   /**< Hook to evaluate before command */
+    struct hook_data after;    /**< Hook to evaluate after command */
+    struct hook_data ignore;   /**< Hook to evaluate to decide if we should ignore hardcoded command */
+    struct hook_data override; /**< Hook to override command with $command */
+  } hooks;
+};
+
+typedef struct command_list COMLIST;
+/** A command list entry.
+ * This structure stores the static array of commands that are 
+ * initially loaded into the command table. Commands can also be
+ * added dynamically, outside of this array.
+ */
+struct command_list {
+  const char *name;    /**< Command name */
+  const char *switches;        /**< Space-separated list of switch names */
+  command_func func;   /**< Function to call when command is run */
+  unsigned int type;   /**< Types of objects that can use the command */
+  const char *command_lock; /**< Command Lock Boolexp */
+};
+
+typedef struct switch_value SWITCH_VALUE;
+/** The value associated with a switch.
+ * Command switches are given integral values at compile time when
+ * the switchinc.c and switches.h files are rebuilt. This structure
+ * associates switch names with switch numbers
+ */
+struct switch_value {
+  const char *name;    /**< Name of the switch */
+  int value;           /**< Number of the switch */
+};
+
+typedef struct com_sort_struc COMSORTSTRUC;
+
+/** Sorted linked list of commands.
+ * This structure is used to build a sorted linked list of pointers
+ * to command data.
+ */
+struct com_sort_struc {
+  struct com_sort_struc *next; /**< Pointer to next in list */
+  COMMAND_INFO *cmd;           /**< Command data */
+};
+
+/** Permissions for commands.
+ * This structure is used to associate names for command permissions
+ * (e.g. "player") with the appropriate bitmask
+ */
+struct command_perms_t {
+  const char *name;    /**< Permission name */
+  unsigned int type;   /**< Bitmask for this permission */
+};
+
+#define SWITCH_NONE 0
+#include "switches.h"
+
+extern switch_mask *switchmask(const char *switches);
+extern COMMAND_INFO *command_find(const char *name);
+extern COMMAND_INFO *command_find_exact(const char *name);
+extern COMMAND_INFO *command_add
+  (const char *name, int type, const char *switchstr, command_func func, const char *command_lock);
+extern COMMAND_INFO *make_command
+  (const char *name, int type, switch_mask *sw, command_func func, const char *command_lock);
+extern COMMAND_INFO *command_modify
+  (const char *name, int type, const char *command_lock,
+   switch_mask *sw, command_func func);
+extern void reserve_alias(const char *a);
+extern int alias_command(const char *command, const char *alias);
+extern void command_init_preconfig(void);
+extern void command_init_postconfig(void);
+extern void command_splitup
+  (dbref player, dbref cause, char *from, char *to, char **args,
+   COMMAND_INFO *cmd, int side);
+extern void command_argparse
+  (dbref player, dbref realcause, dbref cause, char **from, char *to, char **argv,
+   COMMAND_INFO *cmd, int side, int forcenoparse);
+extern char *command_parse
+  (dbref player, dbref cause, dbref realcause, char *string, int fromport);
+extern void do_list_commands(dbref player, int lc);
+extern char *list_commands(void);
+extern int command_check_byname(dbref player, const char *name);
+extern int restrict_command(const char *name, const char *restriction);
+extern void reserve_aliases(void);
+extern void local_commands(void);
+extern void do_command_add(dbref player, char *name, int flags);
+extern void do_command_delete(dbref player, char *name);
+extern void generic_command_failure(dbref player, dbref cause, char *string, int fromport);
+extern int command_lock(const char *command, const char *lock);
+
+#endif                         /* __COMMAND_H */
diff --git a/hdrs/compile.h b/hdrs/compile.h
new file mode 100644 (file)
index 0000000..1b6a146
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef __COMPILE_H
+#define __COMPILE_H
+
+/* Compiler-specific stuff. */
+
+#ifndef __GNUC_PREREQ
+#if defined __GNUC__ && defined __GNUC_MINOR__
+# define __GNUC_PREREQ(maj, min) \
+        ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+#else
+# define __GNUC_PREREQ(maj, min) 0
+#endif
+#endif
+
+/* For gcc 3 and up, this attribute lets the compiler know that the
+ * function returns a newly allocated value, for pointer aliasing
+ * optimizations.
+ */
+#if !defined(__attribute_malloc__) && __GNUC_PREREQ(2, 96)
+#define __attribute_malloc__ __attribute__ ((__malloc__))
+#elif !defined(__attribute_malloc__)
+#define __attribute_malloc__
+#endif
+
+/* The C99 restrict keyword lets the compiler do some more pointer
+ * aliasing optimizations. Essentially, a RESTRICT pointer function
+ * argument can't share that pointer with ones passed via other
+ * arguments. 
+ * This should be a Configure check sometime.
+ */
+#if __GNUC_PREREQ(2, 92)
+#define RESTRICT __restrict
+#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+#define RESTRICT restrict
+#else
+#define RESTRICT
+#endif
+
+/* If a compiler knows a function will never return, it can generate
+   slightly better code for calls to it. */
+#if defined(WIN32) && _MSC_VER >= 1200
+#define NORETURN __declspec(noreturn)
+#elif defined(HASATTRIBUTE)
+#define NORETURN __attribute__ ((__noreturn__))
+#else
+#define NORETURN
+#endif
+
+/* Enable Win32 services support */
+#ifdef WIN32
+#define WIN32SERVICES
+#endif
+
+/* Disable Win32 services support due to it failing to run properly
+   when compiling with MinGW32. Eventually I would like to correct
+   the issue. - EEH */
+#ifdef __MINGW32__
+#undef WIN32SERVICES
+#endif
+
+/* --------------- Stuff for Win32 services ------------------ */
+/*
+   When "exit" is called to handle an error condition, we really want to
+   terminate the game thread, not the whole process.
+   MS VS.NET (_MSC_VER >= 1200) requires the weird noreturn stuff.
+ */
+
+#ifdef WIN32SERVICES
+#ifndef NT_TCP
+#define exit(arg) Win32_Exit (arg)
+#endif
+void NORETURN WIN32_CDECL Win32_Exit(int exit_code);
+#endif
+
+#endif                         /* __COMPILE_H */
diff --git a/hdrs/conf.h b/hdrs/conf.h
new file mode 100644 (file)
index 0000000..fe98684
--- /dev/null
@@ -0,0 +1,471 @@
+/* conf.h */
+
+#ifndef __CONF_H
+#define __CONF_H
+
+#include "copyrite.h"
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include "options.h"
+#include "mushtype.h"
+#include "htab.h"
+
+/* TwinCheck Ooref define */
+#define        OOREF(x,y,z)    ((ooref == NOTHING || x == ooref) ? (y) : (z && y))
+#define BEGINOOREF_L     char l = 0; \
+                          if(options.twinchecks && ooref == NOTHING ) { \
+                               ooref = executor; \
+                               l = 1; \
+                               }
+#define ENDOOREF_L     if(l  == 1) { ooref = NOTHING; }
+
+/* New Guest Setup Configuration */
+#define GUEST_KEYWORD  "GUEST"
+#define GUEST_DESCRIBE "A newcomer to this world, treat it with kindness."
+
+/* limit on player name length */
+#define PLAYER_NAME_LIMIT (options.player_name_len)
+/* limit on object name length */
+#define OBJECT_NAME_LIMIT 256
+/* Limit on attribute name length */
+#define ATTRIBUTE_NAME_LIMIT 1024
+/* Loose limit on command/function name length */
+#define COMMAND_NAME_LIMIT 64
+#define MAX_ZONES      30
+#define NUM_SWITCH_BYTES 22
+
+/* magic cookies */
+#define LOOKUP_TOKEN '*'
+#define NUMBER_TOKEN '#'
+#define ARG_DELIMITER '='
+
+/* magic command cookies */
+#define SAY_TOKEN '"'
+#define POSE_TOKEN ':'
+#define SEMI_POSE_TOKEN ';'
+#define EMIT_TOKEN '\\'
+#define CHAT_TOKEN '+'
+#define NOEVAL_TOKEN ']'
+
+/* delimiter for lists of exit aliases */
+#define EXIT_DELIMITER ';'
+
+#define QUIT_COMMAND "QUIT"
+#define WHO_COMMAND "WHO"
+#define LOGOUT_COMMAND "LOGOUT"
+#define INFO_COMMAND "INFO"
+#define INFO_VERSION "1.1"
+#define DOING_COMMAND "DOING"
+#define SESSION_COMMAND "SESSION"
+#define IDLE_COMMAND "IDLE"
+
+#define PREFIX_COMMAND "OUTPUTPREFIX"
+#define SUFFIX_COMMAND "OUTPUTSUFFIX"
+#define PUEBLO_COMMAND "PUEBLOCLIENT "
+
+/* These CAN be modified, but it's heavily NOT suggested */
+#define PUEBLO_SEND "</xch_mudtext><img xch_mode=purehtml><xch_page clear=text>\n"
+#define PUEBLO_HELLO "This world is Pueblo 1.10 Enhanced.\r\n"
+
+
+#define MAX_OUTPUT 16384
+/* How much output buffer space must be left before we flush the
+ * buffer? Reportedly, using '0' fixes problems with Win32 port,
+ * and may be more efficient in network use. Using (MAX_OUTPUT / 2)
+ * is how it's been done in the past. You get to pick.
+ */
+#define SPILLOVER_THRESHOLD     0
+/* #define SPILLOVER_THRESHOLD  (MAX_OUTPUT / 2) */
+#define COMMAND_TIME_MSEC 1000 /* time slice length in milliseconds */
+#define COMMAND_BURST_SIZE 100 /* commands allowed per user in a burst */
+#define COMMANDS_PER_TIME 1    /* commands per time slice after burst */
+
+
+/* Set this somewhere near the recursion limit */
+#define MAX_ITERS 100
+
+/* From conf.c */
+extern void do_config_list(dbref player, const char *type, int lc);
+
+typedef struct options_table OPTTAB;
+
+typedef int (*config_func) (const char *opt, const char *val, void *loc,
+                           int maxval, int source);
+
+/** Runtime configuration parameter.
+ * This structure represents a runtime configuration option.
+ */
+typedef struct confparm {
+  const char *name;            /**< name of option. */
+  /** the function handler. */
+  config_func handler;
+  void *loc;                   /**< place to put this option. */
+  int max;                     /**< max: string length, integer value. */
+  int overridden;              /**< Has the default been overridden? */
+  const char *group;           /**< The option's group name */
+} COBRA_CONF;
+
+/** Runtime configuration options.
+ * This large structure stores all of the runtime configuration options
+ * that are typically set in mush.cnf.
+ */
+struct options_table {
+  char mud_name[128];  /**< The name of the mush */
+  int port;            /**< The port to listen for connections */
+  int ssl_port;                /**< The port to listen for SSL connections */
+  char input_db[256];  /**< Name of the input database file */
+  char output_db[256]; /**< Name of the output database file */
+  char flagdb[256];    /**<Name of flag database file */
+  char crash_db[256];  /**< Name of the panic database file */
+  char mail_db[256];   /**< Name of the mail database file */
+  dbref player_start;  /**< The room in which new players are created */
+  dbref guest_start; /**<The room in which new guests are created */
+  dbref master_room;   /**< The master room for global commands/exits */
+  dbref sql_master;     /**< SQL Cmd Master Room */
+  dbref ancestor_room; /**< The ultimate parent room */
+  dbref ancestor_exit; /**< The ultimate parent exit */
+  dbref ancestor_thing;        /**< The ultimate parent thing */
+  dbref ancestor_player; /**< The ultimate parent player */
+  dbref powerless;     /**< known powerless object */
+  int idle_timeout;    /**< Maximum idle time allowed, in minutes */
+  int idle_time;       /** Time for the system to consider player 'idle' used in conjuntion with @AUNIDLE */
+  int unconnected_idle_timeout;        /**< Maximum idle time for connections without dbrefs, in minutes */
+  int dump_interval;   /**< Interval between database dumps, in seconds */
+  char dump_message[256]; /**< Message shown at start of nonforking dump */
+  char dump_complete[256]; /**< Message shown at end of nonforking dump */
+  time_t dump_counter; /**< Time since last dump */
+  int ident_timeout;   /**< Timeout for ident lookups */
+  int max_logins;      /**< Maximum total logins allowed at once */
+  int max_guests;      /**< Maximum guests logins allowed at once */
+  int whisper_loudness;        /**< % chance that a noisy whisper is overheard */
+  int blind_page;      /**< Does page default to page/blind? */
+  int page_aliases;    /**< Does page include aliases? */
+  int paycheck;                /**< Number of pennies awarded each day of connection */
+  int guest_paycheck;  /**< Paycheck for guest connections */
+  int starting_money;  /**< Number of pennies for newly created players */
+  int starting_quota;  /**< Object quota for newly created players */
+  int player_queue_limit; /**< Maximum commands a player can queue at once */
+  int queue_chunk;     /**< Number of commands run from queue when no input from sockets is waiting */
+  int active_q_chunk;  /**< Number of commands run from queue when input from sockets is waiting */
+  int func_nest_lim;   /**< Maximum function recursion depth */
+  int func_invk_lim;   /**< Maximum number of function invocations */
+  int call_lim;                /**< Maximum parser calls allowed in a queue cycle */
+  char log_wipe_passwd[256];   /**< Password for logwipe command */
+  char money_singular[32];     /**< Currency unit name, singular */
+  char money_plural[32];       /**< Currency unit name, plural */
+  char compressprog[256];      /**< Program to compress database dumps */
+  char uncompressprog[256];    /**< Program to uncompress database dumps */
+  char compresssuff[256];      /**< Suffix for compressed dump files */
+  char chatdb[256];            /**< Name of the chat database file */
+  int max_player_chans;                /**< Number of channels a player can create */
+  int max_channels;            /**< Total maximum allowed channels */
+  int chan_cost;               /**< Cost to create a channel */
+  char connect_file[2][256];   /**< Names of text and html connection files */
+  char motd_file[2][256];      /**< Names of text and html motd files */
+  char newuser_file[2][256];   /**< Names of text and html new user files */
+  char register_file[2][256];  /**< Names of text and html registration files */
+  char quit_file[2][256];      /**< Names of text and html disconnection files */
+  char down_file[2][256];      /**< Names of text and html server down files */
+  char full_file[2][256];      /**< Names of text and html server full files */
+  char guest_file[2][256];     /**< Names of text and html guest files */
+  int log_commands;    /**< Should we log all commands? */
+  int log_forces;      /**< Should we log force commands? */
+  int support_pueblo;  /**< Should the MUSH send Pueblo tags? */
+  int login_allow;     /**< Are mortals allowed to log in? */
+  int guest_allow;     /**< Are guests allowed to log in? */
+  int create_allow;    /**< Can new players be created? */
+  int reverse_shs;     /**< Should the SHS routines assume little-endian byte order? */
+  char player_flags[BUFFER_LEN];       /**< Space-separated list of flags to set on newly created players. */
+  char room_flags[BUFFER_LEN];         /**< Space-separated list of flags to set on newly created rooms. */
+  char exit_flags[BUFFER_LEN];         /**< Space-separated list of flags to set on newly created exits. */
+  char thing_flags[BUFFER_LEN];                /**< Space-separated list of flags to set on newly created things. */
+  char channel_flags[BUFFER_LEN];      /**< Space-separated list of flags to set on newly created channels. */
+  int warn_interval;   /**< Interval between warning checks */
+  time_t warn_counter; /**< Time since last warning check */
+  dbref base_room;     /**< Room which floating checks consider as the base */
+  dbref default_home;  /**< Home for the homeless */
+  int use_dns;         /**< Should we use DNS lookups? */
+  int safer_ufun;      /**< Should we require security for ufun calls? */
+  char dump_warning_1min[256]; /**< 1 minute nonforking dump warning message */
+  char dump_warning_5min[256]; /**< 5 minute nonforking dump warning message */
+  int noisy_whisper;   /**< Does whisper default to whisper/noisy? */
+  int possessive_get;  /**< Can possessive get be used? */
+  int possessive_get_d;        /**< Can possessive get be used on disconnected players? */
+  int really_safe;     /**< Does the SAFE flag protect objects from nuke */
+  int destroy_possessions;     /**< Are the possessions of a nuked player nuked? */
+  int null_eq_zero;    /**< Is null string treated as 0 in math functions? */
+  int tiny_booleans;   /**< Do strings and db#'s evaluate as false, like TinyMUSH? */
+  int tiny_trim_fun;   /**< Does the trim function take arguments in TinyMUSH order? */
+  int tiny_math;       /**< Can you use strings in math functions, like TinyMUSH? */
+  int twinchecks;      /**< Do twin checks for @force & attribute commands? */
+  int adestroy;                /**< Is the adestroy attribute available? */
+  int amail;           /**< Is the amail attribute available? */
+  int mail_limit;      /**< Maximum number of mail messages per player */
+  int player_listen;   /**< Does listen work on players? */
+  int player_ahear;    /**< Does ahear work on players? */
+  int startups;                /**< Is startup run on startups? */
+  int room_connects;   /**< Do players trigger aconnect/adisconnect on their location? */
+  int ansi_names;      /**< Are object names shown in bold? */
+  int comma_exit_list; /**< Should exit lists be itemized? */
+  int count_all;       /**< Are hidden players included in total player counts? */
+  int exits_connect_rooms;     /**< Does the presence of an exit make a room connected? */
+  int zone_control;    /**< Are only ZMPs allowed to determine zone-based control? */
+  int link_to_object;  /**< Can exits be linked to objects? */
+  int owner_queues;    /**< Are queues tracked by owner or individual object? */
+  int dark_noaenter;   /**< Do DARK players trigger aenters? */
+  int use_ident;       /**< Should we do ident checks on connections? */
+  char ip_addr[64];    /**< What ip address should the server bind to? */
+  char ssl_ip_addr[64];        /**< What ip address should the server bind to? */
+  int player_name_spaces;      /**< Can players have multiword names? */
+  int forking_dump;    /**< Should we fork to dump? */
+  int restrict_building;       /**< Is the builder power required to build? */
+  int free_objects;    /**< If builder power is required, can you create without it? */
+  int flags_on_examine;        /**< Are object flags shown when it's examined? */
+  int ex_public_attribs;       /**< Are visual attributes shown on examine? */
+  int full_invis;      /**< Are DARK players anonymous? */
+  int silent_pemit;    /**< Does pemit default to pemit/silent? */
+  dbref max_dbref;     /**< Maximum allowable database size */
+  int chat_strip_quote;        /**< Should we strip initial quotes in chat? */
+  char wall_prefix[256];       /**< Prefix for wall announcements */
+  int announce_connects;       /**< Should dis/connects be announced? */
+  char access_file[256];       /**< Name of file of access control rules */
+  char names_file[256];        /**< Name of file of forbidden player names */
+  int object_cost;     /**< Cost to create an object */
+  int exit_cost;       /**< Cost to create an exit */
+  int link_cost;       /**< Cost to link an exit */
+  int room_cost;       /**< Cost to dig a room */
+  int queue_cost;      /**< Deposit to queue a command */
+  int quota_cost;      /**< Number of objects per quota unit */
+  int find_cost;       /**< Cost to create an object */
+  int page_cost;       /**< Cost to create an object */
+  int kill_default_cost;       /**< Default cost to use 'kill' */
+  int kill_min_cost;   /**< Minimum cost to use 'kill' */
+  int kill_bonus;      /**< Percentage of cost paid to victim of 'kill' */
+  int queue_loss;      /**< 1/queue_loss chance of a command costing a penny */
+  int max_pennies;     /**< Maximum pennies a player can have */
+  int max_guest_pennies;       /**< Maximum pennies a guest can have */
+  int max_depth;       /**< Maximum container depth */
+  int max_parents;     /**< Maximum parent depth */
+  int purge_interval;  /**< Time between automatic purges */
+  time_t purge_counter;        /**< Time since last automatic purge */
+  int dbck_interval;   /**< Time between automatic dbcks */
+  time_t dbck_counter; /**< Time since last automatic dbck */
+  int max_attrcount;   /**< Maximum number of attributes per object */
+  int float_precision; /**< Precision of floating point display */
+  int newline_one_char;        /**< Should a newline be counted as 1 character or 2? */
+  int player_name_len; /**< Maximum length of player names */
+  int queue_entry_cpu_time;    /**< Maximum cpu time allowed per queue entry */
+  int ascii_names;     /**< Are object names restricted to ascii characters? */
+  int max_global_fns;  /**< Maximum number of functions */
+  char chunk_swap_file[256];   /**< Name of the attribute swap file */
+  int chunk_cache_memory;      /**< Memory to use for the attribute cache */
+  int chunk_migrate_amount;    /**< Number of attrs to migrate each second */
+  int read_remote_desc;        /**< Can players read DESCRIBE attribute remotely? */
+#ifdef HAS_OPENSSL
+  char ssl_private_key_file[256];      /**< File to load the server's cert from */
+  char ssl_ca_file[256];       /**< File to load the CA certs from */
+  int ssl_require_client_cert; /**< Are clients required to present certs? */
+#endif
+  int mem_check;       /**< Turn on the memory allocation checker? */
+  int use_quota;       /**< Are quotas enabled? */
+  int empty_attrs;     /**< Are empty attributes preserved? */
+  int function_side_effects; /**< Turn on side effect functions? */
+  char error_log[256]; /**< File to log connections */
+  char connect_log[256]; /**< File to log connections */
+  char wizard_log[256];        /**< File to log wizard commands */
+  char command_log[256]; /**< File to log suspect commands */
+  char trace_log[256]; /**< File to log trace data */
+  char checkpt_log[256]; /**< File to log checkpoint data */
+#ifdef HAS_MYSQL
+  char sql_platform[256]; /**< Type of SQL server, or "disabled" */
+  char sql_host[256]; /**< Hostname of sql server */
+  char sql_username[256]; /**< Username for sql */
+  char sql_password[256]; /**< Password for sql */
+  char sql_database[256]; /**< Database for sql */
+#endif
+  char guest_prefix[128]; /**< Word used to prefix guest logins */
+  int  guest_roman_numeral; /**< Number guests using roman numerals */
+  /* Division Related Stuff */
+  char player_powergroup[BUFFER_LEN]; /**< Default Assigned Player Powergroup */
+};
+
+extern OPTTAB options;
+extern HASHTAB local_options;
+
+extern COBRA_CONF *add_config(const char *name, config_func handler, void *loc,
+                       int max, const char *group);
+extern COBRA_CONF *new_config(void);
+extern COBRA_CONF *get_config(const char *name);
+
+int cf_bool(const char *opt, const char *val, void *loc, int maxval,
+           int source);
+int cf_str(const char *opt, const char *val, void *loc, int maxval, int source);
+int cf_int(const char *opt, const char *val, void *loc, int maxval, int source);
+int cf_dbref(const char *opt, const char *val, void *loc, int maxval,
+            int source);
+int cf_flag(const char *opt, const char *val, void *loc, int maxval,
+           int source);
+int cf_time(const char *opt, const char *val, void *loc, int maxval,
+           int source);
+
+
+#define NUMQ    36
+
+/* Config group viewing permissions */
+#define CGP_GOD         0x1
+#define CGP_DIRECTOR    0x3
+#define CGP_ADMIN       0x7
+#define Can_View_Config_Group(p,g) \
+        (!(g->viewperms) || (God(p) && (g->viewperms & CGP_GOD)) || \
+         (Director(p) && (g->viewperms & CGP_DIRECTOR)) || \
+         (Admin(p) && (g->viewperms & CGP_ADMIN)))
+
+
+#define DUMP_INTERVAL       (options.dump_interval)
+#define DUMP_NOFORK_MESSAGE  (options.dump_message)
+#define DUMP_NOFORK_COMPLETE (options.dump_complete)
+#define INACTIVITY_LIMIT    (options.idle_timeout)
+#define UNCONNECTED_LIMIT    (options.unconnected_idle_timeout)
+
+#define MAX_LOGINS      (options.max_logins)
+#define MAX_GUESTS      (options.max_guests)
+
+/* dbrefs are in the conf file */
+
+#define TINYPORT         (options.port)
+#define SSLPORT          (options.ssl_port)
+#define PLAYER_START     (options.player_start)
+#define GUEST_START    (options.guest_start)
+#define MASTER_ROOM      (options.master_room)
+#define ANCESTOR_ROOM           (options.ancestor_room)
+#define ANCESTOR_EXIT           (options.ancestor_exit)
+#define ANCESTOR_THING          (options.ancestor_thing)
+#define ANCESTOR_PLAYER         (options.ancestor_player)
+#define MONEY            (options.money_singular)
+#define MONIES           (options.money_plural)
+#define WHISPER_LOUDNESS        (options.whisper_loudness)
+#define BLIND_PAGE      (options.blind_page)
+#define PAGE_ALIASES    (options.page_aliases)
+
+#define START_BONUS      (options.starting_money)
+#define PAY_CHECK        (options.paycheck)
+#define GUEST_PAY_CHECK        (options.guest_paycheck)
+#define START_QUOTA      (options.starting_quota)
+#define LOG_WIPE_PASSWD  (options.log_wipe_passwd)
+#define SUPPORT_PUEBLO   (options.support_pueblo)
+
+#define QUEUE_QUOTA      (options.player_queue_limit)
+
+#define MUDNAME          (options.mud_name)
+#define DEF_DB_IN        (options.input_db)
+#define DEF_DB_OUT       (options.output_db)
+
+#define BASE_ROOM        (options.base_room)
+#define DEFAULT_HOME     (options.default_home)
+
+#define PURGE_INTERVAL   (options.purge_interval)
+#define DBCK_INTERVAL    (options.dbck_interval)
+#define MAX_PARENTS (options.max_parents)
+#define MAX_DEPTH (options.max_depth)
+#define MAX_PENNIES (options.max_pennies)
+#define MAX_GUEST_PENNIES (options.max_guest_pennies)
+#define DBTOP_MAX (options.max_dbref)
+#define QUEUE_LOSS (options.queue_loss)
+#define KILL_BONUS (options.kill_bonus)
+#define KILL_MIN_COST (options.kill_min_cost)
+#define KILL_BASE_COST (options.kill_default_cost)
+#define FIND_COST (options.find_cost)
+#define PAGE_COST (options.page_cost)
+#define QUOTA_COST (options.quota_cost)
+#define QUEUE_COST (options.queue_cost)
+#define ROOM_COST (options.room_cost)
+#define LINK_COST (options.link_cost)
+#define EXIT_COST (options.exit_cost)
+#define OBJECT_COST (options.object_cost)
+#define GOD ((dbref) 1)
+#define ANNOUNCE_CONNECTS (options.announce_connects)
+#define ACCESS_FILE (options.access_file)
+#define NAMES_FILE (options.names_file)
+#define SILENT_PEMIT (options.silent_pemit)
+#define PLAYER_LISTEN (options.player_listen)
+#define PLAYER_AHEAR (options.player_ahear)
+#define STARTUPS (options.startups)
+#define FULL_INVIS (options.full_invis)
+#define EX_PUBLIC_ATTRIBS (options.ex_public_attribs)
+#define FLAGS_ON_EXAMINE (options.flags_on_examine)
+#define FREE_OBJECTS (options.free_objects)
+#define RESTRICTED_BUILDING (options.restrict_building)
+#define NO_FORK (!options.forking_dump)
+#define PLAYER_NAME_SPACES (options.player_name_spaces)
+#define SAFER_UFUN (options.safer_ufun)
+#define NOISY_WHISPER (options.noisy_whisper)
+#define POSSESSIVE_GET (options.possessive_get)
+#define POSSGET_ON_DISCONNECTED (options.possessive_get_d)
+#define REALLY_SAFE (options.really_safe)
+#define DESTROY_POSSESSIONS (options.destroy_possessions)
+#define NULL_EQ_ZERO (options.null_eq_zero)
+#define TINY_BOOLEANS (options.tiny_booleans)
+#define TINY_TRIM_FUN (options.tiny_trim_fun)
+#define ADESTROY_ATTR (options.adestroy)
+#define AMAIL_ATTR (options.amail)
+#define MAIL_LIMIT (options.mail_limit)
+#define ROOM_CONNECTS (options.room_connects)
+#define ANSI_NAMES (options.ansi_names)
+#define COMMA_EXIT_LIST (options.comma_exit_list)
+#define COUNT_ALL (options.count_all)
+#define EXITS_CONNECT_ROOMS (options.exits_connect_rooms)
+#define ZONE_CONTROL_ZMP (options.zone_control)
+#define CHAT_STRIP_QUOTE (options.chat_strip_quote)
+#define WALL_PREFIX (options.wall_prefix)
+#define NO_LINK_TO_OBJECT (!options.link_to_object)
+#define QUEUE_PER_OWNER (options.owner_queues)
+#define DARK_NOAENTER (options.dark_noaenter)
+#define USE_IDENT (options.use_ident)
+#define IDENT_TIMEOUT (options.ident_timeout)
+#define USE_DNS (options.use_dns)
+#define MUSH_IP_ADDR (options.ip_addr)
+#define SSL_IP_ADDR (options.ssl_ip_addr)
+#define MAX_ATTRCOUNT (options.max_attrcount)
+#define HARD_MAX_ATTRCOUNT 1000000
+#define FLOAT_PRECISION (options.float_precision)
+#define RECURSION_LIMIT (options.func_nest_lim)
+#define FUNCTION_LIMIT (options.func_invk_lim)
+#define CALL_LIMIT (options.call_lim)
+#define TINY_MATH (options.tiny_math)
+#define NEWLINE_ONE_CHAR (options.newline_one_char)
+#define ONLY_ASCII_NAMES (options.ascii_names)
+#define MAX_GLOBAL_FNS (options.max_global_fns)
+#define USE_QUOTA (options.use_quota)
+#define EMPTY_ATTRS (options.empty_attrs)
+#define FUNCTION_SIDE_EFFECTS (options.function_side_effects)
+#define ERRLOG (options.error_log)
+#define CONNLOG (options.connect_log)
+#define WIZLOG (options.wizard_log)
+#define CMDLOG (options.command_log)
+#define TRACELOG (options.trace_log)
+#define CHECKLOG (options.checkpt_log)
+#ifdef HAS_MYSQL
+#define SQL_PLATFORM (options.sql_platform)
+#define SQL_HOST (options.sql_host)
+#define SQL_DB (options.sql_database)
+#define SQL_USER (options.sql_username)
+#define SQL_PASS (options.sql_password)
+#endif
+#ifdef _SWMP_
+#define SQLCMD_MasterRoom (options.sql_master)
+#endif
+
+#define CHUNK_SWAP_FILE (options.chunk_swap_file)
+#define CHUNK_CACHE_MEMORY (options.chunk_cache_memory)
+#define CHUNK_MIGRATE_AMOUNT (options.chunk_migrate_amount)
+
+#define READ_REMOTE_DESC (options.read_remote_desc)
+
+#define GUEST_PREFIX   (options.guest_prefix)
+#define GST_NUMBERING   (options.guest_roman_numeral)
+#define GUEST_NUMBER(x)  (const char *) (GST_NUMBERING ? ArabicToRoman(x) : unparse_number((NVAL) x))
+#define PLAYER_DEF_POWERGROUP (options.player_powergroup)
+#endif                         /* __CONF_H */
diff --git a/hdrs/copyrite.h b/hdrs/copyrite.h
new file mode 100644 (file)
index 0000000..873071d
--- /dev/null
@@ -0,0 +1,183 @@
+/* copyrite.h */
+
+/*
+ * Copyright, License, and Credits for PennMUSH 1.x. Revised October 2002.
+ *
+ * I. Copyrights
+ *
+ * PennMUSH 1.x
+ * Copyright (c) 1995-2002, Alan Schwartz, T. Alexander Popiel, Shawn Wagner
+ * Contact email for Alan Schwartz: <dunemush@pennmush.org>. 
+ * 
+ * Some code used in this server may have been derived from the
+ * TinyMUSH 2.2 source code, with permission. TinyMUSH 2.2 is
+ * Copyright (c) 1994-2002, Jean Marie Diaz, Lydia Leong, and Devin Hooker.
+ *
+ * Some code used in this server may have been directive from TinyMUSH 2.0.
+ * Copyright (c) 1995, Joseph Traub, Glenn Crocker.
+ *
+ * Some code used in this server may have been directive from TinyMUD.
+ * Copyright (c) 1995, David Applegate, James Aspnes, Timothy Freeman
+ * and Bennet Yee.
+ *
+ *------------------------------------------------------------------------*
+ *
+ * II. License
+ *
+ * Because PennMUSH includes parts of multiple works, you must comply
+ * with all of the relevant licenses of those works. The portions derived
+ * from TinyMUD/TinyMUSH 2.0 are licensed under the following terms:
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that: (1) source code distributions
+ *   retain the above copyright notice and this paragraph in its entirety, and
+ *   (2) distributions including binary code include the above copyright 
+ *   notice and this paragraph in its entirety in the documentation or other 
+ *   materials provided with the distribution.  The names of the copyright 
+ *   holders may not be used to endorse or promote products derived from 
+ *   this software without specific prior written permission.
+ * 
+ *   THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
+ *   WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ *   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Although not necessary given the above copyright, the TinyMUSH 2.0
+ * developers, Joseph Traub and Glenn Crocker, confirmed in email to
+ * Alan Schwartz in 2002 that they were willing to have any of their
+ * code present in PennMUSH redistributed under the Artistic License.
+ *
+ * The portions derived from TinyMUSH 2.2 are used under the Artistic
+ * License. Jean Marie Diaz, Lydia Leong, and Devin Hooker, the
+ * TinyMUSH 2.2 copyright holders, explicitly agreed to relicense
+ * their source code under the Artistic License in 2002, and TinyMUSH 2.2.5
+ * was released under this license; Alan Schwartz has confirmatory email 
+ * from them authorizing the redistribution of any portions of TinyMUSH 2.2
+ * that may be present in PennMUSH under the Artistic License. 
+ *
+ * The Artistic License is also the license under which you
+ * are granted permission to copy, modify, and redistribute PennMUSH:
+ *
+ * The Artistic License
+ * 
+ * Preamble
+ * 
+ * The intent of this document is to state the conditions under which a
+ * Package may be copied, such that the Copyright Holder maintains some
+ * semblance of artistic control over the development of the package,
+ * while giving the users of the package the right to use and distribute
+ * the Package in a more-or-less customary fashion, plus the right to make
+ * reasonable modifications.
+ * 
+ * Definitions:
+ * 
+ * "Package" refers to the collection of files distributed by the Copyright
+ * Holder, and derivatives of that collection of files created through
+ * textual modification.
+ * "Standard Version" refers to such a Package if it has not been modified,
+ * or has been modified in accordance with the wishes of the Copyright
+ * Holder.
+ * "Copyright Holder" is whoever is named in the copyright or copyrights
+ * for the package.
+ * "You" is you, if you're thinking about copying or distributing this Package.
+ * "Reasonable copying fee" is whatever you can justify on the basis of media
+ * cost, duplication charges, time of people involved, and so on. (You will
+ * not be required to justify it to the Copyright Holder, but only to the
+ * computing community at large as a market that must bear the fee.)
+ * "Freely Available" means that no fee is charged for the item itself,
+ * though there may be fees involved in handling the item. It also means
+ * that recipients of the item may redistribute it under the same conditions
+ * they received it.
+ * 
+ * 1. You may make and give away verbatim copies of the source form of the
+ * Standard Version of this Package without restriction, provided that
+ * you duplicate all of the original copyright notices and associated
+ * disclaimers.
+ * 
+ * 2. You may apply bug fixes, portability fixes and other modifications
+ * derived from the Public Domain or from the Copyright Holder. A Package
+ * modified in such a way shall still be considered the Standard Version.
+ * 
+ * 3. You may otherwise modify your copy of this Package in any way, provided
+ * that you insert a prominent notice in each changed file stating how and
+ * when you changed that file, and provided that you do at least ONE of
+ * the following:
+ * 
+ *  a) place your modifications in the Public Domain or otherwise make them
+ *  Freely Available, such as by posting said modifications to Usenet or an
+ *  equivalent medium, or placing the modifications on a major archive site
+ *  such as ftp.uu.net, or by allowing the Copyright Holder to include your
+ *  modifications in the Standard Version of the Package.
+ * 
+ *  b) use the modified Package only within your corporation or organization.
+ * 
+ *  c) rename any non-standard executables so the names do not conflict with
+ *  standard executables, which must also be provided, and provide a separate
+ *  manual page for each non-standard executable that clearly documents how
+ *  it differs from the Standard Version.
+ * 
+ *  d) make other distribution arrangements with the Copyright Holder.
+ * 
+ * 4. You may distribute the programs of this Package in object code or
+ * executable form, provided that you do at least ONE of the following:
+ * 
+ *  a) distribute a Standard Version of the executables and library files,
+ *  together with instructions (in the manual page or equivalent) on where
+ *  to get the Standard Version.
+ * 
+ *  b) accompany the distribution with the machine-readable source of the
+ *  Package with your modifications.
+ * 
+ *  c) accompany any non-standard executables with their corresponding
+ *  Standard Version executables, giving the non-standard executables
+ *  non-standard names, and clearly documenting the differences in manual
+ *  pages (or equivalent), together with instructions on where to get the
+ *  Standard Version.
+ * 
+ *  d) make other distribution arrangements with the Copyright Holder.
+ * 
+ * 5. You may charge a reasonable copying fee for any distribution of
+ * this Package. You may charge any fee you choose for support of this
+ * Package. You may not charge a fee for this Package itself. However, you
+ * may distribute this Package in aggregate with other (possibly commercial)
+ * programs as part of a larger (possibly commercial) software distribution
+ * provided that you do not advertise this Package as a product of your own.
+ * 
+ * 6. The scripts and library files supplied as input to or produced as
+ * output from the programs of this Package do not automatically fall under
+ * the copyright of this Package, but belong to whomever generated them,
+ * and may be sold commercially, and may be aggregated with this Package.
+ * 
+ * 7. C or perl subroutines supplied by you and linked into this Package
+ * shall not be considered part of this Package.
+ * 
+ * 8. The name of the Copyright Holder may not be used to endorse or
+ * promote products derived from this software without specific prior
+ * written permission.
+ * 
+ * 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
+ * MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ * 
+ * The End
+ * 
+ *---------------------------------------------------------------------*
+ *
+ * III. Credits
+ *
+ * Many people have helped develop PennMUSH. In addition to the people
+ * listed above, and the many people noted in the CHANGES file
+ * for suggestions and patches, special mention is due to:
+ *
+ * Past and present PennMUSH development team members:
+ *  T. Alexander Popiel, Ralph Melton, Thorvald Natvig, Luuk de Waard,
+ *  Shawn Wagner
+ * Past and present PennMUSH porters:
+ *  Nick Gammon, Sylvia, Dan Williams, Ervin Hearn III
+ * TinyMUSH 2.2, TinyMUSH 3.0, TinyMUX 2, and RhostMUSH developers
+ * All PennMUSH users who've sent in bug reports and patches
+ * The admin and players of DuneMUSH, Dune II, M*U*S*H, Rio:MdC, 
+ *  and other places Javelin has beta-tested new versions
+ * Lydia Leong (Amberyl), who maintained PennMUSH until 1995, and taught
+ *  Javelin how to be a Wizard and a God of a MUSH.
+ *
+ */
diff --git a/hdrs/cron.h b/hdrs/cron.h
new file mode 100644 (file)
index 0000000..7a6b4c7
--- /dev/null
@@ -0,0 +1,124 @@
+#ifndef __CRON_H
+#define __CRON_H
+
+#include "conf.h"
+#include "externs.h"
+#include "command.h"
+
+#define CRON_SPEC_SEP          ' '
+#define CRON_NUM_SEP           ','
+#define CRON_SKIP_SEP          '/'
+#define CRON_RANGE_SEP         '-'
+#define CRON_WILDCARD          '*'
+
+#define CRON_MINUTE_MAX                59
+#define CRON_HOUR_MAX          23
+#define CRON_DAY_MAX           30
+#define CRON_MONTH_MAX         11
+#define CRON_WDAY_MAX          6
+
+#define CRON_GLOBAL            -1
+
+#define CRON_NAME_LEN          32
+#define CRON_FORMAT_LEN                128
+
+#define CF_COMMAND             0x1
+#define CF_FUNCTION            0x2
+#define CF_HALT                        0x4
+
+#define CRON_Command(job)      ((job)->type & CF_COMMAND)
+#define CRON_Function(job)     ((job)->type & CF_FUNCTION)
+#define CRON_Halt(job)         ((job)->type & CF_HALT)
+
+#define CF_DEFAULT             (0)
+
+#define CM_JANUARY             0
+#define CM_FEBRUARY            1
+#define CM_MARCH               2
+#define CM_APRIL               3
+#define CM_MAY                 4
+#define CM_JUNE                        5
+#define CM_JULY                        6
+#define CM_AUGUST              7
+#define CM_SEPTEMBER           8
+#define CM_OCTOBER             9
+#define CM_NOVEMBER            10
+#define CM_DECEMBER            11
+
+#define CD_SUNDAY              0
+#define CD_MONDAY              1
+#define CD_TUESDAY             2
+#define CD_WEDNESDAY           3
+#define CD_THURSDAY            4
+#define CD_FRIDAY              5
+#define CD_SATURDAY            6
+
+typedef struct named_value NVALUE;
+typedef struct named_value_alias NVALUE_ALIAS;
+typedef struct CRONSPEC cronspec;
+typedef struct CRONJOB cronjob;
+
+struct named_value
+{
+  const char *name;
+  int value;
+};
+
+struct named_value_alias
+{
+  const char *alias;
+  const char *name;
+};
+
+struct CRONSPEC
+{
+  long long minute;
+  long hour;
+  long day;
+  short month;
+  char wday;
+};
+
+struct CRONJOB
+{
+  char name[CRON_NAME_LEN];
+  long int type;
+  cronspec spec;
+  char format[CRON_FORMAT_LEN];
+  dbref owner;
+  dbref object;
+  char attrib[ATTRIBUTE_NAME_LIMIT];
+};
+
+struct def_crontab  {
+  const char *name;
+  long int type;
+  const char *format;
+  dbref owner;
+  dbref object;
+  const char *attribute;
+};
+
+int start_cron(void);
+int run_cron(void);
+
+NVALUE *get_nvalue(NVALUE *table, const char *name);
+NVALUE *get_nvalue_by_alias(NVALUE *table, NVALUE_ALIAS *alias, const char *name);
+
+cronspec current_spec(void);
+int cmpspec(cronspec a, cronspec b);
+
+long long num_to_bits(unsigned int num);
+long long parse_spec(const char *str, unsigned int maximum, NVALUE *table, NVALUE_ALIAS *alias, char *format, char **fp);
+char *show_spec(cronspec *spec);
+
+cronjob *new_job(void);
+int free_job(cronjob *job);
+cronjob *make_job(dbref owner, const char *name, const char *str);
+void do_list_cronjobs(dbref player);
+
+extern HASHTAB crontab;
+
+extern COMMAND(cmd_cron);
+
+#endif /* __CRON_H */
diff --git a/hdrs/csrimalloc.h b/hdrs/csrimalloc.h
new file mode 100644 (file)
index 0000000..19be689
--- /dev/null
@@ -0,0 +1,102 @@
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/* Modified by Alan Schwartz for PennMUSH.
+ * Should be included after config.h
+ */
+
+#ifndef __CSRIMALLOC_H
+#define __CSRIMALLOC_H
+
+#include "config.h"
+#define univptr_t               Malloc_t
+#define memsize_t               size_t
+#include "confmagic.h"
+
+/*
+ *  defined so users of new features of this malloc can #ifdef
+ *  invocations of those features.
+ */
+#define CSRIMALLOC
+
+#ifdef CSRI_TRACE
+/* Tracing malloc definitions - helps find leaks */
+
+extern univptr_t trace__malloc
+_((size_t nbytes, const char *fname, int linenum));
+extern univptr_t trace__calloc
+_((size_t nelem, size_t elsize, const char *fname, int linenum));
+extern univptr_t trace__realloc
+_((univptr_t cp, size_t nbytes, const char *fname, int linenum));
+extern univptr_t trace__valloc _((size_t size, const char *fname, int linenum));
+extern univptr_t trace__memalign
+_((size_t alignment, size_t size, const char *fname, int linenum));
+extern univptr_t trace__emalloc
+_((size_t nbytes, const char *fname, int linenum));
+extern univptr_t trace__ecalloc
+_((size_t nelem, size_t sz, const char *fname, int linenum));
+extern univptr_t trace__erealloc
+_((univptr_t ptr, size_t nbytes, const char *fname, int linenum));
+extern char *trace__strdup _((const char *s, const char *fname, int linenum));
+extern char *trace__strsave _((const char *s, const char *fname, int linenum));
+extern void trace__free _((univptr_t cp, const char *fname, int linenum));
+extern void trace__cfree _((univptr_t cp, const char *fname, int linenum));
+
+#define malloc(x)               trace__malloc((x), __FILE__, __LINE__)
+#define calloc(x, n)            trace__calloc((x), (n), __FILE__, __LINE__)
+#define realloc(p, x)           trace__realloc((p), (x), __FILE__, __LINE__)
+#define memalign(x, n)          trace__memalign((x), (n), __FILE__, __LINE__)
+#define valloc(x)               trace__valloc((x), __FILE__, __LINE__)
+#define emalloc(x)              trace__emalloc((x), __FILE__, __LINE__)
+#define ecalloc(x, n)           trace__ecalloc((x), (n), __FILE__, __LINE__)
+#define erealloc(p, x)          trace__erealloc((p), (x), __FILE__, __LINE__)
+#define strdup(p)               trace__strdup((p), __FILE__, __LINE__)
+#define strsave(p)              trace__strsave((p), __FILE__, __LINE__)
+/* cfree and free are identical */
+#define cfree(p)                trace__free((p), __FILE__, __LINE__)
+#define free(p)                 trace__free((p), __FILE__, __LINE__)
+
+#else                          /* CSRI_TRACE */
+
+extern univptr_t malloc _((size_t nbytes));
+extern univptr_t calloc _((size_t nelem, size_t elsize));
+extern univptr_t realloc _((univptr_t cp, size_t nbytes));
+extern univptr_t valloc _((size_t size));
+extern univptr_t memalign _((size_t alignment, size_t size));
+extern univptr_t emalloc _((size_t nbytes));
+extern univptr_t ecalloc _((size_t nelem, size_t sz));
+extern univptr_t erealloc _((univptr_t ptr, size_t nbytes));
+extern char *strdup _((const char *s));
+extern char *strsave _((const char *s));
+extern Free_t free _((univptr_t cp));
+extern Free_t cfree _((univptr_t cp));
+
+#endif                         /* CSRI_TRACE */
+
+extern void mal_debug _((int level));
+extern void mal_dumpleaktrace _((FILE * fp));
+extern void mal_heapdump _((FILE * fp));
+extern void mal_leaktrace _((int value));
+extern void mal_sbrkset _((int n));
+extern void mal_slopset _((int n));
+extern void mal_statsdump _((FILE * fp));
+extern void mal_setstatsfile _((FILE * fp));
+extern void mal_trace _((int value));
+extern int mal_verify _((int fullcheck));
+extern void mal_mmap _((char *fname));
+
+
+/*
+ *  You may or may not want this - In gcc version 1.30, on Sun3s running
+ *  SunOS3.5, this works fine.
+ */
+#ifdef __GNUC__
+#ifndef alloca
+#define alloca(n) __builtin_alloca(n)
+#endif
+#endif                         /* __GNUC__ */
+#ifdef sparc
+#define alloca(n) __builtin_alloca(n)
+#endif                         /* sparc */
+
+
+#endif /* __CSRIMALLOC_H__ */                  /* Do not add anything after this line */
diff --git a/hdrs/dbdefs.h b/hdrs/dbdefs.h
new file mode 100644 (file)
index 0000000..015a805
--- /dev/null
@@ -0,0 +1,349 @@
+/* dbdefs.h */
+
+
+#ifndef __DBDEFS_H
+#define __DBDEFS_H
+
+#include <stdio.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <time.h>
+#include "mushdb.h"
+#include "htab.h"
+#include "chunk.h"
+#include "bufferq.h"
+#include "division.h"
+
+extern int depth;
+
+extern dbref first_free;       /* pointer to free list */
+
+/*-------------------------------------------------------------------------
+ * Database access macros
+ */
+
+/* References an whole object */
+#define REFDB(x)        &db[x]
+
+#define Name(x)         (db[(x)].name)
+#define Flags(x)        (db[(x)].flags)
+#define Owner(x)        (db[(x)].owner)
+
+#define Location(x)     (db[(x)].location)
+#define Zone(x)         (db[(x)].zone)
+
+#define Contents(x) (db[(x)].contents)
+#define Next(x)     (db[(x)].next)
+#define Home(x)     (db[(x)].exits)
+#define Exits(x)    (db[(x)].exits)
+#define List(x)     (db[(x)].list)
+
+/* These are only for exits */
+#define Source(x)   (db[(x)].exits)
+#define Destination(x) (db[(x)].location)
+
+#define Locks(x)        (db[(x)].locks)
+
+#define CreTime(x)      (db[(x)].creation_time)
+#define ModTime(x)      (db[(x)].modification_time)
+
+#define AttrCount(x)    (db[(x)].attrcount)
+
+/* Moved from warnings.c because create.c needs it. */
+#define Warnings(x)      (db[(x)].warnings)
+
+#define Pennies(thing) (db[thing].penn)
+
+#define Parent(x)  (db[(x)].parent)
+
+/* Generic type check */
+#define Type(x)   (db[(x)].type)
+#define Typeof(x) (Type(x) & ~TYPE_MARKED)
+
+/* Check for a specific one */
+#define IsPlayer(x)     (!!(Typeof(x) & TYPE_PLAYER))
+#define IsRoom(x)       (!!(Typeof(x) & TYPE_ROOM))
+#define IsThing(x)      (!!(Typeof(x) & TYPE_THING))
+#define IsExit(x)       (!!(Typeof(x) & TYPE_EXIT))
+#define IsDivision(x)  (!!(Typeof(x) & TYPE_DIVISION))
+#define IsMasterDivision(x) (GoodObject(x) && IsDivision(x) && Division(x) == NOTHING)
+
+/* Was Destroyed() */
+#define IsGarbage(x)    (!!(Typeof(x) & TYPE_GARBAGE))
+#define Marked(x)       (!!(db[(x)].type & TYPE_MARKED))
+
+#define IS(thing,type,flag) \
+                     ((Typeof(thing) == type) && has_flag_by_name(thing,flag,type))
+
+#define GoodObject(x) ((x >= 0) && (x < db_top))
+
+/* Can guy talk? */
+#define Mute(x)                (has_flag_by_name(x, "MUTE", TYPE_THING | TYPE_PLAYER | TYPE_ROOM))
+#define IsMuted(x,y)   (Mute(x) || Mute(Location(x)))
+#define CanSpeak(x,y)  (!Mute(x) && (Admin(x) || (!Mute(y)  && eval_lock(x, y, Speech_Lock))))
+
+/******* Player toggles */
+#define Connected(x)    (IS(x, TYPE_PLAYER, "CONNECTED"))      /* 0x200 */
+#define Track_Money(x) (IS(x, TYPE_PLAYER, "TRACK_MONEY"))
+#define Inheritable(x) (IS(x, TYPE_PLAYER, "INHERITABLE"))
+#define ZMaster(x)      (IS(x, TYPE_PLAYER, "ZONE"))   /* 0x800 */
+#define Unregistered(x) (LEVEL(x) <= LEVEL_UNREGISTERED)
+#define Fixed(x)        (IS(Owner(x), TYPE_PLAYER, "FIXED"))
+#ifdef RPMODE_SYS
+#define RPMODE(x)      (IS(Owner(x), TYPE_PLAYER, "RPMODE"))
+#define Blind(x)       (IS(x, TYPE_PLAYER, "BLIND") && (ICRoom(Location(x)) || RPAPPROVED(Location(x))))
+#define ICRoom(x)      (IS(x, TYPE_ROOM, "ICFUNCS"))
+#define RPAPPROVED(x)  (IS(x, TYPE_THING, "RPAPPROVED"))
+#define Paralyzed(x)   (IS(x, TYPE_PLAYER, "PARALYZED") && (ICRoom(Location(x)) || RPAPPROVED(Location(x))))
+#define IsParalyzed(x)  (Paralyzed(x) || Paralyzed(Owner(x)))
+#else
+#define RPMODE(x)      (Fixed(x))
+#endif
+
+/* Flags that apply to players, and all their stuff,
+ * so check the Owner() of the object.
+ */
+
+#define Terse(x)        (IS(Owner(x), TYPE_PLAYER, "TERSE") || IS(x, TYPE_THING, "TERSE"))
+#define Myopic(x)       (IS(Owner(x), TYPE_PLAYER, "MYOPIC"))
+#define Nospoof(x)      (IS(Owner(x),TYPE_PLAYER,"NOSPOOF") || has_flag_by_name(x,"NOSPOOF",NOTYPE))
+#define Paranoid(x)      (IS(Owner(x),TYPE_PLAYER,"PARANOID") || has_flag_by_name(x,"PARANOID",NOTYPE))
+#define Gagged(x)       (IS(Owner(x), TYPE_PLAYER, "GAGGED"))
+#define ShowAnsi(x)     (IS(Owner(x), TYPE_PLAYER, "ANSI"))
+#define ShowAnsiColor(x) (IS(Owner(x), TYPE_PLAYER, "COLOR"))
+#define InProg(x)      (IS(x, TYPE_PLAYER, "INPROGRAM"))
+
+/******* Thing toggles */
+#define DestOk(x)       (IS(x, TYPE_THING, "DESTROY_OK"))
+#define NoLeave(x)      (IS(x, TYPE_THING, "NOLEAVE"))
+#define ThingListen(x)  (IS(x, TYPE_THING, "MONITOR"))
+#define ThingInhearit(x) \
+                        (IS(x, TYPE_THING, "LISTEN_PARENT"))   /* 0x80 */
+#define ThingZTel(x)            (IS(x, TYPE_THING, "Z_TEL"))
+
+/******* Room toggles */
+#define Floating(x)     (IS(x, TYPE_ROOM, "FLOATING")) /* 0x8 */
+#define Abode(x)        (IS(x, TYPE_ROOM, "ABODE"))    /* 0x10 */
+#define JumpOk(x)       (IS(x, TYPE_ROOM, "JUMP_OK"))  /* 0x20 */
+#define NoTel(x)        (IS(x, TYPE_ROOM, "NO_TEL"))   /* 0x40 */
+#define RoomListen(x)   (IS(x, TYPE_ROOM, "LISTENER")) /* 0x100 */
+#define RoomZTel(x)             (IS(x, TYPE_ROOM, "Z_TEL"))    /* 0x200 */
+#define RoomInhearit(x) (IS(x, TYPE_ROOM, "LISTEN_PARENT"))    /* 0x400 */
+
+#define Uninspected(x)  (IS(x, TYPE_ROOM, "UNINSPECTED"))      /* 0x1000 */
+
+#define ZTel(x) (ThingZTel(x) || RoomZTel(x))
+
+/******* Exit toggles */
+#define Cloudy(x)       (IS(x, TYPE_EXIT, "CLOUDY"))   /* 0x8 */
+
+/* Flags anything can have */
+
+#define Audible(x)      (has_flag_by_name(x, "AUDIBLE", NOTYPE))
+#define ChanUseFirstMatch(x) (has_flag_by_name(x, "CHAN_USEFIRSTMATCH", NOTYPE))
+#define ChownOk(x)      (has_flag_by_name(x, "CHOWN_OK", NOTYPE))
+#define Dark(x)         (has_flag_by_name(x, "DARK", NOTYPE))
+#define Debug(x)        (has_flag_by_name(x, "DEBUG", NOTYPE))
+#define EnterOk(x)      (has_flag_by_name(x, "ENTER_OK", NOTYPE))
+#define Going(x)        (has_flag_by_name(x, "GOING", NOTYPE))
+#define Going_Twice(x)  (has_flag_by_name(x, "GOING_TWICE", NOTYPE))
+#define Halted(x)       (has_flag_by_name(x, "HALT", NOTYPE))
+#define Haven(x)        (has_flag_by_name(x, "HAVEN", NOTYPE))
+#define Inherit(x)     (has_flag_by_name(x, "INHERIT", TYPE_THING|TYPE_EXIT|TYPE_ROOM))
+#define Light(x)        (has_flag_by_name(x, "LIGHT", NOTYPE))
+#define LinkOk(x)       (has_flag_by_name(x, "LINK_OK", NOTYPE))
+#define Mistrust(x)     (has_flag_by_name(x, "MISTRUST", TYPE_THING|TYPE_EXIT|TYPE_ROOM))
+#define NoCommand(x)    (has_flag_by_name(x, "NO_COMMAND", NOTYPE))
+#define NoWarn(x)       (has_flag_by_name(x, "NOWARN", NOTYPE))
+#define Opaque(x)       (has_flag_by_name(x, "OPAQUE", NOTYPE))
+#define Orphan(x)       (has_flag_by_name(x, "ORPHAN", NOTYPE))
+#define Puppet(x)       (has_flag_by_name(x, "PUPPET", TYPE_THING|TYPE_ROOM))
+#define Quiet(x)        (has_flag_by_name(x, "QUIET", NOTYPE))
+#define Safe(x)         (has_flag_by_name(x, "SAFE", NOTYPE))
+#define Sticky(x)       (has_flag_by_name(x, "STICKY", NOTYPE))
+#define Suspect(x)      (has_flag_by_name(x,"SUSPECT", NOTYPE))
+#define Transparented(x)      (has_flag_by_name(x, "TRANSPARENT", NOTYPE))
+#define Unfind(x)       (has_flag_by_name(x, "UNFINDABLE", NOTYPE))
+#define Verbose(x)      (has_flag_by_name(x, "VERBOSE", NOTYPE))
+#define Visual(x)       (has_flag_by_name(x, "VISUAL", NOTYPE))
+
+/* Attribute flags */
+#define AF_Internal(a) ((a)->flags & AF_INTERNAL)
+#define AF_Wizard(a) ((a)->flags & AF_PRIVILEGE)
+#define AF_Locked(a) ((a)->flags & AF_LOCKED)
+#define AF_Noprog(a) ((a)->flags & AF_NOPROG)
+#define AF_Mdark(a) ((a)->flags & AF_MDARK)
+#define AF_Private(a) ((a)->flags & AF_PRIVATE)
+#define AF_Nocopy(a) ((a)->flags & AF_NOCOPY)
+#define AF_Visual(a) ((a)->flags & AF_VISUAL)
+#define AF_Regexp(a) ((a)->flags & AF_REGEXP)
+#define AF_Case(a) ((a)->flags & AF_CASE)
+#define AF_Safe(a) ((a)->flags & AF_SAFE)
+#define AF_Command(a) ((a)->flags & AF_COMMAND)
+#define AF_Listen(a) ((a)->flags & AF_LISTEN)
+#define AF_Nodump(a) ((a)->flags & AF_NODUMP)
+#define AF_Listed(a) ((a)->flags & AF_LISTED)
+#define AF_Prefixmatch(a) ((a)->flags & AF_PREFIXMATCH)
+#define AF_Veiled(a) ((a)->flags & AF_VEILED)
+#define AF_Debug(a) ((a)->flags & AF_DEBUG)
+#define AF_Nearby(a) ((a)->flags & AF_NEARBY)
+#define AF_Public(a) ((a)->flags & AF_PUBLIC)
+#define AF_Mhear(a) ((a)->flags & AF_MHEAR)
+#define AF_Ahear(a) ((a)->flags & AF_AHEAR)
+
+/* Non-mortal checks */
+#define TC_God(x)  ((x) == GOD)
+#define God(x)         OOREF(x,TC_God(x),TC_God(ooref))
+#define TC_Director(x) (God(x) || (LEVEL(x) >= LEVEL_DIRECTOR && div_powover(x,x,"Privilege")))
+#define Director(x)    OOREF(x,TC_Director(x),TC_Director(ooref))
+#define TC_Admin(x)    (LEVEL(x) >= LEVEL_ADMIN && div_powover(x,x,"Privilege"))
+#define Admin(x)       OOREF(x,TC_Admin(x), TC_Admin(ooref))
+
+#define IsQuiet(x)      (Quiet(x) || Quiet(Owner(x)))
+#define AreQuiet(x,y)   (Quiet(x) || (Quiet(y) && (Owner(y) == x)))
+#define Mobile(x)       (IsPlayer(x) || IsThing(x) || IsDivision(x))
+#define Alive(x)        (IsPlayer(x) || Puppet(x) || \
+   (Audible(x) && atr_get_noparent(x,"FORWARDLIST")))
+/* Was Dark() */
+#define TC_DarkLegal(x)    (Dark(x) && (Admin(x) || !Alive(x)))
+#define DarkLegal(x)   OOREF(x,TC_DarkLegal(x), TC_DarkLegal(ooref))
+
+
+/* This is carefully ordered, from most to least likely. Hopefully. */
+#define CanEval(x,y)    (!(SAFER_UFUN) || !Admin(y) || God(x) || \
+        ((Director(x) || (Admin(x) && !Director(y))) && !God(y)))
+
+/* AF_PUBLIC overrides SAFER_UFUN */
+#define TC_CanEvalAttr(x,y,a) (CanEval(x,y) || AF_Public(a))
+#define CanEvalAttr(x,y,a)     OOREF(x,TC_CanEvalAttr(x,y,a), TC_CanEvalAttr(ooref,y,a))
+
+/* Note that this is a utility to determine the objects which may or may */
+/* not be controlled, rather than a strict check for the INHERIT flag */
+#define Owns(p,x) (Owner(p) == Owner(x))
+
+
+#define NoWarnable(x) (NoWarn(x) || NoWarn(Owner(x)))
+
+/* Ancestor_Parent() - returns appropriate ancestor object */
+#define Ancestor_Parent(x) (Orphan(x) ? NOTHING : \
+    (IsRoom(x) ? ANCESTOR_ROOM : \
+     (IsExit(x) ? ANCESTOR_EXIT : \
+      (IsPlayer(x) ? ANCESTOR_PLAYER : \
+       (IsThing(x) ? ANCESTOR_THING : NOTHING )))))
+
+
+/*--------------------------------------------------------------------------
+ * Other db stuff
+ */
+
+#ifdef RPMODE_SYS
+struct rplog_t {
+  BUFFERQ *bufferq;
+  char status;
+};
+#endif /* RPMODE_SYS */
+
+/** An object in the database.
+ *
+ */
+struct object {
+  const char *name;            /**< The name of the object */
+  /** An overloaded pointer.
+   * For things and players, points to container object.
+   * For exits, points to destination.
+   * For rooms, points to drop-to.
+   */
+  dbref location;
+  dbref contents;              /**< Pointer to first item */
+  /** An overloaded pointer.
+   * For things and players, points to home.
+   * For rooms, points to first exit.
+   * For exits, points to source room.
+   */
+  dbref exits;
+  dbref next;                  /**< pointer to next in contents/exits chain */
+  dbref parent;                        /**< pointer to parent object */
+  struct lock_list *locks;     /**< list of locks set on the object */
+  dbref owner;                 /**< who controls this object */
+  dbref zone;                  /**< zone master object number */
+  int penn;                    /**< number of pennies object contains */
+  int warnings;                        /**< bitflags of warning types */
+  time_t creation_time;                /**< Time/date of object creation */
+  /** Last modifiction time.
+   * For players, the number of failed logins.
+   * For other objects, the time/date of last modification to its attributes.
+   */
+  time_t modification_time;
+  const char *lastmod; /**< Last Modification */
+  int attrcount;               /**< Number of attribs on the object */
+  int type;                    /**< Object's type */
+  object_flag_type flags;      /**< Pointer to flag bit array */
+  DIVISION division;           /**< Division info for object */
+#ifdef RPMODE_SYS
+  struct rplog_t rplog;
+#endif /* RPMODE_SYS */
+  ALIST *list;                 /**< list of attributes on the object */
+};
+
+/** A structure to hold database statistics.
+ * This structure is used by get_stats() in wiz.c to group
+ * counts of various objects in the database.
+ */
+struct db_stat_info {
+  int total;   /**< Total count */
+  int players; /**< Player count */
+  int rooms;   /**< Room count */
+  int exits;   /**< Exit count */
+  int things;  /**< Thing count */
+  int divisions;       /**< Division count */
+  int channels; /**< Channel count */
+  int garbage; /**< Garbage count */
+};
+
+extern struct object *db;
+extern dbref db_top;
+
+extern void *get_objdata(dbref thing, const char *keybase);
+extern void *set_objdata(dbref thing, const char *keybase, void *data);
+extern void clear_objdata(dbref thing);
+extern void convert_object_powers(dbref, int); /* the code is in division.c.. 
+                                                   * but proto put here cause it uses the object struct
+                                                   */
+
+#define DOLIST(var, first)\
+    for((var) = (first); GoodObject((var)); (var) = Next(var))
+
+#define PUSH(thing, locative) \
+    ((Next(thing) = (locative)), (locative) = (thing))
+
+#define DOLIST_VISIBLE(var, first, player)\
+    for((var) = first_visible((player), (first)); GoodObject((var)); (var) = first_visible((player), Next(var)))
+
+/** A mail message.
+ * This structure represents a single mail message in the linked list
+ * of messages that comprises the mail database. Mail messages are
+ * stored in a doubly-linked list sorted by message recipient.
+ */
+struct mail {
+  struct mail *next;           /**< Pointer to next message */
+  struct mail *prev;           /**< Pointer to previous message */
+  dbref to;                    /**< Recipient dbref */
+  dbref from;                  /**< Sender's dbref */
+  time_t from_ctime;           /**< Sender's creation time */
+  chunk_reference_t msgid;     /**< Message text, compressed */
+  time_t time;                 /**< Message date/time */
+  unsigned char *subject;      /**< Message subject, compressed */
+  int read;                    /**< Bitflags of message status */
+};
+
+typedef struct mail MAIL;
+
+
+extern const char *EOD;
+
+#define SPOOF(player, cause, sw) \
+  if (SW_ISSET(sw, SWITCH_SPOOF) && (controls(player, cause) || Can_Nspemit(player))) \
+    player = cause;
+
+#endif                         /* __DBDEFS_H */
diff --git a/hdrs/dbio.h b/hdrs/dbio.h
new file mode 100644 (file)
index 0000000..b7b3182
--- /dev/null
@@ -0,0 +1,47 @@
+/**
+ * \file dbio.h
+ *
+ * \brief header files for functions for reading/writing database files
+ */
+
+#ifndef __DBIO_H
+#define __DBIO_H
+
+#include <setjmp.h>
+#include <stdio.h>
+
+extern jmp_buf db_err;
+
+/** Run a function, and jump if error */
+#define OUTPUT(fun) do { if ((fun) < 0) longjmp(db_err, 1); } while (0)
+
+
+/* Output */
+extern void putref(FILE * f, long int ref);
+extern void putstring(FILE * f, const char *s);
+extern void db_write_labeled_string(FILE * f, char const *label,
+                                   char const *value);
+extern void db_write_labeled_number(FILE * f, char const *label, int value);
+extern void db_write_labeled_dbref(FILE * f, char const *label, dbref value);
+extern void db_write_flag_db(FILE *);
+
+extern dbref db_write(FILE * f, int flag);
+extern int db_paranoid_write(FILE * f, int flag);
+
+/* Input functions */
+extern const char *getstring_noalloc(FILE * f);
+extern long getref(FILE * f);
+extern void db_read_this_labeled_string(FILE * f, const char *label,
+                                       char **val);
+extern void db_read_labeled_string(FILE * f, char **label, char **val);
+extern void db_read_this_labeled_number(FILE * f, const char *label, int *val);
+extern void db_read_labeled_number(FILE * f, char **label, int *val);
+extern void db_read_this_labeled_dbref(FILE * f, const char *label, dbref *val);
+extern void db_read_labeled_dbref(FILE * f, char **label, dbref *val);
+extern int load_flag_db(FILE *);
+
+extern void init_postconvert();
+
+extern dbref db_read(FILE * f);
+
+#endif
diff --git a/hdrs/division.h b/hdrs/division.h
new file mode 100644 (file)
index 0000000..8d89025
--- /dev/null
@@ -0,0 +1,323 @@
+#ifndef _DIVISION_H_
+#include "ptab.h"
+#define _DIVISION_H_
+
+/* LIMITS {{{1 */
+#define MAX_LEVEL      30
+#define DP_BYTES       ((ps_tab.powerbits >> 3)+1) /* Spots for powers (DP_BYTES * 8) == Max Powers. This is uped automatically */
+#define MAX_DIVREC     12 /* Max Division Command Recursion */
+
+/* PowerScope Levels {{{1 */
+#define YES            3
+#define        YESLTE          2
+#define        YESLT           1
+#define        NO              0
+
+/* PREDEFINED LEVELS {{{1 */
+#define LEVEL_GOD              30
+#define        LEVEL_DIRECTOR          29      /* This fills the spot for wizard status */
+#define LEVEL_ADMIN            28
+#define LEVEL_SYSCODER          27
+#define LEVEL_SYSBUILDER        26
+#define LEVEL_GENERALBUILDER    25
+#define LEVEL_EMPHEAD          24
+#define LEVEL_EMPADMIN          23
+#define LEVEL_EMPBUILDER        22
+#define LEVEL_RPADMIN           21
+#define LEVEL_BUILDER           20
+#define LEVEL_ICLEADER         19
+#define LEVEL_CLASS18           18
+#define LEVEL_CLASS17           17
+#define LEVEL_CLASS16           16
+#define LEVEL_TOPADMIRAL        15
+#define LEVEL_FLAGADMIRAL       14
+#define LEVEL_VICEADMIRAL       13
+#define LEVEL_REARADMIRAL       12
+#define LEVEL_COMMODORE         11
+#define LEVEL_CAPTAIN           10
+#define LEVEL_COMMANDER          9
+#define LEVEL_LTCOMMANDER        8
+#define LEVEL_LIEUTENANT         7
+#define LEVEL_JUNIORLIEUT        6
+#define LEVEL_ENSIGN             5
+#define LEVEL_MIDSHIPMAN         4
+#define LEVEL_PLAYER             3
+#define LEVEL_UNREGISTERED       2
+#define LEVEL_GUEST              1
+
+/* Old CobraMUSH PowerBits {{{1 - Kept for compatibility */
+#define POW_DIVISION            1      /* @DIVCREATE/@DIVDELETE */
+#define        POW_ATTACH              2       /* @ATTACH & @DETACH LTE */
+#define POW_ATTACH_LT          3       /* @ATTACH LT */
+#define POW_BCREATE            4       /* @PCREATE/BUILDER & @NEWPASS BUILDER */
+#define        POW_EMPOWER             5       /* @EMPOWER RANKS LT */
+#define POW_MODIFY             6       /* Full MODIFY */
+#define POW_MODIFY_LTE         7       /* LTE */
+#define        POW_MODIFY_LT           8       /* LT */
+#define POW_RERANK             9       /* RERANK LTE */
+#define POW_RERANK_LT          10      /* RERANKK LT */
+#define        POW_SEE_ALL             11      /* Full SeeAll */
+#define POW_SEE_ALL_LTE                12      /* SeeAll LTE */
+#define POW_SEE_ALL_LT         13      /* SeeAll LT */
+#define POW_SUMMON              14     /* Summon other players to your location */
+#define POW_SUMMON_LT           15     /* Summon players of lower class level */
+#define POW_JOIN                16     /* Join other players */
+#define POW_JOIN_LT             17     /* Join other players of lower class level */
+#define POW_ANNOUNCE            18     /* Power to use @ann, @gann and @dann */
+#define POW_PRIVILEGE           19     /* Expands the functionality of basic powerz */
+#define POW_TPORT_T            20      /* Tport Anything */
+#define POW_TPORT_T_LTE                21      /* Tport Anything LTE */
+#define POW_TPORT_T_LT         22      /* Tport Anything LT */
+#define        POW_TPORT_O             23      /* Tport Anywhere */
+#define        POW_TPORT_O_LTE         24      /* Tport Anywhere LTE */
+#define        POW_TPORT_O_LT          25      /* Tport Anywhere LT */
+#define POW_EMPIRE              26     /* Able to use empire related commands */
+                 /* 27 was a YV Only Power */
+#define POW_MANAGEMENT         28      /* ability to use game management commands */
+#define POW_CEMIT              29      /* Can @cemit */
+#define POW_NOQUOTA            30      /* Has no quota restrictions */
+#define POW_PCREATE            31      /* Can @pcreate */
+#define        POW_GFUNCS              32      /* Can add global @functions */
+#define POW_SEARCH             33      /* Can @search anything in divscope */
+#define        POW_SEARCH_LTE          34      /* Can @search anything LTE " */
+#define        POW_SEARCH_LT           35      /* Can @seach LT */
+#define POW_HALT               36      /* Can @halt in divscope */
+#define        POW_HALT_LTE            37      /* @halt LTE */
+#define        POW_HALT_LT             38      /* @halt LT */
+#define        POW_IDLE                39      /* no inactivity timeout */
+#define        POW_PS                  40      /* Can look at anyones queuein divscope */
+#define POW_PS_LTE             41      /* Can look at LTE queue */
+#define        POW_PS_LT               42      /* Can look at LT queue */
+#define        POW_QUEUE               43      /* queue limit of db_top */
+#define        POW_PEMIT               44      /* Can @pemit HAVEN players */
+#define        POW_PEMIT_LTE           45      /* @pemit LTE */
+#define        POW_PEMIT_LT            46      /* @pe LT */
+#define POW_CQUOTAS            47      /* Can change quotas in divscope */
+#define POW_CQUOTAS_LTE                48      /* LTE */
+#define        POW_CQUOTAS_LT          49      /* LT */
+#define        POW_LOGIN               50      /* Login anytime */
+#define POW_HIDE               51      /* Can @hide */
+#define        POW_NOPAY               52      /* need no money */
+#define POW_BUILD              53      /* Can use builder commands */
+#define        POW_LFINGERS            54      /* can grab stuff remotely */
+#define POW_LFINGERS_LTE       55      /* LTE */
+#define POW_LFINGERS_LT                56      /* LT */
+#define POW_BOOT               57      /* can @boot */
+#define POW_BOOT_LTE           58      /* LTE */
+#define POW_BOOT_LT            59      /* LT */
+#define POW_POLL               60      /* can use @poll */
+#define POW_LANY               61      /* Can @link to any room */
+#define POW_LANY_LTE           62      /* Can @link to any room LTE */
+#define POW_LANY_LT            63      /* Can @link to any room LT */
+#define POW_OANY               64      /* Can @open an exit from any room */
+#define        POW_OANY_LTE            65      /* LTE */
+#define POW_OANY_LT            66      /* LT */
+#define POW_CPRIV              67      /* Can use admin channels */
+#define POW_EANNOUNCE          68      /* @Eannounce */
+#define POW_DANNOUNCE          69      /* @DAnnounce */
+#define        POW_NEWPASS             70      /* @newpass */
+#define        POW_NEWPASS_LTE         71      /* LTE */
+#define        POW_NEWPASS_LT          72      /* LT */
+#define POW_VQUOTAS            73      /* Can @quota playerz */
+#define POW_VQUOTAS_LTE                74
+#define POW_VQUOTAS_LT         75
+#define POW_MAIL               76      /* Mail Administrator */
+#define POW_MAIL_LTE           77
+#define POW_MAIL_LT            78
+#define POW_COMBAT             79 /* Combat */
+#define POW_COMBAT_LTE         80
+#define POW_COMBAT_LT          81
+#define POW_PROG               82 /* @Program */
+#define POW_PROG_LTE           83
+#define POW_PROG_LT            84
+#define POW_PROGL              85 /* Prog Lock */
+#define POW_PROGL_LTE          86
+#define POW_PROGL_LT           87
+#define POW_POWLSET            88 /* @powerlevel */
+#define POW_POWLSET_LTE                89
+#define POW_POWLSET_LT         90
+#define POW_PASS_LOCKS         91      /* Pass all locks */
+#define POW_PASS_LOCKS_LTE     92
+#define POW_PASS_LOCKS_LT      93
+#define POW_PWHO               94 /* Can get PrivWho */
+#define POW_CHOWN               95 /* Can @chown/preserve */
+#define POW_CHOWN_LTE           96
+#define POW_CHOWN_LT            97
+#define POW_NSPEMIT            98 /* NsPemit */
+#define POW_NSPEMIT_LTE                99
+#define POW_NSPEMIT_LT         100
+#define POW_SQL                        101 /* SQL Ok power */
+#define POW_RCACHE             102 /* @READCACHE power */
+#define POW_RPEMIT             103 /* Can RPMode Emit */
+#define POW_RPCHAT             104 /* Can Chat in RPMODE */
+#define POW_RPTEL              105 /* RPTEL.. extension to tport powers to move something in RPMODE */
+#define POW_PUEBLO_SEND                106 /* Can send pueblo tags */
+#define POW_MANY_ATTRIBS       107 /* Can have more than max_attrs_per_obj */
+
+
+/* TYPEDEF {{{1 */
+typedef struct div_table DIVISION;
+typedef unsigned char * div_pbits;
+typedef struct power_alias POWER_ALIAS;
+typedef struct division_power_entry_t POWER;
+typedef struct power_space POWERSPACE;
+typedef struct power_group_t POWERGROUP;
+
+
+/* MACROS {{{1 */
+#define DIV(x) (((db[x].division.object == -1) && !IsPlayer(x)) ? DIV(Owner(x)) : db[x].division)
+#define SDIV(x)                (db[x].division)
+#define DPBITS(x)               (SDIV(x).dp_bytes)
+#define Division(x)    (db[x].division.object)
+#define        SLEVEL(x)       (SDIV(x).level)
+#define LEVEL(x)       (God(x) ? LEVEL_GOD : SDIV(x).level)
+#define div_cansee(x,y)        ((Owns(x,y) && LEVEL(x) >= LEVEL(y)) ||div_powover(x,y,"See_All"))
+
+#define DPBIT_SET(m,n)          (m[(n) >> 3] |= (1 << ((n) & 0x7)))
+#define DPBIT_CLR(m,n)          (m[(n) >> 3] &= ~(1 << ((n) & 0x7)))
+#define DPBIT_ISSET(m,n)        (m[(n) >> 3] & (1 << ((n) & 0x7)))
+#define DPBIT_ZERO(m)           memset(m, 0, DP_BYTES)
+#define powergroup_find(key)   (POWERGROUP *) ptab_find(ps_tab.powergroups, key)
+#define find_power(key)                (POWER *) ptab_find(ps_tab.powers, key)
+
+/* Check for DPBITS() before you do anything otherwise we might crash from old code */
+#define TAKE_DPBIT(m,n)         ((void) (DPBITS(m) && (SDIV(m).dp_bytes[(n) >> 3] &= ~(1 << ((n) & 0x7)))))
+#define GIVE_DPBIT(m,n)         if(!DPBITS(m)) \
+                                 DPBITS(m) = new_power_bitmask();    \
+                               ((void) (DPBITS(m) && (SDIV(m).dp_bytes[(n) >> 3] |= (1 << ((n) & 0x7)))))
+#define HAS_DPBIT(m,n)          (God(m) || DPBIT_ISSET(DPBITS(m), n))
+
+#define RESET_POWER(obj,power)  TAKE_DPBIT(obj, power->flag_yes); \
+                                TAKE_DPBIT(obj, power->flag_lte); \
+                                TAKE_DPBIT(obj, power->flag_lt);
+#define RESET_POWER_DP(m,power) if(m) { \
+                                  DPBIT_CLR(m, power->flag_yes); \
+                                  DPBIT_CLR(m, power->flag_lte); \
+                                  DPBIT_CLR(m, power->flag_lt); \
+                                 }
+/* toggle, is 1 - on, 0 - off
+   marker, is 1 - wiz, 0 - royalty
+   thing, is the object receiving the effects of marker powerz
+   */
+#define marker_powers(t, m, o) SLEVEL(o) = m ? LEVEL_DIRECTOR : LEVEL_ADMIN ; 
+                     //         division_powerlevel_set(GOD, o, (t ? (m ? PL_DIRECTOR : PL_ROYALTY) : PL_PLAYER), 1);  
+
+
+/* power over checks {{{1 */
+#define POWC(x)         int x(int, dbref, dbref)
+POWC(powc_levchk);
+POWC(powc_self);
+POWC(powc_levchk_lte);
+POWC(powc_levset);
+POWC(powc_powl_set);
+POWC(powc_bcreate);
+
+/* STRUCTS {{{1 */
+
+struct power_space {
+  PTAB *powers; /* Point to powers ptab */
+  PTAB *powergroups; /* Point to powergroups ptab */
+  int powerbits; /* current length of powerbits */
+  char _Read_Powers_; /* Signify we actually read powers */
+  div_pbits bits_taken; /* Set of all bits taken in the power array */
+};
+
+struct power_group_t {
+  const char *name; /* power group name */
+  div_pbits max_powers; /* Max setting for powers in power group */
+  div_pbits auto_powers; /* auto settings for powers in power group */
+};
+
+struct power_group_list {
+   struct power_group_t *power_group;
+   struct power_group_list *next;
+};
+
+/* Division Struct on each object */
+struct div_table {
+    dbref object;              /* division object */
+    int level;                 /* rank in division system(classheld in an attribute) */
+    div_pbits dp_bytes;                /* division flags/powerz */
+    struct power_group_list *powergroups;
+};
+
+/* old powers conversion table */
+struct old_division_power_entry_t {
+    const char *name;          /* power name */
+    int flag_yes;              /* yes flag */
+    int flag_lte;              /* yeslte */
+    int flag_lt;               /* yeslt */
+};
+
+/* new powers */
+struct division_power_entry_t {
+  const char *name; /* power name */
+  int (*powc_chk)();
+  int flag_yes;
+  int flag_lte;
+  int flag_lt;
+};
+
+/* Pre-Defined Power Struct */
+struct new_division_power_entry_t  {
+  const char *name;
+  const char *type;
+};
+
+/* Pre-Defined PowerGroup Struct */
+struct powergroup_text_t {
+   const char *name;
+   const char *max_powers;
+   const char *auto_powers;
+};
+
+/* Built-in Alias Table */
+struct power_alias {
+  const char *alias;
+  const char *realname;
+};
+
+/* PennMUSH Conversion Ptab */
+struct convold_ptab {
+  const char *Name;
+  int Op; 
+};
+
+/* Declared External Variables {{{1 */
+extern struct old_division_power_entry_t power_list[];
+extern POWERSPACE ps_tab; 
+
+
+/* PROTOTYPES {{{1 */
+extern void division_set(dbref, dbref, const char *);
+extern void division_empower(dbref, dbref, const char *);
+extern int division_level(dbref, dbref, int);
+extern const char *division_list_powerz(dbref,int);
+extern int div_powover(dbref, dbref, const char *);
+extern int div_inscope(dbref, dbref);
+extern dbref create_div(dbref, const char *);
+extern int check_power_yescode(div_pbits, POWER *);
+extern int yescode_i(char *);
+extern int dpbit_match(div_pbits, div_pbits);
+extern char *yescode_str(int);
+extern div_pbits string_to_dpbits(const char *powers);
+extern void decompile_powers(dbref player, dbref thing, const char *name);
+extern void init_powers();
+extern div_pbits new_power_bitmask();
+extern char power_is_zero(div_pbits pbits, int bytes);
+extern void do_power_cmd(dbref, const char *, const char *, const char *);
+extern void powers_read_all(FILE *);
+extern void power_write_all(FILE *);
+extern POWER *has_power(dbref object, const char *name);
+extern div_pbits convert_old_cobra_powers(unsigned char *dp_bytes);
+extern char *powergroups_list(dbref, char);
+extern char *powergroups_list_on(dbref, char);
+extern void powergroup_db_set(dbref, dbref, const char *, char);
+extern POWER *add_power_type(const char *, const char *);
+extern void do_list_powers(dbref, const char *);
+extern char *list_all_powers(dbref, const char *);
+extern void add_to_div_exit_path(dbref, dbref);
+
+/* vim:ts=8 fdm=marker 
+ */
+#endif                         /* _DIVISION_H_ */
diff --git a/hdrs/extchat.h b/hdrs/extchat.h
new file mode 100644 (file)
index 0000000..1867333
--- /dev/null
@@ -0,0 +1,269 @@
+/*------------------------------------------------------------------
+ * Header file for Javelin's extended @chat system
+ * Based on the Battletech MUSE comsystem ported to PennMUSH by Kalkin
+ *
+ * Why:
+ *  In the old system, channels were represented by bits set in a
+ *  4-byte int on the db object. This had disadvantages - a limit
+ *  of 32 channels, and players could find themselves on null channels.
+ *  In addition, the old system required recompiles to permanently
+ *  add channels, since the chaninfo was in the source.
+ * How:
+ *  Channels are a structure in a linked list.
+ *  Each channel stores a whole bunch of info, including who's
+ *  on it.
+ *  We read/write this list using a chatdb file.
+ *  We also maintain a linked list of channels that the user is
+ *   connected to on the db object, which we set up at load time.
+ *
+ * User interface:
+ * @chat channel = message
+ * +channel message
+ * @channel/on channel [= player] (or @channel channel = on)  do_channel()
+ * @channel/off channel [= player] do_channel()
+ * @channel/who channel do_channel()
+ * @channel/title channel=title do_chan_title()
+ * @channel/list do_chan_list()
+ * @channel/add channel do_chan_admin()
+ * @channel/priv channel = <privlist>  do_chan_admin()
+ *  Privlist being: director, admin, private, moderated, etc.
+ * @channel/joinlock channel = lock
+ * @channel/speaklock channel = lock
+ * @channel/modlock channel = lock
+ * @channel/delete channel
+ * @channel/quiet channel = yes/no
+ * @channel/wipe channel
+ * @channel/buffer channel = <maxlines>
+ * @channel/recall channel [= <lines>]
+ *
+ *------------------------------------------------------------------*/
+
+#ifndef __EXTCHAT_H
+#define __EXTCHAT_H
+
+#ifdef CHAT_SYSTEM
+
+#include "boolexp.h"
+#include "bufferq.h"
+
+#define CU_TITLE_LEN 80
+
+/** A channel user.
+ * This structure represents an object joined to a chat channel.
+ * Each chat channel maintains a linked list of users.
+ */
+struct chanuser {
+  dbref who;                   /**< Dbref of joined object */
+  long int type;               /**< Bitflags for this user */
+  char title[CU_TITLE_LEN];    /**< User's channel title */
+  struct chanuser *next;       /**< Pointer to next user in list */
+};
+
+/* Flags and macros for channel users */
+#define CU_QUIET    0x1                /* Do not hear connection messages */
+#define CU_HIDE     0x2                /* Do not appear on the user list */
+#define CU_GAG      0x4                /* Do not hear any messages */
+#define CU_DEFAULT_FLAGS 0x0
+
+/* channel_broadcast flags */
+#define CB_CHECKQUIET 0x1      /* Check for quiet flag on recipients */
+#define CB_NOSPOOF    0x2      /* Use nospoof emits */
+#define CB_PRESENCE   0x4      /* This is a presence message, not sound */
+
+#define CUdbref(u) ((u)->who)
+#define CUtype(u) ((u)->type)
+#define CUtitle(u) ((u)->title)
+#define CUnext(u) ((u)->next)
+#define Chanuser_Quiet(u)       (CUtype(u) & CU_QUIET)
+#define Chanuser_Hide(u) ((CUtype(u) & CU_HIDE) || (IsPlayer(CUdbref(u)) && hidden(CUdbref(u))))
+#define Chanuser_Gag(u) (CUtype(u) & CU_GAG)
+
+/* This is a chat channel */
+#define CHAN_NAME_LEN 31
+#define CHAN_TITLE_LEN 256
+/** A chat channel.
+ * This structure represents a MUSH chat channel. Channels are organized
+ * into a sorted linked list.
+ */
+struct channel {
+  char name[CHAN_NAME_LEN];    /**< Channel name */
+  char title[CHAN_TITLE_LEN];  /**< Channel description */
+  long int type;               /**< Channel flags */
+  long int cost;               /**< What it cost to make this channel */
+  long int creator;            /**< This is who paid the cost for the channel */
+  long int cobj;               /**< Channel object or #-1 */
+  long int num_users;          /**< Number of connected users */
+  long int max_users;          /**< Maximum allocated users */
+  struct chanuser *users;      /**< Linked list of current users */
+  long int num_messages;       /**< How many messages handled by this chan since startup */
+  boolexp joinlock;    /**< Who may join */
+  boolexp speaklock;   /**< Who may speak */
+  boolexp modifylock;  /**< Who may change things and boot people */
+  boolexp seelock;     /**< Who can see this in a list */
+  boolexp hidelock;    /**< Who may hide from view */
+  struct channel *next;                /**< Next channel in linked list */
+  BUFFERQ *bufferq;            /**< Pointer to channel recall buffer queue */
+};
+
+/** A list of channels on an object.
+ * This structure is a linked list of channels that is associated
+ * with each object
+ */
+struct chanlist {
+  CHAN *chan;                  /**< Channel data */
+  struct chanlist *next;       /**< Next channel in list */
+};
+
+#define Chanlist(x) ((struct chanlist *)get_objdata(x, "CHANNELS"))
+#define s_Chanlist(x, y) set_objdata(x, "CHANNELS", (void *)y)
+
+/** A structure for passing channel data to notify_anything */
+struct na_cpass {
+  CHANUSER *u;           /**< Pointer to channel user */
+  int checkquiet;          /**< Should quiet property be checked? */
+};
+
+
+/* Channel type flags and macros */
+#define CHANNEL_PLAYER  0x1    /* Players may join */
+#define CHANNEL_OBJECT  0x2    /* Objects may join */
+#define CHANNEL_DISABLED 0x4   /* Channel is turned off */
+#define CHANNEL_QUIET   0x8    /* No broadcasts connect/disconnect */
+#define CHANNEL_ADMIN   0x10   /* Admins only */
+#define CHANNEL_DIRECTOR 0x20  /* Directors only */
+#define CHANNEL_CANHIDE 0x40   /* Can non-DARK players hide here? */
+#define CHANNEL_OPEN    0x80   /* Can you speak if you're not joined? */
+#define CHANNEL_NOTITLES 0x100 /* Don't show titles of speakers */
+#define CHANNEL_NONAMES 0x200  /* Don't show names of speakers */
+#define CHANNEL_NOCEMIT 0x400  /* Disallow @cemit */
+#define CHANNEL_COBJ   0x800  /* Channel with a channel object */
+#define CHANNEL_INTERACT       0x1000  /* Filter channel output through interactions */
+#define CHANNEL_DEFAULT_FLAGS   (CHANNEL_PLAYER)
+#define CL_JOIN 0x1
+#define CL_SPEAK 0x2
+#define CL_MOD 0x4
+#define CL_SEE 0x8
+#define CL_HIDE 0x10
+#define CHANNEL_COST (options.chan_cost)
+#define MAX_PLAYER_CHANS (options.max_player_chans)
+#define MAX_CHANNELS (options.max_channels)
+
+const char *ChanObjName _((CHAN *c));
+int ChanObjCheck _((CHAN *c));
+#define ChanName(c) ((c)->name)
+#define ChanObj(c) ((c)->cobj)
+#define ChanType(c) ((c)->type)
+#define ChanTitle(c) ((c)->title)
+#define ChanCreator(c) ((c)->creator)
+#define ChanObj(c) ((c)->cobj)
+#define ChanCost(c) ((c)->cost)
+#define ChanNumUsers(c) ((c)->num_users)
+#define ChanMaxUsers(c) ((c)->max_users)
+#define ChanUsers(c) ((c)->users)
+#define ChanNext(c) ((c)->next)
+#define ChanNumMsgs(c) ((c)->num_messages)
+#define ChanJoinLock(c) ((c)->joinlock)
+#define ChanSpeakLock(c) ((c)->speaklock)
+#define ChanModLock(c) ((c)->modifylock)
+#define ChanSeeLock(c) ((c)->seelock)
+#define ChanHideLock(c) ((c)->hidelock)
+#define ChanBufferQ(c) ((c)->bufferq)
+#define Channel_Quiet(c)        (ChanType(c) & CHANNEL_QUIET)
+#define Channel_Open(c) (ChanType(c) & CHANNEL_OPEN)
+#define Channel_Object(c) (ChanType(c) & CHANNEL_OBJECT)
+#define Channel_Player(c) (ChanType(c) & CHANNEL_PLAYER)
+#define Channel_Disabled(c) (ChanType(c) & CHANNEL_DISABLED)
+#define Channel_Director(c) (ChanType(c) & CHANNEL_DIRECTOR)
+#define Channel_Admin(c) (ChanType(c) & CHANNEL_ADMIN)
+#define Channel_CanHide(c) (ChanType(c) & CHANNEL_CANHIDE)
+#define Channel_NoTitles(c) (ChanType(c) & CHANNEL_NOTITLES)
+#define Channel_NoNames(c) (ChanType(c) & CHANNEL_NONAMES)
+#define Channel_NoCemit(c) (ChanType(c) & CHANNEL_NOCEMIT)
+#define Channel_Interact(c) (ChanType(c) & CHANNEL_INTERACT)
+#define Chan_Ok_Type(c,o) \
+        ((ChanObj(c) == o) || (IsPlayer(o) && Channel_Player(c)) || \
+         (IsThing(o) && Channel_Object(c)))
+#define Chan_Can(p,t) \
+     (!(t & CHANNEL_DISABLED) && (!(t & CHANNEL_DIRECTOR) || Director(p)) || \
+      (!(t & CHANNEL_ADMIN) || Admin(p) || div_powover(p,p,"Chat")))
+/* Who can change channel privileges to type t */
+#define Chan_Can_Priv(p,t,c) (!((t & CHANNEL_COBJ) && !(c & CHANNEL_COBJ)) && !(!(t & CHANNEL_COBJ) && (c & CHANNEL_COBJ)) && Chan_Can(p,t))
+#define Chan_Can_Access(c,p) (Chan_Can(p,ChanType(c)))
+#define Chan_Can_Join(c,p) \
+     (Chan_Can_Access(c,p) && \
+      (eval_chan_lock(c,p,CLOCK_JOIN)))
+#define Chan_Can_Speak(c,p) \
+     (Chan_Can_Access(c,p) && \
+      (eval_chan_lock(c,p, CLOCK_SPEAK)))
+#define Chan_Can_Cemit(c,p) \
+     (!Channel_NoCemit(c) && Chan_Can_Speak(c,p))
+#define Chan_Can_Modify(c,p) \
+     ((ChanCreator(c) == (p) || Director(p)) ||  \
+     (!Guest(p) && Chan_Can_Access(c,p) && \
+      (eval_chan_lock(c,p,CLOCK_MOD))))
+#define Chan_Can_See(c,p) \
+     ((Admin(p) || See_All(p)) || (Chan_Can_Access(c,p) && \
+                                (eval_chan_lock(c,p,CLOCK_SEE))))
+#define Chan_Can_Hide(c,p) \
+     (Can_Hide(p) || (Channel_CanHide(c) && Chan_Can_Access(c,p) && \
+                     (eval_chan_lock(c,p,CLOCK_HIDE))))
+#define Chan_Can_Nuke(c,p) (ChanCreator(c) == (p) || div_powover(p, ChanCreator(c), "Chat"))
+#define Chan_Can_Decomp(c,p) (See_All(p) || (ChanCreator(c) == (p)))
+
+
+
+     /* For use in channel matching */
+enum cmatch_type { CMATCH_NONE, CMATCH_EXACT, CMATCH_PARTIAL, CMATCH_AMBIG };
+#define CMATCHED(i) (((i) == CMATCH_EXACT) | ((i) == CMATCH_PARTIAL))
+
+     /* Some globals */
+extern int num_channels;
+extern void WIN32_CDECL channel_broadcast
+  (CHAN *channel, dbref player, int flags, const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 4, 5)));
+extern CHANUSER *onchannel(dbref who, CHAN *c);
+extern void init_chatdb(void);
+extern int load_chatdb(FILE * fp);
+extern int save_chatdb(FILE * fp);
+extern void do_cemit
+  (dbref player, const char *name, const char *msg, int noisy);
+extern void do_chan_user_flags
+  (dbref player, char *name, const char *isyn, int flag, int silent);
+extern void do_chan_wipe(dbref player, const char *name);
+extern void do_chan_lock
+  (dbref player, const char *name, const char *lockstr, int whichlock);
+extern void do_chan_what(dbref player, const char *partname);
+extern void do_chan_desc(dbref player, const char *name, const char *title);
+extern void do_chan_title(dbref player, const char *name, const char *title);
+extern void do_chan_recall(dbref player, const char *name, char *lineinfo[],
+                          int quiet);
+extern void do_chan_buffer(dbref player, const char *name, const char *lines);
+extern void init_chat(void);
+extern void do_channel
+  (dbref player, const char *name, const char *target, const char *com);
+extern void do_chat(dbref player, CHAN *chan, const char *arg1);
+extern void do_chan_admin
+  (dbref player, char *name, const char *perms, int flag);
+extern enum cmatch_type find_channel(const char *p, CHAN **chan, dbref player);
+extern enum cmatch_type find_channel_partial(const char *p, CHAN **chan,
+                                            dbref player);
+extern void do_channel_list(dbref player, const char *partname);
+extern int do_chat_by_name
+  (dbref player, const char *name, const char *msg, int source);
+extern void do_chan_decompile(dbref player, const char *name, int brief);
+extern void do_chan_chown(dbref player, const char *name, const char *newowner);
+extern const char *channel_description(dbref player);
+
+enum clock_type { CLOCK_JOIN, CLOCK_SPEAK, CLOCK_SEE, CLOCK_HIDE, CLOCK_MOD };
+extern int eval_chan_lock(CHAN *c, dbref p, enum clock_type type);
+
+/** Ways to match channels by partial name */
+enum chan_match_type {
+  PMATCH_ALL,  /**< Match all channels */
+  PMATCH_OFF,  /**< Match channels user isn't on */
+  PMATCH_ON    /**< Match channels user is on */
+};
+
+
+#endif                         /* CHAT_SYSTEM */
+#endif                         /* __EXTCHAT_H */
diff --git a/hdrs/externs.h b/hdrs/externs.h
new file mode 100644 (file)
index 0000000..896ca6d
--- /dev/null
@@ -0,0 +1,676 @@
+/**
+ * \file externs.h
+ *
+ * \brief Header file for external functions called from many source files.
+ *
+ *
+ */
+
+
+#ifndef __EXTERNS_H
+#define __EXTERNS_H
+/* Get the time_t definition that we use in prototypes here */
+#include <time.h>
+#ifdef I_LIBINTL
+#include <libintl.h>
+#endif
+#if defined(HAS_GETTEXT) && !defined(DONT_TRANSLATE)
+/** Macro for a translated string */
+#define T(str) gettext(str)
+/** Macro to note that a string has a translation but not to translate */
+#define N_(str) gettext_noop(str)
+#else
+#define T(str) str
+#define N_(str) str
+#endif
+#include "config.h"
+#include "copyrite.h"
+#include "compile.h"
+#include "mushtype.h"
+#include "dbdefs.h"
+#include "confmagic.h"
+#ifndef HAS_STRCASECMP
+#ifdef WIN32
+#define strcasecmp(s1,s2) _stricmp((s1), (s2))
+#define strncasecmp(s1,s2,n) _strnicmp((s1), (s2), (n))
+#else
+extern int strcasecmp(const char *s1, const char *s2);
+extern int strncasecmp(const char *s1, const char *s2, size_t n);
+#endif
+#endif
+
+/* General Messages */
+#define MSG_HUH        T("Huh? (Type \"Help\" for Help.)") 
+
+/* these symbols must be defined by the interface */
+extern time_t mudtime;
+
+#define FOPEN_READ "rb"             /**< Arguments to fopen when reading */
+#define FOPEN_WRITE "wb"     /**< Arguments to fopen when writing */
+
+extern int shutdown_flag;      /* if non-zero, interface should shut down */
+extern void emergency_shutdown(void);
+extern void boot_desc(DESC *d);        /* remove a player */
+extern DESC *player_desc(dbref player);        /* find descriptors */
+extern DESC *inactive_desc(dbref player);      /* find descriptors */
+extern DESC *port_desc(int port);      /* find descriptors */
+extern void WIN32_CDECL flag_broadcast(const char *flag1,
+                                      const char *flag2, const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 3, 4)));
+
+extern void raw_notify(dbref player, const char *msg);
+extern void notify_list(dbref speaker, dbref thing, const char *atr,
+                       const char *msg, int flags);
+extern dbref short_page(const char *match);
+extern dbref visible_short_page(dbref player, const char *match);
+extern void do_doing(dbref player, const char *message);
+
+/* the following symbols are provided by game.c */
+extern void process_command(dbref player, char *command,
+                           dbref cause, dbref realcause, int from_port);
+extern void init_qids();
+extern int init_game_dbs(void);
+extern void init_game_postdb(const char *conf);
+extern void init_game_config(const char *conf);
+extern void dump_database(void);
+extern void NORETURN mush_panic(const char *message);
+extern void NORETURN mush_panicf(const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 1, 2)));
+extern char *scan_list(dbref player, char *command);
+
+
+#ifdef WIN32
+/* From timer.c */
+extern void init_timer(void);
+#endif                         /* WIN32 */
+
+/* From attrib.c */
+extern dbref atr_on_obj;
+
+/* From bsd.c */
+extern FILE *connlog_fp;
+extern FILE *checklog_fp;
+extern FILE *wizlog_fp;
+extern FILE *tracelog_fp;
+extern FILE *cmdlog_fp;
+extern int restarting;
+#ifdef SUN_OS
+extern int f_close(FILE * file);
+/** SunOS fclose macro */
+#define fclose(f) f_close(f);
+#endif
+extern int hidden(dbref player);
+extern dbref guest_to_connect(dbref player);
+void dump_reboot_db(void);
+void close_ssl_connections(void);
+char *least_idle_ip(dbref player);
+char *least_idle_hostname(dbref player);
+extern int do_command(DESC *d, char *command);
+
+/* sql.c */
+extern void sql_shutdown(void);
+
+/* The #defs for our notify_anything hacks.. Errr. Functions */
+#define NA_NORELAY      0x0001 /**< Don't relay sound */
+#define NA_NOENTER      0x0002 /**< No newline at end */
+#define NA_NOLISTEN     0x0004 /**< Implies NORELAY. Sorta. */
+#define NA_NOPENTER     0x0010 /**< No newline, Pueblo-stylee */
+#define NA_PONLY        0x0020 /**< Pueblo-only */
+#define NA_PUPPET       0x0040 /**< Ok to puppet */
+#define NA_PUPPET2      0x0080 /**< Message to a player from a puppet */
+#define NA_MUST_PUPPET  0x0100 /**< Ok to puppet even in same room */
+#define NA_INTER_HEAR   0x0200 /**< Message is auditory in nature */
+#define NA_INTER_SEE    0x0400 /**< Message is visual in nature */
+#define NA_INTER_PRESENCE  0x0800      /**< Message is about presence */
+#define NA_NOSPOOF        0x1000       /**< Message comes via a NO_SPOOF object. */
+#define NA_PARANOID       0x2000       /**< Message comes via a PARANOID object. */
+#define NA_NOPREFIX       0x4000       /**< Don't use @prefix when forwarding */
+#define NA_INTER_LOCK  0x10000         /**< Message subject to @lock/interact even if not otherwise marked */
+#define NA_INTERACTION  (NA_INTER_HEAR|NA_INTER_SEE|NA_INTER_PRESENCE|NA_INTER_LOCK)   /**< Message follows interaction rules */
+#define NA_SPOOF        0x8000 /**< @ns* message, overrides NOSPOOF */
+#define NA_EVALONCONTACT 0x10000
+
+/** A notify_anything lookup function type definition */
+typedef dbref (*na_lookup) (dbref, void *);
+extern void notify_anything(dbref speaker, na_lookup func,
+                           void *fdata,
+                           char *(*nsfunc) (dbref,
+                                            na_lookup func,
+                                            void *, int), int flags,
+                           const char *message);
+extern void notify_anything_format(dbref speaker, na_lookup func,
+                                  void *fdata,
+                                  char *(*nsfunc) (dbref,
+                                                   na_lookup func,
+                                                   void *, int), int flags,
+                                  const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 6, 7)));
+extern void notify_anything_loc(dbref speaker, na_lookup func,
+                               void *fdata,
+                               char *(*nsfunc) (dbref,
+                                                na_lookup func,
+                                                void *, int), int flags,
+                               const char *message, dbref loc);
+extern dbref na_one(dbref current, void *data);
+extern dbref na_next(dbref current, void *data);
+extern dbref na_loc(dbref current, void *data);
+extern dbref na_jloc(dbref current, void *data);
+extern dbref na_nextbut(dbref current, void *data);
+extern dbref na_except(dbref current, void *data);
+extern dbref na_except2(dbref current, void *data);
+extern dbref na_exceptN(dbref current, void *data);
+extern dbref na_channel(dbref current, void *data);
+extern char *ns_esnotify(dbref speaker, na_lookup func, void *fdata, int para);
+
+/** Basic 'notify player with message */
+#define notify(p,m)           notify_anything(orator, na_one, &(p), NULL, 0, m)
+/** Notify puppet with message, even if owner's there */
+#define notify_must_puppet(p,m)           notify_anything(orator, na_one, &(p), NULL, NA_MUST_PUPPET, m)
+/** Notify player with message, as if from somethign specific */
+#define notify_by(t,p,m)           notify_anything(t, na_one, &(p), NULL, 0, m)
+/** Notfy player with message, but only puppet propagation */
+#define notify_noecho(p,m)    notify_anything(orator, na_one, &(p), NULL, NA_NORELAY | NA_PUPPET, m)
+/** Notify player with message if they're not set QUIET */
+#define quiet_notify(p,m)     if (!IsQuiet(p)) notify(p,m)
+extern void notify_format(dbref player, const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 2, 3)));
+
+/* From compress.c */
+/* Define this to get some statistics on the attribute compression
+ * in @stats/tables. Only for word-based compression (COMPRESSION_TYPE 3 or 4
+ */
+/* #define COMP_STATS /* */
+#if (COMPRESSION_TYPE != 0)
+extern unsigned char *
+compress(char const *s)
+  __attribute_malloc__;
+    extern char *uncompress(unsigned char const *s);
+    extern char *safe_uncompress(unsigned char const *s) __attribute_malloc__;
+#else
+extern char ucbuff[];
+#define init_compress(f) 0
+#define compress(s) ((unsigned char *)strdup(s))
+#define uncompress(s) (strcpy(ucbuff, (char *) s))
+#define safe_uncompress(s) (strdup((char *) s))
+#endif
+
+/* From cque.c */
+struct eval_context {
+  char *wenv[10];                 /**< working environment (%0-%9) */
+  char renv[NUMQ][BUFFER_LEN];    /**< working registers q0-q9,qa-qz */
+  char *wnxt[10];                 /**< environment to shove into the queue */
+  char *rnxt[NUMQ];               /**< registers to shove into the queue */
+  int process_command_port;   /**< port number that a command came from */
+  dbref cplr;                     /**< initiating player */
+  char ccom[BUFFER_LEN];          /**< raw command */
+  char ucom[BUFFER_LEN];      /**< evaluated command */
+  int break_called;           /**< Has the break command been called? */
+  char break_replace[BUFFER_LEN];  /**< What to replace the break with */
+};
+
+typedef struct eval_context EVAL_CONTEXT;
+extern EVAL_CONTEXT global_eval_context;
+/*
+extern char *wenv[10], renv[NUMQ][BUFFER_LEN];
+extern char *wnxt[10], *rnxt[NUMQ];
+*/
+#ifdef _SWMP_
+extern int sql_env[2]; /* Sql Environment */
+#endif
+extern void do_second(void);
+extern int do_top(int ncom);
+extern void do_halt(dbref owner, const char *ncom, dbref victim);
+extern void parse_que(dbref player, const char *command, dbref cause);
+extern void div_parse_que(dbref division, const char *command, dbref called_division, dbref player);
+extern int queue_attribute_base
+  (dbref executor, const char *atrname, dbref enactor, int noparent);
+/** Queue the code in an attribute, including parent objects */
+#define queue_attribute(a,b,c) queue_attribute_base(a,b,c,0)
+/** Queue the code in an attribute, excluding parent objects */
+#define queue_attribute_noparent(a,b,c) queue_attribute_base(a,b,c,1)
+extern void dequeue_semaphores(dbref thing, char const *aname, int count,
+                              int all, int drain);
+extern void shutdown_queues(void);
+extern void do_hourly(void);
+
+
+/* From create.c */
+extern dbref do_dig(dbref player, const char *name, char **argv, int tport);
+extern dbref do_create(dbref player, char *name, int cost);
+extern dbref do_real_open(dbref player, const char *direction,
+                         const char *linkto, dbref pseudo);
+extern void do_open(dbref player, const char *direction, char **links);
+extern void do_link(dbref player, const char *name, const char *room_name,
+                   int preserve);
+extern void do_unlink(dbref player, const char *name);
+extern dbref do_clone(dbref player, char *name, char *newname, int preserve);
+extern void copy_zone(dbref executor, dbref zmo);
+
+/* From game.c */
+extern void report(void);
+extern int Hearer(dbref thing);
+extern int Commer(dbref thing);
+extern int Listener(dbref thing);
+extern dbref orator;
+extern dbref ooref;
+extern dbref tooref;
+
+int parse_chat(dbref player, char *command);
+extern void fork_and_dump(int forking);
+void reserve_fd(void);
+void release_fd(void);
+
+
+/* From look.c */
+/** Enumeration of types of looks that can be performed */
+enum look_type { LOOK_NORMAL, LOOK_TRANS, LOOK_AUTO, LOOK_CLOUDYTRANS,
+  LOOK_CLOUDY
+};
+extern void look_room(dbref player, dbref loc, enum look_type style);
+extern void do_look_around(dbref player);
+extern void do_look_at(dbref player, const char *name, int key);
+
+/* From memcheck.c */
+extern void add_check(const char *ref);
+extern void del_check(const char *ref);
+extern void list_mem_check(dbref player);
+extern void log_mem_check(void);
+
+/* From move.c */
+extern void enter_room(dbref player, dbref loc, int nomovemsgs);
+extern int can_move(dbref player, const char *direction);
+/** Enumeration of types of movements that can be performed */
+enum move_type { MOVE_NORMAL, MOVE_GLOBAL, MOVE_ZONE };
+extern void do_move(dbref player, const char *direction, enum move_type type);
+extern void moveto(dbref what, dbref where);
+extern void safe_tel(dbref player, dbref dest, int nomovemsgs);
+extern int global_exit(dbref player, const char *direction);
+extern int remote_exit(dbref loc, const char *direction);
+extern void move_wrapper(dbref player, const char *command);
+extern void do_follow(dbref player, const char *arg);
+extern void do_unfollow(dbref player, const char *arg);
+extern void do_desert(dbref player, const char *arg);
+extern void do_dismiss(dbref player, const char *arg);
+extern void clear_followers(dbref leader, int noisy);
+extern void clear_following(dbref follower, int noisy);
+
+/* From mycrypt.c */
+extern char *mush_crypt(const char *key);
+
+/* From player.c */
+extern int password_check(dbref player, const char *password);
+extern dbref lookup_player(const char *name);
+/* from player.c */
+extern dbref create_player(const char *name, const char *password,
+                          const char *host, const char *ip);
+extern dbref connect_player(const char *name, const char *password,
+                           const char *host, const char *ip, char *errbuf);
+extern void check_last(dbref player, const char *host, const char *ip);
+extern void check_lastfailed(dbref player, const char *host);
+
+/* From parse.c */
+extern int is_number(const char *str);
+extern int is_strict_number(const char *str);
+extern int is_strict_integer(const char *str);
+
+/* From plyrlist.c */
+void clear_players(void);
+void add_player(dbref player, const char *alias);
+void delete_player(dbref player, const char *alias);
+
+/* From predicat.c */
+
+extern char *WIN32_CDECL tprintf(const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 1, 2)));
+
+extern int could_doit(dbref player, dbref thing);
+extern int did_it(dbref player, dbref thing, const char *what,
+                 const char *def, const char *owhat, const char *odef,
+                 const char *awhat, dbref loc);
+extern int did_it_with(dbref player, dbref thing, const char *what,
+                       const char *def, const char *owhat, const char *odef,
+                       const char *awhat, dbref loc, dbref env0, dbref env1,
+                       int flags);
+extern int did_it_interact(dbref player, dbref thing, const char *what,
+                          const char *def, const char *owhat,
+                          const char *odef, const char *awhat, dbref loc,
+                          int flags);
+extern int real_did_it(dbref player, dbref thing, const char *what,
+                      const char *def, const char *owhat, const char *odef,
+                      const char *awhat, dbref loc, char *myenv[10],
+                      int flags);
+extern int can_see(dbref player, dbref thing, int can_see_loc);
+extern int controls(dbref who, dbref what);
+extern int can_pay_fees(dbref who, int pennies);
+extern void giveto(dbref who, int pennies);
+extern int payfor(dbref who, int cost);
+extern int quiet_payfor(dbref who, int cost);
+extern int nearby(dbref obj1, dbref obj2);
+extern int get_current_quota(dbref who);
+extern void change_quota(dbref who, int payment);
+extern int ok_name(const char *name);
+extern int ok_command_name(const char *name);
+extern int ok_player_name(const char *name, dbref player);
+extern int ok_password(const char *password);
+extern int ok_tag_attribute(dbref player, char *params);
+extern dbref parse_match_possessor(dbref player, const char **str);
+extern void page_return(dbref player, dbref target, const char *type,
+                       const char *message, const char *def);
+extern char *grep_util(dbref player, dbref thing, char *pattern,
+                      char *lookfor, int len, int insensitive);
+extern dbref where_is(dbref thing);
+extern int charge_action(dbref player, dbref thing, const char *awhat);
+dbref first_visible(dbref player, dbref thing);
+
+/* From set.c */
+extern void chown_object(dbref player, dbref thing, dbref newowner,
+                        int preserve);
+
+/* From speech.c */
+const char *spname(dbref thing);
+extern void notify_except(dbref first, dbref exception, const char *msg,
+                         int flags);
+extern void notify_except2(dbref first, dbref exc1, dbref exc2,
+                          const char *msg, int flags);
+/* Return thing/PREFIX + msg */
+extern void make_prefixstr(dbref thing, const char *msg, char *tbuf1);
+extern int filter_found(dbref thing, const char *msg, int flag);
+
+/* From division.c */
+extern void adjust_powers(dbref obj, dbref to);
+extern void clear_division(dbref divi);
+extern POWERSPACE ps_tab;
+
+/* From prog.c */
+extern void prog_load_desc(DESC *d);
+extern int prog_handler(DESC *d, char *input);
+
+#ifdef COBJ
+/* From chanobj.c */
+extern void do_set_cobj(dbref player, const char *name, const char *obj);
+extern void do_reset_cobj(dbref player, const char *name);
+extern const char *ChanObjName(CHAN *c);
+#endif
+
+#ifdef RPMODE_SYS
+/* From rplog.c */
+extern void rplog_shutdown(void);
+extern void rplog_shutdown_room(dbref room);
+extern void rplog_init_room(dbref room);
+extern void init_rplogs(void);
+extern void rplog_room(dbref room, dbref player, char *str);
+extern void rplog_reset(void);
+#endif
+
+/* from rob.c */
+/* Not working? Somethig wrong with GCC?*/
+extern void s_Pennies(dbref thing, int amount);
+
+/* From strutil.c */
+extern char *split_token(char **sp, char sep);
+extern char *chopstr(const char *str, size_t lim);
+extern int string_prefix(const char *RESTRICT string,
+                        const char *RESTRICT prefix);
+extern const char *string_match(const char *src, const char *sub);
+extern char *strupper(const char *s);
+extern char *strlower(const char *s);
+extern char *strinitial(const char *s);
+extern char *upcasestr(char *s);
+extern char *skip_space(const char *s);
+extern char *seek_char(const char *s, char c);
+extern int u_strlen(const unsigned char *s);
+extern unsigned char *u_strcpy
+  (unsigned char *target, const unsigned char *source);
+/** Unsigned char strdup */
+#define u_strdup(x) (unsigned char *)strdup((char *) x)
+#ifndef HAS_STRDUP
+char *
+strdup(const char *s)
+  __attribute_malloc__;
+#endif
+    char *mush_strdup(const char *s, const char *check) __attribute_malloc__;
+#ifdef WIN32
+#ifndef HAS_VSNPRINTF
+#define HAS_VSNPRINTF
+#define vsnprintf _vsnprintf
+#endif
+#endif
+    extern char *remove_markup(const char *orig, size_t * stripped_len);
+    extern char *skip_leading_ansi(const char *s);
+
+/** A string, with ansi attributes broken out from the text */
+    typedef struct {
+      char text[BUFFER_LEN];   /**< Text of the string */
+      char *codes[BUFFER_LEN]; /**< Ansi codes associated with each char of text */
+      size_t len;      /**< Length of text */
+    } ansi_string;
+
+
+    extern ansi_string *parse_ansi_string(const char *src) __attribute_malloc__;
+    extern void free_ansi_string(ansi_string *as);
+    extern void populate_codes(ansi_string *as);
+    extern void depopulate_codes(ansi_string *as);
+#ifdef WIN32
+#define strncoll(s1,s2,n) _strncoll((s1), (s2), (n))
+#define strcasecoll(s1,s2) _stricoll((s1), (s2))
+#define strncasecoll(s1,s2,n) _strnicoll((s1), (s2), (n))
+#else
+    extern int strncoll(const char *s1, const char *s2, size_t t);
+    extern int strcasecoll(const char *s1, const char *s2);
+    extern int strncasecoll(const char *s1, const char *s2, size_t t);
+#endif
+
+/** Append a character to the end of a BUFFER_LEN long string.
+ * You shouldn't use arguments with side effects with this macro.
+ */
+#define safe_chr(x, buf, bp) \
+                    ((*(bp) - (buf) >= BUFFER_LEN - 1) ? \
+                        1 : (*(*(bp))++ = (x), 0))
+/* Like sprintf */
+    extern int safe_format(char *buff, char **bp, const char *RESTRICT fmt, ...)
+  __attribute__ ((__format__(__printf__, 3, 4)));
+/* Append an int to the end of a buffer */
+    extern int safe_integer(int i, char *buff, char **bp);
+    extern int safe_uinteger(unsigned int, char *buff, char **bp);
+/* Same, but for a SBUF_LEN buffer, not BUFFER_LEN */
+#define SBUF_LEN 64    /**< A short buffer */
+    extern int safe_integer_sbuf(int i, char *buff, char **bp);
+/* Append a NVAL to a string */
+    extern int safe_number(NVAL n, char *buff, char **bp);
+/* Append a dbref to a buffer */
+    extern int safe_dbref(dbref d, char *buff, char **bp);
+/* Append a string to a buffer */
+    extern int safe_str(const char *s, char *buff, char **bp);
+/* Append a string to a buffer, sticking it in quotes if there's a space */
+    extern int safe_str_space(const char *s, char *buff, char **bp);
+/* Append len characters of a string to a buffer */
+    extern int safe_strl(const char *s, int len, char *buff, char **bp);
+/** Append a boolean to the end of a string */
+#define safe_boolean(x, buf, bufp) \
+                safe_chr((x) ? '1' : '0', (buf), (bufp))
+/* Append X characters to the end of a string, taking ansi and html codes into
+   account. */
+extern int safe_ansi_string(ansi_string *as, size_t start, size_t len, char *buff, char **bp);
+extern int safe_ansi_string2(ansi_string *as, size_t start, size_t len, char *buff, char **bp);
+
+/* Append N copies of the character X to the end of a string */
+    extern int safe_fill(char x, size_t n, char *buff, char **bp);
+/* Append an accented string */
+    extern int safe_accent(const char *RESTRICT base,
+                          const char *RESTRICT tmplate, size_t len, char *buff,
+                          char **bp);
+
+   extern char *str_escaped_chr(const char *RESTRICT string, char escape_chr);
+    extern char *replace_string
+      (const char *RESTRICT old, const char *RESTRICT newbit,
+       const char *RESTRICT string) __attribute_malloc__;
+    extern char *replace_string2(const char *old[2], const char *newbits[2],
+                                const char *RESTRICT string)
+ __attribute_malloc__;
+    extern const char *standard_tokens[2];     /* ## and #@ */
+    extern char *trim_space_sep(char *str, char sep);
+    extern int do_wordcount(char *str, char sep);
+    extern char *remove_word(char *list, char *word, char sep);
+    extern char *next_in_list(const char **head);
+    extern void safe_itemizer(int cur_num, int done, const char *delim,
+                             const char *conjoin, const char *space,
+                             char *buff, char **bp);
+    extern char *show_time(time_t t, int utc);
+    extern char *show_tm(struct tm *t);
+
+
+/** This structure associates html entities and base ascii representations */
+    typedef struct {
+      const char *base;                /**< Base ascii representation */
+      const char *entity;      /**< HTML entity */
+    } accent_info;
+
+    extern accent_info accent_table[];
+
+    extern int ansi_strlen(const char *string);
+    extern int ansi_strnlen(const char *string, size_t numchars);
+
+/* From unparse.c */
+    const char *real_unparse
+      (dbref player, dbref loc, int obey_myopic, int use_nameformat,
+       int use_nameaccent);
+    extern const char *unparse_object(dbref player, dbref loc);
+/** For back compatibility, an alias for unparse_object */
+#define object_header(p,l) unparse_object(p,l)
+    extern const char *unparse_object_myopic(dbref player, dbref loc);
+    extern const char *unparse_room(dbref player, dbref loc);
+    extern int nameformat(dbref player, dbref loc, char *tbuf1);
+    extern const char *accented_name(dbref thing);
+
+/* From utils.c */
+    extern void parse_attrib(dbref player, char *str, dbref *thing,
+                            ATTR **attrib);
+    extern void parse_anon_attrib(dbref player, char *str, dbref *thing,
+                                 ATTR **attrib);
+    extern void free_anon_attrib(ATTR *attrib);
+    extern int member(dbref thing, dbref list);
+    extern int recursive_member(dbref disallow, dbref from, int count);
+    extern dbref remove_first(dbref first, dbref what);
+    extern dbref reverse(dbref list);
+    extern Malloc_t mush_malloc(size_t size,
+                               const char *check) __attribute_malloc__;
+    extern void mush_free(Malloc_t RESTRICT ptr, const char *RESTRICT check);
+    extern long get_random_long(long low, long high);
+    extern char *shortname(dbref it);
+    extern dbref absolute_room(dbref it);
+    int can_interact(dbref from, dbref to, int type);
+
+
+/* From warnings.c */
+    extern void run_topology(void);
+    extern void do_warnings(dbref player, const char *name, const char *warns);
+    extern void do_wcheck(dbref player, const char *name);
+    extern void do_wcheck_me(dbref player);
+    extern void do_wcheck_all(dbref player);
+    extern void set_initial_warnings(dbref player);
+    extern const char *unparse_warnings(warn_type warnings);
+    extern warn_type parse_warnings(dbref player, const char *warnings);
+
+/* From wild.c */
+    extern int local_wild_match_case(const char *RESTRICT s,
+                                    const char *RESTRICT d, int cs);
+    extern int wildcard(const char *s);
+    extern int quick_wild_new(const char *RESTRICT tstr,
+                             const char *RESTRICT dstr, int cs);
+    extern int regexp_match_case(const char *RESTRICT s, const char *RESTRICT d,
+                                int cs);
+    extern int quick_regexp_match(const char *RESTRICT s,
+                                 const char *RESTRICT d, int cs);
+    extern int wild_match_case(const char *RESTRICT s, const char *RESTRICT d,
+                              int cs);
+    extern int quick_wild(const char *RESTRICT tsr, const char *RESTRICT dstr);
+    extern int atr_wild(const char *RESTRICT tstr, const char *RESTRICT dstr);
+/** Default (case-sensitive) regex match */
+#define regexp_match(s,d) regexp_match_case(s,d,1)
+/** Default (case-insensitive) wildcard match */
+#define wild_match(s,d) wild_match_case(s,d,0)
+/** Default (case-insensitive) local wildcard match */
+#define local_wild_match(s,d) local_wild_match_case(s, d, 0)
+
+    /** Types of lists, based on what their elements are */
+    typedef enum list_type {
+      ALPHANUM_LIST, NUMERIC_LIST, DBREF_LIST, FLOAT_LIST, INSENS_ALPHANUM_LIST,
+      UNKNOWN_LIST
+    } list_type;
+/* From function.c and other fun*.c */
+    extern char *strip_braces(char const *line);
+    extern void save_global_regs(const char *funcname, char *preserve[]);
+    extern void restore_global_regs(const char *funcname, char *preserve[]);
+    extern void free_global_regs(const char *funcname, char *preserve[]);
+    extern void init_global_regs(char *preserve[]);
+    extern void load_global_regs(char *preserve[]);
+    extern void save_global_env(const char *funcname, char *preserve[]);
+    extern void restore_global_env(const char *funcname, char *preserve[]);
+    extern void save_global_nxt(const char *funcname, char *preservew[],
+                               char *preserver[], char *valw[], char *valr[]);
+    extern void restore_global_nxt(const char *funcname, char *preservew[],
+                                  char *preserver[], char *valw[],
+                                  char *valr[]);
+    extern int delim_check(char *buff, char **bp, int nfargs, char **fargs,
+                          int sep_arg, char *sep);
+    extern int get_gender(dbref player);
+    extern int gencomp(char *a, char *b, list_type sort_type);
+    extern char *ArabicToRoman(int);
+    extern int RomanToArabic(char *);
+
+/* From destroy.c */
+    void do_undestroy(dbref player, char *name);
+    dbref free_get(void);
+    void fix_free_list(void);
+    void purge(void);
+    void do_purge(dbref player);
+    void free_object(dbref thing);
+
+    void dbck(void);
+    int undestroy(dbref player, dbref thing);
+
+/* From db.c */
+
+    extern const char *set_name(dbref obj, const char *newname);
+    extern dbref new_object(void);
+    extern const char *set_lmod(dbref obj, const char *lmod);
+
+/* local.c */
+    void local_startup(void);
+    void local_configs(void);
+    void local_dump_database(void);
+    void local_dbck(void);
+    void local_shutdown(void);
+    void local_timer(void);
+    void local_connect(dbref player, int isnew, int num);
+    void local_disconnect(dbref player, int num);
+    void local_data_create(dbref object);
+    void local_data_clone(dbref clone, dbref source);
+    void local_data_free(dbref object);
+    int local_can_interact_first(dbref from, dbref to, int type);
+    int local_can_interact_last(dbref from, dbref to, int type);
+
+    /* flaglocal.c */
+    void local_flags(void);
+
+/* funlist.c */
+    void do_gensort(char *s[], int n, list_type sort_type);
+
+/* sig.c */
+    /** Type definition for signal handlers */
+    typedef void (*Sigfunc) (int);
+/* Set up a signal handler. Use instead of signal() */
+    Sigfunc install_sig_handler(int signo, Sigfunc f);
+/* Call from in a signal handler to re-install the handler. Does nothing
+   with persistent signals */
+    void reload_sig_handler(int signo, Sigfunc f);
+/* Ignore a signal. Like i_s_h with SIG_IGN) */
+    void ignore_signal(int signo);
+/* Block one signal temporarily. */
+    void block_a_signal(int signo);
+/* Unblock a signal */
+    void unblock_a_signal(int signo);
+/* Block all signals en masse. */
+    void block_signals(void);
+
+#endif                         /* __EXTERNS_H */
diff --git a/hdrs/extmail.h b/hdrs/extmail.h
new file mode 100644 (file)
index 0000000..482d56e
--- /dev/null
@@ -0,0 +1,108 @@
+/* mail.h - header for Javelin's extended mailer */
+
+#ifndef _EXTMAIL_H
+#define _EXTMAIL_H
+/* Some of this isn't implemented yet, but heralds the future! */
+#define M_MSGREAD       0x0001
+#define M_UNREAD        0x0FFE
+#define M_CLEARED       0x0002
+#define M_URGENT        0x0004
+#define M_MASS          0x0008
+#define M_EXPIRE        0x0010
+#define M_RECEIPT       0x0020
+#define M_TAG           0x0040
+#define M_FORWARD       0x0080
+/* 0x0100 - 0x0F00 reserved for folder numbers */
+#define M_FMASK         0xF0FF
+#define M_ALL           0x1000 /* In mail_selector, all msgs in all folders */
+#define M_MSUNREAD      0x2000 /* Mail selectors */
+#define M_REPLY         0x4000
+#define M_FOLDER        0x8000 /* In mail selector, all msgs in cur folder */
+/* 0x4000 - 0x8000 available */
+
+#define MAX_FOLDERS     15
+#define FOLDER_NAME_LEN (BUFFER_LEN / 30)
+#define FolderBit(f) (256 * (f))
+#define Urgent(m)       (m->read & M_URGENT)
+#define Mass(m)         (m->read & M_MASS)
+#define Expire(m)       (m->read & M_EXPIRE)
+#define Receipt(m)      (m->read & M_RECEIPT)
+#define Forward(m)      (m->read & M_FORWARD)
+#define Reply(m)        (m->read & M_REPLY)
+#define Tagged(m)       (m->read & M_TAG)
+#define Folder(m)       ((m->read & ~M_FMASK) >> 8)
+#define Read(m)         (m->read & M_MSGREAD)
+#define Cleared(m)      (m->read & M_CLEARED)
+#define Unread(m)       (!Read(m))
+#define All(ms)         (ms.flags & M_ALL)
+#define AllInFolder(ms) (ms.flags & M_FOLDER)
+#define MSFolder(ms)    ((int)((ms.flags & ~M_FMASK) >> 8U))
+
+typedef unsigned int mail_flag;
+
+/** A mail selection.
+ * This structure maintains information about a selected list of
+ * messages. Messages can be selected in several ways.
+ */
+struct mail_selector {
+  int low;             /**< Minimum message number */
+  int high;            /**< Maximum message number */
+  mail_flag flags;     /**< Message flags */
+  dbref player;                /**< Message sender's dbref */
+  int days;            /**< Target message age in days */
+  int day_comp;                /**< Direction of comparison to target age */
+};
+
+typedef int folder_array[MAX_FOLDERS + 1];
+#define FA_Init(fa,x) \
+for (x = 0; x <= MAX_FOLDERS; x++) \
+fa[x] = 0
+
+#define SUBJECT_COOKIE  '/'
+#define SUBJECT_LEN     60
+
+#define MDBF_SUBJECT    0x1
+#define MDBF_ALIASES    0x2
+
+/* Database ends with ***END OF DUMP*** not *** END OF DUMP *** */
+#define MDBF_NEW_EOD    0x4
+
+/* Database contains sender ctimes */
+#define MDBF_SENDERCTIME        0x8
+
+/* From extmail.c */
+extern struct mail *maildb;
+extern void set_player_folder(dbref player, int fnum);
+extern void add_folder_name(dbref player, int fld, const char *name);
+extern struct mail *find_exact_starting_point(dbref player);
+extern void check_mail(dbref player, int folder, int silent);
+extern int dump_mail(FILE * fp);
+extern int load_mail(FILE * fp);
+extern void mail_init(void);
+extern int mdb_top;
+extern void do_mail(dbref player, char *arg1, char *arg2);
+enum mail_stats_type { MSTATS_COUNT, MSTATS_READ, MSTATS_SIZE };
+extern void do_mail_stats(dbref player, char *name, enum mail_stats_type full);
+extern void do_mail_debug(dbref player, const char *action, const char *victim);
+extern void do_mail_nuke(dbref player);
+extern void do_mail_change_folder(dbref player, char *fld, char *newname);
+extern void do_mail_unfolder(dbref player, char *fld);
+extern void do_mail_list(dbref player, const char *msglist);
+extern void do_mail_read(dbref player, char *msglist);
+extern void do_mail_clear(dbref player, const char *msglist);
+extern void do_mail_unclear(dbref player, const char *msglist);
+extern void do_mail_purge(dbref player);
+extern void do_mail_file(dbref player, char *msglist, char *folder);
+extern void do_mail_tag(dbref player, const char *msglist);
+extern void do_mail_untag(dbref player, const char *msglist);
+extern void do_mail_fwd(dbref player, char *msglist, char *tolist);
+extern void do_mail_send
+  (dbref player, char *tolist, char *message, mail_flag flags,
+   int silent, int nosig);
+
+/* From bsd.c */
+extern struct mail *desc_mail(dbref player);
+extern void desc_mail_set(dbref player, struct mail *mp);
+extern void desc_mail_clear(void);
+
+#endif                         /* _EXTMAIL_H */
diff --git a/hdrs/flags.h b/hdrs/flags.h
new file mode 100644 (file)
index 0000000..96f7588
--- /dev/null
@@ -0,0 +1,187 @@
+/* flags.h */
+
+/* flag and powers stuff */
+
+#ifndef __FLAGS_H
+#define __FLAGS_H
+
+#include "mushtype.h"
+#include "ptab.h"
+#include "division.h"
+
+typedef struct flag_info FLAG;
+
+/** A flag.
+ * This structure represents a flag in the table of flags that are
+ * available for setting on objects in the game.
+ */
+struct flag_info {
+  const char *name;    /**< Name of the flag */
+  char letter;         /**< Flag character, which may be nul */
+  int type;            /**< Bitflags of object types this flag applies to */
+  int bitpos;          /**< Bit position assigned to this flag for now */
+  int perms;           /**< Bitflags of who can set this flag */
+  int negate_perms;    /**< Bitflags of who can clear this flag */
+};
+
+typedef struct flag_alias FLAG_ALIAS;
+
+/** A flag alias.
+ * A simple structure that associates an alias with a canonical flag name.
+ */
+struct flag_alias {
+  const char *alias;           /**< The alias name */
+  const char *realname;                /**< The real name of the flag */
+};
+
+typedef struct flagspace FLAGSPACE;
+
+/** A flagspace.
+ * A structure that contains all the information necessary to manage
+ * a set of flags, powers, or whatever.
+ */
+struct flagspace {
+  PTAB *tab;                   /**< Prefix table storing flags by name/alias */
+  FLAG **flags;                        /**< Variable-length array of pointers to canonical flags, indexed by bit */
+  int flagbits;                        /**< Current length of the flags array */
+  FLAG *flag_table;            /**< Pointer to flag table */
+  FLAG_ALIAS *flag_alias_table;        /**< Pointer to flag alias table */
+};
+
+
+/* From flags.c */
+extern int has_flag_by_name(dbref thing, const char *flag, int type);
+extern const char *unparse_flags(dbref thing, dbref player);
+extern const char *flag_description(dbref player, dbref thing);
+extern int sees_flag(dbref privs, dbref thing, const char *name);
+extern void set_flag(dbref player, dbref thing, const char *flag, int negate,
+                    int hear, int listener);
+extern int flaglist_check(const char *ns, dbref player, dbref it, const char *fstr, int type);
+extern int flaglist_check_long(const char *ns, dbref player, dbref it, const char *fstr,
+                              int type);
+extern FLAG *match_flag(const char *name);
+extern const char *show_command_flags(object_flag_type flags, div_pbits powers);
+extern void twiddle_flag_internal(const char *ns, dbref thing, const char *flag, int negate);
+extern object_flag_type new_flag_bitmask(const char *ns);
+extern object_flag_type clone_flag_bitmask(const char *ns,
+    object_flag_type given);
+extern void copy_flag_bitmask(const char *ns, object_flag_type dest, 
+    object_flag_type given);
+extern void destroy_flag_bitmask(object_flag_type bitmask);
+extern void set_flag_bitmask(object_flag_type bitmask, int bit);
+extern void clear_flag_bitmask(object_flag_type bitmask, int bit);
+extern int has_bit(object_flag_type bitmask, int bitpos);
+extern int has_all_bits(const char *ns, object_flag_type source, object_flag_type bitmask);
+extern int null_flagmask(const char *ns, object_flag_type source);
+extern int has_any_bits(const char *ns, object_flag_type source, object_flag_type bitmask);
+extern object_flag_type string_to_bits(const char *ns, const char *str);
+extern const char *bits_to_string(const char *ns, object_flag_type bitmask, dbref privs,
+                                 dbref thing);
+extern void flag_write_all(FILE *, const char *);
+extern void flag_read_all(FILE *, const char *);
+extern int type_from_old_flags(long old_flags);
+extern object_flag_type flags_from_old_flags(long old_flags, long old_toggles,
+                                            int type);
+extern FLAG *add_flag(const char *name, const char letter, int type,
+                     int perms, int negate_perms);
+extern void do_list_flags(dbref player, const char *arg, int lc);
+extern char *list_all_flags(const char *ns, const char *name, dbref privs, int which);
+extern void do_flag_info(const char *ns, dbref player, const char *name);
+extern void do_flag_delete(dbref player, const char *name);
+extern void do_flag_disable(dbref player, const char *name);
+extern void do_flag_alias(dbref player, const char *name, const char *alias);
+extern void do_flag_enable(dbref player, const char *name);
+extern void do_flag_restrict(dbref player, const char *name,
+                            char *args_right[]);
+extern void do_flag_add(dbref player, const char *name, char *args_right[]);
+extern void do_flag_letter(dbref player, const char *name, const char *letter);
+extern void do_flag_type(const char *ns, dbref player, const char *name, char *type_string);
+extern const char *power_to_string(int pwr);
+extern void decompile_flags(dbref player, dbref thing, const char *name);
+
+#define twiddle_flag_bitmask(bm,b,neg) (neg ? clear_flag_bitmask(bm,b) : \
+                                                set_flag_bitmask(bm,b))
+#define has_all_flags_by_mask(x,bm) has_all_bits("FLAG",Flags(x),bm)
+#define has_any_flags_by_mask(x,bm) has_any_bits("FLAG",Flags(x),bm)
+#define twiddle_flag(thing,f,negate) \
+  twiddle_flag_bitmask(Flags(thing),f->bitpos,negate)
+#define set_flag_internal(t,f) twiddle_flag_internal("FLAG",t,f,0)
+#define clear_flag_internal(t,f) twiddle_flag_internal("FLAG",t,f,1)
+
+/*---------------------------------------------------------------------
+ * Object types (no longer part of the flags)
+ */
+
+#define TYPE_ROOM       0x1
+#define TYPE_THING      0x2
+#define TYPE_EXIT       0x4
+#define TYPE_PLAYER     0x8
+#define TYPE_GARBAGE    0x10
+#define TYPE_MARKED     0x20
+#define TYPE_DIVISION  0x40
+#define NOTYPE          0xFFFF
+
+#define ALLTYPES (0 | TYPE_ROOM | TYPE_THING | TYPE_EXIT | TYPE_PLAYER | TYPE_DIVISION)
+
+
+
+/*--------------------------------------------------------------------------
+ * Flag permissions
+ */
+
+#define F_ANY           0x10   /* can be set by anyone - obsolete now */
+#define F_OWNED         0x40   /* can be set on owned objects */
+#define F_PRIVILEGE    0x80    /* can only be set by privileged players */
+#define F_GOD           0x200  /* can only be set by God */
+#define F_INTERNAL      0x400  /* only the game can set this */
+#define F_DARK          0x800  /* only God can see this flag */
+#define F_MDARK         0x1000 /* admin/God can see this flag */
+#define F_ODARK         0x2000 /* owner/admin/God can see this flag */
+#define F_DISABLED      0x4000 /* flag can't be used */
+/* RESERVED            0x8000 */
+
+
+/* we don't use these anymore.. but kept aroudn for DB conversion */
+
+/*--------------------------------------------------------------------------
+ * Powers table
+ */
+
+#define CAN_BUILD       0x10    /* can use builder commands */
+#define TEL_ANYWHERE    0x20    /* teleport self anywhere */
+#define TEL_OTHER       0x40    /* teleport someone else */
+#define SEE_ALL         0x80    /* can examine all and use priv WHO */
+#define NO_PAY          0x100   /* Needs no money */
+#define CHAT_PRIVS      0x200   /* can use restricted channels */
+#define CAN_HIDE        0x400   /* can go DARK on the WHO list */
+#define LOGIN_ANYTIME   0x800   /* not affected by restricted logins */
+#define UNLIMITED_IDLE  0x1000  /* no inactivity timeout */
+#define LONG_FINGERS    0x2000  /* can grab stuff remotely */
+#define CAN_BOOT        0x4000  /* can boot off players */
+#define CHANGE_QUOTAS   0x8000  /* can change other players' quotas */
+#define SET_POLL        0x10000 /* can change the poll */
+#define HUGE_QUEUE      0x20000 /* queue limit of db_top + 1 */
+#define PS_ALL          0x40000 /* look at anyone's queue */
+#define HALT_ANYTHING   0x80000 /* do @halt on others, and @allhalt */
+#define SEARCH_EVERYTHING  0x100000     /* @stats, @search, @entrances all */
+#define GLOBAL_FUNCS    0x200000        /* add global functions */
+#define CREATE_PLAYER   0x400000        /* @pcreate */
+#define IS_GUEST        0x800000        /* Guest, restrict access */
+#define CAN_WALL        0x1000000       /* @wall */
+#define CEMIT           0x2000000       /* Was: Can @cemit */
+#define UNKILLABLE      0x4000000       /* Cannot be killed */
+#define PEMIT_ALL       0x8000000       /* Can @pemit to HAVEN players */
+#define NO_QUOTA        0x10000000      /* Has no quota restrictions */
+#define LINK_ANYWHERE   0x20000000      /* Can @link an exit to any room */
+#define OPEN_ANYWHERE   0x40000000      /* Can @open an exit from any room */
+#define CAN_NSPEMIT     0x80000000      /* Can use @nspemit and nspemit() */
+
+/* These powers are obsolete, but are kept around to implement
+ * DBF_SPLIT_IMMORTAL
+ */
+#define CAN_DEBUG       0x4000000       /* Can set/unset the debug flag */
+#define IMMORTAL        0x100   /* cannot be killed, uses no money */
+
+
+
+#endif                         /* __FLAGS_H */
diff --git a/hdrs/function.h b/hdrs/function.h
new file mode 100644 (file)
index 0000000..6e91f23
--- /dev/null
@@ -0,0 +1,120 @@
+#ifndef _FUNCTIONS_H_
+#define _FUNCTIONS_H_
+
+#include "copyrite.h"
+#include "boolexp.h"
+
+#define FN_REG 0x0
+/* Function arguments aren't parsed */
+#define FN_NOPARSE      0x1
+#define FN_LITERAL      0x2
+#define FN_ARG_MASK     0x3
+/* Function is disabled */
+#define FN_DISABLED     0x4
+/* Function will fail if object is gagged */
+#define FN_NOGAGGED  0x8
+/* Function will fail if object is a guest */
+#define FN_NOGUEST   0x10
+/* Function will fail if object is fixed */
+#define FN_NOFIXED   0x20
+/* Function is director-only */
+#define FN_DIRECTOR 0x40
+/* Function is admin-only */
+#define FN_ADMIN  0x80
+/* Function is god-only */
+#define FN_GOD    0x100
+/* Function is builtin */
+#define FN_BUILTIN 0x200
+/* Function can be overridden with a @function */
+#define FN_OVERRIDE 0x400
+/* Side-effect version of function no workie */
+#define FN_NOSIDEFX 0x800
+/* Log function name */
+#define FN_LOGNAME 0x1000
+/* Log function name and args */
+#define FN_LOGARGS 0x2000
+/* Run Function Ulocal Style */
+#define FN_ULOCAL  0x4000
+#define FN_NORP 0x8000
+#define FN_ONEARG 0x10000
+
+
+#ifndef HAVE_FUN_DEFINED
+typedef struct fun FUN;
+#define HAVE_FUN_DEFINED
+#endif
+
+typedef void (*function_func) (FUN *, char *, char **, int, char *[], int[],
+                              dbref, dbref, dbref, const char *, PE_Info *);
+
+/** A calling pointer to a function.
+ * This union holds either a pointer to a function's code or
+ * the offset of the function in the user-defined function table.
+ */
+union fun_call {
+  function_func fun;   /**< Pointer to compiled function code */
+  size_t offset;       /**< Offset into user-defined function table */
+};
+
+/** A function.
+ * This structure represents a mushcode function.
+ */
+struct fun {
+  const char *name;    /**< Function name */
+  union fun_call where;        /**< Where to find the function to call it */
+  int minargs;         /**< Minimum arguments required, or 0 */
+  /** Maximum arguments allowed.
+   * Maximum arguments allowed. If there is no limit, this is INT_MAX.
+   * If this is negatve, the final argument to the function can contain
+   * commas that won't be parsed, and the maximum number of arguments
+   * is the absolute value of this variable.
+   */
+  int maxargs;
+  unsigned int flags;  /**< Bitflags of function */
+  boolexp lock; /* Usability lock */
+};
+
+typedef struct userfn_entry USERFN_ENTRY;
+
+/** A user-defined function
+ * This structure represents an entry in the user-defined function table.
+ */
+struct userfn_entry {
+  char *fn;            /**< Name of the function */
+  dbref thing;         /**< Dbref of object where the function is defined */
+  char *name;          /**< Name of attribute where the function is defined */
+  unsigned int flags;  /**< Bitflags of function */
+};
+
+extern USERFN_ENTRY *userfn_tab;
+
+extern void do_userfn(char *buff, char **bp,
+                     dbref obj, ATTR *attrib,
+                     int nargs, char **args,
+                     dbref executor, dbref caller, dbref enactor,
+                     PE_Info * pe_info);
+
+extern FUN *func_hash_lookup(const char *name);
+extern int check_func(dbref player, FUN *fp);
+extern int restrict_function(const char *name, const char *restrict);
+extern int alias_function(const char *function, const char *alias);
+extern void do_function_restrict(dbref player, const char *name,
+                                const char *restrict);
+extern void do_function_restore(dbref player, const char *name);
+extern void do_list_functions(dbref player, int lc);
+extern char *list_functions(void);
+extern void do_function(dbref player, char *name, char **argv);
+extern void do_function_toggle(dbref player, char *name, int toggle);
+extern void do_function_report(dbref player, char *name);
+extern void do_function_delete(dbref player, char *name);
+extern void function_init_postconfig(void);
+
+
+#define FUNCTION_PROTO(fun_name) \
+  extern void fun_name (FUN *fun, char *buff, char **bp, int nargs, char *args[], \
+                   int arglen[], dbref executor, dbref caller, dbref enactor, \
+                   char const *called_as, PE_Info *pe_info)
+extern void function_add(const char *name, function_func fun, int minargs,
+                        int maxargs, int ftype);
+
+#endif
diff --git a/hdrs/game.h b/hdrs/game.h
new file mode 100644 (file)
index 0000000..93d4b81
--- /dev/null
@@ -0,0 +1,196 @@
+/* game.h */
+/* Command handlers */
+
+#ifndef __GAME_H
+#define __GAME_H
+
+/* Miscellaneous flags */
+#define CHECK_INVENTORY            0x10
+#define CHECK_NEIGHBORS            0x20
+#define CHECK_SELF                 0x40
+#define CHECK_HERE                 0x80
+#define CHECK_ZONE                 0x100
+#define CHECK_GLOBAL               0x200
+
+/* hash table stuff */
+extern void init_func_hashtab(void);   /* eval.c */
+extern void init_math_hashtab(void);   /* funmath.c */
+extern void init_aname_table(void);    /* atr_tab.c */
+extern void init_flagspaces(void);     /* flags.c */
+extern void init_flag_table(const char *ns);   /* flags.c */
+extern void init_tag_hashtab(void);    /* funstr.c */
+extern void init_pronouns(void);       /* funstr.c */
+
+/* From bsd.c */
+extern void fcache_init(void);
+extern void fcache_load(dbref player);
+extern void hide_player(dbref player, int hide);
+enum motd_type { MOTD_MOTD, MOTD_DOWN, MOTD_FULL, MOTD_LIST };
+extern void do_motd(dbref player, enum motd_type key, const char *message);
+extern void do_poll(dbref player, const char *message);
+/* From cque.c */
+extern int do_wait
+  (dbref player, dbref cause, char *arg1, char *cmd, int until, char finvoc);
+enum queue_type { QUEUE_ALL, QUEUE_NORMAL, QUEUE_SUMMARY, QUEUE_QUICK };
+extern void do_queue(dbref player, const char *what, enum queue_type flag);
+extern void do_halt1(dbref player, const char *arg1, const char *arg2);
+extern void do_allhalt(dbref player);
+extern void do_allrestart(dbref player);
+extern void do_restart(void);
+extern void do_restart_com(dbref player, const char *arg1);
+/* Only QID_ACTIVE, QID_FALSE, and QID_FREEZE actually get thrown on a qid cell.
+ *  * other are just used to pass in function shit */
+enum qid_flags {QID_ACTIVE, QID_KILL, QID_FREEZE, QID_CONT, QID_TIME, QID_QUERY_T, QID_FALSE};
+extern int do_signal_qid(dbref signalby, int qid, enum qid_flags qid_flags, int time);
+
+/* From command.c */
+enum hook_type { HOOK_BEFORE, HOOK_AFTER, HOOK_IGNORE, HOOK_OVERRIDE };
+extern void do_hook(dbref player, char *command, char *obj, char *attrname,
+                   enum hook_type flag);
+
+
+/* From compress.c */
+#if (COMPRESSION_TYPE > 0)
+extern int init_compress(FILE * f);
+#endif
+
+/* From conf.c */
+extern int config_file_startup(const char *conf, int restrictions);
+
+/* From game.c */
+enum dump_type { DUMP_NORMAL, DUMP_DEBUG, DUMP_PARANOID };
+extern void do_dump(dbref player, char *num, enum dump_type flag);
+enum shutdown_type { SHUT_NORMAL, SHUT_PANIC, SHUT_PARANOID };
+extern void do_shutdown(dbref player, enum shutdown_type panic_flag);
+
+/* From look.c */
+enum exam_type { EXAM_NORMAL, EXAM_BRIEF, EXAM_MORTAL };
+extern void do_examine(dbref player, const char *name, enum exam_type flag,
+                      int all);
+extern void do_inventory(dbref player);
+extern void do_find(dbref player, const char *name, char **argv);
+extern void do_whereis(dbref player, const char *name);
+extern void do_score(dbref player);
+extern void do_sweep(dbref player, const char *arg1);
+enum ent_type { ENT_EXITS, ENT_THINGS, ENT_PLAYERS, ENT_ROOMS, ENT_ALL };
+extern void do_entrances(dbref player, const char *where, char **argv,
+                        enum ent_type val);
+enum dec_type { DEC_NORMAL, DEC_DB, DEC_TF, DEC_FLAG, DEC_ATTR };
+extern void do_decompile
+  (dbref player, const char *name, enum dec_type dbflag, int skipdef);
+
+/* From move.c */
+extern void do_get(dbref player, const char *what);
+extern void do_drop(dbref player, const char *name);
+extern void do_enter(dbref player, const char *what);
+extern void do_leave(dbref player);
+extern void do_empty(dbref player, const char *what);
+extern void do_firstexit(dbref player, const char *what);
+
+/* From player.c */
+extern void do_password(dbref player, dbref cause,
+                       const char *old, const char *newobj);
+
+/* From predicat.c */
+extern void do_switch
+  (dbref player, char *expression, char **argv, dbref cause, int first,
+   int notifyme, int regexp);
+extern void do_verb(dbref player, dbref cause, char *arg1, char **argv);
+extern void do_grep
+  (dbref player, char *obj, char *lookfor, int flag, int insensitive);
+
+/* From rob.c */
+extern void do_give(dbref player, char *recipient, char *amnt, int silent);
+extern void do_buy(dbref player, char *item, char *from, int price);
+
+/* From set.c */
+extern void do_name(dbref player, const char *name, char *newname);
+extern void do_chown
+  (dbref player, const char *name, const char *newobj, int preserve);
+extern int do_chzone(dbref player, const char *name, const char *newobj,
+                    int noisy);
+extern int do_set(dbref player, const char *name, char *flag);
+extern void do_cpattr
+  (dbref player, char *oldpair, char **newpair, int move, int noflagcopy);
+  enum edit_type { EDIT_FIRST, EDIT_ALL} ;
+extern void do_gedit(dbref player, char *it, char **argv,
+                    enum edit_type target, int doit);
+extern void do_trigger(dbref player, char *object, char **argv);
+extern void do_use(dbref player, const char *what);
+extern void do_parent(dbref player, char *name, char *parent_name);
+extern void do_wipe(dbref player, char *name);
+
+/* From speech.c */
+extern void do_say(dbref player, const char *tbuf1);
+extern void do_whisper
+  (dbref player, const char *arg1, const char *arg2, int noisy);
+extern void do_whisper_list
+  (dbref player, const char *arg1, const char *arg2, int noisy);
+extern void do_pose(dbref player, const char *tbuf1, int space);
+enum wall_type { WALL_ALL };
+extern void do_wall(dbref player, const char *message, enum wall_type target,
+                   int emit);
+extern void do_page(dbref player, const char *arg1, const char *arg2,
+                   dbref cause, int noeval, int multipage, int override);
+extern void do_page_port(dbref player, const char *arg1, const char *arg2);
+extern void do_think(dbref player, const char *message);
+#define PEMIT_SILENT 0x1
+#define PEMIT_LIST   0x2
+#define PEMIT_SPOOF  0x4
+extern void do_emit(dbref player, const char *tbuf1, int flags);
+extern void do_pemit
+  (dbref player, const char *arg1, const char *arg2, int flags);
+extern void do_pemit_list(dbref player, char *list, const char *message,
+                         int flags);
+extern void do_remit(dbref player, char *arg1, const char *arg2, int flags);
+extern void do_lemit(dbref player, const char *tbuf1, int flags);
+extern void do_zemit(dbref player, const char *arg1, const char *arg2,
+                    int flags);
+extern void do_oemit_list(dbref player, char *arg1, const char *arg2,
+                         int flags);
+extern void do_teach(dbref player, dbref cause, const char *tbuf1);
+
+/* From wiz.c */
+extern void do_debug_examine(dbref player, const char *name);
+extern void do_enable(dbref player, const char *param, int state);
+extern void do_kick(dbref player, const char *num);
+extern void do_search(dbref player, const char *arg1, char **arg3);
+extern dbref do_pcreate
+  (dbref creator, const char *player_name, const char *player_password);
+extern void do_quota
+  (dbref player, const char *arg1, const char *arg2, int set_q);
+extern void do_allquota(dbref player, const char *arg1, int quiet);
+extern void do_teleport
+  (dbref player, const char *arg1, const char *arg2, int silent, int inside);
+extern void do_force(dbref player, const char *what, char *command);
+extern void do_stats(dbref player, const char *name);
+extern void do_newpassword
+  (dbref player, dbref cause, const char *name, const char *password);
+enum boot_type { BOOT_NAME, BOOT_DESC, BOOT_SELF };
+extern void do_boot(dbref player, const char *name, enum boot_type flag);
+extern void do_chzoneall(dbref player, const char *name, const char *target);
+extern int parse_force(char *command);
+enum sitelock_type { SITELOCK_ADD, SITELOCK_REMOVE, SITELOCK_BAN,
+  SITELOCK_CHECK, SITELOCK_LIST
+};
+extern void do_sitelock
+  (dbref player, const char *site, const char *opts, const char *charname,
+   enum sitelock_type type);
+extern void do_sitelock_name(dbref player, const char *name);
+extern void do_chownall
+  (dbref player, const char *name, const char *target, int preserve);
+extern void NORETURN do_reboot(dbref player, int flag);
+
+/* From destroy.c */
+extern void do_dbck(dbref player);
+extern void do_destroy(dbref player, char *name, int confirm);
+extern void pre_destroy(dbref player, dbref thing);
+
+/* From timer.c */
+extern void init_timer(void);
+extern void signal_cpu_limit(int signo);
+
+/* From version.c */
+extern void do_version(dbref player);
+
+#endif                         /* __GAME_H */
diff --git a/hdrs/getpgsiz.h b/hdrs/getpgsiz.h
new file mode 100644 (file)
index 0000000..a7cbc14
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef __GETPGSIZ_H
+#define __GETPGSIZ_H
+
+#ifndef HAS_GETPAGESIZE
+#ifdef PAGESIZE_VALUE
+#define getpagesize() PAGESIZE_VALUE
+#else
+#ifndef WIN32
+#ifdef I_SYS_PARAM
+#include <sys/param.h>
+#endif
+#endif
+#ifdef I_SYS_PAGE
+#include <sys/page.h>
+#endif
+
+#ifdef EXEC_PAGESIZE
+#define getpagesize() EXEC_PAGESIZE
+#else
+#ifdef NBPG
+#define getpagesize() NBPG * CLSIZE
+#ifndef CLSIZE
+#define CLSIZE 1
+#endif                         /* no CLSIZE */
+#else                          /* no NBPG */
+#ifdef NBPC
+#define getpagesize() NBPC
+#else                          /* no NBPC either? Bummer */
+#ifdef PAGESIZE
+#define getpagesize() PAGESIZE
+#else                          /* Sigh. Time for a total guess. */
+#define getpagesize() 1024
+#endif                         /* no PAGESIZE */
+#endif                         /* no NBPC */
+#endif                         /* no NBPG */
+#endif                         /* no EXEC_PAGESIZE */
+#endif                         /* no PAGESIZE_VALUE */
+#endif                         /* not HAS_GETPAGESIZE */
+
+#endif                         /* __GETPGSIZ_H */
diff --git a/hdrs/help.h b/hdrs/help.h
new file mode 100644 (file)
index 0000000..06478b8
--- /dev/null
@@ -0,0 +1,34 @@
+/* help.h */
+
+#ifndef __HELP_H
+#define __HELP_H
+
+#define  LINE_SIZE              90
+#define  TOPIC_NAME_LEN         30
+
+/** A help index entry.
+ *
+ */
+typedef struct {
+  long pos;                    /**< Position of topic in help file, in bytes */
+  char topic[TOPIC_NAME_LEN + 1];      /**< name of topic of help entry */
+} help_indx;
+
+/** A help command.
+ * Multiple help commands can be defined, each associated with a help
+ * file and an in-memory index.
+ */
+typedef struct {
+  char *command;       /**< The name of the help command */
+  char *file;          /**< The file of help text */
+  int admin;           /**< Is this an admin-only help command? */
+  help_indx *indx;     /**< An array of help index entries */
+  size_t entries;      /**< Number of entries in the help file */
+} help_file;
+
+
+extern void init_help_files(void);
+extern void add_help_file
+  (const char *command_name, const char *filename, int admin);
+extern void help_reindex(dbref player);
+#endif                         /* __HELP_H */
diff --git a/hdrs/htab.h b/hdrs/htab.h
new file mode 100644 (file)
index 0000000..3d3dbc1
--- /dev/null
@@ -0,0 +1,60 @@
+/* htab.h - Structures and declarations needed for table hashing */
+
+#ifndef __HTAB_H
+#define __HTAB_H
+
+#define SOME_KEY_LEN 10
+
+#define HTAB_UPSCALE   4
+#define HTAB_DOWNSCALE 2
+
+typedef struct hashentry HASHENT;
+/** A hash table entry.
+ */
+struct hashentry {
+  struct hashentry *next;      /**< Pointer to next entry */
+  void *data;                  /**< Data for this entry */
+  /* int extra_size; */
+  char key[SOME_KEY_LEN];      /**< Key for this entry */
+};
+
+#define HASHENT_SIZE (sizeof(HASHENT)-SOME_KEY_LEN)
+
+typedef struct hashtable HASHTAB;
+/** A hash table.
+ */
+struct hashtable {
+  int hashsize;                        /**< Size of hash table */
+  int mask;                    /**< Mask for entries in table */
+  int entries;                 /**< Number of entries stored */
+  HASHENT **buckets;           /**< Pointer to pointer to entries */
+  int last_hval;               /**< State for hashfirst & hashnext. */
+  HASHENT *last_entry;         /**< State for hashfirst & hashnext. */
+  int entry_size;              /**< Size of each entry */
+};
+
+#define get_hashmask(x) hash_getmask(x)
+#define hashinit(x,y, z) hash_init(x,y, z)
+#define hashfind(key,tab) hash_value(hash_find(tab,key))
+#define hashadd(key,data,tab) hash_add(tab,key,data, 0)
+#define hashadds(key, data, tab, size) hash_add(tab, key, data, size)
+#define hashdelete(key,tab) hash_delete(tab,hash_find(tab,key))
+#define hashflush(tab, size) hash_flush(tab,size)
+#define hashfree(tab) hash_flush(tab, 0)
+extern int hash_getmask(int *size);
+extern void hash_init(HASHTAB *htab, int size, int data_size);
+extern HASHENT *hash_find(HASHTAB *htab, const char *key);
+extern void *hash_value(HASHENT *entry);
+extern char *hash_key(HASHENT *entry);
+extern void hash_resize(HASHTAB *htab, int size);
+extern int hash_add
+  (HASHTAB *htab, const char *key, void *hashdata, int extra_size);
+extern void hash_delete(HASHTAB *htab, HASHENT *entry);
+extern void hash_flush(HASHTAB *htab, int size);
+extern void *hash_firstentry(HASHTAB *htab);
+extern void *hash_nextentry(HASHTAB *htab);
+extern char *hash_firstentry_key(HASHTAB *htab);
+extern char *hash_nextentry_key(HASHTAB *htab);
+extern void hash_stats_header(dbref player);
+extern void hash_stats(dbref player, HASHTAB *htab, const char *hashname);
+#endif
diff --git a/hdrs/ident.h b/hdrs/ident.h
new file mode 100644 (file)
index 0000000..2445bcf
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+   ** ident.h
+   **
+   ** Author: Peter Eriksson <pen@lysator.liu.se>
+   ** Intruder: Pär Emanuelsson <pell@lysator.liu.se>
+   ** Vandal: Shawn Wagner <raevnos@pennmush.org>
+ */
+
+#ifndef __IDENT_H__
+#define __IDENT_H__
+
+#include "config.h"
+
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+
+#ifdef I_SYS_SELECT
+#include <sys/select.h>
+#endif
+
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+
+#include "mysocket.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#ifndef IDBUFSIZE
+#define IDBUFSIZE 2048
+#endif
+
+  /* Using the "auth" service name would be better, but windoze probably
+     doesn't support any notion of getservyname or like functions.
+   */
+#ifndef IDPORT
+#define IDPORT  113
+#endif
+
+  /** Ident call result.
+   * This structure stores the result of an ident call.
+   */
+  typedef struct {
+    char *identifier;          /**< Normally user name */
+    char *opsys;               /**< Operating system */
+    char *charset;             /**< Character set */
+  } IDENT;
+
+/* High-level calls */
+
+  extern char *ident_id(int fd, int *timeout);
+
+  extern IDENT *ident_query(struct sockaddr *laddr, socklen_t llen,
+                           struct sockaddr *raddr, socklen_t rlen,
+                           int *timeout);
+
+  void ident_free(IDENT *id);
+
+#ifdef  __cplusplus
+}
+#endif
+#endif
diff --git a/hdrs/intrface.h b/hdrs/intrface.h
new file mode 100644 (file)
index 0000000..74abd8e
--- /dev/null
@@ -0,0 +1,5 @@
+/* Empty file
+ * This used to be a header file, but now isn't.
+ * We left the empty file to easy the pain for people using 
+ * *local.c files that include it.
+ */
diff --git a/hdrs/lock.h b/hdrs/lock.h
new file mode 100644 (file)
index 0000000..09d4a59
--- /dev/null
@@ -0,0 +1,127 @@
+/* lock.h */
+
+#include "copyrite.h"
+
+#ifndef __LOCK_H
+#define __LOCK_H
+
+#include "mushtype.h"
+#include "conf.h"
+#include "boolexp.h"
+
+/* I'm using a string for a lock type instead of a magic-cookie int for
+ * several reasons:
+ * 1) I don't think it will hurt efficiency that much. I'll profile it
+ * to check.
+ * 2) It will make debugging much easier to see lock types that can easily
+ * be interpreted by a human.
+ * 3) It allows the possibility of having arbitrary user-defined locks.
+ */
+
+/** A list of locks set on an object.
+ * An object's locks are represented as a linked list of these structures.
+ */
+struct lock_list {
+  lock_type type;              /**< Type of lock */
+  boolexp key;         /**< Lock value ("key") */
+  dbref creator;               /**< Dbref of lock creator */
+  int flags;                   /**< Lock flags */
+  struct lock_list *next;      /**< Pointer to next lock in object's list */
+};
+
+/* Our table of lock types, attributes, and default flags */
+typedef struct lock_msg_info LOCKMSGINFO;
+/** A lock.
+ * This structure represents a lock in the table of lock types
+ */
+struct lock_msg_info {
+  lock_type type;              /**< Type of lock */
+  const char *succbase;                /**< Base name of success attribute */
+  const char *failbase;                /**< Base name of failure attribute */
+};
+
+#define LF_VISUAL  0x1         /* Anyone can see this lock with lock()/elock() */
+#define LF_PRIVATE 0x2         /* This lock doesn't get inherited */
+#define LF_PRIVILEGE 0x4       /* Only privileged players can set/unset it */
+#define LF_LOCKED  0x8         /* Only the lock's owner can set/unset it */
+#define LF_NOCLONE 0x10                /* This lock isn't copied in @clone */
+#define LF_OX      0x20                /* This lock's success messages includes OX*. */
+#define LF_NOSUCCACTION 0x40   /* This lock doesn't have an @a-action for success. */
+#define LF_NOFAILACTION 0x80   /* This lock doesn't have an @a-action for failure */
+#define LF_OWNER        0x100  /* Lock can only be set/unset by object's owner */
+
+/* lock.c */
+boolexp getlock(dbref thing, lock_type type);
+boolexp getlock_noparent(dbref thing, lock_type type);
+lock_type match_lock(lock_type type);
+const lock_list *get_lockproto(lock_type type);
+int add_lock(dbref player, dbref thing, lock_type type, boolexp key, int flags);
+int add_lock_raw(dbref player, dbref thing, lock_type type,
+                boolexp key, int flags);
+void free_locks(lock_list *ll);
+int eval_lock(dbref player, dbref thing, lock_type ltype);
+int fail_lock(dbref player, dbref thing, lock_type ltype, const char *def,
+             dbref loc);
+void do_unlock(dbref player, const char *name, lock_type type);
+void do_lock(dbref player, const char *name, const char *keyname,
+            lock_type type);
+void init_locks(void);
+void clone_locks(dbref player, dbref orig, dbref clone);
+void do_lset(dbref player, char *what, char *flags);
+void do_list_locks(dbref player, const char *arg, int lc, const char *label);
+void list_locks(char *buff, char **bp, const char *name);
+const char *lock_flags(lock_list *ll);
+const char *lock_flags_long(lock_list *ll);
+void check_zone_lock(dbref player, dbref zone, int noisy);
+#define L_FLAGS(lock) ((lock)->flags)
+#define L_CREATOR(lock) ((lock)->creator)
+#define L_TYPE(lock) ((lock)->type)
+#define L_KEY(lock) ((lock)->key)
+#define L_NEXT(lock) ((lock)->next)
+/* can p read/evaluate lock l on object x? */
+int lock_visual(dbref, lock_type);
+#define Can_Read_Lock(p,x,l)   \
+    (CanSee(p, x) || controls(p,x) || ((Visual(x) || lock_visual(x, l)) && \
+     eval_lock(p,x,Examine_Lock)))
+
+/* The actual magic cookies. */
+extern const lock_type Basic_Lock;
+extern const lock_type Enter_Lock;
+extern const lock_type Use_Lock;
+extern const lock_type Zone_Lock;
+extern const lock_type Page_Lock;
+extern const lock_type Tport_Lock;
+extern const lock_type Speech_Lock;    /* Who can speak aloud in me */
+extern const lock_type Listen_Lock;    /* Who can trigger ^s/ahears on me */
+extern const lock_type Command_Lock;   /* Who can use $commands on me */
+extern const lock_type Parent_Lock;    /* Who can @parent to me */
+extern const lock_type Link_Lock;      /* Who can @link to me */
+extern const lock_type Leave_Lock;     /* Who can leave me */
+extern const lock_type Drop_Lock;      /* Who can drop me */
+extern const lock_type Give_Lock;      /* Who can give me */
+extern const lock_type Mail_Lock;      /* Who can @mail me */
+extern const lock_type Follow_Lock;    /* Who can follow me */
+extern const lock_type Examine_Lock;   /* Who can examine visual me */
+extern const lock_type Chzone_Lock;    /* Who can @chzone to this object? */
+extern const lock_type Forward_Lock;   /* Who can @forwardlist to object? */
+extern const lock_type Control_Lock;   /* Who can control this object? */
+extern const lock_type Dropto_Lock;    /* Who follows the dropto of this room? */
+extern const lock_type Destroy_Lock;   /* Who can @dest me if I'm dest_ok? */
+extern const lock_type Interact_Lock;
+extern const lock_type MailForward_Lock;       /* Who can forward mail to me */
+extern const lock_type Take_Lock;
+/* channel locks */
+#ifdef  NEWCHAT
+extern const lock_type chan_speak_lock;
+extern const lock_type chan_join_lock;
+extern const lock_type chan_mod_lock;
+extern const lock_type chan_see_lock;
+extern const lock_type chan_hide_lock;
+#endif
+
+/* Declare new lock types here! */
+
+/* The only locktype we don't pass is the uselock */
+#define IS_passlock_type(ltp) (strcasecmp(ltp , Use_Lock)!=0)
+
+#endif                         /* __LOCK_H */
diff --git a/hdrs/log.h b/hdrs/log.h
new file mode 100644 (file)
index 0000000..ea20250
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef LOG_H
+#define LOG_H
+
+/* log types */
+#define LT_ERR    0
+#define LT_CMD    1
+#define LT_WIZ    2
+#define LT_CONN   3
+#define LT_TRACE  4
+#define LT_RPAGE  5            /* Obsolete */
+#define LT_CHECK  6
+#define LT_HUH    7
+
+/* From log.c */
+extern void start_all_logs(void);
+extern void end_all_logs(void);
+extern void redirect_stderr(void);
+extern void WIN32_CDECL do_log
+  (int logtype, dbref player, dbref object, const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 4, 5)));
+extern void WIN32_CDECL do_rawlog(int logtype, const char *fmt, ...)
+  __attribute__ ((__format__(__printf__, 2, 3)));
+extern void do_logwipe(dbref player, int logtype, char *str);
+
+/* Activity log types */
+#define LA_CMD  0
+#define LA_PE   1
+#define LA_LOCK 2
+#define ACTIVITY_LOG_SIZE 3    /* In BUFFER_LEN-size lines */
+extern void log_activity(int type, dbref player, const char *action);
+extern void notify_activity(dbref player, int num_lines, int dump);
+extern const char *last_activity(void);
+extern int last_activity_type(void);
+
+#endif                         /* LOG_H */
diff --git a/hdrs/malias.h b/hdrs/malias.h
new file mode 100644 (file)
index 0000000..44ca603
--- /dev/null
@@ -0,0 +1,54 @@
+/* malias.h - header file for global mailing aliases/lists */
+
+#ifndef _MALIAS_H
+#define _MALIAS_H
+
+
+#define MALIAS_TOKEN    '+'    /* Initial char for alias names */
+
+#define ALIAS_MEMBERS   0x1    /* Only those on the alias */
+#define ALIAS_ADMIN     0x2    /* Only admin/powered */
+#define ALIAS_OWNER     0x4    /* Only the owner */
+
+/** A mail alias.
+ * This structure represents a mail alias (or mailing list).
+ */
+struct mail_alias {
+  char *name;          /**< Name of the alias */
+  unsigned char *desc; /**< Description */
+  int size;            /**< Size of the members array */
+  dbref *members;      /**< Pointer to an array of dbrefs of list members */
+  int nflags;          /**< Permissions for who can use/see alias name */
+  int mflags;          /**< Permissions for who can list alias members */
+  dbref owner;         /**< Who owns (controls) this alias */
+};
+
+
+/* From malias.c */
+struct mail_alias *get_malias(dbref player, char *alias);
+int ismember(struct mail_alias *m, dbref player);
+void do_malias_privs(dbref player, char *alias, char *privs, int typs);
+void do_malias_mprivs(dbref player, char *alias, char *privs);
+extern void do_malias(dbref player, char *arg1, char *arg2);
+extern void do_malias_create(dbref player, char *alias, char *tolist);
+extern void do_malias_members(dbref player, char *alias);
+extern void do_malias_list(dbref player);
+extern void do_malias_desc(dbref player, char *alias, char *desc);
+extern void do_malias_chown(dbref player, char *alias, char *owner);
+extern void do_malias_rename(dbref player, char *alias, char *newname);
+extern void do_malias_destroy(dbref player, char *alias);
+extern void do_malias_all(dbref player);
+extern void do_malias_stats(dbref player);
+extern void do_malias_nuke(dbref player);
+extern void do_malias_add(dbref player, char *alias, char *tolist);
+extern void do_malias_remove(dbref player, char *alias, char *tolist);
+extern void load_malias(FILE * fp);
+extern void save_malias(FILE * fp);
+extern void malias_cleanup(dbref player);
+extern void do_malias_set(dbref player, char *alias, char *tolist);
+#else                          /* MAIL_ALIASES */
+
+/* We still need this one */
+void load_malias(FILE * fp);
+
+#endif
diff --git a/hdrs/match.h b/hdrs/match.h
new file mode 100644 (file)
index 0000000..7ce6954
--- /dev/null
@@ -0,0 +1,61 @@
+/* match.h */
+
+#ifndef __MATCH_H
+#define __MATCH_H
+
+#include "copyrite.h"
+
+/* match constants */
+  /* match modifiers */
+#define MAT_CHECK_KEYS   0x000001
+#define MAT_GLOBAL       0x000002
+#define MAT_REMOTES      0x000004
+#define MAT_CONTROL      0x000008
+  /* individual things to match */
+#define MAT_ME           0x000010
+#define MAT_HERE         0x000020
+#define MAT_ABSOLUTE     0x000040
+#define MAT_PLAYER       0x000080
+#define MAT_NEIGHBOR     0x000100
+#define MAT_POSSESSION   0x000200
+#define MAT_EXIT         0x004000
+  /* special things to match */
+#define MAT_CARRIED_EXIT     0x010000
+#define MAT_CONTAINER        0x020000
+#define MAT_REMOTE_CONTENTS  0x040000
+#define MAT_NEAR             0x080000
+#define MAT_ENGLISH          0x100000
+#define MAT_EXACT            0x200000 /* only match preferred type */
+  /* types of match results - used internally */
+#define MAT_NOISY                0x1000000
+#define MAT_LAST                 0x2000000
+  /* groups of things to match */
+#define MAT_EVERYTHING   (MAT_ME|MAT_HERE|MAT_ABSOLUTE|MAT_PLAYER| \
+                          MAT_NEIGHBOR|MAT_POSSESSION|MAT_EXIT|MAT_ENGLISH)
+#define MAT_NEARBY       (MAT_EVERYTHING|MAT_NEAR)
+#define MAT_OBJECTS      (MAT_ME|MAT_ABSOLUTE|MAT_PLAYER|MAT_NEIGHBOR| \
+                          MAT_POSSESSION)
+#define MAT_NEAR_THINGS  (MAT_OBJECTS|MAT_NEAR)
+#define MAT_LIMITED      (MAT_ABSOLUTE|MAT_PLAYER|MAT_NEIGHBOR)
+#define MAT_REMOTE       (MAT_ABSOLUTE|MAT_PLAYER|MAT_REMOTE_CONTENTS|MAT_EXIT)
+
+
+/* Functions we can call */
+/* These functions do the matching and return the result:
+ * match_result - returns match, NOTHING, or AMBIGUOUS
+ * noisy_match_result - notifies player, returns match or NOTHING
+ * last_match_result - returns a match or NOTHING
+ * match_controlled - returns match if player controls, or NOTHING
+ */
+extern dbref match_result
+  (const dbref who, const char *name, const int type, const long int flags);
+extern dbref noisy_match_result
+  (const dbref who, const char *name, const int type, const long int flags);
+extern dbref last_match_result
+  (const dbref who, const char *name, const int type, const long int flags);
+extern dbref match_controlled(dbref player, const char *name);
+
+#define match_thing(player,name) \
+  noisy_match_result((player), (name), NOTYPE, MAT_EVERYTHING)
+
+#endif                         /* __MATCH_H */
diff --git a/hdrs/mushdb.h b/hdrs/mushdb.h
new file mode 100644 (file)
index 0000000..a44a6d2
--- /dev/null
@@ -0,0 +1,235 @@
+/* mushdb.h */
+
+#include "config.h"
+#include "copyrite.h"
+
+#ifndef __DB_H
+#define __DB_H
+
+/* Power macros */
+#include "flags.h"
+
+#define LastMod(x)             (db[x].lastmod)
+#define TC_Builder(x)       (command_check_byname(x, "@dig"))
+#define Builder(x)         OOREF(x,TC_Builder(x), TC_Builder(ooref))
+#define TC_CanModify(x,y)   (x == y || div_powover(x,y,"Modify"))
+#define CanModify(x,y)     OOREF(x,TC_CanModify(x,y), TC_CanModify(ooref,y))
+#define TC_Site(x)          (God(x) || div_powover(x,x,"Site") || (Inherit_Powers(x) && div_powover(Owner(x),Owner(x),"Site")))
+#define Site(x)                    OOREF(x, TC_Site(x), TC_Site(ooref))
+#define Guest(x)         (LEVEL(x) == LEVEL_GUEST) /* Guest needs no twincheck */
+#define TC_Tel_Anywhere(x)  (God(x))
+#define Tel_Anywhere(x)            OOREF(x,TC_Tel_Anywhere(x),TC_Tel_Anywhere(ooref))
+#define Tel_Anything(x)  (God(x)) /* This needs no twincheck. This is already accounted for in dbdefs.h */
+#define Tel_Where(x,y)   (Tel_Anywhere(x) || OOREF(x,div_powover(x,y,"Tel_Place"),div_powover(ooref,y,"Tel_Place")))
+#define Tel_Thing(x,y)   (Tel_Anything(x) || OOREF(x,div_powover(x,y,"Tel_Thing"),div_powover(ooref,y,"Tel_Thing")))
+#define TC_RPTEL(x)    (div_powover(x,x, "RPTel") || (Inherit_Powers(x) && div_powover(Owner(x), Owner(x), "RPTel")))
+#define Can_RPTEL(x)   OOREF(x,TC_RPTEL(x), TC_RPTEL(x))
+#define Can_BCREATE(x) (OOREF(x,div_powover(x,x, "BCreate"), div_powover(ooref, ooref, "BCreate")))
+#define See_All(x)       (God(x))
+#define CanNewpass(x,y)        OOREF(x,div_powover(x,y,"Newpass"), div_powover(ooref,y,"Newpass"))
+/* #define CanSee(x,y)      (God(x) || div_powover(x,y,POW_SEE_ALL)) */
+#define Prived(x)        OOREF(x,div_powover(x,x,"Privilege"),div_powover(ooref,ooref,"Privilege"))
+#define Priv_Who(x)      (OOREF(x,div_powover(x,x,"PrivWho"),div_powover(ooref,ooref, "PrivWho")) || Site(x))
+#define Can_Hide(x)      OOREF(x,div_powover(x,x,"Hide"),div_powover(ooref,ooref,"Hide"))
+#define Can_Login(x)     (div_powover(x,x,"Login")) /* This will never be checked via ooref */
+#define Can_Idle(x)      (div_powover(x,x,"Idle")) /* this won't either */
+#define Pass_Lock(x,y)   OOREF(x,div_powover(x,y,"Pass_Locks"),div_powover(ooref,y,"Pass_Locks"))
+#define TC_IsMailAdmin(x) (God(x) || (check_power_yescode(DPBITS(x),find_power("MailAdmin")) > 0))
+#define MailAdministrator(x)   OOREF(x,TC_IsMailAdmin(x),TC_IsMailAdmin(ooref))
+#define MailAdmin(x,y)   OOREF(x,div_powover(x,y,"MailAdmin"),div_powover(ooref,y,"MailAdmin"))
+#define TC_Long_Fingers(x) (div_powover(x,x,"Remote") || (Inherit_Powers(x) && div_powover(Owner(x),Owner(x), "Remote")))
+#define Long_Fingers(x)  OOREF(x,TC_Long_Fingers(x),TC_Long_Fingers(ooref))
+#define CanRemote(x,y)   OOREF(x,div_powover(x,y,"Remote"),div_powover(ooref,y,"Remote"))
+#define CanOpen(x,y)     OOREF(x,div_powover(x,y,"Open"),div_powover(ooref,y,"Open"))
+#define Link_Anywhere(x,y) OOREF(x,div_powover(x,y,"Link"),div_powover(ooref,y,"Link"))
+#define Can_Boot(x,y)    OOREF(x,div_powover(x,y,"Boot"),div_powover(ooref,y,"Boot"))
+#define Do_Quotas(x,y)   OOREF(x,div_powover(x,y,"Quota"),div_powover(ooref,y,"Quota"))
+#define Change_Quota(x,y) OOREF(x,div_powover(x,y,"SetQuotas"),div_powover(ooref,y,"SetQuotas"))
+#define Change_Poll(x)   OOREF(x,div_powover(x,x,"Poll"),div_powover(ooref,ooref,"Poll"))
+#define HugeQueue(x)     OOREF(x,div_powover(x,x,"Queue"),div_powover(ooref,ooref,"Queue"))
+#define LookQueue(x)     OOREF(x,div_powover(x,x,"See_Queue"),div_powover(ooref,ooref,"See_Queue"))
+#define CanSeeQ(x,y)     OOREF(x,div_powover(x,y,"See_Queue"),div_powover(ooref,y,"See_Queue"))
+
+#define HaltAny(x)       (Director(x) && OOREF(x,div_powover(x,x,"Halt"),div_powover(ooref,ooref,"Halt")))
+#define CanHalt(x,y)     OOREF(x,div_powover(x,y,"Halt"),div_powover(ooref,y,"Halt"))
+#define CanNuke(x,y)   OOREF(x,div_powover(x,y,"Nuke"),div_powover(ooref, y, "Nuke"))
+#define TC_NoPay(x)         (div_powover(x,x,"NoPay") || div_powover(Owner(x),Owner(x),"NoPay"))
+#define NoPay(x)       OOREF(x,TC_NoPay(x),TC_NoPay(ooref))
+#define TC_MoneyAdmin(x)    (NoPay(x) && Prived(x))
+#define MoneyAdmin(x)     OOREF(x,TC_MoneyAdmin(x),TC_MoneyAdmin(ooref))
+#define TC_NoQuota(x)       (div_powover(x,x,"NoQuota") || div_powover(Owner(x),Owner(x),"NoQuota"))
+#define TC_DNoQuota(x)     div_powover(x, x, "NoQuota")
+#define NoQuota(x)     (IsDivision(x) ? OOREF(x,TC_DNoQuota(x), TC_DNoQuota(ooref)) :  OOREF(x,TC_NoQuota(x),TC_NoQuota(ooref)))
+#define CanSearch(x,y)   OOREF(x,(Owner(x) == Owner(y) || div_powover(x,y,"Search")),(Owner(ooref) == Owner(y) || div_powover(ooref,y,"Search") ))
+#define Global_Funcs(x)  OOREF(x,div_powover(x,x,"GFuncs"),div_powover(ooref,ooref,"GFuncs"))
+#define Create_Player(x) OOREF(x,div_powover(x,x,"PCreate"),div_powover(ooref,ooref,"PCreate"))
+#define Can_Announce(x)  OOREF(x,div_powover(x,x,"Announce"),div_powover(ooref,ooref,"Announce"))
+#define TC_Can_Cemit(x)     (div_powover(x,x,"Cemit") || (Inherit_Powers(x) && div_powover(Owner(x),Owner(x),"Cemit")))
+#define Can_Cemit(x)   OOREF(x,TC_Can_Cemit(x),TC_Can_Cemit(ooref))
+#define Can_Pemit(x,y)  OOREF(x,div_powover(x,y,"Pemit"),div_powover(ooref,y,"Pemit"))
+#define Can_Nspemit(x)   (div_powover(x,x,"Can_NsPemit")) 
+#define CanProg(x,y)     OOREF(x,div_powover(x,y,"Program"),div_powover(ooref,y,"Program"))
+#define CanProgLock(x,y) OOREF(x,div_powover(x,y,"ProgLock"),div_powover(ooref,y,"ProgLock"))
+#define Sql_Ok(x)       (Director(x) || OOREF(x,div_powover(x,x,"SQL_Ok"),div_powover(ooref,ooref,"SQL_Ok")))
+#define Many_Attribs(x)        (OOREF(x,div_powover(x,x,"Many_Attribs"),div_powover(ooref,ooref,"Many_Attribs")))
+#define Can_Pueblo_Send(x)     (Director(x) || OOREF(x,div_powover(x,x,"Pueblo_Send"),div_powover(ooref,ooref,"Pueblo_Send")))
+#define Can_RPEMIT(x)  (div_powover(x,x, "RPEmit") || (Inherit_Powers(x) || div_powover(Owner(x),Owner(x), "RPEmit")) ||Admin(x))
+#define Can_RPCHAT(x)  (div_powover(x, x, "RPChat") || (Inherit_Powers(x) || div_powover(Owner(x),Owner(x), "RPChat")) || Admin(x))
+#define Inherit_Powers(x)      (Inherit(x) && Inheritable(Owner(x)))
+
+/* Permission macros */
+#define TC_Can_See_Flag(p,t,f) ((!(f->perms & (F_DARK | F_MDARK | F_ODARK | F_DISABLED)) || \
+                               ((!Mistrust(p) && (Owner(p) == Owner(t))) && \
+                                !(f->perms & (F_DARK | F_MDARK | F_DISABLED))) || \
+                             ((div_cansee(p,t) && Admin(p)) && !(f->perms & (F_DARK | F_DISABLED))) || \
+                             God(p)))
+#define Can_See_Flag(p,t,f)    OOREF(p,TC_Can_See_Flag(p,t,f),TC_Can_See_Flag(ooref,t,f))
+
+/* Can p locate x? */
+int unfindable(dbref);
+#define TC_Can_Locate(p,x) \
+    (controls(p,x) || nearby(p,x) || CanSee(p,x) \
+  || (command_check_byname(p, "@whereis") && (IsPlayer(x) && !Unfind(x) \
+                     && !unfindable(Location(x))))) && (Unfind(x) ? LEVEL(p) >= LEVEL(x) : 1)
+#define Can_Locate(p,x)        OOREF(p,TC_Can_Locate(p,x),TC_Can_Locate(ooref,x))
+
+
+#define TC_Can_Examine(p,x)    (controls(p,x)|| \
+        div_cansee(p,x) || (Visual(x) && eval_lock(p,x,Examine_Lock)))
+#define Can_Examine(p,x)       OOREF(p,TC_Can_Examine(p,x),TC_Can_Examine(ooref,x))
+#define CanSee(p,x)    Can_Examine(p,x)
+
+       /***< UnUsed macro? 
+        * - RLB
+#define TC_can_link(p,x)  (controls(p,x) || \
+                        (IsExit(x) && (Location(x) == NOTHING)))
+                       */
+
+/* Can p link an exit to x? */
+#define TC_can_link_to(p,x) \
+     (GoodObject(x) \
+   && (controls(p,x) || Link_Anywhere(p,x) || \
+       (LinkOk(x) && eval_lock(p,x,Link_Lock))) \
+   && (!NO_LINK_TO_OBJECT || IsRoom(x)))
+#define can_link_to(p,x) OOREF(p,TC_can_link_to(p,x),TC_can_link_to(ooref,x))
+
+       /* DivRead needs no TC designation */
+#define Can_DivRead_Attr(p,x,a)  ((div_cansee(p,x) && !(a->flags & AF_MDARK)) \
+                               || (div_cansee(p,x) && \
+                                   (div_powover(p,p,"Privilege")|| (Inherit_Powers(p)  \
+                                                                 &&( div_powover(Owner(p), Owner(p),"Privilege" ))))))
+
+/* can p access attribute a on object x? */
+#define TC_Can_Read_Attr(p,x,a)  can_read_attr_internal(p,x,a) 
+
+ /*  (God(p) || (!AF_Internal(a) && !(AF_Mdark(a) && !Admin(p)) &&  \
+    can_read_attr_internal((p), (x), (a))))
+   -- 
+    (CanSee(p,x) || Can_DivRead_Attr(p,x,a) || \
+     (!((a)->flags & AF_MDARK) && \
+      (controls(p,x) || ((a)->flags & AF_VISUAL) || \
+        (Visual(x) && eval_lock(p,x,Examine_Lock)) || \
+       (!Mistrust(p) && (Owner((a)->creator) == Owner(p)))))))
+       */
+#define Can_Read_Attr(p,x,a) OOREF(p,TC_Can_Read_Attr(p,x,a), TC_Can_Read_Attr(ooref,x,a))
+
+/** can p look at object x? */
+#define TC_can_look_at(p, x) \
+      (Long_Fingers(p) || nearby(p, x) || \
+            (nearby(p, Location(x)) && \
+                   (!Opaque(Location(x)) || controls(p, Location(x)))) || \
+            (nearby(Location(p), x) && \
+                   (!Opaque(Location(p)) || controls(p, Location(p)))))
+#define can_look_at(p, x) OOREF(p,TC_can_look_at(p,x), TC_can_look_at(ooref,x))
+
+ /* can anyone access attribute a on object x? */
+#define Is_Visible_Attr(x,a)   \
+    (!AF_Internal(a) && \
+     can_read_attr_internal(NOTHING, (x), (a)))
+
+
+/* can p write attribute a on object x, assuming p may modify x?
+ */
+#define TC_Can_Write_Attr(p,x,a) can_write_attr_internal((p), (x), (a), 1)
+#define Can_Write_Attr(p,x,a)  OOREF(p,TC_Can_Write_Attr(p,x,a),TC_Can_Write_Attr(ooref,x,a))
+#define TC_Can_Write_Attr_Ignore_Safe(p,x,a)  can_write_attr_internal(p,x,a, 0)
+#define Can_Write_Attr_Ignore_Safe(p,x,a) \
+               OOREF(p,TC_Can_Write_Attr_Ignore_Safe(p,x,a), TC_Can_Write_Attr_Ignore_Safe(ooref,x,a))
+  /*
+#define Can_Write_Attr(p,x,a)  \
+   (God(p) || \
+    (!((a)->flags & AF_INTERNAL) && \
+     !((a)->flags & AF_SAFE) && \
+       (((a)->creator == Owner(p)) || !((a)->flags & AF_LOCKED)) \
+   ))
+#define Can_Write_Attr_Ignore_Safe(p,x,a)  \
+   (God(p) || \
+    (!((a)->flags & AF_INTERNAL) && \
+       (((a)->creator == Owner(p)) || !((a)->flags & AF_LOCKED)) \
+   ))
+   */
+
+
+/* Can p forward a message to x (via @forwardlist)? */
+#define Can_Forward(p,x)  \
+    (controls(p,x) || div_powover(p,x,"Pemit") || \
+        ((getlock(x, Forward_Lock) != TRUE_BOOLEXP) && \
+         eval_lock(p, x, Forward_Lock)))
+
+/* Can p forward a mail message to x (via @mailforwardlist) ? */
+#define Can_MailForward(p,x)  \
+    (IsPlayer(x) && (controls(p,x) || \
+        ((getlock(x, MailForward_Lock) != TRUE_BOOLEXP) && \
+         eval_lock(p, x, MailForward_Lock))))
+
+/* Can from pass to's @lock/interact? */
+#define Pass_Interact_Lock(from,to) \
+  (Can_Pemit(from, to) || eval_lock(from, to, Interact_Lock))
+
+/* How many pennies can you have? */
+#define TC_Max_Pennies(p) (Guest(p) ? MAX_GUEST_PENNIES : MAX_PENNIES)
+#define Max_Pennies(p)         OOREF(p,TC_Max_Pennies(p),TC_Max_Pennies(ooref))
+#define TC_Paycheck(p) (Guest(p) ? GUEST_PAY_CHECK : PAY_CHECK)
+#define Paycheck(p)    OOREF(p,TC_Paycheck(p), TC_Paycheck(ooref))
+
+/* DB flag macros - these should be defined whether or not the
+ * corresponding system option is defined 
+ * They are successive binary numbers
+ */
+#define DBF_NO_CHAT_SYSTEM      0x01
+#define DBF_WARNINGS            0x02
+#define DBF_CREATION_TIMES      0x04
+#define DBF_NO_POWERS           0x08
+#define DBF_NEW_LOCKS           0x10
+#define DBF_NEW_STRINGS         0x20
+#define DBF_TYPE_GARBAGE        0x40
+#define DBF_SPLIT_IMMORTAL      0x80
+#define DBF_NO_TEMPLE           0x100
+#define DBF_LESS_GARBAGE        0x200
+#define DBF_AF_VISUAL           0x400
+#define DBF_VALUE_IS_COST       0x800
+#define DBF_LINK_ANYWHERE       0x1000
+#define DBF_NO_STARTUP_FLAG     0x2000
+#define DBF_PANIC               0x4000
+#define DBF_AF_NODUMP           0x8000
+#define DBF_SPIFFY_LOCKS        0x10000
+#define DBF_NEW_FLAGS           0x20000
+#define DBF_DIVISIONS          0x40000
+#define DBF_LABELS             0x100000
+#define DBF_NEW_ATR_LOCK       0x200000
+
+#define FLAG_DBF_CQUOTA_RENAME  0x01  /* Rename CQuota Power to SetQuotas */
+
+#define HAS_COBRADBFLAG(x,y)   (!(x & DBF_TYPE_GARBAGE) && (x & y)) /* Macro exists so cobra & penn dbflags can exist as same DBFs */
+
+/* Reboot DB flag macros - these should be defined whether or not the
+ * corresponding system option is defined 
+ * They are successive binary numbers
+ */
+#define RDBF_SCREENSIZE         0x01
+#define RDBF_TTYPE              0x02
+#define RDBF_PUEBLO_CHECKSUM    0x04
+/* Available: 0x08 - 0x8000 */
+#define RDBF_SU_EXIT_PATH      0x00010000
+
+#endif                         /* __DB_H */
diff --git a/hdrs/mushtype.h b/hdrs/mushtype.h
new file mode 100644 (file)
index 0000000..efd1901
--- /dev/null
@@ -0,0 +1,218 @@
+#ifndef MUSH_TYPES_H
+#define MUSH_TYPES_H
+#include "copyrite.h"
+#include "options.h"
+#include <stdio.h>
+#ifdef WIN32
+#include <windows.h>
+#endif
+#ifdef HAS_OPENSSL
+#include <openssl/ssl.h>
+#endif
+
+/* Connect Flags */
+/** Default connection, nothing special */
+#define CONN_DEFAULT 0
+/** Using Pueblo, Smial, Mushclient, Simplemu, or some other
+ *  pueblo-style HTML aware client */
+#define CONN_HTML 0x1
+/** Using a client that understands telnet options */
+#define CONN_TELNET 0x2
+/** Send a telnet option to test client */
+#define CONN_TELNET_QUERY 0x4
+/** Connection that should be close on load from reboot.db */
+#define CONN_CLOSE_READY 0x8
+/** Validated connection from an SSL concentrator */
+#define CONN_SSL_CONCENTRATOR 0x10
+/* Prompt */
+#define CONN_PROMPT 0x20 /* Do GOAHEAD Prompts */
+
+#define PromptConnection(d) (d->conn_flags & CONN_PROMPT)
+
+/** Iterate through a list of descriptors, and do something with those
+ * that are connected.
+ */
+#define DESC_ITER_CONN(d) \
+        for(d = descriptor_list;(d);d=(d)->next) \
+          if((d)->connected)
+
+/** Is a descriptor hidden? */
+#define Hidden(d)        ((d->hide == 1) && Can_Hide(d->player))
+
+
+
+#define MAX_SNOOPS 30
+
+/* Function number type */
+typedef double NVAL;
+
+/* Dbref type */
+typedef int dbref;
+
+/** The type that stores the warning bitmask */
+typedef long int warn_type;
+
+/* special dbref's */
+#define NOTHING (-1)           /* null dbref */
+#define AMBIGUOUS (-2)         /* multiple possibilities, for matchers */
+#define HOME (-3)              /* virtual room, represents mover's home */
+#define ANY_OWNER (-2)         /* For lstats and @stat */
+
+
+#define INTERACT_SEE 0x1
+#define INTERACT_HEAR 0x2
+#define INTERACT_MATCH 0x4
+#define INTERACT_PRESENCE 0x8
+
+typedef unsigned char *object_flag_type;
+
+/* Boolexps and locks */
+typedef const char *lock_type;
+typedef struct lock_list lock_list;
+
+typedef struct pe_info PE_Info;
+typedef struct debug_info Debug_Info;
+/** process_expression() info
+ * This type is used by process_expression().  In all but parse.c,
+ * this should be left as an incompletely-specified type, making it
+ * impossible to declare anything but pointers to it.
+ *
+ * Unfortunately, we need to know what it is in funlist.c, too,
+ * to prevent denial-of-service attacks.  ARGH!  Don't look at
+ * this struct unless you _really_ want to get your hands dirty.
+ */
+struct pe_info {
+  int fun_invocations;         /**< Invocation count */
+  int fun_depth;               /**< Recursion count */
+  int nest_depth;              /**< Depth of function nesting, for DEBUG */
+  int call_depth;              /**< Function call counter */
+  Debug_Info *debug_strings;   /**< DEBUG infromation */
+};
+
+/* new attribute foo */
+typedef struct attr ATTR;
+typedef ATTR ALIST;
+
+/* from prog.c */
+typedef struct prog_info_t {
+  dbref object;                /* object the program is located on */
+  ATTR *atr;           /* attribute to handle the program */
+  int lock;            /* whether or not the player is locked in the program */
+  int (*function)();   /* For internal programs.  Function to goto next */
+} PROG;
+
+typedef struct su_exit_path_t {
+  dbref player;
+  struct su_exit_path_t *next;
+} SU_PATH;
+
+/** A text block
+ */
+struct text_block {
+  int nchars;                  /**< Number of characters in the block */
+  struct text_block *nxt;      /**< Pointer to next block in queue */
+  unsigned char *start;                /**< Start of text */
+  unsigned char *buf;          /**< Current position in text */
+};
+/** A queue of text blocks.
+ */
+struct text_queue {
+  struct text_block *head;     /**< Pointer to the head of the queue */
+  struct text_block **tail;    /**< Pointer to pointer to tail of the queue */
+};
+
+
+typedef struct client_defaults_t {
+  char *terminal;
+  int flags;
+} CLIENT_DEFAULTS;
+
+/* Descriptor foo */
+#define DOING_LEN 36
+/** Pueblo checksum length.
+ * Pueblo uses md5 now, but if they switch to sha1, this will still
+ * be safe.
+ */
+#define PUEBLO_CHECKSUM_LEN 40
+typedef struct descriptor_data DESC;
+/** A player descriptor's data.
+ * This structure associates a connection's socket (file descriptor)
+ * with a lot of other relevant information.
+ */
+struct descriptor_data {
+  int descriptor;      /**< Connection socket (fd) */
+  int connected;       /**< Connection status */
+  char addr[101];      /**< Hostname of connection source */
+  char ip[101];                /**< IP address of connection source */
+  dbref player;                /**< Dbref of player associated with connection */
+  dbref snooper[MAX_SNOOPS];   /**< dbrefs of snoopers */ 
+  unsigned char *output_prefix;        /**< Text to show before output */
+  unsigned char *output_suffix;        /**< Text to show after output */
+  int output_size;             /**< Size of output left to send */
+  struct text_queue output;    /**< Output text queue */
+  struct text_queue input;     /**< Input text queue */
+  unsigned char *raw_input;    /**< Pointer to start of next raw input */
+  unsigned char *raw_input_at; /**< Pointer to position in raw input */
+  int (*input_handler)(DESC *, char *);        /**< Pointer to input handler */
+  long connected_at;   /**< Time of connection */
+  long last_time;      /**< Time of last activity */
+  long idle_total; /**< Total Idle Secs Expended.. This / Idle_Times == Idle Average for session */
+  int unidle_times; /**< Amoutn of Times unidled from 10 seconds */
+  int quota;           /**< Quota of commands allowed */
+  int cmds;            /**< Number of commands sent */
+  int hide;            /**< Hide status */
+  char doing[DOING_LEN];       /**< Player's doing string */
+#ifdef NT_TCP
+  /* these are for the Windows NT TCP/IO */
+  char input_buffer[512];      /**< WinNT: buffer for reading */
+  char output_buffer[512];     /**< WinNT: buffer for writing */
+  OVERLAPPED InboundOverlapped;        /**< WinNT: for asynchronous reading */
+  OVERLAPPED OutboundOverlapped;       /**< WinNT: for asynchronous writing */
+  BOOL bWritePending;          /**< WinNT: true if in process of writing */
+  BOOL bConnectionDropped;     /**< WinNT: true if we cannot send to player */
+  BOOL bConnectionShutdown;    /**< WinNT: true if connection has been shutdown */
+#endif
+  struct descriptor_data *next;        /**< Next descriptor in linked list */
+  struct descriptor_data *prev;        /**< Previous descriptor in linked list */
+  struct mail *mailp;  /**< Pointer to start of player's mail chain */
+  int conn_flags;      /**< Flags of connection (telnet status, etc.) */
+  unsigned long input_chars;   /**< Characters received */
+  unsigned long output_chars;  /**< Characters sent */
+  int width;                   /**< Screen width */
+  int height;                  /**< Screen height */
+  char *ttype;                 /**< Terminal type */
+  SU_PATH *su_exit_path;               /**< Su Exit Path */
+#ifdef HAS_OPENSSL
+  SSL *ssl;                    /**< SSL object */
+  int ssl_state;               /**< Keep track of state of SSL object */
+#endif
+  char checksum[PUEBLO_CHECKSUM_LEN+1]; /**<Pueblo checksum */
+  char prompt_info[1]; /* prompt info. 0 - in prompt, 1 - fed prompt */ 
+  PROG pinfo;
+};
+
+/* max length of command argument to process_command */
+#define MAX_COMMAND_LEN 4096
+#define BUFFER_LEN ((MAX_COMMAND_LEN)*2)
+#define MAX_ARG 63
+
+#ifdef CHAT_SYSTEM
+/* Channel stuff */
+typedef struct chanuser CHANUSER;
+typedef struct chanlist CHANLIST;
+typedef struct channel CHAN;
+#endif
+
+extern int password_handler(DESC *, char *);
+extern int pw_div_connect(DESC *, char *);
+extern int  pw_player_connect(DESC *, char *);
+extern void feed_snoop(DESC *, const char *, char );
+extern char is_snooped(DESC *);
+extern char set_snoop(dbref, DESC *);
+extern void clr_snoop(dbref, DESC *);
+extern void add_to_exit_path(DESC *d, dbref player);
+extern void announce_connect(dbref player, int isnew, int num);
+extern void announce_disconnect(dbref player);
+
+
+#endif
diff --git a/hdrs/mymalloc.h b/hdrs/mymalloc.h
new file mode 100644 (file)
index 0000000..f622e04
--- /dev/null
@@ -0,0 +1,35 @@
+/* A wrapper header that sets up the proper defines based on
+ * options.h's MALLOC_PACKAGE
+ */
+
+#ifndef _MYMALLOC_H
+#define _MYMALLOC_H
+
+#ifdef WIN32
+#undef malloc
+#undef calloc
+#undef realloc
+#undef free
+#endif
+
+/* If you're using gmalloc on some linux kernels, and have trouble
+ * with the compile, consider uncommenting this line: */
+/*#undef I_MALLOC */
+#ifdef I_MALLOC
+#include <malloc.h>
+#endif
+
+#include "options.h"
+
+#if (MALLOC_PACKAGE == 1)
+#define CSRI
+#elif (MALLOC_PACKAGE == 2)
+#include <stdlib.h>
+#define CSRI
+#define CSRI_TRACE
+#define CSRI_PROFILESIZES
+#define CSRI_DEBUG
+#include "csrimalloc.h"
+#endif
+
+#endif                         /* _MYMALLOC_H */
diff --git a/hdrs/mysocket.h b/hdrs/mysocket.h
new file mode 100644 (file)
index 0000000..0cafc8b
--- /dev/null
@@ -0,0 +1,160 @@
+/* Define required structures and constants if they're needed. 
+* Most of these are in Posix 1001.g, but getnameinfo isn't (Though
+* it's in at least one RFC. Anyways, all this gives us IP version
+* independance.
+*/
+
+#ifndef __MYSOCKET_H
+#define __MYSOCKET_H
+
+#include "copyrite.h"
+#include "config.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#ifndef FD_SETSIZE
+#define FD_SETSIZE 256
+#endif
+#include <winsock.h>
+#include <io.h>
+#undef EINTR                   /* Clashes with errno.h */
+#define EINTR WSAEINTR
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define EINPROGRESS WSAEINPROGRESS
+#define ETIMEDOUT WSAETIMEDOUT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#define ENOSPC          28
+#define snprintf _snprintf
+#define MAXHOSTNAMELEN 32
+#pragma comment( lib, "wsock32.lib")
+#pragma comment( lib, "winmm.lib")
+#pragma comment( lib, "advapi32.lib")
+#endif
+
+/* This number taken from Stevens. It's the size of the largest possible
+ * sockaddr_* struct. Since that includes unix-domain sockets, this
+ * gives us lots of buffer space. */
+#ifndef MAXSOCKADDR
+#define MAXSOCKADDR 128
+#endif
+
+#ifndef HAS_SOCKLEN_T
+typedef unsigned int socklen_t;
+#endif
+
+/** Information about a host.
+ */
+struct hostname_info {
+  const char *hostname;                /**< Host's name */
+  const char *port;            /**< Host's source port */
+};
+
+/** A union for sockaddr manipulation */
+union sockaddr_u {
+  struct sockaddr addr;                /**< A sockaddr structure */
+  char data[MAXSOCKADDR];      /**< A byte array representation */
+};
+
+/* What htons expects. Is this even used anymore? */
+typedef unsigned short Port_t;
+
+struct hostname_info *hostname_convert(struct sockaddr *host, int len);
+struct hostname_info *ip_convert(struct sockaddr *host, int len);
+
+
+/* Open a socket for listening */
+int make_socket
+  (Port_t port, union sockaddr_u *addr, socklen_t *len, const char *host);
+/* Connect somewhere */
+int make_socket_conn(const char *host, struct sockaddr *myiterface,
+                    socklen_t myilen, Port_t port, int *timeout);
+void make_nonblocking(int s);
+void set_keepalive(int s);
+int connect_nonb
+  (int sockfd, const struct sockaddr *saptr, socklen_t salen, int *nsec);
+/* Win32 uses closesocket() to close a socket, and so will we */
+#ifndef WIN32
+#define closesocket(s)  close(s)
+#else
+extern void shutdownsock(DESC *d);
+extern BOOL GetErrorMessage(const DWORD dwError, LPTSTR lpszError, const UINT
+                           nMaxError);
+#endif
+
+
+#ifndef HAS_GETHOSTBYNAME2
+#define gethostbyname2(host, type) gethostbyname((host))
+#endif
+
+#ifndef HAS_INET_PTON
+int inet_pton(int, const char *, void *);
+const char *inet_ntop(int, const void *, char *, size_t);
+#endif
+
+#ifndef HAS_GETADDRINFO
+/** addrinfo structure for systems without it.
+ * Everything here really belongs in <netdb.h>.
+ * These defines are separate for now, to avoid having to modify the
+ * system's header.
+ */
+
+struct addrinfo {
+  int ai_flags;                        /**< AI_PASSIVE, AI_CANONNAME */
+  int ai_family;               /**< PF_xxx */
+  int ai_socktype;             /**< SOCK_xxx */
+  int ai_protocol;             /**< IPPROTO_xxx for IPv4 and IPv6 */
+  size_t ai_addrlen;           /**< length of ai_addr */
+  char *ai_canonname;          /**< canonical name for host */
+  struct sockaddr *ai_addr;    /**< binary address */
+  struct addrinfo *ai_next;    /**< next structure in linked list */
+};
+
+struct addrinfo;
+                       /* following for getaddrinfo() */
+#define AI_PASSIVE               1     /* socket is intended for bind() + listen() */
+#define AI_CANONNAME     2     /* return canonical name */
+
+                       /* error returns */
+#define EAI_ADDRFAMILY   1     /* address family for host not supported */
+#define EAI_AGAIN                2     /* temporary failure in name resolution */
+#define EAI_BADFLAGS     3     /* invalid value for ai_flags */
+#define EAI_FAIL                 4     /* non-recoverable failure in name resolution */
+#define EAI_FAMILY               5     /* ai_family not supported */
+#define EAI_MEMORY               6     /* memory allocation failure */
+#define EAI_NODATA               7     /* no address associated with host */
+#define EAI_NONAME               8     /* host nor service provided, or not known */
+#define EAI_SERVICE              9     /* service not supported for ai_socktype */
+#define EAI_SOCKTYPE    10     /* ai_socktype not supported */
+#define EAI_SYSTEM              11     /* system error returned in errno */
+
+
+int getaddrinfo(const char *hostname, const char *servname,
+               const struct addrinfo *hintsp, struct addrinfo **result);
+/* If we don't have getaddrinfo, we won't have these either... */
+
+void freeaddrinfo(struct addrinfo *old);
+
+#endif                         /* HAS_GETADDRINFO */
+
+#ifndef HAS_GAI_STRERROR
+const char *gai_strerror(int errval);
+#endif
+
+#ifndef HAS_GETNAMEINFO                /* following for getnameinfo() */
+#ifndef __APPLE__
+/* Apple has these in netdb.h */
+#define NI_MAXHOST        1025 /* max hostname returned */
+#define NI_MAXSERV          32 /* max service name returned */
+
+#define NI_NOFQDN            1 /* do not return FQDN */
+#define NI_NUMERICHOST   2     /* return numeric form of hostname */
+#define NI_NAMEREQD          4 /* return error if hostname not found */
+#define NI_NUMERICSERV   8     /* return numeric form of service name */
+#define NI_DGRAM            16 /* datagram service for getservbyname() */
+#endif
+
+int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
+               size_t hostlen, char *serv, size_t servlen, int flags);
+#endif                         /* HAS_GETNAMEINFO */
+
+#endif                         /* MYSOCKET_H */
diff --git a/hdrs/myssl.h b/hdrs/myssl.h
new file mode 100644 (file)
index 0000000..70c2dc6
--- /dev/null
@@ -0,0 +1,31 @@
+/* Code to support SSL connections */
+
+#ifndef _MYSSL_H
+#define _MYSSL_H
+
+#include "copyrite.h"
+
+#ifdef HAS_OPENSSL
+
+SSL_CTX *ssl_init(void);
+SSL *ssl_setup_socket(int sock);
+void ssl_close_connection(SSL * ssl);
+SSL *ssl_listen(int sock, int *state);
+SSL *ssl_resume(int sock, int *state);
+int ssl_accept(SSL * ssl);
+int ssl_handshake(SSL * ssl);
+int ssl_need_accept(int state);
+int ssl_need_handshake(int state);
+int ssl_want_write(int state);
+int ssl_read(SSL * ssl, int state, int net_read_ready, int net_write_ready,
+            char *buf, int bufsize, int *bytes_read);
+int ssl_write(SSL * ssl, int state, int net_read_ready, int net_write_ready,
+             unsigned char *buf, int bufsize, int *offset);
+void ssl_write_session(FILE * fp, SSL * ssl);
+void ssl_read_session(FILE * fp);
+void ssl_write_ssl(FILE * fp, SSL * ssl);
+SSL *ssl_read_ssl(FILE * fp, int sock);
+
+
+#endif                         /* HAS_OPENSSL */
+#endif                         /* _MYSSL_H */
diff --git a/hdrs/oldflags.h b/hdrs/oldflags.h
new file mode 100644 (file)
index 0000000..b1c767d
--- /dev/null
@@ -0,0 +1,161 @@
+/* oldflags.h */
+
+/* The bit values we used to use for flags and toggles in the old days */
+
+#ifndef __OLDFLAGS_H
+#define __OLDFLAGS_H
+
+
+/*--------------------------------------------------------------------------
+ * Generic flags
+ */
+
+#define OLD_TYPE_ROOM       0x0
+#define OLD_TYPE_THING      0x1
+#define OLD_TYPE_EXIT       0x2
+#define OLD_TYPE_PLAYER     0x3
+#define OLD_TYPE_DIVISION   0x4
+#define OLD_TYPE_GARBAGE    0x6
+#define OLD_NOTYPE          0x7        /* no particular type */
+#define OLD_TYPE_MASK       0x7        /* room for expansion */
+
+/* -- empty slot 0x8 -- */
+#define LINK_OK         0x20   /* anybody can link to this room */
+#define DARK            0x40   /* contents of room are not printed */
+                               /* exit doesn't appear as 'obvious' */
+#define VERBOSE         0x80   /* print out command before executing it */
+#define STICKY          0x100  /* goes home when dropped */
+#define TRANSPARENTED     0x200        /* can look through exit to see next room,
+                                * or room "long exit display.
+                                * We don't call it TRANSPARENT because
+                                * that's a Solaris macro
+                                */
+#define HAVEN           0x400  /* this room disallows kills in it */
+                               /* on a player, disallow paging, */
+#define QUIET           0x800  /* On an object, will not emit 'set'
+                                * messages.. on a player.. will not see ANY
+                                * set messages
+                                */
+#define HALT            0x1000 /* object cannot perform actions */
+#define UNFIND          0x2000 /* object cannot be found (or found in */
+#define GOING           0x4000 /* object is available for recycling */
+#define ACCESSED        0x8000 /* Obsolete - only for conversion */
+/* -- empty slot 0x8000, once your db is converted -- */
+#define MARKED          0x10000        /* flag used to trace db checking of room
+                                * linkages. */
+#define NOWARN          0x20000        /* Object will not cause warnings. 
+                                * If set on a player, player will not
+                                * get warnings (independent of player's
+                                * @warning setting
+                                */
+
+#define CHOWN_OK        0x40000        /* object can be 'stolen' and made yours */
+#define ENTER_OK        0x80000        /* object basically acts like a room with
+                                * only one exit (leave), on players 
+                                * means that items can be given freely, AND
+                                * taken from!
+                                */
+#define VISUAL          0x100000       /* People other than owners can see 
+                                        * property list of object.
+                                        */
+#define LIGHT           0x200000       /* Visible in DARK rooms */
+
+#define LOOK_OPAQUE          0x800000  /* Objects inside object will not be
+                                        * seen on a look.
+                                        */
+
+#define DEBUGGING       0x2000000      /* returns parser evals */
+#define SAFE            0x4000000      /* cannot be destroyed */
+#define STARTUP         0x8000000      /* Used for converting old dbs */
+/* -- empty slot 0x8000000, if your db is already converted -- */
+#define AUDIBLE         0x10000000     /* rooms are flagged as having emitter 
+                                        * exits. exits act like emitters, 
+                                        * sound propagates to the exit dest. 
+                                        */
+#define NO_COMMAND      0x20000000     /* don't check for $commands */
+
+#define GOING_TWICE     0x40000000     /* Marked for destruction, but
+                                        * spared once. */
+/* -- empty slot 0x80000000 -- */
+
+/*--------------------------------------------------------------------------
+ * Player flags
+ */
+
+#define PLAYER_TERSE    0x8    /* suppress autolook messages */
+#define PLAYER_MYOPIC   0x10   /* look at everything as if player
+                                * doesn't control it.
+                                */
+#define PLAYER_NOSPOOF  0x20   /* sees origin of emits */
+#define PLAYER_SUSPECT  0x40   /* notifies of a player's name changes,
+                                * (dis)connects, and possible logs
+                                * logs commands.
+                                */
+#define PLAYER_GAGGED   0x80   /* can only move */
+#define PLAYER_MONITOR  0x100  /* sees (dis)connects broadcasted */
+#define PLAYER_CONNECT  0x200  /* connected to game */
+#define PLAYER_ANSI     0x400  /* enable sending of ansi control
+                                * sequences (for examine).
+                                */
+#define PLAYER_ZONE     0x800  /* Zone Master (zone control owner) */
+#define PLAYER_FIXED  0x4000   /* can't @tel or home */
+#ifdef ONLINE_REG
+#define PLAYER_UNREG  0x8000   /* Not yet registered */
+#endif
+#ifdef VACATION_FLAG
+#define PLAYER_VACATION 0x10000        /* On vacation */
+#endif
+#define PLAYER_COLOR      0x80000      /* ANSI color ok */
+#define PLAYER_NOACCENTS 0x100000      /* Strip accented text on output */
+#define PLAYER_PARANOID 0x200000       /* Paranoid nospoof */
+#define PLAYER_PROG    0x400000
+
+
+
+/*--------------------------------------------------------------------------
+ * Thing flags
+ */
+
+#define THING_DEST_OK   0x8    /* can be destroyed by anyone */
+#define THING_PUPPET    0x10   /* echoes to its owner */
+#define THING_LISTEN    0x20   /* checks for ^ patterns */
+#define THING_NOLEAVE   0x40   /* Can't be left */
+#define THING_INHEARIT  0x80   /* checks parent chain for ^ patterns */
+#define THING_Z_TEL     0x100  /* If set on ZMO players may only @tel 
+                                  within the zone */
+
+/*--------------------------------------------------------------------------
+ * Room flags
+ */
+
+#define ROOM_FLOATING   0x8    /* room is not linked to rest of
+                                * MUSH. Don't blather about it.
+                                */
+#define ROOM_ABODE      0x10   /* players may link themselves here */
+#define ROOM_JUMP_OK    0x20   /* anyone may @teleport to here */
+#define ROOM_NO_TEL     0x40   /* mortals cannot @tel from here */
+#define ROOM_TEMPLE     0x80   /* objects dropped here are sacrificed
+                                * (destroyed) and player gets money.
+                                * Now used only for conversion.
+                                */
+#define ROOM_LISTEN    0x100   /* checks for ^ patterns */
+#define ROOM_Z_TEL     0x200   /* If set on a room, players may
+                                * only @tel to another room in the
+                                * same zone 
+                                */
+#define ROOM_INHEARIT  0x400   /* checks parent chain for ^ patterns */
+
+#define ROOM_UNINSPECT 0x1000  /* Not inspected */
+
+
+/*--------------------------------------------------------------------------
+ * Exit flags
+ */
+
+#define EXIT_CLOUDY     0x8    /* Looking through a cloudy transparent
+                                * exit shows the room's desc, not contents.
+                                * Looking through a cloudy !trans exit,
+                                * shows the room's contents, not desc
+                                */
+
+#endif                         /* __OLDFLAGS_H */
diff --git a/hdrs/parse.h b/hdrs/parse.h
new file mode 100644 (file)
index 0000000..6be0dc7
--- /dev/null
@@ -0,0 +1,246 @@
+/* parse.h - parser declarations and macros
+
+ * Written by T. Alexander Popiel, 13 May 1995
+ * Last modified by T. Alexander Popiel, 26 May 1995
+ *
+ * Copyright (c) 1995 by T. Alexander Popiel
+ * See copyrite.h for details.
+ */
+
+#ifndef _PARSE_H_
+#define _PARSE_H_
+
+#include "copyrite.h"
+
+#include "config.h"
+#include <stdlib.h>
+#include <limits.h>
+#include <math.h>
+
+#include "confmagic.h"
+
+/* This is the type to be used for numbers which may be non-integral. */
+#define HUGE_NVAL       HUGE_DOUBLE
+
+/* These are some common error messages. */
+extern char e_int[];           /* #-1 ARGUMENT MUST BE INTEGER */
+extern char e_ints[];          /* #-1 ARGUMENTS MUST BE INTEGERS */
+extern char e_uint[];          /* #-1 ARGUMENT MUST BE POSITIVE INTEGER */
+extern char e_uints[];         /* #-1 ARGUMENTS MUST BE POSITIVE INTEGERS */
+extern char e_num[];           /* #-1 ARGUMENT MUST BE NUMBER */
+extern char e_nums[];          /* #-1 ARGUMENTS MUST BE NUMBERS */
+extern char e_perm[];          /* #-1 PERMISSION DENIED */
+extern char e_atrperm[];       /* #-1 NO PERMISSION TO GET ATTRIBUTE */
+extern char e_match[];         /* #-1 NO MATCH */
+extern char e_notvis[];                /* #-1 NO SUCH OBJECT VISIBLE */
+extern char e_disabled[];      /* #-1 FUNCTION DISABLED */
+extern char e_range[];         /* #-1 OUT OF RANGE */
+
+/* The following routines all take strings as arguments, and return
+ * data of the appropriate types.  
+ */
+
+extern int parse_boolean(char const *str);
+extern dbref parse_dbref(char const *str);
+extern dbref qparse_dbref(char const *str);
+extern dbref parse_objid(char const *str);
+
+#define parse_integer(str) strtol(str, NULL, 10)
+#define parse_uinteger(str) strtoul(str, NULL, 10)
+
+#define parse_number(str) strtod(str, NULL)
+
+
+
+/* The following routines all take varoius arguments, and return
+ * string representations of same.  The string representations
+ * are stored in static buffers, so the next call to each function
+ * will destroy any old string that was there.
+ */
+
+#define unparse_boolean(x) ((x) ? "1" : "0")
+
+char *unparse_dbref(dbref num);
+char *unparse_integer(int num);
+char *unparse_uinteger(unsigned int num);
+char *unparse_number(NVAL num);
+char *unparse_types(int type);
+
+/* The following routines all take strings as arguments, and return
+ * true iff the string is a valid representation of the appropriate type.
+ */
+int is_dbref(char const *str);
+int is_objid(char const *str);
+
+int is_integer(char const *str);
+int is_uinteger(char const *str);
+int is_boolean(char const *str);
+
+/* Split a sep-delimited string into individual elements */
+extern int list2arr(char *r[], int max, char *list, char sep);
+/* split up a sep-delimietd string into individual elements and acknowledge that the seperate character may be escaepd out */
+extern int elist2arr(char *r[], int max, char *list, char sep);
+/* The reverse */
+extern void arr2list(char *r[], int max, char *list, char **lp, char *sep);
+
+
+
+
+/* All function declarations follow the format: */
+#ifndef HAVE_FUN_DEFINED
+typedef struct fun FUN;
+#define HAVE_FUN_DEFINED
+#endif
+/** Common declaration for softcode function implementations */
+#define FUNCTION(fun_name) \
+  /* ARGSUSED */ /* try to keep lint happy */ \
+  void fun_name (FUN *fun, char *buff, char **bp, int nargs, char *args[], \
+                   int arglens[], dbref executor, dbref caller, dbref enactor, \
+                   char const *called_as, PE_Info *pe_info); \
+  void fun_name(FUN *fun __attribute__ ((__unused__)), \
+                char *buff __attribute__ ((__unused__)), \
+                char **bp  __attribute__ ((__unused__)), \
+                int nargs  __attribute__ ((__unused__)), \
+                char *args[]  __attribute__ ((__unused__)), \
+                int arglens[] __attribute__ ((__unused__)), \
+                dbref executor  __attribute__ ((__unused__)), \
+                dbref caller  __attribute__ ((__unused__)), \
+                dbref enactor  __attribute__ ((__unused__)), \
+                char const *called_as  __attribute__ ((__unused__)), \
+                PE_Info *pe_info  __attribute__ ((__unused__)))
+
+/* All results are returned in buff, at the point *bp.  *bp is likely
+ * not equal to buff, so make no assumptions about writing at the
+ * start of the buffer.  *bp must be updated to point at the next
+ * place to be filled (ala safe_str() and safe_chr()).  Be very
+ * careful about not overflowing buff; use of safe_str() and safe_chr()
+ * for all writes into buff is highly recommended.
+ *
+ * nargs is the count of the number of arguments passed to the function,
+ * and args is an array of pointers to them.  args will have at least
+ * nargs elements, or 10 elements, whichever is greater.  The first ten
+ * elements are initialized to NULL for ease of porting functions from
+ * the old style, but relying on such is considered bad form.
+ * The argument strings are stored in BUFFER_LEN buffers, but reliance
+ * on that size is also considered bad form.  The argument strings may
+ * be modified, but modifying the pointers to the argument strings will
+ * cause crashes. 
+ *
+ * executor corresponds to %!, the object invoking the function.
+ * caller   corresponds to %@, the last object to do a U() or similar.
+ * enactor  corresponds to %#, the object that started the whole mess.
+ * Note that fun_ufun() and similar must swap around these parameters
+ * in calling process_expression(); no checks are made in the parser
+ * itself to maintain these values.
+ *
+ * called_as contains a pointer to the name of the function called
+ * (taken from the function table).  This may be used to distinguish
+ * multiple functions which use the same C function for implementation.
+ *
+ * pe_info holds context information used by the parser.  It should
+ * be passed untouched to process_expression(), if it is called.
+ * pe_info should be treated as a black box; its structure and contents
+ * may change without notice.
+ *
+ * Normally, p_e() returns 0. It returns 1 upon hitting the CPU time limit.
+ */
+
+/* process_expression() evaluates expressions.  What a concept. */
+
+int process_expression(char *buff, char **bp, char const **str,
+                      dbref executor, dbref caller, dbref enactor,
+                      int eflags, int tflags, PE_Info * pe_info);
+
+/* buff is a pointer to a BUFFER_LEN string to contain the expression
+ * result.  *bp is the point in buff at which the result should be written.
+ * *bp will be updated to point one past the result of the expression,
+ * and the result will _NOT_ be null-terminated.
+ * For top-level calls to process_expression(), *bp should probably equal
+ * buff.  For calls to process_expression() inside function implementations,
+ * buff and bp should probably be the values passed into the implementation.
+ *
+ * *str is a pointer to a string containing the expression to evaluate.
+ * *str will be updated to point at the terminator which caused return
+ * from process_expression().  The string pointed to by *str will not
+ * be modified.
+ *
+ * executor, caller, and enactor represent %!, %@, and %#, respectively.
+ * No validity checking of any sort is done on these parameters, so please
+ * be careful with them.
+ *
+ * eflags consists of one or more of the following evaluation flags:
+ */
+
+#define PE_NOTHING              0
+#define PE_COMPRESS_SPACES      0x00000001
+#define PE_STRIP_BRACES         0x00000002
+#define PE_COMMAND_BRACES       0x00000004
+#define PE_EVALUATE             0x00000010
+#define PE_FUNCTION_CHECK       0x00000020
+#define PE_FUNCTION_MANDATORY   0x00000040
+#define PE_LITERAL              0x00000100
+#define PE_DOLLAR               0x00000200
+#define PE_DEBUG                0x00000400
+
+#define PE_DEFAULT (PE_COMPRESS_SPACES | PE_STRIP_BRACES | \
+                    PE_EVALUATE | PE_FUNCTION_CHECK)
+
+/* PE_COMPRESS_SPACES strips leading and trailing spaces, and reduces sets
+ * of internal spaces to one space.
+ *
+ * PE_STRIP_BRACES strips off top-level braces.
+ *
+ * PE_COMMAND_BRACES strips off only completely enclosing braces,
+ * suitable for trimming command lists given to noparse commands like
+ * @switch or @break.
+ *
+ * PE_EVALUATE allows %-substitutions, []-evaluation, function evaluation,
+ * and \-stripping.
+ *
+ * PE_FUNCTION_CHECK allows function evaluation.  Note that both PE_EVALUATE
+ * and PE_FUNCTION_CHECK must be active for function evaluation to occur.
+ *
+ * PE_FUNCTION_MANDATORY causes an error to be reported if a function call
+ * is attempted for a non-existant function.  Otherwise, the function call
+ * is not evaluated, but rather treated as normal text.
+ *
+ * PE_LITERAL prevents { and [ from being recognized and causing recursion.
+ *
+ * PE_DEFAULT is the most commonly used set of flags, normally sufficient
+ * for calls to process_expression().
+ *
+ *
+ * tflags consists of one or more of the following termination flags:
+ */
+
+#define PT_NOTHING      0
+#define PT_BRACE        0x00000001
+#define PT_BRACKET      0x00000002
+#define PT_PAREN        0x00000004
+#define PT_COMMA        0x00000008
+#define PT_SEMI         0x00000010
+#define PT_EQUALS       0x00000020
+#define PT_SPACE        0x00000040
+
+/* These represent '\0', '}', ']', ')', ',', ';', '=', and ' ', respectively.
+ * If the character corresponding to a set flag is encountered, then
+ * process_expression() will exit, with *str pointing at the terminating
+ * charater.  '\0' is always a terminating character.
+ *
+ * PT_DEFAULT, below, is provided as syntactic sugar.
+ */
+
+#define PT_DEFAULT PT_NOTHING
+
+/* pe_info is a pointer to a structure of internal state information
+ * for process_expression().  Top-level calls to process_expression()
+ * should pass a NULL as pe_info.  Calls to process_expression() from
+ * function implementations should pass their pe_info as pe_info.
+ * In no case should any other pe_info be passed to process_expression().
+ */
+
+/* For the cpu time limiting. From timer.c */
+extern void start_cpu_timer(void);
+extern void reset_cpu_timer(void);
+
+#endif                         /* !_PARSE_H_ */
diff --git a/hdrs/pcre.h b/hdrs/pcre.h
new file mode 100644 (file)
index 0000000..38e9e00
--- /dev/null
@@ -0,0 +1,139 @@
+/*************************************************
+*       Perl-Compatible Regular Expressions      *
+*************************************************/
+
+/* Copyright (c) 1997-2003 University of Cambridge */
+
+/* Modified a bit by Shawn Wagner for inclusion in PennMUSH. See
+   pcre.c for details. */
+
+#ifndef _PCRE_H
+#define _PCRE_H
+
+#define PCRE_MAJOR          4
+#define PCRE_MINOR          2
+#define PCRE_DATE           14-Apr-2003
+
+#ifndef PCRE_DATA_SCOPE
+#  define PCRE_DATA_SCOPE     extern
+#endif
+
+/* Have to include stdlib.h in order to ensure that size_t is defined;
+it is needed here for malloc. */
+
+#include <stdlib.h>
+
+/* Allow for C++ users */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Options */
+
+#define PCRE_CASELESS           0x0001
+#define PCRE_MULTILINE          0x0002
+#define PCRE_DOTALL             0x0004
+#define PCRE_EXTENDED           0x0008
+#define PCRE_ANCHORED           0x0010
+#define PCRE_DOLLAR_ENDONLY     0x0020
+#define PCRE_EXTRA              0x0040
+#define PCRE_NOTBOL             0x0080
+#define PCRE_NOTEOL             0x0100
+#define PCRE_UNGREEDY           0x0200
+#define PCRE_NOTEMPTY           0x0400
+#define PCRE_UTF8               0x0800
+#define PCRE_NO_AUTO_CAPTURE    0x1000
+#define PCRE_NO_UTF8_CHECK      0x2000
+
+/* Exec-time and get/set-time error codes */
+
+#define PCRE_ERROR_NOMATCH        (-1)
+#define PCRE_ERROR_NULL           (-2)
+#define PCRE_ERROR_BADOPTION      (-3)
+#define PCRE_ERROR_BADMAGIC       (-4)
+#define PCRE_ERROR_UNKNOWN_NODE   (-5)
+#define PCRE_ERROR_NOMEMORY       (-6)
+#define PCRE_ERROR_NOSUBSTRING    (-7)
+#define PCRE_ERROR_MATCHLIMIT     (-8)
+#define PCRE_ERROR_CALLOUT        (-9) /* Never used by PCRE itself */
+
+/* Request types for pcre_fullinfo() */
+
+#define PCRE_INFO_OPTIONS            0
+#define PCRE_INFO_SIZE               1
+#define PCRE_INFO_CAPTURECOUNT       2
+#define PCRE_INFO_BACKREFMAX         3
+#define PCRE_INFO_FIRSTBYTE          4
+#define PCRE_INFO_FIRSTCHAR          4 /* For backwards compatibility */
+#define PCRE_INFO_FIRSTTABLE         5
+#define PCRE_INFO_LASTLITERAL        6
+#define PCRE_INFO_NAMEENTRYSIZE      7
+#define PCRE_INFO_NAMECOUNT          8
+#define PCRE_INFO_NAMETABLE          9
+#define PCRE_INFO_STUDYSIZE         10
+
+/* Request types for pcre_config() */
+
+#define PCRE_CONFIG_UTF8                    0
+#define PCRE_CONFIG_NEWLINE                 1
+#define PCRE_CONFIG_LINK_SIZE               2
+#define PCRE_CONFIG_POSIX_MALLOC_THRESHOLD  3
+#define PCRE_CONFIG_MATCH_LIMIT             4
+
+/* Bit flags for the pcre_extra structure */
+
+#define PCRE_EXTRA_STUDY_DATA          0x0001
+#define PCRE_EXTRA_MATCH_LIMIT         0x0002
+#define PCRE_EXTRA_CALLOUT_DATA        0x0004
+
+/* Types */
+
+  struct real_pcre;            /* declaration; the definition is private  */
+  typedef struct real_pcre pcre;
+
+/* The structure for passing additional data to pcre_exec(). This is defined in
+such as way as to be extensible. */
+
+  typedef struct pcre_extra {
+    unsigned long int flags;   /* Bits for which fields are set */
+    void *study_data;          /* Opaque data from pcre_study() */
+    unsigned long int match_limit;     /* Maximum number of calls to match() */
+    void *callout_data;                /* Data passed back in callouts */
+  } pcre_extra;
+
+/* The structure for passing out data via the pcre_callout_function. We use a
+structure so that new fields can be added on the end in future versions,
+without changing the API of the function, thereby allowing old clients to work
+without modification. */
+
+  typedef struct pcre_callout_block {
+    int version;               /* Identifies version of block */
+    /* ------------------------ Version 0 ------------------------------- */
+    int callout_number;                /* Number compiled into pattern */
+    int *offset_vector;                /* The offset vector */
+    const char *subject;       /* The subject being matched */
+    int subject_length;                /* The length of the subject */
+    int start_match;           /* Offset to start of this match attempt */
+    int current_position;      /* Where we currently are */
+    int capture_top;           /* Max current capture */
+    int capture_last;          /* Most recently closed capture */
+    void *callout_data;                /* Data passed in with the call */
+    /* ------------------------------------------------------------------ */
+  } pcre_callout_block;
+
+
+/* Exported PCRE functions */
+
+  extern pcre *pcre_compile(const char *, int, const char **,
+                           int *, const unsigned char *);
+  extern int pcre_copy_substring(const char *, int *, int, int, char *, int);
+  extern int pcre_exec(const pcre *, const pcre_extra *,
+                      const char *, int, int, int, int *, int);
+  extern const unsigned char *pcre_maketables(void);
+  extern pcre_extra *pcre_study(const pcre *, int, const char **);
+
+#ifdef __cplusplus
+}                              /* extern "C" */
+#endif
+#endif                         /* End of pcre.h */
diff --git a/hdrs/privtab.h b/hdrs/privtab.h
new file mode 100644 (file)
index 0000000..b068c93
--- /dev/null
@@ -0,0 +1,35 @@
+/* privtab.h */
+/* Defines a privilege table entry for general use */
+
+#ifndef __PRIVTAB_H
+#define __PRIVTAB_H
+
+#include "copyrite.h"
+#include "config.h"
+#include "confmagic.h"
+
+
+typedef struct priv_info PRIV;
+/** Privileges.
+ * This structure represents a privilege and its associated data.
+ * Privileges tables are used to provide a unified way to parse
+ * a string of restrictions into a bitmask.
+ */
+struct priv_info {
+  const char *name;    /**< Name of the privilege */
+  char letter;         /**< One-letter abbreviation */
+  long int bits_to_set;        /**< Bitflags required to set this privilege */
+  long int bits_to_show;       /**< Bitflags required to see this privilege */
+};
+
+#define PrivName(x)     ((x)->name)
+#define PrivChar(x)     ((x)->letter)
+#define PrivSetBits(x)  ((x)->bits_to_set)
+#define PrivShowBits(x) ((x)->bits_to_show)
+
+extern int string_to_privs(PRIV *table, const char *str, long int origprivs);
+extern int letter_to_privs(PRIV *table, const char *str, long int origprivs);
+extern const char *privs_to_string(PRIV *table, int privs);
+extern const char *privs_to_letters(PRIV *table, int privs);
+
+#endif                         /* __PRIVTAB_H */
diff --git a/hdrs/ptab.h b/hdrs/ptab.h
new file mode 100644 (file)
index 0000000..79a200f
--- /dev/null
@@ -0,0 +1,35 @@
+/* Prefix-matched-key lookups. */
+
+#ifndef PTAB_H
+#define PTAB_H
+
+struct ptab_entry;
+/** Prefix table.
+ * This structure represents a prefix table. In a prefix table, 
+ * data is looked up by the best matching prefix of the given key.
+ */
+typedef struct ptab {
+  int state;                   /**< Internal table state */
+  int len;                     /**< Table size */
+  int maxlen;                  /**< Maximum table size */
+  int current;                 /**< Internal table state */
+  struct ptab_entry **tab;     /**< Pointer to array of entries */
+} PTAB;
+
+
+void ptab_init(PTAB *);
+void ptab_free(PTAB *);
+void *ptab_find(PTAB *, const char *);
+void *ptab_find_exact(PTAB *, const char *);
+void ptab_delete(PTAB *, const char *);
+void ptab_start_inserts(PTAB *);
+void ptab_end_inserts(PTAB *);
+void ptab_insert(PTAB *, const char *, void *);
+void ptab_stats_header(dbref);
+void ptab_stats(dbref, PTAB *, const char *);
+void *ptab_firstentry_new(PTAB *, char *key);
+void *ptab_nextentry_new(PTAB *, char *key);
+#define ptab_firstentry(x) ptab_firstentry_new(x,NULL)
+#define ptab_nextentry(x) ptab_nextentry_new(x,NULL)
+
+#endif                         /* PTAB_H */
diff --git a/hdrs/pueblo.h b/hdrs/pueblo.h
new file mode 100644 (file)
index 0000000..ebb62bb
--- /dev/null
@@ -0,0 +1,54 @@
+/* pueblo.h */
+
+#ifndef __PUEBLO_H
+#define __PUEBLO_H
+
+/* Ok. The original idea for this came from seeing the Tiny patch for Pueblo. 
+ * A few months later I felt the urge to add some pueblo thingies to Penn, 
+ * and did so, though at a quite different level. This led to the 
+ * discovery of the trouble with bsd.c, which also got partly rewritten.
+ * The commands @emit/html and pemit_html() were added to have Tiny compability
+ */
+
+#include "conf.h"
+
+#define PUEBLOBUFF \
+       char pbuff[BUFFER_LEN]; \
+       char *pp
+#define PUSE \
+       pp=pbuff
+#define PEND \
+       *pp=0;
+
+#define notify_nopenter_by(t,a,b) notify_anything(t, na_one, &(a), NULL, NA_NOPENTER, b)
+#define notify_nopenter(a,b) notify_nopenter_by(GOD, a, b)
+#define notify_noenter_by(t,a,b) notify_anything(t, na_one, &(a), NULL, NA_NOENTER, b)
+#define notify_noenter(a,b) notify_noenter_by(GOD, a, b)
+
+#define tag_wrap(a,b,c) safe_tag_wrap(a,b,c,pbuff,&pp,NOTHING)
+#define tag(a) safe_tag(a,pbuff,&pp)
+#define tag_cancel(a) safe_tag_cancel(a,pbuff,&pp)
+
+#define notify_pueblo(a,b) notify_anything(GOD, na_one, &(a), NULL, NA_PONLY | NA_NOPENTER | NA_NOLISTEN, b)
+
+int safe_tag(char const *a_tag, char *buf, char **bp);
+int safe_tag_cancel(char const *a_tag, char *buf, char **bp);
+int safe_tag_wrap(char const *a_tag, char const *params,
+                 char const *data, char *buf, char **bp, dbref player);
+
+/* Please STAY SANE when modifying. 
+ * Making this something like 'x' and 'y' is a BAD IDEA 
+ */
+
+#define TAG_START '\02'
+#define TAG_END '\03'
+
+/* Start & Cancel raw mud text */
+/* Use this for when we go into pueblo displaying shit */
+#define PUEBLO_CMT(buf,bp)  safe_str("</XCH_MUDTEXT>", buf, bp); \
+                           safe_str("<IMG XCH_MODE=HTML>", buf, bp);
+
+/* Use this when we go back to normal shit */
+#define PUEBLO_SMT(buf, bp) safe_str("<IMG XCH_MODE=TEXT>", buf, bp);
+
+#endif
diff --git a/hdrs/shs.h b/hdrs/shs.h
new file mode 100644 (file)
index 0000000..e9d3953
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef _SHS_H
+#define _SHS_H
+
+#include "config.h"
+#include "confmagic.h"
+
+/* -------------- SHS.H --------------------------- */
+
+#ifdef _WIN32
+#define LONG SHS_LONG
+#endif
+
+#ifdef __MINGW32__
+typedef unsigned char BYTE;
+typedef long LONG;
+#else
+typedef unsigned char BYTE;
+typedef unsigned long LONG;
+#endif
+
+#define SHS_BLOCKSIZE 64
+
+#define SHS_DIGESTSIZE 20
+
+/** An shs digest.
+ */
+typedef struct {
+  LONG digest[5];              /**< message digest */
+  LONG countLo;                        /**< 64-bit bit count, low half */
+  LONG countHi;                        /**< 64-bit bit count, high half */
+  LONG data[16];               /**< SHS data buffer */
+  BYTE reverse_wanted;         /**< true to reverse (little_endian) false to not */
+} SHS_INFO;
+
+void shsInit(SHS_INFO *shsInfo);
+void shsUpdate(SHS_INFO *shsInfo, const BYTE * buffer, int count);
+void shsFinal(SHS_INFO *shsInfo);
+#endif
diff --git a/hdrs/strtree.h b/hdrs/strtree.h
new file mode 100644 (file)
index 0000000..71ccffa
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef _STRTREE_H_
+#define _STRTREE_H_
+
+/* Here we have the tree node structure.  Pretty basic
+ * parentless binary tree.  info holds the red/black
+ * property and the usage count.  This structure is
+ * rarely fully allocated; instead, only enough is
+ * allocated to hold the pointers, info, and the null
+ * terminated string.
+ */
+typedef struct strnode StrNode;
+
+/** A strtree node.
+ * This is a node in a red/black binary strtree.
+ */
+struct strnode {
+  StrNode *left;               /**< Pointer to left child */
+  StrNode *right;              /**< Pointer to right child */
+  unsigned char info;          /**< Red/black and other internal state */
+  char string[BUFFER_LEN];     /**< Node label (value) */
+};
+
+typedef struct strtree StrTree;
+/** A strtree.
+ * A red/black binary tree of strings.
+ */
+struct strtree {
+  StrNode *root;       /**< Pointer to root node */
+  size_t count;                /**< Number of nodes in the tree */
+  size_t mem;          /**< Memory used by the tree */
+};
+
+void st_init(StrTree *root);
+char const *st_insert(char const *s, StrTree *root);
+char const *st_insert_perm(char const *s, StrTree *root);
+char const *st_find(char const *s, StrTree *root);
+void st_delete(char const *s, StrTree *root);
+void st_print(StrTree *root);
+void st_flush(StrTree *root);
+
+extern long st_count;
+
+#endif
diff --git a/hdrs/version.h b/hdrs/version.h
new file mode 100644 (file)
index 0000000..47d5dd3
--- /dev/null
@@ -0,0 +1,2 @@
+#define VERSION "0.72"
+#define VBRANCH "development"
diff --git a/hints/.gitify-empty b/hints/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/hints/a-u-x.sh b/hints/a-u-x.sh
new file mode 100644 (file)
index 0000000..d08f343
--- /dev/null
@@ -0,0 +1,2 @@
+ccflags="-ZB"
+libs="-lbsd"
diff --git a/hints/aix.sh b/hints/aix.sh
new file mode 100644 (file)
index 0000000..2b4fb6a
--- /dev/null
@@ -0,0 +1 @@
+ccflags="-D_BSD_INCLUDES"
diff --git a/hints/cygwin.sh b/hints/cygwin.sh
new file mode 100644 (file)
index 0000000..2336a29
--- /dev/null
@@ -0,0 +1,7 @@
+# Hints for cygwin, tested with 1.3.12-2
+# Note that you need the exim-4.10-1 package so you can include
+# its minires files
+cc='gcc'
+ccflags='-I/usr/src/exim-4.10-1/minires'
+usenm='y'
+d_ipv6='undef'
diff --git a/hints/darwin.sh b/hints/darwin.sh
new file mode 100644 (file)
index 0000000..0a9dffc
--- /dev/null
@@ -0,0 +1,3 @@
+usenm="n"
+cdecl=''
+
diff --git a/hints/dec_osf.sh b/hints/dec_osf.sh
new file mode 100644 (file)
index 0000000..e660576
--- /dev/null
@@ -0,0 +1,2 @@
+ccflags="-taso"
+echo "You will want to use the native malloc."
diff --git a/hints/freebsd.sh b/hints/freebsd.sh
new file mode 100644 (file)
index 0000000..cac0bdf
--- /dev/null
@@ -0,0 +1,6 @@
+usenm=false
+i_malloc='undef'
+i_values='undef'
+ccflags='-I/usr/local/include'
+d_attribut=true
+
diff --git a/hints/freebsd_5.sh b/hints/freebsd_5.sh
new file mode 100644 (file)
index 0000000..ed7a3b8
--- /dev/null
@@ -0,0 +1,6 @@
+usenm=false
+i_malloc='undef'
+i_values='undef'
+ccflags='-D_POSIX_C_SOURCE=2 -D__XSI_VISIBLE=1000 -D__BSD_VISIBLE -I/usr/local/include'
+d_attribut=true
+d_force_ipv4='define'
diff --git a/hints/hpux-gcc.sh b/hints/hpux-gcc.sh
new file mode 100644 (file)
index 0000000..cec7752
--- /dev/null
@@ -0,0 +1,2 @@
+cc="gcc"
+ccflags="-D_INCLUDE_POSIX_SOURCE -D_INCLUDE_HPUX_SOURCE -D_INCLUDE_XOPEN_SOURCE -D_INCLUDE_AES_SOURCE -D_XOPEN_SOURCE_EXTENDED"
diff --git a/hints/hpux.sh b/hints/hpux.sh
new file mode 100644 (file)
index 0000000..d258826
--- /dev/null
@@ -0,0 +1 @@
+ccflags="-w +Obb800 -Aa -D_INCLUDE_POSIX_SOURCE -D_INCLUDE_HPUX_SOURCE -D_INCLUDE_XOPEN_SOURCE -D_INCLUDE_AES_SOURCE -D_XOPEN_SOURCE_EXTENDED"
diff --git a/hints/irix.sh b/hints/irix.sh
new file mode 100644 (file)
index 0000000..f3a87d3
--- /dev/null
@@ -0,0 +1,2 @@
+ccflags="-woff 651"
+nm_opt="-B"
diff --git a/hints/irix_6.sh b/hints/irix_6.sh
new file mode 100644 (file)
index 0000000..091dc15
--- /dev/null
@@ -0,0 +1,6 @@
+ccflags="-mabi=n32"
+loclibpth="/usr/local/lib /usr/gnu/lib /usr/lib32 /usr/lib /lib"
+libs="-lc -lm"
+has_sigchld='define'
+has_sigcld='define'
+nm_opt="-B"
diff --git a/hints/linux_2.sh b/hints/linux_2.sh
new file mode 100644 (file)
index 0000000..36b9674
--- /dev/null
@@ -0,0 +1,2 @@
+nm_opt='-B'
+echo "I suggest using sysmalloc when you edit options.h."
diff --git a/hints/mingw32.sh b/hints/mingw32.sh
new file mode 100644 (file)
index 0000000..ede5bc2
--- /dev/null
@@ -0,0 +1,4 @@
+cc='gcc'
+usenm='n'
+libs='-lwsock32'
+osname='mingw32'
diff --git a/hints/next.sh b/hints/next.sh
new file mode 100644 (file)
index 0000000..560be4b
--- /dev/null
@@ -0,0 +1,5 @@
+ccflags="-W -Wreturn-type -Wunused -Wswitch -Wshadow -Wwrite-strings"
+echo 
+echo "NeXT note: You may have to use the native malloc rather than"
+echo "smalloc.c or gmalloc.c"
+echo 
diff --git a/hints/openbsd.sh b/hints/openbsd.sh
new file mode 100644 (file)
index 0000000..e6717c5
--- /dev/null
@@ -0,0 +1,4 @@
+usenm=false
+inclwanted='/usr/local/include'
+i_malloc='undef'
+d_force_ipv4='define'
diff --git a/hints/os2.sh b/hints/os2.sh
new file mode 100644 (file)
index 0000000..ad624af
--- /dev/null
@@ -0,0 +1,6 @@
+# Hints for OS/2, based on Sylvia's reports of the few config.h
+# tweaks she's had to do
+d_random='define'
+d_lrand48='define'
+d_gettblsz='define'
+tablesize='ulimit(4,0)'
diff --git a/hints/solaris_2.sh b/hints/solaris_2.sh
new file mode 100644 (file)
index 0000000..f0d91fc
--- /dev/null
@@ -0,0 +1,4 @@
+ccflags="-DNO_SIGCONTEXT"
+libs="-lnsl -lsocket -lm -lc -lresolv" 
+i_fcntl="define"
+has_sigchld="define"
diff --git a/hints/sunos_4.sh b/hints/sunos_4.sh
new file mode 100644 (file)
index 0000000..d6bf0ff
--- /dev/null
@@ -0,0 +1 @@
+ccflags="-DSUN_OS -DOLD_ANSI"
diff --git a/hints/ultrix.sh b/hints/ultrix.sh
new file mode 100644 (file)
index 0000000..0480346
--- /dev/null
@@ -0,0 +1 @@
+ccflags="-DOLD_ANSI"
diff --git a/hints/win32-gcc.sh b/hints/win32-gcc.sh
new file mode 100644 (file)
index 0000000..3b2a631
--- /dev/null
@@ -0,0 +1,7 @@
+# Hints for win32 with gcc
+cc='gcc'
+ccflags='-DWIN32'
+usenm='n'
+h_fcntl='false'
+d_ipv6='undef'
+d_setlocale='undef'
diff --git a/hints/win32.sh b/hints/win32.sh
new file mode 100644 (file)
index 0000000..ff5f3a6
--- /dev/null
@@ -0,0 +1,9 @@
+cc=`pwd`'/utils/clwrapper.sh'
+ccflags='/DWIN32 /DNDEBUG /D_CONSOLE'
+ldflags='/MT'
+libs='libcmt.lib winmm.lib wsock32.lib advapi32.lib /link /NODEFAULTLIB:libc.lib /subsystem:console /incremental:no /pdb:PennMUSH.pdb /machine:I386'
+optimize='/O1'
+d_index='undef'
+d_strchr='define'
+usenm='n'
+has_fcntl='false'
diff --git a/options.h.dist b/options.h.dist
new file mode 100644 (file)
index 0000000..adc15dc
--- /dev/null
@@ -0,0 +1,169 @@
+/* options.h */
+
+#ifndef __OPTIONS_H
+#define __OPTIONS_H
+
+/* *********** READ THIS BEFORE YOU MODIFY ANYTHING IN THIS FILE *********** */
+/* WARNING:  All options in this file have the ability to significantly change
+ * the look and feel and sometimes even internal behavior of the program.
+ * The ones shipped as the default have been extensively tested.  Others have
+ * been tested to a (usually) lesser degree, and therefore might still have
+ * latent bugs.  If you change any of them from the default, PLEASE check
+ * to make sure that you know the full effects of what you are changing. And
+ * if you encounter any errors or compile time problems with any options
+ * other than the default settings, PLEASE inform
+ * pennmush-bugs@pennmush.org
+ * immediately, so that they can be fixed.  The same goes for any other bug
+ * you might find in using this software.  All efforts will be made to fix
+ * errors encountered, but unless given a FULL description of the error,
+ * (IE telling me that logging in doesn't work is insufficient.  telling
+ * me that logging in with WCREAT undefined still gives you the registration
+ * message is a lot better.  MOST effective would be a full dbx trace, or a
+ * patch for the bug.)  Enjoy using the program.
+ */
+/***************************************************************************/
+
+/*---------------- Internals with many options ------------------------*/
+
+/* Malloc package options */
+/* malloc() is the routine that allocates memory while the MUSH is
+ * running. Because mallocs vary a lot from operating system to operating
+ * system, you can choose to use one of the mallocs we provide instead of
+ * your operating system's malloc.
+ * Set the value of MALLOC_PACKAGE  to one of these values:
+ *  0 -- Use my system's malloc. Required for Win32 systems.
+ *       Recommended for FreeBSD, Linux, Mac OS X/Darwin, and other OS's
+ *       where you think the malloc routines are efficient and debugged.
+ *       Otherwise, use only as a last resort.
+ *  1 -- Use the CSRI malloc package in normal mode.
+ *       Recommended for most operating systems where system malloc is
+ *       suspect. Known to work well on SunOS 4.1.x.
+ *  2 -- Use the CSRI malloc package in debug mode.
+ *       Only use this if you're tracking down memory leaks. Don't use
+ *       for a production MUSH - it's slow.
+ *  5 -- Use the GNU malloc (gmalloc) package.
+ *       Doesn't work on Alpha processors or FreeBSD systems, and
+ *       reportedly flaky on Linux. Requires an ANSI compiler.
+ *       Otherwise, similar to CSRI malloc.
+ *  3, 4, 6 -- Same as 0, kept for compatibility.
+ *
+ *  Note: For CobraMUSH CSRI seems to work the best.  
+ *        It seems to be the best one recommended for use. 
+ */
+#define MALLOC_PACKAGE 1
+
+/* What type of attribute compression should the MUSH use?
+ * Your options are:
+ * 1 - the default Huffman compression which has been in use for a
+ *     long time. In theory, this should be the best compression,
+ *     possibly at the cost of some speed. It is also 8-bit clean,
+ *     and thus suitable for locales that use extended character sets.
+ *     Sometimes has trouble on some linux systems for some reason.
+ * 2 - Same as 1, for backwards compability.
+ * 3 - Nick Gammon's word-based compression algorithm.
+ *     In theory, this should be considerably faster than Huffman
+ *     when decompressing, and considerably slower when compressing.
+ *     (But you decompress a lot more often). Compression ratio
+ *     is worse than Huffman for small dbs (<1.5Mb of text), but
+ *     better for larger dbs. Win32 systems must use this.
+ * 4 - Raevnos's almost 8-bit clean version of the word-based algorithm.
+ *     Prefer 3 unless you need extended characters. This algorithm
+ *     can encode all characters except 0x06.
+ * 0 - No compression at all. Very fast, but your db in memory
+ *     will be big - at least as large as your on-disk db.
+ *     Possibly suitable for the building stages of a small MUSH.
+ *     This should be 8-bit clean, too.
+ * You can change this at any time, with no worries. It only affects
+ * the in-memory compression of attribute/mail text, not the disk
+ * db compression. Recommend to keep it at 1. When in doubt, try them
+ * all, and check @uptime's memory usage stats for the most efficient
+ * choice among those that are stable for you. When using word-based
+ * compression, you can also #define COMP_STATS to get some detailed
+ * information in @stats/tables.
+ */
+#define COMPRESSION_TYPE       3
+
+
+/*------------------------- Other internals ----------------------*/
+
+/* If defined, use the info_slave to get information from identd,
+ * instead of having the MUSH do it directly.  This may help reduce lag
+ * from new logins.  This does _not_ work under Win32.
+ */
+#define INFO_SLAVE /* */
+
+/* Windows NT users may uncomment this define to get the native network i/o
+ * thread model instead of the bsd socket layer, for vastly better
+ * performance. Doesn't work on Win 95/98. By Nick Gammon
+ */
+/* #define NT_TCP /* */
+
+/*------------------------- MUSH Features ----------------------*/
+
+/* Comment this out if you don't wish to use the built-in mail system.
+ * The @mail command provides a flexible hardcoded mail system, which
+ * uses its own database to store messages.
+ */
+#define USE_MAILER /* */
+
+/* Defining ALLOW_NOSUBJECT marks mail sent with no subject as having
+ * subject '(no subject)'. The default is for the subject of the mail to
+ * be the first 30 characters of the message when not specfied
+ */
+#define ALLOW_NOSUBJECT /* */
+
+/*
+ * Should we have mail aliases (@mailing lists?). This modifies
+ * the maildb format, but you can reverse it.
+ */
+#define MAIL_ALIASES /* */
+
+/* Standard PennMUSH chat system.  Allows user to talk cross-MUSH
+ * to each other, without needing to be in the same room.  Wether or
+ * not you want this depends on what type of MUSH you want.
+#define CHAT_SYSTEM /* */
+
+/* Many MUSHes want to change the +channels to =channels. That's
+ * annoying. So we've got this CHAT_TOKEN_ALIAS, which allows + as well
+ * as = (or whatever) channels. If you want this, define it to be
+ * the character you want to use in addition to +, enclosed in
+ * single quotes, as in '=' or '.' or whatever. Don't define it to '+'!
+ */
+/* #define CHAT_TOKEN_ALIAS  '='/* */
+
+/* Quotas limit players to a fixed number of objects.
+ * Wizards can check and set quotas on players.
+ * See also restricted_building in game/mush.cnf for another way
+ * to slow database growth.
+ */
+#define QUOTA /* */
+
+
+/*------------------------------ DB ----------------------------------*/
+
+/*------------------------- Cosmetic Features --------------------*/
+
+/* If you're using the email registration feature, but want to
+ * use a mailer other than sendmail, put the full path to the mailer
+ * program here. The mailer must accept the -t command-line
+ * argument ("get the recipient address from the message header To:").
+ * If it doesn't, you could probably write a wrapper for it.
+ * Example: #define MAILER "/full/path/to/other/mailer"
+#define MAILER /* */
+
+
+/*--------------------- CobraMUSH Options ------------------------*/
+
+/* These are options specific to CobraMUSH.
+ */
+
+/* Colored WHO */
+#define COLOREDWHO /* */
+
+/* CobraMUSH built in RPMODE System */
+/* #define RPMODE_SYS /* */
+
+/* CRON system */
+#define MUSHCRON /* */
+
+#endif
diff --git a/os2/.gitify-empty b/os2/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/os2/GCCOPT.CMD b/os2/GCCOPT.CMD
new file mode 100644 (file)
index 0000000..a29a721
--- /dev/null
@@ -0,0 +1,2 @@
+GCCOPT=-Zexe -Zcrtdll -Zmt -Zbsd-signals -lsigbsd -lwrap -pipe -lbsd
+
diff --git a/os2/Makefile b/os2/Makefile
new file mode 100644 (file)
index 0000000..203b19a
--- /dev/null
@@ -0,0 +1,192 @@
+# Makefile for PennMUSH \r
+\r
+# - System configuration - #\r
+\r
+#\r
+# This section of the file should be automatically configured by\r
+# the Configure script. If it doesn't work, you might try starting\r
+# from the Makefile.old that's included instead, and reporting\r
+# your problem (including this Makefile) to Paul/Javelin,\r
+# dunemush@pennmush.org\r
+#\r
+\r
+CC=gcc\r
+CCFLAGS=-g -O -W -Wreturn-type -Wswitch -Wshadow -Wwrite-strings -Wformat -Wparentheses -Wuninitialized -I.. -I../hdrs\r
+LDFLAGS=\r
+CLIBS=-lbsd -lsocket -lcrypt \r
+LNS=/bin/ln\r
+
+
+# stupid SYS V shell
+SHELL=/bin/sh
+
+
+all: dune.h options.h hdrs/cmds.h hdrs/funs.h game/mush.cnf
+       @echo "Making all in src."
+       (cd src; make all "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+       @echo "If the make was successful, use 'make install' to install links"
+
+dune.h: dune.h.dist
+       @echo "Please use 'make update' to update your dune.h file from dune.h.dist"
+       @echo "Or you may cp dune.h.dist to dune.h and edit it."
+       exit 1
+
+options.h: options.h.dist
+       @echo "Please use 'make update' to update your options.h file from options.h.dist"
+       @echo "You must cp options.h.dist to options.h and edit it."
+       exit 1
+
+hdrs/cmds.h: src/SWITCHES src/cmds.c
+       (cd utils; sh mkcmds.sh)
+
+hdrs/funs.h: src/fun*.c
+       (cd utils; sh mkcmds.sh)
+
+install: all
+       -rm -f game/netmush
+       -rm -f game/info_slave
+       (cd game; \
+       $(LNS) ../src/netmud netmush; \
+       $(LNS) ../src/info_slave info_slave)
+       @echo "If you plan to run multiple MUSHes, consider running 'make customize'"
+
+netmud: 
+       (cd src; make netmud "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+access:
+       utils/make_access_cnf.sh game
+
+portmsg:
+       (cd src; make portmsg "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+concentrate:
+       (cd src; make concentrate "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+install_conc: concentrate
+       -rm -f game/concentrate
+       (cd game; $(LNS) ../src/concentrate concentrate)
+
+announce:
+       (cd src; make announce "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+ident_made:
+       (cd src/IDENT; make CC="$(CC)" CCFLAGS="$(CCFLAGS)")
+       (cd src; touch ident_made)
+
+dump:
+       (cd src; make dump "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+extract:
+       (cd src; make extract "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+decompress:
+       (cd src; make decompress "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+safety:
+       cp src/*.c /var/pennmush-bak/src
+       cp hdrs/*.h /var/pennmush-bak/hdrs
+       cp * /var/pennmush-bak
+
+distdepend: 
+       (cd src; make depend "CC=$(CC)" "CCFLAGS=$(CCFLAGS)" \
+       "LDFLAGS=$(LDFLAGS)" "CLIBS=$(CLIBS)" )
+
+# REQUIRES GNU INDENT! DON'T INDENT WITH ANYTHING ELSE!
+indent:
+       (cd src; make indent)
+
+protoize:
+       (cd src; make protoize "CCFLAGS=$(CCFLAGS)")
+
+\r
+customize: update-conf\r
+       -@perl utils/customize.pl\r
+\r
+update-conf: game/mushcnf.dst\r
+       -@touch game/mushcnf.dst\r
+       -@perl utils/update-cnf.pl game/mush.cnf game/mushcnf.dst\r
+\r
+game/mush.cnf: game/mushcnf.dst\r
+       -@touch game/mushcnf.dst\r
+       -@perl utils/update-cnf.pl game/mush.cnf game/mushcnf.dst\r
+\r
+update: update-hdr update-conf\r
+\r
+update-hdr:\r
+       -@touch options.h.dist dune.h.dist\r
+       -@perl utils/update.pl options.h options.h.dist\r
+       -@perl utils/update.pl dune.h dune.h.dist\r
+\r
+
+clean:
+       (cd src; make clean)
+       (cd game; rm -f netmush)
+
+distclean: 
+       (cd src; make distclean)
+       (cd hdrs; rm -f *.orig *~ \#* *.rej *.bak funs.h cmds.h)
+       (cd utils; rm -f *.orig *~ \#* *.rej *.bak)
+       (cd game; rm -rf *.log netmush *.orig *.rej *~ *.bak mush.cnf)
+       (cd win32; rm -rf *.rej *.orig *~ *.bak)
+       (cd game/txt; make clean)
+
+totallyclean: distclean 
+       (cd hdrs; rm -rf *.rej)
+       (cd src; rm -rf *.rej)
+       -rm -f Makefile
+
+distci: distclean ci-src ci-game
+
+ci-src:
+       -(yes . | ci -l -f -N$(NAME) FAQ* BUGS CHANGE* READ* Configure \
+         configure utils/* Makefile.SH Patchlevel config_h.SH confmagic.h \
+         *.dist src/Makefile src/IDENT/*  src/*.c src/*.dst \
+         hdrs/* hints/* win32/*)
+
+ci-game:
+       -(yes . | ci -l -f -N$(NAME) game/restart game/mushcnf.dst \
+         game/access.README \
+         game/txt/* game/txt/nws/* game/txt/evt/* game/txt/hlp/* )
+
+diffs: 
+       -rm hdrs/buildinf.h src/funlocal.c src/cmdlocal.c src/local.c hdrs/funs.h hdrs/cmds.h
+       -(rcsdiff -c -r$(OLD) Patchlevel FAQ* BUGS CHANGE* READ* Configure \
+         configure utils/*.sh utils/*.pl \
+         Makefile.SH win32/* config_h.SH confmagic.h \
+         *.dist src/Makefile src/IDENT/*  src/*.c src/*.dst \
+         src/SWITCHES hdrs/*.h hints/* \
+         game/restart game/mushcnf.dst game/txt/hlp/pen*.hlp | \
+       grep -v "No differences encountered" > /tmp/diffs)
+
+etags: 
+       (cd src; make etags)
+
+ctags:
+       (cd src; make ctags)
+
+dist.tar.Z: distclean dist.tar
+       compress /tmp/dist.tar
+
+dist.tar.gz: distclean dist.tar
+       gzip /tmp/dist.tar
+
+dist.tar: distclean
+       (tar -cvFFfX /tmp/dist.tar exclude.tar \
+               Patchlevel FAQ* BUGS CHANGE* READ* Configure configure \
+               Makefile.SH config_h.SH confmagic.h \
+               options.h.dist dune.h.dist src hdrs game hints utils win32)
+       (pgp -sb /tmp/dist.tar)
+
+CSRImalloc.tar.Z:
+       (cd src/CSRI; make clean)
+       (tar -cvFFf /tmp/CSRImalloc.tar `cat exclude.tar`)
+       compress /tmp/CSRImalloc.tar
+
diff --git a/os2/Penn-OS2.htm b/os2/Penn-OS2.htm
new file mode 100644 (file)
index 0000000..65cb2bd
--- /dev/null
@@ -0,0 +1,58 @@
+<html>\r
+<head>\r
+<meta Name="Generator" Content="Lotus Word Pro">\r
+<title>Document Title</title>\r
+</head>\r
+\r
+<body>\r
+\r
+\r
+<h1 align=center>PennMUSH for OS/2 Warp -- Installation Guide</h1>\r
+<p>All questions regarding the OS/2 Port of PennMUSH should be directed to <a href="mailto:penn-os2@pennmush.org"><address>penn-os2@pennmush.org</address></a>. I'll answer questions as quickly as I can. It does help to have a working knowledge of *nix, OS/2 and MUSH to set up and configure Penn under OS/2. \r
+<h2>Compilation</h2>\r
+<p>To compile PennMUSH under OS/2, you will need OS/2 Warp v3 or newer, a file system that mirrors a *nix file system and that supports long file names (I use Toronto Virtual File System for this purpose), The GNU file and text tools, SED, AWK, GREP, EMX GCC, and a *nix style shell (I use MSSH). All of these items are available from <a href="http://hobbes.nmsu.edu">http://hobbes.nmsu.edu</a>. \r
+<p>First, set your GCCOPT environmental variable:\r
+<p>SET GCCOPT=-Zexe -Zcrtdll -Zmt -Zbsd-signals -lsigbsd -lwrap -pipe -lbsd\r
+<p>Then, run configure.  Depending on your setup, configure may or may not produce a good config.h file -- sometimes I get one that is right, sometimes I don't.  I've therefore sent a copy along with these instructions this time.\r
+<p>You'll need to fire up EPM.  Use EPM to edit src\Makefile.  First, find a line that looks like this:\r
+<p>LIBS=$(CLIBS) $(ILIBS) $(RLIBS)\r
+<p>Replace it with:\r
+<p>LIBS=$(ILIBS) $(RLIBS) $(CLIBS)\r
+<p>This keeps EMX's linker happy if you are using identd or rwho.\r
+<p>Find the block that builds buildinf.h:\r
+<p>buildinf:\r
+<p>    -rm ../hdrs/buildinf.h\r
+<p>    @echo &quot;/* This file generated automatically from Makefile */&quot; &gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define BUILDDATE \&quot;`date`\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define COMPILER \&quot;$(CC)\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define CCFLAGS \&quot;$(CCFLAGS)\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define MALLOC_O \&quot;$(MALLOC_O)\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define MALLOC_D \&quot;$(MALLOC_D)\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define RDEFS \&quot;$(RDEFS)\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define IDEFS \&quot;$(IDEFS)\&quot;&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>Replace it with this:\r
+<p>buildinf:\r
+<p>    -rm ../hdrs/buildinf.h\r
+<p>    @echo &quot;/* This file generated automatically from Makefile */&quot; &gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define BUILDDATE \042`date`\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define COMPILER \042$(CC)\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define CCFLAGS \042$(CCFLAGS)\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define MALLOC_O \042$(MALLOC_O)\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define MALLOC_D \042$(MALLOC_D)\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define RDEFS \042$(RDEFS)\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>    @echo &quot;#define IDEFS \042$(IDEFS)\042&quot; &gt;&gt; ../hdrs/buildinf.h\r
+<p>Now use GNU Make 2.75 (and I have tried pamake, pdmake, dmake, nmake and wmake, none of which will work, so you really should use GNU Make to do this) to make the file.  First do a make update, then a make, etc.  You'll end up with several .exe files.\r
+<p>Once you have compiled, follow the *nix directions on configuration, etc. All options (including compressing the database) seem to work -- if you find one that doesn't, please e-mail me so I can investigate it. \r
+<h2>Running it</h2>\r
+<p>To run Penn, use mssh, or an equivalent shell, to run the *nix script files. I recommend putting this in a startup.cmd file, or placing an icon to it in a startup folder on your desktop. Use gzip and tar to extract the archive, and make sure to have EMX .9c or newer installed! It should behave exactly like the *nix version.  Remember that the 'detach' command can be used to run a process in the background:\r
+<p>detach sh mymushname.restart\r
+<p>Since MUSH doesn't need UI, and doesn't really send much to the screen, and you'll have to look at the log files anyway, I usually just go ahead and use detach.  If you aren't familiar with detach, then type help detach at an OS/2 command prompt.\r
+<h2>Known Issues</h2>\r
+<p>1. Penn's output is not currently compatible with telnet's default options. CR/LF mode needs to be set to line-feed only. TinyFugue has no problems with it. This may be a limitation in the baseline distribution, I have not determined this yet. \r
+<p>2. Sometimes there were extra checkpoints in 1.6.10, that were not removed until the next time Penn used the same checkpoint number.  These usually happened when you didn't @shutdown before rebooting the PC.  I haven't seen this in 1.7.x, but I'm letting you know in advance you may have several checkpoint db's.\r
+<p>3. The scripts really need to be rewritten in Rexx, ORexx or Java, to remove the need to have mssh and the *nix utilities installed. \r
+<p>\r
+\r
+</body>\r
+</html>
+
diff --git a/os2/config.h b/os2/config.h
new file mode 100644 (file)
index 0000000..7d1da07
--- /dev/null
@@ -0,0 +1,518 @@
+/*\r
+ * This file was produced by running the config_h.SH script, which\r
+ * gets its values from config.sh, which is generally produced by\r
+ * running Configure.\r
+ *\r
+ * Feel free to modify any of this as the need arises.  Note, however,\r
+ * that running config_h.SH again will wipe out any changes you've made.\r
+ * For a more permanent change edit config.sh and rerun config_h.SH.\r
+ *\r
+ * $Id: config.h,v 1.1.1.1 2004-06-06 20:32:51 ari Exp $\r
+ */\r
+\r
+/*\r
+ * Package name      : pennmush\r
+ * Source directory  : .\r
+ * Configuration time: Thu Sep 25 22:03:18 GMT 1997\r
+ * Configured by     : root\r
+ * Target system     : tr - GNU textutils 1.12\r
+ */\r
+\r
+#ifndef _config_h_\r
+#define _config_h_\r
+\r
+/* getdtablesize:\r
+ *      This catches use of the getdtablesize() subroutine, and remaps it\r
+ *      to either ulimit(4,0) or NOFILE, if getdtablesize() isn't available.\r
+ */\r
+#define getdtablesize() ulimit(4,0) /**/\r
+\r
+/* HAS_BCOPY:\r
+ *      This symbol is defined if the bcopy() routine is available to\r
+ *      copy blocks of memory.\r
+ */\r
+#define HAS_BCOPY       /**/\r
+\r
+/* HAS_BZERO:\r
+ *      This symbol is defined if the bzero() routine is available to\r
+ *      set a memory block to 0.\r
+ */\r
+#define HAS_BZERO       /**/\r
+\r
+/* HASCONST:\r
+ *      This symbol, if defined, indicates that this C compiler knows about\r
+ *      the const type. There is no need to actually test for that symbol\r
+ *      within your programs. The mere use of the "const" keyword will\r
+ *      trigger the necessary tests.\r
+ */\r
+#define HASCONST        /**/\r
+#ifndef HASCONST\r
+#define const\r
+#endif\r
+\r
+/* HAS_GETPAGESIZE:\r
+ *      This symbol, if defined, indicates that the getpagesize system call\r
+ *      is available to get system page size, which is the granularity of\r
+ *      many memory management calls.\r
+ */\r
+#define HAS_GETPAGESIZE         / **/\r
+\r
+/* HAS_GETPRIORITY:\r
+ *      This symbol, if defined, indicates that the getpriority routine is\r
+ *      available to get a process's priority.\r
+ */\r
+/*#define HAS_GETPRIORITY               / **/\r
+\r
+/* HAS_MEMSET:\r
+ *      This symbol, if defined, indicates that the memset routine is available\r
+ *      to set blocks of memory.\r
+ */\r
+#define HAS_MEMSET      / **/\r
+\r
+/* HAS_RENAME:\r
+ *      This symbol, if defined, indicates that the rename routine is available\r
+ *      to rename files.  Otherwise you should do the unlink(), link(), unlink()\r
+ *      trick.\r
+ */\r
+#define HAS_RENAME      / **/\r
+\r
+/* HAS_GETRUSAGE:\r
+ *      This symbol, if defined, indicates that the getrusage() routine is\r
+ *      available to get process statistics with a sub-second accuracy.\r
+ *      Inclusion of <sys/resource.h> and <sys/time.h> may be necessary.\r
+ */\r
+/*#define HAS_GETRUSAGE         / **/\r
+\r
+/* HAS_SELECT:\r
+ *      This symbol, if defined, indicates that the select routine is\r
+ *      available to select active file descriptors. If the timeout field\r
+ *      is used, <sys/time.h> may need to be included.\r
+ */\r
+#define HAS_SELECT      / **/\r
+\r
+/* HAS_SETPGRP:\r
+ *      This symbol, if defined, indicates that the setpgrp routine is\r
+ *      available to set the current process group.\r
+ */\r
+/* USE_BSD_SETPGRP:\r
+ *      This symbol, if defined, indicates that setpgrp needs two\r
+ *      arguments whereas USG one needs none.  See also HAS_SETPGID\r
+ *      for a POSIX interface.\r
+ */\r
+/*#define HAS_SETPGRP           / **/\r
+/*#define USE_BSD_SETPGRP       / **/\r
+\r
+/* HAS_SETPRIORITY:\r
+ *      This symbol, if defined, indicates that the setpriority routine is\r
+ *      available to set a process's priority.\r
+ */\r
+/*#define HAS_SETPRIORITY               / **/\r
+\r
+/* HAS_SIGACTION:\r
+ *      This symbol, if defined, indicates that Vr4's sigaction() routine\r
+ *      is available.\r
+ */\r
+/*#define HAS_SIGACTION / **/\r
+\r
+/* HAS_STRCASECMP:\r
+ *      This symbol, if defined, indicates that the strcasecmp() routine is\r
+ *      available for case-insensitive string compares.\r
+ */\r
+/*#define HAS_STRCASECMP        / **/\r
+\r
+/* HAS_STRDUP:\r
+ *      This symbol, if defined, indicates that the strdup routine is\r
+ *      available to duplicate strings in memory. Otherwise, roll up\r
+ *      your own...\r
+ */\r
+#define HAS_STRDUP              / **/\r
+\r
+/* HAS_SYSCONF:\r
+ *      This symbol, if defined, indicates that sysconf() is available\r
+ *      to determine system related limits and options.\r
+ */\r
+#define HAS_SYSCONF     / **/\r
+\r
+/* UNION_WAIT:\r
+ *      This symbol if defined indicates to the C program that the argument\r
+ *      for the wait() system call should be declared as 'union wait status'\r
+ *      instead of 'int status'. You probably need to include <sys/wait.h>\r
+ *      in the former case (see I_SYS_WAIT).\r
+ */\r
+/*#define UNION_WAIT            / **/\r
+\r
+/* VOIDSIG:\r
+ *      This symbol is defined if this system declares "void (*signal(...))()" in\r
+ *      signal.h.  The old way was to declare it as "int (*signal(...))()".  It\r
+ *      is up to the package author to declare things correctly based on the\r
+ *      symbol.\r
+ */\r
+/* Signal_t:\r
+ *      This symbol's value is either "void" or "int", corresponding to the\r
+ *      appropriate return type of a signal handler.  Thus, you can declare\r
+ *      a signal handler using "Signal_t (*handler)()", and define the\r
+ *      handler using "Signal_t handler(sig)".\r
+ */\r
+#define VOIDSIG         /**/\r
+#define Signal_t void   /* Signal handler's return type */\r
+\r
+/* HASVOLATILE:\r
+ *      This symbol, if defined, indicates that this C compiler knows about\r
+ *      the volatile declaration.\r
+ */\r
+#define HASVOLATILE     /**/\r
+#ifndef HASVOLATILE\r
+#define volatile\r
+#endif\r
+\r
+/* HAS_WAITPID:\r
+ *      This symbol, if defined, indicates that the waitpid routine is\r
+ *      available to wait for child process.\r
+ */\r
+#define HAS_WAITPID   /**/\r
+\r
+/* I_ARPA_INET:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <arpa/inet.h> to get inet_addr and friends declarations.\r
+ */\r
+#define I_ARPA_INET             /**/\r
+\r
+/* I_FCNTL:\r
+ *      This manifest constant tells the C program to include <fcntl.h>.\r
+ */\r
+#define I_FCNTL / **/\r
+\r
+/* I_LIMITS:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <limits.h> to get definition of symbols like WORD_BIT or\r
+ *      LONG_MAX, i.e. machine dependant limitations.\r
+ */\r
+#define I_LIMITS                /**/\r
+\r
+/* I_MEMORY:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <memory.h>.\r
+ */\r
+/*#define I_MEMORY              / **/\r
+\r
+/* I_STDDEF:\r
+ *      This symbol, if defined, indicates that <stddef.h> exists and should\r
+ *      be included.\r
+ */\r
+#define I_STDDEF        /**/\r
+\r
+/* I_STDLIB:\r
+ *      This symbol, if defined, indicates that <stdlib.h> exists and should\r
+ *      be included.\r
+ */\r
+#define I_STDLIB                /**/\r
+\r
+/* I_STRING:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <string.h> (USG systems) instead of <strings.h> (BSD systems).\r
+ */\r
+#define I_STRING                /**/\r
+\r
+/* I_SYS_FILE:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/file.h> to get definition of R_OK and friends.\r
+ */\r
+#define I_SYS_FILE              /**/\r
+\r
+/* USE_TIOCNOTTY:\r
+ *      This symbol, if defined indicate to the C program that the ioctl()\r
+ *      call with TIOCNOTTY should be used to void tty association.\r
+ *      Otherwise (on USG probably), it is enough to close the standard file\r
+ *      decriptors and do a setpgrp().\r
+ */\r
+/*#define USE_TIOCNOTTY / **/\r
+\r
+/* I_SYS_MMAN:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/mman.h>.\r
+ */\r
+/*#define       I_SYS_MMAN              / **/\r
+\r
+/* I_SYS_RESOURCE:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/resource.h>.\r
+ */\r
+#define I_SYS_RESOURCE          /**/\r
+\r
+/* I_SYS_SELECT:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/select.h> in order to get definition of struct timeval.\r
+ */\r
+#define I_SYS_SELECT    /**/\r
+\r
+/* I_SYS_STAT:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/stat.h>.\r
+ */\r
+#define I_SYS_STAT              /**/\r
+\r
+/* I_SYS_TYPES:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/types.h>.\r
+ */\r
+#define I_SYS_TYPES             /**/\r
+\r
+/* I_SYS_WAIT:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/wait.h>.\r
+ */\r
+#define I_SYS_WAIT      /**/\r
+\r
+/* I_TIME:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <time.h>.\r
+ */\r
+/* I_SYS_TIME:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/time.h>.\r
+ */\r
+/*#define I_TIME                / **/\r
+#define I_SYS_TIME              /**/\r
+\r
+/* I_UNISTD:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <unistd.h>.\r
+ */\r
+#define I_UNISTD                /**/\r
+\r
+/* I_VALUES:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <values.h> to get definition of symbols like MINFLOAT or\r
+ *      MAXLONG, i.e. machine dependant limitations.  Probably, you\r
+ *      should use <limits.h> instead, if it is available.\r
+ */\r
+/*#define I_VALUES              / **/\r
+\r
+/* I_STDARG:\r
+ *      This symbol, if defined, indicates that <stdarg.h> exists and should\r
+ *      be included.\r
+ */\r
+#define I_STDARG                /**/\r
+\r
+/* Free_t:\r
+ *      This variable contains the return type of free().  It is usually\r
+ * void, but occasionally int.\r
+ */\r
+/* Malloc_t:\r
+ *      This symbol is the type of pointer returned by malloc and realloc.\r
+ */\r
+#define Malloc_t void *                 /**/\r
+#define Free_t void                     /**/\r
+\r
+/* Pid_t:\r
+ *      This symbol holds the type used to declare process ids in the kernel.\r
+ *      It can be int, uint, pid_t, etc... It may be necessary to include\r
+ *      <sys/types.h> to get any typedef'ed information.\r
+ */\r
+#define Pid_t pid_t             /* PID type */\r
+\r
+/* CAN_PROTOTYPE:\r
+ *      If defined, this macro indicates that the C compiler can handle\r
+ *      function prototypes.\r
+ */\r
+/* _:\r
+ *      This macro is used to declare function parameters for folks who want\r
+ *      to make declarations with prototypes using a different style than\r
+ *      the above macros.  Use double parentheses.  For example:\r
+ *\r
+ *              int main _((int argc, char *argv[]));\r
+ */\r
+#define CAN_PROTOTYPE   /**/\r
+#ifdef CAN_PROTOTYPE\r
+#define _(args) args\r
+#else\r
+#define _(args) ()\r
+#endif\r
+\r
+/* Size_t:\r
+ *      This symbol holds the type used to declare length parameters\r
+ *      for string functions.  It is usually size_t, but may be\r
+ *      unsigned long, int, etc.  It may be necessary to include\r
+ *      <sys/types.h> to get any typedef'ed information.\r
+ */\r
+#define Size_t size_t    /* length paramater for string functions */\r
+\r
+/* VOIDFLAGS:\r
+ *      This symbol indicates how much support of the void type is given by this\r
+ *      compiler.  What various bits mean:\r
+ *\r
+ *          1 = supports declaration of void\r
+ *          2 = supports arrays of pointers to functions returning void\r
+ *          4 = supports comparisons between pointers to void functions and\r
+ *                  addresses of void functions\r
+ *          8 = suports declaration of generic void pointers\r
+ *\r
+ *      The package designer should define VOIDUSED to indicate the requirements\r
+ *      of the package.  This can be done either by #defining VOIDUSED before\r
+ *      including config.h, or by defining defvoidused in Myinit.U.  If the\r
+ *      latter approach is taken, only those flags will be tested.  If the\r
+ *      level of void support necessary is not present, defines void to int.\r
+ */\r
+#ifndef VOIDUSED\r
+#define VOIDUSED 15\r
+#endif\r
+#define VOIDFLAGS 15\r
+#if (VOIDFLAGS & VOIDUSED) != VOIDUSED\r
+#define void int                /* is void to be avoided? */\r
+#define M_VOID                  /* Xenix strikes again */\r
+#endif\r
+\r
+/* HAS_ASSERT:\r
+ *      If defined, this system has the assert() macro.\r
+ */\r
+#define HAS_ASSERT      / **/\r
+\r
+/* HAS_CRYPT:\r
+ *      This symbol, if defined, indicates that the crypt routine is available\r
+ *      to encrypt passwords and the like.\r
+ */\r
+/* I_CRYPT:\r
+ *      This symbol, if defined, indicates that <crypt.h> can be included.\r
+ */\r
+#define HAS_CRYPT               /**/\r
+\r
+/*#define I_CRYPT               / **/\r
+\r
+/* HAS_HUGE_VAL:\r
+ *      If defined, this system has the HUGE_VAL constant. We like this,\r
+ *      and don't bother defining the others below if we find it.\r
+ */\r
+/* HAS_HUGE:\r
+ *      If defined, this system has the HUGE constant. We like this, and\r
+ *      don't bother defining the others below if we find it.\r
+ */\r
+/* HAS_INT_MAX:\r
+ *      If defined, this system has the INT_MAX constant.\r
+ */\r
+/* HAS_MAXINT:\r
+ *      If defined, this system has the MAXINT constant.\r
+ */\r
+/* HAS_MAXDOUBLE:\r
+ *      If defined, this system has the MAXDOUBLE constant.\r
+ */\r
+#define HAS_HUGE_VAL    / **/\r
+\r
+#define HAS_HUGE        / **/\r
+\r
+#define HAS_INT_MAX     / **/\r
+\r
+#define HAS_MAXINT      / **/\r
+\r
+#define HAS_MAXDOUBLE   / **/\r
+\r
+/* HAS_IEEE_MATH:\r
+ *      Defined if the machine supports IEEE math - that is, can safely\r
+ *      return NaN or Inf rather than crash on bad math.\r
+ */\r
+#define HAS_IEEE_MATH /**/\r
+\r
+/* SIGNALS_KEPT:\r
+ *      This symbol is defined if signal handlers needn't be reinstated after\r
+ *      receipt of a signal.\r
+ */\r
+#define SIGNALS_KEPT    /**/\r
+\r
+/* HAS_MEMCPY:\r
+ *      This symbol, if defined, indicates that the memcpy routine is available\r
+ *      to copy blocks of memory. If not, it will be mapped to bcopy\r
+ *      in confmagic.h\r
+ */\r
+#define HAS_MEMCPY      / **/\r
+\r
+/* CAN_NEWSTYLE:\r
+ *      Defined if new-style functions definitions are allowable.\r
+ *      If they are, we can avoid some warnings that you get if\r
+ *      you declare char arguments in a prototype and use old-style\r
+ *      function definitions, which implicitly promote them to ints.\r
+ */\r
+#define CAN_NEWSTYLE /**/\r
+\r
+/* HAS_RANDOM:\r
+ *      Have we got random(), our first choice for number generation.\r
+ */\r
+/* HAS_LRAND48:\r
+ *      Have we got lrand48(), our second choice.\r
+ */\r
+/* HAS_RAND:\r
+ *      Have we got rand(), our last choice.\r
+ */\r
+#define HAS_RANDOM      / **/\r
+/*#define HAS_LRAND48   / **/\r
+#define HAS_RAND        / **/\r
+\r
+/* HAS_GETRLIMIT:\r
+ *      This symbol, if defined, indicates that the getrlimit() routine is\r
+ *      available to get resource limits. Probably means setrlimit too.\r
+ *      Inclusion of <sys/resource.h> and <sys/time.h> may be necessary.\r
+ */\r
+/*#define HAS_GETRLIMIT         / **/\r
+\r
+/* SENDMAIL:\r
+ *      This symbol contains the full pathname to sendmail.\r
+ */\r
+/* HAS_SENDMAIL:\r
+ *      If defined, we have sendmail.\r
+ */\r
+/*#define HAS_SENDMAIL  / **/\r
+#define SENDMAIL        "sendmail"\r
+\r
+/* HAS_SIGCLD:\r
+ *      If defined, this system has the SIGCLD constant (SysVish SIGCHLD).\r
+ */\r
+\r
+#define HAS_SIGCLD      / **/\r
+\r
+/* CAN_PROTOTYPE_SIGNAL:\r
+ *      This symbol is defined if we can safely prototype our rewritten\r
+ *      signal() function as:\r
+ *      Signal_t(*Sigfunc) _((int));\r
+ *      extern Sigfunc signal _((int signo, Sigfunc func));\r
+ */\r
+#define CAN_PROTOTYPE_SIGNAL    /**/\r
+\r
+/* HAS_STRCHR:\r
+ *      This symbol is defined to indicate that the strchr()/strrchr()\r
+ *      functions are available for string searching. If not, try the\r
+ *      index()/rindex() pair.\r
+ */\r
+/* HAS_INDEX:\r
+ *      This symbol is defined to indicate that the index()/rindex()\r
+ *      functions are available for string searching.\r
+ */\r
+#define HAS_STRCHR      / **/\r
+#define HAS_INDEX       / **/\r
+\r
+/* HAS_TIMELOCAL:\r
+ *      This symbol, if defined, indicates that the timelocal() routine is\r
+ *      available.\r
+ */\r
+/* #define HAS_TIMELOCAL           / **/\r
+\r
+/* HAS_SAFE_TOUPPER:\r
+ *      Defined if toupper() can operate safely on any ascii character.\r
+ *      Some systems only allow toupper() on lower-case ascii chars.\r
+ */\r
+#define HAS_SAFE_TOUPPER /**/\r
+\r
+/* UPTIME_PATH:\r
+ *      This symbol gives the full path to the uptime(1) program if\r
+ *      it exists on the system. If not, this symbol is undefined.\r
+ */\r
+/* HAS_UPTIME:\r
+ *      This symbol is defined if uptime(1) is available.\r
+ */\r
+#define HAS_UPTIME      / **/\r
+#define UPTIME_PATH "uptime"\r
+\r
+/* I_SYS_VLIMIT:\r
+ *      This symbol, if defined, indicates to the C program that it should\r
+ *      include <sys/vlimit.h>.\r
+ */\r
+/*#define I_SYS_VLIMIT          / **/\r
+\r
+#endif\r
diff --git a/po/.gitify-empty b/po/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/po/Makefile b/po/Makefile
new file mode 100644 (file)
index 0000000..9c81ff5
--- /dev/null
@@ -0,0 +1,20 @@
+.SUFFIXES: .po .pox .mo
+
+POFILES=ru_RU.po nl_NL.po sv_SE.po hu_HU.po es_ES.po pt_BR.po fr_FR.po \
+da_DK.po de_DE.po no_NO.po pl_PL.po ro_RO.po id_ID.po
+
+.pox.po: $*.pox pennmush.pot
+       -msgmerge -E -v $*.pox pennmush.pot > $*.po
+
+po-files: $(POFILES)
+
+localized: $(LANG).po
+       -msgfmt --strict $(LANG).po
+       -mkdir -p $(LANG)/LC_MESSAGES
+       -mv messages.mo $(LANG)/LC_MESSAGES/pennmush.mo
+
+.po:
+       @echo "No LANG variable set, no localization. Ignore errors."
+
+clean:
+       -rm $(POFILES)
diff --git a/src/.gitify-empty b/src/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/.indent.pro b/src/.indent.pro
new file mode 100644 (file)
index 0000000..dd194bc
--- /dev/null
@@ -0,0 +1,6 @@
+--k-and-r-style
+--indent-level2
+--no-tabs
+--procnames-start-lines
+--leave-preprocessor-space
+
diff --git a/src/Makefile.SH b/src/Makefile.SH
new file mode 100644 (file)
index 0000000..c12becf
--- /dev/null
@@ -0,0 +1,1975 @@
+case $CONFIG in
+'')
+       if test -f config.sh; then TOP=.;
+       elif test -f ../config.sh; then TOP=..;
+       elif test -f ../../config.sh; then TOP=../..;
+       elif test -f ../../../config.sh; then TOP=../../..;
+       elif test -f ../../../../config.sh; then TOP=../../../..;
+       else
+               echo "Can't find config.sh."; exit 1
+       fi
+       . $TOP/config.sh
+       ;;
+esac
+: This forces SH files to create target in same directory as SH file.
+: This is so that make depend always knows where to find SH derivatives.
+case "$0" in
+*/*) cd `expr X$0 : 'X\(.*\)/'` ;;
+esac
+echo "Extracting Makefile (with variable substitutions)"
+if test "x$OSTYPE" = "xmsys"; then
+  OUTFILES="buildinf netmud"
+else
+  OUTFILES="buildinf netmud info_slave console"
+fi
+: This section of the file will have variable substitutions done on it.
+: Move anything that needs config subs from !NO!SUBS! section to !GROK!THIS!.
+: Protect any dollar signs and backticks that you do not want interpreted
+: by putting a backslash in front.  You may delete these comments.
+$spitshell >Makefile <<!GROK!THIS!
+# Makefile for PennMUSH
+
+###############################################################################
+#         You shouldn't have to change anything in this file.                 #
+###############################################################################
+
+OUTFILES=$OUTFILES
+
+!GROK!THIS!
+
+: In the following dollars and backticks do not need the extra backslash.
+$spitshell >>Makefile <<'!NO!SUBS!'
+
+# stupid SYS V shell
+SHELL=/bin/sh
+
+# for lint target
+LINT=lint
+LINTFLAGS=-haz $(DEFINES)
+LINTFILT=egrep -v '(possible pointer|long assign|not yet im|:$$)'
+
+# Libs
+LIBS=$(CLIBS) $(RLIBS) $(ILIBS)
+
+CFLAGS=$(CCFLAGS) $(RDEFS) $(IDEFS)
+
+# Used for protoizing
+#.c.o:
+#      $(CC) $(CFLAGS) -aux-info $<.X -c $<
+
+# List of C files, used for make depend:
+C_FILES=access.c atr_tab.c attrib.c boolexp.c bsd.c bufferq.c \
+       chunk.c cmdlocal.c cmds.c \
+       command.c compress.c conf.c cque.c create.c cron.c db.c destroy.c  division.c extchat.c \
+       extmail.c filecopy.c flaglocal.c flags.c funcrypt.c function.c \
+       fundb.c fundiv.c funlist.c funlocal.c funmath.c funmisc.c funstr.c funtime.c \
+       funufun.c game.c help.c htab.c ident.c local.c lock.c log.c look.c \
+       malias.c match.c memcheck.c move.c mycrypt.c mymalloc.c mysocket.c \
+       myssl.c notify.c parse.c pcre.c player.c plyrlist.c \
+       predicat.c privtab.c prog.o ptab.c rob.c rplog.c services.c set.c shs.c  \
+       sig.c speech.c sql.c strdup.c strtree.c  strutil.c tables.c timer.c unparse.c  \
+       utils.c version.c warnings.c  wild.c wiz.c console.c
+
+
+H_FILES = ../config.h ../confmagic.h ../hdrs/ansi.h ../hdrs/atr_tab.h \
+         ../hdrs/attrib.h ../hdrs/boolexp.h ../hdrs/bufferq.h ../hdrs/case.h \
+         ../hdrs/case.h ../hdrs/chunk.h ../hdrs/command.h ../hdrs/conf.h \
+         ../hdrs/copyrite.h ../hdrs/dbdefs.h ../hdrs/dbio.h ../hdrs/extchat.h \
+         ../hdrs/externs.h ../hdrs/extmail.h ../hdrs/flags.h \
+         ../hdrs/function.h ../hdrs/game.h ../hdrs/getpgsiz.h ../hdrs/help.h \
+         ../hdrs/htab.h ../hdrs/htab.h ../hdrs/ident.h ../hdrs/lock.h \
+         ../hdrs/log.h ../hdrs/log.h ../hdrs/malias.h ../hdrs/match.h \
+         ../hdrs/mushdb.h ../hdrs/mushtype.h \
+         ../hdrs/mymalloc.h ../hdrs/mysocket.h ../hdrs/myssl.h \
+         ../hdrs/parse.h ../hdrs/pcre.h ../hdrs/privtab.h ../hdrs/ptab.h \
+         ../hdrs/strtree.h ../hdrs/version.h ../options.h ../hdrs/division.h ../hdrs/cron.h
+
+# .o versions of above - these are used in the build
+COMMON_O_FILES=access.o atr_tab.o attrib.o boolexp.o bufferq.o \
+       chunk.o cmdlocal.o cmds.o \
+       command.o compress.o conf.o cque.o create.o cron.o db.o destroy.o division.o extchat.o \
+       extmail.o filecopy.o flaglocal.o flags.o funcrypt.o function.o \
+       fundb.o funlist.o fundiv.o funlocal.o funmath.o funmisc.o funstr.o funtime.o \
+       funufun.o game.o help.o htab.o ident.o local.o lock.o log.o look.o \
+       malias.o match.o memcheck.o move.o mycrypt.o mymalloc.o mysocket.o \
+       myssl.o notify.o parse.o pcre.o player.o plyrlist.o predicat.o privtab.o \
+       prog.o   ptab.o rob.o rplog.o services.o set.o shs.o sig.o speech.o sql.o \
+       strdup.o strtree.o  strutil.o tables.o timer.o unparse.o utils.o version.o warnings.o \
+       wild.o wiz.o
+O_FILES=$(COMMON_O_FILES) bsd.o
+CONSOLE_O_FILES=$(COMMON_O_FILES) console.o
+
+# This is a dummy target, in case you type 'make' in the source
+# directory (likely for emacs users who M-x compile.)
+# This means that the top-level make had better specifically 'make all' :)
+first:
+       (cd ..; make)
+
+all: $(OUTFILES)
+
+netmud: $(O_FILES) 
+       @echo "Making netmud."
+       -mv -f netmud netmud~
+       $(CC) $(LDFLAGS) $(CCFLAGS) -o netmud $(O_FILES) $(LIBS) 
+
+console: $(CONSOLE_O_FILES)
+       @echo "Making console."
+       -mv -f console console~
+       $(CC) $(LDFLAGS) $(CCFLAGS) -o console $(CONSOLE_O_FILES) $(LIBS)
+
+# By default, db.c initially allocates enough space for 5000 objects, then
+#   grows the space if needed.  To change this value, include
+#   -DDB_INITIAL_SIZE=xxxx where xxxx is the new value (minimum 1).
+
+
+# We recompile mysocket.c instead of reusing mysocket.o because we
+# want to do some error handing differently for info_slave.
+info_slave: info_slave.c ident.o strdup.o sig.o mymalloc.o mysocket.c
+       @echo "Making info_slave."
+       $(CC) $(CCFLAGS) $(IDEFS) -c info_slave.c
+       $(CC) $(LDFLAGS) $(CCFLAGS) -DINFOSLAVE -o info_slave info_slave.o \
+       ident.o strdup.o sig.o mymalloc.o mysocket.c  $(LIBS)
+
+# ../hdrs/buildinf.h contains build information used by version.c:
+# time/date of build and CFLAGS
+# It should always be out of date.
+buildinf:
+       -rm -f ../hdrs/buildinf.h
+       @echo "/* This file generated automatically from Makefile */" >> ../hdrs/buildinf.h
+       @echo "#define BUILDDATE \"`date`\"" >> ../hdrs/buildinf.h
+       @echo "#define COMPILER \"$(CC)\"" >> ../hdrs/buildinf.h
+       @echo "#define CCFLAGS \"$(CCFLAGS)\"" >> ../hdrs/buildinf.h
+       @echo "#define RDEFS \"$(RDEFS)\"" >> ../hdrs/buildinf.h
+       @echo "#define IDEFS \"$(IDEFS)\"" >> ../hdrs/buildinf.h
+
+# If funlocal.c doesn't exist, we want to build it from
+# funlocal.dst. 
+funlocal.c: funlocal.dst
+       @if [ ! -f funlocal.c ]; then \
+         cp funlocal.dst funlocal.c; \
+       else \
+         echo "********************************************************"; \
+         echo "NOTE! funlocal.dst has been changed. You may need to incorporate these" ; \
+         echo "changes into your funlocal.c. Edit or touch funlocal.c to prevent this message"; \
+         echo "********************************************************"; \
+       fi
+
+flaglocal.c: flaglocal.dst
+       @if [ ! -f flaglocal.c ]; then \
+         cp flaglocal.dst flaglocal.c; \
+       else \
+         echo "********************************************************"; \
+         echo "NOTE! flaglocal.dst has been changed. You may need to incorporate these" ; \
+         echo "changes into your flaglocal.c. Edit or touch flaglocal.c to prevent this message"; \
+         echo "********************************************************"; \
+       fi
+
+cmdlocal.c: cmdlocal.dst
+       @if [ ! -f cmdlocal.c ]; then \
+         cp cmdlocal.dst cmdlocal.c; \
+       else \
+         echo "********************************************************"; \
+         echo "NOTE! cmdlocal.dst has been changed. You may need to incorporate these" ; \
+         echo "changes into your cmdlocal.c. Edit or touch cmdlocal.c to prevent this message"; \
+         echo "********************************************************"; \
+       fi
+
+local.c: local.dst
+       @if [ ! -f local.c ]; then \
+         cp local.dst local.c; \
+       else \
+         echo "********************************************************"; \
+         echo "NOTE! local.dst has been changed. You may need to incorporate these" ; \
+         echo "changes into your local.c. Edit or touch local.c to prevent this message"; \
+         echo "********************************************************"; \
+       fi
+
+../hdrs/patches.h:
+       if [ ! -f ../hdrs/patches.h ]; then \
+         (cd ../utils; sh mkcmds.sh patches); \
+       fi
+
+../po/pennmush.pot: $(C_FILES) $(H_FILES)
+       xgettext -d pennmush -kT -o ../po/pennmush.pot $(C_FILES) $(H_FILES)
+
+etags: 
+       etags *.c ../*.h ../hdrs/*.h
+
+ctags: 
+       ctags *.c 
+
+depend: funlocal.c cmdlocal.c local.c flaglocal.c
+       makedepend -fMakefile.SH -w10 -- $(CFLAGS) -- $(C_FILES) $(H_FILES)
+       perl ../utils/fixdepend.pl Makefile.SH
+
+# Requires GNU indent! 
+indent:
+       (set +e; for file in *.dst *.c  ../hdrs/*.h ; do echo $$file; \
+       /usr/bin/expand $$file > tmpfile; mv -f tmpfile $$file; \
+       /usr/bin/indent -npro -kr -ci2 -ss -psl -ip4 -i2 -cs -l80 -lc75 \
+       -T ATRALIAS -T DESC -T CNode -T CONF -T BQUE -T FUN \
+       -T NVAL -T i_rec -T f_rec -T USERFN_ENTRY -T PRIV -T FLAG \
+       -T FLAGENT -T FLAG_ALIAS -T tlist -T u -T stat -T tcheck -T ATTR \
+       -T ALIST -T CHTAB -T FBLKHDR -T FBLOCK -T OPTTAB -T dbref \
+       -T object_flag_type -T channel_type -T boolexp_type -T CHAN \
+       -T mail_flag -T help_indx -T lock_type -T lock_list -T MEM \
+       -T warn_type -T POWER -T POWER_ALIAS -T acsflag -T StrTree \
+       -T switch_mask -T COMLIST -T COMALIAS -T COMMAND -T SWITCH_VALUE \
+       -T COMSORTSTRUC -T CHANUSER -T Signal_t -T Sigfunc -T univptr_t  \
+       -T __ptr_t -T __malloc_size_t -T HASHENT -T HASHTAB -T COMMAND_INFO \
+       -T CHANLIST -T Malloc_t -T Free_t -T MATH -T socklen_t \
+       -T StrNode -T IDENT -T Size_t -T Port_t -T help_file -T PTAB \
+       -T SHS_INFO -T ansi_string -T MAIL $$file ; done)
+
+clean:
+       -rm -f *.o 
+       -rm -f a.out core gmon.out $(OUTFILES) 
+
+distclean: clean
+       -rm -f *~ *.orig *.rej *.bak funlocal.c cmdlocal.c flaglocal.c local.c \#*
+
+test_compress: comp_h.c
+       $(CC) $(CFLAGS) -o test_compress -DSTANDALONE comp_h.c
+
+portmsg: portmsg.c
+       $(CC) $(CFLAGS) -o portmsg portmsg.c $(LIBS)
+
+# Some dependencies that make depend doesn't handle well
+compress.o: comp_h.c comp_w.c comp_w8.c
+version.o: ../hdrs/buildinf.h
+cmds.o: ../hdrs/funs.h ../hdrs/cmds.h
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
+
+access.o: ../config.h
+access.o: ../hdrs/copyrite.h
+access.o: ../hdrs/conf.h
+access.o: ../options.h
+access.o: ../hdrs/mushtype.h
+access.o: ../hdrs/htab.h
+access.o: ../hdrs/externs.h
+access.o: ../hdrs/compile.h
+access.o: ../hdrs/dbdefs.h
+access.o: ../hdrs/mushdb.h
+access.o: ../hdrs/flags.h
+access.o: ../hdrs/ptab.h
+access.o: ../hdrs/division.h
+access.o: ../hdrs/chunk.h
+access.o: ../hdrs/bufferq.h
+access.o: ../confmagic.h
+access.o: ../hdrs/access.h
+access.o: ../hdrs/mymalloc.h
+access.o: ../hdrs/match.h
+access.o: ../hdrs/parse.h
+access.o: ../hdrs/log.h
+atr_tab.o: ../config.h
+atr_tab.o: ../hdrs/conf.h
+atr_tab.o: ../hdrs/copyrite.h
+atr_tab.o: ../options.h
+atr_tab.o: ../hdrs/mushtype.h
+atr_tab.o: ../hdrs/htab.h
+atr_tab.o: ../hdrs/externs.h
+atr_tab.o: ../hdrs/compile.h
+atr_tab.o: ../hdrs/dbdefs.h
+atr_tab.o: ../hdrs/mushdb.h
+atr_tab.o: ../hdrs/flags.h
+atr_tab.o: ../hdrs/ptab.h
+atr_tab.o: ../hdrs/division.h
+atr_tab.o: ../hdrs/chunk.h
+atr_tab.o: ../hdrs/bufferq.h
+atr_tab.o: ../confmagic.h
+atr_tab.o: ../hdrs/attrib.h
+atr_tab.o: ../hdrs/boolexp.h
+atr_tab.o: ../hdrs/atr_tab.h
+atr_tab.o: ../hdrs/privtab.h
+atr_tab.o: ../hdrs/mymalloc.h
+atr_tab.o: ../hdrs/log.h
+atr_tab.o: ../hdrs/parse.h
+attrib.o: ../hdrs/copyrite.h
+attrib.o: ../config.h
+attrib.o: ../hdrs/conf.h
+attrib.o: ../options.h
+attrib.o: ../hdrs/mushtype.h
+attrib.o: ../hdrs/htab.h
+attrib.o: ../hdrs/externs.h
+attrib.o: ../hdrs/compile.h
+attrib.o: ../hdrs/dbdefs.h
+attrib.o: ../hdrs/mushdb.h
+attrib.o: ../hdrs/flags.h
+attrib.o: ../hdrs/ptab.h
+attrib.o: ../hdrs/division.h
+attrib.o: ../hdrs/chunk.h
+attrib.o: ../hdrs/bufferq.h
+attrib.o: ../confmagic.h
+attrib.o: ../hdrs/attrib.h
+attrib.o: ../hdrs/boolexp.h
+attrib.o: ../hdrs/match.h
+attrib.o: ../hdrs/parse.h
+attrib.o: ../hdrs/privtab.h
+attrib.o: ../hdrs/mymalloc.h
+attrib.o: ../hdrs/strtree.h
+attrib.o: ../hdrs/lock.h
+attrib.o: ../hdrs/log.h
+boolexp.o: ../hdrs/copyrite.h
+boolexp.o: ../config.h
+boolexp.o: ../hdrs/conf.h
+boolexp.o: ../options.h
+boolexp.o: ../hdrs/mushtype.h
+boolexp.o: ../hdrs/htab.h
+boolexp.o: ../hdrs/mushdb.h
+boolexp.o: ../hdrs/flags.h
+boolexp.o: ../hdrs/ptab.h
+boolexp.o: ../hdrs/division.h
+boolexp.o: ../hdrs/match.h
+boolexp.o: ../hdrs/externs.h
+boolexp.o: ../hdrs/compile.h
+boolexp.o: ../hdrs/dbdefs.h
+boolexp.o: ../hdrs/chunk.h
+boolexp.o: ../hdrs/bufferq.h
+boolexp.o: ../confmagic.h
+boolexp.o: ../hdrs/lock.h
+boolexp.o: ../hdrs/boolexp.h
+boolexp.o: ../hdrs/parse.h
+boolexp.o: ../hdrs/attrib.h
+boolexp.o: ../hdrs/log.h
+boolexp.o: ../hdrs/extchat.h
+boolexp.o: ../hdrs/strtree.h
+bsd.o: ../hdrs/copyrite.h
+bsd.o: ../config.h
+bsd.o: ../hdrs/conf.h
+bsd.o: ../options.h
+bsd.o: ../hdrs/mushtype.h
+bsd.o: ../hdrs/htab.h
+bsd.o: ../hdrs/externs.h
+bsd.o: ../hdrs/compile.h
+bsd.o: ../hdrs/dbdefs.h
+bsd.o: ../hdrs/mushdb.h
+bsd.o: ../hdrs/flags.h
+bsd.o: ../hdrs/ptab.h
+bsd.o: ../hdrs/division.h
+bsd.o: ../hdrs/chunk.h
+bsd.o: ../hdrs/bufferq.h
+bsd.o: ../confmagic.h
+bsd.o: ../hdrs/lock.h
+bsd.o: ../hdrs/boolexp.h
+bsd.o: ../hdrs/help.h
+bsd.o: ../hdrs/match.h
+bsd.o: ../hdrs/ansi.h
+bsd.o: ../hdrs/pueblo.h
+bsd.o: ../hdrs/parse.h
+bsd.o: ../hdrs/access.h
+bsd.o: ../hdrs/command.h
+bsd.o: ../hdrs/switches.h
+bsd.o: ../hdrs/version.h
+bsd.o: ../hdrs/patches.h
+bsd.o: ../hdrs/mysocket.h
+bsd.o: ../hdrs/ident.h
+bsd.o: ../hdrs/strtree.h
+bsd.o: ../hdrs/log.h
+bsd.o: ../hdrs/pcre.h
+bsd.o: ../hdrs/myssl.h
+bsd.o: ../hdrs/mymalloc.h
+bsd.o: ../hdrs/extmail.h
+bsd.o: ../hdrs/attrib.h
+bsd.o: ../hdrs/game.h
+bsd.o: ../hdrs/dbio.h
+bufferq.o: ../hdrs/copyrite.h
+bufferq.o: ../config.h
+bufferq.o: ../hdrs/conf.h
+bufferq.o: ../options.h
+bufferq.o: ../hdrs/mushtype.h
+bufferq.o: ../hdrs/htab.h
+bufferq.o: ../hdrs/externs.h
+bufferq.o: ../hdrs/compile.h
+bufferq.o: ../hdrs/dbdefs.h
+bufferq.o: ../hdrs/mushdb.h
+bufferq.o: ../hdrs/flags.h
+bufferq.o: ../hdrs/ptab.h
+bufferq.o: ../hdrs/division.h
+bufferq.o: ../hdrs/chunk.h
+bufferq.o: ../hdrs/bufferq.h
+bufferq.o: ../confmagic.h
+bufferq.o: ../hdrs/log.h
+chunk.o: ../hdrs/copyrite.h
+chunk.o: ../config.h
+chunk.o: ../hdrs/conf.h
+chunk.o: ../options.h
+chunk.o: ../hdrs/mushtype.h
+chunk.o: ../hdrs/htab.h
+chunk.o: ../hdrs/externs.h
+chunk.o: ../hdrs/compile.h
+chunk.o: ../hdrs/dbdefs.h
+chunk.o: ../hdrs/mushdb.h
+chunk.o: ../hdrs/flags.h
+chunk.o: ../hdrs/ptab.h
+chunk.o: ../hdrs/division.h
+chunk.o: ../hdrs/chunk.h
+chunk.o: ../hdrs/bufferq.h
+chunk.o: ../confmagic.h
+chunk.o: ../hdrs/boolexp.h
+chunk.o: ../hdrs/command.h
+chunk.o: ../hdrs/switches.h
+chunk.o: ../hdrs/intrface.h
+chunk.o: ../hdrs/log.h
+chunk.o: ../hdrs/mymalloc.h
+cmdlocal.o: ../hdrs/copyrite.h
+cmdlocal.o: ../config.h
+cmdlocal.o: ../hdrs/conf.h
+cmdlocal.o: ../options.h
+cmdlocal.o: ../hdrs/mushtype.h
+cmdlocal.o: ../hdrs/htab.h
+cmdlocal.o: ../hdrs/externs.h
+cmdlocal.o: ../hdrs/compile.h
+cmdlocal.o: ../hdrs/dbdefs.h
+cmdlocal.o: ../hdrs/mushdb.h
+cmdlocal.o: ../hdrs/flags.h
+cmdlocal.o: ../hdrs/ptab.h
+cmdlocal.o: ../hdrs/division.h
+cmdlocal.o: ../hdrs/chunk.h
+cmdlocal.o: ../hdrs/bufferq.h
+cmdlocal.o: ../confmagic.h
+cmdlocal.o: ../hdrs/parse.h
+cmdlocal.o: ../hdrs/command.h
+cmdlocal.o: ../hdrs/boolexp.h
+cmdlocal.o: ../hdrs/switches.h
+cmdlocal.o: ../hdrs/cmds.h
+cmds.o: ../hdrs/copyrite.h
+cmds.o: ../config.h
+cmds.o: ../hdrs/conf.h
+cmds.o: ../options.h
+cmds.o: ../hdrs/mushtype.h
+cmds.o: ../hdrs/htab.h
+cmds.o: ../hdrs/externs.h
+cmds.o: ../hdrs/compile.h
+cmds.o: ../hdrs/dbdefs.h
+cmds.o: ../hdrs/mushdb.h
+cmds.o: ../hdrs/flags.h
+cmds.o: ../hdrs/ptab.h
+cmds.o: ../hdrs/division.h
+cmds.o: ../hdrs/chunk.h
+cmds.o: ../hdrs/bufferq.h
+cmds.o: ../confmagic.h
+cmds.o: ../hdrs/match.h
+cmds.o: ../hdrs/game.h
+cmds.o: ../hdrs/attrib.h
+cmds.o: ../hdrs/boolexp.h
+cmds.o: ../hdrs/extmail.h
+cmds.o: ../hdrs/malias.h
+cmds.o: ../hdrs/getpgsiz.h
+cmds.o: ../hdrs/parse.h
+cmds.o: ../hdrs/access.h
+cmds.o: ../hdrs/version.h
+cmds.o: ../hdrs/lock.h
+cmds.o: ../hdrs/function.h
+cmds.o: ../hdrs/command.h
+cmds.o: ../hdrs/switches.h
+cmds.o: ../hdrs/log.h
+command.o: ../hdrs/copyrite.h
+command.o: ../config.h
+command.o: ../hdrs/conf.h
+command.o: ../options.h
+command.o: ../hdrs/mushtype.h
+command.o: ../hdrs/htab.h
+command.o: ../hdrs/externs.h
+command.o: ../hdrs/compile.h
+command.o: ../hdrs/dbdefs.h
+command.o: ../hdrs/mushdb.h
+command.o: ../hdrs/flags.h
+command.o: ../hdrs/ptab.h
+command.o: ../hdrs/division.h
+command.o: ../hdrs/chunk.h
+command.o: ../hdrs/bufferq.h
+command.o: ../confmagic.h
+command.o: ../hdrs/game.h
+command.o: ../hdrs/match.h
+command.o: ../hdrs/attrib.h
+command.o: ../hdrs/boolexp.h
+command.o: ../hdrs/extmail.h
+command.o: ../hdrs/getpgsiz.h
+command.o: ../hdrs/parse.h
+command.o: ../hdrs/access.h
+command.o: ../hdrs/version.h
+command.o: ../hdrs/function.h
+command.o: ../hdrs/command.h
+command.o: ../hdrs/switches.h
+command.o: ../hdrs/mymalloc.h
+command.o: ../hdrs/log.h
+command.o: ../hdrs/cmds.h
+command.o: switchinc.c
+compress.o: ../config.h
+compress.o: ../options.h
+compress.o: ../hdrs/mushtype.h
+compress.o: ../hdrs/copyrite.h
+compress.o: ../hdrs/log.h
+compress.o: comp_w.c
+compress.o: ../hdrs/conf.h
+compress.o: ../hdrs/htab.h
+compress.o: ../hdrs/externs.h
+compress.o: ../hdrs/compile.h
+compress.o: ../hdrs/dbdefs.h
+compress.o: ../hdrs/mushdb.h
+compress.o: ../hdrs/flags.h
+compress.o: ../hdrs/ptab.h
+compress.o: ../hdrs/division.h
+compress.o: ../hdrs/chunk.h
+compress.o: ../hdrs/bufferq.h
+compress.o: ../confmagic.h
+compress.o: ../hdrs/mymalloc.h
+conf.o: ../hdrs/copyrite.h
+conf.o: ../config.h
+conf.o: ../hdrs/conf.h
+conf.o: ../options.h
+conf.o: ../hdrs/mushtype.h
+conf.o: ../hdrs/htab.h
+conf.o: ../hdrs/externs.h
+conf.o: ../hdrs/compile.h
+conf.o: ../hdrs/dbdefs.h
+conf.o: ../hdrs/mushdb.h
+conf.o: ../hdrs/flags.h
+conf.o: ../hdrs/ptab.h
+conf.o: ../hdrs/division.h
+conf.o: ../hdrs/chunk.h
+conf.o: ../hdrs/bufferq.h
+conf.o: ../confmagic.h
+conf.o: ../hdrs/pueblo.h
+conf.o: ../hdrs/parse.h
+conf.o: ../hdrs/boolexp.h
+conf.o: ../hdrs/command.h
+conf.o: ../hdrs/switches.h
+conf.o: ../hdrs/log.h
+conf.o: ../hdrs/game.h
+conf.o: ../hdrs/attrib.h
+conf.o: ../hdrs/help.h
+conf.o: ../hdrs/function.h
+cque.o: ../hdrs/copyrite.h
+cque.o: ../config.h
+cque.o: ../hdrs/conf.h
+cque.o: ../options.h
+cque.o: ../hdrs/mushtype.h
+cque.o: ../hdrs/htab.h
+cque.o: ../hdrs/boolexp.h
+cque.o: ../hdrs/chunk.h
+cque.o: ../hdrs/command.h
+cque.o: ../hdrs/division.h
+cque.o: ../hdrs/ptab.h
+cque.o: ../hdrs/switches.h
+cque.o: ../hdrs/mushdb.h
+cque.o: ../hdrs/flags.h
+cque.o: ../hdrs/match.h
+cque.o: ../hdrs/externs.h
+cque.o: ../hdrs/compile.h
+cque.o: ../hdrs/dbdefs.h
+cque.o: ../hdrs/bufferq.h
+cque.o: ../confmagic.h
+cque.o: ../hdrs/parse.h
+cque.o: ../hdrs/strtree.h
+cque.o: ../hdrs/mymalloc.h
+cque.o: ../hdrs/game.h
+cque.o: ../hdrs/attrib.h
+cque.o: ../hdrs/log.h
+create.o: ../hdrs/copyrite.h
+create.o: ../config.h
+create.o: ../hdrs/conf.h
+create.o: ../options.h
+create.o: ../hdrs/mushtype.h
+create.o: ../hdrs/htab.h
+create.o: ../hdrs/externs.h
+create.o: ../hdrs/compile.h
+create.o: ../hdrs/dbdefs.h
+create.o: ../hdrs/mushdb.h
+create.o: ../hdrs/flags.h
+create.o: ../hdrs/ptab.h
+create.o: ../hdrs/division.h
+create.o: ../hdrs/chunk.h
+create.o: ../hdrs/bufferq.h
+create.o: ../confmagic.h
+create.o: ../hdrs/attrib.h
+create.o: ../hdrs/boolexp.h
+create.o: ../hdrs/match.h
+create.o: ../hdrs/extchat.h
+create.o: ../hdrs/log.h
+create.o: ../hdrs/lock.h
+create.o: ../hdrs/parse.h
+create.o: ../hdrs/game.h
+create.o: ../hdrs/command.h
+create.o: ../hdrs/switches.h
+db.o: ../hdrs/copyrite.h
+db.o: ../config.h
+db.o: ../hdrs/conf.h
+db.o: ../options.h
+db.o: ../hdrs/mushtype.h
+db.o: ../hdrs/htab.h
+db.o: ../hdrs/dbio.h
+db.o: ../hdrs/externs.h
+db.o: ../hdrs/compile.h
+db.o: ../hdrs/dbdefs.h
+db.o: ../hdrs/mushdb.h
+db.o: ../hdrs/flags.h
+db.o: ../hdrs/ptab.h
+db.o: ../hdrs/division.h
+db.o: ../hdrs/chunk.h
+db.o: ../hdrs/bufferq.h
+db.o: ../confmagic.h
+db.o: ../hdrs/attrib.h
+db.o: ../hdrs/boolexp.h
+db.o: ../hdrs/mymalloc.h
+db.o: ../hdrs/game.h
+db.o: ../hdrs/lock.h
+db.o: ../hdrs/log.h
+db.o: ../hdrs/strtree.h
+db.o: ../hdrs/parse.h
+db.o: ../hdrs/privtab.h
+db.o: ../hdrs/extmail.h
+destroy.o: ../config.h
+destroy.o: ../hdrs/copyrite.h
+destroy.o: ../hdrs/conf.h
+destroy.o: ../options.h
+destroy.o: ../hdrs/mushtype.h
+destroy.o: ../hdrs/htab.h
+destroy.o: ../hdrs/mushdb.h
+destroy.o: ../hdrs/flags.h
+destroy.o: ../hdrs/ptab.h
+destroy.o: ../hdrs/division.h
+destroy.o: ../hdrs/match.h
+destroy.o: ../hdrs/externs.h
+destroy.o: ../hdrs/compile.h
+destroy.o: ../hdrs/dbdefs.h
+destroy.o: ../hdrs/chunk.h
+destroy.o: ../hdrs/bufferq.h
+destroy.o: ../confmagic.h
+destroy.o: ../hdrs/log.h
+destroy.o: ../hdrs/game.h
+destroy.o: ../hdrs/extmail.h
+destroy.o: ../hdrs/malias.h
+destroy.o: ../hdrs/attrib.h
+destroy.o: ../hdrs/boolexp.h
+destroy.o: ../hdrs/lock.h
+division.o: ../hdrs/copyrite.h
+division.o: ../config.h
+division.o: ../hdrs/conf.h
+division.o: ../options.h
+division.o: ../hdrs/mushtype.h
+division.o: ../hdrs/htab.h
+division.o: ../hdrs/externs.h
+division.o: ../hdrs/compile.h
+division.o: ../hdrs/dbdefs.h
+division.o: ../hdrs/mushdb.h
+division.o: ../hdrs/flags.h
+division.o: ../hdrs/ptab.h
+division.o: ../hdrs/division.h
+division.o: ../hdrs/chunk.h
+division.o: ../hdrs/bufferq.h
+division.o: ../confmagic.h
+division.o: ../hdrs/boolexp.h
+division.o: ../hdrs/command.h
+division.o: ../hdrs/switches.h
+division.o: ../hdrs/parse.h
+division.o: ../hdrs/cmds.h
+division.o: ../hdrs/dbio.h
+division.o: ../hdrs/match.h
+division.o: ../hdrs/log.h
+division.o: ../hdrs/ansi.h
+extchat.o: ../hdrs/copyrite.h
+extchat.o: ../config.h
+extchat.o: ../hdrs/conf.h
+extchat.o: ../options.h
+extchat.o: ../hdrs/mushtype.h
+extchat.o: ../hdrs/htab.h
+extchat.o: ../hdrs/externs.h
+extchat.o: ../hdrs/compile.h
+extchat.o: ../hdrs/dbdefs.h
+extchat.o: ../hdrs/mushdb.h
+extchat.o: ../hdrs/flags.h
+extchat.o: ../hdrs/ptab.h
+extchat.o: ../hdrs/division.h
+extchat.o: ../hdrs/chunk.h
+extchat.o: ../hdrs/bufferq.h
+extchat.o: ../confmagic.h
+extchat.o: ../hdrs/attrib.h
+extchat.o: ../hdrs/boolexp.h
+extchat.o: ../hdrs/match.h
+extchat.o: ../hdrs/extchat.h
+extchat.o: ../hdrs/ansi.h
+extchat.o: ../hdrs/privtab.h
+extchat.o: ../hdrs/mymalloc.h
+extchat.o: ../hdrs/pueblo.h
+extchat.o: ../hdrs/parse.h
+extchat.o: ../hdrs/lock.h
+extchat.o: ../hdrs/log.h
+extchat.o: ../hdrs/game.h
+extchat.o: ../hdrs/function.h
+extchat.o: ../hdrs/command.h
+extchat.o: ../hdrs/switches.h
+extchat.o: ../hdrs/dbio.h
+extmail.o: ../config.h
+extmail.o: ../hdrs/copyrite.h
+extmail.o: ../hdrs/conf.h
+extmail.o: ../options.h
+extmail.o: ../hdrs/mushtype.h
+extmail.o: ../hdrs/htab.h
+extmail.o: ../hdrs/externs.h
+extmail.o: ../hdrs/compile.h
+extmail.o: ../hdrs/dbdefs.h
+extmail.o: ../hdrs/mushdb.h
+extmail.o: ../hdrs/flags.h
+extmail.o: ../hdrs/ptab.h
+extmail.o: ../hdrs/division.h
+extmail.o: ../hdrs/chunk.h
+extmail.o: ../hdrs/bufferq.h
+extmail.o: ../confmagic.h
+extmail.o: ../hdrs/match.h
+extmail.o: ../hdrs/extmail.h
+extmail.o: ../hdrs/malias.h
+extmail.o: ../hdrs/attrib.h
+extmail.o: ../hdrs/boolexp.h
+extmail.o: ../hdrs/parse.h
+extmail.o: ../hdrs/mymalloc.h
+extmail.o: ../hdrs/pueblo.h
+extmail.o: ../hdrs/log.h
+extmail.o: ../hdrs/lock.h
+extmail.o: ../hdrs/command.h
+extmail.o: ../hdrs/switches.h
+extmail.o: ../hdrs/dbio.h
+flaglocal.o: ../hdrs/copyrite.h
+flaglocal.o: ../config.h
+flaglocal.o: ../hdrs/conf.h
+flaglocal.o: ../options.h
+flaglocal.o: ../hdrs/mushtype.h
+flaglocal.o: ../hdrs/htab.h
+flaglocal.o: ../hdrs/externs.h
+flaglocal.o: ../hdrs/compile.h
+flaglocal.o: ../hdrs/dbdefs.h
+flaglocal.o: ../hdrs/mushdb.h
+flaglocal.o: ../hdrs/flags.h
+flaglocal.o: ../hdrs/ptab.h
+flaglocal.o: ../hdrs/division.h
+flaglocal.o: ../hdrs/chunk.h
+flaglocal.o: ../hdrs/bufferq.h
+flaglocal.o: ../confmagic.h
+flags.o: ../config.h
+flags.o: ../hdrs/conf.h
+flags.o: ../hdrs/copyrite.h
+flags.o: ../options.h
+flags.o: ../hdrs/mushtype.h
+flags.o: ../hdrs/htab.h
+flags.o: ../hdrs/externs.h
+flags.o: ../hdrs/compile.h
+flags.o: ../hdrs/dbdefs.h
+flags.o: ../hdrs/mushdb.h
+flags.o: ../hdrs/flags.h
+flags.o: ../hdrs/ptab.h
+flags.o: ../hdrs/division.h
+flags.o: ../hdrs/chunk.h
+flags.o: ../hdrs/bufferq.h
+flags.o: ../confmagic.h
+flags.o: ../hdrs/boolexp.h
+flags.o: ../hdrs/command.h
+flags.o: ../hdrs/switches.h
+flags.o: ../hdrs/attrib.h
+flags.o: ../hdrs/parse.h
+flags.o: ../hdrs/match.h
+flags.o: ../hdrs/privtab.h
+flags.o: ../hdrs/game.h
+flags.o: ../hdrs/lock.h
+flags.o: ../hdrs/log.h
+flags.o: ../hdrs/dbio.h
+flags.o: ../hdrs/oldflags.h
+flags.o: ../hdrs/ansi.h
+funcrypt.o: ../hdrs/copyrite.h
+funcrypt.o: ../config.h
+funcrypt.o: ../hdrs/conf.h
+funcrypt.o: ../options.h
+funcrypt.o: ../hdrs/mushtype.h
+funcrypt.o: ../hdrs/htab.h
+funcrypt.o: ../hdrs/case.h
+funcrypt.o: ../hdrs/externs.h
+funcrypt.o: ../hdrs/compile.h
+funcrypt.o: ../hdrs/dbdefs.h
+funcrypt.o: ../hdrs/mushdb.h
+funcrypt.o: ../hdrs/flags.h
+funcrypt.o: ../hdrs/ptab.h
+funcrypt.o: ../hdrs/division.h
+funcrypt.o: ../hdrs/chunk.h
+funcrypt.o: ../hdrs/bufferq.h
+funcrypt.o: ../confmagic.h
+funcrypt.o: ../hdrs/version.h
+funcrypt.o: ../hdrs/extchat.h
+funcrypt.o: ../hdrs/parse.h
+funcrypt.o: ../hdrs/function.h
+funcrypt.o: ../hdrs/boolexp.h
+funcrypt.o: ../hdrs/command.h
+funcrypt.o: ../hdrs/switches.h
+funcrypt.o: ../hdrs/game.h
+funcrypt.o: ../hdrs/attrib.h
+funcrypt.o: ../hdrs/ansi.h
+funcrypt.o: ../hdrs/match.h
+function.o: ../hdrs/copyrite.h
+function.o: ../config.h
+function.o: ../hdrs/conf.h
+function.o: ../options.h
+function.o: ../hdrs/mushtype.h
+function.o: ../hdrs/htab.h
+function.o: ../hdrs/externs.h
+function.o: ../hdrs/compile.h
+function.o: ../hdrs/dbdefs.h
+function.o: ../hdrs/mushdb.h
+function.o: ../hdrs/flags.h
+function.o: ../hdrs/ptab.h
+function.o: ../hdrs/division.h
+function.o: ../hdrs/chunk.h
+function.o: ../hdrs/bufferq.h
+function.o: ../confmagic.h
+function.o: ../hdrs/attrib.h
+function.o: ../hdrs/boolexp.h
+function.o: ../hdrs/function.h
+function.o: ../hdrs/match.h
+function.o: ../hdrs/parse.h
+function.o: ../hdrs/lock.h
+function.o: ../hdrs/game.h
+function.o: ../hdrs/mymalloc.h
+function.o: ../hdrs/funs.h
+fundb.o: ../hdrs/copyrite.h
+fundb.o: ../config.h
+fundb.o: ../hdrs/conf.h
+fundb.o: ../options.h
+fundb.o: ../hdrs/mushtype.h
+fundb.o: ../hdrs/htab.h
+fundb.o: ../hdrs/externs.h
+fundb.o: ../hdrs/compile.h
+fundb.o: ../hdrs/dbdefs.h
+fundb.o: ../hdrs/mushdb.h
+fundb.o: ../hdrs/flags.h
+fundb.o: ../hdrs/ptab.h
+fundb.o: ../hdrs/division.h
+fundb.o: ../hdrs/chunk.h
+fundb.o: ../hdrs/bufferq.h
+fundb.o: ../confmagic.h
+fundb.o: ../hdrs/match.h
+fundb.o: ../hdrs/parse.h
+fundb.o: ../hdrs/boolexp.h
+fundb.o: ../hdrs/command.h
+fundb.o: ../hdrs/switches.h
+fundb.o: ../hdrs/game.h
+fundb.o: ../hdrs/privtab.h
+fundb.o: ../hdrs/lock.h
+fundb.o: ../hdrs/log.h
+fundb.o: ../hdrs/attrib.h
+fundb.o: ../hdrs/function.h
+fundiv.o: ../hdrs/copyrite.h
+fundiv.o: ../config.h
+fundiv.o: ../hdrs/conf.h
+fundiv.o: ../options.h
+fundiv.o: ../hdrs/mushtype.h
+fundiv.o: ../hdrs/htab.h
+fundiv.o: ../hdrs/externs.h
+fundiv.o: ../hdrs/compile.h
+fundiv.o: ../hdrs/dbdefs.h
+fundiv.o: ../hdrs/mushdb.h
+fundiv.o: ../hdrs/flags.h
+fundiv.o: ../hdrs/ptab.h
+fundiv.o: ../hdrs/division.h
+fundiv.o: ../hdrs/chunk.h
+fundiv.o: ../hdrs/bufferq.h
+fundiv.o: ../confmagic.h
+fundiv.o: ../hdrs/match.h
+fundiv.o: ../hdrs/parse.h
+fundiv.o: ../hdrs/boolexp.h
+fundiv.o: ../hdrs/command.h
+fundiv.o: ../hdrs/switches.h
+fundiv.o: ../hdrs/game.h
+fundiv.o: ../hdrs/privtab.h
+fundiv.o: ../hdrs/lock.h
+fundiv.o: ../hdrs/log.h
+fundiv.o: ../hdrs/attrib.h
+funlist.o: ../hdrs/copyrite.h
+funlist.o: ../config.h
+funlist.o: ../hdrs/conf.h
+funlist.o: ../options.h
+funlist.o: ../hdrs/mushtype.h
+funlist.o: ../hdrs/htab.h
+funlist.o: ../hdrs/case.h
+funlist.o: ../hdrs/externs.h
+funlist.o: ../hdrs/compile.h
+funlist.o: ../hdrs/dbdefs.h
+funlist.o: ../hdrs/mushdb.h
+funlist.o: ../hdrs/flags.h
+funlist.o: ../hdrs/ptab.h
+funlist.o: ../hdrs/division.h
+funlist.o: ../hdrs/chunk.h
+funlist.o: ../hdrs/bufferq.h
+funlist.o: ../confmagic.h
+funlist.o: ../hdrs/parse.h
+funlist.o: ../hdrs/boolexp.h
+funlist.o: ../hdrs/function.h
+funlist.o: ../hdrs/mymalloc.h
+funlist.o: ../hdrs/pcre.h
+funlist.o: ../hdrs/match.h
+funlist.o: ../hdrs/attrib.h
+funlist.o: ../hdrs/lock.h
+funlocal.o: ../hdrs/copyrite.h
+funlocal.o: ../config.h
+funlocal.o: ../hdrs/conf.h
+funlocal.o: ../options.h
+funlocal.o: ../hdrs/mushtype.h
+funlocal.o: ../hdrs/htab.h
+funlocal.o: ../hdrs/externs.h
+funlocal.o: ../hdrs/compile.h
+funlocal.o: ../hdrs/dbdefs.h
+funlocal.o: ../hdrs/mushdb.h
+funlocal.o: ../hdrs/flags.h
+funlocal.o: ../hdrs/ptab.h
+funlocal.o: ../hdrs/division.h
+funlocal.o: ../hdrs/chunk.h
+funlocal.o: ../hdrs/bufferq.h
+funlocal.o: ../confmagic.h
+funlocal.o: ../hdrs/parse.h
+funlocal.o: ../hdrs/boolexp.h
+funlocal.o: ../hdrs/function.h
+funmath.o: ../hdrs/copyrite.h
+funmath.o: ../config.h
+funmath.o: ../hdrs/conf.h
+funmath.o: ../options.h
+funmath.o: ../hdrs/mushtype.h
+funmath.o: ../hdrs/htab.h
+funmath.o: ../hdrs/externs.h
+funmath.o: ../hdrs/compile.h
+funmath.o: ../hdrs/dbdefs.h
+funmath.o: ../hdrs/mushdb.h
+funmath.o: ../hdrs/flags.h
+funmath.o: ../hdrs/ptab.h
+funmath.o: ../hdrs/division.h
+funmath.o: ../hdrs/chunk.h
+funmath.o: ../hdrs/bufferq.h
+funmath.o: ../confmagic.h
+funmath.o: ../hdrs/parse.h
+funmisc.o: ../hdrs/copyrite.h
+funmisc.o: ../config.h
+funmisc.o: ../hdrs/conf.h
+funmisc.o: ../options.h
+funmisc.o: ../hdrs/mushtype.h
+funmisc.o: ../hdrs/htab.h
+funmisc.o: ../hdrs/case.h
+funmisc.o: ../hdrs/externs.h
+funmisc.o: ../hdrs/compile.h
+funmisc.o: ../hdrs/dbdefs.h
+funmisc.o: ../hdrs/mushdb.h
+funmisc.o: ../hdrs/flags.h
+funmisc.o: ../hdrs/ptab.h
+funmisc.o: ../hdrs/division.h
+funmisc.o: ../hdrs/chunk.h
+funmisc.o: ../hdrs/bufferq.h
+funmisc.o: ../confmagic.h
+funmisc.o: ../hdrs/version.h
+funmisc.o: ../hdrs/match.h
+funmisc.o: ../hdrs/parse.h
+funmisc.o: ../hdrs/boolexp.h
+funmisc.o: ../hdrs/function.h
+funmisc.o: ../hdrs/command.h
+funmisc.o: ../hdrs/switches.h
+funmisc.o: ../hdrs/game.h
+funmisc.o: ../hdrs/attrib.h
+funstr.o: ../hdrs/copyrite.h
+funstr.o: ../config.h
+funstr.o: ../hdrs/conf.h
+funstr.o: ../options.h
+funstr.o: ../hdrs/mushtype.h
+funstr.o: ../hdrs/htab.h
+funstr.o: ../hdrs/ansi.h
+funstr.o: ../hdrs/externs.h
+funstr.o: ../hdrs/compile.h
+funstr.o: ../hdrs/dbdefs.h
+funstr.o: ../hdrs/mushdb.h
+funstr.o: ../hdrs/flags.h
+funstr.o: ../hdrs/ptab.h
+funstr.o: ../hdrs/division.h
+funstr.o: ../hdrs/chunk.h
+funstr.o: ../hdrs/bufferq.h
+funstr.o: ../confmagic.h
+funstr.o: ../hdrs/case.h
+funstr.o: ../hdrs/match.h
+funstr.o: ../hdrs/parse.h
+funstr.o: ../hdrs/pueblo.h
+funstr.o: ../hdrs/attrib.h
+funstr.o: ../hdrs/boolexp.h
+funstr.o: ../hdrs/lock.h
+funtime.o: ../hdrs/copyrite.h
+funtime.o: ../config.h
+funtime.o: ../hdrs/conf.h
+funtime.o: ../options.h
+funtime.o: ../hdrs/mushtype.h
+funtime.o: ../hdrs/htab.h
+funtime.o: ../hdrs/externs.h
+funtime.o: ../hdrs/compile.h
+funtime.o: ../hdrs/dbdefs.h
+funtime.o: ../hdrs/mushdb.h
+funtime.o: ../hdrs/flags.h
+funtime.o: ../hdrs/ptab.h
+funtime.o: ../hdrs/division.h
+funtime.o: ../hdrs/chunk.h
+funtime.o: ../hdrs/bufferq.h
+funtime.o: ../confmagic.h
+funtime.o: ../hdrs/parse.h
+funtime.o: ../hdrs/log.h
+funtime.o: ../hdrs/match.h
+funtime.o: ../hdrs/attrib.h
+funtime.o: ../hdrs/boolexp.h
+funufun.o: ../hdrs/copyrite.h
+funufun.o: ../config.h
+funufun.o: ../hdrs/conf.h
+funufun.o: ../options.h
+funufun.o: ../hdrs/mushtype.h
+funufun.o: ../hdrs/htab.h
+funufun.o: ../hdrs/externs.h
+funufun.o: ../hdrs/compile.h
+funufun.o: ../hdrs/dbdefs.h
+funufun.o: ../hdrs/mushdb.h
+funufun.o: ../hdrs/flags.h
+funufun.o: ../hdrs/ptab.h
+funufun.o: ../hdrs/division.h
+funufun.o: ../hdrs/chunk.h
+funufun.o: ../hdrs/bufferq.h
+funufun.o: ../confmagic.h
+funufun.o: ../hdrs/match.h
+funufun.o: ../hdrs/parse.h
+funufun.o: ../hdrs/mymalloc.h
+funufun.o: ../hdrs/attrib.h
+funufun.o: ../hdrs/boolexp.h
+funufun.o: ../hdrs/lock.h
+game.o: ../hdrs/copyrite.h
+game.o: ../config.h
+game.o: ../hdrs/conf.h
+game.o: ../options.h
+game.o: ../hdrs/mushtype.h
+game.o: ../hdrs/htab.h
+game.o: ../hdrs/externs.h
+game.o: ../hdrs/compile.h
+game.o: ../hdrs/dbdefs.h
+game.o: ../hdrs/mushdb.h
+game.o: ../hdrs/flags.h
+game.o: ../hdrs/ptab.h
+game.o: ../hdrs/division.h
+game.o: ../hdrs/chunk.h
+game.o: ../hdrs/bufferq.h
+game.o: ../confmagic.h
+game.o: ../hdrs/game.h
+game.o: ../hdrs/attrib.h
+game.o: ../hdrs/boolexp.h
+game.o: ../hdrs/match.h
+game.o: ../hdrs/case.h
+game.o: ../hdrs/extmail.h
+game.o: ../hdrs/myssl.h
+game.o: ../hdrs/getpgsiz.h
+game.o: ../hdrs/parse.h
+game.o: ../hdrs/access.h
+game.o: ../hdrs/version.h
+game.o: ../hdrs/strtree.h
+game.o: ../hdrs/command.h
+game.o: ../hdrs/switches.h
+game.o: ../hdrs/log.h
+game.o: ../hdrs/lock.h
+game.o: ../hdrs/function.h
+game.o: ../hdrs/help.h
+game.o: ../hdrs/dbio.h
+help.o: ../config.h
+help.o: ../hdrs/conf.h
+help.o: ../hdrs/copyrite.h
+help.o: ../options.h
+help.o: ../hdrs/mushtype.h
+help.o: ../hdrs/htab.h
+help.o: ../hdrs/externs.h
+help.o: ../hdrs/compile.h
+help.o: ../hdrs/dbdefs.h
+help.o: ../hdrs/mushdb.h
+help.o: ../hdrs/flags.h
+help.o: ../hdrs/ptab.h
+help.o: ../hdrs/division.h
+help.o: ../hdrs/chunk.h
+help.o: ../hdrs/bufferq.h
+help.o: ../confmagic.h
+help.o: ../hdrs/boolexp.h
+help.o: ../hdrs/command.h
+help.o: ../hdrs/switches.h
+help.o: ../hdrs/help.h
+help.o: ../hdrs/log.h
+help.o: ../hdrs/ansi.h
+help.o: ../hdrs/parse.h
+help.o: ../hdrs/pueblo.h
+help.o: ../hdrs/mymalloc.h
+htab.o: ../config.h
+htab.o: ../hdrs/copyrite.h
+htab.o: ../hdrs/conf.h
+htab.o: ../options.h
+htab.o: ../hdrs/mushtype.h
+htab.o: ../hdrs/htab.h
+htab.o: ../hdrs/externs.h
+htab.o: ../hdrs/compile.h
+htab.o: ../hdrs/dbdefs.h
+htab.o: ../hdrs/mushdb.h
+htab.o: ../hdrs/flags.h
+htab.o: ../hdrs/ptab.h
+htab.o: ../hdrs/division.h
+htab.o: ../hdrs/chunk.h
+htab.o: ../hdrs/bufferq.h
+htab.o: ../confmagic.h
+htab.o: ../hdrs/mymalloc.h
+ident.o: ../config.h
+ident.o: ../hdrs/conf.h
+ident.o: ../hdrs/copyrite.h
+ident.o: ../options.h
+ident.o: ../hdrs/mushtype.h
+ident.o: ../hdrs/htab.h
+ident.o: ../hdrs/externs.h
+ident.o: ../hdrs/compile.h
+ident.o: ../hdrs/dbdefs.h
+ident.o: ../hdrs/mushdb.h
+ident.o: ../hdrs/flags.h
+ident.o: ../hdrs/ptab.h
+ident.o: ../hdrs/division.h
+ident.o: ../hdrs/chunk.h
+ident.o: ../hdrs/bufferq.h
+ident.o: ../confmagic.h
+ident.o: ../hdrs/attrib.h
+ident.o: ../hdrs/boolexp.h
+ident.o: ../hdrs/ident.h
+ident.o: ../hdrs/mysocket.h
+ident.o: ../hdrs/mymalloc.h
+local.o: ../hdrs/copyrite.h
+local.o: ../config.h
+local.o: ../hdrs/conf.h
+local.o: ../options.h
+local.o: ../hdrs/mushtype.h
+local.o: ../hdrs/htab.h
+local.o: ../hdrs/dbio.h
+local.o: ../hdrs/externs.h
+local.o: ../hdrs/compile.h
+local.o: ../hdrs/dbdefs.h
+local.o: ../hdrs/mushdb.h
+local.o: ../hdrs/flags.h
+local.o: ../hdrs/ptab.h
+local.o: ../hdrs/division.h
+local.o: ../hdrs/chunk.h
+local.o: ../hdrs/bufferq.h
+local.o: ../confmagic.h
+local.o: ../hdrs/parse.h
+local.o: ../hdrs/command.h
+local.o: ../hdrs/boolexp.h
+local.o: ../hdrs/switches.h
+lock.o: ../hdrs/copyrite.h
+lock.o: ../config.h
+lock.o: ../hdrs/conf.h
+lock.o: ../options.h
+lock.o: ../hdrs/mushtype.h
+lock.o: ../hdrs/htab.h
+lock.o: ../hdrs/externs.h
+lock.o: ../hdrs/compile.h
+lock.o: ../hdrs/dbdefs.h
+lock.o: ../hdrs/mushdb.h
+lock.o: ../hdrs/flags.h
+lock.o: ../hdrs/ptab.h
+lock.o: ../hdrs/division.h
+lock.o: ../hdrs/chunk.h
+lock.o: ../hdrs/bufferq.h
+lock.o: ../confmagic.h
+lock.o: ../hdrs/boolexp.h
+lock.o: ../hdrs/attrib.h
+lock.o: ../hdrs/lock.h
+lock.o: ../hdrs/match.h
+lock.o: ../hdrs/log.h
+lock.o: ../hdrs/mymalloc.h
+lock.o: ../hdrs/strtree.h
+lock.o: ../hdrs/privtab.h
+lock.o: ../hdrs/parse.h
+log.o: ../hdrs/copyrite.h
+log.o: ../config.h
+log.o: ../hdrs/conf.h
+log.o: ../options.h
+log.o: ../hdrs/mushtype.h
+log.o: ../hdrs/htab.h
+log.o: ../hdrs/externs.h
+log.o: ../hdrs/compile.h
+log.o: ../hdrs/dbdefs.h
+log.o: ../hdrs/mushdb.h
+log.o: ../hdrs/flags.h
+log.o: ../hdrs/ptab.h
+log.o: ../hdrs/division.h
+log.o: ../hdrs/chunk.h
+log.o: ../hdrs/bufferq.h
+log.o: ../confmagic.h
+log.o: ../hdrs/log.h
+look.o: ../config.h
+look.o: ../hdrs/copyrite.h
+look.o: ../hdrs/conf.h
+look.o: ../options.h
+look.o: ../hdrs/mushtype.h
+look.o: ../hdrs/htab.h
+look.o: ../hdrs/externs.h
+look.o: ../hdrs/compile.h
+look.o: ../hdrs/dbdefs.h
+look.o: ../hdrs/mushdb.h
+look.o: ../hdrs/flags.h
+look.o: ../hdrs/ptab.h
+look.o: ../hdrs/division.h
+look.o: ../hdrs/chunk.h
+look.o: ../hdrs/bufferq.h
+look.o: ../confmagic.h
+look.o: ../hdrs/lock.h
+look.o: ../hdrs/boolexp.h
+look.o: ../hdrs/attrib.h
+look.o: ../hdrs/match.h
+look.o: ../hdrs/ansi.h
+look.o: ../hdrs/pueblo.h
+look.o: ../hdrs/extchat.h
+look.o: ../hdrs/game.h
+look.o: ../hdrs/command.h
+look.o: ../hdrs/switches.h
+look.o: ../hdrs/parse.h
+look.o: ../hdrs/privtab.h
+look.o: ../hdrs/log.h
+malias.o: ../config.h
+malias.o: ../hdrs/copyrite.h
+malias.o: ../hdrs/conf.h
+malias.o: ../options.h
+malias.o: ../hdrs/mushtype.h
+malias.o: ../hdrs/htab.h
+malias.o: ../hdrs/externs.h
+malias.o: ../hdrs/compile.h
+malias.o: ../hdrs/dbdefs.h
+malias.o: ../hdrs/mushdb.h
+malias.o: ../hdrs/flags.h
+malias.o: ../hdrs/ptab.h
+malias.o: ../hdrs/division.h
+malias.o: ../hdrs/chunk.h
+malias.o: ../hdrs/bufferq.h
+malias.o: ../confmagic.h
+malias.o: ../hdrs/match.h
+malias.o: ../hdrs/parse.h
+malias.o: ../hdrs/malias.h
+malias.o: ../hdrs/privtab.h
+malias.o: ../hdrs/mymalloc.h
+malias.o: ../hdrs/pueblo.h
+malias.o: ../hdrs/log.h
+malias.o: ../hdrs/dbio.h
+match.o: ../hdrs/copyrite.h
+match.o: ../config.h
+match.o: ../hdrs/conf.h
+match.o: ../options.h
+match.o: ../hdrs/mushtype.h
+match.o: ../hdrs/htab.h
+match.o: ../hdrs/mushdb.h
+match.o: ../hdrs/flags.h
+match.o: ../hdrs/ptab.h
+match.o: ../hdrs/division.h
+match.o: ../hdrs/externs.h
+match.o: ../hdrs/compile.h
+match.o: ../hdrs/dbdefs.h
+match.o: ../hdrs/chunk.h
+match.o: ../hdrs/bufferq.h
+match.o: ../confmagic.h
+match.o: ../hdrs/case.h
+match.o: ../hdrs/match.h
+match.o: ../hdrs/parse.h
+memcheck.o: ../config.h
+memcheck.o: ../hdrs/conf.h
+memcheck.o: ../hdrs/copyrite.h
+memcheck.o: ../options.h
+memcheck.o: ../hdrs/mushtype.h
+memcheck.o: ../hdrs/htab.h
+memcheck.o: ../hdrs/externs.h
+memcheck.o: ../hdrs/compile.h
+memcheck.o: ../hdrs/dbdefs.h
+memcheck.o: ../hdrs/mushdb.h
+memcheck.o: ../hdrs/flags.h
+memcheck.o: ../hdrs/ptab.h
+memcheck.o: ../hdrs/division.h
+memcheck.o: ../hdrs/chunk.h
+memcheck.o: ../hdrs/bufferq.h
+memcheck.o: ../confmagic.h
+memcheck.o: ../hdrs/mymalloc.h
+memcheck.o: ../hdrs/log.h
+move.o: ../hdrs/copyrite.h
+move.o: ../config.h
+move.o: ../hdrs/conf.h
+move.o: ../options.h
+move.o: ../hdrs/mushtype.h
+move.o: ../hdrs/htab.h
+move.o: ../hdrs/externs.h
+move.o: ../hdrs/compile.h
+move.o: ../hdrs/dbdefs.h
+move.o: ../hdrs/mushdb.h
+move.o: ../hdrs/flags.h
+move.o: ../hdrs/ptab.h
+move.o: ../hdrs/division.h
+move.o: ../hdrs/chunk.h
+move.o: ../hdrs/bufferq.h
+move.o: ../confmagic.h
+move.o: ../hdrs/attrib.h
+move.o: ../hdrs/boolexp.h
+move.o: ../hdrs/match.h
+move.o: ../hdrs/lock.h
+move.o: ../hdrs/parse.h
+move.o: ../hdrs/log.h
+move.o: ../hdrs/command.h
+move.o: ../hdrs/switches.h
+move.o: ../hdrs/cmds.h
+move.o: ../hdrs/game.h
+mycrypt.o: ../config.h
+mycrypt.o: ../hdrs/conf.h
+mycrypt.o: ../hdrs/copyrite.h
+mycrypt.o: ../options.h
+mycrypt.o: ../hdrs/mushtype.h
+mycrypt.o: ../hdrs/htab.h
+mycrypt.o: ../confmagic.h
+mymalloc.o: ../config.h
+mymalloc.o: ../options.h
+mymalloc.o: ../confmagic.h
+mymalloc.o: csrimalloc.c
+mymalloc.o: ../hdrs/conf.h
+mymalloc.o: ../hdrs/copyrite.h
+mymalloc.o: ../hdrs/mushtype.h
+mymalloc.o: ../hdrs/htab.h
+mymalloc.o: ../hdrs/externs.h
+mymalloc.o: ../hdrs/compile.h
+mymalloc.o: ../hdrs/dbdefs.h
+mymalloc.o: ../hdrs/mushdb.h
+mymalloc.o: ../hdrs/flags.h
+mymalloc.o: ../hdrs/ptab.h
+mymalloc.o: ../hdrs/division.h
+mymalloc.o: ../hdrs/chunk.h
+mymalloc.o: ../hdrs/bufferq.h
+mymalloc.o: ../hdrs/mymalloc.h
+mymalloc.o: ../hdrs/csrimalloc.h
+mysocket.o: ../hdrs/copyrite.h
+mysocket.o: ../config.h
+mysocket.o: ../hdrs/conf.h
+mysocket.o: ../options.h
+mysocket.o: ../hdrs/mushtype.h
+mysocket.o: ../hdrs/htab.h
+mysocket.o: ../hdrs/externs.h
+mysocket.o: ../hdrs/compile.h
+mysocket.o: ../hdrs/dbdefs.h
+mysocket.o: ../hdrs/mushdb.h
+mysocket.o: ../hdrs/flags.h
+mysocket.o: ../hdrs/ptab.h
+mysocket.o: ../hdrs/division.h
+mysocket.o: ../hdrs/chunk.h
+mysocket.o: ../hdrs/bufferq.h
+mysocket.o: ../confmagic.h
+mysocket.o: ../hdrs/mymalloc.h
+mysocket.o: ../hdrs/mysocket.h
+mysocket.o: ../hdrs/ident.h
+myssl.o: ../hdrs/copyrite.h
+myssl.o: ../config.h
+myssl.o: ../hdrs/conf.h
+myssl.o: ../options.h
+myssl.o: ../hdrs/mushtype.h
+myssl.o: ../hdrs/htab.h
+myssl.o: ../hdrs/mysocket.h
+myssl.o: ../confmagic.h
+myssl.o: ../hdrs/myssl.h
+myssl.o: ../hdrs/log.h
+myssl.o: ../hdrs/parse.h
+notify.o: ../hdrs/copyrite.h
+notify.o: ../config.h
+notify.o: ../hdrs/conf.h
+notify.o: ../options.h
+notify.o: ../hdrs/mushtype.h
+notify.o: ../hdrs/htab.h
+notify.o: ../hdrs/mushdb.h
+notify.o: ../hdrs/flags.h
+notify.o: ../hdrs/ptab.h
+notify.o: ../hdrs/division.h
+notify.o: ../hdrs/externs.h
+notify.o: ../hdrs/compile.h
+notify.o: ../hdrs/dbdefs.h
+notify.o: ../hdrs/chunk.h
+notify.o: ../hdrs/bufferq.h
+notify.o: ../confmagic.h
+notify.o: ../hdrs/lock.h
+notify.o: ../hdrs/boolexp.h
+notify.o: ../hdrs/help.h
+notify.o: ../hdrs/match.h
+notify.o: ../hdrs/ansi.h
+notify.o: ../hdrs/pueblo.h
+notify.o: ../hdrs/parse.h
+notify.o: ../hdrs/access.h
+notify.o: ../hdrs/version.h
+notify.o: ../hdrs/patches.h
+notify.o: ../hdrs/mysocket.h
+notify.o: ../hdrs/ident.h
+notify.o: ../hdrs/strtree.h
+notify.o: ../hdrs/log.h
+notify.o: ../hdrs/mymalloc.h
+notify.o: ../hdrs/extmail.h
+notify.o: ../hdrs/attrib.h
+notify.o: ../hdrs/game.h
+parse.o: ../hdrs/copyrite.h
+parse.o: ../config.h
+parse.o: ../hdrs/conf.h
+parse.o: ../options.h
+parse.o: ../hdrs/mushtype.h
+parse.o: ../hdrs/htab.h
+parse.o: ../hdrs/externs.h
+parse.o: ../hdrs/compile.h
+parse.o: ../hdrs/dbdefs.h
+parse.o: ../hdrs/mushdb.h
+parse.o: ../hdrs/flags.h
+parse.o: ../hdrs/ptab.h
+parse.o: ../hdrs/division.h
+parse.o: ../hdrs/chunk.h
+parse.o: ../hdrs/bufferq.h
+parse.o: ../confmagic.h
+parse.o: ../hdrs/ansi.h
+parse.o: ../hdrs/boolexp.h
+parse.o: ../hdrs/function.h
+parse.o: ../hdrs/case.h
+parse.o: ../hdrs/match.h
+parse.o: ../hdrs/parse.h
+parse.o: ../hdrs/attrib.h
+parse.o: ../hdrs/pcre.h
+parse.o: ../hdrs/log.h
+parse.o: ../hdrs/mymalloc.h
+pcre.o: ../config.h
+pcre.o: ../hdrs/pcre.h
+pcre.o: ../confmagic.h
+player.o: ../hdrs/copyrite.h
+player.o: ../config.h
+player.o: ../hdrs/conf.h
+player.o: ../options.h
+player.o: ../hdrs/mushtype.h
+player.o: ../hdrs/htab.h
+player.o: ../hdrs/externs.h
+player.o: ../hdrs/compile.h
+player.o: ../hdrs/dbdefs.h
+player.o: ../hdrs/mushdb.h
+player.o: ../hdrs/flags.h
+player.o: ../hdrs/ptab.h
+player.o: ../hdrs/division.h
+player.o: ../hdrs/chunk.h
+player.o: ../hdrs/bufferq.h
+player.o: ../confmagic.h
+player.o: ../hdrs/attrib.h
+player.o: ../hdrs/boolexp.h
+player.o: ../hdrs/access.h
+player.o: ../hdrs/parse.h
+player.o: ../hdrs/mymalloc.h
+player.o: ../hdrs/log.h
+player.o: ../hdrs/lock.h
+player.o: ../hdrs/extmail.h
+plyrlist.o: ../config.h
+plyrlist.o: ../hdrs/copyrite.h
+plyrlist.o: ../hdrs/conf.h
+plyrlist.o: ../options.h
+plyrlist.o: ../hdrs/mushtype.h
+plyrlist.o: ../hdrs/htab.h
+plyrlist.o: ../hdrs/externs.h
+plyrlist.o: ../hdrs/compile.h
+plyrlist.o: ../hdrs/dbdefs.h
+plyrlist.o: ../hdrs/mushdb.h
+plyrlist.o: ../hdrs/flags.h
+plyrlist.o: ../hdrs/ptab.h
+plyrlist.o: ../hdrs/division.h
+plyrlist.o: ../hdrs/chunk.h
+plyrlist.o: ../hdrs/bufferq.h
+plyrlist.o: ../confmagic.h
+predicat.o: ../hdrs/copyrite.h
+predicat.o: ../config.h
+predicat.o: ../hdrs/conf.h
+predicat.o: ../options.h
+predicat.o: ../hdrs/mushtype.h
+predicat.o: ../hdrs/htab.h
+predicat.o: ../hdrs/externs.h
+predicat.o: ../hdrs/compile.h
+predicat.o: ../hdrs/dbdefs.h
+predicat.o: ../hdrs/mushdb.h
+predicat.o: ../hdrs/flags.h
+predicat.o: ../hdrs/ptab.h
+predicat.o: ../hdrs/division.h
+predicat.o: ../hdrs/chunk.h
+predicat.o: ../hdrs/bufferq.h
+predicat.o: ../confmagic.h
+predicat.o: ../hdrs/attrib.h
+predicat.o: ../hdrs/boolexp.h
+predicat.o: ../hdrs/lock.h
+predicat.o: ../hdrs/match.h
+predicat.o: ../hdrs/ansi.h
+predicat.o: ../hdrs/parse.h
+predicat.o: ../hdrs/privtab.h
+predicat.o: ../hdrs/mymalloc.h
+privtab.o: ../hdrs/copyrite.h
+privtab.o: ../config.h
+privtab.o: ../hdrs/conf.h
+privtab.o: ../options.h
+privtab.o: ../hdrs/mushtype.h
+privtab.o: ../hdrs/htab.h
+privtab.o: ../hdrs/privtab.h
+privtab.o: ../confmagic.h
+privtab.o: ../hdrs/externs.h
+privtab.o: ../hdrs/compile.h
+privtab.o: ../hdrs/dbdefs.h
+privtab.o: ../hdrs/mushdb.h
+privtab.o: ../hdrs/flags.h
+privtab.o: ../hdrs/ptab.h
+privtab.o: ../hdrs/division.h
+privtab.o: ../hdrs/chunk.h
+privtab.o: ../hdrs/bufferq.h
+ptab.o: ../config.h
+ptab.o: ../hdrs/copyrite.h
+ptab.o: ../hdrs/conf.h
+ptab.o: ../options.h
+ptab.o: ../hdrs/mushtype.h
+ptab.o: ../hdrs/htab.h
+ptab.o: ../hdrs/externs.h
+ptab.o: ../hdrs/compile.h
+ptab.o: ../hdrs/dbdefs.h
+ptab.o: ../hdrs/mushdb.h
+ptab.o: ../hdrs/flags.h
+ptab.o: ../hdrs/ptab.h
+ptab.o: ../hdrs/division.h
+ptab.o: ../hdrs/chunk.h
+ptab.o: ../hdrs/bufferq.h
+ptab.o: ../confmagic.h
+rob.o: ../config.h
+rob.o: ../hdrs/copyrite.h
+rob.o: ../hdrs/conf.h
+rob.o: ../options.h
+rob.o: ../hdrs/mushtype.h
+rob.o: ../hdrs/htab.h
+rob.o: ../hdrs/externs.h
+rob.o: ../hdrs/compile.h
+rob.o: ../hdrs/dbdefs.h
+rob.o: ../hdrs/mushdb.h
+rob.o: ../hdrs/flags.h
+rob.o: ../hdrs/ptab.h
+rob.o: ../hdrs/division.h
+rob.o: ../hdrs/chunk.h
+rob.o: ../hdrs/bufferq.h
+rob.o: ../confmagic.h
+rob.o: ../hdrs/attrib.h
+rob.o: ../hdrs/boolexp.h
+rob.o: ../hdrs/match.h
+rob.o: ../hdrs/parse.h
+rob.o: ../hdrs/log.h
+rob.o: ../hdrs/lock.h
+rob.o: ../hdrs/game.h
+rob.o: ../hdrs/case.h
+rplog.o: ../hdrs/copyrite.h
+rplog.o: ../config.h
+rplog.o: ../hdrs/conf.h
+rplog.o: ../options.h
+rplog.o: ../hdrs/mushtype.h
+rplog.o: ../hdrs/htab.h
+rplog.o: ../hdrs/externs.h
+rplog.o: ../hdrs/compile.h
+rplog.o: ../hdrs/dbdefs.h
+rplog.o: ../hdrs/mushdb.h
+rplog.o: ../hdrs/flags.h
+rplog.o: ../hdrs/ptab.h
+rplog.o: ../hdrs/division.h
+rplog.o: ../hdrs/chunk.h
+rplog.o: ../hdrs/bufferq.h
+rplog.o: ../confmagic.h
+rplog.o: ../hdrs/parse.h
+rplog.o: ../hdrs/command.h
+rplog.o: ../hdrs/boolexp.h
+rplog.o: ../hdrs/switches.h
+rplog.o: ../hdrs/cmds.h
+rplog.o: ../hdrs/attrib.h
+rplog.o: ../hdrs/match.h
+rplog.o: ../hdrs/ansi.h
+rplog.o: ../hdrs/log.h
+set.o: ../hdrs/copyrite.h
+set.o: ../config.h
+set.o: ../hdrs/conf.h
+set.o: ../options.h
+set.o: ../hdrs/mushtype.h
+set.o: ../hdrs/htab.h
+set.o: ../hdrs/externs.h
+set.o: ../hdrs/compile.h
+set.o: ../hdrs/dbdefs.h
+set.o: ../hdrs/mushdb.h
+set.o: ../hdrs/flags.h
+set.o: ../hdrs/ptab.h
+set.o: ../hdrs/division.h
+set.o: ../hdrs/chunk.h
+set.o: ../hdrs/bufferq.h
+set.o: ../confmagic.h
+set.o: ../hdrs/game.h
+set.o: ../hdrs/match.h
+set.o: ../hdrs/attrib.h
+set.o: ../hdrs/boolexp.h
+set.o: ../hdrs/ansi.h
+set.o: ../hdrs/command.h
+set.o: ../hdrs/switches.h
+set.o: ../hdrs/mymalloc.h
+set.o: ../hdrs/lock.h
+set.o: ../hdrs/log.h
+shs.o: ../hdrs/copyrite.h
+shs.o: ../config.h
+sig.o: ../config.h
+sig.o: ../hdrs/conf.h
+sig.o: ../hdrs/copyrite.h
+sig.o: ../options.h
+sig.o: ../hdrs/mushtype.h
+sig.o: ../hdrs/htab.h
+sig.o: ../hdrs/externs.h
+sig.o: ../hdrs/compile.h
+sig.o: ../hdrs/dbdefs.h
+sig.o: ../hdrs/mushdb.h
+sig.o: ../hdrs/flags.h
+sig.o: ../hdrs/ptab.h
+sig.o: ../hdrs/division.h
+sig.o: ../hdrs/chunk.h
+sig.o: ../hdrs/bufferq.h
+sig.o: ../confmagic.h
+speech.o: ../hdrs/copyrite.h
+speech.o: ../config.h
+speech.o: ../hdrs/conf.h
+speech.o: ../options.h
+speech.o: ../hdrs/mushtype.h
+speech.o: ../hdrs/htab.h
+speech.o: ../hdrs/ansi.h
+speech.o: ../hdrs/externs.h
+speech.o: ../hdrs/compile.h
+speech.o: ../hdrs/dbdefs.h
+speech.o: ../hdrs/mushdb.h
+speech.o: ../hdrs/flags.h
+speech.o: ../hdrs/ptab.h
+speech.o: ../hdrs/division.h
+speech.o: ../hdrs/chunk.h
+speech.o: ../hdrs/bufferq.h
+speech.o: ../confmagic.h
+speech.o: ../hdrs/lock.h
+speech.o: ../hdrs/boolexp.h
+speech.o: ../hdrs/log.h
+speech.o: ../hdrs/match.h
+speech.o: ../hdrs/attrib.h
+speech.o: ../hdrs/parse.h
+speech.o: ../hdrs/game.h
+speech.o: ../hdrs/pcre.h
+sql.o: ../hdrs/copyrite.h
+sql.o: ../config.h
+sql.o: ../hdrs/conf.h
+sql.o: ../options.h
+sql.o: ../hdrs/mushtype.h
+sql.o: ../hdrs/htab.h
+sql.o: ../hdrs/externs.h
+sql.o: ../hdrs/compile.h
+sql.o: ../hdrs/dbdefs.h
+sql.o: ../hdrs/mushdb.h
+sql.o: ../hdrs/flags.h
+sql.o: ../hdrs/ptab.h
+sql.o: ../hdrs/division.h
+sql.o: ../hdrs/chunk.h
+sql.o: ../hdrs/bufferq.h
+sql.o: ../confmagic.h
+sql.o: ../hdrs/access.h
+sql.o: ../hdrs/log.h
+sql.o: ../hdrs/parse.h
+sql.o: ../hdrs/boolexp.h
+sql.o: ../hdrs/command.h
+sql.o: ../hdrs/switches.h
+sql.o: ../hdrs/function.h
+strdup.o: ../config.h
+strdup.o: ../hdrs/conf.h
+strdup.o: ../hdrs/copyrite.h
+strdup.o: ../options.h
+strdup.o: ../hdrs/mushtype.h
+strdup.o: ../hdrs/htab.h
+strdup.o: ../hdrs/mymalloc.h
+strdup.o: ../confmagic.h
+strtree.o: ../hdrs/copyrite.h
+strtree.o: ../config.h
+strtree.o: ../hdrs/conf.h
+strtree.o: ../options.h
+strtree.o: ../hdrs/mushtype.h
+strtree.o: ../hdrs/htab.h
+strtree.o: ../hdrs/externs.h
+strtree.o: ../hdrs/compile.h
+strtree.o: ../hdrs/dbdefs.h
+strtree.o: ../hdrs/mushdb.h
+strtree.o: ../hdrs/flags.h
+strtree.o: ../hdrs/ptab.h
+strtree.o: ../hdrs/division.h
+strtree.o: ../hdrs/chunk.h
+strtree.o: ../hdrs/bufferq.h
+strtree.o: ../confmagic.h
+strtree.o: ../hdrs/strtree.h
+strutil.o: ../config.h
+strutil.o: ../hdrs/copyrite.h
+strutil.o: ../hdrs/conf.h
+strutil.o: ../options.h
+strutil.o: ../hdrs/mushtype.h
+strutil.o: ../hdrs/htab.h
+strutil.o: ../hdrs/case.h
+strutil.o: ../hdrs/ansi.h
+strutil.o: ../hdrs/pueblo.h
+strutil.o: ../hdrs/parse.h
+strutil.o: ../confmagic.h
+strutil.o: ../hdrs/externs.h
+strutil.o: ../hdrs/compile.h
+strutil.o: ../hdrs/dbdefs.h
+strutil.o: ../hdrs/mushdb.h
+strutil.o: ../hdrs/flags.h
+strutil.o: ../hdrs/ptab.h
+strutil.o: ../hdrs/division.h
+strutil.o: ../hdrs/chunk.h
+strutil.o: ../hdrs/bufferq.h
+strutil.o: ../hdrs/mymalloc.h
+strutil.o: ../hdrs/log.h
+timer.o: ../hdrs/copyrite.h
+timer.o: ../config.h
+timer.o: ../hdrs/conf.h
+timer.o: ../options.h
+timer.o: ../hdrs/mushtype.h
+timer.o: ../hdrs/htab.h
+timer.o: ../hdrs/externs.h
+timer.o: ../hdrs/compile.h
+timer.o: ../hdrs/dbdefs.h
+timer.o: ../hdrs/mushdb.h
+timer.o: ../hdrs/flags.h
+timer.o: ../hdrs/ptab.h
+timer.o: ../hdrs/division.h
+timer.o: ../hdrs/chunk.h
+timer.o: ../hdrs/bufferq.h
+timer.o: ../confmagic.h
+timer.o: ../hdrs/attrib.h
+timer.o: ../hdrs/boolexp.h
+timer.o: ../hdrs/lock.h
+timer.o: ../hdrs/extmail.h
+timer.o: ../hdrs/match.h
+timer.o: ../hdrs/access.h
+timer.o: ../hdrs/log.h
+timer.o: ../hdrs/game.h
+timer.o: ../hdrs/help.h
+timer.o: ../hdrs/parse.h
+unparse.o: ../hdrs/copyrite.h
+unparse.o: ../config.h
+unparse.o: ../hdrs/conf.h
+unparse.o: ../options.h
+unparse.o: ../hdrs/mushtype.h
+unparse.o: ../hdrs/htab.h
+unparse.o: ../hdrs/externs.h
+unparse.o: ../hdrs/compile.h
+unparse.o: ../hdrs/dbdefs.h
+unparse.o: ../hdrs/mushdb.h
+unparse.o: ../hdrs/flags.h
+unparse.o: ../hdrs/ptab.h
+unparse.o: ../hdrs/division.h
+unparse.o: ../hdrs/chunk.h
+unparse.o: ../hdrs/bufferq.h
+unparse.o: ../confmagic.h
+unparse.o: ../hdrs/lock.h
+unparse.o: ../hdrs/boolexp.h
+unparse.o: ../hdrs/attrib.h
+unparse.o: ../hdrs/ansi.h
+unparse.o: ../hdrs/pueblo.h
+unparse.o: ../hdrs/parse.h
+utils.o: ../hdrs/copyrite.h
+utils.o: ../config.h
+utils.o: ../hdrs/conf.h
+utils.o: ../options.h
+utils.o: ../hdrs/mushtype.h
+utils.o: ../hdrs/htab.h
+utils.o: ../hdrs/match.h
+utils.o: ../hdrs/externs.h
+utils.o: ../hdrs/compile.h
+utils.o: ../hdrs/dbdefs.h
+utils.o: ../hdrs/mushdb.h
+utils.o: ../hdrs/flags.h
+utils.o: ../hdrs/ptab.h
+utils.o: ../hdrs/division.h
+utils.o: ../hdrs/chunk.h
+utils.o: ../hdrs/bufferq.h
+utils.o: ../confmagic.h
+utils.o: ../hdrs/mymalloc.h
+utils.o: ../hdrs/log.h
+utils.o: ../hdrs/attrib.h
+utils.o: ../hdrs/boolexp.h
+utils.o: ../hdrs/lock.h
+version.o: ../config.h
+version.o: ../hdrs/copyrite.h
+version.o: ../hdrs/conf.h
+version.o: ../options.h
+version.o: ../hdrs/mushtype.h
+version.o: ../hdrs/htab.h
+version.o: ../hdrs/externs.h
+version.o: ../hdrs/compile.h
+version.o: ../hdrs/dbdefs.h
+version.o: ../hdrs/mushdb.h
+version.o: ../hdrs/flags.h
+version.o: ../hdrs/ptab.h
+version.o: ../hdrs/division.h
+version.o: ../hdrs/chunk.h
+version.o: ../hdrs/bufferq.h
+version.o: ../confmagic.h
+version.o: ../hdrs/version.h
+version.o: ../hdrs/patches.h
+version.o: ../hdrs/buildinf.h
+warnings.o: ../config.h
+warnings.o: ../hdrs/copyrite.h
+warnings.o: ../hdrs/conf.h
+warnings.o: ../options.h
+warnings.o: ../hdrs/mushtype.h
+warnings.o: ../hdrs/htab.h
+warnings.o: ../hdrs/externs.h
+warnings.o: ../hdrs/compile.h
+warnings.o: ../hdrs/dbdefs.h
+warnings.o: ../hdrs/mushdb.h
+warnings.o: ../hdrs/flags.h
+warnings.o: ../hdrs/ptab.h
+warnings.o: ../hdrs/division.h
+warnings.o: ../hdrs/chunk.h
+warnings.o: ../hdrs/bufferq.h
+warnings.o: ../confmagic.h
+warnings.o: ../hdrs/lock.h
+warnings.o: ../hdrs/boolexp.h
+warnings.o: ../hdrs/match.h
+warnings.o: ../hdrs/attrib.h
+wild.o: ../config.h
+wild.o: ../hdrs/copyrite.h
+wild.o: ../hdrs/conf.h
+wild.o: ../options.h
+wild.o: ../hdrs/mushtype.h
+wild.o: ../hdrs/htab.h
+wild.o: ../hdrs/case.h
+wild.o: ../hdrs/externs.h
+wild.o: ../hdrs/compile.h
+wild.o: ../hdrs/dbdefs.h
+wild.o: ../hdrs/mushdb.h
+wild.o: ../hdrs/flags.h
+wild.o: ../hdrs/ptab.h
+wild.o: ../hdrs/division.h
+wild.o: ../hdrs/chunk.h
+wild.o: ../hdrs/bufferq.h
+wild.o: ../confmagic.h
+wild.o: ../hdrs/mymalloc.h
+wild.o: ../hdrs/parse.h
+wild.o: ../hdrs/pcre.h
+wiz.o: ../hdrs/copyrite.h
+wiz.o: ../config.h
+wiz.o: ../hdrs/conf.h
+wiz.o: ../options.h
+wiz.o: ../hdrs/mushtype.h
+wiz.o: ../hdrs/htab.h
+wiz.o: ../hdrs/externs.h
+wiz.o: ../hdrs/compile.h
+wiz.o: ../hdrs/dbdefs.h
+wiz.o: ../hdrs/mushdb.h
+wiz.o: ../hdrs/flags.h
+wiz.o: ../hdrs/ptab.h
+wiz.o: ../hdrs/division.h
+wiz.o: ../hdrs/chunk.h
+wiz.o: ../hdrs/bufferq.h
+wiz.o: ../confmagic.h
+wiz.o: ../hdrs/attrib.h
+wiz.o: ../hdrs/boolexp.h
+wiz.o: ../hdrs/match.h
+wiz.o: ../hdrs/access.h
+wiz.o: ../hdrs/parse.h
+wiz.o: ../hdrs/mymalloc.h
+wiz.o: ../hdrs/lock.h
+wiz.o: ../hdrs/log.h
+wiz.o: ../hdrs/game.h
+wiz.o: ../hdrs/command.h
+wiz.o: ../hdrs/switches.h
+wiz.o: ../hdrs/extmail.h
+../hdrs/atr_tab.o: ../hdrs/attrib.h
+../hdrs/atr_tab.o: ../hdrs/mushtype.h
+../hdrs/atr_tab.o: ../hdrs/copyrite.h
+../hdrs/atr_tab.o: ../options.h
+../hdrs/atr_tab.o: ../hdrs/boolexp.h
+../hdrs/atr_tab.o: ../hdrs/chunk.h
+../hdrs/attrib.o: ../hdrs/mushtype.h
+../hdrs/attrib.o: ../hdrs/copyrite.h
+../hdrs/attrib.o: ../options.h
+../hdrs/attrib.o: ../hdrs/boolexp.h
+../hdrs/attrib.o: ../hdrs/chunk.h
+../hdrs/boolexp.o: ../hdrs/copyrite.h
+../hdrs/boolexp.o: ../hdrs/chunk.h
+../hdrs/boolexp.o: ../hdrs/mushtype.h
+../hdrs/boolexp.o: ../options.h
+../hdrs/case.o: ../config.h
+../hdrs/case.o: ../config.h
+../hdrs/chunk.o: ../hdrs/mushtype.h
+../hdrs/chunk.o: ../hdrs/copyrite.h
+../hdrs/chunk.o: ../options.h
+../hdrs/command.o: ../hdrs/boolexp.h
+../hdrs/command.o: ../hdrs/copyrite.h
+../hdrs/command.o: ../hdrs/chunk.h
+../hdrs/command.o: ../hdrs/mushtype.h
+../hdrs/command.o: ../options.h
+../hdrs/command.o: ../hdrs/division.h
+../hdrs/command.o: ../hdrs/ptab.h
+../hdrs/command.o: ../hdrs/switches.h
+../hdrs/conf.o: ../hdrs/copyrite.h
+../hdrs/conf.o: ../options.h
+../hdrs/conf.o: ../hdrs/mushtype.h
+../hdrs/conf.o: ../hdrs/htab.h
+../hdrs/dbdefs.o: ../hdrs/mushdb.h
+../hdrs/dbdefs.o: ../config.h
+../hdrs/dbdefs.o: ../hdrs/copyrite.h
+../hdrs/dbdefs.o: ../hdrs/flags.h
+../hdrs/dbdefs.o: ../hdrs/mushtype.h
+../hdrs/dbdefs.o: ../options.h
+../hdrs/dbdefs.o: ../hdrs/ptab.h
+../hdrs/dbdefs.o: ../hdrs/division.h
+../hdrs/dbdefs.o: ../hdrs/htab.h
+../hdrs/dbdefs.o: ../hdrs/chunk.h
+../hdrs/dbdefs.o: ../hdrs/bufferq.h
+../hdrs/externs.o: ../config.h
+../hdrs/externs.o: ../hdrs/copyrite.h
+../hdrs/externs.o: ../hdrs/compile.h
+../hdrs/externs.o: ../hdrs/mushtype.h
+../hdrs/externs.o: ../options.h
+../hdrs/externs.o: ../hdrs/dbdefs.h
+../hdrs/externs.o: ../hdrs/mushdb.h
+../hdrs/externs.o: ../hdrs/flags.h
+../hdrs/externs.o: ../hdrs/ptab.h
+../hdrs/externs.o: ../hdrs/division.h
+../hdrs/externs.o: ../hdrs/htab.h
+../hdrs/externs.o: ../hdrs/chunk.h
+../hdrs/externs.o: ../hdrs/bufferq.h
+../hdrs/externs.o: ../confmagic.h
+../hdrs/flags.o: ../hdrs/mushtype.h
+../hdrs/flags.o: ../hdrs/copyrite.h
+../hdrs/flags.o: ../options.h
+../hdrs/flags.o: ../hdrs/ptab.h
+../hdrs/flags.o: ../hdrs/division.h
+../hdrs/function.o: ../hdrs/copyrite.h
+../hdrs/function.o: ../hdrs/boolexp.h
+../hdrs/function.o: ../hdrs/chunk.h
+../hdrs/function.o: ../hdrs/mushtype.h
+../hdrs/function.o: ../options.h
+../hdrs/ident.o: ../config.h
+../hdrs/ident.o: ../hdrs/mysocket.h
+../hdrs/ident.o: ../hdrs/copyrite.h
+../hdrs/ident.o: ../confmagic.h
+../hdrs/lock.o: ../hdrs/copyrite.h
+../hdrs/lock.o: ../hdrs/mushtype.h
+../hdrs/lock.o: ../options.h
+../hdrs/lock.o: ../hdrs/conf.h
+../hdrs/lock.o: ../hdrs/htab.h
+../hdrs/lock.o: ../hdrs/boolexp.h
+../hdrs/lock.o: ../hdrs/chunk.h
+../hdrs/match.o: ../hdrs/copyrite.h
+../hdrs/mushdb.o: ../config.h
+../hdrs/mushdb.o: ../hdrs/copyrite.h
+../hdrs/mushdb.o: ../hdrs/flags.h
+../hdrs/mushdb.o: ../hdrs/mushtype.h
+../hdrs/mushdb.o: ../options.h
+../hdrs/mushdb.o: ../hdrs/ptab.h
+../hdrs/mushdb.o: ../hdrs/division.h
+../hdrs/mushtype.o: ../hdrs/copyrite.h
+../hdrs/mushtype.o: ../options.h
+../hdrs/mymalloc.o: ../options.h
+../hdrs/mysocket.o: ../hdrs/copyrite.h
+../hdrs/mysocket.o: ../config.h
+../hdrs/mysocket.o: ../confmagic.h
+../hdrs/myssl.o: ../hdrs/copyrite.h
+../hdrs/parse.o: ../hdrs/copyrite.h
+../hdrs/parse.o: ../config.h
+../hdrs/parse.o: ../confmagic.h
+../hdrs/privtab.o: ../hdrs/copyrite.h
+../hdrs/privtab.o: ../config.h
+../hdrs/privtab.o: ../confmagic.h
+../hdrs/division.o: ../hdrs/ptab.h
diff --git a/src/SWITCHES b/src/SWITCHES
new file mode 100644 (file)
index 0000000..a8ee94e
--- /dev/null
@@ -0,0 +1,163 @@
+ACCESS
+ADDALIAS
+ADD
+AFTER
+ALIAS
+ALL
+ANY
+ATTRIBS
+AUTO
+BAN
+BEFORE
+BLIND
+BRIEF
+BUFFER
+CHECK
+CHOWN
+CHUNKS
+CLEAR
+CMD
+COMBAT
+COMMANDS
+CONN
+CONNECT
+CONNECTED
+CONTENTS
+COSTS
+COUNT
+CREATE
+DATABASE
+DB
+DEBUG
+DECOMPILE
+DEFAULTS
+DELALIAS
+DELETE
+DELIMIT
+DESCRIBE
+DESTROY
+DISABLE
+DOWN
+DSTATS
+EMIT
+ENABLE
+EQSPLIT
+ERR
+EXITS
+FILE
+FIRST
+FLAGS
+FOLDERS
+FORWARD
+FREESPACE
+FSTATS
+FULL
+FUNCTIONS
+FWD
+GAG
+GLOBALS
+HEADER
+HERE
+HIDE
+INFO
+INIT
+IGNORE
+ILIST
+INSIDE
+INVENTORY
+IPRINT
+JOIN
+LEAVE
+LETTER
+LIST
+LOGOUT
+LOWERCASE
+LOCK
+LOCKS
+LSARGS
+MAX
+ME
+MEMBERS
+MOD
+MORTAL
+MOTD
+MUTE
+NAME
+NO
+NOEVAL
+NOFLAGCOPY
+NOISY
+NOSIG
+NOSPACE
+NOTIFY
+NUKE
+OBJECT
+OFF
+ON
+OUTSIDE
+OVERRIDE
+PAGING
+PANIC
+PARANOID
+PLAYERS
+PORT
+POWERS
+PRESERVE
+PRINT
+PRIVS
+PURGE
+QUICK
+QUIET
+QUIT
+RAW
+READ
+REBOOT
+RECALL
+REMOVE
+RENAME
+REGEXP
+REGIONS
+REGISTER
+RESET
+RESTORE
+RESTRICT
+RETROACTIVE
+ROOM
+ROOMS
+RSARGS
+SEE
+SEEFLAG
+SELF
+SEND
+SET
+SILENT
+SKIPDEFAULTS
+SPEAK
+SPOOF
+STATS
+SUMMARY
+TABLES
+TAG
+TELEPORT
+TF
+THINGS
+TITLE
+TRACE
+TYPE
+UNCLEAR
+UNHIDE
+UNGAG
+UNFOLDER
+UNMUTE
+UNTAG
+UNTIL
+URGENT
+USEFLAG
+WHAT
+WHO
+WIPE
+WIZ
+WIZARD
+WRITE
+YES
+ZONE
diff --git a/src/access.c b/src/access.c
new file mode 100644 (file)
index 0000000..6d1bee4
--- /dev/null
@@ -0,0 +1,715 @@
+/**
+ * \file
+ *
+ * \brief Access control lists for PennMUSH.
+ * \verbatim
+ *
+ * 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
+ * DENY_SILENT          Don't log when someone's denied access from here
+ * REGEXP               Treat the hostname pattern as a regular expression
+ * *GOD                  God can connect from this pattern.
+ * *DIRECTOR             Directors can connect from this pattern.
+ * *ADMIN                Admins can connect from this pattern.
+ *
+ * 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.
+ * 
+ * \endverbatim
+ */
+
+#include "config.h"
+#include "copyrite.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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 "conf.h"
+#include "externs.h"
+#include "access.h"
+#include "mymalloc.h"
+#include "match.h"
+#include "parse.h"
+#include "log.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "confmagic.h"
+
+
+/** An access flag. */
+typedef struct a_acsflag acsflag;
+/** An access flag.
+ * This structure is used to build a table of access control flags.
+ */
+struct a_acsflag {
+  const char *name;            /**< Name of the access flag */
+  int toggle;                  /**< Is this a negatable flag? */
+  int flag;                    /**< Bitmask of the 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},
+  {"deny_silent", 0, ACS_DENY_SILENT},
+  {"regexp", 0, ACS_REGEXP},
+  {"god", 1, ACS_GOD},
+  {"director", 1, ACS_DIRECTOR},
+  {"admin", 1, ACS_ADMIN},
+  {NULL, 0, 0}
+};
+
+static struct access *access_top;
+static int add_access_node
+  (const char *host, const dbref who, const int can, const int cant,
+   const char *comment);
+static void free_access_list(void);
+
+static int
+add_access_node(const char *host, const dbref who, 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->who = who;
+  tmp->can = can;
+  tmp->cant = cant;
+  strcpy(tmp->host, host);
+  if (comment)
+    strcpy(tmp->comment, comment);
+  else
+    tmp->comment[0] = '\0';
+  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;
+}
+
+
+/** Read the access.cnf file.
+ * Initialize the access rules linked list and read in the access.cnf file.
+ * Return 1 if successful, 0 if not
+ */
+int
+read_access_file(void)
+{
+  FILE *fp;
+  char buf[BUFFER_LEN];
+  char *p;
+  int can, cant;
+  int retval;
+  dbref who;
+  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 */
+  release_fd();
+  fp = fopen(ACCESS_FILE, FOPEN_READ);
+  if (!fp) {
+    do_log(LT_ERR, GOD, GOD, T("No %s file found."), ACCESS_FILE);
+    retval = 0;
+  } else {
+    do_rawlog(LT_ERR, "Reading %s", ACCESS_FILE);
+    while (fgets(buf, BUFFER_LEN, fp)) {
+      /* Strip end of line if it's \r\n or \n */
+      if ((p = strchr(buf, '\r')))
+       *p = '\0';
+      else if ((p = strchr(buf, '\n')))
+       *p = '\0';
+      /* Find beginning of line; ignore blank lines */
+      p = buf;
+      if (*p && isspace((unsigned char) *p))
+       p++;
+      if (*p && *p != '#') {
+       can = cant = 0;
+       comment = NULL;
+       /* Is this the @sitelock entry? */
+       if (!strncasecmp(p, "@sitelock", 9)) {
+         if (!add_access_node("@sitelock", AMBIGUOUS, ACS_SITELOCK, 0, ""))
+           do_log(LT_ERR, GOD, GOD, T("Failed to add sitelock node!"));
+       } else {
+         if ((comment = strchr(p, '#'))) {
+           *comment++ = '\0';
+           while (*comment && isspace((unsigned char) *comment))
+             comment++;
+         }
+         /* Move past the host name */
+         while (*p && !isspace((unsigned char) *p))
+           p++;
+         if (*p)
+           *p++ = '\0';
+         if (!parse_access_options(p, &who, &can, &cant, NOTHING))
+           /* Nothing listed, so assume we can't do anything! */
+           cant = ACS_DEFAULT;
+         if (!add_access_node(buf, who, can, cant, comment))
+           do_log(LT_ERR, GOD, GOD, T("Failed to add access node!"));
+       }
+      }
+    }
+    retval = 1;
+    fclose(fp);
+  }
+  reserve_fd();
+  return retval;
+}
+
+/** Write the access.cnf file.
+ * Writes out the access.cnf file from the linked list
+ */
+void
+write_access_file(void)
+{
+  FILE *fp;
+  char tmpf[BUFFER_LEN];
+  struct access *ap;
+  acsflag *c;
+
+  sprintf(tmpf, "%s.tmp", ACCESS_FILE);
+  /* Be sure we have a file descriptor */
+  release_fd();
+  fp = fopen(tmpf, FOPEN_WRITE);
+  if (!fp) {
+    do_log(LT_ERR, GOD, GOD, T("Unable to open %s."), tmpf);
+  } else {
+    for (ap = access_top; ap; ap = ap->next) {
+      if (strcmp(ap->host, "@sitelock") == 0) {
+       fprintf(fp, "@sitelock\n");
+       continue;
+      }
+      fprintf(fp, "%s %d ", ap->host, ap->who);
+      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);
+  }
+  reserve_fd();
+  return;
+}
+
+#ifdef FORCE_IPV4
+static char *
+ip4_to_ip6(const char *addr)
+{
+  static char tbuf1[BUFFER_LEN];
+  char *bp;
+  bp = tbuf1;
+  safe_format(tbuf1, &bp, "::ffff:%s", addr);
+  *bp = '\0';
+  return tbuf1;
+}
+#endif
+
+/** Decide if a host can access someway.
+ * \param hname a host or user+host pattern.
+ * \param flag the access type we're testing.
+ * \param who the player attempting access.
+ * \retval 1 access permitted.
+ * \retval 0 access denied.
+ * \verbatim
+ * 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)
+ * \endverbatim
+ */
+int
+site_can_access(const char *hname, int flag, dbref who)
+{
+  struct access *ap;
+  acsflag *c;
+  char *p;
+
+  if (!hname || !*hname)
+    return 0;
+
+  if ((p = strchr(hname, '@')))
+    p++;
+
+  for (ap = access_top; ap; ap = ap->next) {
+    if (!(ap->can & ACS_SITELOCK)
+       && ((ap->can & ACS_REGEXP)
+           ? (regexp_match_case(ap->host, hname, 0)
+              || (p && regexp_match_case(ap->host, p, 0))
+#ifdef FORCE_IPV4
+              || regexp_match_case(ip4_to_ip6(ap->host), hname, 0)
+              || (p && regexp_match_case(ip4_to_ip6(ap->host), p, 0))
+#endif
+           )
+           : (quick_wild(ap->host, hname)
+              || (p && quick_wild(ap->host, p))
+#ifdef FORCE_IPV4
+              || quick_wild(ip4_to_ip6(ap->host), hname)
+              || (p && quick_wild(ip4_to_ip6(ap->host), p))
+#endif
+           ))
+       && (ap->who == AMBIGUOUS || ap->who == who)) {
+      /* Got one */
+      if (flag & ACS_CONNECT) {
+       if ((ap->cant & ACS_GOD) && God(who))   /* God can't connect from here */
+         return 0;
+       else if ((ap->cant & ACS_DIRECTOR) && Director(who))
+         /* Directors can't connect from here */
+         return 0;
+       else if ((ap->cant & ACS_ADMIN) && Admin(who))
+         /* Admins can't connect from here */
+         return 0;
+      }
+      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;
+    }
+  }
+
+  /* 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;
+}
+
+
+/** Return the first access rule that matches a host.
+ * \param hname a host or user+host pattern.
+ * \param who the player attempting access.
+ * \param rulenum pointer to rule position.
+ * \return pointer to first matching access rule or NULL.
+ */
+struct access *
+site_check_access(const char *hname, dbref who, int *rulenum)
+{
+  struct access *ap;
+  char *p;
+
+  *rulenum = 0;
+  if (!hname || !*hname)
+    return 0;
+
+  if ((p = strchr(hname, '@')))
+    p++;
+
+  for (ap = access_top; ap; ap = ap->next) {
+    (*rulenum)++;
+    if (!(ap->can & ACS_SITELOCK)
+       && ((ap->can & ACS_REGEXP)
+           ? (regexp_match_case(ap->host, hname, 0)
+              || (p && regexp_match_case(ap->host, p, 0))
+#ifdef FORCE_IPV4
+              || regexp_match_case(ip4_to_ip6(ap->host), hname, 0)
+              || (p && regexp_match_case(ip4_to_ip6(ap->host), p, 0))
+#endif
+           )
+           : (quick_wild(ap->host, hname)
+              || (p && quick_wild(ap->host, p))
+#ifdef FORCE_IPV4
+              || quick_wild(ip4_to_ip6(ap->host), hname)
+              || (p && quick_wild(ip4_to_ip6(ap->host), p))
+#endif
+           ))
+       && (ap->who == AMBIGUOUS || ap->who == who)) {
+      /* Got one */
+      return ap;
+    }
+  }
+  return NULL;
+}
+
+/** Display an access rule.
+ * \param ap pointer to access rule.
+ * \param rulenum access rule's number in the list.
+ * \param who unused.
+ * \param buff buffer to store output.
+ * \param bp pointer into buff.
+ * This function provides an appealing display of an access rule
+ * in the list.
+ */
+int
+format_access(struct access *ap, int rulenum,
+             dbref who __attribute__ ((__unused__)), char *buff, char **bp)
+{
+  if (ap) {
+    safe_format(buff, bp, T("Matched line %d: %s %s"), rulenum, ap->host,
+               (ap->can & ACS_REGEXP) ? "(regexp)" : "");
+    safe_chr('\n', buff, bp);
+    safe_format(buff, bp, T("Comment: %s"), ap->comment);
+    safe_chr('\n', buff, bp);
+    safe_str(T("Connections allowed by: "), buff, bp);
+    if (ap->cant & ACS_CONNECT)
+      safe_str(T("No one"), buff, bp);
+    else if (ap->cant & ACS_ADMIN)
+      safe_str(T("All but admin"), buff, bp);
+    else if (ap->cant & ACS_DIRECTOR)
+      safe_str(T("All but directors"), buff, bp);
+    else if (ap->cant & ACS_GOD)
+      safe_str(T("All but God"), buff, bp);
+    else
+      safe_str(T("All"), buff, bp);
+    safe_chr('\n', buff, bp);
+    if (ap->cant & ACS_GUEST)
+      safe_str(T("Guest connections are NOT allowed"), buff, bp);
+    else
+      safe_str(T("Guest connections are allowed"), buff, bp);
+    safe_chr('\n', buff, bp);
+    if (ap->cant & ACS_CREATE)
+      safe_str(T("Creation is NOT allowed"), buff, bp);
+    else
+      safe_str(T("Creation is allowed"), buff, bp);
+    safe_chr('\n', buff, bp);
+    if (ap->can & ACS_REGISTER)
+      safe_str(T("Email registration is allowed"), buff, bp);
+    if (ap->can & ACS_SUSPECT)
+      safe_str(T("Players connecting are set SUSPECT"), buff, bp);
+    if (ap->can & ACS_DENY_SILENT)
+      safe_str(T("Denied connections are not logged"), buff, bp);
+  } else {
+    safe_str(T("No matching access rule"), buff, bp);
+  }
+  return 0;
+}
+
+
+/** Add an access rule to the linked list.
+ * \param player enactor.
+ * \param host host pattern to add.
+ * \param who player to which rule applies, or AMBIGUOUS.
+ * \param can flags of allowed actions.
+ * \param cant flags of disallowed actions.
+ * \retval 1 success.
+ * \retval 0 failure.
+ * \verbatim
+ * This function adds an access rule 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
+ * \endverbatim
+ */
+int
+add_access_sitelock(dbref player, const char *host, dbref who, int can,
+                   int cant)
+{
+  struct access *end;
+  struct access *tmp;
+
+  tmp = (struct access *) mush_malloc(sizeof(struct access), "struct_access");
+  if (!tmp)
+    return 0;
+
+  tmp->who = who;
+  tmp->can = can;
+  tmp->cant = cant;
+  strcpy(tmp->host, host);
+  sprintf(tmp->comment, "By %s(#%d) on %s", Name(player), player,
+         show_time(mudtime, 0));
+  tmp->next = NULL;
+
+  if (!access_top) {
+    /* Add to the beginning, but first add a sitelock marker */
+    if (!add_access_node("@sitelock", AMBIGUOUS, 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", AMBIGUOUS, 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;
+}
+
+/** Remove an access rule from the linked list.
+ * \param pattern access rule host pattern to match.
+ * \return number of rule removed.
+ * \verbatim
+ * This function removes an access rule from the list.
+ * Only rules that appear after the "@sitelock" rule can be
+ * removed with this function.
+ * \endverbatim
+ */
+int
+remove_access_sitelock(const char *pattern)
+{
+  struct access *ap, *next, *prev = NULL;
+  int n = 0;
+
+  /* We only want to be able to delete entries added with @sitelock */
+  for (ap = access_top; ap; ap = ap->next)
+    if (strcmp(ap->host, "@sitelock") == 0) {
+      prev = ap;
+      ap = ap->next;
+      break;
+    }
+
+  while (ap) {
+    next = ap->next;
+    if (strcasecmp(pattern, ap->host) == 0) {
+      n++;
+      mush_free(ap, "struct_access");
+      if (prev)
+       prev->next = next;
+      else
+       access_top = next;
+    } else {
+      prev = ap;
+    }
+    ap = next;
+  }
+
+  return n;
+}
+
+/* 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;
+}
+
+
+/** Display the access list.
+ * \param player enactor.
+ * Sends the complete access list to the player.
+ */
+void
+do_list_access(dbref player)
+{
+  struct access *ap;
+  acsflag *c;
+  char flaglist[BUFFER_LEN];
+  int rulenum = 0;
+  char *bp;
+
+  for (ap = access_top; ap; ap = ap->next) {
+    rulenum++;
+    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_format(player,
+                   "%3d SITE: %-20s  DBREF: %-6s FLAGS:%s", rulenum,
+                   ap->host, unparse_dbref(ap->who), flaglist);
+      notify_format(player, " COMMENT: %s", ap->comment);
+    } else {
+      notify(player,
+            T
+            ("---- @sitelock will add sites immediately below this line ----"));
+    }
+
+  }
+}
+
+/** Parse access options into fields.
+ * \param opts access options to read from.
+ * \param who pointer to player to whom rule applies, or AMBIGUOUS.
+ * \param can pointer to flags of allowed actions.
+ * \param cant pointer to flags of disallowed actions.
+ * \param player enactor.
+ * \return number of options successfully parsed.
+ * 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(const char *opts, dbref *who, int *can, int *cant,
+                    dbref player)
+{
+  char myopts[BUFFER_LEN];
+  char *p;
+  char *w;
+  acsflag *c;
+  int found, totalfound, first;
+
+  if (!opts || !*opts)
+    return 0;
+  strcpy(myopts, opts);
+  totalfound = 0;
+  first = 1;
+  if (who)
+    *who = AMBIGUOUS;
+  p = trim_space_sep(myopts, ' ');
+  while ((w = split_token(&p, ' '))) {
+    found = 0;
+
+    if (first && who) {                /* Check for a character */
+      first = 0;
+      if (is_integer(w)) {     /* We have a dbref */
+       *who = parse_integer(w);
+       if (*who != AMBIGUOUS && !GoodObject(*who))
+         *who = AMBIGUOUS;
+       continue;
+      }
+    }
+
+    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_format(player, T("Unknown access option: %s"), w);
+      else
+       do_log(LT_ERR, GOD, GOD, T("Unknown access flag: %s"), w);
+    } else {
+      totalfound += found;
+    }
+  }
+  return totalfound;
+}
diff --git a/src/announce.c b/src/announce.c
new file mode 100644 (file)
index 0000000..97dc3bf
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ *    announce - sits listening on a port, and whenever anyone connects
+ *                 announces a message and disconnects them
+ *
+ *      Usage:  announce [port] < message_file
+ *
+ *      Author: Lawrence Brown <lpb@cs.adfa.oz.au>      Aug 90
+ *
+ *      Bits of code are adapted from the Berkeley telnetd sources
+ */
+
+#define PORT    4201
+
+#include <sys/param.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdio.h>
+#include <ctype.h>
+
+extern char **environ;
+extern int errno;
+char *Name;                    /* name of this program for error messages */
+char msg[2048];
+
+int
+main(argc, argv)
+    int argc;
+    char **argv;
+{
+  int s, ns, foo;
+  static struct sockaddr_in sin = { AF_INET };
+  char *host, *inet_ntoa( /* ??? */ );
+  char tmp[80];
+  long ct;
+  int opt;
+
+  Name = argv[0];              /* save name of program for error messages  */
+  sin.sin_port = htons((u_short) PORT);        /* Assume PORT */
+  argc--, argv++;
+  if (argc > 0) {              /*   unless specified on command-line       */
+    sin.sin_port = atoi(*argv);
+    sin.sin_port = htons((u_short) sin.sin_port);
+  }
+  strcpy(msg, "");
+  strcpy(tmp, "");
+  while (1) {
+    if ((gets(tmp)) == NULL)
+      break;
+    strcat(tmp, "\r\n");
+    strcat(msg, tmp);
+  }
+  msg[2048] = '\0';
+  signal(SIGHUP, SIG_IGN);     /* get socket, bind port to it      */
+  s = socket(AF_INET, SOCK_STREAM, 0);
+  if (s < 0) {
+    perror("announce: socket");;
+    exit(1);
+  }
+  opt = 1;
+  if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0) {
+    perror("setsockopt");
+    exit(1);
+  }
+  if (bind(s, &sin, sizeof sin) < 0) {
+    perror("bind");
+    exit(1);
+  }
+  if ((foo = fork()) != 0) {
+    fprintf(stderr, "announce: pid %d running on port %d\n", foo,
+           ntohs((u_short) sin.sin_port));
+    exit(0);
+  } else {
+    setpriority(PRIO_PROCESS, getpid(), 10);
+  }
+  if (listen(s, 1) < 0) {      /* start listening on port */
+    perror("announce: listen");
+    exit(1);
+  }
+  foo = sizeof sin;
+  for (;;) {                   /* loop forever, accepting requests & printing msg */
+    ns = accept(s, &sin, &foo);
+    if (ns < 0) {
+      perror("announce: accept");
+      exit(1);
+    }
+    host = inet_ntoa(sin.sin_addr.s_addr);
+    ct = time(0L);
+    fprintf(stderr, "CONNECTION made from %s at %s", host, ctime(&ct));
+    write(ns, msg, strlen(msg));
+    sleep(5);
+    close(ns);
+  }
+}                              /* main */
diff --git a/src/atr_tab.c b/src/atr_tab.c
new file mode 100644 (file)
index 0000000..d3316d3
--- /dev/null
@@ -0,0 +1,535 @@
+/**
+ * \file atr_tab.c
+ *
+ * \brief The table of standard attributes and code to manipulate it.
+ *
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "conf.h"
+#include "externs.h"
+#include "attrib.h"
+#include "atr_tab.h"
+#include "command.h"
+#include "ptab.h"
+#include "privtab.h"
+#include "mymalloc.h"
+#include "dbdefs.h"
+#include "log.h"
+#include "parse.h"
+#include "confmagic.h"
+
+/* CatchAll */
+extern ATTR *catchall;
+
+/** An alias for an attribute.
+ */
+typedef struct atr_alias {
+  const char *alias;           /**< The alias. */
+  const char *realname;                /**< The attribute's canonical name. */
+} ATRALIAS;
+
+
+/** Prefix table for standard attribute names */
+PTAB ptab_attrib;
+
+/** Attribute flags */
+PRIV attr_privs[] = {
+  {"no_command", '$', AF_NOPROG, AF_NOPROG},
+  {"no_inherit", 'i', AF_PRIVATE, AF_PRIVATE},
+  {"pow_inherit", 't', AF_POWINHERIT, AF_POWINHERIT},
+  {"private", 'i', AF_PRIVATE, AF_PRIVATE},
+  {"no_clone", 'c', AF_NOCOPY, AF_NOCOPY},
+  {"privilege", 'w', AF_PRIVILEGE, AF_PRIVILEGE},
+  {"visual", 'v', AF_VISUAL, AF_VISUAL},
+  {"mortal_dark", 'm', AF_MDARK, AF_MDARK},
+  {"hidden", 'm', AF_MDARK, AF_MDARK},
+  {"regexp", 'R', AF_REGEXP, AF_REGEXP},
+  {"case", 'C', AF_CASE, AF_CASE},
+  {"locked", '+', AF_LOCKED, AF_LOCKED},
+  {"safe", 'S', AF_SAFE, AF_SAFE},
+  {"internal", '\0', AF_INTERNAL, AF_INTERNAL},
+  {"prefixmatch", '\0', AF_PREFIXMATCH, AF_PREFIXMATCH},
+  {"veiled", 'V', AF_VEILED, AF_VEILED},
+  {"debug", 'b', AF_DEBUG, AF_DEBUG},
+  {"public", 'p', AF_PUBLIC, AF_PUBLIC},
+  {"nearby", 'n', AF_NEARBY, AF_NEARBY},
+  {"amhear", 'M', AF_MHEAR, AF_MHEAR},
+  {"aahear", 'A', AF_AHEAR, AF_AHEAR},
+  {NULL, '\0', 0, 0}
+};
+
+
+/*----------------------------------------------------------------------
+ * Prefix-table functions of various sorts
+ */
+
+static ATTR *aname_find_exact(const char *name);
+void init_aname_table(void);
+
+/** Attribute table lookup by name or alias.
+ * given an attribute name, look it up in the complete attribute table
+ * (real names plus aliases), and return the appropriate real attribute.
+ */
+ATTR *
+aname_hash_lookup(const char *name)
+{
+  ATTR *ap;
+  /* Exact matches always work */
+  if ((ap = (ATTR *) ptab_find_exact(&ptab_attrib, name)))
+    return ap;
+  /* Prefix matches work if the attribute is AF_PREFIXMATCH */
+  if ((ap = (ATTR *) ptab_find(&ptab_attrib, name)) && AF_Prefixmatch(ap))
+    return ap;
+  return NULL;
+}
+
+/** Build the basic attribute table.
+ */
+void
+init_aname_table(void)
+{
+  ATTR *ap;
+
+  ptab_init(&ptab_attrib);
+  ptab_start_inserts(&ptab_attrib);
+  for (ap = attr; ap->name; ap++)
+    ptab_insert(&ptab_attrib, ap->name, ap);
+  ptab_end_inserts(&ptab_attrib);
+}
+
+/** Associate a new alias with an existing attribute.
+ */
+int
+alias_attribute(const char *atr, const char *alias)
+{
+  ATTR *ap;
+
+  /* Make sure the alias doesn't exist already */
+  if (aname_find_exact(alias))
+    return 0;
+
+  /* Look up the original */
+  ap = aname_find_exact(atr);
+  if (!ap)
+    return 0;
+
+  ptab_start_inserts(&ptab_attrib);
+  ptab_insert(&ptab_attrib, strupper(alias), ap);
+  ptab_end_inserts(&ptab_attrib);
+  return 1;
+}
+
+static ATTR *
+aname_find_exact(const char *name)
+{
+  char atrname[BUFFER_LEN];
+  strcpy(atrname, name);
+  upcasestr(atrname);
+  return (ATTR *) ptab_find_exact(&ptab_attrib, atrname);
+}
+
+/* Sets global locks on attributes */
+
+void do_attribute_lock(dbref player, char *name, char *lock, switch_mask swi) {
+  ATTR *ap, *ap2;
+  int i, insert = 0;
+  boolexp key;
+
+  /* Parse name and lock  */
+  if ((!name || !*name) && !SW_ISSET(swi, SWITCH_DEFAULTS)) {
+    notify(player, T("Which attribute do you mean?"));
+    return;
+  }
+
+/* Parse Lock Here */
+  key = parse_boolexp(player, lock, "ATTR");
+
+  if(key == TRUE_BOOLEXP) {
+    notify(player, T("I don't understand that key."));
+    return;
+  }
+  if(!SW_ISSET(swi, SWITCH_DEFAULTS)) {
+    upcasestr(name);
+    /* Is this attribute already in the table? */
+    if(*name == '@')
+      name++;
+    ap = (ATTR *) ptab_find_exact(&ptab_attrib, name);
+  } else ap = catchall;
+
+  if (ap) {
+    if (AF_Internal(ap)) {
+      /* Don't muck with internal attributes */
+      notify(player, T("That attribute's permissions can not be changed."));
+      return;
+    }
+
+    if(!controls(player, AL_CREATOR(ap))) {
+      notify(player, T(e_perm));
+      return;
+    } 
+
+     /* If a lock is already set on whatever we're doing.. clear it */
+    free_boolexp(SW_ISSET(swi, SWITCH_WRITE) ? AL_WLock(ap) : AL_RLock(ap));
+  } else {
+    /* Create fresh if the name is ok */
+    if (!good_atr_name(name)) {
+      notify(player, T("Invalid attribute name."));
+      return;
+    }
+    insert = 1;
+    ap = (ATTR *) mush_malloc(sizeof(ATTR), "ATTR");
+    if (!ap) {
+      notify(player, "Critical memory failure - Alert God!");
+      do_log(LT_ERR, 0, 0, "do_attribute_lock: unable to malloc ATTR");
+      return;
+    }
+    if(!SW_ISSET(swi, SWITCH_DEFAULTS))
+      AL_NAME(ap) = mush_strdup(name, "GLOBAL.ATR.NAME");
+    else 
+      AL_NAME(ap) = NULL;
+    AL_FLAGS(ap) = 0;
+    AL_WLock(ap) = TRUE_BOOLEXP;
+    AL_RLock(ap) = TRUE_BOOLEXP;   
+    ap->data = NULL_CHUNK_REFERENCE;
+  }
+
+
+  AL_CREATOR(ap) = player;
+  if(SW_ISSET(swi, SWITCH_WRITE))
+    AL_WLock(ap) = key;
+  else
+    AL_RLock(ap) = key;
+
+
+  /* Only insert when it's not already in the table */
+  if (insert) {
+    if(!SW_ISSET(swi, SWITCH_DEFAULTS)) {
+      ptab_start_inserts(&ptab_attrib);
+      ptab_insert(&ptab_attrib, name, ap);
+      ptab_end_inserts(&ptab_attrib);
+    } 
+    /* And If its the catchall.. just point the catchall to ap */
+    else catchall = ap;
+  }
+
+  /* Ok, now we need to see if there are any attributes of this name
+   * set on objects in the db. If so, and if we're retroactive, set 
+   * perms/creator 
+   */
+  if (SW_ISSET(swi, SWITCH_RETROACTIVE) && !SW_ISSET(swi, SWITCH_DEFAULTS)) {
+    for (i = 0; i < db_top; i++) {
+      if ((ap2 = atr_get_noparent(i, name))) {
+       free_boolexp(SW_ISSET(swi, SWITCH_WRITE) ? AL_WLock(ap2) : AL_RLock(ap2));
+       if(SW_ISSET(swi, SWITCH_WRITE))
+         AL_WLock(ap2) = dup_bool(AL_WLock(ap));
+       else
+         AL_RLock(ap2) = dup_bool(AL_RLock(ap));
+      }
+    }
+  }
+
+  notify(player, "Global Atr Lock Set.");
+}
+
+
+/** Add new standard attributes, or change permissions on them.
+ * \verbatim
+ * Given the name and permission string for an attribute, add it to
+ * the attribute table (or modify the permissions if it's already
+ * there). Permissions may be changed retroactively, which modifies
+ * permissions on any copies of that attribute set on objects in the
+ * database. This is the top-level code for @attribute/access.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the attribute name.
+ * \param perms a string of attribute permissions, space-separated.
+ * \param retroactive if true, apply the permissions retroactively.
+ */
+void
+do_attribute_access(dbref player, char *name, char *perms, int retroactive)
+{
+  ATTR *ap, *ap2;
+  int flags = 0;
+  int i;
+  int insert = 0;
+
+  /* Parse name and perms */
+  if (!name || !*name) {
+    notify(player, T("Which attribute do you mean?"));
+    return;
+  }
+  flags = string_to_privs(attr_privs, perms, 0);
+  if (!flags) {
+    notify(player, T("I don't understand those permissions."));
+    return;
+  }
+  upcasestr(name);
+  /* Is this attribute already in the table? */
+  if(*name == '@')
+    name++;
+  ap = (ATTR *) ptab_find_exact(&ptab_attrib, name);
+  if (ap) {
+    if (AF_Internal(ap)) {
+      /* Don't muck with internal attributes */
+      notify(player, T("That attribute's permissions can not be changed."));
+      return;
+    }
+    /* It already exists.. Make sure they can override the guy who set this before */
+    if(!controls(player, AL_CREATOR(ap))) {
+      notify(player, T(e_perm));
+      return;
+    }
+  } else {
+    /* Create fresh */
+    insert = 1;
+    ap = (ATTR *) mush_malloc(sizeof(ATTR), "ATTR");
+    if (!ap) {
+      notify(player, "Critical memory failure - Alert God!");
+      do_log(LT_ERR, 0, 0, "do_attribute_access: unable to malloc ATTR");
+      return;
+    }
+    AL_WLock(ap) = TRUE_BOOLEXP;
+    AL_RLock(ap) = TRUE_BOOLEXP;   
+    AL_NAME(ap) = mush_strdup(name, "GLOBAL.ATR.NAME");
+    ap->data = NULL_CHUNK_REFERENCE;
+  }
+
+  AL_FLAGS(ap) = flags;
+  AL_CREATOR(ap) = player;
+
+
+  /* Only insert when it's not already in the table */
+  if (insert) {
+    ptab_start_inserts(&ptab_attrib);
+    ptab_insert(&ptab_attrib, name, ap);
+    ptab_end_inserts(&ptab_attrib);
+  }
+
+  /* Ok, now we need to see if there are any attributes of this name
+   * set on objects in the db. If so, and if we're retroactive, set 
+   * perms/creator 
+   */
+  if (retroactive) {
+    for (i = 0; i < db_top; i++) {
+      if ((ap2 = atr_get_noparent(i, name))) {
+       AL_FLAGS(ap2) = flags;
+       AL_CREATOR(ap2) = player;
+      }
+    }
+  }
+
+  notify_format(player, T("%s -- Attribute permissions now: %s"), name,
+               privs_to_string(attr_privs, flags));
+}
+
+
+/** Delete an attribute from the attribute table.
+ * \verbatim
+ * Top-level function for @attrib/delete.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the attribute to delete.
+ * \param catchall delete the catchall attribute
+ */
+void
+do_attribute_delete(dbref player, char *name, char def)
+{
+  ATTR *ap;
+
+  if ((!name || !*name) && !def) {
+    notify(player, T("Which attribute do you mean?"));
+    return;
+  }
+
+  /* Is this attribute in the table? */
+
+  if (*name == '@')
+    name++;
+
+  ap = def ? catchall : (ATTR *) ptab_find_exact(&ptab_attrib, name);
+  if (!ap) {
+    notify(player, T("That attribute isn't in the attribute table"));
+    return;
+  }
+
+  /* Free Atr Name */
+  mush_free((Malloc_t) AL_NAME(ap), "GLOBAL.ATR.NAME");
+
+  /* Free Locks */
+  free_boolexp(AL_WLock(ap));
+  free_boolexp(AL_RLock(ap));
+
+  /* Ok, take it out of the hash table */
+  if(def) {
+    mush_free(catchall, "ATTR");
+    catchall = NULL;
+  } else
+    ptab_delete(&ptab_attrib, name);
+
+  if(def)
+    notify(player, T("Default attribute permissions deleted."));
+  else
+    notify_format(player, T("Removed %s from attribute table."), name);
+
+  return;
+}
+
+/** Rename an attribute in the attribute table.
+ * \verbatim
+ * Top-level function for @attrib/rename.
+ * \endverbatim
+ * \param player the enactor.
+ * \param old the name of the attribute to rename.
+ * \param newname the new name (surprise!)
+ */
+void
+do_attribute_rename(dbref player, char *old, char *newname)
+{
+  ATTR *ap;
+  if (!old || !*old || !newname || !*newname) {
+    notify(player, T("Which attributes do you mean?"));
+    return;
+  }
+  upcasestr(old);
+  upcasestr(newname);
+  /* Is the new name valid? */
+  if (!good_atr_name(newname)) {
+    notify(player, T("Invalid attribute name."));
+    return;
+  }
+  /* Is the new name already in use? */
+  ap = (ATTR *) ptab_find_exact(&ptab_attrib, newname);
+  if (ap) {
+    notify_format(player,
+                 T("The name %s is already used in the attribute table."),
+                 newname);
+    return;
+  }
+  /* Is the old name a real attribute? */
+  ap = (ATTR *) ptab_find_exact(&ptab_attrib, old);
+  if (!ap) {
+    notify(player, T("That attribute isn't in the attribute table"));
+    return;
+  }
+  /* Ok, take it out and put it back under the new name */
+  ptab_delete(&ptab_attrib, old);
+  /*  This causes a slight memory leak if you rename an attribute
+     added via /access. But that doesn't happen often. Will fix
+     someday.  */
+  AL_NAME(ap) = strdup(newname);
+  ptab_start_inserts(&ptab_attrib);
+  ptab_insert(&ptab_attrib, newname, ap);
+  ptab_end_inserts(&ptab_attrib);
+  notify_format(player,
+               T("Renamed %s to %s in attribute table."), old, newname);
+  return;
+}
+
+/** Display information on an attribute from the table.
+ * \verbatim
+ * Top-level function for @attribute.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the attribute.
+ */
+void
+do_attribute_info(dbref player, char *name)
+{
+  ATTR *ap;
+  if (!name || !*name) {
+    if(catchall)
+      ap = catchall;
+    else {
+      notify(player, T("Which attribute do you mean?"));
+      return;
+    }
+  }
+
+  /* Is this attribute in the table? */
+
+  if (!ap) {
+    if (*name == '@')
+      name++;
+
+    ap = aname_hash_lookup(name);
+  }
+
+  if (!ap) {
+    notify(player, T("That attribute isn't in the attribute table"));
+    return;
+  }
+  notify_format(player, "Attribute: %s", ap == catchall ? "Default Attribute" : AL_NAME(ap));
+  notify_format(player,
+               "    Flags: %s", privs_to_string(attr_privs, AL_FLAGS(ap)));
+  notify_format(player, "  Creator: %s", unparse_dbref(AL_CREATOR(ap)));
+  /* Now Locks */
+    if(!(AL_RLock(ap) == TRUE_BOOLEXP))
+      notify_format(player, "Readlock: %s", unparse_boolexp(player, AL_RLock(ap), UB_ALL ));
+    if(!(AL_WLock(ap) == TRUE_BOOLEXP))
+      notify_format(player, "Writelock: %s", unparse_boolexp(player, AL_WLock(ap), UB_ALL ));
+  return;
+}
+
+/** Display a list of standard attributes.
+ * \verbatim
+ * Top-level function for @list/attribs.
+ * \endverbatim
+ * \param player the enactor.
+ * \param lc if true, display the list in lowercase; otherwise uppercase.
+ */
+void
+do_list_attribs(dbref player, int lc)
+{
+  char *b = list_attribs();
+  notify_format(player, "Attribs: %s", lc ? strlower(b) : b);
+}
+
+/** Return a list of standard attributes.
+ * This functions returns the list of standard attributes, separated by
+ * spaces, in a statically allocated buffer.
+ */
+char *
+list_attribs(void)
+{
+  ATTR *ap;
+  const char *ptrs[BUFFER_LEN / 2];
+  static char buff[BUFFER_LEN];
+  char *bp;
+  int nptrs = 0, i;
+
+  ap = (ATTR *) ptab_firstentry(&ptab_attrib);
+  while (ap) {
+    ptrs[nptrs++] = AL_NAME(ap);
+    ap = (ATTR *) ptab_nextentry(&ptab_attrib);
+  }
+  bp = buff;
+  safe_str(ptrs[0], buff, &bp);
+  for (i = 1; i < nptrs; i++) {
+    safe_chr(' ', buff, &bp);
+    safe_str(ptrs[i], buff, &bp);
+  }
+  *bp = '\0';
+  return buff;
+}
+
+/** Attr things to be done after the config file is loaded but before
+ * objects are restarted.
+ */
+void
+attr_init_postconfig(void)
+{
+  ATTR *a;
+  /* read_remote_desc affects AF_NEARBY flag on DESCRIBE attribute */
+  a = aname_hash_lookup("DESCRIBE");
+  if (READ_REMOTE_DESC)
+    a->flags &= ~AF_NEARBY;
+  else
+    a->flags |= AF_NEARBY;
+}
diff --git a/src/attrib.c b/src/attrib.c
new file mode 100644 (file)
index 0000000..a9c06f8
--- /dev/null
@@ -0,0 +1,2238 @@
+/**
+ * \file attrib.c
+ *
+ * \brief Manipulate attributes on objects.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <string.h>
+#include <ctype.h>
+#include "conf.h"
+#include "externs.h"
+#include "chunk.h"
+#include "attrib.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "parse.h"
+#include "htab.h"
+#include "privtab.h"
+#include "mymalloc.h"
+#include "strtree.h"
+#include "flags.h"
+#include "mushdb.h"
+#include "lock.h"
+#include "log.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#pragma warning( disable : 4761)        /* disable warning re conversion */
+#endif
+
+/* Catchall Attribute any non-standard attributes will conform to this */
+ATTR *catchall;
+
+/** A string tree of attribute names in use, to save us memory since
+ * many are duplicated.
+ */
+StrTree atr_names;
+/** Table of attribute flags. */
+extern PRIV attr_privs[];
+dbref atr_on_obj = NOTHING;
+
+/** A flag to show if we're in the middle of a @wipe (this changes
+ * behaviour for atr_clr()).  Yes, this is gross and ugly, but it
+ * seemed like a better idea than propogating signature changes
+ * for atr_clr() and do_set_atr() through the entire codebase.  If
+ * you come up with a better way, PLEASE fix this...
+ */
+int we_are_wiping;
+
+dbref global_parent_depth[] = { 0, 0 };
+
+/** A string to hold the name of a missing prefix branch, set by
+ * can_write_attr_internal.  Again, gross and ugly.  Please fix.
+ */
+static char missing_name[ATTRIBUTE_NAME_LIMIT + 1];
+
+/*======================================================================*/
+
+/** How many attributes go in a "page" of attribute memory? */
+#define ATTRS_PER_PAGE (200)
+
+/** A page of memory for attributes.
+ * This structure is a collection of attribute memory. Rather than
+ * allocate new attributes one at a time, we allocate them in pages,
+ * and build a linked free list from the allocated page.
+ */
+typedef struct atrpage {
+  ATTR atrs[ATTRS_PER_PAGE];    /**< Array of attribute structures */
+} ATTRPAGE;
+
+static ATTR *atr_free_list;
+static ATTR *get_atr_free_list(void);
+static ATTR *find_atr_pos_in_list(ATTR *** pos, char const *name);
+static int can_create_attr(dbref player, dbref obj, char const *atr_name,
+                           int flags);
+static ATTR *find_atr_in_list(ATTR * atr, char const *name);
+
+/** Utility define for can_write_attr_internal and can_create_attr.
+ * \param p the player trying to write
+ * \param a the attribute to be written
+ * \param o the object trying to write attribute to
+ * \param s obey the safe flag?
+ * \param n do non-standard check
+ * \param lo lock owner
+ */
+
+/* (!n || !catchall || (AL_WLock(catchall) == TRUE_BOOLEXP))
+ */
+
+/*
+#define Cannot_Write_This_Attr(p,a,o,s,n) (!God(p) && AF_Internal(a) || \
+             (s && AF_Safe(a)) || \
+           ( ((AL_FLAGS(a) & AF_PRIVILEGE) && !(Prived(p) || (Inherit_Powers(p) && Prived(Owner(p)) )))   ||  \
+             !(  (controls(p, o) && ( (Owner(o) == Owner(lo)) || controls(p,lo)) && catchall && AL_WLock(a) == TRUE_BOOLEXP ? \
+                     AL_CREATOR(catchall) : AL_CREATOR(a)))) ||   ((AL_WLock(a) == TRUE_BOOLEXP && (!n || !catchall || \
+                     (AL_WLock(catchall) == TRUE_BOOLEXP))) && controls(p,o) ) ||   ((AL_WLock(a) != TRUE_BOOLEXP ? \
+                      eval_boolexp(p, AL_WLock(a), o, NULL) : (n && catchall && AL_WLock(catchall) != TRUE_BOOLEXP && \
+                                                 eval_boolexp(p, AL_WLock(catchall), o, NULL))) )) 
+                                                 */
+
+
+/*
+#define Cannot_Write_This_Attr(p,a,o,s,n,lo) (!God(p) && AF_Internal(a) || \
+              (s && AF_Safe(a)) || \
+            ( ((AL_FLAGS(a) & AF_PRIVILEGE) && !(Prived(p) || (Inherit_Powers(p) && Prived(Owner(p)) )))   ||  \
+              !(  (controls(p, o) && ( (Owner(o) == Owner(lo)) || controls(p,lo))) ||  \
+                (AL_WLock(a) == TRUE_BOOLEXP && controls(p,o) ) ||  \
+               (AL_WLock(a) != TRUE_BOOLEXP && eval_boolexp(p, AL_WLock(a), o, NULL) )) ))
+
+*/
+#define Cannot_Write_This_Attr(p,a,o,s,n,lo) cannot_write_this_attr_internal(p,a,o,s,n,lo)
+
+
+
+/* player - player trying to write
+ * attr        - attribute trying to write
+ * obj         - object trying to be written to
+ * safe        - obey safe flag
+ * ns_chk      - do non-standard check
+ * lock_owner  - lock owner
+ **/
+
+int cannot_write_this_attr_internal(dbref player, ATTR *attr, dbref obj, char safe, char ns_chk, dbref lowner) {
+  dbref lock_owner;
+
+  if(!GoodObject(lowner))
+    lock_owner = AL_CREATOR(attr);
+  else
+    lock_owner = lowner;
+
+  if(God(player))
+      return 0;
+
+  if(AF_Internal(attr))
+    return 1;
+
+  if((AL_FLAGS(attr) & AF_PRIVILEGE) && !(Prived(player) || (Inherit_Powers(player) && Prived(Owner(player)))))
+    return 1;
+
+
+  if(ns_chk && catchall && !controls(player, AL_CREATOR(catchall)) && AL_WLock(catchall) != TRUE_BOOLEXP && 
+      !eval_boolexp(player, AL_WLock(catchall), obj, NULL))
+    return 1;
+  if(!controls(player, lock_owner) && AL_WLock(attr) != TRUE_BOOLEXP && !eval_boolexp(player, AL_WLock(attr), obj, NULL))
+    return 1;
+
+  return 0;
+}
+/*======================================================================*/
+
+/** Initialize the attribute string tree.
+ */
+void
+init_atr_name_tree(void)
+{
+  st_init(&atr_names);
+}
+
+/** Lookup table for good_atr_name */
+extern char atr_name_table[UCHAR_MAX + 1];
+
+/** Decide if a name is valid for an attribute.
+ * A good attribute name is at least one character long, no more than
+ * ATTRIBUTE_NAME_LIMIT characters long, and every character is a 
+ * valid character. An attribute name may not start or end with a backtick.
+ * An attribute name may not contain multiple consecutive backticks.
+ * \param s a string to test for validity as an attribute name.
+ */
+int
+good_atr_name(char const *s)
+{
+  const unsigned char *a;
+  int len = 0;
+  if (!s || !*s)
+    return 0;
+  if (*s == '`')
+    return 0;
+  if (strstr(s, "``"))
+    return 0;
+  for (a = (const unsigned char *) s; *a; a++, len++)
+    if (!atr_name_table[*a])
+      return 0;
+  if (*(s + len - 1) == '`')
+    return 0;
+  return len <= ATTRIBUTE_NAME_LIMIT;
+}
+
+/** Find an attribute table entry, given a name.
+ * A trivial wrapper around aname_hash_lookup.
+ * \param string an attribute name.
+ */
+ATTR *
+atr_match(const char *string)
+{
+  return aname_hash_lookup(string);
+}
+
+/** Find the first attribute branching off the specified attribute.
+ * \param branch the attribute to look under
+ */
+ATTR *
+atr_sub_branch(ATTR * branch)
+{
+  char const *name, *n2;
+  size_t len;
+
+  name = AL_NAME(branch);
+  len = strlen(name);
+  for (branch = AL_NEXT(branch); branch; branch = AL_NEXT(branch)) {
+    n2 = AL_NAME(branch);
+    if (strlen(n2) <= len)
+      return NULL;
+    if (n2[len] == '`') {
+      if (!strncmp(n2, name, len))
+        return branch;
+      else
+        return NULL;
+    }
+  }
+  return NULL;
+}
+
+/** Scan an attribute list for an attribute with the specified name.
+ * This continues from whatever start point is passed in.
+ * \param atr the start of the list to search from
+ * \param name the attribute name to look for
+ * \return the matching attribute, or NULL
+ */
+static ATTR *
+find_atr_in_list(ATTR * atr, char const *name)
+{
+  int comp;
+
+  while (atr) {
+    comp = strcoll(name, AL_NAME(atr));
+    if (comp < 0)
+      return NULL;
+    if (comp == 0)
+      return atr;
+    atr = AL_NEXT(atr);
+  }
+
+  return NULL;
+}
+
+/** Find the place to insert/delete an attribute with the specified name.
+ * \param pos a pointer to the ATTR ** holding the list position
+ * \param name the attribute name to look for
+ * \return the matching attribute, or NULL if no matching attribute
+ */
+static ATTR *
+find_atr_pos_in_list(ATTR *** pos, char const *name)
+{
+  int comp;
+
+  while (**pos) {
+    comp = strcoll(name, AL_NAME(**pos));
+    if (comp < 0)
+      return NULL;
+    if (comp == 0)
+      return **pos;
+    *pos = &AL_NEXT(**pos);
+  }
+
+  return NULL;
+}
+
+/** Convert a string of attribute flags to a bitmask.
+ * Given a space-separated string of attribute flags, look them up
+ * and return a bitmask of them if player is permitted to use
+ * all of those flags.
+ * \param player the dbref to use for privilege checks.
+ * \param p a space-separated string of attribute flags.
+ * \return an attribute flag bitmask.
+ */
+int
+string_to_atrflag(dbref player, char const *p)
+{
+  int f;
+  f = string_to_privs(attr_privs, p, 0);
+  if (!f)
+    return -1;
+  if (!Admin(player) && (f & AF_MDARK))
+    return -1;
+  if (!div_powover(player, player, "Privilege") && (f & AF_PRIVILEGE))
+    return -1;
+  return f;
+}
+
+/** Convert an attribute flag bitmask into a list of the full
+ * names of the flags.
+ * \param mask the bitmask of attribute flags to display.
+ * \return a pointer to a static buffer with the full names of the flags.
+ */
+const char *
+atrflag_to_string(int mask)
+{
+  return privs_to_string(attr_privs, mask);
+}
+
+/** Utility define for atr_add and can_create_attr */
+#define set_default_flags(atr,flags, lock_owner, ns_chk) \
+  do { \
+    ATTR *std = atr_match(AL_NAME((atr))); \
+                                           \
+    if (std && !strcmp(AL_NAME(std), AL_NAME((atr)))) { \
+      AL_FLAGS(atr) = AL_FLAGS(std); \
+      AL_WLock(atr) = dup_bool(AL_WLock(std)); \
+      AL_RLock(atr) = dup_bool(AL_RLock(std)); \
+      lock_owner = AL_CREATOR(std); \
+      ns_chk = 0; \
+    } else { \
+      if (flags != NOTHING) \
+        AL_FLAGS(atr) |= flags; \
+      AL_WLock(atr) = AL_RLock(atr) = TRUE_BOOLEXP; \
+      lock_owner = NOTHING; \
+      ns_chk = 1; \
+    } \
+  } while (0)
+
+#define free_atr_locks(atr) \
+    free_boolexp(AL_WLock(atr)); \
+    free_boolexp(AL_RLock(atr)); 
+
+/** Can an attribute of specified name be created?
+ * This function determines if an attribute can be created by examining
+ * the tree path to the attribute, and the standard attribute flags for
+ * those parts of the path that don't exist yet.
+ * \param player the player trying to do the write.
+ * \param obj the object targetted for the write.
+ * \param atr the attribute being interrogated.
+ * \param flags the default flags to add to the attribute.
+ * \retval 0 if the player cannot write the attribute.
+ * \retval 1 if the player can write the attribute.
+ */
+static int
+can_create_attr(dbref player, dbref obj, char const *atr_name, int flags)
+{
+  char *p;
+  ATTR tmpatr, *atr;
+  int ns_chk, num_new = 1;
+  dbref lock_owner;
+  missing_name[0] = '\0';
+
+  atr = &tmpatr;
+  AL_FLAGS(atr) = 0;
+  AL_CREATOR(atr) = ooref != NOTHING ? ooref : player;
+  AL_NAME(atr) = atr_name;
+
+  set_default_flags(atr, flags, lock_owner, ns_chk);
+  if(lock_owner == NOTHING)
+    lock_owner = AL_CREATOR(atr);
+
+  if (Cannot_Write_This_Attr(player, atr, obj, 1, ns_chk, lock_owner)) {
+    free_atr_locks(atr);
+    return 0;
+  }
+
+  free_atr_locks(atr);
+
+  strcpy(missing_name, atr_name);
+  atr = List(obj);
+  for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
+    *p = '\0';
+    if (atr != &tmpatr)
+      atr = find_atr_in_list(atr, missing_name);
+    if (!atr) {
+      atr = &tmpatr;
+      AL_CREATOR(atr) = ooref != NOTHING ? Owner(ooref) : Owner(player);
+    }
+    if (atr == &tmpatr) {
+      AL_NAME(atr) = missing_name;
+      set_default_flags(atr, flags, lock_owner, ns_chk);
+      if(lock_owner == NOTHING)
+       lock_owner = AL_CREATOR(atr);
+      num_new++;
+    }
+    if (Cannot_Write_This_Attr(player, atr, obj, 1, ns_chk, lock_owner)) {
+      free_atr_locks(atr);
+      missing_name[0] = '\0';
+      return 0;
+    }
+    free_atr_locks(atr);
+    *p = '`';
+  }
+
+  if ((AttrCount(obj) + num_new) >
+      (Many_Attribs(obj) ? HARD_MAX_ATTRCOUNT : MAX_ATTRCOUNT)) {
+    do_log(LT_ERR, player, obj,
+           T("Attempt by %s(%d) to create too many attributes on %s(%d)"),
+           Name(player), player, Name(obj), obj);
+    return 0;
+  }
+
+  return 1;
+}
+
+/*======================================================================*/
+
+/** Do the work of creating the attribute entry on an object.
+ * This doesn't do any permissions checking.  You should do that yourself.
+ * \param thing the object to hold the attribute
+ * \param atr_name the name for the attribute
+ */
+static ATTR *
+create_atr(dbref thing, char const *atr_name)
+{
+  ATTR *ptr, **ins;
+  char const *name;
+
+  /* put the name in the string table */
+  name = st_insert(atr_name, &atr_names);
+  if (!name)
+    return NULL;
+
+  /* allocate a new page, if needed */
+  ptr = get_atr_free_list();
+  atr_free_list = AL_NEXT(ptr);
+
+  /* initialize atr */
+  AL_WLock(ptr) = TRUE_BOOLEXP;
+  AL_RLock(ptr) = TRUE_BOOLEXP;
+  AL_NAME(ptr) = name;
+  ptr->data = NULL_CHUNK_REFERENCE;
+
+  /* link it in */
+  ins = &List(thing);
+  (void) find_atr_pos_in_list(&ins, AL_NAME(ptr));
+  AL_NEXT(ptr) = *ins;
+  *ins = ptr;
+  AttrCount(thing)++;
+
+  return ptr;
+}
+
+/** Add an attribute to an object, dangerously.
+ * This is a stripped down version of atr_add, without duplicate checking,
+ * permissions checking, attribute count checking, or auto-ODARKing.  
+ * If anyone uses this outside of database load or atr_cpy (below), 
+ * I will personally string them up by their toes.  - Alex 
+ * \param thing object to set the attribute on.
+ * \param atr name of the attribute to set.
+ * \param s value of the attribute to set.
+ * \param player the attribute creator.
+ * \param flags bitmask of attribute flags for this attribute.
+ * \param derefs the initial deref count to use for the attribute value.
+ */
+void
+atr_new_add(dbref thing, const char *RESTRICT atr, const char *RESTRICT s,
+            dbref player, int flags, unsigned char derefs, boolexp wlock,
+            boolexp rlock)
+{
+  ATTR *ptr;
+  boolexp lock;
+
+  if (!EMPTY_ATTRS && !*s)
+    return;
+
+  /* Don't fail on a bad name, but do log it */
+  if (!good_atr_name(atr))
+    do_rawlog(LT_ERR, T("Bad attribute name %s on object %s"), atr,
+              unparse_dbref(thing));
+
+  ptr = create_atr(thing, atr);
+  if (!ptr)
+    return;
+
+  if((lock = dup_bool(wlock))) 
+    AL_WLock(ptr) = lock;
+
+  if((lock = dup_bool(rlock))) 
+    AL_RLock(ptr) = lock;
+
+  AL_FLAGS(ptr) = (flags != NOTHING) ? flags : 0;
+  AL_FLAGS(ptr) &= ~AF_COMMAND & ~AF_LISTEN;
+  AL_CREATOR(ptr) = ooref != NOTHING ? ooref : player;
+
+  /* replace string with new string */
+  if (!s || !*s) {
+    /* nothing */
+  } else {
+    unsigned char *t = compress(s);
+    if (!t)
+      return;
+    ptr->data = chunk_create(t, u_strlen(t), derefs);
+    free(t);
+
+    if (*s == '$')
+      AL_FLAGS(ptr) |= AF_COMMAND;
+    if (*s == '^')
+      AL_FLAGS(ptr) |= AF_LISTEN;
+  }
+}
+
+/** Attribute error - invalid name */
+#define AE_BADNAME -3
+/** Attribute error - attempt to overwrite a safe attribute */
+#define AE_SAFE -2
+/** Attribute error - general failure */
+#define AE_ERROR -1
+
+/** Add an attribute to an object, safely.
+ * \verbatim
+ * This is the function that should be called in hardcode to add
+ * an attribute to an object (but not to process things like @set that
+ * may add or clear an attribute - see do_set_atr() for that).
+ * \endverbatim
+ * \param thing object to set the attribute on.
+ * \param atr name of the attribute to set.
+ * \param s value of the attribute to set.
+ * \param player the attribute creator.
+ * \param flags bitmask of attribute flags for this attribute.
+ * \retval AE_BADNAME invalid attribute name.
+ * \retval AE_SAFE attempt to overwrite a SAFE attribute.
+ * \retval AE_ERROR general failure.
+ * \retval 1 success.
+ */
+int
+atr_add(dbref thing, const char *RESTRICT atr, const char *RESTRICT s,
+        dbref player, int flags)
+{
+  ATTR *ptr;
+  dbref lock_owner; /* Not used.. but set in set_default_flags */
+  int ns_chk;
+  char *p;
+
+  tooref = ooref;
+  if (player == GOD)            /* This normally only done internally */
+    ooref = NOTHING;
+
+  if (!s || (!EMPTY_ATTRS && !*s)) {
+    ooref = tooref;
+    return atr_clr(thing, atr, player);
+  }
+
+  if (!good_atr_name(atr)) {
+    ooref = tooref;
+    return AE_BADNAME;
+  }
+
+  /* walk the list, looking for a preexisting value */
+  ptr = find_atr_in_list(List(thing), atr);
+
+  /* check for permission to modify existing atr */
+  if ((ptr && AF_Safe(ptr))) {
+    ooref = tooref;
+    return AE_SAFE;
+  }
+  if (ptr && !Can_Write_Attr(player, thing, ptr)) {
+    ooref = tooref;
+    return AE_ERROR;
+  }
+
+  /* make a new atr, if needed */
+  if (!ptr) {
+    if (!can_create_attr(player, thing, atr, flags)) {
+      ooref = tooref;
+      return AE_ERROR;
+    }
+
+    strcpy(missing_name, atr);
+    ptr = List(thing);
+    for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
+      *p = '\0';
+
+      ptr = find_atr_in_list(ptr, missing_name);
+
+      if (!ptr) {
+        ptr = create_atr(thing, missing_name);
+        if (!ptr) {
+          ooref = tooref;
+          return AE_ERROR;
+        }
+
+        /* update modification time here, because from now on,
+         * we modify even if we fail */
+        if (!IsPlayer(thing) && !AF_Nodump(ptr)) {
+          char lmbuf[1024];
+          ModTime(thing) = mudtime;
+          snprintf(lmbuf, 1023, "%s[#%d]", ptr->name, player);
+          lmbuf[strlen(lmbuf) + 1] = '\0';
+          set_lmod(thing, lmbuf);
+        }
+
+        set_default_flags(ptr, flags, lock_owner, ns_chk);
+        AL_FLAGS(ptr) &= ~AF_COMMAND & ~AF_LISTEN;
+        AL_CREATOR(ptr) = ooref != NOTHING ? Owner(ooref) : Owner(player);
+        if (!EMPTY_ATTRS) {
+          unsigned char *t = compress(" ");
+          if (!t) {
+            ooref = tooref;
+            return AE_ERROR;
+          }
+          ptr->data = chunk_create(t, u_strlen(t), 0);
+          free(t);
+        }
+      }
+
+      *p = '`';
+    }
+
+    ptr = create_atr(thing, atr);
+    if (!ptr) {
+      ooref = tooref;
+      return AE_ERROR;
+    }
+
+    set_default_flags(ptr, flags, lock_owner, ns_chk);
+  }
+  /* update modification time here, because from now on,
+   * we modify even if we fail */
+  if (!IsPlayer(thing) && !AF_Nodump(ptr)) {
+    char lmbuf[1024];
+    ModTime(thing) = mudtime;
+    snprintf(lmbuf, 1023, "%s[#%d]", ptr->name, player);
+    lmbuf[strlen(lmbuf) + 1] = '\0';
+    set_lmod(thing, lmbuf);
+  }
+
+  /* change owner */
+  AL_CREATOR(ptr) = Owner(player);
+
+  AL_FLAGS(ptr) &= ~AF_COMMAND & ~AF_LISTEN;
+
+  /* replace string with new string */
+  if (ptr->data)
+    chunk_delete(ptr->data);
+  if (!s || !*s) {
+    ptr->data = NULL_CHUNK_REFERENCE;
+  } else {
+    unsigned char *t = compress(s);
+    if (!t) {
+      ptr->data = NULL_CHUNK_REFERENCE;
+      ooref = tooref;
+      return AE_ERROR;
+    }
+    ptr->data = chunk_create(t, u_strlen(t), 0);
+    free(t);
+
+    if (*s == '$')
+      AL_FLAGS(ptr) |= AF_COMMAND;
+    if (*s == '^')
+      AL_FLAGS(ptr) |= AF_LISTEN;
+  }
+
+  ooref = tooref;
+  return 1;
+}
+
+/** Remove an attribute from an object.
+ * This function clears an attribute from an object. 
+ * Permission is denied if the attribute is a branch, not a leaf.
+ * \param thing object to clear attribute from.
+ * \param atr name of attribute to remove.
+ * \param player enactor attempting to remove attribute.
+ * \retval 0 no attribute found to reset
+ * \retval AE_SAFE attribute is safe
+ * \retval AE_ERROR other failure
+ */
+int
+atr_clr(dbref thing, char const *atr, dbref player)
+{
+  ATTR *ptr, **prev, *sub;
+  size_t len;
+
+  tooref = ooref;
+  if (player == GOD)
+    ooref = NOTHING;
+
+  prev = &List(thing);
+  ptr = find_atr_pos_in_list(&prev, atr);
+
+  if (!ptr) {
+    ooref = tooref;
+    return 0;
+  }
+
+  if (ptr && AF_Safe(ptr)) {
+    ooref = tooref;
+    return AE_SAFE;
+  }
+  if (!Can_Write_Attr(player, thing, ptr)) {
+    ooref = tooref;
+    return AE_ERROR;
+  }
+
+  sub = atr_sub_branch(ptr);
+  if (!we_are_wiping && sub) {
+    ooref = tooref;
+    return AE_ERROR;
+  }
+
+  if (!IsPlayer(thing) && !AF_Nodump(ptr)) {
+    char lmbuf[1024];
+    ModTime(thing) = mudtime;
+    snprintf(lmbuf, 1023, "%s[#%d]", ptr->name, player);
+    lmbuf[strlen(lmbuf) + 1] = '\0';
+    set_lmod(thing, lmbuf);
+  }
+
+  *prev = AL_NEXT(ptr);
+
+  if (ptr->data)
+    chunk_delete(ptr->data);
+
+  len = strlen(AL_NAME(ptr));
+  st_delete(AL_NAME(ptr), &atr_names);
+
+  free_atr_locks(ptr);
+
+  AL_NEXT(ptr) = atr_free_list;
+  AL_FLAGS(ptr) = 0;
+  atr_free_list = ptr;
+  AttrCount(thing)--;
+
+  if (we_are_wiping && sub) {
+    while (*prev != sub)
+      prev = &AL_NEXT(*prev);
+    ptr = *prev;
+    while (ptr && strlen(AL_NAME(ptr)) > len && AL_NAME(ptr)[len] == '`') {
+      *prev = AL_NEXT(ptr);
+
+      if (ptr->data)
+        chunk_delete(ptr->data);
+      st_delete(AL_NAME(ptr), &atr_names);
+
+      AL_NEXT(ptr) = atr_free_list;
+      AL_FLAGS(ptr) = 0;
+      atr_free_list = ptr;
+      AttrCount(thing)--;
+
+      ptr = *prev;
+    }
+  }
+  ooref = tooref;
+  return 1;
+}
+
+/** Retrieve an attribute from an object or its ancestors.
+ * This function retrieves an attribute from an object, or from its
+ * parent chain, returning a pointer to the first attribute that
+ * matches or NULL. This is a pointer to an attribute structure, not
+ * to the value of the attribute, so the value is usually accessed
+ * through atr_value() or safe_atr_value().
+ * \param obj the object containing the attribute.
+ * \param atrname the name of the attribute.
+ * \return pointer to the attribute structure retrieved, or NULL.
+ */
+ATTR *
+atr_get(dbref obj, char const *atrname)
+{
+  static char name[ATTRIBUTE_NAME_LIMIT + 1];
+  char *p;
+  ATTR *atr;
+  int parent_depth;
+  dbref target;
+  dbref ancestor;
+
+  global_parent_depth[0] = 0;
+  global_parent_depth[1] = NOTHING;
+  if (obj == NOTHING || !good_atr_name(atrname))
+    return NULL;
+
+  /* First try given name, then try alias match. */
+  strcpy(name, atrname);
+  for (;;) {
+    /* Hunt through the parents/ancestor chain... */
+    ancestor = Ancestor_Parent(obj);
+    target = obj;
+    parent_depth = 0;
+    while (parent_depth < MAX_PARENTS && GoodObject(target)) {
+      /* If the ancestor of the object is in its explict parent chain,
+       * we use it there, and don't check the ancestor later.
+       */
+      if (target == ancestor)
+        ancestor = NOTHING;
+      atr = List(target);
+
+      /* If we're looking at a parent/ancestor, then we
+       * need to check the branch path for privacy... */
+      if (target != obj) {
+        for (p = strchr(name, '`'); p; p = strchr(p + 1, '`')) {
+          *p = '\0';
+          atr = find_atr_in_list(atr, name);
+          if (!atr || AF_Private(atr)) {
+            *p = '`';
+            goto continue_target;
+          }
+          *p = '`';
+        }
+      }
+
+      /* Now actually find the attribute. */
+      atr = find_atr_in_list(atr, name);
+      global_parent_depth[1] = atr_on_obj = target;
+      if (atr && (target == obj || !AF_Private(atr))) {
+        global_parent_depth[0] = parent_depth;
+        return atr;
+      }
+
+    continue_target:
+      /* Attribute wasn't on this object.  Check a parent or ancestor. */
+      parent_depth++;
+      target = Parent(target);
+      if (!GoodObject(target)) {
+        parent_depth = 0;
+        target = ancestor;
+      }
+    }
+
+    /* Try the alias, too... */
+    atr = atr_match(atrname);
+    if (!atr || !strcmp(name, AL_NAME(atr)))
+      break;
+    strcpy(name, AL_NAME(atr));
+  }
+
+  global_parent_depth[0] = parent_depth;
+  return NULL;
+}
+
+/** Retrieve an attribute from an object.
+ * This function retrieves an attribute from an object, and does not
+ * check the parent chain. It returns a pointer to the attribute
+ * or NULL.  This is a pointer to an attribute structure, not
+ * to the value of the attribute, so the (compressed) value is usually 
+ * to the value of the attribute, so the value is usually accessed
+ * through atr_value() or safe_atr_value().
+ * \param thing the object containing the attribute.
+ * \param atr the name of the attribute.
+ * \return pointer to the attribute structure retrieved, or NULL.
+ */
+ATTR *
+atr_get_noparent(dbref thing, char const *atr)
+{
+  ATTR *ptr;
+
+  if (thing == NOTHING || !good_atr_name(atr))
+    return NULL;
+
+  /* try real name */
+  ptr = find_atr_in_list(List(thing), atr);
+  if (ptr)
+    return ptr;
+
+  ptr = atr_match(atr);
+  if (!ptr || !strcmp(atr, AL_NAME(ptr)))
+    return NULL;
+  atr = AL_NAME(ptr);
+
+  /* try alias */
+  ptr = find_atr_in_list(List(thing), atr);
+  if (ptr)
+    return ptr;
+
+  return NULL;
+}
+
+
+/** Apply a function to a set of attributes.
+ * This function applies another function to a set of attributes on an
+ * object specified by a (wildcarded) pattern to match against the
+ * attribute name.
+ * \param player the enactor.
+ * \param thing the object containing the attribute.
+ * \param name the pattern to match against the attribute name.
+ * \param mortal only fetch mortal-visible attributes?
+ * \param func the function to call for each matching attribute.
+ * \param args additional arguments to pass to the function.
+ * \return the sum of the return values of the functions called.
+ */
+int
+atr_iter_get(dbref player, dbref thing, const char *name, int mortal,
+             aig_func func, void *args)
+{
+  ATTR *ptr, **indirect;
+  int result;
+  int len;
+
+  result = 0;
+  if (!name || !*name)
+    name = "*";
+  len = strlen(name);
+
+  if (!wildcard(name) && name[len - 1] != '`') {
+    ptr = atr_get_noparent(thing, strupper(name));
+    if (ptr && (mortal ? Is_Visible_Attr(thing, ptr)
+                : Can_Read_Attr(player, thing, ptr)))
+      result = func(player, thing, NOTHING, name, ptr, args);
+  } else {
+    indirect = &List(thing);
+    while (*indirect) {
+      ptr = *indirect;
+      if ((mortal ? Is_Visible_Attr(thing, ptr)
+           : Can_Read_Attr(player, thing, ptr))
+          && atr_wild(name, AL_NAME(ptr)))
+        result += func(player, thing, NOTHING, name, ptr, args);
+      if (ptr == *indirect)
+        indirect = &AL_NEXT(ptr);
+    }
+  }
+
+  return result;
+}
+
+/** Free the memory associated with all attributes of an object.
+ * This function frees all of an object's attribute memory.
+ * This includes the memory allocated to hold the attribute's value,
+ * and the attribute's entry in the object's string tree.
+ * Freed attribute structures are added to the free list.
+ * \param thing dbref of object
+ */
+void
+atr_free(dbref thing)
+{
+  ATTR *ptr;
+
+  if (!List(thing))
+    return;
+
+  if (!IsPlayer(thing)) {
+    char lmbuf[1024];
+    ModTime(thing) = mudtime;
+    snprintf(lmbuf, 1023, "ATRWIPE[#%d]", thing);
+    lmbuf[strlen(lmbuf) + 1] = '\0';
+    set_lmod(thing, lmbuf);
+  }
+
+  while ((ptr = List(thing))) {
+    List(thing) = AL_NEXT(ptr);
+    if (AL_WLock(ptr) != TRUE_BOOLEXP)
+      free_boolexp(AL_WLock(ptr));
+    if (AL_RLock(ptr) != TRUE_BOOLEXP)
+      free_boolexp(AL_RLock(ptr));
+
+    if (ptr->data)
+      chunk_delete(ptr->data);
+    st_delete(AL_NAME(ptr), &atr_names);
+
+    AL_NEXT(ptr) = atr_free_list;
+    atr_free_list = ptr;
+  }
+}
+
+/** Copy all of the attributes from one object to another.
+ * \verbatim
+ * This function is used by @clone to copy all of the attributes
+ * from one object to another.
+ * \endverbatim
+ * \param dest destination object to receive attributes.
+ * \param source source object containing attributes.
+ */
+void
+atr_cpy(dbref dest, dbref source)
+{
+  ATTR *ptr;
+  int max_attrs;
+
+  max_attrs = (Many_Attribs(dest) ? HARD_MAX_ATTRCOUNT : MAX_ATTRCOUNT);
+  List(dest) = NULL;
+  for (ptr = List(source); ptr; ptr = AL_NEXT(ptr))
+    if (!AF_Nocopy(ptr)
+        && (AttrCount(dest) < max_attrs)) {
+      atr_new_add(dest, AL_NAME(ptr), atr_value(ptr),
+                  AL_CREATOR(ptr), AL_FLAGS(ptr), AL_DEREFS(ptr),
+                  AL_WLock(ptr), AL_RLock(ptr));
+      AttrCount(dest)++;
+    }
+}
+
+/** Structure for keeping track of which attributes have appeared
+ * on children when doing command matching. */
+typedef struct used_attr {
+  struct used_attr *next;       /**< Next attribute in list */
+  char const *name;             /**< The name of the attribute */
+  int no_prog;                  /**< Was it AF_NOPROG */
+} UsedAttr;
+
+/** Find an attribute in the list of seen attributes.
+ * Since attributes are checked in collation order, the pointer to the
+ * list is updated to reflect the current search position.
+ * For efficiency of insertions, the pointer used is a trailing pointer,
+ * pointing at the pointer to the next used struct.
+ * To allow a useful return code, the pointer used is actually a pointer
+ * to the pointer mentioned above.  Yes, I know three-star coding is bad,
+ * but I have good reason, here.
+ * \param prev the pointer to the pointer to the pointer to the next
+ *             used attribute.
+ * \param name the name of the attribute to look for.
+ * \retval 0 the attribute was not in the list,
+ *           **prev now points to the next atfer.
+ * \retval 1 the attribute was in the list,
+ *           **prev now points to the entry for it.
+ */
+static int
+find_attr(UsedAttr *** prev, char const *name)
+{
+  int comp;
+
+  comp = 1;
+  while (**prev) {
+    comp = strcoll(name, prev[0][0]->name);
+    if (comp <= 0)
+      break;
+    *prev = &prev[0][0]->next;
+  }
+  return comp == 0;
+}
+
+/** Insert an attribute in the list of seen attributes.
+ * Since attributes are inserted in collation order, an updated insertion
+ * point is returned (so subsequent calls don't have to go hunting as far).
+ * \param prev the pointer to the pointer to the attribute list.
+ * \param name the name of the attribute to insert.
+ * \param no_prog the AF_NOPROG value from the attribute.
+ * \return the pointer to the pointer to the next attribute after
+ *         the one inserted.
+ */
+static UsedAttr **
+use_attr(UsedAttr ** prev, char const *name, int no_prog)
+{
+  int found;
+  UsedAttr *used;
+
+  found = find_attr(&prev, name);
+  if (!found) {
+    used = mush_malloc(sizeof *used, "used_attr");
+    used->next = *prev;
+    used->name = name;
+    used->no_prog = 0;
+    *prev = used;
+  }
+  prev[0]->no_prog |= no_prog;
+  /* do_rawlog(LT_TRACE, "Recorded %s: %d -> %d", name,
+     no_prog, prev[0]->no_prog); */
+  return &prev[0]->next;
+}
+
+/** Match input against a $command or ^listen attribute.
+ * This function attempts to match a string against either the $commands
+ * or ^listens on an object. Matches may be glob or regex matches, 
+ * depending on the attribute's flags. With the reasonably safe assumption
+ * that most of the matches are going to fail, the faster non-capturing
+ * glob match is done first, and the capturing version only called when
+ * we already know it'll match. Due to the way PCRE works, there's no
+ * advantage to doing something similar for regular expression matches.
+ * \param thing object containing attributes to check.
+ * \param player the enactor, for privilege checks.
+ * \param type either '$' or '^', indicating the type of attribute to check.
+ * \param end character that denotes the end of a command (usually ':').
+ * \param str string to match against attributes.
+ * \param just_match if true, return match without executing code.
+ * \param atrname used to return the list of matching object/attributes.
+ * \param abp pointer to end of atrname.
+ * \param errobj if an attribute matches, but the lock fails, this pointer
+ *        is used to return the failing dbref. If NULL, we don't bother.
+ * \return number of attributes that matched, or 0
+ */
+int
+atr_comm_match(dbref thing, dbref player, int type, int end,
+               char const *str, int just_match, char *atrname, char **abp,
+               dbref * errobj)
+{
+  int flag_mask;
+  ATTR *ptr;
+  int parent_depth;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  char *s;
+  int match, match_found;
+  dbref parent;
+  UsedAttr *used_list, **prev;
+  ATTR *skip[ATTRIBUTE_NAME_LIMIT / 2];
+  int skipcount;
+  int lock_checked = 0;
+  dbref local_ooref;
+
+  /* check for lots of easy ways out */
+  if ((type != '$' && type != '^') || !GoodObject(thing) || Halted(thing)
+      || (type == '$' && NoCommand(thing)))
+    return 0;
+
+  if (type == '$') {
+    flag_mask = AF_COMMAND;
+    parent_depth = GoodObject(Parent(thing));
+  } else {
+    flag_mask = AF_LISTEN;
+    if (ThingInhearit(thing) || RoomInhearit(thing)) {
+      parent_depth = GoodObject(Parent(thing));
+    } else {
+      parent_depth = 0;
+    }
+  }
+
+  match = 0;
+  used_list = NULL;
+  prev = &used_list;
+
+  skipcount = 0;
+  /* do_rawlog(LT_TRACE, "Searching %s:", Name(thing)); */
+  for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
+    if (skipcount && ptr == skip[skipcount - 1]) {
+      size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
+      while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
+             AL_NAME(AL_NEXT(ptr))[len] == '`') {
+        ptr = AL_NEXT(ptr);
+        /* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
+      }
+      skipcount--;
+      continue;
+    }
+    if (parent_depth)
+      prev = use_attr(prev, AL_NAME(ptr), AF_Noprog(ptr));
+    if (AF_Noprog(ptr)) {
+      skip[skipcount] = atr_sub_branch(ptr);
+      if (skip[skipcount])
+        skipcount++;
+      continue;
+    }
+    if (!(AL_FLAGS(ptr) & flag_mask))
+      continue;
+    strcpy(tbuf1, atr_value(ptr));
+    s = tbuf1;
+    do {
+      s = strchr(s + 1, end);
+    } while (s && s[-1] == '\\');
+    if (!s)
+      continue;
+    *s++ = '\0';
+    if (type == '^' && !AF_Ahear(ptr)) {
+      if ((thing == player && !AF_Mhear(ptr))
+         || (thing != player && AF_Mhear(ptr)))
+       continue;
+    }
+
+    if (AF_Regexp(ptr)) {
+      /* Turn \: into : */
+      char *from, *to;
+      for (from = tbuf1, to = tbuf2; *from; from++, to++) {
+        if (*from == '\\' && *(from + 1) == ':')
+          from++;
+        *to = *from;
+      }
+      *to = '\0';
+    } else
+      strcpy(tbuf2, tbuf1);
+
+    match_found = 0;
+    if (AF_Regexp(ptr)) {
+      if (regexp_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
+        match_found = 1;
+        match++;
+      }
+    } else {
+      if (quick_wild_new(tbuf2 + 1, str, AF_Case(ptr))) {
+        match_found = 1;
+        match++;
+        wild_match_case(tbuf2 + 1, str, AF_Case(ptr));
+      }
+    }
+    if (match_found) {
+      /* We only want to do the lock check once, so that any side
+       * effects in the lock are only performed once per utterance.
+       * Thus, '$foo *r:' and '$foo b*:' on the same object will only
+       * run the lock once for 'foo bar'.
+       */
+      if (!lock_checked) {
+        lock_checked = 1;
+        if ((type == '$' && !eval_lock(player, thing, Command_Lock))
+            || (type == '^' && !eval_lock(player, thing, Listen_Lock))
+            || !eval_lock(player, thing, Use_Lock)) {
+          match--;
+          if (errobj)
+            *errobj = thing;
+          /* If we failed the lock, there's no point in continuing at all. */
+          goto exit_sequence;
+        }
+      }
+      if (atrname && abp) {
+        safe_chr(' ', atrname, abp);
+        safe_dbref(thing, atrname, abp);
+        safe_chr('/', atrname, abp);
+        safe_str(AL_NAME(ptr), atrname, abp);
+      }
+      if (!just_match) {
+        local_ooref = ooref;
+        ooref = AL_CREATOR(ptr);
+        parse_que(thing, s, player);
+        ooref = local_ooref;
+      }
+    }
+  }
+
+  /* Don't need to free used_list here, because if !parent_depth,
+   * we would never have allocated it. */
+  if (!parent_depth)
+    return match;
+
+  for (parent_depth = MAX_PARENTS, parent = Parent(thing);
+       parent_depth-- && parent != NOTHING; parent = Parent(parent)) {
+    /* do_rawlog(LT_TRACE, "Searching %s:", Name(parent)); */
+    skipcount = 0;
+    prev = &used_list;
+    for (ptr = List(parent); ptr; ptr = AL_NEXT(ptr)) {
+      if (skipcount && ptr == skip[skipcount - 1]) {
+        size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
+        while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
+               AL_NAME(AL_NEXT(ptr))[len] == '`') {
+          ptr = AL_NEXT(ptr);
+          /* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
+        }
+        skipcount--;
+        continue;
+      }
+      if (AF_Private(ptr)) {
+        /* do_rawlog(LT_TRACE, "Private %s:", AL_NAME(ptr)); */
+        skip[skipcount] = atr_sub_branch(ptr);
+        if (skip[skipcount])
+          skipcount++;
+        continue;
+      }
+      if (find_attr(&prev, AL_NAME(ptr))) {
+        /* do_rawlog(LT_TRACE, "Found %s:", AL_NAME(ptr)); */
+        if (prev[0]->no_prog || AF_Noprog(ptr)) {
+          skip[skipcount] = atr_sub_branch(ptr);
+          if (skip[skipcount])
+            skipcount++;
+          prev[0]->no_prog = AF_NOPROG;
+        }
+        continue;
+      }
+      if (GoodObject(Parent(parent)))
+        prev = use_attr(prev, AL_NAME(ptr), AF_Noprog(ptr));
+      if (AF_Noprog(ptr)) {
+        /* do_rawlog(LT_TRACE, "NoProg %s:", AL_NAME(ptr)); */
+        skip[skipcount] = atr_sub_branch(ptr);
+        if (skip[skipcount])
+          skipcount++;
+        continue;
+      }
+      if (!(AL_FLAGS(ptr) & flag_mask))
+        continue;
+      strcpy(tbuf1, atr_value(ptr));
+      s = tbuf1;
+      do {
+        s = strchr(s + 1, end);
+      } while (s && s[-1] == '\\');
+      if (!s)
+        continue;
+      *s++ = '\0';
+      if (type == '^' && !AF_Ahear(ptr)) {
+       if ((thing == player && !AF_Mhear(ptr))
+           || (thing != player && AF_Mhear(ptr)))
+         continue;
+      }
+
+      if (AF_Regexp(ptr)) {
+        /* Turn \: into : */
+        char *from, *to;
+        for (from = tbuf1, to = tbuf2; *from; from++, to++) {
+          if (*from == '\\' && *(from + 1) == ':')
+            from++;
+          *to = *from;
+        }
+        *to = '\0';
+      } else
+        strcpy(tbuf2, tbuf1);
+
+      match_found = 0;
+      if (AF_Regexp(ptr)) {
+        if (regexp_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
+          match_found = 1;
+          match++;
+        }
+      } else {
+        if (quick_wild_new(tbuf2 + 1, str, AF_Case(ptr))) {
+          match_found = 1;
+          match++;
+          wild_match_case(tbuf2 + 1, str, AF_Case(ptr));
+        }
+      }
+      if (match_found) {
+        /* Since we're still checking the lock on the child, not the
+         * parent, we don't actually want to reset lock_checked with
+         * each parent checked.  Sorry for the misdirection, Alan.
+         *  - Alex */
+        if (!lock_checked) {
+          lock_checked = 1;
+          if ((type == '$' && !eval_lock(player, thing, Command_Lock))
+              || (type == '^' && !eval_lock(player, thing, Listen_Lock))
+              || !eval_lock(player, thing, Use_Lock)) {
+            match--;
+            if (errobj)
+              *errobj = thing;
+            /* If we failed the lock, there's no point in continuing at all. */
+            goto exit_sequence;
+          }
+        }
+        if (atrname && abp) {
+          safe_chr(' ', atrname, abp);
+          if (Can_Examine(player, parent))
+            safe_dbref(parent, atrname, abp);
+          else
+            safe_dbref(thing, atrname, abp);
+
+          safe_chr('/', atrname, abp);
+          safe_str(AL_NAME(ptr), atrname, abp);
+        }
+        if (!just_match) {
+          local_ooref = ooref;
+          ooref = AL_CREATOR(ptr);
+          if (AL_FLAGS(ptr) & AF_POWINHERIT)
+            div_parse_que(parent, s, thing, player);
+          else
+            parse_que(thing, s, player);
+          ooref = local_ooref;
+        }
+      }
+    }
+  }
+
+  /* This is where I wish for 'try {} finally {}'... */
+exit_sequence:
+  while (used_list) {
+    UsedAttr *temp = used_list->next;
+    mush_free(used_list, "used_attr");
+    used_list = temp;
+  }
+  return match;
+}
+
+
+/* hacked version of atr_comm_match for divisions */
+int
+atr_comm_divmatch(dbref thing, dbref player, int type, int end,
+                  char const *str, int just_match, char *atrname,
+                  char **abp, dbref * errobj)
+{
+  int flag_mask;
+  ATTR *ptr;
+  int division_depth;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  char *s;
+  int match, match_found;
+  dbref division, local_ooref;
+  UsedAttr *used_list, **prev;
+  ATTR *skip[ATTRIBUTE_NAME_LIMIT / 2];
+  int skipcount;
+  int lock_checked = 0;
+
+  /* check for lots of easy ways out */
+  if ((type != '$' && type != '^') || !GoodObject(thing) || Halted(thing)
+      || (type == '$' && NoCommand(thing)))
+    return 0;
+
+  if (type == '$') {
+    flag_mask = AF_COMMAND;
+    division_depth = GoodObject(Division(thing));
+  } else {
+    flag_mask = AF_LISTEN;
+    if (ThingInhearit(thing) || RoomInhearit(thing)) {
+      division_depth = GoodObject(Division(thing));
+    } else {
+      division_depth = 0;
+    }
+  }
+
+  match = 0;
+  used_list = NULL;
+  prev = &used_list;
+
+  skipcount = 0;
+  /* do_rawlog(LT_TRACE, "Searching %s:", Name(thing)); */
+  for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
+    if (skipcount && ptr == skip[skipcount - 1]) {
+      size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
+      while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
+             AL_NAME(AL_NEXT(ptr))[len] == '`') {
+        ptr = AL_NEXT(ptr);
+        /* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
+      }
+      skipcount--;
+      continue;
+    }
+    if (division_depth)
+      prev = use_attr(prev, AL_NAME(ptr), AF_Noprog(ptr));
+    if (AF_Noprog(ptr)) {
+      skip[skipcount] = atr_sub_branch(ptr);
+      if (skip[skipcount])
+        skipcount++;
+      continue;
+    }
+    if (!(AL_FLAGS(ptr) & flag_mask))
+      continue;
+    strcpy(tbuf1, atr_value(ptr));
+    s = tbuf1;
+    do {
+      s = strchr(s + 1, end);
+    } while (s && s[-1] == '\\');
+    if (!s)
+      continue;
+    *s++ = '\0';
+
+    if (AF_Regexp(ptr)) {
+      /* Turn \: into : */
+      char *from, *to;
+      for (from = tbuf1, to = tbuf2; *from; from++, to++) {
+        if (*from == '\\' && *(from + 1) == ':')
+          from++;
+        *to = *from;
+      }
+      *to = '\0';
+    } else
+      strcpy(tbuf2, tbuf1);
+
+    match_found = 0;
+    if (AF_Regexp(ptr)) {
+      if (regexp_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
+        match_found = 1;
+        match++;
+      }
+    } else {
+      if (quick_wild_new(tbuf2 + 1, str, AF_Case(ptr))) {
+        match_found = 1;
+        match++;
+        wild_match_case(tbuf2 + 1, str, AF_Case(ptr));
+      }
+    }
+    if (match_found) {
+      /* We only want to do the lock check once, so that any side
+       * effects in the lock are only performed once per utterance.
+       * Thus, '$foo *r:' and '$foo b*:' on the same object will only
+       * run the lock once for 'foo bar'.
+       */
+      if (!lock_checked) {
+        lock_checked = 1;
+        if ((type == '$' && !eval_lock(player, thing, Command_Lock))
+            || (type == '^' && !eval_lock(player, thing, Listen_Lock))
+            || !eval_lock(player, thing, Use_Lock)) {
+          match--;
+          if (errobj)
+            *errobj = thing;
+          /* If we failed the lock, there's no point in continuing at all. */
+          goto exit_sequence;
+        }
+      }
+      if (atrname && abp) {
+        safe_chr(' ', atrname, abp);
+        safe_dbref(thing, atrname, abp);
+        safe_chr('/', atrname, abp);
+        safe_str(AL_NAME(ptr), atrname, abp);
+      }
+      if (!just_match) {
+        local_ooref = ooref;
+        ooref = AL_CREATOR(ptr);
+        parse_que(thing, s, player);
+        ooref = local_ooref;
+      }
+    }
+  }
+
+  /* Don't need to free used_list here, because if !division_depth,
+   * we would never have allocated it. */
+  if (!division_depth)
+    return match;
+
+  for (division_depth = MAX_PARENTS, division = Division(thing);
+       division_depth-- && division != NOTHING;
+       division = Division(division)) {
+    /* do_rawlog(LT_TRACE, "Searching %s:", Name(division)); */
+    skipcount = 0;
+    prev = &used_list;
+    for (ptr = List(division); ptr; ptr = AL_NEXT(ptr)) {
+      if (skipcount && ptr == skip[skipcount - 1]) {
+        size_t len = strrchr(AL_NAME(ptr), '`') - AL_NAME(ptr);
+        while (AL_NEXT(ptr) && strlen(AL_NAME(AL_NEXT(ptr))) > len &&
+               AL_NAME(AL_NEXT(ptr))[len] == '`') {
+          ptr = AL_NEXT(ptr);
+          /* do_rawlog(LT_TRACE, "  Skipping %s", AL_NAME(ptr)); */
+        }
+        skipcount--;
+        continue;
+      }
+      if (AF_Private(ptr)) {
+        /* do_rawlog(LT_TRACE, "Private %s:", AL_NAME(ptr)); */
+        skip[skipcount] = atr_sub_branch(ptr);
+        if (skip[skipcount])
+          skipcount++;
+        continue;
+      }
+      if (find_attr(&prev, AL_NAME(ptr))) {
+        /* do_rawlog(LT_TRACE, "Found %s:", AL_NAME(ptr)); */
+        if (prev[0]->no_prog || AF_Noprog(ptr)) {
+          skip[skipcount] = atr_sub_branch(ptr);
+          if (skip[skipcount])
+            skipcount++;
+          prev[0]->no_prog = AF_NOPROG;
+        }
+        continue;
+      }
+      if (GoodObject(Division(division)))
+        prev = use_attr(prev, AL_NAME(ptr), AF_Noprog(ptr));
+      if (AF_Noprog(ptr)) {
+        /* do_rawlog(LT_TRACE, "NoProg %s:", AL_NAME(ptr)); */
+        skip[skipcount] = atr_sub_branch(ptr);
+        if (skip[skipcount])
+          skipcount++;
+        continue;
+      }
+      if (!(AL_FLAGS(ptr) & flag_mask))
+        continue;
+      strcpy(tbuf1, atr_value(ptr));
+      s = tbuf1;
+      do {
+        s = strchr(s + 1, end);
+      } while (s && s[-1] == '\\');
+      if (!s)
+        continue;
+      *s++ = '\0';
+
+      if (AF_Regexp(ptr)) {
+        /* Turn \: into : */
+        char *from, *to;
+        for (from = tbuf1, to = tbuf2; *from; from++, to++) {
+          if (*from == '\\' && *(from + 1) == ':')
+            from++;
+          *to = *from;
+        }
+        *to = '\0';
+      } else
+        strcpy(tbuf2, tbuf1);
+
+      match_found = 0;
+      if (AF_Regexp(ptr)) {
+        if (regexp_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
+          match_found = 1;
+          match++;
+        }
+      } else {
+        if (quick_wild_new(tbuf2 + 1, str, AF_Case(ptr))) {
+          match_found = 1;
+          match++;
+          wild_match_case(tbuf2 + 1, str, AF_Case(ptr));
+        }
+      }
+      if (match_found) {
+        /* Since we're still checking the lock on the child, not the
+         * division, we don't actually want to reset lock_checked with
+         * each division checked.  Sorry for the misdirection, Alan.
+         *  - Alex */
+        if (!lock_checked) {
+          lock_checked = 1;
+          if ((type == '$' && !eval_lock(player, thing, Command_Lock))
+              || (type == '^' && !eval_lock(player, thing, Listen_Lock))
+              || !eval_lock(player, thing, Use_Lock)) {
+            match--;
+            if (errobj)
+              *errobj = thing;
+            /* If we failed the lock, there's no point in continuing at all. */
+            goto exit_sequence;
+          }
+        }
+        if (atrname && abp) {
+          safe_chr(' ', atrname, abp);
+          if (Can_Examine(player, division))
+            safe_dbref(division, atrname, abp);
+          else
+            safe_dbref(thing, atrname, abp);
+
+          safe_chr('/', atrname, abp);
+          safe_str(AL_NAME(ptr), atrname, abp);
+        }
+        if (!just_match) {
+          local_ooref = ooref;
+          ooref = AL_CREATOR(ptr);
+          if (AL_FLAGS(ptr) & AF_POWINHERIT)
+            div_parse_que(division, s, thing, player);
+          else
+            parse_que(thing, s, player);
+          ooref = local_ooref;
+        }
+      }
+    }
+  }
+
+  /* This is where I wish for 'try {} finally {}'... */
+exit_sequence:
+  while (used_list) {
+    UsedAttr *temp = used_list->next;
+    mush_free(used_list, "used_attr");
+    used_list = temp;
+  }
+  return match;
+}
+
+
+/** Match input against a specified object's specified $command 
+ * attribute. Matches may be glob or regex matches. Used in command hooks.
+ * depending on the attribute's flags. 
+ * \param thing object containing attributes to check.
+ * \param player the enactor, for privilege checks.
+ * \param atr the name of the attribute
+ * \param str the string to match
+ * \retval 1 attribute matched.
+ * \retval 0 attribute failed to match.
+ */
+int
+one_comm_match(dbref thing, dbref player, const char *atr, const char *str)
+{
+  ATTR *ptr;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  char *s;
+
+  /* check for lots of easy ways out */
+  if (!GoodObject(thing) || Halted(thing) || NoCommand(thing))
+    return 0;
+
+  if (!(ptr = atr_get(thing, atr)))
+    return 0;
+
+  if (AF_Noprog(ptr) || !AF_Command(ptr))
+    return 0;
+
+  strcpy(tbuf1, atr_value(ptr));
+  s = tbuf1;
+  do {
+    s = strchr(s + 1, ':');
+  } while (s && s[-1] == '\\');
+  if (!s)
+    return 0;
+  *s++ = '\0';
+
+  if (AF_Regexp(ptr)) {
+    /* Turn \: into : */
+    char *from, *to;
+    for (from = tbuf1, to = tbuf2; *from; from++, to++) {
+      if (*from == '\\' && *(from + 1) == ':')
+        from++;
+      *to = *from;
+    }
+    *to = '\0';
+  } else
+    strcpy(tbuf2, tbuf1);
+
+  if (AF_Regexp(ptr) ?
+      regexp_match_case(tbuf2 + 1, str, AF_Case(ptr)) :
+      wild_match_case(tbuf2 + 1, str, AF_Case(ptr))) {
+    if (!eval_lock(player, thing, Command_Lock)
+        || !eval_lock(player, thing, Use_Lock))
+      return 0;
+    parse_que(thing, s, player);
+    return 1;
+  }
+  return 0;
+}
+
+/*======================================================================*/
+
+/** Set or clear an attribute on an object.
+ * \verbatim
+ * This is the primary function for implementing @set.
+ * A new interface (as of 1.6.9p0) for setting attributes,
+ * which takes care of case-fixing, object-level flag munging,
+ * alias recognition, add/clr distinction, etc.  Enjoy.
+ * \endverbatim
+ * \param thing object to set the attribute on or remove it from.
+ * \param atr name of the attribute to set or clear.
+ * \param s value to set the attribute to (or NULL to clear).
+ * \param player enactor, for permission checks.
+ * \param flags attribute flags.
+ * \retval -1 failure of one sort.
+ * \retval 0 failure of another sort.
+ * \retval 1 success.
+ */
+int
+do_set_atr(dbref thing, const char *RESTRICT atr, const char *RESTRICT s,
+           dbref player, int flags)
+{
+  ATTR *old;
+  char name[BUFFER_LEN];
+  char tbuf1[BUFFER_LEN];
+  int res;
+  int was_hearer;
+  int was_listener;
+  dbref contents;
+  if (!EMPTY_ATTRS && s && !*s)
+    s = NULL;
+  if (IsGarbage(thing)) {
+    notify(player, T("Garbage is garbage."));
+    return 0;
+  }
+
+  /* Taking care of controls check in can_write_attr_internal now */
+  /*
+     if (!controls(player, thing)) 
+     return 0;
+   */
+
+  strcpy(name, atr);
+  upcasestr(name);
+  if (!strcmp(name, "ALIAS") && IsPlayer(thing)) {
+    old = atr_get_noparent(thing, "ALIAS");
+    if (old) {
+      /* Old alias - we're allowed to change to a different case */
+      strcpy(tbuf1, atr_value(old));
+      if (s
+          && (!*s
+              || (strcasecmp(s, tbuf1) && !ok_player_name(s, player)))) {
+        notify(player, T("That is not a valid alias."));
+        return -1;
+      }
+      if (Can_Write_Attr(player, thing, old))
+        delete_player(thing, tbuf1);
+    } else {
+      /* No old alias */
+      if (s && *s && !ok_player_name(s, player)) {
+        notify(player, T("That is not a valid alias."));
+        return -1;
+      }
+    }
+  } else if (s && *s && (!strcmp(name, "FORWARDLIST")
+                        || !strcmp(name, "MAILFORWARDLIST")
+                         || !strcmp(name, "DEBUGFORWARDLIST"))) {
+    /* You can only set this to dbrefs of things you're allowed to
+     * forward to. If you get one wrong, we puke.
+     */
+    char *fwdstr, *curr;
+    dbref fwd;
+    strcpy(tbuf1, s);
+    fwdstr = trim_space_sep(tbuf1, ' ');
+    while ((curr = split_token(&fwdstr, ' ')) != NULL) {
+      if (!is_objid(curr)) {
+        notify_format(player, T("%s should contain only dbrefs."), name);
+        return -1;
+      }
+      fwd = parse_objid(curr);
+      if (!GoodObject(fwd) || IsGarbage(fwd)) {
+        notify_format(player, T("Invalid dbref #%d in %s."), fwd, name);
+        return -1;
+      }
+      if ((!strcmp(name, "FORWARDLIST") || !strcmp(name, "DEBUGFORWARDLIST"))
+         && !Can_Forward(thing, fwd)) {
+        notify_format(player, T("I don't think #%d wants to hear from %s."),
+                     fwd, Name(thing));
+        return -1;
+      }
+      if (!strcmp(name, "MAILFORWARDLIST") && !Can_MailForward(thing, fwd)) {
+       notify_format(player, T("I don't think #%d wants %s's mail."), fwd,
+                     Name(thing));
+        return -1;
+      }
+    }
+    /* If you made it here, all your dbrefs were ok */
+  }
+
+  was_hearer = Hearer(thing);
+  was_listener = Listener(thing);
+  res =
+      s ? atr_add(thing, name, s, player,
+                  (flags & 0x02) ? AF_NOPROG : NOTHING)
+      : atr_clr(thing, name, player);
+  if (res == AE_SAFE) {
+    notify(player,
+           T("That attribute is SAFE.  Set it !SAFE to modify it."));
+    return 0;
+  } else if (res == AE_BADNAME) {
+    notify(player, T("That's not a very good name for an attribute."));
+    return 0;
+  } else if (res == AE_ERROR) {
+    if (*missing_name) {
+      if (s && (EMPTY_ATTRS || *s))
+        notify_format(player, T("You must set %s first."), missing_name);
+      else
+        notify_format(player,
+                      T
+                      ("%s is a branch attribute; remove its children first."),
+                      missing_name);
+    } else
+      notify(player, T("That attribute cannot be changed by you."));
+    return 0;
+  } else if (!res) {
+    notify(player, T("No such attribute to reset."));
+    return 0;
+  }
+  if (!strcmp(name, "ALIAS") && IsPlayer(thing)) {
+    if (s && *s) {
+      add_player(thing, s);
+      notify(player, T("Alias set."));
+    } else {
+      notify(player, T("Alias removed."));
+    }
+    return 1;
+  } else if (!strcmp(name, "LISTEN")) {
+    if (IsRoom(thing))
+      contents = Contents(thing);
+    else {
+      contents = Location(thing);
+      if (GoodObject(contents))
+        contents = Contents(contents);
+    }
+    if (GoodObject(contents)) {
+      if (!s && !was_listener && !Hearer(thing)) {
+        notify_except(contents, thing,
+                      tprintf(T("%s loses its ears and becomes deaf."),
+                              Name(thing)), NA_INTER_PRESENCE);
+      } else if (s && !was_hearer && !was_listener) {
+        notify_except(contents, thing,
+                      tprintf(T("%s grows ears and can now hear."),
+                              Name(thing)), NA_INTER_PRESENCE);
+      }
+    }
+  }
+  if ((flags & 0x01) && !AreQuiet(player, thing))
+    notify_format(player,
+                  "%s/%s - %s.", Name(thing), name,
+                  s ? T("Set") : T("Cleared"));
+  return 1;
+}
+
+/** Lock or unlock an attribute.
+ * Attribute locks are largely obsolete and should be deprecated,
+ * but this is the code that does them.
+ * \param player the enactor.
+ * \param arg1 the object/attribute, as a string.
+ * \param arg2 the desired lock status ('on' or 'off').
+ */
+void
+do_atrlock(dbref player, const char *arg1, const char *arg2,
+           char write_lock)
+{
+  dbref thing, creator;
+  char *p;
+  ATTR *ptr;
+  int status;
+  boolexp key;
+
+  if (!arg2 || !*arg2)
+    status = 0;
+
+  if (!arg1 || !*arg1) {
+    notify(player, T("You need to give an object/attribute pair."));
+    return;
+  }
+  if (!(p = strchr(arg1, '/')) || !(*(p + 1))) {
+    notify(player, T("You need to give an object/attribute pair."));
+    return;
+  }
+  *p++ = '\0';
+
+  if ((thing = noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING)) ==
+      NOTHING)
+    return;
+
+  if (!controls(player, thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  ptr = atr_get_noparent(thing, strupper(p));
+  if (ptr && Can_Read_Attr(player, thing, ptr)) {
+    if (!Can_Write_Attr(player, thing, ptr)) {
+      notify(player,
+             T
+             ("You need to be able to set the attribute to change its lock."));
+      return;
+    } else {
+      creator = ooref != NOTHING ? Owner(ooref) : Owner(player);
+      if (!status) {
+        char lmbuf[1024];
+        ModTime(thing) = mudtime;
+        snprintf(lmbuf, 1023, "%s unlock-atr%slock[#%d]", ptr->name,
+                 write_lock ? "write" : "read", player);
+        lmbuf[strlen(lmbuf) + 1] = '\0';
+        set_lmod(thing, lmbuf);
+        AL_CREATOR(ptr) = creator;
+        notify_format(player, "Unlocked attribute %slock.",
+                      write_lock ? "write" : "read");
+        free_boolexp(write_lock ? AL_WLock(ptr) : AL_RLock(ptr));
+        if (write_lock)
+          AL_WLock(ptr) = TRUE_BOOLEXP;
+        else
+          AL_RLock(ptr) = TRUE_BOOLEXP;
+      } else {
+        key = parse_boolexp(creator, arg2, "ATTR");
+        if (key == TRUE_BOOLEXP)
+          notify(player, T("I don't understand that key."));
+        else {
+          char lmbuf[1024];
+          ModTime(thing) = mudtime;
+          snprintf(lmbuf, 1023, "%s atr%slock[#%d]", ptr->name,
+                   write_lock ? "write" : "read", player);
+          lmbuf[strlen(lmbuf) + 1] = '\0';
+          set_lmod(thing, lmbuf);
+          AL_CREATOR(ptr) = creator;
+          notify_format(player, "Locked attribute %slock.",
+                        write_lock ? "write" : "read");
+          free_boolexp(write_lock ? AL_WLock(ptr) : AL_RLock(ptr));
+          if (write_lock)
+            AL_WLock(ptr) = key;
+          else
+            AL_RLock(ptr) = key;
+        }
+      }
+    }
+  } else
+    notify(player, T("No such attribute."));
+  return;
+}
+
+/** Change ownership of an attribute.
+ * \verbatim
+ * This function is used to implement @atrchown.
+ * \endverbatim
+ * \param player the enactor, for permission checking.
+ * \param arg1 the object/attribute to change, as a string.
+ * \param arg2 the name of the new owner (or "me").
+ */
+void
+do_atrchown(dbref player, const char *arg1, const char *arg2)
+{
+  dbref thing, new_owner;
+  char *p;
+  ATTR *ptr;
+  if (!arg1 || !*arg1) {
+    notify(player, T("You need to give an object/attribute pair."));
+    return;
+  }
+  if (!(p = strchr(arg1, '/')) || !(*(p + 1))) {
+    notify(player, T("You need to give an object/attribute pair."));
+    return;
+  }
+  *p++ = '\0';
+
+  if ((thing = noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING)) ==
+      NOTHING)
+    return;
+
+  if (ooref != NOTHING && ooref != player && !God(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  } else if (!Owns(player, thing) && !God(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  if (!controls(player, thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  if ((!arg2 && !*arg2) || !strcasecmp(arg2, "me"))
+    new_owner = player;
+  else
+    new_owner = lookup_player(arg2);
+  if (new_owner == NOTHING) {
+    notify(player, T("I can't find that player"));
+    return;
+  }
+
+  ptr = atr_get_noparent(thing, strupper(p));
+  if (ptr && Can_Read_Attr(player, thing, ptr)) {
+    if (Can_Write_Attr(player, thing, ptr)) {
+      if (!controls(player, Owner(thing))) {
+        notify(player, T("You can only chown an attribute to yourself."));
+        return;
+      }
+      AL_CREATOR(ptr) = ooref != NOTHING ? Owner(ooref) : Owner(new_owner);
+      notify(player, T("Attribute owner changed."));
+      return;
+    } else {
+      notify(player, T("You don't have the permission to chown that."));
+      return;
+    }
+  } else
+    notify(player, T("No such attribute."));
+}
+
+
+/** Return the head of the attribute free list.
+ * This function returns the head of the attribute free list. If there
+ * are no more ATTR's on the free list, allocate a new page.
+ * \return the pointer to the head of the attribute free list.
+ */
+static ATTR *
+get_atr_free_list(void)
+{
+  if (!atr_free_list) {
+    ATTRPAGE *page;
+    int j;
+    page = (ATTRPAGE *) mush_malloc(sizeof(ATTRPAGE), "ATTRPAGE");
+    if (!page)
+      mush_panic("Couldn't allocate memory in get_atr_free_list");
+    for (j = 0; j < ATTRS_PER_PAGE - 1; j++)
+      AL_NEXT(page->atrs + j) = page->atrs + j + 1;
+    AL_NEXT(page->atrs + ATTRS_PER_PAGE - 1) = NULL;
+    atr_free_list = page->atrs;
+  }
+  return atr_free_list;
+}
+
+/** Traversal routine for Can_Read_Attr.
+ * This function determines if an attribute can be read by examining
+ * the tree path to the attribute.  This is not the full Can_Read_Attr
+ * check; only the stuff after See_All (just to avoid function calls
+ * when the answer is trivialized by special powers).  If the specified
+ * player is NOTHING, then we're doing a generic mortal visibility check.
+ * \param player the player trying to do the read.
+ * \param obj the object targetted for the read (may be a child of a parent!).
+ * \param atr the attribute being interrogated.
+ * \retval 0 if the player cannot read the attribute.
+ * \retval 1 if the player can read the attribute.
+ */
+int
+can_read_attr_internal(dbref player, dbref obj, ATTR * atr)
+{
+  static char name[ATTRIBUTE_NAME_LIMIT + 1];
+  char *p;
+  int cansee;
+  int canlook;
+  int comp;
+  dbref target;
+  dbref ancestor;
+  int visible;
+  int parent_depth;
+  int r_lock;
+
+  visible = (player == NOTHING);
+
+  /* Do an easy yes.. If they can write, it they can read it */
+  if (Can_Write_Attr(player, obj, atr))
+    return 1;
+
+  /* Evaluate read lock first here */
+  if (AL_RLock(atr) == TRUE_BOOLEXP)
+    r_lock = 1;
+  else
+    r_lock = (eval_boolexp(player, AL_RLock(atr), obj, NULL)
+              || controls(player, AL_CREATOR(atr)));
+
+  if (visible) {
+    cansee = ((Visual(obj) &&
+               eval_lock(PLAYER_START, obj, Examine_Lock) &&
+               eval_lock(MASTER_ROOM, obj, Examine_Lock))) &&
+        (AL_CREATOR(atr) == Owner(player) || r_lock);
+    canlook = 0;
+  } else {
+    cansee = ((controls(player, obj) && r_lock)
+              || ((Visual(obj) && eval_lock(player, obj, Examine_Lock)))
+              || div_cansee(player, obj))
+        && (AL_CREATOR(atr) == Owner(player) || r_lock);
+    canlook = can_look_at(player, obj);
+  }
+
+  /* Take an easy out if there is one... */
+  /* If we can't see the attribute itself, then that's easy. */
+  if (AF_Internal(atr) || (!Admin(player) && (AF_Mdark(atr) ||
+                                              !(cansee
+                                                ||
+                                                ((AF_Visual(atr)
+                                                  ||
+                                                  ((AL_RLock(atr) !=
+                                                    TRUE_BOOLEXP)
+                                                   && r_lock))
+                                                 && (!AF_Nearby(atr)
+                                                     || canlook))
+                                                || (!visible
+                                                    && !Mistrust(player)
+                                                    &&
+                                                    (Owner(AL_CREATOR(atr))
+                                                     == Owner(player)))))))
+    return 0;
+
+  /* If the attribute isn't on a branch, then that's also easy. */
+  if (!strchr(AL_NAME(atr), '`'))
+    return 1;
+  /* Nope, we actually have to go looking for the attribute in a tree. */
+  strcpy(name, AL_NAME(atr));
+  ancestor = Ancestor_Parent(obj);
+  target = obj;
+  parent_depth = 0;
+  while (parent_depth < MAX_PARENTS && GoodObject(target)) {
+    /* If the ancestor of the object is in its explict parent chain,
+     * we use it there, and don't check the ancestor later.
+     */
+    if (target == ancestor)
+      ancestor = NOTHING;
+    atr = List(target);
+    /* Check along the branch for permissions... */
+    for (p = strchr(name, '`'); p; p = strchr(p + 1, '`')) {
+      *p = '\0';
+      while (atr) {
+        comp = strcoll(name, AL_NAME(atr));
+        if (comp < 0) {
+          *p = '`';
+          goto continue_target;
+        }
+        if (comp == 0)
+          break;
+        atr = AL_NEXT(atr);
+      }
+      if (!atr || (target != obj && AF_Private(atr))) {
+        *p = '`';
+        goto continue_target;
+      }
+
+      if (AF_Internal(atr) || (!Admin(player) && (AF_Mdark(atr) ||
+                                                  !(cansee
+                                                    ||
+                                                    ((AF_Visual(atr)
+                                                      ||
+                                                      ((AL_RLock(atr) !=
+                                                        TRUE_BOOLEXP)
+                                                       && r_lock))
+                                                     && (!AF_Nearby(atr)
+                                                         || canlook))
+                                                    || (!visible
+                                                        &&
+                                                        !Mistrust(player)
+                                                        &&
+                                                        (Owner
+                                                         (AL_CREATOR(atr))
+                                                         ==
+                                                         Owner
+                                                         (player)))))))
+        return 0;
+
+      *p = '`';
+    }
+
+    /* Now actually find the attribute. */
+    while (atr) {
+      comp = strcoll(name, AL_NAME(atr));
+      if (comp < 0)
+        break;
+      if (comp == 0)
+        return 1;
+      atr = AL_NEXT(atr);
+    }
+
+  continue_target:
+
+    /* Attribute wasn't on this object.  Check a parent or ancestor. */
+    parent_depth++;
+    target = Parent(target);
+    if (!GoodObject(target)) {
+      parent_depth = 0;
+      target = ancestor;
+    }
+  }
+
+  return 0;
+}
+
+/** Traversal routine for Can_Write_Attr.
+ * This function determines if an attribute can be written by examining
+ * the tree path to the attribute.  As a side effect, missing_name is
+ * set to the name of a missing prefix branch, if any.  Yes, side effects
+ * are evil.  Please fix if you can.
+ * \param player the player trying to do the write.
+ * \param obj the object targetted for the write.
+ * \param atr the attribute being interrogated.
+ * \param safe whether to check the safe attribute flag.
+ * \retval 0 if the player cannot write the attribute.
+ * \retval 1 if the player can write the attribute.
+ */
+
+int
+can_write_attr_internal(dbref player, dbref obj, ATTR * atr, int safe)
+{
+  char *p;
+  missing_name[0] = '\0';
+  if (Cannot_Write_This_Attr(player, atr, obj, safe, !atr_match(atr->name), AL_CREATOR(atr)))
+    return 0;
+  strcpy(missing_name, AL_NAME(atr));
+  atr = List(obj);
+  for (p = strchr(missing_name, '`'); p; p = strchr(p + 1, '`')) {
+    *p = '\0';
+    atr = find_atr_in_list(atr, missing_name);
+    if (!atr)
+      return 0;
+    if (Cannot_Write_This_Attr(player, atr, obj, safe, !atr_match(atr->name), AL_CREATOR(atr))) {
+      missing_name[0] = '\0';
+      return 0;
+    }
+    *p = '`';
+  }
+
+  return 1;
+}
+
+/** Return the compressed data for an attribute.
+ * This is a chokepoint function for accessing the chunk data.
+ * \param atr the attribute struct from which to get the data reference.
+ * \return a pointer to the compressed data, in a static buffer.
+ */
+unsigned char const *
+atr_get_compressed_data(ATTR * atr)
+{
+  static unsigned char buffer[BUFFER_LEN * 2];
+  unsigned int len;
+  if (!atr->data)
+    return (unsigned char *) "";
+  len = chunk_fetch(atr->data, buffer, sizeof(buffer));
+  if (len > sizeof(buffer))
+    return (unsigned char *) "";
+  buffer[len] = '\0';
+  return buffer;
+}
+
+/** Return the uncompressed data for an attribute in a static buffer.
+ * This is a wrapper function, to centralize the use of compression/
+ * decompression on attributes.
+ * \param atr the attribute struct from which to get the data reference.
+ * \return a pointer to the uncompressed data, in a static buffer.
+ */
+char *
+atr_value(ATTR * atr)
+{
+  return uncompress(atr_get_compressed_data(atr));
+}
+
+/** Return the uncompressed data for an attribute in a dynamic buffer.
+ * This is a wrapper function, to centralize the use of compression/
+ * decompression on attributes.
+ * \param atr the attribute struct from which to get the data reference.
+ * \return a pointer to the uncompressed data, in a dynamic buffer.
+ */
+char *
+safe_atr_value(ATTR * atr)
+{
+  return safe_uncompress(atr_get_compressed_data(atr));
+}
diff --git a/src/boolexp.c b/src/boolexp.c
new file mode 100644 (file)
index 0000000..e105987
--- /dev/null
@@ -0,0 +1,2138 @@
+/**
+ * \file boolexp.c
+ *
+ * \brief Boolean expression parser.
+ *
+ * This code implements a parser for boolean expressions of the form
+ * used in locks. Summary of parsing rules, lowest to highest precedence:
+ * \verbatim
+ * E -> T; E -> T | E                   (or)
+ * T -> F; T -> F & T                   (and)
+ * F -> !F;F -> A                       (not)
+ * A -> @L; A -> I                      (indirect)
+ * I -> =Identifier ; I -> C            (equality)
+ * C -> +Identifier ; C -> O            (carry)
+ * O -> $Identifier ; O -> L            (owner)
+ * L -> (E); L -> eval/attr/flag lock   (parens, special atoms)
+ * L -> E, L is an object name or dbref or #t* or #f*   (simple atoms)
+ * \endverbatim
+ *
+ * Previously, the boolexp code just used a parse tree of the
+ * boolexp. Now, it turns the parse tree into bytecode that can be
+ * stored in the chunk manager. It probably also evaluates faster, but
+ * no profiling has been done to support this claim. It certainly
+ * involves less non-tail recursion.
+ *
+ * It's a three-stage process. First, the lock string is turned into a
+ * parse tree. Second, the tree is walked and "assembler" instructions
+ * are generated, including labels for jumps. Third, the "assembly" is
+ * stepped through and bytecode emitted, with labeled jumps replaced
+ * by distances that are offsets from the start of the
+ * bytecode. Pretty standard stuff.
+ *
+ * Each bytecode instruction is 5 bytes long (1 byte opcode + 4 byte
+ * int argument), and the minimum number of instructions in a compiled
+ * boolexp is 2, for a minimum size of 10 bytes. Compare this to the
+ * size of one parse-tree node, 16 bytes. Savings appear to be
+ * substantial, especially with complex locks with lots of ors or
+ * ands.
+ *
+ * Many lock keys have string arguments. The strings are standard
+ * 0-terminated C strings stored in a section of the same string as
+ * the bytecode instructions, starting right after the last
+ * instruction. They're accessed by offset from the start of the
+ * bytecode string. If the same string appears multiple times in the
+ * lock, only one copy is actually present in the string section.
+ *
+ * The VM for the bytecode is a simple register-based one.  The
+ * registers are R, the result register, set by test instructions and
+ * a few others, and S, the string register, which holds the extra
+ * string in the few tests that need two (A:B, A/B). There are
+ * instructions for each lock key type. There's a few extra ones to
+ * make decompiling back into a string dead easy. Nothing very
+ * complex.
+ *
+ * Future directions?
+ * \verbatim
+ * [Development] Raevnos is tempted in passing to re-write the boolexp parser in
+ * lex and yacc.
+ * [Development] Brazil laughs.
+ * [Development] Brazil says, "That might not be a bad idea."
+ * [Development] Raevnos has redone everything else in boolexp.c in Penn, so why
+ * not? :)
+ * [Development] Raevnos says, "Using the justification that it's a lot easier to
+ * expand the langage by adding new key types that way."
+ * \endverbatim
+ *
+ * So now you know who to blame if that particular item appears in a
+ * changelog for Penn or MUX.
+ *
+ * On a more serious note, a) #1234 is equivalent to b)
+ * =#1234|+#1234. Detecting b and turning it into a, or vis versa,
+ * would be easy to do. a is a common key, but turning it into b gets
+ * rid of a test instruction in the VM, at the cost of more
+ * instructions in generated lock bytecode. CISC or RISC? :) It's also
+ * easy to turn !!foo into foo, but nobody makes locks like that. Same
+ * with !#true and !#false. Of possibly more interest is rearranging
+ * the logic when ands, ors and nots are being used together. For
+ * example, !a|!b can become !(a&b).
+ *
+ * There's more useful room for improvement in the lock
+ * @warnings. Checking things like flag and power keys for valid flags
+ * comes to mind.
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include "conf.h"
+#include "mushdb.h"
+#include "match.h"
+#include "externs.h"
+#include "lock.h"
+#include "parse.h"
+#include "attrib.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "log.h"
+#include "extchat.h"
+#include "strtree.h"
+#include "confmagic.h"
+
+
+#ifdef WIN32
+#pragma warning( disable : 4761)        /* disable warning re conversion */
+#endif
+
+/* #define DEBUG_BYTECODE */
+
+/** Parse tree node types */
+typedef enum boolexp_type {
+  BOOLEXP_AND, /**< A&B */
+  BOOLEXP_OR, /**< A|B */
+  BOOLEXP_NOT, /**< !A */
+  BOOLEXP_CONST, /**< A */
+  BOOLEXP_ATR, /**< A:B */
+  BOOLEXP_IND, /**< @A/B */
+  BOOLEXP_CARRY, /**< +A */
+  BOOLEXP_IS, /**< =A */
+  BOOLEXP_OWNER, /**< $A */
+  BOOLEXP_EVAL, /**< A/B */
+  BOOLEXP_FLAG, /**< A^B */
+  BOOLEXP_BOOL /**< #true, #false */
+} boolexp_type;
+
+/** An attribute lock specification for the parse tree.
+ * This structure is a piece of a boolexp that's used to store 
+ * attribute locks (CANDO:1), eval locks (CANDO/1), and flag locks
+ * FLAG^WIZARD.
+ */
+struct boolatr {
+  const char *name;             /**< Name of attribute, flag, etc. to test */
+  char text[BUFFER_LEN];        /**< Value to test against */
+};
+
+/** A boolean expression parse tree node.
+ * Boolean expressions are most widely used in locks. This structure
+ * is a general representation of the possible boolean expressions
+ * that can be specified in MUSHcode. It's used internally by the lock
+ * compiler.
+ */
+struct boolexp_node {
+  /** Type of expression.
+   * The type of expressio is one of the boolexp_type's, such as
+   * and, or, not, constant, attribute, indirect, carry, is,
+   * owner, eval, flag, etc.
+   */
+  boolexp_type type;
+  dbref thing;                  /**< An object, or a boolean val */
+  /** The expression itself.
+   * This union comprises the various possible types of data we
+   * might need to represent any of the expression types.
+   */
+  union {
+    /** And and or locks: combinations of boolexps.
+     * This union member is used with and and or locks.
+     */
+    struct {
+      struct boolexp_node *a;   /**< One boolean expression */
+      struct boolexp_node *b;   /**< Another boolean expression */
+    } sub;
+    struct boolexp_node *n;             /**< Not locks: boolean expression to negate */
+    struct boolatr *atr_lock;   /**< Atr, eval and flag locks */
+    const char *ind_lock;       /**< Indirect locks */
+  } data;
+};
+
+
+/** The opcodes supported by the boolexp virtual machine. */
+typedef enum bvm_opcode {
+  OP_JMPT, /**< Jump to ARG if R is true */
+  OP_JMPF, /**< Jump to ARG if R is false */
+  OP_TCONST, /**< Tests plain #ARG */
+  OP_TATR, /**< Tests S:ARG */
+  OP_TIND, /**< Tests @#ARG/S */
+  OP_TCARRY, /**< Tests +#ARG */
+  OP_TIS, /**< Tests =#ARG */
+  OP_TOWNER, /**< Tests $#ARG */
+  OP_TEVAL, /**< Tests S/ARG */
+  OP_TFLAG, /**< Tests FLAG^ARG */
+  OP_TTYPE, /**< Tests TYPE^ARG */
+  OP_TPOWER, /**< Tests POWER^ARG */
+#ifdef CHAT_SYSTEM
+  OP_TCHANNEL, /**< Tests CHANNEL^ARG */
+#endif /* CHAT_SYSTEM */
+  OP_TIP, /**< Tests IP^ARG */
+  OP_THOSTNAME, /**< Tests HOSTNAME^ARG */
+  OP_TOBJID, /**< Tests OBJID^ARG */
+  OP_TDBREFLIST, /**< Tests DBREFLIST^ARG */
+  OP_TDIVISION, /**< Tests DIVISION^ARG */
+  OP_TLEVEL, /**< Tests Level */
+  OP_TPWRGRP, /**< Tests PowerGroup */
+  OP_TSWITCHES, /**< Test for Switch */
+  OP_LOADS, /**< Load ARG into S */
+  OP_LOADR, /**< Load ARG into R */
+  OP_NEGR,  /**< Negate R */
+  OP_PAREN, /**< ARG = 0 for a (, ARG = 1 for a ) in decompiling */
+  OP_LABEL, /**< A label. Not actually in compiled bytecode */
+  OP_RET    /**< Stop evaluating bytecode */
+} bvm_opcode;
+
+/** The size of a single bytecode instruction. Probably 5 bytes
+ * everywhere. */
+#define INSN_LEN (1 + sizeof(int))
+
+/** Information describing one VM instruction or label in the
+ * intermediate "assembly" generated from a parse tree. The nodes are
+ * part of a linked list.  */
+struct bvm_asmnode {
+  bvm_opcode op; /**< The opcode */
+  int arg; /**< The arg value, or a label or string number */
+  struct bvm_asmnode *next; /**< Pointer to the next node */
+};
+
+/** Information describing a string to emit in the string section of
+ * the bytecode. The nodes are part of a linked list.  */
+struct bvm_strnode {
+  char *s; /**< The string */
+  int len; /**< Its length */
+  struct bvm_strnode *next; /**< Pointer to the next node */
+};
+
+/** A struct describing the complete assembly information needed to
+ * generate bytecode */
+struct bvm_asm {
+  struct bvm_asmnode *head; /**< The start of the list of assembly instructions */
+  struct bvm_asmnode *tail; /**< The end of the list */
+  struct bvm_strnode *shead; /**< The start of the list of strings */
+  struct bvm_strnode *stail; /**< The end of the list */
+  int label; /**< The current label id to use */
+  int strcount; /**< The number of nodes in the string list */
+};
+
+/** The flag lock key (A^B) only allows a few values for A. This
+ * struct and the the following table define the allowable ones. When
+ * adding a new type here, a matching new bytecode instruction should
+ * be added. */
+struct flag_lock_types {
+  const char *name; /**< The value of A */
+  bvm_opcode op;  /**< The associated opcode */
+};
+
+/** What's allowed on the left-hand-side of LHS^RHS lock keys */
+static struct flag_lock_types flag_locks[] = {
+  {"FLAG", OP_TFLAG},
+  {"POWER", OP_TPOWER},
+  {"TYPE", OP_TTYPE},
+#ifdef CHAT_SYSTEM
+  {"CHANNEL", OP_TCHANNEL},
+#endif /* CHAT_SYSTEM */
+  {"OBJID", OP_TOBJID},
+  {"IP", OP_TIP},
+  {"HOSTNAME", OP_THOSTNAME},
+  {"DBREFLIST", OP_TDBREFLIST},
+  {"DIVISION", OP_TDIVISION},
+  {"SWITCH", OP_TSWITCHES},
+  {"LEVEL", OP_TLEVEL},
+  {"POWERGROUP", OP_TPWRGRP},
+  {NULL, 0}
+};
+
+static unsigned char *
+safe_get_bytecode(boolexp b)
+    __attribute_malloc__;
+static unsigned char *
+get_bytecode(boolexp b, u_int_16 * storelen);
+static struct boolexp_node *
+alloc_bool(void)
+    __attribute_malloc__;
+static struct boolatr *
+alloc_atr(const char *name, const char *s)
+    __attribute_malloc__;
+static void
+skip_whitespace(void);
+static void
+free_bool(struct boolexp_node *b);
+static struct boolexp_node *
+test_atr(char *s, char c);
+static struct boolexp_node *
+parse_boolexp_R(void);
+static struct boolexp_node *
+parse_boolexp_L(void);
+static struct boolexp_node *
+parse_boolexp_O(void);
+static struct boolexp_node *
+parse_boolexp_C(void);
+static struct boolexp_node *
+parse_boolexp_I(void);
+static struct boolexp_node *
+parse_boolexp_A(void);
+static struct boolexp_node *
+parse_boolexp_F(void);
+static struct boolexp_node *
+parse_boolexp_T(void);
+static struct boolexp_node *
+parse_boolexp_E(void);
+static int
+check_attrib_lock(dbref player, dbref target,
+                  const char *atrname, const char *str);
+static void
+free_boolexp_node(struct boolexp_node *b);
+static int
+gen_label_id(struct bvm_asm *a);
+static void
+append_insn(struct bvm_asm *a, bvm_opcode op, int arg, const char *s);
+static struct bvm_asm *
+generate_bvm_asm(struct boolexp_node *b)
+    __attribute_malloc__;
+static void
+generate_bvm_asm1(struct bvm_asm *a, struct boolexp_node *b,
+                  boolexp_type outer);
+static int
+pos_of_label(struct bvm_asm *a, int label);
+static int
+offset_to_string(struct bvm_asm *a, int c);
+static struct bvm_asmnode *
+insn_after_label(struct bvm_asm *a, int label);
+static void
+opt_thread_jumps(struct bvm_asm *a);
+static void
+optimize_bvm_asm(struct bvm_asm *a);
+static boolexp
+emit_bytecode(struct bvm_asm *a, int derefs);
+static void
+free_bvm_asm(struct bvm_asm *a);
+#ifdef DEBUG_BYTECODE
+static int
+sizeof_boolexp_node(struct boolexp_node *b);
+static void
+print_bytecode(boolexp b);
+#endif
+extern void complain
+    (dbref player, dbref i, const char *name, const char *desc, ...)
+    __attribute__ ((__format__(__printf__, 4, 5)));
+void
+check_lock(dbref player, dbref i, const char *name, boolexp be);
+int
+warning_lock_type(const boolexp l);
+
+
+/** String tree of attribute names. Used in the parse tree. Might go
+ * away as the trees aren't persistant any more. */
+extern StrTree atr_names;
+/** String tree of lock names. Used in the parse tree. Might go away
+ * as the trees aren't persistant any more. */
+extern StrTree lock_names;
+/** Are we currently loading the db? If so, we avoid certain checks that
+ * would create a circularity.
+ */
+extern int loading_db;
+/* We need the stuff for SWITCH_VALUE and switch_mask */
+extern SWITCH_VALUE switch_list[];
+
+/** Given a chunk id, return the bytecode for a boolexp.  
+ * \param b the boolexp to retrieve.
+ * \return a malloced copy of the bytecode.
+ */
+static unsigned char *
+safe_get_bytecode(boolexp b)
+{
+  unsigned char *bytecode;
+  u_int_16 len;
+
+  len = chunk_len(b);
+  bytecode = mush_malloc(len, "boolexp.bytecode");
+  chunk_fetch(b, bytecode, len);
+  return bytecode;
+}
+
+/** Given a chunk id, return the bytecode for a boolexp.  
+ * \param b The boolexp to retrieve.
+ * \return a static copy of the bytecode.
+ */
+static unsigned char *
+get_bytecode(boolexp b, u_int_16 * storelen)
+{
+  unsigned static char bytecode[BUFFER_LEN * 2];
+  u_int_16 len;
+
+  len = chunk_fetch(b, bytecode, sizeof bytecode);
+  if (storelen)
+    *storelen = len;
+  return bytecode;
+}
+
+/* Public functions */
+
+/** Copy a boolexp.
+ * This function makes a copy of a boolexp, allocating new memory for
+ * the copy.
+ * \param b a boolexp to copy.
+ * \return an allocated copy of the boolexp.
+ */
+boolexp
+dup_bool(boolexp b)
+{
+  boolexp r;
+  unsigned char *bytecode;
+  u_int_16 len = 0;
+
+  if (b == TRUE_BOOLEXP)
+    return TRUE_BOOLEXP;
+
+  bytecode = get_bytecode(b, &len);
+
+  r = chunk_create(bytecode, len, 1);
+
+  return r;
+}
+
+/** Free a boolexp
+ * This function deallocates a boolexp
+ * \param b a boolexp to delete
+ */
+void
+free_boolexp(boolexp b)
+{
+  if (b != TRUE_BOOLEXP)
+    chunk_delete(b);
+}
+
+/** Determine the memory usage of a boolexp.
+ * This function computes the total memory usage of a boolexp.
+ * \param b boolexp to analyze.
+ * \return size of boolexp in bytes.
+ */
+int
+sizeof_boolexp(boolexp b)
+{
+
+  if (b == TRUE_BOOLEXP)
+    return 0;
+  else
+    return chunk_len(b);
+}
+
+/** Evaluate a boolexp.
+ * This is the main function to be called by other hardcode. It
+ * determines whether a player can pass a boolexp lock on a given
+ * object.
+ * \param player the player trying to pass the lock.
+ * \param b the boolexp to evaluate.
+ * \param target the object with the lock.
+ * \retval 0 player fails to pass lock.
+ * \retval 1 player successfully passes lock.
+ */
+int
+eval_boolexp(dbref player /* The player trying to pass */ ,
+             boolexp b /* The boolexp */ ,
+             dbref target /* The object with the lock */,
+            unsigned char * switches)
+{
+  static int boolexp_recursion = 0;
+
+  if (!GoodObject(player))
+    return 0;
+
+  if (boolexp_recursion > MAX_DEPTH) {
+    notify(player, T("Too much recursion in lock!"));
+    return 0;
+  }
+  if (b == TRUE_BOOLEXP) {
+    return 1;
+  } else {
+    bvm_opcode op;
+    int arg;
+    ATTR *a;
+    POWER *pwr;
+    int div, div2;
+    int r = 0;
+    char *s = NULL;
+    unsigned char *bytecode, *pc;
+
+    bytecode = pc = safe_get_bytecode(b);
+
+    while (1) {
+      op = (bvm_opcode) * pc;
+      memcpy(&arg, pc + 1, sizeof arg);
+      pc += INSN_LEN;
+      switch (op) {
+      case OP_RET:
+        goto done;
+      case OP_JMPT:
+        if (r)
+          pc = bytecode + arg;
+        break;
+      case OP_JMPF:
+        if (!r)
+          pc = bytecode + arg;
+        break;
+      case OP_LABEL:
+      case OP_PAREN:
+        break;
+      case OP_LOADS:
+        s = (char *) bytecode + arg;
+        break;
+      case OP_LOADR:
+        r = arg;
+        break;
+      case OP_NEGR:
+        r = !r;
+        break;
+      case OP_TCONST:
+        r = (GoodObject(arg)
+             && !IsGarbage(arg)
+             && (arg == player || member(arg, Contents(player))));
+        break;
+      case OP_TIS:
+        r = (GoodObject(arg)
+             && !IsGarbage(arg)
+             && arg == player);
+        break;
+      case OP_TCARRY:
+        r = (GoodObject(arg)
+             && !IsGarbage(arg)
+             && member(arg, Contents(player)));
+        break;
+      case OP_TOWNER:
+        r = (GoodObject(arg)
+             && !IsGarbage(arg)
+             && Owner(arg) == Owner(player));
+        break;
+      case OP_TIND:
+        /* We only allow evaluation of indirect locks if target can run
+         * the lock on the referenced object.
+         */
+        boolexp_recursion++;
+        if (!GoodObject(arg) || IsGarbage(arg))
+          r = 0;
+        else if (!Can_Read_Lock(target, arg, s))
+          r = 0;
+        else
+          r = eval_boolexp(player, getlock(arg, s), arg, switches);
+        boolexp_recursion--;
+        break;
+      case OP_TATR:
+        boolexp_recursion++;
+        a = atr_get(player, s);
+        if (!a || !Can_Read_Attr(target, player, a))
+          r = 0;
+        else {
+          char tbuf[BUFFER_LEN];
+          strcpy(tbuf, atr_value(a));
+          r = local_wild_match((char *) bytecode + arg, tbuf);
+        }
+        boolexp_recursion--;
+        break;
+      case OP_TEVAL:
+        boolexp_recursion++;
+        r = check_attrib_lock(player, target, s, (char *) bytecode + arg);
+        boolexp_recursion--;
+        break;
+      case OP_TSWITCHES:
+       if(switches) {
+         SWITCH_VALUE *sw_val; 
+         r = 0;
+
+          for(sw_val = switch_list; sw_val->name != NULL; sw_val++) 
+           if(SW_ISSET(switches, sw_val->value)
+              && !strcasecmp(sw_val->name, (char *) bytecode + arg)) {
+             r = 1;
+             break;
+           }
+       } else
+         r = 1;
+       break;
+      case OP_TFLAG:
+        /* Note that both fields of a boolattr struct are upper-cased */
+        if (sees_flag(target, player, (char *) bytecode + arg))
+          r = 1;
+        else
+          r = 0;
+        break;
+      case OP_TPOWER:
+        boolexp_recursion++;
+        if (God(player))
+          r = 1;
+        else if (!Can_Examine(target, player))
+          r = 0;
+        else {
+          /* check if they have ANY scope of the power */
+          if (*(bytecode + arg) == '=') {
+            if (!(pwr = find_power((char *) bytecode + arg + 1)))
+              r = 0;
+            else if (pwr) {
+              if (check_power_yescode(DPBITS(player), pwr) > NO)
+                r = 1;
+              else
+                r = 0;
+            }
+          } else {
+            if (!(pwr = find_power((char *) bytecode + arg)))
+              r = 0;
+            else if (pwr) {
+              if (check_power_yescode(DPBITS(player), pwr) > NO)
+                r = 1;
+              else if (Inherit(player) && Inheritable(Owner(player))
+                       && (check_power_yescode(DPBITS(Owner(player)), pwr)
+                           > NO))
+                r = 1;
+              else
+                r = 0;
+            }
+          }
+        }
+        boolexp_recursion--;
+        break;
+      case OP_TDIVISION:
+        boolexp_recursion++;
+        /* basicaly what we're doing is if we catch a '+' key
+         * we're going to loop upwards in the players division
+         * tree & tre to match it to the division lock somewhere
+         * upwards.
+         */
+        s = (char *) bytecode + arg;
+        if (*s != '+') {
+          div = parse_dbref(s);
+          r = (Division(player) == div);
+        } else {
+          s++;
+          div = parse_dbref(s);
+          r = (Division(player) == div);
+          div2 = Division(player);
+          while (!r && GoodObject(div2)) {
+            div2 = Division(div2);
+            r = (div2 == div);
+          }
+        }
+        boolexp_recursion--;
+        break;
+      case OP_TPWRGRP:
+        boolexp_recursion++;
+        s = (char *) bytecode + arg;
+        break;
+      case OP_TLEVEL:
+        boolexp_recursion++;
+        s = (char *) bytecode + arg;
+        switch (*s) {
+        case '>':
+          r = LEVEL(player) > parse_number(s + 1);
+          break;
+        case '<':
+          r = parse_number(s + 1) > LEVEL(player);
+          break;
+        default:
+          r = LEVEL(player) == parse_number(s);
+        }
+        boolexp_recursion--;
+        break;
+      case OP_TOBJID:
+        {
+          dbref d;
+          d = parse_objid((char *) bytecode + arg);
+          r = (player == d);
+          break;
+        }
+#ifdef CHAT_SYSTEM
+      case OP_TCHANNEL:
+        {
+          CHAN *chan;
+          boolexp_recursion++;
+          find_channel((char *) bytecode + arg, &chan, target);
+          r = chan && onchannel(player, chan);
+          boolexp_recursion--;
+        }
+        break;
+#endif /* CHAT_SYSTEM */
+      case OP_TIP:
+        boolexp_recursion++;
+        if (!Connected(Owner(player)))
+          r = 0;
+        else {
+          /* We use the attribute for permission checks, but we
+           * do the actual boolexp itself with the least idle
+           * descriptor's ip address.
+           */
+          a = atr_get(Owner(player), "LASTIP");
+          if (!a || !Can_Read_Attr(target, player, a))
+            r = 0;
+          else {
+            char *p = least_idle_ip(Owner(player));
+            r = p ? quick_wild((char *) bytecode + arg, p) : 0;
+          }
+        }
+        boolexp_recursion--;
+        break;
+      case OP_THOSTNAME:
+        boolexp_recursion++;
+        if (!Connected(Owner(player)))
+          r = 0;
+        else {
+          /* See comment for OP_TIP */
+          a = atr_get(Owner(player), "LASTSITE");
+          if (!a || !Can_Read_Attr(target, player, a))
+            r = 0;
+          else {
+            char *p = least_idle_hostname(Owner(player));
+            r = p ? quick_wild((char *) bytecode + arg, p) : 0;
+          }
+        }
+        boolexp_recursion--;
+        break;
+      case OP_TTYPE:
+        switch (bytecode[arg]) {
+        case 'R':
+          r = Typeof(player) == TYPE_ROOM;
+          break;
+        case 'E':
+          r = Typeof(player) == TYPE_EXIT;
+          break;
+        case 'T':
+          r = Typeof(player) == TYPE_THING;
+          break;
+        case 'P':
+          r = Typeof(player) == TYPE_PLAYER;
+          break;
+        case 'D':
+          r = Typeof(player) == TYPE_DIVISION;
+          break;
+        }
+        break;
+      case OP_TDBREFLIST:
+       {
+         char *idstr, *curr, *orig;
+         dbref mydb;
+         
+         r = 0;
+         a = atr_get(target, (char *) bytecode + arg);
+         if (!a)
+           break;
+           
+         orig = safe_atr_value(a);
+         idstr = trim_space_sep(orig, ' ');
+         
+         while ((curr = split_token(&idstr, ' ')) != NULL) {
+           mydb = parse_objid(curr);
+           if (mydb == player) {
+             r = 1; 
+             break;
+           }
+         }
+         free((Malloc_t) orig);
+       }
+       break;
+      default:
+        do_log(LT_ERR, 0, 0, "Bad boolexp opcode %d %d in object #%d",
+               op, arg, target);
+        report();
+        r = 0;
+      }
+    }
+  done:
+    mush_free(bytecode, "boolexp.bytecode");
+    return r;
+  }
+}
+
+/** Pretty-print object references for unparse_boolexp().
+ * \param player the object seeing the decompiled lock.
+ * \param thing the object referenced in the lock.
+ * \param flag How to print thing.
+ * \param buff The start of the output buffer.
+ * \param bp Pointer to the current position in buff.
+ * \return 0 on success, true on buffer overflow.
+ */
+static int
+safe_boref(dbref player, dbref thing, enum u_b_f flag, char *buff,
+           char **bp)
+{
+  switch (flag) {
+  case UB_MEREF:
+    if (player == thing)
+      return safe_strl("me", 2, buff, bp);
+    else
+      return safe_dbref(thing, buff, bp);
+  case UB_DBREF:
+    return safe_dbref(thing, buff, bp);
+  case UB_ALL:
+  default:
+    return safe_str(unparse_object(player, thing), buff, bp);
+  }
+}
+
+/** Display a boolexp.
+ * This function returns the textual representation of the boolexp.
+ * \param player The object wanting the decompiled boolexp.
+ * \param b The boolexp to decompile.
+ * \param flag How to format objects in the result.
+ * \return a static string with the decompiled boolexp.
+ */
+char *
+unparse_boolexp(dbref player, boolexp b, enum u_b_f flag)
+{
+  static char boolexp_buf[BUFFER_LEN];
+  char *buftop = boolexp_buf;
+  unsigned char *bytecode = NULL;
+
+  if (b == TRUE_BOOLEXP)
+    safe_str("*UNLOCKED*", boolexp_buf, &buftop);
+  else {
+    bvm_opcode op;
+    int arg;
+    unsigned char *pc;
+    unsigned char *s = NULL;
+
+    bytecode = pc = get_bytecode(b, NULL);
+
+    while (1) {
+      op = (bvm_opcode) * pc;
+      memcpy(&arg, pc + 1, sizeof arg);
+      pc += INSN_LEN;
+      /* Handle most negation cases */
+      if (op != OP_RET && (bvm_opcode) * pc == OP_NEGR && op != OP_PAREN)
+        safe_chr('!', boolexp_buf, &buftop);
+      switch (op) {
+      case OP_JMPT:
+        safe_chr('|', boolexp_buf, &buftop);
+        break;
+      case OP_JMPF:
+        safe_chr('&', boolexp_buf, &buftop);
+        break;
+      case OP_RET:
+        goto done;
+      case OP_LABEL:           /* Will never happen, but shuts up the compiler */
+      case OP_NEGR:
+        break;
+      case OP_LOADS:
+        s = bytecode + arg;
+        break;
+      case OP_LOADR:
+        if (arg)
+          safe_str("#TRUE", boolexp_buf, &buftop);
+        else
+          safe_str("#FALSE", boolexp_buf, &buftop);
+        break;
+      case OP_PAREN:
+        if (arg == 0) {
+          int pstack = 1, parg;
+          unsigned char *tpc = pc;
+          while (1) {
+            if ((bvm_opcode) * tpc == OP_PAREN) {
+              memcpy(&parg, tpc + 1, sizeof parg);
+              if (parg)
+                pstack--;
+              else
+                pstack++;
+              if (pstack == 0) {
+                tpc += INSN_LEN;
+                break;
+              }
+            }
+            tpc += INSN_LEN;
+          }
+          if ((bvm_opcode) * tpc == OP_NEGR)
+            safe_strl("!(", 2, boolexp_buf, &buftop);
+          else
+            safe_chr('(', boolexp_buf, &buftop);
+        } else if (arg == 1)
+          safe_chr(')', boolexp_buf, &buftop);
+        break;
+      case OP_TCONST:
+        safe_boref(player, arg, flag, boolexp_buf, &buftop);
+        break;
+      case OP_TATR:
+        safe_format(boolexp_buf, &buftop, "%s:%s", s, bytecode + arg);
+        break;
+      case OP_TIND:
+        safe_chr(AT_TOKEN, boolexp_buf, &buftop);
+        safe_boref(player, arg, flag, boolexp_buf, &buftop);
+        safe_format(boolexp_buf, &buftop, "/%s", s);
+        break;
+      case OP_TCARRY:
+        safe_chr(IN_TOKEN, boolexp_buf, &buftop);
+        safe_boref(player, arg, flag, boolexp_buf, &buftop);
+        break;
+      case OP_TIS:
+        safe_chr(IS_TOKEN, boolexp_buf, &buftop);
+        safe_boref(player, arg, flag, boolexp_buf, &buftop);
+        break;
+      case OP_TOWNER:
+        safe_chr(OWNER_TOKEN, boolexp_buf, &buftop);
+        safe_boref(player, arg, flag, boolexp_buf, &buftop);
+        break;
+      case OP_TEVAL:
+        safe_format(boolexp_buf, &buftop, "%s/%s", s, bytecode + arg);
+        break;
+      case OP_TFLAG:
+        safe_format(boolexp_buf, &buftop, "FLAG^%s", bytecode + arg);
+        break;
+      case OP_TTYPE:
+        safe_format(boolexp_buf, &buftop, "TYPE^%s", bytecode + arg);
+        break;
+      case OP_TPOWER:
+        safe_format(boolexp_buf, &buftop, "POWER^%s", bytecode + arg);
+        break;
+      case OP_TDIVISION:
+        safe_format(boolexp_buf, &buftop, "DIVISION^%s", bytecode + arg);
+        break;
+      case OP_TLEVEL:
+        safe_format(boolexp_buf, &buftop, "LEVEL^%s", bytecode + arg);
+        break;
+      case OP_TPWRGRP:
+        safe_format(boolexp_buf, &buftop, "POWERGROUP^%s", bytecode + arg);
+        break;
+      case OP_TOBJID:
+        safe_format(boolexp_buf, &buftop, "OBJID^%s", bytecode + arg);
+        break;
+#ifdef CHAT_SYSTEM
+      case OP_TCHANNEL:
+        safe_format(boolexp_buf, &buftop, "CHANNEL^%s", bytecode + arg);
+        break;
+#endif /* CHAT_SYSTEM */
+      case OP_TIP:
+        safe_format(boolexp_buf, &buftop, "IP^%s", bytecode + arg);
+        break;
+      case OP_THOSTNAME:
+        safe_format(boolexp_buf, &buftop, "HOSTNAME^%s", bytecode + arg);
+        break;
+      case OP_TSWITCHES:
+       safe_format(boolexp_buf, &buftop, "SWITCHES^%s", bytecode + arg);
+       break;
+      case OP_TDBREFLIST:
+       safe_format(boolexp_buf, &buftop, "DBREFLIST^%s", bytecode + arg);
+       break;
+      }
+    }
+  }
+done:
+  *buftop++ = '\0';
+
+  return boolexp_buf;
+}
+
+/* Parser and parse-tree related functions. If the parser returns NULL, you lose */
+/** The source string for the lock we're parsing */
+static const char *parsebuf;
+/** The player from whose perspective we're parsing */
+static dbref parse_player;
+/** The name of the lock we're parsing */
+static lock_type parse_ltype;
+
+/** Allocate a boolatr for a parse tree node.
+ * \param name the name of the attribute.
+ * \param s a pattern to match against.
+ * \return a newly allocated boolatr.
+ */
+static struct boolatr *
+alloc_atr(const char *name, const char *s)
+{
+  struct boolatr *a;
+  size_t len;
+
+  if (s)
+    len = strlen(s) + 1;
+  else
+    len = 1;
+
+  a = (struct boolatr *)
+      mush_malloc(sizeof(struct boolatr) - BUFFER_LEN + len, "boolatr");
+  if (!a)
+    return NULL;
+  a->name = st_insert(strupper(name), &atr_names);
+  if (!a->name) {
+    mush_free(a, "boolatr");
+    return NULL;
+  }
+  if (s)
+    memcpy(a->text, s, len);
+  else
+    a->text[0] = '\0';
+  return a;
+}
+
+/** Returns a new boolexp_node for the parse tree.
+ * \return a new newly allocated boolexp_node.
+ */
+static struct boolexp_node *
+alloc_bool(void)
+{
+  struct boolexp_node *b;
+
+  b = mush_malloc(sizeof *b, "boolexp_node");
+
+  b->data.sub.a = NULL;
+  b->data.sub.b = NULL;
+  b->thing = NOTHING;
+
+  return b;
+}
+
+/** Frees a boolexp node.
+ * \param b the boolexp_node to deallocate.
+ */
+static void
+free_bool(struct boolexp_node *b)
+{
+  mush_free(b, "boolexp_node");
+}
+
+/** Free a boolexp ast node.
+ * This function frees a boolexp, including all subexpressions,
+ * recursively.
+ * \param b boolexp to free.
+ */
+static void
+free_boolexp_node(struct boolexp_node *b)
+{
+  if (b) {
+    switch (b->type) {
+    case BOOLEXP_AND:
+    case BOOLEXP_OR:
+      free_boolexp_node(b->data.sub.a);
+      free_boolexp_node(b->data.sub.b);
+      free_bool(b);
+      break;
+    case BOOLEXP_NOT:
+      free_boolexp_node(b->data.n);
+      free_bool(b);
+      break;
+    case BOOLEXP_CONST:
+    case BOOLEXP_CARRY:
+    case BOOLEXP_IS:
+    case BOOLEXP_OWNER:
+    case BOOLEXP_BOOL:
+      free_bool(b);
+      break;
+    case BOOLEXP_IND:
+      if (b->data.ind_lock)
+        st_delete(b->data.ind_lock, &lock_names);
+      free_bool(b);
+      break;
+    case BOOLEXP_ATR:
+    case BOOLEXP_EVAL:
+    case BOOLEXP_FLAG:
+      if (b->data.atr_lock) {
+        if (b->data.atr_lock->name)
+          st_delete(b->data.atr_lock->name, &atr_names);
+        mush_free((Malloc_t) b->data.atr_lock, "boolatr");
+      }
+      free_bool(b);
+      break;
+    }
+  }
+}
+
+/** Skip over leading whitespace characters in parsebuf */
+static void
+skip_whitespace()
+{
+  while (*parsebuf && isspace((unsigned char) *parsebuf))
+    parsebuf++;
+}
+
+static struct boolexp_node *
+test_atr(char *s, char c)
+{
+  struct boolexp_node *b;
+  char tbuf1[BUFFER_LEN];
+  strcpy(tbuf1, strupper(s));
+  for (s = tbuf1; *s && (*s != c); s++);
+  if (!*s)
+    return 0;
+  *s++ = 0;
+  if (strlen(tbuf1) == 0 || !good_atr_name(tbuf1))
+    return 0;
+  if (c == '^') {
+    int n;
+    for (n = 0; flag_locks[n].name; n++) {
+      if (strcmp(flag_locks[n].name, tbuf1) == 0)
+        break;
+    }
+    if (!flag_locks[n].name)
+      return 0;
+  }
+  b = alloc_bool();
+  if (c == ':')
+    b->type = BOOLEXP_ATR;
+  else if (c == '/')
+    b->type = BOOLEXP_EVAL;
+  else if (c == '^')
+    b->type = BOOLEXP_FLAG;
+  b->data.atr_lock = alloc_atr(tbuf1, s);
+  return b;
+}
+
+/* L -> E, L is an object name or dbref or #t* or #f* */
+static struct boolexp_node *
+parse_boolexp_R()
+{
+  struct boolexp_node *b;
+  char tbuf1[BUFFER_LEN];
+  char *p;
+  b = alloc_bool();
+  b->type = BOOLEXP_CONST;
+  p = tbuf1;
+  while (*parsebuf
+         && *parsebuf != AND_TOKEN && *parsebuf != '/'
+         && *parsebuf != OR_TOKEN && *parsebuf != ')') {
+    *p++ = *parsebuf++;
+  }
+  /* strip trailing whitespace */
+  *p-- = '\0';
+  while (isspace((unsigned char) *p))
+    *p-- = '\0';
+  /* do the match */
+  if (loading_db) {
+    if (*tbuf1 == '#' && *(tbuf1 + 1)) {
+      if (*(tbuf1 + 1) == 't' || *(tbuf1 + 1) == 'T') {
+        b->type = BOOLEXP_BOOL;
+        b->thing = 1;
+      } else if (*(tbuf1 + 1) == 'f' || *(tbuf1 + 1) == 'F') {
+        b->type = BOOLEXP_BOOL;
+        b->thing = 0;
+      } else {
+        b->thing = parse_integer(tbuf1 + 1);
+      }
+    } else {
+      /* Ooog. Dealing with a malformed lock in the database. */
+      free_bool(b);
+      return NULL;
+    }
+    return b;
+  } else {
+    /* Are these special atoms? */
+    if (*tbuf1 && *tbuf1 == '#' && *(tbuf1 + 1)) {
+      if (*(tbuf1 + 1) == 't' || *(tbuf1 + 1) == 'T') {
+        b->type = BOOLEXP_BOOL;
+        b->thing = 1;
+        return b;
+      } else if (*(tbuf1 + 1) == 'f' || *(tbuf1 + 1) == 'F') {
+        b->type = BOOLEXP_BOOL;
+        b->thing = 0;
+        return b;
+      }
+    }
+    b->thing =
+        match_result(parse_player, tbuf1, TYPE_THING, MAT_EVERYTHING);
+    if (b->thing == NOTHING) {
+      notify_format(parse_player, T("I don't see %s here."), tbuf1);
+      free_bool(b);
+      return NULL;
+    } else if (b->thing == AMBIGUOUS) {
+      notify_format(parse_player, T("I don't know which %s you mean!"),
+                    tbuf1);
+      free_bool(b);
+      return NULL;
+    } else {
+      return b;
+    }
+  }
+}
+
+/* L -> (E); L -> eval/attr/flag lock, (lock) */
+static struct boolexp_node *
+parse_boolexp_L()
+{
+  struct boolexp_node *b;
+  char *p;
+  const char *savebuf;
+  char tbuf1[BUFFER_LEN];
+  skip_whitespace();
+  switch (*parsebuf) {
+  case '(':
+    parsebuf++;
+    b = parse_boolexp_E();
+    skip_whitespace();
+    if (b == NULL || *parsebuf++ != ')') {
+      free_boolexp_node(b);
+      return NULL;
+    } else {
+      return b;
+    }
+    /* break; */
+  default:
+    /* must have hit an object ref */
+    /* load the name into our buffer */
+    p = tbuf1;
+    savebuf = parsebuf;
+    while (*parsebuf
+           && *parsebuf != AND_TOKEN
+           && *parsebuf != OR_TOKEN && *parsebuf != ')') {
+      *p++ = *parsebuf++;
+    }
+    /* strip trailing whitespace */
+    *p-- = '\0';
+    while (isspace((unsigned char) *p))
+      *p-- = '\0';
+    /* check for an attribute */
+    b = test_atr(tbuf1, ':');
+    if (b)
+      return b;
+    /* check for an eval */
+    b = test_atr(tbuf1, '/');
+    if (b)
+      return b;
+    /* Check for a flag */
+    b = test_atr(tbuf1, '^');
+    if (b)
+      return b;
+    /* Nope. Check for an object reference */
+    parsebuf = savebuf;
+    return parse_boolexp_R();
+  }
+}
+
+/* O -> $Identifier ; O -> L */
+static struct boolexp_node *
+parse_boolexp_O()
+{
+  struct boolexp_node *b2, *t;
+  skip_whitespace();
+  if (*parsebuf == OWNER_TOKEN) {
+    parsebuf++;
+    b2 = alloc_bool();
+    b2->type = BOOLEXP_OWNER;
+    t = parse_boolexp_R();
+    if (t == NULL) {
+      free_boolexp_node(b2);
+      return NULL;
+    } else {
+      b2->thing = t->thing;
+      free_boolexp_node(t);
+      return b2;
+    }
+  }
+  return parse_boolexp_L();
+}
+
+/* C -> +Identifier ; C -> O */
+static struct boolexp_node *
+parse_boolexp_C()
+{
+  struct boolexp_node *b2, *t;
+  skip_whitespace();
+  if (*parsebuf == IN_TOKEN) {
+    parsebuf++;
+    b2 = alloc_bool();
+    b2->type = BOOLEXP_CARRY;
+    t = parse_boolexp_R();
+    if (t == NULL) {
+      free_boolexp_node(b2);
+      return NULL;
+    } else {
+      b2->thing = t->thing;
+      free_boolexp_node(t);
+      return b2;
+    }
+  }
+  return parse_boolexp_O();
+}
+
+/* I -> =Identifier ; I -> C */
+static struct boolexp_node *
+parse_boolexp_I()
+{
+  struct boolexp_node *b2, *t;
+  skip_whitespace();
+  if (*parsebuf == IS_TOKEN) {
+    parsebuf++;
+    b2 = alloc_bool();
+    b2->type = BOOLEXP_IS;
+    t = parse_boolexp_R();
+    if (t == NULL) {
+      free_boolexp_node(b2);
+      return NULL;
+    } else {
+      b2->thing = t->thing;
+      free_boolexp_node(t);
+      return b2;
+    }
+  }
+  return parse_boolexp_C();
+}
+
+/* A -> @L; A -> I */
+static struct boolexp_node *
+parse_boolexp_A()
+{
+  struct boolexp_node *b2, *t;
+  skip_whitespace();
+  if (*parsebuf == AT_TOKEN) {
+    parsebuf++;
+    b2 = alloc_bool();
+    b2->type = BOOLEXP_IND;
+    t = parse_boolexp_R();
+    if (t == NULL) {
+      free_boolexp_node(b2);
+      return NULL;
+    }
+    b2->thing = t->thing;
+    free_boolexp_node(t);
+    if (*parsebuf == '/') {
+      char tbuf1[BUFFER_LEN], *p;
+      const char *m;
+      parsebuf++;
+      p = tbuf1;
+      while (*parsebuf
+             && *parsebuf != AND_TOKEN
+             && *parsebuf != OR_TOKEN && *parsebuf != ')') {
+        *p++ = *parsebuf++;
+      }
+      /* strip trailing whitespace */
+      *p-- = '\0';
+      while (isspace((unsigned char) *p))
+        *p-- = '\0';
+      upcasestr(tbuf1);
+      if (!good_atr_name(tbuf1)) {
+        free_boolexp_node(b2);
+        return NULL;
+      }
+      m = match_lock(tbuf1);
+      b2->data.ind_lock = st_insert(m ? m : tbuf1, &lock_names);
+    } else {
+      b2->data.ind_lock = st_insert(parse_ltype, &atr_names);
+    }
+    return b2;
+  }
+  return parse_boolexp_I();
+}
+
+/* F -> !F;F -> A */
+static struct boolexp_node *
+parse_boolexp_F()
+{
+  struct boolexp_node *b2;
+  skip_whitespace();
+  if (*parsebuf == NOT_TOKEN) {
+    parsebuf++;
+    b2 = alloc_bool();
+    b2->type = BOOLEXP_NOT;
+    if ((b2->data.n = parse_boolexp_F()) == NULL) {
+      free_boolexp_node(b2);
+      return NULL;
+    } else
+      return b2;
+  }
+  return parse_boolexp_A();
+}
+
+
+/* T -> F; T -> F & T */
+static struct boolexp_node *
+parse_boolexp_T()
+{
+  struct boolexp_node *b, *b2;
+
+  if ((b = parse_boolexp_F()) == NULL) {
+    return b;
+  } else {
+    skip_whitespace();
+    if (*parsebuf == AND_TOKEN) {
+      parsebuf++;
+      b2 = alloc_bool();
+      b2->type = BOOLEXP_AND;
+      b2->data.sub.a = b;
+      if ((b2->data.sub.b = parse_boolexp_T()) == NULL) {
+        free_boolexp_node(b2);
+        return NULL;
+      } else {
+        return b2;
+      }
+    } else {
+      return b;
+    }
+  }
+}
+
+/* E -> T; E -> T | E */
+static struct boolexp_node *
+parse_boolexp_E()
+{
+  struct boolexp_node *b, *b2;
+
+  if ((b = parse_boolexp_T()) == NULL) {
+    return b;
+  } else {
+    skip_whitespace();
+    if (*parsebuf == OR_TOKEN) {
+      parsebuf++;
+      b2 = alloc_bool();
+      b2->type = BOOLEXP_OR;
+      b2->data.sub.a = b;
+      if ((b2->data.sub.b = parse_boolexp_E()) == NULL) {
+        free_boolexp_node(b2);
+        return NULL;
+      } else {
+        return b2;
+      }
+    } else {
+      return b;
+    }
+  }
+}
+
+/* Functions for turning the parse tree into assembly */
+
+/** Create a label identifier.
+ * \param a the assembler list the label is for.
+ * \return a new label id
+ */
+static int
+gen_label_id(struct bvm_asm *a)
+{
+  int l = a->label;
+  a->label++;
+  return l;
+}
+
+/** Add an instruction to the assembler list.
+ * \param a the assembler list.
+ * \param op the opcode of the instruction.
+ * \param arg the argument for the instruction if numeric.
+ * \param s the string to use as the argument for the instruction. If non-NULL, arg's value is ignored.
+ */
+static void
+append_insn(struct bvm_asm *a, bvm_opcode op, int arg, const char *s)
+{
+  struct bvm_asmnode *newop;
+
+  if (s) {
+    struct bvm_strnode *newstr;
+    int count = 0, found = 0;
+
+    /* Look for an existing string */
+    for (newstr = a->shead; newstr; newstr = newstr->next, count++) {
+      if (strcmp(newstr->s, s) == 0) {
+        arg = count;
+        found = 1;
+        break;
+      }
+    }
+    /* Allocate a new string if needed. */
+    if (!found) {
+      newstr = mush_malloc(sizeof *newstr, "bvm.strnode");
+      if (!s)
+        mush_panic(T
+                   ("Unable to allocate memory for boolexp string node!"));
+      newstr->s = mush_strdup(s, "bvm.string");
+      if (!newstr->s)
+        mush_panic(T("Unable to allocate memory for boolexp string!"));
+      newstr->len = strlen(s) + 1;
+      newstr->next = NULL;
+      if (a->shead == NULL)
+        a->shead = a->stail = newstr;
+      else {
+        a->stail->next = newstr;
+        a->stail = newstr;
+      }
+      arg = a->strcount;
+      a->strcount++;
+    }
+  }
+
+  newop = mush_malloc(sizeof *newop, "bvm.asmnode");
+  if (!newop)
+    mush_panic(T("Unable to allocate memory for boolexp asm node!"));
+  newop->op = op;
+  newop->arg = arg;
+  newop->next = NULL;
+  if (a->head == NULL)
+    a->head = a->tail = newop;
+  else {
+    a->tail->next = newop;
+    a->tail = newop;
+  }
+}
+
+/** Does the actual work of walking the parse tree and creating an
+ * assembler list from it.
+ * \param a the assembler list.
+ * \param b the root of the parse tree.
+ * \param outer the type of root's parent node.
+ */
+static void
+generate_bvm_asm1(struct bvm_asm *a, struct boolexp_node *b,
+                  boolexp_type outer)
+{
+  int lbl;
+
+  switch (b->type) {
+  case BOOLEXP_AND:
+    lbl = gen_label_id(a);
+    if (outer == BOOLEXP_NOT)
+      append_insn(a, OP_PAREN, 0, NULL);
+    generate_bvm_asm1(a, b->data.sub.a, b->type);
+    append_insn(a, OP_JMPF, lbl, NULL);
+    generate_bvm_asm1(a, b->data.sub.b, b->type);
+    if (outer == BOOLEXP_NOT)
+      append_insn(a, OP_PAREN, 1, NULL);
+    append_insn(a, OP_LABEL, lbl, NULL);
+    break;
+  case BOOLEXP_OR:
+    lbl = gen_label_id(a);
+    if (outer == BOOLEXP_NOT || outer == BOOLEXP_AND)
+      append_insn(a, OP_PAREN, 0, NULL);
+    generate_bvm_asm1(a, b->data.sub.a, b->type);
+    append_insn(a, OP_JMPT, lbl, NULL);
+    generate_bvm_asm1(a, b->data.sub.b, b->type);
+    if (outer == BOOLEXP_NOT || outer == BOOLEXP_AND)
+      append_insn(a, OP_PAREN, 1, NULL);
+    append_insn(a, OP_LABEL, lbl, NULL);
+    break;
+  case BOOLEXP_IND:
+    append_insn(a, OP_LOADS, 0, b->data.ind_lock);
+    append_insn(a, OP_TIND, b->thing, NULL);
+    break;
+  case BOOLEXP_IS:
+    append_insn(a, OP_TIS, b->thing, NULL);
+    break;
+  case BOOLEXP_CARRY:
+    append_insn(a, OP_TCARRY, b->thing, NULL);
+    break;
+  case BOOLEXP_OWNER:
+    append_insn(a, OP_TOWNER, b->thing, NULL);
+    break;
+  case BOOLEXP_NOT:
+    generate_bvm_asm1(a, b->data.n, b->type);
+    append_insn(a, OP_NEGR, 0, NULL);
+    break;
+  case BOOLEXP_CONST:
+    append_insn(a, OP_TCONST, b->thing, NULL);
+    break;
+  case BOOLEXP_BOOL:
+    append_insn(a, OP_LOADR, b->thing, NULL);
+    break;
+  case BOOLEXP_ATR:
+    append_insn(a, OP_LOADS, 0, b->data.atr_lock->name);
+    append_insn(a, OP_TATR, 0, b->data.atr_lock->text);
+    break;
+  case BOOLEXP_EVAL:
+    append_insn(a, OP_LOADS, 0, b->data.atr_lock->name);
+    append_insn(a, OP_TEVAL, 0, b->data.atr_lock->text);
+    break;
+  case BOOLEXP_FLAG:
+    {
+      enum bvm_opcode op = OP_RET;
+      int n;
+      for (n = 0; flag_locks[n].name; n++) {
+        if (strcmp(b->data.atr_lock->name, flag_locks[n].name) == 0) {
+          op = flag_locks[n].op;
+          break;
+        }
+      }
+      append_insn(a, op, 0, b->data.atr_lock->text);
+      break;
+    }
+  }
+}
+
+/** Turn a parse tree into an assembler list.
+ * \param the parse tree
+ * \return newly allocated assembler list.
+ */
+static struct bvm_asm *
+generate_bvm_asm(struct boolexp_node *b)
+{
+  struct bvm_asm *a;
+
+  if (!b)
+    return NULL;
+
+  a = mush_malloc(sizeof *a, "bvm.asm");
+  if (!a)
+    return NULL;
+
+  a->strcount = a->label = 0;
+  a->head = a->tail = NULL;
+  a->shead = a->stail = NULL;
+
+  generate_bvm_asm1(a, b, BOOLEXP_CONST);
+  append_insn(a, OP_RET, 0, NULL);
+
+  return a;
+}
+
+/** Frees an assembler list.
+ * \param a the assembler list to deallocate.
+ */
+static void
+free_bvm_asm(struct bvm_asm *a)
+{
+  struct bvm_asmnode *i, *tmp1;
+  struct bvm_strnode *s, *tmp2;
+
+  if (!a)
+    return;
+
+  for (i = a->head; i; i = tmp1) {
+    tmp1 = i->next;
+    mush_free(i, "bvm.asmnode");
+  }
+
+  for (s = a->shead; s; s = tmp2) {
+    tmp2 = s->next;
+    mush_free(s->s, "bvm.string");
+    mush_free(s, "bvm.strnode");
+  }
+  mush_free(a, "bvm.asm");
+}
+
+/** Find the position of a labeled instruction.
+ * \param as the assembler list.
+ * \param the id of the label to find.
+ * \return the number of instructions before the label.
+ */
+static int
+pos_of_label(struct bvm_asm *as, int label)
+{
+  int offset = 0;
+  struct bvm_asmnode *a;
+  for (a = as->head; a; a = a->next) {
+    if (a->op == OP_LABEL && a->arg == label)
+      return offset;
+    if (a->op != OP_LABEL)
+      offset++;
+  }
+  return offset;                /* Never reached! */
+}
+
+/** Find the position of a string.
+ * \param a the assembler list
+ * \param c The c-th string is the one that's wanted.
+ * \return the distance from the start of the string section to the start of the c-th string.
+ */
+static int
+offset_to_string(struct bvm_asm *a, int c)
+{
+  int offset = 0;
+  int n = 0;
+  struct bvm_strnode *s;
+
+  for (s = a->shead; s; s = s->next, n++) {
+    if (n == c)
+      return offset;
+    else
+      offset += s->len;
+  }
+  return offset;                /* Never reached! */
+}
+
+/** Find the next instruction after a label.
+ * \param a the assembler list.
+ * \param label the label id to look for.
+ * \return a pointer to the first real instruction after a label; where a jump to that label will go to.
+ */
+static struct bvm_asmnode *
+insn_after_label(struct bvm_asm *a, int label)
+{
+  struct bvm_asmnode *n;
+
+  for (n = a->head; n; n = n->next) {
+    if (n->op == OP_LABEL && n->arg == label) {
+      do {
+        n = n->next;
+      } while (n->op == OP_LABEL);
+      return n;
+    }
+  }
+  return NULL;
+}
+
+/** Avoid jumps that lead straight to another jump. If the second jump
+ * is on the same condition as the first one, jump instead to its
+ * destination. If it's the opposite condition, jump instead to the
+ * first instruction after the second jump to avoid the useless
+ * conditional check. 
+ * \param a the assembler list to thread. */
+static void
+opt_thread_jumps(struct bvm_asm *a)
+{
+  struct bvm_asmnode *n, *target;
+
+  for (n = a->head; n;) {
+    if (n->op == OP_JMPT || n->op == OP_JMPF) {
+      target = insn_after_label(a, n->arg);
+      if (target && (target->op == OP_JMPT || target->op == OP_JMPF)) {
+        if (target->op == n->op) {
+          /* Avoid daisy-chained conditional jumps on the same
+             condition.
+           */
+          n->arg = target->arg;
+        } else {
+          /* Avoid useless conditional jumps on different conditions by
+             jumping to the next instruction after. Ex: a&b|c */
+          struct bvm_asmnode *newlbl;
+          newlbl = mush_malloc(sizeof *newlbl, "bvm.asmnode");
+          if (!newlbl)
+            mush_panic(T
+                       ("Unable to allocate memory for boolexp asm node!"));
+          newlbl->op = OP_LABEL;
+          n->arg = newlbl->arg = gen_label_id(a);
+          if (target->next)
+            newlbl->next = target->next;
+          else
+            newlbl->next = NULL;
+          target->next = newlbl;
+          if (a->tail == target)
+            a->tail = newlbl;
+        }
+      } else
+        n = n->next;
+    } else
+      n = n->next;
+  }
+}
+
+/** Do some trivial optimizations.  
+ * \param a the assembler list to transform.
+ */
+static void
+optimize_bvm_asm(struct bvm_asm *a)
+{
+  if (!a)
+    return;
+  opt_thread_jumps(a);
+}
+
+/** Turn assembly into bytecode. 
+ * \param a the assembly list to emit.
+ * \param the compiled bytecode.
+ */
+static boolexp
+emit_bytecode(struct bvm_asm *a, int derefs)
+{
+  boolexp b;
+  struct bvm_asmnode *i;
+  struct bvm_strnode *s;
+  unsigned char *pc, *bytecode;
+  u_int_16 len, blen;
+
+  if (!a)
+    return TRUE_BOOLEXP;
+
+  /* Calculate the total size of the bytecode */
+  len = 0;
+
+  for (i = a->head; i; i = i->next) {
+    if (i->op == OP_LABEL)
+      continue;
+    len++;
+  }
+
+  len *= INSN_LEN;
+  blen = len;
+
+  for (s = a->shead; s; s = s->next)
+    len += s->len;
+
+  pc = bytecode = mush_malloc(len, "boolexp.bytecode");
+  if (!pc)
+    return TRUE_BOOLEXP;
+
+  /* Emit the instructions */
+  for (i = a->head; i; i = i->next) {
+    switch (i->op) {
+    case OP_LABEL:
+      continue;
+    case OP_JMPT:
+    case OP_JMPF:
+      i->arg = pos_of_label(a, i->arg) * INSN_LEN;
+      break;
+    case OP_LOADS:
+    case OP_TEVAL:
+    case OP_TATR:
+    case OP_TFLAG:
+    case OP_TPOWER:
+    case OP_TOBJID:
+    case OP_TTYPE:
+    case OP_TDIVISION:
+    case OP_TSWITCHES:
+    case OP_TLEVEL:
+    case OP_TPWRGRP:
+#ifdef CHAT_SYSTEM
+    case OP_TCHANNEL:
+#endif /* CHAT_SYSTEM */
+    case OP_TIP:
+    case OP_THOSTNAME:
+    case OP_TDBREFLIST:
+      i->arg = blen + offset_to_string(a, i->arg);
+      break;
+    default:
+      break;
+    }
+
+    *pc = (char) i->op;
+    memcpy(pc + 1, &i->arg, sizeof i->arg);
+    pc += INSN_LEN;
+  }
+
+  /* Emit the strings section */
+  for (s = a->shead; s; s = s->next) {
+    memcpy(pc, s->s, s->len);
+    pc += s->len;
+  }
+
+  b = chunk_create(bytecode, len, derefs);
+  mush_free(bytecode, "boolexp.bytecode");
+  return b;
+}
+
+/** Compile a string into boolexp bytecode.
+ * Given a textual representation of a boolexp in a string, parse it into
+ * a syntax tree, compile to bytecode, and return a pointer to a boolexp
+ * structure.
+ * \param player the enactor.
+ * \param buf string representation of a boolexp.
+ * \param ltype the type of lock for which the boolexp is being parsed.
+ * \param derefs the starting deref count for chunk storage.
+ * \return pointer to a newly allocated boolexp.
+ */
+boolexp
+parse_boolexp_d(dbref player, const char *buf, lock_type ltype, int derefs)
+{
+  struct boolexp_node *ast;
+  struct bvm_asm *bvasm;
+  boolexp bytecode;
+  /* Parse */
+  parsebuf = buf;
+  parse_player = player;
+  parse_ltype = ltype;
+  ast = parse_boolexp_E();
+  if (!ast)
+    return TRUE_BOOLEXP;
+  bvasm = generate_bvm_asm(ast);
+  if (!bvasm) {
+    free_boolexp_node(ast);
+    return TRUE_BOOLEXP;
+  }
+  optimize_bvm_asm(bvasm);
+  bytecode = emit_bytecode(bvasm, derefs);
+#ifdef DEBUG_BYTECODE
+  printf("\nSource string: \"%s\"\n", buf);
+  printf("Parse tree size: %d bytes\n", sizeof_boolexp_node(ast));
+  print_bytecode(bytecode);
+#endif
+  free_boolexp_node(ast);
+  free_bvm_asm(bvasm);
+  return bytecode;
+}
+
+/** Compile a string into boolexp bytecode.
+ * Given a textual representation of a boolexp in a string, parse it into
+ * a syntax tree, compile to bytecode, and return a pointer to a boolexp
+ * structure.
+ * \param player the enactor.
+ * \param buf string representation of a boolexp.
+ * \param ltype the type of lock for which the boolexp is being parsed.
+ * \return pointer to a newly allocated boolexp.
+ */
+boolexp
+parse_boolexp(dbref player, const char *buf, lock_type ltype)
+{
+  return parse_boolexp_d(player, buf, ltype, 0);
+}
+
+/** Test to see if an eval lock passes, with a exact match.
+ * \param player the object attempting to pass the lock.
+ * \param target the object the lock is on.
+ * \param atrname the name of the attribute to evaluate on target.
+ * \param str What the attribute should evaluate to to succeed.
+ * \retval 1 the lock succeeds.
+ * \retval 0 the lock fails.
+ */
+static int
+check_attrib_lock(dbref player, dbref target,
+                  const char *atrname, const char *str)
+{
+
+  ATTR *a;
+  char *asave;
+  const char *ap;
+  char buff[BUFFER_LEN], *bp;
+  char *preserve[NUMQ];
+  if (!atrname || !*atrname || !str || !*str)
+    return 0;
+  /* fail if there's no matching attribute */
+  a = atr_get(target, strupper(atrname));
+  if (!a)
+    return 0;
+  if (!Can_Read_Attr(target, target, a))
+    return 0;
+  asave = safe_atr_value(a);
+  /* perform pronoun substitution */
+  save_global_regs("check_attrib_lock_save", preserve);
+  bp = buff;
+  ap = asave;
+  process_expression(buff, &bp, &ap, target, player,
+                     player, PE_DEFAULT, PT_DEFAULT, NULL);
+  *bp = '\0';
+  restore_global_regs("check_attrib_lock_save", preserve);
+  free(asave);
+
+  return !strcasecmp(buff, str);
+}
+
+#ifdef DEBUG_BYTECODE
+
+/** Find the size of a parse tree node, recursively to count all child nodes.
+ * \param b the root of the parse tree.
+ * \return the size of the parse tree in bytes.
+ */
+static int
+sizeof_boolexp_node(struct boolexp_node *b)
+{
+  if (!b)
+    return 0;
+  switch (b->type) {
+  case BOOLEXP_CONST:
+  case BOOLEXP_IS:
+  case BOOLEXP_CARRY:
+  case BOOLEXP_OWNER:
+  case BOOLEXP_IND:
+  case BOOLEXP_BOOL:
+    return sizeof *b;
+  case BOOLEXP_NOT:
+    return sizeof *b + sizeof_boolexp_node(b->data.n);
+  case BOOLEXP_AND:
+  case BOOLEXP_OR:
+    return sizeof *b +
+        sizeof_boolexp_node(b->data.sub.a) +
+        sizeof_boolexp_node(b->data.sub.b);
+  case BOOLEXP_ATR:
+  case BOOLEXP_EVAL:
+  case BOOLEXP_FLAG:
+    return sizeof *b + sizeof *b->data.atr_lock - BUFFER_LEN +
+        strlen(b->data.atr_lock->text) + 1;
+  default:
+    /* Broken lock */
+    return sizeof *b;
+  }
+}
+
+/** Print out a decompiled-to-assembly bytecode to stdout.
+ * \param b the boolexp to decompile.
+ */
+static void
+print_bytecode(boolexp b)
+{
+  bvm_opcode op;
+  int arg, len = 0, pos = 0;
+  char *pc, *bytecode;
+
+  if (b == TRUE_BOOLEXP) {
+    puts("NULL bytecode!");
+    return;
+  }
+
+  pc = bytecode = get_bytecode(b, &len);
+
+  printf("Total length of bytecode+strings: %d bytes\n", len);
+
+  while (1) {
+    op = (bvm_opcode) * pc;
+    memcpy(&arg, pc + 1, sizeof arg);
+    pc += INSN_LEN;
+    printf("%-5d ", pos);
+    pos++;
+    switch (op) {
+    case OP_RET:
+      puts("RET");
+      return;
+    case OP_PAREN:
+      printf("PAREN %c\n", (arg == 0) ? '(' : ((arg == 1) ? ')' : '!'));
+      break;
+    case OP_JMPT:
+      printf("JMPT %d\n", arg / INSN_LEN);
+      break;
+    case OP_JMPF:
+      printf("JMPF %d\n", arg / INSN_LEN);
+      break;
+    case OP_TCONST:
+      printf("TCONST #%d\n", arg);
+      break;
+    case OP_TCARRY:
+      printf("TCARRY #%d\n", arg);
+      break;
+    case OP_TIS:
+      printf("TIS #%d\n", arg);
+      break;
+    case OP_TOWNER:
+      printf("TOWNER #%d\n", arg);
+      break;
+    case OP_TIND:
+      printf("TIND #%d\n", arg);
+      break;
+    case OP_TATR:
+      printf("TATR \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TEVAL:
+      printf("TEVAL \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TFLAG:
+      printf("TFLAG \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TPOWER:
+      printf("TPOWER \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TOBJID:
+      printf("TOBJID \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TDIVISION:
+      printf("TDIVISION \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TDIVISION:
+      printf("TSWITCHES \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TLEVEL:
+      printf("TLEVEL \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TPWRGRP:
+      printf("TPWRGRP \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TTYPE:
+      printf("TTYPE \"%s\"\n", bytecode + arg);
+      break;
+#ifdef CHAT_SYSTEM
+    case OP_TCHANNEL:
+      printf("TCHANNEL \"%s\"\n", bytecode + arg);
+      break;
+#endif /* CHAT_SYSTEM */
+    case OP_TIP:
+      printf("TIP \"%s\"\n", bytecode + arg);
+      break;
+    case OP_THOSTNAME:
+      printf("THOSTNAME \"%s\"\n", bytecode + arg);
+      break;
+    case OP_TDBREFLIST:
+      printf("TDBREFLIST \"%s\"\n", bytecode + arg);
+      break;
+    case OP_LOADS:
+      printf("LOADS \"%s\"\n", bytecode + arg);
+      break;
+    case OP_LOADR:
+      printf("LOADR %d\n", arg);
+      break;
+    case OP_NEGR:
+      puts("NEGR");
+      break;
+    default:
+      printf("Hmm: %d %d\n", op, arg);
+    }
+  }
+}
+#endif
+
+/* Warnings-related stuff here because I don't want to export details
+   of the bytecode outside this file. */
+#define W_UNLOCKED      0x1  /**< Returned if a boolexp is unlocked */
+#define W_LOCKED        0x2  /**< Returned if a boolexp is locked */
+
+/** Check to see if a lock is considered possibly unlocked or not.
+ *  This is really simple-minded for efficiency. Basically, if it's *
+ *  unlocked, it's unlocked. If it's locked to something starting with
+ *  a specific db#, it's locked. Anything else, and we don't know.
+ *  \param l the boolexp to check.
+ *  \retval W_UNLOCKED the boolexp is unlocked.
+ *  \retval W_LOCKED the boolexp is considered locked.
+ *  \retval W_LOCKED|W_UNLOCKED the boolexp is in an unknown state.
+ */
+int
+warning_lock_type(const boolexp l)
+     /* 0== unlocked. 1== locked, 2== sometimes */
+{
+  if (l == TRUE_BOOLEXP)
+    return W_UNLOCKED;
+  /* Two instructions means one of the simple lock cases */
+  else if (sizeof_boolexp(l) == (INSN_LEN + INSN_LEN))
+    return W_LOCKED;
+  else
+    return W_LOCKED | W_UNLOCKED;
+}
+
+/** Check for lock-check @warnings.
+ * Things like non-existant attributes in eval locks, references to
+ * garbage objects, or indirect locks that aren't present or visible.
+ * \param player the object to report warnings to.
+ * \param i the object the lock is on.
+ * \param name the lock type.
+ * \param be the lock key.
+ */
+void
+check_lock(dbref player, dbref i, const char *name, boolexp be)
+{
+  unsigned char *pc, *bytecode;
+  bvm_opcode op;
+  int arg;
+  char *s = NULL;
+
+  bytecode = pc = get_bytecode(be, NULL);
+
+  while (1) {
+    op = (bvm_opcode) * pc;
+    memcpy(&arg, pc + 1, sizeof arg);
+    pc += INSN_LEN;
+    switch (op) {
+    case OP_RET:
+      return;
+    case OP_LOADS:
+      s = (char *) bytecode + arg;
+      break;
+    case OP_TCONST:
+    case OP_TCARRY:
+    case OP_TIS:
+    case OP_TOWNER:
+      if (!GoodObject(arg) || IsGarbage(arg))
+        complain(player, i, "lock-checks",
+                 T("%s lock refers to garbage object"), name);
+      break;
+    case OP_TEVAL:
+      {
+        ATTR *a;
+        a = atr_get(i, s);
+        if (!a || !Can_Read_Attr(i, i, a))
+          complain(player, i, "lock-checks",
+                   T
+                   ("%s lock has eval-lock that uses a nonexistant attribute '%s'."),
+                   name, s);
+      }
+      break;
+    case OP_TIND:
+      if (!GoodObject(arg) || IsGarbage(arg))
+        complain(player, i, "lock-checks",
+                 T("%s lock refers to garbage object"), name);
+      else if (!(Can_Read_Lock(i, arg, s)
+                 && getlock(arg, s) != TRUE_BOOLEXP))
+        complain(player, i, "lock-checks",
+                 T
+                 ("%s lock has indirect lock to %s/%s that it can't read"),
+                 name, unparse_object(player, arg), s);
+      break;
+    default:
+      break;
+    }
+  }
+}
diff --git a/src/bsd.c b/src/bsd.c
new file mode 100644 (file)
index 0000000..df85f89
--- /dev/null
+++ b/src/bsd.c
@@ -0,0 +1,5769 @@
+/**
+ * \file bsd.c
+ *
+ * \brief Network communication through BSD sockets for PennMUSH.
+ *
+ * While mysocket.c provides low-level functions for working with
+ * sockets, bsd.c focuses on player descriptors, a higher-level
+ * structure that tracks all information associated with a connection,
+ * and through which connection i/o is done.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef WIN32
+#define FD_SETSIZE 256
+#include <windows.h>
+#include <winsock.h>
+#include <io.h>
+#define EINTR WSAEINTR
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define MAXHOSTNAMELEN 32
+#define LC_MESSAGES 6
+#else                          /* !WIN32 */
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <sys/ioctl.h>
+#include <errno.h>
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#endif
+#ifdef I_NETDB
+#include <netdb.h>
+#endif
+#ifdef I_SYS_PARAM
+#include <sys/param.h>
+#endif
+#ifdef I_SYS_STAT
+#include <sys/stat.h>
+#endif
+#endif                         /* !WIN32 */
+#include <time.h>
+#ifdef I_SYS_WAIT
+#include <sys/wait.h>
+#endif
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef I_SYS_SELECT
+#include <sys/select.h>
+#endif
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#ifdef HAS_GETRLIMIT
+#include <sys/resource.h>
+#endif
+#include <limits.h>
+#ifdef I_FLOATINGPOINT
+#include <floatingpoint.h>
+#endif
+#include <locale.h>
+#ifdef __APPLE__
+#define LC_MESSAGES     6
+#define AUTORESTART
+#endif
+#include <setjmp.h>
+
+#include "conf.h"
+
+#if defined(WIN32) && defined(INFO_SLAVE)
+#undef INFO_SLAVE
+#endif
+
+#ifdef INFO_SLAVE
+#include <sys/uio.h>
+#endif
+
+#include "externs.h"
+#include "chunk.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "help.h"
+#include "match.h"
+#include "ansi.h"
+#include "pueblo.h"
+#include "parse.h"
+#include "access.h"
+#include "command.h"
+#include "version.h"
+#include "patches.h"
+#include "mysocket.h"
+#include "ident.h"
+#include "strtree.h"
+#include "log.h"
+#include "pcre.h"
+#ifdef HAS_OPENSSL
+#include "myssl.h"
+#endif
+#include "mymalloc.h"
+#include "extmail.h"
+#include "attrib.h"
+#include "game.h"
+#include "dbio.h"
+#include "confmagic.h"
+#ifdef HAS_WAITPID
+/** What does wait*() return? */
+#define WAIT_TYPE int
+#else
+#ifdef UNION_WAIT
+#define WAIT_TYPE union wait
+#else
+#define WAIT_TYPE int
+#endif
+#endif
+
+
+/* BSD 4.2 and maybe some others need these defined */
+#ifndef FD_ZERO
+/** An fd_set is 4 bytes */
+#define fd_set int
+/** Clear an fd_set */
+#define FD_ZERO(p)       (*p = 0)
+/** Set a bit in an fd_set */
+#define FD_SET(n,p)      (*p |= (1<<(n)))
+/** Clear a bit in an fd_set */
+#define FD_CLR(n,p)      (*p &= ~(1<<(n)))
+/** Check a bit in an fd_set */
+#define FD_ISSET(n,p)    (*p & (1<<(n)))
+#endif                         /* defines for BSD 4.2 */
+
+#ifdef HAS_GETRUSAGE
+void rusage_stats(void);
+#endif
+int que_next(void);            /* from cque.c */
+
+extern int on_second;          /**< Are we ready to do per-second processing? */
+void dispatch(void);           /* from timer.c */
+dbref email_register_player(const char *name, const char *email, const char *host, const char *ip);    /* from player.c */
+
+extern time_t start_time;      /**< When was the mush last rebooted? */
+extern time_t first_start_time;        /**< When was the mush first started? */
+extern int reboot_count;       /**< How many times have we been rebooted? */
+extern int database_loaded;    /* From game.c */
+static int extrafd;
+int shutdown_flag = 0;         /**< Is it time to shut down? */
+extern int paranoid_dump;      /**< Are we doing a paranoid dump? */
+#ifdef CHAT_SYSTEM
+void chat_player_announce(dbref player, char *msg, int ungag);
+#endif /* CHAT_SYSTEM */
+
+static int login_number = 0;
+static int under_limit = 1;
+
+char cf_motd_msg[BUFFER_LEN];  /**< The message of the day */
+char cf_downmotd_msg[BUFFER_LEN];      /**< The down message */
+char cf_fullmotd_msg[BUFFER_LEN];      /**< The 'mush full' message */
+static char poll_msg[DOING_LEN];
+char confname[BUFFER_LEN];     /**< Name of the config file */
+char errlog[BUFFER_LEN];       /**< Name of the error log file */
+
+/* Default Connection flags for certain clients
+ */
+static CLIENT_DEFAULTS client_maps[]  = {
+  {"TINYFUGUE", CONN_PROMPT},
+  {NULL, -1}
+};
+
+
+/** Is this descriptor connected to a telnet-compatible terminal? */
+#define TELNET_ABLE(d) ((d)->conn_flags & (CONN_TELNET | CONN_TELNET_QUERY))
+
+
+/* When the mush gets a new connection, it tries sending a telnet
+ * option negotiation code for setting client-side line-editing mode
+ * to it. If it gets a reply, a flag in the descriptor struct is
+ * turned on indicated telnet-awareness.
+ * 
+ * If the reply indicates that the client supports linemode, further
+ * instructions as to what linemode options are to be used is sent.
+ * Those options: Client-side line editing, and expanding literal
+ * client-side-entered tabs into spaces.
+ * 
+ * Option negotation requests sent by the client are processed,
+ * with the only one we confirm rather than refuse outright being
+ * suppress-go-ahead, since a number of telnet clients try it.
+ *
+ * The character 255 is the telnet option escape character, so when it
+ * is sent to a telnet-aware client by itself (Since it's also often y-umlaut)
+ * it must be doubled to escape it for the client. This is done automatically,
+ * and is the original purpose of adding telnet option support.
+ */
+
+/* Telnet codes */
+#define IAC            255     /**< interpret as command: */
+#define GOAHEAD                249     /**< Go Ahead command */
+#define NOP            241     /**< no operation */
+#define AYT            246     /**< are you there? */
+#define DONT           254     /**< you are not to use option */
+#define DO             253     /**< please, you use option */
+#define WONT           252     /**< I won't use option */
+#define WILL           251     /**< I will use option */
+#define SB             250     /**< interpret as subnegotiation */
+#define SE             240     /**< end sub negotiation */
+#define TN_SGA         3       /**< Suppress go-ahead */
+#define TN_LINEMODE    34      /**< Line mode */
+#define TN_NAWS                31      /**< Negotiate About Window Size */
+#define TN_TTYPE       24      /**< Ask for termial type information */
+static void test_telnet(DESC *d);
+static void setup_telnet(DESC *d);
+static int handle_telnet(DESC *d, unsigned char **q, unsigned char *qend);
+static const char *empabb(dbref);
+static int do_su_exit(DESC *d);
+
+#ifdef NT_TCP
+/* for Windows NT IO-completion-port method of TCP/IP - NJG */
+
+/* Windows NT TCP/IP routines written by Nick Gammon <nick@gammon.com.au> */
+
+#include <process.h>
+HANDLE CompletionPort;         /* IOs are queued up on this port */
+SOCKET MUDListenSocket;                /* for our listening thread */
+DWORD dwMUDListenThread;       /* thread handle for listening thread */
+SOCKADDR_IN saServer;          /* for listening thread */
+void __cdecl MUDListenThread(void *pVoid);     /* the listening thread */
+DWORD platform;                        /* which version of Windows are we using? */
+OVERLAPPED lpo_aborted;                /* special to indicate a player has finished TCP IOs */
+OVERLAPPED lpo_shutdown;       /* special to indicate a player should do a shutdown */
+void ProcessWindowsTCP(void);  /* handle NT-style IOs */
+CRITICAL_SECTION cs;           /* for thread synchronisation */
+#endif
+
+
+static const char *create_fail =
+  "Either there is already a player with that name, or that name is illegal.";
+static const char *password_fail = "The password is invalid (or missing).";
+static const char *register_fail =
+  "Unable to register that player with that email address.";
+static const char *register_success =
+  "Registration successful! You will receive your password by email.";
+static const char *shutdown_message = "Going down - Bye";
+#ifdef HAS_OPENSSL
+static const char *ssl_shutdown_message = 
+  "GAME: SSL connections must be dropped, sorry.";
+#endif
+/** Where we save the descriptor info across reboots. */
+#define REBOOTFILE              "reboot.db"
+
+#if 0
+/* For translation */
+static void dummy_msgs(void);
+static void
+dummy_msgs()
+{
+  char *temp;
+  temp = T("Either that player does not exist, or has a different password.");
+  temp =
+    T
+    ("Either there is already a player with that name, or that name is illegal.");
+  temp = T("The password is invalid (or missing).");
+  temp = T("Unable to register that player with that email address.");
+  temp = T("Registration successful! You will receive your password by email.");
+  temp = T("Going down - Bye");
+  temp = T("GAME: SSL connections must be dropped, sorry.");
+}
+
+#endif
+
+DESC *descriptor_list = NULL;  /**< The linked list of descriptors */
+
+static int sock;
+#ifdef HAS_OPENSSL
+static int sslsock = 0;
+SSL *ssl_master_socket = NULL; /**< Master SSL socket for ssl port */
+#endif
+static int ndescriptors = 0;
+#ifdef WIN32
+static WSADATA wsadata;
+#endif
+int restarting = 0;    /**< Are we restarting the server after a reboot? */
+static int maxd = 0;
+
+extern const unsigned char *tables;
+
+#ifdef INFO_SLAVE
+static fd_set info_pending;
+static int info_slave;
+Pid_t info_slave_pid = -1;     /**< Process id of the info_slave process */
+int info_slave_state = 0;      /**< State of the info_slave process */
+static int info_query_spill, info_reap_spill;
+static time_t info_queue_time = 0;
+#endif
+
+sig_atomic_t signal_shutdown_flag = 0; /**< Have we caught a shutdown signal? */
+sig_atomic_t signal_dump_flag = 0;     /**< Have we caught a dump signal? */
+
+#ifdef HAS_GETRLIMIT
+static void init_rlimit(void);
+#endif
+#ifndef BOOLEXP_DEBUGGING
+#ifdef WIN32SERVICES
+void shutdown_checkpoint(void);
+void mainthread(int argc, char **argv);
+#else
+int main(int argc, char **argv);
+#endif
+#endif
+void set_signals(void);
+static struct timeval *timeval_sub(struct timeval *now, struct timeval *then);
+#ifdef WIN32
+/** Windows doesn't have gettimeofday(), so we implement it here */
+#define our_gettimeofday(now) win_gettimeofday((now))
+static void win_gettimeofday(struct timeval *now);
+#else
+/** A wrapper for gettimeofday() in case the system doesn't have it */
+#define our_gettimeofday(now) gettimeofday((now), (struct timezone *)NULL)
+#endif
+static long int msec_diff(struct timeval *now, struct timeval *then);
+static struct timeval *msec_add(struct timeval *t, int x);
+static void update_quotas(struct timeval *last, struct timeval *current);
+
+static int how_many_fds(void);
+static void shovechars(Port_t port, Port_t sslport);
+static int test_connection(int newsock);
+#ifndef INFO_SLAVE
+static DESC *new_connection(int oldsock, int *result, int use_ssl);
+#endif
+
+static void clearstrings(DESC *d);
+
+/** A block of cached text. */
+typedef struct fblock {
+  unsigned char *buff;   /**< Pointer to the block as a string */
+  size_t len;            /**< Length of buff */
+} FBLOCK;
+
+/** The complete collection of cached text files. */
+struct fcache_entries {
+  FBLOCK connect_fcache[2];    /**< connect.txt and connect.html */
+  FBLOCK motd_fcache[2];       /**< motd.txt and motd.html */
+  FBLOCK newuser_fcache[2];    /**< newuser.txt and newuser.html */
+  FBLOCK register_fcache[2];   /**< register.txt and register.html */
+  FBLOCK quit_fcache[2];       /**< quit.txt and quit.html */
+  FBLOCK down_fcache[2];       /**< down.txt and down.html */
+  FBLOCK full_fcache[2];       /**< full.txt and full.html */
+  FBLOCK guest_fcache[2];      /**< guest.txt and guest.html */
+};
+
+void feed_snoop(DESC *, const char *, char );
+char is_snooped(DESC *);
+char set_snoop(dbref, DESC *);
+void clr_snoop(dbref, DESC *);
+void announce_connect(dbref player, int isnew, int num);
+void announce_disconnect(dbref player);
+void add_to_exit_path(DESC *d, dbref player);
+
+static struct fcache_entries fcache;
+static void fcache_dump(DESC *d, FBLOCK fp[2], const unsigned char *prefix);
+static int fcache_read(FBLOCK *cp, const char *filename);
+static void logout_sock(DESC *d);
+static void shutdownsock(DESC *d);
+static DESC *initializesock(int s, char *addr, char *ip, int use_ssl);
+int process_output(DESC *d);
+/* Notify.c */
+extern void free_text_block(struct text_block *t);
+extern void add_to_queue(struct text_queue *q, const unsigned char *b, int n);
+extern int queue_write(DESC *d, const unsigned char *b, int n);
+extern int queue_eol(DESC *d);
+extern int queue_newwrite(DESC *d, const unsigned char *b, int n);
+extern int queue_string(DESC *d, const char *s);
+extern int queue_string_eol(DESC *d, const char *s);
+extern void freeqs(DESC *d);
+static void welcome_user(DESC *d);
+static void dump_info(DESC *call_by);
+static void save_command(DESC *d, const unsigned char *command);
+static int process_input(DESC *d, int output_ready);
+static void process_input_helper(DESC *d, char *tbuf1, int got);
+static void set_userstring(unsigned char **userstring, const char *command);
+static void process_commands(void);
+static void parse_puebloclient(DESC *d, char *command);
+static int dump_messages(DESC *d, dbref player, int new);
+static int check_connect(DESC *d, const char *msg);
+static void parse_connect(const char *msg, char *command, char *user,
+                         char *pass);
+static void close_sockets(void);
+dbref find_player_by_desc(int port);
+static DESC *lookup_desc(dbref executor, const char *name);
+void NORETURN bailout(int sig);
+void WIN32_CDECL signal_shutdown(int sig);
+void WIN32_CDECL signal_dump(int sig);
+void reaper(int sig);
+extern Pid_t forked_dump_pid;  /**< Process id of forking dump process */
+extern time_t last_dump_time;  /**< Time of last database dump */
+static void dump_users(DESC *call_by, char *match, int doing);
+static const char *time_format_1(long int dt);
+static const char *time_format_2(long int dt);
+
+void inactivity_check(void);
+#ifdef INFO_SLAVE
+static void make_info_slave(void);
+static void promote_info_slave(void);
+static void query_info_slave(int fd);
+static void reap_info_slave(void);
+void kill_info_slave(void);
+#endif
+void reopen_logs(void);
+void load_reboot_db(void);
+#ifdef HAS_GETRLIMIT
+static void
+init_rlimit(void)
+{
+  /* Unlimit file descriptors. */
+  /* Ultrix 4.4 and others may have getrlimit but may not be able to
+   * change number of file descriptors
+   */
+#ifdef RLIMIT_NOFILE
+  struct rlimit *rlp;
+
+  rlp = (struct rlimit *) malloc(sizeof(struct rlimit));
+  if (getrlimit(RLIMIT_NOFILE, rlp)) {
+    perror("init_rlimit: getrlimit()");
+    free(rlp);
+    return;
+  }
+  /* This check seems dumb, but apparently FreeBSD may return 0 for
+   * the max # of descriptors!
+   */
+  if (rlp->rlim_max > rlp->rlim_cur) {
+    rlp->rlim_cur = rlp->rlim_max;
+    if (setrlimit(RLIMIT_NOFILE, rlp))
+      perror("init_rlimit: setrlimit()");
+  }
+  free(rlp);
+#endif
+  return;
+}
+#endif                         /* HAS_GETRLIMIT */
+
+#ifdef NT_TCP
+BOOL
+IsValidAddress(const void *lp, UINT nBytes, BOOL bReadWrite)
+{
+  return (lp != NULL &&
+         !IsBadReadPtr(lp, nBytes) &&
+         (!bReadWrite || !IsBadWritePtr((LPVOID) lp, nBytes)));
+
+}
+
+BOOL
+GetErrorMessage(const DWORD dwError, LPTSTR lpszError, const UINT nMaxError)
+{
+
+  LPTSTR lpBuffer;
+  BOOL bRet =
+    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+                 NULL,
+                 dwError,
+                 0,
+                 (LPTSTR) & lpBuffer,
+                 0,
+                 NULL);
+
+  if (bRet == FALSE)
+    *lpszError = '\0';
+  else {
+    lstrcpyn(lpszError, lpBuffer, nMaxError);
+    LocalFree(lpBuffer);
+  }
+  return bRet;
+}
+
+#endif
+
+#ifndef BOOLEXP_DEBUGGING
+#ifdef WIN32SERVICES
+/* Under WIN32, MUSH is a "service", so we just start a thread here.
+ * The real "main" is in win32/services.c
+ */
+void
+mainthread(int argc, char **argv)
+#else
+/** The main function.
+ * \param argc number of arguments.
+ * \param argv vector of arguments.
+ * \return exit code.
+ */
+int
+main(int argc, char **argv)
+#endif                         /* WIN32SERVICES */
+{
+#ifdef AUTORESTART
+  FILE *id;
+#endif
+  FILE *newerr;
+
+  /* read the configuration file */
+  if (argc < 2) {
+    fprintf(stderr, "ERROR: Usage: %s /path/to/config_file\n", argv[0]);
+    exit(2);
+  }
+
+#ifdef WIN32
+  {
+    unsigned short wVersionRequested = MAKEWORD(1, 1);
+    int err;
+
+    /* Need to include library: wsock32.lib for Windows Sockets */
+    err = WSAStartup(wVersionRequested, &wsadata);
+    if (err) {
+      printf(T("Error %i on WSAStartup\n"), err);
+      exit(1);
+    }
+  }
+#endif                         /* WIN32 */
+
+#ifdef NT_TCP
+
+/* Find which version of Windows we are using - Completion ports do */
+/* not work with Windows 95/98 */
+
+  {
+    OSVERSIONINFO VersionInformation;
+
+    VersionInformation.dwOSVersionInfoSize = sizeof(VersionInformation);
+    GetVersionEx(&VersionInformation);
+    platform = VersionInformation.dwPlatformId;
+    printf(T("Running under Windows %s\n"),
+          platform == VER_PLATFORM_WIN32_NT ? "NT" : "95/98");
+  }
+#endif
+
+#ifdef HAS_GETRLIMIT
+  init_rlimit();               /* unlimit file descriptors */
+#endif
+
+  /* These are FreeBSDisms to fix floating point exceptions */
+#ifdef HAS_FPSETROUND
+  fpsetround(FP_RN);
+#endif
+#ifdef HAS_FPSETMASK
+  fpsetmask(0L);
+#endif
+
+  time(&mudtime);
+
+  /* If we have setlocale, call it to set locale info
+   * from environment variables
+   */
+#ifdef HAS_SETLOCALE
+  {
+    char *loc;
+    if ((loc = setlocale(LC_CTYPE, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set ctype locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting ctype locale to %s", loc);
+    if ((loc = setlocale(LC_TIME, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set time locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting time locale to %s", loc);
+    if ((loc = setlocale(LC_MESSAGES, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set messages locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting messages locale to %s", loc);
+    if ((loc = setlocale(LC_COLLATE, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set collate locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting collate locale to %s", loc);
+  }
+#endif
+#ifdef HAS_TEXTDOMAIN
+  textdomain("pennmush");
+#endif
+#ifdef HAS_BINDTEXTDOMAIN
+  bindtextdomain("pennmush", "../po");
+#endif
+
+  /* Build the locale-dependant tables used by PCRE */
+  tables = pcre_maketables();
+
+/* this writes a file used by the restart script to check for active mush */
+#ifdef AUTORESTART
+  id = fopen("runid", "w");
+  fprintf(id, "%d", getpid());
+  fclose(id);
+#endif
+
+  strncpy(confname, argv[1], BUFFER_LEN - 1);
+  confname[BUFFER_LEN - 1] = '\0';
+  init_game_config(confname);
+
+  /* save a file descriptor */
+  reserve_fd();
+#ifndef WIN32
+  extrafd = open("/dev/null", O_RDWR);
+#endif
+
+  /* decide if we're in @shutdown/reboot */
+  restarting = 0;
+  newerr = fopen(REBOOTFILE, "r");
+  if (newerr) {
+    restarting = 1;
+    fclose(newerr);
+  }
+
+  init_qids();
+  if (init_game_dbs() < 0) {
+    do_rawlog(LT_ERR, T("ERROR: Couldn't load databases! Exiting."));
+    exit(2);
+  }
+
+  init_game_postdb(confname);
+
+  database_loaded = 1;
+
+  set_signals();
+
+#ifdef INFO_SLAVE
+  make_info_slave();
+#endif
+
+  /* go do it */
+#ifdef CSRI
+#ifdef CSRI_DEBUG
+  mal_verify(1);
+#endif
+#ifdef CSRI_TRACE
+  mal_leaktrace(1);
+#endif
+#endif
+  load_reboot_db();
+#ifdef NT_TCP
+
+  /* If we are running Windows NT we must create a completion port, */
+  /* and start up a listening thread for new connections */
+
+  if (platform == VER_PLATFORM_WIN32_NT) {
+    int nRet;
+
+    /* create initial IO completion port, so threads have something to wait on */
+
+    CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+
+    if (!CompletionPort) {
+      char sMessage[200];
+      GetErrorMessage(GetLastError(), sMessage, sizeof sMessage);
+      printf("Error %ld (%s) on CreateIoCompletionPort\n",
+            GetLastError(), sMessage);
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+
+    InitializeCriticalSection(&cs);
+
+    /* Create a TCP/IP stream socket */
+    MUDListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+    /* Fill in the the address structure */
+    saServer.sin_port = htons((u_short) TINYPORT);
+    saServer.sin_family = AF_INET;
+    saServer.sin_addr.s_addr = INADDR_ANY;
+
+    /* bind our name to the socket */
+    nRet = bind(MUDListenSocket, (LPSOCKADDR) & saServer, sizeof saServer);
+
+    if (nRet) {
+      printf("Error %ld on Win32: bind\n", WSAGetLastError());
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+    /* Set the socket to listen */
+    nRet = listen(MUDListenSocket, SOMAXCONN);
+
+    if (nRet) {
+      printf("Error %ld on Win32: listen\n", WSAGetLastError());
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+    /* Create the MUD listening thread */
+    dwMUDListenThread = _beginthread(MUDListenThread, 0,
+                                    (void *) (SOCKET) MUDListenSocket);
+
+    if (dwMUDListenThread == -1) {
+      printf("Error %ld on _beginthread\n", errno);
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+    do_rawlog(LT_ERR, T("Listening (NT-style) on port %d"), TINYPORT);
+  }
+#endif                         /* NT_TCP */
+
+  shovechars((Port_t) TINYPORT, (Port_t) SSLPORT);
+#ifdef CSRI
+#ifdef CSRI_DEBUG
+  mal_verify(1);
+#endif
+#endif
+
+  /* someone has told us to shut down */
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+  shutdown_queues();
+
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+  close_sockets();
+  sql_shutdown();
+
+#ifdef INFO_SLAVE
+  kill_info_slave();
+#endif
+
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+  dump_database();
+
+  local_shutdown();
+
+#ifdef RPMODE_SYS
+  rplog_shutdown();
+#endif
+
+  end_all_logs();
+
+#ifdef CSRI
+#ifdef CSRI_PROFILESIZES
+  mal_statsdump(stderr);
+#endif
+#ifdef CSRI_TRACE
+  mal_dumpleaktrace(stderr);
+#endif
+  fflush(stderr);
+#endif
+
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+#ifdef HAS_GETRUSAGE
+  rusage_stats();
+#endif                         /* HAS_RUSAGE */
+
+  do_rawlog(LT_ERR, T("MUSH shutdown completed."));
+
+#ifdef NT_TCP
+
+  /* critical section not needed any more */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    DeleteCriticalSection(&cs);
+#endif
+
+  closesocket(sock);
+#ifdef WIN32
+#ifdef WIN32SERVICES
+  shutdown_checkpoint();
+#endif
+  WSACleanup();                        /* clean up */
+#else
+#ifdef __APPLE__
+  unlink("runid");
+#endif
+  exit(0);
+#endif
+}
+#endif                         /* BOOLEXP_DEBUGGING */
+
+/** Close and reopen the logfiles - called on SIGHUP */
+void
+reopen_logs(void)
+{
+  FILE *newerr;
+  /* close up the log files */
+  end_all_logs();
+  newerr = fopen(errlog, "a");
+  if (!newerr) {
+    fprintf(stderr,
+           T("Unable to open %s. Error output continues to stderr.\n"),
+           errlog);
+  } else {
+    if (!freopen(errlog, "a", stderr)) {
+      printf(T("Ack!  Failed reopening stderr!"));
+      exit(1);
+    }
+    setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+    fclose(newerr);
+  }
+  start_all_logs();
+}
+
+/** Install our default signal handlers. */
+void
+set_signals(void)
+{
+
+#ifndef WIN32
+  /* we don't care about SIGPIPE, we notice it in select() and write() */
+  ignore_signal(SIGPIPE);
+  install_sig_handler(SIGUSR2, signal_dump);
+  install_sig_handler(SIGQUIT, signal_shutdown);
+  install_sig_handler(SIGINT, signal_shutdown);
+  install_sig_handler(SIGTERM, bailout);
+#else
+  /* Win32 stuff: 
+   *   No support for SIGUSR2 or SIGINT.
+   *   SIGTERM is never generated on NT-based Windows (according to MSDN)
+   *   MSVC++ will let you get away with installing a handler anyway,
+   *   but VS.NET will not. So if it's MSVC++, we give it a try.
+   */
+#if _MSC_VER < 1200
+  install_sig_handler(SIGTERM, bailout);
+#endif
+#endif
+
+#ifndef WIN32
+  install_sig_handler(SIGCHLD, reaper);
+#endif
+
+}
+
+#ifdef WIN32
+/** Get the time using Windows function call.
+ * Looks weird, but it works. :-P
+ * \param now address to store timeval data.
+ */
+static void
+win_gettimeofday(struct timeval *now)
+{
+
+  FILETIME win_time;
+
+  GetSystemTimeAsFileTime(&win_time);
+  /* dwLow is in 100-s nanoseconds, not microseconds */
+  now->tv_usec = win_time.dwLowDateTime % 10000000 / 10;
+
+  /* dwLow contains at most 429 least significant seconds, since 32 bits maxint is 4294967294 */
+  win_time.dwLowDateTime /= 10000000;
+
+  /* Make room for the seconds of dwLow in dwHigh */
+  /* 32 bits of 1 = 4294967295. 4294967295 / 429 = 10011578 */
+  win_time.dwHighDateTime %= 10011578;
+  win_time.dwHighDateTime *= 429;
+
+  /* And add them */
+  now->tv_sec = win_time.dwHighDateTime + win_time.dwLowDateTime;
+}
+
+#endif
+
+/** Return the difference between two timeval structs as a timeval struct.
+ * \param now pointer to the timeval to subtract from.
+ * \param then pointer to the timeval to subtract.
+ * \return pointer to a statically allocated timeval of the difference.
+ */
+static struct timeval *
+timeval_sub(struct timeval *now, struct timeval *then)
+{
+  static struct timeval mytime;
+  mytime.tv_sec = now->tv_sec;
+  mytime.tv_usec = now->tv_usec;
+
+  mytime.tv_sec -= then->tv_sec;
+  mytime.tv_usec -= then->tv_usec;
+  if (mytime.tv_usec < 0) {
+    mytime.tv_usec += 1000000;
+    mytime.tv_sec--;
+  }
+  return &mytime;
+}
+
+/** Return the difference between two timeval structs in milliseconds.
+ * \param now pointer to the timeval to subtract from.
+ * \param then pointer to the timeval to subtract.
+ * \return milliseconds of difference between them.
+ */
+static long int
+msec_diff(struct timeval *now, struct timeval *then)
+{
+  long int secs = now->tv_sec - then->tv_sec;
+  if (secs == 0)
+    return (now->tv_usec - then->tv_usec) / 1000;
+  else if (secs == 1)
+    return (now->tv_usec + (1000000 - then->tv_usec)) / 100;
+  else if (secs > 1)
+    return (secs * 1000) + ((now->tv_usec + (1000000 - then->tv_usec)) / 1000);
+  else
+    return 0;
+}
+
+/** Add a given number of milliseconds to a timeval.
+ * \param t pointer to a timeval struct.
+ * \param x number of milliseconds to add to t.
+ * \return address of static timeval struct representing the sum.
+ */
+static struct timeval *
+msec_add(struct timeval *t, int x)
+{
+  static struct timeval mytime;
+  mytime.tv_sec = t->tv_sec;
+  mytime.tv_usec = t->tv_usec;
+  mytime.tv_sec += x / 1000;
+  mytime.tv_usec += (x % 1000) * 1000;
+  if (mytime.tv_usec >= 1000000) {
+    mytime.tv_sec += mytime.tv_usec / 1000000;
+    mytime.tv_usec = mytime.tv_usec % 1000000;
+  }
+  return &mytime;
+}
+
+/** Update each descriptor's allowed rate of issuing commands.
+ * Players are rate-limited; they may only perform up to a certain
+ * number of commands per time slice. This function is run periodically
+ * to refresh each descriptor's available command quota based on how
+ * many slices have passed since it was last updated.
+ * \param last pointer to timeval struct of last time quota was updated.
+ * \param current pointer to timeval struct of current time.
+ */
+static void
+update_quotas(struct timeval *last, struct timeval *current)
+{
+  int nslices;
+  DESC *d;
+  nslices = (int) msec_diff(current, last) / COMMAND_TIME_MSEC;
+
+  if (nslices > 0) {
+    for (d = descriptor_list; d; d = d->next) {
+      d->quota += COMMANDS_PER_TIME * nslices;
+      if (d->quota > COMMAND_BURST_SIZE)
+       d->quota = COMMAND_BURST_SIZE;
+    }
+  }
+}
+
+static const char *empabb(dbref player) {
+        static char str[4];
+        ATTR *a;
+       /*
+        dbref start, end, last;
+       */
+       dbref start;
+
+        memset(str, '\0', 4);
+
+        if(!IsDivision(SDIV(player).object))
+               goto bad_empabb_value;
+        start = SDIV(player).object;
+
+       /*
+        for(last = end = start; GoodObject(end) && IsDivision(end) &&
+                        !has_flag_by_name(end, "EMPIRE", TYPE_DIVISION) ; last = end, end = SDIV(end).object)
+                ;
+        if(!has_flag_by_name(end, "EMPIRE", TYPE_DIVISION)) {
+                if(end == NOTHING && IsDivision(last))
+                        end = last;
+                else end = start;
+        }
+       */
+        /* K, end is the empire we're grabbing this off of */
+        a = atr_get(start, "ALIAS");
+        if(!a)
+                goto bad_empabb_value;
+        strncpy(str, atr_value(a), 3);
+        if(!str[0])
+                goto bad_empabb_value;
+        return str;
+
+
+bad_empabb_value:
+        strncpy(str, "---", 3);
+        return str;
+}
+
+
+static void
+shovechars(Port_t port, Port_t sslport __attribute__ ((__unused__)))
+{
+  /* this is the main game loop */
+
+  fd_set input_set, output_set;
+  time_t now;
+  struct timeval last_slice, current_time, then;
+  struct timeval next_slice, *returned_time;
+  struct timeval timeout, slice_timeout;
+  int found;
+  int queue_timeout;
+  DESC *d, *dnext;
+#ifndef INFO_SLAVE
+  DESC *newd;
+  int result;
+#endif
+  int avail_descriptors;
+#ifdef INFO_SLAVE
+  union sockaddr_u addr;
+  socklen_t addr_len;
+  int newsock;
+#endif
+  int input_ready, output_ready;
+
+#ifdef NT_TCP
+  if (platform != VER_PLATFORM_WIN32_NT)
+#endif
+    if (!restarting) {
+      sock = make_socket(port, NULL, NULL, MUSH_IP_ADDR);
+      if (sock >= maxd)
+       maxd = sock + 1;
+#ifdef HAS_OPENSSL
+      if (sslport) {
+       sslsock = make_socket(sslport, NULL, NULL, SSL_IP_ADDR);
+       ssl_master_socket = ssl_setup_socket(sslsock);
+       if (sslsock >= maxd)
+         maxd = sslsock + 1;
+      }
+#endif
+    }
+  our_gettimeofday(&last_slice);
+
+  avail_descriptors = how_many_fds() - 4;
+#ifdef INFO_SLAVE
+  avail_descriptors -= 2;      /* reserve some more for setting up the slave */
+  FD_ZERO(&info_pending);
+#endif
+
+  /* done. print message to the log */
+  do_rawlog(LT_ERR, "%d file descriptors available.", avail_descriptors);
+  do_rawlog(LT_ERR, "RESTART FINISHED.");
+
+  our_gettimeofday(&then);
+
+  while (shutdown_flag == 0) {
+    our_gettimeofday(&current_time);
+
+    update_quotas(&last_slice, &current_time);
+    last_slice.tv_sec = current_time.tv_sec;
+    last_slice.tv_usec = current_time.tv_usec;
+
+    if (msec_diff(&current_time, &then) >= 1000) {
+      on_second = 1;
+      then.tv_sec = current_time.tv_sec;
+      then.tv_usec = current_time.tv_usec;
+    }
+
+    process_commands();
+
+    if (signal_shutdown_flag) {
+      flag_broadcast(0, 0, T("GAME: Shutdown by external signal"));
+      do_rawlog(LT_ERR, T("SHUTDOWN by external signal"));
+#ifdef AUTORESTART
+      system("touch NORESTART");
+#endif
+      shutdown_flag = 1;
+    }
+
+    if (signal_dump_flag) {
+      paranoid_dump = 0;
+      do_rawlog(LT_CHECK, "DUMP by external signal");
+      fork_and_dump(1);
+      signal_dump_flag = 0;
+    }
+
+    if (shutdown_flag)
+      break;
+
+    /* test for events */
+    dispatch();
+
+    /* any queued robot commands waiting? */
+    /* timeout.tv_sec used to be set to que_next(), the number of
+     * seconds before something on the queue needed to run, but this
+     * caused a problem with stuff that had to be triggered by alarm
+     * signal every second, so we're reduced to what's below:
+     */
+    queue_timeout = que_next();
+    timeout.tv_sec = queue_timeout ? 1 : 0;
+    timeout.tv_usec = 0;
+
+    returned_time = msec_add(&last_slice, COMMAND_TIME_MSEC);
+    next_slice.tv_sec = returned_time->tv_sec;
+    next_slice.tv_usec = returned_time->tv_usec;
+
+    returned_time = timeval_sub(&next_slice, &current_time);
+    slice_timeout.tv_sec = returned_time->tv_sec;
+    slice_timeout.tv_usec = returned_time->tv_usec;
+    /* Make sure slice_timeout cannot have a negative time. Better
+       safe than sorry. */
+    if (slice_timeout.tv_sec < 0)
+      slice_timeout.tv_sec = 0;
+    if (slice_timeout.tv_usec < 0)
+      slice_timeout.tv_usec = 0;
+
+#ifdef NT_TCP
+
+    /* for Windows NT, we handle IOs in a separate function for simplicity */
+    if (platform == VER_PLATFORM_WIN32_NT) {
+      ProcessWindowsTCP();
+      continue;
+    }                          /* end of NT_TCP and Windows NT */
+#endif
+
+    FD_ZERO(&input_set);
+    FD_ZERO(&output_set);
+    if (ndescriptors < avail_descriptors)
+      FD_SET(sock, &input_set);
+#ifdef HAS_OPENSSL
+    if (sslsock)
+      FD_SET(sslsock, &input_set);
+#endif
+#ifdef INFO_SLAVE
+    if (info_slave_state > 0)
+      FD_SET(info_slave, &input_set);
+#endif
+    for (d = descriptor_list; d; d = d->next) {
+      if (d->input.head) {
+       timeout.tv_sec = slice_timeout.tv_sec;
+       timeout.tv_usec = slice_timeout.tv_usec;
+      } else
+       FD_SET(d->descriptor, &input_set);
+      if (d->output.head)
+       FD_SET(d->descriptor, &output_set);
+    }
+
+    found = select(maxd, &input_set, &output_set, (fd_set *) 0, &timeout);
+    if (found < 0) {
+#ifdef WIN32
+      if (found == SOCKET_ERROR && WSAGetLastError() != WSAEINTR)
+#else
+      if (errno != EINTR)
+#endif
+      {
+       perror("select");
+       return;
+      }
+#ifdef INFO_SLAVE
+      now = mudtime;
+      if (info_slave_state == 2 && now > info_queue_time + 30) {
+       /* rerun any pending queries that got lost */
+       info_queue_time = now;
+       for (newsock = 0; newsock < maxd; newsock++)
+         if (FD_ISSET(newsock, &info_pending))
+           query_info_slave(newsock);
+      }
+#endif
+    } else {
+      /* if !found then time for robot commands */
+
+      if (!found) {
+       do_top(options.queue_chunk);
+       continue;
+      } else {
+       do_top(options.active_q_chunk);
+      }
+      now = mudtime;
+#ifdef INFO_SLAVE
+      if (info_slave_state > 0 && FD_ISSET(info_slave, &input_set)) {
+       if (info_slave_state == 1)
+         promote_info_slave();
+       else {
+         reap_info_slave();
+       }
+      } else if (info_slave_state == 2 && now > info_queue_time + 30) {
+       /* rerun any pending queries that got lost */
+       info_queue_time = now;
+       for (newsock = 0; newsock < maxd; newsock++)
+         if (FD_ISSET(newsock, &info_pending))
+           query_info_slave(newsock);
+      }
+
+      if (FD_ISSET(sock, &input_set)) {
+       addr_len = sizeof(addr);
+       newsock = accept(sock, (struct sockaddr *) &addr, &addr_len);
+       if (newsock < 0) {
+         if (test_connection(newsock) < 0)
+           continue;           /* this should _not_ be return. */
+       }
+       ndescriptors++;
+       query_info_slave(newsock);
+       if (newsock >= maxd)
+         maxd = newsock + 1;
+      }
+#ifdef HAS_OPENSSL
+      if (sslsock && FD_ISSET(sslsock, &input_set)) {
+       addr_len = sizeof(addr);
+       newsock = accept(sslsock, (struct sockaddr *) &addr, &addr_len);
+       if (newsock < 0) {
+         if (test_connection(newsock) < 0)
+           continue;           /* this should _not_ be return. */
+       }
+       ndescriptors++;
+       query_info_slave(newsock);
+       if (newsock >= maxd)
+         maxd = newsock + 1;
+      }
+#endif
+#else                          /* INFO_SLAVE */
+      if (FD_ISSET(sock, &input_set)) {
+       if (!(newd = new_connection(sock, &result, 0))) {
+         if (test_connection(result) < 0)
+           continue;           /* this should _not_ be return. */
+       } else {
+         ndescriptors++;
+         if (newd->descriptor >= maxd)
+           maxd = newd->descriptor + 1;
+       }
+      }
+#ifdef HAS_OPENSSL
+      if (sslsock && FD_ISSET(sslsock, &input_set)) {
+       if (!(newd = new_connection(sslsock, &result, 1))) {
+         if (test_connection(result) < 0)
+           continue;           /* this should _not_ be return. */
+       } else {
+         ndescriptors++;
+         if (newd->descriptor >= maxd)
+           maxd = newd->descriptor + 1;
+       }
+      }
+#endif
+#endif
+      for (d = descriptor_list; d; d = dnext) {
+       dnext = d->next;
+       input_ready = FD_ISSET(d->descriptor, &input_set);
+       output_ready = FD_ISSET(d->descriptor, &output_set);
+       if (input_ready) {
+         if (!process_input(d, output_ready)) {
+           shutdownsock(d);
+           continue;
+         }
+       }
+       if (output_ready) {
+         if (!process_output(d)) {
+           shutdownsock(d);
+         }
+       }
+      }
+    }
+  }
+}
+
+static int
+test_connection(int newsock)
+{
+#ifdef WIN32
+  if (newsock == INVALID_SOCKET && WSAGetLastError() != WSAEINTR)
+#else
+  if (errno && errno != EINTR)
+#endif
+  {
+    perror("test_connection");
+    return -1;
+  }
+  return newsock;
+}
+
+
+#ifndef INFO_SLAVE
+static DESC *
+new_connection(int oldsock, int *result, int use_ssl)
+{
+  int newsock;
+  union sockaddr_u addr;
+  struct hostname_info *hi;
+  socklen_t addr_len;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  char *bp;
+  char *socket_ident;
+  char *chp;
+
+  *result = 0;
+  addr_len = MAXSOCKADDR;
+  newsock = accept(oldsock, (struct sockaddr *) (addr.data), &addr_len);
+  if (newsock < 0) {
+    *result = newsock;
+    return 0;
+  }
+  bp = tbuf2;
+  hi = ip_convert(&addr.addr, addr_len);
+  safe_str(hi ? hi->hostname : "", tbuf2, &bp);
+  *bp = '\0';
+  bp = tbuf1;
+  if (USE_IDENT) {
+    int timeout = IDENT_TIMEOUT;
+    socket_ident = ident_id(newsock, &timeout);
+    if (socket_ident) {
+      /* Truncate at first non-printable character */
+      for (chp = socket_ident; *chp && isprint((unsigned char) *chp); chp++) ;
+      *chp = '\0';
+      safe_str(socket_ident, tbuf1, &bp);
+      safe_chr('@', tbuf1, &bp);
+      free(socket_ident);
+    }
+  }
+  hi = hostname_convert(&addr.addr, addr_len);
+  safe_str(hi ? hi->hostname : "", tbuf1, &bp);
+  *bp = '\0';
+  if (Forbidden_Site(tbuf1) || Forbidden_Site(tbuf2)) {
+    if (!Deny_Silent_Site(tbuf1, AMBIGUOUS)
+       || !Deny_Silent_Site(tbuf2, AMBIGUOUS)) {
+      do_log(LT_CONN, 0, 0, "[%d/%s/%s] %s (%s %s)", newsock, tbuf1, tbuf2,
+            T("Refused connection"), T("remote port"),
+            hi ? hi->port : T("(unknown)"));
+    }
+    shutdown(newsock, 2);
+    closesocket(newsock);
+#ifndef WIN32
+    errno = 0;
+#endif
+    return 0;
+  }
+  do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection opened."), newsock, tbuf1,
+        tbuf2);
+  set_keepalive(newsock);
+  return initializesock(newsock, tbuf1, tbuf2, use_ssl);
+}
+#endif
+
+static void
+clearstrings(DESC *d)
+{
+  if (d->output_prefix) {
+    mush_free((Malloc_t) d->output_prefix, "userstring");
+    d->output_prefix = 0;
+  }
+  if (d->output_suffix) {
+    mush_free((Malloc_t) d->output_suffix, "userstring");
+    d->output_suffix = 0;
+  }
+}
+
+/* Display a cached text file. If a prefix line was given,
+ * display that line before the text file, but only if we've
+ * got a text file to display
+ */
+static void
+fcache_dump(DESC *d, FBLOCK fb[2], const unsigned char *prefix)
+{
+  /* If we've got nothing nice to say, don't say anything */
+  if (!fb[0].buff && !((d->conn_flags & CONN_HTML) && fb[1].buff))
+    return;
+  /* We've got something to say */
+  if (prefix) {
+    queue_newwrite(d, prefix, u_strlen(prefix));
+    queue_eol(d);
+  }
+  if (d->conn_flags & CONN_HTML) {
+    if (fb[1].buff)
+      queue_newwrite(d, fb[1].buff, fb[1].len);
+    else
+      queue_write(d, fb[0].buff, fb[0].len);
+  } else
+    queue_write(d, fb[0].buff, fb[0].len);
+}
+
+
+static int
+fcache_read(FBLOCK *fb, const char *filename)
+{
+  if (!fb || !filename)
+    return -1;
+
+  /* Free prior cache */
+  if (fb->buff) {
+    mush_free(fb->buff, "fcache_data");
+  }
+
+  fb->buff = NULL;
+  fb->len = 0;
+
+#ifdef WIN32
+  /* Win32 read code here */
+  {
+    HANDLE fh;
+    BY_HANDLE_FILE_INFORMATION sb;
+    DWORD r = 0;
+
+
+    if ((fh = CreateFile(filename, GENERIC_READ, 0, NULL,
+                        OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
+      return -1;
+
+    if (!GetFileInformationByHandle(fh, &sb)) {
+      CloseHandle(fh);
+      return -1;
+    }
+
+    fb->len = sb.nFileSizeLow;
+
+    if (!(fb->buff = mush_malloc(sb.nFileSizeLow, "fcache_data"))) {
+      CloseHandle(fh);
+      return -1;
+    }
+
+    if (!ReadFile(fh, fb->buff, sb.nFileSizeLow, &r, NULL) || fb->len != r) {
+      CloseHandle(fh);
+      mush_free(fb->buff, "fcache_data");
+      fb->buff = NULL;
+      return -1;
+    }
+
+    CloseHandle(fh);
+
+    fb->len = sb.nFileSizeLow;
+    return (int) fb->len;
+  }
+#else
+  /* Posix read code here */
+  {
+    int fd, n;
+    struct stat sb;
+
+    release_fd();
+    if ((fd = open(filename, O_RDONLY, 0)) < 0) {
+      do_log(LT_ERR, 0, 0, T("Couldn't open cached text file '%s'"), filename);
+      reserve_fd();
+      return -1;
+    }
+
+    if (fstat(fd, &sb) < 0) {
+      do_log(LT_ERR, 0, 0, T("Couldn't get the size of text file '%s'"),
+            filename);
+      close(fd);
+      reserve_fd();
+      return -1;
+    }
+
+
+    if (!(fb->buff = mush_malloc(sb.st_size, "fcache_data"))) {
+      do_log(LT_ERR, 0, 0, T("Couldn't allocate %d bytes of memory for '%s'!"),
+            (int) sb.st_size, filename);
+      close(fd);
+      reserve_fd();
+      return -1;
+    }
+
+    if ((n = read(fd, fb->buff, sb.st_size)) != sb.st_size) {
+      do_log(LT_ERR, 0, 0, T("Couldn't read all of '%s'"), filename);
+      close(fd);
+      mush_free(fb->buff, "fcache_data");
+      fb->buff = NULL;
+      reserve_fd();
+      return -1;
+    }
+
+    close(fd);
+    reserve_fd();
+    fb->len = sb.st_size;
+  }
+#endif                         /* Posix read code */
+
+  return fb->len;
+}
+
+/** Load all of the cached text files.
+ * \param player the enactor.
+ */
+void
+fcache_load(dbref player)
+{
+  int conn, motd, new, reg, quit, down, full;
+  int guest;
+  int i;
+
+  for (i = 0; i < (SUPPORT_PUEBLO ? 2 : 1); i++) {
+    conn = fcache_read(&fcache.connect_fcache[i], options.connect_file[i]);
+    motd = fcache_read(&fcache.motd_fcache[i], options.motd_file[i]);
+    new = fcache_read(&fcache.newuser_fcache[i], options.newuser_file[i]);
+    reg = fcache_read(&fcache.register_fcache[i], options.register_file[i]);
+    quit = fcache_read(&fcache.quit_fcache[i], options.quit_file[i]);
+    down = fcache_read(&fcache.down_fcache[i], options.down_file[i]);
+    full = fcache_read(&fcache.full_fcache[i], options.full_file[i]);
+    guest = fcache_read(&fcache.guest_fcache[i], options.guest_file[i]);
+
+    if (player != NOTHING) {
+      notify_format(player,
+                   T
+                   ("%s sizes:  NewUser...%d  Connect...%d  Guest...%d  Motd...%d  Quit...%d  Register...%d  Down...%d  Full...%d"),
+                   i ? "HTMLFile" : "File", new, conn, guest, motd, quit,
+                   reg, down, full);
+    }
+  }
+
+}
+
+/** Initialize all of the cached text files (at startup).
+ */
+void
+fcache_init(void)
+{
+  fcache_load(NOTHING);
+}
+
+static void
+logout_sock(DESC *d)
+{
+  SU_PATH *path_entry;
+
+  int n;
+  char tbuf1[BUFFER_LEN];
+
+  if (d->connected) {
+    fcache_dump(d, fcache.quit_fcache, NULL);
+    do_log(LT_CONN, 0, 0,
+          T("[%d/%s/%s] Logout by %s(#%d) <Connection not dropped>"),
+          d->descriptor, d->addr, d->ip, Name(d->player), d->player);
+    if(d->last_time > 0) {
+      d->idle_total += difftime(mudtime, d->last_time);
+      d->unidle_times++;
+    }
+    snprintf(tbuf1, BUFFER_LEN-1, "%ld %ld %d %d", (mudtime - d->connected_at),
+       d->idle_total, d->unidle_times, d->cmds); 
+    tbuf1[strlen(tbuf1)+1] = '\0';
+    (void) atr_add(d->player, "LASTACTIVITY", tbuf1, GOD, NOTHING);
+    announce_disconnect(d->player);
+    do_mail_purge(d->player);
+    if (MAX_LOGINS) {
+      login_number--;
+      if (!under_limit && (login_number < MAX_LOGINS)) {
+       under_limit = 1;
+       do_log(LT_CONN, 0, 0,
+              T("Below maximum player limit of %d. Logins enabled."),
+              MAX_LOGINS);
+      }
+    }
+  } else {
+    do_log(LT_CONN, 0, 0,
+          T("[%d/%s/%s] Logout, never connected. <Connection not dropped>"),
+          d->descriptor, d->addr, d->ip);
+  }
+  process_output(d);           /* flush our old output */
+  /* pretend we have a new connection */
+  d->input_handler = do_command;
+  d->connected = 0;
+  d->output_prefix = 0;
+  d->output_suffix = 0;
+  d->output_size = 0;
+  d->output.head = 0;
+  d->player = 0;
+  d->output.tail = &d->output.head;
+  d->input.head = 0;
+  d->input.tail = &d->input.head;
+  d->raw_input = 0;
+  d->raw_input_at = 0;
+  d->quota = COMMAND_BURST_SIZE;
+  d->last_time = mudtime;
+  d->idle_total = 0;
+  d->unidle_times = 0;
+  d->cmds = 0;
+  d->hide = 0;
+  d->doing[0] = '\0';
+  d->mailp = NULL;
+  d->pinfo.object = NOTHING;
+  d->pinfo.atr = NULL;
+  d->pinfo.lock = 0;
+  d->pinfo.function = NULL;
+
+  while(d->su_exit_path) {
+    path_entry = d->su_exit_path;
+    d->su_exit_path = path_entry->next;
+    mush_free(path_entry, "SU_EXIT_PATH");
+  }
+  welcome_user(d);
+  for(n = 0; n < MAX_SNOOPS; n++)
+    d->snooper[n] = -1;
+}
+
+/** Disconnect a descriptor.
+ * This sends appropriate disconnection text, flushes output, and
+ * then closes the associated socket.
+ * \param d pointer to descriptor to disconnect.
+ */
+static void
+shutdownsock(DESC *d)
+{
+  char tbuf1[BUFFER_LEN];
+#ifdef NT_TCP
+
+  /* don't close down the socket twice */
+  if (d->bConnectionShutdown)
+    return;
+  /* make sure we don't try to initiate or process any outstanding IOs */
+  d->bConnectionShutdown = TRUE;
+  d->bConnectionDropped = TRUE;
+#endif
+
+  if (d->connected) {
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Logout by %s(#%d)"),
+          d->descriptor, d->addr, d->ip, Name(d->player), d->player);
+    if (d->connected != 2) {
+      fcache_dump(d, fcache.quit_fcache, NULL);
+      /* Player was not allowed to log in from the connect screen */
+      if(d->last_time > 0) {
+       d->idle_total += difftime(mudtime, d->last_time);
+       d->unidle_times++;
+      }
+      snprintf(tbuf1, BUFFER_LEN-1, "%ld %ld %d %d", (mudtime - d->connected_at), 
+         d->idle_total , d->unidle_times, d->cmds);
+      tbuf1[strlen(tbuf1)+1] = '\0';
+      (void) atr_add(d->player, "LASTACTIVITY", tbuf1, GOD, NOTHING);
+      announce_disconnect(d->player);
+      do_mail_purge(d->player);
+    }
+    if (MAX_LOGINS) {
+      login_number--;
+      if (!under_limit && (login_number < MAX_LOGINS)) {
+       under_limit = 1;
+       do_log(LT_CONN, 0, 0,
+              T("Below maximum player limit of %d. Logins enabled."),
+              MAX_LOGINS);
+      }
+    }
+  } else {
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection closed, never connected."),
+          d->descriptor, d->addr, d->ip);
+  }
+  process_output(d);
+  clearstrings(d);
+#ifdef NT_TCP
+
+  if (platform == VER_PLATFORM_WIN32_NT) {
+    /* cancel any pending reads or writes on this socket */
+
+    if (!CancelIo((HANDLE) d->descriptor)) {
+      char sMessage[200];
+      GetErrorMessage(GetLastError(), sMessage, sizeof sMessage);
+      printf("Error %ld (%s) on CancelIo\n", GetLastError(), sMessage);
+    }
+    /* post a notification that it is safe to free the descriptor */
+    /* we can't free the descriptor here (below) as there may be some */
+    /* queued completed IOs that will crash when they refer to a descriptor */
+    /* (d) that has been freed. */
+
+    if (!PostQueuedCompletionStatus(CompletionPort, 0, (DWORD) d, &lpo_aborted)) {
+      char sMessage[200];
+      DWORD nError = GetLastError();
+      GetErrorMessage(nError, sMessage, sizeof sMessage);
+      printf
+       ("Error %ld (%s) on PostQueuedCompletionStatus in shutdownsock\n",
+        nError, sMessage);
+    }
+  }
+#endif
+  shutdown(d->descriptor, 2);
+  closesocket(d->descriptor);
+#ifdef NT_TCP
+
+  /* protect removing the descriptor from our linked list from */
+  /* any interference from the listening thread */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    EnterCriticalSection(&cs);
+#endif
+
+  if (d->prev)
+    d->prev->next = d->next;
+  else                         /* d was the first one! */
+    descriptor_list = d->next;
+  if (d->next)
+    d->next->prev = d->prev;
+
+#ifdef HAS_OPENSSL
+  if (sslsock && d->ssl) {
+    ssl_close_connection(d->ssl);
+    d->ssl = NULL;
+  }
+#endif
+
+#ifdef NT_TCP
+  /* safe to allow the listening thread to continue now */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    LeaveCriticalSection(&cs);
+  else
+    /* we cannot free the strings or descriptor if we have queued IOs */
+#endif
+  {
+    freeqs(d);
+    mush_free(d->ttype, "terminal description");
+    mush_free((Malloc_t) d, "descriptor");
+  }
+
+  ndescriptors--;
+}
+
+/* ARGSUSED */
+static DESC *
+initializesock(int s, char *addr, char *ip, int use_ssl
+               __attribute__ ((__unused__)))
+{
+  DESC *d;
+  int n;
+
+  d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
+  if (!d)
+    mush_panic("Out of memory.");
+  d->descriptor = s;
+  d->input_handler = do_command;
+  d->connected = 0;
+  d->connected_at = mudtime;
+  make_nonblocking(s);
+  d->output_prefix = 0;
+  d->output_suffix = 0;
+  d->output_size = 0;
+  d->output.head = 0;
+  d->player = 0;
+  d->output.tail = &d->output.head;
+  d->input.head = 0;
+  d->input.tail = &d->input.head;
+  d->raw_input = 0;
+  d->raw_input_at = 0;
+  d->quota = COMMAND_BURST_SIZE;
+  d->last_time = mudtime;
+  d->idle_total = 0;
+  d->unidle_times = 0;
+  d->cmds = 0;
+  d->hide = 0;
+  d->doing[0] = '\0';
+  d->mailp = NULL;
+  strncpy(d->addr, addr, 100);
+  d->addr[99] = '\0';
+  strncpy(d->ip, ip, 100);
+  d->ip[99] = '\0';
+  d->conn_flags = CONN_DEFAULT;
+  d->input_chars = 0;
+  d->output_chars = 0;
+  d->ttype = mush_strdup("unknown", "terminal description");
+  d->checksum[0] = '\0';
+  d->su_exit_path = NULL;
+  d->pinfo.object = NOTHING;
+  d->pinfo.atr = NULL;
+  d->pinfo.lock = 0;
+  d->pinfo.function = NULL;
+#ifdef HAS_OPENSSL
+  d->ssl = NULL;
+  d->ssl_state = 0;
+#endif
+#ifdef NT_TCP
+  /* protect adding the descriptor from the linked list from */
+  /* any interference from socket shutdowns */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    EnterCriticalSection(&cs);
+#endif
+
+  if (descriptor_list)
+    descriptor_list->prev = d;
+  d->next = descriptor_list;
+  d->prev = NULL;
+  descriptor_list = d;
+
+#ifdef NT_TCP
+  /* ok to continue now */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    LeaveCriticalSection(&cs);
+  d->OutboundOverlapped.hEvent = NULL;
+  d->InboundOverlapped.hEvent = NULL;
+  d->InboundOverlapped.Offset = 0;
+  d->InboundOverlapped.OffsetHigh = 0;
+  d->bWritePending = FALSE;    /* no write pending yet */
+  d->bConnectionShutdown = FALSE;      /* not shutdown yet */
+  d->bConnectionDropped = FALSE;       /* not dropped yet */
+#else
+  d->width = 78;
+  d->height = 24;
+#ifdef HAS_OPENSSL
+  if (use_ssl && sslsock) {
+    d->ssl = ssl_listen(d->descriptor, &d->ssl_state);
+    if (d->ssl_state < 0) {
+      /* Error we can't handle */
+      ssl_close_connection(d->ssl);
+      d->ssl = NULL;
+      d->ssl_state = 0;
+    }
+  }
+#endif
+  test_telnet(d);
+  welcome_user(d);
+#endif
+  for(n = 0; n < MAX_SNOOPS; n++)
+    d->snooper[n] = -1;
+  return d;
+}
+
+#ifdef INFO_SLAVE
+static void
+make_info_slave(void)
+{
+#ifdef HAS_SOCKETPAIR
+  int socks[2];
+#else
+  union sockaddr_u addr;
+  socklen_t opt;
+#endif
+  char num[NI_MAXSERV];
+  Pid_t child;
+  int n;
+
+  if (info_slave_state != 0) {
+    if (info_slave_pid > 0) {
+      closesocket(info_slave);
+      kill(info_slave_pid, 15);
+      info_slave_pid = -1;
+    }
+    info_slave_state = 0;
+  }
+#ifdef HAS_SOCKETPAIR
+  /* Use Posix.1g names... */
+#ifndef AF_LOCAL
+#define AF_LOCAL AF_UNIX
+#endif
+  if (socketpair(AF_LOCAL, SOCK_STREAM, 0, socks) < 0) {
+    perror("creating slave stream socketpair");
+    return;
+  }
+  if (socks[0] >= maxd)
+    maxd = socks[0] + 1;
+  if (socks[1] >= maxd)
+    maxd = socks[1] + 1;
+#else
+  info_slave = make_socket(0, &addr, &opt, MUSH_IP_ADDR);
+  if (getsockname(info_slave, (struct sockaddr *) &addr.data, &opt) < 0) {
+    perror("getsockname");
+    fflush(stderr);
+    closesocket(info_slave);
+    return;
+  }
+  if (getnameinfo(&addr.addr, opt, NULL, 0, num, sizeof num,
+                 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+    perror("getting address of slave stream socket");
+    fflush(stderr);
+    closesocket(info_slave);
+    return;
+  }
+  listen(info_slave, 1);
+#endif
+  child = fork();
+  if (child < 0) {
+    perror("forking info slave");
+#ifdef HAS_SOCKETPAIR
+    closesocket(socks[0]);
+    closesocket(socks[1]);
+#else
+    closesocket(info_slave);
+#endif
+    return;
+  }
+  if (child) {
+    info_slave_state = 1;
+    info_slave_pid = child;
+#ifdef HAS_SOCKETPAIR
+    info_slave = socks[0];
+    closesocket(socks[1]);
+    do_rawlog(LT_ERR,
+             "Spawning info slave using socketpair, pid %d, ident %d",
+             child, USE_IDENT);
+#else
+    do_rawlog(LT_ERR, "Spawning info slave on port %s, pid %d, ident %d",
+             num, child, USE_IDENT);
+#endif
+  } else {
+    /* Close unneeded fds and sockets */
+    for (n = 3; n < maxd; n++) {
+      if (n == fileno(stderr))
+       continue;
+#ifdef HAS_SOCKETPAIR
+      if (n == socks[1])
+       continue;
+#endif
+      close(n);
+    }
+#ifdef HAS_SOCKETPAIR
+    sprintf(num, "%d", socks[1]);
+#endif
+    if (!USE_IDENT)
+      execl("./info_slave", "./info_slave", num, "-1", USE_DNS ? "1" : "0",
+           (char *) NULL);
+    else
+      execl("./info_slave", "./info_slave", num, tprintf("%d", IDENT_TIMEOUT),
+           USE_DNS ? "1" : "0", (char *) NULL);
+    perror("execing info slave");
+    exit(1);
+  }
+  info_query_spill = 0;
+  info_reap_spill = 0;
+  if (info_slave >= maxd)
+    maxd = info_slave + 1;
+#ifdef HAS_SOCKETPAIR
+  promote_info_slave();
+#endif
+}
+
+static void
+promote_info_slave(void)
+{
+  int j;
+#ifndef HAS_SOCKETPAIR
+  int newsock;
+  union sockaddr_u addr;
+  socklen_t addr_len;
+  char port[NI_MAXSERV];
+#endif
+
+  if (info_slave_state != 1) {
+    make_info_slave();
+    return;
+  }
+#ifndef HAS_SOCKETPAIR
+  addr_len = MAXSOCKADDR;
+  newsock = accept(info_slave, (struct sockaddr *) addr.data, &addr_len);
+  if (newsock < 0) {
+    perror("accepting info slave connection");
+    make_info_slave();
+    return;
+  }
+  closesocket(info_slave);
+  info_slave = newsock;
+#endif
+  make_nonblocking(info_slave);
+  /* Do authentication here, if we care */
+  info_slave_state = 2;
+#ifdef HAS_SOCKETPAIR
+  do_rawlog(LT_ERR, "Accepted info slave from unix-domain socket");
+#else
+  if (getnameinfo(&addr.addr, addr_len, NULL, 0, port, sizeof port,
+                 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+    perror("getting info slave port number");
+  } else {
+    do_rawlog(LT_ERR, "Accepted info slave from port %s", port);
+  }
+#endif
+  for (j = 0; j < maxd; j++)
+    if (FD_ISSET(j, &info_pending))
+      query_info_slave(j);
+  if (info_slave >= maxd)
+    maxd = info_slave + 1;
+}
+
+static void
+query_info_slave(int fd)
+{
+  int size, slen;
+  socklen_t llen, rlen;
+  static char buf[1024];       /* overkill */
+  union sockaddr_u laddr, raddr;
+  char *bp;
+  struct hostname_info *hi;
+  struct iovec dat[6];
+
+  FD_SET(fd, &info_pending);
+  info_queue_time = mudtime;
+
+  if (info_slave_state != 2) {
+    make_info_slave();
+    return;
+  }
+
+  /* cleanup for truncated packet */
+  size = info_query_spill;
+  memset(buf, 0, size);
+  info_query_spill = 0;
+  dat[0].iov_base = buf;
+  dat[0].iov_len = size;
+
+  rlen = MAXSOCKADDR;
+  if (getpeername(fd, (struct sockaddr *) raddr.data, &rlen) < 0) {
+    perror("socket peer vanished");
+    shutdown(fd, 2);
+    closesocket(fd);
+    FD_CLR(fd, &info_pending);
+    return;
+  }
+
+  /* Check for forbidden sites before bothering with ident */
+  bp = buf;
+  hi = ip_convert(&raddr.addr, rlen);
+  safe_str(hi ? hi->hostname : "Not found", buf, &bp);
+  *bp = '\0';
+  if (Forbidden_Site(buf)) {
+    char port[NI_MAXSERV];
+    if (getnameinfo(&raddr.addr, rlen, NULL, 0, port, sizeof port,
+                   NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+      perror("getting remote port number");
+    else {
+      if (!Deny_Silent_Site(buf, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0, T("[%d/%s] Refused connection (remote port %s)"),
+              fd, buf, port);
+      }
+    }
+    shutdown(fd, 2);
+    closesocket(fd);
+    FD_CLR(fd, &info_pending);
+    return;
+  }
+
+  /* Packet format: SIZESTRUCT for remote address, SIZE STRUCT for
+   * local address, FD. We use writev() to make the packet format
+   * explicit to the reader.  */
+  dat[1].iov_base = (char *) &rlen;
+  dat[1].iov_len = sizeof rlen;
+  dat[2].iov_base = (char *) &raddr.addr;
+  dat[2].iov_len = rlen;
+
+  llen = MAXSOCKADDR;
+  if (getsockname(fd, (struct sockaddr *) laddr.data, &llen) < 0) {
+    perror("socket self vanished");
+    closesocket(fd);
+    FD_CLR(fd, &info_pending);
+    return;
+  }
+
+  dat[3].iov_base = (char *) &llen;
+  dat[3].iov_len = sizeof llen;
+  dat[4].iov_base = (char *) &laddr.addr;
+  dat[4].iov_len = llen;
+  dat[5].iov_base = (char *) &fd;
+  dat[5].iov_len = sizeof fd;
+  size = dat[0].iov_len + dat[1].iov_len + dat[2].iov_len + dat[3].iov_len
+    + dat[4].iov_len + dat[5].iov_len;
+  slen = writev(info_slave, dat, 6);
+  if (slen < 0) {
+    perror("info slave query: write error");
+    if (errno != EWOULDBLOCK)
+      make_info_slave();
+    return;
+  } else if (slen < size) {
+    /* drop partial packet on floor.  cleanup later. */
+    perror("info slave query: partial packet");
+    info_query_spill = size - slen;
+  }
+}
+
+static void
+reap_info_slave(void)
+{
+  int fd, len, size;
+  static char buf[10000];      /* overkill */
+  char *bp, *bp2;
+  struct iovec dat[2];
+
+  if (info_slave_state != 2) {
+    make_info_slave();
+    return;
+  }
+
+  if (info_reap_spill) {
+    /* clean up some mess */
+    len = read(info_slave, buf, info_reap_spill);
+    if (len < 1) {
+      /* crap.  lost the slave. */
+      perror("info slave reap spill");
+      make_info_slave();
+      return;
+    }
+    info_reap_spill -= len;
+    if (info_reap_spill)
+      /* can't read enough, come back later */
+      return;
+  }
+  for (;;) {
+    /* Packet format: FD, STRLEN, STR. We use readv() to cut down on
+       the number of read() system calls. */
+    /* grab the fd and string length */
+    dat[0].iov_base = (char *) &fd;
+    dat[0].iov_len = sizeof fd;
+    dat[1].iov_base = (char *) &size;
+    dat[1].iov_len = sizeof size;
+    len = readv(info_slave, dat, 2);
+    if (len < 0 && errno == EWOULDBLOCK)
+      /* got all the data */
+      return;
+    if (len < (int) (sizeof fd + sizeof size)) {
+      /* we're hosed now */
+      perror("info slave reap reading fd and hostname length");
+      make_info_slave();
+      return;
+    }
+    if (size < 0 || size > (int) sizeof buf) {
+      perror("info slave real size");
+      make_info_slave();
+      return;
+    }
+
+    /* grab the actual string */
+    len = read(info_slave, buf, size);
+
+    if (len < 0) {
+      /* crap.  lost the slave. */
+      perror("info slave reap string");
+      make_info_slave();
+      return;
+    }
+    buf[len] = '\0';
+    if (len < size) {
+      /* crap... lost some.  clean up the mess and requery */
+      info_reap_spill = size - len;
+      query_info_slave(fd);
+      return;
+    }
+    /* okay, now we have some info! */
+    if (!FD_ISSET(fd, &info_pending))
+      /* got a bloody duplicate */
+      return;
+    FD_CLR(fd, &info_pending);
+    /* buf ideally contains ipaddr^local port^ident-info */
+    bp = NULL;
+    if ((bp2 = strchr(buf, '^'))) {
+      *bp2++ = '\0';
+      /* buf is ip addr, bp is local port^ident info */
+      if ((bp = strchr(bp2, '^')))
+       *bp++ = '\0';
+    }
+    /* Now, either buf = ipaddr, bp2 = port, bp = ident info,
+     * or buf = ipaddr, bp2 = port
+     */
+    if (Forbidden_Site(buf) || (bp && Forbidden_Site(bp))) {
+      if (!Deny_Silent_Site(buf, AMBIGUOUS) || !Deny_Silent_Site(bp, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Refused connection."), fd,
+              bp ? bp : "", buf);
+      }
+      shutdown(fd, 2);
+      closesocket(fd);
+      return;
+    }
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection opened."), fd,
+          bp ? bp : "", buf);
+    set_keepalive(fd);
+    (void) initializesock(fd, bp ? bp : buf, buf, (atoi(bp2) == SSLPORT));
+  }
+}
+
+/** Kill the info_slave process, typically at shutdown.
+ */
+void
+kill_info_slave(void)
+{
+  WAIT_TYPE my_stat;
+  Pid_t pid;
+  struct timeval pad;
+
+  if (info_slave_state != 0) {
+    if (info_slave_pid > 0) {
+      do_rawlog(LT_ERR, "kill: killing info_slave_pid %d", info_slave_pid);
+
+      block_a_signal(SIGCHLD);
+
+      closesocket(info_slave);
+      kill(info_slave_pid, 15);
+      /* Have to wait long enough for the info_slave to actually
+         die. This will hopefully be enough time. */
+      pad.tv_sec = 0;
+      pad.tv_usec = 100;
+      select(0, NULL, NULL, NULL, &pad);
+
+#ifdef HAS_WAITPID
+      pid = waitpid(info_slave_pid, &my_stat, WNOHANG);
+#else
+      pid = wait3(&my_stat, WNOHANG, 0);
+#endif
+      info_slave_pid = -1;
+      unblock_a_signal(SIGCHLD);
+    }
+    info_slave_state = 0;
+  }
+}
+#endif
+
+
+
+/** Flush pending output for a descriptor.
+ * This function actually sends the queued output over the descriptor's
+ * socket.
+ * \param d pointer to descriptor to send output to.
+ * \retval 1 successfully flushed at least some output.
+ * \retval 0 something failed, and the descriptor should probably be closed.
+ */
+int
+process_output(DESC *d)
+{
+  struct text_block **qp, *cur;
+  int cnt;
+#ifdef HAS_OPENSSL
+  int input_ready = 0;
+#endif
+
+#ifdef HAS_OPENSSL
+  /* Insure that we're not in a state where we need an SSL_handshake() */
+  if (d->ssl && (ssl_need_handshake(d->ssl_state))) {
+    d->ssl_state = ssl_handshake(d->ssl);
+    if (d->ssl_state < 0) {
+      /* Fatal error */
+      ssl_close_connection(d->ssl);
+      d->ssl = NULL;
+      d->ssl_state = 0;
+      return 0;
+    } else if (ssl_need_handshake(d->ssl_state)) {
+      /* We're still not ready to send to this connection. Alas. */
+      return 1;
+    }
+  }
+  /* Insure that we're not in a state where we need an SSL_accept() */
+  if (d->ssl && (ssl_need_accept(d->ssl_state))) {
+    d->ssl_state = ssl_accept(d->ssl);
+    if (d->ssl_state < 0) {
+      /* Fatal error */
+      ssl_close_connection(d->ssl);
+      d->ssl = NULL;
+      d->ssl_state = 0;
+      return 0;
+    } else if (ssl_need_accept(d->ssl_state)) {
+      /* We're still not ready to send to this connection. Alas. */
+      return 1;
+    }
+  }
+  if (d->ssl) {
+    /* process_output, alas, gets called from all kinds of places.
+     * We need to know if the descriptor is waiting on input, though.
+     * So let's find out
+     */
+    struct timeval pad;
+    fd_set input_set;
+
+    pad.tv_sec = 0;
+    pad.tv_usec = 0;
+    FD_ZERO(&input_set);
+    FD_SET(d->descriptor, &input_set);
+    input_ready = select(d->descriptor + 1, &input_set, NULL, NULL, &pad);
+    if (input_ready < 0) {
+      /* Well, shoot, we have no idea. Guess and proceed. */
+      perror("select in process_output");
+      input_ready = 0;
+    }
+  }
+#endif
+
+  for (qp = &d->output.head; ((cur = *qp) != NULL);) {
+#ifdef HAS_OPENSSL
+    if (d->ssl) {
+      cnt = 0;
+      d->ssl_state = ssl_write(d->ssl, d->ssl_state, input_ready, 1, cur->start,
+                              cur->nchars, &cnt);
+      if (ssl_want_write(d->ssl_state))
+       return 1;               /* Need to retry */
+    } else {
+#endif
+      cnt = send(d->descriptor, cur->start, cur->nchars, 0);
+      if (cnt < 0) {
+#ifdef WIN32
+       if (cnt == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
+#else
+#ifdef EAGAIN
+       if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
+#else
+       if (errno == EWOULDBLOCK)
+#endif
+#endif
+         return 1;
+       return 0;
+      }
+#ifdef HAS_OPENSSL
+    }
+#endif
+    d->output_size -= cnt;
+    d->output_chars += cnt;
+    if (cnt == cur->nchars) {
+      if (!cur->nxt)
+       d->output.tail = qp;
+      *qp = cur->nxt;
+#ifdef DEBUG
+      do_rawlog(LT_ERR, "free_text_block(0x%x) at 2.", cur);
+#endif                         /* DEBUG */
+      free_text_block(cur);
+      continue;                        /* do not adv ptr */
+    }
+    cur->nchars -= cnt;
+    cur->start += cnt;
+    break;
+  }
+  return 1;
+}
+
+
+static void
+welcome_user(DESC *d)
+{
+  if (SUPPORT_PUEBLO && !(d->conn_flags & CONN_HTML))
+    queue_newwrite(d, (unsigned char *) PUEBLO_HELLO, strlen(PUEBLO_HELLO));
+  fcache_dump(d, fcache.connect_fcache, NULL);
+}
+
+static void
+save_command(DESC *d, const unsigned char *command)
+{
+  add_to_queue(&d->input, command, u_strlen(command) + 1);
+}
+
+static void
+test_telnet(DESC *d)
+{
+  /* Use rfc 1184 to test telnet support, as it tries to set linemode
+     with client-side editing. Good for Broken Telnet Programs. */
+  if (!TELNET_ABLE(d)) {
+    /*  IAC DO LINEMODE */
+    unsigned char query[3] = "\xFF\xFD\x22";
+    queue_newwrite(d, query, 3);
+    d->conn_flags |= CONN_TELNET_QUERY;
+    process_output(d);
+  }
+}
+
+static void
+setup_telnet(DESC *d)
+{
+  /* Win2k telnet doesn't do local echo by default,
+     apparently. Unfortunately, there doesn't seem to be a telnet
+     option for local echo, just remote echo. */
+  d->conn_flags |= CONN_TELNET;
+  if (d->conn_flags & CONN_TELNET_QUERY) {
+    /* IAC DO NAWS IAC DO TERMINAL-TYPE */
+    unsigned char extra_options[6] = "\xFF\xFD\x1F" "\xFF\xFD\x18";
+    d->conn_flags &= ~CONN_TELNET_QUERY;
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Switching to Telnet mode."),
+          d->descriptor, d->addr, d->ip);
+    queue_newwrite(d, extra_options, 6);
+    process_output(d);
+  }
+}
+
+static int
+handle_telnet(DESC *d, unsigned char **q, unsigned char *qend)
+{
+  int i;
+
+  /* *(*q - q) == IAC at this point. */
+  switch (**q) {
+  case SB:                     /* Sub-option */
+    if (*q >= qend)
+      return -1;
+    (*q)++;
+    if (**q == TN_LINEMODE) {
+      if ((*q + 2) >= qend)
+       return -1;
+      *q += 2;
+      while (*q < qend && **q != SE)
+       (*q)++;
+      if (*q >= qend)
+       return -1;
+    } else if (**q == TN_NAWS) {
+      /* Learn what size window the client is using. */
+      union {
+       short s;
+       unsigned char bytes[2];
+      } raw;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      /* Width */
+      if (**q == IAC) {
+       raw.bytes[0] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[0] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      if (**q == IAC) {
+       raw.bytes[1] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[1] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+
+      d->width = ntohs(raw.s);
+
+      /* Height */
+      if (**q == IAC) {
+       raw.bytes[0] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[0] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      if (**q == IAC) {
+       raw.bytes[1] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[1] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      d->height = ntohs(raw.s);
+
+      /* IAC SE */
+      if (*q + 1 >= qend)
+       return -1;
+      (*q)++;
+    } else if (**q == TN_TTYPE) {
+      /* Read the terminal type: TERMINAL-TYPE IS blah IAC SE */
+      char tbuf[BUFFER_LEN], *bp = tbuf;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      /* Skip IS */
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+
+      /* Read up to IAC SE */
+      while (1) {
+       if (*q >= qend)
+         return -1;
+       if (**q == IAC) {
+         if (*q + 1 >= qend)
+           return -1;
+         if (*(*q + 1) == IAC) {
+           safe_chr((char) IAC, tbuf, &bp);
+           (*q)++;
+         } else
+           break;
+       } else
+         safe_chr(**q, tbuf, &bp);
+       (*q)++;
+      }
+      while (*q < qend && **q != SE)
+       (*q)++;
+      *bp = '\0';
+      mush_free(d->ttype, "terminal description");
+      d->ttype = mush_strdup(tbuf, "terminal description");
+      /* We have the terminal type, now set any defaults if we find 'em */
+      for(i = 0 ;  client_maps[i].terminal != NULL; i++)
+       if(!strcmp(client_maps[i].terminal, d->ttype)) {
+         d->conn_flags |= client_maps[i].flags;
+         break;
+       }
+    } else {
+      while (*q < qend && **q != SE)
+       (*q)++;
+    }
+    break;
+  case NOP:                    /* No-op */
+    if (*q >= qend)
+      return -1;
+#ifdef DEBUG_TELNET
+    fprintf(stderr, "Got IAC NOP\n");
+#endif
+    break;
+  case AYT:                    /* Are you there? */
+    if (*q >= qend)
+      return -1;
+    else {
+      static char ayt_reply[] = "\r\n*** AYT received, I'm here ***\r\n";
+      queue_newwrite(d, (unsigned char *) ayt_reply, strlen(ayt_reply));
+      process_output(d);
+    }
+    break;
+  case WILL:                   /* Client is willing to do something, or confirming */
+    setup_telnet(d);
+    if (*q >= qend)
+      return -1;
+    (*q)++;
+
+    if (**q == TN_LINEMODE) {
+      /* Set up our preferred linemode options. */
+      /* IAC SB LINEMODE MODE (EDIT|SOFT_TAB) IAC SE */
+      unsigned char reply[7] = "\xFF\xFA\x22\x01\x09\xFF\xF0";
+      queue_newwrite(d, reply, 7);
+#ifdef DEBUG_TELNET
+      fprintf(stderr, "Setting linemode options.\n");
+#endif
+    } else if (**q == TN_TTYPE) {
+      /* Ask for terminal type id: IAC SB TERMINAL-TYPE SEND IAC SEC */
+      unsigned char reply[6] = "\xFF\xFA\x18\x01\xFF\xF0";
+      queue_newwrite(d, reply, 6);
+    } else if (**q == TN_SGA || **q == TN_NAWS) {
+      /* This is good to be at. */
+    } else {                   /* Refuse options we don't handle */
+      unsigned char reply[3];
+      reply[0] = IAC;
+      reply[1] = DONT;
+      reply[2] = **q;
+      queue_newwrite(d, reply, sizeof reply);
+      process_output(d);
+    }
+    break;
+  case DO:                     /* Client is asking us to do something */
+    setup_telnet(d);
+    if (*q >= qend)
+      return -1;
+    (*q)++;
+    if (**q == TN_LINEMODE) {
+    } else if (**q == TN_SGA) {
+      /* IAC WILL SGA IAC DO SGA */
+      unsigned char reply[6] = "\xFF\xFB\x03\xFF\xFD\x03";
+      queue_newwrite(d, reply, 6);
+      process_output(d);
+#ifdef DEBUG_TELNET
+      fprintf(stderr, "GOT IAC DO SGA, sending IAC WILL SGA IAG DO SGA\n");
+#endif
+    } else {
+      /* Stuff we won't do */
+      unsigned char reply[3];
+      reply[0] = IAC;
+      reply[1] = WONT;
+      reply[2] = (char) **q;
+      queue_newwrite(d, reply, sizeof reply);
+      process_output(d);
+    }
+    break;
+  case WONT:                   /* Client won't do something we want. */
+  case DONT:                   /* Client doesn't want us to do something */
+    setup_telnet(d);
+#ifdef DEBUG_TELNET
+    fprintf(stderr, "Got IAC %s 0x%x\n", **q == WONT ? "WONT" : "DONT",
+           *(*q + 1));
+#endif
+    if (*q + 1 >= qend)
+      return -1;
+    (*q)++;
+    break;
+  default:                     /* Also catches IAC IAC for a literal 255 */
+    return 0;
+  }
+  return 1;
+}
+
+static void
+process_input_helper(DESC *d, char *tbuf1, int got)
+{
+  unsigned char *p, *pend, *q, *qend;
+
+  if (!d->raw_input) {
+    d->raw_input =
+      (unsigned char *) mush_malloc(sizeof(char) * MAX_COMMAND_LEN,
+                                   "descriptor_raw_input");
+    if (!d->raw_input)
+      mush_panic("Out of memory");
+    d->raw_input_at = d->raw_input;
+  }
+  p = d->raw_input_at;
+  d->input_chars += got;
+  pend = d->raw_input + MAX_COMMAND_LEN - 1;
+  for (q = (unsigned char *) tbuf1, qend = (unsigned char *) tbuf1 + got;
+       q < qend; q++) {
+    if (*q == '\r') {
+      /* A broken client (read: WinXP telnet) might send only CR, and not CRLF
+       * so it's nice of us to try to handle this.
+       */
+      *p = '\0';
+      if (p > d->raw_input)
+       save_command(d, d->raw_input);
+      p = d->raw_input;
+      if (((q + 1) < qend) && (*(q + 1) == '\n'))
+       q++;                    /* For clients that work */
+    } else if (*q == '\n') {
+      *p = '\0';
+      if (p > d->raw_input)
+       save_command(d, d->raw_input);
+      p = d->raw_input;
+    } else if (*q == '\b') {
+      if (p > d->raw_input)
+       p--;
+    } else if ((unsigned char) *q == IAC) {    /* Telnet option foo */
+      if (q >= qend)
+       break;
+      q++;
+      if (!TELNET_ABLE(d) || handle_telnet(d, &q, qend) == 0) {
+       if (p < pend && isprint(*q))
+         *p++ = *q;
+      }
+    } else if (p < pend && isprint(*q)) {
+      *p++ = *q;
+    }
+  }
+  if (p > d->raw_input) {
+    d->raw_input_at = p;
+  } else {
+    mush_free((Malloc_t) d->raw_input, "descriptor_raw_input");
+    d->raw_input = 0;
+    d->raw_input_at = 0;
+  }
+}
+
+/* ARGSUSED */
+static int
+process_input(DESC *d, int output_ready __attribute__ ((__unused__)))
+{
+  int got = 0;
+  char tbuf1[BUFFER_LEN];
+
+  errno = 0;
+
+#ifdef HAS_OPENSSL
+  if (d->ssl) {
+    /* Insure that we're not in a state where we need an SSL_handshake() */
+    if (ssl_need_handshake(d->ssl_state)) {
+      d->ssl_state = ssl_handshake(d->ssl);
+      if (d->ssl_state < 0) {
+       /* Fatal error */
+       ssl_close_connection(d->ssl);
+       d->ssl = NULL;
+       d->ssl_state = 0;
+       return 0;
+      } else if (ssl_need_handshake(d->ssl_state)) {
+       /* We're still not ready to send to this connection. Alas. */
+       return 1;
+      }
+    }
+    /* Insure that we're not in a state where we need an SSL_accept() */
+    if (ssl_need_accept(d->ssl_state)) {
+      d->ssl_state = ssl_accept(d->ssl);
+      if (d->ssl_state < 0) {
+       /* Fatal error */
+       ssl_close_connection(d->ssl);
+       d->ssl = NULL;
+       d->ssl_state = 0;
+       return 0;
+      } else if (ssl_need_accept(d->ssl_state)) {
+       /* We're still not ready to send to this connection. Alas. */
+       return 1;
+      }
+    }
+    /* It's an SSL connection, proceed accordingly */
+    d->ssl_state =
+      ssl_read(d->ssl, d->ssl_state, 1, output_ready, tbuf1, sizeof tbuf1,
+              &got);
+    if (d->ssl_state < 0) {
+      /* Fatal error */
+      ssl_close_connection(d->ssl);
+      d->ssl = NULL;
+      d->ssl_state = 0;
+      return 0;
+    }
+  } else {
+#endif
+    got = recv(d->descriptor, tbuf1, sizeof tbuf1, 0);
+    if (got <= 0) {
+      /* At this point, select() says there's data waiting to be read from
+       * the socket, but we shouldn't assume that read() will actually get it
+       * and blindly act like a got of -1 is a disconnect-worthy error.
+       */
+#ifdef EAGAIN
+      if ((errno == EWOULDBLOCK) || (errno == EAGAIN) || (errno == EINTR))
+#else
+      if ((errno == EWOULDBLOCK) || (errno == EINTR))
+#endif
+       return 1;
+      else
+       return 0;
+    }
+#ifdef HAS_OPENSSL
+  }
+#endif
+
+  process_input_helper(d, tbuf1, got);
+
+  return 1;
+}
+
+static void
+set_userstring(unsigned char **userstring, const char *command)
+{
+  if (*userstring) {
+    mush_free((Malloc_t) *userstring, "userstring");
+    *userstring = NULL;
+  }
+  while (*command && isspace((unsigned char) *command))
+    command++;
+  if (*command)
+    *userstring = (unsigned char *) mush_strdup(command, "userstring");
+}
+
+static void
+process_commands(void)
+{
+  int nprocessed;
+  DESC *cdesc, *dnext;
+  struct text_block *t;
+  int retval = 1;
+
+  do {
+    nprocessed = 0;
+    for (cdesc = descriptor_list; cdesc;
+        cdesc = (nprocessed > 0 && retval > 0) ? cdesc->next : dnext) {
+      dnext = cdesc->next;
+      if (cdesc->quota > 0 && (t = cdesc->input.head)) {
+       cdesc->quota--;
+       nprocessed++;
+       start_cpu_timer();
+       feed_snoop(cdesc,(const char *) t->start, 0);
+       /* check AUNIDLE */
+       if(options.idle_time && ((mudtime - cdesc->last_time) > options.idle_time)) {
+         if(atr_get(cdesc->player, "AUNIDLE"))
+           queue_attribute_noparent(cdesc->player, "AUNIDLE", cdesc->player);
+         if(GoodObject(Division(cdesc->player)) && atr_get(Division(cdesc->player), "AUNIDLE"))
+           queue_attribute(Division(cdesc->player), "AUNIDLE", cdesc->player);
+       }
+       retval = cdesc->input_handler(cdesc, (char *) t->start);
+       reset_cpu_timer();
+       if(retval == -1 && do_su_exit(cdesc)) 
+         retval = 1;
+
+       if (retval == 0) {
+         shutdownsock(cdesc);
+       } else if (retval == -1) {
+         logout_sock(cdesc);
+       } else {
+         cdesc->input.head = t->nxt;
+         if (!cdesc->input.head)
+           cdesc->input.tail = &cdesc->input.head;
+         if (t) {
+#ifdef DEBUG
+           do_rawlog(LT_ERR, "free_text_block(0x%x) at 5.", t);
+#endif                         /* DEBUG */
+           free_text_block(t);
+         }
+       }
+      }
+    }
+  } while (nprocessed > 0);
+}
+
+/** Send a descriptor's output prefix */
+#define send_prefix(d) \
+  if (d->output_prefix) { \
+    queue_newwrite(d, d->output_prefix, u_strlen(d->output_prefix)); \
+    queue_eol(d); \
+  }
+
+/** Send a descriptor's output suffix */
+#define send_suffix(d) \
+  if (d->output_suffix) { \
+    queue_newwrite(d, d->output_suffix, u_strlen(d->output_suffix)); \
+    queue_eol(d); \
+  }
+
+int
+do_command(DESC *d, char *command)
+{
+  int j;
+
+  depth = 0;
+
+  (d->cmds)++;
+
+  if (!strcmp(command, IDLE_COMMAND))
+    return 1;
+  if(difftime(mudtime, d->last_time) >= 300) { 
+    d->idle_total += difftime(mudtime, d->last_time);
+    d->unidle_times++;
+  }
+  d->last_time = mudtime;
+  if (!strcmp(command, QUIT_COMMAND)) {
+    return 0;
+  } else if (!strcmp(command, LOGOUT_COMMAND)) {
+    return -1;
+  } else if (!strcmp(command, INFO_COMMAND)) {
+    send_prefix(d);
+    dump_info(d);
+    send_suffix(d);
+  } else if (!strncmp(command, WHO_COMMAND, strlen(WHO_COMMAND))) {
+    send_prefix(d);
+    dump_users(d, command + strlen(WHO_COMMAND), 0);
+    send_suffix(d);
+  } else if (!strncmp(command, DOING_COMMAND, strlen(DOING_COMMAND))) {
+    send_prefix(d);
+    dump_users(d, command + strlen(DOING_COMMAND), 1);
+    send_suffix(d);
+  } else if (!strncmp(command, SESSION_COMMAND, strlen(SESSION_COMMAND))) {
+    send_prefix(d);
+    dump_users(d, command + strlen(SESSION_COMMAND), 2);
+    send_suffix(d);
+  } else if (!strncmp(command, PREFIX_COMMAND, strlen(PREFIX_COMMAND))) {
+    set_userstring(&d->output_prefix, command + strlen(PREFIX_COMMAND));
+  } else if (!strncmp(command, SUFFIX_COMMAND, strlen(SUFFIX_COMMAND))) {
+    set_userstring(&d->output_suffix, command + strlen(SUFFIX_COMMAND));
+  } else if (!strncmp(command, "SCREENWIDTH", 11)) {
+    d->width = parse_integer(command + 11);
+  } else if (!strncmp(command, "SCREENHEIGHT", 12)) {
+    d->height = parse_integer(command + 12);
+  } else if (SUPPORT_PUEBLO
+            && !strncmp(command, PUEBLO_COMMAND, strlen(PUEBLO_COMMAND))) {
+    parse_puebloclient(d, command);
+    if (!(d->conn_flags & CONN_HTML)) {
+      queue_newwrite(d, (unsigned char *) PUEBLO_SEND, strlen(PUEBLO_SEND));
+      process_output(d);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Switching to Pueblo mode."),
+            d->descriptor, d->addr, d->ip);
+      d->conn_flags |= CONN_HTML;
+      if (!d->connected)
+       welcome_user(d);
+    }
+  } else {
+    if (d->connected) {
+      send_prefix(d);
+      global_eval_context.cplr = d->player;
+      strcpy(global_eval_context.ccom, command);
+      strcpy(global_eval_context.ucom, "");
+
+      /* Clear %0-%9 and r(0) - r(9) */
+      for (j = 0; j < 10; j++)
+       global_eval_context.wenv[j] = (char *) NULL;
+      for (j = 0; j < NUMQ; j++)
+       global_eval_context.renv[j][0] = '\0';
+      global_eval_context.process_command_port = d->descriptor;
+
+      process_command(d->player, command, d->player, d->player, 1);
+      send_suffix(d);
+      strcpy(global_eval_context.ccom, "");
+      strcpy(global_eval_context.ucom, "");
+      global_eval_context.cplr = NOTHING;
+    } else {
+      if (!check_connect(d, command))
+       return 0;
+    }
+  }
+  return 1;
+}
+
+static void
+parse_puebloclient(DESC *d, char *command)
+{
+  const char *p, *end;
+  if ((p = string_match(command, "md5="))) {
+    /* Skip md5=" */
+    p += 5;
+    if ((end = strchr(p, '"'))) {
+      if ((end > p) && ((end - p) <= PUEBLO_CHECKSUM_LEN)) {
+       /* Got it! */
+       strncpy(d->checksum, p, end - p);
+       d->checksum[end - p] = '\0';
+      }
+    }
+  }
+}
+
+static int
+dump_messages(DESC *d, dbref player, int isnew)
+{
+  int is_hidden;
+  int num = 0;
+  DESC *tmpd;
+
+  d->connected = 1;
+  d->connected_at = mudtime;
+  d->player = player;
+  d->doing[0] = '\0';
+
+  if (MAX_LOGINS) {
+    /* check for exceeding max player limit */
+    login_number++;
+    if (under_limit && (login_number > MAX_LOGINS)) {
+      under_limit = 0;
+      do_rawlog(LT_CONN,
+               T("Limit of %d players reached. Logins disabled.\n"),
+               MAX_LOGINS);
+    }
+  }
+  /* give players a message on connection */
+  if (!options.login_allow || !under_limit ||
+      (Guest(player) && !options.guest_allow)) {
+    if (!options.login_allow) {
+      fcache_dump(d, fcache.down_fcache, NULL);
+      if (cf_downmotd_msg && *cf_downmotd_msg)
+       raw_notify(player, cf_downmotd_msg);
+    } else if (MAX_LOGINS && !under_limit) {
+      fcache_dump(d, fcache.full_fcache, NULL);
+      if (cf_fullmotd_msg && *cf_fullmotd_msg)
+       raw_notify(player, cf_fullmotd_msg);
+    }
+    if (!Can_Login(player)) {
+      /* when the connection has been refused, we want to update the
+       * LASTFAILED info on the player
+       */
+      check_lastfailed(player, d->addr);
+      return 0;
+    }
+  }
+  d->mailp = find_exact_starting_point(player);
+
+  /* check to see if this is a reconnect and also set DARK status */
+  is_hidden = Can_Hide(player) && Dark(player);
+  DESC_ITER_CONN(tmpd) {
+    if (tmpd->player == player) {
+      num++;
+      if (is_hidden)
+       tmpd->hide = 1;
+    }
+  }
+  /* give permanent text messages */
+  if (isnew)
+    fcache_dump(d, fcache.newuser_fcache, NULL);
+  if (num == 1)
+    fcache_dump(d, fcache.motd_fcache, NULL);
+  if (Guest(player))
+    fcache_dump(d, fcache.guest_fcache, NULL);
+
+  if (ModTime(player))
+    notify_format(player, T("%ld failed connections since last login."),
+                 ModTime(player));
+  ModTime(player) = (time_t) 0;
+  announce_connect(player, isnew, num);        /* broadcast connect message */
+  check_last(player, d->addr, d->ip);  /* set Last, Lastsite, give paycheck */
+  /* Check folder 0, not silently (i.e. Report lack of mail, too) */
+  queue_eol(d);
+  if (command_check_byname(player, "@MAIL"))
+    check_mail(player, 0, 0);
+  set_player_folder(player, 0);
+  do_look_around(player);
+  if (Haven(player))
+    notify(player, T("Your HAVEN flag is set. You cannot receive pages."));
+  local_connect(player, isnew, num);
+  return 1;
+}
+
+static int
+check_connect(DESC *d, const char *msg)
+{
+  char command[MAX_COMMAND_LEN];
+  char user[MAX_COMMAND_LEN];
+  char password[MAX_COMMAND_LEN];
+  char errbuf[BUFFER_LEN];
+  dbref player;
+
+  parse_connect(msg, command, user, password);
+
+  if (string_prefix("connect", command)) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connected to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Check if we're fake siting this guy.. */
+      if(has_flag_by_name(player, "WEIRDSITE", TYPE_PLAYER)) {
+             ATTR *a;
+             a = atr_get(player, "LASTSITE");
+             strncpy(d->addr, !a ? "localhost" : atr_value(a), 100);
+      }
+
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (!strcasecmp(command, "cd")) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Connected dark to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Set player dark */
+      d->connected = 1;
+      d->player = player;
+      set_flag(player, player, "DARK", 0, 0, 0);
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (!strcasecmp(command, "cv")) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connected to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Set player !dark */
+      d->connected = 1;
+      d->player = player;
+      set_flag(player, player, "DARK", 1, 0, 0);
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (!strcasecmp(command, "ch")) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Connected hidden to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Set player hidden */
+      d->connected = 1;
+      d->player = player;
+      if (Can_Hide(player))
+       d->hide = 1;
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (string_prefix("create", command)) {
+    if (!Site_Can_Create(d->addr) || !Site_Can_Create(d->ip)) {
+      fcache_dump(d, fcache.register_fcache, NULL);
+      if (!Deny_Silent_Site(d->addr, AMBIGUOUS)
+         && !Deny_Silent_Site(d->ip, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Refused create for '%s'."),
+              d->descriptor, d->addr, d->ip, user);
+      }
+      return 0;
+    }
+    if (!options.login_allow || !options.create_allow) {
+      if (!options.login_allow)
+       fcache_dump(d, fcache.down_fcache, NULL);
+      else
+       fcache_dump(d, fcache.register_fcache, NULL);
+      do_rawlog(LT_CONN,
+               "REFUSED CREATION for %s from %s on descriptor %d.\n",
+               user, d->addr, d->descriptor);
+      return 0;
+    } else if (MAX_LOGINS && !under_limit) {
+      fcache_dump(d, fcache.full_fcache, NULL);
+      do_rawlog(LT_CONN,
+               "REFUSED CREATION for %s from %s on descriptor %d.\n",
+               user, d->addr, d->descriptor);
+      return 0;
+    }
+    player = create_player(user, password, d->addr, d->ip);
+    if (player == NOTHING) {
+      queue_string_eol(d, T(create_fail));
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Failed create for '%s' (bad name)."),
+            d->descriptor, d->addr, d->ip, user);
+    } else if (player == AMBIGUOUS) {
+      queue_string_eol(d, T(password_fail));
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Failed create for '%s' (bad password)."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0, "[%d/%s/%s] Created %s(#%d)",
+            d->descriptor, d->addr, d->ip, Name(player), player);
+      if ((dump_messages(d, player, 1)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }                          /* successful player creation */
+
+  } else if (string_prefix("register", command)) {
+    if (!Site_Can_Register(d->addr) || !Site_Can_Register(d->ip)) {
+      fcache_dump(d, fcache.register_fcache, NULL);
+      if (!Deny_Silent_Site(d->addr, AMBIGUOUS)
+         && !Deny_Silent_Site(d->ip, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0,
+              T("[%d/%s/%s] Refused registration (bad site) for '%s'."),
+              d->descriptor, d->addr, d->ip, user);
+      }
+      return 0;
+    }
+    if (!options.create_allow) {
+      fcache_dump(d, fcache.register_fcache, NULL);
+      do_rawlog(LT_CONN,
+               "Refused registration (creation disabled) for %s from %s on descriptor %d.\n",
+               user, d->addr, d->descriptor);
+      return 0;
+    }
+    if ((player = email_register_player(user, password, d->addr, d->ip)) ==
+       NOTHING) {
+      queue_string_eol(d, T(register_fail));
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed registration for '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      queue_string_eol(d, T(register_success));
+      do_log(LT_CONN, 0, 0, "[%d/%s/%s] Registered %s(#%d) to %s",
+            d->descriptor, d->addr, d->ip, Name(player), player, password);
+    }
+    /* Whether it succeeds or fails, leave them connected */
+
+  } else {
+    /* invalid command, just repeat login screen */
+    welcome_user(d);
+  }
+  /* If they were in a program, get them back in it */
+  if(InProg(d->player)) {
+    queue_string(d, "Loading @Program onto Descriptor....\r\n");
+    prog_load_desc(d);
+  }
+
+  return 1;
+}
+
+static void
+parse_connect(const char *msg1, char *command, char *user, char *pass)
+{
+  unsigned char *p;
+  unsigned const char *msg = (unsigned const char *) msg1;
+
+  while (*msg && isspace(*msg))
+    msg++;
+  p = (unsigned char *) command;
+  while (*msg && isprint(*msg) && !isspace(*msg))
+    *p++ = *msg++;
+  *p = '\0';
+  while (*msg && isspace(*msg))
+    msg++;
+  p = (unsigned char *) user;
+
+  if (PLAYER_NAME_SPACES && *msg == '\"') {
+    for (; *msg && ((*msg == '\"') || isspace(*msg)); msg++) ;
+    while (*msg && (*msg != '\"')) {
+      while (*msg && !isspace(*msg) && (*msg != '\"'))
+       *p++ = *msg++;
+      if (*msg == '\"') {
+       msg++;
+       while (*msg && isspace(*msg))
+         msg++;
+       break;
+      }
+      while (*msg && isspace(*msg))
+       msg++;
+      if (*msg && (*msg != '\"'))
+       *p++ = ' ';
+    }
+  } else
+    while (*msg && isprint(*msg) && !isspace(*msg))
+      *p++ = *msg++;
+
+  *p = '\0';
+  while (*msg && isspace(*msg))
+    msg++;
+  p = (unsigned char *) pass;
+  while (*msg && isprint(*msg) && !isspace(*msg))
+    *p++ = *msg++;
+  *p = '\0';
+}
+
+static void
+close_sockets(void)
+{
+  DESC *d, *dnext;
+
+  for (d = descriptor_list; d; d = dnext) {
+    dnext = d->next;
+    send(d->descriptor, T(shutdown_message), strlen(T(shutdown_message)), 0);
+    send(d->descriptor, "\r\n", 2, 0);
+#ifdef HAS_OPENSSL
+    if (d->ssl) {
+      ssl_close_connection(d->ssl);
+      d->ssl = NULL;
+      d->ssl_state = 0;
+    }
+#endif
+    if (shutdown(d->descriptor, 2) < 0)
+      perror("shutdown");
+    closesocket(d->descriptor);
+  }
+#ifdef NT_TCP
+  /* shutdown listening thread */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    closesocket(MUDListenSocket);
+#endif
+
+}
+
+/** Give everyone the boot.
+ */
+void
+emergency_shutdown(void)
+{
+  close_sockets();
+}
+
+/** Disconnect a descriptor.
+ * \param d pointer to descriptor to disconnect.
+ */
+void
+boot_desc(DESC *d)
+{
+  shutdownsock(d);
+}
+
+/** Given a player dbref, return the player's first connected descriptor.
+ * \param player dbref of player.
+ * \return pointer to player's first connected descriptor, or NULL.
+ */
+DESC *
+player_desc(dbref player)
+{
+  DESC *d;
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected && (d->player == player)) {
+      return d;
+    }
+  }
+  return (DESC *) NULL;
+}
+
+/** Page a specified socket.
+ * \param player the enactor.
+ * \param pc string containing port number to send message to.
+ * \param message message to send.
+ */
+void
+do_page_port(dbref player, const char *pc, const char *message)
+{
+  int p, key;
+  DESC *d;
+  const char *gap;
+  char tbuf[BUFFER_LEN], *tbp = tbuf;
+  dbref target = NOTHING;
+
+  if (!Admin(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  p = atoi(pc);
+  if (p <= 0) {
+    notify(player, T("That's not a port number."));
+    return;
+  }
+
+  if (!message || !*message) {
+    notify(player, T("What do you want to page with?"));
+    return;
+  }
+
+  gap = " ";
+  switch (*message) {
+  case SEMI_POSE_TOKEN:
+    gap = "";
+  case POSE_TOKEN:
+    key = 1;
+    break;
+  default:
+    key = 3;
+    break;
+  }
+
+  d = port_desc(p);
+  if (!d) {
+    notify(player, T("That port's not active."));
+    return;
+  }
+  if (d->connected)
+    target = d->player;
+  switch (key) {
+  case 1:
+    safe_format(tbuf, &tbp, T("From afar, %s%s%s"), Name(player), gap,
+               message + 1);
+    notify_format(player, T("Long distance to %s: %s%s%s"),
+                 target != NOTHING ? Name(target) :
+                 T("a connecting player"), Name(player), gap, message + 1);
+    break;
+  case 3:
+    safe_format(tbuf, &tbp, T("%s pages: %s"), Name(player), message);
+    notify_format(player, T("You paged %s with '%s'."),
+                 target != NOTHING ? Name(target) :
+                 T("a connecting player"), message);
+    break;
+  }
+  *tbp = '\0';
+  if (target != NOTHING)
+    page_return(player, target, "Idle", "IDLE", NULL);
+  if (Typeof(player) != TYPE_PLAYER && Nospoof(target))
+    queue_string_eol(d, tprintf("[#%d] %s", player, tbuf));
+  else
+    queue_string_eol(d, tbuf);
+}
+
+
+/** Return an inactive descriptor, as long as there's more than
+ * one descriptor connected. Used for boot/me.
+ * \param player player to find an inactive descriptor for.
+ * \return pointer to player's inactive descriptor, or NULL.
+ */
+DESC *
+inactive_desc(dbref player)
+{
+  DESC *d, *in = NULL;
+  time_t now;
+  int numd = 0;
+  now = mudtime;
+  DESC_ITER_CONN(d) {
+    if (d->player == player) {
+      numd++;
+      if (now - d->last_time > 60)
+       in = d;
+    }
+  }
+  if (numd > 1)
+    return in;
+  else
+    return (DESC *) NULL;
+}
+
+/** Given a port (a socket number), return the descriptor.
+ * \param port port (socket file descriptor number).
+ * \return pointer to descriptor associated with the port.
+ */
+DESC *
+port_desc(int port)
+{
+  DESC *d;
+  for (d = descriptor_list; (d); d = d->next) {
+    if (d->descriptor == port) {
+      return d;
+    }
+  }
+  return (DESC *) NULL;
+}
+
+/** Given a port, find the matching player dbref.
+ * \param port (socket file descriptor number).
+ * \return dbref of connected player using that port, or NOTHING.
+ */
+dbref
+find_player_by_desc(int port)
+{
+  DESC *d;
+  for (d = descriptor_list; (d); d = d->next) {
+    if (d->connected && (d->descriptor == port)) {
+      return d->player;
+    }
+  }
+
+  /* didn't find anything */
+  return NOTHING;
+}
+
+
+#ifndef WIN32
+/** Handler for SIGINT. Note that we've received it, and reinstall.
+ * \param sig signal caught.
+ */
+void
+signal_shutdown(int sig __attribute__ ((__unused__)))
+{
+  signal_shutdown_flag = 1;
+  reload_sig_handler(SIGINT, signal_shutdown);
+}
+
+/** Handler for SIGUSR2. Note that we've received it, and reinstall
+ * \param sig signal caught.
+ */
+void
+signal_dump(int sig __attribute__ ((__unused__)))
+{
+  signal_dump_flag = 1;
+  reload_sig_handler(SIGUSR2, signal_dump);
+}
+#endif
+
+/** A general handler to puke and die.
+ * \param sig signal caught.
+ */
+void
+bailout(int sig)
+{
+  mush_panicf(T("BAILOUT: caught signal %d"), sig);
+}
+
+#ifndef WIN32
+/** Reap child processes, notably info_slaves and forking dumps,
+ * when we receive a SIGCHLD signal. Don't fear this function. :)
+ * \param sig signal caught.
+ */
+void
+reaper(int sig __attribute__ ((__unused__)))
+{
+  WAIT_TYPE my_stat;
+  Pid_t pid;
+
+#ifdef HAS_WAITPID
+  while ((pid = waitpid(-1, &my_stat, WNOHANG)) > 0)
+#else
+  while ((pid = wait3(&my_stat, WNOHANG, 0)) > 0)
+#endif
+  {
+#ifdef INFO_SLAVE
+    if (info_slave_pid > -1 && pid == info_slave_pid) {
+      do_rawlog(LT_ERR, T("info_slave on pid %d exited!"), pid);
+      info_slave_state = 0;
+      info_slave_pid = -1;
+    } else
+#endif
+    if (forked_dump_pid > -1 && pid == forked_dump_pid) {
+      /* Most failures are handled by the forked mush already */
+      if (WIFSIGNALED(my_stat)) {
+       do_rawlog(LT_ERR, T("ERROR! forking dump exited with signal %d"),
+                 WTERMSIG(my_stat));
+      } else if (WIFEXITED(my_stat) && WEXITSTATUS(my_stat) == 0) {
+       time(&last_dump_time);
+       if (DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE)
+         flag_broadcast(0, 0, "%s", DUMP_NOFORK_COMPLETE);
+
+      }
+      forked_dump_pid = -1;
+    }
+  }
+  reload_sig_handler(SIGCHLD, reaper);
+}
+#endif /* !WIN32 */
+
+
+static void
+dump_info(DESC *call_by)
+{
+  int count = 0;
+  DESC *d;
+  queue_string_eol(call_by, tprintf("### Begin INFO %s", INFO_VERSION));
+
+  /* Count connected players */
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected) {
+      if (!GoodObject(d->player))
+       continue;
+      if (COUNT_ALL || !Hidden(d) || call_by->player == d->player )
+       count++;
+    }
+  }
+  queue_string_eol(call_by, tprintf("Name: %s", options.mud_name));
+  queue_string_eol(call_by,
+                  tprintf("Uptime: %s", show_time(first_start_time, 0)));
+  queue_string_eol(call_by, tprintf("Connected: %d", count));
+  queue_string_eol(call_by, tprintf("Size: %d", db_top));
+  queue_string_eol(call_by, tprintf("Version: CobraMUSH v%s [%s]", VERSION,
+                       VBRANCH));
+#ifdef PATCHES
+  queue_string_eol(call_by, tprintf("Patches: %s", PATCHES));
+#endif
+  queue_string_eol(call_by, "### End INFO");
+}
+
+static void
+dump_users(DESC *call_by, char *match, int doing)
+    /* doing: 0 if normal WHO, 1 if DOING, 2 if SESSION */
+{
+  DESC *d;
+#ifdef COLOREDWHO
+  int tcount = 0; 
+#endif
+  int count = 0;
+  time_t now;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  int csite;
+
+  if (!GoodObject(call_by->player)) {
+    do_log(LT_ERR, 0, 0, T("Bogus caller #%d of dump_users"), call_by->player);
+    return;
+  }
+  while (*match && *match == ' ')
+    match++;
+  now = mudtime;
+
+  /* If an admin types "DOING" it gives him the normal player WHO,
+   * BUT flags are not shown. Privileged WHO does not show @doings.
+   */
+
+  if (SUPPORT_PUEBLO && (call_by->conn_flags & CONN_HTML)) {
+    queue_newwrite(call_by, (unsigned char *) "<img xch_mode=html>", 19);
+    queue_newwrite(call_by, (unsigned char *) "<PRE>", 5);
+  }
+
+  if ((doing == 1) || !call_by->player || !Priv_Who(call_by->player)) {
+    if (poll_msg[0] == '\0')
+      strcpy(poll_msg, "Doing");
+    if (ShowAnsi(call_by->player))
+      sprintf(tbuf2, "%s%-16s %4s %10s %6s  %s%s\n", ANSI_HILITE,
+             T("Player Name"), T("Aff"), T("On For"), T("Idle"), poll_msg, ANSI_NORMAL);
+    else
+      sprintf(tbuf2, "%-16s %4s %10s %6s  %s\n",
+             T("Player Name"), T("Aff"), T("On For"), T("Idle"), poll_msg);
+    queue_string(call_by, tbuf2);
+  } else if (doing == 2) {
+    sprintf(tbuf2, "%s%-16s %6s %9s %5s %5s Des  Sent    Recv  Pend%s\n", ShowAnsi(call_by->player) ? ANSI_HILITE :
+                   "", T("Player Name"), T("Loc #"), T("On For"), T("Idle"), T("Cmds"),
+                   ShowAnsi(call_by->player) ? ANSI_NORMAL : "");
+    queue_string(call_by, tbuf2);
+  } else {
+    sprintf(tbuf2, "%s%-16s %6s %9s %5s %5s Des  Host%s\n", ShowAnsi(call_by->player) ? ANSI_HILITE : "",
+           T("Player Name"), T("Loc #"), T("On For"), T("Idle"), T("Cmds"), ShowAnsi(call_by->player) ? ANSI_NORMAL : "");
+    queue_string(call_by, tbuf2);
+  }
+
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected) {
+      if (!GoodObject(d->player))
+       continue;
+      if (COUNT_ALL || (!Hidden(d) || call_by->player == d->player 
+                       || (call_by->player && Priv_Who(call_by->player)))) {
+        count++;
+#ifdef COLOREDWHO
+        tcount++;
+#endif
+      }
+      if (match && !(string_prefix(Name(d->player), match)))
+       continue;
+      csite = CanSee(call_by->player, d->player);
+
+      if (call_by->connected && doing == 0 && call_by->player
+         && Priv_Who(call_by->player)) {
+        if (Hidden(d) && !csite)
+          continue;
+
+       sprintf(tbuf1, "%-16s %6s %9s %5s  %4d %3d%c  %s", tprintf("%s%s", Name(d->player), InProg(d->player) ? "(P)" : ""),
+               Can_Locate(call_by->player, d->player) ? unparse_dbref(Location(d->player)) : "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), csite ? d->cmds : 0,
+               csite ? d->descriptor : 0,
+#ifdef HAS_OPENSSL
+               d->ssl ? 'S' : ' ',
+#else
+               ' ',
+#endif
+               csite ? d->addr : "---");
+       tbuf1[78] = '\0';
+       if (Dark(d->player)) {
+         tbuf1[71] = '\0';
+         strcat(tbuf1, " (Dark)");
+       } else if (Hidden(d)) {
+         tbuf1[71] = '\0';
+         strcat(tbuf1, " (Hide)");
+       }
+      } else if (call_by->connected && doing == 2 && call_by->player
+                && Priv_Who(call_by->player)) {
+       sprintf(tbuf1, "%-16s %6s %9s %5s %5d %3d%c %5lu %7lu %5d",
+               tprintf("%s%s", Name(d->player), InProg(d->player) ? "(P)" : ""),
+               Can_Locate(call_by->player, d->player) ? unparse_dbref(Location(d->player)) : "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), csite ? d->cmds : 0,
+               csite ? d->descriptor : 0,
+#ifdef HAS_OPENSSL
+               d->ssl ? 'S' : ' ',
+#else
+               ' ',
+#endif
+
+               csite ? d->input_chars : 0, csite ? d->output_chars : 0,
+               csite ? d->output_size : 0);
+      } else {
+       if (!Hidden(d)
+           || call_by->player == d->player ||
+           (call_by->player && Priv_Who(call_by->player) && (doing))) {
+         sprintf(tbuf1, "%-16s %4s %10s   %4s%c %s", tprintf("%s%s", Name(d->player), InProg(d->player) ? "(P)" : ""), empabb(d->player),
+                 time_format_1(now - d->connected_at),
+                 time_format_2(now - d->last_time),
+                 (Dark(d->player) ? 'D' : (Hidden(d) ? 'H' : ' '))
+                 , d->doing);
+       }
+      }
+
+      if (!Hidden(d) || (call_by->player && Priv_Who(call_by->player))) {
+#ifdef COLOREDWHO
+       if(ShowAnsiColor(call_by->player))
+               queue_string(call_by, tprintf("%s%s%s%s%s", ANSI_NORMAL, (tcount % 2 ?  "" : ANSI_HILITE), 
+                                       (tcount % 2 ? ANSI_CYAN : ANSI_WHITE),
+                                       tbuf1, ANSI_NORMAL));
+
+       else 
+#endif
+               queue_string(call_by, tbuf1);
+       queue_newwrite(call_by, (unsigned char *) "\r\n", 2);
+      }
+    } else if (call_by->player && Priv_Who(call_by->player) && doing != 1
+              && (!match || !*match)) {
+#ifdef COLOREDWHO
+      tcount++;
+#endif
+      if (doing == 0) {
+       /* Privileged WHO for non-logged in connections */
+       sprintf(tbuf1, "%-16s %6s %9s %5s  %4d %3d%c  %s", T("Connecting..."),
+               "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), d->cmds, d->descriptor,
+               #ifdef HAS_OPENSSL
+                             d->ssl ? 'S' : ' ',
+               #else
+                             ' ',
+               #endif
+
+               d->addr);
+       tbuf1[78] = '\0';
+      } else {
+       /* SESSION for non-logged in connections */
+       sprintf(tbuf1, "%-16s %5s %9s %5s %5d %3d%c %5lu %7lu %5d",
+               T("Connecting..."), "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), d->cmds, d->descriptor,
+               #ifdef HAS_OPENSSL
+                             d->ssl ? 'S' : ' ',
+               #else
+                             ' ',
+               #endif
+               d->input_chars, d->output_chars, d->output_size);
+      }
+#ifdef COLOREDWHO
+      if(ShowAnsiColor(call_by->player))
+             queue_string(call_by, tprintf("%s%s%s%s%s", ANSI_NORMAL,tcount % 2 ?  "" : ANSI_HILITE,
+                                     tcount % 2 ? ANSI_CYAN : ANSI_WHITE, tbuf1, ANSI_NORMAL));
+      else 
+#endif
+             queue_string(call_by, tbuf1);
+      queue_newwrite(call_by, (unsigned char *) "\r\n", 2);
+    }
+  }
+  switch (count) {
+  case 0:
+    strcpy(tbuf1, T("There are no players connected."));
+    break;
+  case 1:
+    strcpy(tbuf1, T("There is 1 player connected."));
+    break;
+  default:
+    sprintf(tbuf1, T("There are %d players connected."), count);
+    break;
+  }
+
+#ifdef COLOREDWHO
+  if(ShowAnsiColor(call_by->player))
+         queue_string(call_by, tprintf("%s%s%s%s%s", ANSI_NORMAL, (tcount+1) % 2 ? "" : ANSI_HILITE ,
+                                 (tcount+1) % 2 ? ANSI_CYAN : ANSI_WHITE, tbuf1, ANSI_NORMAL));
+  else 
+#endif
+         queue_string(call_by, tbuf1);
+  if (SUPPORT_PUEBLO && (call_by->conn_flags & CONN_HTML)) {
+    queue_newwrite(call_by, (unsigned char *) "\n</PRE>\n", 8);
+    queue_newwrite(call_by, (unsigned char *) "<img xch_mode=purehtml>", 23);
+  } else
+    queue_newwrite(call_by, (unsigned char *) "\r\n", 2);
+}
+
+static const char *
+time_format_1(long dt)
+{
+  register struct tm *delta;
+  time_t holder;               /* A hack for 64bit SGI */
+  static char buf[64];
+  if (dt < 0)
+    dt = 0;
+  holder = (time_t) dt;
+  delta = gmtime(&holder);
+  if (delta->tm_yday > 0) {
+    sprintf(buf, "%dd %02d:%02d",
+           delta->tm_yday, delta->tm_hour, delta->tm_min);
+  } else {
+    sprintf(buf, "%02d:%02d", delta->tm_hour, delta->tm_min);
+  }
+  return buf;
+}
+
+static const char *
+time_format_2(long dt)
+{
+  register struct tm *delta;
+  static char buf[64];
+  if (dt < 0)
+    dt = 0;
+
+  delta = gmtime((time_t *) & dt);
+  if (delta->tm_yday > 0) {
+    sprintf(buf, "%dd", delta->tm_yday);
+  } else if (delta->tm_hour > 0) {
+    sprintf(buf, "%dh", delta->tm_hour);
+  } else if (delta->tm_min > 0) {
+    sprintf(buf, "%dm", delta->tm_min);
+  } else {
+    sprintf(buf, "%ds", delta->tm_sec);
+  }
+  return buf;
+}
+
+/* connection messages
+ * isnew: newly creaetd or not?
+ * num: how many times connected?
+ */
+void
+announce_connect(dbref player, int isnew, int num)
+{
+  dbref loc;
+  char tbuf1[BUFFER_LEN];
+  dbref zone;
+  dbref obj;
+  int j;
+
+  set_flag_internal(player, "CONNECTED");
+
+  if (isnew) {
+    /* A brand new player created. */
+    sprintf(tbuf1, T("%s created."), Name(player));
+    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
+  }
+
+  /* Redundant, but better for translators */
+  if (Dark(player)) {
+    sprintf(tbuf1, (num > 1) ? T("%s has DARK-reconnected.") :
+           T("%s has DARK-connected."), Name(player));
+  } else if (hidden(player)) {
+    sprintf(tbuf1, (num > 1) ? T("%s has HIDDEN-reconnected.") :
+           T("%s has HIDDEN-connected."), Name(player));
+  } else {
+    sprintf(tbuf1, (num > 1) ? T("%s has reconnected.") :
+           T("%s has connected."), Name(player));
+  }
+
+  /* send out messages */
+  if (!Dark(player))
+    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
+
+#ifdef CHAT_SYSTEM
+  if (ANNOUNCE_CONNECTS)
+    chat_player_announce(player, tbuf1, (num == 1));
+#endif /* CHAT_SYSTEM */
+
+  loc = Location(player);
+  if (!GoodObject(loc)) {
+    notify(player, T("You are nowhere!"));
+    return;
+  }
+  orator = player;
+
+  if (cf_motd_msg && *cf_motd_msg) {
+    raw_notify(player, cf_motd_msg);
+  }
+  raw_notify(player, " ");
+
+  if(ANNOUNCE_CONNECTS)
+    notify_except(Contents(player), player, tbuf1, 0);
+  /* added to allow player's inventory to hear a player connect */
+  if(ANNOUNCE_CONNECTS)
+    if(!Dark(player))
+    notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
+
+  /* clear the environment for possible actions */
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = NULL;
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = NULL;
+
+  /* do the person's personal connect action */
+  (void) queue_attribute(player, "ACONNECT", player);
+  if (ROOM_CONNECTS) {
+    /* Do the room the player connected into */
+    if (IsRoom(loc) || IsThing(loc)) {
+      (void) queue_attribute(loc, "ACONNECT", player);
+    }
+  }
+  /* do the person's division */
+  if (GoodObject(SDIV(player).object))
+    (void) queue_attribute(SDIV(player).object, "ACONNECT", player);
+  /* do the zone of the player's location's possible aconnect */
+  if ((zone = Zone(loc)) != NOTHING) {
+    switch (Typeof(zone)) {
+    case TYPE_THING:
+      (void) queue_attribute(zone, "ACONNECT", player);
+      break;
+    case TYPE_ROOM:
+      /* check every object in the room for a connect action */
+      DOLIST(obj, Contents(zone)) {
+       (void) queue_attribute(obj, "ACONNECT", player);
+      }
+      break;
+    default:
+      do_log(LT_ERR, 0, 0,
+            T("Invalid zone #%d for %s(#%d) has bad type %d"), zone,
+            Name(player), player, Typeof(zone));
+    }
+  }
+  /* now try the master room */
+  DOLIST(obj, Contents(MASTER_ROOM)) {
+    (void) queue_attribute(obj, "ACONNECT", player);
+  }
+}
+
+void
+announce_disconnect(dbref player)
+{
+  dbref loc;
+  int num;
+  DESC *d;
+  char tbuf1[BUFFER_LEN];
+  dbref zone, obj;
+  int j;
+
+  loc = Location(player);
+  if (!GoodObject(loc))
+    return;
+
+  orator = player;
+
+  for (num = 0, d = descriptor_list; d; d = d->next)
+    if (d->connected && (d->player == player))
+      num++;
+  if (num < 2) {
+    sprintf(tbuf1, T("%s has disconnected."), Name(player));
+
+    if (ANNOUNCE_CONNECTS) {
+      if (!Dark(player))
+       notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
+      /* notify contents */
+      notify_except(Contents(player), player, tbuf1, 0);
+    }
+
+    /* clear the environment for possible actions */
+    for (j = 0; j < 10; j++)
+      global_eval_context.wnxt[j] = NULL;
+    for (j = 0; j < NUMQ; j++)
+      global_eval_context.rnxt[j] = NULL;
+
+    /* Setup all connect information as info to pass */
+    (void) queue_attribute(player, "ADISCONNECT", player);
+    /* do the person's division */
+    if (GoodObject(SDIV(player).object))
+      (void) queue_attribute(SDIV(player).object, "ACONNECT", player);
+    if (ROOM_CONNECTS)
+      if (IsRoom(loc) || IsThing(loc)) {
+       (void) queue_attribute(loc, "ADISCONNECT", player);
+      }
+    /* do the zone of the player's location's possible adisconnect */
+    if ((zone = Zone(loc)) != NOTHING) {
+      switch (Typeof(zone)) {
+      case TYPE_DIVISION:
+      case TYPE_THING:
+       (void) queue_attribute(zone, "ADISCONNECT", player);
+       break;
+      case TYPE_ROOM:
+       /* check every object in the room for a connect action */
+       DOLIST(obj, Contents(zone)) {
+         (void) queue_attribute(obj, "ADISCONNECT", player);
+       }
+       break;
+      default:
+       do_log(LT_ERR, 0, 0,
+              T("Invalid zone #%d for %s(#%d) has bad type %d"), zone,
+              Name(player), player, Typeof(zone));
+      }
+    }
+    /* now try the master room */
+    DOLIST(obj, Contents(MASTER_ROOM)) {
+      (void) queue_attribute(obj, "ADISCONNECT", player);
+    }
+    clear_flag_internal(player, "CONNECTED");
+    (void) atr_add(player, "LASTLOGOUT", show_time(mudtime, 0), GOD, NOTHING);
+  } else {
+    /* note: when you partially disconnect, ADISCONNECTS are not executed */
+    sprintf(tbuf1, T("%s has partially disconnected."), Name(player));
+
+    if (ANNOUNCE_CONNECTS) {
+      if (!Dark(player))
+       notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
+      /* notify contents */
+      notify_except(Contents(player), player, tbuf1, 0);
+    }
+  }
+
+#ifdef CHAT_SYSTEM
+  if (ANNOUNCE_CONNECTS)
+    chat_player_announce(player, tbuf1, 0);
+#endif /* CHAT_SYSTEM */
+
+  /* Monitor broadcasts */
+  /* Redundant, but better for translators */
+  if (Dark(player)) {
+    sprintf(tbuf1, (num < 2) ? T("%s has DARK-disconnected.") :
+           T("%s has partially DARK-disconnected."), Name(player));
+  } else if (hidden(player)) {
+    sprintf(tbuf1, (num < 2) ? T("%s has HIDDEN-disconnected.") :
+           T("%s has partially HIDDEN-disconnected."), Name(player));
+  } else {
+    sprintf(tbuf1, (num < 2) ? T("%s has disconnected.") :
+           T("%s has partially disconnected."), Name(player));
+  }
+  if (!Dark(player))
+    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
+  if(Guest(player))  { /* queue for destruction */
+      set_flag_internal(player, "GOING");
+      set_flag_internal(player, "GOING_TWICE");
+    }
+  local_disconnect(player, num);
+}
+
+/** Set an motd message.
+ * \verbatim
+ * This implements @motd.
+ * \endverbatim
+ * \param player the enactor.
+ * \param key type of MOTD to set.
+ * \param message text to set the motd to.
+ */
+void
+do_motd(dbref player, enum motd_type key, const char *message)
+{
+
+  if (!Site(player) && key != MOTD_LIST) {
+    notify(player,
+          T
+          ("You may get 15 minutes of fame and glory in life, but not right now."));
+    return;
+  }
+  switch (key) {
+  case MOTD_MOTD:
+    strcpy(cf_motd_msg, message);
+    notify(player, T("Motd set."));
+    break;
+  case MOTD_DOWN:
+    strcpy(cf_downmotd_msg, message);
+    notify(player, T("Down motd set."));
+    break;
+  case MOTD_FULL:
+    strcpy(cf_fullmotd_msg, message);
+    notify(player, T("Full motd set."));
+    break;
+  case MOTD_LIST:
+    notify_format(player, "MOTD: %s", cf_motd_msg);
+    if (Site(player)) {
+      notify_format(player, T("Down MOTD: %s"), cf_downmotd_msg);
+      notify_format(player, T("Full MOTD: %s"), cf_fullmotd_msg);
+    }
+  }
+}
+
+/** Set a DOING message.
+ * \verbatim
+ * This implements @doing.
+ * \endverbatim
+ * \param player the enactor.
+ * \param message the message to set.
+ */
+void
+do_doing(dbref player, const char *message)
+{
+  char buf[MAX_COMMAND_LEN];
+  DESC *d;
+  int i;
+
+  if (!Connected(player)) {
+    /* non-connected things have no need for a doing */
+    notify(player, T("Why would you want to do that?"));
+    return;
+  }
+  strncpy(buf, remove_markup(message, NULL), DOING_LEN - 1);
+
+  /* now smash undesirable characters and truncate */
+  for (i = 0; i < DOING_LEN; i++) {
+    if ((buf[i] == '\r') || (buf[i] == '\n') ||
+       (buf[i] == '\t') || (buf[i] == BEEP_CHAR))
+      buf[i] = ' ';
+  }
+  buf[DOING_LEN - 1] = '\0';
+
+  /* set it */
+  for (d = descriptor_list; d; d = d->next)
+    if (d->connected && (d->player == player))
+      strcpy(d->doing, buf);
+  if (strlen(message) >= DOING_LEN) {
+    notify_format(player,
+                 T("Doing set. %d characters lost."),
+                 strlen(message) - (DOING_LEN - 1));
+  } else
+    notify(player, T("Doing set."));
+}
+
+/** Set a poll message (which replaces "Doing" in the DOING output).
+ * \verbatim
+ * This implements @poll.
+ * \endverbatim
+ * \param player the enactor.
+ * \param message the message to set.
+ */
+void
+do_poll(dbref player, const char *message)
+{
+  int i;
+
+  if (!Change_Poll(player)) {
+    notify(player, T("Who do you think you are, Gallup?"));
+    return;
+  }
+  strncpy(poll_msg, remove_markup(message, NULL), DOING_LEN - 1);
+  for (i = 0; i < DOING_LEN; i++) {
+    if ((poll_msg[i] == '\r') || (poll_msg[i] == '\n') ||
+       (poll_msg[i] == '\t') || (poll_msg[i] == BEEP_CHAR))
+      poll_msg[i] = ' ';
+  }
+  poll_msg[DOING_LEN - 1] = '\0';
+
+  if (strlen(message) >= DOING_LEN) {
+    poll_msg[DOING_LEN - 1] = 0;
+    notify_format(player,
+                 T("Poll set. %d characters lost."),
+                 strlen(message) - (DOING_LEN - 1));
+  } else
+    notify(player, T("Poll set."));
+  do_log(LT_WIZ, player, NOTHING, T("Poll Set to '%s'."), poll_msg);
+}
+
+/** Match the partial name of a connected player.
+ * \param match string to match.
+ * \return dbref of a unique connected player whose name partial-matches, 
+ * AMBIGUOUS, or NOTHING.
+ */
+dbref
+short_page(const char *match)
+{
+  DESC *d;
+  dbref who1 = NOTHING;
+  int count = 0;
+
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected) {
+      if (match && !string_prefix(Name(d->player), match))
+       continue;
+      if (!strcasecmp(Name(d->player), match)) {
+       count = 1;
+       who1 = d->player;
+       break;
+      }
+      if (who1 == NOTHING || d->player != who1) {
+       who1 = d->player;
+       count++;
+      }
+    }
+  }
+
+  if (count > 1)
+    return AMBIGUOUS;
+  else if (count == 0)
+    return NOTHING;
+
+  return who1;
+}
+
+/** Match the partial name of a connected player the enactor can see.
+ * \param player the enactor
+ * \param match string to match.
+ * \return dbref of a unique connected player whose name partial-matches, 
+ * AMBIGUOUS, or NOTHING.
+ */
+dbref
+visible_short_page(dbref player, const char *match)
+{
+  dbref target;
+  target = short_page(match);
+  if (Priv_Who(player) || !GoodObject(target))
+    return target;
+  if (Dark(target) || (hidden(target) && !nearby(player, target)))
+    return NOTHING;
+  return target;
+}
+
+/* LWHO() function - really belongs elsewhere but needs stuff declared here */
+
+/* ARGSUSED */
+FUNCTION(fun_lwho)
+{
+  DESC *d;
+  dbref victim;
+  int first, powered = (*called_as == 'L') && Priv_Who(executor);
+
+  first = 1;
+  if(nargs && args[0] && *args[0]) {
+    if(!powered) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+
+    if( (victim = noisy_match_result(executor, args[0], NOTYPE, MAT_EVERYTHING)) == 0) {
+      safe_str(T(e_notvis), buff, bp);
+      return;
+    }
+    if(!controls(executor, victim)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if(!Priv_Who(victim))
+      powered = 0;
+  } else victim = executor;
+  
+  DESC_ITER_CONN(d) {
+    if (!Hidden(d) || (powered && CanSee(victim,d->player))) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_dbref(d->player, buff, bp);
+    }
+  }
+}
+
+
+/** Look up a DESC by character name or file descriptor.
+ * \param executor the dbref of the object calling the function calling this.
+ * \param name the name or descriptor to look up.
+ * \retval a pointer to the proper DESC, or NULL
+ */
+static DESC *
+lookup_desc(dbref executor, const char *name)
+{
+  DESC *d;
+
+  /* Given a file descriptor. See-all only. */
+  if (is_strict_integer(name)) {
+    int fd = parse_integer(name);
+    DESC_ITER_CONN(d) {
+      if (d->descriptor == fd) {
+       if (Priv_Who(executor) || d->player == executor
+       || (Inherit_Powers(executor) && Priv_Who(Owner(executor))))
+         return d;
+       else
+         return NULL;
+      }
+    }
+    return NULL;
+  } else {                    /* Look up player name */
+    DESC *match = NULL;
+    dbref target = lookup_player(name);
+    if (target == NOTHING) {
+      target = match_result(executor, name, TYPE_PLAYER,
+                           MAT_ABSOLUTE | MAT_PLAYER | MAT_ME);
+    }
+    if (!GoodObject(target) || !Connected(target))
+      return NULL;
+    else {
+      /* walk the descriptor list looking for a match of a dbref */
+      DESC_ITER_CONN(d) {
+       if ((d->player == target) &&
+           (!Hidden(d) || Priv_Who(executor) ||
+               (Inherit_Powers(executor) && Priv_Who(Owner(executor)))) &&
+           (!match || (d->last_time > match->last_time)))
+         match = d;
+      }
+      return match;
+    }
+  }
+}
+
+/** Return the ip address of the least-idle connection of a player.
+ * This function performs no permission checking, and returns the
+ * pointer from the descriptor itself (so don't destroy the result!)
+ * \param player dbref of player to get ip for.
+ * \return IP address of player as a string or NULL if not found.
+ */
+char *
+least_idle_ip(dbref player)
+{
+  DESC *d, *match = NULL;
+  DESC_ITER_CONN(d) {
+    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
+      match = d;
+  }
+  return match ? (match->ip) : NULL;
+}
+
+/** Return the hostname of the least-idle connection of a player.
+ * This function performs no permission checking, and returns a static
+ * string.
+ * \param player dbref of player to get ip for.
+ * \return hostname of player as a string or NULL if not found.
+ */
+char *
+least_idle_hostname(dbref player)
+{
+  DESC *d, *match = NULL;
+  static char hostname[101];
+  char *p;
+
+  DESC_ITER_CONN(d) {
+    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
+      match = d;
+  }
+  if (!match)
+    return NULL;
+  strcpy(hostname, match->addr);
+  if ((p = strchr(hostname, '@'))) {
+    p++;
+    return p;
+  } else
+    return hostname;
+}
+
+/* ZWHO() function - really belongs in eval.c but needs stuff declared here */
+/* ARGSUSED */
+FUNCTION(fun_zwho)
+{
+  DESC *d;
+  dbref zone, victim;
+  int first;
+  int powered = (strcmp(called_as, "ZMWHO") && Priv_Who(executor) || (Inherit_Powers(executor) && Priv_Who(Owner(executor))));
+  first = 1;
+
+  zone = match_thing(executor, args[0]);
+
+  if (nargs == 1) {
+    victim = executor;
+  } else if ((nargs == 2) && powered) {
+    if ((victim = match_thing(executor, args[1])) == 0) {
+      safe_str(T(e_match), buff, bp);
+      return;
+    }
+  } else {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (!GoodObject(zone) || !(eval_lock(victim, zone, Zone_Lock) || CanModify(victim,zone))) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if ((getlock(zone, Zone_Lock) == TRUE_BOOLEXP) ||
+      (IsPlayer(zone) && !(has_flag_by_name(zone, "SHARED", TYPE_PLAYER)))) {
+    safe_str(T("#-1 INVALID ZONE."), buff, bp);
+    return;
+  }
+
+  /* Use lowest privilege for victim */
+  if (!Priv_Who(victim))
+    powered = 0;
+
+  DESC_ITER_CONN(d) {
+    if (!Hidden(d) || powered) {
+      if (Zone(Location(d->player)) == zone) {
+       if (first) {
+         first = 0;
+       } else {
+         safe_chr(' ', buff, bp);
+       }
+       safe_dbref(d->player, buff, bp);
+      }
+    }
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_doing)
+{
+  /* Gets a player's @doing */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d)
+    safe_str(d->doing, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_hostname)
+{
+  /* Gets a player's hostname */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_str(d->addr, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ipaddr)
+{
+  /* Gets a player's IP address */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_str(d->ip, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cmds)
+{
+  /* Gets a player's IP address */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_integer(d->cmds, buff, bp);
+  else
+    safe_integer(-1, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sent)
+{
+  /* Gets a player's bytes sent */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_integer(d->input_chars, buff, bp);
+  else
+    safe_integer(-1, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_recv)
+{
+  /* Gets a player's bytes received */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_integer(d->output_chars, buff, bp);
+  else
+    safe_integer(-1, buff, bp);
+}
+
+FUNCTION(fun_poll)
+{
+  /* Gets the current poll */
+  if (poll_msg[0] == '\0')
+    strcpy(poll_msg, "Doing");
+
+  safe_str(poll_msg, buff, bp);
+}
+
+FUNCTION(fun_pueblo)
+{
+  /* Return the status of the pueblo flag on the least idle descriptor we
+   * find that matches the player's dbref.
+   */
+  DESC *match = lookup_desc(executor, args[0]);
+  if (match)
+    safe_boolean(match->conn_flags & CONN_HTML, buff, bp);
+  else
+    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
+}
+
+FUNCTION(fun_ssl)
+{
+  /* Return the status of the ssl flag on the least idle descriptor we
+   * find that matches the player's dbref.
+   */
+#ifdef HAS_OPENSSL
+  DESC *match;
+  if (!sslsock) {
+    safe_boolean(0, buff, bp);
+    return;
+  }
+  match = lookup_desc(executor, args[0]);
+  if (match) {
+    if (match->player == executor || Site(executor))
+      safe_boolean((match->ssl != NULL), buff, bp);
+    else
+      safe_str(T(e_perm), buff, bp);
+  } else
+    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
+#else
+  safe_boolean(0, buff, bp);
+#endif
+}
+
+FUNCTION(fun_width)
+{
+  DESC *match;
+  if (!*args[0])
+    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
+  else if ((match = lookup_desc(executor, args[0])))
+    safe_integer(match->width, buff, bp);
+  else
+    safe_str("78", buff, bp);
+}
+
+FUNCTION(fun_height)
+{
+  DESC *match;
+  if (!*args[0])
+    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
+  else if ((match = lookup_desc(executor, args[0])))
+    safe_integer(match->height, buff, bp);
+  else
+    safe_str("24", buff, bp);
+}
+
+FUNCTION(fun_terminfo)
+{
+  DESC *match;
+  if (!*args[0])
+    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
+  else if ((match = lookup_desc(executor, args[0]))) {
+    if (match->player == executor || Site(executor)) {
+      safe_str(match->ttype, buff, bp);
+      if (match->conn_flags & CONN_HTML)
+       safe_str(" pueblo", buff, bp);
+      if (match->conn_flags & CONN_TELNET)
+       safe_str(" telnet", buff, bp);
+#ifdef HAS_OPENSSL
+      if (sslsock && match->ssl)
+       safe_str(" ssl", buff, bp);
+#endif
+    } else
+      safe_str(T(e_perm), buff, bp);
+  } else
+    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idlesecs)
+{
+  /* returns the number of seconds a player has been idle, using
+   * their least idle connection
+   */
+
+  DESC *match = lookup_desc(executor, args[0]);
+  if (match)
+    safe_number(difftime(mudtime, match->last_time), buff, bp);
+  else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idle_average) {
+  DESC *match = lookup_desc(executor, args[0]);
+  double idle_time;
+
+  if(match) {
+    idle_time = difftime(mudtime, match->last_time);
+    if(idle_time >= 300)
+      safe_number(((match->idle_total + idle_time) / (match->unidle_times+1)), buff, bp);
+    else if(match->unidle_times == 0)
+      safe_number(0, buff, bp);
+    else
+      safe_number((match->idle_total / match->unidle_times), buff, bp);
+    
+  } else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idle_total) {
+  DESC *match = lookup_desc(executor, args[0]);
+  double idle_time;
+
+  if(match) {
+    idle_time = difftime(mudtime, match->last_time);
+    safe_number(idle_time >= 300 ? (match->idle_total+idle_time) : match->idle_total, buff, bp);
+  } else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idle_times) {
+  DESC *match = lookup_desc(executor, args[0]);
+
+  if(match) {
+     safe_number((difftime(mudtime, match->last_time) >= 300) ? 
+        (match->unidle_times+1) : match->unidle_times, buff, bp);
+  } else
+    safe_str("-1", buff, bp);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_conn)
+{
+  /* returns the number of seconds a player has been connected, using
+   * their longest-connected descriptor
+   */
+
+  DESC *match = lookup_desc(executor, args[0]);
+  if (match)
+    safe_number(difftime(mudtime, match->connected_at), buff, bp);
+  else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lports)
+{
+  DESC *d;
+  int first = 1;
+
+  if (!Priv_Who(executor)
+       && !(Inherit_Powers(executor) && Priv_Who(Owner(executor)))) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  DESC_ITER_CONN(d) {
+    if (first)
+      first = 0;
+    else
+      safe_chr(' ', buff, bp);
+    safe_integer(d->descriptor, buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ports)
+{
+  /* returns a list of the network descriptors that a player is
+   * connected to 
+   */
+
+  dbref target;
+  DESC *d;
+  int first;
+
+  target = lookup_player(args[0]);
+  if (target == NOTHING) {
+    target = match_result(executor, args[0], TYPE_PLAYER,
+                         MAT_ABSOLUTE | MAT_PLAYER | MAT_ME);
+  }
+  if (target != executor && !Priv_Who(executor)
+       && !(Inherit_Powers(executor) && Priv_Who(Owner(executor)))) {
+    /* This should probably be a safe_str */
+    notify(executor, T("Permission denied."));
+    return;
+  }
+  if (!GoodObject(target) || !Connected(target)) {
+    return;
+  }
+  /* Walk descriptor chain. */
+  first = 1;
+  DESC_ITER_CONN(d) {
+    if (d->player == target) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_integer(d->descriptor, buff, bp);
+    }
+  }
+}
+
+
+/** Hide or unhide a player.
+ * Although hiding is a per-descriptor state, this function sets all of
+ * a player's connected descriptors to be hidden.
+ * \param player dbref of player to hide.
+ * \param hide if 1, hide; if 0, unhide.
+ */
+void
+hide_player(dbref player, int hide)
+{
+  DESC *d;
+  if (!Connected(player))
+    return;
+  if (!Can_Hide(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* change status on WHO */
+  if (Can_Hide(player)) {
+    DESC_ITER_CONN(d) {
+      if (d->player == player)
+       d->hide = hide;
+    }
+  }
+  if (hide)
+    notify(player, T("You no longer appear on the WHO list."));
+  else
+    notify(player, T("You now appear on the WHO list."));
+}
+
+/** Perform the periodic check of inactive descriptors, and 
+ * disconnect them or autohide them as appropriate.
+ */
+void
+inactivity_check(void)
+{
+  DESC *d, *nextd;
+  ATTR *a;
+  char tbuf[BUFFER_LEN];
+  time_t now, idle, idle_for, unconnected_idle;
+  if (!INACTIVITY_LIMIT && !UNCONNECTED_LIMIT)
+    return;
+  now = mudtime;
+  idle = INACTIVITY_LIMIT ? INACTIVITY_LIMIT : INT_MAX;
+  unconnected_idle = UNCONNECTED_LIMIT ? UNCONNECTED_LIMIT : INT_MAX;
+  for (d = descriptor_list; d; d = nextd) {
+    nextd = d->next;
+    idle_for = now - d->last_time;
+    /* If they've been connected for 60 seconds without getting a telnet-option
+       back, the client probably doesn't understand them */
+    if ((d->conn_flags & CONN_TELNET_QUERY) && idle_for > 60)
+      d->conn_flags &= ~CONN_TELNET_QUERY;
+    if(d->connected && GoodObject(d->player) && ((a = atr_get(d->player, "IDLE_TIMEOUT"))!=NULL)) {
+           memset(tbuf, '\0', BUFFER_LEN);
+           strncpy(tbuf, atr_value(a), BUFFER_LEN-1);
+           idle = atoi(tbuf);
+           if(idle > 0)
+             goto after_idle_atr_check;
+    } 
+    idle = INACTIVITY_LIMIT ? INACTIVITY_LIMIT : INT_MAX;
+after_idle_atr_check:
+    if ((d->connected) ? (idle_for > idle ) : (idle_for > unconnected_idle)) {
+
+      if (!d->connected)
+       shutdownsock(d);
+      else if (!Can_Idle(d->player)) {
+
+       queue_string(d, T("\n*** Inactivity timeout ***\n"));
+       do_log(LT_CONN, 0, 0,
+              T("[%d/%s/%s] Logout by %s(#%d) <Inactivity Timeout>"),
+              d->descriptor, d->addr, d->ip, Name(d->player), d->player);
+       boot_desc(d);
+      } else if (Unfind(d->player)) {
+
+       if ((Can_Hide(d->player)) && (!Hidden(d))) {
+         queue_string(d,
+                      T
+                      ("\n*** Inactivity limit reached. You are now HIDDEN. ***\n"));
+         d->hide = 1;
+       }
+      }
+    }
+  }
+}
+
+
+/** Given a player dbref, return the player's hidden status.
+ * \param player dbref of player to check.
+ * \retval 1 player is hidden.
+ * \retval 0 player is not hidden.
+ */
+int
+hidden(dbref player)
+{
+  DESC *d;
+  DESC_ITER_CONN(d) {
+    if (d->player == player) {
+      if (Hidden(d))
+       return 1;
+      else
+       return 0;
+    }
+  }
+  return 0;
+}
+
+
+/** Return the mailp of the player closest in db# to player,
+ * or NULL if there's nobody on-line.
+ * In the current mail system, mail is stored in a linked list, sorted
+ * by recipient, which makes the most common operations (listing and reading
+ * your mail) fast. When a player first connects, we store (on the
+ * mailp element of their descriptor) a pointer to the beginning of
+ * their part of the linked list. Rather than search the whole linked
+ * list to find this location, we look at the mailp's of all the other
+ * connected players, and find the mailp of the player whose dbref
+ * is closest to the connecting player, and start our search from that
+ * point. This scales up nicely - as a mushes get larger, the linked
+ * list gets larger, but the more people connected at once, the faster
+ * the search for a newly connecting player's first mail.
+ * \param player player whose db# we want to get near.
+ * \return pointer to first mail of connected player with db# closest to
+ * player.
+ */
+MAIL *
+desc_mail(dbref player)
+{
+  DESC *d;
+  int i;
+  int diff = db_top;
+  static MAIL *mp;
+  mp = NULL;
+  DESC_ITER_CONN(d) {
+    i = abs(d->player - player);
+    if (i == 0)
+      return d->mailp;
+    if ((i < diff) && d->mailp) {
+      diff = i;
+      mp = d->mailp;
+    }
+  }
+  return mp;
+}
+
+/** Set a player's mail position on all their descriptors.
+ * \param player player to set mail position for.
+ * \param mp pointer to first mail in their list.
+ */
+void
+desc_mail_set(dbref player, MAIL *mp)
+{
+  DESC *d;
+  DESC_ITER_CONN(d) {
+    if (d->player == player)
+      d->mailp = mp;
+  }
+}
+
+/** Clear mail positions on all descriptors. Called from do_mail_nuke().
+ */
+void
+desc_mail_clear(void)
+{
+  DESC *d;
+  DESC_ITER_CONN(d) {
+    d->mailp = NULL;
+  }
+}
+
+
+
+
+#ifdef SUN_OS
+/* SunOS's implementation of stdio breaks when you get a file descriptor
+ * greater than 128. Brain damage, brain damage, brain damage!
+ *
+ * Our objective, therefore, is not to fix stdio, but to work around it,
+ * so that performance degrades semi-gracefully when you are using a lot
+ * of file descriptors.
+ * Therefore, we'll save a file descriptor when we start up that is less
+ * than 128, so that if we get a file descriptor that is >= 128, we can
+ * use our own saved file descriptor instead. This is only one level of
+ * defense; if you have more than 128 fd's in use, and you try two fopen's
+ * before doing an fclose(), the second will fail.
+ */
+
+FILE *
+fopen(file, mode)
+    const char *file;
+    const char *mode;
+{
+/* FILE *f; */
+  int fd, rw, oflags = 0;
+/* char tbchar; */
+  rw = (mode[1] == '+') || (mode[1] && (mode[2] == '+'));
+  switch (*mode) {
+  case 'a':
+    oflags = O_CREAT | (rw ? O_RDWR : O_WRONLY);
+    break;
+  case 'r':
+    oflags = rw ? O_RDWR : O_RDONLY;
+    break;
+  case 'w':
+    oflags = O_TRUNC | O_CREAT | (rw ? O_RDWR : O_WRONLY);
+    break;
+  default:
+    return (NULL);
+  }
+/* SunOS fopen() doesn't use the 't' or 'b' flags. */
+
+
+  fd = open(file, oflags, 0666);
+  if (fd < 0)
+    return NULL;
+  /* My addition, to cope with SunOS brain damage! */
+  if (fd >= 128) {
+    close(fd);
+    if ((extrafd < 128) && (extrafd >= 0)) {
+      close(extrafd);
+      fd = open(file, oflags, 0666);
+      extrafd = -1;
+    } else {
+      return NULL;
+    }
+  }
+  /* End addition. */
+
+  return fdopen(fd, mode);
+}
+
+
+#undef fclose(x)
+int
+f_close(stream)
+    FILE *stream;
+{
+  int fd = fileno(stream);
+  /* if extrafd is bad, and the fd we're closing is good, recycle the
+   * fd into extrafd.
+   */
+  fclose(stream);
+  if (((extrafd < 0)) && (fd >= 0) && (fd < 128)) {
+    extrafd = open("/dev/null", O_RDWR);
+    if (extrafd >= 128) {
+      /* To our surprise, we didn't get a usable fd. */
+      close(extrafd);
+      extrafd = -1;
+    }
+  }
+  return 0;
+}
+
+#define fclose(x) f_close(x)
+#endif                         /* SUN_OS */
+
+static int
+how_many_fds(void)
+{
+  /* Determine how many open file descriptors we're allowed
+   * In order, we'll try:
+   * 1. sysconf(_SC_OPEN_MAX) - POSIX.1
+   * 2. OPEN_MAX constant - POSIX.1 limits.h
+   * 3. getdtablesize - BSD (which Config maps to ulimit or NOFILE if needed)
+   */
+  static int open_max = 0;
+#ifdef WIN32
+  int iMaxSocketsAllowed;
+#endif
+  if (open_max)
+    return open_max;
+#ifdef WIN32
+  /* Typically, WIN32 allows many open sockets, but won't perform
+   * well if too many are used. The best approach is to give the
+   * admin a single point of control (MAX_LOGINS in MUSH.CNF) and then
+   * allow a few more connections than that here for clients to get access
+   * to an E-mail address or at least a title. 2 is an arbitrary number.
+   *
+   * If max_logins is set to 0 in mush.cnf (unlimited logins),
+   * we'll allocate 120 sockets for now.
+   *
+   * wsadata.iMaxSockets isn't valid for WinSock versions 2.0
+   * and later, but we are requesting version 1.1, so it's valid.
+   */
+  iMaxSocketsAllowed = options.max_logins ? (2 * options.max_logins) : 120;
+  if (wsadata.iMaxSockets < iMaxSocketsAllowed)
+    iMaxSocketsAllowed = wsadata.iMaxSockets;
+  return iMaxSocketsAllowed;
+#else
+#ifdef HAS_SYSCONF
+  errno = 0;
+  if ((open_max = sysconf(_SC_OPEN_MAX)) < 0) {
+    if (errno == 0)            /* Value was indeterminate */
+      open_max = 0;
+  }
+  if (open_max)
+    return open_max;
+#endif
+#ifdef OPEN_MAX
+  open_max = OPEN_MAX;
+  return open_max;
+#endif
+  /* Caching getdtablesize is dangerous, since it's affected by
+   * getrlimit, so we don't.
+   */
+  open_max = 0;
+  return getdtablesize();
+#endif                         /* WIN 32 */
+}
+
+#ifdef HAS_OPENSSL
+/** Take down all SSL client connections and close the SSL server socket.
+ * Typically, this is in preparation for a shutdown/reboot.
+ */
+void
+close_ssl_connections(void)
+{
+  DESC *d;
+
+  if (!sslsock)
+    return;
+
+  /* Close clients */
+  DESC_ITER_CONN(d) {
+    if (d->ssl) {
+      queue_string_eol(d, T(ssl_shutdown_message));
+      process_output(d);
+      ssl_close_connection(d->ssl);
+      d->ssl = NULL;
+      d->conn_flags |= CONN_CLOSE_READY;
+    }
+  }
+  /* Close server socket */
+  ssl_close_connection(ssl_master_socket);
+  shutdown(sslsock, 2);
+  closesocket(sslsock);
+  sslsock = 0;
+  options.ssl_port = 0;
+}
+#endif
+
+
+/** Dump the descriptor list to our REBOOTFILE so we can restore it on reboot.
+ */
+void
+dump_reboot_db(void)
+{
+  FILE *f;
+  DESC *d;
+  SU_PATH *exit_path;
+  long flags = RDBF_SCREENSIZE | RDBF_TTYPE | RDBF_PUEBLO_CHECKSUM
+               | RDBF_SU_EXIT_PATH;
+  if (setjmp(db_err)) {
+    flag_broadcast(0, 0, T("GAME: Error writing reboot database!"));
+    exit(0);
+  } else {
+
+    f = fopen(REBOOTFILE, "w");
+    /* This shouldn't happen */
+    if (!f) {
+      flag_broadcast(0, 0, T("GAME: Error writing reboot database!"));
+      exit(0);
+    }
+    /* Write out the reboot db flags here */
+    fprintf(f, "V%ld\n", flags);
+    putref(f, sock);
+    putref(f, maxd);
+    /* First, iterate through all descriptors to get to the end
+     * we do this so the descriptor_list isn't reversed on reboot
+     */
+    for (d = descriptor_list; d && d->next; d = d->next) ;
+    /* Second, we iterate backwards from the end of descriptor_list
+     * which is now in the d variable.
+     */
+    for (; d != NULL; d = d->prev) {
+      putref(f, d->descriptor);
+      putref(f, d->connected_at);
+      putref(f, d->hide);
+      putref(f, d->cmds);
+      putref(f, d->idle_total);
+      putref(f, d->unidle_times);
+      if (GoodObject(d->player))
+       putref(f, d->player);
+      else
+       putref(f, -1);
+      putref(f, d->last_time);
+      if (d->output_prefix)
+       putstring(f, (char *) d->output_prefix);
+      else
+       putstring(f, "__NONE__");
+      if (d->output_suffix)
+       putstring(f, (char *) d->output_suffix);
+      else
+       putstring(f, "__NONE__");
+      putstring(f, d->addr);
+      putstring(f, d->ip);
+      putstring(f, d->doing);
+      putref(f, d->conn_flags);
+      putref(f, d->width);
+      putref(f, d->height);
+      putstring(f, d->ttype);
+      putstring(f, d->checksum);
+      for(exit_path = d->su_exit_path; exit_path; exit_path = exit_path->next)
+        putref(f, exit_path->player);
+      putref(f, NOTHING);
+    }                          /* for loop */
+
+    putref(f, 0);
+    putstring(f, poll_msg);
+    putref(f, first_start_time);
+    putref(f, reboot_count);
+    fclose(f);
+  }
+}
+
+/** Load the descriptor list back from the REBOOTFILE on reboot.
+ */
+void
+load_reboot_db(void)
+{
+  FILE *f;
+  DESC *d = NULL;
+  DESC *closed = NULL, *nextclosed;
+  int val;
+  int n;
+  const char *temp;
+  char c;
+  long flags = 0;
+  char tbuf1[BUFFER_LEN];
+  SU_PATH *epnext, *epprev;
+  dbref exit_path;
+  f = fopen(REBOOTFILE, "r");
+  if (!f) {
+    restarting = 0;
+    return;
+  }
+  restarting = 1;
+  /* Get the first line and see if it's a set of reboot db flags.
+   * Those start with V<number>
+   * If not, assume we're using the original format, in which the
+   * sock appears first
+   * */
+  c = getc(f);                 /* Skip the V */
+  if (c == 'V') {
+    flags = getref(f);
+  } else {
+    ungetc(c, f);
+  }
+
+  sock = getref(f);
+  val = getref(f);
+  if (val > maxd)
+    maxd = val;
+
+  while ((val = getref(f)) != 0) {
+    ndescriptors++;
+    d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
+    d->descriptor = val;
+    d->input_handler = do_command;
+    d->connected_at = getref(f);
+    d->hide = getref(f);
+    d->cmds = getref(f);
+    d->idle_total = getref(f);
+    d->unidle_times = getref(f);
+    d->player = getref(f);
+    d->last_time = getref(f);
+    d->pinfo.object = NOTHING;
+    d->pinfo.atr = NULL;
+    d->pinfo.lock = 0;
+    d->pinfo.function = NULL;
+    d->connected = GoodObject(d->player) ? 1 : 0;
+    /* setup snooper array */
+    for(n = 0; n < MAX_SNOOPS; n++)
+      d->snooper[n] = -1;
+
+    temp = getstring_noalloc(f);
+    d->output_prefix = NULL;
+    if (strcmp(temp, "__NONE__"))
+      set_userstring(&d->output_prefix, temp);
+    temp = getstring_noalloc(f);
+    d->output_suffix = NULL;
+    if (strcmp(temp, "__NONE__"))
+      set_userstring(&d->output_suffix, temp);
+    strcpy(d->addr, getstring_noalloc(f));
+    strcpy(d->ip, getstring_noalloc(f));
+    strcpy(d->doing, getstring_noalloc(f));
+    d->conn_flags = getref(f);
+    if (flags & RDBF_SCREENSIZE) {
+      d->width = getref(f);
+      d->height = getref(f);
+    } else {
+      d->width = 78;
+      d->width = 24;
+    }
+    if (flags & RDBF_TTYPE)
+      d->ttype = mush_strdup(getstring_noalloc(f), "terminal description");
+    else
+      d->ttype = mush_strdup("unknown", "terminal description");
+    if (flags & RDBF_PUEBLO_CHECKSUM)
+      strcpy(d->checksum, getstring_noalloc(f));
+    else
+      d->checksum[0] = '\0';
+    d->su_exit_path = NULL;
+    if (flags & RDBF_SU_EXIT_PATH) {
+      exit_path = getref(f);
+      while(GoodObject(exit_path)) {
+        add_to_exit_path(d, exit_path);
+        exit_path = getref(f);
+      }
+      epprev = NULL;
+      while(d->su_exit_path) {
+        epnext = d->su_exit_path->next;
+        d->su_exit_path->next = epprev;
+        epprev = d->su_exit_path;
+        d->su_exit_path = epnext;
+      }
+      d->su_exit_path = epprev;
+    }
+    d->input_chars = 0;
+    d->output_chars = 0;
+    d->output_size = 0;
+    d->output.head = 0;
+    d->output.tail = &d->output.head;
+    d->input.head = 0;
+    d->input.tail = &d->input.head;
+    d->raw_input = NULL;
+    d->raw_input_at = NULL;
+    d->quota = options.starting_quota;
+    d->mailp = NULL;
+#ifdef HAS_OPENSSL
+    d->ssl = NULL;
+    d->ssl_state = 0;
+#endif
+    if (d->conn_flags & CONN_CLOSE_READY) {
+      /* This isn't really an open descriptor, we're just tracking
+       * it so we can announce the disconnect properly. Do so, but
+       * don't link it into the descriptor list. Instead, keep a
+       * separate list.
+       */
+      if (closed)
+       closed->prev = d;
+      d->next = closed;
+      d->prev = NULL;
+      closed = d;
+    } else {
+      if (descriptor_list)
+       descriptor_list->prev = d;
+      d->next = descriptor_list;
+      d->prev = NULL;
+      descriptor_list = d;
+      if (d->connected && d->player && GoodObject(d->player) &&
+         IsPlayer(d->player))
+       set_flag_internal(d->player, "CONNECTED");
+      else if ((!d->player || !GoodObject(d->player)) && d->connected) {
+       d->connected = 0;
+       d->player = 0;
+      }
+    /* If they were in a program, get them back into it */
+      if(d->connected && InProg(d->player))
+        prog_load_desc(d);
+    }
+  }                            /* while loop */
+
+  /* Now announce disconnects of everyone who's not really here */
+  while (closed) {
+    nextclosed = closed->next;
+    if(closed->last_time > 0) {
+      closed->idle_total += difftime(mudtime, closed->last_time);
+      closed->unidle_times++;
+    }
+
+    snprintf(tbuf1, BUFFER_LEN-1, "%ld %ld %d %d", (mudtime - closed->connected_at),
+        closed->idle_total , closed->unidle_times, closed->cmds);
+    tbuf1[strlen(tbuf1)+1] = '\0';
+    (void) atr_add(closed->player, "LASTACTIVITY", tbuf1, GOD, NOTHING);
+    announce_disconnect(closed->player);
+    mush_free(closed, "descriptor");
+    closed = nextclosed;
+  }
+
+  strcpy(poll_msg, getstring_noalloc(f));
+  first_start_time = getref(f);
+  reboot_count = getref(f) + 1;
+  DESC_ITER_CONN(d) {
+    d->mailp = find_exact_starting_point(d->player);
+  }
+#ifdef HAS_OPENSSL
+  if (SSLPORT) {
+    sslsock = make_socket(SSLPORT, NULL, NULL, SSL_IP_ADDR);
+    ssl_master_socket = ssl_setup_socket(sslsock);
+    if (sslsock >= maxd)
+      maxd = sslsock + 1;
+  }
+#endif
+
+  fclose(f);
+  remove(REBOOTFILE);
+  flag_broadcast(0, 0, T("GAME: Reboot finished."));
+}
+
+
+#ifdef NT_TCP
+
+/* --------------------------------------------------------------------------- */
+/* Thread to listen on MUD port - for Windows NT */
+/* --------------------------------------------------------------------------- */
+void __cdecl
+MUDListenThread(void *pVoid)
+{
+  SOCKET MUDListenSocket = (SOCKET) pVoid;
+  SOCKET socketClient;
+  union sockaddr_u addr;
+  int nLen;
+  socklen_t addr_len;
+  struct hostname_info *hi;
+  char *socket_ident;
+  char *chp;
+  BOOL b;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  char *bp;
+  DESC *d;
+  printf(T("Starting MUD listening thread ...\n"));
+  /* Loop forever accepting connections */
+  while (TRUE) {
+
+    /* Block on accept() */
+    nLen = sizeof(SOCKADDR_IN);
+    socketClient = accept(MUDListenSocket, (LPSOCKADDR) & addr, &nLen);
+    if (socketClient == INVALID_SOCKET) {
+      /* parent thread closes the listening socket */
+      /* when it wants this thread to stop. */
+      break;
+    }
+    /* We have a connection */
+    /*  */
+
+    bp = tbuf2;
+    addr_len = sizeof(addr);
+    hi = ip_convert(&addr.addr, addr_len);
+    safe_str(hi ? hi->hostname : "", tbuf2, &bp);
+    *bp = '\0';
+    bp = tbuf1;
+    if (USE_IDENT) {
+      int timeout = IDENT_TIMEOUT;
+      socket_ident = ident_id(socketClient, &timeout);
+      if (socket_ident) {
+       /* Truncate at first non-printable character */
+       for (chp = socket_ident; *chp && isprint((unsigned char) *chp); chp++) ;
+       *chp = '\0';
+       safe_str(socket_ident, tbuf1, &bp);
+       safe_chr('@', tbuf1, &bp);
+       free(socket_ident);
+      }
+    }
+
+    hi = hostname_convert(&addr.addr, addr_len);
+    safe_str(hi ? hi->hostname : "", tbuf1, &bp);
+    *bp = '\0';
+    if (Forbidden_Site(tbuf1) || Forbidden_Site(tbuf2)) {
+      if (!Deny_Silent_Site(tbuf1, AMBIGUOUS)
+         || !Deny_Silent_Site(tbuf2, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0, T("[%d/%s] Refused connection (remote port %s)"),
+              socketClient, tbuf1, hi->port);
+      }
+      shutdown(socketClient, 2);
+      closesocket(socketClient);
+      continue;
+    }
+    do_log(LT_CONN, 0, 0, T("[%d/%s] Connection opened."), socketClient, tbuf1);
+    d = initializesock(socketClient, tbuf1, tbuf2, 0);
+    printf(T("[%d/%s] Connection opened.\n"), socketClient, tbuf1);
+/* add this socket to the IO completion port */
+    CompletionPort = CreateIoCompletionPort((HANDLE) socketClient,
+                                           CompletionPort, (DWORD) d, 1);
+    if (!CompletionPort) {
+      char sMessage[200];
+      GetErrorMessage(GetLastError(), sMessage, sizeof sMessage);
+      printf
+       ("Error %ld (%s) on CreateIoCompletionPort for socket %ld\n",
+        GetLastError(), sMessage, socketClient);
+      shutdownsock(d);
+      continue;
+    }
+/* welcome the user - we can't do this until the completion port is created
+*/
+
+    test_telnet(d);
+    welcome_user(d);
+/* do the first read */
+    b = ReadFile((HANDLE) socketClient,
+                d->input_buffer,
+                sizeof(d->input_buffer) - 1, NULL, &d->InboundOverlapped);
+    if (!b && GetLastError() != ERROR_IO_PENDING) {
+      shutdownsock(d);
+      continue;
+    }
+  }                            /* end of while loop */
+
+  printf(T("End of MUD listening thread ...\n"));
+}                              /* end of MUDListenThread */
+
+
+/*
+   This is called from within shovechars when it needs to see if any IOs have
+   completed for the Windows NT version.
+
+   The 4 sorts of IO completions are:
+
+   1. Outstanding read completing (there should always be an outstanding read)
+   2. Outstanding write completing
+   3. A special "shutdown" message to tell us to shutdown the socket
+   4. A special "aborted" message to tell us the socket has shut down, and we
+   can now free the descriptor.
+
+   The latter 2 are posted by the application by PostQueuedCompletionStatus
+   when it is necessary to signal these "events".
+
+   The reason for posting the special messages is to shut down sockets in an
+   orderly way.
+
+ */
+void
+ProcessWindowsTCP(void)
+{
+  LPOVERLAPPED lpo;
+  DWORD nbytes;
+  BOOL b;
+  DWORD nError;
+  DESC *d;
+  time_t now;
+  /* pull out the next completed IO, waiting 50 ms for it if necessary */
+  b = GetQueuedCompletionStatus(CompletionPort, &nbytes, (LPDWORD) & d, &lpo, 50);     /* twentieth-second timeout */
+  nError = GetLastError();
+  /* ignore timeouts and cancelled IOs */
+  if (!b && (nError == WAIT_TIMEOUT || nError == ERROR_OPERATION_ABORTED)) {
+    /* process queued commands */
+    do_top(options.queue_chunk);
+    return;
+  }
+
+  /* shutdown this descriptor if wanted */
+  if (lpo == &lpo_shutdown) {
+    shutdownsock(d);           /* shut him down */
+    return;
+  }
+  /* *now* we can free the descriptor (posted by shutdownsock) */
+  if (lpo == &lpo_aborted) {
+    freeqs(d);
+    mush_free((Malloc_t) d, "descriptor");
+    return;
+  }
+  /* if address of descriptor is bad - don't try using it */
+
+  if (!IsValidAddress(d, sizeof(DESC), TRUE)) {
+    printf("Invalid descriptor %08lX on GetQueuedCompletionStatus\n", d);
+    return;
+  }
+  /* bad IO - shut down this client */
+
+  if (!b) {
+    shutdownsock(d);
+    return;
+  }
+  /* see if read completed */
+
+  if (lpo == &d->InboundOverlapped && !d->bConnectionDropped) {
+
+    /* zero length IO completion means connection dropped by client */
+
+    if (nbytes == 0) {
+      shutdownsock(d);
+      return;
+    }
+    now = mudtime;
+    if((d->last_time - now) >= 10) {
+      queue_attribute(d->player, "AUNIDLE", d->player);
+    } else notify_format(d->player, "WHAT?!?! debug: %d", (d->last_time - now) );
+
+    if(difftime(mudtime, d->last_time) >= 300) {
+      d->idle_total += difftime(mudtime, d->last_time);
+      d->unidle_times++;
+    }
+    d->last_time = now;
+
+    /* process the player's input */
+    process_input_helper(d, d->input_buffer, nbytes);
+    /* now fire off another read */
+    b = ReadFile((HANDLE) d->descriptor,
+                d->input_buffer,
+                sizeof(d->input_buffer) - 1, NULL, &d->InboundOverlapped);
+    if (!b && GetLastError() != ERROR_IO_PENDING) {
+      d->bConnectionDropped = TRUE;    /* do no more reads */
+      /* post a notification that the descriptor should be shutdown */
+      if (!PostQueuedCompletionStatus
+         (CompletionPort, 0, (DWORD) d, &lpo_shutdown)) {
+       char sMessage[200];
+       DWORD nError = GetLastError();
+       GetErrorMessage(nError, sMessage, sizeof sMessage);
+       printf
+         ("Error %ld (%s) on PostQueuedCompletionStatus in ProcessWindowsTCP (read)\n",
+          nError, sMessage);
+       shutdownsock(d);
+      }
+      return;
+    }
+  }
+  /* end of read completing */
+  /* see if write completed */
+  else if (lpo == &d->OutboundOverlapped && !d->bConnectionDropped) {
+    struct text_block **qp, *cur;
+    DWORD nBytes;
+    qp = &d->output.head;
+    if ((cur = *qp) == NULL)
+      d->bWritePending = FALSE;
+    else {                     /* here if there is more to write */
+
+      /* if buffer too long, write what we can and queue up the rest */
+
+      if (cur->nchars <= sizeof(d->output_buffer)) {
+       nBytes = cur->nchars;
+       memcpy(d->output_buffer, cur->start, nBytes);
+       if (!cur->nxt)
+         d->output.tail = qp;
+       *qp = cur->nxt;
+       free_text_block(cur);
+      }
+      /* end of able to write the lot */
+      else {
+       nBytes = sizeof(d->output_buffer);
+       memcpy(d->output_buffer, cur->start, nBytes);
+       cur->nchars -= nBytes;
+       cur->start += nBytes;
+      }                                /* end of buffer too long */
+
+      d->OutboundOverlapped.Offset = 0;
+      d->OutboundOverlapped.OffsetHigh = 0;
+      b = WriteFile((HANDLE) d->descriptor,
+                   d->output_buffer, nBytes, NULL, &d->OutboundOverlapped);
+      d->bWritePending = FALSE;
+      if (!b)
+       if (GetLastError() == ERROR_IO_PENDING)
+         d->bWritePending = TRUE;
+       else {
+         d->bConnectionDropped = TRUE; /* do no more reads */
+         /* post a notification that the descriptor should be shutdown */
+         if (!PostQueuedCompletionStatus
+             (CompletionPort, 0, (DWORD) d, &lpo_shutdown)) {
+           char sMessage[200];
+           DWORD nError = GetLastError();
+           GetErrorMessage(nError, sMessage, sizeof sMessage);
+           printf
+             ("Error %ld (%s) on PostQueuedCompletionStatus in ProcessWindowsTCP (write)\n",
+              nError, sMessage);
+           shutdownsock(d);
+         }
+         return;
+       }
+    }                          /* end of more to write */
+
+  }
+
+  /* end of write completing */
+  /* process queued commands */
+  do_top(options.active_q_chunk);
+}
+
+#endif
+
+/* Syntax: @snoop[/list] [!]<descriptor>
+ */  
+COMMAND(cmd_snoop) {
+  DESC *d;
+  int descn, on, n, sn;
+  char snooplist[MAX_SNOOPS][BUFFER_LEN];
+  char buf[BUFFER_LEN], *bp;
+
+  if(SW_ISSET(sw, SWITCH_LIST)) {
+         descn = atoi(arg_left);
+
+         bp = buf;
+
+         d = port_desc(descn);
+
+          if (LEVEL(player) <= 28) {
+                 notify(player, MSG_HUH);
+                 return;
+         }
+         /* make sure teh desc exists and they're connected (no spying on 'em at the connect screen!) */
+         if(!d || (d && !IsPlayer(d->player))) {
+                 notify(player, "There is no one connected on that descriptor.");
+                 return;
+         }
+
+         for(sn = 0, n = 0; n < MAX_SNOOPS; n++)
+                 if(d->snooper[n] != -1) {
+                         memset(snooplist[sn], '\0', BUFFER_LEN);
+                         snprintf(snooplist[sn++], BUFFER_LEN-1, "%s", Name(d->snooper[n])); 
+                 }
+         *snooplist[sn] = '\0';
+
+         for(n = 0; n < sn; n++) {
+                 if(n != 0)
+                         safe_str(", ", buf, &bp);
+                 if(n == (sn-1) && sn > 1)
+                         safe_str("& ", buf, &bp);
+                 safe_str(snooplist[n], buf, &bp);
+         }
+         *bp = '\0';
+         notify_format(player, "%s is being snooped on by: %s", Name(d->player), buf);
+
+  } else {
+  if(*arg_left== '!') {
+    on = 0;
+    descn = atoi(arg_left+1);
+  } else { 
+    descn = atoi(arg_left);
+    on = 1;
+  }
+
+  d = port_desc(descn);
+  if (LEVEL(player) <= 28) {
+    notify(player, MSG_HUH);
+    return;
+  }
+  /* make sure teh desc exists and they're connected (no spying on 'em at the connect screen!) */
+  if(!d || (d && !IsPlayer(d->player))) {
+    notify(player, "There is no one connected on that descriptor.");
+    return;
+  }
+  
+  if(on) {
+         if((d->player == player)) {
+                 notify(player, "You can't snoop yourself."); 
+                 return;
+         }
+
+  switch(set_snoop(player,d)) {
+    case -1: /* Too Many */
+      notify(player, "Sorry, can't snoop at this time.");
+      return;
+    case -2: /* Already Snoopin on 'em */
+      notify(player, "You can only snoop one person at a time.");
+      return;
+    default:
+      notify_format(player, T("Snoop now set on %s(%d)"), Name(d->player), descn); 
+  }
+  } else {
+    for(on = n = 0; n < MAX_SNOOPS; n++)
+      if(d->snooper[n] == player)  {
+       d->snooper[n] = -1;
+       on = 1;
+      }
+      notify(player, on ? "Snoop deactivated." : "Snooping Error.");
+  }
+  }
+}
+
+
+void feed_snoop(DESC *d, const char *s, char dir) {
+  int n;
+  char snstr[BUFFER_LEN];
+
+
+  if(!d ||!d->connected)
+    return;
+  memset(snstr, '\0', BUFFER_LEN);
+  strncpy(snstr, remove_markup(s, NULL), BUFFER_LEN-1);
+  if(*s && !*snstr)
+         return;
+  for(n = 0; n < MAX_SNOOPS ; n++)
+    if(!IsPlayer(d->snooper[n]))
+      continue;
+    else if(GoodObject(d->snooper[n]) && IsPlayer(d->snooper[n])) {
+      if(dir == 1) /* player see's */
+        notify_format((dbref) d->snooper[n], T("%s%s<-%s %s"), object_header(d->snooper[n],d->player), ANSI_BLUE,
+           ANSI_NORMAL, snstr);
+      else /* player types */
+       notify_format((dbref) d->snooper[n], T("%s%s->%s %s%s"), object_header(d->snooper[n],d->player), 
+           ANSI_BLUE, ANSI_RED, snstr, ANSI_NORMAL);
+    }
+
+}
+
+char is_snooped(DESC *d) {
+  int n;
+
+  for(n = 0; n < MAX_SNOOPS; n++) 
+    if(IsPlayer(d->snooper[n]))
+      return 1;
+  return 0;
+}
+
+
+char set_snoop(dbref plyr, DESC *d) {
+  int n;
+  /* take first available spot */
+  for( n = 0; n < MAX_SNOOPS ; n++)
+    if(d->snooper[n] == -1)
+      break;
+    else if(d->snooper[n] == plyr)
+      return -2; /* they're already snooping */
+  if(n == MAX_SNOOPS-1) /* too many snoopers on player */
+    return -1;
+  d->snooper[n] = plyr;
+  return 1;
+}
+
+void clr_snoop(dbref plyr, DESC *d) {
+  int n;
+
+  for(n = 0; n < MAX_SNOOPS; n++)
+    if(d->snooper[n] == plyr)
+      d->snooper[n] = -1;
+}
+
+/* switch users */
+COMMAND(cmd_su) {
+  DESC *d, *match = NULL;
+  dbref target;
+  int num = 0, is_hidden;
+
+  /* Stage 1.  Make sure arg_left exists */
+  if(arg_left && *arg_left) {
+    if(!strcasecmp(cmd->name, "@SD")) { 
+      target = match_result(player, arg_left, TYPE_DIVISION, MAT_EVERYTHING);
+      if(!GoodObject(target) || Typeof(target) != TYPE_DIVISION) {
+       notify(player, "No such division.");
+       return;
+      }
+      /* Check to see if special conditions exist, where they can enter without a password */
+      if(controls(player, target)) {   /* condition 1: they control it */
+       /* trigger did_it on exiting division.. Possibly save their exiting values, for if they come back in? */
+       did_it(player, Division(player), "SDOUT", NULL, NULL,  NULL, "ASDOUT", Location(player));
+       add_to_div_exit_path(player, Division(player));
+       Division(player) = target;
+       /* triger did_it on incoming division.. to i guess set 'em for something.. *shrugs* */
+       did_it(player, Division(player), "SDIN", tprintf("You have switched into Division: %s", object_header(player, Division(player)))
+             , NULL,  NULL, "ASDIN", Location(player));
+       return;
+      }
+      /* get least idle desc */
+      DESC_ITER_CONN(d) 
+       if ((d->player == player) && (!match || (d->last_time > match->last_time))) 
+         match = d;
+      /* We're only entering using a password at this moment */
+       queue_newwrite(match, (unsigned char *) tprintf(T("Password: %c%c"),
+                                                       IAC, GOAHEAD), 13);
+       match->input_handler = password_handler;
+       match->pinfo.object = target;
+       match->pinfo.function = &pw_div_connect;
+       match->pinfo.lock = 0x40;
+    } else {
+      target = lookup_player(arg_left);
+      if(target == NOTHING) {
+       notify(player, "No such player.");
+       return;
+      }
+      do_log(LT_WIZ, player, target, "** @SU ATTEMPT **");
+      /* get least idle desc */
+      DESC_ITER_CONN(d) 
+       if ((d->player == player) && (!match || (d->last_time > match->last_time))) 
+         match = d;
+  /* Step 2.  Find if we can get in without a pass, if
+   * we need a pass. Throw them into password_handler() internal
+   * prompt
+   */
+      if(div_powover(player, target, "@SU")) {
+       do_log(LT_WIZ, player, target, "** @SU SUCCESS **");
+       /* Phase 3a. Put Guy in user */
+       add_to_exit_path(match, player);
+       announce_disconnect(player);
+       match->player = target;
+       match->mailp = find_exact_starting_point(target);
+       is_hidden = Can_Hide(target) && Dark(target);
+       DESC_ITER_CONN(d)
+         if(d->player == player) {
+           num++;
+           if(is_hidden)
+             d->hide = 1;
+         }
+
+       if(ModTime(target))
+         notify_format(target, T("%ld failed connections since last login."), ModTime(target));
+       announce_connect(target, 0, num);
+       check_last(target, match->addr, match->ip); /* set last, lastsite, give paycheck */
+       queue_eol(match);
+       if(command_check_byname(target, "@MAIL"))
+         check_mail(target, 0, 0);
+       set_player_folder(target, 0);
+       do_look_around(target);
+       if(Haven(target))
+         notify(player, T("Your HAVEN flag is set.  You cannot receive pages."));
+      } else {
+       /* Part 3b.  Put guy in password program */
+       queue_newwrite(match, (unsigned char *) tprintf(T("Password: %c%c"),
+                                                       IAC, GOAHEAD), 13);
+       match->input_handler = password_handler;
+       match->pinfo.object = target;
+       match->pinfo.function = &pw_player_connect;
+       match->pinfo.lock = 0x40;
+      }
+    }
+  } else {
+    if(SW_ISSET(sw, SWITCH_LOGOUT)) {
+      /* @sd/logout - check to see if there is any division @sd'ing history.. And backtrack us */
+      ATTR *divrcd;
+      char tbuf[BUFFER_LEN], *p_buf[BUFFER_LEN / 2], *tbp, sep[2];
+      int cnt;
+      dbref div_obj;
+
+      divrcd = atr_get(player, "XYXX_DIVRCD");
+      if(divrcd == NULL) {
+       notify(player, "You have not switched into any divisions.");
+       return;
+      }
+
+      cnt = list2arr(p_buf, BUFFER_LEN / 2, safe_atr_value(divrcd), ' ');
+      if(cnt < 1) {
+       notify(player, "You have not switched into any divisions.");
+       return;
+      }
+
+      /* Set them into cnt-1 if its good */
+      div_obj = parse_number(p_buf[cnt-1]);
+      if(GoodObject(div_obj) && IsDivision(div_obj)) {
+       /* Trigger ASDOUT */
+       did_it(player, Division(player), "SDOUT", NULL, NULL,  NULL, "ASDOUT", Location(player));
+       Division(player) = div_obj;
+       /* Trigger SDIN */
+       did_it(player, Division(player), "SDIN", tprintf("You have went back to your other division: %s", object_header(player, div_obj))
+                , NULL,  NULL, "ASDIN", Location(player));
+      }
+
+      /* now  chop off last one, and arr2list() */
+      if(cnt == 1) { /* clear the attribute */
+       atr_clr(player, "XYXX_DIVRCD", GOD);
+      } else {
+       memset(tbuf, '\0', BUFFER_LEN);
+       tbp = tbuf;
+       sep[0] = ' ';
+       sep[1] = '\0';
+       arr2list(p_buf, cnt-1, tbuf, &tbp, sep);
+       /* Add the attribute back */
+       (void) atr_add(player, "XYXX_DIVRCD", tbuf, GOD, NOTHING);
+      }
+    } else {
+      notify(player, "Must specify what player you wish to @su into.");
+    }
+  }
+}
+
+void add_to_exit_path(DESC *d, dbref player) {
+  SU_PATH *path_entry;
+
+  if(!d)
+    return;
+
+  path_entry = (SU_PATH *) mush_malloc(sizeof(SU_PATH), "SU_PATH_ENTRY");
+
+  path_entry->player = player;
+  if(d->su_exit_path)
+    path_entry->next = d->su_exit_path;
+  else
+    path_entry->next = NULL;
+  d->su_exit_path = path_entry;
+}
+
+/* If they're logged in.. Log 'em out through their su path */
+static int do_su_exit(DESC *d) {
+  DESC *c;
+  SU_PATH *path_entry, *mark_path;
+  int is_hidden, num = 0;
+
+  if(d->su_exit_path) {
+    path_entry = d->su_exit_path;
+    while(path_entry)
+      if(GoodObject(path_entry->player) && IsPlayer(path_entry->player))
+       break;
+      else { /* Guess the guy got nuked along the way..  free the spot */
+       mark_path = path_entry;
+       path_entry = path_entry->next;
+       mush_free(mark_path, "SU_PATH_ENTRY");
+      }
+    if(!path_entry)
+      return 0;
+    d->su_exit_path = path_entry;
+    /* Disconnect appearance */
+    announce_disconnect(d->player);
+    d->player = path_entry->player;
+    /* Clear path_entry spot */
+    d->su_exit_path = path_entry->next;
+    mush_free(path_entry, "SU_PATH_ENTRY");
+    d->mailp = find_exact_starting_point(d->player);
+    is_hidden = Can_Hide(d->player) && Dark(d->player);
+    DESC_ITER_CONN(c)
+      if(c->player == d->player) {
+       num++;
+       if(is_hidden)
+         c->hide = 1;
+      }
+    if(ModTime(d->player))
+      notify_format(d->player, T("%ld failed connections since last login."), ModTime(d->player));
+    announce_connect(d->player, 0, num);
+    check_last(d->player, d->addr, d->ip); /* set last, lastsite, give paycheck */
+    queue_eol(d);
+    if(command_check_byname(d->player, "@MAIL"))
+      check_mail(d->player, 0, 0);
+    set_player_folder(d->player, 0);
+    do_look_around(d->player);
+    if(Haven(d->player))
+      notify(d->player, T("Your HAVEN flag is set.  You cannot receive pages."));
+    return 1;
+  } else return 0;
+}
diff --git a/src/bufferq.c b/src/bufferq.c
new file mode 100644 (file)
index 0000000..ad66f78
--- /dev/null
@@ -0,0 +1,250 @@
+/**
+ * \file bufferq.c
+ *
+ * \brief Code for managing queues of buffers, a handy data structure.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#ifdef I_STDLIB
+#include <stdlib.h>
+#endif
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <time.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+
+#include "conf.h"
+#include "externs.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "bufferq.h"
+#include "mymalloc.h"
+#include "log.h"
+#include "confmagic.h"
+
+static void shift_bufferq(BUFFERQ * bq, int space_needed);
+
+/** Add data to a buffer queue.
+ * \param bq pointer to buffer queue.
+ * \param type caller-specific integer
+ * \param player caller-specific dbref
+ * \param msg caller-specific string to add.
+ */
+void
+add_to_bufferq(BUFFERQ * bq, int type, dbref player, const char *msg)
+{
+  int len = strlen(msg);
+  int room = len + 1 + BUFFERQLINEOVERHEAD;
+  if (!bq)
+    return;
+  if (room > bq->buffer_size)
+    return;
+  if ((bq->buffer_end > bq->buffer) &&
+      ((bq->buffer_size - (bq->buffer_end - bq->buffer)) < room))
+    shift_bufferq(bq, room);
+  memcpy(bq->buffer_end, &len, sizeof(len));
+  bq->buffer_end += sizeof(len);
+  memcpy(bq->buffer_end, &player, sizeof(player));
+  bq->buffer_end += sizeof(player);
+  memcpy(bq->buffer_end, &type, sizeof(type));
+  bq->buffer_end += sizeof(type);
+  memcpy(bq->buffer_end, &mudtime, sizeof(time_t));
+  bq->buffer_end += sizeof(time_t);
+  memcpy(bq->buffer_end, msg, len + 1);
+  bq->buffer_end += len + 1;
+  strcpy(bq->last_string, msg);
+  bq->last_type = type;
+  bq->num_buffered++;
+}
+
+
+static void
+shift_bufferq(BUFFERQ * bq, int space_needed)
+{
+  char *p = bq->buffer;
+  int size, jump;
+  int skipped = 0;
+
+  while ((space_needed > 0) && (p < bq->buffer_end)) {
+    /* First 4 bytes is the size of the first string, not including \0 */
+    memcpy(&size, p, sizeof(size));
+    /* Jump to the start of the next string */
+    jump = size + BUFFERQLINEOVERHEAD + 1;
+    p += jump;
+    space_needed -= jump;
+    skipped++;
+  }
+
+  if ((p != bq->buffer_end) && (space_needed > 0)) {
+    /* Not good. We couldn't get the space we needed even after we
+     * emptied the buffer. This should never happen, but if it does,
+     * we'll just log a fault and do nothing.
+     */
+    do_rawlog(LT_ERR, "Unable to get enough buffer queue space");
+    return;
+  }
+
+  /* Shift everything here and after up to the front
+   * At this point, p may be pointing at the very end of the buffer,
+   * in which case, we just move it to the front with no shifting.
+   */
+  if (p < bq->buffer_end)
+    memmove(bq->buffer, p, bq->buffer_end - p);
+  bq->buffer_end -= (p - bq->buffer);
+  bq->num_buffered -= skipped;
+}
+
+/** Allocate memory for a buffer queue to hold a given number of lines.
+ * \param bq pointer to buffer queue.
+ * \param lines lines to allocate for buffer queue.
+ * \retval address of allocated buffer queue.
+ */
+BUFFERQ *
+allocate_bufferq(int lines)
+{
+  BUFFERQ *bq;
+  int bytes = lines * (BUFFER_LEN + BUFFERQLINEOVERHEAD);
+  bq = mush_malloc(sizeof(BUFFERQ), "bufferq");
+  bq->buffer = mush_malloc(bytes, "bufferq.buffer");
+  *bq->buffer = '\0';
+  bq->buffer_end = bq->buffer;
+  bq->num_buffered = 0;
+  bq->buffer_size = bytes;
+  strcpy(bq->last_string, "");
+  bq->last_type = 0;
+  return bq;
+}
+
+/** Free memory of a buffer queue.
+ * \param bq pointer to buffer queue.
+ */
+void
+free_bufferq(BUFFERQ * bq)
+{
+  if (!bq)
+    return;
+  if (bq->buffer)
+    mush_free(bq->buffer, "bufferq.buffer");
+  mush_free(bq, "bufferq");
+}
+
+/** Reallocate a buffer queue (to change its size)
+ * \param bq pointer to buffer queue.
+ * \param lines new number of lines to store in buffer queue.
+ * \retval address of reallocated buffer queue.
+ */
+BUFFERQ *
+reallocate_bufferq(BUFFERQ * bq, int lines)
+{
+  char *newbuff;
+  ptrdiff_t bufflen;
+  int bytes = lines * (BUFFER_LEN + 2 * BUFFERQLINEOVERHEAD);
+  /* If we were accidentally called without a buffer, deal */
+  if (!bq) {
+    return allocate_bufferq(lines);
+  }
+  /* Are we not changing size? */
+  if (bq->buffer_size == bytes)
+    return bq;
+  if (bq->buffer_size > bytes) {
+    /* Shrinking the buffer */
+    if ((bq->buffer_end - bq->buffer) >= bytes)
+      shift_bufferq(bq, bq->buffer_end - bq->buffer - bytes);
+  }
+  bufflen = bq->buffer_end - bq->buffer;
+  newbuff = (char *) realloc(bq->buffer, bytes);
+  if (newbuff) {
+    bq->buffer = newbuff;
+    bq->buffer_end = bq->buffer + bufflen;
+    bq->buffer_size = bytes;
+  }
+  return bq;
+}
+
+
+
+/** Iterate through messages in a bufferq.
+ * This function returns the next message in a bufferq, given
+ * a pointer to the start of entry (which is modified).
+ * It returns NULL when there are no more messages to get.
+ * Call this in a loop to get all messages; do not intersperse
+ * with calls to insert messages!
+ * \param bq pointer to buffer queue structure.
+ * \param p address of pointer to track start of next entry. If that's
+ *   the address of a null pointer, reset to beginning.
+ * \param player address of pointer to return player data in
+ * \param type address of pointer to return type value in.
+ * \param timestamp address of pointer to return timestamp in.
+ * \return next message text or NULL if no more.
+ */
+char *
+iter_bufferq(BUFFERQ * bq, char **p, dbref *player, int *type,
+            time_t * timestamp)
+{
+  static char tbuf1[BUFFER_LEN];
+  int size;
+
+  if (!p || !bq || !bq->buffer || (bq->buffer == bq->buffer_end))
+    return NULL;
+
+  if (*p == bq->buffer_end)
+    return NULL;
+
+  if (!*p)
+    *p = bq->buffer;           /* Reset to beginning */
+
+  memcpy(&size, *p, sizeof(size));
+  *p += sizeof(size);
+  memcpy(player, *p, sizeof(dbref));
+  *p += sizeof(dbref);
+  memcpy(type, *p, sizeof(int));
+  *p += sizeof(int);
+  memcpy(timestamp, *p, sizeof(time_t));
+  *p += sizeof(time_t);
+  memcpy(tbuf1, *p, size + 1);
+  *p += size + 1;
+  return tbuf1;
+}
+
+/** Size of bufferq buffer in lines.
+ * \param bq pointer to buffer queue.
+ * \return size of buffer queue in lines
+ */
+int
+bufferq_lines(BUFFERQ * bq)
+{
+  if (bq && bq->buffer)
+    return bq->buffer_size / (BUFFER_LEN + BUFFERQLINEOVERHEAD);
+  else
+    return 0;
+}
+
+/** Is a buffer queue empty?
+ * \param bq pointer to buffer queue.
+ * \retval 1 the buffer queue is empty (has no messages).
+ * \retval 0 the buffer queue is not empty (has messages).
+ */
+int
+isempty_bufferq(BUFFERQ * bq)
+{
+  if (!bq || !bq->buffer)
+    return 1;
+  if (bq->buffer == bq->buffer_end)
+    return 1;
+  return 0;
+}
diff --git a/src/chunk.c b/src/chunk.c
new file mode 100644 (file)
index 0000000..32aefc7
--- /dev/null
@@ -0,0 +1,2539 @@
+/**
+ * \file chunk.c
+ *
+ * \brief Chunk memory management system
+ *
+ * <h3>Synopsis:</h3>
+ * The chunk memory management system has three goals: to reduce overall
+ * memory consumption, to improve locality of reference, and to allow
+ * less-used sections of memory to be paged out to disk.  These three
+ * goals are accomplished by implementing an allocation management layer
+ * on top of malloc(), with significantly reduced overhead and the ability
+ * to rearrange allocations to actively control fragmentation and increase
+ * locality.
+ * 
+ * 
+ * <h3>Basic operation:</h3>
+ * The managed memory pool is divided into regions of approximately 64KB.
+ * These regions contain variable-size chunks representing allocated and
+ * available (free) memory.  No individual allocation may be larger than
+ * will fit in a single region, and no allocation may be smaller than one
+ * byte.  Each chunk has between two and four bytes of overhead (indicating
+ * the used/free status, the size of the chunk, and the number of
+ * dereferences for the chunk), and each region has additional overhead
+ * of about 42 bytes.
+ * 
+ * Allocations are made with the chunk_create() call, which is given
+ * the size of the data, the data value to be stored, and an initial
+ * dereference count to be assigned to the chunk.  Once created, the
+ * value of a chunk cannot be changed; the storage is immutable.
+ * chunk_create() returns an integral reference value that can be
+ * used to retrieve or free the allocation.
+ * 
+ * Allocations are accessed with the chunk_fetch(), chunk_len(), and
+ * chunk_derefs() calls.  Each of these if given a reference (as
+ * returned by chunk_create()), and chunk_fetch() is additionally
+ * given a buffer and length to fill with the allocated value.  Both
+ * chunk_fetch() and chunk_len() increment a chunk's dereference
+ * count (up to the maximum of 255), which is used in migration to
+ * improve locality.
+ * 
+ * Allocations are freed with the chunk_delete() call, which also
+ * requires a reference as input.
+ * 
+ * Finally, allocations are allowed to rearrange themselves with the
+ * chunk_migration() call.  chunk_migration() takes an array of
+ * pointers to chunk references as input, and examines each of the
+ * indicated chunks to see which need to be moved to improve the
+ * distribution of allocations.  If any allocations are moved, then
+ * the references to the moved allocations are updated in place
+ * (hence the array of pointers to references, instead of just an
+ * array of references).  Migration may be done incrementally by
+ * submitting only a portion of the allocations with each call to
+ * chunk_migration(); however, _all_ allocations made with chunk_create()
+ * must eventually be submitted for migration in order to maintain the
+ * memory pool in a non-fragmented state.
+ * 
+ * 
+ * <h3>Migration:</h3>
+ * Under normal conditions, extended use of this chunk allocation system
+ * would lead to a significantly fragmented datastore, unless there was
+ * some means to defragment the storage arena.  In the long run, this could
+ * be very bad, leading to quite a mess.  Calling chunk_migration() gives
+ * the allocator permission to move allocations around both to defragment
+ * the arena and to improve locality of reference (by making sure that
+ * all the infrequently used chunks are segregated from the chunks in
+ * active use).  Of course, moving all the allocated chunks at once would
+ * be a slow and painful process.  Instead, migration may be done
+ * incrementally, giving permission to move a small number of chunks
+ * at any one time, and spreading out the cost of defragmenting the
+ * data store.
+ * 
+ * Just because you give permission to move a chunk doesn't mean that it
+ * will be moved.  The chunk may be perfectly happy where it is, with
+ * no need to move it elsewhere.  Chunks are only moved when their
+ * personal happiness would be improved by a move.  In general, maximizing
+ * the happiness of individual chunks will improve the happiness of the
+ * whole.
+ * 
+ * There are two things that factor into a chunk's happiness.
+ * The things that make a chunk unhappy are:
+ * <ul>
+ * <li> Having a dereference count different from the region average.
+ *      The greater the difference, the more unhappy the chunk is.
+ * <li> Being in a sparsely populated region.  The fewer chunks in a
+ *      region, the more unhappy the chunks in it.
+ * </ul>
+ * Neither of these factors are absolute; both of them have different
+ * weights that add into a general unhappiness for the chunk.  The lower
+ * the unhappiness, the better.
+ * 
+ * Over time and usage, the dereference counts for chunks will increase
+ * and eventually reach a maximum value of 255.  (The count is limited
+ * by the fact that it's stored in a single byte for each chunk.)  If
+ * this is left unchecked, eventually all chunks would have a dereference
+ * count of 255, and the counts would be useless for improving locality.
+ * To counteract this, when the average dereference count for a certain
+ * number of regions exceeds 128, the 'migration period' is incremented
+ * and all chunk dereference counts are halved.  The critical number of
+ * regions is determined based on the cache size and the total number of
+ * regions.  If you're not using forking dumps, then period change should
+ * be controlled primarily by the frequency of database dumps (which end
+ * up incrementing the dereference count on all chunks, and thus all
+ * regions).  Given a dump frequency of once per hour (the default), there
+ * should be a period change about every 2.6 days.
+ * 
+ * 
+ * <h3>Statistics:</h3>
+ * The chunk memory management system keeps several statistics about
+ * the allocation pool, both to maintain good operation through active
+ * encouragement of locality, and to satisfy the curiosity of people
+ * using the system (and its designer ;-)).  These statistics are
+ * reported (in PennMUSH) through the use of the @stats command,
+ * with /chunks switch.
+ * 
+ * @stats/chunks generates output similar to this:
+ * \verbatim
+ * Chunks:         99407 allocated (   8372875 bytes,     223808 ( 2%) overhead)
+ *                   74413 short     (   1530973 bytes,     148826 ( 9%) overhead)
+ *                   24994 medium    (   6841902 bytes,      74982 ( 1%) overhead)
+ *                       0 long      (         0 bytes,          0 ( 0%) overhead)
+ *                   128 free      (   1319349 bytes,      23058 ( 1%) fragmented)
+ * Regions:          147 total,       16 cached
+ * Paging:        158686 out,     158554 in
+ * Storage:      9628500 total (86% saturation)
+ *  
+ * Period:             1 (   5791834 accesses so far,       1085 chunks at max)
+ * Migration:     245543 moves this period
+ *                  145536 slide
+ *                      45 away
+ *                   30719 fill exact
+ *                   69243 fill inexact
+ * \endverbatim
+ * First, the number of allocated chunks is given, along with their
+ * total size and overhead.  Then, the allocated chunks are broken
+ * up by size-range; short chunks (2 to 63 bytes) with two bytes of
+ * overhead each, medium chunks (64 to 8191 bytes) with three bytes
+ * of overhead each, and long chunks (8192 to ~64K bytes) with four
+ * bytes of overhead each.  Rounding out the individual chunk statistics
+ * is the number of free chunks, their total size, and the amount of
+ * fragmented free space (free space not in the largest free chunk for
+ * its region is considered fragmented).
+ * 
+ * Next comes statistics on regions: the number of regions in use
+ * and the number held in the memory cache.  All regions not in the
+ * cache are paged out to disk.  Paging statistics follow, listing
+ * the number of times a region has been moved out of or into 
+ * memory cache.  After that, the total amount of storage (in memory
+ * or on disk) used is given, along with the saturation rate (where
+ * saturation is indicated by what fraction of the used space is
+ * actually allocated in chunks).
+ * 
+ * Finally comes statistics on migration and the migration period.
+ * The period number is listed, along with the total number of
+ * dereferences in the period and how many chunks have the maximum
+ * dereference count of 255.  Then the amount of migration movement
+ * is listed, both in total and broken up by category.  Slides occur
+ * when an allocation is shifted to the other side of a neighboring
+ * free space.  Away moves are made when an allocation is extremely
+ * unhappy where it is, and is pushed out to somewhere else.  Fills
+ * are when an allocation is moved in order to fill in a free space;
+ * the space can be either exactly filled by the move, or inexactly
+ * filled (leaving some remaining free space).
+ * 
+ * 
+ * <h3>Histograms:</h3>
+ * The chunk memory management system can also display a few
+ * histograms about itself.  These histograms are reported (in PennMUSH)
+ * through the use of the @stats command, with the /regions, /freespace,
+ * or /paging switches.
+ * 
+ * All of @stats/regions, @stats/freespace, and @stas/paging produce
+ * histograms vs. region average dereference count.  The histograms
+ * use buckets four counts wide, so all regions from 0-3 will be in
+ * the first bucket, 4-7 in the second, etc., up to 252-255 in the
+ * last bucket.  If the heights of the buckets are significantly
+ * different, then the highest spikes will be allowed to extend off
+ * the top of the histogram (with their real values labeled in
+ * parenthesis next to them).
+ * 
+ * @stats/regions is a histogram of how many regions at each count
+ * currently exist.  In a healthy game, there should be a large spike
+ * at some dereference count between 64 and 128 (representing the
+ * largely unused portion of the database), a lesser spike at 255
+ * (representing the portion of the database that's used very frequently),
+ * and a smattering of regions at other counts, with either new areas
+ * of the database (below the large spike) or semi-frequently used
+ * areas (above the large spike).  New migration periods occur when
+ * the large spike would pass 128, at which point everything is halved
+ * and the spike is pushed back down to 64.
+ * 
+ * @stats/freespace is a histogram of how much free space exists in
+ * regions at each dereference count.  This histogram is included
+ * to aid in diagnosis of the cause for dropping saturation rates.
+ * 
+ * @stats/paging is a histogram of the number of regions being paged
+ * in or out at each dereference count.  As of this writing, a very
+ * unhealthy behaviour is observed, wherein the histogram shows a
+ * trapeziod between 64 and 128, drowning out most of the rest of the
+ * chart.  This indicates that as time goes on, the attributes
+ * associated with a single low-use object are getting scattered
+ * randomly throughout all the low-use regions, and thus when dumps
+ * occur (with their linear enumeration of all attributes on objects)
+ * the low-use regions thrash in and out of cache.  This can be very
+ * detrimental to dump performance.  Something will have to be done
+ * to fix this tendency of migration.  Healthy behaviour will make
+ * some other pattern in the paging histogram which has not yet been
+ * determined.
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include "conf.h"
+
+#include <limits.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <sys/types.h>
+#ifdef WIN32
+#include <io.h>
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
+
+#include "externs.h"
+#include "boolexp.h"
+#include "command.h"
+#include "dbdefs.h"
+#include "intrface.h"
+#include "log.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#pragma warning( disable : 4761)       /* disable warning re conversion */
+#endif
+
+/* A whole bunch of debugging #defines. */
+/** Basic debugging stuff - are assertions checked? */
+#undef CHUNK_DEBUG
+/** Paranoid people check for region validity after every operation
+ * that modifies a region. */
+#undef CHUNK_PARANOID
+/** Log all moves and slides during migration. */
+#undef DEBUG_CHUNK_MIGRATE
+/** Log creation of regions. */
+#undef DEBUG_CHUNK_REGION_CREATE
+/** Log paging of regions. */
+#undef DEBUG_CHUNK_PAGING
+/** Log all mallocs. */
+#undef DEBUG_CHUNK_MALLOC
+
+/** For debugging, we keep a rolling log of debug messages.
+ * These get dumped to disk if we're about to panic.
+ */
+#define ROLLING_LOG_SIZE 200
+#define ROLLING_LOG_ENTRY_LEN 1024
+
+/* debug... */
+#ifdef CHUNK_DEBUG
+#define ASSERT(x) assert(x)
+#else                          /* CHUNK_DEBUG */
+static int ignore;     /**< Used to shut up compiler warnings when not asserting */
+#define ASSERT(x) ignore++
+#endif                         /* CHUNK_DEBUG */
+
+/*
+ * Sizes, limits, etc.
+ */
+/** Region size, including header.
+ * This is a little less than 64K to allow for malloc overhead without
+ * spilling into next page */
+#define REGION_SIZE 65500
+
+/** Region capacity.
+ * This is the size minus the fixed region overhead.
+ */
+#define REGION_CAPACITY (REGION_SIZE - FIRST_CHUNK_OFFSET_IN_REGION)
+
+/** Maximum chunk length.
+ * This is fairly arbitrary, but must be less than
+ * REGION_CAPACITY (it must fit in a region).
+ */
+#define MAX_CHUNK_LEN (16384-1)
+
+/** Number of oddballs tracked in regions.
+ * This is used to figure out when we should pull regions in because
+ * we have an opportunity to migrate chunks that don't match.
+ * Relatively arbitrary; too low means you don't move things out
+ * enough, but boosting it too high wastes memory.
+ */
+#define NUM_ODDBALLS 10
+
+/** Minimum disagreement to be an oddball.
+ * This is used to figure out when we should pull regions in because
+ * we have an opportunity to migrate chunks that don't match.
+ * Relatively arbitrary; too low means you don't move things out
+ * enough, but boosting it too high wastes migration time.
+ */
+#define ODDBALL_THRESHOLD 8
+
+/*
+ * FIXME: pulling config variables out of my left ear. Fix later.
+ */
+/** How much space is initially allocated for the in-memory region array? */
+#define FIXME_INIT_REGION_LEN 20
+/** How much does the region array grow by each time it has to grow? */
+#define FIXME_REGION_ARRAY_INCREMENT 10
+
+/** Limit for when being a nearly-empty region counts against being
+ * a good region.  This is exponential: an empty region gets a penalty
+ * of 1 << LONLINESS_LIMIT.  A near-empty region gets a penalty of
+ * 1 << (LONLINESS_LIMIT - used_count).
+ *
+ * Rationale: we don't want to reuse empty regions (or make new regions)
+ * for trivialities.
+ */
+#define LONLINESS_LIMIT 5
+
+/** Free space limit for when we consider making new regions.
+ * The total free space must be less than this percent of capacity.
+ *
+ * Rationale: we don't want to waste memory with lots of extra regions.
+ */
+#define FREE_PERCENT_LIMIT 2
+
+/** Bias for allocating chunks in a region that's already in memory.
+ * Actually, this is a bias against allocating in swapped-out regions,
+ * but that's a nit...
+ *
+ * Rationale: reduce the amount of paging during migration.
+ */
+#define IN_MEMORY_BIAS 4
+
+/*
+ *  Structures and Accessor Macros
+ */
+/*
+ * What a chunk_reference_t looks like from the inside
+ */
+/** Get the region from a chunk_reference_t. */
+#define ChunkReferenceToRegion(ref) ((ref) >> 16)
+/** Get the offset from a chunk_reference_t. */
+#define ChunkReferenceToOffset(ref) ((ref) & 0xFFFF)
+/** Make a chunk_reference_t from a region and offset. */
+#define ChunkReference(region, offset) \
+  ((chunk_reference_t)(((region) << 16) | (offset)))
+
+/** Sentinel value used to mark unused cache regions. */
+#define INVALID_REGION_ID 0xffff
+
+/**
+ * \verbatim
+ * The chunk headers look like this:
+ *
+ * Short:
+ * byte 0     byte 1
+ *  76543210   76543210
+ * +--------+ +--------+
+ * |ft len  | | deref  |
+ * +--------+ +--------+
+ *  ||\__6_/   \___8__/
+ *  ||   |         `----- deref count (decays)
+ *  ||   `--------------- length of data
+ *  |`------------------- tag bit (0 for short)
+ *  `-------------------- free flag (0 for allocated, 1 for free)
+ *
+ * Medium:
+ * byte 0     byte 1     byte 2
+ *  76543210   76543210   76543210
+ * +--------+ +--------+ +--------+
+ * |ftg lenM| | deref  | |  lenL  |
+ * +--------+ +--------+ +--------+
+ *  |||\_5_/   \___8__/   \___8__/
+ *  |||  |         |          `----- length, least significant
+ *  |||  |         `---------------- deref count (decays)
+ *  |||  `-------------------------- length, most significant
+ *  ||`----------------------------- tag bit (0 for medium)
+ *  |`------------------------------ tag bit (1 for medium/long)
+ *  `------------------------------- free flag
+ *
+ * Long:
+ * byte 0     byte 1     byte 2     byte 3
+ *  76543210   76543210   76543210   76543210
+ * +--------+ +--------+ +--------+ +--------+
+ * |ftg     | | deref  | |  lenM  | |  lenL  |
+ * +--------+ +--------+ +--------+ +--------+
+ *  |||        \___8__/   \_______16________/
+ *  |||            |               `----------- length of data
+ *  |||            `--------------------------- deref count (decays)
+ *  ||`---------------------------------------- tag bit (1 for long)
+ *  |`----------------------------------------- tag bit (1 for medium/long)
+ *  `------------------------------------------ free flag
+ *
+ *
+ * Note in particular that the dereference count is always in the second
+ * byte of a chunk, to simplify the access logic.
+ * \endverbatim
+ */
+
+/*
+ * Fields in chunk headers
+ */
+#define CHUNK_FREE_MASK   0x80
+#define CHUNK_FREE        0x80
+#define CHUNK_USED        0x00
+
+#define CHUNK_TAG1_MASK   0x40
+#define CHUNK_TAG1_SHORT  0x00
+#define CHUNK_TAG1_MEDIUM 0x40
+#define CHUNK_TAG1_LONG   0x40
+#define CHUNK_TAG1_OFFSET 0
+
+#define CHUNK_TAG2_MASK   0x20
+#define CHUNK_TAG2_MEDIUM 0x00
+#define CHUNK_TAG2_LONG   0x20
+#define CHUNK_TAG2_OFFSET 0
+
+#define CHUNK_SHORT_LEN_MASK 0x3F
+#define CHUNK_SHORT_LEN_OFFSET 0
+
+#define CHUNK_MEDIUM_LEN_MSB_MASK 0x1F
+#define CHUNK_MEDIUM_LEN_LSB_MASK 0xFF
+#define CHUNK_MEDIUM_LEN_MSB_OFFSET 0
+#define CHUNK_MEDIUM_LEN_LSB_OFFSET 2
+
+#define CHUNK_LONG_LEN_MSB_MASK 0xFF
+#define CHUNK_LONG_LEN_LSB_MASK 0xFF
+#define CHUNK_LONG_LEN_MSB_OFFSET 2
+#define CHUNK_LONG_LEN_LSB_OFFSET 3
+
+#define CHUNK_DEREF_OFFSET 1
+
+#define CHUNK_DEREF_MAX 0xFF
+
+#define CHUNK_AGED_MASK   0x10
+#define CHUNK_AGED_OFFSET 0
+
+#define CHUNK_SHORT_DATA_OFFSET 2
+#define CHUNK_MEDIUM_DATA_OFFSET 3
+#define CHUNK_LONG_DATA_OFFSET 4
+
+#define MIN_CHUNK_LEN 1
+#define MIN_REMNANT_LEN (CHUNK_SHORT_DATA_OFFSET + MIN_CHUNK_LEN)
+
+#define MAX_SHORT_CHUNK_LEN CHUNK_SHORT_LEN_MASK
+#define MAX_MEDIUM_CHUNK_LEN \
+        ((CHUNK_MEDIUM_LEN_MSB_MASK << 8) | CHUNK_MEDIUM_LEN_LSB_MASK)
+#define MAX_LONG_CHUNK_LEN \
+        (REGION_CAPACITY - CHUNK_LONG_DATA_OFFSET)
+
+#define LenToFullLen(len) \
+  ((len) + \
+   (((len) > MAX_SHORT_CHUNK_LEN) \
+    ? ((len) > MAX_MEDIUM_CHUNK_LEN) \
+      ? CHUNK_LONG_DATA_OFFSET \
+      : CHUNK_MEDIUM_DATA_OFFSET \
+    : CHUNK_SHORT_DATA_OFFSET))
+
+#define ChunkPointer(region, offset) \
+  (((unsigned char *)(regions[(region)].in_memory)) + (offset))
+#define ChunkReferenceToPointer(ref) \
+  ChunkPointer(ChunkReferenceToRegion((ref)), ChunkReferenceToOffset((ref)))
+
+/*
+ * Macros for probing and manipulating chunk headers
+ */
+#define CPLenShort(cptr) \
+  ((cptr)[CHUNK_SHORT_LEN_OFFSET] & CHUNK_SHORT_LEN_MASK)
+#define CPLenMedium(cptr) \
+  ((((cptr)[CHUNK_MEDIUM_LEN_MSB_OFFSET] & CHUNK_MEDIUM_LEN_MSB_MASK) << 8) + \
+   ((cptr)[CHUNK_MEDIUM_LEN_LSB_OFFSET] & CHUNK_MEDIUM_LEN_LSB_MASK))
+#define CPLenLong(cptr) \
+  ((((cptr)[CHUNK_LONG_LEN_MSB_OFFSET] & CHUNK_LONG_LEN_MSB_MASK) << 8) + \
+   ((cptr)[CHUNK_LONG_LEN_LSB_OFFSET] & CHUNK_LONG_LEN_LSB_MASK))
+#define CPLen(cptr) \
+  ((*(cptr) & CHUNK_TAG1_MASK) \
+   ? (*(cptr) & CHUNK_TAG2_MASK) \
+     ? CPLenLong((cptr)) \
+     : CPLenMedium((cptr)) \
+   : CPLenShort((cptr)))
+#define ChunkLen(region, offset) \
+  CPLen(ChunkPointer((region), (offset)))
+#define CPFullLen(cptr) \
+  ((*(cptr) & CHUNK_TAG1_MASK) \
+   ? (*(cptr) & CHUNK_TAG2_MASK) \
+     ? (CPLenLong((cptr)) + CHUNK_LONG_DATA_OFFSET) \
+     : (CPLenMedium((cptr)) + CHUNK_MEDIUM_DATA_OFFSET) \
+   : (CPLenShort((cptr)) + CHUNK_SHORT_DATA_OFFSET))
+#define ChunkFullLen(region, offset) \
+  CPFullLen(ChunkPointer((region), (offset)))
+
+#define ChunkIsFree(region, offset) \
+  ((*ChunkPointer((region), (offset)) & CHUNK_FREE_MASK) == CHUNK_FREE)
+#define ChunkIsShort(region, offset) \
+  ((*ChunkPointer((region), (offset)) & CHUNK_TAG1_MASK) == CHUNK_TAG1_SHORT)
+#define ChunkIsMedium(region, offset) \
+  ((*ChunkPointer((region), (offset)) & (CHUNK_TAG1_MASK | CHUNK_TAG2_MASK)) \
+   == (CHUNK_TAG1_MEDIUM | CHUNK_TAG2_MEDIUM))
+#define ChunkIsLong(region, offset) \
+  ((*ChunkPointer((region), (offset)) & (CHUNK_TAG1_MASK | CHUNK_TAG2_MASK)) \
+   == (CHUNK_TAG1_LONG | CHUNK_TAG2_LONG))
+
+#define ChunkDerefs(region, offset) \
+  (ChunkPointer((region), (offset))[CHUNK_DEREF_OFFSET])
+
+#define CPDataPtr(cptr) \
+  ((*(cptr) & CHUNK_TAG1_MASK) \
+   ? (*(cptr) & CHUNK_TAG2_MASK) \
+     ? (cptr) + CHUNK_LONG_DATA_OFFSET \
+     : (cptr) + CHUNK_MEDIUM_DATA_OFFSET \
+   : (cptr) + CHUNK_SHORT_DATA_OFFSET)
+#define ChunkDataPtr(region, offset) \
+  (CPDataPtr(ChunkPointer(region, offset)))
+
+#define ChunkNextFree(region, offset) \
+  ((ChunkDataPtr(region, offset)[0] << 8) + ChunkDerefs(region, offset))
+
+/* 0 for no, 1 for yes with room, 2 for exact */
+#define FitsInSpace(size, capacity) \
+  (((size) == (capacity)) ? 2 : ((size) <= (capacity) - MIN_REMNANT_LEN))
+
+/** Region info that gets paged out with its region.
+ * This is at the start of the region;
+ * the rest of the 64K bytes of the region contain chunks.
+ */
+typedef struct region_header {
+  u_int_16 region_id;          /**< will be INVALID_REGION_ID if not in use */
+  u_int_16 first_free;         /**< offset of 1st free chunk */
+  struct region_header *prev;  /**< linked list prev for LRU cache */
+  struct region_header *next;  /**< linked list next for LRU cache */
+} RegionHeader;
+
+#define FIRST_CHUNK_OFFSET_IN_REGION sizeof(RegionHeader)
+
+/** In-memory (never paged) region info.  */
+typedef struct region {
+  u_int_16 used_count;         /**< number of used chunks */
+  u_int_16 free_count;         /**< number of free chunks */
+  u_int_16 free_bytes;         /**< number of free bytes (with headers) */
+  u_int_16 largest_free_chunk; /**< largest single free chunk */
+  unsigned int total_derefs;   /**< total of all used chunk derefs */
+  unsigned int period_last_touched;    /**< "this" period, for deref counts;
+                                           we don't page in regions to update
+                                           counts on period change! */
+  RegionHeader *in_memory;     /**< cache entry; NULL if paged out */
+  u_int_16 oddballs[NUM_ODDBALLS];     /**< chunk offsets with odd derefs */
+} Region;
+
+#define RegionDerefs(region) \
+  (regions[(region)].used_count \
+   ? (regions[(region)].total_derefs >> \
+      (curr_period - regions[(region)].period_last_touched)) / \
+     regions[(region)].used_count \
+   : 0)
+#define RegionDerefsWithChunk(region, derefs) \
+   (((regions[(region)].total_derefs >> \
+     (curr_period - regions[(region)].period_last_touched)) + derefs) / \
+    (regions[(region)].used_count + 1))
+
+/*
+ *  Globals
+ */
+
+/** Swap File */
+#ifdef WIN32
+typedef HANDLE fd_type;
+static HANDLE swap_fd;
+static HANDLE swap_fd_child = INVALID_HANDLE_VALUE;
+#else
+typedef int fd_type;
+static int swap_fd;
+static int swap_fd_child = -1;
+#endif
+static char child_filename[300];
+
+/** Deref scale control.
+ * When the deref counts get too big, the current period is incremented
+ * and all derefs are divided by 2. */
+static u_int_32 curr_period;
+
+/*
+ * Info about all regions
+ */
+static u_int_32 region_count;  /**< regions in use */
+static u_int_32 region_array_len;      /**< length of regions array */
+static Region *regions;                /**< regions array, realloced as (rarely) needed */
+
+/*
+ * regions presently in memory
+ */
+static u_int_32 cached_region_count;   /**< number of regions in cache */
+static RegionHeader *cache_head;       /**< most recently used region */
+static RegionHeader *cache_tail;       /**< least recently used region */
+
+/*
+ * statistics
+ */
+static int stat_used_short_count;      /**< How many short chunks? */
+static int stat_used_short_bytes;      /**< How much space in short chunks? */
+static int stat_used_medium_count;     /**< How many medium chunks? */
+static int stat_used_medium_bytes;     /**< How much space in medium chunks? */
+static int stat_used_long_count;       /**< How many long chunks? */
+static int stat_used_long_bytes;       /**< How much space in long chunks? */
+static int stat_deref_count;           /**< Dereferences this period */
+static int stat_deref_maxxed;          /**< Number of chunks with max derefs */
+/** histogram for average derefs of regions being paged in/out */
+static int stat_paging_histogram[CHUNK_DEREF_MAX + 1];
+static int stat_page_out;              /**< Number of page-outs */
+static int stat_page_in;               /**< Number of page-ins */
+static int stat_migrate_slide;         /**< Number of slide migrations */
+static int stat_migrate_move;          /**< Number of move migrations */
+static int stat_migrate_away;          /**< Number of chunk evictions */
+static int stat_create;                        /**< Number of chunk creations */
+static int stat_delete;                        /**< Number of chunk deletions */
+
+
+
+/*
+ * migration globals that are used for holding relevant data...
+ */
+static int m_count;            /**< The used length for the arrays. */
+static chunk_reference_t **m_references; /**< The passed-in references array. */
+
+#ifdef CHUNK_PARANOID
+/** Log of recent actions for debug purposes */
+static char rolling_log[ROLLING_LOG_SIZE][ROLLING_LOG_ENTRY_LEN];
+static int rolling_pos;
+static int noisy_log = 0;
+#endif
+
+/*
+ * Forward decls
+ */
+static void find_oddballs(u_int_16 region);
+
+/*
+ * Debug routines
+ */
+/** Add a message to the rolling log. */
+static void
+debug_log(char const *format, ...)
+{
+#ifdef CHUNK_PARANOID
+  va_list args;
+
+  va_start(args, format);
+  vsprintf(rolling_log[rolling_pos], format, args);
+  va_end(args);
+
+  rolling_log[rolling_pos][ROLLING_LOG_ENTRY_LEN - 1] = '\0';
+  if (noisy_log) {
+    fprintf(tracelog_fp, "%s\n", rolling_log[rolling_pos]);
+    fflush(tracelog_fp);
+  }
+  rolling_pos = (rolling_pos + 1) % ROLLING_LOG_SIZE;
+#else
+  if (format)
+    return;                    /* shut up the compiler warning */
+#endif
+}
+
+#ifdef CHUNK_PARANOID
+/** Dump the rolling log. */
+static void
+dump_debug_log(FILE * fp)
+{
+  int j;
+  fputs("Recent chunk activity:\n", fp);
+  j = rolling_pos;
+  do {
+    if (rolling_log[j][0]) {
+      fputs(rolling_log[j], fp);
+      fputc('\n', fp);
+      rolling_log[j][0] = '\0';
+    }
+    j = (j + 1) % ROLLING_LOG_SIZE;
+  } while (j != rolling_pos);
+  fputs("End of recent chunk activity.\n", fp);
+  fflush(fp);
+}
+
+/** Test if a chunk is migratable. */
+static int
+migratable(u_int_16 region, u_int_16 offset)
+{
+  chunk_reference_t ref = ChunkReference(region, offset);
+  int j;
+
+  for (j = 0; j < m_count; j++)
+    if (m_references[j][0] == ref)
+      return 1;
+  return 0;
+}
+
+/** Give a detailed map of a region.
+ * Lists pertinent region information, and all the chunks in the region.
+ * Does not print the contents of the chunks (which would probably be
+ * unreadable, anyway).
+ * \param region the region to display.
+ * \param fp the FILE* to output to.
+ */
+static void
+debug_dump_region(u_int_16 region, FILE * fp)
+{
+  Region *rp = regions + region;
+  RegionHeader *rhp;
+  u_int_16 offset, count;
+
+  ASSERT(region < region_count);
+  rhp = rp->in_memory;
+
+  fprintf(fp, "region: id:%04x period:%-8x deref:%-8x (%-2x per chunk)\n",
+         region, rp->period_last_touched, rp->total_derefs,
+         RegionDerefs(region));
+  fprintf(fp, "        #used:%-4x #free:%-4x fbytes:%-4x hole:%-4x ",
+         rp->used_count, rp->free_count, rp->free_bytes,
+         rp->largest_free_chunk);
+  if (rhp)
+    fprintf(fp, "first:%-4x h_id:%-4x\n", rhp->first_free, rhp->region_id);
+  else
+    fprintf(fp, "PAGED\n");
+  fflush(fp);
+
+  if (rhp) {
+    for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+        offset < REGION_SIZE; offset += ChunkFullLen(region, offset)) {
+      fprintf(fp, "chunk:%c%4s %-6s off:%04x full:%04x ",
+             migratable(region, offset) ? '*' : ' ',
+             ChunkIsFree(region, offset) ? "FREE" : "",
+             ChunkIsShort(region, offset) ? "SHORT" :
+             (ChunkIsMedium(region, offset) ? "MEDIUM" : "LONG"),
+             offset, ChunkFullLen(region, offset));
+      if (ChunkIsFree(region, offset)) {
+       fprintf(fp, "next:%04x\n", ChunkNextFree(region, offset));
+      } else {
+       fprintf(fp, "doff:%04x len:%04x ",
+               ChunkDataPtr(region, offset) - (unsigned char *) rhp,
+               ChunkLen(region, offset));
+       count = ChunkDerefs(region, offset);
+       if (count == 0xFF) {
+         fprintf(fp, "deref:many\n");
+       } else {
+         fprintf(fp, "deref:%04x\n", count);
+       }
+      }
+    }
+  }
+}
+
+/** Make sure a chunk is real.
+ * Detect bogus chunk references handed to the system.
+ * \param region the region to verify.
+ * \param offset the offset to verify.
+ */
+static void
+verify_used_chunk(u_int_16 region, u_int_16 offset)
+{
+  u_int_16 pos;
+
+  ASSERT(region < region_count);
+
+  for (pos = FIRST_CHUNK_OFFSET_IN_REGION;
+       pos < REGION_SIZE; pos += ChunkFullLen(region, pos)) {
+    if (pos == offset) {
+      if (ChunkIsFree(region, pos))
+       mush_panic("Invalid reference to free chunk as used");
+      return;
+    }
+  }
+  mush_panic("Invalid reference to non-chunk as used");
+}
+
+/** Verify that a region is sane.
+ * Do a throrough consistency check on a region, verifying all the region
+ * totals, making sure the counts are consistent, and that all the space
+ * in the region is accounted for.
+ * \param region the region to verify.
+ * \return true if the region is valid.
+ */
+static int
+region_is_valid(u_int_16 region)
+{
+  int result;
+  Region *rp;
+  RegionHeader *rhp;
+  int used_count;
+  int free_count;
+  int free_bytes;
+  int largest_free;
+  unsigned int total_derefs;
+  int len;
+  int was_free;
+  int dump;
+  u_int_16 next_free;
+  u_int_16 offset;
+
+  if (region >= region_count) {
+    do_rawlog(LT_ERR, "region 0x%04x is not valid: region_count is 0x%04x",
+             region, region_count);
+    return 0;
+  }
+  result = 1;
+
+  rp = regions + region;
+  if (rp->used_count > REGION_SIZE / MIN_REMNANT_LEN) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: chunk count is ludicrous: 0x%04x",
+             region, rp->used_count);
+    result = 0;
+  }
+  if (rp->free_count > REGION_SIZE / MIN_REMNANT_LEN) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: free count is ludicrous: 0x%04x",
+             region, rp->free_count);
+    result = 0;
+  }
+  if (rp->largest_free_chunk > rp->free_bytes) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: largest free chunk > free bytes:"
+             " 0x%04x > 0x%04x",
+             region, rp->largest_free_chunk, rp->free_bytes);
+    result = 0;
+  }
+  if (!rp->in_memory)
+    return result;
+
+  rhp = rp->in_memory;
+
+  if (rhp->region_id != region) {
+    do_rawlog(LT_ERR, "region 0x%04x is not valid: region in cache is 0x%04x",
+             region, rhp->region_id);
+    result = 0;
+  }
+  dump = 0;
+  used_count = 0;
+  total_derefs = 0;
+  free_count = 0;
+  free_bytes = 0;
+  largest_free = 0;
+  was_free = 0;
+  next_free = rhp->first_free;
+  for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+       offset < REGION_SIZE; offset += len) {
+    if (was_free && ChunkIsFree(region, offset)) {
+      do_rawlog(LT_ERR,
+               "region 0x%04x is not valid: uncoalesced free chunk:"
+               " 0x%04x (see map)", region, offset);
+      result = 0;
+      dump = 1;
+    }
+    len = ChunkFullLen(region, offset);
+    was_free = ChunkIsFree(region, offset);
+    if (was_free) {
+      free_count++;
+      free_bytes += len;
+      if (largest_free < len)
+       largest_free = len;
+      if (next_free != offset) {
+       do_rawlog(LT_ERR,
+                 "region 0x%04x is not valid: free chain broken:"
+                 " 0x%04x, expecting 0x%04x (see map)",
+                 region, offset, next_free);
+       result = 0;
+       dump = 1;
+      }
+      next_free = ChunkNextFree(region, offset);
+    } else {
+      used_count++;
+      total_derefs += ChunkDerefs(region, offset);
+      if (ChunkIsMedium(region, offset) &&
+         ChunkLen(region, offset) <= MAX_SHORT_CHUNK_LEN) {
+       do_rawlog(LT_ERR,
+                 "region 0x%04x is not valid: medium chunk too small:"
+                 " 0x%04x (see map)", region, offset);
+       result = 0;
+       dump = 1;
+      }
+      if (ChunkIsLong(region, offset) &&
+         ChunkLen(region, offset) <= MAX_MEDIUM_CHUNK_LEN) {
+       do_rawlog(LT_ERR,
+                 "region 0x%04x is not valid: long chunk too small:"
+                 " 0x%04x (see map)", region, offset);
+       result = 0;
+       dump = 1;
+      }
+    }
+  }
+  if (offset != REGION_SIZE) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: last chunk past bounds (see map)",
+             region);
+    result = 0;
+  }
+  if (next_free != 0) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: free chain unterminated:"
+             " expecting 0x%04x (see map)", region, next_free);
+    result = 0;
+    dump = 1;
+  }
+  if (rp->used_count != used_count) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: used count is wrong:"
+             " 0x%04x should be 0x%04x", region, rp->used_count, used_count);
+    result = 0;
+  }
+  if (rp->total_derefs != total_derefs) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: total derefs is wrong:"
+             " 0x%04x should be 0x%04x",
+             region, rp->total_derefs, total_derefs);
+    result = 0;
+  }
+  if (rp->free_count != free_count) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: free count is wrong:"
+             " 0x%04x should be 0x%04x", region, rp->free_count, free_count);
+    result = 0;
+  }
+  if (rp->free_bytes != free_bytes) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: free bytes is wrong:"
+             " 0x%04x should be 0x%04x", region, rp->free_bytes, free_bytes);
+    result = 0;
+  }
+  if (rp->largest_free_chunk != largest_free) {
+    do_rawlog(LT_ERR,
+             "region 0x%04x is not valid: largest free is wrong:"
+             " 0x%04x should be 0x%04x",
+             region, rp->largest_free_chunk, largest_free);
+    result = 0;
+  }
+  if (dump) {
+    debug_dump_region(region, tracelog_fp);
+  }
+  return result;
+}
+#endif
+
+
+/*
+ * Utility Routines - Chunks
+ */
+/** Write a used chunk.
+ * \param region the region to put the chunk in.
+ * \param offset the offset to put the chunk at.
+ * \param full_len the length for the chunk, including headers.
+ * \param data the externally supplied data for the chunk.
+ * \param data_len the length of the externally supplied data.
+ * \param derefs the deref count to set on the chunk.
+ */
+static void
+write_used_chunk(u_int_16 region, u_int_16 offset, u_int_16 full_len,
+                unsigned char const *data, u_int_16 data_len,
+                unsigned char derefs)
+{
+  unsigned char *cptr = ChunkPointer(region, offset);
+  if (full_len <= MAX_SHORT_CHUNK_LEN + CHUNK_SHORT_DATA_OFFSET) {
+    /* chunk is short */
+    cptr[0] = full_len - CHUNK_SHORT_DATA_OFFSET +
+      CHUNK_USED + CHUNK_TAG1_SHORT;
+    cptr[CHUNK_DEREF_OFFSET] = derefs;
+    memcpy(cptr + CHUNK_SHORT_DATA_OFFSET, data, data_len);
+  } else if (full_len <= MAX_MEDIUM_CHUNK_LEN + CHUNK_MEDIUM_DATA_OFFSET) {
+    /* chunk is medium */
+    u_int_16 len = full_len - CHUNK_MEDIUM_DATA_OFFSET;
+    cptr[0] = (len >> 8) + CHUNK_USED + CHUNK_TAG1_MEDIUM + CHUNK_TAG2_MEDIUM;
+    cptr[CHUNK_DEREF_OFFSET] = derefs;
+    cptr[CHUNK_MEDIUM_LEN_LSB_OFFSET] = len & 0xff;
+    memcpy(cptr + CHUNK_MEDIUM_DATA_OFFSET, data, data_len);
+  } else {
+    /* chunk is long */
+    u_int_16 len = full_len - CHUNK_LONG_DATA_OFFSET;
+    cptr[0] = CHUNK_USED + CHUNK_TAG1_LONG + CHUNK_TAG2_LONG;
+    cptr[CHUNK_DEREF_OFFSET] = derefs;
+    cptr[CHUNK_LONG_LEN_MSB_OFFSET] = len >> 8;
+    cptr[CHUNK_LONG_LEN_LSB_OFFSET] = len & 0xff;
+    memcpy(cptr + CHUNK_LONG_DATA_OFFSET, data, data_len);
+  }
+}
+
+/** Write a free chunk.
+ * \param region the region to put the chunk in.
+ * \param offset the offset to put the chunk at.
+ * \param full_len the length for the chunk, including headers.
+ * \param next the offset for the next free chunk.
+ */
+static void
+write_free_chunk(u_int_16 region, u_int_16 offset, u_int_16 full_len,
+                u_int_16 next)
+{
+  unsigned char *cptr = ChunkPointer(region, offset);
+  if (full_len <= MAX_SHORT_CHUNK_LEN + CHUNK_SHORT_DATA_OFFSET) {
+    /* chunk is short */
+    cptr[0] = full_len - CHUNK_SHORT_DATA_OFFSET +
+      CHUNK_FREE + CHUNK_TAG1_SHORT;
+    cptr[CHUNK_SHORT_DATA_OFFSET] = next >> 8;
+    cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
+  } else if (full_len <= MAX_MEDIUM_CHUNK_LEN + CHUNK_MEDIUM_DATA_OFFSET) {
+    /* chunk is medium */
+    u_int_16 len = full_len - CHUNK_MEDIUM_DATA_OFFSET;
+    cptr[0] = (len >> 8) + CHUNK_FREE + CHUNK_TAG1_MEDIUM + CHUNK_TAG2_MEDIUM;
+    cptr[CHUNK_MEDIUM_LEN_LSB_OFFSET] = len & 0xff;
+    cptr[CHUNK_MEDIUM_DATA_OFFSET] = next >> 8;
+    cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
+  } else {
+    /* chunk is long */
+    u_int_16 len = full_len - CHUNK_LONG_DATA_OFFSET;
+    cptr[0] = CHUNK_FREE + CHUNK_TAG1_LONG + CHUNK_TAG2_LONG;
+    cptr[CHUNK_LONG_LEN_MSB_OFFSET] = len >> 8;
+    cptr[CHUNK_LONG_LEN_LSB_OFFSET] = len & 0xff;
+    cptr[CHUNK_LONG_DATA_OFFSET] = next >> 8;
+    cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
+  }
+}
+
+/** Write the next pointer for a free chunk.
+ * \param region the region of the chunk to write in.
+ * \param offset the offset of the chunk to write in.
+ * \param next the offset for the next free chunk.
+ */
+static void
+write_next_free(u_int_16 region, u_int_16 offset, u_int_16 next)
+{
+  unsigned char *cptr = ChunkPointer(region, offset);
+  if (ChunkIsShort(region, offset)) {
+    /* chunk is short */
+    cptr[CHUNK_SHORT_DATA_OFFSET] = next >> 8;
+    cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
+  } else if (ChunkIsMedium(region, offset)) {
+    /* chunk is medium */
+    cptr[CHUNK_MEDIUM_DATA_OFFSET] = next >> 8;
+    cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
+  } else {
+    /* chunk is long */
+    cptr[CHUNK_LONG_DATA_OFFSET] = next >> 8;
+    cptr[CHUNK_DEREF_OFFSET] = next & 0xff;
+  }
+}
+
+/** Combine neighboring free chunks, if possible.
+ * The left-hand candidate chunk is passed in.
+ * \param region the region of the chunks to coalesce.
+ * \param offset the offset of the left-hand chunk to coalesce.
+ */
+static void
+coalesce_frees(u_int_16 region, u_int_16 offset)
+{
+  Region *rp = regions + region;
+  u_int_16 full_len, next;
+  full_len = ChunkFullLen(region, offset);
+  next = ChunkNextFree(region, offset);
+  if (offset + full_len == next) {
+    full_len += ChunkFullLen(region, next);
+    next = ChunkNextFree(region, next);
+    write_free_chunk(region, offset, full_len, next);
+    rp->free_count--;
+    if (rp->largest_free_chunk < full_len)
+      rp->largest_free_chunk = full_len;
+  }
+}
+
+/** Free a used chunk.
+ * \param region the region of the chunk to free.
+ * \param offset the offset of the chunk to free.
+ */
+static void
+free_chunk(u_int_16 region, u_int_16 offset)
+{
+  Region *rp = regions + region;
+  u_int_16 full_len, left;
+
+  full_len = ChunkFullLen(region, offset);
+  rp->total_derefs -= ChunkDerefs(region, offset);
+  rp->used_count--;
+  rp->free_count++;
+  rp->free_bytes += full_len;
+  if (rp->largest_free_chunk < full_len)
+    rp->largest_free_chunk = full_len;
+
+  if (ChunkIsShort(region, offset)) {
+    /* chunk is short */
+    stat_used_short_count--;
+    stat_used_short_bytes -= full_len;
+  } else if (ChunkIsMedium(region, offset)) {
+    /* chunk is medium */
+    stat_used_medium_count--;
+    stat_used_medium_bytes -= full_len;
+  } else {
+    /* chunk is long */
+    stat_used_long_count--;
+    stat_used_long_bytes -= full_len;
+  }
+
+  left = rp->in_memory->first_free;
+  if (!left) {
+    write_free_chunk(region, offset, full_len, 0);
+    rp->largest_free_chunk = full_len;
+    rp->in_memory->first_free = offset;
+  } else if (left > offset) {
+    write_free_chunk(region, offset, full_len, left);
+    rp->in_memory->first_free = offset;
+    left = 0;
+  } else {
+    u_int_16 next;
+    next = ChunkNextFree(region, left);
+    while (next && next < offset) {
+      left = next;
+      next = ChunkNextFree(region, left);
+    }
+    write_free_chunk(region, offset, full_len, next);
+    write_next_free(region, left, offset);
+  }
+  coalesce_frees(region, offset);
+  if (left)
+    coalesce_frees(region, left);
+}
+
+/** Find the largest free chunk in a region.
+ * \param region the region to search for a large hole in.
+ * \return the size of the largest free chunk.
+ */
+static u_int_16
+largest_hole(u_int_16 region)
+{
+  u_int_16 size;
+  u_int_16 offset;
+  size = 0;
+  for (offset = regions[region].in_memory->first_free;
+       offset; offset = ChunkNextFree(region, offset))
+    if (size < ChunkFullLen(region, offset))
+      size = ChunkFullLen(region, offset);
+  return size;
+}
+
+/** Allocate a used chunk out of a free hole.
+ * This possibly splits the hole into two chunks, and it maintains the
+ * free list.  It does NOT write the used chunk; that must be done by
+ * caller.
+ * \param region the region to allocate in.
+ * \param offset the offset of the hole to use.
+ * \param full_len the length (including headers) of the space to allocate.
+ * \param align the alignment to use: 0 = easiest, 1 = left, 2 = right.
+ * \return the offset of the allocated space.
+ */
+static u_int_16
+split_hole(u_int_16 region, u_int_16 offset, u_int_16 full_len, int align)
+{
+  Region *rp = regions + region;
+  u_int_16 hole_len = ChunkFullLen(region, offset);
+
+  rp->used_count++;
+  if (full_len <= MAX_SHORT_CHUNK_LEN + CHUNK_SHORT_DATA_OFFSET) {
+    /* chunk is short */
+    stat_used_short_count++;
+    stat_used_short_bytes += full_len;
+  } else if (full_len <= MAX_MEDIUM_CHUNK_LEN + CHUNK_MEDIUM_DATA_OFFSET) {
+    /* chunk is medium */
+    stat_used_medium_count++;
+    stat_used_medium_bytes += full_len;
+  } else {
+    /* chunk is long */
+    stat_used_long_count++;
+    stat_used_long_bytes += full_len;
+  }
+
+  if (hole_len == full_len) {
+    rp->free_count--;
+    rp->free_bytes -= full_len;
+    if (rp->in_memory->first_free == offset)
+      rp->in_memory->first_free = ChunkNextFree(region, offset);
+    else {
+      u_int_16 hole;
+      for (hole = rp->in_memory->first_free;
+          hole; hole = ChunkNextFree(region, hole))
+       if (ChunkNextFree(region, hole) == offset)
+         break;
+      ASSERT(hole);
+      write_next_free(region, hole, ChunkNextFree(region, offset));
+    }
+    if (rp->largest_free_chunk == hole_len)
+      rp->largest_free_chunk = largest_hole(region);
+    return offset;
+  }
+
+  ASSERT(hole_len >= full_len + MIN_REMNANT_LEN);
+  if (!align) {
+    if (rp->in_memory->first_free == offset)
+      align = 1;
+    else
+      align = 2;
+  }
+  if (align == 1) {
+    rp->free_bytes -= full_len;
+    write_free_chunk(region, offset + full_len,
+                    hole_len - full_len, ChunkNextFree(region, offset));
+    if (rp->in_memory->first_free == offset)
+      rp->in_memory->first_free += full_len;
+    else {
+      u_int_16 hole;
+      for (hole = rp->in_memory->first_free;
+          hole; hole = ChunkNextFree(region, hole))
+       if (ChunkNextFree(region, hole) == offset)
+         break;
+      ASSERT(hole);
+      write_next_free(region, hole, offset + full_len);
+    }
+    if (rp->largest_free_chunk == hole_len)
+      rp->largest_free_chunk = largest_hole(region);
+    return offset;
+  } else {
+    rp->free_bytes -= full_len;
+    write_free_chunk(region, offset,
+                    hole_len - full_len, ChunkNextFree(region, offset));
+    if (rp->largest_free_chunk == hole_len)
+      rp->largest_free_chunk = largest_hole(region);
+    return offset + hole_len - full_len;
+  }
+}
+
+/*
+ *  Utility Routines - Cache
+ */
+
+/** Read a region from a file.
+ * \param fd file to read from
+ * \param rhp region buffer to use
+ * \param region region to read
+ */
+static void
+read_cache_region(fd_type fd, RegionHeader * rhp, u_int_16 region)
+{
+  off_t file_offset = region * REGION_SIZE;
+  int j;
+  char *pos;
+  size_t remaining;
+  int done;
+
+  debug_log("read_cache_region %04x", region);
+
+  /* Try to seek up to 3 times... */
+  for (j = 0; j < 3; j++)
+#ifdef WIN32
+    if (SetFilePointer(fd, file_offset, NULL, FILE_BEGIN) == file_offset)
+      break;
+#else
+    if (lseek(fd, file_offset, SEEK_SET) == file_offset)
+      break;
+#endif
+  if (j >= 3)
+#ifdef WIN32
+    mush_panicf("chunk swap file seek, GetLastError %d", GetLastError());
+#else
+    mush_panicf("chunk swap file seek, errno %d: %s", errno, strerror(errno));
+#endif
+  pos = (char *) rhp;
+  remaining = REGION_SIZE;
+  for (j = 0; j < 10; j++) {
+#ifdef WIN32
+#ifndef __MINGW32__
+    if (!ReadFile(fd, pos, remaining, &done, NULL)) {
+      /* nothing */
+    }
+#endif
+#else
+    done = read(fd, pos, remaining);
+#endif
+    if (done >= 0) {
+      remaining -= done;
+      pos += done;
+      if (!remaining)
+       return;
+    }
+#ifndef WIN32
+    if (done == -1 && errno == EAGAIN)
+      sleep(0);
+#endif
+  }
+#ifdef WIN32
+  mush_panicf("chunk swap file read, %d remaining, GetLastError %d",
+             remaining, GetLastError());
+#else
+  mush_panicf("chunk swap file read, %d remaining, errno %d: %s",
+             remaining, errno, strerror(errno));
+#endif
+}
+
+/** Write a region to a file.
+ * \param fd file to write to
+ * \param rhp region buffer to use
+ * \param region region to write
+ */
+static void
+write_cache_region(fd_type fd, RegionHeader * rhp, u_int_16 region)
+{
+  off_t file_offset = region * REGION_SIZE;
+  int j;
+  char *pos;
+  size_t remaining;
+  int done;
+
+  debug_log("write_cache_region %04x", region);
+
+  /* Try to seek up to 3 times... */
+  for (j = 0; j < 3; j++)
+#ifdef WIN32
+    if (SetFilePointer(fd, file_offset, NULL, FILE_BEGIN) == file_offset)
+      break;
+#else
+    if (lseek(fd, file_offset, SEEK_SET) == file_offset)
+      break;
+#endif
+  if (j >= 3)
+#ifdef WIN32
+    mush_panicf("chunk swap file seek, GetLastError %d", GetLastError());
+#else
+    mush_panicf("chunk swap file seek, errno %d: %s", errno, strerror(errno));
+#endif
+  pos = (char *) rhp;
+  remaining = REGION_SIZE;
+  for (j = 0; j < 10; j++) {
+#ifdef WIN32
+#ifndef __MINGW32__
+    if (!WriteFile(fd, pos, remaining, &done, NULL)) {
+      /* nothing */
+    }
+#else
+    done = write(fd, pos, remaining);
+#endif
+#else
+    done = write(fd, pos, remaining);
+#endif
+    if (done >= 0) {
+      remaining -= done;
+      pos += done;
+      if (!remaining)
+       return;
+    }
+#ifndef WIN32
+    if (done == -1 && errno == EAGAIN)
+      sleep(0);
+#endif
+  }
+#ifdef WIN32
+  mush_panicf("chunk swap file write, %d remaining, GetLastError %d",
+             remaining, GetLastError());
+#else
+  mush_panicf("chunk swap file write, %d remaining, errno %d: %s",
+             remaining, errno, strerror(errno));
+#endif
+}
+
+/** Update cache position to stave off recycling.
+ * \param rhp the cached region to keep around.
+ */
+static void
+touch_cache_region(RegionHeader * rhp)
+{
+  debug_log("touch_cache_region %04x", rhp->region_id);
+
+  if (cache_head == rhp)
+    return;
+  if (cache_tail == rhp)
+    cache_tail = rhp->prev;
+  if (rhp->prev)
+    rhp->prev->next = rhp->next;
+  if (rhp->next)
+    rhp->next->prev = rhp->prev;
+
+  if (cache_head)
+    cache_head->prev = rhp;
+  rhp->next = cache_head;
+  rhp->prev = NULL;
+  cache_head = rhp;
+  if (!cache_tail)
+    cache_tail = rhp;
+}
+
+/** Find space in the cache.
+ * This is likely to require paging out something.
+ * \return a pointer to an available cache region.
+ */
+static RegionHeader *
+find_available_cache_region(void)
+{
+  RegionHeader *rhp;
+
+  debug_log("find_available_cache_region");
+
+  if (!cache_tail ||
+      cached_region_count * REGION_SIZE < (unsigned) CHUNK_CACHE_MEMORY) {
+    /* first use ... normal case if empty ... so allocate space */
+#ifdef DEBUG_CHUNK_MALLOC
+    do_rawlog(LT_TRACE, "CHUNK: malloc()ing a cache region");
+#endif
+    rhp = mush_malloc(REGION_SIZE, "chunk region cache buffer");
+    if (!rhp) {
+      mush_panic("chunk region cache buffer allocation failure");
+    }
+    cached_region_count++;
+    rhp->region_id = INVALID_REGION_ID;
+    rhp->prev = NULL;
+    rhp->next = NULL;
+    return rhp;
+  }
+  if (cache_tail->region_id == INVALID_REGION_ID)
+    return cache_tail;
+
+  rhp = cache_tail;
+  /* page the current occupant out */
+  find_oddballs(rhp->region_id);
+#ifdef DEBUG_CHUNK_PAGING
+  do_rawlog(LT_TRACE, "CHUNK: Paging out region %04x (offset %08x)",
+           rhp->region_id, (unsigned) file_offset);
+#endif
+  write_cache_region(swap_fd, rhp, rhp->region_id);
+  /* keep statistics */
+  stat_paging_histogram[RegionDerefs(rhp->region_id)]++;
+  stat_page_out++;
+
+  /* mark the paged out region as not in memory */
+  regions[rhp->region_id].in_memory = NULL;
+  /* mark it not in use for sanity check reasons */
+  rhp->region_id = INVALID_REGION_ID;
+
+  return rhp;
+}
+
+/** Bring a paged out region back into memory.
+ * If neccessary, make room by paging another region out.
+ * \param region the region to bring in.
+ */
+static void
+bring_in_region(u_int_16 region)
+{
+  Region *rp = regions + region;
+  RegionHeader *rhp, *prev, *next;
+  u_int_32 offset;
+  unsigned int shift;
+
+  debug_log("bring_in_region %04x", region);
+
+  ASSERT(region < region_count);
+  if (rp->in_memory)
+    return;
+  rhp = find_available_cache_region();
+  ASSERT(rhp->region_id == INVALID_REGION_ID);
+
+  /* This is cheesy, but I _really_ don't want to do dual data structures */
+  prev = rhp->prev;
+  next = rhp->next;
+
+  /* page it in */
+#ifdef DEBUG_CHUNK_PAGING
+  do_rawlog(LT_TRACE, "CHUNK: Paging in region %04x (offset %08x)",
+           region, (unsigned) file_offset);
+#endif
+  read_cache_region(swap_fd, rhp, region);
+  /* link the region to its cache entry */
+  rp->in_memory = rhp;
+
+  /* touch the cache entry */
+  rhp->prev = prev;
+  rhp->next = next;
+  touch_cache_region(rhp);
+
+  /* make derefs current */
+  if (rp->period_last_touched != curr_period) {
+    shift = curr_period - rp->period_last_touched;
+    if (shift > 8) {
+      rp->total_derefs = 0;
+      for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+          offset < REGION_SIZE; offset += ChunkFullLen(region, offset)) {
+       ChunkDerefs(region, offset) = 0;
+      }
+    } else {
+      rp->total_derefs = 0;
+      for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+          offset < REGION_SIZE; offset += ChunkFullLen(region, offset)) {
+       if (ChunkIsFree(region, offset))
+         continue;
+       ChunkDerefs(region, offset) >>= shift;
+       rp->total_derefs += ChunkDerefs(region, offset);
+      }
+    }
+    rp->period_last_touched = curr_period;
+  }
+
+  /* keep statistics */
+  stat_page_in++;
+  stat_paging_histogram[RegionDerefs(region)]++;
+}
+
+/*
+ * Utility Routines - Regions
+ */
+/** Create a new region.
+ * Recycle an empty region if possible.
+ * \return the region id for the new region.
+ */
+static u_int_16
+create_region(void)
+{
+  u_int_16 region;
+
+  for (region = 0; region < region_count; region++)
+    if (regions[region].used_count == 0)
+      break;
+  if (region >= region_count) {
+    if (region_count >= region_array_len) {
+      /* need to grow the regions array */
+      region_array_len += FIXME_REGION_ARRAY_INCREMENT;
+#ifdef DEBUG_CHUNK_MALLOC
+      do_rawlog(LT_TRACE, "CHUNK: realloc()ing region array");
+#endif
+      regions = (Region *) realloc(regions, region_array_len * sizeof(Region));
+      if (!regions)
+       mush_panic("chunk: region array realloc failure");
+    }
+    region = region_count;
+    region_count++;
+    regions[region].in_memory = NULL;
+  }
+
+  regions[region].used_count = 0;
+  regions[region].free_count = 1;
+  regions[region].free_bytes = REGION_CAPACITY;
+  regions[region].largest_free_chunk = regions[region].free_bytes;
+  regions[region].total_derefs = 0;
+  regions[region].period_last_touched = curr_period;
+  if (!regions[region].in_memory)
+    regions[region].in_memory = find_available_cache_region();
+  regions[region].in_memory->region_id = region;
+  regions[region].in_memory->first_free = FIRST_CHUNK_OFFSET_IN_REGION;
+  write_free_chunk(region, FIRST_CHUNK_OFFSET_IN_REGION,
+                  regions[region].free_bytes, 0);
+
+  touch_cache_region(regions[region].in_memory);
+  return region;
+}
+
+/** Find the oddball chunks in a region.
+ * \param region the region to search in.
+ */
+static void
+find_oddballs(u_int_16 region)
+{
+  Region *rp = regions + region;
+  int j, d1, d2;
+  u_int_16 offset, len;
+  int mean;
+
+  for (j = 0; j < NUM_ODDBALLS; j++)
+    rp->oddballs[j] = 0;
+
+  mean = RegionDerefs(region);
+
+  for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+       offset < REGION_SIZE; offset += len) {
+    len = ChunkFullLen(region, offset);
+    if (ChunkIsFree(region, offset))
+      continue;
+    d1 = abs(mean - ChunkDerefs(region, offset));
+    if (d1 < ODDBALL_THRESHOLD)
+      continue;
+    j = NUM_ODDBALLS;
+    while (j--) {
+      if (!rp->oddballs[j])
+       continue;
+      d2 = abs(mean - ChunkDerefs(region, rp->oddballs[j]));
+      if (d1 < d2)
+       break;
+      if (j < NUM_ODDBALLS - 1)
+       rp->oddballs[j + 1] = rp->oddballs[j];
+    }
+    j++;
+    if (j >= NUM_ODDBALLS)
+      continue;
+    rp->oddballs[j] = offset;
+  }
+}
+
+/** Find the best region to hold a chunk.
+ * This is done by going through all the known regions and getting
+ * prospective unhappiness ratings if the chunk was placed there.
+ * The region with the least unhappiness wins.  Note that this may
+ * well be a new region, if all existing regions are either full or
+ * sufficiently unhappy.
+ * \param full_len the size of the chunk, including headers.
+ * \param derefs the number of dereferences on the chunk.
+ * \param old_region the region the chunk was in before (if any).
+ * \return the region id for the least unhappy region.
+ */
+static u_int_16
+find_best_region(u_int_16 full_len, int derefs, u_int_16 old_region)
+{
+  u_int_16 best_region, region;
+  int best_score, score;
+  int free_bytes;
+  Region *rp;
+
+  best_region = INVALID_REGION_ID;
+  best_score = INT_MAX;
+  free_bytes = 0;
+  for (region = 0; region < region_count; region++) {
+    rp = regions + region;
+    free_bytes += rp->free_bytes;
+    if (!FitsInSpace(full_len, rp->largest_free_chunk) &&
+       !(rp->free_count == 2 &&
+         rp->free_bytes - rp->largest_free_chunk == full_len))
+      continue;
+
+    if (region == old_region)
+      score = derefs - RegionDerefs(region);
+    else
+      score = derefs - RegionDerefsWithChunk(region, derefs);
+    if (score < 0)
+      score = -score;
+    if (!rp->in_memory)
+      score += IN_MEMORY_BIAS;
+    if (rp->used_count <= LONLINESS_LIMIT)
+      score += 1 << (LONLINESS_LIMIT - rp->used_count);
+
+    if (best_score > score) {
+      best_score = score;
+      best_region = region;
+    }
+  }
+
+  if (best_region == INVALID_REGION_ID) {
+#ifdef DEBUG_CHUNK_REGION_CREATE
+    do_rawlog(LT_TRACE, "find_best_region had to create region %04x", region);
+#endif
+    best_region = create_region();
+  } else if (best_score > (1 << LONLINESS_LIMIT) + IN_MEMORY_BIAS &&
+            (free_bytes * 100 / (REGION_CAPACITY * region_count)) <
+            FREE_PERCENT_LIMIT) {
+#ifdef DEBUG_CHUNK_REGION_CREATE
+    do_rawlog(LT_TRACE, "find_best_region chose to create region %04x", region);
+#endif
+    best_region = create_region();
+  }
+  return best_region;
+}
+
+/** Find the best offset in a region to hold a chunk.
+ * \param full_len the length of the chunk, including headers.
+ * \param region the region to allocate in.
+ * \param old_region the region the chunk was in before (if any).
+ * \param old_offset the offset the chunk was at before (if any).
+ */
+static u_int_16
+find_best_offset(u_int_16 full_len, u_int_16 region,
+                u_int_16 old_region, u_int_16 old_offset)
+{
+  u_int_16 fits, offset;
+
+  bring_in_region(region);
+
+  fits = 0;
+  for (offset = regions[region].in_memory->first_free; offset;
+       offset = ChunkNextFree(region, offset)) {
+    if (region == old_region) {
+      if (offset > old_offset)
+       break;
+      if (offset + ChunkFullLen(region, offset) == old_offset)
+       return fits ? fits : offset;
+    }
+    if (ChunkFullLen(region, offset) == full_len)
+      return offset;
+    if (!fits && ChunkFullLen(region, offset) >= full_len + MIN_REMNANT_LEN)
+      fits = offset;
+  }
+
+  return fits;
+}
+
+/*
+ * Utility Routines - Statistics and debugging
+ */
+/** Compile a histogram for the region dereferences.
+ * \return histogram data for the regions.
+ */
+static int *
+chunk_regionhist(void)
+{
+  static int histogram[CHUNK_DEREF_MAX + 1];
+  unsigned int j;
+
+  for (j = 0; j <= CHUNK_DEREF_MAX; j++)
+    histogram[j] = 0;
+  for (j = 0; j < region_count; j++) {
+    histogram[RegionDerefs(j)]++;
+  }
+  return histogram;
+}
+
+/** Compile a histogram for the region free space.
+ * \return histogram data for the free space.
+ */
+static int const *
+chunk_freehist(void)
+{
+  static int histogram[CHUNK_DEREF_MAX + 1];
+  unsigned int j;
+
+  for (j = 0; j <= CHUNK_DEREF_MAX; j++)
+    histogram[j] = 0;
+  for (j = 0; j < region_count; j++) {
+    histogram[RegionDerefs(j)] += regions[j].free_bytes;
+  }
+  return histogram;
+}
+
+/** Display statistics to a player, or dump them to a log
+ */
+#define STAT_OUT(x) \
+  do { \
+    s = (x); \
+    if (GoodObject(player)) \
+      notify(player, s); \
+    else \
+      do_rawlog(LT_TRACE, "%s", s); \
+  } while (0)
+
+/** Display the stats summary page.
+ * \param player the player to display it to, or NOTHING to log it.
+ */
+static void
+chunk_statistics(dbref player)
+{
+  const char *s;
+  int overhead;
+  int free_count = 0;
+  int free_bytes = 0;
+  int free_large = 0;
+  int used_count = 0;
+  int used_bytes = 0;
+  u_int_16 rid;
+
+  for (rid = 0; rid < region_count; rid++) {
+    free_count += regions[rid].free_count;
+    free_bytes += regions[rid].free_bytes;
+    free_large += regions[rid].largest_free_chunk;
+    used_count += regions[rid].used_count;
+  }
+  used_bytes = (REGION_CAPACITY * region_count) - free_bytes;
+
+  if (!GoodObject(player)) {
+    do_rawlog(LT_TRACE, "---- Chunk statistics");
+  }
+  overhead = stat_used_short_count * CHUNK_SHORT_DATA_OFFSET +
+    stat_used_medium_count * CHUNK_MEDIUM_DATA_OFFSET +
+    stat_used_long_count * CHUNK_LONG_DATA_OFFSET;
+  STAT_OUT(tprintf
+          ("Chunks:    %10d allocated (%10d bytes, %10d (%2d%%) overhead)",
+           used_count, used_bytes, overhead,
+           used_bytes ? overhead * 100 / used_bytes : 0));
+  overhead = stat_used_short_count * CHUNK_SHORT_DATA_OFFSET;
+  STAT_OUT(tprintf
+          ("             %10d short     (%10d bytes, %10d (%2d%%) overhead)",
+           stat_used_short_count, stat_used_short_bytes, overhead,
+           stat_used_short_bytes ? overhead * 100 /
+           stat_used_short_bytes : 0));
+  overhead = stat_used_medium_count * CHUNK_MEDIUM_DATA_OFFSET;
+  STAT_OUT(tprintf
+          ("             %10d medium    (%10d bytes, %10d (%2d%%) overhead)",
+           stat_used_medium_count, stat_used_medium_bytes, overhead,
+           stat_used_medium_bytes ? overhead * 100 /
+           stat_used_medium_bytes : 0));
+  overhead = stat_used_long_count * CHUNK_LONG_DATA_OFFSET;
+  STAT_OUT(tprintf
+          ("             %10d long      (%10d bytes, %10d (%2d%%) overhead)",
+           stat_used_long_count, stat_used_long_bytes, overhead,
+           stat_used_long_bytes ? overhead * 100 / stat_used_long_bytes : 0));
+  STAT_OUT(tprintf
+          ("           %10d free      (%10d bytes, %10d (%2d%%) fragmented)",
+           free_count, free_bytes, free_bytes - free_large,
+           free_bytes ? (free_bytes - free_large) * 100 / free_bytes : 0));
+  overhead = region_count * REGION_SIZE + region_array_len * sizeof(Region);
+  STAT_OUT(tprintf("Storage:   %10d total (%2d%% saturation)",
+                  overhead, used_bytes * 100 / overhead));
+  STAT_OUT(tprintf("Regions:   %10d total, %8d cached",
+                  region_count, cached_region_count));
+  STAT_OUT(tprintf("Paging:    %10d out, %10d in",
+                  stat_page_out, stat_page_in));
+  STAT_OUT(" ");
+  STAT_OUT(tprintf("Period:    %10d (%10d accesses so far, %10d chunks at max)",
+                  curr_period, stat_deref_count, stat_deref_maxxed));
+  STAT_OUT(tprintf("Activity:  %10d creates, %10d deletes this period",
+                  stat_create, stat_delete));
+  STAT_OUT(tprintf("Migration: %10d moves this period",
+                  stat_migrate_slide + stat_migrate_move));
+  STAT_OUT(tprintf("             %10d slide    %10d move",
+                  stat_migrate_slide, stat_migrate_move));
+  STAT_OUT(tprintf("             %10d in region%10d out of region",
+                  stat_migrate_slide + stat_migrate_move - stat_migrate_away,
+                  stat_migrate_away));
+}
+
+/** Show just the page counts.
+ * \param player the player to display it to, or NOTHING to log it.
+ */
+static void
+chunk_page_stats(dbref player)
+{
+  const char *s;
+  STAT_OUT(tprintf("Paging:    %10d out, %10d in",
+                  stat_page_out, stat_page_in));
+}
+
+/** Display the per-region stats.
+ * \param player the player to display it to, or NOTHING to log it.
+ */
+static void
+chunk_region_statistics(dbref player)
+{
+  u_int_16 rid;
+  const char *s;
+
+  if (!GoodObject(player)) {
+    do_rawlog(LT_TRACE, "---- Region statistics");
+  }
+  for (rid = 0; rid < region_count; rid++) {
+    STAT_OUT(tprintf
+            ("region:%4d  #used:%5d  #free:%5d  "
+             "fbytes:%04x  largest:%04x  deref:%3d",
+             rid, regions[rid].used_count, regions[rid].free_count,
+             regions[rid].free_bytes, regions[rid].largest_free_chunk,
+             RegionDerefs(rid)));
+  }
+}
+
+/** Display a histogram.
+ * \param player the player to display it to, or NOTHING to log it.
+ * \param histogram the histogram data to display.
+ * \param legend the legend for the histogram.
+ */
+static void
+chunk_histogram(dbref player, int const *histogram, char const *legend)
+{
+  const char *s;
+  int j, k, max, pen, ante;
+  char buffer[20][65];
+  char num[16];
+
+  max = pen = ante = 0;
+  for (j = 0; j < 64; j++) {
+    k = histogram[j * 4 + 0] + histogram[j * 4 + 1] +
+      histogram[j * 4 + 2] + histogram[j * 4 + 3];
+    if (max < k) {
+      ante = pen;
+      pen = max;
+      max = k;
+    } else if (pen < k) {
+      ante = pen;
+      pen = k;
+    } else if (ante < k) {
+      ante = k;
+    }
+  }
+  if (ante < max / 2) {
+    if (pen < max / 2 && ante >= pen / 2)
+      max = pen;
+    else
+      max = ante;
+  }
+  if (max == 0)
+    max = 1;
+  for (j = 0; j < 20; j++) {
+    for (k = 0; k < 64; k++)
+      buffer[j][k] = ' ';
+    buffer[j][64] = '\0';
+  }
+  for (j = 0; j < 64; j++) {
+    k = histogram[j * 4 + 0] + histogram[j * 4 + 1] +
+      histogram[j * 4 + 2] + histogram[j * 4 + 3];
+    k = k * 20 / max;
+    if (k >= 20)
+      k = 20;
+    while (k-- > 0)
+      buffer[k][j] = '*';
+  }
+  pen = 0;
+  for (j = 0; j < 64; j++) {
+    k = histogram[j * 4 + 0] + histogram[j * 4 + 1] +
+      histogram[j * 4 + 2] + histogram[j * 4 + 3];
+    if (k > max) {
+      sprintf(num, "(%d)", k);
+      if (j < 32) {
+       if (j < pen)
+         ante = 18;
+       else
+         ante = 19;
+       memcpy(buffer[ante] + j + 1, num, strlen(num));
+       pen = j + strlen(num) + 1;
+      } else {
+       if (j - (int) strlen(num) < pen)
+         ante = 18;
+       else
+         ante = 19;
+       memcpy(buffer[ante] + j - strlen(num), num, strlen(num));
+       pen = j;
+      }
+    }
+  }
+  STAT_OUT("");
+  STAT_OUT(legend);
+  STAT_OUT(tprintf("%6d |%s", max, buffer[19]));
+  j = 19;
+  while (j-- > 1)
+    STAT_OUT(tprintf("       |%s", buffer[j]));
+  STAT_OUT(tprintf("     0 |%s", buffer[0]));
+  for (j = 0, k = 2; j < 64; j++, k += 4)
+    buffer[0][j] = '-';
+  STAT_OUT(tprintf("       +%s", buffer[0]));
+  STAT_OUT(tprintf("        0%31s%32d", "|", 255));
+}
+
+#undef STAT_OUT
+
+
+/*
+ * Utility Routines - Migration
+ */
+
+static void
+migrate_sort(void)
+{
+  int j, k;
+  chunk_reference_t *t;
+
+  for (j = 1; j < m_count; j++) {
+    t = m_references[j];
+    for (k = j; k--;) {
+      if (m_references[k][0] < t[0])
+       break;
+      m_references[k + 1] = m_references[k];
+    }
+    m_references[k + 1] = t;
+  }
+}
+
+/** Slide an allocated chunk over into a neighboring free space.
+ * \param region the region of the free space.
+ * \param offset the offset of the free space.
+ * \param which the index (in the migration arrays) of the chunk to move.
+ */
+static void
+migrate_slide(u_int_16 region, u_int_16 offset, int which)
+{
+  Region *rp = regions + region;
+  u_int_16 o_len, len, next, other, prev, o_off, o_oth;
+
+  debug_log("migrate_slide %d (%08x) to %04x%04x",
+           which, m_references[which][0], region, offset);
+
+  bring_in_region(region);
+
+  len = ChunkFullLen(region, offset);
+  next = ChunkNextFree(region, offset);
+  other = ChunkReferenceToOffset(m_references[which][0]);
+  o_len = ChunkFullLen(region, other);
+
+  o_off = offset;
+  o_oth = other;
+  if (other > offset) {
+    memmove(ChunkPointer(region, offset), ChunkPointer(region, other), o_len);
+#ifdef DEBUG_CHUNK_MIGRATE
+    do_rawlog(LT_TRACE, "CHUNK: Sliding chunk %08x to %04x%04x",
+             m_references[which][0], region, offset);
+#endif
+    m_references[which][0] = ChunkReference(region, offset);
+    other = offset + o_len;
+  } else {
+    prev = offset + len - o_len;
+    memmove(ChunkPointer(region, prev), ChunkPointer(region, other), o_len);
+#ifdef DEBUG_CHUNK_MIGRATE
+    do_rawlog(LT_TRACE, "CHUNK: Sliding chunk %08x to %04x%04x",
+             m_references[which][0], region, prev);
+#endif
+    m_references[which][0] = ChunkReference(region, prev);
+  }
+  write_free_chunk(region, other, len, next);
+  coalesce_frees(region, other);
+  if (rp->in_memory->first_free == offset) {
+    rp->in_memory->first_free = other;
+  } else {
+    prev = rp->in_memory->first_free;
+    while (prev && ChunkNextFree(region, prev) != offset)
+      prev = ChunkNextFree(region, prev);
+    write_next_free(region, prev, other);
+    coalesce_frees(region, prev);
+  }
+
+  stat_migrate_slide++;
+
+#ifdef CHUNK_PARANOID
+  if (!region_is_valid(region)) {
+    do_rawlog(LT_TRACE, "Invalid region after migrate_slide!");
+    do_rawlog(LT_TRACE, "Was moving %04x%04x to %04x%04x (became %08x)...",
+             region, o_oth, region, o_off, m_references[which][0]);
+    do_rawlog(LT_TRACE, "Chunk length %04x into hole length %04x", o_len, len);
+    debug_dump_region(region, tracelog_fp);
+    dump_debug_log(tracelog_fp);
+    mush_panic("Invalid region after migrate_slide!");
+  }
+#endif
+}
+
+/** Move an allocated chunk into a free hole.
+ * \param region the region of the free space.
+ * \param offset the offset of the free space.
+ * \param align the alignment to use: 0 = easiest, 1 = left, 2 = right.
+ * \param which the index (in the migration arrays) of the chunk to move.
+ */
+static void
+migrate_move(u_int_16 region, u_int_16 offset, int which, int align)
+{
+  Region *rp = regions + region;
+  u_int_16 s_reg, s_off, s_len, o_off, length;
+  Region *srp;
+
+  debug_log("migrate_move %d (%08x) to %04x%04x, alignment %d",
+           which, m_references[which][0], region, offset, align);
+
+  s_reg = ChunkReferenceToRegion(m_references[which][0]);
+  s_off = ChunkReferenceToOffset(m_references[which][0]);
+  srp = regions + s_reg;
+
+  bring_in_region(region);
+  if (!srp->in_memory) {
+    touch_cache_region(rp->in_memory);
+    bring_in_region(s_reg);
+    touch_cache_region(rp->in_memory);
+  }
+
+  s_len = ChunkFullLen(s_reg, s_off);
+  length = ChunkFullLen(region, offset);
+
+  if (s_reg == region && (s_off + s_len == offset || offset + length == s_off)) {
+    migrate_slide(region, offset, which);
+    return;
+  }
+#ifdef CHUNK_PARANOID
+  if (!FitsInSpace(s_len, ChunkFullLen(region, offset))) {
+    dump_debug_log(tracelog_fp);
+    mush_panicf("Trying to migrate into too small a hole: %04x into %04x!",
+               s_len, length);
+  }
+#endif
+
+  o_off = offset;
+  offset = split_hole(region, offset, s_len, align);
+  memcpy(ChunkPointer(region, offset), ChunkPointer(s_reg, s_off), s_len);
+#ifdef DEBUG_CHUNK_MIGRATE
+  do_rawlog(LT_TRACE, "CHUNK: moving chunk %08x to %04x%04x",
+           m_references[which][0], region, offset);
+#endif
+  m_references[which][0] = ChunkReference(region, offset);
+  rp->total_derefs += ChunkDerefs(region, offset);
+  free_chunk(s_reg, s_off);
+
+  stat_migrate_move++;
+
+#ifdef CHUNK_PARANOID
+  if (!region_is_valid(region)) {
+    do_rawlog(LT_TRACE, "Invalid region after migrate_move!");
+    do_rawlog(LT_TRACE, "Was moving %04x%04x to %04x%04x (became %04x%04x)...",
+             s_reg, s_off, region, o_off, region, offset);
+    do_rawlog(LT_TRACE, "Chunk length %04x into hole length %04x, alignment %d",
+             s_len, length, align);
+    debug_dump_region(region, tracelog_fp);
+    mush_panic("Invalid region after migrate_move!");
+  }
+#endif
+}
+
+static void
+migrate_region(u_int_16 region)
+{
+  chunk_reference_t high, low;
+  int j, derefs;
+  u_int_16 offset, length, best_region, best_offset;
+
+  bring_in_region(region);
+
+  high = ChunkReference(region, REGION_SIZE);
+  low = ChunkReference(region, 0);
+
+  for (j = 0; j < m_count; j++) {
+    if (low < m_references[j][0] && m_references[j][0] < high) {
+      offset = ChunkReferenceToOffset(m_references[j][0]);
+      derefs = ChunkDerefs(region, offset);
+      length = ChunkFullLen(region, offset);
+      best_region = find_best_region(length, derefs, region);
+      best_offset = find_best_offset(length, best_region, region, offset);
+      if (best_offset)
+       migrate_move(best_region, best_offset, j, 1);
+      if (best_region != region)
+       stat_migrate_away++;
+    }
+  }
+  migrate_sort();
+}
+
+
+
+/*
+ * Interface routines
+ */
+/** Allocate a chunk of storage.
+ * \param data the data to be stored.
+ * \param len the length of the data to be stored.
+ * \param derefs the deref count to set on the chunk.
+ * \return the chunk reference for retrieving (or deleting) the data.
+ */
+chunk_reference_t
+chunk_create(unsigned char const *data, u_int_16 len, unsigned char derefs)
+{
+  u_int_16 full_len, region, offset;
+
+  if (len < MIN_CHUNK_LEN || len > MAX_CHUNK_LEN)
+    mush_panicf("Illegal chunk length requested: %d bytes", len);
+
+  full_len = LenToFullLen(len);
+  region = find_best_region(full_len, derefs, INVALID_REGION_ID);
+  offset = find_best_offset(full_len, region, INVALID_REGION_ID, 0);
+  if (!offset) {
+    region = create_region();
+#ifdef DEBUG_CHUNK_REGION_CREATE
+    do_rawlog(LT_TRACE, "chunk_create created region %04x", region);
+#endif
+    offset = FIRST_CHUNK_OFFSET_IN_REGION;
+  }
+  offset = split_hole(region, offset, full_len, 0);
+  write_used_chunk(region, offset, full_len, data, len, derefs);
+  regions[region].total_derefs += derefs;
+  touch_cache_region(regions[region].in_memory);
+#ifdef CHUNK_PARANOID
+  if (!region_is_valid(region))
+    mush_panic("Invalid region after chunk_create!");
+#endif
+  stat_create++;
+  return ChunkReference(region, offset);
+}
+
+/** Deallocate a chunk of storage.
+ * \param reference the reference to the chunk to be freed.
+ */
+void
+chunk_delete(chunk_reference_t reference)
+{
+  u_int_16 region, offset;
+  region = ChunkReferenceToRegion(reference);
+  offset = ChunkReferenceToOffset(reference);
+  ASSERT(region < region_count);
+  bring_in_region(region);
+#ifdef CHUNK_PARANOID
+  verify_used_chunk(region, offset);
+#endif
+  free_chunk(region, offset);
+  touch_cache_region(regions[region].in_memory);
+#ifdef CHUNK_PARANOID
+  if (!region_is_valid(region))
+    mush_panic("Invalid region after chunk_delete!");
+#endif
+  stat_delete++;
+}
+
+/** Fetch a chunk of data.
+ * If the chunk is too large to fit in the supplied buffer, then
+ * the buffer will be left untouched.  The length of the data is
+ * returned regardless; this can be used to resize the buffer
+ * (or just as information for further processing of the data).
+ * \param reference the reference to the chunk to be fetched.
+ * \param buffer the buffer to put the data into.
+ * \param buffer_len the length of the buffer.
+ * \return the length of the data.
+ */
+u_int_16
+chunk_fetch(chunk_reference_t reference,
+           unsigned char *buffer, u_int_16 buffer_len)
+{
+  u_int_16 region, offset, len;
+  region = ChunkReferenceToRegion(reference);
+  offset = ChunkReferenceToOffset(reference);
+  ASSERT(region < region_count);
+  bring_in_region(region);
+#ifdef CHUNK_PARANOID
+  verify_used_chunk(region, offset);
+#endif
+  len = ChunkLen(region, offset);
+  if (len <= buffer_len)
+    memcpy(buffer, ChunkDataPtr(region, offset), len);
+  touch_cache_region(regions[region].in_memory);
+  stat_deref_count++;
+  if (ChunkDerefs(region, offset) < CHUNK_DEREF_MAX) {
+    ChunkDerefs(region, offset)++;
+    regions[region].total_derefs++;
+    if (ChunkDerefs(region, offset) == CHUNK_DEREF_MAX)
+      stat_deref_maxxed++;
+  }
+  return len;
+}
+
+/** Get the length of a chunk.
+ * This is equivalent to calling chunk_fetch(reference, NULL, 0).
+ * It can be used to glean the proper size for a buffer to actually
+ * retrieve the data, if you're being stingy.
+ * \param reference the reference to the chunk to be queried.
+ * \return the length of the data.
+ */
+u_int_16
+chunk_len(chunk_reference_t reference)
+{
+  return chunk_fetch(reference, NULL, 0);
+}
+
+/** Get the deref count of a chunk.
+ * This can be used to preserve the deref count across database saves
+ * or similar save and restore operations.
+ * \param reference the reference to the chunk to be queried.
+ * \return the deref count for data.
+ */
+unsigned char
+chunk_derefs(chunk_reference_t reference)
+{
+  u_int_16 region, offset;
+  region = ChunkReferenceToRegion(reference);
+  offset = ChunkReferenceToOffset(reference);
+  ASSERT(region < region_count);
+  bring_in_region(region);
+#ifdef CHUNK_PARANOID
+  verify_used_chunk(region, offset);
+#endif
+  return ChunkDerefs(region, offset);
+}
+
+/** Migrate allocated chunks around.
+ * 
+ * \param count the number of chunks to move.
+ * \param references an array of pointers to chunk references,
+ * which will be updated in place if necessary.
+ */
+void
+chunk_migration(int count, chunk_reference_t ** references)
+{
+  int k, l;
+  unsigned total;
+  u_int_16 region, offset;
+
+  debug_log("*** chunk_migration starts, count = %d", count);
+
+  /* Before everything, see if we need a new period. */
+  total = 0;
+  for (region = 0; region < region_count; region++) {
+    if (RegionDerefs(region) > (CHUNK_DEREF_MAX / 2))
+      total++;
+  }
+  if (total > cached_region_count || total > region_count / 2)
+    chunk_new_period();
+
+  m_count = count;
+  m_references = references;
+  migrate_sort();
+
+  /* Go through each of the regions. */
+  for (region = 0; region < region_count; region++) {
+    /* Make sure we have something to migrate, in the region. */
+    for (k = 0; k < m_count; k++)
+      if (ChunkReferenceToRegion(m_references[k][0]) == region)
+       break;
+    if (k >= m_count)
+      continue;
+
+    if (!regions[region].in_memory) {
+      /* If not in memory, see if we've got an oddball. */
+      while (k < m_count) {
+       if (ChunkReferenceToRegion(m_references[k][0]) != region)
+         break;
+       offset = ChunkReferenceToOffset(m_references[k][0]);
+       for (l = 0; l < NUM_ODDBALLS; l++) {
+         if (regions[region].oddballs[l] == offset) {
+           /* Yup, have an oddball... that's worth bringing it in. */
+           bring_in_region(region);
+           goto do_migrate;
+         }
+       }
+       k++;
+      }
+    } else {
+    do_migrate:
+      /* It's in memory, so migrate it. */
+      migrate_region(region);
+    }
+  }
+
+  m_references = NULL;
+  m_count = 0;
+
+  debug_log("*** chunk_migration ends", count);
+}
+
+/** Get the number of paged regions.
+ * Since the memory allocator cannot be reliably accessed from
+ * multiple processes if any of the chunks have been swapped out
+ * to disk, it's useful to check on the number of paged out regions
+ * before doing any forking maneuvers.
+ * \return the number of regions pages out.
+ */
+int
+chunk_num_swapped(void)
+{
+  int count;
+  u_int_16 region;
+  count = 0;
+  for (region = 0; region < region_count; region++)
+    if (!regions[region].in_memory)
+      count++;
+  return count;
+}
+
+/** Initialize chunk subsystem.
+ * Nothing to see here... just call it before using the subsystem.
+ */
+void
+chunk_init(void)
+{
+  /* In any case, this assert should be in main code, not here */
+  ASSERT(BUFFER_LEN <= MAX_LONG_CHUNK_LEN);
+
+#ifdef WIN32
+  swap_fd = CreateFile(CHUNK_SWAP_FILE, GENERIC_READ | GENERIC_WRITE,
+                      0, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
+  if (swap_fd == INVALID_HANDLE_VALUE)
+    mush_panicf("Cannot open swap file: %d", GetLastError());
+#else
+  swap_fd = open(CHUNK_SWAP_FILE, O_RDWR | O_TRUNC | O_CREAT, 0600);
+  if (swap_fd < 0)
+    mush_panicf("Cannot open swap file: %s", strerror(errno));
+#endif
+  curr_period = 0;
+
+  cached_region_count = 0;
+  cache_head = NULL;
+  cache_tail = NULL;
+
+  region_count = 0;
+  region_array_len = FIXME_INIT_REGION_LEN;
+#ifdef DEBUG_CHUNK_MALLOC
+  do_rawlog(LT_TRACE, "CHUNK: malloc()ing initial region array");
+#endif
+  regions = mush_malloc(region_array_len * sizeof(Region), "chunk region list");
+  if (!regions)
+    mush_panic("cannot malloc space for chunk region list");
+
+  /*
+  command_add("@DEBUGCHUNK", CMD_T_ANY | CMD_T_GOD, 0, 0, 0,
+              switchmask("ALL BRIEF FULL"), cmd_debugchunk);
+             */
+  do_rawlog(LT_TRACE, "CHUNK: chunk subsystem initialized");
+}
+
+/** Report statistics.
+ * Display either the statistics summary or one of the histograms.
+ * \param player the player to display it to, or NOTHING to log it.
+ * \param which what type of information to display.
+ */
+void
+chunk_stats(dbref player, enum chunk_stats_type which)
+{
+  switch (which) {
+  case CSTATS_SUMMARY:
+    chunk_statistics(player);
+    break;
+  case CSTATS_REGIONG:
+    chunk_histogram(player, chunk_regionhist(),
+                   "Chart number of regions (y) vs. references (x)");
+    break;
+  case CSTATS_PAGINGG:
+    chunk_histogram(player, stat_paging_histogram,
+                   "Chart pages in/out (y) vs. references (x)");
+    break;
+  case CSTATS_FREESPACEG:
+    chunk_histogram(player, chunk_freehist(),
+                   "Chart region free space (y) vs. references (x)");
+    break;
+  case CSTATS_REGION:
+    chunk_region_statistics(player);
+    break;
+  case CSTATS_PAGING:
+    chunk_page_stats(player);
+    break;
+  }
+}
+
+/** Start a new migration period.
+ * This chops all the dereference counts in half.  Since this is called
+ * from migration as needed, you probably shouldn't bother calling it
+ * yourself.
+ */
+void
+chunk_new_period(void)
+{
+  RegionHeader *rhp;
+  Region *rp;
+  u_int_16 region, offset;
+  int shift;
+
+#ifdef LOG_CHUNK_STATS
+  /* Log stats */
+  chunk_statistics(NOTHING);
+#endif
+
+  /* Reset period info */
+  curr_period++;
+  stat_deref_count = 0;
+  stat_deref_maxxed = 0;
+  stat_migrate_slide = 0;
+  stat_migrate_move = 0;
+  stat_migrate_away = 0;
+  stat_create = 0;
+  stat_delete = 0;
+
+  /* make derefs current */
+  for (rhp = cache_head; rhp; rhp = rhp->next) {
+    region = rhp->region_id;
+    if (region == INVALID_REGION_ID)
+      continue;
+    rp = regions + region;
+
+    shift = curr_period - rp->period_last_touched;
+    if (shift > 8) {
+      rp->total_derefs = 0;
+      for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+          offset < REGION_SIZE; offset += ChunkFullLen(region, offset)) {
+       ChunkDerefs(region, offset) = 0;
+      }
+    } else {
+      rp->total_derefs = 0;
+      for (offset = FIRST_CHUNK_OFFSET_IN_REGION;
+          offset < REGION_SIZE; offset += ChunkFullLen(region, offset)) {
+       if (ChunkIsFree(region, offset))
+         continue;
+       ChunkDerefs(region, offset) >>= shift;
+       rp->total_derefs += ChunkDerefs(region, offset);
+      }
+    }
+    rp->period_last_touched = curr_period;
+  }
+}
+
+#ifndef WIN32
+/** Clone the chunkswap file for forking dumps.
+ * \retval 0 if unable to clone the swap file
+ * \retval 1 if swap file clone succeeded
+ */
+int
+chunk_fork_file(void)
+{
+  unsigned int j;
+  RegionHeader *rhp, *prev, *next;
+
+  /* abort if already cloned */
+  if (swap_fd_child >= 0)
+    return 0;
+
+  j = 0;
+  for (;;) {
+    sprintf(child_filename, "%s.%d", CHUNK_SWAP_FILE, j);
+    swap_fd_child = open(child_filename, O_RDWR | O_EXCL | O_CREAT, 0600);
+    if (swap_fd_child >= 0)
+      break;
+    if (j >= 10)
+      return 0;
+    j++;
+  }
+
+  rhp = find_available_cache_region();
+  prev = rhp->prev;
+  next = rhp->next;
+  for (j = 0; j < region_count; j++) {
+    if (regions[j].in_memory)
+      continue;
+
+    read_cache_region(swap_fd, rhp, j);
+    write_cache_region(swap_fd_child, rhp, j);
+  }
+  rhp->region_id = INVALID_REGION_ID;
+  rhp->prev = prev;
+  rhp->next = next;
+
+  return 1;
+}
+
+/** Assert that we're the parent after fork.
+ */
+void
+chunk_fork_parent(void)
+{
+  if (swap_fd_child < 0)
+    return;
+
+  close(swap_fd_child);
+  swap_fd_child = -1;
+}
+
+/** Assert that we're the child after fork.
+ */
+void
+chunk_fork_child(void)
+{
+  if (swap_fd_child < 0)
+    return;
+
+  close(swap_fd);
+  swap_fd = swap_fd_child;
+  swap_fd_child = -1;
+}
+
+/** Assert that we're done with the cloned chunkswap file.
+ */
+void
+chunk_fork_done(void)
+{
+  if (swap_fd_child < 0)
+    close(swap_fd);
+  else
+    close(swap_fd_child);
+
+  unlink(child_filename);
+  swap_fd_child = -1;
+}
+#endif                         /* !WIN32 */
diff --git a/src/cmdlocal.dst b/src/cmdlocal.dst
new file mode 100644 (file)
index 0000000..1fe28a4
--- /dev/null
@@ -0,0 +1,80 @@
+/*-----------------------------------------------------------------
+ * Local stuff
+ *
+ * This file contains custom stuff, and some of the items here are
+ * called from within PennMUSH at specific times.
+ */
+
+/* Here are some includes you're likely to need or want.
+ */
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "parse.h"
+#include "htab.h"
+#include "flags.h"
+#include "command.h"
+#include "cmds.h"
+#include "confmagic.h"
+
+extern HASHTAB htab_reserved_aliases;
+
+/* Called during the command init sequence before any commands are
+ * added (including local_commands, below). This is where you
+ * want to reserve any strings that you *don't* want any command
+ * to alias to (because you want to preserve it for matching exits
+ * or globals)
+ */
+void
+reserve_aliases()
+{
+#ifdef EXAMPLE
+  /* Example: Don't alias any commands to cardinal directions.
+   * Remove the #ifdef EXAMPLE and #endif to use this code
+   */
+  reserve_alias("W");
+  reserve_alias("E");
+  reserve_alias("S");
+  reserve_alias("N");
+  reserve_alias("NW");
+  reserve_alias("NE");
+  reserve_alias("SW");
+  reserve_alias("SE");
+  reserve_alias("U");
+  reserve_alias("D");
+#endif
+}
+
+#ifdef EXAMPLE
+COMMAND (cmd_local_silly) {
+  if (SW_ISSET(sw, SWITCH_NOISY))
+    notify_format(player, "Noisy silly with %s", arg_left);
+  notify_format(player, "SillyCommand %s", arg_left);
+}
+#endif
+
+
+/* Called during the command init sequence.
+ * This is where you'd put calls to command_add to insert a local
+ * command into the command hash table. Any command you add here
+ * will be auto-aliased for you.
+ * The way to call command_add is illustrated below. The arguments are:
+ *   Name of the command, a string ("@SILLY")
+ *   Command parsing modifiers, a bitmask (see hdrs/command.h)
+ *   Flags to restrict command to, a string ("TRUST") or NULL
+ *     (Someone with *any* one of these flags can use the command)
+ *   Powers to restrict command to, a string ("Site Privilege") or NULL
+ *     (Someone with this power can use the command)
+ *   Switches the command can take, a string or NULL ("NOISY NOEVAL")
+ *   Hardcoded function the command should call (cmd_local_silly)
+ */
+void
+local_commands()
+{
+#ifdef EXAMPLE
+  command_add("@SILLY", CMD_T_ANY, NULL, NULL, "NOISY NOEVAL",
+             cmd_local_silly);
+#endif
+}
diff --git a/src/cmds.c b/src/cmds.c
new file mode 100644 (file)
index 0000000..8c9e2d0
--- /dev/null
@@ -0,0 +1,1145 @@
+/**
+ * \file cmds.c
+ *
+ * \brief Definitions of commands.
+ * This file is a set of functions that defines commands. The parsing
+ * of commands is elsewhere (command.c), as are the implementations
+ * of most of the commands (throughout the source.)
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <string.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "dbdefs.h"
+#include "mushdb.h"
+#include "match.h"
+#include "game.h"
+#include "attrib.h"
+#include "extmail.h"
+#include "malias.h"
+#include "getpgsiz.h"
+#include "parse.h"
+#include "access.h"
+#include "version.h"
+#include "lock.h"
+#include "function.h"
+#include "command.h"
+#include "flags.h"
+#include "log.h"
+#include "confmagic.h"
+
+/* External Stuff */
+void do_poor(dbref player, char *arg1);
+void do_list_memstats(dbref player);
+
+#define DOL_MAP 1              /**< dolist/map bitflag */
+#define DOL_NOTIFY 2           /**< dolist/notify bitflag */
+#define DOL_DELIM 4            /**< dolist/delim bitflag */
+
+void do_dolist(dbref player, char *list, char *command,
+              dbref cause, unsigned int flags);
+void do_list(dbref player, char *arg, int lc);
+void do_writelog(dbref player, char *str, int ltype);
+void do_readcache(dbref player);
+void do_scan(dbref player, char *command, int flag);
+void do_uptime(dbref player, int mortal);
+extern int config_set(const char *opt, char *val, int source, int restrictions);
+
+/** Is there a right-hand side of the equal sign? From command.c */
+extern int rhs_present;
+
+
+COMMAND(cmd_zclone) {
+  dbref zone;
+
+  zone = parse_dbref(arg_left);
+
+  if(GoodObject(zone) && has_flag_by_name(zone, "ZCLONE_OK", TYPE_THING)) {
+    copy_zone(player, zone);
+    notify(player, "Zone Copied.");
+  } else notify(player, "Zone is not Zclone_Ok.");
+}
+
+COMMAND (cmd_allhalt) {
+  do_allhalt(player);
+}
+
+COMMAND (cmd_allquota) {
+  do_allquota(player, arg_left, SW_ISSET(sw, SWITCH_QUIET));
+}
+
+COMMAND (cmd_atrlock) {
+  do_atrlock(player, arg_left, arg_right, SW_ISSET(sw, SWITCH_WRITE));
+}
+
+COMMAND (cmd_attribute) {
+  if (SW_ISSET(sw, SWITCH_ACCESS))
+    do_attribute_access(player, arg_left, arg_right,           
+       SW_ISSET(sw, SWITCH_RETROACTIVE));
+  else if(SW_ISSET(sw, SWITCH_LOCK)) {
+    if(SW_ISSET(sw, SWITCH_DEFAULTS)) 
+      do_attribute_lock(player, NULL, arg_left, sw);
+    else 
+      do_attribute_lock(player, arg_left, arg_right, sw);
+  }
+  else if (SW_ISSET(sw, SWITCH_DELETE))
+    do_attribute_delete(player, arg_left, SW_ISSET(sw, SWITCH_DEFAULTS));
+  else if (SW_ISSET(sw, SWITCH_RENAME))
+    do_attribute_rename(player, arg_left, arg_right);
+  else
+    do_attribute_info(player, arg_left);
+}
+
+COMMAND (cmd_atrchown) {
+  do_atrchown(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_boot) {
+  if (SW_ISSET(sw, SWITCH_PORT))
+    do_boot(player, arg_left, BOOT_DESC);
+  else if (SW_ISSET(sw, SWITCH_ME))
+    do_boot(player, (char *) NULL, BOOT_SELF);
+  else
+    do_boot(player, arg_left, BOOT_NAME);
+}
+
+/** Has the break command been called? */
+
+COMMAND (cmd_break) {
+  if (parse_boolean(arg_left)) {
+    global_eval_context.break_called = 1;
+    if (arg_right && *arg_right) {
+      char const *sp = arg_right;
+      char *bp = global_eval_context.break_replace;
+      process_expression(global_eval_context.break_replace, &bp, &sp,
+                       player, player, cause,
+                       PE_COMMAND_BRACES, PT_DEFAULT, NULL);
+      *bp++ = '\0';
+    }
+  }
+}
+
+COMMAND (cmd_assert) {
+  if (!parse_boolean(arg_left)) {
+    global_eval_context.break_called = 1;
+    if (arg_right && *arg_right) {
+      char const *sp = arg_right;
+      char *bp = global_eval_context.break_replace;
+      process_expression(global_eval_context.break_replace, &bp, &sp,
+                       player, player, cause,
+                       PE_COMMAND_BRACES, PT_DEFAULT, NULL);
+      *bp++ = '\0';
+    }
+  }
+}
+
+
+COMMAND (cmd_chownall) {
+  do_chownall(player, arg_left, arg_right, SW_ISSET(sw, SWITCH_PRESERVE));
+}
+
+COMMAND (cmd_chown) {
+  do_chown(player, arg_left, arg_right, SW_ISSET(sw, SWITCH_PRESERVE));
+}
+
+COMMAND (cmd_chzoneall) {
+  do_chzoneall(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_chzone) {
+  (void) do_chzone(player, arg_left, arg_right, 1);
+}
+
+COMMAND (cmd_config) {
+  int lc;
+  lc = SW_ISSET(sw, SWITCH_LOWERCASE);
+  if (SW_ISSET(sw, SWITCH_GLOBALS))
+    do_config_list(player, NULL, lc);
+  else if (SW_ISSET(sw, SWITCH_DEFAULTS))
+    do_config_list(player, NULL, lc);
+  else if (SW_ISSET(sw, SWITCH_COSTS))
+    do_config_list(player, NULL, lc);
+  else if (SW_ISSET(sw, SWITCH_FUNCTIONS))
+    do_list_functions(player, lc);
+  else if (SW_ISSET(sw, SWITCH_COMMANDS))
+    do_list_commands(player, lc);
+  else if (SW_ISSET(sw, SWITCH_ATTRIBS))
+    do_list_attribs(player, lc);
+  else if (SW_ISSET(sw, SWITCH_LIST))
+    do_config_list(player, arg_left, lc);
+  else if (SW_ISSET(sw, SWITCH_FLAGS))
+    do_list_flags(player, arg_left, lc);
+  else if (SW_ISSET(sw, SWITCH_POWERS)) /* TODO: Make it so you can throw wildcards in here & what not */
+    do_list_powers(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_SET)) {
+    if (!Site(player)) {
+      notify(player, T("You can't remake the world in your image."));
+      return;
+    }
+    if (!arg_left || !*arg_left) {
+      notify(player, T("What did you want to set?"));
+      return;
+    }
+    if (!config_set(arg_left, arg_right, 1, 0)
+       && !config_set(arg_left, arg_right, 1, 1))
+      notify(player, T("Couldn't set that option"));
+    else
+      notify(player, T("Option set."));
+  } else
+    do_config_list(player, arg_left, lc);
+}
+
+COMMAND (cmd_cpattr) {
+  do_cpattr(player, arg_left, args_right, 0, SW_ISSET(sw, SWITCH_NOFLAGCOPY));
+}
+
+COMMAND (cmd_create) {
+  do_create(player, arg_left, parse_integer(arg_right));
+}
+
+COMMAND (cmd_clone) {
+  if (SW_ISSET(sw, SWITCH_PRESERVE))
+    do_clone(player, arg_left, arg_right, SWITCH_PRESERVE);
+  else
+    do_clone(player, arg_left, arg_right, SWITCH_NONE);
+}
+
+COMMAND (cmd_dbck) {
+  do_dbck(player);
+}
+
+COMMAND (cmd_decompile) {
+  if (SW_ISSET(sw, SWITCH_DB))
+    do_decompile(player, arg_left, DEC_DB, SW_ISSET(sw, SWITCH_SKIPDEFAULTS));
+  else if (SW_ISSET(sw, SWITCH_TF))
+    do_decompile(player, arg_left, DEC_TF, SW_ISSET(sw, SWITCH_SKIPDEFAULTS));
+  else if (SW_ISSET(sw, SWITCH_FLAGS))
+    do_decompile(player, arg_left, DEC_FLAG, SW_ISSET(sw, SWITCH_SKIPDEFAULTS));
+  else if (SW_ISSET(sw, SWITCH_ATTRIBS))
+    do_decompile(player, arg_left, DEC_ATTR, SW_ISSET(sw, SWITCH_SKIPDEFAULTS));
+  else
+    do_decompile(player, arg_left, DEC_NORMAL,
+                SW_ISSET(sw, SWITCH_SKIPDEFAULTS));
+}
+
+COMMAND (cmd_teach) {
+  do_teach(player, cause, arg_left);
+}
+
+COMMAND (cmd_destroy) {
+  do_destroy(player, arg_left, (SW_ISSET(sw, SWITCH_OVERRIDE)));
+}
+
+COMMAND (cmd_dig) {
+  do_dig(player, arg_left, args_right, (SW_ISSET(sw, SWITCH_TELEPORT)));
+}
+
+COMMAND (cmd_division) {
+  dbref target;
+
+  if(SW_ISSET(sw,SWITCH_CREATE)) {
+    create_div(player,arg_left);
+    return;
+  }
+
+  target = noisy_match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
+  if(target == NOTHING)
+    return;
+  division_set(player, target, arg_right);
+}
+
+COMMAND (cmd_level) {
+  dbref target;
+
+  target = noisy_match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
+
+  if(target == NOTHING)
+    return;
+
+  if(arg_right) {
+    if(division_level(player, target, atoi(arg_right)) > 0) {
+      notify_format(player, "Level set to: %d", LEVEL(target));
+      notify_format(target, "GAME: Level set to '%d'", LEVEL(target));
+    } else {
+      notify(player, "Permission Denied.");
+    }
+  } else {
+    notify(player, "Must supply level.");
+  }
+}
+
+COMMAND (cmd_empower) {
+  dbref target;
+
+  target = noisy_match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
+
+  if(target == NOTHING)
+    return;
+  division_empower(player, target, arg_right);
+}
+
+COMMAND (cmd_disable) {
+  do_enable(player, arg_left, 0);
+}
+
+COMMAND (cmd_doing) {
+  if (SW_ISSET(sw, SWITCH_HEADER))
+    do_poll(player, arg_left);
+  else
+    do_doing(player, arg_left);
+}
+
+COMMAND (cmd_dolist) {
+  unsigned int flags = 0;
+  if (SW_ISSET(sw, SWITCH_NOTIFY))
+    flags |= DOL_NOTIFY;
+  if (SW_ISSET(sw, SWITCH_DELIMIT))
+    flags |= DOL_DELIM;
+  do_dolist(player, arg_left, arg_right, cause, flags);
+}
+
+COMMAND (cmd_dump) {
+  if (SW_ISSET(sw, SWITCH_PARANOID))
+    do_dump(player, arg_left, DUMP_PARANOID);
+  else if (SW_ISSET(sw, SWITCH_DEBUG))
+    do_dump(player, arg_left, DUMP_DEBUG);
+  else
+    do_dump(player, arg_left, DUMP_NORMAL);
+}
+
+COMMAND (cmd_edit) {
+  do_gedit(player, arg_left, args_right,
+          SW_ISSET(sw, SWITCH_FIRST) ? EDIT_FIRST : EDIT_ALL,
+          SW_ISSET(sw, SWITCH_CHECK) ? 0 : 1);
+}
+
+COMMAND (cmd_elock) {
+  do_lock(player, arg_left, arg_right, Enter_Lock);
+}
+
+COMMAND (cmd_emit) {
+  int spflags = !strcmp(cmd->name, "@NSEMIT") ? PEMIT_SPOOF : 0;
+  SPOOF(player, cause, sw);
+
+  if (SW_ISSET(sw, SWITCH_ROOM)) {
+    do_lemit(player, arg_left,
+            (SW_ISSET(sw, SWITCH_SILENT) * PEMIT_SILENT) | spflags);
+  } else {
+    if(Admin(player) || !RPMODE(player) || Can_RPEMIT(player))
+           do_emit(player, arg_left, spflags);
+    else notify(player, "You can't do that in RPMODE.");
+  }
+}
+
+COMMAND (cmd_enable) {
+  do_enable(player, arg_left, 1);
+}
+
+COMMAND (cmd_entrances) {
+  if (SW_ISSET(sw, SWITCH_EXITS))
+    do_entrances(player, arg_left, args_right, ENT_EXITS);
+  else if (SW_ISSET(sw, SWITCH_THINGS))
+    do_entrances(player, arg_left, args_right, ENT_THINGS);
+  else if (SW_ISSET(sw, SWITCH_PLAYERS))
+    do_entrances(player, arg_left, args_right, ENT_PLAYERS);
+  else if (SW_ISSET(sw, SWITCH_ROOMS))
+    do_entrances(player, arg_left, args_right, ENT_ROOMS);
+  else
+    do_entrances(player, arg_left, args_right, ENT_ALL);
+}
+
+COMMAND (cmd_eunlock) {
+  do_unlock(player, arg_left, Enter_Lock);
+}
+
+COMMAND (cmd_find) {
+  do_find(player, arg_left, args_right);
+}
+
+COMMAND (cmd_firstexit) {
+  do_firstexit(player, arg_left);
+}
+
+COMMAND (cmd_flag) {
+  if (SW_ISSET(sw, SWITCH_LIST))
+    do_list_flags(player, arg_left, 0);
+  else if (SW_ISSET(sw, SWITCH_ADD))
+    do_flag_add(player, arg_left, args_right);
+  else if (SW_ISSET(sw, SWITCH_DELETE))
+    do_flag_delete(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_ALIAS))
+    do_flag_alias(player, arg_left, args_right[1]);
+  else if (SW_ISSET(sw, SWITCH_RESTRICT))
+    do_flag_restrict(player, arg_left, args_right);
+  else if (SW_ISSET(sw, SWITCH_DISABLE))
+    do_flag_disable(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_ENABLE))
+    do_flag_enable(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_LETTER))
+    do_flag_letter(player, arg_left, args_right[1]);
+  else if (SW_ISSET(sw, SWITCH_TYPE))
+    do_flag_type("FLAG", player, arg_left, args_right[1]);
+  else
+    do_flag_info("FLAG", player, arg_left);
+}
+
+COMMAND (cmd_force) {
+  do_force(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_function) {
+  if (SW_ISSET(sw, SWITCH_DELETE))
+    do_function_delete(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_ENABLE))
+    do_function_toggle(player, arg_left, 1);
+  else if (SW_ISSET(sw, SWITCH_DISABLE))
+    do_function_toggle(player, arg_left, 0);
+  else if (SW_ISSET(sw, SWITCH_RESTRICT))
+    do_function_restrict(player, arg_left, args_right[1]);
+  else if (SW_ISSET(sw, SWITCH_RESTORE))
+    do_function_restore(player, arg_left);
+  else {
+    int split;
+    char *saved;
+    split = 0;
+    saved = NULL;
+    if (args_right[1] && *args_right[1] && !(args_right[2] && *args_right[2])) {
+      split = 1;
+      saved = args_right[2];
+      if ((args_right[2] = strchr(args_right[1], '/')) == NULL) {
+       notify(player, T("#-1 INVALID SECOND ARGUMENT"));
+       return;
+      }
+      *args_right[2]++ = '\0';
+    }
+    if (args_right[1] && *args_right[1])
+      do_function(player, arg_left, args_right);
+    else if (arg_left && *arg_left)
+      do_function_report(player, arg_left);
+    else
+      do_function(player, NULL, NULL);
+    if (split) {
+      if (args_right[2])
+       *--args_right[2] = '/';
+      args_right[2] = saved;
+    }
+  }
+}
+
+COMMAND (cmd_grep) {
+  do_grep(player, arg_left, arg_right, ((SW_ISSET(sw, SWITCH_IPRINT))
+                                       || (SW_ISSET(sw, SWITCH_PRINT))),
+         ((SW_ISSET(sw, SWITCH_IPRINT))
+          || (SW_ISSET(sw, SWITCH_ILIST))));
+}
+
+COMMAND (cmd_halt) {
+  if (SW_ISSET(sw, SWITCH_ALL))
+    do_allhalt(player);
+  else
+    do_halt1(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_hide) {
+  hide_player(player, !(SW_ISSET(sw, SWITCH_NO) || SW_ISSET(sw, SWITCH_OFF)));
+}
+
+COMMAND (cmd_hook) {
+  enum hook_type flags;
+
+  if (SW_ISSET(sw, SWITCH_AFTER))
+    flags = HOOK_AFTER;
+  else if (SW_ISSET(sw, SWITCH_BEFORE))
+    flags = HOOK_BEFORE;
+  else if (SW_ISSET(sw, SWITCH_IGNORE))
+    flags = HOOK_IGNORE;
+  else if (SW_ISSET(sw, SWITCH_OVERRIDE))
+    flags = HOOK_OVERRIDE;
+  else {
+    notify(player, T("You must give a switch for @hook"));
+    return;
+  }
+  do_hook(player, arg_left, args_right[1], args_right[2], flags);
+}
+
+COMMAND (cmd_huh_command) {
+    notify(player, MSG_HUH);
+}
+
+COMMAND (cmd_kick) {
+  do_kick(player, arg_left);
+}
+
+COMMAND (cmd_lemit) {
+  int spflags = !strcmp(cmd->name, "@NSLEMIT") ? PEMIT_SPOOF : 0;
+  SPOOF(player, cause, sw);
+  do_lemit(player, arg_left,
+          (SW_ISSET(sw, SWITCH_SILENT) * PEMIT_SILENT) | spflags);
+}
+
+COMMAND (cmd_link) {
+  do_link(player, arg_left, arg_right, SW_ISSET(sw, SWITCH_PRESERVE));
+}
+
+COMMAND (cmd_listmotd) {
+  do_motd(player, MOTD_LIST, "");
+}
+
+COMMAND (cmd_list) {
+  int lc;
+  lc = SW_ISSET(sw, SWITCH_LOWERCASE);
+  if (SW_ISSET(sw, SWITCH_MOTD))
+    do_motd(player, MOTD_LIST, "");
+  else if (SW_ISSET(sw, SWITCH_FUNCTIONS))
+    do_list_functions(player, lc);
+  else if (SW_ISSET(sw, SWITCH_COMMANDS))
+    do_list_commands(player, lc);
+  else if (SW_ISSET(sw, SWITCH_ATTRIBS))
+    do_list_attribs(player, lc);
+  else if (SW_ISSET(sw, SWITCH_LOCKS))
+    do_list_locks(player, arg_left, lc, T("Locks"));
+  else if (SW_ISSET(sw, SWITCH_FLAGS))
+    do_list_flags(player, arg_left, lc);
+  /* TODO: Throw wildcards into this */
+  else if (SW_ISSET(sw, SWITCH_POWERS))
+    do_list_powers(player, arg_left);
+  else
+    do_list(player, arg_left, lc);
+}
+
+COMMAND (cmd_lock) {
+  if ((switches) && (switches[0]))
+    do_lock(player, arg_left, arg_right, switches);
+  else
+    do_lock(player, arg_left, arg_right, Basic_Lock);
+}
+
+COMMAND (cmd_log) {
+  if (SW_ISSET(sw, SWITCH_CHECK))
+    do_writelog(player, arg_left, LT_CHECK);
+  else if (SW_ISSET(sw, SWITCH_CMD))
+    do_writelog(player, arg_left, LT_CMD);
+  else if (SW_ISSET(sw, SWITCH_CONN))
+    do_writelog(player, arg_left, LT_CONN);
+  else if (SW_ISSET(sw, SWITCH_ERR))
+    do_writelog(player, arg_left, LT_ERR);
+  else if (SW_ISSET(sw, SWITCH_TRACE))
+    do_writelog(player, arg_left, LT_TRACE);
+  else if (SW_ISSET(sw, SWITCH_WIZ))
+    do_writelog(player, arg_left, LT_WIZ);
+  else
+    do_writelog(player, arg_left, LT_CMD);
+}
+
+COMMAND (cmd_logwipe) {
+  if (SW_ISSET(sw, SWITCH_CHECK))
+    do_logwipe(player, LT_CHECK, arg_left);
+  else if (SW_ISSET(sw, SWITCH_CMD))
+    do_logwipe(player, LT_CMD, arg_left);
+  else if (SW_ISSET(sw, SWITCH_CONN))
+    do_logwipe(player, LT_CONN, arg_left);
+  else if (SW_ISSET(sw, SWITCH_TRACE))
+    do_logwipe(player, LT_TRACE, arg_left);
+  else if (SW_ISSET(sw, SWITCH_WIZ))
+    do_logwipe(player, LT_WIZ, arg_left);
+  else
+    do_logwipe(player, LT_ERR, arg_left);
+}
+
+COMMAND (cmd_lset) {
+  do_lset(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_mail) {
+  int urgent = SW_ISSET(sw, SWITCH_URGENT);
+  int silent = SW_ISSET(sw, SWITCH_SILENT);
+  int nosig = SW_ISSET(sw, SWITCH_NOSIG);
+  /* First, mail commands that can be used even if you're gagged */
+  if (SW_ISSET(sw, SWITCH_STATS))
+    do_mail_stats(player, arg_left, MSTATS_COUNT);
+  else if (SW_ISSET(sw, SWITCH_DSTATS))
+    do_mail_stats(player, arg_left, MSTATS_READ);
+  else if (SW_ISSET(sw, SWITCH_FSTATS))
+    do_mail_stats(player, arg_left, MSTATS_SIZE);
+  else if (SW_ISSET(sw, SWITCH_DEBUG))
+    do_mail_debug(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_NUKE))
+    do_mail_nuke(player);
+  else if (SW_ISSET(sw, SWITCH_FOLDERS))
+    do_mail_change_folder(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_UNFOLDER))
+    do_mail_unfolder(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_LIST))
+    do_mail_list(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_READ))
+    do_mail_read(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_CLEAR))
+    do_mail_clear(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_UNCLEAR))
+    do_mail_unclear(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_PURGE))
+    do_mail_purge(player);
+  else if (SW_ISSET(sw, SWITCH_FILE))
+    do_mail_file(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_TAG))
+    do_mail_tag(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_UNTAG))
+    do_mail_untag(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_FWD) || SW_ISSET(sw, SWITCH_FORWARD)
+          || SW_ISSET(sw, SWITCH_SEND) || silent || urgent || nosig) {
+    /* These commands are not allowed to gagged players */
+    if (Gagged(player)) {
+      notify(player, T("You cannot do that while gagged."));
+      return;
+    }
+    if (SW_ISSET(sw, SWITCH_FWD))
+      do_mail_fwd(player, arg_left, arg_right);
+    else if (SW_ISSET(sw, SWITCH_FORWARD))
+      do_mail_fwd(player, arg_left, arg_right);
+    else if (SW_ISSET(sw, SWITCH_SEND) || silent || urgent || nosig)
+      do_mail_send(player, arg_left, arg_right,
+                  urgent ? M_URGENT : 0, silent, nosig);
+  } else
+    do_mail(player, arg_left, arg_right);      /* Does its own gagged check */
+}
+
+
+COMMAND (cmd_malias) {
+  if (SW_ISSET(sw, SWITCH_LIST))
+    do_malias_list(player);
+  else if (SW_ISSET(sw, SWITCH_ALL))
+    do_malias_all(player);
+  else if (SW_ISSET(sw, SWITCH_MEMBERS) || SW_ISSET(sw, SWITCH_WHO))
+    do_malias_members(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_CREATE))
+    do_malias_create(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_SET))
+    do_malias_set(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_DESTROY))
+    do_malias_destroy(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_ADD))
+    do_malias_add(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_REMOVE))
+    do_malias_remove(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_DESCRIBE))
+    do_malias_desc(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_RENAME))
+    do_malias_rename(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_STATS))
+    do_malias_stats(player);
+  else if (SW_ISSET(sw, SWITCH_CHOWN))
+    do_malias_chown(player, arg_left, arg_right);
+  else if (SW_ISSET(sw, SWITCH_USEFLAG))
+    do_malias_privs(player, arg_left, arg_right, 0);
+  else if (SW_ISSET(sw, SWITCH_SEEFLAG))
+    do_malias_privs(player, arg_left, arg_right, 1);
+  else if (SW_ISSET(sw, SWITCH_NUKE))
+    do_malias_nuke(player);
+  else
+    do_malias(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_map) {
+  unsigned int flags = DOL_MAP;
+  if (SW_ISSET(sw, SWITCH_DELIMIT))
+    flags |= DOL_DELIM;
+  do_dolist(player, arg_left, arg_right, cause, flags);
+}
+
+COMMAND (cmd_motd) {
+  if (SW_ISSET(sw, SWITCH_CONNECT))
+    do_motd(player, MOTD_MOTD, arg_left);
+  else if (SW_ISSET(sw, SWITCH_LIST))
+    do_motd(player, MOTD_LIST, "");
+  else if (SW_ISSET(sw, SWITCH_DOWN))
+    do_motd(player, MOTD_DOWN, arg_left);
+  else if (SW_ISSET(sw, SWITCH_FULL))
+    do_motd(player, MOTD_FULL, arg_left);
+  else
+    do_motd(player, MOTD_MOTD, arg_left);
+}
+
+COMMAND (cmd_mvattr) {
+  do_cpattr(player, arg_left, args_right, 1, SW_ISSET(sw, SWITCH_NOFLAGCOPY));
+}
+
+COMMAND (cmd_name) {
+  do_name(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_newpassword) {
+  do_newpassword(player, cause, arg_left, arg_right);
+}
+
+COMMAND (cmd_nuke) {
+  do_destroy(player, arg_left, 1);
+}
+
+COMMAND (cmd_oemit) {
+  int spflags = !strcmp(cmd->name, "@NSOEMIT") ? PEMIT_SPOOF : 0;
+  SPOOF(player, cause, sw);
+  do_oemit_list(player, arg_left, arg_right, spflags);
+}
+
+COMMAND (cmd_open) {
+  do_open(player, arg_left, args_right);
+}
+
+COMMAND (cmd_parent) {
+  do_parent(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_password) {
+  do_password(player, cause, arg_left, arg_right);
+}
+
+COMMAND (cmd_pcreate) {
+  do_pcreate(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_pemit) {
+  int flags;
+
+  SPOOF(player, cause, sw);
+  if (SW_ISSET(sw, SWITCH_SILENT))
+    flags = PEMIT_SILENT;
+  else if (SW_ISSET(sw, SWITCH_NOISY))
+    flags = 0;
+  else
+    flags = SILENT_PEMIT ? PEMIT_SILENT : 0;
+
+  if (!strcmp(cmd->name, "@NSPEMIT"))
+    flags |= PEMIT_SPOOF;
+  if (SW_ISSET(sw, SWITCH_LIST))
+    do_pemit_list(player, arg_left, arg_right, flags);
+  else if (SW_ISSET(sw, SWITCH_CONTENTS))
+    do_remit(player, arg_left, arg_right, flags);
+  else
+    do_pemit(player, arg_left, arg_right, flags);
+}
+
+COMMAND (cmd_poll) {
+  do_poll(player, arg_left);
+}
+
+COMMAND (cmd_poor) {
+  do_poor(player, arg_left);
+}
+
+COMMAND (cmd_ps) {
+  if (SW_ISSET(sw, SWITCH_ALL))
+    do_queue(player, arg_left, QUEUE_ALL);
+  else if (SW_ISSET(sw, SWITCH_SUMMARY) || SW_ISSET(sw, SWITCH_COUNT))
+    do_queue(player, arg_left, QUEUE_SUMMARY);
+  else if (SW_ISSET(sw, SWITCH_QUICK))
+    do_queue(player, arg_left, QUEUE_QUICK);
+  else
+    do_queue(player, arg_left, QUEUE_NORMAL);
+}
+
+COMMAND (cmd_purge) {
+  do_purge(player);
+}
+
+COMMAND (cmd_quota) {
+  if (SW_ISSET(sw, SWITCH_ALL))
+    do_allquota(player, arg_left, (SW_ISSET(sw, SWITCH_QUIET)));
+  else if (SW_ISSET(sw, SWITCH_SET))
+    do_quota(player, arg_left, arg_right, 1);
+  else
+    do_quota(player, arg_left, "", 0);
+}
+
+COMMAND (cmd_readcache) {
+  do_readcache(player);
+}
+
+COMMAND (cmd_remit) {
+  int flags;
+  SPOOF(player, cause, sw);
+  if (SW_ISSET(sw, SWITCH_SILENT))
+    flags = PEMIT_SILENT;
+  else if (SW_ISSET(sw, SWITCH_NOISY))
+    flags = 0;
+  else
+    flags = SILENT_PEMIT ? PEMIT_SILENT : 0;
+  if (SW_ISSET(sw, SWITCH_LIST))
+    flags |= PEMIT_LIST;
+  if (!strcmp(cmd->name, "@NSREMIT"))
+    flags |= PEMIT_SPOOF;
+  do_remit(player, arg_left, arg_right, flags);
+}
+
+COMMAND (cmd_rejectmotd) {
+  do_motd(player, MOTD_DOWN, arg_left);
+}
+
+COMMAND (cmd_restart) {
+  if (SW_ISSET(sw, SWITCH_ALL))
+    do_allrestart(player);
+  else
+    do_restart_com(player, arg_left);
+}
+
+COMMAND (cmd_scan) {
+  if (SW_ISSET(sw, SWITCH_ROOM))
+    do_scan(player, arg_left, CHECK_NEIGHBORS | CHECK_HERE);
+  else if (SW_ISSET(sw, SWITCH_SELF))
+    do_scan(player, arg_left, CHECK_INVENTORY | CHECK_SELF);
+  else if (SW_ISSET(sw, SWITCH_ZONE))
+    do_scan(player, arg_left, CHECK_ZONE);
+  else if (SW_ISSET(sw, SWITCH_GLOBALS))
+    do_scan(player, arg_left, CHECK_GLOBAL);
+  else
+    do_scan(player, arg_left, CHECK_INVENTORY | CHECK_NEIGHBORS |
+           CHECK_SELF | CHECK_HERE | CHECK_ZONE | CHECK_GLOBAL);
+}
+
+COMMAND (cmd_search) {
+  do_search(player, arg_left, args_right);
+}
+
+COMMAND (cmd_select) {
+  do_switch(player, arg_left, args_right, cause, 1,
+           SW_ISSET(sw, SWITCH_NOTIFY), SW_ISSET(sw, SWITCH_REGEXP));
+}
+
+COMMAND (cmd_set) {
+  do_set(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_shutdown) {
+  enum shutdown_type paranoid;
+  paranoid = (SW_ISSET(sw, SWITCH_PARANOID)) ? SHUT_PARANOID : SHUT_NORMAL ;
+  if (SW_ISSET(sw, SWITCH_REBOOT))
+    do_reboot(player, paranoid == SHUT_PARANOID);
+  else if (SW_ISSET(sw, SWITCH_PANIC))
+    do_shutdown(player, SHUT_PANIC);
+  else
+    do_shutdown(player, paranoid);
+}
+
+COMMAND (cmd_sitelock) {
+  if (SW_ISSET(sw, SWITCH_BAN))
+    do_sitelock(player, arg_left, NULL, NULL, SITELOCK_BAN);
+  else if (SW_ISSET(sw, SWITCH_REGISTER))
+    do_sitelock(player, arg_left, NULL, NULL, SITELOCK_ADD);
+  else if (SW_ISSET(sw, SWITCH_NAME))
+    do_sitelock_name(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_REMOVE))
+    do_sitelock(player, arg_left, NULL, NULL, SITELOCK_REMOVE);
+  else if (SW_ISSET(sw, SWITCH_CHECK))
+    do_sitelock(player, arg_left, NULL, NULL, SITELOCK_CHECK);
+  else if (!arg_left || !*arg_left)
+    do_sitelock(player, NULL, NULL, NULL, SITELOCK_LIST);
+  else
+    do_sitelock(player, arg_left, args_right[1], args_right[2], SITELOCK_ADD);
+}
+
+COMMAND (cmd_stats) {
+  if (SW_ISSET(sw, SWITCH_TABLES))
+    do_list_memstats(player);
+  else if (SW_ISSET(sw, SWITCH_CHUNKS)) {
+    if (SW_ISSET(sw, SWITCH_REGIONS))
+      chunk_stats(player, CSTATS_REGION);
+    else
+      chunk_stats(player, CSTATS_SUMMARY);
+  } else if (SW_ISSET(sw, SWITCH_REGIONS))
+    chunk_stats(player, CSTATS_REGIONG);
+  else if (SW_ISSET(sw, SWITCH_PAGING))
+    chunk_stats(player, CSTATS_PAGINGG);
+  else if (SW_ISSET(sw, SWITCH_FREESPACE))
+    chunk_stats(player, CSTATS_FREESPACEG);
+  else
+    do_stats(player, arg_left);
+}
+
+COMMAND (cmd_sweep) {
+  if (SW_ISSET(sw, SWITCH_CONNECTED))
+    do_sweep(player, "connected");
+  else if (SW_ISSET(sw, SWITCH_HERE))
+    do_sweep(player, "here");
+  else if (SW_ISSET(sw, SWITCH_INVENTORY))
+    do_sweep(player, "inventory");
+  else if (SW_ISSET(sw, SWITCH_EXITS))
+    do_sweep(player, "exits");
+  else
+    do_sweep(player, arg_left);
+}
+
+COMMAND (cmd_switch) {
+  do_switch(player, arg_left, args_right, cause, SW_ISSET(sw, SWITCH_FIRST),
+           SW_ISSET(sw, SWITCH_NOTIFY), SW_ISSET(sw, SWITCH_REGEXP));
+}
+
+COMMAND (cmd_squota) {
+  do_quota(player, arg_left, arg_right, 1);
+}
+
+
+COMMAND (cmd_teleport) {
+  if (rhs_present && !*arg_right)
+    notify(player, T("You can't teleport to nothing!"));
+  else
+    do_teleport(player, arg_left, arg_right, (SW_ISSET(sw, SWITCH_SILENT)),
+               (SW_ISSET(sw, SWITCH_INSIDE)));
+}
+
+COMMAND (cmd_trigger) {
+  do_trigger(player, arg_left, args_right);
+}
+
+COMMAND (cmd_ulock) {
+  do_lock(player, arg_left, arg_right, Use_Lock);
+}
+
+COMMAND (cmd_undestroy) {
+  do_undestroy(player, arg_left);
+}
+
+COMMAND (cmd_unlink) {
+  do_unlink(player, arg_left);
+}
+
+COMMAND (cmd_unlock) {
+  if ((switches) && (switches[0]))
+    do_unlock(player, arg_left, switches);
+  else
+    do_unlock(player, arg_left, Basic_Lock);
+}
+
+COMMAND (cmd_uptime) {
+  do_uptime(player, SW_ISSET(sw, SWITCH_MORTAL));
+}
+
+COMMAND (cmd_uunlock) {
+  do_unlock(player, arg_left, Use_Lock);
+}
+
+COMMAND (cmd_verb) {
+  do_verb(player, cause, arg_left, args_right);
+}
+
+COMMAND (cmd_version) {
+  do_version(player);
+}
+
+COMMAND (cmd_wait) {
+  int qid;
+
+  qid = do_wait(player, cause, arg_left, arg_right, SW_ISSET(sw, SWITCH_UNTIL), 0);
+  atr_add(player, "QID", tprintf("%d", qid), player, NOTHING); 
+}
+
+COMMAND (cmd_wall) {
+  do_wall(player, arg_left, WALL_ALL, SW_ISSET(sw, SWITCH_EMIT));
+}
+
+COMMAND (cmd_warnings) {
+  do_warnings(player, arg_left, arg_right);
+}
+
+COMMAND (cmd_wcheck) {
+  if (SW_ISSET(sw, SWITCH_ALL))
+    do_wcheck_all(player);
+  else if (SW_ISSET(sw, SWITCH_ME))
+    do_wcheck_me(player);
+  else
+    do_wcheck(player, arg_left);
+}
+
+COMMAND (cmd_whereis) {
+  do_whereis(player, arg_left);
+}
+
+COMMAND (cmd_wipe) {
+  do_wipe(player, arg_left);
+}
+
+COMMAND (cmd_zemit) {
+  int flags = !strcmp(cmd->name, "@NSZEMIT") ? PEMIT_SPOOF : 0;
+  do_zemit(player, arg_left, arg_right, flags);
+}
+
+COMMAND (cmd_brief) {
+  do_examine(player, arg_left, EXAM_BRIEF, 0);
+}
+
+COMMAND (cmd_drop) {
+  do_drop(player, arg_left);
+}
+
+COMMAND (cmd_examine) {
+  int all = SW_ISSET(sw, SWITCH_ALL);
+  if (SW_ISSET(sw, SWITCH_BRIEF))
+    do_examine(player, arg_left, EXAM_BRIEF, all);
+  else if (SW_ISSET(sw, SWITCH_DEBUG))
+    do_debug_examine(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_MORTAL))
+    do_examine(player, arg_left, EXAM_MORTAL, all);
+  else
+    do_examine(player, arg_left, EXAM_NORMAL, all);
+}
+
+COMMAND (cmd_empty) {
+  do_empty(player, arg_left);
+}
+
+COMMAND (cmd_enter) {
+  do_enter(player, arg_left);
+}
+
+COMMAND (cmd_dismiss) {
+  do_dismiss(player, arg_left);
+}
+
+COMMAND (cmd_desert) {
+  do_desert(player, arg_left);
+}
+
+COMMAND (cmd_follow) {
+  do_follow(player, arg_left);
+}
+
+COMMAND (cmd_unfollow) {
+  do_unfollow(player, arg_left);
+}
+
+COMMAND (cmd_get) {
+  do_get(player, arg_left);
+}
+
+COMMAND (cmd_buy) {
+  char *from = NULL;
+  char *forwhat = NULL;
+  int price = -1;
+
+  upcasestr(arg_left);
+
+  from = strstr(arg_left, " FROM ");
+  forwhat = strstr(arg_left, " FOR ");
+  if (from) {
+    *from = '\0';
+    from += 6;
+  }
+  if (forwhat) {
+    *forwhat = '\0';
+    forwhat += 5;
+  }
+  if (forwhat && !is_strict_integer(forwhat)) {
+    notify(player, T("Buy for WHAT price?"));
+    return;
+  } else if (forwhat) {
+    price = parse_integer(forwhat);
+    if (price < 0) {
+      notify(player, T("You can't buy things by taking money."));
+      return;
+    }
+  }
+
+  if (from)
+    from = trim_space_sep(from, ' ');
+  do_buy(player, arg_left, from, price);
+}
+
+COMMAND (cmd_give) {
+  do_give(player, arg_left, arg_right, (SW_ISSET(sw, SWITCH_SILENT)));
+}
+
+COMMAND (cmd_goto) {
+  move_wrapper(player, arg_left);
+}
+
+COMMAND (cmd_inventory) {
+  do_inventory(player);
+}
+
+COMMAND (cmd_look) {
+  do_look_at(player, arg_left, (SW_ISSET(sw, SWITCH_OUTSIDE)));
+}
+
+COMMAND (cmd_leave) {
+  do_leave(player);
+}
+
+COMMAND (cmd_page) {
+  char *t;
+  if ((arg_right) && (*arg_right)) {
+    t = arg_right;
+  } else {
+    t = NULL;
+  }
+  if (SW_ISSET(sw, SWITCH_PORT))
+    do_page_port(player, arg_left, t);
+  else
+    do_page(player, arg_left, t, cause, SW_ISSET(sw, SWITCH_NOEVAL),
+           !(SW_ISSET(sw, SWITCH_BLIND) ||
+             (!(SW_ISSET(sw, SWITCH_LIST)) && (BLIND_PAGE))),
+           SW_ISSET(sw, SWITCH_OVERRIDE));
+}
+
+COMMAND (cmd_pose) {
+  do_pose(player, arg_left, (SW_ISSET(sw, SWITCH_NOSPACE)));
+}
+
+COMMAND (cmd_say) {
+  do_say(player, arg_left);
+}
+
+COMMAND (cmd_score) {
+  do_score(player);
+}
+
+COMMAND (cmd_semipose) {
+  do_pose(player, arg_left, 1);
+}
+
+COMMAND (cmd_take) {
+  do_get(player, arg_left);
+}
+
+COMMAND (cmd_think) {
+  do_think(player, arg_left);
+}
+
+COMMAND (cmd_whisper) {
+  do_whisper(player, arg_left, arg_right,
+            (SW_ISSET(sw, SWITCH_NOISY) ||
+             (!SW_ISSET(sw, SWITCH_SILENT) && NOISY_WHISPER)));
+}
+
+COMMAND (cmd_use) {
+  do_use(player, arg_left);
+}
+
+COMMAND (command_atrset) {
+  dbref thing;
+
+  if(!GoodObject((thing = match_thing(player, arg_left))))
+    return;
+
+  /* If it's &attr obj, we must pass a NULL. If &attr obj=, pass "" */
+  if (rhs_present) {
+    do_set_atr(thing, switches, arg_right, player,
+              0x1 | (SW_ISSET(sw, SWITCH_NOEVAL) ? 0 : 0x02));
+  } else {
+    do_set_atr(thing, switches, NULL, player, 1);
+  }
+}
+
+COMMAND (cmd_null) {
+  return;
+}
+
+COMMAND (cmd_warn_on_missing) {
+  notify_format(Owner(player),
+               T
+               ("No command found in code by %s - don't start code with functions."),
+               unparse_dbref(player));
+  return;
+}
diff --git a/src/command.c b/src/command.c
new file mode 100644 (file)
index 0000000..ab316da
--- /dev/null
@@ -0,0 +1,1925 @@
+/**
+ * \file command.c
+
+ *
+ * \brief Parsing of commands.
+ *
+ * Sets up a hash table for the commands, and parses input for commands.
+ * This implementation is almost totally by Thorvald Natvig, with
+ * various mods by Javelin and others over time.
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <string.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "dbdefs.h"
+#include "mushdb.h"
+#include "game.h"
+#include "match.h"
+#include "attrib.h"
+#include "extmail.h"
+#include "getpgsiz.h"
+#include "parse.h"
+#include "access.h"
+#include "version.h"
+#include "ptab.h"
+#include "htab.h"
+#include "function.h"
+#include "command.h"
+#include "mymalloc.h"
+#include "flags.h"
+#include "log.h"
+#include "cmds.h"
+#include "confmagic.h"
+
+PTAB ptab_command;     /**< Prefix table for command names. */
+PTAB ptab_command_perms;       /**< Prefix table for command permissions */
+
+HASHTAB htab_reserved_aliases; /**< Hash table for reserved command aliases */
+
+static const char *command_isattr(char *command);
+static int command_check(dbref player, COMMAND_INFO *cmd, switch_mask sw);
+static int switch_find(COMMAND_INFO *cmd, char *sw);
+static void strccat(char *buff, char **bp, const char *from);
+static int has_hook(struct hook_data *hook);
+extern int global_fun_invocations;     /**< Counter for function invocations */
+extern int global_fun_recursions;      /**< Counter for function recursion */
+
+int run_hook(dbref player, dbref cause, struct hook_data *hook,
+            char *saveregs[], int save);
+int command_lock(const char *name, const char *lock);
+
+/** The list of standard commands. Additional commands can be added
+ * at runtime with add_command().
+ */
+COMLIST commands[] = {
+
+  {"@COMMAND",
+   "ADD ALIAS DELETE EQSPLIT LOCK LSARGS RSARGS NOEVAL ON OFF QUIET ENABLE DISABLE RESTRICT",
+   cmd_command,
+   CMD_T_PLAYER | CMD_T_EQSPLIT, NULL },
+  {"@@", NULL, cmd_null, CMD_T_ANY | CMD_T_NOPARSE, NULL},
+  {"@ALLHALT", NULL, cmd_allhalt, CMD_T_ANY, "POWER^HALT"},
+  {"@ALLQUOTA", "QUIET", cmd_allquota, CMD_T_ANY, "POWER^CQUOTA"},
+  {"@ASSERT", NULL, cmd_assert, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, NULL},
+  {"@ATRLOCK", "READ WRITE", cmd_atrlock, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@ATRCHOWN", NULL, cmd_atrchown, CMD_T_EQSPLIT, NULL},
+  {"@ATTRIBUTE", "ACCESS DEFAULTS LOCK WRITE READ DELETE RENAME RETROACTIVE", cmd_attribute,
+   CMD_T_ANY | CMD_T_EQSPLIT, "POWER^GFUNCS"},
+  {"@BOOT", "PORT ME", cmd_boot, CMD_T_ANY, NULL},
+  {"@BREAK", NULL, cmd_break, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, NULL},
+#ifdef CHAT_SYSTEM
+  {"@CEMIT", "NOEVAL NOISY SPOOF", cmd_cemit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@CHANNEL",
+   "LIST ADD DELETE RENAME NAME PRIVS QUIET NOISY DECOMPILE DESCRIBE CHOWN WIPE MUTE UNMUTE GAG UNGAG HIDE UNHIDE WHAT TITLE BRIEF RECALL BUFFER SET OBJECT",
+   cmd_channel,
+   CMD_T_ANY | CMD_T_SWITCHES | CMD_T_EQSPLIT | CMD_T_NOGAGGED | CMD_T_RS_ARGS,
+   NULL},
+  {"@CHAT", NULL, cmd_chat, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+#endif /* CHAT_SYSTEM */
+  {"@CHOWNALL", "PRESERVE", cmd_chownall, CMD_T_ANY | CMD_T_EQSPLIT, "POWER^MODIFY"},
+
+  {"@CHOWN", "PRESERVE", cmd_chown,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@CHZONEALL", NULL, cmd_chzoneall, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+
+  {"@CHZONE", NULL, cmd_chzone,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+#ifdef CHAT_SYSTEM
+  {"@COBJ", "RESET", cmd_cobj, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED , NULL},
+#endif /* CHAT_SYSTEM */
+  {"@CONFIG",
+   "SET LOWERCASE LIST GLOBALS DEFAULTS COSTS FLAGS POWERS FUNCTIONS COMMANDS ATTRIBS",
+   cmd_config, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@CPATTR", "CONVERT", cmd_cpattr, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS,
+   NULL},
+  {"@CREATE", NULL, cmd_create, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   NULL},
+#ifdef MUSHCRON
+  {"@CRON", "ADD DELETE LIST COMMANDS FUNCTIONS", cmd_cron, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_LS_NOPARSE, "POWER^CRON"},
+#endif /* MUSHCRON */
+  {"@CLONE", "PRESERVE", cmd_clone, CMD_T_ANY | CMD_T_NOGAGGED | CMD_T_EQSPLIT,
+   NULL},
+#ifdef CHAT_SYSTEM
+  {"@CLOCK", "JOIN SPEAK MOD SEE HIDE", cmd_clock,
+   CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+#endif
+  {"@DBCK", NULL, cmd_dbck, CMD_T_ANY, "POWER^SITE"},
+
+  {"@DECOMPILE", "DB TF FLAGS ATTRIBS SKIPDEFAULTS", cmd_decompile,
+   CMD_T_ANY, NULL},
+
+  {"@DESTROY", "OVERRIDE", cmd_destroy, CMD_T_ANY, NULL},
+
+  {"@DIG", "TELEPORT", cmd_dig,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, "POWER^BUILDER"},
+  {"@DISABLE", NULL, cmd_disable, CMD_T_ANY, "POWER^SITE"},
+  {"@DIVISION", "CREATE", cmd_division, CMD_T_ANY | CMD_T_EQSPLIT, "(POWER^DIVISION|POWER^ATTACH)"},
+
+  {"@DOING", "HEADER", cmd_doing,
+   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_NOGAGGED, NULL},
+  {"@DOLIST", "NOTIFY DELIMIT", cmd_dolist,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, NULL},
+  {"@DRAIN", "ALL ANY", cmd_notify_drain, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@DUMP", "PARANOID DEBUG", cmd_dump, CMD_T_ANY, "POWER^SITE"},
+
+  {"@EDIT", NULL, cmd_edit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE |
+   CMD_T_NOGAGGED, NULL},
+  {"@ELOCK", NULL, cmd_elock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   NULL},
+  {"@EMIT", "ROOM NOEVAL SILENT SPOOF", cmd_emit, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@EMPOWER", NULL, cmd_empower, CMD_T_ANY | CMD_T_EQSPLIT, "POWER^EMPOWER"},
+  {"@ENABLE", NULL, cmd_enable, CMD_T_ANY | CMD_T_NOGAGGED, "POWER^SITE"},
+
+  {"@ENTRANCES", "EXITS THINGS PLAYERS ROOMS", cmd_entrances,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, NULL},
+  {"@EUNLOCK", NULL, cmd_eunlock, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+
+  {"@FIND", NULL, cmd_find,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, NULL},
+  {"@FIRSTEXIT", NULL, cmd_firstexit, CMD_T_ANY, NULL},
+  {"@FLAG", "ADD TYPE LETTER LIST RESTRICT DELETE ALIAS DISABLE ENABLE",
+   cmd_flag,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, NULL},
+
+  {"@FORCE", "NOEVAL", cmd_force, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   NULL},
+  {"@FUNCTION", "DELETE ENABLE DISABLE RESTORE RESTRICT", cmd_function,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, NULL},
+  {"@GREP", "LIST PRINT ILIST IPRINT", cmd_grep,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE | CMD_T_NOGAGGED, NULL},
+  {"@HALT", "ALL", cmd_halt, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@HIDE", "NO OFF YES ON", cmd_hide, CMD_T_ANY, NULL},
+  {"@HOOK", "AFTER BEFORE IGNORE OVERRIDE", cmd_hook, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS,
+   "POWER^SITE"},
+  {"@KICK", NULL, cmd_kick, CMD_T_ANY, "POWER^SITE"},
+
+  {"@LEMIT", "NOEVAL SILENT SPOOF", cmd_lemit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@LEVEL", NULL, cmd_level, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@LINK", "PRESERVE", cmd_link, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@LISTMOTD", NULL, cmd_listmotd, CMD_T_ANY, NULL},
+
+  {"@LIST", "LOWERCASE MOTD LOCKS FLAGS FUNCTIONS POWERS COMMANDS ATTRIBS",
+   cmd_list,
+   CMD_T_ANY, NULL},
+  {"@LOCK", NULL, cmd_lock,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_SWITCHES | CMD_T_NOGAGGED, NULL},
+  {"@LOG", "CHECK CMD CONN ERR TRACE WIZ", cmd_log,
+   CMD_T_ANY | CMD_T_NOGAGGED, "POWER^SITE"},
+  {"@LOGWIPE", "CHECK CMD CONN ERR TRACE WIZ", cmd_logwipe,
+   CMD_T_ANY | CMD_T_NOGAGGED | CMD_T_GOD, "POWER^SITE"},
+  {"@LSET", NULL, cmd_lset,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@MAIL",
+   "NOEVAL NOSIG STATS DSTATS FSTATS DEBUG NUKE FOLDERS UNFOLDER LIST READ CLEAR UNCLEAR PURGE FILE TAG UNTAG FWD FORWARD SEND SILENT URGENT",
+   cmd_mail, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@MALIAS",
+   "SET CREATE DESTROY DESCRIBE RENAME STATS CHOWN NUKE ADD REMOVE LIST ALL WHO MEMBERS USEFLAG SEEFLAG",
+   cmd_malias, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@MAP", "DELIMIT", cmd_map, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE,
+   NULL},
+  {"@MOTD", "CONNECT LIST DOWN FULL", cmd_motd,
+   CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@MVATTR", "CONVERT", cmd_mvattr, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS,
+   NULL},
+  {"@NAME", NULL, cmd_name, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED
+   | CMD_T_NOGUEST, NULL},
+  {"@NEWPASSWORD", NULL, cmd_newpassword, CMD_T_ANY | CMD_T_EQSPLIT
+   | CMD_T_RS_NOPARSE, "(POWER^NEWPASS|POWER^BCREATE)"},
+  {"@NOTIFY", "ALL ANY", cmd_notify_drain, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+#ifdef CHAT_SYSTEM
+  {"@NSCEMIT", "NOEVAL NOISY SPOOF", cmd_cemit,
+    CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "POWER^CAN_NSPEMIT"},
+#endif /* CHAT_SYSTEM */
+  {"@NSPEMIT", "LIST SILENY NOISY NOEVAL", cmd_pemit,
+   CMD_T_ANY | CMD_T_EQSPLIT, "POWER^CAN_NSPEMIT"},
+  {"@NSEMIT", "ROOM NOEVAL SILENT SPOOF", cmd_emit, CMD_T_ANY | CMD_T_NOGAGGED,
+    "POWER^CAN_NSPEMIT"},
+  {"@NSLEMIT", "NOEVAL SILENT SPOOF", cmd_lemit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "POWER^CAN_NSPEMIT"},
+  {"@NSOEMIT", "NOEVAL SPOOF", cmd_oemit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "POWER^CAN_NSPEMIT"},
+  {"@NSPEMIT", "LIST SILENT NOISY NOEVAL", cmd_pemit,
+   CMD_T_ANY | CMD_T_EQSPLIT, "POWER^CAN_NSPEMIT"},
+  {"@NSREMIT", "LIST NOEVAL NOISY SILENT SPOOF", cmd_remit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "POWER^CAN_NSPEMIT"},
+  {"@NSZEMIT", NULL, cmd_zemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   "POWER^CAN_NSPEMIT"},
+  {"@NUKE", NULL, cmd_nuke, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@OEMIT", "NOEVAL SPOOF", cmd_oemit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   NULL},
+  {"@OPEN", NULL, cmd_open,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, NULL},
+  {"@PARENT", NULL, cmd_parent, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@PASSWORD", NULL, cmd_password, CMD_T_PLAYER | CMD_T_EQSPLIT
+   | CMD_T_RS_NOPARSE | CMD_T_NOGUEST, NULL},
+  {"@PCREATE", NULL, cmd_pcreate, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+
+  {"@PEMIT", "LIST CONTENTS SILENT NOISY NOEVAL SPOOF", cmd_pemit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@POLL", NULL, cmd_poll, CMD_T_ANY, NULL},
+  {"@POOR", NULL, cmd_poor, CMD_T_ANY, NULL},
+  {"@POWER", "ALIAS LIST ADD DELETE", cmd_power, CMD_T_ANY | CMD_T_EQSPLIT , NULL},
+  {"@POWERGROUP", "AUTO MAX ADD DELETE LIST RAW", cmd_powergroup, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@PROGRAM", "LOCK QUIT", cmd_prog, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@PROMPT", NULL, cmd_prompt, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@PS", "ALL SUMMARY COUNT QUICK", cmd_ps, CMD_T_ANY, NULL},
+  {"@PURGE", NULL, cmd_purge, CMD_T_ANY,  "POWER^SITE"},
+  {"@QUOTA", "ALL SET", cmd_quota, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@READCACHE", NULL, cmd_readcache, CMD_T_ANY,  "(POWER^SITE|POWER^RCACHE)"},
+  {"@RECYCLE", "OVERRIDE", cmd_destroy, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+
+  {"@REMIT", "LIST NOEVAL NOISY SILENT SPOOF", cmd_remit,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@REJECTMOTD", NULL, cmd_rejectmotd, CMD_T_ANY, "POWER^SITE"},
+  {"@RESTART", "ALL", cmd_restart, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+#ifdef RPMODE_SYS
+  {"@CRPLOG", "QUIET RESET COMBAT", cmd_rplog, CMD_T_ANY, "POWER^COMBAT"},
+#endif
+  {"@SCAN", "ROOM SELF ZONE GLOBALS", cmd_scan,
+   CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@SD", "LOGOUT", cmd_su, CMD_T_ANY, NULL},
+  {"@SEARCH", NULL, cmd_search,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE, NULL},
+  {"@SELECT", "NOTIFY REGEXP", cmd_select,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE, NULL},
+  {"@SET", NULL, cmd_set, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@SHUTDOWN", "PANIC REBOOT PARANOID", cmd_shutdown, CMD_T_ANY, "POWER^Site"},
+#ifdef HAS_MYSQL
+  {"@SQL", NULL, cmd_sql, CMD_T_ANY, "POWER^SQL_OK"},
+#else
+  {"@SQL", NULL, cmd_unimplemented, CMD_T_ANY,  "POWER^SQL_OK"},
+#endif
+
+  {"@SITELOCK", "BAN CHECK REGISTER REMOVE NAME", cmd_sitelock,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, "POWER^SITE"},
+   {"@SNOOP", "LIST", cmd_snoop, CMD_T_ANY, "POWER^SITE"},
+  {"@STATS", "CHUNKS FREESPACE PAGING REGIONS TABLES", cmd_stats,
+   CMD_T_ANY, NULL},
+
+  {"@SWEEP", "CONNECTED HERE INVENTORY EXITS", cmd_sweep, CMD_T_ANY, NULL},
+  {"@SWITCH", "NOTIFY FIRST ALL REGEXP", cmd_switch,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE |
+   CMD_T_NOGAGGED, NULL},
+  {"@SQUOTA", NULL, cmd_squota, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@SU", NULL, cmd_su, CMD_T_ANY, NULL},
+  {"@TELEPORT", "SILENT INSIDE", cmd_teleport,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"@TRIGGER", NULL, cmd_trigger,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, NULL},
+  {"@ULOCK", NULL, cmd_ulock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   NULL},
+  {"@UNDESTROY", NULL, cmd_undestroy, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@UNLINK", NULL, cmd_unlink, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+
+  {"@UNLOCK", NULL, cmd_unlock,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_SWITCHES | CMD_T_NOGAGGED, NULL},
+  {"@UNRECYCLE", NULL, cmd_undestroy, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@UPTIME", "MORTAL", cmd_uptime, CMD_T_ANY, NULL},
+  {"@UUNLOCK", NULL, cmd_uunlock, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@VERB", NULL, cmd_verb, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, NULL},
+  {"@VERSION", NULL, cmd_version, CMD_T_ANY, NULL},
+  {"@WAIT", "UNTIL", cmd_wait, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE,
+   NULL},
+  {"@WALL", "NOEVAL EMIT", cmd_wall, CMD_T_ANY, "POWER^ANNOUNCE"},
+
+  {"@WARNINGS", NULL, cmd_warnings, CMD_T_ANY | CMD_T_EQSPLIT, NULL},
+  {"@WCHECK", "ALL ME", cmd_wcheck, CMD_T_ANY, NULL},
+  {"@WHEREIS", NULL, cmd_whereis, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"@WIPE", NULL, cmd_wipe, CMD_T_ANY, NULL},
+  {"@ZCLONE", NULL, cmd_zclone, CMD_T_ANY, "POWER^BUILDER"},
+  {"@ZEMIT", NULL, cmd_zemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED,
+   NULL},
+
+  {"BUY", NULL, cmd_buy, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"BRIEF", NULL, cmd_brief, CMD_T_ANY, NULL},
+  {"DESERT", NULL, cmd_desert, CMD_T_PLAYER | CMD_T_THING, NULL},
+  {"DISMISS", NULL, cmd_dismiss, CMD_T_PLAYER | CMD_T_THING, NULL},
+  {"DROP", NULL, cmd_drop, CMD_T_PLAYER | CMD_T_THING, NULL},
+  {"EXAMINE", "ALL BRIEF DEBUG MORTAL", cmd_examine, CMD_T_ANY, NULL},
+  {"EMPTY", NULL, cmd_empty, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, NULL},
+  {"ENTER", NULL, cmd_enter, CMD_T_ANY, NULL},
+
+  {"FOLLOW", NULL, cmd_follow,
+   CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, NULL},
+
+  {"GET", NULL, cmd_get, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, NULL},
+  {"GIVE", "SILENT", cmd_give, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"GOTO", NULL, cmd_goto, CMD_T_PLAYER | CMD_T_THING, NULL},
+  {"INVENTORY", NULL, cmd_inventory, CMD_T_ANY, NULL},
+
+  {"LOOK", "OUTSIDE", cmd_look, CMD_T_ANY, NULL},
+  {"LEAVE", NULL, cmd_leave, CMD_T_PLAYER | CMD_T_THING, NULL},
+
+  {"PAGE", "BLIND NOEVAL LIST PORT OVERRIDE", cmd_page,
+   CMD_T_ANY | CMD_T_RS_NOPARSE | CMD_T_NOPARSE | CMD_T_EQSPLIT |
+   CMD_T_NOGAGGED, NULL},
+  {"POSE", "NOEVAL NOSPACE", cmd_pose, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"SCORE", NULL, cmd_score, CMD_T_ANY, NULL},
+  {"SAY", "NOEVAL", cmd_say, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+  {"SEMIPOSE", "NOEVAL", cmd_semipose, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+
+  {"TAKE", NULL, cmd_take, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED,
+   NULL},
+  {"TEACH", NULL, cmd_teach, CMD_T_ANY | CMD_T_NOPARSE, NULL},
+  {"THINK", "NOEVAL", cmd_think, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+
+  {"UNFOLLOW", NULL, cmd_unfollow,
+   CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, NULL},
+  {"USE", NULL, cmd_use, CMD_T_ANY | CMD_T_NOGAGGED, NULL},
+
+  {"WHISPER", "LIST NOISY SILENT NOEVAL", cmd_whisper,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, NULL},
+  {"WITH", "NOEVAL ROOM", cmd_with, CMD_T_PLAYER | CMD_T_THING | CMD_T_EQSPLIT,
+   NULL},
+
+/* ATTRIB_SET is an undocumented command - it's sugar to make it possible
+ * enable/disable attribute setting with &XX or @XX
+ */
+
+  {"ATTRIB_SET", NULL, command_atrset,
+   CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED | CMD_T_INTERNAL, NULL},
+
+/* A way to stop people starting commands with functions */
+  {"WARN_ON_MISSING", NULL, cmd_warn_on_missing,
+   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_INTERNAL, NULL},
+/* A way to let people override the Huh? message */
+  {"HUH_COMMAND", NULL, cmd_huh_command,
+   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_INTERNAL, NULL},
+/* A way to let people override the unimplemented message */
+  {"UNIMPLEMENTED_COMMAND", NULL, cmd_unimplemented,
+   CMD_T_ANY | CMD_T_NOPARSE | CMD_T_INTERNAL, NULL},
+  {NULL, NULL, NULL, 0, NULL}, 
+};
+
+
+/* switch_list is defined in switchinc.c */
+#include "switchinc.c"
+
+/** Table of command permissions/restrictions. */
+struct command_perms_t command_perms[] = {
+  {"player", CMD_T_PLAYER},
+  {"thing", CMD_T_THING},
+  {"exit", CMD_T_EXIT},
+  {"room", CMD_T_ROOM},
+  {"any", CMD_T_ANY},
+  {"god", CMD_T_GOD},
+  {"nobody", CMD_T_DISABLED},
+  {"nogagged", CMD_T_NOGAGGED},
+  {"noguest", CMD_T_NOGUEST},
+  {"nofix", CMD_T_NOFIXED},
+  {"nofixed", CMD_T_NOFIXED},
+  {"norp", CMD_T_NORPMODE},
+  {"norpmode", CMD_T_NORPMODE},
+  {"logargs", CMD_T_LOGARGS},
+  {"logname", CMD_T_LOGNAME},
+#ifdef DANGEROUS
+  {"listed", CMD_T_LISTED},
+  {"switches", CMD_T_SWITCHES},
+  {"internal", CMD_T_INTERNAL},
+  {"ls_space", CMD_T_LS_SPACE},
+  {"ls_noparse", CMD_T_LS_NOPARSE},
+  {"rs_space", CMD_T_RS_SPACE},
+  {"rs_noparse", CMD_T_RS_NOPARSE},
+  {"eqsplit", CMD_T_EQSPLIT},
+  {"ls_args", CMD_T_LS_ARGS},
+  {"rs_args", CMD_T_RS_ARGS},
+#endif
+  {NULL, 0}
+};
+
+
+static void
+strccat(char *buff, char **bp, const char *from)
+{
+  if (*buff)
+    safe_str(", ", buff, bp);
+  safe_str(from, buff, bp);
+}
+static int
+switch_find(COMMAND_INFO *cmd, char *sw)
+       {
+  SWITCH_VALUE *sw_val;
+  int len;
+
+  if (!sw || !*sw)
+    return 0;
+  len = strlen(sw);
+  /* Special case, for init */
+  sw_val = switch_list;
+  if (!cmd) {
+    while (sw_val->name) {
+      if (strcmp(sw_val->name, sw) == 0)
+       return sw_val->value;
+      sw_val++;
+    }
+    return 0;
+  } else {
+    while (sw_val->name) {
+         if (SW_ISSET(cmd->sw, sw_val->value) && (strncmp(sw_val->name, sw, len) == 0))
+                 return sw_val->value;
+      sw_val++;
+    }
+  }
+  return 0;
+}
+
+/** Allocate and populate a COMMAND_INFO structure.
+ * This function generates a new COMMAND_INFO structure, populates it
+ * with given values, and returns a pointer. It should not be used
+ * for local hacks - use command_add() instead.
+ * \param name command name.
+ * \param type types of objects that can use the command.
+ * \param flagmask mask of flags (one is sufficient to use the command).
+ * \param powers mask of powers (one is sufficient to use the command).
+ * \param sw mask of switches the command accepts.
+ * \param func function to call when the command is executed.
+ * \return pointer to a newly allocated COMMAND_INFO structure.
+ */
+COMMAND_INFO *
+make_command(const char *name, int type, switch_mask *sw, command_func func, const char *command_lock)
+{
+  COMMAND_INFO *cmd;
+  cmd = (COMMAND_INFO *) mush_malloc(sizeof(COMMAND_INFO), "command");
+  memset(cmd, 0, sizeof(COMMAND_INFO));
+  cmd->name = name;
+  cmd->restrict_message = NULL;
+  cmd->func = func;
+  cmd->type = type;
+  cmd->lock = !command_lock ? TRUE_BOOLEXP : parse_boolexp(GOD, command_lock, "Command_Lock");
+  if (sw)
+    memcpy(cmd->sw, sw, sizeof(switch_mask));
+  else
+    SW_ZERO(cmd->sw);
+  cmd->hooks.before.obj = NOTHING;
+  cmd->hooks.before.attrname = NULL;
+  cmd->hooks.after.obj = NOTHING;
+  cmd->hooks.after.attrname = NULL;
+  cmd->hooks.ignore.obj = NOTHING;
+  cmd->hooks.ignore.attrname = NULL;
+  cmd->hooks.override.obj = NOTHING;
+  cmd->hooks.override.attrname = NULL;
+  return cmd;
+}
+
+/** Add a new command to the command table.
+ * This function is the top-level function for adding a new command.
+ * \param name name of the command.
+ * \param type types of objects that can use the command.
+ * \param switchstr space-separated list of switches for the command.
+ * \param func function to call when command is executed.
+ * \param command_lock is the lock boolexp string to lock command to
+ * \return pointer to a newly allocated COMMAND_INFO entry or NULL.
+ */
+COMMAND_INFO *
+command_add(const char *name, int type, const char *switchstr, command_func func, const char *command_lock)
+{
+  switch_mask *sw = switchmask(switchstr);
+
+  ptab_start_inserts(&ptab_command);
+  ptab_insert(&ptab_command, name,
+             make_command(name, type, sw, func, command_lock));
+  ptab_end_inserts(&ptab_command);
+  return command_find(name);
+}
+
+
+/** Search for a command by (partial) name.
+ * This function searches the command table for a (partial) name match
+ * and returns a pointer to the COMMAND_INFO entry. It returns NULL
+ * if the name given is a reserved alias or if no command table entry
+ * matches.
+ * \param name name of command to match.
+ * \return pointer to a COMMAND_INFO entry, or NULL.
+ */
+COMMAND_INFO *
+command_find(const char *name)
+{
+
+  char cmdname[BUFFER_LEN];
+  strcpy(cmdname, name);
+  upcasestr(cmdname);
+  if (hash_find(&htab_reserved_aliases, cmdname))
+    return NULL;
+  return (COMMAND_INFO *) ptab_find(&ptab_command, cmdname);
+}
+
+/** Search for a command by exact name.
+ * This function searches the command table for an exact name match
+ * and returns a pointer to the COMMAND_INFO entry. It returns NULL
+ * if the name given is a reserved alias or if no command table entry
+ * matches.
+ * \param name name of command to match.
+ * \return pointer to a COMMAND_INFO entry, or NULL.
+ */
+COMMAND_INFO *
+command_find_exact(const char *name)
+{
+
+  char cmdname[BUFFER_LEN];
+  strcpy(cmdname, name);
+  upcasestr(cmdname);
+  if (hash_find(&htab_reserved_aliases, cmdname))
+    return NULL;
+  return (COMMAND_INFO *) ptab_find_exact(&ptab_command, cmdname);
+}
+
+
+/** Modify a command's entry in the table.
+ * Given a command name and other parameters, look up the command
+ * in the table, and if it's there, modify the parameters.
+ * \param name name of command to modify.
+ * \param type new types for command, or -1 to leave unchanged.
+ * \param flagmask new mask of flags for command, or NULL to leave unchanged.
+ * \param powers new mask of powers for command, or -1 to leave unchanged.
+ * \param sw new mask of switches for command, or NULL to leave unchanged.
+ * \param func new function to call, or NULL to leave unchanged.
+ * \return pointer to modified command entry, or NULL.
+ */
+COMMAND_INFO *
+command_modify(const char *name, int type, const char *command_lock, switch_mask *sw, command_func func)
+{
+  COMMAND_INFO *cmd;
+  cmd = command_find(name);
+  if (!cmd)
+    return NULL;
+  if (type != -1)
+    cmd->type = type;
+  if (command_lock) {
+    if(cmd->lock != TRUE_BOOLEXP)
+      free_boolexp(cmd->lock);
+    cmd->lock = parse_boolexp(GOD, command_lock, "Command_Lock");
+  }
+  if (sw)
+    memcpy(cmd->sw, sw, sizeof(switch_mask));
+  if (func)
+    cmd->func = func;
+  return cmd;
+}
+
+/** Convert a switch string to a switch mask.
+ * Given a space-separated list of switches in string form, return
+ * a pointer to a static switch mask.
+ * \param switches list of switches as a string.
+ * \return pointer to a static switch mask.
+ */
+switch_mask *
+switchmask(const char *switches)
+{
+  static switch_mask sw;
+  char buff[BUFFER_LEN];
+  char *p, *s;
+  int switchnum;
+  SW_ZERO(sw);
+  if (!switches || !switches[0])
+    return NULL;
+  strcpy(buff, switches);
+  p = buff;
+  while (p) {
+    s = split_token(&p, ' ');
+    switchnum = switch_find(NULL, s);
+    if (!switchnum)
+      return NULL;
+    else
+      SW_SET(sw, switchnum);
+  }
+  return &sw;
+}
+
+/** Add an alias to the table of reserved aliases.
+ * This function adds an alias to the table of reserved aliases, preventing
+ * it from being matched for standard commands. It's typically used to
+ * insure that 'e' will match a global 'east;e' exit rather than the
+ * 'examine' command.
+ * \param a alias to reserve.
+ */
+void
+reserve_alias(const char *a)
+{
+  static char placeholder[2] = "x";
+  hashadd(strupper(a), (void *) placeholder, &htab_reserved_aliases);
+}
+
+/** Initialize command tables (before reading config file).
+ * This function performs command table initialization that should take place
+ * before the configuration file has been read. It initializes the
+ * command prefix table and the reserved alias table, inserts all of the
+ * commands from the commands array into the prefix table, initializes
+ * the command permission prefix table, and inserts all the permissions
+ * from the command_perms array into the prefix table. Finally, it
+ * calls local_commands() to do any cmdlocal.c work.
+ */
+void
+command_init_preconfig(void)
+{
+  struct command_perms_t *c;
+  COMLIST *cmd;
+  static int done = 0;
+  if (done == 1)
+    return;
+  done = 1;
+
+  ptab_init(&ptab_command);
+  hashinit(&htab_reserved_aliases, 16, sizeof(COMMAND_INFO));
+  reserve_aliases();
+  ptab_start_inserts(&ptab_command);
+  for (cmd = commands; cmd->name; cmd++) {
+    ptab_insert(&ptab_command, cmd->name,
+               make_command(cmd->name, cmd->type, switchmask(cmd->switches),
+                            cmd->func, cmd->command_lock));
+  }
+  ptab_end_inserts(&ptab_command);
+
+  ptab_init(&ptab_command_perms);
+  ptab_start_inserts(&ptab_command_perms);
+  for (c = command_perms; c->name; c++)
+    ptab_insert(&ptab_command_perms, c->name, c);
+  ptab_end_inserts(&ptab_command_perms);
+
+  local_commands();
+}
+
+/** Initialize commands (after reading config file).
+ * This function performs command initialization that should take place
+ * after the configuration file has been read.
+ * Currently, there isn't any.
+ */
+void
+command_init_postconfig(void)
+{
+  return;
+}
+
+
+/** Alias a command.
+ * Given a command name and an alias for it, install the alias.
+ * \param command canonical command name.
+ * \param alias alias to associate with command.
+ * \retval 0 failure (couldn't locate command).
+ * \retval 1 success.
+ */
+int
+alias_command(const char *command, const char *alias)
+{
+  COMMAND_INFO *cmd;
+
+  /* Make sure the alias doesn't exit already */
+  if (command_find_exact(alias))
+    return 0;
+
+  /* Look up the original */
+  cmd = command_find_exact(command);
+  if (!cmd)
+    return 0;
+
+  ptab_start_inserts(&ptab_command);
+  ptab_insert(&ptab_command, strupper(alias), cmd);
+  ptab_end_inserts(&ptab_command);
+  return 1;
+}
+
+/* This is set to true for EQ_SPLIT commands that actually have a rhs.
+ * Used in @teleport, ATTRSET and possibly other checks. It's ugly.
+ * Blame Talek for it. ;)
+ */
+int rhs_present;
+
+/** Parse the command arguments into arrays.
+ * This function does the real work of parsing command arguments into
+ * argument arrays. It is called separately to parse the left and
+ * right sides of commands that are split at equal signs.
+ * \param player the enactor.
+ * \param cause the dbref causing the execution.
+ * \param from pointer to address of where to parse arguments from.
+ * \param to string to store parsed arguments into.
+ * \param argv array of parsed arguments.
+ * \param cmd pointer to command data.
+ * \param right_side if true, parse on the right of the =. Otherwise, left.
+ * \param forcenoparse if true, do no evaluation during parsing.
+ */
+void
+command_argparse(dbref player, dbref realcause, dbref cause, char **from, char *to,
+                char *argv[], COMMAND_INFO *cmd, int right_side,
+                int forcenoparse)
+{
+  int parse, split, args, i, done;
+  char *t, *f;
+  char *aold;
+
+  f = *from;
+
+  parse =
+    (right_side) ? (cmd->type & CMD_T_RS_NOPARSE) : (cmd->type & CMD_T_NOPARSE);
+  if (parse || forcenoparse)
+    parse = PE_NOTHING;
+  else
+    parse = PE_DEFAULT | PE_COMMAND_BRACES;
+
+  if (right_side)
+    split = PT_NOTHING;
+  else
+    split = (cmd->type & CMD_T_EQSPLIT) ? PT_EQUALS : PT_NOTHING;
+
+  if (right_side) {
+    if (cmd->type & CMD_T_RS_ARGS)
+      args = (cmd->type & CMD_T_RS_SPACE) ? PT_SPACE : PT_COMMA;
+    else
+      args = 0;
+  } else {
+    if (cmd->type & CMD_T_LS_ARGS)
+      args = (cmd->type & CMD_T_LS_SPACE) ? PT_SPACE : PT_COMMA;
+    else
+      args = 0;
+  }
+
+  if ((parse == PE_NOTHING) && args)
+    parse = PE_COMMAND_BRACES;
+
+  i = 1;
+  done = 0;
+  *to = '\0';
+
+  if (args) {
+    t = to + 1;
+  } else {
+    t = to;
+  }
+
+  while (*f && !done) {
+    aold = t;
+    while (*f == ' ')
+      f++;
+    process_expression(to, &t, (const char **) &f, player, realcause, cause,
+                      parse, (split | args), NULL);
+    *t = '\0';
+    if (args) {
+      argv[i] = aold;
+      if (*f)
+       f++;
+      i++;
+      t++;
+      if (i == MAX_ARG)
+       done = 1;
+    }
+    if (split && (*f == '=')) {
+      rhs_present = 1;
+      f++;
+      *from = f;
+      done = 1;
+    }
+  }
+
+  *from = f;
+
+  if (args)
+    while (i < MAX_ARG)
+      argv[i++] = NULL;
+}
+
+/** Determine whether a command is an attribute to set an attribute.
+ * Is this command an attempt to set an attribute like @VA or &NUM?
+ * If so, return the attrib's name. Otherwise, return NULL
+ * \param command command string (first word of input).
+ * \return name of the attribute to be set, or NULL.
+ */
+static const char *
+command_isattr(char *command)
+{
+  ATTR *a;
+  char buff[BUFFER_LEN];
+  char *f, *t;
+
+  if (((command[0] == '&') && (command[1])) ||
+      ((command[0] == '@') && (command[1] == '_') && (command[2]))) {
+    /* User-defined attributes: @_NUM or &NUM */
+    if (command[0] == '@')
+      return command + 2;
+    else
+      return command + 1;
+  } else if (command[0] == '@') {
+    f = command + 1;
+    buff[0] = '@';
+    t = buff + 1;
+    while ((*f) && (*f != '/'))
+      *t++ = *f++;
+    *t = '\0';
+    /* @-commands have priority over @-attributes with the same name */
+    if (command_find(buff))
+      return NULL;
+    a = atr_match(buff + 1);
+    if (a)
+      return AL_NAME(a);
+  }
+  return NULL;
+}
+
+/** A handy macro to free up the command_parse-allocated variables */
+#define command_parse_free_args \
+    mush_free((Malloc_t) command, "string"); \
+    mush_free((Malloc_t) swtch, "string"); \
+    mush_free((Malloc_t) ls, "string"); \
+    mush_free((Malloc_t) rs, "string"); \
+    mush_free((Malloc_t) switches, "string")
+
+/** Parse commands.
+ * Parse the commands. This is the big one!
+ * We distinguish parsing of input sent from a player at a socket
+ * (in which case attribute values to set are not evaluated) and
+ * input sent in any other way (in which case attribute values to set
+ * are evaluated, and attributes are set NO_COMMAND).
+ * Return NULL if the command was recognized and handled, the evaluated
+ * text to match against $-commands otherwise.
+ * \param player the enactor.
+ * \param cause dbref that caused the command to be executed.
+ * \param string the input to be parsed.
+ * \param fromport if true, command was typed by a player at a socket.
+ * \return NULL if a command was handled, otherwise the evaluated input.
+ */
+char *
+command_parse(dbref player, dbref cause, dbref realcause, char *string, int fromport)
+{
+  char *command, *swtch, *ls, *rs, *switches;
+  static char commandraw[BUFFER_LEN];
+  static char exit_command[BUFFER_LEN], *ec;
+  char *lsa[MAX_ARG];
+  char *rsa[MAX_ARG];
+  char *ap, *swp;
+  const char *attrib, *replacer;
+  COMMAND_INFO *cmd;
+  char *p, *t, *c, *c2;
+  char command2[BUFFER_LEN];
+  char b;
+  int switchnum;
+  switch_mask sw;
+  int noeval;
+  int noevtoken = 0;
+  char *retval;
+
+  rhs_present = 0;
+
+  command = (char *) mush_malloc(BUFFER_LEN, "string");
+  swtch = (char *) mush_malloc(BUFFER_LEN, "string");
+  ls = (char *) mush_malloc(BUFFER_LEN, "string");
+  rs = (char *) mush_malloc(BUFFER_LEN, "string");
+  switches = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!command || !swtch || !ls || !rs || !switches)
+    mush_panic("Couldn't allocate memory in command_parse");
+  p = string;
+  replacer = NULL;
+  attrib = NULL;
+  cmd = NULL;
+  c = command;
+  /* All those one character commands.. Sigh */
+
+  global_fun_invocations = global_fun_recursions = 0;
+  if (*p == NOEVAL_TOKEN) {
+    noevtoken = 1;
+    p = string + 1;
+    string = p;
+    memmove(global_eval_context.ccom, (char *) global_eval_context.ccom + 1, BUFFER_LEN - 1);
+  }
+  if (*p == '[') {
+    if ((cmd = command_find("WARN_ON_MISSING"))) {
+      if (!(cmd->type & CMD_T_DISABLED)) {
+       cmd->func(cmd, player, cause, sw, string, NULL, NULL, ls, lsa, rs, rsa, fromport);
+       command_parse_free_args;
+       return NULL;
+      }
+    }
+  }
+  switch (*p) {
+  case '\0':
+    /* Just in case. You never know */
+    command_parse_free_args;
+    return NULL;
+  case SAY_TOKEN:
+    replacer = "SAY";
+    if (CHAT_STRIP_QUOTE)
+      p--;                     /* Since 'say' strips out the '"' */
+    break;
+  case POSE_TOKEN:
+    replacer = "POSE";
+    break;
+  case SEMI_POSE_TOKEN:
+    if (*(p + 1) && *(p + 1) == ' ')
+      replacer = "POSE";
+    else
+      replacer = "SEMIPOSE";
+    break;
+  case EMIT_TOKEN:
+    replacer = "@EMIT";
+    break;
+#ifdef CHAT_SYSTEM
+  case CHAT_TOKEN:
+#ifdef CHAT_TOKEN_ALIAS
+  case CHAT_TOKEN_ALIAS:
+#endif
+    /* parse_chat() destructively modifies the command to replace
+     * the first space with a '=' if the command is an actual
+     * chat command */
+    if (parse_chat(player, p + 1) && command_check_byname(player, "@CHAT")) {
+      /* This is a "+chan foo" chat style
+       * We set noevtoken to keep its noeval way, and
+       * set the cmd to allow @hook. */
+      replacer = "@CHAT";
+      noevtoken = 1;
+      break;
+    }
+#endif /* CHAT_SYSTEM */
+  case NUMBER_TOKEN:
+    /* parse_force() destructively modifies the command to replace
+     * the first space with a '=' if the command is an actual
+     * chat command */
+    if (Mobile(player) && parse_force(p)) {
+      replacer = "@FORCE";
+      noevtoken = 1;
+    }
+    break;
+  }
+
+  if (replacer) {
+    cmd = command_find(replacer);
+    if(*p != NUMBER_TOKEN)
+      p++;
+  } else {
+    /* At this point, we have not done a replacer, so we continue with the
+     * usual processing. Exits have next priority.  We still pass them
+     * through the parser so @hook on GOTO can work on them.
+     */
+    if (can_move(player, p)) {
+      ec = exit_command;
+      safe_str("GOTO ", exit_command, &ec);
+      safe_str(p, exit_command, &ec);
+      *ec = '\0';
+      p = string = exit_command;
+      noevtoken = 1; /* But don't parse the exit name! */
+    }
+    c = command;
+    while (*p == ' ')
+      p++;
+    process_expression(command, &c, (const char **) &p, player, realcause,
+                      cause, noevtoken ? PE_NOTHING :
+                                         ((PE_DEFAULT & ~PE_FUNCTION_CHECK)
+                                          | PE_COMMAND_BRACES),
+                      PT_SPACE, NULL);
+    *c = '\0';
+    strcpy(commandraw, command);
+    upcasestr(command);
+
+    /* Catch &XX and @XX attribute pairs. If that's what we've got,
+     * use the magical ATTRIB_SET command
+     */
+    attrib = command_isattr(command);
+    if (attrib) {
+      cmd = command_find("ATTRIB_SET");
+    } else {
+      c = command;
+      while ((*c) && (*c != '/') && (*c != ' '))
+       c++;
+      b = *c;
+      *c = '\0';
+      cmd = command_find(command);
+      *c = b;
+      /* Is this for internal use? If so, players can't use it! */
+      if (cmd && (cmd->type & CMD_T_INTERNAL))
+       cmd = NULL;
+    }
+  }
+
+  /* Set up commandraw for future use. This will contain the canonicalization
+   * of the command name and may later have the parsed rest of the input
+   * appended at the position pointed to by c2.
+   */
+  c2 = c;
+  if (!cmd) {
+    c2 = commandraw + strlen(commandraw);
+  } else {
+    if (replacer) {
+      /* These commands don't allow switches, and need a space
+       * added after their canonical name
+       */
+      c2 = commandraw;
+      safe_str(cmd->name, commandraw, &c2);
+      safe_chr(' ', commandraw, &c2);
+    } else if (*c2 == '/') {
+      /* Oh... DAMN */
+      c2 = commandraw;
+      strcpy(switches, commandraw);
+      safe_str(cmd->name, commandraw, &c2);
+      t = strchr(switches, '/');
+      safe_str(t, commandraw, &c2);
+    } else {
+      c2 = commandraw;
+      safe_str(cmd->name, commandraw, &c2);
+    }
+  }
+
+  /* Test if this either isn't a command, or is a disabled one
+   * If so, return Fully Parsed string for further processing.
+   */
+
+  if (!cmd || (cmd->type & CMD_T_DISABLED)) {
+    if (*p) {
+      if (*p == ' ') {
+       safe_chr(' ', commandraw, &c2);
+       p++;
+      }
+      process_expression(commandraw, &c2, (const char **) &p, player, realcause,
+                        cause, noevtoken ? PE_NOTHING :
+                        ((PE_DEFAULT & ~PE_FUNCTION_CHECK) |
+                         PE_COMMAND_BRACES), PT_DEFAULT, NULL);
+    }
+    *c2 = '\0';
+    command_parse_free_args;
+    return commandraw;
+  }
+
+ /* Parse out any switches */
+  SW_ZERO(sw);
+  swp = switches;
+  *swp = '\0';
+
+  t = NULL;
+
+  /* Don't parse switches for one-char commands */
+  if (!replacer) {
+    while (*c == '/') {
+      t = swtch;
+      c++;
+      while ((*c) && (*c != ' ') && (*c != '/'))
+       *t++ = *c++;
+      *t = '\0';
+      switchnum = switch_find(cmd, upcasestr(swtch));
+      if (!switchnum) {
+       if (cmd->type & CMD_T_SWITCHES) {
+         if (*swp)
+           strcat(swp, " ");
+         strcat(swp, swtch);
+       } else {
+         notify_format(player,
+                       T("%s doesn't know switch %s."), cmd->name, swtch);
+         command_parse_free_args;
+         return NULL;
+       }
+      } else {
+       SW_SET(sw, switchnum);
+      }
+    }
+  }
+
+  if (!t)
+    SW_SET(sw, SWITCH_NONE);
+  if (noevtoken)
+    SW_SET(sw, SWITCH_NOEVAL);
+
+  /* Check the permissions */
+  if (!command_check(player, cmd, sw)) {
+    command_parse_free_args;
+    return NULL;
+  }
+
+  /* If we're calling ATTRIB_SET, the switch is the attribute name */
+  if (attrib)
+    swp = (char *) attrib;
+  else if (!*swp)
+    swp = NULL;
+
+  strcpy(command2, p);
+  if (*p == ' ')
+    p++;
+  ap = p;
+
+  /* noeval and direct players.
+   * If the noeval switch is set:
+   *  (1) if we split on =, and an = is present, eval lhs, not rhs
+   *  (2) if we split on =, and no = is present, do not eval arg
+   *  (3) if we don't split on =, do not eval arg
+   * Special case for ATTRIB_SET by a directly connected player:
+   * Treat like noeval, except for #2. Eval arg if no =.
+   */
+
+  if ((cmd->func == command_atrset) && fromport) {
+    /* Special case: eqsplit, noeval of rhs only */
+    command_argparse(player, realcause, cause, &p, ls, lsa, cmd, 0, 0);
+    command_argparse(player, realcause, cause, &p, rs, rsa, cmd, 1, 1);
+    SW_SET(sw, SWITCH_NOEVAL); /* Needed for ATTRIB_SET */
+  } else {
+    noeval = SW_ISSET(sw, SWITCH_NOEVAL) || noevtoken;
+    if (cmd->type & CMD_T_EQSPLIT) {
+      char *savep = p;
+      command_argparse(player, realcause, cause, &p, ls, lsa, cmd, 0, noeval);
+      if (noeval && !noevtoken && *p) {
+       /* oops, we have a right hand side, should have evaluated */
+       p = savep;
+       command_argparse(player, realcause, cause, &p, ls, lsa, cmd, 0, 0);
+      }
+      command_argparse(player, realcause, cause, &p, rs, rsa, cmd, 1, noeval);
+    } else {
+      command_argparse(player, realcause, cause, &p, ls, lsa, cmd, 0, noeval);
+    }
+  }
+
+
+  /* Finish setting up commandraw, if we may need it for hooks */
+  if (has_hook(&cmd->hooks.ignore) || has_hook(&cmd->hooks.override)) {
+    p = command2;
+    if (*p && (*p == ' ')) {
+      safe_chr(' ', commandraw, &c2);
+      p++;
+    }
+    if (cmd->type & CMD_T_ARGS) {
+      int lsa_index;
+      if (lsa[1]) {
+       safe_str(lsa[1], commandraw, &c2);
+       for (lsa_index = 2; lsa[lsa_index]; lsa_index++) {
+         safe_chr(',', commandraw, &c2);
+         safe_str(lsa[lsa_index], commandraw, &c2);
+        }
+      }
+    } else {
+      safe_str(ls, commandraw, &c2);
+    }
+    if (cmd->type & CMD_T_EQSPLIT) {
+      safe_chr('=', commandraw, &c2);
+      if (cmd->type & CMD_T_RS_ARGS) {
+       int rsa_index;
+       /* This is counterintuitive, but rsa[]
+        * starts at 1. */
+       if (rsa[1]) {
+         safe_str(rsa[1], commandraw, &c2);
+         for (rsa_index = 2; rsa[rsa_index]; rsa_index++) {
+           safe_chr(',', commandraw, &c2);
+           safe_str(rsa[rsa_index], commandraw, &c2);
+         }
+       }
+      } else {
+        safe_str(rs, commandraw, &c2);
+      }
+#ifdef NEVER
+      /* We used to do this, but we're not sure why */
+      process_expression(commandraw, &c2, (const char **) &p, player, realcause,
+                        cause, noevtoken ? PE_NOTHING :
+                        ((PE_DEFAULT & ~PE_FUNCTION_CHECK) |
+                         PE_COMMAND_BRACES), PT_DEFAULT, NULL);
+#endif
+    }
+    *c2 = '\0';
+  }
+
+  retval = NULL;
+  if (cmd->func == NULL) {
+    do_rawlog(LT_ERR, T("No command vector on command %s."), cmd->name);
+    return NULL;
+  } else {
+    char *saveregs[NUMQ];
+    init_global_regs(saveregs);
+    /* If we have a hook/ignore that returns false, we don't do the command */
+    if (run_hook(player, cause, &cmd->hooks.ignore, saveregs, 1)) {
+      /* If we have a hook/override, we use that instead */
+      if (!has_hook(&cmd->hooks.override) ||
+         !one_comm_match(cmd->hooks.override.obj, player,
+                         cmd->hooks.override.attrname, commandraw)) {
+       /* Otherwise, we do hook/before, the command, and hook/after */
+       run_hook(player, cause, &cmd->hooks.before, saveregs, 1);
+       cmd->func(cmd, player, cause, sw, string, swp, ap, ls, lsa, rs, rsa, fromport);
+       run_hook(player, cause, &cmd->hooks.after, saveregs, 0);
+      }
+      /* Either way, we might log */
+      if (cmd->type & CMD_T_LOGARGS)
+       do_log(LT_CMD, player, cause, "%s", string);
+      else if (cmd->type & CMD_T_LOGNAME)
+       do_log(LT_CMD, player, cause, "%s", commandraw);
+    } else {
+      retval = commandraw;
+    }
+    free_global_regs("hook.regs", saveregs);
+  }
+
+  command_parse_free_args;
+  return retval;
+}
+
+#undef command_parse_free_args
+/** Execute the huh_command when no command is matched.
+ *  param player the enactor.
+ *  param cause dbref that caused the command to be executed.
+ *  param string the input given.
+ */
+void
+generic_command_failure(dbref player, dbref cause, char *string, int fromport)
+{
+  COMMAND_INFO *cmd;
+  char *saveregs[NUMQ];
+
+  if ((cmd = command_find("HUH_COMMAND"))) {
+    if (!(cmd->type & CMD_T_DISABLED)) {
+      init_global_regs(saveregs);
+      if (run_hook(player, cause, &cmd->hooks.ignore, saveregs, 1)) {
+        /* If we have a hook/override, we use that instead */
+        if (!has_hook(&cmd->hooks.override) ||
+            !one_comm_match(cmd->hooks.override.obj, player,
+                            cmd->hooks.override.attrname, "HUH_COMMAND")) {
+          /* Otherwise, we do hook/before, the command, and hook/after */
+          run_hook(player, cause, &cmd->hooks.before, saveregs, 1);
+          cmd->func(cmd, player, cause, NULL, string, NULL, NULL, string, NULL,
+                    NULL, NULL, fromport);
+          run_hook(player, cause, &cmd->hooks.after, saveregs, 0);
+        }
+        /* Either way, we might log */
+        if (cmd->type & CMD_T_LOGARGS)
+          do_log(LT_HUH, player, cause, "%s", string);
+      }
+      free_global_regs("hook.regs", saveregs);
+    }
+  }
+}
+
+
+/** Add a restriction to a command.
+ * Given a command name and a restriction, apply the restriction to the
+ * command in addition to whatever its usual restrictions are.
+ * This is used by the configuration file startup in conf.c
+ * Valid restrictions are:
+ * \verbatim
+ *   nobody     disable the command
+ *   nogagged   can't be used by gagged players
+ *   nofixed    can't be used by fixed players
+ *   noguest    can't be used by guests
+ *   admin      can only be used by admins
+ *   director   can only be used by directors
+ *   god        can only be used by god
+ *   noplayer   can't be used by players, just objects/rooms/exits
+ *   logargs    log name and arguments when command is run
+ *   logname    log just name when command is run
+ * \endverbatim
+ * Return 1 on success, 0 on failure.
+ * \param name name of command to restrict.
+ * \param restriction space-separated string of restrictions
+ * \retval 1 successfully restricted command.
+ * \retval 0 failure (unable to find command name).
+ */
+int
+restrict_command(const char *name, const char *restriction)
+{
+  COMMAND_INFO *command;
+  struct command_perms_t *c;
+  char *message;
+  int clear;
+  char *tp;
+
+  if (!name || !*name || !restriction || !*restriction ||
+      !(command = command_find(name)))
+    return 0;
+
+  if (command->restrict_message) {
+    mush_free((Malloc_t) command->restrict_message, "cmd_restrict_message");
+    command->restrict_message = NULL;
+  }
+
+  message = strchr(restriction, '"');
+  if (message) {
+    *(message++) = '\0';
+    if ((message = trim_space_sep(message, ' ')) && *message)
+      command->restrict_message = mush_strdup(message, "cmd_restrict_message");
+  }
+
+  while (restriction && *restriction) {
+    if ((tp = strchr(restriction, ' ')))
+      *tp++ = '\0';
+
+    clear = 0;
+    if (*restriction == '!') {
+      restriction++;
+      clear = 1;
+    }
+
+    if (!strcasecmp(restriction, "noplayer")) {
+      /* Pfft. And even !noplayer works. */
+      clear = !clear;
+      restriction += 2;
+    }
+
+    if ((c = ptab_find(&ptab_command_perms, restriction))) {
+      if (clear)
+       command->type &= ~c->type;
+      else
+       command->type |= c->type;
+    }
+    restriction = tp;
+  }
+  return 1;
+}
+
+int command_lock(const char *name, const char *lock) {
+  COMMAND_INFO *command;
+  char *message;
+  boolexp key;
+
+  if(!name || !*name || !lock || !*lock || !(command = command_find(name)))
+    return 0;
+
+  message = strchr(lock, '"');
+  if(message) {
+    *(message++) = '\0';
+    if((message = trim_space_sep(message, ' ')) && *message) {
+      if(command->restrict_message) 
+       mush_free((Malloc_t) command->restrict_message, "cmd_restrict_message");
+      command->restrict_message = mush_strdup(message, "cmd_restrict_message");
+    }
+  }
+  /* Check to see if there is already a boolexp there.. If so free */
+  if(command->lock != TRUE_BOOLEXP)
+    free_boolexp(command->lock);
+
+  key = parse_boolexp(GOD, lock, "Command_Lock");
+
+  if(key == TRUE_BOOLEXP)
+    return 0;
+  command->lock = key;
+
+  return 1;
+}
+
+/** Command stub for \@command/add-ed commands.
+ * This does nothing more than notify the player
+ * with "This command has not been implemented"
+ */
+COMMAND (cmd_unimplemented) {
+  char *saveregs[NUMQ];
+
+  if (strcmp(cmd->name, "UNIMPLEMENTED_COMMAND") != 0 &&
+      (cmd = command_find("UNIMPLEMENTED_COMMAND"))) {
+    if (!(cmd->type & CMD_T_DISABLED)) {
+      init_global_regs(saveregs);
+      if (run_hook(player, cause, &cmd->hooks.ignore, saveregs, 1)) {
+      /* If we have a hook/override, we use that instead */
+       if (!has_hook(&cmd->hooks.override) ||
+           !one_comm_match(cmd->hooks.override.obj, player,
+                           cmd->hooks.override.attrname, "HUH_COMMAND")) {
+         /* Otherwise, we do hook/before, the command, and hook/after */
+         run_hook(player, cause, &cmd->hooks.before, saveregs, 1);
+
+         cmd->func(cmd, player, cause, sw, raw, switches, args_raw,
+                   arg_left, args_left, arg_right, args_right, fromport);
+         run_hook(player, cause, &cmd->hooks.after, saveregs, 0);
+       }
+      }
+      free_global_regs("hook.regs", saveregs);
+      return;
+    }
+  }
+
+  /* Either we were already in UNIMPLEMENTED_COMMAND, or we couldn't find it */
+  notify(player, "This command has not been implemented");
+}
+
+/** Adds a user-added command
+ * \verbatim
+ * This code implements @command/add, which adds a
+ * command with cmd_unimplemented as a stub
+ * \endverbatim
+ * \param player the enactor
+ * \param name the name
+ * \param flags CMD_T_* flags
+ */
+void
+do_command_add(dbref player, char *name, int flags)
+{
+  COMMAND_INFO *command;
+
+  if (!God(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  name = trim_space_sep(name, ' ');
+  upcasestr(name);
+  command = command_find(name);
+  if (!command) {
+    if (!ok_command_name(name)) {
+      notify(player, T("Bad command name."));
+    } else {
+      command_add(mush_strdup(name, "command_add"),
+                 flags, (flags & CMD_T_NOPARSE ? NULL : "NOEVAL"),
+                 cmd_unimplemented, NULL);
+      notify_format(player, T("Command %s added."), name);
+    }
+  } else {
+    notify_format(player, T("Command %s already exists"), command->name);
+  }
+}
+
+/** Deletes a user-added command
+ * \verbatim
+ * This code implements @command/delete, which deletes a
+ * command added via @command/add
+ * \endverbatim
+ * \param player the enactor
+ * \param name name of the command to delete
+ */
+void
+do_command_delete(dbref player, char *name)
+{
+  int acount;
+  char alias[BUFFER_LEN];
+  COMMAND_INFO *cptr;
+  COMMAND_INFO *command;
+
+  if (!God(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  upcasestr(name);
+  command = command_find_exact(name);
+  if(!command) {
+         notify(player, T("No such command."));
+         return;
+  }
+  if (strcasecmp(command->name, name) == 0) {
+    /* This is the command, not an alias */
+    if (command->func != cmd_unimplemented) {
+      notify(player, T ("You can't delete built-in commands. @command/disable instead."));
+      return;
+    } else {
+      acount = 0;
+      cptr = ptab_firstentry_new(&ptab_command, alias);
+      while (cptr) {
+      if (cptr == command) {
+        ptab_delete(&ptab_command, alias);
+        acount++;
+        cptr = ptab_firstentry_new(&ptab_command, alias);
+      } else
+        cptr = ptab_nextentry_new(&ptab_command, alias);
+      }
+      mush_free((Malloc_t) command->name, "command_add");
+      mush_free((Malloc_t) command, "command");
+      if (acount > 1)
+     notify_format(player, T("Removed %s and aliases from command table."), name);
+      else
+      notify_format(player, T("Removed %s from command table."), name);
+    }
+  } else {
+    /* This is an alias. Just remove it */
+    ptab_delete(&ptab_command, name);
+    notify_format(player, T("Removed %s from command table."), name);
+  }
+}
+
+/** Definition of the \@command command.
+ * This is the only command which should be defined in this
+ * file, because it uses variables from this file, etc.
+ */
+COMMAND (cmd_command) {
+  COMMAND_INFO *command;
+  SWITCH_VALUE *sw_val;
+  char buff[BUFFER_LEN];
+  char *bp = buff;
+
+  if (!arg_left[0]) {
+    notify(player, T("You must specify a command."));
+    return;
+  }
+  if (SW_ISSET(sw, SWITCH_ADD)) {
+    int flags = CMD_T_ANY;
+    flags |= SW_ISSET(sw, SWITCH_NOEVAL) ? CMD_T_NOPARSE : 0;
+    flags |= SW_ISSET(sw, SWITCH_RSARGS) ? CMD_T_RS_ARGS : 0;
+    flags |= SW_ISSET(sw, SWITCH_LSARGS) ? CMD_T_LS_ARGS : 0;
+    flags |= SW_ISSET(sw, SWITCH_LSARGS) ? CMD_T_LS_ARGS : 0;
+    flags |= SW_ISSET(sw, SWITCH_EQSPLIT) ? CMD_T_EQSPLIT : 0;
+    do_command_add(player, arg_left, flags);
+    return;
+  }
+  if (SW_ISSET(sw, SWITCH_ALIAS)) {
+    if (Director(player)) {
+      if (!ok_command_name(upcasestr(arg_right))) {
+       notify(player, "I can't alias a command to that!");
+      } else if (!alias_command(arg_left, arg_right)) {
+       notify(player, "Unable to set alias.");
+      } else {
+       if (!SW_ISSET(sw, SWITCH_QUIET))
+         notify(player, "Alias set.");
+      }
+    } else {
+      notify(player, T("Permission denied."));
+    }
+    return;
+  }
+
+  if (SW_ISSET(sw, SWITCH_DELETE)) {
+    do_command_delete(player, arg_left);
+    return;
+  }
+  command = command_find(arg_left);
+  if (!command) {
+    notify(player, T("No such command."));
+    return;
+  }
+  if (Site(player)) {
+    if (SW_ISSET(sw, SWITCH_ON) || SW_ISSET(sw, SWITCH_ENABLE))
+      command->type &= ~CMD_T_DISABLED;
+    else if (SW_ISSET(sw, SWITCH_OFF) || SW_ISSET(sw, SWITCH_DISABLE))
+      command->type |= CMD_T_DISABLED;
+
+    if(SW_ISSET(sw, SWITCH_LOCK)) {
+      if(arg_right && *arg_right) {
+       boolexp key;
+
+       key = parse_boolexp(player, arg_right, "Command");
+       if(key != TRUE_BOOLEXP)  {
+         if(command->lock != TRUE_BOOLEXP)
+           free_boolexp(command->lock);
+         command->lock = key;
+         notify(player, "Command locked.");
+       } else notify(player, T("I don't understand that key."));
+      } else {
+       if(command->lock != TRUE_BOOLEXP) 
+         free_boolexp(command->lock);
+       command->lock = TRUE_BOOLEXP;
+       notify(player, "Command unlocked.");
+      }
+      return;
+    }
+
+    if (SW_ISSET(sw, SWITCH_RESTRICT)) {
+      if (!arg_right || !arg_right[0]) {
+       notify(player, T("How do you want to restrict the command?"));
+       return;
+      }
+
+      if (!restrict_command(arg_left, arg_right))
+       notify(player, T("Restrict attempt failed."));
+    }
+
+    if ((command->func == cmd_command) && (command->type & CMD_T_DISABLED)) {
+      notify(player, T("@command is ALWAYS enabled."));
+      command->type &= ~CMD_T_DISABLED;
+    }
+  }
+  if (!SW_ISSET(sw, SWITCH_QUIET)) {
+    notify_format(player,
+                 "Name         : %s (%s)", command->name,
+                 (command->type & CMD_T_DISABLED) ? "Disabled" : "Enabled");
+    if ((command->type & CMD_T_ANY) == CMD_T_ANY)
+      safe_strl("Any", 3, buff, &bp);
+    else {
+      buff[0] = '\0';
+      if (command->type & CMD_T_ROOM)
+       strccat(buff, &bp, "Room");
+      if (command->type & CMD_T_THING)
+       strccat(buff, &bp, "Thing");
+      if (command->type & CMD_T_EXIT)
+       strccat(buff, &bp, "Exit");
+      if (command->type & CMD_T_PLAYER)
+       strccat(buff, &bp, "Player");
+      if (command->type & CMD_T_DIVISION)
+       strccat(buff, &bp, "Division");
+    }
+    *bp = '\0';
+    notify_format(player, "Types        : %s", buff);
+    buff[0] = '\0';
+    bp = buff;
+    if (command->type & CMD_T_SWITCHES)
+      strccat(buff, &bp, "Switches");
+    if (command->type & CMD_T_NOGAGGED)
+      strccat(buff, &bp, "Nogagged");
+    if (command->type & CMD_T_NOFIXED)
+      strccat(buff, &bp, "Nofixed");
+    if(command->type & CMD_T_NORPMODE)
+      strccat(buff, &bp, "NoRPMode");
+    if (command->type & CMD_T_NOGUEST)
+      strccat(buff, &bp, "Noguest");
+    if (command->type & CMD_T_EQSPLIT)
+      strccat(buff, &bp, "Eqsplit");
+    if (command->type & CMD_T_GOD)
+      strccat(buff, &bp, "God");
+    if (command->type & CMD_T_LOGARGS)
+      strccat(buff, &bp, "LogArgs");
+    else if (command->type & CMD_T_LOGNAME)
+      strccat(buff, &bp, "LogName");
+    *bp = '\0';
+    notify_format(player, "Restrict     : %s", buff);
+    buff[0] = '\0';
+    notify_format(player, "Command Lock : %s",  unparse_boolexp(player, command->lock, UB_MEREF));
+    bp = buff;
+    for (sw_val = switch_list; sw_val->name; sw_val++)
+      if (SW_ISSET(command->sw, sw_val->value))
+       strccat(buff, &bp, sw_val->name);
+    *bp = '\0';
+    notify_format(player, "Switches     : %s", buff);
+    buff[0] = '\0';
+    bp = buff;
+    if (command->type & CMD_T_LS_ARGS) {
+      if (command->type & CMD_T_LS_SPACE)
+       strccat(buff, &bp, "Space-Args");
+      else
+       strccat(buff, &bp, "Args");
+    }
+    if (command->type & CMD_T_LS_NOPARSE)
+      strccat(buff, &bp, "Noparse");
+    if (command->type & CMD_T_EQSPLIT) {
+      *bp = '\0';
+      notify_format(player, "Leftside     : %s", buff);
+      buff[0] = '\0';
+      bp = buff;
+      if (command->type & CMD_T_RS_ARGS) {
+       if (command->type & CMD_T_RS_SPACE)
+         strccat(buff, &bp, "Space-Args");
+       else
+         strccat(buff, &bp, "Args");
+      }
+      if (command->type & CMD_T_RS_NOPARSE)
+       strccat(buff, &bp, "Noparse");
+      *bp = '\0';
+      notify_format(player, "Rightside    : %s", buff);
+    } else {
+      *bp = '\0';
+      notify_format(player, "Arguments    : %s", buff);
+    }
+    if (Site(player)) {
+      if (GoodObject(command->hooks.before.obj))
+       notify_format(player, "@hook/before: #%d/%s",
+                     command->hooks.before.obj,
+                     command->hooks.before.attrname);
+      if (GoodObject(command->hooks.after.obj))
+       notify_format(player, "@hook/after: #%d/%s", command->hooks.after.obj,
+                     command->hooks.after.attrname);
+      if (GoodObject(command->hooks.ignore.obj))
+       notify_format(player, "@hook/ignore: #%d/%s",
+                     command->hooks.ignore.obj,
+                     command->hooks.ignore.attrname);
+      if (GoodObject(command->hooks.override.obj))
+       notify_format(player, "@hook/override: #%d/%s",
+                     command->hooks.override.obj,
+                     command->hooks.override.attrname);
+    }
+  }
+}
+
+/** Display a list of defined commands.
+ * This function sends a player the list of commands.
+ * \param player the enactor.
+ * \param lc if true, list is in lowercase rather than uppercase.
+ */
+void
+do_list_commands(dbref player, int lc)
+{
+  char *b = list_commands();
+  notify_format(player, "Commands: %s", lc ? strlower(b) : b);
+}
+
+/** Return a list of defined commands.
+ * This function returns a space-separated list of commands as a string.
+ */
+char *
+list_commands(void)
+{
+  COMMAND_INFO *command;
+  const char *ptrs[BUFFER_LEN / 2];
+  static char buff[BUFFER_LEN];
+  char *bp;
+  int nptrs = 0, i;
+  command = (COMMAND_INFO *) ptab_firstentry(&ptab_command);
+  while (command) {
+    ptrs[nptrs] = command->name;
+    nptrs++;
+    command = (COMMAND_INFO *) ptab_nextentry(&ptab_command);
+  }
+  bp = buff;
+  safe_str(ptrs[0], buff, &bp);
+  for (i = 1; i < nptrs; i++) {
+    safe_chr(' ', buff, &bp);
+    safe_str(ptrs[i], buff, &bp);
+  }
+  *bp = '\0';
+  return buff;
+}
+
+
+/* Check command permissions. Return 1 if player can use command,
+ * 0 otherwise, and maybe be noisy about it.
+ */
+static int
+command_check(dbref player, COMMAND_INFO *cmd, switch_mask switches)
+{
+  int ok;
+  char *mess = NULL;
+
+  /* God doesn't get fucked with */
+  if(LEVEL(player) >= LEVEL_GOD)
+         return 1;
+  /* If disabled, return silently */
+  if (cmd->type & CMD_T_DISABLED)
+    return 0;
+  if ((cmd->type & CMD_T_NOGAGGED) && Gagged(player)) {
+    mess = T("You cannot do that while gagged.");
+    goto send_error;
+  }
+  if ((cmd->type & CMD_T_NOFIXED) && Fixed(player)) {
+    mess = T("You cannot do that while fixed.");
+    goto send_error;
+  }
+#ifdef RPMODE_SYS
+  if((cmd->type & CMD_T_NORPMODE) && RPMODE(player)) {
+         mess = T("You cannot do that while in RPMODE");
+         goto send_error;
+  }
+#endif
+  if ((cmd->type & CMD_T_NOGUEST) && Guest(player)) {
+    mess =  T("Guests cannot do that.");
+    return 0;
+  }
+  if ((cmd->type & CMD_T_GOD) && (!God(player))) {
+    mess =  T("Only God can do that.");
+    return 0;
+  }
+  switch (Typeof(player)) {
+  case TYPE_ROOM:
+    ok = (cmd->type & CMD_T_ROOM);
+    break;
+  case TYPE_THING:
+    ok = (cmd->type & CMD_T_THING);
+    break;
+  case TYPE_EXIT:
+    ok = (cmd->type & CMD_T_EXIT);
+    break;
+  case TYPE_PLAYER:
+    ok = (cmd->type & CMD_T_PLAYER);
+    break;
+  case TYPE_DIVISION:
+    ok = (cmd->type & CMD_T_DIVISION);
+    break;
+  default:
+    ok = 0;
+  }
+  if (!ok) {
+    mess = T("Permission denied, command is type-restricted.");
+    goto send_error;
+  }
+  /* A command can specify required flags or powers, and if
+   * any match, the player is ok to do the command.
+   */
+  ok = 1;
+
+  if(!God(player) && !eval_boolexp(player, cmd->lock, player, switches) ) {
+        mess =  T("Permission denied.");
+        goto send_error;
+  }
+
+  return ok;
+send_error:
+  if(cmd->restrict_message)
+    notify(player, cmd->restrict_message);
+  else if(mess)
+    notify(player, mess);
+  return 0;
+}
+
+/** Determine whether a player can use a command.
+ * This function checks whether a player can use a command.
+ * If the command is disallowed, the player is informed.
+ * \param player player whose privileges are checked.
+ * \param name name of command.
+ * \retval 0 player may not use command.
+ * \retval 1 player may use command.
+ */
+int
+command_check_byname(dbref player, const char *name)
+{
+  COMMAND_INFO *cmd;
+  cmd = command_find(name);
+  if (!cmd)
+    return 0;
+  return command_check(player, cmd, NULL);
+}
+
+static int
+has_hook(struct hook_data *hook)
+{
+  if (!hook || !GoodObject(hook->obj) || IsGarbage(hook->obj)
+      || !hook->attrname)
+    return 0;
+  return 1;
+}
+
+
+/** Run a command hook.
+ * This function runs a hook before or after a command execution.
+ * \param player the enactor.
+ * \param cause dbref that caused command to execute.
+ * \param hook pointer to the hook.
+ * \param saveregs array to store a copy of the final q-registers.
+ * \param save if true, use saveregs to store a ending q-registers.
+ * \retval 1 Hook doesn't exist, or evaluates to a non-false value
+ * \retval 0 Hook exists and evaluates to a false value
+ */
+int
+run_hook(dbref player, dbref cause, struct hook_data *hook, char *saveregs[],
+        int save)
+{
+  ATTR *atr;
+  char *code;
+  const char *cp;
+  char buff[BUFFER_LEN], *bp;
+  char *origregs[NUMQ];
+
+  if (!has_hook(hook))
+    return 1;
+
+  atr = atr_get(hook->obj, hook->attrname);
+
+  if (!atr)
+    return 1;
+
+  code = safe_atr_value(atr);
+  if (!code)
+    return 1;
+  add_check("hook.code");
+
+  save_global_regs("run_hook", origregs);
+  restore_global_regs("hook.regs", saveregs);
+
+  cp = code;
+  bp = buff;
+
+  process_expression(buff, &bp, &cp, hook->obj, cause, player, PE_DEFAULT,
+                    PT_DEFAULT, NULL);
+  *bp = '\0';
+
+  if (save)
+    save_global_regs("hook.regs", saveregs);
+  restore_global_regs("run_hook", origregs);
+
+  mush_free(code, "hook.code");
+  return parse_boolean(buff);
+}
+
+/** Set up or remove a command hook.
+ * \verbatim
+ * This is the top-level function for @hook. If an object and attribute
+ * are given, establishes a hook; if neither are given, removes a hook.
+ * \endverbatim
+ * \param player the enactor.
+ * \param command command to hook.
+ * \param obj name of object containing the hook attribute.
+ * \param attrname of hook attribute on obj.
+ * \param flag type of hook
+ */
+void
+do_hook(dbref player, char *command, char *obj, char *attrname,
+       enum hook_type flag)
+{
+  COMMAND_INFO *cmd;
+  struct hook_data *h;
+
+  cmd = command_find(command);
+  if (!cmd) {
+    notify(player, T("No such command."));
+    return;
+  }
+  if ((cmd->func == cmd_password) || (cmd->func == cmd_newpassword)) {
+    notify(player, T("Hooks not allowed with that command."));
+    return;
+  }
+
+  if (flag == HOOK_BEFORE)
+    h = &cmd->hooks.before;
+  else if (flag == HOOK_AFTER)
+    h = &cmd->hooks.after;
+  else if (flag == HOOK_IGNORE)
+    h = &cmd->hooks.ignore;
+  else if (flag == HOOK_OVERRIDE)
+    h = &cmd->hooks.override;
+  else {
+    notify(player, T("Unknown hook type"));
+    return;
+  }
+
+  if (!obj && !attrname) {
+    notify_format(player, T("Hook removed from %s."), cmd->name);
+    h->obj = NOTHING;
+    mush_free(h->attrname, "hook.attr");
+    h->attrname = NULL;
+  } else if (!obj || !*obj || !attrname || !*attrname) {
+    notify(player, T("You must give both an object and attribute."));
+  } else {
+    dbref objdb = match_thing(player, obj);
+    if (!GoodObject(objdb)) {
+      notify(player, T("Invalid hook object."));
+      return;
+    }
+    h->obj = objdb;
+    if (h->attrname)
+      mush_free(h->attrname, "hook.attr");
+    h->attrname = mush_strdup(strupper(attrname), "hook.attr");
+    notify_format(player, T("Hook set for %s"), cmd->name);
+  }
+}
diff --git a/src/comp_h.c b/src/comp_h.c
new file mode 100644 (file)
index 0000000..6cfa42e
--- /dev/null
@@ -0,0 +1,603 @@
+/**
+ * \file comp_h.c
+ *
+ * \brief Huffman compression routines.
+ *
+ * One of several options for attribute compression. Usually the best.
+ * Talek's rewrite of compress.c, using a Huffman compression routine.
+ * This routine adds some time to the MUSH startup, since it reads a file 
+ * in order to auto-tune the compression at each restart. The SAMPLE_SIZE
+ * define can trade efficiency for speed.
+ * This rewrite was inspired by Javelin's rewrite on a similar vein.
+ * Most of the comments are his. The nasty ones are Talek's.
+ *
+ */
+
+/* Compression routines */
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+#ifdef WIN32
+#pragma warning( disable : 4244)       /* NJG: disable warning re conversion */
+#endif
+
+#define TABLE_SIZE      256    /**< allow all characters */
+#define EOS             0      /**< use null code for end of string */
+#define CHAR_BITS       8      /**< number of bits in char */
+#define CHAR_MASK       255    /**< mask for just one char */
+#define CODE_BITS       25     /**< max number of bits in code */
+#ifndef SAMPLE_SIZE
+#define SAMPLE_SIZE     0      /**< sample entire database */
+#endif
+
+
+/** Type for a huffman code. It must be at least CODE_BITS+CHAR_BITS-1
+ * bits long.
+ */
+typedef unsigned long CType;
+
+/** A node in the huffman compression tree. */
+typedef struct cnode {
+  struct cnode *left;          /**< Left child node. */
+  struct cnode *right;         /**< Right child node. */
+  unsigned char c;             /**< character at this node. */
+} CNode;
+
+static CNode *ctop;
+static CType ctable[TABLE_SIZE];
+static char ltable[TABLE_SIZE];
+
+static int fix_tree_depth(CNode *node, int height, int zeros);
+static void add_ones(CNode *node);
+static void build_ctable(CNode *root, CType code, int numbits);
+int init_compress(FILE * f);
+
+
+/** Huffman-compress a string.
+ * Compress a string: this is pretty easy. For each char in the string,
+ * look up its code in ctable and add it to the compressed string we
+ * build, keeping careful track of the number of bits we add.
+ * Then stick the EOS character at the end.
+ *
+ * Important notes:
+ *   This function mallocs memory that should be freed by the caller!
+ *   The caller is also currently responsible for adding mem checks
+ *   Don't use it to compress strings longer than BUFFER_LEN or the
+ *     later uncompression will not go well.
+ *
+ * \param s string to be compressed.
+ * \return newly allocated compressed string.
+ */
+unsigned char *
+compress(const char *s)
+{
+  CType stage;
+  int bits = 0;
+  const unsigned char *p;
+  unsigned char *b, *buf;
+  int needed_length;
+
+  /* Part 1 - how long will the compressed string be? */
+  for (p = (const unsigned char *) s; p && *p; p++)
+    bits += ltable[*p];
+  bits += CHAR_BITS * 2 - 1;   /* add space for the ending \0 */
+  needed_length = bits / CHAR_BITS;
+
+  /* Part 2 - Actually get around to compressing the data... */
+  p = (const unsigned char *) s;
+  b = buf = (unsigned char *) malloc(needed_length);
+  stage = 0;
+  bits = 0;
+
+  while (p && *p) {
+    /* Put code on stage */
+    stage |= ctable[*p] << bits;
+    bits += ltable[*p];
+    /* Put any full bytes of stage into the compressed string */
+    while (bits >= CHAR_BITS) {
+      *b++ = stage & CHAR_MASK;
+      stage = stage >> CHAR_BITS;
+      bits -= CHAR_BITS;
+    }
+    p++;
+  }
+  /* Put in EOS, and put the rest of the stage into the compressed string */
+  /* This relies on EOS == 00000000 */
+  bits += ltable[EOS] + CHAR_BITS - 1;
+  while (bits >= CHAR_BITS) {
+    *b++ = stage & CHAR_MASK;
+    stage = stage >> CHAR_BITS;
+    bits -= CHAR_BITS;
+  }
+
+  return buf;
+}
+
+/** Walk the huffman tree.
+ * This macro is used for walking the compression tree.
+ * It is a macro for efficiency.
+ */
+#define WALK_TREE(bitpos) \
+do { \
+  if (*p & (bitpos)) \
+    node = node->right; \
+  else \
+    node = node->left; \
+  if (!node->left && !node->right) { \
+    /* Got a char */ \
+    *b++ = node->c; \
+    if (!*p || ((long)(b - buf) >= (long)(sizeof(buf) - 1))) { \
+      *b++ = EOS; \
+      return buf; \
+    } \
+    if (node->c == EOS) \
+      return buf; \
+    node = ctop; \
+  } \
+} while (0)
+
+/** Huffman uncompress a string.
+ * Uncompression is a snap, too. Go bit by bit, using the
+ * bits to traverse the binary tree (0=left, 1=right) until reaching
+ * a leaf node, which is the uncompressed character.
+ * Stop when the leaf node turns out to be EOS.
+ *
+ * To avoid generating memory problems, this function should be
+ * used with something of the format
+ * \verbatim
+ * char tbuf1[BUFFER_LEN];
+ * strcpy(tbuf1, uncompress(a->value));
+ * \endverbatim
+ * if you are using something of type char *buff, use the
+ * safe_uncompress function instead.
+ *
+ * \param s a compressed string.
+ * \return a pointer to a static buffer containing the uncompressed string.
+ */
+char *
+uncompress(const unsigned char *s)
+{
+
+  static char buf[BUFFER_LEN];
+  const unsigned char *p;
+  char *b;
+  CNode *node;
+
+  buf[0] = '\0';
+  if (!s || !*s)
+    return buf;
+  p = s;
+  b = buf;
+  /* Finally start decompressing the string... */
+  node = ctop;
+  for (;;) {
+    WALK_TREE(1);
+    WALK_TREE(2);
+    WALK_TREE(4);
+    WALK_TREE(8);
+    WALK_TREE(16);
+    WALK_TREE(32);
+    WALK_TREE(64);
+    WALK_TREE(128);
+    p++;
+  }
+}
+
+/** Huffman uncompress a string, allocating memory.
+ * this function should be used when you're doing something like
+ * \verbatim
+ * char *attrib = safe_uncompress(a->value);
+ *
+ * NEVER use it with something like
+ *
+ * char tbuf1[BUFFER_LEN]; 
+ * strcpy(tbuf1, safe_uncompress(a->value));
+ * \endverbatim
+ * or you will create a horrendous memory leak.
+ *
+ * \param s compressed string to uncompress.
+ * \return pointer to newly allocated string containing uncompressed text.
+ */
+char *
+safe_uncompress(unsigned char const *s)
+{
+  return (char *) strdup((char *) uncompress(s));
+}
+
+
+static int
+fix_tree_depth(CNode *node, int height, int zeros)
+{
+  int a, b;
+  CNode *temp;
+
+  if (!node)
+    return height + (zeros > 2);
+  a = fix_tree_depth(node->left, height + 1 + (zeros == 7), (zeros + 1) % 8);
+  b = fix_tree_depth(node->right, height + 1, 0);
+  if ((a > CODE_BITS) && (b < (a - 1))) {
+#ifdef STANDALONE
+    printf("Rotate right at depth %d.\n", height);
+#endif
+    temp = node->right;
+    node->right = node->left;
+    node->left = node->right->left;
+    node->right->left = node->right->right;
+    node->right->right = temp;
+    a = fix_tree_depth(node->left, height + 1 + (zeros == 7), (zeros + 1) % 8);
+    b = fix_tree_depth(node->right, height + 1, 0);
+  } else if ((b > CODE_BITS) && (a < (b - 1))) {
+#ifdef STANDALONE
+    printf("Rotate left at depth %d.\n", height);
+#endif
+    temp = node->left;
+    node->left = node->right;
+    node->right = node->left->right;
+    node->left->right = node->left->left;
+    node->left->left = temp;
+    a = fix_tree_depth(node->left, height + 1 + (zeros == 7), (zeros + 1) % 8);
+    b = fix_tree_depth(node->right, height + 1, 0);
+  }
+  return ((a > b) ? a : b);
+}
+
+/* Add 1s to the tree, recursively */
+static void
+add_ones(CNode *node)
+{
+  int count;
+
+  count = 0;
+  do {
+    if (node->right)
+      add_ones(node->right);
+    if ((count >= 7) || ((count >= 3) && !node->left && !node->right)) {
+      ctop = (CNode *) malloc((unsigned) sizeof(CNode));
+      if (!ctop) {
+       do_rawlog(LT_ERR,
+                 "Cannot allocate memory for compression tree. Aborting.");
+       exit(1);
+      }
+      ctop->left = node->left;
+      ctop->right = node->right;
+      ctop->c = node->c;
+      node->left = (CNode *) NULL;
+      node->right = ctop;
+      node = ctop;
+      count = 0;
+    }
+    node = node->left;
+    count++;
+  } while (node);
+}
+
+/* Build ctable and ltable from the tree, recursively */
+static void
+build_ctable(CNode *root, CType code, int numbits)
+{
+#ifdef STANDALONE
+  int i;
+#endif
+
+  if (!root->left && !root->right) {
+    ctable[root->c] = code;
+    ltable[root->c] = numbits;
+#ifdef STANDALONE
+    printf(isprint(root->c) ? "Code for '%c':\t" : "Code for %d:\t", root->c);
+    for (i = 0; i < numbits; i++)
+      printf("%d", (code >> i) & 1);
+    printf("\n");
+#endif
+    if (numbits > CODE_BITS) {
+      do_rawlog(LT_ERR, "Illegal compression code length (%d). Aborting.",
+               numbits);
+      exit(1);
+    }
+  } else {
+    if (root->left)
+      build_ctable(root->left, code | (0 << numbits), numbits + 1);
+    if (root->right)
+      build_ctable(root->right, code | (1 << numbits), numbits + 1);
+  }
+}
+
+/** Initialize huffman compression.
+ * Initialize the compression tree and table in 5 steps:
+ * 1. Initialize arrays and things
+ * 2. Read indb (up to SAMPLE_SIZE chars, if defined) and count
+ *    the frequency of every character
+ * 3. Cheat the relative frequency of some known special chars
+ *    and upper-case letters
+ * 4. Construct an (un)compression tree based on frequencies
+ * 5. Construct a compression table by searching the tree
+ * \param f filehandle to read from to build the tree.
+ */
+int
+init_compress(FILE * f)
+{
+  int total;
+  unsigned char c;
+  struct {
+    long freq;
+    CNode *node;
+  } table[TABLE_SIZE];
+  int indx, count;
+  long temp;
+  CNode *node;
+
+#ifdef STANDALONE
+  printf("init_compress: Part 1\n");
+#endif
+
+  /* Part 1: initialize */
+  for (total = 0; total < TABLE_SIZE; total++) {
+    table[total].freq = 0;
+    table[total].node = (CNode *) malloc((unsigned) sizeof(CNode));
+    if (!table[total].node) {
+      do_rawlog(LT_ERR,
+               "Cannot allocate memory for compression tree. Aborting.");
+      exit(1);
+    }
+    table[total].node->c = total;
+    table[total].node->left = (CNode *) NULL;
+    table[total].node->right = (CNode *) NULL;
+  }
+
+#ifdef STANDALONE
+  printf("init_compress: Part 2\n");
+#endif
+
+  /* Part 2: count frequencies */
+  if (f) {
+    total = 0;
+    while (!feof(f) && (!SAMPLE_SIZE || (total++ < SAMPLE_SIZE))) {
+      c = fgetc(f);
+      table[c].freq++;
+    }
+  }
+#ifdef STANDALONE
+  for (indx = 0; indx < TABLE_SIZE; indx++) {
+    printf(isprint(indx) ? "Frequency for '%c': %d\n"
+          : "Frequency for %d: %d\n", (unsigned char) indx, table[indx].freq);
+  }
+#endif
+
+#ifdef STANDALONE
+  printf("init_compress: Part 3\n");
+#endif
+
+  /* Part 3: Cheat the frequencies. Because there's a lot of wierd
+   * stuff in indb (like ]'s and upper-case letters), we downplay it
+   * by cutting frequencies. Actually, we shouldn't need to much.
+   */
+
+  /* The ']' character is artificially raised by being the
+   * start-of-attribute marker in indb.  Set it back to '[',
+   * which it should be balancing...
+   */
+  table[']'].freq = table['['].freq;
+
+  /* The DEL character is returned once for no apparent reason (I think
+   * it is returned at EOF), so remove that one count...
+   */
+  if (table[255].freq)
+    table[255].freq--;
+
+  /* Newlines really aren't all that common in the attributes, so
+   * chop the value substantially.
+   */
+  table['\n'].freq /= 16;
+
+#ifdef STANDALONE
+  printf("init_compress: Part 4(a)\n");
+#endif
+
+  /* Part 4(a): Sort the table.  I'm using a stupid insert sort here
+   * because this only gets called once, and I don't want to conform
+   * to the qsort interface.  NOTE: I don't sort in EOS.
+   */
+  for (indx = 2; indx < TABLE_SIZE; indx++) {
+    for (count = indx;
+        (count > 1) && (table[count - 1].freq < table[count].freq); count--) {
+      temp = table[count].freq;
+      table[count].freq = table[count - 1].freq;
+      table[count - 1].freq = temp;
+      node = table[count].node;
+      table[count].node = table[count - 1].node;
+      table[count - 1].node = node;
+    }
+  }
+
+#ifdef STANDALONE
+  printf("init_compress: Part 4(b)\n");
+#endif
+
+  /* Part 4(b): Now we've got a list sorted from most freq (table[0]) to
+   * least freq. We build a binary tree by traversing the list, making
+   * a subtree out of the two least frequent nodes (creating a parent
+   * node whose frequency is the sum of its children's frequency),
+   * and reinserting the subtree's parent into the list. We repeat
+   * until there's only one node in the list, the root of the tree.
+   * NOTE: I'm still not dealing with EOS.
+   */
+  for (indx = TABLE_SIZE - 1; indx > 0; indx--) {
+#ifdef NEVER
+    printf("Freq. table:\n");
+    for (count = indx; count >= 0; count--)
+      printf("%3d: %d\t", table[count].node->c, table[count].freq);
+    printf("\n");
+#endif
+    node = (CNode *) malloc((unsigned) sizeof(CNode));
+    if (!node) {
+      do_rawlog(LT_ERR,
+               "Cannot allocate memory for compression tree. Aborting.");
+      exit(1);
+    }
+    node->left = table[indx].node;
+    node->right = table[indx - 1].node;
+    table[indx - 1].freq += table[indx].freq;
+    table[indx - 1].node = node;
+    for (count = indx - 1;
+        (count > 1) && (table[count - 1].freq <= table[count].freq); count--) {
+      temp = table[count].freq;
+      table[count].freq = table[count - 1].freq;
+      table[count - 1].freq = temp;
+      node = table[count].node;
+      table[count].node = table[count - 1].node;
+      table[count - 1].node = node;
+    }
+  }
+
+#ifdef NEVER
+  build_ctable(table[1].node, 0, 0);
+#endif
+
+#ifdef STANDALONE
+  printf("init_compress: Part 4(c)\n");
+#endif
+
+  /* Part 4(c): If necessary, squash the tree so that it obeys
+   * the code length limitations (CODE_BITS et all).  This is
+   * done recursively, with rotations.
+   */
+  (void) fix_tree_depth(table[1].node, 0, 2);
+
+#ifdef STANDALONE
+  printf("init_compress: Part 4(d)\n");
+#endif
+
+  /* Part 4(d): It is now time to insure that sequences of eight 0s
+   * never occur in the output data, because having nulls in the
+   * output would royally confuse strcpy et all.
+   */
+
+  /* Force a 1 at fifth position on the left edge of tree. (Or terminating
+   * 1 for the all 0 code.)
+   */
+  node = table[1].node;                /* top of tree */
+  for (count = 0; node->left && (count < 4); count++)
+    node = node->left;
+  ctop = (CNode *) malloc((unsigned) sizeof(CNode));
+  if (!ctop) {
+    do_rawlog(LT_ERR, "Cannot allocate memory for compression tree. Aborting.");
+    exit(1);
+  }
+  ctop->left = node->left;
+  ctop->right = node->right;
+  ctop->c = node->c;
+  node->left = (CNode *) NULL;
+  node->right = ctop;
+
+  /* Recursively descend tree adding 1s where needed. */
+
+  add_ones(table[1].node);
+
+#ifdef STANDALONE
+  printf("init_compress: Part 4(e)\n");
+#endif
+
+  /* Part 4(e): Finally add in EOS as 00000000.
+   */
+  node = table[1].node;                /* top of tree */
+  for (count = 0; count < 8; count++) {
+    if (!node->left) {
+      ctop = (CNode *) malloc((unsigned) sizeof(CNode));
+      if (!ctop) {
+       do_rawlog(LT_ERR,
+                 "Cannot allocate memory for compression tree. Aborting.");
+       exit(1);
+      }
+      ctop->left = (CNode *) NULL;
+      ctop->right = (CNode *) NULL;
+      ctop->c = EOS;
+      node->left = ctop;
+    }
+    node = node->left;
+  }
+
+#ifdef STANDALONE
+  printf("init_compress: Part 5\n");
+#endif
+
+  /* Part 5: Now traverse the tree, depth-first, and construct
+   * the compression table.
+   */
+
+  ctop = table[1].node;
+  build_ctable(ctop, 0, 0);
+
+#ifdef STANDALONE
+  printf("init_compress: Done\n");
+#endif
+
+  /* Whew */
+  return 0;
+}
+
+#ifdef STANDALONE
+void
+main(argc, argv)
+    int argc;
+    char *argv[];
+{
+  FILE *input;
+  unsigned char buffer[BUFFER_LEN];
+  unsigned char otherbuf[BUFFER_LEN];
+  unsigned char newbuffer[BUFFER_LEN];
+  unsigned char *p1, *p2;
+  int count;
+
+  if ((input = fopen(argv[1], "rb")) == NULL) {
+    printf("Can't open %s.\n", argv[1]);
+    exit(1);
+  }
+  init_compress(input);
+  fclose(input);
+  do {
+    printf("Enter text: ");
+    fgets(buffer, 4095, stdin);
+    if ((buffer[0] == '\n') || (buffer[0] == '\r') || (buffer[0] == '\0'))
+      exit(0);
+    printf("Text: %s!\n", buffer);
+    printf("Compressing\n");
+    strcpy(otherbuf, compress(buffer));
+    printf("Compressed: ");
+    p1 = otherbuf;
+    while (p1 && *p1) {
+      for (count = 0; count < 8; count++)
+       printf("%d", (*p1 >> count) & 1);
+      p1++;
+    }
+    printf("\n");
+    printf("Length: %d, Complength: %d\n", strlen(buffer), strlen(otherbuf));
+    printf("Uncompressing\n");
+    strcpy(newbuffer, uncompress(otherbuf));
+    printf("Text: %s!\n", newbuffer);
+    printf("Strcoll(orig,uncomp) = %d\n", strcoll(newbuffer, buffer));
+    printf("strlen(orig) = %d, strlen(uncomp) = %d\n", strlen(buffer),
+          strlen(newbuffer));
+    p1 = buffer;
+    p2 = newbuffer;
+/*
+ * while (p1 && p2 && *p1 && *p2) {
+ * if (*p1 != *p2) printf("Unequal: %d and %d\n",*p1,*p2);
+ * else printf("Equal: %c and %c\n",*p1,*p2);
+ * p1++; p2++;
+ * }
+ */
+    strcpy(newbuffer, otherbuf);
+/*
+ * printf("Trying safe.\n");
+ * buf = safe_uncompress(newbuffer);
+ * printf("Safe uncompress: %s!\n",buf);
+ */
+  } while (1);
+}
+#endif
diff --git a/src/comp_w.c b/src/comp_w.c
new file mode 100644 (file)
index 0000000..802c3d3
--- /dev/null
@@ -0,0 +1,396 @@
+/**
+ * \file comp_w.c
+ *
+ * \brief Word-based compression.
+ *
+ * Table-lookup (word) compress, written by Nick Gammon. 21/Oct/95.
+ *
+ * This method maintains a table of 32768 words, where a "word" is defined
+ * as a sequence of alpha or numeric characters (such as "dog123").
+ *
+ * Compression
+ * -----------
+ *
+ * The text to be compressed is broken up into "words" and other symbols
+ * (e.g. commas, brackets, spaces and so on).
+ *
+ * Words are looked up in the word table by using a hash-table lookup. If
+ * found, the "index" into the table is emitted with the high order bit set.
+ * If not found, the word is added to the table, and is then emitted as
+ * described above.
+ *
+ * For example: "The dog and the other dog sat on the mat (eating fish)."
+ * would compress as:
+ *
+ * 0x8001      The
+ * 0x8002      dog
+ * 0x8003      and
+ * 0x8004      the
+ * 0x8005      other
+ * 0x8002      dog
+ * 0x8007      sat
+ * 0x8008      on
+ * 0x8004      the
+ * 0x8009      mat
+ * 0x28        (
+ * 0x800A      eating
+ * 0x800B      fish)
+ * 0x2E        .
+ *
+ * In the above example, the uncompressed text is 55 bytes, and the compressed 
+ * text is 26 bytes.
+ *
+ * For simplicity, the above example assumes that the words hash to consecutive
+ * table positions.
+ *
+ * Note that the trailing punctuation character (space, period or whatever) is 
+ * considered _part_ of the word. This is to save having to store multiple 
+ * spaces between each word, and is relying on the fact that a certain word is
+ * usually followed by the same punctuation. For example, the word "and" would
+ * normally be followed by a space.
+ *
+ * In the above example, the space following "mat" is stored in the table, and
+ * thus the "(" character had to be output separately. The ")" following the
+ * word "fish" is considered part of the word and is stored in the table, 
+ * however the last period was output as a separate character.
+ *
+ * Note how the high-order bit is turned on for words in the table, this is so
+ * the decompression routine can detect table lookup characters from ordinary
+ * ones. Also, any table index with a low-order byte of zero cannot be used,
+ * as this would cause the resulting "string" to be prematurely truncated (by
+ * strcpy, strcmp etc.). Thus the lookup routine skips any positions that hash
+ * to the value 0xXX00. (There are only 256 such indices).
+ *
+ * Also, to prevent characters which the user might type in with the high order
+ * bit set causing decompression confusion, all text is stripped of its high
+ * order bit before being added to the table or emitted.
+ *
+ * In the event of two words hashing to the same value, the compression routine
+ * scans forwards for COLLISION_LIMIT entries, looking for a match or a spare
+ * table entry. If none is found, the word is output "as is" (i.e.
+ * uncompressed). You might speed up compression slightly by lowering
+ * COLLISION_LIMIT, at the cost of slightly lower compression ratios. 
+ *
+ * The collision limit does not affect _decompression_ as that merely involves
+ * a direct table lookup.
+ *
+ * Decompression
+ * -------------
+ *
+ * Decompression involves a simple loop, in which each byte of the compressed
+ * text is examined. If it has the high-order bit clear, it is just emitted.
+ * If the high order bit is set it, along with the next byte in the compressed
+ * text, are used to index into the word lookup table. The word found there is
+ * then emitted.
+ *
+ * Speed
+ * -----
+ *
+ * Tests conducted on (hopefully typical) text show that decompression is about
+ * 4 times as fast as Huffman decompression.
+ *
+ * Compression however is about 3.5 times as _slow_ as Huffman compression.
+ *
+ * The slower compression time is considered acceptable on the grounds that
+ * text is much more often _decompressed_ in a MUSH than compressed. Compression
+ * mainly takes part at database load time (say, once a week) whereas 
+ * decompression take part every hour, as the database is dumped to disk, and
+ * whenever an object description is displayed, or an attribute searched for,
+ *
+ * Compression ratio
+ * -----------------
+ *
+ * The "table compression" method has a poor compression ratio for small amounts
+ * of text, because of the overhead of the 32768 pointers, and the data stored
+ * in them. However, as the database size increases, the ratio improves because
+ * the table overhead becomes progressively less significant.
+ *
+ * The break-even points is with about 1.5 Mb of text, where both the table 
+ * compression and Huffman compress to about 63% of the size of the original.
+ *
+ * After that, the compression ratio gradually improves until reaching somewhere
+ * between 40% and 50% of the size of the original, as the amount of text to
+ * compress reaches 10 Mb.
+ *
+ * The nature of Huffman compression however is such that it will always be 
+ * fixed at about 63% regardless of the amount of data compressed.
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+#define MAXTABLE 32768         /**< Maximum words in the table */
+#define MAXWORDS 100           /**< Maximum size of a word */
+#define COLLISION_LIMIT 20     /**< Maximum allowed collisions */
+
+#define COMPRESS_HASH_MASK 0x7FFF      /**< 32767 in hex */
+
+#define TABLE_FLAG 0x80                /**< Distinguish a table */
+#define TABLE_MASK 0x7F                /**< Mask out words within a table */
+
+/* Table of words */
+
+static char *words[MAXTABLE];
+static size_t words_len[MAXTABLE];
+
+/* The word we are currently compressing */
+
+static char word[MAXWORDS + 2];
+static size_t wordpos = 0;
+
+/* Stats */
+#ifdef COMP_STATS
+static long total_mallocs = 0;
+static long total_uncomp = 0;
+static long total_comp = 0;
+static long total_entries = 0;
+#endif
+
+/* Work pointer for compression */
+
+static unsigned char *b;
+
+static void output_previous_word(void);
+int init_compress(FILE * f);
+#ifdef COMP_STATS
+void compress_stats(long *entries, long *mem_used,
+                   long *total_uncompressed, long *total_compressed);
+#endif
+static unsigned int hash_fn(const char *s, int hashtab_mask);
+
+static void
+output_previous_word(void)
+{
+  char *p;
+  int i, j;
+
+  word[wordpos++] = 0;         /* word's trailing null */
+
+  /* Don't bother putting single or double letter words in the table */
+
+  if (wordpos <= 3) {
+    p = word;
+    while (*p)
+      *b++ = *p++;
+    return;
+  }
+  /* search table to see if word is already in it; */
+
+  for (i = hash_fn(word, COMPRESS_HASH_MASK), j = 0;
+       i < MAXTABLE &&
+       (words[i] || (i & 0xFF) == 0) && j < COLLISION_LIMIT; i++, j++)
+    if (words[i])
+      if (strcmp(word, words[i]) == 0) {
+       *b++ = (i >> 8) | TABLE_FLAG;
+       *b++ = i & 0xFF;
+       return;
+      }
+  /* not in table, add to it */
+
+  if ((i & 0xFF) == 0) {
+    i++;                       /* make sure we don't have a null in the message */
+    j++;
+  }
+  /* Can't add to table if full */
+
+  if (i >= MAXTABLE || j >= COLLISION_LIMIT) {
+    p = word;
+    while (*p)
+      *b++ = *p++;
+    return;
+  }
+  words[i] = malloc(wordpos);
+
+  if (!words[i])
+    mush_panic("Out of memory in string compression routine");
+
+#ifdef COMP_STATS
+  total_mallocs += wordpos;
+  total_entries++;
+#endif
+
+  strncpy(words[i], word, wordpos);
+  words_len[i] = wordpos;
+
+  *b++ = (i >> 8) | TABLE_FLAG;
+  *b++ = i & 0xFF;
+
+}                              /* end of output_previous_word */
+
+/** Word-compress a string.
+ *
+ * Important notes:
+ *   This function mallocs memory that should be freed by the caller!
+ *   The caller is also currently responsible for adding mem checks
+ *   Don't use it to compress strings longer than BUFFER_LEN or the
+ *     later uncompression will not go well.
+ *
+ * \param s string to be compressed.
+ * \return newly allocated compressed string.
+ */
+unsigned char *
+compress(char const *s)
+{
+  const unsigned char *p;
+  static unsigned char buf[BUFFER_LEN];
+
+  p = (unsigned char *) s;
+  b = buf;
+
+  wordpos = 0;
+
+/* break up input into words */
+  while (*p) {
+    if (!(isdigit(*p) || isalpha(*p)) || wordpos >= MAXWORDS) {
+      if (wordpos) {
+       word[wordpos++] = *p & 0x7F;    /* add trailing punctuation */
+       output_previous_word();
+       wordpos = 0;
+      } else
+       *b++ = *p & 0x7F;
+    } else
+      word[wordpos++] = *p & 0x7F;
+    p++;
+  }
+
+  if (wordpos)
+    output_previous_word();
+
+  *b = 0;                      /* trailing null */
+
+#ifdef COMP_STATS
+  total_comp += u_strlen(buf); /* calculate size of compressed   text */
+  total_uncomp += strlen(s);   /* calculate size of uncompressed text */
+#endif
+
+  return u_strdup(buf);
+}                              /* end of compress; */
+
+
+/** Word-uncompress a string.
+ * To avoid generating memory problems, this function should be
+ * used with something of the format
+ * \verbatim
+ * char tbuf1[BUFFER_LEN];
+ * strcpy(tbuf1, uncompress(a->value));
+ * \endverbatim
+ * if you are using something of type char *buff, use the
+ * safe_uncompress function instead.
+ * 
+ * \param s a compressed string.
+ * \return a pointer to a static buffer containing the uncompressed string.
+ */
+char *
+uncompress(unsigned char const *s)
+{
+
+  const unsigned char *p;
+  char c;
+  int i;
+  static char buf[BUFFER_LEN];
+
+  buf[0] = '\0';
+  if (!s || !*s)
+    return buf;
+  p = (unsigned char *) s;
+  b = (unsigned char *) buf;
+
+  while (*p) {
+    c = *p;
+    if (c & TABLE_FLAG) {
+      i = ((c & TABLE_MASK) << 8) | *(++p);
+      if (i >= MAXTABLE || words[i] == NULL) {
+       static int panicking = 0;
+       if (panicking) {
+         do_rawlog(LT_ERR,
+                   "Error in string decompression occurred during panic dump.");
+         exit(1);
+       } else {
+         panicking = 1;        /* don't panic from within panic */
+         do_rawlog(LT_ERR, "Error in string decompression, i = %i", i);
+         mush_panic("Fatal error in decompression");
+       }
+      }
+      strncpy((char *) b, words[i], words_len[i]);
+      b += words_len[i] - 1;
+    } else
+      *b++ = c;
+    p++;
+  }
+
+  *b++ = 0;                    /* trailing null */
+
+  return buf;
+
+}                              /* end of uncompress; */
+
+/** Word-uncompress a string, allocating memory.
+ * this function should be used when you're doing something like
+ * \verbatim
+ * char *attrib = safe_uncompress(a->value);
+ *
+ * NEVER use it with something like
+ *
+ * char tbuf1[BUFFER_LEN];
+ * strcpy(tbuf1, safe_uncompress(a->value));
+ * \endverbatim
+ * or you will create a horrendous memory leak.
+ *
+ * \param s compressed string to uncompress.
+ * \return pointer to newly allocated string containing uncompressed text.
+ */
+char *
+safe_uncompress(unsigned char const *s)
+{
+  return (char *) strdup((char *) uncompress(s));
+}
+
+
+/** Initialize the word compression.
+ * This function clears the words table the first time through.
+ * \param f (unused).
+ */
+int
+init_compress(FILE * f __attribute__ ((__unused__)))
+{
+  memset(words, 0, sizeof words);
+  memset(words_len, 0, sizeof words_len);
+  return 0;
+}
+
+#ifdef COMP_STATS
+/** Return word-compression statistics.
+ */
+void
+compress_stats(long *entries, long *mem_used, long *total_uncompressed,
+              long *total_compressed)
+{
+
+  *entries = total_entries;
+  *mem_used = total_mallocs;
+  *total_uncompressed = total_uncomp;
+  *total_compressed = total_comp;
+
+}
+#endif
+
+static unsigned
+hash_fn(const char *s, int hashtab_mask)
+{
+  /* hash function, using masks (based on TinyMUSH 2.0) */
+
+  unsigned hashval;
+  const char *p;
+
+  for (hashval = 0, p = s; *p; p++)
+    hashval = (hashval << 5) + hashval + *p;
+  return (hashval & hashtab_mask);
+}
diff --git a/src/comp_w8.c b/src/comp_w8.c
new file mode 100644 (file)
index 0000000..bc0604d
--- /dev/null
@@ -0,0 +1,402 @@
+/**
+ * \file comp_w8.c
+ *
+ * \brief Word-based compression.
+ *
+ * Table-lookup (word) compress, written by Nick Gammon. 21/Oct/95.
+ * 8bit clean version by Shawn Wagner.
+ *
+ * This method maintains a table of 32768 words, where a "word" is defined
+ * as a sequence of alpha or numeric characters (such as "dog123").
+ *
+ * Compression
+ * -----------
+ *
+ * The text to be compressed is broken up into "words" and other symbols
+ * (e.g. commas, brackets, spaces and so on).
+ *
+ * Words are looked up in the word table by using a hash-table lookup. If
+ * found, the "index" into the table is emitted with the high order bit set.
+ * If not found, the word is added to the table, and is then emitted as
+ * described above.
+ *
+ * For example: "The dog and the other dog sat on the mat (eating fish)."
+ * would compress as:
+ *
+ * 0x8001      The
+ * 0x8002      dog
+ * 0x8003      and
+ * 0x8004      the
+ * 0x8005      other
+ * 0x8002      dog
+ * 0x8007      sat
+ * 0x8008      on
+ * 0x8004      the
+ * 0x8009      mat
+ * 0x28        (
+ * 0x800A      eating
+ * 0x800B      fish)
+ * 0x2E        .
+ *
+ * In the above example, the uncompressed text is 55 bytes, and the compressed 
+ * text is 26 bytes.
+ *
+ * For simplicity, the above example assumes that the words hash to consecutive
+ * table positions.
+ *
+ * Note that the trailing punctuation character (space, period or whatever) is 
+ * considered _part_ of the word. This is to save having to store multiple 
+ * spaces between each word, and is relying on the fact that a certain word is
+ * usually followed by the same punctuation. For example, the word "and" would
+ * normally be followed by a space.
+ *
+ * In the above example, the space following "mat" is stored in the table, and
+ * thus the "(" character had to be output separately. The ")" following the
+ * word "fish" is considered part of the word and is stored in the table, 
+ * however the last period was output as a separate character.
+ *
+ * Note how the high-order bit is turned on for words in the table, this is so
+ * the decompression routine can detect table lookup characters from ordinary
+ * ones. Also, any table index with a low-order byte of zero cannot be used,
+ * as this would cause the resulting "string" to be prematurely truncated (by
+ * strcpy, strcmp etc.). Thus the lookup routine skips any positions that hash
+ * to the value 0xXX00. (There are only 256 such indices).
+ *
+ * Also, to prevent characters which the user might type in with the high order
+ * bit set causing decompression confusion, all text is stripped of its high
+ * order bit before being added to the table or emitted.
+ *
+ * In the event of two words hashing to the same value, the compression routine
+ * scans forwards for COLLISION_LIMIT entries, looking for a match or a spare
+ * table entry. If none is found, the word is output "as is" (i.e.
+ * uncompressed). You might speed up compression slightly by lowering
+ * COLLISION_LIMIT, at the cost of slightly lower compression ratios. 
+ *
+ * The collision limit does not affect _decompression_ as that merely involves
+ * a direct table lookup.
+ *
+ * Decompression
+ * -------------
+ *
+ * Decompression involves a simple loop, in which each byte of the compressed
+ * text is examined. If it has the high-order bit clear, it is just emitted.
+ * If the high order bit is set it, along with the next byte in the compressed
+ * text, are used to index into the word lookup table. The word found there is
+ * then emitted.
+ *
+ * Speed
+ * -----
+ *
+ * Tests conducted on (hopefully typical) text show that decompression is about
+ * 4 times as fast as Huffman decompression.
+ *
+ * Compression however is about 3.5 times as _slow_ as Huffman compression.
+ *
+ * The slower compression time is considered acceptable on the grounds that
+ * text is much more often _decompressed_ in a MUSH than compressed. Compression
+ * mainly takes part at database load time (say, once a week) whereas 
+ * decompression take part every hour, as the database is dumped to disk, and
+ * whenever an object description is displayed, or an attribute searched for,
+ *
+ * Compression ratio
+ * -----------------
+ *
+ * The "table compression" method has a poor compression ratio for small amounts
+ * of text, because of the overhead of the 32768 pointers, and the data stored
+ * in them. However, as the database size increases, the ratio improves because
+ * the table overhead becomes progressively less significant.
+ *
+ * The break-even points is with about 1.5 Mb of text, where both the table 
+ * compression and Huffman compress to about 63% of the size of the original.
+ *
+ * After that, the compression ratio gradually improves until reaching somewhere
+ * between 40% and 50% of the size of the original, as the amount of text to
+ * compress reaches 10 Mb.
+ *
+ * The nature of Huffman compression however is such that it will always be 
+ * fixed at about 63% regardless of the amount of data compressed.
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+#define MAXTABLE 32768         /**< Maximum words in the table */
+#define MAXWORDS 100           /**< Maximum length of a word */
+#define COLLISION_LIMIT 20     /**< Maximum allowed collisions */
+
+#define COMPRESS_HASH_MASK 0x7FFF      /**< 32767 in hex */
+
+#define MARKER_CHAR 0x06       /**< Separates words. This char is the only one that can't be represented */
+#define TABLE_FLAG 0x80                /**< Distinguishes a table */
+#define TABLE_MASK 0x7F                /**< Mask out words within a table */
+
+/* Table of words */
+
+static char *words[MAXTABLE];
+static size_t words_len[MAXTABLE];
+
+/* The word we are currently compressing */
+
+static char word[MAXWORDS + 2];
+static size_t wordpos = 0;
+
+/* Stats */
+#ifdef COMP_STATS
+static long total_mallocs = 0;
+static long total_uncomp = 0;
+static long total_comp = 0;
+static long total_entries = 0;
+#endif
+
+/* Work pointer for compression */
+
+static unsigned char *b;
+
+static void output_previous_word(void);
+int init_compress(FILE * f);
+#ifdef COMP_STATS
+void compress_stats(long *entries, long *mem_used,
+                   long *total_uncompressed, long *total_compressed);
+#endif
+static unsigned int hash_fn(const char *s, int hashtab_mask);
+
+static void
+output_previous_word(void)
+{
+  char *p;
+  int i, j;
+
+  word[wordpos++] = 0;         /* word's trailing null */
+
+/* Don't bother putting few-letter words in the table */
+
+  if (wordpos <= 4) {
+    p = word;
+    while (*p)
+      *b++ = *p++;
+    return;
+  }
+/* search table to see if word is already in it; */
+
+  for (i = hash_fn(word, COMPRESS_HASH_MASK), j = 0;
+       i < MAXTABLE &&
+       (words[i] || (i & 0xFF) == 0) && j < COLLISION_LIMIT; i++, j++)
+    if (words[i])
+      if (strcmp(word, words[i]) == 0) {
+       *b++ = MARKER_CHAR;
+       *b++ = (i >> 8) | TABLE_FLAG;
+       *b++ = i & 0xFF;
+       return;
+      }
+/* not in table, add to it */
+
+  if ((i & 0xFF) == 0) {
+    i++;                       /* make sure we don't have a null in the message */
+    j++;
+  }
+/* Can't add to table if full */
+
+  if (i >= MAXTABLE || j >= COLLISION_LIMIT) {
+    p = word;
+    while (*p)
+      *b++ = *p++;
+    return;
+  }
+  words[i] = malloc(wordpos);
+
+  if (!words[i])
+    mush_panic("Out of memory in string compression routine");
+
+#ifdef COMP_STATS
+  total_mallocs += wordpos;
+  total_entries++;
+#endif
+
+  strncpy(words[i], word, wordpos);
+  words_len[i] = wordpos;
+
+  *b++ = MARKER_CHAR;
+  *b++ = (i >> 8) | TABLE_FLAG;
+  *b++ = i & 0xFF;
+
+}                              /* end of output_previous_word */
+
+/** Word-compress a string.
+ *
+ * Important notes:
+ *   This function mallocs memory that should be freed by the caller!
+ *   The caller is also currently responsible for adding mem checks
+ *   Don't use it to compress strings longer than BUFFER_LEN or the
+ *     later uncompression will not go well.
+ *
+ * \param s string to be compressed.
+ * \return newly allocated compressed string.
+ */
+unsigned char *
+compress(char const *s)
+{
+  const unsigned char *p;
+  static unsigned char buf[BUFFER_LEN];
+
+  p = (unsigned char *) s;
+  b = buf;
+
+  wordpos = 0;
+
+/* break up input into words */
+  while (*p) {
+    if (!(isdigit(*p) || isalpha(*p)) || wordpos >= MAXWORDS) {
+      if (wordpos) {
+       word[wordpos++] = *p;   /* add trailing punctuation */
+       output_previous_word();
+       wordpos = 0;
+      } else
+       *b++ = *p;
+    } else
+      word[wordpos++] = *p;
+    p++;
+  }
+
+  if (wordpos)
+    output_previous_word();
+
+  *b = 0;                      /* trailing null */
+
+#ifdef COMP_STATS
+  total_comp += u_strlen(buf); /* calculate size of compressed   text */
+  total_uncomp += strlen(s);   /* calculate size of uncompressed text */
+#endif
+
+  return u_strdup(buf);
+}                              /* end of compress; */
+
+
+/** Word-uncompress a string.
+ * To avoid generating memory problems, this function should be
+ * used with something of the format
+ * \verbatim
+ * char tbuf1[BUFFER_LEN];
+ * strcpy(tbuf1, uncompress(a->value));
+ * \endverbatim
+ * if you are using something of type char *buff, use the
+ * safe_uncompress function instead.
+ * 
+ * \param s a compressed string.
+ * \return a pointer to a static buffer containing the uncompressed string.
+ */
+char *
+uncompress(unsigned char const *s)
+{
+
+  const unsigned char *p;
+  char c;
+  int i;
+  static char buf[BUFFER_LEN];
+
+  buf[0] = '\0';
+  if (!s || !*s)
+    return buf;
+  p = (unsigned char *) s;
+  b = buf;
+
+  while (*p) {
+    c = *p;
+    if (c == MARKER_CHAR) {
+      p++;
+      c = *p;
+      i = ((c & TABLE_MASK) << 8) | *(++p);
+      if (i >= MAXTABLE || words[i] == NULL) {
+       static int panicking = 0;
+       if (panicking) {
+         fprintf(stderr,
+                 "Error in string decompression occurred during panic dump.\n");
+         exit(1);
+       } else {
+         panicking = 1;        /* don't panic from within panic */
+         fprintf(stderr, "Error in string decompression, i = %i\n", i);
+         mush_panic("Fatal error in decompression");
+       }
+      }
+      strncpy((char *) b, words[i], words_len[i]);
+      b += words_len[i] - 1;
+    } else
+      *b++ = c;
+    p++;
+  }
+
+  *b++ = 0;                    /* trailing null */
+
+  return buf;
+
+}                              /* end of uncompress; */
+
+/** Word-uncompress a string, allocating memory.
+ * this function should be used when you're doing something like
+ * \verbatim
+ * char *attrib = safe_uncompress(a->value);
+ *
+ * NEVER use it with something like
+ *
+ * char tbuf1[BUFFER_LEN];
+ * strcpy(tbuf1, safe_uncompress(a->value));
+ * \endverbatim
+ * or you will create a horrendous memory leak.
+ *
+ * \param s compressed string to uncompress.
+ * \return pointer to newly allocated string containing uncompressed text.
+ */
+char *
+safe_uncompress(unsigned char const *s)
+{
+  return (char *) strdup((char *) uncompress(s));
+}
+
+
+/** Initialize the word compression.
+ * This function clears the words table the first time through.
+ * \param f (unused).
+ */
+int
+init_compress(FILE * f __attribute__ ((__unused__)))
+{
+  memset(words, 0, sizeof words);
+  memset(words_len, 0, sizeof words_len);
+  return 0;
+}
+
+#ifdef COMP_STATS
+/** Return word-compression statistics.
+ */
+void
+compress_stats(long *entries, long *mem_used, long *total_uncompressed,
+              long *total_compressed)
+{
+
+  *entries = total_entries;
+  *mem_used = total_mallocs;
+  *total_uncompressed = total_uncomp;
+  *total_compressed = total_comp;
+
+}
+#endif
+
+static unsigned
+hash_fn(const char *s, int hashtab_mask)
+{
+  /* hash function, using masks (based on TinyMUSH 2.0) */
+
+  unsigned hashval;
+  const char *p;
+
+  for (hashval = 0, p = s; *p; p++)
+    hashval = (hashval << 5) + hashval + *p;
+  return (hashval & hashtab_mask);
+}
diff --git a/src/compress.c b/src/compress.c
new file mode 100644 (file)
index 0000000..1c8fa49
--- /dev/null
@@ -0,0 +1,41 @@
+/**
+ * \file compress.c
+ *
+ * \brief Compression routine wrapper file for PennMUSH.
+ *
+ * This file does nothing but conditionally include the appropriate
+ * attribute compression source code.
+ *
+ */
+
+#include "config.h"
+#include "options.h"
+
+/* It's rather dumb mushtype.h has to be included */
+#include "mushtype.h"
+#include "log.h"
+
+#if defined(COMPRESSION_TYPE) && (COMPRESSION_TYPE == 0)
+/* No compression */
+char ucbuff[BUFFER_LEN];       /**< Dummy buffer for no compression */
+
+#elif (COMPRESSION_TYPE == 1) || (COMPRESSION_TYPE == 2)
+/* Huffman compression */
+#include "comp_h.c"
+
+#elif (COMPRESSION_TYPE == 3)
+/* Word compression - necessary for Win32 */
+#include "comp_w.c"
+#elif (COMPRESSION_TYPE == 4)
+/* Nearly 8-bit clean word compression. Prefer 3 unless you're using a 
+ * language with an extended character set. 0x06 is the only character
+ * we can't encode right now.
+ */
+#include "comp_w8.c"
+#else
+/* You didn't define it, or gave an invalid value!
+ * Lucky for you, we're forgiving. You get no compression.
+ */
+char ucbuff[BUFFER_LEN];
+
+#endif                         /* Compression type checks */
diff --git a/src/conf.c b/src/conf.c
new file mode 100644 (file)
index 0000000..8004144
--- /dev/null
@@ -0,0 +1,1686 @@
+/**
+ * \file conf.c
+ *
+ * \brief PennMUSH runtime configuration.
+ *
+ * configuration adjustment. Some of the ideas and bits and pieces of the
+ * code here are based on TinyMUSH 2.0.
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "pueblo.h"
+#include "mushdb.h"
+#include "parse.h"
+#include "boolexp.h"
+#include "command.h"
+#include "flags.h"
+#include "log.h"
+#include "dbdefs.h"
+#include "game.h"
+#include "attrib.h"
+#include "help.h"
+#include "function.h"
+#include "confmagic.h"
+
+time_t mudtime;                        /**< game time, in seconds */
+
+static void show_compile_options(dbref player);
+static char *config_list_helper(dbref player, COBRA_CONF *cp, int lc);
+static char *config_list_helper2(dbref player, COBRA_CONF *cp, int lc);
+
+OPTTAB options;                /**< The table of configuration options */
+HASHTAB local_options; /**< Hash table for local config options */
+
+int config_set(const char *opt, char *val, int source, int restrictions);
+void conf_default_set(void);
+
+/** Table of all runtime configuration options. */
+COBRA_CONF conftable[] = {
+  {"input_database", cf_str, options.input_db, sizeof options.input_db, 0,
+   "files"}
+  ,
+  {"output_database", cf_str, options.output_db, sizeof options.output_db, 0,
+   "files"}
+  ,
+    {"flag_database", cf_str, options.flagdb, sizeof options.flagdb, 0, "files"}
+  ,
+  {"crash_database", cf_str, options.crash_db, sizeof options.crash_db, 0,
+   "files"}
+  ,
+  {"mail_database", cf_str, options.mail_db, sizeof options.mail_db, 0, "files"}
+  ,
+  {"chat_database", cf_str, options.chatdb, sizeof options.chatdb, 0, "files"}
+  ,
+  {"compress_suffix", cf_str, options.compresssuff, sizeof options.compresssuff,
+   0,
+   "files"}
+  ,
+  {"compress_program", cf_str, options.compressprog,
+   sizeof options.compressprog, 0,
+   "files"}
+  ,
+  {"uncompress_program", cf_str, options.uncompressprog,
+   sizeof options.uncompressprog, 0,
+   "files"}
+  ,
+  {"access_file", cf_str, options.access_file, sizeof options.access_file, 0,
+   "files"}
+  ,
+  {"names_file", cf_str, options.names_file, sizeof options.access_file, 0,
+   "files"}
+  ,
+  {"connect_file", cf_str, options.connect_file[0],
+   sizeof options.connect_file[0], 0, "messages"}
+  ,
+  {"motd_file", cf_str, options.motd_file[0], sizeof options.motd_file[0], 0,
+   "messages"}
+  ,
+  {"newuser_file", cf_str, options.newuser_file[0],
+   sizeof options.newuser_file[0], 0, "messages"}
+  ,
+
+  {"register_create_file", cf_str, options.register_file[0],
+   sizeof options.register_file[0],
+   0, "messages"}
+  ,
+  {"quit_file", cf_str, options.quit_file[0], sizeof options.quit_file[0], 0,
+   "messages"}
+  ,
+  {"down_file", cf_str, options.down_file[0], sizeof options.down_file[0], 0,
+   "messages"}
+  ,
+  {"full_file", cf_str, options.full_file[0], sizeof options.full_file[0], 0,
+   "messages"}
+  ,
+  {"guest_file", cf_str, options.guest_file[0], sizeof options.guest_file[0], 0,
+   "messages"}
+  ,
+
+  {"connect_html_file", cf_str, options.connect_file[1],
+   sizeof options.connect_file[1], 0,
+   "messages"}
+  ,
+  {"motd_html_file", cf_str, options.motd_file[1],
+   sizeof options.connect_file[1], 0,
+   "messages"}
+  ,
+  {"newuser_html_file", cf_str, options.newuser_file[1],
+   sizeof options.newuser_file[1], 0,
+   "messages"}
+  ,
+  {"register_create_html_file", cf_str, options.register_file[1],
+   sizeof options.register_file[1], 0, "messages"}
+  ,
+  {"quit_html_file", cf_str, options.quit_file[1], sizeof options.quit_file[1],
+   0,
+   "messages"}
+  ,
+  {"down_html_file", cf_str, options.down_file[1], sizeof options.down_file[1],
+   0,
+   "messages"}
+  ,
+  {"full_html_file", cf_str, options.full_file[1], sizeof options.full_file[1],
+   0,
+   "messages"}
+  ,
+  {"guest_html_file", cf_str, options.guest_file[1],
+   sizeof options.guest_file[1], 0,
+   "messages"}
+  ,
+
+    {"def_powergroup", cf_str, &options.player_powergroup, sizeof options.player_powergroup, 0, "db"}
+   ,
+  {"player_start", cf_dbref, &options.player_start, 100000, 0, "db"}
+  ,
+    {"guest_start", cf_dbref, &options.guest_start, 100000, 0, "db"}
+  ,
+  {"master_room", cf_dbref, &options.master_room, 100000, 0, "db"}
+  ,
+  {"base_room", cf_dbref, &options.base_room, 100000, 0, "db"}
+  ,
+  {"default_home", cf_dbref, &options.default_home, 100000, 0, "db"}
+  ,
+  {"sql_mroom", cf_dbref, &options.sql_master, 100000, 0, "db"}
+  ,
+  {"exits_connect_rooms", cf_bool, &options.exits_connect_rooms, 2, 0, "db"}
+  ,
+  {"zone_control_zmp_only", cf_bool, &options.zone_control, 2, 0, "db"}
+  ,
+  {"ancestor_room", cf_int, &options.ancestor_room, 100000, 0, "db"}
+  ,
+  {"ancestor_exit", cf_int, &options.ancestor_exit, 100000, 0, "db"}
+  ,
+  {"ancestor_thing", cf_int, &options.ancestor_thing, 100000, 0, "db"}
+  ,
+  {"ancestor_player", cf_int, &options.ancestor_player, 100000, 0, "db"}
+  ,
+  {"powerless", cf_int, &options.powerless, 100000, 0, "db"}
+  ,
+  {"mud_name", cf_str, options.mud_name, 128, 0, "net"}
+  ,
+  {"ip_addr", cf_str, options.ip_addr, 64, 0, "net"}
+  ,
+  {"ssl_ip_addr", cf_str, options.ssl_ip_addr, 64, 0, "net"}
+  ,
+  {"port", cf_int, &options.port, 32000, 0, "net"}
+  ,
+  {"ssl_port", cf_int, &options.ssl_port, 32000, 0, "net"}
+  ,
+  {"use_dns", cf_bool, &options.use_dns, 2, 0, "net"}
+  ,
+  {"use_ident", cf_bool, &options.use_ident, 2, 0, "net"}
+  ,
+  {"ident_timeout", cf_time, &options.ident_timeout, 60, 0, "net"}
+  ,
+  {"logins", cf_bool, &options.login_allow, 2, 0, "net"}
+  ,
+  {"player_creation", cf_bool, &options.create_allow, 2, 0, "net"}
+  ,
+  {"guests", cf_bool, &options.guest_allow, 2, 0, "net"}
+  ,
+  {"pueblo", cf_bool, &options.support_pueblo, 2, 0, "net"}
+  ,
+#ifdef HAS_MYSQL
+  {"sql_platform", cf_str, options.sql_platform, sizeof options.sql_platform, 0,
+   "net"}
+  ,
+  {"sql_host", cf_str, options.sql_host, sizeof options.sql_host, 0, "net"}
+  ,
+  {"sql_username", cf_str, options.sql_username, sizeof options.sql_username, 0,
+   NULL}
+  ,
+  {"sql_password", cf_str, options.sql_password, sizeof options.sql_password, 0,
+   NULL}
+  ,
+  {"sql_database", cf_str, options.sql_database, sizeof options.sql_database, 0,
+   NULL}
+  ,
+#endif
+
+  {"forking_dump", cf_bool, &options.forking_dump, 2, 0, "dump"}
+  ,
+  {"dump_message", cf_str, options.dump_message, sizeof options.dump_message, 0,
+   "dump"}
+  ,
+  {"dump_complete", cf_str, options.dump_complete, sizeof options.dump_complete,
+   0, "dump"}
+  ,
+  {"dump_warning_1min", cf_str, options.dump_warning_1min,
+   sizeof options.dump_warning_1min,
+   0, "dump"}
+  ,
+  {"dump_warning_5min", cf_str, options.dump_warning_5min,
+   sizeof options.dump_warning_5min, 0,
+   "dump"}
+  ,
+  {"dump_interval", cf_time, &options.dump_interval, 100000, 0, "dump"}
+  ,
+  {"warn_interval", cf_time, &options.warn_interval, 32000, 0, "dump"}
+  ,
+  {"purge_interval", cf_time, &options.purge_interval, 10000, 0, "dump"}
+  ,
+  {"dbck_interval", cf_time, &options.dbck_interval, 10000, 0, "dump"}
+  ,
+
+  {"money_singular", cf_str, options.money_singular,
+   sizeof options.money_singular, 0,
+   "cosmetic"}
+  ,
+  {"money_plural", cf_str, options.money_plural, sizeof options.money_plural, 0,
+   "cosmetic"}
+  ,
+  {"player_name_spaces", cf_bool, &options.player_name_spaces, 2, 0,
+   "cosmetic"}
+  ,
+  {"ansi_names", cf_bool, &options.ansi_names, 2, 0, "cosmetic"}
+  ,
+  {"only_ascii_in_names", cf_bool, &options.ascii_names, 2, 0, "cosmetic"}
+  ,
+  {"float_precision", cf_int, &options.float_precision, 10000, 0, "cosmetic"}
+  ,
+  {"comma_exit_list", cf_bool, &options.comma_exit_list, 2, 0, "cosmetic"}
+  ,
+  {"count_all", cf_bool, &options.count_all, 2, 0, "cosmetic"}
+  ,
+  {"blind_page", cf_bool, &options.blind_page, 2, 0, "cosmetic"}
+  ,
+  {"page_aliases", cf_bool, &options.page_aliases, 2, 0, "cosmetic"}
+  ,
+  {"flags_on_examine", cf_bool, &options.flags_on_examine, 2, 0, "cosmetic"}
+  ,
+  {"ex_public_attribs", cf_bool, &options.ex_public_attribs, 2, 0,
+   "cosmetic"}
+  ,
+  {"wall_prefix", cf_str, options.wall_prefix, sizeof options.wall_prefix, 0,
+   "cosmetic"}
+  ,
+  {"announce_connects", cf_bool, &options.announce_connects, 2, 0, "cosmetic"}
+  ,
+  {"chat_strip_quote", cf_bool, &options.chat_strip_quote, 2, 0, "cosmetic"}
+  ,
+  {"newline_one_char", cf_bool, &options.newline_one_char, 2, 0, "cosmetic"}
+  ,
+
+  {"max_dbref", cf_dbref, &options.max_dbref, -1, 0, "limits"}
+  ,
+  {"max_attrs_per_obj", cf_int, &options.max_attrcount, 8192, 0, "limits"}
+  ,
+  {"max_logins", cf_int, &options.max_logins, 128, 0, "limits"}
+  ,
+  {"max_guests", cf_int, &options.max_guests, 128, 0, "limits"}
+  ,
+  {"idle_timeout", cf_time, &options.idle_timeout, 100000, 0, "limits"}
+  ,
+  {"idle_time", cf_time, &options.idle_time, 100000, 0, "limits"}
+  ,
+  {"unconnected_idle_timeout", cf_time, &options.unconnected_idle_timeout,
+   100000, 0, "limits"}
+  ,
+  {"whisper_loudness", cf_int, &options.whisper_loudness, 100, 0, "limits"}
+  ,
+  {"starting_quota", cf_int, &options.starting_quota, 10000, 0, "limits"}
+  ,
+  {"starting_money", cf_int, &options.starting_money, 10000, 0, "limits"}
+  ,
+  {"paycheck", cf_int, &options.paycheck, 1000, 0, "limits"}
+  ,
+  {"guest_paycheck", cf_int, &options.guest_paycheck, 1000, 0, "limits"}
+  ,
+  {"max_pennies", cf_int, &options.max_pennies, 100000, 0, "limits"}
+  ,
+  {"max_guest_pennies", cf_int, &options.max_guest_pennies, 100000, 0,
+   "limits"}
+  ,
+  {"max_parents", cf_int, &options.max_parents, 10000, 0, "limits"}
+  ,
+  {"mail_limit", cf_int, &options.mail_limit, 5000, 0, "limits"}
+  ,
+  {"max_depth", cf_int, &options.max_depth, 10000, 0, "limits"}
+  ,
+  {"player_queue_limit", cf_int, &options.player_queue_limit, 100000, 0,
+   "limits"}
+  ,
+  {"queue_loss", cf_int, &options.queue_loss, 10000, 0, "limits"}
+  ,
+  {"queue_chunk", cf_int, &options.queue_chunk, 100000, 0, "limits"}
+  ,
+  {"active_queue_chunk", cf_int, &options.active_q_chunk, 100000, 0,
+   "limits"}
+  ,
+  {"function_recursion_limit", cf_int, &options.func_nest_lim, 100000, 0,
+   "limits"}
+  ,
+  {"function_invocation_limit", cf_int, &options.func_invk_lim, 100000, 0,
+   "limits"}
+  ,
+  {"call_limit", cf_int, &options.call_lim, 1000000, 0,
+   "limits"}
+  ,
+  {"player_name_len", cf_int, &options.player_name_len, BUFFER_LEN, 0,
+   "limits"}
+  ,
+  {"queue_entry_cpu_time", cf_int, &options.queue_entry_cpu_time, 100000, 0,
+   "limits"}
+  ,
+  {"max_global_fns", cf_int, &options.max_global_fns, 2000, 0, 0}
+  ,
+  {"use_quota", cf_bool, &options.use_quota, 2, 0, "limits"}
+  ,
+  {"max_channels", cf_int, &options.max_channels, 1000, 0, "chat"}
+  ,
+  {"max_player_chans", cf_int, &options.max_player_chans, 100, 0, "chat"}
+  ,
+  {"chan_cost", cf_int, &options.chan_cost, 10000, 0, "chat"}
+  ,
+
+  {"log_commands", cf_bool, &options.log_commands, 2, 0, "log"}
+  ,
+  {"log_forces", cf_bool, &options.log_forces, 2, 0, "log"}
+  ,
+  {"error_log", cf_str, options.error_log, sizeof options.error_log, 0,
+   "log"}
+  ,
+  {"command_log", cf_str, options.command_log, sizeof options.command_log, 0,
+   "log"}
+  ,
+  {"wizard_log", cf_str, options.wizard_log, sizeof options.wizard_log, 0,
+   "log"}
+  ,
+  {"checkpt_log", cf_str, options.checkpt_log, sizeof options.checkpt_log, 0,
+   "log"}
+  ,
+  {"trace_log", cf_str, options.trace_log, sizeof options.trace_log, 0,
+   "log"}
+  ,
+  {"connect_log", cf_str, options.connect_log, sizeof options.connect_log, 0,
+   "log"}
+  ,
+
+  {"player_flags", cf_flag, options.player_flags, sizeof options.player_flags,
+   0, "flags"}
+  ,
+  {"room_flags", cf_flag, options.room_flags, sizeof options.room_flags, 0,
+   "flags"}
+  ,
+  {"exit_flags", cf_flag, options.exit_flags, sizeof options.exit_flags, 0,
+   "flags"}
+  ,
+  {"thing_flags", cf_flag, options.thing_flags, sizeof options.thing_flags, 0,
+   "flags"}
+  ,
+  {"channel_flags", cf_flag, options.channel_flags,
+   sizeof options.channel_flags, 0,
+   "flags"}
+  ,
+
+  {"safer_ufun", cf_bool, &options.safer_ufun, 2, 0, "funcs"}
+  ,
+  {"function_side_effects", cf_bool, &options.function_side_effects, 2, 0,
+   "funcs"}
+  ,
+
+  {"noisy_whisper", cf_bool, &options.noisy_whisper, 2, 0, "cmds"}
+  ,
+  {"possessive_get", cf_bool, &options.possessive_get, 2, 0, "cmds"}
+  ,
+  {"possessive_get_d", cf_bool, &options.possessive_get_d, 2, 0, "cmds"}
+  ,
+  {"link_to_object", cf_bool, &options.link_to_object, 2, 0, "cmds"}
+  ,
+  {"owner_queues", cf_bool, &options.owner_queues, 2, 0, "cmds"}
+  ,
+  {"full_invis", cf_bool, &options.full_invis, 2, 0, "cmds"}
+  ,
+  {"dark_noaenter", cf_bool, &options.dark_noaenter, 2, 0, "cmds"}
+  ,
+  {"really_safe", cf_bool, &options.really_safe, 2, 0, "cmds"}
+  ,
+  {"destroy_possessions", cf_bool, &options.destroy_possessions, 2, 0,
+   "cmds"}
+  ,
+
+  {"null_eq_zero", cf_bool, &options.null_eq_zero, 2, 0, "tiny"}
+  ,
+  {"tiny_booleans", cf_bool, &options.tiny_booleans, 2, 0, "tiny"}
+  ,
+  {"tiny_trim_fun", cf_bool, &options.tiny_trim_fun, 2, 0, "tiny"}
+  ,
+  {"tiny_math", cf_bool, &options.tiny_math, 2, 0, "tiny"}
+  ,
+  {"twinchecks", cf_bool, &options.twinchecks, 2, 0, "tiny" }
+  ,
+  {"silent_pemit", cf_bool, &options.silent_pemit, 2, 0, "tiny"}
+  ,
+
+  {"adestroy", cf_bool, &options.adestroy, 2, 0, "attribs"}
+  ,
+  {"amail", cf_bool, &options.amail, 2, 0, "attribs"}
+  ,
+  {"player_listen", cf_bool, &options.player_listen, 2, 0, "attribs"}
+  ,
+  {"player_ahear", cf_bool, &options.player_ahear, 2, 0, "attribs"}
+  ,
+  {"startups", cf_bool, &options.startups, 2, 0, "attribs"}
+  ,
+  {"read_remote_desc", cf_bool, &options.read_remote_desc, 2, 0, "attribs"}
+  ,
+  {"room_connects", cf_bool, &options.room_connects, 2, 0, "attribs"}
+  ,
+  {"reverse_shs", cf_bool, &options.reverse_shs, 2, 0, "attribs"}
+  ,
+  {"empty_attrs", cf_bool, &options.empty_attrs, 2, 0, "attribs"}
+  ,
+  {"object_cost", cf_int, &options.object_cost, 10000, 0, "costs"}
+  ,
+  {"exit_cost", cf_int, &options.exit_cost, 10000, 0, "costs"}
+  ,
+  {"link_cost", cf_int, &options.link_cost, 10000, 0, "costs"}
+  ,
+  {"room_cost", cf_int, &options.room_cost, 10000, 0, "costs"}
+  ,
+  {"queue_cost", cf_int, &options.queue_cost, 10000, 0, "costs"}
+  ,
+  {"quota_cost", cf_int, &options.quota_cost, 10000, 0, "costs"}
+  ,
+  {"find_cost", cf_int, &options.find_cost, 10000, 0, "costs"}
+  ,
+  {"page_cost", cf_int, &options.page_cost, 10000, 0, "costs"}
+  ,
+  {"kill_default_cost", cf_int, &options.kill_default_cost, 10000, 0,
+   "costs"}
+  ,
+  {"kill_min_cost", cf_int, &options.kill_min_cost, 10000, 0, "costs"}
+  ,
+  {"kill_bonus", cf_int, &options.kill_bonus, 100, 0, "costs"}
+  ,
+
+  {"log_wipe_passwd", cf_str, options.log_wipe_passwd,
+   sizeof options.log_wipe_passwd, 0,
+   NULL}
+  ,
+
+  {"chunk_swap_file", cf_str, options.chunk_swap_file,
+   sizeof options.chunk_swap_file, 0, "files"}
+  ,
+  {"chunk_cache_memory", cf_int, &options.chunk_cache_memory,
+   1000000000, 65510 * 2, "files"}
+  ,
+  {"chunk_migrate", cf_int, &options.chunk_migrate_amount, 100000, 0,
+   "limits"}
+  ,
+#ifdef HAS_OPENSSL
+  {"ssl_private_key_file", cf_str, options.ssl_private_key_file,
+   sizeof options.ssl_private_key_file, 0, "files"}
+  ,
+  {"ssl_ca_file", cf_str, options.ssl_ca_file,
+   sizeof options.ssl_ca_file, 0, "files"}
+  ,
+  {"ssl_require_client_cert", cf_bool, &options.ssl_require_client_cert,
+   2, 0, "net"}
+  ,
+#endif
+  {"mem_check", cf_bool, &options.mem_check, 2, 0, "log"}
+  ,
+ /* Guest Name Options */ 
+  {"guest_prefix", cf_str, options.guest_prefix, 127, 0, "cosmetic"}
+  ,
+  {"guest_numeral", cf_bool, &options.guest_roman_numeral, 2, 0, "cosmetic"}
+  ,
+  {NULL, NULL, NULL, 0, 0, NULL}
+};
+
+/** A runtime configuration group.
+ * This struction represents the name and information about a group
+ * of runtime configuration directives. Groups are used to organize
+ * the display of configuration options.
+ */
+typedef struct confgroupparm {
+  const char *name;            /**< name of group */
+  const char *desc;            /**< description of group */
+  int viewperms;               /**< who can view this group */
+} COBRA_CONFGROUP;
+
+/** The table of all configuration groups. */
+COBRA_CONFGROUP confgroups[] = {
+  {"attribs", "Options affecting attributes", 0},
+  {"chat", "Chat system options", 0},
+  {"cmds", "Options affecting command behavior", 0},
+  {"compile", "Compile-time options", 0},
+  {"cosmetic", "Cosmetic options", 0},
+  {"costs", "Costs", 0},
+  {"db", "Database options", 0},
+  {"dump", "Options affecting dumps and other periodic processes", 0},
+  {"files", "Files used by the MUSH", CGP_GOD},
+  {"flags", "Default flags for new objects", 0},
+  {"funcs", "Options affecting function behavior", 0},
+  {"limits", "Limits and other constants", 0},
+  {"log", "Logging options", 0},
+  {"messages", "Message files sent by the MUSH", CGP_GOD},
+  {"net", "Networking and connection-related options", 0},
+  {"tiny", "TinyMUSH compatibility options", 0},
+  {NULL, NULL, 0}
+};
+
+COBRA_CONF *new_config(void);
+COBRA_CONF *get_config(const char *name);
+
+/** Returns a pointer to a newly allocated CONF object.
+ * \return pointer to newly allocated CONF object.
+ */
+COBRA_CONF *
+new_config(void)
+{
+  return ((COBRA_CONF *) mush_malloc(sizeof(COBRA_CONF), "config"));
+}
+
+/** Add a new local runtime configuration parameter to local_options.
+ * This function will not override an existing local configuration
+ * option.
+ * \param name name of the configuration option.
+ * \param handler cf_* function to handle the option.
+ * \param loc address to store the value of the option.
+ * \param max maximum value allowed for the option.
+ * \param group name of the option group the option should display with.
+ * \return pointer to configuration structure or NULL for failure.
+ */
+COBRA_CONF *
+add_config(const char *name, config_func handler, void *loc, int max,
+          const char *group)
+{
+  COBRA_CONF *cnf;
+  if ((cnf = get_config(name)))
+    return cnf;
+  if ((cnf = new_config()) == NULL)
+    return NULL;
+  cnf->name = mush_strdup(strupper(name), "config name");
+  cnf->handler = handler;
+  cnf->loc = loc;
+  cnf->max = max;
+  cnf->overridden = 0;
+  cnf->group = group;
+  hashadd(name, (void *) cnf, &local_options);
+  return cnf;
+}
+
+/** Return a local runtime configuration parameter by name.
+ * This function returns a point to a configuration structure (COBRA_CONF *)
+ * if one exists in the local runtime options that matches the given
+ * name. Only local_options is searched.
+ * \param name name of the configuration option.
+ * \return pointer to configuration structure or NULL for failure.
+ */
+COBRA_CONF *
+get_config(const char *name)
+{
+  return ((COBRA_CONF *) hashfind(name, &local_options));
+}
+
+/** Parse a boolean configuration option.
+ * \param opt name of the configuration option.
+ * \param val value of the option.
+ * \param loc address to store the value.
+ * \param maxval (unused).
+ * \param source 0 if read from config file; 1 if from command.
+ * \retval 0 failure (unable to parse val).
+ * \retval 1 success.
+ */
+int
+cf_bool(const char *opt, const char *val, void *loc,
+       int maxval __attribute__ ((__unused__)), int source)
+{
+  /* enter boolean parameter */
+
+  if (!strcasecmp(val, "yes") || !strcasecmp(val, "true") ||
+      !strcasecmp(val, "1"))
+    *((int *) loc) = 1;
+  else if (!strcasecmp(val, "no") || !strcasecmp(val, "false") ||
+          !strcasecmp(val, "0"))
+    *((int *) loc) = 0;
+  else {
+    if (source == 0) {
+      do_rawlog(LT_ERR, T("CONFIG: option %s value %s invalid.\n"), opt, val);
+    }
+    return 0;
+  }
+  return 1;
+}
+
+
+/** Parse a string configuration option.
+ * \param opt name of the configuration option.
+ * \param val value of the option.
+ * \param loc address to store the value.
+ * \param maxval maximum length of value string.
+ * \param source 0 if read from config file; 1 if from command.
+ * \retval 0 failure (unable to parse val).
+ * \retval 1 success.
+ */
+int
+cf_str(const char *opt, const char *val, void *loc, int maxval, int source)
+{
+  /* enter string parameter */
+  size_t len = strlen(val);
+
+  /* truncate if necessary */
+  if (len >= (size_t) maxval) {
+    if (source == 0) {
+      do_rawlog(LT_ERR, T("CONFIG: option %s value truncated\n"), opt);
+    }
+    len = maxval - 1;
+  }
+  memcpy(loc, val, len);
+  *((char *) loc + len) = '\0';
+  return 1;
+}
+
+/** Parse a dbref configuration option.
+ * \param opt name of the configuration option.
+ * \param val value of the option.
+ * \param loc address to store the value.
+ * \param maxval maximum dbref.
+ * \param source 0 if read from config file; 1 if from command.
+ * \retval 0 failure (unable to parse val).
+ * \retval 1 success.
+ */
+int
+cf_dbref(const char *opt, const char *val, void *loc, int maxval, int source)
+{
+  /* enter dbref or integer parameter */
+
+  int n;
+  size_t offset = 0;
+
+  if (val && val[0] == '#')
+    offset = 1;
+
+  n = parse_integer(val + offset);
+
+  /* enforce limits */
+  if ((maxval >= 0) && (n > maxval)) {
+    n = maxval;
+    if (source == 0) {
+      do_rawlog(LT_ERR, T("CONFIG: option %s value limited to #%d\n"), opt,
+               maxval);
+    }
+  }
+  if (source && (!GoodObject(n) || IsGarbage(n))) {
+    do_rawlog(LT_ERR,
+             T("CONFIG: attempt to set option %s to a bad dbref (#%d)"),
+             opt, n);
+    return 0;
+  }
+  *((dbref *) loc) = n;
+  return 1;
+}
+
+/** Parse an integer configuration option.
+ * \param opt name of the configuration option.
+ * \param val value of the option.
+ * \param loc address to store the value.
+ * \param maxval maximum value.
+ * \param source 0 if read from config file; 1 if from command.
+ * \retval 0 failure (unable to parse val).
+ * \retval 1 success.
+ */
+int
+cf_int(const char *opt, const char *val, void *loc, int maxval, int source)
+{
+  /* enter integer parameter */
+
+  int n;
+  int offset = 0;
+
+  if (val && val[0] == '#')
+    offset = 1;
+  n = parse_integer(val + offset);
+
+  /* enforce limits */
+  if ((maxval >= 0) && (n > maxval)) {
+    n = maxval;
+    if (source == 0) {
+      do_rawlog(LT_ERR, T("CONFIG: option %s value limited to %d\n"), opt,
+               maxval);
+    }
+  }
+  *((int *) loc) = n;
+  return 1;
+}
+
+
+/** Parse an time configuration option with a default unit of seconds 
+ * \param opt name of the configuration option.
+ * \param val value of the option.
+ * \param loc address to store the value.
+ * \param maxval maximum value.
+ * \param source 0 if read from config file; 1 if from command.
+ * \retval 0 failure (unable to parse val).
+ * \retval 1 success.
+ */
+int
+cf_time(const char *opt, const char *val, void *loc, int maxval, int source)
+{
+  /* enter time parameter */
+  char *end = NULL;
+  int in_minutes = 0;
+  int n, secs = 0;
+
+
+  if (strstr(opt, "idle"))
+    in_minutes = 1;
+
+  while (val && *val) {
+    n = strtol(val, &end, 10);
+
+    switch (*end) {
+    case '\0':
+      if (in_minutes)
+       secs += n * 60;
+      else
+       secs += n;
+      goto done;               /* Sigh. What I wouldn't give for named loops in C */
+    case 's':
+    case 'S':
+      secs += n;
+      break;
+    case 'm':
+    case 'M':
+      secs += n * 60;
+      break;
+    case 'h':
+    case 'H':
+      secs += n * 3600;
+      break;
+    default:
+      if (source == 0)
+       do_rawlog(LT_ERR, T("CONFIG: Unknown time interval in option %s"), opt);
+      return 0;
+    }
+    val = end + 1;
+  }
+done:
+  /* enforce limits */
+  if ((maxval >= 0) && (secs > maxval)) {
+    secs = maxval;
+    if (source == 0) {
+      do_rawlog(LT_ERR, T("CONFIG: option %s value limited to %d\n"), opt,
+               maxval);
+    }
+  }
+  *((int *) loc) = secs;
+  return 1;
+}
+
+/** Parse a flag configuration option.
+ * This is just like parsing a string option, but collects multiple
+ * string options with the same name into a single value.
+ * \param opt name of the configuration option.
+ * \param val value of the option.
+ * \param loc address to store the value.
+ * \param maxval maximum length of value.
+ * \param source 0 if read from config file; 1 if from command.
+ * \retval 0 failure (unable to parse val).
+ * \retval 1 success.
+ */
+int
+cf_flag(const char *opt, const char *val, void *loc, int maxval, int source)
+{
+  size_t len = strlen(val);
+  size_t total = strlen((char *) loc);
+
+  /* truncate if necessary */
+  if (len + total + 1 >= (size_t) maxval) {
+    len = maxval - total - 1;
+    if (len <= 0) {
+      if (source == 0)
+       do_rawlog(LT_ERR, T("CONFIG: option %s value overflow\n"), opt);
+      return 0;
+    }
+    if (source == 0)
+      do_rawlog(LT_ERR, T("CONFIG: option %s value truncated\n"), opt);
+  }
+  sprintf((char *) loc, "%s %s", (char *) loc, val);
+  return 1;
+}
+
+/** Set a configuration option.
+ * This function sets a runtime configuration option. During the load
+ * of the configuration file, it gets run twice - once to set the
+ * main set of options and once again to set restrictions and aliases
+ * that require having the flag table available.
+ * \param opt name of the option.
+ * \param val value to set the option to.
+ * \param source 0 if being set from mush.cnf, 1 from softcode.
+ * \param restrictions 1 if we're setting restriction options, 0 for others.
+ * \retval 1 success.
+ * \retval 0 failure.
+ */
+int
+config_set(const char *opt, char *val, int source, int restrictions)
+{
+  COBRA_CONF *cp;
+  char *p;
+
+  if (!val)
+    return 0;                  /* NULL val is no good, but "" is ok */
+
+  /* Was this "restrict_command <command> <restriction>"? If so, do it */
+  if(!strcasecmp(opt, "lock_command")) {
+    if(!restrictions)
+      return 1;
+    for(p = val; *p && !isspace((unsigned char) *p); p++)
+      ;
+    if(*p) {
+      *p++ = '\0';
+      if(!command_lock(val, p)) {
+       if(source == 0)
+         do_rawlog(LT_ERR, T("CONFIG: Invalid command or lock bool expression for %s.\n"), val);
+       return 0;
+      }
+    } else {
+      if(source == 0)
+       do_rawlog(LT_ERR, T("CONFIG: command_lock %s requires a lock boolexp.\n"), val);
+       return 0;
+    }
+    return 1;
+  } else if (!strcasecmp(opt, "restrict_command")) {
+    if (!restrictions)
+      return 1;
+    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
+    if (*p) {
+      *p++ = '\0';
+      if (!restrict_command(val, p)) {
+       if (source == 0) {
+         do_rawlog(LT_ERR,
+                   T("CONFIG: Invalid command or restriction for %s.\n"), val);
+       }
+       return 0;
+      }
+    } else {
+      if (source == 0) {
+       do_rawlog(LT_ERR,
+                 T
+                 ("CONFIG: restrict_command %s requires a restriction value.\n"),
+                 val);
+      }
+      return 0;
+    }
+    return 1;
+  } else if (!strcasecmp(opt, "restrict_function")) {
+    if (!restrictions)
+      return 1;
+    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
+    if (*p) {
+      *p++ = '\0';
+      if (!restrict_function(val, p)) {
+       if (source == 0) {
+         do_rawlog(LT_ERR,
+                   T("CONFIG: Invalid function or restriction for %s.\n"),
+                   val);
+       }
+       return 0;
+      }
+    } else {
+      if (source == 0) {
+       do_rawlog(LT_ERR,
+                 T
+                 ("CONFIG: restrict_function %s requires a restriction value.\n"),
+                 val);
+      }
+      return 0;
+    }
+    return 1;
+  } else if (!strcasecmp(opt, "reserve_alias")) {
+    if (!restrictions)
+      return 1;
+    reserve_alias(val);
+    return 1;
+  } else if (!strcasecmp(opt, "command_alias")) {
+    if (!restrictions)
+      return 1;
+    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
+    if (*p) {
+      *p++ = '\0';
+      if (!alias_command(val, p)) {
+       if (source == 0) {
+         do_rawlog(LT_ERR, T("CONFIG: Couldn't alias %s to %s.\n"), p, val);
+       }
+       return 0;
+      }
+    } else {
+      if (source == 0) {
+       do_rawlog(LT_ERR,
+                 T("CONFIG: command_alias %s requires an alias.\n"), val);
+      }
+      return 0;
+    }
+    return 1;
+  } else if (!strcasecmp(opt, "attribute_alias")) {
+    if (!restrictions)
+      return 1;
+    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
+    if (*p) {
+      *p++ = '\0';
+      if (!alias_attribute(val, p)) {
+       if (source == 0) {
+         do_rawlog(LT_ERR, T("CONFIG: Couldn't alias %s to %s.\n"), p, val);
+       }
+       return 0;
+      }
+    } else {
+      if (source == 0) {
+       do_rawlog(LT_ERR,
+                 T("CONFIG: attribute_alias %s requires an alias.\n"), val);
+      }
+      return 0;
+    }
+    return 1;
+  } else if (!strcasecmp(opt, "function_alias")) {
+    if (!restrictions)
+      return 1;
+    for (p = val; *p && !isspace((unsigned char) *p); p++) ;
+    if (*p) {
+      *p++ = '\0';
+      if (!alias_function(val, p)) {
+       if (source == 0) {
+         do_rawlog(LT_ERR, T("CONFIG: Couldn't alias %s to %s.\n"), p, val);
+       }
+       return 0;
+      }
+    } else {
+      if (source == 0) {
+       do_rawlog(LT_ERR,
+                 T("CONFIG: function_alias %s requires an alias.\n"), val);
+      }
+      return 0;
+    }
+    return 1;
+  } else if (!strcasecmp(opt, "help_command")
+            || !strcasecmp(opt, "ahelp_command")) {
+    char *comm, *file;
+    int admin = !strcasecmp(opt, "ahelp_command");
+    if (!restrictions)
+      return 1;
+    /* Add a new help-like command */
+    if (source == 1)
+      return 0;
+    if (!val || !*val) {
+      do_rawlog(LT_ERR,
+               T
+               ("CONFIG: help_command requires a command name and file name.\n"));
+      return 0;
+    }
+    comm = val;
+    for (file = val; *file && !isspace((unsigned char) *file); file++) ;
+    if (*file) {
+      *file++ = '\0';
+      add_help_file(comm, file, admin);
+      return 1;
+    } else {
+      do_rawlog(LT_ERR,
+               T
+               ("CONFIG: help_command requires a command name and file name.\n"));
+      return 0;
+    }
+  } else if (restrictions) {
+    return 0;
+  }
+  /* search conf table for the option; if found, add it, if not found,
+   * complain about it. Forbid use of @config to set options without
+   * groups (log_wipe_passwd), or the file and message groups (@config/set
+   * output_data=../../.bashrc? Ouch.)  */
+  for (cp = conftable; cp->name; cp++) {
+    int i = 0;
+    if ((!source || (cp->group && strcmp(cp->group, "files") != 0
+                    && strcmp(cp->group, "messages") != 0))
+       && !strcasecmp(cp->name, opt)) {
+      i = cp->handler(opt, val, cp->loc, cp->max, source);
+      if (i)
+       cp->overridden = 1;
+      return i;
+    }
+  }
+  for (cp = (COBRA_CONF *) hash_firstentry(&local_options); cp;
+       cp = (COBRA_CONF *) hash_nextentry(&local_options)) {
+    int i = 0;
+    if ((!source || (cp->group && strcmp(cp->group, "files") != 0
+                    && strcmp(cp->group, "messages") != 0))
+       && !strcasecmp(cp->name, opt)) {
+      i = cp->handler(opt, val, cp->loc, cp->max, source);
+      if (i)
+       cp->overridden = 1;
+      return i;
+    }
+  }
+
+  if (source == 0) {
+    do_rawlog(LT_ERR, T("CONFIG: directive '%s' in cnf file ignored.\n"), opt);
+  }
+  return 0;
+}
+
+/** Set the default configuration options.
+ */
+void
+conf_default_set(void)
+{
+  strcpy(options.mud_name, "TinyMUSH");
+  options.port = 4201;
+  options.ssl_port = 0;
+  strcpy(options.input_db, "data/indb");
+  strcpy(options.output_db, "data/outdb");
+  strcpy(options.crash_db, "data/PANIC.db");
+  strcpy(options.chatdb, "data/chatdb");
+  strcpy(options.flagdb, "data/flagdb");
+  options.chan_cost = 1000;
+  options.max_player_chans = 3;
+  options.max_channels = 200;
+  strcpy(options.mail_db, "data/maildb");
+  options.player_start = 0;
+  options.guest_start = options.player_start;
+  options.master_room = 2;
+  options.sql_master = 2;
+  options.base_room = 0;
+  options.default_home = 0;
+  options.ancestor_room = -1;
+  options.ancestor_exit = -1;
+  options.ancestor_thing = -1;
+  options.ancestor_player = -1;
+  options.powerless    = 1; /* This is bad, they should change this ASAP when they start a game */
+  options.idle_timeout = 0;
+  options.idle_time = 0;
+  options.unconnected_idle_timeout = 300;
+  options.dump_interval = 3601;
+  strcpy(options.dump_message,
+        T("GAME: Dumping database. Game may freeze for a minute"));
+  strcpy(options.dump_complete, T("GAME: Dump complete. Time in."));
+  options.ident_timeout = 5;
+  options.max_logins = 128;
+  options.max_guests = 0;
+  options.whisper_loudness = 100;
+  options.blind_page = 1;
+  options.page_aliases = 0;
+  options.paycheck = 50;
+  options.guest_paycheck = 0;
+  options.starting_money = 100;
+  options.starting_quota = 20;
+  options.player_queue_limit = 100;
+  options.queue_chunk = 3;
+  options.active_q_chunk = 0;
+  options.func_nest_lim = 50;
+  options.func_invk_lim = 2500;
+  options.call_lim = 0;
+  options.use_quota = 1;
+  options.function_side_effects = 1;
+  options.empty_attrs = 1;
+  strcpy(options.money_singular, "Penny");
+  strcpy(options.money_plural, "Pennies");
+  strcpy(options.log_wipe_passwd, "zap!");
+#ifdef WIN32
+  strcpy(options.compressprog, "");
+  strcpy(options.uncompressprog, "");
+  strcpy(options.compresssuff, "");
+#else
+  strcpy(options.compressprog, "compress");
+  strcpy(options.uncompressprog, "uncompress");
+  strcpy(options.compresssuff, ".Z");
+#endif                         /* WIN32 */
+  strcpy(options.connect_file[0], "txt/connect.txt");
+  strcpy(options.motd_file[0], "txt/motd.txt");
+  strcpy(options.newuser_file[0], "txt/newuser.txt");
+  strcpy(options.register_file[0], "txt/register.txt");
+  strcpy(options.quit_file[0], "txt/quit.txt");
+  strcpy(options.down_file[0], "txt/down.txt");
+  strcpy(options.full_file[0], "txt/full.txt");
+  strcpy(options.guest_file[0], "txt/guest.txt");
+  strcpy(options.error_log, "");
+  strcpy(options.connect_log, "");
+  strcpy(options.command_log, "");
+  strcpy(options.trace_log, "");
+  strcpy(options.wizard_log, "");
+  strcpy(options.checkpt_log, "");
+  options.log_commands = 0;
+  options.log_forces = 1;
+  options.support_pueblo = 0;
+  options.login_allow = 1;
+  options.guest_allow = 1;
+  options.create_allow = 1;
+  strcpy(options.player_flags, "");
+  strcpy(options.room_flags, "");
+  strcpy(options.exit_flags, "");
+  strcpy(options.thing_flags, "");
+  strcpy(options.channel_flags, "");
+  options.warn_interval = 3600;
+  options.use_dns = 1;
+  options.safer_ufun = 1;
+  strcpy(options.dump_warning_1min,
+        T("GAME: Database will be dumped in 1 minute."));
+  strcpy(options.dump_warning_5min,
+        T("GAME: Database will be dumped in 5 minutes."));
+  options.noisy_whisper = 0;
+  options.possessive_get = 1;
+  options.possessive_get_d = 1;
+  options.really_safe = 1;
+  options.destroy_possessions = 1;
+  options.null_eq_zero = 0;
+  options.tiny_booleans = 0;
+  options.tiny_math = 0;
+  options.tiny_trim_fun = 0;
+  options.twinchecks = 1; /* enable twin permission */
+  options.adestroy = 0;
+  options.amail = 0;
+  options.mail_limit = 5000;
+  options.player_listen = 1;
+  options.player_ahear = 1;
+  options.startups = 1;
+  options.room_connects = 0;
+  options.reverse_shs = 1;
+  options.ansi_names = 1;
+  options.comma_exit_list = 1;
+  options.count_all = 0;
+  options.exits_connect_rooms = 0;
+  options.zone_control = 1;
+  options.link_to_object = 1;
+  options.owner_queues = 0;
+  options.dark_noaenter = 0;
+  options.use_ident = 1;
+  strcpy(options.ip_addr, "");
+  strcpy(options.ssl_ip_addr, "");
+  options.player_name_spaces = 0;
+  options.forking_dump = 1;
+  options.restrict_building = 0;
+  options.free_objects = 1;
+  options.flags_on_examine = 1;
+  options.ex_public_attribs = 1;
+  options.full_invis = 0;
+  options.silent_pemit = 0;
+  options.max_dbref = 0;
+  options.chat_strip_quote = 1;
+  strcpy(options.wall_prefix, "Announcement:");
+  strcpy(options.access_file, "access.cnf");
+  strcpy(options.names_file, "names.cnf");
+  options.object_cost = 10;
+  options.exit_cost = 1;
+  options.link_cost = 1;
+  options.room_cost = 10;
+  options.queue_cost = 10;
+  options.quota_cost = 1;
+  options.find_cost = 100;
+  options.page_cost = 0;
+  options.kill_default_cost = 100;
+  options.kill_min_cost = 10;
+  options.kill_bonus = 50;
+  options.queue_loss = 63;
+  options.max_pennies = 100000;
+  options.max_guest_pennies = 100000;
+  options.max_depth = 10;
+  options.max_parents = 10;
+  options.max_global_fns = 50;
+  options.purge_interval = 601;
+  options.dbck_interval = 599;
+  options.max_attrcount = 2048;
+  options.float_precision = 6;
+  options.newline_one_char = 1;
+  options.player_name_len = 16;
+  options.queue_entry_cpu_time = 1500;
+  options.ascii_names = 1;
+  options.call_lim = 10000;
+  strcpy(options.chunk_swap_file, "data/chunkswap");
+  options.chunk_cache_memory = 1000000;
+  options.chunk_migrate_amount = 50;
+  options.read_remote_desc = 0;
+#ifdef HAS_OPENSSL
+  strcpy(options.ssl_private_key_file, "");
+  strcpy(options.ssl_ca_file, "");
+  options.ssl_require_client_cert = 0;
+#endif
+  options.mem_check = 0;
+#ifdef HAS_MYSQL
+  strcpy(options.sql_platform, "disabled");
+  strcpy(options.sql_database, "");
+  strcpy(options.sql_username, "");
+  strcpy(options.sql_password, "");
+  strcpy(options.sql_host, "127.0.0.1");
+#endif
+  /* Guest Options */
+  strcpy(options.guest_prefix, "Guest-");
+  /* Default to Regular Numbering */
+  options.guest_roman_numeral = 0;
+  /* Make Player the Default PowerGroup */
+  strcpy(options.player_powergroup, "Player");
+}
+
+/* Limit how many files we can nest */
+static int conf_recursion = 0;
+
+/** Read a configuration file.
+ * This function is called to read a configuration file. We may recurse,
+ * as there's an 'include' directive. It's called twice, once before
+ * the flag table load (when we want all options except restriction/alias
+ * options) and once after (when we only want restriction/alias options.)
+ * \param conf name of configuration file to read.
+ * \param restrictions 1 if reading restriction options, 0 otherwise.
+ * \retval 1 success.
+ * \retval 0 failure.
+ */
+int
+config_file_startup(const char *conf, int restrictions)
+{
+  /* read a configuration file. Return 0 on failure, 1 on success */
+  /* If 'restrictions' is 0, ignore restrict*. If it's 1, only
+   * look at restrict*
+   */
+
+  FILE *fp = NULL;
+  COBRA_CONF *cp;
+  char tbuf1[BUFFER_LEN];
+  char *p, *q, *s;
+
+  static char cfile[BUFFER_LEN];       /* Remember the last one */
+  if (conf_recursion == 0) {
+    if (conf && *conf)
+      strcpy(cfile, conf);
+    fp = fopen(cfile, FOPEN_READ);
+    if (fp == NULL) {
+      do_rawlog(LT_ERR, T("ERROR: Cannot open configuration file %s."), cfile);
+      return 0;
+    }
+    do_rawlog(LT_ERR, "Reading %s", cfile);
+  } else {
+    if (conf && *conf)
+      fp = fopen(conf, FOPEN_READ);
+    if (fp == NULL) {
+      do_rawlog(LT_ERR, T("ERROR: Cannot open configuration file %s."),
+               (conf && *conf) ? conf : "Unknown");
+      return 0;
+    }
+    do_rawlog(LT_ERR, "Reading %s", conf);
+  }
+
+  fgets(tbuf1, BUFFER_LEN, fp);
+  while (!feof(fp)) {
+
+    p = tbuf1;
+
+    if (*p == '#') {
+      /* comment line */
+      fgets(tbuf1, BUFFER_LEN, fp);
+      continue;
+    }
+    /* this is a real line. Strip the end-of-line and characters following it.
+     * Split the line into command and argument portions. If it exists,
+     * also strip off the trailing comment. We try to make this work
+     * whether the eol is \n (unix, yay), \r\n (dos/win, ew), or \r (mac, hmm)
+     * This basically rules out embedded newlines as currently written
+     */
+
+    for (p = tbuf1; *p && (*p != '\n') && (*p != '\r'); p++) ;
+    *p = '\0';                 /* strip the end of line char(s) */
+    for (p = tbuf1; *p && isspace((unsigned char) *p); p++)    /* strip spaces */
+      ;
+    for (q = p; *q && !isspace((unsigned char) *q); q++)       /* move over command */
+      ;
+    if (*q)
+      *q++ = '\0';             /* split off command */
+    for (; *q && isspace((unsigned char) *q); q++)     /* skip spaces */
+      ;
+    /* If the first character of the value is a #, and that is
+       followed by a number, treat it as a dbref instead of a
+       comment. */
+    if (*q == '#' && isdigit((unsigned char) *(q + 1))) {
+      for (s = q + 1; *s && (*s != '#'); s++)  /* look for a real comment */
+       ;
+    } else {
+      for (s = q; *s && (*s != '#'); s++)      /* look for comment */
+       ;
+    }
+    if (*s)                    /* if found nuke it */
+      *s = '\0';
+    for (s = s - 1; (s >= q) && isspace((unsigned char) *s); s--)      /* smash trailing stuff */
+      *s = '\0';
+
+    if (strlen(p) != 0) {      /* skip blank lines */
+      /* Handle include filename directives separetly */
+      if (strcasecmp(p, "include") == 0) {
+       conf_recursion++;
+       if (conf_recursion > 10) {
+         do_rawlog(LT_ERR, T("CONFIG: include depth too deep in file %s"),
+                   conf);
+       } else {
+         config_file_startup(q, restrictions);
+       }
+       conf_recursion--;
+      } else
+       config_set(p, q, 0, restrictions);
+    }
+    fgets(tbuf1, BUFFER_LEN, fp);
+  }
+
+  /* Warn about any config options that aren't overridden by the
+   * config file.
+   */
+  if (conf_recursion == 0) {
+    for (cp = conftable; cp->name; cp++) {
+      if (!cp->overridden) {
+       do_rawlog(LT_ERR,
+                 T
+                 ("CONFIG: directive '%s' missing from cnf file, using default value."),
+                 cp->name);
+      }
+    }
+    for (cp = (COBRA_CONF *) hash_firstentry(&local_options); cp;
+        cp = (COBRA_CONF *) hash_nextentry(&local_options)) {
+      if (!cp->overridden) {
+       do_rawlog(LT_ERR,
+                 T
+                 ("CONFIG: local directive '%s' missing from cnf file. using default value."),
+                 cp->name);
+      }
+    }
+
+    /* these directives aren't player-settable but need to be initialized */
+    mudtime = time(NULL);
+    options.dump_counter = mudtime + options.dump_interval;
+    options.purge_counter = mudtime + options.purge_interval;
+    options.dbck_counter = mudtime + options.dbck_interval;
+    options.warn_counter = mudtime + options.warn_interval;
+
+#ifdef WIN32
+    /* if we're on Win32, complain about compression */
+    if ((options.compressprog && *options.compressprog)) {
+      do_rawlog(LT_ERR,
+               T
+               ("CONFIG: compression program is specified but not used in Win32, ignoring"),
+               options.compressprog);
+    }
+
+    if (((options.compresssuff && *options.compresssuff))) {
+      do_rawlog(LT_ERR,
+               T
+               ("CONFIG: compression suffix is specified but not used in Win32, ignoring"),
+               options.compresssuff);
+    }
+
+    /* Also remove the compression options */
+    *options.uncompressprog = 0;
+    *options.compressprog = 0;
+    *options.compresssuff = 0;
+
+#endif
+
+  }
+  fclose(fp);
+  return 1;
+}
+
+/** List configuration directives or groups.
+ * \param player the enactor.
+ * \param type the directive or group name.
+ * \param lc if 1, list in lowercase.
+ */
+void
+do_config_list(dbref player, const char *type, int lc)
+{
+  COBRA_CONFGROUP *cgp;
+  COBRA_CONF *cp;
+
+  if (SUPPORT_PUEBLO)
+    notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
+  if (type && *type) {
+    /* Look up the type in the group table */
+    int found = 0;
+    for (cgp = confgroups; cgp->name; cgp++) {
+      if (string_prefix(T(cgp->name), type)
+         && Can_View_Config_Group(player, cgp)) {
+       found = 1;
+       break;
+      }
+    }
+    if (!found) {
+      /* It wasn't a group. Is is one or more specific options? */
+      for (cp = conftable; cp->name; cp++) {
+       if (cp->group && string_prefix(cp->name, type)) {
+         notify(player, config_list_helper(player, cp, lc));
+         found = 1;
+       }
+      }
+      if (!found) {
+       /* Ok, maybe a local option? */
+       for (cp = (COBRA_CONF *) hash_firstentry(&local_options); cp;
+            cp = (COBRA_CONF *) hash_nextentry(&local_options)) {
+         if (cp->group && !strcasecmp(cp->name, type)) {
+           notify(player, config_list_helper(player, cp, lc));
+           found = 1;
+         }
+       }
+      }
+      if (!found) {
+       /* Wasn't found at all. Ok. */
+       notify(player, T("I only know the following types of options:"));
+       for (cgp = confgroups; cgp->name; cgp++) {
+         if (Can_View_Config_Group(player, cgp))
+           notify_format(player, " %-15s %s", T(cgp->name), cgp->desc);
+       }
+      }
+    } else {
+      /* Show all entries of that type */
+      notify(player, cgp->desc);
+      if (string_prefix("compile", type))
+       show_compile_options(player);
+      else {
+       for (cp = conftable; cp->name; cp++) {
+         if (cp->group && !strcmp(cp->group, cgp->name)) {
+           notify(player, config_list_helper(player, cp, lc));
+         }
+       }
+       for (cp = (COBRA_CONF *) hash_firstentry(&local_options); cp;
+            cp = (COBRA_CONF *) hash_nextentry(&local_options)) {
+         if (cp->group && !strcasecmp(cp->group, cgp->name)) {
+           notify(player, config_list_helper(player, cp, lc));
+         }
+       }
+      }
+    }
+  } else {
+    /* If we're here, we ran @config without a type. */
+    notify(player,
+          T("Use: @config/list <type of options> where type is one of:"));
+    for (cgp = confgroups; cgp->name; cgp++) {
+      if (Can_View_Config_Group(player, cgp))
+       notify_format(player, " %-15s %s", T(cgp->name), cgp->desc);
+    }
+  }
+  if (SUPPORT_PUEBLO)
+    notify_noenter(player, tprintf("%c/SAMP%c", TAG_START, TAG_END));
+}
+
+/** Lowercase a string if we've been asked to */
+#define MAYBE_LC(x) (lc ? strlower(x) : x)
+static char *
+config_list_helper(dbref player __attribute__ ((__unused__)), COBRA_CONF *cp, int lc)
+{
+  static char result[BUFFER_LEN];
+  char *bp = result;
+
+  if ((cp->handler == cf_str) || (cp->handler == cf_flag))
+    safe_format(result, &bp, " %-40s %s", MAYBE_LC(cp->name), (char *) cp->loc);
+  else if (cp->handler == cf_int)
+    safe_format(result, &bp, " %-40s %d", MAYBE_LC(cp->name),
+               *((int *) cp->loc));
+  else if (cp->handler == cf_time) {
+    div_t n;
+    int secs = *(int *) cp->loc;
+
+    safe_format(result, &bp, " %-40s ", MAYBE_LC(cp->name));
+
+    if (secs >= 3600) {
+      n = div(secs, 3600);
+      secs = n.rem;
+      safe_format(result, &bp, "%dh", n.quot);
+    }
+    if (secs >= 60) {
+      n = div(secs, 60);
+      secs = n.rem;
+      safe_format(result, &bp, "%dm", n.quot);
+    }
+    if (secs)
+      safe_format(result, &bp, "%ds", secs);
+  } else if (cp->handler == cf_bool)
+    safe_format(result, &bp, " %-40s %s", MAYBE_LC(cp->name),
+               (*((int *) cp->loc) ? "Yes" : "No"));
+  else if (cp->handler == cf_dbref)
+    safe_format(result, &bp, " %-40s #%d", MAYBE_LC(cp->name),
+               *((dbref *) cp->loc));
+  *bp = '\0';
+  return result;
+}
+
+/* This one doesn't return the names */
+static char *
+config_list_helper2(dbref player __attribute__ ((__unused__)), COBRA_CONF *cp, int lc
+                   __attribute__ ((__unused__)))
+{
+  static char result[BUFFER_LEN];
+  char *bp = result;
+  if ((cp->handler == cf_str) || (cp->handler == cf_flag))
+    safe_format(result, &bp, "%s", (char *) cp->loc);
+  else if (cp->handler == cf_int)
+    safe_format(result, &bp, "%d", *((int *) cp->loc));
+  else if (cp->handler == cf_time) {
+    div_t n;
+    int secs = *(int *) cp->loc;
+
+    if (secs >= 3600) {
+      n = div(secs, 3600);
+      secs = n.rem;
+      safe_format(result, &bp, "%dh", n.quot);
+    }
+    if (secs >= 60) {
+      n = div(secs, 60);
+      secs = n.rem;
+      safe_format(result, &bp, "%dm", n.quot);
+    }
+    if (secs)
+      safe_format(result, &bp, "%ds", secs);
+  } else if (cp->handler == cf_bool)
+    safe_format(result, &bp, "%s", (*((int *) cp->loc) ? "Yes" : "No"));
+  else if (cp->handler == cf_dbref)
+    safe_format(result, &bp, "#%d", *((dbref *) cp->loc));
+  *bp = '\0';
+  return result;
+}
+
+#undef MAYBE_LC
+
+/* config(option): returns value of option
+ * config(): returns list of all option names
+ */
+FUNCTION(fun_config)
+{
+  COBRA_CONF *cp;
+
+  if (args[0] && *args[0]) {
+    for (cp = conftable; cp->name; cp++) {
+      if (cp->group && !strcasecmp(cp->name, args[0])) {
+       safe_str(config_list_helper2(executor, cp, 0), buff, bp);
+       return;
+      }
+    }
+    for (cp = (COBRA_CONF *) hash_firstentry(&local_options); cp;
+        cp = (COBRA_CONF *) hash_nextentry(&local_options)) {
+      if (cp->group && !strcasecmp(cp->name, args[0])) {
+       safe_str(config_list_helper2(executor, cp, 0), buff, bp);
+       return;
+      }
+    }
+    safe_str(T("#-1 NO SUCH CONFIG OPTION"), buff, bp);
+    return;
+  } else {
+    for (cp = conftable; cp->name; cp++) {
+      safe_str(cp->name, buff, bp);
+      safe_chr(' ', buff, bp);
+    }
+    for (cp = (COBRA_CONF *) hash_firstentry(&local_options); cp;
+        cp = (COBRA_CONF *) hash_nextentry(&local_options)) {
+      safe_str(cp->name, buff, bp);
+      safe_chr(' ', buff, bp);
+    }
+  }
+}
+
+/** Enable or disable a configuration option.
+ * \param player the enactor.
+ * \param param the option to enable/disable.
+ * \param state 1 to enable, 0 to disable.
+ */
+void
+do_enable(dbref player, const char *param, int state)
+{
+  COBRA_CONF *cp;
+
+  for (cp = conftable; cp->name; cp++) {
+    if (cp->group && !strcasecmp(cp->name, param)) {
+      if (cp->handler == cf_bool) {
+       cf_bool(param, (state ? "yes" : "no"), cp->loc, cp->max, 1);
+       if (state == 0)
+         notify(player, T("Disabled."));
+       else
+         notify(player, T("Enabled."));
+       do_log(LT_WIZ, player, NOTHING, "%s %s",
+              cp->name, (state) ? "ENABLED" : "DISABLED");
+      } else
+       notify(player, T("That isn't a on/off option."));
+      return;
+    }
+  }
+  notify(player, T("No such option."));
+}
+
+static void
+show_compile_options(dbref player)
+{
+#if (COMPRESSION_TYPE == 0)
+  notify(player, T(" Attributes are not compressed in memory."));
+#endif
+#if (COMPRESSION_TYPE == 1) || (COMPRESSION_TYPE == 2)
+  notify(player, T(" Attributes are Huffman compressed in memory."));
+#endif
+#if (COMPRESSION_TYPE == 3)
+  notify(player, T(" Attributes are word compressed in memory."));
+#endif
+#if (COMPRESSION_TYPE == 4)
+  notify(player, T(" Attributes are 8-bit word compressed in memory."));
+#endif
+
+#ifdef HAS_OPENSSL
+  notify(player, T(" The MUSH was compiled with SSL support."));
+#endif
+
+#ifdef INFO_SLAVE
+  notify(player, T(" DNS and ident lookups are handled by a slave process."));
+#else
+  notify(player, T(" DNS and ident lookups are handled by the MUSH process."));
+#endif
+
+#ifdef NT_TCP
+  notify(player, T(" Windows NT native TCP/IP networking functions in use."));
+#else
+  notify(player, T(" BSD sockets networking in use."));
+#endif
+
+  notify(player, T(" Floating point functions are enabled."));
+
+#ifdef FUNCTION_SIDE_EFFECTS
+  notify(player, T(" Side effect functions are enabled."));
+#else
+  notify(player, T(" Side effect functions are disabled."));
+#endif
+
+#ifdef USE_MAILER
+  notify(player,
+        T(" The extended built-in MUSH mailing system is being used."));
+#ifdef ALLOW_NOSUBJECT
+  notify(player,
+        T(" Messages without subject lines have subject (no subject)"));
+#else
+  notify(player,
+        T
+        (" Messages without subject lines use the beginning of the message as subject"));
+#endif
+#else
+  notify(player, T(" The built-in MUSH mailing system is not being used."));
+#endif
+
+#ifdef CHAT_SYSTEM
+  notify(player, T(" The extended chat system is enabled."));
+#else
+  notify(player, T(" The chat system is disabled."));
+#endif
+
+#ifdef HAS_GETDATE
+  notify(player, T(" Extended convtime() is supported."));
+#else
+  notify(player, T(" convtime() is stricter."));
+#endif
+
+#if defined(HAS_ITIMER) || defined(WIN32)
+  notify(player, T(" CPU usage limiting is supported."));
+#else
+  notify(player, T(" CPU usage limiting is NOT supported."));
+#endif
+
+}
diff --git a/src/console.c b/src/console.c
new file mode 100644 (file)
index 0000000..8a4fdaf
--- /dev/null
@@ -0,0 +1,4930 @@
+/**
+ * \file console.c
+ *
+ * \brief Alternative main function for PennMUSH which runs on the console
+ * instead of over the network.
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef WIN32
+#define FD_SETSIZE 256
+#include <windows.h>
+#include <winsock.h>
+#include <io.h>
+#define EINTR WSAEINTR
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define MAXHOSTNAMELEN 32
+#define LC_MESSAGES 6
+#else                          /* !WIN32 */
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <sys/ioctl.h>
+#include <errno.h>
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#endif
+#ifdef I_NETDB
+#include <netdb.h>
+#endif
+#ifdef I_SYS_PARAM
+#include <sys/param.h>
+#endif
+#ifdef I_SYS_STAT
+#include <sys/stat.h>
+#endif
+#endif                         /* !WIN32 */
+#include <time.h>
+#ifdef I_SYS_WAIT
+#include <sys/wait.h>
+#endif
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef I_SYS_SELECT
+#include <sys/select.h>
+#endif
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#ifdef HAS_GETRLIMIT
+#include <sys/resource.h>
+#endif
+#include <limits.h>
+#ifdef I_FLOATINGPOINT
+#include <floatingpoint.h>
+#endif
+#include <locale.h>
+#ifdef __APPLE__
+#define LC_MESSAGES     6
+#define AUTORESTART
+#endif
+#include <setjmp.h>
+
+#include "conf.h"
+
+#include "externs.h"
+#include "chunk.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "help.h"
+#include "match.h"
+#include "ansi.h"
+#include "pueblo.h"
+#include "parse.h"
+#include "access.h"
+#include "command.h"
+#include "version.h"
+#include "patches.h"
+#include "mysocket.h"
+#include "ident.h"
+#include "strtree.h"
+#include "log.h"
+#include "pcre.h"
+#include "mymalloc.h"
+#include "extmail.h"
+#include "attrib.h"
+#include "game.h"
+#include "dbio.h"
+#include "confmagic.h"
+#ifdef HAS_WAITPID
+/** What does wait*() return? */
+#define WAIT_TYPE int
+#else
+#ifdef UNION_WAIT
+#define WAIT_TYPE union wait
+#else
+#define WAIT_TYPE int
+#endif
+#endif
+
+
+/* BSD 4.2 and maybe some others need these defined */
+#ifndef FD_ZERO
+/** An fd_set is 4 bytes */
+#define fd_set int
+/** Clear an fd_set */
+#define FD_ZERO(p)       (*p = 0)
+/** Set a bit in an fd_set */
+#define FD_SET(n,p)      (*p |= (1<<(n)))
+/** Clear a bit in an fd_set */
+#define FD_CLR(n,p)      (*p &= ~(1<<(n)))
+/** Check a bit in an fd_set */
+#define FD_ISSET(n,p)    (*p & (1<<(n)))
+#endif                         /* defines for BSD 4.2 */
+
+#ifdef HAS_GETRUSAGE
+void rusage_stats(void);
+#endif
+int que_next(void);            /* from cque.c */
+
+extern int on_second;          /**< Are we ready to do per-second processing? */
+void dispatch(void);           /* from timer.c */
+dbref email_register_player(const char *name, const char *email, const char *host, const char *ip);    /* from player.c */
+
+extern time_t start_time;      /**< When was the mush last rebooted? */
+extern time_t first_start_time;        /**< When was the mush first started? */
+extern int reboot_count;       /**< How many times have we been rebooted? */
+extern int database_loaded;    /* From game.c */
+static int extrafd;
+int shutdown_flag = 0;         /**< Is it time to shut down? */
+extern int paranoid_dump;      /**< Are we doing a paranoid dump? */
+#ifdef CHAT_SYSTEM
+void chat_player_announce(dbref player, char *msg, int ungag);
+#endif /* CHAT_SYSTEM */
+
+static int login_number = 0;
+static int under_limit = 1;
+
+char cf_motd_msg[BUFFER_LEN];  /**< The message of the day */
+char cf_downmotd_msg[BUFFER_LEN];      /**< The down message */
+char cf_fullmotd_msg[BUFFER_LEN];      /**< The 'mush full' message */
+static char poll_msg[DOING_LEN];
+char confname[BUFFER_LEN];     /**< Name of the config file */
+char errlog[BUFFER_LEN];       /**< Name of the error log file */
+
+/* Default Connection flags for certain clients
+ */
+static CLIENT_DEFAULTS client_maps[]  = {
+  {"TINYFUGUE", CONN_PROMPT},
+  {NULL, -1}
+};
+
+
+/** Is this descriptor connected to a telnet-compatible terminal? */
+#define TELNET_ABLE(d) ((d)->conn_flags & (CONN_TELNET | CONN_TELNET_QUERY))
+
+
+/* When the mush gets a new connection, it tries sending a telnet
+ * option negotiation code for setting client-side line-editing mode
+ * to it. If it gets a reply, a flag in the descriptor struct is
+ * turned on indicated telnet-awareness.
+ * 
+ * If the reply indicates that the client supports linemode, further
+ * instructions as to what linemode options are to be used is sent.
+ * Those options: Client-side line editing, and expanding literal
+ * client-side-entered tabs into spaces.
+ * 
+ * Option negotation requests sent by the client are processed,
+ * with the only one we confirm rather than refuse outright being
+ * suppress-go-ahead, since a number of telnet clients try it.
+ *
+ * The character 255 is the telnet option escape character, so when it
+ * is sent to a telnet-aware client by itself (Since it's also often y-umlaut)
+ * it must be doubled to escape it for the client. This is done automatically,
+ * and is the original purpose of adding telnet option support.
+ */
+
+/* Telnet codes */
+#define IAC            255     /**< interpret as command: */
+#define GOAHEAD                249     /**< Go Ahead command */
+#define NOP            241     /**< no operation */
+#define AYT            246     /**< are you there? */
+#define DONT           254     /**< you are not to use option */
+#define DO             253     /**< please, you use option */
+#define WONT           252     /**< I won't use option */
+#define WILL           251     /**< I will use option */
+#define SB             250     /**< interpret as subnegotiation */
+#define SE             240     /**< end sub negotiation */
+#define TN_SGA         3       /**< Suppress go-ahead */
+#define TN_LINEMODE    34      /**< Line mode */
+#define TN_NAWS                31      /**< Negotiate About Window Size */
+#define TN_TTYPE       24      /**< Ask for termial type information */
+static void test_telnet(DESC *d);
+static void setup_telnet(DESC *d);
+static int handle_telnet(DESC *d, unsigned char **q, unsigned char *qend);
+static const char *empabb(dbref);
+static int do_su_exit(DESC *d);
+
+#ifdef NT_TCP
+/* for Windows NT IO-completion-port method of TCP/IP - NJG */
+
+/* Windows NT TCP/IP routines written by Nick Gammon <nick@gammon.com.au> */
+
+#include <process.h>
+HANDLE CompletionPort;         /* IOs are queued up on this port */
+SOCKET MUDListenSocket;                /* for our listening thread */
+DWORD dwMUDListenThread;       /* thread handle for listening thread */
+SOCKADDR_IN saServer;          /* for listening thread */
+void __cdecl MUDListenThread(void *pVoid);     /* the listening thread */
+DWORD platform;                        /* which version of Windows are we using? */
+OVERLAPPED lpo_aborted;                /* special to indicate a player has finished TCP IOs */
+OVERLAPPED lpo_shutdown;       /* special to indicate a player should do a shutdown */
+void ProcessWindowsTCP(void);  /* handle NT-style IOs */
+CRITICAL_SECTION cs;           /* for thread synchronisation */
+#endif
+
+
+static const char *create_fail =
+  "Either there is already a player with that name, or that name is illegal.";
+static const char *password_fail = "The password is invalid (or missing).";
+static const char *register_fail =
+  "Unable to register that player with that email address.";
+static const char *register_success =
+  "Registration successful! You will receive your password by email.";
+static const char *shutdown_message = "Going down - Bye";
+/** Where we save the descriptor info across reboots. */
+#define REBOOTFILE              "reboot.db"
+
+#if 0
+/* For translation */
+static void dummy_msgs(void);
+static void
+dummy_msgs()
+{
+  char *temp;
+  temp = T("Either that player does not exist, or has a different password.");
+  temp =
+    T
+    ("Either there is already a player with that name, or that name is illegal.");
+  temp = T("The password is invalid (or missing).");
+  temp = T("Unable to register that player with that email address.");
+  temp = T("Registration successful! You will receive your password by email.");
+  temp = T("Going down - Bye");
+  temp = T("GAME: SSL connections must be dropped, sorry.");
+}
+
+#endif
+
+DESC *descriptor_list = NULL;  /**< The linked list of descriptors */
+
+#ifdef WIN32
+static WSADATA wsadata;
+#endif
+int restarting = 0;    /**< Are we restarting the server after a reboot? */
+int ndescriptors = 0;
+
+extern const unsigned char *tables;
+
+sig_atomic_t signal_shutdown_flag = 0; /**< Have we caught a shutdown signal? */
+sig_atomic_t signal_dump_flag = 0;     /**< Have we caught a dump signal? */
+
+#ifdef HAS_GETRLIMIT
+static void init_rlimit(void);
+#endif
+#ifndef BOOLEXP_DEBUGGING
+#ifdef WIN32SERVICES
+void shutdown_checkpoint(void);
+void mainthread(int argc, char **argv);
+#else
+int main(int argc, char **argv);
+#endif
+#endif
+void set_signals(void);
+static struct timeval *timeval_sub(struct timeval *now, struct timeval *then);
+#ifdef WIN32
+/** Windows doesn't have gettimeofday(), so we implement it here */
+#define our_gettimeofday(now) win_gettimeofday((now))
+static void win_gettimeofday(struct timeval *now);
+#else
+/** A wrapper for gettimeofday() in case the system doesn't have it */
+#define our_gettimeofday(now) gettimeofday((now), (struct timezone *)NULL)
+#endif
+static long int msec_diff(struct timeval *now, struct timeval *then);
+static struct timeval *msec_add(struct timeval *t, int x);
+static void update_quotas(struct timeval *last, struct timeval *current);
+
+static void shovechars(void);
+
+static void clearstrings(DESC *d);
+
+/** A block of cached text. */
+typedef struct fblock {
+  unsigned char *buff;   /**< Pointer to the block as a string */
+  size_t len;            /**< Length of buff */
+} FBLOCK;
+
+/** The complete collection of cached text files. */
+struct fcache_entries {
+  FBLOCK connect_fcache[2];    /**< connect.txt and connect.html */
+  FBLOCK motd_fcache[2];       /**< motd.txt and motd.html */
+  FBLOCK newuser_fcache[2];    /**< newuser.txt and newuser.html */
+  FBLOCK register_fcache[2];   /**< register.txt and register.html */
+  FBLOCK quit_fcache[2];       /**< quit.txt and quit.html */
+  FBLOCK down_fcache[2];       /**< down.txt and down.html */
+  FBLOCK full_fcache[2];       /**< full.txt and full.html */
+  FBLOCK guest_fcache[2];      /**< guest.txt and guest.html */
+};
+
+void feed_snoop(DESC *, const char *, char );
+char is_snooped(DESC *);
+char set_snoop(dbref, DESC *);
+void clr_snoop(dbref, DESC *);
+void announce_connect(dbref player, int isnew, int num);
+void announce_disconnect(dbref player);
+void add_to_exit_path(DESC *d, dbref player);
+
+static struct fcache_entries fcache;
+static void fcache_dump(DESC *d, FBLOCK fp[2], const unsigned char *prefix);
+static int fcache_read(FBLOCK *cp, const char *filename);
+static void logout_sock(DESC *d);
+static void shutdownsock(DESC *d);
+static DESC *initializesock(int s, char *addr, char *ip, int use_ssl);
+int process_output(DESC *d);
+/* Notify.c */
+extern void free_text_block(struct text_block *t);
+extern void add_to_queue(struct text_queue *q, const unsigned char *b, int n);
+extern int queue_write(DESC *d, const unsigned char *b, int n);
+extern int queue_eol(DESC *d);
+extern int queue_newwrite(DESC *d, const unsigned char *b, int n);
+extern int queue_string(DESC *d, const char *s);
+extern int queue_string_eol(DESC *d, const char *s);
+extern void freeqs(DESC *d);
+static void welcome_user(DESC *d);
+static void dump_info(DESC *call_by);
+static void save_command(DESC *d, const unsigned char *command);
+static int process_input(DESC *d, int output_ready);
+static void process_input_helper(DESC *d, char *tbuf1, int got);
+static void set_userstring(unsigned char **userstring, const char *command);
+static void process_commands(void);
+static void parse_puebloclient(DESC *d, char *command);
+static int dump_messages(DESC *d, dbref player, int new);
+static int check_connect(DESC *d, const char *msg);
+static void parse_connect(const char *msg, char *command, char *user,
+                         char *pass);
+static void close_sockets(void);
+dbref find_player_by_desc(int port);
+static DESC *lookup_desc(dbref executor, const char *name);
+void NORETURN bailout(int sig);
+void WIN32_CDECL signal_shutdown(int sig);
+void WIN32_CDECL signal_dump(int sig);
+void reaper(int sig);
+extern Pid_t forked_dump_pid;  /**< Process id of forking dump process */
+extern time_t last_dump_time;  /**< Time of last database dump */
+static void dump_users(DESC *call_by, char *match, int doing);
+static const char *time_format_1(long int dt);
+static const char *time_format_2(long int dt);
+
+void inactivity_check(void);
+void reopen_logs(void);
+void load_reboot_db(void);
+#ifdef HAS_GETRLIMIT
+static void
+init_rlimit(void)
+{
+  /* Unlimit file descriptors. */
+  /* Ultrix 4.4 and others may have getrlimit but may not be able to
+   * change number of file descriptors
+   */
+#ifdef RLIMIT_NOFILE
+  struct rlimit *rlp;
+
+  rlp = (struct rlimit *) malloc(sizeof(struct rlimit));
+  if (getrlimit(RLIMIT_NOFILE, rlp)) {
+    perror("init_rlimit: getrlimit()");
+    free(rlp);
+    return;
+  }
+  /* This check seems dumb, but apparently FreeBSD may return 0 for
+   * the max # of descriptors!
+   */
+  if (rlp->rlim_max > rlp->rlim_cur) {
+    rlp->rlim_cur = rlp->rlim_max;
+    if (setrlimit(RLIMIT_NOFILE, rlp))
+      perror("init_rlimit: setrlimit()");
+  }
+  free(rlp);
+#endif
+  return;
+}
+#endif                         /* HAS_GETRLIMIT */
+
+#ifdef NT_TCP
+BOOL
+IsValidAddress(const void *lp, UINT nBytes, BOOL bReadWrite)
+{
+  return (lp != NULL &&
+         !IsBadReadPtr(lp, nBytes) &&
+         (!bReadWrite || !IsBadWritePtr((LPVOID) lp, nBytes)));
+
+}
+
+BOOL
+GetErrorMessage(const DWORD dwError, LPTSTR lpszError, const UINT nMaxError)
+{
+
+  LPTSTR lpBuffer;
+  BOOL bRet =
+    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
+                 NULL,
+                 dwError,
+                 0,
+                 (LPTSTR) & lpBuffer,
+                 0,
+                 NULL);
+
+  if (bRet == FALSE)
+    *lpszError = '\0';
+  else {
+    lstrcpyn(lpszError, lpBuffer, nMaxError);
+    LocalFree(lpBuffer);
+  }
+  return bRet;
+}
+
+#endif
+
+#ifndef BOOLEXP_DEBUGGING
+#ifdef WIN32SERVICES
+/* Under WIN32, MUSH is a "service", so we just start a thread here.
+ * The real "main" is in win32/services.c
+ */
+void
+mainthread(int argc, char **argv)
+#else
+/** The main function.
+ * \param argc number of arguments.
+ * \param argv vector of arguments.
+ * \return exit code.
+ */
+int
+main(int argc, char **argv)
+#endif                         /* WIN32SERVICES */
+{
+#ifdef AUTORESTART
+  FILE *id;
+#endif
+  FILE *newerr;
+
+  /* read the configuration file */
+  if (argc < 2) {
+    fprintf(stderr, "ERROR: Usage: %s /path/to/config_file\n", argv[0]);
+    exit(2);
+  }
+
+#ifdef WIN32
+  {
+    unsigned short wVersionRequested = MAKEWORD(1, 1);
+    int err;
+
+    /* Need to include library: wsock32.lib for Windows Sockets */
+    err = WSAStartup(wVersionRequested, &wsadata);
+    if (err) {
+      printf(T("Error %i on WSAStartup\n"), err);
+      exit(1);
+    }
+  }
+#endif                         /* WIN32 */
+
+#ifdef NT_TCP
+
+/* Find which version of Windows we are using - Completion ports do */
+/* not work with Windows 95/98 */
+
+  {
+    OSVERSIONINFO VersionInformation;
+
+    VersionInformation.dwOSVersionInfoSize = sizeof(VersionInformation);
+    GetVersionEx(&VersionInformation);
+    platform = VersionInformation.dwPlatformId;
+    printf(T("Running under Windows %s\n"),
+          platform == VER_PLATFORM_WIN32_NT ? "NT" : "95/98");
+  }
+#endif
+
+#ifdef HAS_GETRLIMIT
+  init_rlimit();               /* unlimit file descriptors */
+#endif
+
+  /* These are FreeBSDisms to fix floating point exceptions */
+#ifdef HAS_FPSETROUND
+  fpsetround(FP_RN);
+#endif
+#ifdef HAS_FPSETMASK
+  fpsetmask(0L);
+#endif
+
+  time(&mudtime);
+
+  /* If we have setlocale, call it to set locale info
+   * from environment variables
+   */
+#ifdef HAS_SETLOCALE
+  {
+    char *loc;
+    if ((loc = setlocale(LC_CTYPE, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set ctype locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting ctype locale to %s", loc);
+    if ((loc = setlocale(LC_TIME, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set time locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting time locale to %s", loc);
+    if ((loc = setlocale(LC_MESSAGES, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set messages locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting messages locale to %s", loc);
+    if ((loc = setlocale(LC_COLLATE, "")) == NULL)
+      do_rawlog(LT_ERR, "Failed to set collate locale from environment.");
+    else
+      do_rawlog(LT_ERR, "Setting collate locale to %s", loc);
+  }
+#endif
+#ifdef HAS_TEXTDOMAIN
+  textdomain("pennmush");
+#endif
+#ifdef HAS_BINDTEXTDOMAIN
+  bindtextdomain("pennmush", "../po");
+#endif
+
+  /* Build the locale-dependant tables used by PCRE */
+  tables = pcre_maketables();
+
+/* this writes a file used by the restart script to check for active mush */
+#ifdef AUTORESTART
+  id = fopen("runid", "w");
+  fprintf(id, "%d", getpid());
+  fclose(id);
+#endif
+
+  strncpy(confname, argv[1], BUFFER_LEN - 1);
+  confname[BUFFER_LEN - 1] = '\0';
+  init_game_config(confname);
+
+  /* save a file descriptor */
+  reserve_fd();
+#ifndef WIN32
+  extrafd = open("/dev/null", O_RDWR);
+#endif
+
+  /* decide if we're in @shutdown/reboot */
+  restarting = 0;
+  newerr = fopen(REBOOTFILE, "r");
+  if (newerr) {
+    restarting = 1;
+    fclose(newerr);
+  }
+
+  init_qids();
+  if (init_game_dbs() < 0) {
+    do_rawlog(LT_ERR, T("ERROR: Couldn't load databases! Exiting."));
+    exit(2);
+  }
+
+  init_game_postdb(confname);
+
+  database_loaded = 1;
+
+  set_signals();
+
+  /* go do it */
+#ifdef CSRI
+#ifdef CSRI_DEBUG
+  mal_verify(1);
+#endif
+#ifdef CSRI_TRACE
+  mal_leaktrace(1);
+#endif
+#endif
+  load_reboot_db();
+#ifdef NT_TCP
+
+  /* If we are running Windows NT we must create a completion port, */
+  /* and start up a listening thread for new connections */
+
+  if (platform == VER_PLATFORM_WIN32_NT) {
+    int nRet;
+
+    /* create initial IO completion port, so threads have something to wait on */
+
+    CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
+
+    if (!CompletionPort) {
+      char sMessage[200];
+      GetErrorMessage(GetLastError(), sMessage, sizeof sMessage);
+      printf("Error %ld (%s) on CreateIoCompletionPort\n",
+            GetLastError(), sMessage);
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+
+    InitializeCriticalSection(&cs);
+
+    /* Create a TCP/IP stream socket */
+    MUDListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+    /* Fill in the the address structure */
+    saServer.sin_port = htons((u_short) TINYPORT);
+    saServer.sin_family = AF_INET;
+    saServer.sin_addr.s_addr = INADDR_ANY;
+
+    /* bind our name to the socket */
+    nRet = bind(MUDListenSocket, (LPSOCKADDR) & saServer, sizeof saServer);
+
+    if (nRet) {
+      printf("Error %ld on Win32: bind\n", WSAGetLastError());
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+    /* Set the socket to listen */
+    nRet = listen(MUDListenSocket, SOMAXCONN);
+
+    if (nRet) {
+      printf("Error %ld on Win32: listen\n", WSAGetLastError());
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+    /* Create the MUD listening thread */
+    dwMUDListenThread = _beginthread(MUDListenThread, 0,
+                                    (void *) (SOCKET) MUDListenSocket);
+
+    if (dwMUDListenThread == -1) {
+      printf("Error %ld on _beginthread\n", errno);
+      WSACleanup();            /* clean up */
+      exit(1);
+    }
+    do_rawlog(LT_ERR, T("Listening (NT-style) on port %d"), TINYPORT);
+  }
+#endif                         /* NT_TCP */
+
+  shovechars();
+#ifdef CSRI
+#ifdef CSRI_DEBUG
+  mal_verify(1);
+#endif
+#endif
+
+  /* someone has told us to shut down */
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+  shutdown_queues();
+
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+  close_sockets();
+  sql_shutdown();
+
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+  dump_database();
+
+  local_shutdown();
+
+#ifdef RPMODE_SYS
+  rplog_shutdown();
+#endif
+
+  end_all_logs();
+
+#ifdef CSRI
+#ifdef CSRI_PROFILESIZES
+  mal_statsdump(stderr);
+#endif
+#ifdef CSRI_TRACE
+  mal_dumpleaktrace(stderr);
+#endif
+  fflush(stderr);
+#endif
+
+#ifdef WIN32SERVICES
+  /* Keep service manager happy */
+  shutdown_checkpoint();
+#endif
+
+#ifdef HAS_GETRUSAGE
+  rusage_stats();
+#endif                         /* HAS_RUSAGE */
+
+  do_rawlog(LT_ERR, T("MUSH shutdown completed."));
+
+#ifdef NT_TCP
+
+  /* critical section not needed any more */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    DeleteCriticalSection(&cs);
+#endif
+
+#ifdef WIN32
+#ifdef WIN32SERVICES
+  shutdown_checkpoint();
+#endif
+  WSACleanup();                        /* clean up */
+#else
+#ifdef __APPLE__
+  unlink("runid");
+#endif
+  exit(0);
+#endif
+}
+#endif                         /* BOOLEXP_DEBUGGING */
+
+/** Close and reopen the logfiles - called on SIGHUP */
+void
+reopen_logs(void)
+{
+  FILE *newerr;
+  /* close up the log files */
+  end_all_logs();
+  newerr = fopen(errlog, "a");
+  if (!newerr) {
+    fprintf(stderr,
+           T("Unable to open %s. Error output continues to stderr.\n"),
+           errlog);
+  } else {
+    if (!freopen(errlog, "a", stderr)) {
+      printf(T("Ack!  Failed reopening stderr!"));
+      exit(1);
+    }
+    setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+    fclose(newerr);
+  }
+  start_all_logs();
+}
+
+/** Install our default signal handlers. */
+void
+set_signals(void)
+{
+
+#ifndef WIN32
+  /* we don't care about SIGPIPE, we notice it in select() and write() */
+  ignore_signal(SIGPIPE);
+  install_sig_handler(SIGUSR2, signal_dump);
+  install_sig_handler(SIGQUIT, signal_shutdown);
+  install_sig_handler(SIGINT, signal_shutdown);
+  install_sig_handler(SIGTERM, bailout);
+#else
+  /* Win32 stuff: 
+   *   No support for SIGUSR2 or SIGINT.
+   *   SIGTERM is never generated on NT-based Windows (according to MSDN)
+   *   MSVC++ will let you get away with installing a handler anyway,
+   *   but VS.NET will not. So if it's MSVC++, we give it a try.
+   */
+#if _MSC_VER < 1200
+  install_sig_handler(SIGTERM, bailout);
+#endif
+#endif
+
+#ifndef WIN32
+  install_sig_handler(SIGCHLD, reaper);
+#endif
+
+}
+
+#ifdef WIN32
+/** Get the time using Windows function call.
+ * Looks weird, but it works. :-P
+ * \param now address to store timeval data.
+ */
+static void
+win_gettimeofday(struct timeval *now)
+{
+
+  FILETIME win_time;
+
+  GetSystemTimeAsFileTime(&win_time);
+  /* dwLow is in 100-s nanoseconds, not microseconds */
+  now->tv_usec = win_time.dwLowDateTime % 10000000 / 10;
+
+  /* dwLow contains at most 429 least significant seconds, since 32 bits maxint is 4294967294 */
+  win_time.dwLowDateTime /= 10000000;
+
+  /* Make room for the seconds of dwLow in dwHigh */
+  /* 32 bits of 1 = 4294967295. 4294967295 / 429 = 10011578 */
+  win_time.dwHighDateTime %= 10011578;
+  win_time.dwHighDateTime *= 429;
+
+  /* And add them */
+  now->tv_sec = win_time.dwHighDateTime + win_time.dwLowDateTime;
+}
+
+#endif
+
+/** Return the difference between two timeval structs as a timeval struct.
+ * \param now pointer to the timeval to subtract from.
+ * \param then pointer to the timeval to subtract.
+ * \return pointer to a statically allocated timeval of the difference.
+ */
+static struct timeval *
+timeval_sub(struct timeval *now, struct timeval *then)
+{
+  static struct timeval mytime;
+  mytime.tv_sec = now->tv_sec;
+  mytime.tv_usec = now->tv_usec;
+
+  mytime.tv_sec -= then->tv_sec;
+  mytime.tv_usec -= then->tv_usec;
+  if (mytime.tv_usec < 0) {
+    mytime.tv_usec += 1000000;
+    mytime.tv_sec--;
+  }
+  return &mytime;
+}
+
+/** Return the difference between two timeval structs in milliseconds.
+ * \param now pointer to the timeval to subtract from.
+ * \param then pointer to the timeval to subtract.
+ * \return milliseconds of difference between them.
+ */
+static long int
+msec_diff(struct timeval *now, struct timeval *then)
+{
+  long int secs = now->tv_sec - then->tv_sec;
+  if (secs == 0)
+    return (now->tv_usec - then->tv_usec) / 1000;
+  else if (secs == 1)
+    return (now->tv_usec + (1000000 - then->tv_usec)) / 100;
+  else if (secs > 1)
+    return (secs * 1000) + ((now->tv_usec + (1000000 - then->tv_usec)) / 1000);
+  else
+    return 0;
+}
+
+/** Add a given number of milliseconds to a timeval.
+ * \param t pointer to a timeval struct.
+ * \param x number of milliseconds to add to t.
+ * \return address of static timeval struct representing the sum.
+ */
+static struct timeval *
+msec_add(struct timeval *t, int x)
+{
+  static struct timeval mytime;
+  mytime.tv_sec = t->tv_sec;
+  mytime.tv_usec = t->tv_usec;
+  mytime.tv_sec += x / 1000;
+  mytime.tv_usec += (x % 1000) * 1000;
+  if (mytime.tv_usec >= 1000000) {
+    mytime.tv_sec += mytime.tv_usec / 1000000;
+    mytime.tv_usec = mytime.tv_usec % 1000000;
+  }
+  return &mytime;
+}
+
+/** Update each descriptor's allowed rate of issuing commands.
+ * Players are rate-limited; they may only perform up to a certain
+ * number of commands per time slice. This function is run periodically
+ * to refresh each descriptor's available command quota based on how
+ * many slices have passed since it was last updated.
+ * \param last pointer to timeval struct of last time quota was updated.
+ * \param current pointer to timeval struct of current time.
+ */
+static void
+update_quotas(struct timeval *last, struct timeval *current)
+{
+  int nslices;
+  DESC *d;
+  nslices = (int) msec_diff(current, last) / COMMAND_TIME_MSEC;
+
+  if (nslices > 0) {
+    for (d = descriptor_list; d; d = d->next) {
+      d->quota += COMMANDS_PER_TIME * nslices;
+      if (d->quota > COMMAND_BURST_SIZE)
+       d->quota = COMMAND_BURST_SIZE;
+    }
+  }
+}
+
+static const char *empabb(dbref player) {
+        static char str[4];
+        ATTR *a;
+       /*
+        dbref start, end, last;
+       */
+       dbref start;
+
+        memset(str, '\0', 4);
+
+        if(!IsDivision(SDIV(player).object))
+               goto bad_empabb_value;
+        start = SDIV(player).object;
+
+       /*
+        for(last = end = start; GoodObject(end) && IsDivision(end) &&
+                        !has_flag_by_name(end, "EMPIRE", TYPE_DIVISION) ; last = end, end = SDIV(end).object)
+                ;
+        if(!has_flag_by_name(end, "EMPIRE", TYPE_DIVISION)) {
+                if(end == NOTHING && IsDivision(last))
+                        end = last;
+                else end = start;
+        }
+       */
+        /* K, end is the empire we're grabbing this off of */
+        a = atr_get(start, "ALIAS");
+        if(!a)
+                goto bad_empabb_value;
+        strncpy(str, atr_value(a), 3);
+        if(!str[0])
+                goto bad_empabb_value;
+        return str;
+
+
+bad_empabb_value:
+        strncpy(str, "---", 3);
+        return str;
+}
+
+
+static void
+shovechars()
+{
+  /* this is the main game loop */
+
+  fd_set input_set, output_set;
+  time_t now;
+  struct timeval last_slice, current_time, then;
+  struct timeval next_slice, *returned_time;
+  struct timeval timeout, slice_timeout;
+  int found;
+  int queue_timeout;
+  DESC *d, *dnext;
+  int input_ready, output_ready;
+
+  d = initializesock(0, "localhost", "127.0.0.1", 0);
+
+  our_gettimeofday(&last_slice);
+
+  /* done. print message to the log */
+  do_rawlog(LT_ERR, "RESTART FINISHED.");
+
+  our_gettimeofday(&then);
+
+  while (shutdown_flag == 0) {
+    our_gettimeofday(&current_time);
+
+    update_quotas(&last_slice, &current_time);
+    last_slice.tv_sec = current_time.tv_sec;
+    last_slice.tv_usec = current_time.tv_usec;
+
+    if (msec_diff(&current_time, &then) >= 1000) {
+      on_second = 1;
+      then.tv_sec = current_time.tv_sec;
+      then.tv_usec = current_time.tv_usec;
+    }
+
+    process_commands();
+
+    if (signal_shutdown_flag) {
+      flag_broadcast(0, 0, T("GAME: Shutdown by external signal"));
+      do_rawlog(LT_ERR, T("SHUTDOWN by external signal"));
+#ifdef AUTORESTART
+      system("touch NORESTART");
+#endif
+      shutdown_flag = 1;
+    }
+
+    if (signal_dump_flag) {
+      paranoid_dump = 0;
+      do_rawlog(LT_CHECK, "DUMP by external signal");
+      fork_and_dump(1);
+      signal_dump_flag = 0;
+    }
+
+    if (shutdown_flag)
+      break;
+
+    /* test for events */
+    dispatch();
+
+    /* any queued robot commands waiting? */
+    /* timeout.tv_sec used to be set to que_next(), the number of
+     * seconds before something on the queue needed to run, but this
+     * caused a problem with stuff that had to be triggered by alarm
+     * signal every second, so we're reduced to what's below:
+     */
+    queue_timeout = que_next();
+    timeout.tv_sec = queue_timeout ? 1 : 0;
+    timeout.tv_usec = 0;
+
+    returned_time = msec_add(&last_slice, COMMAND_TIME_MSEC);
+    next_slice.tv_sec = returned_time->tv_sec;
+    next_slice.tv_usec = returned_time->tv_usec;
+
+    returned_time = timeval_sub(&next_slice, &current_time);
+    slice_timeout.tv_sec = returned_time->tv_sec;
+    slice_timeout.tv_usec = returned_time->tv_usec;
+    /* Make sure slice_timeout cannot have a negative time. Better
+       safe than sorry. */
+    if (slice_timeout.tv_sec < 0)
+      slice_timeout.tv_sec = 0;
+    if (slice_timeout.tv_usec < 0)
+      slice_timeout.tv_usec = 0;
+
+#ifdef NT_TCP
+
+    /* for Windows NT, we handle IOs in a separate function for simplicity */
+    if (platform == VER_PLATFORM_WIN32_NT) {
+      ProcessWindowsTCP();
+      continue;
+    }                          /* end of NT_TCP and Windows NT */
+#endif
+
+    FD_ZERO(&input_set);
+    FD_ZERO(&output_set);
+    for (d = descriptor_list; d; d = d->next) {
+      if (d->input.head) {
+       timeout.tv_sec = slice_timeout.tv_sec;
+       timeout.tv_usec = slice_timeout.tv_usec;
+      } else {
+        if(d->descriptor == 0)
+         FD_SET(STDIN_FILENO, &input_set);
+        else
+         FD_SET(d->descriptor, &input_set);
+      }
+      if (d->output.head) {
+        if(d->descriptor == 0)
+          FD_SET(STDOUT_FILENO, &output_set);
+        else
+         FD_SET(d->descriptor, &output_set);
+      }
+    }
+
+    found = select(2, &input_set, &output_set, (fd_set *) 0, &timeout);
+    if (found < 0) {
+#ifdef WIN32
+      if (found == SOCKET_ERROR && WSAGetLastError() != WSAEINTR)
+#else
+      if (errno != EINTR)
+#endif
+      {
+       perror("select");
+       return;
+      }
+    } else {
+      /* if !found then time for robot commands */
+
+      if (!found) {
+       do_top(options.queue_chunk);
+       continue;
+      } else {
+       do_top(options.active_q_chunk);
+      }
+      now = mudtime;
+      for (d = descriptor_list; d; d = dnext) {
+       dnext = d->next;
+        if(d->descriptor == 0) {
+         input_ready = FD_ISSET(STDIN_FILENO, &input_set);
+         output_ready = FD_ISSET(STDOUT_FILENO, &output_set);
+        } else {
+         input_ready = FD_ISSET(d->descriptor, &input_set);
+         output_ready = FD_ISSET(d->descriptor, &output_set);
+        }
+       if (input_ready) {
+         if (!process_input(d, output_ready)) {
+           shutdownsock(d);
+            if(d->descriptor == 0)
+              return;
+           continue;
+         }
+       }
+       if (output_ready) {
+         if (!process_output(d)) {
+           shutdownsock(d);
+            if(d->descriptor == 0)
+              return;
+         }
+       }
+      }
+    }
+  }
+}
+
+static void
+clearstrings(DESC *d)
+{
+  if (d->output_prefix) {
+    mush_free((Malloc_t) d->output_prefix, "userstring");
+    d->output_prefix = 0;
+  }
+  if (d->output_suffix) {
+    mush_free((Malloc_t) d->output_suffix, "userstring");
+    d->output_suffix = 0;
+  }
+}
+
+/* Display a cached text file. If a prefix line was given,
+ * display that line before the text file, but only if we've
+ * got a text file to display
+ */
+static void
+fcache_dump(DESC *d, FBLOCK fb[2], const unsigned char *prefix)
+{
+  /* If we've got nothing nice to say, don't say anything */
+  if (!fb[0].buff && !((d->conn_flags & CONN_HTML) && fb[1].buff))
+    return;
+  /* We've got something to say */
+  if (prefix) {
+    queue_newwrite(d, prefix, u_strlen(prefix));
+    queue_eol(d);
+  }
+  if (d->conn_flags & CONN_HTML) {
+    if (fb[1].buff)
+      queue_newwrite(d, fb[1].buff, fb[1].len);
+    else
+      queue_write(d, fb[0].buff, fb[0].len);
+  } else
+    queue_write(d, fb[0].buff, fb[0].len);
+}
+
+
+static int
+fcache_read(FBLOCK *fb, const char *filename)
+{
+  if (!fb || !filename)
+    return -1;
+
+  /* Free prior cache */
+  if (fb->buff) {
+    mush_free(fb->buff, "fcache_data");
+  }
+
+  fb->buff = NULL;
+  fb->len = 0;
+
+#ifdef WIN32
+  /* Win32 read code here */
+  {
+    HANDLE fh;
+    BY_HANDLE_FILE_INFORMATION sb;
+    DWORD r = 0;
+
+
+    if ((fh = CreateFile(filename, GENERIC_READ, 0, NULL,
+                        OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
+      return -1;
+
+    if (!GetFileInformationByHandle(fh, &sb)) {
+      CloseHandle(fh);
+      return -1;
+    }
+
+    fb->len = sb.nFileSizeLow;
+
+    if (!(fb->buff = mush_malloc(sb.nFileSizeLow, "fcache_data"))) {
+      CloseHandle(fh);
+      return -1;
+    }
+
+    if (!ReadFile(fh, fb->buff, sb.nFileSizeLow, &r, NULL) || fb->len != r) {
+      CloseHandle(fh);
+      mush_free(fb->buff, "fcache_data");
+      fb->buff = NULL;
+      return -1;
+    }
+
+    CloseHandle(fh);
+
+    fb->len = sb.nFileSizeLow;
+    return (int) fb->len;
+  }
+#else
+  /* Posix read code here */
+  {
+    int fd, n;
+    struct stat sb;
+
+    release_fd();
+    if ((fd = open(filename, O_RDONLY, 0)) < 0) {
+      do_log(LT_ERR, 0, 0, T("Couldn't open cached text file '%s'"), filename);
+      reserve_fd();
+      return -1;
+    }
+
+    if (fstat(fd, &sb) < 0) {
+      do_log(LT_ERR, 0, 0, T("Couldn't get the size of text file '%s'"),
+            filename);
+      close(fd);
+      reserve_fd();
+      return -1;
+    }
+
+
+    if (!(fb->buff = mush_malloc(sb.st_size, "fcache_data"))) {
+      do_log(LT_ERR, 0, 0, T("Couldn't allocate %d bytes of memory for '%s'!"),
+            (int) sb.st_size, filename);
+      close(fd);
+      reserve_fd();
+      return -1;
+    }
+
+    if ((n = read(fd, fb->buff, sb.st_size)) != sb.st_size) {
+      do_log(LT_ERR, 0, 0, T("Couldn't read all of '%s'"), filename);
+      close(fd);
+      mush_free(fb->buff, "fcache_data");
+      fb->buff = NULL;
+      reserve_fd();
+      return -1;
+    }
+
+    close(fd);
+    reserve_fd();
+    fb->len = sb.st_size;
+  }
+#endif                         /* Posix read code */
+
+  return fb->len;
+}
+
+/** Load all of the cached text files.
+ * \param player the enactor.
+ */
+void
+fcache_load(dbref player)
+{
+  int conn, motd, new, reg, quit, down, full;
+  int guest;
+  int i;
+
+  for (i = 0; i < (SUPPORT_PUEBLO ? 2 : 1); i++) {
+    conn = fcache_read(&fcache.connect_fcache[i], options.connect_file[i]);
+    motd = fcache_read(&fcache.motd_fcache[i], options.motd_file[i]);
+    new = fcache_read(&fcache.newuser_fcache[i], options.newuser_file[i]);
+    reg = fcache_read(&fcache.register_fcache[i], options.register_file[i]);
+    quit = fcache_read(&fcache.quit_fcache[i], options.quit_file[i]);
+    down = fcache_read(&fcache.down_fcache[i], options.down_file[i]);
+    full = fcache_read(&fcache.full_fcache[i], options.full_file[i]);
+    guest = fcache_read(&fcache.guest_fcache[i], options.guest_file[i]);
+
+    if (player != NOTHING) {
+      notify_format(player,
+                   T
+                   ("%s sizes:  NewUser...%d  Connect...%d  Guest...%d  Motd...%d  Quit...%d  Register...%d  Down...%d  Full...%d"),
+                   i ? "HTMLFile" : "File", new, conn, guest, motd, quit,
+                   reg, down, full);
+    }
+  }
+
+}
+
+/** Initialize all of the cached text files (at startup).
+ */
+void
+fcache_init(void)
+{
+  fcache_load(NOTHING);
+}
+
+static void
+logout_sock(DESC *d)
+{
+  SU_PATH *path_entry;
+
+  int n;
+  char tbuf1[BUFFER_LEN];
+
+  if (d->connected) {
+    fcache_dump(d, fcache.quit_fcache, NULL);
+    do_log(LT_CONN, 0, 0,
+          T("[%d/%s/%s] Logout by %s(#%d) <Connection not dropped>"),
+          d->descriptor, d->addr, d->ip, Name(d->player), d->player);
+    if(d->last_time > 0) {
+      d->idle_total += difftime(mudtime, d->last_time);
+      d->unidle_times++;
+    }
+    snprintf(tbuf1, BUFFER_LEN-1, "%ld %ld %d %d", (mudtime - d->connected_at),
+       d->idle_total, d->unidle_times, d->cmds); 
+    tbuf1[strlen(tbuf1)+1] = '\0';
+    (void) atr_add(d->player, "LASTACTIVITY", tbuf1, GOD, NOTHING);
+    announce_disconnect(d->player);
+    do_mail_purge(d->player);
+    if (MAX_LOGINS) {
+      login_number--;
+      if (!under_limit && (login_number < MAX_LOGINS)) {
+       under_limit = 1;
+       do_log(LT_CONN, 0, 0,
+              T("Below maximum player limit of %d. Logins enabled."),
+              MAX_LOGINS);
+      }
+    }
+  } else {
+    do_log(LT_CONN, 0, 0,
+          T("[%d/%s/%s] Logout, never connected. <Connection not dropped>"),
+          d->descriptor, d->addr, d->ip);
+  }
+  process_output(d);           /* flush our old output */
+  /* pretend we have a new connection */
+  d->input_handler = do_command;
+  d->connected = 0;
+  d->output_prefix = 0;
+  d->output_suffix = 0;
+  d->output_size = 0;
+  d->output.head = 0;
+  d->player = 0;
+  d->output.tail = &d->output.head;
+  d->input.head = 0;
+  d->input.tail = &d->input.head;
+  d->raw_input = 0;
+  d->raw_input_at = 0;
+  d->quota = COMMAND_BURST_SIZE;
+  d->last_time = mudtime;
+  d->idle_total = 0;
+  d->unidle_times = 0;
+  d->cmds = 0;
+  d->hide = 0;
+  d->doing[0] = '\0';
+  d->mailp = NULL;
+  d->pinfo.object = NOTHING;
+  d->pinfo.atr = NULL;
+  d->pinfo.lock = 0;
+  d->pinfo.function = NULL;
+
+  while(d->su_exit_path) {
+    path_entry = d->su_exit_path;
+    d->su_exit_path = path_entry->next;
+    mush_free(path_entry, "SU_EXIT_PATH");
+  }
+  welcome_user(d);
+  for(n = 0; n < MAX_SNOOPS; n++)
+    d->snooper[n] = -1;
+}
+
+/** Disconnect a descriptor.
+ * This sends appropriate disconnection text, flushes output, and
+ * then closes the associated socket.
+ * \param d pointer to descriptor to disconnect.
+ */
+static void
+shutdownsock(DESC *d)
+{
+  char tbuf1[BUFFER_LEN];
+  int i;
+#ifdef NT_TCP
+
+  /* don't close down the socket twice */
+  if (d->bConnectionShutdown)
+    return;
+  /* make sure we don't try to initiate or process any outstanding IOs */
+  d->bConnectionShutdown = TRUE;
+  d->bConnectionDropped = TRUE;
+#endif
+
+  if (d->connected) {
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Logout by %s(#%d)"),
+          d->descriptor, d->addr, d->ip, Name(d->player), d->player);
+    if (d->connected != 2) {
+      fcache_dump(d, fcache.quit_fcache, NULL);
+      /* Player was not allowed to log in from the connect screen */
+      if(d->last_time > 0) {
+       d->idle_total += difftime(mudtime, d->last_time);
+       d->unidle_times++;
+      }
+      snprintf(tbuf1, BUFFER_LEN-1, "%ld %ld %d %d", (mudtime - d->connected_at), 
+         d->idle_total , d->unidle_times, d->cmds);
+      tbuf1[strlen(tbuf1)+1] = '\0';
+      (void) atr_add(d->player, "LASTACTIVITY", tbuf1, GOD, NOTHING);
+      announce_disconnect(d->player);
+      do_mail_purge(d->player);
+    }
+    if (MAX_LOGINS) {
+      login_number--;
+      if (!under_limit && (login_number < MAX_LOGINS)) {
+       under_limit = 1;
+       do_log(LT_CONN, 0, 0,
+              T("Below maximum player limit of %d. Logins enabled."),
+              MAX_LOGINS);
+      }
+    }
+  } else {
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connection closed, never connected."),
+          d->descriptor, d->addr, d->ip);
+  }
+  process_output(d);
+  clearstrings(d);
+#ifdef NT_TCP
+
+  if (platform == VER_PLATFORM_WIN32_NT) {
+    /* cancel any pending reads or writes on this socket */
+
+    if (!CancelIo((HANDLE) d->descriptor)) {
+      char sMessage[200];
+      GetErrorMessage(GetLastError(), sMessage, sizeof sMessage);
+      printf("Error %ld (%s) on CancelIo\n", GetLastError(), sMessage);
+    }
+    /* post a notification that it is safe to free the descriptor */
+    /* we can't free the descriptor here (below) as there may be some */
+    /* queued completed IOs that will crash when they refer to a descriptor */
+    /* (d) that has been freed. */
+
+    if (!PostQueuedCompletionStatus(CompletionPort, 0, (DWORD) d, &lpo_aborted)) {
+      char sMessage[200];
+      DWORD nError = GetLastError();
+      GetErrorMessage(nError, sMessage, sizeof sMessage);
+      printf
+       ("Error %ld (%s) on PostQueuedCompletionStatus in shutdownsock\n",
+        nError, sMessage);
+    }
+  }
+#endif
+  if(d->descriptor != 0) {
+    shutdown(d->descriptor, 2);
+    closesocket(d->descriptor);
+  } else {
+    freeqs(d);
+    d->input_handler = do_command;
+    d->connected = 0;
+    d->connected_at = mudtime;
+    d->output_prefix = 0;
+    d->output_suffix = 0;
+    d->output_size = 0;
+    d->output.head = 0;
+    d->player = 0;
+    d->output.tail = &d->output.head;
+    d->input.head = 0;
+    d->input.tail = &d->input.head;
+    d->raw_input = 0;
+    d->raw_input_at = 0;
+    d->quota = COMMAND_BURST_SIZE;
+    d->last_time = mudtime;
+    d->idle_total = 0;
+    d->unidle_times = 0;
+    d->cmds = 0;
+    d->hide = 0;
+    d->doing[0] = '\0';
+    d->mailp = NULL;
+    strncpy(d->addr, "localhost", 100);
+    d->addr[99] = '\0';
+    strncpy(d->ip, "127.0.0.1", 100);
+    d->ip[99] = '\0';
+    d->conn_flags = CONN_DEFAULT;
+    d->input_chars = 0;
+    d->output_chars = 0;
+    d->ttype = mush_strdup("unknown", "terminal description");
+    d->checksum[0] = '\0';
+    d->su_exit_path = NULL;
+    d->pinfo.object = NOTHING;
+    d->pinfo.atr = NULL;
+    d->pinfo.lock = 0;
+    d->pinfo.function = NULL;
+    d->width = 78;
+    d->height = 24;
+    welcome_user(d);
+    for(i = 0; i < MAX_SNOOPS; i++)
+      d->snooper[i] = -1;
+  }
+#ifdef NT_TCP
+
+  /* protect removing the descriptor from our linked list from */
+  /* any interference from the listening thread */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    EnterCriticalSection(&cs);
+#endif
+
+  if(d->descriptor != 0) {
+    if (d->prev)
+      d->prev->next = d->next;
+    else                               /* d was the first one! */
+      descriptor_list = d->next;
+    if (d->next)
+      d->next->prev = d->prev;
+  }
+
+#ifdef NT_TCP
+  /* safe to allow the listening thread to continue now */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    LeaveCriticalSection(&cs);
+  else
+    /* we cannot free the strings or descriptor if we have queued IOs */
+#endif
+  if(d->descriptor != 0) {
+    freeqs(d);
+    mush_free(d->ttype, "terminal description");
+    mush_free((Malloc_t) d, "descriptor");
+  }
+
+  ndescriptors--;
+}
+
+/* ARGSUSED */
+static DESC *
+initializesock(int s, char *addr, char *ip, int use_ssl
+               __attribute__ ((__unused__)))
+{
+  DESC *d;
+  int n;
+
+  d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
+  if (!d)
+    mush_panic("Out of memory.");
+  d->descriptor = s;
+  d->input_handler = do_command;
+  d->connected = 0;
+  d->connected_at = mudtime;
+  make_nonblocking(s);
+  d->output_prefix = 0;
+  d->output_suffix = 0;
+  d->output_size = 0;
+  d->output.head = 0;
+  d->player = 0;
+  d->output.tail = &d->output.head;
+  d->input.head = 0;
+  d->input.tail = &d->input.head;
+  d->raw_input = 0;
+  d->raw_input_at = 0;
+  d->quota = COMMAND_BURST_SIZE;
+  d->last_time = mudtime;
+  d->idle_total = 0;
+  d->unidle_times = 0;
+  d->cmds = 0;
+  d->hide = 0;
+  d->doing[0] = '\0';
+  d->mailp = NULL;
+  strncpy(d->addr, addr, 100);
+  d->addr[99] = '\0';
+  strncpy(d->ip, ip, 100);
+  d->ip[99] = '\0';
+  d->conn_flags = CONN_DEFAULT;
+  d->input_chars = 0;
+  d->output_chars = 0;
+  d->ttype = mush_strdup("unknown", "terminal description");
+  d->checksum[0] = '\0';
+  d->su_exit_path = NULL;
+  d->pinfo.object = NOTHING;
+  d->pinfo.atr = NULL;
+  d->pinfo.lock = 0;
+  d->pinfo.function = NULL;
+#ifdef NT_TCP
+  /* protect adding the descriptor from the linked list from */
+  /* any interference from socket shutdowns */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    EnterCriticalSection(&cs);
+#endif
+
+  if (descriptor_list)
+    descriptor_list->prev = d;
+  d->next = descriptor_list;
+  d->prev = NULL;
+  descriptor_list = d;
+
+#ifdef NT_TCP
+  /* ok to continue now */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    LeaveCriticalSection(&cs);
+  d->OutboundOverlapped.hEvent = NULL;
+  d->InboundOverlapped.hEvent = NULL;
+  d->InboundOverlapped.Offset = 0;
+  d->InboundOverlapped.OffsetHigh = 0;
+  d->bWritePending = FALSE;    /* no write pending yet */
+  d->bConnectionShutdown = FALSE;      /* not shutdown yet */
+  d->bConnectionDropped = FALSE;       /* not dropped yet */
+#else
+  d->width = 78;
+  d->height = 24;
+  test_telnet(d);
+  welcome_user(d);
+#endif
+  for(n = 0; n < MAX_SNOOPS; n++)
+    d->snooper[n] = -1;
+  return d;
+}
+
+/** Flush pending output for a descriptor.
+ * This function actually sends the queued output over the descriptor's
+ * socket.
+ * \param d pointer to descriptor to send output to.
+ * \retval 1 successfully flushed at least some output.
+ * \retval 0 something failed, and the descriptor should probably be closed.
+ */
+int
+process_output(DESC *d)
+{
+  struct text_block **qp, *cur;
+  int cnt;
+
+  for (qp = &d->output.head; ((cur = *qp) != NULL);) {
+    if(d->descriptor == 0)
+      cnt = write(STDOUT_FILENO, cur->start, cur->nchars);
+    else
+      cnt = send(d->descriptor, cur->start, cur->nchars, 0);
+      if (cnt < 0) {
+#ifdef WIN32
+       if (cnt == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
+#else
+#ifdef EAGAIN
+       if ((errno == EWOULDBLOCK) || (errno == EAGAIN))
+#else
+       if (errno == EWOULDBLOCK)
+#endif
+#endif
+         return 1;
+       return 0;
+      }
+    d->output_size -= cnt;
+    d->output_chars += cnt;
+    if (cnt == cur->nchars) {
+      if (!cur->nxt)
+       d->output.tail = qp;
+      *qp = cur->nxt;
+#ifdef DEBUG
+      do_rawlog(LT_ERR, "free_text_block(0x%x) at 2.", cur);
+#endif                         /* DEBUG */
+      free_text_block(cur);
+      continue;                        /* do not adv ptr */
+    }
+    cur->nchars -= cnt;
+    cur->start += cnt;
+    break;
+  }
+  return 1;
+}
+
+
+static void
+welcome_user(DESC *d)
+{
+  if (SUPPORT_PUEBLO && !(d->conn_flags & CONN_HTML))
+    queue_newwrite(d, (unsigned char *) PUEBLO_HELLO, strlen(PUEBLO_HELLO));
+  fcache_dump(d, fcache.connect_fcache, NULL);
+}
+
+static void
+save_command(DESC *d, const unsigned char *command)
+{
+  add_to_queue(&d->input, command, u_strlen(command) + 1);
+}
+
+static void
+test_telnet(DESC *d)
+{
+  /* Use rfc 1184 to test telnet support, as it tries to set linemode
+     with client-side editing. Good for Broken Telnet Programs. */
+  if (d->descriptor != 0 && !TELNET_ABLE(d)) {
+    /*  IAC DO LINEMODE */
+    unsigned char query[3] = "\xFF\xFD\x22";
+    queue_newwrite(d, query, 3);
+    d->conn_flags |= CONN_TELNET_QUERY;
+    process_output(d);
+  }
+}
+
+static void
+setup_telnet(DESC *d)
+{
+  /* Win2k telnet doesn't do local echo by default,
+     apparently. Unfortunately, there doesn't seem to be a telnet
+     option for local echo, just remote echo. */
+  d->conn_flags |= CONN_TELNET;
+  if (d->conn_flags & CONN_TELNET_QUERY) {
+    /* IAC DO NAWS IAC DO TERMINAL-TYPE */
+    unsigned char extra_options[6] = "\xFF\xFD\x1F" "\xFF\xFD\x18";
+    d->conn_flags &= ~CONN_TELNET_QUERY;
+    do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Switching to Telnet mode."),
+          d->descriptor, d->addr, d->ip);
+    queue_newwrite(d, extra_options, 6);
+    process_output(d);
+  }
+}
+
+static int
+handle_telnet(DESC *d, unsigned char **q, unsigned char *qend)
+{
+  int i;
+
+  /* *(*q - q) == IAC at this point. */
+  switch (**q) {
+  case SB:                     /* Sub-option */
+    if (*q >= qend)
+      return -1;
+    (*q)++;
+    if (**q == TN_LINEMODE) {
+      if ((*q + 2) >= qend)
+       return -1;
+      *q += 2;
+      while (*q < qend && **q != SE)
+       (*q)++;
+      if (*q >= qend)
+       return -1;
+    } else if (**q == TN_NAWS) {
+      /* Learn what size window the client is using. */
+      union {
+       short s;
+       unsigned char bytes[2];
+      } raw;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      /* Width */
+      if (**q == IAC) {
+       raw.bytes[0] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[0] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      if (**q == IAC) {
+       raw.bytes[1] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[1] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+
+      d->width = ntohs(raw.s);
+
+      /* Height */
+      if (**q == IAC) {
+       raw.bytes[0] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[0] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      if (**q == IAC) {
+       raw.bytes[1] = IAC;
+       if (*q >= qend)
+         return -1;
+       (*q)++;
+      } else
+       raw.bytes[1] = **q;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      d->height = ntohs(raw.s);
+
+      /* IAC SE */
+      if (*q + 1 >= qend)
+       return -1;
+      (*q)++;
+    } else if (**q == TN_TTYPE) {
+      /* Read the terminal type: TERMINAL-TYPE IS blah IAC SE */
+      char tbuf[BUFFER_LEN], *bp = tbuf;
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+      /* Skip IS */
+      if (*q >= qend)
+       return -1;
+      (*q)++;
+
+      /* Read up to IAC SE */
+      while (1) {
+       if (*q >= qend)
+         return -1;
+       if (**q == IAC) {
+         if (*q + 1 >= qend)
+           return -1;
+         if (*(*q + 1) == IAC) {
+           safe_chr((char) IAC, tbuf, &bp);
+           (*q)++;
+         } else
+           break;
+       } else
+         safe_chr(**q, tbuf, &bp);
+       (*q)++;
+      }
+      while (*q < qend && **q != SE)
+       (*q)++;
+      *bp = '\0';
+      mush_free(d->ttype, "terminal description");
+      d->ttype = mush_strdup(tbuf, "terminal description");
+      /* We have the terminal type, now set any defaults if we find 'em */
+      for(i = 0 ;  client_maps[i].terminal != NULL; i++)
+       if(!strcmp(client_maps[i].terminal, d->ttype)) {
+         d->conn_flags |= client_maps[i].flags;
+         break;
+       }
+    } else {
+      while (*q < qend && **q != SE)
+       (*q)++;
+    }
+    break;
+  case NOP:                    /* No-op */
+    if (*q >= qend)
+      return -1;
+#ifdef DEBUG_TELNET
+    fprintf(stderr, "Got IAC NOP\n");
+#endif
+    break;
+  case AYT:                    /* Are you there? */
+    if (*q >= qend)
+      return -1;
+    else {
+      static char ayt_reply[] = "\r\n*** AYT received, I'm here ***\r\n";
+      queue_newwrite(d, (unsigned char *) ayt_reply, strlen(ayt_reply));
+      process_output(d);
+    }
+    break;
+  case WILL:                   /* Client is willing to do something, or confirming */
+    setup_telnet(d);
+    if (*q >= qend)
+      return -1;
+    (*q)++;
+
+    if (**q == TN_LINEMODE) {
+      /* Set up our preferred linemode options. */
+      /* IAC SB LINEMODE MODE (EDIT|SOFT_TAB) IAC SE */
+      unsigned char reply[7] = "\xFF\xFA\x22\x01\x09\xFF\xF0";
+      queue_newwrite(d, reply, 7);
+#ifdef DEBUG_TELNET
+      fprintf(stderr, "Setting linemode options.\n");
+#endif
+    } else if (**q == TN_TTYPE) {
+      /* Ask for terminal type id: IAC SB TERMINAL-TYPE SEND IAC SEC */
+      unsigned char reply[6] = "\xFF\xFA\x18\x01\xFF\xF0";
+      queue_newwrite(d, reply, 6);
+    } else if (**q == TN_SGA || **q == TN_NAWS) {
+      /* This is good to be at. */
+    } else {                   /* Refuse options we don't handle */
+      unsigned char reply[3];
+      reply[0] = IAC;
+      reply[1] = DONT;
+      reply[2] = **q;
+      queue_newwrite(d, reply, sizeof reply);
+      process_output(d);
+    }
+    break;
+  case DO:                     /* Client is asking us to do something */
+    setup_telnet(d);
+    if (*q >= qend)
+      return -1;
+    (*q)++;
+    if (**q == TN_LINEMODE) {
+    } else if (**q == TN_SGA) {
+      /* IAC WILL SGA IAC DO SGA */
+      unsigned char reply[6] = "\xFF\xFB\x03\xFF\xFD\x03";
+      queue_newwrite(d, reply, 6);
+      process_output(d);
+#ifdef DEBUG_TELNET
+      fprintf(stderr, "GOT IAC DO SGA, sending IAC WILL SGA IAG DO SGA\n");
+#endif
+    } else {
+      /* Stuff we won't do */
+      unsigned char reply[3];
+      reply[0] = IAC;
+      reply[1] = WONT;
+      reply[2] = (char) **q;
+      queue_newwrite(d, reply, sizeof reply);
+      process_output(d);
+    }
+    break;
+  case WONT:                   /* Client won't do something we want. */
+  case DONT:                   /* Client doesn't want us to do something */
+    setup_telnet(d);
+#ifdef DEBUG_TELNET
+    fprintf(stderr, "Got IAC %s 0x%x\n", **q == WONT ? "WONT" : "DONT",
+           *(*q + 1));
+#endif
+    if (*q + 1 >= qend)
+      return -1;
+    (*q)++;
+    break;
+  default:                     /* Also catches IAC IAC for a literal 255 */
+    return 0;
+  }
+  return 1;
+}
+
+static void
+process_input_helper(DESC *d, char *tbuf1, int got)
+{
+  unsigned char *p, *pend, *q, *qend;
+
+  if (!d->raw_input) {
+    d->raw_input =
+      (unsigned char *) mush_malloc(sizeof(char) * MAX_COMMAND_LEN,
+                                   "descriptor_raw_input");
+    if (!d->raw_input)
+      mush_panic("Out of memory");
+    d->raw_input_at = d->raw_input;
+  }
+  p = d->raw_input_at;
+  d->input_chars += got;
+  pend = d->raw_input + MAX_COMMAND_LEN - 1;
+  for (q = (unsigned char *) tbuf1, qend = (unsigned char *) tbuf1 + got;
+       q < qend; q++) {
+    if (*q == '\r') {
+      /* A broken client (read: WinXP telnet) might send only CR, and not CRLF
+       * so it's nice of us to try to handle this.
+       */
+      *p = '\0';
+      if (p > d->raw_input)
+       save_command(d, d->raw_input);
+      p = d->raw_input;
+      if (((q + 1) < qend) && (*(q + 1) == '\n'))
+       q++;                    /* For clients that work */
+    } else if (*q == '\n') {
+      *p = '\0';
+      if (p > d->raw_input)
+       save_command(d, d->raw_input);
+      p = d->raw_input;
+    } else if (*q == '\b') {
+      if (p > d->raw_input)
+       p--;
+    } else if ((unsigned char) *q == IAC) {    /* Telnet option foo */
+      if (q >= qend)
+       break;
+      q++;
+      if (!TELNET_ABLE(d) || handle_telnet(d, &q, qend) == 0) {
+       if (p < pend && isprint(*q))
+         *p++ = *q;
+      }
+    } else if (p < pend && isprint(*q)) {
+      *p++ = *q;
+    }
+  }
+  if (p > d->raw_input) {
+    d->raw_input_at = p;
+  } else {
+    mush_free((Malloc_t) d->raw_input, "descriptor_raw_input");
+    d->raw_input = 0;
+    d->raw_input_at = 0;
+  }
+}
+
+/* ARGSUSED */
+static int
+process_input(DESC *d, int output_ready __attribute__ ((__unused__)))
+{
+  int got = 0;
+  char tbuf1[BUFFER_LEN];
+
+  errno = 0;
+
+  if(d->descriptor == 0)
+    got = read(STDIN_FILENO, tbuf1, sizeof tbuf1);
+  else
+    got = recv(d->descriptor, tbuf1, sizeof tbuf1, 0);
+
+    if (got <= 0) {
+      /* At this point, select() says there's data waiting to be read from
+       * the socket, but we shouldn't assume that read() will actually get it
+       * and blindly act like a got of -1 is a disconnect-worthy error.
+       */
+#ifdef EAGAIN
+      if ((errno == EWOULDBLOCK) || (errno == EAGAIN) || (errno == EINTR))
+#else
+      if ((errno == EWOULDBLOCK) || (errno == EINTR))
+#endif
+       return 1;
+      else
+       return 0;
+    }
+
+  process_input_helper(d, tbuf1, got);
+
+  return 1;
+}
+
+static void
+set_userstring(unsigned char **userstring, const char *command)
+{
+  if (*userstring) {
+    mush_free((Malloc_t) *userstring, "userstring");
+    *userstring = NULL;
+  }
+  while (*command && isspace((unsigned char) *command))
+    command++;
+  if (*command)
+    *userstring = (unsigned char *) mush_strdup(command, "userstring");
+}
+
+static void
+process_commands(void)
+{
+  int nprocessed;
+  DESC *cdesc, *dnext;
+  struct text_block *t;
+  int retval = 1;
+
+  do {
+    nprocessed = 0;
+    for (cdesc = descriptor_list; cdesc;
+        cdesc = (nprocessed > 0 && retval > 0) ? cdesc->next : dnext) {
+      dnext = cdesc->next;
+      if (cdesc->quota > 0 && (t = cdesc->input.head)) {
+       cdesc->quota--;
+       nprocessed++;
+       start_cpu_timer();
+       feed_snoop(cdesc,(const char *) t->start, 0);
+       /* check AUNIDLE */
+       if(options.idle_time && ((mudtime - cdesc->last_time) > options.idle_time)) {
+         if(atr_get(cdesc->player, "AUNIDLE"))
+           queue_attribute_noparent(cdesc->player, "AUNIDLE", cdesc->player);
+         if(GoodObject(Division(cdesc->player)) && atr_get(Division(cdesc->player), "AUNIDLE"))
+           queue_attribute(Division(cdesc->player), "AUNIDLE", cdesc->player);
+       }
+       retval = cdesc->input_handler(cdesc, (char *) t->start);
+       reset_cpu_timer();
+       if(retval == -1 && do_su_exit(cdesc)) 
+         retval = 1;
+
+       if (retval == 0) {
+         shutdownsock(cdesc);
+       } else if (retval == -1) {
+         logout_sock(cdesc);
+       } else {
+         cdesc->input.head = t->nxt;
+         if (!cdesc->input.head)
+           cdesc->input.tail = &cdesc->input.head;
+         if (t) {
+#ifdef DEBUG
+           do_rawlog(LT_ERR, "free_text_block(0x%x) at 5.", t);
+#endif                         /* DEBUG */
+           free_text_block(t);
+         }
+       }
+      }
+    }
+  } while (nprocessed > 0);
+}
+
+/** Send a descriptor's output prefix */
+#define send_prefix(d) \
+  if (d->output_prefix) { \
+    queue_newwrite(d, d->output_prefix, u_strlen(d->output_prefix)); \
+    queue_eol(d); \
+  }
+
+/** Send a descriptor's output suffix */
+#define send_suffix(d) \
+  if (d->output_suffix) { \
+    queue_newwrite(d, d->output_suffix, u_strlen(d->output_suffix)); \
+    queue_eol(d); \
+  }
+
+int
+do_command(DESC *d, char *command)
+{
+  int j;
+
+  depth = 0;
+
+  (d->cmds)++;
+
+  if (!strcmp(command, IDLE_COMMAND))
+    return 1;
+  if(difftime(mudtime, d->last_time) >= 300) { 
+    d->idle_total += difftime(mudtime, d->last_time);
+    d->unidle_times++;
+  }
+  d->last_time = mudtime;
+  if (!strcmp(command, QUIT_COMMAND)) {
+    return 0;
+  } else if (!strcmp(command, LOGOUT_COMMAND)) {
+    return -1;
+  } else if (!strcmp(command, INFO_COMMAND)) {
+    send_prefix(d);
+    dump_info(d);
+    send_suffix(d);
+  } else if (!strncmp(command, WHO_COMMAND, strlen(WHO_COMMAND))) {
+    send_prefix(d);
+    dump_users(d, command + strlen(WHO_COMMAND), 0);
+    send_suffix(d);
+  } else if (!strncmp(command, DOING_COMMAND, strlen(DOING_COMMAND))) {
+    send_prefix(d);
+    dump_users(d, command + strlen(DOING_COMMAND), 1);
+    send_suffix(d);
+  } else if (!strncmp(command, SESSION_COMMAND, strlen(SESSION_COMMAND))) {
+    send_prefix(d);
+    dump_users(d, command + strlen(SESSION_COMMAND), 2);
+    send_suffix(d);
+  } else if (!strncmp(command, PREFIX_COMMAND, strlen(PREFIX_COMMAND))) {
+    set_userstring(&d->output_prefix, command + strlen(PREFIX_COMMAND));
+  } else if (!strncmp(command, SUFFIX_COMMAND, strlen(SUFFIX_COMMAND))) {
+    set_userstring(&d->output_suffix, command + strlen(SUFFIX_COMMAND));
+  } else if (!strncmp(command, "SCREENWIDTH", 11)) {
+    d->width = parse_integer(command + 11);
+  } else if (!strncmp(command, "SCREENHEIGHT", 12)) {
+    d->height = parse_integer(command + 12);
+  } else if (SUPPORT_PUEBLO
+            && !strncmp(command, PUEBLO_COMMAND, strlen(PUEBLO_COMMAND))) {
+    parse_puebloclient(d, command);
+    if (!(d->conn_flags & CONN_HTML)) {
+      queue_newwrite(d, (unsigned char *) PUEBLO_SEND, strlen(PUEBLO_SEND));
+      process_output(d);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Switching to Pueblo mode."),
+            d->descriptor, d->addr, d->ip);
+      d->conn_flags |= CONN_HTML;
+      if (!d->connected)
+       welcome_user(d);
+    }
+  } else {
+    if (d->connected) {
+      send_prefix(d);
+      global_eval_context.cplr = d->player;
+      strcpy(global_eval_context.ccom, command);
+      strcpy(global_eval_context.ucom, "");
+
+      /* Clear %0-%9 and r(0) - r(9) */
+      for (j = 0; j < 10; j++)
+       global_eval_context.wenv[j] = (char *) NULL;
+      for (j = 0; j < NUMQ; j++)
+       global_eval_context.renv[j][0] = '\0';
+      global_eval_context.process_command_port = d->descriptor;
+
+      process_command(d->player, command, d->player, d->player, 1);
+      send_suffix(d);
+      strcpy(global_eval_context.ccom, "");
+      strcpy(global_eval_context.ucom, "");
+      global_eval_context.cplr = NOTHING;
+    } else {
+      if (!check_connect(d, command))
+       return 0;
+    }
+  }
+  return 1;
+}
+
+static void
+parse_puebloclient(DESC *d, char *command)
+{
+  const char *p, *end;
+  if ((p = string_match(command, "md5="))) {
+    /* Skip md5=" */
+    p += 5;
+    if ((end = strchr(p, '"'))) {
+      if ((end > p) && ((end - p) <= PUEBLO_CHECKSUM_LEN)) {
+       /* Got it! */
+       strncpy(d->checksum, p, end - p);
+       d->checksum[end - p] = '\0';
+      }
+    }
+  }
+}
+
+static int
+dump_messages(DESC *d, dbref player, int isnew)
+{
+  int is_hidden;
+  int num = 0;
+  DESC *tmpd;
+
+  d->connected = 1;
+  d->connected_at = mudtime;
+  d->player = player;
+  d->doing[0] = '\0';
+
+  if (MAX_LOGINS) {
+    /* check for exceeding max player limit */
+    login_number++;
+    if (under_limit && (login_number > MAX_LOGINS)) {
+      under_limit = 0;
+      do_rawlog(LT_CONN,
+               T("Limit of %d players reached. Logins disabled.\n"),
+               MAX_LOGINS);
+    }
+  }
+  /* give players a message on connection */
+  if (!options.login_allow || !under_limit ||
+      (Guest(player) && !options.guest_allow)) {
+    if (!options.login_allow) {
+      fcache_dump(d, fcache.down_fcache, NULL);
+      if (cf_downmotd_msg && *cf_downmotd_msg)
+       raw_notify(player, cf_downmotd_msg);
+    } else if (MAX_LOGINS && !under_limit) {
+      fcache_dump(d, fcache.full_fcache, NULL);
+      if (cf_fullmotd_msg && *cf_fullmotd_msg)
+       raw_notify(player, cf_fullmotd_msg);
+    }
+    if (!Can_Login(player)) {
+      /* when the connection has been refused, we want to update the
+       * LASTFAILED info on the player
+       */
+      check_lastfailed(player, d->addr);
+      return 0;
+    }
+  }
+  d->mailp = find_exact_starting_point(player);
+
+  /* check to see if this is a reconnect and also set DARK status */
+  is_hidden = Can_Hide(player) && Dark(player);
+  DESC_ITER_CONN(tmpd) {
+    if (tmpd->player == player) {
+      num++;
+      if (is_hidden)
+       tmpd->hide = 1;
+    }
+  }
+  /* give permanent text messages */
+  if (isnew)
+    fcache_dump(d, fcache.newuser_fcache, NULL);
+  if (num == 1)
+    fcache_dump(d, fcache.motd_fcache, NULL);
+  if (Guest(player))
+    fcache_dump(d, fcache.guest_fcache, NULL);
+
+  if (ModTime(player))
+    notify_format(player, T("%ld failed connections since last login."),
+                 ModTime(player));
+  ModTime(player) = (time_t) 0;
+  announce_connect(player, isnew, num);        /* broadcast connect message */
+  check_last(player, d->addr, d->ip);  /* set Last, Lastsite, give paycheck */
+  /* Check folder 0, not silently (i.e. Report lack of mail, too) */
+  queue_eol(d);
+  if (command_check_byname(player, "@MAIL"))
+    check_mail(player, 0, 0);
+  set_player_folder(player, 0);
+  do_look_around(player);
+  if (Haven(player))
+    notify(player, T("Your HAVEN flag is set. You cannot receive pages."));
+  local_connect(player, isnew, num);
+  return 1;
+}
+
+static int
+check_connect(DESC *d, const char *msg)
+{
+  char command[MAX_COMMAND_LEN];
+  char user[MAX_COMMAND_LEN];
+  char password[MAX_COMMAND_LEN];
+  char errbuf[BUFFER_LEN];
+  dbref player;
+
+  parse_connect(msg, command, user, password);
+
+  if (string_prefix("connect", command)) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connected to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Check if we're fake siting this guy.. */
+      if(has_flag_by_name(player, "WEIRDSITE", TYPE_PLAYER)) {
+             ATTR *a;
+             a = atr_get(player, "LASTSITE");
+             strncpy(d->addr, !a ? "localhost" : atr_value(a), 100);
+      }
+
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (!strcasecmp(command, "cd")) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Connected dark to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Set player dark */
+      d->connected = 1;
+      d->player = player;
+      set_flag(player, player, "DARK", 0, 0, 0);
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (!strcasecmp(command, "cv")) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Connected to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Set player !dark */
+      d->connected = 1;
+      d->player = player;
+      set_flag(player, player, "DARK", 1, 0, 0);
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (!strcasecmp(command, "ch")) {
+    if ((player =
+        connect_player(user, password, d->addr, d->ip, errbuf)) == NOTHING) {
+      queue_string_eol(d, errbuf);
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed connect to '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Connected hidden to %s(#%d) in %s(#%d)"),
+            d->descriptor, d->addr, d->ip, Name(player), player,
+            Name(Location(player)), Location(player));
+      /* Set player hidden */
+      d->connected = 1;
+      d->player = player;
+      if (Can_Hide(player))
+       d->hide = 1;
+      if ((dump_messages(d, player, 0)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }
+
+  } else if (string_prefix("create", command)) {
+    if (!Site_Can_Create(d->addr) || !Site_Can_Create(d->ip)) {
+      fcache_dump(d, fcache.register_fcache, NULL);
+      if (!Deny_Silent_Site(d->addr, AMBIGUOUS)
+         && !Deny_Silent_Site(d->ip, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Refused create for '%s'."),
+              d->descriptor, d->addr, d->ip, user);
+      }
+      return 0;
+    }
+    if (!options.login_allow || !options.create_allow) {
+      if (!options.login_allow)
+       fcache_dump(d, fcache.down_fcache, NULL);
+      else
+       fcache_dump(d, fcache.register_fcache, NULL);
+      do_rawlog(LT_CONN,
+               "REFUSED CREATION for %s from %s on descriptor %d.\n",
+               user, d->addr, d->descriptor);
+      return 0;
+    } else if (MAX_LOGINS && !under_limit) {
+      fcache_dump(d, fcache.full_fcache, NULL);
+      do_rawlog(LT_CONN,
+               "REFUSED CREATION for %s from %s on descriptor %d.\n",
+               user, d->addr, d->descriptor);
+      return 0;
+    }
+    player = create_player(user, password, d->addr, d->ip);
+    if (player == NOTHING) {
+      queue_string_eol(d, T(create_fail));
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Failed create for '%s' (bad name)."),
+            d->descriptor, d->addr, d->ip, user);
+    } else if (player == AMBIGUOUS) {
+      queue_string_eol(d, T(password_fail));
+      do_log(LT_CONN, 0, 0,
+            T("[%d/%s/%s] Failed create for '%s' (bad password)."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      do_log(LT_CONN, 0, 0, "[%d/%s/%s] Created %s(#%d)",
+            d->descriptor, d->addr, d->ip, Name(player), player);
+      if ((dump_messages(d, player, 1)) == 0) {
+       d->connected = 2;
+       return 0;
+      }
+    }                          /* successful player creation */
+
+  } else if (string_prefix("register", command)) {
+    if (!Site_Can_Register(d->addr) || !Site_Can_Register(d->ip)) {
+      fcache_dump(d, fcache.register_fcache, NULL);
+      if (!Deny_Silent_Site(d->addr, AMBIGUOUS)
+         && !Deny_Silent_Site(d->ip, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0,
+              T("[%d/%s/%s] Refused registration (bad site) for '%s'."),
+              d->descriptor, d->addr, d->ip, user);
+      }
+      return 0;
+    }
+    if (!options.create_allow) {
+      fcache_dump(d, fcache.register_fcache, NULL);
+      do_rawlog(LT_CONN,
+               "Refused registration (creation disabled) for %s from %s on descriptor %d.\n",
+               user, d->addr, d->descriptor);
+      return 0;
+    }
+    if ((player = email_register_player(user, password, d->addr, d->ip)) ==
+       NOTHING) {
+      queue_string_eol(d, T(register_fail));
+      do_log(LT_CONN, 0, 0, T("[%d/%s/%s] Failed registration for '%s'."),
+            d->descriptor, d->addr, d->ip, user);
+    } else {
+      queue_string_eol(d, T(register_success));
+      do_log(LT_CONN, 0, 0, "[%d/%s/%s] Registered %s(#%d) to %s",
+            d->descriptor, d->addr, d->ip, Name(player), player, password);
+    }
+    /* Whether it succeeds or fails, leave them connected */
+
+  } else {
+    /* invalid command, just repeat login screen */
+    welcome_user(d);
+  }
+  /* If they were in a program, get them back in it */
+  if(InProg(d->player)) {
+    queue_string(d, "Loading @Program onto Descriptor....\r\n");
+    prog_load_desc(d);
+  }
+
+  return 1;
+}
+
+static void
+parse_connect(const char *msg1, char *command, char *user, char *pass)
+{
+  unsigned char *p;
+  unsigned const char *msg = (unsigned const char *) msg1;
+
+  while (*msg && isspace(*msg))
+    msg++;
+  p = (unsigned char *) command;
+  while (*msg && isprint(*msg) && !isspace(*msg))
+    *p++ = *msg++;
+  *p = '\0';
+  while (*msg && isspace(*msg))
+    msg++;
+  p = (unsigned char *) user;
+
+  if (PLAYER_NAME_SPACES && *msg == '\"') {
+    for (; *msg && ((*msg == '\"') || isspace(*msg)); msg++) ;
+    while (*msg && (*msg != '\"')) {
+      while (*msg && !isspace(*msg) && (*msg != '\"'))
+       *p++ = *msg++;
+      if (*msg == '\"') {
+       msg++;
+       while (*msg && isspace(*msg))
+         msg++;
+       break;
+      }
+      while (*msg && isspace(*msg))
+       msg++;
+      if (*msg && (*msg != '\"'))
+       *p++ = ' ';
+    }
+  } else
+    while (*msg && isprint(*msg) && !isspace(*msg))
+      *p++ = *msg++;
+
+  *p = '\0';
+  while (*msg && isspace(*msg))
+    msg++;
+  p = (unsigned char *) pass;
+  while (*msg && isprint(*msg) && !isspace(*msg))
+    *p++ = *msg++;
+  *p = '\0';
+}
+
+static void
+close_sockets(void)
+{
+  DESC *d, *dnext;
+
+  for (d = descriptor_list; d; d = dnext) {
+    dnext = d->next;
+    if(d->descriptor == 0) {
+      write(STDOUT_FILENO, T(shutdown_message), strlen(T(shutdown_message)));
+      write(STDOUT_FILENO, "\r\n", 2);
+    } else {
+      send(d->descriptor, T(shutdown_message), strlen(T(shutdown_message)), 0);
+      send(d->descriptor, "\r\n", 2, 0);
+      if (shutdown(d->descriptor, 2) < 0)
+        perror("shutdown");
+      closesocket(d->descriptor);
+    }
+  }
+#ifdef NT_TCP
+  /* shutdown listening thread */
+  if (platform == VER_PLATFORM_WIN32_NT)
+    closesocket(MUDListenSocket);
+#endif
+
+}
+
+/** Give everyone the boot.
+ */
+void
+emergency_shutdown(void)
+{
+  close_sockets();
+}
+
+/** Disconnect a descriptor.
+ * \param d pointer to descriptor to disconnect.
+ */
+void
+boot_desc(DESC *d)
+{
+  shutdownsock(d);
+}
+
+/** Given a player dbref, return the player's first connected descriptor.
+ * \param player dbref of player.
+ * \return pointer to player's first connected descriptor, or NULL.
+ */
+DESC *
+player_desc(dbref player)
+{
+  DESC *d;
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected && (d->player == player)) {
+      return d;
+    }
+  }
+  return (DESC *) NULL;
+}
+
+/** Page a specified socket.
+ * \param player the enactor.
+ * \param pc string containing port number to send message to.
+ * \param message message to send.
+ */
+void
+do_page_port(dbref player, const char *pc, const char *message)
+{
+  int p, key;
+  DESC *d;
+  const char *gap;
+  char tbuf[BUFFER_LEN], *tbp = tbuf;
+  dbref target = NOTHING;
+
+  if (!Admin(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  p = atoi(pc);
+  if (p <= 0) {
+    notify(player, T("That's not a port number."));
+    return;
+  }
+
+  if (!message || !*message) {
+    notify(player, T("What do you want to page with?"));
+    return;
+  }
+
+  gap = " ";
+  switch (*message) {
+  case SEMI_POSE_TOKEN:
+    gap = "";
+  case POSE_TOKEN:
+    key = 1;
+    break;
+  default:
+    key = 3;
+    break;
+  }
+
+  d = port_desc(p);
+  if (!d) {
+    notify(player, T("That port's not active."));
+    return;
+  }
+  if (d->connected)
+    target = d->player;
+  switch (key) {
+  case 1:
+    safe_format(tbuf, &tbp, T("From afar, %s%s%s"), Name(player), gap,
+               message + 1);
+    notify_format(player, T("Long distance to %s: %s%s%s"),
+                 target != NOTHING ? Name(target) :
+                 T("a connecting player"), Name(player), gap, message + 1);
+    break;
+  case 3:
+    safe_format(tbuf, &tbp, T("%s pages: %s"), Name(player), message);
+    notify_format(player, T("You paged %s with '%s'."),
+                 target != NOTHING ? Name(target) :
+                 T("a connecting player"), message);
+    break;
+  }
+  *tbp = '\0';
+  if (target != NOTHING)
+    page_return(player, target, "Idle", "IDLE", NULL);
+  if (Typeof(player) != TYPE_PLAYER && Nospoof(target))
+    queue_string_eol(d, tprintf("[#%d] %s", player, tbuf));
+  else
+    queue_string_eol(d, tbuf);
+}
+
+
+/** Return an inactive descriptor, as long as there's more than
+ * one descriptor connected. Used for boot/me.
+ * \param player player to find an inactive descriptor for.
+ * \return pointer to player's inactive descriptor, or NULL.
+ */
+DESC *
+inactive_desc(dbref player)
+{
+  DESC *d, *in = NULL;
+  time_t now;
+  int numd = 0;
+  now = mudtime;
+  DESC_ITER_CONN(d) {
+    if (d->player == player) {
+      numd++;
+      if (now - d->last_time > 60)
+       in = d;
+    }
+  }
+  if (numd > 1)
+    return in;
+  else
+    return (DESC *) NULL;
+}
+
+/** Given a port (a socket number), return the descriptor.
+ * \param port port (socket file descriptor number).
+ * \return pointer to descriptor associated with the port.
+ */
+DESC *
+port_desc(int port)
+{
+  DESC *d;
+  for (d = descriptor_list; (d); d = d->next) {
+    if (d->descriptor == port) {
+      return d;
+    }
+  }
+  return (DESC *) NULL;
+}
+
+/** Given a port, find the matching player dbref.
+ * \param port (socket file descriptor number).
+ * \return dbref of connected player using that port, or NOTHING.
+ */
+dbref
+find_player_by_desc(int port)
+{
+  DESC *d;
+  for (d = descriptor_list; (d); d = d->next) {
+    if (d->connected && (d->descriptor == port)) {
+      return d->player;
+    }
+  }
+
+  /* didn't find anything */
+  return NOTHING;
+}
+
+
+#ifndef WIN32
+/** Handler for SIGINT. Note that we've received it, and reinstall.
+ * \param sig signal caught.
+ */
+void
+signal_shutdown(int sig __attribute__ ((__unused__)))
+{
+  signal_shutdown_flag = 1;
+  reload_sig_handler(SIGINT, signal_shutdown);
+}
+
+/** Handler for SIGUSR2. Note that we've received it, and reinstall
+ * \param sig signal caught.
+ */
+void
+signal_dump(int sig __attribute__ ((__unused__)))
+{
+  signal_dump_flag = 1;
+  reload_sig_handler(SIGUSR2, signal_dump);
+}
+#endif
+
+/** A general handler to puke and die.
+ * \param sig signal caught.
+ */
+void
+bailout(int sig)
+{
+  mush_panicf(T("BAILOUT: caught signal %d"), sig);
+}
+
+#ifndef WIN32
+/** Reap child processes, notably info_slaves and forking dumps,
+ * when we receive a SIGCHLD signal. Don't fear this function. :)
+ * \param sig signal caught.
+ */
+void
+reaper(int sig __attribute__ ((__unused__)))
+{
+  WAIT_TYPE my_stat;
+  Pid_t pid;
+
+#ifdef HAS_WAITPID
+  while ((pid = waitpid(-1, &my_stat, WNOHANG)) > 0)
+#else
+  while ((pid = wait3(&my_stat, WNOHANG, 0)) > 0)
+#endif
+  {
+    if (forked_dump_pid > -1 && pid == forked_dump_pid) {
+      /* Most failures are handled by the forked mush already */
+      if (WIFSIGNALED(my_stat)) {
+       do_rawlog(LT_ERR, T("ERROR! forking dump exited with signal %d"),
+                 WTERMSIG(my_stat));
+      } else if (WIFEXITED(my_stat) && WEXITSTATUS(my_stat) == 0) {
+       time(&last_dump_time);
+       if (DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE)
+         flag_broadcast(0, 0, "%s", DUMP_NOFORK_COMPLETE);
+
+      }
+      forked_dump_pid = -1;
+    }
+  }
+  reload_sig_handler(SIGCHLD, reaper);
+}
+#endif /* !WIN32 */
+
+
+static void
+dump_info(DESC *call_by)
+{
+  int count = 0;
+  DESC *d;
+  queue_string_eol(call_by, tprintf("### Begin INFO %s", INFO_VERSION));
+
+  /* Count connected players */
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected) {
+      if (!GoodObject(d->player))
+       continue;
+      if (COUNT_ALL || !Hidden(d) || call_by->player == d->player )
+       count++;
+    }
+  }
+  queue_string_eol(call_by, tprintf("Name: %s", options.mud_name));
+  queue_string_eol(call_by,
+                  tprintf("Uptime: %s", show_time(first_start_time, 0)));
+  queue_string_eol(call_by, tprintf("Connected: %d", count));
+  queue_string_eol(call_by, tprintf("Size: %d", db_top));
+  queue_string_eol(call_by, tprintf("Version: CobraMUSH v%s [%s]", VERSION,
+                       VBRANCH));
+#ifdef PATCHES
+  queue_string_eol(call_by, tprintf("Patches: %s", PATCHES));
+#endif
+  queue_string_eol(call_by, "### End INFO");
+}
+
+static void
+dump_users(DESC *call_by, char *match, int doing)
+    /* doing: 0 if normal WHO, 1 if DOING, 2 if SESSION */
+{
+  DESC *d;
+#ifdef COLOREDWHO
+  int tcount = 0; 
+#endif
+  int count = 0;
+  time_t now;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  int csite;
+
+  if (!GoodObject(call_by->player)) {
+    do_log(LT_ERR, 0, 0, T("Bogus caller #%d of dump_users"), call_by->player);
+    return;
+  }
+  while (*match && *match == ' ')
+    match++;
+  now = mudtime;
+
+  /* If an admin types "DOING" it gives him the normal player WHO,
+   * BUT flags are not shown. Privileged WHO does not show @doings.
+   */
+
+  if (SUPPORT_PUEBLO && (call_by->conn_flags & CONN_HTML)) {
+    queue_newwrite(call_by, (unsigned char *) "<img xch_mode=html>", 19);
+    queue_newwrite(call_by, (unsigned char *) "<PRE>", 5);
+  }
+
+  if ((doing == 1) || !call_by->player || !Priv_Who(call_by->player)) {
+    if (poll_msg[0] == '\0')
+      strcpy(poll_msg, "Doing");
+    if (ShowAnsi(call_by->player))
+      sprintf(tbuf2, "%s%-16s %4s %10s %6s  %s%s\n", ANSI_HILITE,
+             T("Player Name"), T("Aff"), T("On For"), T("Idle"), poll_msg, ANSI_NORMAL);
+    else
+      sprintf(tbuf2, "%-16s %4s %10s %6s  %s\n",
+             T("Player Name"), T("Aff"), T("On For"), T("Idle"), poll_msg);
+    queue_string(call_by, tbuf2);
+  } else if (doing == 2) {
+    sprintf(tbuf2, "%s%-16s %6s %9s %5s %5s Des  Sent    Recv  Pend%s\n", ShowAnsi(call_by->player) ? ANSI_HILITE :
+                   "", T("Player Name"), T("Loc #"), T("On For"), T("Idle"), T("Cmds"),
+                   ShowAnsi(call_by->player) ? ANSI_NORMAL : "");
+    queue_string(call_by, tbuf2);
+  } else {
+    sprintf(tbuf2, "%s%-16s %6s %9s %5s %5s Des  Host%s\n", ShowAnsi(call_by->player) ? ANSI_HILITE : "",
+           T("Player Name"), T("Loc #"), T("On For"), T("Idle"), T("Cmds"), ShowAnsi(call_by->player) ? ANSI_NORMAL : "");
+    queue_string(call_by, tbuf2);
+  }
+
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected) {
+      if (!GoodObject(d->player))
+       continue;
+      if (COUNT_ALL || (!Hidden(d) || call_by->player == d->player 
+                       || (call_by->player && Priv_Who(call_by->player)))) {
+        count++;
+#ifdef COLOREDWHO
+        tcount++;
+#endif
+      }
+      if (match && !(string_prefix(Name(d->player), match)))
+       continue;
+      csite = CanSee(call_by->player, d->player);
+
+      if (call_by->connected && doing == 0 && call_by->player
+         && Priv_Who(call_by->player)) {
+        if (Hidden(d) && !csite)
+          continue;
+
+       sprintf(tbuf1, "%-16s %6s %9s %5s  %4d %3d%c  %s", tprintf("%s%s", Name(d->player), InProg(d->player) ? "(P)" : ""),
+               Can_Locate(call_by->player, d->player) ? unparse_dbref(Location(d->player)) : "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), csite ? d->cmds : 0,
+               csite ? d->descriptor : 0,
+               ' ',
+               csite ? d->addr : "---");
+       tbuf1[78] = '\0';
+       if (Dark(d->player)) {
+         tbuf1[71] = '\0';
+         strcat(tbuf1, " (Dark)");
+       } else if (Hidden(d)) {
+         tbuf1[71] = '\0';
+         strcat(tbuf1, " (Hide)");
+       }
+      } else if (call_by->connected && doing == 2 && call_by->player
+                && Priv_Who(call_by->player)) {
+       sprintf(tbuf1, "%-16s %6s %9s %5s %5d %3d%c %5lu %7lu %5d",
+               tprintf("%s%s", Name(d->player), InProg(d->player) ? "(P)" : ""),
+               Can_Locate(call_by->player, d->player) ? unparse_dbref(Location(d->player)) : "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), csite ? d->cmds : 0,
+               csite ? d->descriptor : 0,
+               ' ',
+
+               csite ? d->input_chars : 0, csite ? d->output_chars : 0,
+               csite ? d->output_size : 0);
+      } else {
+       if (!Hidden(d)
+           || call_by->player == d->player ||
+           (call_by->player && Priv_Who(call_by->player) && (doing))) {
+         sprintf(tbuf1, "%-16s %4s %10s   %4s%c %s", tprintf("%s%s", Name(d->player), InProg(d->player) ? "(P)" : ""), empabb(d->player),
+                 time_format_1(now - d->connected_at),
+                 time_format_2(now - d->last_time),
+                 (Dark(d->player) ? 'D' : (Hidden(d) ? 'H' : ' '))
+                 , d->doing);
+       }
+      }
+
+      if (!Hidden(d) || (call_by->player && Priv_Who(call_by->player))) {
+#ifdef COLOREDWHO
+       if(ShowAnsiColor(call_by->player))
+               queue_string(call_by, tprintf("%s%s%s%s%s", ANSI_NORMAL, (tcount % 2 ?  "" : ANSI_HILITE), 
+                                       (tcount % 2 ? ANSI_CYAN : ANSI_WHITE),
+                                       tbuf1, ANSI_NORMAL));
+
+       else 
+#endif
+               queue_string(call_by, tbuf1);
+       queue_newwrite(call_by, (unsigned char *) "\r\n", 2);
+      }
+    } else if (call_by->player && Priv_Who(call_by->player) && doing != 1
+              && (!match || !*match)) {
+#ifdef COLOREDWHO
+      tcount++;
+#endif
+      if (doing == 0) {
+       /* Privileged WHO for non-logged in connections */
+       sprintf(tbuf1, "%-16s %6s %9s %5s  %4d %3d%c  %s", T("Connecting..."),
+               "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), d->cmds, d->descriptor,
+                             ' ',
+
+               d->addr);
+       tbuf1[78] = '\0';
+      } else {
+       /* SESSION for non-logged in connections */
+       sprintf(tbuf1, "%-16s %5s %9s %5s %5d %3d%c %5lu %7lu %5d",
+               T("Connecting..."), "#-1",
+               time_format_1(now - d->connected_at),
+               time_format_2(now - d->last_time), d->cmds, d->descriptor,
+                             ' ',
+               d->input_chars, d->output_chars, d->output_size);
+      }
+#ifdef COLOREDWHO
+      if(ShowAnsiColor(call_by->player))
+             queue_string(call_by, tprintf("%s%s%s%s%s", ANSI_NORMAL,tcount % 2 ?  "" : ANSI_HILITE,
+                                     tcount % 2 ? ANSI_CYAN : ANSI_WHITE, tbuf1, ANSI_NORMAL));
+      else 
+#endif
+             queue_string(call_by, tbuf1);
+      queue_newwrite(call_by, (unsigned char *) "\r\n", 2);
+    }
+  }
+  switch (count) {
+  case 0:
+    strcpy(tbuf1, T("There are no players connected."));
+    break;
+  case 1:
+    strcpy(tbuf1, T("There is 1 player connected."));
+    break;
+  default:
+    sprintf(tbuf1, T("There are %d players connected."), count);
+    break;
+  }
+
+#ifdef COLOREDWHO
+  if(ShowAnsiColor(call_by->player))
+         queue_string(call_by, tprintf("%s%s%s%s%s", ANSI_NORMAL, (tcount+1) % 2 ? "" : ANSI_HILITE ,
+                                 (tcount+1) % 2 ? ANSI_CYAN : ANSI_WHITE, tbuf1, ANSI_NORMAL));
+  else 
+#endif
+         queue_string(call_by, tbuf1);
+  if (SUPPORT_PUEBLO && (call_by->conn_flags & CONN_HTML)) {
+    queue_newwrite(call_by, (unsigned char *) "\n</PRE>\n", 8);
+    queue_newwrite(call_by, (unsigned char *) "<img xch_mode=purehtml>", 23);
+  } else
+    queue_newwrite(call_by, (unsigned char *) "\r\n", 2);
+}
+
+static const char *
+time_format_1(long dt)
+{
+  register struct tm *delta;
+  time_t holder;               /* A hack for 64bit SGI */
+  static char buf[64];
+  if (dt < 0)
+    dt = 0;
+  holder = (time_t) dt;
+  delta = gmtime(&holder);
+  if (delta->tm_yday > 0) {
+    sprintf(buf, "%dd %02d:%02d",
+           delta->tm_yday, delta->tm_hour, delta->tm_min);
+  } else {
+    sprintf(buf, "%02d:%02d", delta->tm_hour, delta->tm_min);
+  }
+  return buf;
+}
+
+static const char *
+time_format_2(long dt)
+{
+  register struct tm *delta;
+  static char buf[64];
+  if (dt < 0)
+    dt = 0;
+
+  delta = gmtime((time_t *) & dt);
+  if (delta->tm_yday > 0) {
+    sprintf(buf, "%dd", delta->tm_yday);
+  } else if (delta->tm_hour > 0) {
+    sprintf(buf, "%dh", delta->tm_hour);
+  } else if (delta->tm_min > 0) {
+    sprintf(buf, "%dm", delta->tm_min);
+  } else {
+    sprintf(buf, "%ds", delta->tm_sec);
+  }
+  return buf;
+}
+
+/* connection messages
+ * isnew: newly creaetd or not?
+ * num: how many times connected?
+ */
+void
+announce_connect(dbref player, int isnew, int num)
+{
+  dbref loc;
+  char tbuf1[BUFFER_LEN];
+  dbref zone;
+  dbref obj;
+  int j;
+
+  set_flag_internal(player, "CONNECTED");
+
+  if (isnew) {
+    /* A brand new player created. */
+    sprintf(tbuf1, T("%s created."), Name(player));
+    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
+  }
+
+  /* Redundant, but better for translators */
+  if (Dark(player)) {
+    sprintf(tbuf1, (num > 1) ? T("%s has DARK-reconnected.") :
+           T("%s has DARK-connected."), Name(player));
+  } else if (hidden(player)) {
+    sprintf(tbuf1, (num > 1) ? T("%s has HIDDEN-reconnected.") :
+           T("%s has HIDDEN-connected."), Name(player));
+  } else {
+    sprintf(tbuf1, (num > 1) ? T("%s has reconnected.") :
+           T("%s has connected."), Name(player));
+  }
+
+  /* send out messages */
+  if (!Dark(player))
+    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
+
+#ifdef CHAT_SYSTEM
+  if (ANNOUNCE_CONNECTS)
+    chat_player_announce(player, tbuf1, (num == 1));
+#endif /* CHAT_SYSTEM */
+
+  loc = Location(player);
+  if (!GoodObject(loc)) {
+    notify(player, T("You are nowhere!"));
+    return;
+  }
+  orator = player;
+
+  if (cf_motd_msg && *cf_motd_msg) {
+    raw_notify(player, cf_motd_msg);
+  }
+  raw_notify(player, " ");
+
+  if(ANNOUNCE_CONNECTS)
+    notify_except(Contents(player), player, tbuf1, 0);
+  /* added to allow player's inventory to hear a player connect */
+  if(ANNOUNCE_CONNECTS)
+    if(!Dark(player))
+    notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
+
+  /* clear the environment for possible actions */
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = NULL;
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = NULL;
+
+  /* do the person's personal connect action */
+  (void) queue_attribute(player, "ACONNECT", player);
+  if (ROOM_CONNECTS) {
+    /* Do the room the player connected into */
+    if (IsRoom(loc) || IsThing(loc)) {
+      (void) queue_attribute(loc, "ACONNECT", player);
+    }
+  }
+  /* do the person's division */
+  if (GoodObject(SDIV(player).object))
+    (void) queue_attribute(SDIV(player).object, "ACONNECT", player);
+  /* do the zone of the player's location's possible aconnect */
+  if ((zone = Zone(loc)) != NOTHING) {
+    switch (Typeof(zone)) {
+    case TYPE_THING:
+      (void) queue_attribute(zone, "ACONNECT", player);
+      break;
+    case TYPE_ROOM:
+      /* check every object in the room for a connect action */
+      DOLIST(obj, Contents(zone)) {
+       (void) queue_attribute(obj, "ACONNECT", player);
+      }
+      break;
+    default:
+      do_log(LT_ERR, 0, 0,
+            T("Invalid zone #%d for %s(#%d) has bad type %d"), zone,
+            Name(player), player, Typeof(zone));
+    }
+  }
+  /* now try the master room */
+  DOLIST(obj, Contents(MASTER_ROOM)) {
+    (void) queue_attribute(obj, "ACONNECT", player);
+  }
+}
+
+void
+announce_disconnect(dbref player)
+{
+  dbref loc;
+  int num;
+  DESC *d;
+  char tbuf1[BUFFER_LEN];
+  dbref zone, obj;
+  int j;
+
+  loc = Location(player);
+  if (!GoodObject(loc))
+    return;
+
+  orator = player;
+
+  for (num = 0, d = descriptor_list; d; d = d->next)
+    if (d->connected && (d->player == player))
+      num++;
+  if (num < 2) {
+    sprintf(tbuf1, T("%s has disconnected."), Name(player));
+
+    if (ANNOUNCE_CONNECTS) {
+      if (!Dark(player))
+       notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
+      /* notify contents */
+      notify_except(Contents(player), player, tbuf1, 0);
+    }
+
+    /* clear the environment for possible actions */
+    for (j = 0; j < 10; j++)
+      global_eval_context.wnxt[j] = NULL;
+    for (j = 0; j < NUMQ; j++)
+      global_eval_context.rnxt[j] = NULL;
+
+    /* Setup all connect information as info to pass */
+    (void) queue_attribute(player, "ADISCONNECT", player);
+    /* do the person's division */
+    if (GoodObject(SDIV(player).object))
+      (void) queue_attribute(SDIV(player).object, "ACONNECT", player);
+    if (ROOM_CONNECTS)
+      if (IsRoom(loc) || IsThing(loc)) {
+       (void) queue_attribute(loc, "ADISCONNECT", player);
+      }
+    /* do the zone of the player's location's possible adisconnect */
+    if ((zone = Zone(loc)) != NOTHING) {
+      switch (Typeof(zone)) {
+      case TYPE_DIVISION:
+      case TYPE_THING:
+       (void) queue_attribute(zone, "ADISCONNECT", player);
+       break;
+      case TYPE_ROOM:
+       /* check every object in the room for a connect action */
+       DOLIST(obj, Contents(zone)) {
+         (void) queue_attribute(obj, "ADISCONNECT", player);
+       }
+       break;
+      default:
+       do_log(LT_ERR, 0, 0,
+              T("Invalid zone #%d for %s(#%d) has bad type %d"), zone,
+              Name(player), player, Typeof(zone));
+      }
+    }
+    /* now try the master room */
+    DOLIST(obj, Contents(MASTER_ROOM)) {
+      (void) queue_attribute(obj, "ADISCONNECT", player);
+    }
+    clear_flag_internal(player, "CONNECTED");
+    (void) atr_add(player, "LASTLOGOUT", show_time(mudtime, 0), GOD, NOTHING);
+  } else {
+    /* note: when you partially disconnect, ADISCONNECTS are not executed */
+    sprintf(tbuf1, T("%s has partially disconnected."), Name(player));
+
+    if (ANNOUNCE_CONNECTS) {
+      if (!Dark(player))
+       notify_except(Contents(loc), player, tbuf1, NA_INTER_PRESENCE);
+      /* notify contents */
+      notify_except(Contents(player), player, tbuf1, 0);
+    }
+  }
+
+#ifdef CHAT_SYSTEM
+  if (ANNOUNCE_CONNECTS)
+    chat_player_announce(player, tbuf1, 0);
+#endif /* CHAT_SYSTEM */
+
+  /* Monitor broadcasts */
+  /* Redundant, but better for translators */
+  if (Dark(player)) {
+    sprintf(tbuf1, (num < 2) ? T("%s has DARK-disconnected.") :
+           T("%s has partially DARK-disconnected."), Name(player));
+  } else if (hidden(player)) {
+    sprintf(tbuf1, (num < 2) ? T("%s has HIDDEN-disconnected.") :
+           T("%s has partially HIDDEN-disconnected."), Name(player));
+  } else {
+    sprintf(tbuf1, (num < 2) ? T("%s has disconnected.") :
+           T("%s has partially disconnected."), Name(player));
+  }
+  if (!Dark(player))
+    flag_broadcast(0, "MONITOR", "%s %s", T("GAME:"), tbuf1);
+  if(Guest(player))  { /* queue for destruction */
+      set_flag_internal(player, "GOING");
+      set_flag_internal(player, "GOING_TWICE");
+    }
+  local_disconnect(player, num);
+}
+
+/** Set an motd message.
+ * \verbatim
+ * This implements @motd.
+ * \endverbatim
+ * \param player the enactor.
+ * \param key type of MOTD to set.
+ * \param message text to set the motd to.
+ */
+void
+do_motd(dbref player, enum motd_type key, const char *message)
+{
+
+  if (!Site(player) && key != MOTD_LIST) {
+    notify(player,
+          T
+          ("You may get 15 minutes of fame and glory in life, but not right now."));
+    return;
+  }
+  switch (key) {
+  case MOTD_MOTD:
+    strcpy(cf_motd_msg, message);
+    notify(player, T("Motd set."));
+    break;
+  case MOTD_DOWN:
+    strcpy(cf_downmotd_msg, message);
+    notify(player, T("Down motd set."));
+    break;
+  case MOTD_FULL:
+    strcpy(cf_fullmotd_msg, message);
+    notify(player, T("Full motd set."));
+    break;
+  case MOTD_LIST:
+    notify_format(player, "MOTD: %s", cf_motd_msg);
+    if (Site(player)) {
+      notify_format(player, T("Down MOTD: %s"), cf_downmotd_msg);
+      notify_format(player, T("Full MOTD: %s"), cf_fullmotd_msg);
+    }
+  }
+}
+
+/** Set a DOING message.
+ * \verbatim
+ * This implements @doing.
+ * \endverbatim
+ * \param player the enactor.
+ * \param message the message to set.
+ */
+void
+do_doing(dbref player, const char *message)
+{
+  char buf[MAX_COMMAND_LEN];
+  DESC *d;
+  int i;
+
+  if (!Connected(player)) {
+    /* non-connected things have no need for a doing */
+    notify(player, T("Why would you want to do that?"));
+    return;
+  }
+  strncpy(buf, remove_markup(message, NULL), DOING_LEN - 1);
+
+  /* now smash undesirable characters and truncate */
+  for (i = 0; i < DOING_LEN; i++) {
+    if ((buf[i] == '\r') || (buf[i] == '\n') ||
+       (buf[i] == '\t') || (buf[i] == BEEP_CHAR))
+      buf[i] = ' ';
+  }
+  buf[DOING_LEN - 1] = '\0';
+
+  /* set it */
+  for (d = descriptor_list; d; d = d->next)
+    if (d->connected && (d->player == player))
+      strcpy(d->doing, buf);
+  if (strlen(message) >= DOING_LEN) {
+    notify_format(player,
+                 T("Doing set. %d characters lost."),
+                 strlen(message) - (DOING_LEN - 1));
+  } else
+    notify(player, T("Doing set."));
+}
+
+/** Set a poll message (which replaces "Doing" in the DOING output).
+ * \verbatim
+ * This implements @poll.
+ * \endverbatim
+ * \param player the enactor.
+ * \param message the message to set.
+ */
+void
+do_poll(dbref player, const char *message)
+{
+  int i;
+
+  if (!Change_Poll(player)) {
+    notify(player, T("Who do you think you are, Gallup?"));
+    return;
+  }
+  strncpy(poll_msg, remove_markup(message, NULL), DOING_LEN - 1);
+  for (i = 0; i < DOING_LEN; i++) {
+    if ((poll_msg[i] == '\r') || (poll_msg[i] == '\n') ||
+       (poll_msg[i] == '\t') || (poll_msg[i] == BEEP_CHAR))
+      poll_msg[i] = ' ';
+  }
+  poll_msg[DOING_LEN - 1] = '\0';
+
+  if (strlen(message) >= DOING_LEN) {
+    poll_msg[DOING_LEN - 1] = 0;
+    notify_format(player,
+                 T("Poll set. %d characters lost."),
+                 strlen(message) - (DOING_LEN - 1));
+  } else
+    notify(player, T("Poll set."));
+  do_log(LT_WIZ, player, NOTHING, T("Poll Set to '%s'."), poll_msg);
+}
+
+/** Match the partial name of a connected player.
+ * \param match string to match.
+ * \return dbref of a unique connected player whose name partial-matches, 
+ * AMBIGUOUS, or NOTHING.
+ */
+dbref
+short_page(const char *match)
+{
+  DESC *d;
+  dbref who1 = NOTHING;
+  int count = 0;
+
+  for (d = descriptor_list; d; d = d->next) {
+    if (d->connected) {
+      if (match && !string_prefix(Name(d->player), match))
+       continue;
+      if (!strcasecmp(Name(d->player), match)) {
+       count = 1;
+       who1 = d->player;
+       break;
+      }
+      if (who1 == NOTHING || d->player != who1) {
+       who1 = d->player;
+       count++;
+      }
+    }
+  }
+
+  if (count > 1)
+    return AMBIGUOUS;
+  else if (count == 0)
+    return NOTHING;
+
+  return who1;
+}
+
+/** Match the partial name of a connected player the enactor can see.
+ * \param player the enactor
+ * \param match string to match.
+ * \return dbref of a unique connected player whose name partial-matches, 
+ * AMBIGUOUS, or NOTHING.
+ */
+dbref
+visible_short_page(dbref player, const char *match)
+{
+  dbref target;
+  target = short_page(match);
+  if (Priv_Who(player) || !GoodObject(target))
+    return target;
+  if (Dark(target) || (hidden(target) && !nearby(player, target)))
+    return NOTHING;
+  return target;
+}
+
+/* LWHO() function - really belongs elsewhere but needs stuff declared here */
+
+/* ARGSUSED */
+FUNCTION(fun_lwho)
+{
+  DESC *d;
+  dbref victim;
+  int first, powered = (*called_as == 'L') && Priv_Who(executor);
+
+  first = 1;
+  if(nargs && args[0] && *args[0]) {
+    if(!powered) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+
+    if( (victim = noisy_match_result(executor, args[0], NOTYPE, MAT_EVERYTHING)) == 0) {
+      safe_str(T(e_notvis), buff, bp);
+      return;
+    }
+    if(!controls(executor, victim)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if(!Priv_Who(victim))
+      powered = 0;
+  } else victim = executor;
+  
+  DESC_ITER_CONN(d) {
+    if (!Hidden(d) || (powered && CanSee(victim,d->player))) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_dbref(d->player, buff, bp);
+    }
+  }
+}
+
+
+/** Look up a DESC by character name or file descriptor.
+ * \param executor the dbref of the object calling the function calling this.
+ * \param name the name or descriptor to look up.
+ * \retval a pointer to the proper DESC, or NULL
+ */
+static DESC *
+lookup_desc(dbref executor, const char *name)
+{
+  DESC *d;
+
+  /* Given a file descriptor. See-all only. */
+  if (is_strict_integer(name)) {
+    int fd = parse_integer(name);
+    DESC_ITER_CONN(d) {
+      if (d->descriptor == fd) {
+       if (Priv_Who(executor) || d->player == executor
+       || (Inherit_Powers(executor) && Priv_Who(Owner(executor))))
+         return d;
+       else
+         return NULL;
+      }
+    }
+    return NULL;
+  } else {                    /* Look up player name */
+    DESC *match = NULL;
+    dbref target = lookup_player(name);
+    if (target == NOTHING) {
+      target = match_result(executor, name, TYPE_PLAYER,
+                           MAT_ABSOLUTE | MAT_PLAYER | MAT_ME);
+    }
+    if (!GoodObject(target) || !Connected(target))
+      return NULL;
+    else {
+      /* walk the descriptor list looking for a match of a dbref */
+      DESC_ITER_CONN(d) {
+       if ((d->player == target) &&
+           (!Hidden(d) || Priv_Who(executor) ||
+               (Inherit_Powers(executor) && Priv_Who(Owner(executor)))) &&
+           (!match || (d->last_time > match->last_time)))
+         match = d;
+      }
+      return match;
+    }
+  }
+}
+
+/** Return the ip address of the least-idle connection of a player.
+ * This function performs no permission checking, and returns the
+ * pointer from the descriptor itself (so don't destroy the result!)
+ * \param player dbref of player to get ip for.
+ * \return IP address of player as a string or NULL if not found.
+ */
+char *
+least_idle_ip(dbref player)
+{
+  DESC *d, *match = NULL;
+  DESC_ITER_CONN(d) {
+    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
+      match = d;
+  }
+  return match ? (match->ip) : NULL;
+}
+
+/** Return the hostname of the least-idle connection of a player.
+ * This function performs no permission checking, and returns a static
+ * string.
+ * \param player dbref of player to get ip for.
+ * \return hostname of player as a string or NULL if not found.
+ */
+char *
+least_idle_hostname(dbref player)
+{
+  DESC *d, *match = NULL;
+  static char hostname[101];
+  char *p;
+
+  DESC_ITER_CONN(d) {
+    if ((d->player == player) && (!match || (d->last_time > match->last_time)))
+      match = d;
+  }
+  if (!match)
+    return NULL;
+  strcpy(hostname, match->addr);
+  if ((p = strchr(hostname, '@'))) {
+    p++;
+    return p;
+  } else
+    return hostname;
+}
+
+/* ZWHO() function - really belongs in eval.c but needs stuff declared here */
+/* ARGSUSED */
+FUNCTION(fun_zwho)
+{
+  DESC *d;
+  dbref zone, victim;
+  int first;
+  int powered = (strcmp(called_as, "ZMWHO") && Priv_Who(executor) || (Inherit_Powers(executor) && Priv_Who(Owner(executor))));
+  first = 1;
+
+  zone = match_thing(executor, args[0]);
+
+  if (nargs == 1) {
+    victim = executor;
+  } else if ((nargs == 2) && powered) {
+    if ((victim = match_thing(executor, args[1])) == 0) {
+      safe_str(T(e_match), buff, bp);
+      return;
+    }
+  } else {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (!GoodObject(zone) || !(eval_lock(victim, zone, Zone_Lock) || CanModify(victim,zone))) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if ((getlock(zone, Zone_Lock) == TRUE_BOOLEXP) ||
+      (IsPlayer(zone) && !(has_flag_by_name(zone, "SHARED", TYPE_PLAYER)))) {
+    safe_str(T("#-1 INVALID ZONE."), buff, bp);
+    return;
+  }
+
+  /* Use lowest privilege for victim */
+  if (!Priv_Who(victim))
+    powered = 0;
+
+  DESC_ITER_CONN(d) {
+    if (!Hidden(d) || powered) {
+      if (Zone(Location(d->player)) == zone) {
+       if (first) {
+         first = 0;
+       } else {
+         safe_chr(' ', buff, bp);
+       }
+       safe_dbref(d->player, buff, bp);
+      }
+    }
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_doing)
+{
+  /* Gets a player's @doing */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d)
+    safe_str(d->doing, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_hostname)
+{
+  /* Gets a player's hostname */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_str(d->addr, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ipaddr)
+{
+  /* Gets a player's IP address */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_str(d->ip, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cmds)
+{
+  /* Gets a player's IP address */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_integer(d->cmds, buff, bp);
+  else
+    safe_integer(-1, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sent)
+{
+  /* Gets a player's bytes sent */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_integer(d->input_chars, buff, bp);
+  else
+    safe_integer(-1, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_recv)
+{
+  /* Gets a player's bytes received */
+  DESC *d = lookup_desc(executor, args[0]);
+  if (d && (d->player == executor || Site(executor)))
+    safe_integer(d->output_chars, buff, bp);
+  else
+    safe_integer(-1, buff, bp);
+}
+
+FUNCTION(fun_poll)
+{
+  /* Gets the current poll */
+  if (poll_msg[0] == '\0')
+    strcpy(poll_msg, "Doing");
+
+  safe_str(poll_msg, buff, bp);
+}
+
+FUNCTION(fun_pueblo)
+{
+  /* Return the status of the pueblo flag on the least idle descriptor we
+   * find that matches the player's dbref.
+   */
+  DESC *match = lookup_desc(executor, args[0]);
+  if (match)
+    safe_boolean(match->conn_flags & CONN_HTML, buff, bp);
+  else
+    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
+}
+
+FUNCTION(fun_ssl)
+{
+  /* Return the status of the ssl flag on the least idle descriptor we
+   * find that matches the player's dbref.
+   */
+  safe_boolean(0, buff, bp);
+}
+
+FUNCTION(fun_width)
+{
+  DESC *match;
+  if (!*args[0])
+    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
+  else if ((match = lookup_desc(executor, args[0])))
+    safe_integer(match->width, buff, bp);
+  else
+    safe_str("78", buff, bp);
+}
+
+FUNCTION(fun_height)
+{
+  DESC *match;
+  if (!*args[0])
+    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
+  else if ((match = lookup_desc(executor, args[0])))
+    safe_integer(match->height, buff, bp);
+  else
+    safe_str("24", buff, bp);
+}
+
+FUNCTION(fun_terminfo)
+{
+  DESC *match;
+  if (!*args[0])
+    safe_str(T("#-1 FUNCTION REQUIRES ONE ARGUMENT"), buff, bp);
+  else if ((match = lookup_desc(executor, args[0]))) {
+    if (match->player == executor || Site(executor)) {
+      safe_str(match->ttype, buff, bp);
+      if (match->conn_flags & CONN_HTML)
+       safe_str(" pueblo", buff, bp);
+      if (match->conn_flags & CONN_TELNET)
+       safe_str(" telnet", buff, bp);
+    } else
+      safe_str(T(e_perm), buff, bp);
+  } else
+    safe_str(T("#-1 NOT CONNECTED"), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idlesecs)
+{
+  /* returns the number of seconds a player has been idle, using
+   * their least idle connection
+   */
+
+  DESC *match = lookup_desc(executor, args[0]);
+  if (match)
+    safe_number(difftime(mudtime, match->last_time), buff, bp);
+  else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idle_average) {
+  DESC *match = lookup_desc(executor, args[0]);
+  double idle_time;
+
+  if(match) {
+    idle_time = difftime(mudtime, match->last_time);
+    if(idle_time >= 300)
+      safe_number(((match->idle_total + idle_time) / (match->unidle_times+1)), buff, bp);
+    else if(match->unidle_times == 0)
+      safe_number(0, buff, bp);
+    else
+      safe_number((match->idle_total / match->unidle_times), buff, bp);
+    
+  } else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idle_total) {
+  DESC *match = lookup_desc(executor, args[0]);
+  double idle_time;
+
+  if(match) {
+    idle_time = difftime(mudtime, match->last_time);
+    safe_number(idle_time >= 300 ? (match->idle_total+idle_time) : match->idle_total, buff, bp);
+  } else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_idle_times) {
+  DESC *match = lookup_desc(executor, args[0]);
+
+  if(match) {
+     safe_number((difftime(mudtime, match->last_time) >= 300) ? 
+        (match->unidle_times+1) : match->unidle_times, buff, bp);
+  } else
+    safe_str("-1", buff, bp);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_conn)
+{
+  /* returns the number of seconds a player has been connected, using
+   * their longest-connected descriptor
+   */
+
+  DESC *match = lookup_desc(executor, args[0]);
+  if (match)
+    safe_number(difftime(mudtime, match->connected_at), buff, bp);
+  else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lports)
+{
+  DESC *d;
+  int first = 1;
+
+  if (!Priv_Who(executor)
+       && !(Inherit_Powers(executor) && Priv_Who(Owner(executor)))) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  DESC_ITER_CONN(d) {
+    if (first)
+      first = 0;
+    else
+      safe_chr(' ', buff, bp);
+    safe_integer(d->descriptor, buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ports)
+{
+  /* returns a list of the network descriptors that a player is
+   * connected to 
+   */
+
+  dbref target;
+  DESC *d;
+  int first;
+
+  target = lookup_player(args[0]);
+  if (target == NOTHING) {
+    target = match_result(executor, args[0], TYPE_PLAYER,
+                         MAT_ABSOLUTE | MAT_PLAYER | MAT_ME);
+  }
+  if (target != executor && !Priv_Who(executor)
+       && !(Inherit_Powers(executor) && Priv_Who(Owner(executor)))) {
+    /* This should probably be a safe_str */
+    notify(executor, T("Permission denied."));
+    return;
+  }
+  if (!GoodObject(target) || !Connected(target)) {
+    return;
+  }
+  /* Walk descriptor chain. */
+  first = 1;
+  DESC_ITER_CONN(d) {
+    if (d->player == target) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_integer(d->descriptor, buff, bp);
+    }
+  }
+}
+
+
+/** Hide or unhide a player.
+ * Although hiding is a per-descriptor state, this function sets all of
+ * a player's connected descriptors to be hidden.
+ * \param player dbref of player to hide.
+ * \param hide if 1, hide; if 0, unhide.
+ */
+void
+hide_player(dbref player, int hide)
+{
+  DESC *d;
+  if (!Connected(player))
+    return;
+  if (!Can_Hide(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* change status on WHO */
+  if (Can_Hide(player)) {
+    DESC_ITER_CONN(d) {
+      if (d->player == player)
+       d->hide = hide;
+    }
+  }
+  if (hide)
+    notify(player, T("You no longer appear on the WHO list."));
+  else
+    notify(player, T("You now appear on the WHO list."));
+}
+
+/** Perform the periodic check of inactive descriptors, and 
+ * disconnect them or autohide them as appropriate.
+ */
+void
+inactivity_check(void)
+{
+  DESC *d, *nextd;
+  ATTR *a;
+  char tbuf[BUFFER_LEN];
+  time_t now, idle, idle_for, unconnected_idle;
+  if (!INACTIVITY_LIMIT && !UNCONNECTED_LIMIT)
+    return;
+  now = mudtime;
+  idle = INACTIVITY_LIMIT ? INACTIVITY_LIMIT : INT_MAX;
+  unconnected_idle = UNCONNECTED_LIMIT ? UNCONNECTED_LIMIT : INT_MAX;
+  for (d = descriptor_list; d; d = nextd) {
+    nextd = d->next;
+    idle_for = now - d->last_time;
+    /* If they've been connected for 60 seconds without getting a telnet-option
+       back, the client probably doesn't understand them */
+    if ((d->conn_flags & CONN_TELNET_QUERY) && idle_for > 60)
+      d->conn_flags &= ~CONN_TELNET_QUERY;
+    if(d->connected && GoodObject(d->player) && ((a = atr_get(d->player, "IDLE_TIMEOUT"))!=NULL)) {
+           memset(tbuf, '\0', BUFFER_LEN);
+           strncpy(tbuf, atr_value(a), BUFFER_LEN-1);
+           idle = atoi(tbuf);
+           if(idle > 0)
+             goto after_idle_atr_check;
+    } 
+    idle = INACTIVITY_LIMIT ? INACTIVITY_LIMIT : INT_MAX;
+after_idle_atr_check:
+    if ((d->connected) ? (idle_for > idle ) : (idle_for > unconnected_idle)) {
+
+      if (!d->connected)
+       shutdownsock(d);
+      else if (!Can_Idle(d->player)) {
+
+       queue_string(d, T("\n*** Inactivity timeout ***\n"));
+       do_log(LT_CONN, 0, 0,
+              T("[%d/%s/%s] Logout by %s(#%d) <Inactivity Timeout>"),
+              d->descriptor, d->addr, d->ip, Name(d->player), d->player);
+       boot_desc(d);
+      } else if (Unfind(d->player)) {
+
+       if ((Can_Hide(d->player)) && (!Hidden(d))) {
+         queue_string(d,
+                      T
+                      ("\n*** Inactivity limit reached. You are now HIDDEN. ***\n"));
+         d->hide = 1;
+       }
+      }
+    }
+  }
+}
+
+
+/** Given a player dbref, return the player's hidden status.
+ * \param player dbref of player to check.
+ * \retval 1 player is hidden.
+ * \retval 0 player is not hidden.
+ */
+int
+hidden(dbref player)
+{
+  DESC *d;
+  DESC_ITER_CONN(d) {
+    if (d->player == player) {
+      if (Hidden(d))
+       return 1;
+      else
+       return 0;
+    }
+  }
+  return 0;
+}
+
+
+/** Return the mailp of the player closest in db# to player,
+ * or NULL if there's nobody on-line.
+ * In the current mail system, mail is stored in a linked list, sorted
+ * by recipient, which makes the most common operations (listing and reading
+ * your mail) fast. When a player first connects, we store (on the
+ * mailp element of their descriptor) a pointer to the beginning of
+ * their part of the linked list. Rather than search the whole linked
+ * list to find this location, we look at the mailp's of all the other
+ * connected players, and find the mailp of the player whose dbref
+ * is closest to the connecting player, and start our search from that
+ * point. This scales up nicely - as a mushes get larger, the linked
+ * list gets larger, but the more people connected at once, the faster
+ * the search for a newly connecting player's first mail.
+ * \param player player whose db# we want to get near.
+ * \return pointer to first mail of connected player with db# closest to
+ * player.
+ */
+MAIL *
+desc_mail(dbref player)
+{
+  DESC *d;
+  int i;
+  int diff = db_top;
+  static MAIL *mp;
+  mp = NULL;
+  DESC_ITER_CONN(d) {
+    i = abs(d->player - player);
+    if (i == 0)
+      return d->mailp;
+    if ((i < diff) && d->mailp) {
+      diff = i;
+      mp = d->mailp;
+    }
+  }
+  return mp;
+}
+
+/** Set a player's mail position on all their descriptors.
+ * \param player player to set mail position for.
+ * \param mp pointer to first mail in their list.
+ */
+void
+desc_mail_set(dbref player, MAIL *mp)
+{
+  DESC *d;
+  DESC_ITER_CONN(d) {
+    if (d->player == player)
+      d->mailp = mp;
+  }
+}
+
+/** Clear mail positions on all descriptors. Called from do_mail_nuke().
+ */
+void
+desc_mail_clear(void)
+{
+  DESC *d;
+  DESC_ITER_CONN(d) {
+    d->mailp = NULL;
+  }
+}
+
+
+
+
+#ifdef SUN_OS
+/* SunOS's implementation of stdio breaks when you get a file descriptor
+ * greater than 128. Brain damage, brain damage, brain damage!
+ *
+ * Our objective, therefore, is not to fix stdio, but to work around it,
+ * so that performance degrades semi-gracefully when you are using a lot
+ * of file descriptors.
+ * Therefore, we'll save a file descriptor when we start up that is less
+ * than 128, so that if we get a file descriptor that is >= 128, we can
+ * use our own saved file descriptor instead. This is only one level of
+ * defense; if you have more than 128 fd's in use, and you try two fopen's
+ * before doing an fclose(), the second will fail.
+ */
+
+FILE *
+fopen(file, mode)
+    const char *file;
+    const char *mode;
+{
+/* FILE *f; */
+  int fd, rw, oflags = 0;
+/* char tbchar; */
+  rw = (mode[1] == '+') || (mode[1] && (mode[2] == '+'));
+  switch (*mode) {
+  case 'a':
+    oflags = O_CREAT | (rw ? O_RDWR : O_WRONLY);
+    break;
+  case 'r':
+    oflags = rw ? O_RDWR : O_RDONLY;
+    break;
+  case 'w':
+    oflags = O_TRUNC | O_CREAT | (rw ? O_RDWR : O_WRONLY);
+    break;
+  default:
+    return (NULL);
+  }
+/* SunOS fopen() doesn't use the 't' or 'b' flags. */
+
+
+  fd = open(file, oflags, 0666);
+  if (fd < 0)
+    return NULL;
+  /* My addition, to cope with SunOS brain damage! */
+  if (fd >= 128) {
+    close(fd);
+    if ((extrafd < 128) && (extrafd >= 0)) {
+      close(extrafd);
+      fd = open(file, oflags, 0666);
+      extrafd = -1;
+    } else {
+      return NULL;
+    }
+  }
+  /* End addition. */
+
+  return fdopen(fd, mode);
+}
+
+
+#undef fclose(x)
+int
+f_close(stream)
+    FILE *stream;
+{
+  int fd = fileno(stream);
+  /* if extrafd is bad, and the fd we're closing is good, recycle the
+   * fd into extrafd.
+   */
+  fclose(stream);
+  if (((extrafd < 0)) && (fd >= 0) && (fd < 128)) {
+    extrafd = open("/dev/null", O_RDWR);
+    if (extrafd >= 128) {
+      /* To our surprise, we didn't get a usable fd. */
+      close(extrafd);
+      extrafd = -1;
+    }
+  }
+  return 0;
+}
+
+#define fclose(x) f_close(x)
+#endif                         /* SUN_OS */
+
+/** Dump the descriptor list to our REBOOTFILE so we can restore it on reboot.
+ */
+void
+dump_reboot_db(void)
+{
+  FILE *f;
+  DESC *d;
+  SU_PATH *exit_path;
+  long flags = RDBF_SCREENSIZE | RDBF_TTYPE | RDBF_PUEBLO_CHECKSUM
+               | RDBF_SU_EXIT_PATH;
+  if (setjmp(db_err)) {
+    flag_broadcast(0, 0, T("GAME: Error writing reboot database!"));
+    exit(0);
+  } else {
+
+    f = fopen(REBOOTFILE, "w");
+    /* This shouldn't happen */
+    if (!f) {
+      flag_broadcast(0, 0, T("GAME: Error writing reboot database!"));
+      exit(0);
+    }
+    /* Write out the reboot db flags here */
+    fprintf(f, "V%ld\n", flags);
+    putref(f, 0);
+    putref(f, 0);
+    /* First, iterate through all descriptors to get to the end
+     * we do this so the descriptor_list isn't reversed on reboot
+     */
+    for (d = descriptor_list; d && d->next; d = d->next) ;
+    /* Second, we iterate backwards from the end of descriptor_list
+     * which is now in the d variable.
+     */
+    for (; d != NULL; d = d->prev) {
+      putref(f, d->descriptor);
+      putref(f, d->connected_at);
+      putref(f, d->hide);
+      putref(f, d->cmds);
+      putref(f, d->idle_total);
+      putref(f, d->unidle_times);
+      if (GoodObject(d->player))
+       putref(f, d->player);
+      else
+       putref(f, -1);
+      putref(f, d->last_time);
+      if (d->output_prefix)
+       putstring(f, (char *) d->output_prefix);
+      else
+       putstring(f, "__NONE__");
+      if (d->output_suffix)
+       putstring(f, (char *) d->output_suffix);
+      else
+       putstring(f, "__NONE__");
+      putstring(f, d->addr);
+      putstring(f, d->ip);
+      putstring(f, d->doing);
+      putref(f, d->conn_flags);
+      putref(f, d->width);
+      putref(f, d->height);
+      putstring(f, d->ttype);
+      putstring(f, d->checksum);
+      for(exit_path = d->su_exit_path; exit_path; exit_path = exit_path->next)
+        putref(f, exit_path->player);
+      putref(f, NOTHING);
+    }                          /* for loop */
+
+    putref(f, 0);
+    putstring(f, poll_msg);
+    putref(f, first_start_time);
+    putref(f, reboot_count);
+    fclose(f);
+  }
+}
+
+/** Load the descriptor list back from the REBOOTFILE on reboot.
+ */
+void
+load_reboot_db(void)
+{
+  FILE *f;
+  DESC *d = NULL;
+  DESC *closed = NULL, *nextclosed;
+  int val;
+  int n;
+  const char *temp;
+  char c;
+  long flags = 0;
+  char tbuf1[BUFFER_LEN];
+  SU_PATH *epnext, *epprev;
+  dbref exit_path;
+  f = fopen(REBOOTFILE, "r");
+  if (!f) {
+    restarting = 0;
+    return;
+  }
+  restarting = 1;
+  /* Get the first line and see if it's a set of reboot db flags.
+   * Those start with V<number>
+   * If not, assume we're using the original format, in which the
+   * sock appears first
+   * */
+  c = getc(f);                 /* Skip the V */
+  if (c == 'V') {
+    flags = getref(f);
+  } else {
+    ungetc(c, f);
+  }
+
+  val = getref(f);
+  val = getref(f);
+
+  while ((val = getref(f)) != 0) {
+    ndescriptors++;
+    d = (DESC *) mush_malloc(sizeof(DESC), "descriptor");
+    d->descriptor = val;
+    d->input_handler = do_command;
+    d->connected_at = getref(f);
+    d->hide = getref(f);
+    d->cmds = getref(f);
+    d->idle_total = getref(f);
+    d->unidle_times = getref(f);
+    d->player = getref(f);
+    d->last_time = getref(f);
+    d->pinfo.object = NOTHING;
+    d->pinfo.atr = NULL;
+    d->pinfo.lock = 0;
+    d->pinfo.function = NULL;
+    d->connected = GoodObject(d->player) ? 1 : 0;
+    /* setup snooper array */
+    for(n = 0; n < MAX_SNOOPS; n++)
+      d->snooper[n] = -1;
+
+    temp = getstring_noalloc(f);
+    d->output_prefix = NULL;
+    if (strcmp(temp, "__NONE__"))
+      set_userstring(&d->output_prefix, temp);
+    temp = getstring_noalloc(f);
+    d->output_suffix = NULL;
+    if (strcmp(temp, "__NONE__"))
+      set_userstring(&d->output_suffix, temp);
+    strcpy(d->addr, getstring_noalloc(f));
+    strcpy(d->ip, getstring_noalloc(f));
+    strcpy(d->doing, getstring_noalloc(f));
+    d->conn_flags = getref(f);
+    if (flags & RDBF_SCREENSIZE) {
+      d->width = getref(f);
+      d->height = getref(f);
+    } else {
+      d->width = 78;
+      d->width = 24;
+    }
+    if (flags & RDBF_TTYPE)
+      d->ttype = mush_strdup(getstring_noalloc(f), "terminal description");
+    else
+      d->ttype = mush_strdup("unknown", "terminal description");
+    if (flags & RDBF_PUEBLO_CHECKSUM)
+      strcpy(d->checksum, getstring_noalloc(f));
+    else
+      d->checksum[0] = '\0';
+    d->su_exit_path = NULL;
+    if (flags & RDBF_SU_EXIT_PATH) {
+      exit_path = getref(f);
+      while(GoodObject(exit_path)) {
+        add_to_exit_path(d, exit_path);
+        exit_path = getref(f);
+      }
+      epprev = NULL;
+      while(d->su_exit_path) {
+        epnext = d->su_exit_path->next;
+        d->su_exit_path->next = epprev;
+        epprev = d->su_exit_path;
+        d->su_exit_path = epnext;
+      }
+      d->su_exit_path = epprev;
+    }
+    d->input_chars = 0;
+    d->output_chars = 0;
+    d->output_size = 0;
+    d->output.head = 0;
+    d->output.tail = &d->output.head;
+    d->input.head = 0;
+    d->input.tail = &d->input.head;
+    d->raw_input = NULL;
+    d->raw_input_at = NULL;
+    d->quota = options.starting_quota;
+    d->mailp = NULL;
+    if (d->conn_flags & CONN_CLOSE_READY) {
+      /* This isn't really an open descriptor, we're just tracking
+       * it so we can announce the disconnect properly. Do so, but
+       * don't link it into the descriptor list. Instead, keep a
+       * separate list.
+       */
+      if (closed)
+       closed->prev = d;
+      d->next = closed;
+      d->prev = NULL;
+      closed = d;
+    } else {
+      if (descriptor_list)
+       descriptor_list->prev = d;
+      d->next = descriptor_list;
+      d->prev = NULL;
+      descriptor_list = d;
+      if (d->connected && d->player && GoodObject(d->player) &&
+         IsPlayer(d->player))
+       set_flag_internal(d->player, "CONNECTED");
+      else if ((!d->player || !GoodObject(d->player)) && d->connected) {
+       d->connected = 0;
+       d->player = 0;
+      }
+    /* If they were in a program, get them back into it */
+      if(d->connected && InProg(d->player))
+        prog_load_desc(d);
+    }
+  }                            /* while loop */
+
+  /* Now announce disconnects of everyone who's not really here */
+  while (closed) {
+    nextclosed = closed->next;
+    if(closed->last_time > 0) {
+      closed->idle_total += difftime(mudtime, closed->last_time);
+      closed->unidle_times++;
+    }
+
+    snprintf(tbuf1, BUFFER_LEN-1, "%ld %ld %d %d", (mudtime - closed->connected_at),
+        closed->idle_total , closed->unidle_times, closed->cmds);
+    tbuf1[strlen(tbuf1)+1] = '\0';
+    (void) atr_add(closed->player, "LASTACTIVITY", tbuf1, GOD, NOTHING);
+    announce_disconnect(closed->player);
+    mush_free(closed, "descriptor");
+    closed = nextclosed;
+  }
+
+  strcpy(poll_msg, getstring_noalloc(f));
+  first_start_time = getref(f);
+  reboot_count = getref(f) + 1;
+  DESC_ITER_CONN(d) {
+    d->mailp = find_exact_starting_point(d->player);
+  }
+
+  fclose(f);
+  remove(REBOOTFILE);
+  flag_broadcast(0, 0, T("GAME: Reboot finished."));
+}
+
+
+#ifdef NT_TCP
+
+/* --------------------------------------------------------------------------- */
+/* Thread to listen on MUD port - for Windows NT */
+/* --------------------------------------------------------------------------- */
+void __cdecl
+MUDListenThread(void *pVoid)
+{
+  SOCKET MUDListenSocket = (SOCKET) pVoid;
+  SOCKET socketClient;
+  union sockaddr_u addr;
+  int nLen;
+  socklen_t addr_len;
+  struct hostname_info *hi;
+  char *socket_ident;
+  char *chp;
+  BOOL b;
+  char tbuf1[BUFFER_LEN];
+  char tbuf2[BUFFER_LEN];
+  char *bp;
+  DESC *d;
+  printf(T("Starting MUD listening thread ...\n"));
+  /* Loop forever accepting connections */
+  while (TRUE) {
+
+    /* Block on accept() */
+    nLen = sizeof(SOCKADDR_IN);
+    socketClient = accept(MUDListenSocket, (LPSOCKADDR) & addr, &nLen);
+    if (socketClient == INVALID_SOCKET) {
+      /* parent thread closes the listening socket */
+      /* when it wants this thread to stop. */
+      break;
+    }
+    /* We have a connection */
+    /*  */
+
+    bp = tbuf2;
+    addr_len = sizeof(addr);
+    hi = ip_convert(&addr.addr, addr_len);
+    safe_str(hi ? hi->hostname : "", tbuf2, &bp);
+    *bp = '\0';
+    bp = tbuf1;
+    if (USE_IDENT) {
+      int timeout = IDENT_TIMEOUT;
+      socket_ident = ident_id(socketClient, &timeout);
+      if (socket_ident) {
+       /* Truncate at first non-printable character */
+       for (chp = socket_ident; *chp && isprint((unsigned char) *chp); chp++) ;
+       *chp = '\0';
+       safe_str(socket_ident, tbuf1, &bp);
+       safe_chr('@', tbuf1, &bp);
+       free(socket_ident);
+      }
+    }
+
+    hi = hostname_convert(&addr.addr, addr_len);
+    safe_str(hi ? hi->hostname : "", tbuf1, &bp);
+    *bp = '\0';
+    if (Forbidden_Site(tbuf1) || Forbidden_Site(tbuf2)) {
+      if (!Deny_Silent_Site(tbuf1, AMBIGUOUS)
+         || !Deny_Silent_Site(tbuf2, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0, T("[%d/%s] Refused connection (remote port %s)"),
+              socketClient, tbuf1, hi->port);
+      }
+      shutdown(socketClient, 2);
+      closesocket(socketClient);
+      continue;
+    }
+    do_log(LT_CONN, 0, 0, T("[%d/%s] Connection opened."), socketClient, tbuf1);
+    d = initializesock(socketClient, tbuf1, tbuf2, 0);
+    printf(T("[%d/%s] Connection opened.\n"), socketClient, tbuf1);
+/* add this socket to the IO completion port */
+    CompletionPort = CreateIoCompletionPort((HANDLE) socketClient,
+                                           CompletionPort, (DWORD) d, 1);
+    if (!CompletionPort) {
+      char sMessage[200];
+      GetErrorMessage(GetLastError(), sMessage, sizeof sMessage);
+      printf
+       ("Error %ld (%s) on CreateIoCompletionPort for socket %ld\n",
+        GetLastError(), sMessage, socketClient);
+      shutdownsock(d);
+      continue;
+    }
+/* welcome the user - we can't do this until the completion port is created
+*/
+
+    test_telnet(d);
+    welcome_user(d);
+/* do the first read */
+    b = ReadFile((HANDLE) socketClient,
+                d->input_buffer,
+                sizeof(d->input_buffer) - 1, NULL, &d->InboundOverlapped);
+    if (!b && GetLastError() != ERROR_IO_PENDING) {
+      shutdownsock(d);
+      continue;
+    }
+  }                            /* end of while loop */
+
+  printf(T("End of MUD listening thread ...\n"));
+}                              /* end of MUDListenThread */
+
+
+/*
+   This is called from within shovechars when it needs to see if any IOs have
+   completed for the Windows NT version.
+
+   The 4 sorts of IO completions are:
+
+   1. Outstanding read completing (there should always be an outstanding read)
+   2. Outstanding write completing
+   3. A special "shutdown" message to tell us to shutdown the socket
+   4. A special "aborted" message to tell us the socket has shut down, and we
+   can now free the descriptor.
+
+   The latter 2 are posted by the application by PostQueuedCompletionStatus
+   when it is necessary to signal these "events".
+
+   The reason for posting the special messages is to shut down sockets in an
+   orderly way.
+
+ */
+void
+ProcessWindowsTCP(void)
+{
+  LPOVERLAPPED lpo;
+  DWORD nbytes;
+  BOOL b;
+  DWORD nError;
+  DESC *d;
+  time_t now;
+  /* pull out the next completed IO, waiting 50 ms for it if necessary */
+  b = GetQueuedCompletionStatus(CompletionPort, &nbytes, (LPDWORD) & d, &lpo, 50);     /* twentieth-second timeout */
+  nError = GetLastError();
+  /* ignore timeouts and cancelled IOs */
+  if (!b && (nError == WAIT_TIMEOUT || nError == ERROR_OPERATION_ABORTED)) {
+    /* process queued commands */
+    do_top(options.queue_chunk);
+    return;
+  }
+
+  /* shutdown this descriptor if wanted */
+  if (lpo == &lpo_shutdown) {
+    shutdownsock(d);           /* shut him down */
+    return;
+  }
+  /* *now* we can free the descriptor (posted by shutdownsock) */
+  if (lpo == &lpo_aborted) {
+    freeqs(d);
+    mush_free((Malloc_t) d, "descriptor");
+    return;
+  }
+  /* if address of descriptor is bad - don't try using it */
+
+  if (!IsValidAddress(d, sizeof(DESC), TRUE)) {
+    printf("Invalid descriptor %08lX on GetQueuedCompletionStatus\n", d);
+    return;
+  }
+  /* bad IO - shut down this client */
+
+  if (!b) {
+    shutdownsock(d);
+    return;
+  }
+  /* see if read completed */
+
+  if (lpo == &d->InboundOverlapped && !d->bConnectionDropped) {
+
+    /* zero length IO completion means connection dropped by client */
+
+    if (nbytes == 0) {
+      shutdownsock(d);
+      return;
+    }
+    now = mudtime;
+    if((d->last_time - now) >= 10) {
+      queue_attribute(d->player, "AUNIDLE", d->player);
+    } else notify_format(d->player, "WHAT?!?! debug: %d", (d->last_time - now) );
+
+    if(difftime(mudtime, d->last_time) >= 300) {
+      d->idle_total += difftime(mudtime, d->last_time);
+      d->unidle_times++;
+    }
+    d->last_time = now;
+
+    /* process the player's input */
+    process_input_helper(d, d->input_buffer, nbytes);
+    /* now fire off another read */
+    b = ReadFile((HANDLE) d->descriptor,
+                d->input_buffer,
+                sizeof(d->input_buffer) - 1, NULL, &d->InboundOverlapped);
+    if (!b && GetLastError() != ERROR_IO_PENDING) {
+      d->bConnectionDropped = TRUE;    /* do no more reads */
+      /* post a notification that the descriptor should be shutdown */
+      if (!PostQueuedCompletionStatus
+         (CompletionPort, 0, (DWORD) d, &lpo_shutdown)) {
+       char sMessage[200];
+       DWORD nError = GetLastError();
+       GetErrorMessage(nError, sMessage, sizeof sMessage);
+       printf
+         ("Error %ld (%s) on PostQueuedCompletionStatus in ProcessWindowsTCP (read)\n",
+          nError, sMessage);
+       shutdownsock(d);
+      }
+      return;
+    }
+  }
+  /* end of read completing */
+  /* see if write completed */
+  else if (lpo == &d->OutboundOverlapped && !d->bConnectionDropped) {
+    struct text_block **qp, *cur;
+    DWORD nBytes;
+    qp = &d->output.head;
+    if ((cur = *qp) == NULL)
+      d->bWritePending = FALSE;
+    else {                     /* here if there is more to write */
+
+      /* if buffer too long, write what we can and queue up the rest */
+
+      if (cur->nchars <= sizeof(d->output_buffer)) {
+       nBytes = cur->nchars;
+       memcpy(d->output_buffer, cur->start, nBytes);
+       if (!cur->nxt)
+         d->output.tail = qp;
+       *qp = cur->nxt;
+       free_text_block(cur);
+      }
+      /* end of able to write the lot */
+      else {
+       nBytes = sizeof(d->output_buffer);
+       memcpy(d->output_buffer, cur->start, nBytes);
+       cur->nchars -= nBytes;
+       cur->start += nBytes;
+      }                                /* end of buffer too long */
+
+      d->OutboundOverlapped.Offset = 0;
+      d->OutboundOverlapped.OffsetHigh = 0;
+      b = WriteFile((HANDLE) d->descriptor,
+                   d->output_buffer, nBytes, NULL, &d->OutboundOverlapped);
+      d->bWritePending = FALSE;
+      if (!b)
+       if (GetLastError() == ERROR_IO_PENDING)
+         d->bWritePending = TRUE;
+       else {
+         d->bConnectionDropped = TRUE; /* do no more reads */
+         /* post a notification that the descriptor should be shutdown */
+         if (!PostQueuedCompletionStatus
+             (CompletionPort, 0, (DWORD) d, &lpo_shutdown)) {
+           char sMessage[200];
+           DWORD nError = GetLastError();
+           GetErrorMessage(nError, sMessage, sizeof sMessage);
+           printf
+             ("Error %ld (%s) on PostQueuedCompletionStatus in ProcessWindowsTCP (write)\n",
+              nError, sMessage);
+           shutdownsock(d);
+         }
+         return;
+       }
+    }                          /* end of more to write */
+
+  }
+
+  /* end of write completing */
+  /* process queued commands */
+  do_top(options.active_q_chunk);
+}
+
+#endif
+
+/* Syntax: @snoop[/list] [!]<descriptor>
+ */  
+COMMAND(cmd_snoop) {
+  DESC *d;
+  int descn, on, n, sn;
+  char snooplist[MAX_SNOOPS][BUFFER_LEN];
+  char buf[BUFFER_LEN], *bp;
+
+  if(SW_ISSET(sw, SWITCH_LIST)) {
+         descn = atoi(arg_left);
+
+         bp = buf;
+
+         d = port_desc(descn);
+
+          if (LEVEL(player) <= 28) {
+                 notify(player, MSG_HUH);
+                 return;
+         }
+         /* make sure teh desc exists and they're connected (no spying on 'em at the connect screen!) */
+         if(!d || (d && !IsPlayer(d->player))) {
+                 notify(player, "There is no one connected on that descriptor.");
+                 return;
+         }
+
+         for(sn = 0, n = 0; n < MAX_SNOOPS; n++)
+                 if(d->snooper[n] != -1) {
+                         memset(snooplist[sn], '\0', BUFFER_LEN);
+                         snprintf(snooplist[sn++], BUFFER_LEN-1, "%s", Name(d->snooper[n])); 
+                 }
+         *snooplist[sn] = '\0';
+
+         for(n = 0; n < sn; n++) {
+                 if(n != 0)
+                         safe_str(", ", buf, &bp);
+                 if(n == (sn-1) && sn > 1)
+                         safe_str("& ", buf, &bp);
+                 safe_str(snooplist[n], buf, &bp);
+         }
+         *bp = '\0';
+         notify_format(player, "%s is being snooped on by: %s", Name(d->player), buf);
+
+  } else {
+  if(*arg_left== '!') {
+    on = 0;
+    descn = atoi(arg_left+1);
+  } else { 
+    descn = atoi(arg_left);
+    on = 1;
+  }
+
+  d = port_desc(descn);
+  if (LEVEL(player) <= 28) {
+    notify(player, MSG_HUH);
+    return;
+  }
+  /* make sure teh desc exists and they're connected (no spying on 'em at the connect screen!) */
+  if(!d || (d && !IsPlayer(d->player))) {
+    notify(player, "There is no one connected on that descriptor.");
+    return;
+  }
+  
+  if(on) {
+         if((d->player == player)) {
+                 notify(player, "You can't snoop yourself."); 
+                 return;
+         }
+
+  switch(set_snoop(player,d)) {
+    case -1: /* Too Many */
+      notify(player, "Sorry, can't snoop at this time.");
+      return;
+    case -2: /* Already Snoopin on 'em */
+      notify(player, "You can only snoop one person at a time.");
+      return;
+    default:
+      notify_format(player, T("Snoop now set on %s(%d)"), Name(d->player), descn); 
+  }
+  } else {
+    for(on = n = 0; n < MAX_SNOOPS; n++)
+      if(d->snooper[n] == player)  {
+       d->snooper[n] = -1;
+       on = 1;
+      }
+      notify(player, on ? "Snoop deactivated." : "Snooping Error.");
+  }
+  }
+}
+
+
+void feed_snoop(DESC *d, const char *s, char dir) {
+  int n;
+  char snstr[BUFFER_LEN];
+
+
+  if(!d ||!d->connected)
+    return;
+  memset(snstr, '\0', BUFFER_LEN);
+  strncpy(snstr, remove_markup(s, NULL), BUFFER_LEN-1);
+  if(*s && !*snstr)
+         return;
+  for(n = 0; n < MAX_SNOOPS ; n++)
+    if(!IsPlayer(d->snooper[n]))
+      continue;
+    else if(GoodObject(d->snooper[n]) && IsPlayer(d->snooper[n])) {
+      if(dir == 1) /* player see's */
+        notify_format((dbref) d->snooper[n], T("%s%s<-%s %s"), object_header(d->snooper[n],d->player), ANSI_BLUE,
+           ANSI_NORMAL, snstr);
+      else /* player types */
+       notify_format((dbref) d->snooper[n], T("%s%s->%s %s%s"), object_header(d->snooper[n],d->player), 
+           ANSI_BLUE, ANSI_RED, snstr, ANSI_NORMAL);
+    }
+
+}
+
+char is_snooped(DESC *d) {
+  int n;
+
+  for(n = 0; n < MAX_SNOOPS; n++) 
+    if(IsPlayer(d->snooper[n]))
+      return 1;
+  return 0;
+}
+
+
+char set_snoop(dbref plyr, DESC *d) {
+  int n;
+  /* take first available spot */
+  for( n = 0; n < MAX_SNOOPS ; n++)
+    if(d->snooper[n] == -1)
+      break;
+    else if(d->snooper[n] == plyr)
+      return -2; /* they're already snooping */
+  if(n == MAX_SNOOPS-1) /* too many snoopers on player */
+    return -1;
+  d->snooper[n] = plyr;
+  return 1;
+}
+
+void clr_snoop(dbref plyr, DESC *d) {
+  int n;
+
+  for(n = 0; n < MAX_SNOOPS; n++)
+    if(d->snooper[n] == plyr)
+      d->snooper[n] = -1;
+}
+
+/* switch users */
+COMMAND(cmd_su) {
+  DESC *d, *match = NULL;
+  dbref target;
+  int num = 0, is_hidden;
+
+  /* Stage 1.  Make sure arg_left exists */
+  if(arg_left && *arg_left) {
+    if(!strcasecmp(cmd->name, "@SD")) { 
+      target = match_result(player, arg_left, TYPE_DIVISION, MAT_EVERYTHING);
+      if(!GoodObject(target) || Typeof(target) != TYPE_DIVISION) {
+       notify(player, "No such division.");
+       return;
+      }
+      /* Check to see if special conditions exist, where they can enter without a password */
+      if(controls(player, target)   /* condition 1: they control it */
+       ) {
+       add_to_div_exit_path(player, Division(player));
+       Division(player) = target;
+       notify_format(player, "You have switched into Division: %s", object_header(player, Division(player)));
+       return;
+      }
+      /* get least idle desc */
+      DESC_ITER_CONN(d) 
+       if ((d->player == player) && (!match || (d->last_time > match->last_time))) 
+         match = d;
+      /* We're only entering using a password at this moment */
+       queue_newwrite(match, (unsigned char *) tprintf(T("Password: %c%c"),
+                                                       IAC, GOAHEAD), 13);
+       match->input_handler = password_handler;
+       match->pinfo.object = target;
+       match->pinfo.function = &pw_div_connect;
+       match->pinfo.lock = 0x40;
+    } else {
+      target = lookup_player(arg_left);
+      if(target == NOTHING) {
+       notify(player, "No such player.");
+       return;
+      }
+      do_log(LT_WIZ, player, target, "** @SU ATTEMPT **");
+      /* get least idle desc */
+      DESC_ITER_CONN(d) 
+       if ((d->player == player) && (!match || (d->last_time > match->last_time))) 
+         match = d;
+  /* Step 2.  Find if we can get in without a pass, if
+   * we need a pass. Throw them into password_handler() internal
+   * prompt
+   */
+      if(div_powover(player, target, "@SU")) {
+       do_log(LT_WIZ, player, target, "** @SU SUCCESS **");
+       /* Phase 3a. Put Guy in user */
+       add_to_exit_path(match, player);
+       announce_disconnect(player);
+       match->player = target;
+       match->mailp = find_exact_starting_point(target);
+       is_hidden = Can_Hide(target) && Dark(target);
+       DESC_ITER_CONN(d)
+         if(d->player == player) {
+           num++;
+           if(is_hidden)
+             d->hide = 1;
+         }
+
+       if(ModTime(target))
+         notify_format(target, T("%ld failed connections since last login."), ModTime(target));
+       announce_connect(target, 0, num);
+       check_last(target, match->addr, match->ip); /* set last, lastsite, give paycheck */
+       queue_eol(match);
+       if(command_check_byname(target, "@MAIL"))
+         check_mail(target, 0, 0);
+       set_player_folder(target, 0);
+       do_look_around(target);
+       if(Haven(target))
+         notify(player, T("Your HAVEN flag is set.  You cannot receive pages."));
+      } else {
+       /* Part 3b.  Put guy in password program */
+       queue_newwrite(match, (unsigned char *) tprintf(T("Password: %c%c"),
+                                                       IAC, GOAHEAD), 13);
+       match->input_handler = password_handler;
+       match->pinfo.object = target;
+       match->pinfo.function = &pw_player_connect;
+       match->pinfo.lock = 0x40;
+      }
+    }
+  } else {
+    if(SW_ISSET(sw, SWITCH_LOGOUT)) {
+      /* @sd/logout - check to see if there is any division @sd'ing history.. And backtrack us */
+      ATTR *divrcd;
+      char tbuf[BUFFER_LEN], *p_buf[BUFFER_LEN / 2], *tbp, sep[2];
+      int cnt;
+      dbref div_obj;
+
+      divrcd = atr_get(player, "XYXX_DIVRCD");
+      if(divrcd == NULL) {
+       notify(player, "You have not switched into any divisions.");
+       return;
+      }
+
+      cnt = list2arr(p_buf, BUFFER_LEN / 2, safe_atr_value(divrcd), ' ');
+      if(cnt < 1) {
+       notify(player, "You have not switched into any divisions.");
+       return;
+      }
+
+      /* Set them into cnt-1 if its good */
+      div_obj = parse_number(p_buf[cnt-1]);
+      if(GoodObject(div_obj) && IsDivision(div_obj)) {
+       Division(player) = div_obj;
+       notify_format(player, "You haev been went back to your other division: %s", object_header(player, div_obj));
+      }
+
+      /* now  chop off last one, and arr2list() */
+      if(cnt == 1) { /* clear the attribute */
+       atr_clr(player, "XYXX_DIVRCD", GOD);
+      } else {
+       memset(tbuf, '\0', BUFFER_LEN);
+       tbp = tbuf;
+       sep[0] = ' ';
+       sep[1] = '\0';
+       arr2list(p_buf, cnt-1, tbuf, &tbp, sep);
+       /* Add the attribute back */
+       (void) atr_add(player, "XYXX_DIVRCD", tbuf, GOD, NOTHING);
+      }
+    } else {
+      notify(player, "Must specify what player you wish to @su into.");
+    }
+  }
+}
+
+void add_to_exit_path(DESC *d, dbref player) {
+  SU_PATH *path_entry;
+
+  if(!d)
+    return;
+
+  path_entry = (SU_PATH *) mush_malloc(sizeof(SU_PATH), "SU_PATH_ENTRY");
+
+  path_entry->player = player;
+  if(d->su_exit_path)
+    path_entry->next = d->su_exit_path;
+  else
+    path_entry->next = NULL;
+  d->su_exit_path = path_entry;
+}
+
+/* If they're logged in.. Log 'em out through their su path */
+static int do_su_exit(DESC *d) {
+  DESC *c;
+  SU_PATH *path_entry, *mark_path;
+  int is_hidden, num = 0;
+
+  if(d->su_exit_path) {
+    path_entry = d->su_exit_path;
+    while(path_entry)
+      if(GoodObject(path_entry->player) && IsPlayer(path_entry->player))
+       break;
+      else { /* Guess the guy got nuked along the way..  free the spot */
+       mark_path = path_entry;
+       path_entry = path_entry->next;
+       mush_free(mark_path, "SU_PATH_ENTRY");
+      }
+    if(!path_entry)
+      return 0;
+    d->su_exit_path = path_entry;
+    /* Disconnect appearance */
+    announce_disconnect(d->player);
+    d->player = path_entry->player;
+    /* Clear path_entry spot */
+    d->su_exit_path = path_entry->next;
+    mush_free(path_entry, "SU_PATH_ENTRY");
+    d->mailp = find_exact_starting_point(d->player);
+    is_hidden = Can_Hide(d->player) && Dark(d->player);
+    DESC_ITER_CONN(c)
+      if(c->player == d->player) {
+       num++;
+       if(is_hidden)
+         c->hide = 1;
+      }
+    if(ModTime(d->player))
+      notify_format(d->player, T("%ld failed connections since last login."), ModTime(d->player));
+    announce_connect(d->player, 0, num);
+    check_last(d->player, d->addr, d->ip); /* set last, lastsite, give paycheck */
+    queue_eol(d);
+    if(command_check_byname(d->player, "@MAIL"))
+      check_mail(d->player, 0, 0);
+    set_player_folder(d->player, 0);
+    do_look_around(d->player);
+    if(Haven(d->player))
+      notify(d->player, T("Your HAVEN flag is set.  You cannot receive pages."));
+    return 1;
+  } else return 0;
+}
+
+void
+close_ssl_connections(void)
+{
+}
+
+void
+kill_info_slave(void)
+{
+}
+
diff --git a/src/cque.c b/src/cque.c
new file mode 100644 (file)
index 0000000..c1668a2
--- /dev/null
@@ -0,0 +1,1616 @@
+/**
+ * \file cque.c
+ *
+ * \brief Queue for PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <string.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+
+#include "conf.h"
+#include "boolexp.h"
+#include "command.h"
+#include "mushdb.h"
+#include "match.h"
+#include "externs.h"
+#include "parse.h"
+#include "strtree.h"
+#include "mymalloc.h"
+#include "game.h"
+#include "attrib.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "log.h"
+#include "confmagic.h"
+
+
+#define MAX_QID        16384
+
+extern dbref global_parent_depth[1];
+EVAL_CONTEXT global_eval_context;
+
+enum qid_flags qid_table[MAX_QID];     /**< List of flagged qids. */
+int  qid_cnt;                  /**< What QID we're at */
+
+/** A queue entry.
+ * This structure reprsents a queue entry on a linked list of queue
+ * entries (a queue). It is used for all of the queues.
+ */
+typedef struct bque {
+  struct bque *next;                   /**< pointer to next entry on queue */
+  dbref player;                        /**< player who will do command */
+  dbref queued;                        /**< object whose QUEUE gets incremented for this command */
+  dbref cause;                 /**< player causing command (for %N) */
+  dbref realcause;             /** most of the time same as cause.. except for divisions. */
+  dbref ooref;                 /**< Used when doing twin checks */
+  dbref sem;                   /**< semaphore object to block on */
+  char *semattr;               /**< semaphore attribute to block on */
+  int left;                    /**< seconds left until execution */
+  char *env[10];               /**< environment, from wild match */
+  char *rval[NUMQ];            /**< environment, from setq() */
+  char *comm;                  /**< command to be executed */
+#ifdef _SWMP_
+  int sql_env[2];              /**< sql environment 0- Query ID, 1-Auth ID */
+#endif
+  char fqueued;                        /**< function inserted into queue  */
+  enum qid_flags qid; /**<  queue identification # */
+} BQUE;
+
+static BQUE *qfirst = NULL, *qlast = NULL, *qwait = NULL;
+static BQUE *qlfirst = NULL, *qllast = NULL;
+static BQUE *qsemfirst = NULL, *qsemlast = NULL;
+
+static int add_to_generic(dbref player, int am, const char *name, int flags);
+static int add_to(dbref player, int am);
+static int add_to_sem(dbref player, int am, const char *name);
+static int queue_limit(dbref player);
+void free_qentry(BQUE *point);
+static int pay_queue(dbref player, const char *command);
+int wait_que(dbref player, int wait, char *command,
+             dbref cause, dbref sem, const char *semattr, int until, char finvoc);
+void init_qids();
+int  create_qid();
+int que_next(void);
+
+static void show_queue(dbref player, dbref victim, int q_type,
+                      int q_quiet, int q_all, BQUE *q_ptr, int *tot, int *self,
+                      int *del);
+static void check_qsigs(BQUE **qchk);
+static void do_raw_restart(dbref victim);
+static int waitable_attr(dbref thing, const char *atr);
+static void shutdown_a_queue(BQUE **head, BQUE **tail);
+
+extern sig_atomic_t cpu_time_limit_hit;        /**< Have we used too much CPU? */
+
+/** Attribute flags to be set or checked on attributes to be used
+ * as semaphores.
+ */
+#define SEMAPHORE_FLAGS (AF_LOCKED | AF_PRIVATE | AF_NOCOPY | AF_NODUMP)
+
+
+void init_qids() {
+       int i;
+
+       /* set whole thing to NULL */
+       for(i = 0; i < MAX_QID; i++)
+               qid_table[i] = QID_FALSE;
+       qid_table[MAX_QID] = '\0';
+       qid_cnt = 0;
+}
+
+int create_qid() { /* find an unused QID */
+       int i;
+
+       for(i = 0; i < MAX_QID; i++)
+               if(qid_table[i] == QID_FALSE)
+                       break;
+       /* No Good QID */
+       if(qid_table[i] != QID_FALSE || i == MAX_QID)
+               return -1;
+       if(i > qid_cnt) /* set this so we don't go too high looking for a qid */
+               qid_cnt = i;
+       /* flag the QID entry & return it */
+       qid_table[i] = QID_ACTIVE;
+       return i;
+}
+
+/* Returns true if the attribute on thing can be used as a semaphore.
+ * atr should be given in UPPERCASE. 
+ */
+static int
+waitable_attr(dbref thing, const char *atr)
+{
+  ATTR *a;
+  if (!atr || !*atr)
+    return 0;
+  a = atr_get_noparent(thing, atr);
+  if (!a) {                    /* Attribute isn't set */
+    a = atr_match(atr);
+    if (!a)                    /* It's not a built in attribute */
+      return 1;
+    return !strcmp(AL_NAME(a), "SEMAPHORE");   /* Only allow SEMAPHORE for now */
+  } else {                     /* Attribute is set. Check for proper owner and flags and value */
+    if ((AL_CREATOR(a) == GOD) && (AL_FLAGS(a) == SEMAPHORE_FLAGS)) {
+      char *v = atr_value(a);
+      if (!*v || is_integer(v))
+       return 1;
+      else
+       return 0;
+    } else {
+      return 0;
+    }
+  }
+  return 0;                    /* Not reached */
+}
+
+static int
+add_to_generic(dbref player, int am, const char *name, int flags)
+{
+  int num = 0;
+  ATTR *a;
+  char buff[MAX_COMMAND_LEN];
+  a = atr_get_noparent(player, name);
+  if (a)
+    num = parse_integer(atr_value(a));
+  num += am;
+  if (num) {
+    sprintf(buff, "%d", num);
+    (void) atr_add(player, name, buff, GOD, flags);
+  } else {
+    (void) atr_clr(player, name, GOD);
+  }
+  return (num);
+}
+
+static int
+add_to(dbref player, int am)
+{
+  return (add_to_generic(player, am, "QUEUE", NOTHING));
+}
+
+static int
+add_to_sem(dbref player, int am, const char *name)
+{
+  return (add_to_generic
+         (player, am, name ? name : "SEMAPHORE", SEMAPHORE_FLAGS));
+}
+
+static int
+queue_limit(dbref player)
+{
+  /* returns 1 if player has exceeded his queue limit, and always
+     increments QUEUE by one. */
+  int nlimit;
+
+  nlimit = add_to(player, 1);
+  if (HugeQueue(player))
+    return nlimit > (QUEUE_QUOTA + db_top);
+  else
+    return nlimit > QUEUE_QUOTA;
+}
+
+/** Free a queue entry.
+ * \param point queue entry to free.
+ */
+void
+free_qentry(BQUE *point)
+{
+  int a;
+
+#ifdef _SWMP_
+  sqenv_clear(sql_env[0]);
+#endif
+  /* first free up the QID */
+  qid_table[point->qid] = QID_FALSE;
+  if(point->qid == qid_cnt) {
+         for(a = qid_cnt; a > -1; a--)
+                 if(qid_table[a] != QID_FALSE)
+                         break;
+         if(qid_table[a] != QID_FALSE)
+                 qid_cnt = a;
+         else  /* nothing on the queue at all.. set us down to 0 */
+                 qid_cnt = 0;
+  }
+  for (a = 0; a < 10; a++)
+    if (point->env[a]) {
+      mush_free((Malloc_t) point->env[a], "bqueue_env");
+    }
+  for (a = 0; a < NUMQ; a++)
+    if (point->rval[a]) {
+      mush_free((Malloc_t) point->rval[a], "bqueue_rval");
+    }
+  if (point->semattr)
+    mush_free((Malloc_t) point->semattr, "bqueue_semattr");
+  if (point->comm)
+    mush_free((Malloc_t) point->comm, "bqueue_comm");
+  mush_free((Malloc_t) point, "BQUE");
+}
+
+static int
+pay_queue(dbref player, const char *command)
+{
+  int estcost;
+  estcost =
+    QUEUE_COST +
+    (QUEUE_LOSS ? ((get_random_long(0, QUEUE_LOSS - 1) == 0) ? 1 : 0) : 0);
+  if (!quiet_payfor(player, estcost)) {
+    notify(Owner(player), T("Not enough money to queue command."));
+    return 0;
+  }
+  if (estcost != QUEUE_COST && Track_Money(Owner(player))) {
+    char *preserve_wnxt[10];
+    char *preserve_rnxt[NUMQ];
+    char *val_wnxt[10];
+    char *val_rnxt[NUMQ];
+    char *preserves[10];
+    char *preserveq[NUMQ];
+    save_global_nxt("pay_queue_save", preserve_wnxt, preserve_rnxt, val_wnxt,
+                   val_rnxt);
+    save_global_regs("pay_queue_save", preserveq);
+    save_global_env("pay_queue_save", preserves);
+    notify_format(Owner(player),
+                 T("GAME: Object %s(%s) lost a %s to queue loss."),
+                 Name(player), unparse_dbref(player), MONEY);
+    restore_global_regs("pay_queue_save", preserveq);
+    restore_global_env("pay_queue_save", preserves);
+    restore_global_nxt("pay_queue_save", preserve_wnxt, preserve_rnxt, val_wnxt,
+                      val_rnxt);
+  }
+  if (queue_limit(QUEUE_PER_OWNER ? Owner(player) : player)) {
+    notify_format(Owner(player),
+                 T("Runaway object: %s(%s). Commands halted."),
+                 Name(player), unparse_dbref(player));
+    do_log(LT_TRACE, player, player, T("Runaway object %s executing: %s"),
+          unparse_dbref(player), command);
+    /* Refund the queue costs */
+    giveto(player, QUEUE_COST);
+    add_to(QUEUE_PER_OWNER ? Owner(player) : player, -1);
+    /* wipe out that object's queue and set it HALT */
+    do_halt(Owner(player), "", player);
+    set_flag_internal(player, "HALT");
+    return 0;
+  }
+  return 1;
+}
+
+/** Add a new entry onto the player or object command queues.
+ * This function adds a new entry to the back of the player or
+ * object command queues (depending on whether the call was
+ * caused by a player or an object).
+ * \param player the enactor for the queued command.
+ * \param command the command to enqueue.
+ * \param cause the player or object causing the command to be queued.
+ */
+/* add Originator to this list.. to enable twin checking */
+void
+parse_que(dbref player, const char *command, dbref cause)
+{
+  int a;
+  BQUE *tmp;
+  int qid;
+  if (!IsPlayer(player) && (Halted(player)))
+    return;
+  if (!pay_queue(player, command))     /* make sure player can afford to do it */
+    return;
+  if((qid = create_qid()) == -1) /* No room for a process ID, don't do anything */
+         return;
+  tmp = (BQUE *) mush_malloc(sizeof(BQUE), "BQUE");
+  tmp->qid = qid;
+  tmp->comm = mush_strdup(command, "bqueue_comm");
+  tmp->semattr = NULL;
+  tmp->player = player;
+  tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player;
+  tmp->next = NULL;
+  tmp->left = 0;
+  tmp->realcause = tmp->cause = cause;
+  tmp->fqueued = 0;
+  tmp->ooref = options.twinchecks ? ooref : NOTHING;
+#ifdef _SWMP_
+  tmp->sql_env[0] = sql_env[0];
+  tmp->sql_env[1] = sql_env[1];
+#endif
+  for (a = 0; a < 10; a++)
+    if (!global_eval_context.wnxt[a])
+      tmp->env[a] = NULL;
+    else {
+      tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
+    }
+  for (a = 0; a < NUMQ; a++)
+    if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
+      tmp->rval[a] = NULL;
+    else {
+      tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
+    }
+
+  if (IsPlayer(cause)) {
+    if (qlast) {
+      qlast->next = tmp;
+      qlast = tmp;
+    } else
+      qlast = qfirst = tmp;
+  } else {
+    if (qllast) {
+      qllast->next = tmp;
+      qllast = tmp;
+    } else
+      qllast = qlfirst = tmp;
+  }
+}
+
+void
+div_parse_que(dbref division, const char *command, dbref called_division, dbref player)
+{
+  int a, qid;
+  BQUE *tmp;
+
+  if (!IsPlayer(division) && (Halted(division)))
+    return;
+  if((qid = create_qid()) == -1) /* No room to process shit.. don't do shit */
+         return;
+  tmp = (BQUE *) mush_malloc(sizeof(BQUE), "BQUE");
+  tmp->qid = qid;
+  tmp->comm = mush_strdup(command, "bqueue_comm");
+  tmp->semattr = NULL;
+  tmp->player = division;
+  tmp->queued = QUEUE_PER_OWNER ? Owner(division) : division;
+  tmp->next = NULL;
+  tmp->left = 0;
+  tmp->cause = player;
+  tmp->realcause = called_division;
+  tmp->ooref = options.twinchecks ? ooref : NOTHING;
+  tmp->fqueued = 0;
+  for (a = 0; a < 10; a++)
+    if (!global_eval_context.wnxt[a])
+      tmp->env[a] = NULL;
+    else {
+      tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
+    }
+  for (a = 0; a < NUMQ; a++)
+    if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
+      tmp->rval[a] = NULL;
+    else {
+      tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
+    }
+  if (IsPlayer(player)) {
+    if (qlast) {
+      qlast->next = tmp;
+      qlast = tmp;
+    } else
+      qlast = qfirst = tmp;
+  } else {
+    if (qllast) {
+      qllast->next = tmp;
+      qllast = tmp;
+    } else
+      qllast = qlfirst = tmp;
+  }
+}
+
+
+
+/** Enqueue the action part of an attribute.
+ * This function is a front-end to parse_que() that takes an attribute, 
+ * removes ^....: or $....: from its value, and queues what's left.
+ * \param executor object containing the attribute.
+ * \param atrname attribute name.
+ * \param enactor the enactor.
+ * \param noparent if true, parents of executor are not checked for atrname.
+ * \retval 0 failure.
+ * \retval 1 success.
+ */
+int
+queue_attribute_base(dbref executor, const char *atrname, dbref enactor,
+                    int noparent)
+{
+  ATTR *a;
+  char *start, *command;
+  dbref powinherit = NOTHING;
+  dbref local_ooref;
+
+  a = (noparent ? atr_get_noparent(executor, strupper(atrname)) :
+       atr_get(executor, strupper(atrname)));
+  if (!a)
+    return 0;
+  if(AL_FLAGS(a) & AF_POWINHERIT)
+    powinherit = atr_on_obj;
+  start = safe_atr_value(a);
+  command = start;
+  /* Trim off $-command or ^-command prefix */
+  if (*command == '$' || *command == '^') {
+    do {
+      command = strchr(command + 1, ':');
+    } while (command && command[-1] == '\\');
+    if (!command)
+      /* Oops, had '$' or '^', but no ':' */
+      command = start;
+    else
+      /* Skip the ':' */
+      command++;
+  }
+  local_ooref = ooref;
+  if(options.twinchecks && ( ((global_parent_depth[0] < 1 && global_parent_depth[1] != executor) || 
+                         global_parent_depth[1] != NOTHING) &&
+                         !has_flag_by_name(global_parent_depth[1], "AUTH_PARENT", NOTYPE) )) ooref = AL_CREATOR(a);
+  /* Now we're going to do a little magick... If we caught powinhearit isn't nothing we're using div_parse_que instead */
+  if(GoodObject(powinherit))
+    div_parse_que(powinherit, command, executor, enactor);
+  else
+    parse_que(executor, command, enactor);
+  ooref = local_ooref;
+  free(start);
+  global_parent_depth[1] = NOTHING;
+  return 1;
+}
+
+/** Queue an entry on the wait or semaphore queues.
+ * This function creates and adds a queue entry to the wait queue
+ * or the semaphore queue. Wait queue entries are sorted by when
+ * they're due to expire; semaphore queue entries are just added
+ * to the back of the queue.
+ * \param player the enqueuing object.
+ * \param wait time to wait, or 0.
+ * \param command command to enqueue.
+ * \param cause object that caused command to be enqueued.
+ * \param sem object to serve as a semaphore, or NOTHING.
+ * \param semattr attribute to serve as a semaphore, or NULL (to use SEMAPHORE).
+ * \param until 1 if we wait until an absolute time.
+ */
+int
+wait_que(dbref player, int wait, char *command, dbref cause, dbref sem,
+        const char *semattr, int until, char finvoc)
+{
+  BQUE *tmp;
+  int a, qid;
+  if (wait == 0) {
+    if (sem != NOTHING)
+      add_to_sem(sem, -1, semattr);
+    parse_que(player, command, cause);
+    return 0;
+  }
+  if (!pay_queue(player, command))     /* make sure player can afford to do it */
+    return -1;
+  if((qid = create_qid()) < 0) /* can't obtain a QID */
+         return -1;
+  tmp = (BQUE *) mush_malloc(sizeof(BQUE), "BQUE");
+  tmp->qid = qid;
+  tmp->comm = mush_strdup(command, "bqueue_comm");
+  tmp->player = player;
+  tmp->queued = QUEUE_PER_OWNER ? Owner(player) : player;
+  tmp->realcause = tmp->cause = cause;
+  tmp->semattr = NULL;
+  tmp->next = NULL;
+  tmp->ooref = ooref; /* catch state ooref */
+  tmp->fqueued = finvoc;
+#ifdef _SWMP_
+  tmp->sql_env[0] = sql_env[0];
+  tmp->sql_env[1] = sql_env[1];
+#endif
+  for (a = 0; a < 10; a++) {
+    if (!global_eval_context.wnxt[a])
+      tmp->env[a] = NULL;
+    else {
+      tmp->env[a] = mush_strdup(global_eval_context.wnxt[a], "bqueue_env");
+    }
+  }
+  for (a = 0; a < NUMQ; a++) {
+    if (!global_eval_context.rnxt[a] || !global_eval_context.rnxt[a][0])
+      tmp->rval[a] = NULL;
+    else {
+      tmp->rval[a] = mush_strdup(global_eval_context.rnxt[a], "bqueue_rval");
+    }
+  }
+
+  if (until) {
+    tmp->left = wait;
+  } else {
+    if (wait >= 0)
+      tmp->left = mudtime + wait;
+    else
+      tmp->left = 0;           /* semaphore wait without a timeout */
+  }
+  tmp->sem = sem;
+  if (sem == NOTHING) {
+    /* No semaphore, put on normal wait queue, sorted by time */
+    BQUE *point, *trail = NULL;
+
+    for (point = qwait;
+        point && (point->left <= tmp->left); point = point->next)
+      trail = point;
+
+    tmp->next = point;
+    if (trail != NULL)
+      trail->next = tmp;
+    else
+      qwait = tmp;
+  } else {
+
+    /* Put it on the end of the semaphore queue */
+    tmp->semattr =
+      mush_strdup(semattr ? semattr : "SEMAPHORE", "bqueue_semattr");
+    if (qsemlast != NULL) {
+      qsemlast->next = tmp;
+      qsemlast = tmp;
+    } else {
+      qsemfirst = qsemlast = tmp;
+    }
+  }
+  return qid;
+}
+
+/** Once-a-second check for queued commands.
+ * This function is called every second to check for commands
+ * on the wait queue or semaphore queue, and to move a command
+ * off the low priority object queue and onto the normal priority
+ * player queue.
+ */
+void
+do_second(void)
+{
+  BQUE *trail = NULL, *point, *next;
+  /* move contents of low priority queue onto end of normal one 
+   * this helps to keep objects from getting out of control since 
+   * its effects on other objects happen only after one second 
+   * this should allow @halt to be typed before getting blown away 
+   * by scrolling text.
+   */
+  if (qlfirst) {
+    if (qlast)
+      qlast->next = qlfirst;
+    else
+      qfirst = qlfirst;
+    qlast = qllast;
+    qllast = qlfirst = NULL;
+  }
+  /* do resort signal checks */
+  check_qsigs(&qwait);
+  check_qsigs(&qsemfirst);
+  /* check regular wait queue */
+
+  while (qwait && qwait->left <= mudtime) {
+    point = qwait;
+    qwait = point->next;
+    point->next = NULL;
+    point->left = 0;
+    if (IsPlayer(point->cause)) {
+      if (qlast) {
+       qlast->next = point;
+       qlast = point;
+      } else
+       qlast = qfirst = point;
+    } else {
+      if (qllast) {
+       qllast->next = point;
+       qllast = point;
+      } else
+       qllast = qlfirst = point;
+    }
+  }
+
+  /* check for semaphore timeouts */
+
+  for (point = qsemfirst, trail = NULL; point; point = next) {
+    if (point->left == 0 || point->left > mudtime) {
+      next = (trail = point)->next;
+      continue;                        /* skip non-timed, frozen, and those that haven't gone off yet */
+    }
+    if (trail != NULL)
+      trail->next = next = point->next;
+    else
+      qsemfirst = next = point->next;
+    if (point == qsemlast)
+      qsemlast = trail;
+    add_to_sem(point->sem, -1, point->semattr);
+    point->sem = NOTHING;
+    point->next = NULL;
+    if (IsPlayer(point->cause)) {
+      if (qlast) {
+       qlast->next = point;
+       qlast = point;
+      } else
+       qlast = qfirst = point;
+    } else {
+      if (qllast) {
+       qllast->next = point;
+       qllast = point;
+      } else
+       qllast = qlfirst = point;
+    }
+  }
+}
+
+/** Execute some commands from the top of the queue.
+ * This function dequeues and executes commands on the normal
+ * priority (player) queue.
+ * \param ncom number of commands to execute.
+ * \return number of commands executed.
+ */
+int
+do_top(int ncom)
+{
+  int a, i;
+  BQUE *entry;
+  char tbuf[BUFFER_LEN];
+  char *r;
+  char const *s;
+  dbref local_ooref;
+  int break_count;
+
+  for (i = 0; i < ncom; i++) {
+
+    if (!qfirst)
+      return i;
+    /* We must dequeue before execution, so that things like
+     * queued @kick or @ps get a sane queue image.
+     */
+    entry = qfirst;
+    if (!(qfirst = entry->next))
+      qlast = NULL;
+    if (GoodObject(entry->player) && !IsGarbage(entry->player)) {
+      global_eval_context.cplr = entry->player;
+#ifdef _SWMP_
+      sql_env[0] = entry->sql_env[0];
+      sql_env[1] = entry->sql_env[1];
+#endif
+      giveto(global_eval_context.cplr, QUEUE_COST);
+      add_to(entry->queued, -1);
+      entry->player = 0;
+      if (IsPlayer(global_eval_context.cplr) || !Halted(global_eval_context.cplr)) {
+       for (a = 0; a < 10; a++)
+         global_eval_context.wenv[a] = entry->env[a];
+       for (a = 0; a < NUMQ; a++) {
+         if (entry->rval[a])
+           strcpy(global_eval_context.renv[a], entry->rval[a]);
+         else
+           global_eval_context.renv[a][0] = '\0';
+       }
+       global_eval_context.process_command_port = 0;
+       s = entry->comm;
+       global_eval_context.break_called = 0;
+       break_count = 100;
+       *(global_eval_context.break_replace) = '\0';
+       start_cpu_timer();
+       while (!cpu_time_limit_hit && *s) {
+         r = global_eval_context.ccom;
+         local_ooref = ooref;
+         ooref = entry->ooref;
+         if(!entry->fqueued) {
+           process_expression(global_eval_context.ccom, &r, &s, global_eval_context.cplr, entry->cause,
+                            entry->realcause, PE_NOTHING, PT_SEMI, NULL);
+           *r = '\0';
+           if (*s == ';')
+             s++;
+            strcpy(tbuf, global_eval_context.ccom);
+            process_command(global_eval_context.cplr, tbuf, entry->cause, entry->realcause, 0);
+            if(global_eval_context.break_called) {
+              global_eval_context.break_called = 0;
+              s = global_eval_context.break_replace;
+              if(!*global_eval_context.break_replace) {
+                ooref = local_ooref;
+                break;
+              }
+              break_count--;
+              if(!break_count) {
+                notify(global_eval_context.cplr, T("@break recursion exceeded."));
+                ooref = local_ooref;
+                break;
+              }
+            }
+         } else {
+                 process_expression(global_eval_context.ccom, &r, &s, global_eval_context.cplr, entry->cause, entry->realcause, PE_DEFAULT, 
+                                 PT_DEFAULT, (PE_Info *) NULL);
+                 *r = '\0';
+                 notify(global_eval_context.cplr, global_eval_context.ccom);
+         }
+         ooref = local_ooref;
+       }
+       reset_cpu_timer();
+      }
+    }
+    free_qentry(entry);
+  }
+
+  return i;
+}
+
+/** Determine whether it's time to run a queued command.
+ * This function returns the number of seconds we expect to wait
+ * before it's time to run a queued command.
+ * If there are commands in the player queue, that's 0.
+ * If there are commands in the object queue, that's 1.
+ * Otherwise, we check wait and semaphore queues to see what's next.
+ * \return seconds left before a queue entry will be ready.
+ */
+int
+que_next(void)
+{
+  int min, curr;
+  BQUE *point;
+  /* If there are commands in the player queue, they should be run
+   * immediately.
+   */
+  if (qfirst != NULL)
+    return 0;
+  /* If there are commands in the object queue, they should be run in
+   * one second.
+   */
+  if (qlfirst != NULL)
+    return 1;
+  /* Check out the wait and semaphore queues, looking for the smallest
+   * wait value. Return that - 1, since commands get moved to the player
+   * queue when they have one second to go.
+   */
+  min = 5;
+
+  /* Wait queue is in sorted order so we only have to look at the first
+     item on it. Anything else is wasted time. */
+  if (qwait) {
+    curr = qwait->left - mudtime;
+    if (curr <= 2)
+      return 1;
+    if (curr < min)
+      min = curr;
+  }
+
+  for (point = qsemfirst; point; point = point->next) {
+    if (point->left == 0)      /* no timeout */
+      continue;
+    curr = point->left - mudtime;
+    if (curr <= 2)
+      return 1;
+    if (curr < min) {
+      min = curr;
+    }
+  }
+
+  return (min - 1);
+}
+
+static int
+drain_helper(dbref player __attribute__ ((__unused__)), dbref thing,
+            dbref parent __attribute__ ((__unused__)),
+            char const *pattern __attribute__ ((__unused__)), ATTR *atr,
+            void *args __attribute__ ((__unused__)))
+{
+  if (waitable_attr(thing, AL_NAME(atr)))
+    (void) atr_clr(thing, AL_NAME(atr), GOD);
+  return 0;
+}
+
+/** Drain or notify a semaphore.
+ * This function dequeues an entry in the semaphore queue and either
+ * discards it (drain) or executes it (notify). Maybe more than one.
+ * \param thing object serving as semaphore.
+ * \param aname attribute serving as semaphore.
+ * \param count number of entries to dequeue.
+ * \param all if 1, dequeue all entries.
+ * \param drain if 1, drain rather than notify the entries.
+ */
+void
+dequeue_semaphores(dbref thing, char const *aname, int count, int all,
+                  int drain)
+{
+  BQUE **point;
+  BQUE *entry;
+
+  if (all)
+    count = INT_MAX;
+
+  /* Go through the semaphore queue and do it */
+  point = &qsemfirst;
+  while (*point && count > 0) {
+    entry = *point;
+    if (entry->sem != thing || (aname && strcmp(entry->semattr, aname))) {
+      point = &(entry->next);
+      continue;
+    }
+
+    /* Remove the queue entry from the semaphore list */
+    *point = entry->next;
+    entry->next = NULL;
+    if (qsemlast == entry) {
+      qsemlast = qsemfirst;
+      if (qsemlast)
+       while (qsemlast->next)
+         qsemlast = qsemlast->next;
+    }
+
+    /* Update bookkeeping */
+    count--;
+    add_to_sem(entry->sem, -1, entry->semattr);
+
+    /* Dispose of the entry as appropriate: discard if @drain, or put
+     * into either the player or the object queue. */
+    if (drain) {
+      giveto(entry->player, QUEUE_COST);
+      add_to(entry->queued, -1);
+      free_qentry(entry);
+    } else if (IsPlayer(entry->cause)) {
+      if (qlast) {
+       qlast->next = entry;
+       qlast = entry;
+      } else {
+       qlast = qfirst = entry;
+      }
+    } else {
+      if (qllast) {
+       qllast->next = entry;
+       qllast = entry;
+      } else {
+       qllast = qlfirst = entry;
+      }
+    }
+  }
+
+  /* If @drain/all, clear the relevant attribute(s) */
+  if (drain && all) {
+    if (aname)
+      (void) atr_clr(thing, aname, GOD);
+    else
+      atr_iter_get(GOD, thing, "**", 0, drain_helper, NULL);
+  }
+
+  /* If @notify and count was higher than the number of queue entries,
+   * make the semaphore go negative.  This does not apply to
+   * @notify/any or @notify/all. */
+  if (!drain && aname && !all && count > 0)
+    add_to_sem(thing, -count, aname);
+}
+
+COMMAND (cmd_notify_drain) {
+  int drain;
+  char *pos;
+  char const *aname;
+  dbref thing;
+  int count;
+  int all;
+
+  /* Figure out whether we're in notify or drain */
+  drain = (cmd->name[1] == 'D');
+
+  /* Make sure they gave an object ref */
+  if (!arg_left || !*arg_left) {
+    notify(player, T("You must specify an object to use for the semaphore."));
+    return;
+  }
+
+  /* Figure out which attribute we're using */
+  pos = strchr(arg_left, '/');
+  if (pos) {
+    if (SW_ISSET(sw, SWITCH_ANY)) {
+      notify(player,
+            T
+            ("You may not specify a semaphore attribute with the ANY switch."));
+      return;
+    }
+    *pos++ = '\0';
+    upcasestr(pos);
+    aname = pos;
+  } else {
+    if (SW_ISSET(sw, SWITCH_ANY)) {
+      aname = NULL;
+    } else {
+      aname = "SEMAPHORE";
+    }
+  }
+
+  /* Figure out which object we're using */
+  thing = noisy_match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
+  if (!GoodObject(thing))
+    return;
+  /* must control something or have it link_ok in order to use it as 
+   * as a semaphore.
+   */
+  if ((!controls(player, thing) && !LinkOk(thing))
+      || (aname && !waitable_attr(thing, aname))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  /* Figure out how many times to notify */
+  all = SW_ISSET(sw, SWITCH_ALL);
+  if (arg_right && *arg_right) {
+    if (all) {
+      notify(player,
+            T("You may not specify a semaphore count with the ALL switch."));
+      return;
+    }
+    if (!is_uinteger(arg_right)) {
+      notify(player, T("The semaphore count must be an integer."));
+      return;
+    }
+    count = parse_integer(arg_right);
+  } else {
+    if (drain)
+      all = 1;
+    if (all)
+      count = INT_MAX;
+    else
+      count = 1;
+  }
+
+  dequeue_semaphores(thing, aname, count, all, drain);
+
+  if (drain) {
+    quiet_notify(player, T("Drained."));
+  } else {
+    quiet_notify(player, T("Notified."));
+  }
+}
+
+/** Softcode interface to add a command to the wait or semaphore queue.
+ * \verbatim
+ * This is the top-level function for @wait.
+ * \endverbatim
+ * \param player the enactor
+ * \param cause the object causing the command to be added.
+ * \param arg1 the wait time, semaphore object/attribute, or both.
+ * \param cmd command to queue.
+ * \param until if 1, wait until an absolute time.
+ * returns qid
+ */
+int
+do_wait(dbref player, dbref cause, char *arg1, char *cmd, int until, char finvoc)
+{
+  dbref thing;
+  char *tcount = NULL, *aname = NULL;
+  int waitfor, num;
+  ATTR *a;
+  char *arg2;
+  int j, qid;
+
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = global_eval_context.wenv[j];
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = global_eval_context.renv[j];
+
+  arg2 = strip_braces(cmd);
+  if (is_strict_integer(arg1)) {
+    /* normal wait */
+    qid = wait_que(player, parse_integer(arg1), arg2, cause, NOTHING, NULL, until, finvoc);
+    mush_free(arg2, "strip_braces.buff");
+    return qid;
+  }
+  /* semaphore wait with optional timeout */
+
+  /* find the thing to wait on */
+  aname = strchr(arg1, '/');
+  if (aname)
+    *aname++ = '\0';
+  if ((thing =
+       noisy_match_result(player, arg1, NOTYPE, MAT_EVERYTHING)) == NOTHING) {
+    mush_free(arg2, "strip_braces.buff");
+    return -1;
+  }
+
+  /* aname is either time, attribute or attribute/time.
+   * After this:
+   * tcount will hold string timeout or NULL for none
+   * aname will hold attribute name.
+   */
+  if (aname) {
+    tcount = strchr(aname, '/');
+    if (!tcount) {
+      if (is_strict_integer(aname)) {  /* Timeout */
+       tcount = aname;
+       aname = (char *) "SEMAPHORE";
+      } else {                 /* Attribute */
+       upcasestr(aname);
+      }
+    } else {                   /* attribute/timeout */
+      *tcount++ = '\0';
+      upcasestr(aname);
+    }
+  } else {
+    aname = (char *) "SEMAPHORE";
+  }
+
+  if ((!controls(player, thing) && !LinkOk(thing))
+      || (aname && !waitable_attr(thing, aname))) {
+    notify(player, T("Permission denied."));
+    mush_free(arg2, "strip_braces.buff");
+    return -1;
+  }
+  /* get timeout, default of -1 */
+  if (tcount && *tcount)
+    waitfor = atol(tcount);
+  else
+    waitfor = -1;
+  add_to_sem(thing, 1, aname);
+  a = atr_get_noparent(thing, aname);
+  if (a)
+    num = parse_integer(atr_value(a));
+  else
+    num = 0;
+  if (num <= 0) {
+    thing = NOTHING;
+    waitfor = -1;              /* just in case there was a timeout given */
+  }
+  qid = wait_que(player, waitfor, arg2, cause, thing, aname, until, finvoc);
+  mush_free(arg2, "strip_braces.buff");
+  return qid;
+}
+
+static void
+show_queue(dbref player, dbref victim, int q_type, int q_quiet, int q_all,
+          BQUE *q_ptr, int *tot, int *self, int *del)
+{
+  BQUE *tmp;
+  for (tmp = q_ptr; tmp; tmp = tmp->next) {
+    (*tot)++;
+    if (!GoodObject(tmp->player))
+      (*del)++;
+    else if (q_all || (Owner(tmp->player) == victim)) {
+      (*self)++;
+      if (!q_quiet && (CanSeeQ(player, victim)
+                      || Owns(tmp->player, player))) {
+       switch (q_type) {
+       case 1:         /* wait queue */
+         notify_format(player, "[QID: %d%s/%ld]%s:%s", tmp->qid, qid_table[tmp->qid] == QID_FREEZE ? "(F)" : "",
+                         tmp->left - mudtime, unparse_object(player, tmp->player), tmp->comm);
+         break;
+       case 2:         /* semaphore queue */
+         if (tmp->left != 0) {
+           notify_format(player, "[QID: %d%s/#%d/%s/%ld]%s:%s", tmp->qid, qid_table[tmp->qid] == QID_FREEZE ? "(F)" : "",
+                           tmp->sem, tmp->semattr, tmp->left - mudtime,
+                         unparse_object(player, tmp->player), tmp->comm);
+         } else {
+           notify_format(player, "[QID: %d%s/#%d/%s]%s:%s", tmp->qid, qid_table[tmp->qid] == QID_FREEZE ? "(F)" : "",
+                           tmp->sem, tmp->semattr, unparse_object(player, tmp->player),
+                         tmp->comm);
+         }
+         break;
+       default:                /* player or object queue */
+         notify_format(player, "[QID: %d%s] %s:%s", tmp->qid, qid_table[tmp->qid] == QID_FREEZE ? "(F)" : "", 
+                         unparse_object(player, tmp->player), tmp->comm);
+       }
+      }
+    }
+  }
+}
+
+/** Display a player's queued commands.
+ * \verbatim
+ * This is the top-level function for @ps.
+ * \endverbatim
+ * \param player the enactor.
+ * \param what name of player whose queue is to be displayed.
+ * \param flag type of display. 0 - normal, 1 - all, 2 - summary, 3 - quick
+ */
+void
+do_queue(dbref player, const char *what, enum queue_type flag)
+{
+  dbref victim = NOTHING;
+  int all = 0;
+  int quick = 0;
+  int dpq = 0, doq = 0, dwq = 0, dsq = 0;
+  int pq = 0, oq = 0, wq = 0, sq = 0;
+  int tpq = 0, toq = 0, twq = 0, tsq = 0;
+  if (flag == QUEUE_SUMMARY || flag == QUEUE_QUICK)
+    quick = 1;
+  if (flag == QUEUE_ALL || flag == QUEUE_SUMMARY) {
+    all = 1;
+    victim = player;
+  } else {
+    if (!what || !*what)
+      victim = player;
+    else
+      victim = match_result(player, what, TYPE_PLAYER,
+                           MAT_PLAYER | MAT_ABSOLUTE | MAT_ME);
+    }
+  if (!CanSeeQ(player, victim))
+    victim = player;
+
+  switch (victim) {
+  case NOTHING:
+    notify(player, T("I couldn't find that player."));
+    break;
+  case AMBIGUOUS:
+    notify(player, T("I don't know who you mean!"));
+    break;
+  default:
+
+    if (!quick) {
+      if (all == QUEUE_ALL)
+       notify(player, T("Queue for : all"));
+      else
+       notify_format(player, T("Queue for : %s"), Name(victim));
+    }
+    victim = Owner(victim);
+    if (!quick)
+      notify(player, T("Player Queue:"));
+    show_queue(player, victim, 0, quick, all, qfirst, &tpq, &pq, &dpq);
+    if (!quick)
+      notify(player, T("Object Queue:"));
+    show_queue(player, victim, 0, quick, all, qlfirst, &toq, &oq, &doq);
+    if (!quick)
+      notify(player, T("Wait Queue:"));
+    show_queue(player, victim, 1, quick, all, qwait, &twq, &wq, &dwq);
+    if (!quick)
+      notify(player, T("Semaphore Queue:"));
+    show_queue(player, victim, 2, quick, all, qsemfirst, &tsq, &sq, &dsq);
+    if (!quick)
+      notify(player, T("------------  Queue Done  ------------"));
+    notify_format(player,
+                 "Totals: Player...%d/%d[%ddel]  Object...%d/%d[%ddel]  Wait...%d/%d  Semaphore...%d/%d",
+                 pq, tpq, dpq, oq, toq, doq, wq, twq, sq, tsq);
+  }
+}
+
+
+static void check_qsigs(BQUE **qchk) {
+ BQUE *next, *save, *trail, *point, *last;
+ char qid_chk_t[MAX_QID];
+
+  memset(qid_chk_t, 0, MAX_QID);
+
+  for(save = trail = point = *qchk; point != NULL;point = save) {
+          if(qid_table[point->qid] == QID_ACTIVE) { /* don't have to bother with these */
+                  trail = save;
+                  save = point->next;
+                 qid_chk_t[point->qid] = 1;
+                  continue;
+          } else if(qid_chk_t[point->qid] == 1) {
+                 trail = save;
+                 save = point->next;
+                 continue;
+         }
+         qid_chk_t[point->qid] = 1;
+          /* see what type of PID or on. */
+          switch(qid_table[point->qid]) { /* Check for all possible re-sort signals */
+                  case QID_FREEZE:
+                          point->left++;
+                          break;
+                  case QID_CONT: /* these we're just resorting, set to active */
+                  case QID_TIME:
+                          qid_table[point->qid] = QID_ACTIVE;
+                          break;
+                  default: /* this doesn't happen */
+                          trail = save;
+                          save = point->next;
+                          continue;
+          }
+
+         /* detach the process for a second */
+         /*
+          last = save;
+          save = (trail->next = point->next);
+          trail = last;
+         */
+
+          if(*qchk == point) { /* its the first one.. so adjust the next one to be the first one */
+                  *qchk = save = point->next;
+         } else {
+                 trail->next = save = point->next;
+                 trail = trail;
+         }
+         /* detach. */
+          point->next = NULL;
+         /* retach it */
+          for(last = NULL, next = *qchk; next != NULL && (next->left <= point->left); next = next->next)
+                  last = next;
+          point->next = next;
+          if(last != NULL)
+                  last->next = point;
+          else
+                  *qchk = point;
+  }
+}
+
+/* find a queue by qid and return the queue struct of shit */
+/* qid - qid 
+ * qproc - set process we're on
+ * qlist - set which queue list we're on
+ * return queue process we found
+ */
+BQUE *find_qid(int qid, BQUE **qlist, BQUE **qprev) {
+       BQUE *qproc;
+
+       for(*qprev = qproc = qfirst; qproc; *qprev = qproc, qproc = qproc->next)
+               if(qproc->qid == qid) {
+                       return qproc;
+               }
+       for(*qprev = qproc = qlfirst; qproc; *qprev = qproc, qproc = qproc->next)
+               if(qproc->qid == qid) {
+                       return qproc;
+               }
+       for(*qprev = NULL, qproc = qwait; qproc; *qprev = qproc, qproc = qproc->next)
+               if(qproc->qid == qid) {
+                       *qlist = qwait;
+                       return qproc;
+               }
+       for(*qprev = NULL, qproc = qsemfirst; qproc; *qprev = qproc, qproc = qproc->next)
+               if(qproc->qid == qid) {
+                       *qlist = qsemfirst;
+                       return qproc;
+               }
+
+       return NULL;
+}
+
+int do_signal_qid(dbref signalby, int qid, enum qid_flags qflags, int time) {
+       BQUE *qproc, *qprev, *qsave;
+
+       /* Signal a QID with such & such signal */
+        
+       /* First Quick Check to make sure its a valid QID */
+       if(qid > qid_cnt || qid >= MAX_QID || qid_table[qid] == QID_FALSE)
+               return -1;
+       qproc = find_qid(qid, &qsave, &qprev);
+       if(!qproc || qproc->qid != qid)
+               return -1;
+       /* Check Signals */
+       switch(qflags) {
+               case QID_FREEZE:
+                       if(controls(signalby, qproc->player)) 
+                         qid_table[qid] = QID_FREEZE;
+                       else 
+                               return -3;
+                       break;
+               case QID_CONT:
+                       if(controls(signalby, qproc->player))
+                               qid_table[qid] = QID_ACTIVE;
+                       else
+                               return -3;
+                       break;
+               case QID_QUERY_T:
+                       if(!controls(signalby, qproc->player))
+                               return -3;
+                       if(qsave == qwait || qsave == qsemfirst) {
+                               return (qproc->left - mudtime);
+                       } else return -3;
+               case QID_TIME: /* modify queue time  */
+                       if(!controls(signalby, qproc->player))
+                               return -3;
+                       if(time < 0) /* can't wait negative amount of time */
+                               return 0;
+                       if(!qsave) /* We don't adjust time of regular queue thingies */
+                               return -2;
+                       qproc->left = mudtime + time;
+                       /* this will let it stay frozen if it is */
+                       if(qid_table[qid] == QID_ACTIVE)
+                         qid_table[qid] = QID_TIME;
+                       break;
+               case QID_KILL:
+                               if(!controls(signalby, qproc->player) && !CanHalt(signalby, qproc->player))
+                                       return -3;
+                               add_to(QUEUE_PER_OWNER ? Owner(qproc->player) : qproc->player, -1);
+                               giveto(Owner(qproc->player), QUEUE_COST);
+                               if(!qsave) { /* This is all we have to do for these, isn't that great? */
+                                       qproc->player = NOTHING;
+                               } else { /* Wait queues or semas
+                                         * we have to kill 'em completely from this point */
+                                       if(qprev)
+                                               qprev->next = qproc->next;
+                                       else {
+                                               if(qsave == qwait)
+                                                  qwait = qproc->next;
+                                               else {
+                                                  qsemfirst = qproc->next;
+                                                  add_to_sem(qproc->sem, -1, qproc->semattr);
+                                               }
+                                       }
+                                       free_qentry(qproc);
+                               }
+                       break;
+               default:
+                       return -2; /* Not a valid signal */
+
+       }
+       return 1;
+}
+
+
+/** Halt an object, internal use.
+ * This function is used to halt objects by other hardcode.
+ * See do_halt1() for the softcode interface.
+ * \param owner the enactor.
+ * \param ncom command to queue after halting.
+ * \param victim object to halt.
+ */
+void
+do_halt(dbref owner, const char *ncom, dbref victim)
+{
+  BQUE *tmp, *trail = NULL, *point, *next;
+  int num = 0;
+  dbref player;
+  if (victim == NOTHING)
+    player = owner;
+  else
+    player = victim;
+  quiet_notify(Owner(player),
+              tprintf("%s: %s(#%d).", T("Halted"), Name(player), player));
+  for (tmp = qfirst; tmp; tmp = tmp->next)
+    if (GoodObject(tmp->player)
+       && ((tmp->player == player)
+           || (Owner(tmp->player) == player))) {
+      num--;
+      giveto(player, QUEUE_COST);
+      tmp->player = NOTHING;
+    }
+  for (tmp = qlfirst; tmp; tmp = tmp->next)
+    if (GoodObject(tmp->player)
+       && ((tmp->player == player)
+           || (Owner(tmp->player) == player))) {
+      num--;
+      giveto(player, QUEUE_COST);
+      tmp->player = NOTHING;
+    }
+  /* remove wait q stuff */
+  for (point = qwait; point; point = next) {
+    if (((point->player == player)
+        || (Owner(point->player) == player))) {
+      num--;
+      giveto(player, QUEUE_COST);
+      if (trail)
+       trail->next = next = point->next;
+      else
+       qwait = next = point->next;
+      free_qentry(point);
+    } else
+      next = (trail = point)->next;
+  }
+
+  /* clear semaphore queue */
+
+  for (point = qsemfirst, trail = NULL; point; point = next) {
+    if (((point->player == player)
+        || (Owner(point->player) == player))) {
+      num--;
+      giveto(player, QUEUE_COST);
+      if (trail)
+       trail->next = next = point->next;
+      else
+       qsemfirst = next = point->next;
+      if (point == qsemlast)
+       qsemlast = trail;
+      add_to_sem(point->sem, -1, point->semattr);
+      free_qentry(point);
+    } else
+      next = (trail = point)->next;
+  }
+
+  add_to(QUEUE_PER_OWNER ? Owner(player) : player, num);
+
+  if (ncom && *ncom) {
+    int j;
+    for (j = 0; j < 10; j++)
+      global_eval_context.wnxt[j] = global_eval_context.wenv[j];
+    for (j = 0; j < NUMQ; j++)
+      global_eval_context.rnxt[j] = global_eval_context.renv[j];
+    parse_que(player, ncom, player);
+  }
+}
+
+/** Halt an object, softcode interface.
+ * \verbatim
+ * This is the top-level function for @halt.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 string representing object to halt.
+ * \param arg2 option string representing command to queue after halting.
+ */
+void
+do_halt1(dbref player, const char *arg1, const char *arg2)
+{
+  dbref victim;
+  if (*arg1 == '\0')
+    do_halt(player, "", player);
+  else {
+    if ((victim =
+        noisy_match_result(player, arg1, NOTYPE,
+                           MAT_OBJECTS | MAT_HERE)) == NOTHING)
+      return;
+    if (!Owns(player, victim) && !CanHalt(player, victim)) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    if (arg2 && *arg2 && !controls(player, victim)) {
+      notify(player, T("You may not use @halt obj=command on this object."));
+      return;
+    }
+    /* If victim's a player, we halt all of their objects */
+    /* If not, we halt victim and set the HALT flag if no new command */
+    /* was given */
+    do_halt(player, arg2, victim);
+    if (IsPlayer(victim)) {
+      if (victim == player) {
+       notify(player, T("All of your objects have been halted."));
+      } else {
+       notify_format(player,
+                     T("All objects for %s have been halted."), Name(victim));
+       notify_format(victim,
+                     T("All of your objects have been halted by %s."),
+                     Name(player));
+      }
+    } else {
+      if (Owner(victim) != player) {
+       notify_format(player, "%s: %s's %s(%s)", T("Halted"),
+                     Name(Owner(victim)), Name(victim), unparse_dbref(victim));
+       notify_format(Owner(victim),
+                     "%s: %s(%s), by %s", T("Halted"),
+                     Name(victim), unparse_dbref(victim), Name(player));
+      }
+      if (*arg2 == '\0')
+       set_flag_internal(victim, "HALT");
+    }
+  }
+}
+
+/** Halt all objects in the database.
+ * \param player the enactor.
+ */
+void
+do_allhalt(dbref player)
+{
+  dbref victim;
+  if (!HaltAny(player)) {
+    notify(player,
+          T("You do not have the power to bring the world to a halt."));
+    return;
+  }
+  for (victim = 0; victim < db_top; victim++) {
+    if (IsPlayer(victim)) {
+      notify_format(victim,
+                   T("Your objects have been globally halted by %s"),
+                   Name(player));
+      do_halt(victim, "", victim);
+    }
+  }
+}
+
+/** Restart all objects in the database.
+ * \verbatim
+ * A restart is a halt and then triggering the @startup.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_allrestart(dbref player)
+{
+  dbref thing;
+
+  if (!HaltAny(player)) {
+    notify(player, T("You do not have the power to restart the world."));
+    return;
+  }
+  do_allhalt(player);
+  for (thing = 0; thing < db_top; thing++) {
+    if (!IsGarbage(thing) && !(Halted(thing))) {
+      (void) queue_attribute_noparent(thing, "STARTUP", thing);
+      do_top(5);
+    }
+    if (IsPlayer(thing)) {
+      notify_format(thing,
+                   T("Your objects are being globally restarted by %s"),
+                   Name(player));
+    }
+  }
+}
+
+static void
+do_raw_restart(victim)
+    dbref victim;
+{
+  dbref thing;
+
+  if (IsPlayer(victim)) {
+    for (thing = 0; thing < db_top; thing++) {
+      if ((Owner(thing) == victim) && !IsGarbage(thing)
+         && !(Halted(thing)))
+       (void) queue_attribute_noparent(thing, "STARTUP", thing);
+    }
+  } else {
+    /* A single object */
+    if (!IsGarbage(victim) && !(Halted(victim)))
+      (void) queue_attribute_noparent(victim, "STARTUP", victim);
+  }
+}
+
+/** Restart an object.
+ * \param player the enactor.
+ * \param arg1 string representing the object to restart.
+ */
+void
+do_restart_com(dbref player, const char *arg1)
+{
+  dbref victim;
+  if (*arg1 == '\0') {
+    do_halt(player, "", player);
+    do_raw_restart(player);
+  } else {
+    if ((victim =
+        noisy_match_result(player, arg1, NOTYPE, MAT_OBJECTS)) == NOTHING)
+      return;
+    if (!Owns(player, victim) && !CanHalt(player, victim)) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    if (Owner(victim) != player) {
+      if (IsPlayer(victim)) {
+       notify_format(player,
+                     T("All objects for %s are being restarted."),
+                     Name(victim));
+       notify_format(victim,
+                     T("All of your objects are being restarted by %s."),
+                     Name(player));
+      } else {
+       notify_format(player,
+                     "Restarting: %s's %s(%s)",
+                     Name(Owner(victim)), Name(victim), unparse_dbref(victim));
+       notify_format(Owner(victim),
+                     "Restarting: %s(%s), by %s",
+                     Name(victim), unparse_dbref(victim), Name(player));
+      }
+    } else {
+      if (victim == player)
+       notify(player, T("All of your objects are being restarted."));
+      else
+       notify_format(player, "Restarting: %s(%s)", Name(victim),
+                     unparse_dbref(victim));
+    }
+    do_halt(player, "", victim);
+    do_raw_restart(victim);
+  }
+}
+
+/** Dequeue all queue entries, refunding deposits. 
+ * This function dequeues all entries in all queues, without executing
+ * them and refunds queue deposits. It's called at shutdown.
+ */
+void
+shutdown_queues(void)
+{
+  shutdown_a_queue(&qfirst, &qlast);
+  shutdown_a_queue(&qlfirst, &qllast);
+  shutdown_a_queue(&qsemfirst, &qsemlast);
+  shutdown_a_queue(&qwait, NULL);
+}
+
+
+static void
+shutdown_a_queue(BQUE **head, BQUE **tail)
+{
+  BQUE *entry;
+  /* Drain out a queue */
+  while (*head) {
+    entry = *head;
+    if (!(*head = entry->next) && tail)
+      *tail = NULL;
+    if (GoodObject(entry->player) && !IsGarbage(entry->player)) {
+      global_eval_context.cplr = entry->player;
+      giveto(global_eval_context.cplr, QUEUE_COST);
+      add_to(entry->queued, -1);
+    }
+    free_qentry(entry);
+  }
+}
+
+FUNCTION(fun_wait) {
+       char tbuf[BUFFER_LEN], *tbp;
+       const char *p;
+
+       if(!args[0] || !*args[0] || !args[1] || !*args[1])
+               safe_str("#-1", buff, bp);
+       else if(!command_check_byname(executor, "@wait"))
+               safe_str("#-1 PERMISSION DENIED", buff, bp);
+       else  {
+               tbp = tbuf;
+               p = args[0];
+               process_expression(tbuf, &tbp, &p, executor, caller, enactor, PE_DEFAULT, PT_DEFAULT, pe_info);
+               *tbp = '\0';
+               safe_integer(do_wait(executor, caller, tbuf, args[1], 0, 1), buff, bp);
+       }
+}
diff --git a/src/create.c b/src/create.c
new file mode 100644 (file)
index 0000000..6974e90
--- /dev/null
@@ -0,0 +1,900 @@
+/**
+ * \file create.c
+ *
+ * \brief Functions for creating objects of all types.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "match.h"
+#include "extchat.h"
+#include "log.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "lock.h"
+#include "parse.h"
+#include "game.h"
+#include "command.h"
+#include "confmagic.h"
+
+static dbref parse_linkable_room(dbref player, const char *room_name);
+static dbref check_var_link(const char *dest_name);
+static dbref clone_object(dbref player, dbref thing, const char *newname,
+                         int preserve);
+
+struct db_stat_info current_state; /**< Current stats for database */
+
+
+dbref copy_exit(dbref loc, dbref tail, dbref exit, dbref *oldrooms, dbref *newrooms, dbref new_owner, int num_rooms); 
+dbref copy_room(dbref room, dbref newowner);
+dbref copy_zmo(dbref old_zone, dbref new_owner);
+
+/* utility for open and link */
+static dbref
+parse_linkable_room(dbref player, const char *room_name)
+{
+  dbref room;
+
+  /* parse room */
+  if (!strcasecmp(room_name, "here")) {
+    room = IsExit(player) ? Source(player) : Location(player);
+  } else if (!strcasecmp(room_name, "home")) {
+    return HOME;               /* HOME is always linkable */
+  } else {
+    room = parse_dbref(room_name);
+  }
+
+  /* check room */
+  if (!GoodObject(room)) {
+    notify(player, T("That is not a valid object."));
+    return NOTHING;
+  } else if (Going(room)) {
+    notify(player, T("That room is being destroyed. Sorry."));
+    return NOTHING;
+  } else if (!can_link_to(player, room)) {
+    notify(player, T("You can't link to that."));
+    return NOTHING;
+  } else {
+    return room;
+  }
+}
+
+static dbref
+check_var_link(const char *dest_name)
+{
+  /* This allows an exit to be linked to a 'variable' destination.
+   * Such exits can be linked by anyone but the owner's ability
+   * to link to the destination is checked when it's computed.
+   */
+
+  if (!strcasecmp("VARIABLE", dest_name))
+    return AMBIGUOUS;
+  else
+    return NOTHING;
+}
+
+/** Create an exit.
+ * This function opens an exit and optionally links it.
+ * \param player the enactor.
+ * \param direction the name of the exit.
+ * \param linkto the room to link to, as a string.
+ * \param pseudo a phony location for player if a back exit is needed. This is bpass by do_open() as the source room of the back exit.
+ * \return dbref of the new exit, or NOTHING.
+ */
+dbref
+do_real_open(dbref player, const char *direction, const char *linkto,
+            dbref pseudo)
+{
+  dbref loc =
+    (pseudo !=
+     NOTHING) ? pseudo : (IsExit(player) ? Source(player) : Location(player));
+  dbref new_exit;
+  if (!command_check_byname(player, "@dig")) {
+    notify(player, "Permission denied.");
+    return NOTHING;
+  }
+  if ((loc == NOTHING) || (!IsRoom(loc))) {
+    notify(player, T("Sorry you can only make exits out of rooms."));
+    return NOTHING;
+  }
+  if (Going(loc)) {
+    notify(player, T("You can't make an exit in a place that's crumbling."));
+    return NOTHING;
+  }
+  if (!*direction) {
+    notify(player, T("Open where?"));
+    return NOTHING;
+  } else if (!ok_name(direction)) {
+    notify(player, T("That's a strange name for an exit!"));
+    return NOTHING;
+  }
+  if (!CanOpen(player, loc) && !controls(player, loc)) {
+    notify(player, T("Permission denied."));
+  } else if (can_pay_fees(player, EXIT_COST)) {
+    /* create the exit */
+    new_exit = new_object();
+
+    /* initialize everything */
+    set_name(new_exit, direction);
+    Owner(new_exit) = Owner(player);
+    Zone(new_exit) = Zone(player);
+    SDIV(new_exit).object = SDIV(player).object;
+    SLEVEL(new_exit) = LEVEL(player);
+    Source(new_exit) = loc;
+    Type(new_exit) = TYPE_EXIT;
+    Flags(new_exit) = string_to_bits("FLAG", options.exit_flags);
+    set_lmod(new_exit, "NONE");
+
+    /* link it in */
+    PUSH(new_exit, Exits(loc));
+
+    /* and we're done */
+    notify_format(player, T("Opened exit %s"), unparse_dbref(new_exit));
+
+    /* check second arg to see if we should do a link */
+    if (linkto && *linkto != '\0') {
+      notify(player, T("Trying to link..."));
+      if ((loc = check_var_link(linkto)) == NOTHING)
+       loc = parse_linkable_room(player, linkto);
+      if (loc != NOTHING) {
+       if (!payfor(player, LINK_COST)) {
+         notify_format(player, T("You don't have enough %s to link."), MONIES);
+       } else {
+         /* it's ok, link it */
+         Location(new_exit) = loc;
+         notify_format(player, T("Linked exit #%d to #%d"), new_exit, loc);
+       }
+      }
+    }
+    current_state.exits++;
+    local_data_create(new_exit);
+    return new_exit;
+  }
+  return NOTHING;
+}
+
+/** Open a new exit.
+ * \verbatim
+ * This is the top-level function for @open. It calls do_real_open()
+ * to do the real work of opening both the exit forward and the exit back.
+ * \endverbatim
+ * \param player the enactor.
+ * \param direction name of the exit forward.
+ * \param links 1-based array containing name of destination and optionally name of exit back.
+ */
+void
+do_open(dbref player, const char *direction, char **links)
+{
+  dbref forward;
+  forward = do_real_open(player, direction, links[1], NOTHING);
+  if (links[2] && GoodObject(forward) && GoodObject(Location(forward))) {
+    do_real_open(player, links[2], "here", Location(forward));
+  }
+}
+
+/** Unlink an exit or room.
+ * \verbatim
+ * This is the top-level function for @unlink, which can unlink an exit
+ * or remove a drop-to from a room.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the object to unlink.
+ */
+void
+do_unlink(dbref player, const char *name)
+{
+  dbref exit_l;
+  long match_flags = MAT_EXIT | MAT_HERE | MAT_ABSOLUTE;
+
+  if (!OOREF(player,div_powover(player, player,  "Link"), div_powover(ooref, ooref, "Link"))) {
+    match_flags |= MAT_CONTROL;
+  }
+  switch (exit_l = match_result(player, name, TYPE_EXIT, match_flags)) {
+  case NOTHING:
+    notify(player, T("Unlink what?"));
+    break;
+  case AMBIGUOUS:
+    notify(player, T("I don't know which one you mean!"));
+    break;
+  default:
+    if (!controls(player, exit_l)) {
+      notify(player, T("Permission denied."));
+    } else {
+      switch (Typeof(exit_l)) {
+      case TYPE_EXIT:
+       Location(exit_l) = NOTHING;
+       notify_format(player, T("Unlinked exit #%d."), exit_l);
+       break;
+      case TYPE_ROOM:
+       Location(exit_l) = NOTHING;
+       notify(player, T("Dropto removed."));
+       break;
+      default:
+       notify(player, T("You can't unlink that!"));
+       break;
+      }
+    }
+  }
+}
+
+/** Link an exit, room, player, or thing.
+ * \verbatim
+ * This is the top-level function for @link, which is used to link an
+ * exit to a destination, set a player or thing's home, or set a 
+ * drop-to on a room.
+ *
+ * Linking an exit usually seizes ownership of the exit and costs 1 penny.
+ * 1 penny is also transferred to the former owner.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the object to link.
+ * \param room_name the name of the link destination.
+ * \param preserve if 1, preserve ownership and zone data on exit relink.
+ */
+void
+do_link(dbref player, const char *name, const char *room_name, int preserve)
+{
+  /* Use this to link to a room that you own. 
+   * It usually seizes ownership of the exit and costs 1 penny,
+   * plus a penny transferred to the exit owner if they aren't you.
+   * You must own the linked-to room AND specify it by room number.
+   */
+
+  dbref thing;
+  dbref room;
+
+  if (!room_name || !*room_name) {
+    do_unlink(player, name);
+    return;
+  }
+  if (!IsRoom(player) && GoodObject(Location(player)) &&
+      IsExit(Location(player))) {
+    notify(player, T("You somehow wound up in a exit. No biscuit."));
+    return;
+  }
+  if ((thing = noisy_match_result(player, name, TYPE_EXIT, MAT_EVERYTHING))
+      != NOTHING) {
+    switch (Typeof(thing)) {
+    case TYPE_EXIT:
+      if ((room = check_var_link(room_name)) == NOTHING)
+       room = parse_linkable_room(player, room_name);
+      if (room == NOTHING)
+       return;
+      if (GoodObject(room) && !can_link_to(player, room)) {
+       notify(player, T("Permission denied."));
+       break;
+      }
+      /* We may link an exit if it's unlinked and we pass the link-lock
+       * or if we control it.
+       */
+      if (!(controls(player, thing)
+           || ((Location(thing) == NOTHING)
+               && eval_lock(player, thing, Link_Lock)))) {
+       notify(player, T("Permission denied."));
+       return;
+      }
+      if (preserve && !OOREF(player,div_powover(player, player,  "Link"), div_powover(ooref,ooref, "Link"))) {
+       notify(player, T("Permission denied."));
+       return;
+      }
+      /* handle costs */
+      if (Owner(thing) == Owner(player)) {
+       if (!payfor(player, LINK_COST)) {
+         notify_format(player, T("It costs %d %s to link this exit."),
+                       LINK_COST, ((LINK_COST == 1) ? MONEY : MONIES));
+         return;
+       }
+      } else {
+       if (!payfor(player, LINK_COST + EXIT_COST)) {
+         int a = LINK_COST + EXIT_COST;
+         notify_format(player, T("It costs %d %s to link this exit."), a,
+                       ((a == 1) ? MONEY : MONIES));
+         return;
+       } else if (!preserve) {
+         /* pay the owner for his loss */
+         giveto(Owner(thing), EXIT_COST);
+         chown_object(player, thing, player, 0);
+       }
+      }
+
+      /* link has been validated and paid for; do it */
+      if (!preserve) {
+       Owner(thing) = Owner(player);
+       Zone(thing) = Zone(player);
+        SDIV(thing).object = SDIV(player).object;
+        SLEVEL(thing) = LEVEL(player);
+      }
+      Location(thing) = room;
+
+      /* notify the player */
+      notify_format(player, T("Linked exit #%d to #%d"), thing, room);
+      break;
+    case TYPE_DIVISION:
+    case TYPE_PLAYER:
+    case TYPE_THING:
+      if ((room =
+          noisy_match_result(player, room_name, NOTYPE, MAT_EVERYTHING)) < 0) {
+       notify(player, T("No match."));
+       return;
+      }
+      if (IsExit(room)) {
+       notify(player, T("That is an exit."));
+       return;
+      }
+      if (thing == room) {
+       notify(player, T("You may not link something to itself."));
+       return;
+      }
+      /* abode */
+      if (!controls(player, room) && !Abode(room)) {
+       notify(player, T("Permission denied."));
+       break;
+      }
+      if (!controls(player, thing)) {
+       notify(player, T("Permission denied."));
+      } else if (room == HOME) {
+       notify(player, T("Can't set home to home."));
+      } else {
+       /* do the link */
+       Home(thing) = room;     /* home */
+       if (!Quiet(player) && !(Quiet(thing) && (Owner(thing) == player)))
+         notify(player, T("Home set."));
+      }
+      break;
+    case TYPE_ROOM:
+      if ((room = parse_linkable_room(player, room_name)) == NOTHING)
+       return;
+      if ((room != HOME) && (!IsRoom(room))) {
+       notify(player, T("That is not a room!"));
+       return;
+      }
+      if (!controls(player, thing)) {
+       notify(player, T("Permission denied."));
+      } else {
+       /* do the link, in location */
+       Location(thing) = room; /* dropto */
+       notify(player, T("Dropto set."));
+      }
+      break;
+    default:
+      notify(player, "Internal error: weird object type.");
+      do_log(LT_ERR, NOTHING, NOTHING,
+            T("Weird object! Type of #%d is %d"), thing, Typeof(thing));
+      break;
+    }
+  }
+}
+
+/** Create a room.
+ * \verbatim
+ * This is the top-level interface for @dig.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the room to create.
+ * \param argv array of additional arguments to command (exit forward,exit back)
+ * \param tport if 1, teleport the player to the new room.
+ * \return dbref of new room, or NOTHING.
+ */
+dbref
+do_dig(dbref player, const char *name, char **argv, int tport)
+{
+  dbref room;
+
+  /* we don't need to know player's location!  hooray! */
+  if (*name == '\0') {
+    notify(player, T("Dig what?"));
+  } else if (!ok_name(name)) {
+    notify(player, T("That's a silly name for a room!"));
+  } else if (can_pay_fees(player, ROOM_COST)) {
+    room = new_object();
+
+    /* Initialize everything */
+    set_name(room, name);
+    Owner(room) = Owner(player);
+    Zone(room) = Zone(player);
+    SDIV(room).object = SDIV(player).object;
+    SLEVEL(room) = LEVEL(player);
+    Type(room) = TYPE_ROOM;
+    Flags(room) = string_to_bits("FLAG", options.room_flags);
+    set_lmod(room, "NONE");
+
+    notify_format(player, T("%s created with room number %d."), name, room);
+    if (argv[1] && *argv[1]) {
+      char nbuff[MAX_COMMAND_LEN];
+      sprintf(nbuff, "#%d", room);
+      do_real_open(player, argv[1], nbuff, NOTHING);
+    }
+    if (argv[2] && *argv[2]) {
+      do_real_open(player, argv[2], "here", room);
+    }
+    current_state.rooms++;
+    local_data_create(room);
+    if (tport) {
+      /* We need to use the full command, because we need NO_TEL
+       * and Z_TEL checking */
+      char roomstr[MAX_COMMAND_LEN];
+      sprintf(roomstr, "#%d", room);
+      do_teleport(player, "me", roomstr, 0, 0);        /* if flag, move the player */
+    }
+    return room;
+  }
+  return NOTHING;
+}
+
+/** Create a thing.
+ * \verbatim
+ * This is the top-level function for @create.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of thing to create.
+ * \param cost pennies spent in creation.
+ * \return dbref of new thing, or NOTHING.
+ */
+dbref
+do_create(dbref player, char *name, int cost)
+{
+  dbref loc;
+  dbref thing;
+
+  if (*name == '\0') {
+    notify(player, T("Create what?"));
+    return NOTHING;
+  } else if (!ok_name(name)) {
+    notify(player, T("That's a silly name for a thing!"));
+    return NOTHING;
+  } else if (cost < OBJECT_COST) {
+    cost = OBJECT_COST;
+  }
+  if (can_pay_fees(player, cost)) {
+    /* create the object */
+    thing = new_object();
+
+    /* initialize everything */
+    set_name(thing, name);
+    if (!IsExit(player))       /* Exits shouldn't have contents! */
+      Location(thing) = player;
+    else
+      Location(thing) = Source(player);
+    Owner(thing) = Owner(player);
+    Zone(thing) = Zone(player);
+    SDIV(thing).object = SDIV(player).object;
+    SLEVEL(thing) = LEVEL(player);
+    s_Pennies(thing, cost);
+    Type(thing) = TYPE_THING;
+    Flags(thing) = string_to_bits("FLAG", options.thing_flags);
+    set_lmod(thing, "NONE");
+
+    /* home is here (if we can link to it) or player's home */
+    if ((loc = Location(player)) != NOTHING &&
+       (controls(player, loc) || Abode(loc))) {
+      Home(thing) = loc;       /* home */
+    } else {
+      Home(thing) = Home(player);      /* home */
+    }
+
+    /* link it in */
+    if (!IsExit(player))
+      PUSH(thing, Contents(player));
+    else
+      PUSH(thing, Contents(Source(player)));
+
+    /* and we're done */
+    notify_format(player, "Created: Object %s.", unparse_dbref(thing));
+    current_state.things++;
+    local_data_create(thing);
+    return thing;
+  }
+  return NOTHING;
+}
+
+/* Clone an object. The new object is owned by the cloning player */
+static dbref
+clone_object(dbref player, dbref thing, const char *newname, int preserve)
+{
+  dbref clone;
+
+  clone = new_object();
+
+  memcpy(REFDB(clone), REFDB(thing), sizeof(struct object));
+  Owner(clone) = Owner(player);
+  Name(clone) = NULL;
+  if (newname && *newname)
+    set_name(clone, newname);
+  else
+    set_name(clone, Name(thing));
+  s_Pennies(clone, Pennies(thing));
+  atr_cpy(clone, thing);
+  Locks(clone) = NULL;
+  clone_locks(player, thing, clone);
+  Zone(clone) = Zone(thing);
+  SDIV(clone).object = SDIV(thing).object;
+  adjust_powers(clone, player);
+  Parent(clone) = Parent(thing);
+  Flags(clone) = clone_flag_bitmask("FLAG", Flags(thing));
+  set_lmod(clone, "NONE");
+#ifdef RPMODE_SYS
+  db[clone].rplog.bufferq = NULL;
+  db[clone].rplog.status = 0;
+#endif
+  DPBITS(clone) = NULL;
+  if (!preserve) {
+    Warnings(clone) = 0;       /* zap warnings */
+  } else if (Warnings(clone) || DPBITS(clone)) {
+    notify(player, T("Warning: @CLONE/PRESERVE on an object with powers or warnings."));
+  }
+  /* We give the clone the same modification time that its
+   * other clone has, but update the creation time */
+
+  CreTime(clone) = mudtime;
+  Contents(clone) = Location(clone) = Next(clone) = NOTHING;
+
+  return clone;
+
+}
+
+/** Clone an object.
+ * \verbatim
+ * This is the top-level function for @clone, which creates a duplicate
+ * of a (non-player) object.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the object to clone.
+ * \param newname the name to give the duplicate.
+ * \param preserve if 1, preserve ownership and privileges on duplicate.
+ * \return dbref of the duplicate, or NOTHING.
+ */
+dbref
+do_clone(dbref player, char *name, char *newname, int preserve)
+{
+  dbref clone, thing;
+  char dbnum[BUFFER_LEN];
+
+  if (newname && *newname && !ok_name(newname)) {
+    notify(player, T("That is not a reasonable name."));
+    return NOTHING;
+  }
+  thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
+  if ((thing == NOTHING))
+    return NOTHING;
+
+  if (!controls(player, thing) || IsPlayer(thing) ||
+      (IsRoom(thing) && !command_check_byname(player, "@dig")) ||
+      (IsExit(thing) && !command_check_byname(player, "@open")) ||
+      (IsThing(thing) && !command_check_byname(player, "@create"))) {
+    notify(player, T("Permission denied."));
+    return NOTHING;
+  }
+  /* don't allow cloning of destructed things */
+  if (IsGarbage(thing)) {
+    notify(player, T("There's nothing left of it to clone!"));
+    return NOTHING;
+  }
+  if (preserve && !Director(player)) {
+    notify(player,
+          T("You cannot @CLONE/PRESERVE.  Use normal @CLONE instead."));
+    return NOTHING;
+  }
+  /* make sure owner can afford it */
+  switch (Typeof(thing)) {
+  case TYPE_THING:
+    if (can_pay_fees(player, Pennies(thing))) {
+      clone = clone_object(player, thing, newname, preserve);
+      notify_format(player, T("Cloned: Object %s."), unparse_dbref(clone));
+      if (IsRoom(player))
+       moveto(clone, player);
+      else
+       moveto(clone, Location(player));
+      current_state.things++;
+      local_data_clone(clone, thing);
+      real_did_it(player, clone, NULL, NULL, NULL, NULL, "ACLONE", NOTHING,
+                 global_eval_context.wenv, 0);
+      return clone;
+    }
+    return NOTHING;
+    break;
+  case TYPE_ROOM:
+    if (can_pay_fees(player, ROOM_COST)) {
+      clone = clone_object(player, thing, newname, preserve);
+      Exits(clone) = NOTHING;
+      notify_format(player, T("Cloned: Room #%d."), clone);
+      current_state.rooms++;
+      local_data_clone(clone, thing);
+      real_did_it(player, clone, NULL, NULL, NULL, NULL, "ACLONE", NOTHING,
+                 global_eval_context.wenv, 0);
+      return clone;
+    }
+    return NOTHING;
+    break;
+  case TYPE_EXIT:
+    /* For exits, we don't want people to be able to link it to
+       a location they can't with @open. So, all this stuff. 
+     */
+    switch (Location(thing)) {
+    case NOTHING:
+      strcpy(dbnum, "#-1");
+      break;
+    case HOME:
+      strcpy(dbnum, "home");
+      break;
+    case AMBIGUOUS:
+      strcpy(dbnum, "variable");
+      break;
+    default:
+      strcpy(dbnum, unparse_dbref(Location(thing)));
+    }
+    if (newname && *newname)
+      clone = do_real_open(player, newname, dbnum, NOTHING);
+    else
+      clone = do_real_open(player, Name(thing), dbnum, NOTHING);
+    if (!GoodObject(clone))
+      return NOTHING;
+    else {
+      atr_cpy(clone, thing);
+      clone_locks(player, thing, clone);
+      Zone(clone) = Zone(thing);
+      SDIV(clone).object = SDIV(thing).object;
+      SLEVEL(clone) = LEVEL(thing);
+      Parent(clone) = Parent(thing);
+      Flags(clone) = clone_flag_bitmask("FLAG", Flags(thing));
+      adjust_powers(clone, player);
+      DPBITS(clone) = NULL;
+#ifdef RPMODE_SYS
+      db[clone].rplog.bufferq = NULL;
+      db[clone].rplog.status = 0;
+#endif /* RPMODE_SYS */
+      if (!preserve) {
+       Warnings(clone) = 0;    /* zap warnings */
+      } else {
+       Warnings(clone) = Warnings(thing);
+      }
+      if (Warnings(clone) || DPBITS(clone))
+        notify(player, T("Warning: @CLONE/PRESERVE on an exit with powers or warnings."));
+      notify_format(player, T("Cloned: Exit #%d."), clone);
+      local_data_clone(clone, thing);
+      return clone;
+    }
+  }
+  return NOTHING;
+
+}
+
+
+
+
+
+
+void copy_zone(dbref executor, dbref zmo) {
+  ATTR *atr;
+  dbref newzone, cur;
+  int num_rooms, num_exits, i, ei;
+  dbref *oldrooms, *newrooms;
+  dbref *oldexits, *newexits;
+  /* dbref_list variables */
+  char *da_buf_arr[BUFFER_LEN / 2];
+  int da_sz;
+  int *da_vals = NULL;
+  char buf[BUFFER_LEN], *bp;
+  /* */
+
+  newzone = copy_zmo(zmo, executor);
+
+  /* Grab DBREF_LIST atrs and stick in da_vals */
+  atr = atr_get(zmo, "DBREF_LIST");
+  if(atr) {
+    da_sz = list2arr(da_buf_arr, BUFFER_LEN / 2, atr_value(atr), ' ');
+
+    da_vals = (int *) mush_malloc(sizeof(int) * da_sz, "copy_zone.dbref_list_size");
+    for(i = 0; i < da_sz ; i++)
+      da_vals[i] = parse_dbref(da_buf_arr[i]);
+  }
+
+  num_exits = num_rooms = 0;
+  for(cur = 0; cur < db_top; cur++)
+    if(IsRoom(cur) && Zone(cur) == zmo) {
+      num_rooms++;
+      for(i = Exits(cur); i != NOTHING; i = Next(i))
+       num_exits++;
+    }
+
+  oldrooms = (dbref *) mush_malloc(sizeof(dbref) * num_rooms, "copy_zone");
+  newrooms = (dbref *) mush_malloc(sizeof(dbref) * num_rooms, "copy_zone");
+  oldexits = (dbref *) mush_malloc(sizeof(dbref) * num_exits, "copy_zone");
+  newexits = (dbref *) mush_malloc(sizeof(dbref) * num_exits, "copy_zone");
+
+  for(i = cur = 0; cur < db_top; cur++)
+    if(IsRoom(cur) && Zone(cur) == zmo)
+      oldrooms[i++] = cur;
+
+  for(i = 0; i < num_rooms; i++) {
+    /* Make Room */
+    newrooms[i] = copy_room(oldrooms[i], executor);
+    Zone(newrooms[i]) = newzone;
+  }
+
+
+  for(ei = i = 0 ; i < num_rooms; i++) {
+    /* Copy Exits */
+    for(cur = Exits(oldrooms[i]); cur != NOTHING; cur = Next(cur))  {
+      oldexits[ei] = cur; 
+      newexits[ei++] = copy_exit(newrooms[i], Exits(newrooms[i]), cur, oldrooms, newrooms, executor, num_rooms);
+    }
+
+    /* Now Turn the Exits around */
+    (void) reverse(newrooms[i]);
+  }
+
+
+  /*  Change dbref_list on object to new dbrefs */
+  if(da_vals) {
+    bp = buf;
+    for(cur = 0; cur < da_sz; cur++) {
+      for(i = 0; i < num_rooms; i++)
+       if(da_vals[cur] == oldrooms[i]) {
+         safe_str(unparse_dbref(newrooms[i]), buf, &bp);
+         if((cur+1) < da_sz) safe_chr(' ', buf, &bp);
+         break;
+       }
+      if(i == num_rooms) {
+       for(i = 0; i < num_exits; i++)
+         if(da_vals[cur] == oldexits[i]) {
+           safe_str(unparse_dbref(newexits[i]), buf, &bp);
+           if((cur+1) < da_sz) safe_chr(' ', buf, &bp);
+           break;
+         }
+       if(i == num_exits)
+         safe_str("#-1 ", buf, &bp);
+      }
+    }
+    *bp = '\0';
+      /* add new dbref_list */
+    atr_add(newzone, "DBREF_LIST", buf, executor, NOTHING);
+    mush_free(da_vals, "copy_zone.dbref_list_size");
+  }
+
+  /* Execute AZCLONE */
+  atr = atr_get(newzone, "AZCLONE");
+  if(atr) {
+    global_eval_context.wnxt[0] = '\0';
+    parse_que(newzone, atr_value(atr), executor);
+  }
+
+  /* Free everything we're done. */
+  mush_free(oldrooms, "copy_zone");
+  mush_free(newrooms, "copy_zone");
+  mush_free(oldexits, "copy_zone");
+  mush_free(newexits, "copy_zone");
+}
+
+
+dbref copy_room(dbref room, dbref newowner) {
+  dbref new_room;
+  /* Copy room and all its shit but not exits or zone */
+
+  new_room = new_object();
+
+
+  memcpy(REFDB(new_room), REFDB(room), sizeof(struct object));
+
+  Type(new_room) = TYPE_ROOM;
+  Owner(new_room) = newowner;
+  Name(new_room) = NULL;
+
+  set_name(new_room, Name(room));
+
+  atr_cpy(new_room, room);
+  Locks(new_room) = NULL;
+  clone_locks(newowner, room, new_room);
+
+  SLEVEL(new_room) = LEVEL(newowner);
+  SDIV(new_room).object = SDIV(newowner).object;
+
+  Parent(new_room) = Parent(room);
+  Flags(new_room) = clone_flag_bitmask("FLAG", Flags(room));
+  set_lmod(new_room, "NONE");
+
+  DPBITS(new_room) = NULL;
+#ifdef RPMODE_SYS
+  db[new_room].rplog.bufferq = NULL;
+  db[new_room].rplog.status = 0;
+#endif /* RPMODE_SYS */
+
+  Contents(new_room) = Location(new_room) = Next(new_room) = Exits(new_room) =  NOTHING;
+
+
+  /* we don't have to rplog_init rooms because of this */
+  twiddle_flag_internal("FLAG", new_room, "ICFUNCS", 1);
+
+  /* spruce up more later.. just copying basic room for now */
+
+  current_state.rooms++;
+  local_data_create(room);
+
+  return new_room;
+}
+
+dbref copy_exit(dbref loc, dbref tail, dbref exit, dbref *oldrooms, dbref *newrooms, dbref new_owner, int num_rooms) {
+  int i;
+  dbref new_exit;
+
+  /* Copy exit into new_exit, finding the destination and home by
+   * looking in oldrooms and using the offest into newrooms */
+
+  new_exit = new_object();
+
+  set_name(new_exit, Name(exit));
+  Owner(new_exit) = new_owner;
+  Zone(new_exit) = Zone(new_owner);
+  SDIV(new_exit).object = SDIV(new_owner).object;
+  SLEVEL(new_exit) = LEVEL(new_owner);
+  Type(new_exit) = TYPE_EXIT;
+  set_lmod(new_exit, "NONE");
+
+  Flags(new_exit) = clone_flag_bitmask("FLAG", Flags(exit));
+  Parent(new_exit) = Parent(exit);
+  atr_cpy(new_exit, exit);
+
+
+  DPBITS(new_exit) = NULL;
+#ifdef RPMODE_SYS
+  db[new_exit].rplog.bufferq = NULL;
+  db[new_exit].rplog.status = 0;
+#endif
+
+  Source(new_exit) = loc;
+  Destination(new_exit) = NOTHING;
+  PUSH(new_exit, Exits(loc));
+  current_state.exits++;
+
+  /* Link Exit to Destination */
+  for(i = 0 ; i < num_rooms; i++)
+    if(oldrooms[i] == Destination(exit))
+      Destination(new_exit) = newrooms[i];
+  return new_exit;
+}
+
+
+dbref copy_zmo(dbref old_zone, dbref new_owner) {
+  dbref new_zone;
+  /* Clone Zone and any special old_zones.. */
+
+  new_zone = new_object();
+
+  memcpy(REFDB(new_zone), REFDB(old_zone), sizeof(struct object));
+  Owner(new_zone) = Owner(new_owner);
+  Name(new_zone) = NULL;
+  set_name(new_zone, Name(old_zone));
+  s_Pennies(new_zone, Pennies(old_zone));
+  atr_cpy(new_zone, old_zone);
+  Locks(new_zone) = NULL;
+  clone_locks(new_owner, old_zone, new_zone);
+  Zone(new_zone) = Zone(old_zone);
+  SDIV(new_zone).object = SDIV(old_zone).object;
+  adjust_powers(new_zone, new_owner);
+  Parent(new_zone) = Parent(old_zone);
+  Flags(new_zone) = clone_flag_bitmask("FLAG", Flags(old_zone));
+  set_lmod(new_zone, "NONE");
+  Warnings(new_zone) = 0;        /* zap warnings */
+  DPBITS(new_zone) = NULL;
+#ifdef RPMODE_SYS
+  db[new_zone].rplog.bufferq = NULL;
+  db[new_zone].rplog.status = 0;
+#endif /* RPMODE_SYS */
+
+  twiddle_flag_internal("FLAG", new_zone, "ZCLONE_OK", 1);
+
+  Contents(new_zone) = Location(new_zone) = Next(new_zone) = NOTHING;
+
+  CreTime(new_zone) =  mudtime;
+  current_state.things++;
+  Location(new_zone) = new_owner;
+  PUSH(new_zone, Contents(new_owner));
+
+  return new_zone;
+}
diff --git a/src/cron.c b/src/cron.c
new file mode 100644 (file)
index 0000000..0fe71eb
--- /dev/null
@@ -0,0 +1,777 @@
+/*
+ * cron.c - A cron daemon for CobraMUSH
+ * Written originally by grapenut (Jeremy Ritter)  used with permission
+ * for the CobraMUSH distribution.
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include "conf.h"
+#include "externs.h"
+#include "time.h"
+#include "parse.h"
+#include "privtab.h"
+#include "attrib.h"
+#include "lock.h"
+#include "log.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "cron.h"
+
+#include <ctype.h>
+#ifdef I_STRING
+#include <string.h>
+#endif
+
+#ifdef MUSHCRON
+
+HASHTAB crontab;
+
+enum SPEC_TYPE {
+  CS_MINUTE = 0,
+  CS_HOUR = 1,
+  CS_DAY = 2,
+  CS_MONTH = 3,
+  CS_WDAY = 4,
+  CS_TASK = 5
+};
+
+struct def_crontab default_cron_table[] = {
+  {"HOURLY", CF_COMMAND,  "0 * * * *", GOD, CRON_GLOBAL, "HOURLY"},
+  {"DAILY", CF_COMMAND,  "0 0 * * *", GOD, CRON_GLOBAL, "DAILY"},
+  {NULL, 0, NULL, 0, 0, NULL}
+};
+
+static PRIV cron_type_table[] = {
+  {"Command", 'C', CF_COMMAND, CF_COMMAND},
+  {"Function", 'F', CF_FUNCTION, CF_FUNCTION},
+  {"Halt", 'H', CF_HALT, CF_HALT},
+  {NULL, '\0', 0, 0}
+};
+
+static NVALUE month_table[] = {
+  {"Jan", CM_JANUARY},
+  {"Feb", CM_FEBRUARY},
+  {"Mar", CM_MARCH},
+  {"Apr", CM_APRIL},
+  {"May", CM_MAY},
+  {"Jun", CM_JUNE},
+  {"Jul", CM_JULY},
+  {"Aug", CM_AUGUST},
+  {"Sep", CM_SEPTEMBER},
+  {"Oct", CM_OCTOBER},
+  {"Nov", CM_NOVEMBER},
+  {"Dec", CM_DECEMBER},
+  {NULL, 0}
+};
+
+static NVALUE_ALIAS month_alias_table[] = {
+  {"January", "Jan"},
+  {"February", "Feb"},
+  {"March", "Mar"},
+  {"April", "Apr"},
+  {"June", "Jun"},
+  {"July", "Jul"},
+  {"August", "Aug"},
+  {"September", "Sep"},
+  {"October", "Oct"},
+  {"November", "Nov"},
+  {"December", "Dec"},
+  {NULL, NULL}
+};
+
+static NVALUE wday_table[] = {
+  {"Mon", CD_MONDAY},
+  {"Tue", CD_TUESDAY},
+  {"Wed", CD_WEDNESDAY},
+  {"Thu", CD_THURSDAY},
+  {"Fri", CD_FRIDAY},
+  {"Sat", CD_SATURDAY},
+  {"Sun", CD_SUNDAY},
+  {NULL, 0}
+};
+
+static NVALUE_ALIAS wday_alias_table[] = {
+  {"Monday", "Mon"},
+  {"Tuesday", "Tue"},
+  {"Wednesday", "Wed"},
+  {"Thursday", "Thu"},
+  {"Friday", "Fri"},
+  {"Saturday", "Sat"},
+  {"Sunday", "Sun"},
+  {NULL, NULL}
+};
+
+extern time_t mudtime;
+
+
+int
+start_cron(void)
+{
+  int i;
+  char tmp_format[BUFFER_LEN];
+  char *fp_h, *fp_t;
+  cronjob *job;
+  long long spec;
+  enum SPEC_TYPE which;
+
+
+  hashinit(&crontab, 32, sizeof(cronjob));
+
+  /* Load Default CronTable */
+  for(i = 0; default_cron_table[i].name != NULL; i++) {
+     job = new_job();
+     if(!job)
+       break;
+     job->owner = default_cron_table[i].owner;
+     job->object = default_cron_table[i].object;
+     job->type = default_cron_table[i].type;
+     strcpy(job->name, default_cron_table[i].name);
+     strcpy(job->format, default_cron_table[i].format);
+     strcpy(job->attrib, default_cron_table[i].attribute);
+     memset(tmp_format, '\0', BUFFER_LEN);
+     strcpy(tmp_format,  job->format);
+
+    which = CS_MINUTE;
+    fp_h = fp_t = tmp_format;
+
+    while(fp_h && which < CS_TASK ) {
+       if(fp_t && (fp_t = strchr(fp_h, ' '))) 
+          *fp_t++ = '\0';
+      
+       switch(which) {
+          case CS_MINUTE:
+            spec = parse_spec((const char *) fp_h, CRON_MINUTE_MAX, NULL, NULL, NULL, NULL);
+            memcpy(&job->spec.minute, &spec, sizeof(job->spec.minute));
+            break;
+          case CS_HOUR:
+            spec = parse_spec((const char *) fp_h, CRON_HOUR_MAX, NULL, NULL, NULL, NULL);
+            memcpy(&job->spec.hour, &spec, sizeof(job->spec.hour));
+            break;
+          case CS_DAY:
+            spec = parse_spec((const char *) fp_h, CRON_DAY_MAX, NULL, NULL, NULL, NULL);
+            memcpy(&job->spec.day, &spec, sizeof job->spec.day);
+            break;
+          case CS_MONTH:
+            spec = parse_spec((const char *) fp_h, CRON_MONTH_MAX, month_table, month_alias_table, NULL, NULL);
+            memcpy(&job->spec.month, &spec, sizeof(job->spec.month));
+            break;
+          case CS_WDAY:
+            spec = parse_spec((const char *) fp_h, CRON_DAY_MAX, wday_table, wday_alias_table, NULL, NULL);
+            memcpy(&job->spec.wday, &spec, sizeof(job->spec.wday));
+            break;
+       }
+       which++;
+       fp_h = fp_t;
+    }
+    hashadd(job->name, (void *) job, &crontab);     
+  }
+  return 1;
+}
+
+NVALUE *
+get_nvalue(NVALUE *table, const char *name)
+{
+  NVALUE *n;
+  for (n = table; n && n->name; n++) {
+    if (!strcasecmp(n->name, name)) {
+      return n;
+    }
+  }
+  return NULL;
+}
+
+NVALUE *
+get_nvalue_by_alias(NVALUE *table, NVALUE_ALIAS *alias, const char *name)
+{
+  NVALUE *n;
+  NVALUE_ALIAS *a;
+
+  n = get_nvalue(table, name);
+  if (n)
+    return n;
+  for (a = alias; a && a->alias; a++) {
+    if (!strcasecmp(a->alias, name)) {
+      n = get_nvalue(table, a->name);
+      if (!n)
+        continue;
+      return n;
+    }
+  }
+  return NULL;
+}
+
+NVALUE *
+get_nvalue_by_value(NVALUE *table, int value)
+{
+  NVALUE *n;
+  for (n = table; n && n->name; n++) {
+    if (n->value == value) {
+      return n;
+    }
+  }
+  return NULL;
+}
+
+cronspec
+current_spec(void)
+{
+  cronspec spec;
+  struct tm *current_time = localtime(&mudtime);
+
+  spec.minute = (typeof(spec.minute)) 1 << current_time->tm_min;
+  spec.hour = (typeof(spec.hour)) 1 << current_time->tm_hour;
+  spec.day = (typeof(spec.day)) 1 << (current_time->tm_mday - 1);
+  spec.month = (typeof(spec.month)) 1 << current_time->tm_mon;
+  spec.wday = (typeof(spec.wday)) 1 << current_time->tm_wday;
+
+  return spec;
+}
+
+int
+cmpspec(cronspec a, cronspec b)
+{
+  if (a.minute & b.minute &&
+      a.hour & b.hour &&
+      a.day & b.day &&
+      a.month & b.month &&
+      a.wday & b.wday)
+    return 1;
+  return 0;
+}
+
+int
+run_cron(void)
+{
+  cronspec spec;
+  cronjob *job;
+  char buff[BUFFER_LEN];
+  char *bp;
+  const char *sp;
+  dbref start, end;
+  ATTR *a;
+  char *s;
+  int stop = 0;
+  
+
+  if (mudtime % 60)
+    return 0;
+
+
+  spec = current_spec();
+  
+  for (job = (cronjob *) hash_firstentry(&crontab); job; job = (cronjob *) hash_nextentry(&crontab)) {
+    if (cmpspec(spec, job->spec)) {
+      if (CRON_Halt(job)) {
+        continue;
+      }
+      if (!GoodObject(job->object)) {
+        start = 0;
+        end = db_top;
+      } else {
+        start = job->object;
+        end = job->object;
+      }
+      do {
+       /* Job owner must control object to do it */
+       if(controls(job->owner,start)) {
+          if (CRON_Command(job)) {
+           queue_attribute(start, job->attrib, job->owner);
+          } else if (CRON_Function(job)) {
+           /* TODO: Review this section & possibly rewrite to make sure its secure. */
+           bp = buff;
+           a = (ATTR *) atr_get(start, job->attrib);
+           if (!a) {
+             start++;
+             continue;
+           }
+           s = (char *) safe_atr_value(a);
+           if (!s) {
+             start++;
+             continue;
+           }
+            sp = s;
+            process_expression(buff, &bp, &sp, start, start, start, PE_DEFAULT, PT_DEFAULT, NULL);
+            *bp = '\0';
+           free(s);
+          } else {
+            stop = 1;
+            continue;
+          }
+       }
+        start++;
+      } while (start < end && !stop);
+    }
+  }
+
+  return 1;
+}
+
+long long
+num_to_bits(unsigned int num)
+{
+  return (long long) 1 << num;
+}
+
+long long
+parse_spec(const char *str, unsigned int maximum, NVALUE *table, NVALUE_ALIAS *alias, char *format, char **fp)
+{
+  char c;
+  int x;
+  int num;
+  int range;
+  int skip;
+  int iter;
+  char *sp;
+  char *endptr;
+  long long spec = 0;
+  char buff[BUFFER_LEN];
+  char *bp;
+  NVALUE *nv;
+  
+  if (fp) {
+    c = **fp;
+    **fp = '\0';
+  }
+  if (fp)
+    **fp = c;
+
+  bp = buff;
+  safe_str(str, buff, &bp);
+  *bp = '\0';
+  bp = buff;
+
+  do {
+    num = -1;
+    range = -1;
+    skip = 1;
+    
+    sp = split_token(&bp, CRON_NUM_SEP);
+    if (!sp || !*sp)
+      break;
+    
+    endptr = strchr(sp, CRON_SKIP_SEP);
+    if (endptr) {
+      *endptr++ = '\0';
+      if (*endptr && isdigit(*endptr)) {
+        skip = strtol(endptr, NULL, 10);
+      }
+    }
+    
+    endptr = strchr(sp, CRON_RANGE_SEP);
+    if (endptr) {
+      *endptr++ = '\0';
+      if (*endptr) {
+        if (isdigit(*endptr)) {
+          range = strtol(endptr, NULL, 10);
+        } else if ((nv = get_nvalue_by_alias(table, alias, endptr))) {
+          range = nv->value;
+        }
+      }
+    }
+    
+    if (isdigit(*sp)) {
+      num = strtol(sp, NULL, 10);
+    } else if (*sp == CRON_WILDCARD) {
+      num = 0;
+      range = maximum;
+    } else if ((nv = get_nvalue_by_alias(table, alias, sp))) {
+      num = nv->value;
+    } else
+      continue;
+    
+    if (num < 0 || num > maximum)
+      continue;
+    
+    if (range < 0) {
+      range = num;
+    } else if (range > maximum) {
+      range = maximum;
+    }
+    
+    if (skip < 1 || skip > maximum)
+      continue;
+
+    if (format) {
+      if (num == 0 && range == maximum) {
+        safe_chr(CRON_WILDCARD, format, fp);
+      } else {
+
+        nv = get_nvalue_by_value(table, num);
+        if (nv)
+          safe_str(nv->name, format, fp);
+        else
+          safe_integer(num, format, fp);
+
+        if (range != num) {
+            safe_chr(CRON_RANGE_SEP, format, fp);
+
+            nv = get_nvalue_by_value(table, range);
+            if (nv)
+              safe_str(nv->name, format, fp);
+            else
+              safe_integer(range, format, fp);
+            
+        }
+      }
+      if (skip > 1) {
+        safe_chr(CRON_SKIP_SEP, format, fp);
+        safe_integer(skip, format, fp);
+      }
+    }
+
+    iter = 1;
+    do {
+      if (iter == 1)
+        spec |= num_to_bits(num);
+      iter++;
+      num++;
+      if (num > maximum && range < maximum)
+        num = 0;
+      if (iter > skip)
+        iter = 1;
+    } while (num <= range);
+    if (bp)
+      safe_chr(CRON_NUM_SEP, format, fp);
+  } while (bp);
+
+  bp = buff;
+  for (x = sizeof(long long) * 8 - 1; x > -1; x--) {
+    if (spec & ((long long) 1 << x)) {
+      safe_integer(x, buff, &bp);
+      if ((x + 1) > -1)
+        safe_chr(' ', buff, &bp);
+    }
+  }
+  *bp = '\0';
+  bp = buff;
+
+  
+  return spec;
+
+}
+
+char *
+show_spec(cronspec *spec)
+{
+  static char buff[BUFFER_LEN];
+  char *bp;
+  int x;
+
+  if (!spec)
+    return NULL;
+  
+  bp = buff;
+
+  for (x = 0; x <= CRON_MINUTE_MAX; x++) {
+    if (spec->minute & ((typeof(spec->minute)) 1 << x))
+      safe_integer(1, buff, &bp);
+    else
+      safe_integer(0, buff, &bp);
+  }
+
+  safe_chr('\n', buff, &bp);
+
+  for (x = 0; x <= CRON_HOUR_MAX; x++) {
+    if (spec->hour & ((typeof(spec->hour)) 1 << x))
+      safe_integer(1, buff, &bp);
+    else
+      safe_integer(0, buff, &bp);
+  }
+
+  safe_chr('\n', buff, &bp);
+
+  for (x = 0; x <= CRON_DAY_MAX; x++) {
+    if (spec->day & ((typeof(spec->day)) 1 << x))
+      safe_integer(1, buff, &bp);
+    else
+      safe_integer(0, buff, &bp);
+  }
+
+  safe_chr('\n', buff, &bp);
+
+  for (x = 0; x <= CRON_MONTH_MAX; x++) {
+    if (spec->month & ((typeof(spec->month)) 1 << x))
+      safe_integer(1, buff, &bp);
+    else
+      safe_integer(0, buff, &bp);
+  }
+
+  safe_chr('\n', buff, &bp);
+  
+  for (x = 0; x <= CRON_WDAY_MAX; x++) {
+    if (spec->wday & ((typeof(spec->wday)) 1 << x))
+      safe_integer(1, buff, &bp);
+    else
+      safe_integer(0, buff, &bp);
+  }
+
+  *bp = '\0';
+
+  return buff;
+}
+
+
+cronjob *
+new_job(void)
+{
+  return (cronjob *) mush_malloc(sizeof(cronjob), "CRONJOB");
+}
+
+int
+free_job(cronjob *job)
+{
+  if (job) {
+    hashdelete(job->name, &crontab);
+    mush_free(job, "CRONJOB");
+  }
+  return 1;
+}
+
+cronjob *
+make_job(dbref owner, const char *name, const char *str)
+{
+  cronjob *job;
+  enum SPEC_TYPE which;
+  char *fp;
+  char *sp;
+  char *tp;
+  char *bp;
+  char buff[BUFFER_LEN];
+  long long spec;
+  
+  if (!name || !str || !GoodObject(owner))
+    return NULL;
+
+  job = (cronjob *) hashfind(strupper(name), &crontab);
+  if (!job) {
+    job = new_job();
+    if (!job) {
+      notify(owner, "@CRON: ERROR: Unable to allocate cron-job.");
+      return NULL;
+    }
+  }
+
+  job->owner = owner;
+  fp = job->format;
+
+  bp = job->name;
+  safe_strl(strupper(name), 32, job->name, &bp);
+  *bp = '\0';
+
+  bp = buff;
+  safe_str(str, buff, &bp);
+  *bp = '\0';
+  bp = buff;
+  
+  which = CS_MINUTE;
+  
+  do {
+    sp = split_token(&bp, CRON_SPEC_SEP);
+    if (!sp || !*sp)
+      break;
+    switch (which) {
+      case CS_MINUTE:
+        spec = parse_spec((const char *) sp, CRON_MINUTE_MAX, NULL, NULL, job->format, &fp);
+        memcpy(&job->spec.minute, &spec, sizeof(job->spec.minute));
+        break;
+      case CS_HOUR:
+        safe_chr(CRON_SPEC_SEP, job->format, &fp);
+        spec = parse_spec((const char *) sp, CRON_HOUR_MAX, NULL, NULL, job->format, &fp);
+        memcpy(&job->spec.hour, &spec, sizeof(job->spec.hour));
+        break;
+      case CS_DAY:
+        safe_chr(CRON_SPEC_SEP, job->format, &fp);
+        spec = parse_spec((const char *) sp, CRON_DAY_MAX, NULL, NULL, job->format, &fp);
+        memcpy(&job->spec.day, &spec, sizeof(job->spec.day));
+        break;
+      case CS_MONTH:
+        safe_chr(CRON_SPEC_SEP, job->format, &fp);
+        spec = parse_spec((const char *) sp, CRON_MONTH_MAX, month_table, month_alias_table, job->format, &fp);
+        memcpy(&job->spec.month, &spec, sizeof(job->spec.month));
+        break;
+      case CS_WDAY:
+        safe_chr(CRON_SPEC_SEP, job->format, &fp);
+        spec = parse_spec((const char *) sp, CRON_WDAY_MAX, wday_table, wday_alias_table, job->format, &fp);
+        memcpy(&job->spec.wday, &spec, sizeof(job->spec.wday));
+        break;
+      default:
+        continue;
+    }
+    which++;
+  } while (bp && which < CS_TASK);
+
+  if(!bp) {
+    free_job(job);
+    notify(owner, "@CRON: Invalid job format.");
+    return NULL;
+  }
+
+  sp = strchr(bp, '/');
+  if (!sp) {
+    free_job(job);
+    notify(owner, "@CRON: Invalid job format.");
+    return NULL;
+  }
+  *sp++ = '\0';
+  
+  if (!strcasecmp(bp, "GLOBAL")) {
+    if(!Admin(owner)) {  /* Allow Admin and higher to set the global crons */
+      notify(owner, "@CRON: Permission denied.");
+      return NULL;
+    }
+
+    job->object = NOTHING;
+  } else {
+    job->object = noisy_match_result(owner, bp, NOTYPE, MAT_EVERYTHING);
+    if (!GoodObject(job->object)) {
+      free_job(job);
+      return NULL;
+    } else if(!controls(owner, job->object)) {
+      notify(owner, "@CRON: Permission denied.");
+      return NULL;
+    }
+  }
+
+  tp = job->attrib;
+  safe_str(strupper(sp), job->attrib, &tp);
+  *tp = '\0';
+
+  *fp = '\0';
+
+  hashadd(job->name, (void *) job, &crontab);
+
+  return job;
+}
+
+#define JobObject_Str(x)   GoodObject(x) ? tprintf("#%d", x) : "Global"
+
+void
+do_list_cronjobs(dbref player)
+{
+  cronjob *job;
+  int jobs;
+
+  notify_format(player, "%-16s %-10s %-10s %s", "Name", "Owner", "Object", "Attribute");
+  notify(player,        "-----------------------------------------------------------");
+  for (job = hash_firstentry(&crontab), jobs = 0; job; job = hash_nextentry(&crontab), jobs++) {
+    if (CanSee(player, job->owner))
+      notify_format(player, "%-16s %-10d %-10s %s", job->name, job->owner, JobObject_Str(job->object), job->attrib);
+  }
+  if (jobs < 1)
+    notify(player, "@CRON: There are no cronjobs.");
+}
+
+COMMAND(cmd_cron) {
+  cronjob *job;
+  if (SW_ISSET(sw, SWITCH_ADD)) {
+    long int type = 0;
+    if (!*arg_left || !*arg_right) {
+      notify(player, "@CRON: You must supply a job name and spec.");
+      return;
+    }
+
+    /* Do make_job parsing here */
+    
+    if (!(job = make_job(player, (const char *) arg_left, (const char *) arg_right)))
+      return;
+    
+    notify_format(player, "@CRON: Added job %s that runs %s/%s at %s.", job->name, GoodObject(job->object) ? tprintf("#%d", job->object) : "GLOBAL", job->attrib, job->format);
+
+    if (SW_ISSET(sw, SWITCH_COMMANDS)) {
+      type |= CF_COMMAND;
+    }
+
+    if (SW_ISSET(sw, SWITCH_FUNCTIONS)) {
+      notify(player, "DEV: Function Crons are Disabled for the time being while going a security review.");
+      return;
+    /*  type |= CF_FUNCTION; */
+    }
+    
+    if (CRON_Command(job) && CRON_Function(job)) {
+      notify(player, "@CRON: A job may not run as both a command and a function.");
+    } else {
+      notify_format(player, "@CRON: Job type set to %s.", privs_to_string(cron_type_table, type));
+      job->type = type;
+    }
+    
+    if (!CRON_Command(job) && !CRON_Function(job)) {
+      notify(player, "@CRON: You must use @CRON/SET to specify whether it is a Command or Function.");
+    }
+    return;
+
+  } else if (SW_ISSET(sw, SWITCH_SET)) {
+    long int oldtype;
+    long int newtype;
+    
+    if (!*arg_left) {
+      notify(player, "@CRON: You must supply a job name.");
+      return;
+    }
+    
+    job = hashfind(strupper((const char *) arg_left), &crontab);
+    if (!job) {
+      notify(player, "@CRON: That is not a valid cronjob.");
+      return;
+    }
+    
+    if (!*arg_right) {
+      notify(player, "@CRON: You must supply a toggle string.");
+      return;
+    }
+    
+    oldtype = job->type;
+    newtype = string_to_privs(cron_type_table, arg_right, job->type);
+    
+    if (newtype & CF_COMMAND && newtype & CF_FUNCTION) {
+      notify(player, "@CRON: A job may not run as both a command and a function.");
+      return;
+    }
+    
+    job->type = newtype;
+    newtype = oldtype ^ job->type;
+    notify_format(player, "@CRON: %s%s %s.", privs_to_string(cron_type_table, newtype),
+                  (oldtype & newtype) ? " removed from" : ((job->type & newtype) ?
+                                                           " set on" :
+                                                           "No flags changed on"),
+                  job->name);
+    return;
+  } else if (SW_ISSET(sw, SWITCH_DELETE)) {
+    if (!*arg_left) {
+      notify(player, "@CRON: You must supply a job name.");
+      return;
+    }
+
+    job = hashfind(strupper((const char *) arg_left), &crontab);
+    if (!job) {
+      notify(player, "@CRON: That is not a valid cronjob.");
+      return;
+    }
+    notify_format(player, "@CRON: Deleted job '%s'.", job->name);
+    free_job(job);
+    return;
+
+  } else {
+  
+    if (!*arg_left) {
+       do_list_cronjobs(player);
+    } else {
+      job = hashfind(strupper((const char *) arg_left), &crontab);
+      if (!job) {
+        notify(player, "@CRON: That is not a valid cronjob.");
+        return;
+      }
+      notify_format(player, "NAME: %-35s TYPE: %s\nATTR: %6s/%-26s SPEC: %s", job->name, privs_to_string(cron_type_table, job->type),
+                    GoodObject(job->object) ? tprintf("#%d", job->object) : "GLOBAL", job->attrib, job->format);
+      /* More or Less Debug Information? */
+     notify(player, show_spec(&job->spec));
+  
+      return;
+    }
+    
+    return;
+  }
+}
+
+#endif /* MUSHCRON */
+
diff --git a/src/csrimalloc.c b/src/csrimalloc.c
new file mode 100644 (file)
index 0000000..924510a
--- /dev/null
@@ -0,0 +1,3464 @@
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+#include "config.h"
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef I_FCNTL
+#include <fcntl.h>
+#endif
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_SYS_MMAN
+#include <sys/mman.h>
+#endif
+#ifdef I_SYS_STAT
+#include <sys/stat.h>
+#endif
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include "conf.h"
+#include "externs.h"
+#include "mymalloc.h"
+#include "csrimalloc.h"                /* Must be after mymalloc.h! */
+
+#ifdef CSRI_TRACE
+#undef malloc(x)
+#undef calloc(x, n)
+#undef realloc(p, x)
+#undef memalign(x, n)
+#undef valloc(x)
+#undef emalloc(x)
+#undef ecalloc(x, n)
+#undef erealloc(p, x)
+#undef strdup(p)
+#undef strsave(p)
+#undef cfree(p)
+#undef free(p)
+extern char *strdup _((const char *));
+#endif
+
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+/*  Modified by Javelin to replace references to _mal_statsfile
+ *  with stderr, to make glibc happy.
+ */
+#ifndef __DEFS_H__
+#define __DEFS_H__
+/* 
+ *  This file includes all relevant include files, and defines various
+ *  types and constants. It also defines lots of macros which operate on
+ *  the malloc blocks and pointers, to try and keep the ugliness
+ *  abstracted away from the source code.
+ */
+
+#include <errno.h>
+
+#ifndef REGISTER
+#ifdef __GNUC__
+   /* gcc probably does better register allocation than I do */
+#define REGISTER
+#else
+#define REGISTER register
+#endif
+#endif
+
+#if defined(__STDC__)
+#define proto(x)        x
+#else
+#define proto(x)        ()
+#define const
+#define volatile
+#endif
+
+#ifndef EXTERNS_H__
+#define EXTERNS_H__
+
+/* Lots of ugliness as we cope with non-standardness */
+
+/* 
+ *  Malloc definitions from General Utilities <stdlib.h>. Note that we
+ *  disagree with Berkeley Unix on the return type of free/cfree.
+ */
+extern univptr_t malloc proto((size_t));
+extern univptr_t calloc proto((size_t, size_t));
+extern univptr_t realloc proto((univptr_t, size_t));
+extern Free_t free proto((univptr_t));
+
+/* General Utilities <stdlib.h> */
+
+extern void abort proto((void));
+extern void exit proto((int));
+extern char *getenv proto((const char *));
+
+/*
+ *  Input/Output <stdio.h> Note we disagree with Berkeley Unix on
+ *  sprintf().
+ */
+
+#if 0                          /* can't win with this one */
+extern int sprintf proto((char *, const char *, ...));
+#endif
+
+extern int fputs proto((const char *, FILE *));
+extern int fflush proto((FILE *));
+#if 0
+extern int setvbuf proto((FILE *, char *, int, memsize_t));
+#endif
+
+
+/* Character Handling: <string.h> */
+
+#ifndef HAS_MEMSET
+extern univptr_t memset proto((univptr_t, int, memsize_t));
+#endif
+
+#ifndef HAS_MEMCPY
+extern univptr_t memcpy proto((univptr_t, const univptr_t, memsize_t));
+#endif
+
+#if 0
+/* We'd better not have to do this - Javelin */
+extern char *strcpy proto((char *, const char *));
+extern memsize_t strlen proto((const char *));
+#endif
+
+/* UNIX -- unistd.h */
+extern int write
+proto((int /*fd */ , const char * /*buf */ , int /*nbytes */ ));
+extern int open proto((const char * /*path */ , int /*flags */ , ...));
+
+#define caddr_t char *
+extern caddr_t sbrk proto((int));
+
+#ifdef _SC_PAGESIZE            /* Solaris 2.x, SVR4? */
+#define getpagesize()           sysconf(_SC_PAGESIZE)
+#else                          /* ! _SC_PAGESIZE */
+#ifdef _SC_PAGE_SIZE           /* HP, IBM */
+#define getpagesize()   sysconf(_SC_PAGE_SIZE)
+#else                          /* ! _SC_PAGE_SIZE */
+#ifndef getpagesize
+extern int getpagesize proto((void));
+#endif                         /* getpagesize */
+#endif                         /* _SC_PAGE_SIZE */
+#endif                         /* _SC_PAGESIZE */
+
+#ifdef _AIX                    /* IBM AIX doesn't declare sbrk, but is STDHEADERS. */
+extern caddr_t sbrk proto((int));
+#endif
+
+/* Backwards compatibility with BSD/Sun -- these are going to vanish one day */
+extern univptr_t valloc proto((size_t));
+extern univptr_t memalign proto((size_t, size_t));
+extern Free_t cfree proto((univptr_t));
+
+/* Malloc definitions - my additions.  Yuk, should use malloc.h properly!!  */
+extern univptr_t emalloc proto((size_t));
+extern univptr_t ecalloc proto((size_t, size_t));
+extern univptr_t erealloc proto((univptr_t, size_t));
+extern char *strsave proto((const char *));
+#ifdef CSRI_DEBUG
+extern void mal_debug proto((int));
+extern int mal_verify proto((int));
+#endif
+extern void mal_dumpleaktrace proto((FILE *));
+extern void mal_heapdump proto((FILE *));
+extern void mal_leaktrace proto((int));
+extern void mal_mmap proto((char *));
+extern void mal_sbrkset proto((int));
+extern void mal_slopset proto((int));
+#ifdef CSRI_PROFILESIZES
+extern void mal_statsdump proto((FILE *));
+#endif
+extern void mal_trace proto((int));
+
+/* Internal definitions */
+extern int __nothing proto((void));
+extern univptr_t _mal_sbrk proto((size_t));
+extern univptr_t _mal_mmap proto((size_t));
+
+#ifdef HAVE_MMAP
+extern int madvise proto((caddr_t, size_t, int));
+extern caddr_t mmap proto((caddr_t, size_t, int, int, int, off_t));
+#endif
+
+#endif /* EXTERNS_H__ */                       /* Do not add anything after this line */
+
+/* $Id: csrimalloc.c,v 1.2 2005-12-13 20:34:11 ari Exp $ */
+#ifndef __ASSERT_H__
+#define __ASSERT_H__
+#ifdef CSRI_DEBUG
+#define ASSERT(p, s) \
+        ((void)(!(p)?__m_botch((s),"",(univptr_t)0,0,__FILE__,__LINE__):0))
+#define ASSERT_SP(p, s, s2, sp) \
+        ((void)(!(p)?__m_botch((s),(s2),(univptr_t)(sp),0,__FILE__,__LINE__):0))
+#define ASSERT_EP(p, s, s2, ep) \
+        ((void)(!(p)?__m_botch((s),(s2),(univptr_t)(ep),1,__FILE__,__LINE__):0))
+
+extern int __m_botch
+proto((const char *, const char *, univptr_t, int, const char *, int));
+#else
+#define ASSERT(p, s)
+#define ASSERT_SP(p, s, s2, sp)
+#define ASSERT_EP(p, s, s2, ep)
+#endif
+#endif /* __ASSERT_H__ */                      /* Do not add anything after this line */
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE    1
+#endif
+
+#ifdef __STDC__
+#include <limits.h>
+#ifndef BITSPERBYTE
+#define BITSPERBYTE     CHAR_BIT
+#endif
+#else
+#include <values.h>
+#endif
+
+/* $Id: csrimalloc.c,v 1.2 2005-12-13 20:34:11 ari Exp $ */
+#ifndef __ALIGN_H__
+#define __ALIGN_H__
+/* 
+ * 'union word' must be aligned to the most pessimistic alignment needed
+ * on the architecture (See align.h) since all pointers returned by
+ * malloc and friends are really pointers to a Word. This is the job of
+ * the 'foo' field in the union, which actually decides the size. (On
+ * Sun3s, 1 int/long (4 bytes) is good enough, on Sun4s, 8 bytes are
+ * necessary, i.e. 2 ints/longs)
+ */
+/*
+ * 'union word' should also be the size necessary to ensure that an
+ * sbrk() immediately following an sbrk(sizeof(union word) * n) returns
+ * an address one higher than the first sbrk. i.e. contiguous space from
+ * successive sbrks. (Most sbrks will do this regardless of the size -
+ * Sun's doesnt) This is not vital - the malloc will work, but will not
+ * be able to coalesce between sbrk'ed segments.
+ */
+
+#if defined(sparc) || defined(__sparc__) || defined(__sgi) || defined(__hpux)
+/*
+ * Sparcs require doubles to be aligned on double word, SGI R4000 based
+ * machines are 64 bit, this is the conservative way out
+ */
+/* this will waste space on R4000s or the 64bit UltraSparc */
+#define ALIGN long foo[2]
+#define NALIGN 8
+#endif
+
+#if defined(__alpha)
+/* 64 bit */
+#define ALIGN long foo
+#define NALIGN 8
+#endif
+
+/* This default seems to work on most 32bit machines */
+#ifndef ALIGN
+#define ALIGN long foo
+#define NALIGN 4
+#endif
+
+/* Align with power of 2 */
+#define SIMPLEALIGN(X, N) (univptr_t)(((size_t)((char *)(X) + (N-1))) & ~(N-1))
+
+#if     NALIGN == 2 || NALIGN == 4 || NALIGN == 8 || NALIGN == 16
+  /* if NALIGN is a power of 2, the next line will do ok */
+#define ALIGNPTR(X) SIMPLEALIGN(X, NALIGN)
+#else
+  /* otherwise we need the generic version; hope the compiler isn't too smart */
+static size_t _N = NALIGN;
+#define ALIGNPTR(X) (univptr_t)((((size_t)((univptr_t)(X)+(NALIGN-1)))/_N)*_N)
+#endif
+
+/*
+ * If your sbrk does not return blocks that are aligned to the
+ * requirements of malloc(), as specified by ALIGN above, then you need
+ * to define SBRKEXTRA so that it gets the extra memory needed to find
+ * an alignment point. Not needed on Suns on which sbrk does return
+ * properly aligned blocks. But on U*x Vaxen, A/UX and UMIPS at least,
+ * you need to do this. It is safer to take the non Sun route (!! since
+ * the non-sun part also works on Suns, maybe we should just remove the
+ * Sun ifdef)
+ */
+#if ! (defined(sun) || defined(__sun__))
+#define SBRKEXTRA               (sizeof(Word) - 1)
+#define SBRKALIGN(x)    ALIGNPTR(x)
+#else
+#define SBRKEXTRA               0
+#define SBRKALIGN(x)    (x)
+#endif
+
+#ifndef BITSPERBYTE
+  /*
+   * These values should work with any binary representation of integers
+   * where the high-order bit contains the sign. Should be in values.h,
+   * but just in case...
+   */
+  /* a number used normally for size of a shift */
+#if gcos
+#define BITSPERBYTE     9
+#else                          /* ! gcos */
+#define BITSPERBYTE     8
+#endif                         /* gcos */
+#endif                         /* BITSPERBYTE */
+
+#ifndef BITS
+#define BITS(type)      (BITSPERBYTE * (int) sizeof(type))
+#endif                         /* BITS */
+
+/* size_t with only the high-order bit turned on */
+#define HIBITSZ (((size_t) 1) << (BITS(size_t) - 1))
+
+#endif /* __ALIGN_H__ */                       /* Do not add anything after this line */
+
+/*
+ * We assume that FREE is a 0 bit, and the tag for a free block, Or'ing the
+ * tag with ALLOCED should turn on the high bit, And'ing with SIZEMASK
+ * should turn it off.
+ */
+
+#define FREE            ((size_t) 0)
+#define ALLOCED         (HIBITSZ)
+#define SIZEMASK        (~HIBITSZ)
+
+#define MAXBINS         8
+
+#define DEF_SBRKUNITS   1024
+
+union word {                   /* basic unit of storage */
+  size_t size;                 /* size of this block + 1 bit status */
+  union word *next;            /* next free block */
+  union word *prev;            /* prev free block */
+  univptr_t ptr;               /* stops lint complaining, keeps alignment */
+  char c;
+  int i;
+  char *cp;
+  char **cpp;
+  int *ip;
+  int **ipp;
+   ALIGN;                      /* alignment stuff - wild fun */
+};
+
+typedef union word Word;
+
+/*
+ *  WARNING - Many of these macros are UNSAFE because they have multiple
+ *  evaluations of the arguments. Use with care, avoid side-effects.
+ */
+/*
+ * These macros define operations on a pointer to a block. The zero'th
+ * word of a block is the size field, where the top bit is 1 if the
+ * block is allocated. This word is also referred to as the starting tag.
+ * The last word of the block is identical to the zero'th word of the
+ * block and is the end tag.  IF the block is free, the second last word
+ * is a pointer to the next block in the free list (a doubly linked list
+ * of all free blocks in the heap), and the third from last word is a
+ * pointer to the previous block in the free list.  HEADERWORDS is the
+ * number of words before the pointer in the block that malloc returns,
+ * TRAILERWORDS is the number of words after the returned block. Note
+ * that the zero'th and the last word MUST be the boundary tags - this
+ * is hard-wired into the algorithm. Increasing HEADERWORDS or
+ * TRAILERWORDS suitably should be accompanied by additional macros to
+ * operate on those words. The routines most likely to be affected are
+ * malloc/realloc/free/memalign/mal_verify/mal_heapdump.
+ */
+/*
+ * There are two ways we can refer to a block -- by pointing at the
+ * start tag, or by pointing at the end tag. For reasons of efficiency
+ * and performance, free blocks are always referred to by the end tag,
+ * while allocated blocks are usually referred to by the start tag.
+ * Accordingly, the following macros indicate the type of their argument
+ * by using either 'p', 'sp', or 'ep' to indicate a pointer. 'p' means
+ * the pointer could point at either the start or end tag. 'sp' means it
+ * must point at a start tag for that macro to work correctly, 'ep'
+ * means it must point at the end tag. Usually, macros operating on free
+ * blocks (NEXT, PREV, VALID_PREV_PTR, VALID_NEXT_PTR) take 'ep', while
+ * macros operating on allocated blocks (REALSIZE, MAGIC_PTR,
+ * SET_REALSIZE) take 'sp'. The size field may be validated using either
+ * VALID_START_SIZE_FIELD for an 'sp' or VALID_END_SIZE_FIELD for an
+ * 'ep'.
+ */
+/*
+ *  SIZE, SIZEFIELD and TAG are valid for allocated and free blocks,
+ *  REALSIZE is valid for allocated blocks when debugging, and NEXT and
+ *  PREV are valid for free blocks. We could speed things up by making
+ *  the free list singly linked when not debugging - the double link are
+ *  just so we can check for pointer validity. (PREV(NEXT(ep)) == ep and
+ *  NEXT(PREV(ep)) == ep). FREESIZE is used only to get the size field
+ *  from FREE blocks - in this implementation, free blocks have a tag
+ *  bit of 0 so no masking is necessary to operate on the SIZEFIELD when
+ *  a block is free. We take advantage of that as a minor optimization.
+ */
+#define SIZEFIELD(p)            ((p)->size)
+#define SIZE(p)                 ((size_t) (SIZEFIELD(p) & SIZEMASK))
+#define ALLOCMASK(n)            ((n) | ALLOCED)
+#define FREESIZE(p)             SIZEFIELD(p)
+/*
+ *  FREEMASK should be (n) & SIZEMASK, but since (n) will always have
+ *  the hi bit off after the conversion from bytes requested by the user
+ *  to words.
+ */
+#define FREEMASK(n)             (n)
+#define TAG(p)                  (SIZEFIELD(p) & ~SIZEMASK)
+
+#ifdef CSRI_DEBUG
+#define REALSIZE(sp)            (((sp)+1)->size)
+#endif                         /* CSRI_DEBUG */
+
+#define NEXT(ep)                (((ep)-1)->next)
+#define PREV(ep)                (((ep)-2)->prev)
+
+/*
+ *  HEADERWORDS is the real block header in an allocated block - the
+ *  free block header uses extra words in the block itself
+ */
+#ifdef CSRI_DEBUG
+#define HEADERWORDS             2      /* Start boundary tag + real size in bytes */
+#else                          /* ! CSRI_DEBUG */
+#define HEADERWORDS             1      /* Start boundary tag */
+#endif                         /* CSRI_DEBUG */
+
+#define TRAILERWORDS            1
+
+#define FREEHEADERWORDS         1      /* Start boundary tag */
+
+#define FREETRAILERWORDS        3      /* next and prev, end boundary tag */
+
+#define ALLOC_OVERHEAD  (HEADERWORDS + TRAILERWORDS)
+#define FREE_OVERHEAD   (FREEHEADERWORDS + FREETRAILERWORDS)
+
+/*
+ *  The allocator views memory as a list of non-contiguous arenas. (If
+ *  successive sbrks() return contiguous blocks, they are colaesced into
+ *  one arena - if a program never calls sbrk() other than malloc(),
+ *  then there should only be one arena. This malloc will however
+ *  happily coexist with other allocators calling sbrk() and uses only
+ *  the blocks given to it by sbrk. It expects the same courtesy from
+ *  other allocators. The arenas are chained into a linked list using
+ *  the first word of each block as a pointer to the next arena. The
+ *  second word of the arena, and the last word, contain fake boundary
+ *  tags that are permanantly marked allocated, so that no attempt is
+ *  made to coalesce past them. See the code in dumpheap for more info.
+ */
+#define ARENASTART              2      /* next ptr + fake start tag */
+
+#ifdef CSRI_DEBUG
+  /* 
+   * 1 for prev link in free block - next link is absorbed by header
+   * REALSIZE word
+   */
+#define FIXEDOVERHEAD           (1 + ALLOC_OVERHEAD)
+#else                          /* ! CSRI_DEBUG */
+  /* 1 for prev link, 1 for next link, + header and trailer */
+#define FIXEDOVERHEAD           (2 + ALLOC_OVERHEAD)
+#endif                         /* CSRI_DEBUG */
+
+/* 
+ * Check that pointer is safe to dereference i.e. actually points
+ * somewhere within the heap and is properly aligned.
+ */
+#define PTR_IN_HEAP(p) \
+        ((p) > _malloc_loword && (p) < _malloc_hiword && \
+         ALIGNPTR(p) == ((univptr_t) (p)))
+
+/* Check that the size field is valid */
+#define VALID_START_SIZE_FIELD(sp) \
+        (PTR_IN_HEAP((sp) + SIZE(sp) - 1) && \
+         SIZEFIELD(sp) == SIZEFIELD((sp) + SIZE(sp) - 1))
+
+#define VALID_END_SIZE_FIELD(ep) \
+        (PTR_IN_HEAP((ep) - SIZE(ep) + 1) && \
+         SIZEFIELD(ep) == SIZEFIELD((ep) - SIZE(ep) + 1))
+
+
+#define ulong unsigned long
+
+#ifdef CSRI_DEBUG
+  /* 
+   * Byte that is stored at the end of each block if the requested size
+   * of the block is not a multiple of sizeof(Word). (If it is a multiple
+   * of sizeof(Word), then we don't need to put the magic because the
+   * endboundary tag will be corrupted and the tests that check the
+   * validity of the boundary tag should detect it
+   */
+#ifndef u_char
+#define u_char          unsigned char
+#endif                         /* u_char */
+
+#define MAGIC_BYTE              ((u_char) '\252')
+
+  /* 
+   * Check if size of the block is identical to requested size. Typical
+   * tests will be of the form DONT_NEED_MAGIC(p) || something for
+   * short-circuited protection, because blocks where DONT_NEED_MAGIC is
+   * true will be tested for boundary tag detection so we don't need the
+   * magic byte at the end.
+   */
+#define DONT_NEED_MAGIC(sp) \
+        (REALSIZE(sp) == ((SIZE(sp) - ALLOC_OVERHEAD) * sizeof(Word)))
+
+  /*
+   * VALID_REALSIZE must not be used if either DONT_NEED_MAGIC or TOO_SMALL
+   * are true
+   */
+#define VALID_REALSIZE(sp) \
+        (REALSIZE(sp) < ((SIZE(sp) - ALLOC_OVERHEAD)*sizeof(Word)))
+
+  /* Location of the magic byte */
+#define MAGIC_PTR(sp)           ((u_char *)((sp) + HEADERWORDS) + REALSIZE(sp))
+
+  /*
+   * malloc code should only use the next three macros SET_REALSIZE,
+   * VALID_MAGIC and TOO_SMALL, since they are the only ones which have
+   * non-CSRI_DEBUG (null) alternatives
+   */
+
+  /* Macro sets the realsize of a block if necessary */
+#define SET_REALSIZE(sp, n) \
+        (TOO_SMALL(sp) ? 0 : \
+         (REALSIZE(sp) = (n), \
+          DONT_NEED_MAGIC(sp) || (*MAGIC_PTR(sp) = MAGIC_BYTE)))
+
+  /* Macro tests that the magic byte is valid if it exists */
+#define VALID_MAGIC(sp) \
+        (TOO_SMALL(sp) || DONT_NEED_MAGIC(sp) || \
+         (VALID_REALSIZE(sp) && (*MAGIC_PTR(sp) == MAGIC_BYTE)))
+
+  /*
+   * Bytes left over memalign may be too small to hold the real size, they
+   * may be 1 word, i.e start and end tag stored in same word!
+   */
+#define TOO_SMALL(sp) (SIZE(sp) < ALLOC_OVERHEAD)
+
+#else                          /* ! CSRI_DEBUG */
+#define SET_REALSIZE(sp, n)
+#define VALID_MAGIC(sp) (1)
+#define TOO_SMALL(sp)           (0)
+#endif                         /* CSRI_DEBUG */
+
+/* 
+ *  Check that a free list ptr points to a block with something pointing
+ *  back. This is the only reason we use a doubly linked free list.
+ */
+#define VALID_NEXT_PTR(ep) (PTR_IN_HEAP(NEXT(ep)) && PREV(NEXT(ep)) == (ep))
+#define VALID_PREV_PTR(ep) (PTR_IN_HEAP(PREV(ep)) && NEXT(PREV(ep)) == (ep))
+
+/*
+ *  quick bit-arithmetic to check a number (including 1) is a power of two.
+ */
+#define is_power_of_2(x) ((((x) - 1) & (x)) == 0)
+
+/*
+ *  An array to try and keep track of the distribution of sizes of
+ *  malloc'ed blocks
+ */
+#ifdef CSRI_PROFILESIZES
+#define MAXPROFILESIZE 2*1024
+#define COUNTSIZE(nw) (_malloc_scount[((nw) < MAXPROFILESIZE) ? (nw) : 0]++)
+#else
+#define COUNTSIZE(nw)
+#endif
+
+#ifndef USESTDIO
+  /* 
+   * Much better not to use stdio - stdio calls malloc() and can
+   * therefore cause problems when debugging heap corruption. However,
+   * there's no guaranteed way to turn off buffering on a FILE pointer in
+   * ANSI.  This is a vile kludge!
+   */
+#define fputs(s, f)             write(fileno(f), (s), strlen(s))
+#define setvbuf(f, s, n, l)     __nothing()
+#define fflush(f)               __nothing()
+#endif                         /* USESTDIO */
+
+#ifdef CSRI_TRACE
+  /* 
+   * Prints a trace string. For convenience, x is an
+   * sprintf(_malloc_statsbuf, ...) or strcpy(_malloc_statsbuf, ...);
+   * something which puts the appropriate text to be printed in
+   * _malloc_statsbuf - ugly, but more convenient than making x a string.
+   */
+#define PRCSRI_TRACE(x) \
+  if (_malloc_tracing) { \
+        (void) x; \
+        (void) fputs(_malloc_statsbuf, stderr); \
+  } else \
+        _malloc_tracing += 0
+#else
+#define PRCSRI_TRACE(x)
+#endif
+
+#ifdef CSRI_DEBUG
+#define CHECKHEAP() \
+  if (_malloc_debugging >= 2) \
+        (void) mal_verify(_malloc_debugging >= 3); \
+  else \
+        _malloc_debugging += 0
+
+#define CHECKFREEPTR(ep, msg) \
+  if (_malloc_debugging >= 1) { \
+        ASSERT_EP(PTR_IN_HEAP(ep), "pointer outside heap", (msg), (ep)); \
+        ASSERT_EP(TAG(ep) == FREE, "already-allocated block", (msg), (ep)); \
+        ASSERT_EP(VALID_END_SIZE_FIELD(ep), "corrupt block", msg, (ep)); \
+        ASSERT_EP(VALID_NEXT_PTR(ep), "corrupt link to next free block", (msg), (ep)); \
+        ASSERT_EP(VALID_PREV_PTR(ep), "corrupt link to previous free block", (msg), (ep)); \
+  } else \
+        _malloc_debugging += 0
+
+#define CHECKALLOCPTR(sp, msg) \
+  if (_malloc_debugging >= 1) { \
+        ASSERT_SP(PTR_IN_HEAP(sp), "pointer outside heap", (msg), (sp)); \
+        ASSERT_SP(TAG(sp) != FREE, "already-freed block", (msg), (sp)); \
+        ASSERT_SP(VALID_START_SIZE_FIELD(sp), "corrupt block", (msg), (sp)); \
+        ASSERT_SP(VALID_MAGIC(sp), "block with end overwritten", (msg), (sp)); \
+  } else \
+        _malloc_debugging += 0
+
+#else                          /* !CSRI_DEBUG */
+#define CHECKHEAP()
+#define CHECKFREEPTR(ep, msg)
+#define CHECKALLOCPTR(sp, msg)
+#endif                         /* CSRI_DEBUG */
+
+#define FREEMAGIC               '\125'
+
+/*
+ *  Memory functions but in words. We just call memset/memcpy, and hope
+ *  that someone has optimized them. If you are on pure 4.2BSD, either
+ *  redefine these in terms of bcopy/your own memcpy, or
+ *  get the functions from one of 4.3src/Henry Spencer's strings
+ *  package/C News src
+ */
+#define MEMSET(p, c, n) \
+        (void) memset((univptr_t) (p), (c), (memsize_t) ((n) * sizeof(Word)))
+#define MEMCPY(p1, p2, n) \
+        (void) memcpy((univptr_t) (p1), (univptr_t) (p2), \
+                                  (memsize_t) ((n) * sizeof(Word)))
+
+
+#ifdef CSRI_DEBUG
+#define DMEMSET(p, n)   MEMSET((p), FREEMAGIC, (n))
+#else
+#define DMEMSET(p, n)
+#endif
+
+/* 
+ *  Thanks to Hugh Redelmeier for pointing out that an rcsid is "a
+ *  variable accessed in a way not obvious to the compiler", so should
+ *  be called volatile. Amazing - a use for const volatile...
+ */
+#ifndef RCSID                  /* define RCSID(x) to nothing if don't want the rcs headers */
+#if defined(lint) || defined(__STRICT_ANSI__)
+#define RCSID(x)
+#else
+#define RCSID(x)                static const volatile char *rcsid = x ;
+#endif
+#endif
+
+#endif /* __DEFS_H__ */                        /* Do not add anything after this line */
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*
+ *  All globals are names starting with _malloc, which should not clash
+ *  with anything else.
+ */
+/*
+ *  Remember to initialize the variable in globals.c if you want, and
+ *  provide an alternative short name in globrename.h
+ */
+/* $Id: csrimalloc.c,v 1.2 2005-12-13 20:34:11 ari Exp $ */
+#ifndef __GLOBALRENAME_H__
+#define __GLOBALRENAME_H__
+/*
+ *  Renaming all external symbols that are internal to the malloc to be
+ *  unique within 6 characters for machines whose linkers just can't keep
+ *  up. We hope the cpp is smart enough - if not, get GNU cccp or the
+ *  cpp that comes with X Windows Version 11 Release 3.
+ */
+#ifdef SHORTNAMES
+#define _malloc_minchunk        __MAL1_minchunk
+
+#define _malloc_rovers          __MAL2_rovers
+#define _malloc_binmax          __MALH_binmax
+#define _malloc_firstbin        __MALG_firstbin
+#define _malloc_lastbin         __MAL3_lastbin
+#define _malloc_hiword          __MAL4_hiword
+#define _malloc_loword          __MAL5_loword
+
+#define _malloc_sbrkunits       __MAL6_sbrkunits
+
+#define _malloc_mem                     __MAL7_mem
+
+#define _malloc_tracing         __MAL8_tracing
+#define _malloc_statsbuf        __MALA_statsbuf
+
+#define _malloc_leaktrace       __MALB_leaktrace
+
+#ifdef CSRI_PROFILESIZES
+#define _malloc_scount          __MALC_scount
+#endif                         /* CSRI_PROFILESIZES */
+
+#ifdef CSRI_DEBUG
+/*
+ *  0 or 1 means checking all pointers before using them. Reasonably
+ *  thorough.  2 means check the entire heap on every call to
+ *  malloc/free/realloc/memalign. (the rest call these)
+ */
+#define _malloc_debugging       __MALD_debugging
+#endif                         /* CSRI_DEBUG */
+#define _malloc_version         __MALE_version
+
+#define _malloc_memfunc         __MALF_memfunc
+
+#endif /* SHORTNAMES */                        /* Do not add anything after this line */
+#endif /* __GLOBALRENAME_H__ */                         /* Do not add anything after this line */
+const char *_malloc_version =
+  "CSRI, University of Toronto Malloc Version 1.18alpha";
+
+/*
+ *  _malloc_minchunk is the minimum number of units that a block can be
+ *  cut down to.  If the difference between the required block size, and
+ *  the first available block is greater than _malloc_minchunk, the
+ *  block is chopped into two pieces, else the whole block is returned.
+ *  The larger this is, the less fragmentation there will be, but the
+ *  greater the waste. The actual minimum size of a block is therefore
+ *  _malloc_minchunk*sizeof(Word) This consists of one word each for the
+ *  boundary tags, one for the next and one for the prev pointers in a
+ *  free block.
+ */
+size_t _malloc_minchunk = FIXEDOVERHEAD;
+
+/*
+ * _malloc_rovers is the array of pointers into that each point 'someplace'
+ * into a list of free blocks smaller than a specified size. We start our
+ * search for a block from _malloc_rovers, thus starting the search at a
+ * different place everytime, rather than at the start of the list.  This
+ * improves performance considerably, sez Knuth
+ */
+Word *_malloc_rovers[MAXBINS + 1] = { NULL };
+const size_t _malloc_binmax[MAXBINS] = {
+  8, 16, 32, 64, 128, 256, 512, 1024
+};
+int _malloc_firstbin = 0;
+int _malloc_lastbin = 0;
+Word *_malloc_hiword = NULL;
+Word *_malloc_loword = NULL;
+
+/* 
+ *  _malloc_sbrkunits is the multiple of sizeof(Word) to actually use in
+ *  sbrk() calls - _malloc_sbrkunits should be large enough that sbrk
+ *  isn't called too often, but small enough that any program that
+ *  mallocs a few bytes doesn't end up being very large. I've set it to
+ *  1K resulting in a sbrk'ed size of 8K. This is (coincidentally!) the
+ *  pagesize on Suns.  I think that this seems a reasonable number for
+ *  modern programs that malloc heavily. For small programs, you may
+ *  want to set it to a lower number.
+ */
+size_t _malloc_sbrkunits = DEF_SBRKUNITS;
+
+Word *_malloc_mem = NULL;
+
+/*
+ *  Do not call any output routine other than fputs() - use sprintf() if
+ *  you want to format something before printing. We don't want stdio
+ *  calling malloc() if we can help it
+ */
+int _malloc_tracing = 0;       /* No tracing */
+char _malloc_statsbuf[128];
+
+int _malloc_leaktrace = 0;
+
+#ifdef CSRI_PROFILESIZES
+int _malloc_scount[MAXPROFILESIZE];
+#endif                         /* CSRI_PROFILESIZES */
+
+#ifdef CSRI_DEBUG
+/*
+ *  0 or 1 means checking all pointers before using them. Reasonably
+ *  thorough.  2 means check the entire heap on every call to
+ *  malloc/free/realloc/memalign. (the rest call these)
+ */
+int _malloc_debugging = 0;
+#endif                         /* CSRI_DEBUG */
+
+univptr_t (*_malloc_memfunc) proto((size_t)) = _mal_sbrk;
+
+#ifndef __GLOBALS_H__
+#define __GLOBALS_H__
+/*
+ *  Remember to initialize the variable in globals.c if you want, and
+ *  provide an alternative short name in globrename.h
+ */
+
+extern
+size_t _malloc_minchunk;
+
+extern Word *_malloc_rovers[];
+extern const
+size_t _malloc_binmax[];
+extern int
+ _malloc_firstbin;
+extern int
+ _malloc_lastbin;
+extern Word *_malloc_hiword;
+extern Word *_malloc_loword;
+
+extern
+size_t _malloc_sbrkunits;
+
+extern Word *_malloc_mem;
+
+extern int
+ _malloc_tracing;              /* No tracing */
+extern char
+ _malloc_statsbuf[];
+
+extern int
+ _malloc_leaktrace;
+
+#ifdef CSRI_PROFILESIZES
+extern int
+ _malloc_scount[];
+#endif                         /* CSRI_PROFILESIZES */
+
+#ifdef CSRI_DEBUG
+/*
+ *  0 or 1 means checking all pointers before using them. Reasonably
+ *  thorough.  2 means check the entire heap on every call to
+ *  malloc/free/realloc/memalign. (the rest call these)
+ */
+extern int
+ _malloc_debugging;
+#endif                         /* CSRI_DEBUG */
+
+extern
+univptr_t (*_malloc_memfunc) proto((size_t));
+
+extern int
+__m_prblock proto((univptr_t, int, FILE *));
+
+#endif /* __GLOBALS_H__ */                     /* Do not add anything after this line */
+
+/*
+   ** sptree.h:  The following type declarations provide the binary tree
+   **  representation of event-sets or priority queues needed by splay trees
+   **
+   **  assumes that data and datb will be provided by the application
+   **  to hold all application specific information
+   **
+   **  assumes that key will be provided by the application, comparable
+   **  with the compare function applied to the addresses of two keys.
+ */
+
+#ifndef SPTREE_H
+#define SPTREE_H
+
+typedef struct _spblk {
+  struct _spblk *leftlink;
+  struct _spblk *rightlink;
+  struct _spblk *uplink;
+
+  univptr_t
+   key;                                /* formerly time/timetyp */
+  univptr_t
+   data;                       /* formerly aux/auxtype */
+  univptr_t
+   datb;
+} SPBLK;
+
+typedef struct {
+  SPBLK *root;                 /* root node */
+
+  /* Statistics, not strictly necessary, but handy for tuning  */
+
+  int
+   lookups;                    /* number of splookup()s */
+  int
+   lkpcmps;                    /* number of lookup comparisons */
+
+  int
+   enqs;                       /* number of spenq()s */
+  int
+   enqcmps;                    /* compares in spenq */
+
+  int
+   splays;
+  int
+   splayloops;
+
+} SPTREE;
+
+#if defined(__STDC__)
+#define __proto(x)      x
+#else
+#define __proto(x)      ()
+#endif
+
+/* sptree.c */
+/* init tree */
+extern SPTREE *__spinit __proto((void));
+/* find key in a tree */
+extern SPBLK *__splookup __proto((univptr_t, SPTREE *));
+/* enter an item, allocating or replacing */
+extern SPBLK *__spadd __proto((univptr_t, univptr_t, univptr_t, SPTREE *));
+/* scan forward through tree */
+extern void
+__spscan __proto((void (*)__proto((SPBLK *)), SPBLK *, SPTREE *));
+/* return tree statistics */
+extern char *__spstats __proto((SPTREE *));
+/* delete node from tree */
+extern void
+__spdelete __proto((SPBLK *, SPTREE *));
+
+#undef __proto
+
+#endif                         /* SPTREE_H */
+
+#ifndef __CSRI_TRACE_H__
+#define __CSRI_TRACE_H__
+extern void
+__m_install_record proto((univptr_t, const char *));
+extern void
+__m_delete_record proto((univptr_t));
+
+#define RECORD_FILE_AND_LINE(addr, fname, linenum) \
+        if (_malloc_leaktrace) { \
+                (void) sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum); \
+                __m_install_record(addr, _malloc_statsbuf); \
+        } else \
+                _malloc_leaktrace += 0
+
+#define DELETE_RECORD(addr) \
+        if (_malloc_leaktrace) \
+                __m_delete_record(addr); \
+        else \
+                _malloc_leaktrace += 0
+
+#endif /* __CSRI_TRACE_H__ */                  /* Do not add anything after this line */
+
+#include "confmagic.h"
+
+#ifdef CSRI_TRACE
+/* Tracing malloc definitions - helps find leaks */
+univptr_t
+trace__malloc _((size_t nbytes, const char *fname, int linenum));
+univptr_t
+trace__calloc _((size_t nelem, size_t elsize, const char *fname, int linenum));
+univptr_t
+trace__realloc _((univptr_t cp, size_t nbytes, const char *fname, int linenum));
+univptr_t trace__valloc _((size_t size, const char *fname, int linenum));
+univptr_t
+ trace__memalign
+_((size_t alignment, size_t size, const char *fname, int linenum));
+univptr_t trace__emalloc _((size_t nbytes, const char *fname, int linenum));
+univptr_t
+trace__ecalloc _((size_t nelem, size_t sz, const char *fname, int linenum));
+univptr_t
+ trace__erealloc
+_((univptr_t ptr, size_t nbytes, const char *fname, int linenum));
+char *trace__strdup _((const char *s, const char *fname, int linenum));
+char *trace__strsave _((const char *s, const char *fname, int linenum));
+void
+trace__free _((univptr_t cp, const char *fname, int linenum));
+void
+trace__cfree _((univptr_t cp, const char *fname, int linenum));
+#else                          /* CSRI_TRACE */
+univptr_t
+malloc _((size_t nbytes));
+univptr_t
+calloc _((size_t nelem, size_t elsize));
+univptr_t
+realloc _((univptr_t cp, size_t nbytes));
+univptr_t
+valloc _((size_t size));
+univptr_t
+memalign _((size_t alignment, size_t size));
+univptr_t
+emalloc _((size_t nbytes));
+univptr_t
+ecalloc _((size_t nelem, size_t sz));
+univptr_t
+erealloc _((univptr_t ptr, size_t nbytes));
+Free_t free _((univptr_t cp));
+Free_t cfree _((univptr_t cp));
+#endif                         /* CSRI_TRACE */
+
+int
+ __m_botch
+_((const char *s1, const char *s2, univptr_t p,
+   int is_end_ptr, const char *filename, int linenumber));
+void
+__m_prnode _((SPBLK * spblk));
+void
+mal_contents _((FILE * fp));
+#ifdef CSRI_DEBUG
+void
+mal_debug _((int level));
+int
+mal_verify _((int fullcheck));
+#endif
+void
+mal_dumpleaktrace _((FILE * fp));
+void
+mal_heapdump _((FILE * fp));
+void
+mal_leaktrace _((int value));
+void
+mal_sbrkset _((int n));
+void
+mal_slopset _((int n));
+#ifdef CSRI_PROFILESIZES
+void
+mal_statsdump _((FILE * fp));
+#endif
+void
+mal_trace _((int value));
+void
+mal_mmap _((char *fname));
+
+#ifdef CSRI_TRACE
+
+univptr_t
+trace__emalloc(nbytes, fname, linenum)
+    size_t nbytes;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = emalloc(nbytes);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+
+univptr_t
+trace__erealloc(ptr, nbytes, fname, linenum)
+    univptr_t
+     ptr;
+    size_t nbytes;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = erealloc(ptr, nbytes);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+univptr_t
+trace__ecalloc(nelem, sz, fname, linenum)
+    size_t nelem, sz;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = ecalloc(nelem, sz);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+
+/*
+ * These are the wrappers around malloc for detailed tracing and leak
+ * detection.  Allocation routines call RECORD_FILE_AND_LINE to record the
+ * filename/line number keyed on the block address in the splay tree,
+ * de-allocation functions call DELETE_RECORD to delete the specified block
+ * address and its associated file/line from the splay tree.
+ */
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+univptr_t
+trace__malloc(nbytes, fname, linenum)
+    size_t nbytes;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = malloc(nbytes);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+/* ARGSUSED if CSRI_TRACE is not defined */
+void
+trace__free(cp, fname, linenum)
+    univptr_t cp;
+    const char *fname;
+    int linenum;
+{
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  DELETE_RECORD(cp);
+  free(cp);
+}
+
+univptr_t
+trace__realloc(cp, nbytes, fname, linenum)
+    univptr_t
+     cp;
+    size_t nbytes;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t old;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  old = cp;
+  cp = realloc(cp, nbytes);
+  if (old != cp) {
+    DELETE_RECORD(old);
+    RECORD_FILE_AND_LINE(cp, fname, linenum);
+  }
+  return (cp);
+}
+
+univptr_t
+trace__calloc(nelem, elsize, fname, linenum)
+    size_t nelem, elsize;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = calloc(nelem, elsize);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+/* ARGSUSED if CSRI_TRACE is not defined */
+void
+trace__cfree(cp, fname, linenum)
+    univptr_t cp;
+    const char *fname;
+    int linenum;
+{
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  DELETE_RECORD(cp);
+  /* No point calling cfree() - it just calls free() */
+  free(cp);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+univptr_t
+trace__memalign(alignment, size, fname, linenum)
+    size_t alignment, size;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = memalign(alignment, size);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+univptr_t
+trace__valloc(size, fname, linenum)
+    size_t size;
+    const char *fname;
+    int
+     linenum;
+{
+  univptr_t cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = valloc(size);
+  RECORD_FILE_AND_LINE(cp, fname, linenum);
+  return (cp);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+char *
+trace__strdup(s, fname, linenum)
+    const char *s;
+    const char *fname;
+    int linenum;
+{
+  char *cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = (char *) strdup(s);
+  RECORD_FILE_AND_LINE((univptr_t) cp, fname, linenum);
+  return (cp);
+}
+
+char *
+trace__strsave(s, fname, linenum)
+    const char *s;
+    const char *fname;
+    int linenum;
+{
+  char *cp;
+
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "%s:%d:", fname, linenum));
+  cp = strsave(s);
+  RECORD_FILE_AND_LINE((univptr_t) cp, fname, linenum);
+  return (cp);
+}
+#endif                         /* CSRI_TRACE */
+int
+__nothing()
+{
+  return 0;
+}
+
+/*
+ * Simple botch routine - writes directly to stderr.  CAREFUL -- do not use
+ * printf because of the vile hack we use to redefine fputs with write for
+ * normal systems (i.e not super-pure ANSI)!
+ */
+int
+__m_botch(s1, s2, p, is_end_ptr, filename, linenumber)
+    const char *s1, *s2;
+    univptr_t p;
+    const char *filename;
+    int linenumber, is_end_ptr;
+{
+  static char linebuf[32];     /* Enough for a BIG linenumber! */
+  static int notagain = 0;
+
+  if (notagain == 0) {
+    (void) fflush(stderr);
+    (void) sprintf(linebuf, "%d: ", linenumber);
+    (void) fputs("memory corruption found, file ", stderr);
+    (void) fputs(filename, stderr);
+    (void) fputs(", line ", stderr);
+    (void) fputs(linebuf, stderr);
+    (void) fputs(s1, stderr);
+    if (s2 && *s2) {
+      (void) fputs(" ", stderr);
+      (void) fputs(s2, stderr);
+    }
+    (void) fputs("\n", stderr);
+    (void) __m_prblock(p, is_end_ptr, stderr);
+    /*
+     * In case stderr is buffered and was written to before we
+     * tried to unbuffer it
+     */
+    (void) fflush(stderr);
+    notagain++;                        /* just in case abort() tries to cleanup */
+    abort();
+  }
+  return 0;                    /* SHOULDNTHAPPEN */
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+int
+__m_prblock(p, is_end_ptr, fp)
+    univptr_t p;
+    int is_end_ptr;
+    FILE *fp;
+{
+  Word *blk = (Word *) p;
+  Word *blkend;
+  ulong tag;
+  ulong blksize;
+  char buf[512];               /* long enough for the sprintfs below */
+
+  if (blk == NULL)
+    return 0;
+
+  if (!PTR_IN_HEAP(blk)) {
+    sprintf(buf, "  ** pointer 0x%lx not in heap\n", (ulong) blk);
+    fputs(buf, fp);
+    return 0;
+  }
+  blksize = SIZE(blk);
+  tag = TAG(blk);
+  if (is_end_ptr) {
+    blkend = blk;
+    blk -= blksize - 1;
+  } else {
+    blkend = blk + blksize - 1;
+  }
+  (void) sprintf(buf, "  %s blk: 0x%lx to 0x%lx, %lu (0x%lx) words",
+                tag == FREE ? "Free" : "Allocated", (ulong) blk,
+                (ulong) blkend, blksize, blksize);
+  (void) fputs(buf, fp);
+  if (is_end_ptr && !PTR_IN_HEAP(blk)) {
+    sprintf(buf, "  ** start pointer 0x%lx not in heap\n", (ulong) blk);
+    fputs(buf, fp);
+    return 0;
+  }
+  if (!is_end_ptr && !PTR_IN_HEAP(blkend)) {
+    sprintf(buf, "  ** end pointer 0x%lx not in heap\n", (ulong) blk);
+    fputs(buf, fp);
+    return 0;
+  }
+  if (tag == FREE) {
+#ifdef CSRI_DEBUG
+    int i;
+#endif
+    int n;
+    char *cp;
+
+    (void) sprintf(buf, " next=0x%lx, prev=0x%lx\n",
+                  (ulong) NEXT(blkend), (ulong) PREV(blkend));
+    (void) fputs(buf, fp);
+    /* Make sure free block is filled with FREEMAGIC */
+    n = (blksize - FREE_OVERHEAD) * sizeof(Word);
+    cp = (char *) (blk + FREEHEADERWORDS);
+#ifdef CSRI_DEBUG
+    for (i = 0; i < n; i++, cp++) {
+      if (*cp != FREEMAGIC) {
+       (void) fputs("  ** modified after free().\n", fp);
+       break;
+      }
+    }
+#endif
+  } else {
+#ifdef CSRI_DEBUG
+    if (!TOO_SMALL(blk)) {
+      (void) sprintf(buf, " really %lu bytes\n", (ulong) REALSIZE(blk));
+      (void) fputs(buf, fp);
+    } else {
+      (void) fputs("\n", fp);
+    }
+#else
+    (void) fputs("\n", fp);
+#endif
+  }
+  if (TAG(blk) == FREE) {
+    if (!VALID_NEXT_PTR(blkend))
+      (void) fputs("  ** bad next pointer\n", fp);
+    if (!VALID_PREV_PTR(blkend))
+      (void) fputs("  ** bad prev pointer\n", fp);
+  } else {
+    if (!VALID_MAGIC(blk))
+      (void) fputs("  ** end of block overwritten\n", fp);
+  }
+  if (!VALID_START_SIZE_FIELD(blk)) {
+    sprintf(buf, "  ** bad size field: tags = 0x%x, 0x%x\n",
+           (unsigned int) SIZEFIELD(blk), (unsigned int) SIZEFIELD(blkend));
+    (void) fputs(buf, fp);
+    return 0;
+  }
+  (void) fflush(fp);
+  return 1;
+}
+
+/*
+ * Similar to malloc_verify except that it prints the heap as it goes along.
+ * The only ASSERTs in this routine are those that would cause it to wander
+ * off into the sunset because of corrupt tags.
+ */
+void
+mal_heapdump(fp)
+    FILE *fp;
+{
+  REGISTER Word *ptr;
+  REGISTER Word *blk;
+  REGISTER Word *blkend;
+  int i;
+  char buf[512];               /* long enough for the sprintfs below */
+
+  if (_malloc_loword == NULL) {        /* Nothing malloc'ed yet */
+    (void) fputs("Null heap - nothing malloc'ed yet\n", fp);
+    return;
+  }
+  (void) fputs("Heap printout:\n", fp);
+  (void) fputs("Free list rover pointers:\n", fp);
+  sprintf(buf, "  First non-null bin is %d\n", _malloc_firstbin);
+  (void) fputs(buf, fp);
+  for (i = 0; i < MAXBINS; i++) {
+    if ((ptr = _malloc_rovers[i]) == NULL)
+      continue;
+    (void) sprintf(buf, "  %d: 0x%lx\n", i, (ulong) ptr);
+    (void) fputs(buf, fp);
+    if (!PTR_IN_HEAP(ptr))
+      (void) fputs("  ** not in heap\n", fp);
+    if (!VALID_END_SIZE_FIELD(ptr))
+      (void) fputs("  ** bad end size field\n", fp);
+    if (!VALID_NEXT_PTR(ptr))
+      (void) fputs("  ** bad next pointer\n", fp);
+    if (!VALID_PREV_PTR(ptr))
+      (void) fputs("  ** bad prev pointer\n", fp);
+  }
+  if (_malloc_rovers[MAXBINS] != NULL) {
+    (void) sprintf(buf, "  ** rover terminator is 0x%lx, fixing\n",
+                  (ulong) _malloc_rovers[MAXBINS]);
+    (void) fputs(buf, fp);
+    _malloc_rovers[MAXBINS] = NULL;
+  }
+  for (ptr = _malloc_mem; ptr != NULL; ptr = ptr->next) {
+    /* print the arena */
+    (void) sprintf(buf, "Arena from 0x%lx to 0x%lx, %lu (0x%lx) words\n",
+                  (ulong) ptr, (ulong) (ptr + SIZE(ptr + 1)),
+                  (ulong) SIZE(ptr + 1) + 1, (ulong) SIZE(ptr + 1) + 1);
+    (void) fputs(buf, fp);
+    (void) sprintf(buf, "Next arena is 0x%lx\n", (ulong) ptr->next);
+    (void) fputs(buf, fp);
+    (void) fflush(fp);
+    ASSERT(SIZEFIELD(ptr + 1) == SIZEFIELD(ptr + SIZE(ptr + 1)),
+          "mal_dumpheap: corrupt malloc arena");
+    blkend = ptr + SIZE(ptr + 1);
+    for (blk = ptr + ARENASTART; blk < blkend; blk += SIZE(blk)) {
+      if (!__m_prblock((univptr_t) blk, 0, fp)) {
+       __m_botch("mal_dumpheap: corrupt block", "",
+                 (univptr_t) 0, 0, __FILE__, __LINE__);
+      }
+    }
+  }
+  (void) fputs("==============\n", fp);
+  (void) fflush(fp);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+/*
+ *  malloc which dies if it can't allocate enough storage.
+ */
+univptr_t
+emalloc(nbytes)
+    size_t nbytes;
+{
+  univptr_t cp = malloc(nbytes);
+
+  if (cp == 0) {
+    (void) fputs("No more memory for emalloc\n", stderr);
+#ifdef CSRI_DEBUG
+    (void) fflush(stderr);
+    abort();
+#else
+    exit(EXIT_FAILURE);
+#endif
+  }
+  return (cp);
+}
+
+/*
+ *  realloc which dies if it can't allocate enough storage.
+ */
+univptr_t
+erealloc(ptr, nbytes)
+    univptr_t
+     ptr;
+    size_t nbytes;
+{
+  univptr_t cp = realloc(ptr, nbytes);
+
+  if (cp == 0) {
+    (void) fputs("No more memory for erealloc\n", stderr);
+#ifdef CSRI_DEBUG
+    (void) fflush(stderr);
+    abort();
+#else
+    exit(EXIT_FAILURE);
+#endif
+  }
+  return (cp);
+}
+
+/*
+ *  calloc which dies if it can't allocate enough storage.
+ */
+univptr_t
+ecalloc(nelem, sz)
+    size_t nelem, sz;
+{
+  size_t nbytes = nelem * sz;
+  univptr_t cp = emalloc(nbytes);
+
+  (void) memset((univptr_t) cp, 0, (memsize_t) nbytes);
+  return (cp);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+/* gets memory from the system via the sbrk() system call.  Most Un*xes */
+univptr_t
+_mal_sbrk(nbytes)
+    size_t nbytes;
+{
+  static char *lastsbrk;
+  char *p;
+
+  p = (char *) sbrk((int) nbytes);
+  /*
+   * Some old SVR3 apparently have a kernel bug: after several sbrk
+   * calls, sbrk suddenly starts returning values lower than the ones it
+   * returned before.  Yeesh!
+   */
+  if (nbytes > 0) {
+    ASSERT(p > lastsbrk,
+          "system call error? sbrk returned value lower than previous calls");
+    lastsbrk = p;
+  }
+  return p;
+}
+
+/*
+ * gets memory from the system via mmaping a file.  This was written for SunOS
+ * versions greater than 4.0.  The filename is specified by the environment
+ * variable CSRIMALLOC_MMAPFILE or by the call to mal_mmapset().  Using this
+ * instead of sbrk() has the advantage of bypassing the swap system, allowing
+ * processes to run with huge heaps even on systems configured with small swap
+ * space.
+ */
+static char *mmap_filename;
+
+#ifdef HAVE_MMAP
+/* Sun gets size_t wrong, and these follow, thanks to my #defines! */
+#undef caddr_t
+#undef size_t
+#undef u_char
+
+univptr_t
+_mal_mmap(nbytes)
+    size_t nbytes;
+{
+  static struct {
+    int i_fd;
+    caddr_t i_data;
+    caddr_t i_end;
+    size_t i_size;
+    size_t i_alloced;
+  } mmf;
+  struct stat stbuf;
+
+  if (mmf.i_data != NULL) {
+    /* Already initialized & mmaped the file */
+    univptr_t p = mmf.i_data + mmf.i_alloced;
+
+    if ((char *) p + nbytes > mmf.i_end) {
+      errno = ENOMEM;
+      return (univptr_t) -1;
+    }
+    mmf.i_alloced += nbytes;
+    return p;
+  }
+  /*
+   * This code is run the first time the function is called, it opens
+   * the file and mmaps the
+   */
+  if (mmap_filename == NULL) {
+    mmap_filename = getenv("CSRIMALLOC_MMAPFILE");
+    if (mmap_filename == NULL) {
+      errno = ENOMEM;
+      return (univptr_t) -1;
+    }
+  }
+  mmf.i_fd = open(mmap_filename, O_RDWR, 0666);
+  if (mmf.i_fd < 0 || fstat(mmf.i_fd, &stbuf) < 0)
+    return (univptr_t) -1;
+  if (stbuf.st_size < nbytes) {
+    errno = ENOMEM;
+    return (univptr_t) -1;
+  }
+  mmf.i_size = stbuf.st_size;
+  mmf.i_data = mmap((caddr_t) 0, mmf.i_size, PROT_READ | PROT_WRITE,
+                   MAP_SHARED, mmf.i_fd, (off_t) 0);
+  if (mmf.i_data == (caddr_t) - 1)
+    return (univptr_t) -1;
+  mmf.i_end = mmf.i_data + mmf.i_size;
+  mmf.i_alloced = nbytes;
+  /* Advise vm system of random access pattern */
+  (void) madvise(mmf.i_data, mmf.i_size, MADV_RANDOM);
+  return mmf.i_data;
+}
+#else                          /* !HAVE_MMAP */
+univptr_t
+_mal_mmap(nbytes)
+    size_t nbytes __attribute__ ((__unused__));
+{
+  return (univptr_t) -1;
+}
+
+#endif                         /* HAVE_MMAP */
+
+void
+mal_mmap(fname)
+    char *fname;
+{
+  _malloc_memfunc = _mal_mmap;
+  mmap_filename = fname;
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+/* 
+ *  These routines provide an interface for tracing memory leaks. The
+ *  user can turn on leak tracing at any time by calling
+ *  mal_leaktrace(1), after which every block allocated by
+ *  _malloc()/_calloc()/_realloc()/_valloc()/_memalign() has a string
+ *  (containing the filename and linenumber of the routine invoking it)
+ *  stored in a database. When _free()/_cfree() is called on that block,
+ *  the record is deleted from the database. The user can call
+ *  mal_dumpleaktrace() to show the list of blocks allocated, and
+ *  where they were allocated. The location of leaks can usually be
+ *  detected from this.
+ */
+/*
+ *  The tree implementation used to store the blocks is a splay-tree,
+ *  using an implementation in C by Dave Brower (daveb@rtech.uucp),
+ *  translated from Douglas Jones' original Pascal. However, any data
+ *  structure that permits insert(), delete() and traverse()/apply() of
+ *  key, value pairs should be suitable. Only this file needs to be
+ *  changed.
+ */
+static SPTREE *sp = NULL;
+
+/*
+ *  num is a sequence number, incremented for ever block. min_num gets
+ *  set to num after every dumpleaktrace - subsequent dumps do not print
+ *  any blocks with sequence numbers less than min_num
+ */
+static unsigned long min_num = 0;
+static unsigned long num = 0;
+
+/*
+ * These are used by mal_contents to count number of allocated blocks and the
+ * number of bytes allocated.  Better way to do this is to walk the heap
+ * rather than scan the splay tree.
+ */
+static unsigned long nmallocs;
+static unsigned long global_nbytes;
+
+static FILE *dumpfp = NULL;
+
+/* 
+ *  Turns recording of FILE and LINE number of each call to
+ *  malloc/free/realloc/calloc/cfree/memalign/valloc on (if value != 0)
+ *  or off, (if value == 0)
+ */
+void
+mal_leaktrace(value)
+    int value;
+{
+  _malloc_leaktrace = (value != 0);
+  if (sp == NULL)
+    sp = __spinit();
+}
+
+/*
+ *  The routine which actually does the printing. I know it is silly to
+ *  print address in decimal, but sort doesn't read hex, so sorting the
+ *  printed data by address is impossible otherwise. Urr. The format is
+ *              FILE:LINE: sequence_number address_in_decimal (address_in_hex)
+ */
+void
+__m_prnode(spblk)
+    SPBLK *spblk;
+{
+  if ((unsigned long) spblk->datb < min_num)
+    return;
+  (void) sprintf(_malloc_statsbuf, "%s%8lu %8lu(0x%08lx)\n",
+                (char *) spblk->data, (unsigned long) spblk->datb,
+                (unsigned long) spblk->key, (unsigned long) spblk->key);
+  (void) fputs(_malloc_statsbuf, dumpfp);
+}
+
+/*
+ *  Dumps all blocks which have been recorded.
+ */
+void
+mal_dumpleaktrace(fp)
+    FILE *fp;
+{
+  dumpfp = fp;
+  __spscan(__m_prnode, (SPBLK *) NULL, sp);
+  (void) fflush(dumpfp);
+  min_num = num;
+}
+
+/*
+ *  Inserts a copy of a string keyed by the address addr into the tree
+ *  that stores the leak trace information. The string is presumably of
+ *  the form "file:linenumber:". It also stores a sequence number that
+ *  gets incremented with each call to this routine.
+ */
+void
+__m_install_record(addr, s)
+    univptr_t addr;
+    const char *s;
+{
+  num++;
+  (void) __spadd(addr, strsave(s), (char *) num, sp);
+}
+
+/* Deletes the record keyed by addr if it exists */
+void
+__m_delete_record(addr)
+    univptr_t addr;
+{
+  SPBLK *result;
+
+  if ((result = __splookup(addr, sp)) != NULL) {
+    free(result->data);
+    result->data = 0;
+    __spdelete(result, sp);
+  }
+}
+
+static void __m_count _((SPBLK * spblk));
+
+static void
+__m_count(spblk)
+    SPBLK *spblk;
+{
+  Word *p;
+
+  nmallocs++;
+  p = (Word *) spblk->key;
+  p -= HEADERWORDS;
+
+  CHECKALLOCPTR(p, "when checking in __m_count");
+  global_nbytes += SIZE(p) * sizeof(Word);
+  return;
+}
+
+void
+mal_contents(fp)
+    FILE *fp;
+{
+  nmallocs = 0;
+  global_nbytes = 0;
+  __spscan(__m_count, (SPBLK *) NULL, sp);
+  (void) sprintf(_malloc_statsbuf,
+                "%% %lu bytes %lu mallocs %p vm\n",
+                global_nbytes, nmallocs, sbrk(0));
+  (void) fputs(_malloc_statsbuf, fp);
+  (void) fflush(fp);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+/*
+ * GETBIN, UNLINK, LINK and CARVE are free-list maintenance macros used in
+ * several places.  A free-list is a doubly-linked list of non-contiguous
+ * blocks, marked by boundary tags that indicate the size.
+ */
+
+/* GETBIN returns a number such that i <= _malloc_binmax[bin] */
+#define GETBIN(i)   \
+        (((i) <= _malloc_binmax[3]) ? \
+                (((i) <= _malloc_binmax[1]) ? \
+                        (((i) <= _malloc_binmax[0]) ? 0 : 1) \
+                : \
+                        (((i) <= _malloc_binmax[2]) ? 2 : 3) \
+                ) \
+        : \
+                (((i) <= _malloc_binmax[5]) ? \
+                        (((i) <= _malloc_binmax[4]) ? 4 : 5) \
+                : \
+                        (((i) <= _malloc_binmax[6]) ? 6 : 7) \
+                ) \
+        )
+
+/* UNLINK removes the block 'ep' from the free list 'epbin' */
+#define UNLINK(ep, epbin) \
+        { \
+                REGISTER Word *epnext = NEXT(ep); \
+                if (ep == epnext) { \
+                        _malloc_rovers[epbin] = NULL; \
+                        if (_malloc_firstbin == epbin) \
+                                while (! _malloc_rovers[_malloc_firstbin] && \
+                                       _malloc_firstbin < MAXBINS-1) \
+                                        _malloc_firstbin++; \
+                } else { \
+                        REGISTER Word *epprev = PREV(ep); \
+                        NEXT(epprev) = epnext; \
+                        PREV(epnext) = epprev; \
+                        if (ep == _malloc_rovers[epbin]) \
+                                _malloc_rovers[epbin] = epprev; \
+                } \
+        }
+
+/*
+ * LINK adds the block 'ep' (psize words) to the free list 'epbin',
+ * immediately after the block pointed to by that bin's rover.
+ */
+#define LINK(ep, epsize, epbin) \
+        { \
+                REGISTER Word *epprev; \
+                REGISTER Word *eprover = _malloc_rovers[epbin]; \
+                \
+                if (eprover == NULL) { \
+                        _malloc_rovers[epbin] = eprover = epprev = ep; \
+                        if (_malloc_firstbin > epbin) \
+                                _malloc_firstbin = epbin; \
+                } else { \
+                        CHECKFREEPTR(eprover, "while checking rover"); \
+                        epprev = PREV(eprover); \
+                } \
+                NEXT(ep) = eprover; \
+                PREV(eprover) = ep; \
+                NEXT(epprev) = ep; \
+                PREV(ep) = epprev; /* PREV(eprover) */\
+                SIZEFIELD(ep) = SIZEFIELD(ep-epsize+1) = FREEMASK(epsize); \
+        }
+
+#define CARVE(ep, epsize, epbin, reqsize) \
+         { \
+                REGISTER size_t eprest = epsize - reqsize; \
+                int newepbin; \
+                \
+                if (eprest >= _malloc_minchunk) { \
+                        newepbin = (int)GETBIN(eprest); \
+                        if (newepbin != epbin) { \
+                                UNLINK(ep, epbin); \
+                                LINK(ep, eprest, newepbin); \
+                        } else { \
+                                SIZEFIELD(ep) = SIZEFIELD(ep-eprest+1) \
+                                        = FREEMASK(eprest); \
+                        } \
+                } else { \
+                        /* alloc the entire block */ \
+                        UNLINK(ep, epbin); \
+                        reqsize = epsize; \
+                } \
+         }
+
+static int grabhunk _((size_t));
+
+static int
+grabhunk(nwords)
+    size_t nwords;
+{
+  univptr_t cp;
+  size_t morecore;
+  Word *ptr;
+  size_t sbrkwords;
+  size_t blksize;
+  static char *spare;
+  static int nspare;
+
+  /* 
+   *  two words for fake boundary tags for the entire block, and one
+   *  for the next ptr of the block.
+   */
+#define EXCESS 3
+  sbrkwords = (size_t) (((nwords + EXCESS) / _malloc_sbrkunits + 1) *
+                       _malloc_sbrkunits);
+  morecore = sbrkwords * sizeof(Word) + SBRKEXTRA;
+  if ((cp = (*_malloc_memfunc) (morecore)) == (univptr_t) -1)
+    return (0);
+  /* 
+   * Should first GUARANTEE that what sbrk returns is aligned to
+   * Word boundaries - see align.h. Unfortunately, to guarantee
+   * that the pointer returned by sbrk is aligned on a word
+   * boundary, we must ask for sizeof(Word) -1 extra bytes, since
+   * we have no guarantee what other sbrk'ed blocks exist. (Sun
+   * sbrk always returns an aligned value, that is another story!)
+   * We use spare and nspare to keep track of the bytes wasted, so
+   * that we can try and reuse them later. If no other sbrk()s are
+   * called, then nspare rotates through the values 3, 2, 1, 0,
+   * and the first branch of the if() is always taken.
+   */
+  if ((spare + nspare) == (char *) cp) {
+    ptr = (Word *) SBRKALIGN(spare);
+    morecore += nspare;
+    sbrkwords = morecore / sizeof(Word);
+  } else {
+    ptr = (Word *) SBRKALIGN(cp);
+    morecore -= (char *) ptr - (char *) cp;
+  }
+  spare = (char *) (ptr + sbrkwords);
+  nspare = (morecore - sbrkwords * sizeof(Word));
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "sbrk %lu\n",
+                      (ulong) sbrkwords * sizeof(Word)));
+
+  /*
+   * If the new chunk adjoins _malloc_hiword, then _malloc_hiword
+   * need not be a fake boundary tag any longer, (its a real one) and
+   * the higher end of the block we sbrk'ed is the fake tag.  So we
+   * tag it appropriately, make the start of the block point to the
+   * old _malloc_hiword, and free it.  If we aren't next to
+   * _malloc_hiword, then someone else sbrk'ed in between, so we
+   * can't coalesce over the boundary anyway, in which case we just
+   * change _malloc_hiword to be in the new sbrk'ed block without
+   * damaging the old one. And we free the block.
+   */
+  if (ptr != _malloc_hiword + 1 || _malloc_mem == NULL) {
+    /* Non-contiguous sbrk'ed block, or first sbrk we've done. */
+    /* 
+     * First push this block on the stack of non-contiguous blocks
+     * we've sbrked. !! For real paranoia, we'd also check
+     * _malloc_mem...
+     */
+    REGISTER Word *tmp = _malloc_mem;
+
+    _malloc_mem = ptr;
+    ptr->next = tmp;
+    ptr++;
+    sbrkwords--;
+
+    _malloc_hiword = ptr;
+    if (_malloc_loword == NULL || _malloc_loword > ptr) {
+      /* First time - set lower bound. */
+      PRCSRI_TRACE(sprintf(_malloc_statsbuf, "heapstart 0x%lx\n", (ulong) ptr));
+      _malloc_loword = ptr;
+    }
+    /*
+     * Fake boundary tags to indicate the ends of an arena.
+     * Since they are marked as allocated, no attempt will be
+     * made to coalesce before or after them.
+     */
+    SIZEFIELD(ptr) = ALLOCED | sbrkwords;
+    _malloc_hiword += sbrkwords - 1;
+    PRCSRI_TRACE(sprintf(_malloc_statsbuf, "heapend 0x%lx\n",
+                        (ulong) _malloc_hiword));
+    SIZEFIELD(_malloc_hiword) = ALLOCED | sbrkwords;
+
+    /* * Subtract 2 for the special arena end tags. */
+    sbrkwords -= 2;
+    ptr++;
+    DMEMSET(ptr + FREEHEADERWORDS, sbrkwords - FREE_OVERHEAD);
+    ptr = _malloc_hiword - 1;
+    _malloc_lastbin = (int) GETBIN(sbrkwords);
+    LINK(ptr, sbrkwords, _malloc_lastbin)
+      _malloc_rovers[_malloc_lastbin] = ptr;
+    while (_malloc_rovers[_malloc_firstbin] == NULL &&
+          _malloc_firstbin < MAXBINS - 1)
+      _malloc_firstbin++;
+    return (1);
+  }
+  /*
+   * If we get here, then the sbrked chunk is contiguous, so we fake
+   * up the boundary tags and size to look like an allocated block
+   * and then call free()
+   */
+  ptr--;
+  blksize = SIZE(ptr) + sbrkwords;
+  SIZEFIELD(ptr) = ALLOCMASK(sbrkwords);
+  _malloc_hiword += sbrkwords;
+  SIZEFIELD(_malloc_hiword - 1) = SIZEFIELD(ptr);
+  /* Update special arena end tags of the memory chunk */
+  SIZEFIELD(_malloc_hiword) = ALLOCMASK(blksize);
+  SIZEFIELD(_malloc_hiword - blksize + 1) = ALLOCMASK(blksize);
+  SET_REALSIZE(ptr, (sbrkwords - ALLOC_OVERHEAD) * sizeof(Word));
+  free((univptr_t) (ptr + HEADERWORDS));
+  return (1);
+}
+
+univptr_t
+malloc(nbytes)
+    size_t nbytes;
+{
+  REGISTER Word *start, *search = NULL;
+  REGISTER Word *p;
+  REGISTER size_t required;
+  REGISTER size_t searchsize;
+  int bin;
+
+#ifdef SVID_MALLOC_0
+  /* SVID requires that malloc(0) return NULL, ick! */
+  if (nbytes == 0) {
+    errno = EINVAL;
+    return (NULL);
+  }
+#endif                         /* SVID_MALLOC_0 */
+
+  required = ALLOC_OVERHEAD + (nbytes + sizeof(Word) - 1) / sizeof(Word);
+  if (required < (size_t) _malloc_minchunk)
+    required = _malloc_minchunk;
+
+  searchsize = 0;
+  bin = (int) GETBIN(required);
+  if (bin < _malloc_firstbin)
+    bin = _malloc_firstbin;
+  /* typically, we expect to execute this loop only once */
+  while (searchsize < required && bin < MAXBINS) {
+    if ((search = _malloc_rovers[bin++]) == NULL) {
+      continue;
+    }
+    if (search == _malloc_hiword - 1) {
+      /* avoid final "wilderness" block */
+      CHECKFREEPTR(search, "while checking \"wilderness\" in malloc()");
+      search = NEXT(search);
+    }
+    start = search;
+    do {
+      CHECKFREEPTR(search, "while searching in malloc()");
+      searchsize = FREESIZE(search);
+      if (searchsize >= required) {
+       break;
+      } else {
+       search = NEXT(search);
+      }
+    } while (search != start);
+  }
+
+  if (searchsize < required) {
+    if (grabhunk(required) == 0) {
+      errno = ENOMEM;
+      return (NULL);
+    }
+    /*
+     * We made sure in grabhunk() or free() that
+     * _malloc_rovers[lastbin] is pointing to the newly sbrked
+     * (and freed) block.
+     */
+    bin = _malloc_lastbin;
+    search = _malloc_rovers[bin];
+    searchsize = FREESIZE(search);
+  } else if (bin > 0) {
+    bin--;
+  }
+  CARVE(search, searchsize, bin, required);
+  p = search - searchsize + 1;
+  SIZEFIELD(p) = SIZEFIELD(p + required - 1) = ALLOCMASK(required);
+  PRCSRI_TRACE(sprintf
+              (_malloc_statsbuf, "+ %lu %lu 0x%lx\n", (ulong) nbytes,
+               (ulong) (required - ALLOC_OVERHEAD) * sizeof(Word),
+               (ulong) (p + HEADERWORDS)));
+  COUNTSIZE(required);
+  SET_REALSIZE(p, nbytes);
+  return ((univptr_t) (p + HEADERWORDS));
+}
+
+
+
+Free_t
+free(cp)
+    univptr_t
+     cp;
+{
+  /* 
+   * This is where the boundary tags come into their own. The
+   * boundary tag guarantees a constant time insert with full
+   * coalescing (the time varies slightly for the four case possible,
+   * but still, essentially a very fast free.
+   */
+  /*
+   *  P0 is the block being freed. P1 is the pointer to the block
+   *  before the block being freed, and P2 is the block after it.
+   *  We can either coalesce with P1, P2, both, or neither
+   */
+  REGISTER Word *p0, *p1, *p2;
+  REGISTER size_t sizep0;
+  int bin, oldbin = -1;
+
+  if (cp == NULL)
+#ifdef INT_FREE
+    return 1;
+#else
+    return;
+#endif
+
+  p0 = (Word *) cp;
+  p0 -= HEADERWORDS;
+
+  CHECKALLOCPTR(p0, "passed to free()");
+  /* With debugging, the CHECKALLOCPTR would have already aborted */
+  if (TAG(p0) == FREE) {
+    errno = EINVAL;
+#ifdef INT_FREE
+    return 0;
+#else
+    return;
+#endif
+  }
+  /*
+   * clear the entire block that used to be p0's, just in case
+   * someone tries to refer to it or anything in it again.  We leave
+   * the end tags alone for now - we'll smash them individually
+   * depending on the way p0 merges with p1 and/or p2.
+   */
+  sizep0 = SIZE(p0);
+  DMEMSET(p0 + FREEHEADERWORDS, sizep0 - FREE_OVERHEAD);
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "- %lu 0x%lx\n",
+                      (ulong) (sizep0 - ALLOC_OVERHEAD) * sizeof(Word),
+                      (ulong) (p0 + HEADERWORDS)));
+
+  p1 = p0 - 1;
+  /*
+   * p0 now points to the end of the block -- we start treating it as
+   * a free block
+   */
+  p0 += sizep0 - 1;
+  p2 = p0 + 1;
+
+  /*
+   * We can't match the SIZEFIELDs of p1/p2 with p1/p2 + SIZE(p1/p2)
+   * -1 because they might be a fake tag to indicate the bounds of
+   * the arena. Further, we should only check p1 if p0-1 is not the
+   * _malloc_loword or an arena bound - else p1 is probably not a
+   * valid pointer. If tag p0-1 is allocated, then it could be an
+   * arena bound.
+   */
+
+  if (TAG(p2) == FREE) {
+    /*
+     * Aha - block p2 (physically after p0) is free.  Merging
+     * p0 with p2 merely means increasing p2's size to
+     * incorporate p0 - no other pointer shuffling needed.
+     * We'll move it to the right free-list later, if necessary.
+     */
+    p2 += FREESIZE(p2) - 1;
+    oldbin = (int) GETBIN(FREESIZE(p2));
+    CHECKFREEPTR(p2, "while checking p2 in free()");
+    sizep0 += FREESIZE(p2);
+    SIZEFIELD(p2 - sizep0 + 1) = SIZEFIELD(p2) = FREEMASK(sizep0);
+    /*  Smash p0's old end tag and p2's old start tag. */
+    DMEMSET(p0 - FREETRAILERWORDS + 1, FREETRAILERWORDS + FREEHEADERWORDS);
+    p0 = p2;                   /* p0 just vanished - became part of p2 */
+  }
+  if (TAG(p1) == FREE) {
+    /*
+     * The block p1 (physically precedes p0 in memory) is free.
+     * We grow p0 backward to absorb p1 and delete p1 from its
+     * free list, since it no longer exists.
+     */
+    CHECKFREEPTR(p1, "while checking p1 in free()");
+    sizep0 += FREESIZE(p1);
+    bin = (int) GETBIN(FREESIZE(p1));
+    UNLINK(p1, bin);
+    SIZEFIELD(p0 - sizep0 + 1) = SIZEFIELD(p0) = FREEMASK(sizep0);
+    /*
+     * smash the free list pointers in p1 (SIZE, NEXT, PREV) to
+     * make sure no one refers to them again. We cannot smash
+     * the start boundary tag because it becomes the start tag
+     * for the new block.  Also trash p0's start tag.
+     */
+    DMEMSET(p1 - FREETRAILERWORDS + 1, FREETRAILERWORDS + FREEHEADERWORDS);
+  }
+  bin = (int) GETBIN(sizep0);
+  if (oldbin != bin) {
+    /*
+     * If we're here, it means block P0 needs to be inserted in
+     * the correct free list, either because it didn't merge
+     * with anything, or because it merged with p1 so we
+     * deleted p1, or it merged with p2 and grew out p2's
+     * existing free-list.
+     */
+    if (oldbin >= 0) {
+      /* merged with P2, still in P2's free-list */
+      UNLINK(p0, oldbin);
+    }
+    LINK(p0, sizep0, bin);
+    _malloc_lastbin = bin;
+    _malloc_rovers[bin] = p0;
+  }
+  CHECKHEAP();
+#ifdef INT_FREE
+  return 0;
+#else
+  return;
+#endif
+}
+
+
+/*
+ *  WARNING: This realloc() IS *NOT* upwards compatible with the
+ *  convention that the last freed block since the last malloc may be
+ *  realloced. Allegedly, this was because the old free() didn't
+ *  coalesce blocks, and reallocing a freed block would perform the
+ *  compaction. Yuk!
+ */
+univptr_t
+realloc(cp, nbytes)
+    univptr_t
+     cp;
+    size_t nbytes;
+{
+  REGISTER Word *p0 = (Word *) cp;
+  REGISTER Word *p1;
+  univptr_t tmp;
+  REGISTER size_t required;
+  REGISTER size_t sizep0;
+  int bin;
+
+  if (p0 == NULL)
+    return (malloc(nbytes));
+
+  if (nbytes == 0) {
+    free(cp);
+    return (NULL);
+  }
+  required = ALLOC_OVERHEAD + (nbytes + sizeof(Word) - 1) / sizeof(Word);
+  if (required < (size_t) _malloc_minchunk)
+    required = _malloc_minchunk;
+
+  p0 -= HEADERWORDS;
+
+  CHECKALLOCPTR(p0, "passed to realloc()");
+  /* With debugging, the CHECKALLOCPTR would have already aborted */
+  if (TAG(p0) == FREE) {
+    errno = EINVAL;
+    return (NULL);
+  }
+  sizep0 = SIZE(p0);
+  if (sizep0 >= required) {
+    /* Shrinking the block */
+    size_t after = sizep0 - required;
+
+    SET_REALSIZE(p0, nbytes);
+    if (after < _malloc_minchunk) {
+      /*
+       * Not enough to free what's left so we return the
+       * block intact - print no-op for neatness in
+       * trace output.
+       */
+      PRCSRI_TRACE(strcpy(_malloc_statsbuf, "no-op\n"));
+      return (cp);
+    }
+    SIZEFIELD(p0 + required - 1) = SIZEFIELD(p0) = ALLOCMASK(required);
+    p0 += required;
+    /*
+     * We free what's after the block - mark it alloced and
+     * throw it to free() to figure out whether to merge it
+     * with what follows...
+     */
+    SIZEFIELD(p0 + after - 1) = SIZEFIELD(p0) = ALLOCMASK(after);
+    SET_REALSIZE(p0, (after - ALLOC_OVERHEAD) * sizeof(Word));
+    free((univptr_t) (p0 + HEADERWORDS));
+    return (cp);
+  }
+  /*
+   * If we get here, then we are growing the block p0 to something
+   * bigger.
+   */
+  p1 = p0 + sizep0;
+  required -= sizep0;
+  if (TAG(p1) != FREE || FREESIZE(p1) < required) {
+    /* Have to do it the hard way: block after us cannot be used */
+    tmp = malloc(nbytes);
+    if (tmp != NULL) {
+      MEMCPY(tmp, cp, ((SIZE(p0) - ALLOC_OVERHEAD)));
+      free(cp);
+    }
+    return (tmp);
+  }
+  /*
+   * block after us is free and big enough to provide the required
+   * space, so we grow into that block.
+   */
+  p1 += FREESIZE(p1) - 1;
+  CHECKFREEPTR(p1, "while checking p1 in realloc()");
+  bin = (int) GETBIN(FREESIZE(p1));
+  CARVE(p1, FREESIZE(p1), bin, required);
+  sizep0 += required;
+  SIZEFIELD(p0) = SIZEFIELD(p0 + sizep0 - 1) = ALLOCMASK(sizep0);
+  SET_REALSIZE(p0, nbytes);
+  PRCSRI_TRACE(sprintf(_malloc_statsbuf, "++ %lu %lu 0x%lx\n",
+                      (ulong) nbytes,
+                      (ulong) (sizep0 - ALLOC_OVERHEAD) * sizeof(Word),
+                      (ulong) cp));
+  CHECKHEAP();
+  return (cp);
+}
+
+
+
+/*
+ *  !! Given what we know about alignment, we should be able to do better
+ *  than memset and set words. Hopefully memset has been tuned.
+ */
+univptr_t
+calloc(nelem, elsize)
+    size_t nelem, elsize;
+{
+  REGISTER size_t nbytes = nelem * elsize;
+  REGISTER univptr_t cp = malloc(nbytes);
+
+  if (cp)
+    (void) memset((univptr_t) cp, 0, (memsize_t) nbytes);
+  return (cp);
+}
+
+
+/*
+ *  Why would anyone want this.... ?
+ */
+Free_t
+cfree(cp)
+    univptr_t
+     cp;
+{
+#ifdef INT_FREE
+  return free(cp);
+#else
+  free(cp);
+  return;
+#endif
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+/* 
+ * !! memalign may leave small (< _malloc_minchunk) blocks as garbage.
+ * Not worth fixing now -- I've only seen two applications call valloc()
+ * or memalign(), and they do it only once in their life.
+ */
+/* 
+ * This is needed to be compatible with Sun mallocs - Dunno how many
+ * programs need it - the X server sure does... Returns a block 'size'
+ * bytes long, such that the address is a multiple of 'alignment'.
+ * (alignment MUST be a power of 2). This routine is possibly more
+ * convoluted than free() - certainly uglier. Since it is rarely called
+ * - possibly once in a program, it should be ok.  Since this is called
+ * from valloc() which is usually needed in conjunction with
+ * mmap()/munmap(), note the comment in the Sun manual page about
+ * freeing segments of size 128K and greater. Ugh.
+ */
+univptr_t
+memalign(alignment, size)
+    size_t alignment, size;
+{
+  univptr_t cp;
+  univptr_t addr;
+  REGISTER Word *p0, *p1;
+  REGISTER size_t before, after;
+  size_t blksize;
+#ifdef CSRI_DEBUG
+  int tmp_debugging = _malloc_debugging;
+#endif                         /* CSRI_DEBUG */
+
+  if (alignment < sizeof(int) || !is_power_of_2(alignment) || size == 0) {
+    errno = EINVAL;
+    return (NULL);
+  }
+  if (alignment < sizeof(Word))
+    return (malloc(size));     /* We guarantee this alignment anyway */
+  /*
+   * Life starts to get complicated - need to get a block large
+   * enough to hold a block 'size' long, starting on an 'alignment'
+   * boundary
+   */
+  if ((cp = malloc((size_t) (size + alignment - 1))) == NULL)
+    return (NULL);
+  addr = SIMPLEALIGN(cp, alignment);
+  /*
+   * This is all we really need - can go back now, except that we
+   * might be wasting 'alignment - 1' bytes, which can be large since
+   * this junk is usually called to align with things like pagesize.
+   * So we try to push any free space before 'addr' and after 'addr +
+   * size' back on the free list by making the memaligned chunk
+   * ('addr' to 'addr + size') a block, and then doing stuff with the
+   * space left over - either making them free blocks or coalescing
+   * them whichever way is simplest. This usually involves making
+   * them look like allocated blocks and calling free() which has all
+   * the code to deal with this, and should do it reasonably fast.
+   */
+  p0 = (Word *) cp;
+  p0 -= HEADERWORDS;
+  /*
+   * p0 now points to the word tag starting the block which we got
+   * from malloc. This remains invariant from now on - p1 is our
+   * temporary pointer
+   */
+  p1 = (Word *) addr;
+  p1 -= HEADERWORDS;
+  blksize = (size + sizeof(Word) - 1) / sizeof(Word);
+  before = p1 - p0;
+  after = SIZE(p0) - ALLOC_OVERHEAD - blksize - before;
+  /*
+   *  p1 now points to the word before addr - this is going to be the
+   *  start of the memaligned block
+   */
+  if (after < _malloc_minchunk) {
+    /*
+     * We merge the extra space after the memaligned block into
+     * it since that space isn't enough for a separate block.
+     * Note that if the block after the one that malloc
+     * returned is free, we might be able to merge the space
+     * into that block even if it is too small - unfortunately,
+     * free() won't accept a block of this size, and I don't
+     * want to do that code here, so we'll just let it go to
+     * waste in the memaligned block. !! fix later, maybe
+     */
+    blksize += after;
+    after = 0;
+  }
+  /*
+   * We mark the newly carved memaligned block p1 as alloced. addr is
+   * (p1 + 1) which is the address we'll return
+   */
+  SIZEFIELD(p1) = ALLOCMASK(blksize + ALLOC_OVERHEAD);
+  SIZEFIELD(p1 + blksize + ALLOC_OVERHEAD - 1) = SIZEFIELD(p1);
+  SET_REALSIZE(p1, size);
+  if (after > 0) {
+    /* We can now free the block after the memaligned block. */
+    p1 += blksize + ALLOC_OVERHEAD;    /* SIZE(p1) */
+    /*
+     * p1 now points to the space after the memaligned block. we
+     * fix the size, mark it alloced, and call free - the block
+     * after this may be free, which isn't simple to coalesce - let
+     * free() do it.
+     */
+    SIZEFIELD(p1) = ALLOCMASK(after);
+    SIZEFIELD(p1 + after - 1) = SIZEFIELD(p1);
+    SET_REALSIZE(p1, (after - ALLOC_OVERHEAD) * sizeof(Word));
+#ifdef CSRI_DEBUG
+    /*
+     * Full heap checking will break till we finish memalign
+     * because the tags aren't all correct yet, but we still
+     * call free().
+     */
+    _malloc_debugging = 0;
+#endif                         /* CSRI_DEBUG */
+    free((univptr_t) (p1 + HEADERWORDS));
+  }
+  if (addr != cp) {
+    /*
+     *  If what's 'before' is large enough to be freed, add p0 to
+     *  free list after changing its size to just consist of the
+     *  space before the memaligned block, also setting the
+     *  alloced flag. Then call free() -- may merge with preceding
+     *  block. (block after it is the memaligned block)
+     */
+    /*
+     * Else the space before the block is too small to form a
+     * free block, and the preceding block isn't free, so we
+     * aren't touching it. Theoretically, we could put it in
+     * the preceding alloc'ed block, but there are painful
+     * complications if this is the start of the arena. We
+     * pass, but MUST mark it as allocated. This sort of garbage
+     * can split up the arena -- fix later with special case
+     * maybe?!!
+     */
+    p1 = p0;
+    SIZEFIELD(p1) = ALLOCMASK(before);
+    SIZEFIELD(p1 + before - 1) = SIZEFIELD(p1);
+    if (!TOO_SMALL(p1)) {
+      SET_REALSIZE(p1, (before - ALLOC_OVERHEAD) * sizeof(Word));
+    }
+    if (before >= FREE_OVERHEAD) {
+      free(cp);
+    }
+  }
+#ifdef CSRI_DEBUG
+  _malloc_debugging = tmp_debugging;
+#endif                         /* CSRI_DEBUG */
+  return (addr);
+}
+
+/* Just following the Sun manual page here */
+univptr_t
+valloc(size)
+    size_t size;
+{
+  static size_t pagesz = 0;
+
+  if (pagesz == 0)
+    pagesz = (size_t) getpagesize();
+  return (memalign(pagesz, size));
+}
+
+/* Set various malloc options */
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+/* 
+ *  Sets debugging level - level 0 and 1 both perform normal checking -
+ *  making sure a pointer is valid before it is used for any heap data,
+ *  and doing consistency checking on any block it touches while it
+ *  works. Level 2 asks for a mal_verify() on every malloc(), free() or
+ *  realloc(), thus checking the entire heap's pointers for consistency.
+ *  Level 3 makes mal_verify() check that all free blocks contain a
+ *  magic pattern that is put into a free block when it is freed.
+ */
+#ifdef CSRI_DEBUG
+void
+mal_debug(level)
+    int level;
+{
+  if (level < 0 || level > 3) {
+    return;
+  }
+  _malloc_debugging = level;
+}
+#endif                         /* CSRI_DEBUG */
+
+/*
+ *  Allows you to control the number of system calls made, which might
+ *  be helpful in a program allocating a lot of memory - call this once
+ *  to set a number big enough to contain all the allocations. Or for
+ *  very little allocation, so that you don't get a huge space just
+ *  because you alloc'e a couple of strings
+ */
+void
+mal_sbrkset(n)
+    int n;
+{
+  if (n < (int) (_malloc_minchunk * sizeof(Word))) {
+    /* sbrk'ing anything less than a Word isn't a great idea. */
+    return;
+  }
+  _malloc_sbrkunits = (n + sizeof(Word) - 1) / sizeof(Word);
+  return;
+}
+
+/* 
+ *  Since the minimum size block allocated is sizeof(Word)*_malloc_minchunk,
+ *  adjusting _malloc_minchunk is one way to control
+ *  memory fragmentation, and if you do a lot of mallocs and frees of
+ *  objects that have a similar size, then a good way to speed things up
+ *  is to set _malloc_minchunk such that the minimum size block covers
+ *  most of the objects you allocate
+ */
+void
+mal_slopset(n)
+    int n;
+{
+  if (n < 0) {
+    return;
+  }
+  _malloc_minchunk = (n + sizeof(Word) - 1) / sizeof(Word) + FIXEDOVERHEAD;
+  return;
+}
+
+/*
+ *  Turns tracing on (if value != 0) or off, (if value == 0)
+ */
+void
+mal_trace(value)
+    int value;
+{
+  if (value) {
+    /* 
+     *  Write something to the stats file so stdio can initialize
+     *  its buffers i.e. call malloc() at least once while tracing
+     *  is off, if the unbuffering failed.
+     */
+    (void) fputs("Malloc tracing starting\n", stderr);
+    _malloc_tracing = 1;
+    if (_malloc_loword != NULL) {
+      /*
+       * malloc happened before tracing turned on, so make
+       * sure we print the heap start for xmem analysis.
+       */
+      PRCSRI_TRACE(sprintf(_malloc_statsbuf, "heapstart 0x%lx\n",
+                          (ulong) _malloc_loword));
+    }
+  } else {
+    /* For symmetry */
+    (void) fputs("Malloc tracing stopped\n", stderr);
+    _malloc_tracing = 0;
+  }
+  (void) fflush(stderr);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+/*
+ *  Dumps the distribution of allocated sizes we've gathered so far
+ */
+#ifdef CSRI_PROFILESIZES
+void
+mal_statsdump(fp)
+    FILE *fp;
+{
+  int i;
+  char buf[128];
+
+  for (i = 1; i < MAXPROFILESIZE; i++) {
+    if (_malloc_scount[i] > 0) {
+      (void) sprintf(buf, "%lu: %lu\n", (ulong) i * sizeof(Word),
+                    (ulong) _malloc_scount[i]);
+      (void) fputs(buf, fp);
+      _malloc_scount[i] = 0;
+    }
+  }
+  if (_malloc_scount[0] > 0) {
+    (void) sprintf(buf, ">= %lu: %lu\n",
+                  (ulong) MAXPROFILESIZE * sizeof(Word),
+                  (ulong) _malloc_scount[0]);
+    (void) fputs(buf, fp);
+    _malloc_scount[0] = 0;
+  }
+  (void) fflush(fp);
+}
+#endif                         /* CSRI_PROFILESIZES */
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+
+
+
+
+/* 
+ *  makes a copy of a null terminated string in malloc'ed storage. Dies
+ *  if enough memory isn't available or there is a malloc error
+ */
+char *
+strsave(s)
+    const char *s;
+{
+  if (s)
+    return (strcpy((char *) emalloc((size_t) (strlen(s) + 1)), s));
+  else
+    return ((char *) NULL);
+}
+
+/*  Author: Mark Moraes <moraes@csri.toronto.edu> */
+
+/*LINTLIBRARY */
+
+
+/*
+ *  Goes through the entire heap checking all pointers, tags for
+ *  consistency. Should catch most casual heap corruption (overwriting
+ *  the end of a malloc'ed chunk, etc..) Nonetheless, heap corrupters
+ *  tend to be devious and ingenious in ways they corrupt heaps (Believe
+ *  me, I know:-). We should probably do the same thing if CSRI_DEBUG is not
+ *  defined, but return 0 instead of aborting. If fullcheck is non-zero,
+ *  it also checks that free blocks contain the magic pattern written
+ *  into them when they were freed to make sure the program is not still
+ *  trying to access those blocks.
+ */
+#ifdef CSRI_DEBUG
+int
+mal_verify(fullcheck)
+    int fullcheck;
+{
+  REGISTER Word *ptr, *p, *blk, *blkend;
+  int i;
+
+  if (_malloc_loword == NULL)  /* Nothing malloc'ed yet */
+    return (0);
+
+  for (i = 0; i < MAXBINS; i++) {
+    ptr = _malloc_rovers[i];
+    if (ptr != NULL) {
+      ASSERT(i >= _malloc_firstbin, "firstbin too high");
+      CHECKFREEPTR(ptr, "when verifying ROVER pointer");
+    }
+  }
+  ASSERT(_malloc_rovers[MAXBINS] == NULL, "ROVER terminator not NULL");
+  for (ptr = _malloc_mem; ptr != NULL; ptr = ptr->next) {
+    /*
+     *  Check arena bounds - not same as checking block tags,
+     *  despite similar appearance of the test
+     */
+    ASSERT(SIZEFIELD(ptr + 1) == SIZEFIELD(ptr + SIZE(ptr + 1)),
+          "corrupt malloc arena");
+    blkend = ptr + SIZE(ptr + 1);
+    for (blk = ptr + ARENASTART; blk < blkend; blk += SIZE(blk)) {
+      ASSERT_SP(PTR_IN_HEAP(blk), "corrupt pointer", "", blk);
+      ASSERT_SP(VALID_START_SIZE_FIELD(blk), "corrupt SIZE field", "", blk);
+      if (TAG(blk) == FREE) {
+       p = blk + FREESIZE(blk) - 1;
+       ASSERT_EP(VALID_NEXT_PTR(p), "corrupt NEXT pointer", "", p);
+       ASSERT_EP(VALID_PREV_PTR(p), "corrupt PREV pointer", "", p);
+       if (fullcheck) {
+         /* Make sure all free blocks are filled with FREEMAGIC */
+         int n;
+         char *cp;
+
+         n = (SIZE(blk) - FREE_OVERHEAD) * sizeof(Word);
+         cp = (char *) (blk + FREEHEADERWORDS);
+         for (i = 0; i < n; i++, cp++) {
+           ASSERT_SP(*cp == FREEMAGIC,
+                     "free block modified after free()", "", blk);
+         }
+       }
+      } else {
+       ASSERT_SP(VALID_MAGIC(blk), "overwritten end of block", "", blk);
+      }
+    }
+  }
+  return (0);
+}
+#endif                         /* CSRI_DEBUG */
+
+/*
+ *  This file contains a few splay tree routines snarfed from David
+ *  Brower's package, with globals renamed to keep them internal to the
+ *  malloc, and not clash with similar routines that the application may
+ *  use. The comments have been left with the original names - most of
+ *  the renaming just involved prepending an __ before the name -
+ *  spinstall got remapped to __spadd. Function prototypes added for
+ *  external declarations. - Mark Moraes.
+ */
+/*
+ * spdaveb.c -- daveb's new splay tree functions.
+ *
+ * The functions in this file provide an interface that is nearly
+ * the same as the hash library I swiped from mkmf, allowing
+ * replacement of one by the other.  Hey, it worked for me!
+ *
+ * splookup() -- given a key, find a node in a tree.
+ * spinstall() -- install an item in the tree, overwriting existing value.
+ * spfhead() -- fast (non-splay) find the first node in a tree.
+ * spscan() -- forward scan tree from the head.
+ * spfnext() -- non-splaying next.
+ * spstats() -- make char string of stats for a tree.
+ *
+ * Written by David Brower, daveb@rtech.uucp 1/88. (now daveb@acm.org)
+ */
+/*LINTLIBRARY */
+
+/* we keep a private free list of these blocks. */
+#define MAXFREESPBLKS       1024
+
+#if defined(__STDC__) && defined(ANSI_TYPES)
+#endif
+
+
+#define COMPARE(a,b)    (((char *) (a)) - ((char *) (b)))
+
+
+/* insert item into the tree */
+static SPBLK *spenq proto((SPBLK *, SPTREE *));
+/* return and remove lowest item in subtree */
+static SPBLK *spdeq proto((SPBLK **));
+/* reorganize tree */
+static void splay proto((SPBLK *, SPTREE *));
+/* fast non-splaying head */
+static SPBLK *spfhead proto((SPTREE *));
+/* fast non-splaying next */
+static SPBLK *spfnext proto((SPBLK *));
+
+/* USER SUPPLIED! */
+
+extern univptr_t emalloc proto((size_t));
+
+#if 0
+static SPBLK *
+spmalloc(q)
+    REGISTER SPTREE *q;
+{
+  REGISTER SPBLK *p = q->qfree;
+  REGISTER int i;
+
+  if (p != NULL) {
+    q->qfree = p->leftlink;
+    p->leftlink = 0;
+    q->nfree--;
+    return p;
+  }
+  return (SPBLK *) emalloc(sizeof(SPBLK));
+}
+
+static void
+spfree(n, q)
+    REGISTER SPBLK *n;
+    REGISTER SPTREE *q;
+{
+  if (q->nfree > MAXFREESPBLKS) {
+    (void) free((univptr_t) n);
+  } else {
+    n->leftlink = q->qfree;
+    q->qfree = n;
+    q->nfree++;
+    n->rightlink = n->uplink = 0;
+    n->key = n->data = n->datb = 0;
+  }
+}
+#else
+#define spmalloc(q) ((SPBLK *) emalloc(sizeof(SPBLK)))
+#define spfree(n,q) ((void) free((univptr_t)n))
+#endif
+\f
+
+/*----------------
+ *
+ * splookup() -- given key, find a node in a tree.
+ *
+ *      Splays the found node to the root.
+ */
+SPBLK *
+__splookup(key, q)
+    REGISTER univptr_t key;
+    REGISTER SPTREE *q;
+
+{
+  REGISTER SPBLK *n;
+  REGISTER int Sct;
+  REGISTER int c;
+
+  /* find node in the tree */
+  n = q->root;
+  c = ++(q->lkpcmps);
+  q->lookups++;
+  while (n && (Sct = COMPARE(key, n->key))) {
+    c++;
+    n = (Sct < 0) ? n->leftlink : n->rightlink;
+  }
+  q->lkpcmps = c;
+
+  /* reorganize tree around this node */
+  if (n != NULL)
+    splay(n, q);
+
+  return (n);
+}
+\f
+
+
+/*----------------
+ *
+ * spinstall() -- install an entry in a tree, overwriting any existing node.
+ *
+ *      If the node already exists, replace its contents.
+ *      If it does not exist, then allocate a new node and fill it in.
+ */
+
+SPBLK *
+__spadd(key, data, datb, q)
+    REGISTER univptr_t key;
+    REGISTER univptr_t data;
+    REGISTER univptr_t datb;
+    REGISTER SPTREE *q;
+
+{
+  REGISTER SPBLK *n;
+
+  if (NULL == (n = __splookup(key, q))) {
+    n = spmalloc(q);
+    n->key = (univptr_t) key;
+    n->leftlink = NULL;
+    n->rightlink = NULL;
+    n->uplink = NULL;
+    (void) spenq(n, q);
+  }
+  n->data = data;
+  n->datb = datb;
+
+  return (n);
+}
+\f
+
+
+
+/*----------------
+ *
+ * spfhead() -- return the "lowest" element in the tree.
+ *
+ *      returns a reference to the head event in the event-set q.
+ *      avoids splaying but just searches for and returns a pointer to
+ *      the bottom of the left branch.
+ */
+static SPBLK *
+spfhead(q)
+    REGISTER SPTREE *q;
+
+{
+  REGISTER SPBLK *x;
+
+  if (NULL != (x = q->root))
+    while (x->leftlink != NULL)
+      x = x->leftlink;
+
+  return (x);
+
+}                              /* spfhead */
+
+
+
+/*----------------
+ *
+ * spscan() -- apply a function to nodes in ascending order.
+ *
+ *      if n is given, start at that node, otherwise start from
+ *      the head.
+ */
+
+void
+__spscan(f, n, q)
+    REGISTER void (*f) (SPBLK *);
+    REGISTER SPBLK *n;
+    REGISTER SPTREE *q;
+{
+  REGISTER SPBLK *x;
+
+  for (x = n != NULL ? n : spfhead(q); x != NULL; x = spfnext(x))
+    (*f) (x);
+}
+
+
+/*----------------
+ *
+ * spfnext() -- fast return next higer item in the tree, or NULL.
+ *
+ *      return the successor of n in q, represented as a splay tree.
+ *      This is a fast (on average) version that does not splay.
+ */
+static SPBLK *
+spfnext(n)
+    REGISTER SPBLK *n;
+
+{
+  REGISTER SPBLK *next;
+  REGISTER SPBLK *x;
+
+  /* a long version, avoids splaying for fast average,
+   * poor amortized bound
+   */
+
+  if (n == NULL)
+    return (n);
+
+  x = n->rightlink;
+  if (x != NULL) {
+    while (x->leftlink != NULL)
+      x = x->leftlink;
+    next = x;
+  } else {                     /* x == NULL */
+    x = n->uplink;
+    next = NULL;
+    while (x != NULL) {
+      if (x->leftlink == n) {
+       next = x;
+       x = NULL;
+      } else {
+       n = x;
+       x = n->uplink;
+      }
+    }
+  }
+
+  return (next);
+
+}                              /* spfnext */
+
+
+char *
+__spstats(q)
+    SPTREE *q;
+{
+  static char buf[128];
+  float llen;
+  float elen;
+  float sloops;
+
+  if (q == NULL)
+    return ((char *) "");
+
+  llen = q->lookups ? (float) q->lkpcmps / q->lookups : 0;
+  elen = q->enqs ? (float) q->enqcmps / q->enqs : 0;
+  sloops = q->splays ? (float) q->splayloops / q->splays : 0;
+
+  (void) sprintf(buf, "f(%d %4.2f) i(%d %4.2f) s(%d %4.2f)",
+                q->lookups, llen, q->enqs, elen, q->splays, sloops);
+
+  return buf;
+}
+
+/*
+   spaux.c:  This code implements the following operations on an event-set
+   or priority-queue implemented using splay trees:
+
+   spdelete( n, q )             n is removed from q.
+
+   In the above, n and np are pointers to single items (type
+   SPBLK *); q is an event-set (type SPTREE *),
+   The type definitions for these are taken
+   from file sptree.h.  All of these operations rest on basic
+   splay tree operations from file sptree.c.
+
+   The basic splay tree algorithms were originally presented in:
+
+   Self Adjusting Binary Trees,
+   by D. D. Sleator and R. E. Tarjan,
+   Proc. ACM SIGACT Symposium on Theory
+   of Computing (Boston, Apr 1983) 235-245.
+
+   The operations in this package supplement the operations from
+   file splay.h to provide support for operations typically needed
+   on the pending event set in discrete event simulation.  See, for
+   example,
+
+   Introduction to Simula 67,
+   by Gunther Lamprecht, Vieweg & Sohn, Braucschweig, Wiesbaden, 1981.
+   (Chapter 14 contains the relevant discussion.)
+
+   Simula Begin,
+   by Graham M. Birtwistle, et al, Studentlitteratur, Lund, 1979.
+   (Chapter 9 contains the relevant discussion.)
+
+   Many of the routines in this package use the splay procedure,
+   for bottom-up splaying of the queue.  Consequently, item n in
+   delete and item np in all operations listed above must be in the
+   event-set prior to the call or the results will be
+   unpredictable (eg:  chaos will ensue).
+
+   Note that, in all cases, these operations can be replaced with
+   the corresponding operations formulated for a conventional
+   lexicographically ordered tree.  The versions here all use the
+   splay operation to ensure the amortized bounds; this usually
+   leads to a very compact formulation of the operations
+   themselves, but it may slow the average performance.
+
+   Alternative versions based on simple binary tree operations are
+   provided (commented out) for head, next, and prev, since these
+   are frequently used to traverse the entire data structure, and
+   the cost of traversal is independent of the shape of the
+   structure, so the extra time taken by splay in this context is
+   wasted.
+
+   This code was written by:
+   Douglas W. Jones with assistance from Srinivas R. Sataluri
+
+   Translated to C by David Brower, daveb@rtech.uucp
+
+   Thu Oct  6 12:11:33 PDT 1988 (daveb) Fixed spdeq, which was broken
+   handling one-node trees.  I botched the pascal translation of
+   a VAR parameter.  Changed interface, so callers must also be
+   corrected to pass the node by address rather than value.
+   Mon Apr  3 15:18:32 PDT 1989 (daveb)
+   Apply fix supplied by Mark Moraes <moraes@csri.toronto.edu> to
+   spdelete(), which dropped core when taking out the last element
+   in a subtree -- that is, when the right subtree was empty and
+   the leftlink was also null, it tried to take out the leftlink's
+   uplink anyway.
+ */
+/*----------------
+ *
+ * spdelete() -- Delete node from a tree.
+ *
+ *      n is deleted from q; the resulting splay tree has been splayed
+ *      around its new root, which is the successor of n
+ *
+ */
+void
+__spdelete(n, q)
+    REGISTER SPBLK *n;
+    REGISTER SPTREE *q;
+
+{
+  REGISTER SPBLK *x;
+
+  splay(n, q);
+  x = spdeq(&q->root->rightlink);
+  if (x == NULL) {             /* empty right subtree */
+    q->root = q->root->leftlink;
+    if (q->root)
+      q->root->uplink = NULL;
+  } else {                     /* non-empty right subtree */
+    x->uplink = NULL;
+    x->leftlink = q->root->leftlink;
+    x->rightlink = q->root->rightlink;
+    if (x->leftlink != NULL)
+      x->leftlink->uplink = x;
+    if (x->rightlink != NULL)
+      x->rightlink->uplink = x;
+    q->root = x;
+  }
+  spfree(n, q);
+}                              /* spdelete */
+
+
+/*
+
+ *  sptree.c:  The following code implements the basic operations on
+ *  an event-set or priority-queue implemented using splay trees:
+ *
+ *  SPTREE *spinit( compare )   Make a new tree
+ *  SPBLK *spenq( n, q )        Insert n in q after all equal keys.
+ *  SPBLK *spdeq( np )          Return first key under *np, removing it.
+ *  void splay( n, q )          n (already in q) becomes the root.
+ *
+ *  In the above, n points to an SPBLK type, while q points to an
+ *  SPTREE.
+ *
+ *  The implementation used here is based on the implementation
+ *  which was used in the tests of splay trees reported in:
+ *
+ *    An Empirical Comparison of Priority-Queue and Event-Set Implementations,
+ *      by Douglas W. Jones, Comm. ACM 29, 4 (Apr. 1986) 300-311.
+ *
+ *  The changes made include the addition of the enqprior
+ *  operation and the addition of up-links to allow for the splay
+ *  operation.  The basic splay tree algorithms were originally
+ *  presented in:
+ *
+ *      Self Adjusting Binary Trees,
+ *              by D. D. Sleator and R. E. Tarjan,
+ *                      Proc. ACM SIGACT Symposium on Theory
+ *                      of Computing (Boston, Apr 1983) 235-245.
+ *
+ *  The enq and enqprior routines use variations on the
+ *  top-down splay operation, while the splay routine is bottom-up.
+ *  All are coded for speed.
+ *
+ *  Written by:
+ *    Douglas W. Jones
+ *
+ *  Translated to C by:
+ *    David Brower, daveb@rtech.uucp
+ *
+ * Thu Oct  6 12:11:33 PDT 1988 (daveb) Fixed spdeq, which was broken
+ *      handling one-node trees.  I botched the pascal translation of
+ *      a VAR parameter.
+ */
+/*----------------
+ *
+ * spinit() -- initialize an empty splay tree
+ *
+ */
+SPTREE *
+__spinit()
+{
+  REGISTER SPTREE *q;
+
+  q = (SPTREE *) emalloc(sizeof(*q));
+
+  q->lookups = 0;
+  q->lkpcmps = 0;
+  q->enqs = 0;
+  q->enqcmps = 0;
+  q->splays = 0;
+  q->splayloops = 0;
+  q->root = NULL;
+  return (q);
+}
+
+/*----------------
+ *
+ *  spenq() -- insert item in a tree.
+ *
+ *  put n in q after all other nodes with the same key; when this is
+ *  done, n will be the root of the splay tree representing q, all nodes
+ *  in q with keys less than or equal to that of n will be in the
+ *  left subtree, all with greater keys will be in the right subtree;
+ *  the tree is split into these subtrees from the top down, with rotations
+ *  performed along the way to shorten the left branch of the right subtree
+ *  and the right branch of the left subtree
+ */
+static SPBLK *
+spenq(n, q)
+    REGISTER SPBLK *n;
+    REGISTER SPTREE *q;
+{
+  REGISTER SPBLK *left;                /* the rightmost node in the left tree */
+  REGISTER SPBLK *right;       /* the leftmost node in the right tree */
+  REGISTER SPBLK *next;                /* the root of the unsplit part */
+  REGISTER SPBLK *temp;
+
+  REGISTER univptr_t key;
+
+  q->enqs++;
+  n->uplink = NULL;
+  next = q->root;
+  q->root = n;
+  if (next == NULL) {          /* trivial enq */
+    n->leftlink = NULL;
+    n->rightlink = NULL;
+  } else {                     /* difficult enq */
+    key = n->key;
+    left = n;
+    right = n;
+
+    /* n's left and right children will hold the right and left
+       splayed trees resulting from splitting on n->key;
+       note that the children will be reversed! */
+
+    q->enqcmps++;
+    if (COMPARE(next->key, key) > 0)
+      goto two;
+
+  one:                         /* assert next->key <= key */
+
+    do {                       /* walk to the right in the left tree */
+      temp = next->rightlink;
+      if (temp == NULL) {
+       left->rightlink = next;
+       next->uplink = left;
+       right->leftlink = NULL;
+       goto done;              /* job done, entire tree split */
+      }
+      q->enqcmps++;
+      if (COMPARE(temp->key, key) > 0) {
+       left->rightlink = next;
+       next->uplink = left;
+       left = next;
+       next = temp;
+       goto two;               /* change sides */
+      }
+      next->rightlink = temp->leftlink;
+      if (temp->leftlink != NULL)
+       temp->leftlink->uplink = next;
+      left->rightlink = temp;
+      temp->uplink = left;
+      temp->leftlink = next;
+      next->uplink = temp;
+      left = temp;
+      next = temp->rightlink;
+      if (next == NULL) {
+       right->leftlink = NULL;
+       goto done;              /* job done, entire tree split */
+      }
+      q->enqcmps++;
+
+    } while (COMPARE(next->key, key) <= 0);    /* change sides */
+
+  two:                         /* assert next->key > key */
+
+    do {                       /* walk to the left in the right tree */
+      temp = next->leftlink;
+      if (temp == NULL) {
+       right->leftlink = next;
+       next->uplink = right;
+       left->rightlink = NULL;
+       goto done;              /* job done, entire tree split */
+      }
+      q->enqcmps++;
+      if (COMPARE(temp->key, key) <= 0) {
+       right->leftlink = next;
+       next->uplink = right;
+       right = next;
+       next = temp;
+       goto one;               /* change sides */
+      }
+      next->leftlink = temp->rightlink;
+      if (temp->rightlink != NULL)
+       temp->rightlink->uplink = next;
+      right->leftlink = temp;
+      temp->uplink = right;
+      temp->rightlink = next;
+      next->uplink = temp;
+      right = temp;
+      next = temp->leftlink;
+      if (next == NULL) {
+       left->rightlink = NULL;
+       goto done;              /* job done, entire tree split */
+      }
+      q->enqcmps++;
+
+    } while (COMPARE(next->key, key) > 0);     /* change sides */
+
+    goto one;
+
+  done:                        /* split is done, branches of n need reversal */
+
+    temp = n->leftlink;
+    n->leftlink = n->rightlink;
+    n->rightlink = temp;
+  }
+
+  return (n);
+
+}                              /* spenq */
+
+
+/*----------------
+ *
+ *  spdeq() -- return and remove head node from a subtree.
+ *
+ *  remove and return the head node from the node set; this deletes
+ *  (and returns) the leftmost node from q, replacing it with its right
+ *  subtree (if there is one); on the way to the leftmost node, rotations
+ *  are performed to shorten the left branch of the tree
+ */
+static SPBLK *
+spdeq(np)
+    SPBLK **np;                        /* pointer to a node pointer */
+
+{
+  REGISTER SPBLK *deq;         /* one to return */
+  REGISTER SPBLK *next;                /* the next thing to deal with */
+  REGISTER SPBLK *left;                /* the left child of next */
+  REGISTER SPBLK *farleft;     /* the left child of left */
+  REGISTER SPBLK *farfarleft;  /* the left child of farleft */
+
+  if (np == NULL || *np == NULL) {
+    deq = NULL;
+  } else {
+    next = *np;
+    left = next->leftlink;
+    if (left == NULL) {
+      deq = next;
+      *np = next->rightlink;
+
+      if (*np != NULL)
+       (*np)->uplink = NULL;
+
+    } else
+      for (;;) {               /* left is not null */
+       /* next is not it, left is not NULL, might be it */
+       farleft = left->leftlink;
+       if (farleft == NULL) {
+         deq = left;
+         next->leftlink = left->rightlink;
+         if (left->rightlink != NULL)
+           left->rightlink->uplink = next;
+         break;
+       }
+       /* next, left are not it, farleft is not NULL, might be it */
+       farfarleft = farleft->leftlink;
+       if (farfarleft == NULL) {
+         deq = farleft;
+         left->leftlink = farleft->rightlink;
+         if (farleft->rightlink != NULL)
+           farleft->rightlink->uplink = left;
+         break;
+       }
+       /* next, left, farleft are not it, rotate */
+       next->leftlink = farleft;
+       farleft->uplink = next;
+       left->leftlink = farleft->rightlink;
+       if (farleft->rightlink != NULL)
+         farleft->rightlink->uplink = left;
+       farleft->rightlink = left;
+       left->uplink = farleft;
+       next = farleft;
+       left = farfarleft;
+      }
+  }
+
+  return (deq);
+
+}                              /* spdeq */
+
+
+/*----------------
+ *
+ *  splay() -- reorganize the tree.
+ *
+ *  the tree is reorganized so that n is the root of the
+ *  splay tree representing q; results are unpredictable if n is not
+ *  in q to start with; q is split from n up to the old root, with all
+ *  nodes to the left of n ending up in the left subtree, and all nodes
+ *  to the right of n ending up in the right subtree; the left branch of
+ *  the right subtree and the right branch of the left subtree are
+ *  shortened in the process
+ *
+ *  this code assumes that n is not NULL and is in q; it can sometimes
+ *  detect n not in q and complain
+ */
+
+static void
+splay(n, q)
+    REGISTER SPBLK *n;
+    SPTREE *q;
+
+{
+  REGISTER SPBLK *up;          /* points to the node being dealt with */
+  REGISTER SPBLK *prev;                /* a descendent of up, already dealt with */
+  REGISTER SPBLK *upup;                /* the parent of up */
+  REGISTER SPBLK *upupup;      /* the grandparent of up */
+  REGISTER SPBLK *left;                /* the top of left subtree being built */
+  REGISTER SPBLK *right;       /* the top of right subtree being built */
+
+  left = n->leftlink;
+  right = n->rightlink;
+  prev = n;
+  up = prev->uplink;
+
+  q->splays++;
+
+  while (up != NULL) {
+    q->splayloops++;
+
+    /* walk up the tree towards the root, splaying all to the left of
+       n into the left subtree, all to right into the right subtree */
+
+    upup = up->uplink;
+    if (up->leftlink == prev) {        /* up is to the right of n */
+      if (upup != NULL && upup->leftlink == up) {      /* rotate */
+       upupup = upup->uplink;
+       upup->leftlink = up->rightlink;
+       if (upup->leftlink != NULL)
+         upup->leftlink->uplink = upup;
+       up->rightlink = upup;
+       upup->uplink = up;
+       if (upupup == NULL)
+         q->root = up;
+       else if (upupup->leftlink == upup)
+         upupup->leftlink = up;
+       else
+         upupup->rightlink = up;
+       up->uplink = upupup;
+       upup = upupup;
+      }
+      up->leftlink = right;
+      if (right != NULL)
+       right->uplink = up;
+      right = up;
+
+    } else {                   /* up is to the left of n */
+      if (upup != NULL && upup->rightlink == up) {     /* rotate */
+       upupup = upup->uplink;
+       upup->rightlink = up->leftlink;
+       if (upup->rightlink != NULL)
+         upup->rightlink->uplink = upup;
+       up->leftlink = upup;
+       upup->uplink = up;
+       if (upupup == NULL)
+         q->root = up;
+       else if (upupup->rightlink == upup)
+         upupup->rightlink = up;
+       else
+         upupup->leftlink = up;
+       up->uplink = upupup;
+       upup = upupup;
+      }
+      up->rightlink = left;
+      if (left != NULL)
+       left->uplink = up;
+      left = up;
+    }
+    prev = up;
+    up = upup;
+  }
+
+#ifdef SPLAYCSRI_DEBUG
+  if (q->root != prev) {
+/*      fprintf(stderr, " *** bug in splay: n not in q *** " ); */
+    abort();
+  }
+#endif
+
+  n->leftlink = left;
+  n->rightlink = right;
+  if (left != NULL)
+    left->uplink = n;
+  if (right != NULL)
+    right->uplink = n;
+  q->root = n;
+  n->uplink = NULL;
+
+}                              /* splay */
diff --git a/src/db.c b/src/db.c
new file mode 100644 (file)
index 0000000..0937cb9
--- /dev/null
+++ b/src/db.c
@@ -0,0 +1,2162 @@
+/**
+ * \file db.c
+ *
+ * \brief Loading and saving the PennMUSH object database.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <stdlib.h>
+#include "conf.h"
+#include "dbio.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "mymalloc.h"
+#include "game.h"
+#include "flags.h"
+#include "lock.h"
+#include "dbdefs.h"
+#include "log.h"
+#include "strtree.h"
+#include "parse.h"
+#include "privtab.h"
+#include "htab.h"
+#include "extmail.h"
+#include "confmagic.h"
+
+
+#ifdef WIN32
+#pragma warning( disable : 4761)      /* disable warning re conversion */
+#endif
+
+#ifdef WIN32SERVICES
+void shutdown_checkpoint(void);
+#endif
+
+/** Get a ref out of the database if a given db flag is set */
+#define MAYBE_GET(f,x) \
+        (indb_flags & (x)) ? getref(f) : 0
+
+extern int paranoid_checkpt;   /* from game.c */
+
+long indb_flags;               /**< What flags are in the db we read at startup? */
+long flagdb_flags;             /**< What flags are in the flag db we read at startup? */
+
+int loading_db = 0;   /**< Are we loading the database? */
+
+char db_timestamp[100];        /**< Time the read database was saved. */
+
+struct object *db = NULL; /**< The object db array */
+dbref db_top = 0;        /**< The number of objects in the db array */
+
+dbref errobj;            /**< Dbref of object on which an error has occurred */
+
+int dbline = 0;                  /**< Line of the database file being read */
+int use_flagfile = 0;    /**< Use seperate file for flags */
+
+/** String that markes the end of dumps */
+const char *EOD = "***END OF DUMP***\n";
+
+#ifndef DB_INITIAL_SIZE
+#define DB_INITIAL_SIZE 5000   /**< Initial size for db array */
+#endif                         /* DB_INITIAL_SIZE */
+
+dbref db_size = DB_INITIAL_SIZE;  /**< Current size of db array */
+
+HASHTAB htab_objdata;        /**< Object data hash table */
+HASHTAB htab_objdata_keys;    /**< Object data keys hash table */
+
+static void db_grow(dbref newtop);
+
+static void db_write_obj_basic(FILE * f, dbref i, struct object *o);
+int db_paranoid_write_object(FILE * f, dbref i, int flag);
+void putlocks(FILE * f, lock_list *l);
+void getlocks(dbref i, FILE * f);
+void get_new_locks(dbref i, FILE * f, int c);
+void db_read_attrs(FILE * f, dbref i, int c);
+int get_list(FILE * f, dbref i);
+static unsigned char * getbytes(FILE * f);
+void putbytes(FILE * f, unsigned char *lbytes, int byte_limit);
+void db_free(void);
+int load_flag_db(FILE *);
+void db_write_flag_db(FILE *);
+static void init_objdata_htab(int size);
+static void db_write_flags(FILE * f);
+static void db_write_powers(FILE * f);
+static dbref db_read_oldstyle(FILE * f);
+
+StrTree object_names;      /**< String tree of object names */
+StrTree _clastmods;
+extern StrTree atr_names;
+
+void init_names(void);
+void init_lmods(void);
+void create_minimal_db(void);
+
+extern struct db_stat_info current_state;
+
+/** Initialize the name strtree.
+ */
+void
+init_names(void)
+{
+  st_init(&object_names);
+}
+
+void init_lmods(void) {
+  st_init(&_clastmods);
+}
+
+/** Set an object's name through the name strtree.
+ * We maintain object names in a strtree because many objects have
+ * the same name (cardinal exits, weapons and armor, etc.)
+ * This function is used to set an object's name; if the name's already
+ * in the strtree, we just get a pointer to it, saving memory.
+ * (If not, we add it to the strtree and use that pointer).
+ * \param obj dbref of object whose name is to be set.
+ * \param newname name to set on the object, or NULL to clear the name.
+ * \return object's new name, or NULL if none is given.
+ */
+const char *
+set_name(dbref obj, const char *newname)
+{
+  /* if pointer not null unalloc it */
+  if (Name(obj))
+    st_delete(Name(obj), &object_names);
+  if (!newname || !*newname)
+    return NULL;
+  Name(obj) = st_insert(newname, &object_names);
+  return Name(obj);
+}
+
+/* Alot of things could of modified the samething.. */
+const char *set_lmod(dbref obj, const char *lmod) {
+  if(db[obj].lastmod)
+    st_delete(db[obj].lastmod, &_clastmods);
+  if(!lmod || !*lmod) {
+    /* this can happen.. */
+    LastMod(obj) = st_insert((const char *)"NONE", &_clastmods);
+  } else LastMod(obj) = st_insert(lmod, &_clastmods);
+
+  return db[obj].lastmod;
+}
+
+int db_init = 0;  /**< Has the db array been initialized yet? */
+
+static void
+db_grow(dbref newtop)
+{
+  struct object *newdb;
+  dbref initialized;
+  struct object *o;
+
+  if (newtop > db_top) {
+    initialized = db_top;
+    current_state.total = newtop;
+    current_state.garbage += newtop - db_top;
+    db_top = newtop;
+    if (!db) {
+      /* make the initial one */
+      db_size = (db_init) ? db_init : DB_INITIAL_SIZE;
+      while (db_top > db_size)
+       db_size *= 2;
+      if ((db = (struct object *)
+          malloc(db_size * sizeof(struct object))) == NULL) {
+       do_rawlog(LT_ERR, "ERROR: out of memory while creating database!");
+       abort();
+      }
+    }
+    /* maybe grow it */
+    if (db_top > db_size) {
+      /* make sure it's big enough */
+      while (db_top > db_size)
+       db_size *= 2;
+      if ((newdb = (struct object *)
+          realloc(db, db_size * sizeof(struct object))) == NULL) {
+       do_rawlog(LT_ERR, "ERROR: out of memory while extending database!");
+       abort();
+      }
+      db = newdb;
+    }
+    while (initialized < db_top) {
+      o = db + initialized;
+      o->name = 0;
+      o->list = 0;
+      o->location = NOTHING;
+      o->contents = NOTHING;
+      o->exits = NOTHING;
+      o->next = NOTHING;
+      o->parent = NOTHING;
+      o->locks = NULL;
+      o->owner = GOD;
+      o->zone = NOTHING;
+      o->penn = 0;
+      o->type = TYPE_GARBAGE;
+      o->flags = NULL;
+      o->division.dp_bytes = NULL;
+      o->division.level = 1;
+      o->division.object = -1;
+      o->division.powergroups = NULL;
+      o->warnings = 0;
+      o->modification_time = o->creation_time = mudtime;
+      o->attrcount = 0;
+      initialized++;
+    }
+  }
+}
+
+/** Allocate a new object structure.
+ * This function allocates and returns a new object structure.
+ * The caller must see that it gets appropriately typed and otherwise
+ * initialized.
+ * \return dbref of newly allocated object.
+ */
+dbref
+new_object(void)
+{
+  dbref newobj;
+  struct object *o;
+  /* if stuff in free list use it */
+  if ((newobj = free_get()) == NOTHING) {
+    /* allocate more space */
+    newobj = db_top;
+    db_grow(db_top + 1);
+  }
+  /* clear it out */
+  o = db + newobj;
+  o->name = 0;
+  o->list = 0;
+  o->location = NOTHING;
+  o->contents = NOTHING;
+  o->exits = NOTHING;
+  o->next = NOTHING;
+  o->parent = NOTHING;
+  o->locks = NULL;
+  o->owner = GOD;
+  o->zone = NOTHING;
+  o->penn = 0;
+  o->type = TYPE_GARBAGE;
+  o->flags = new_flag_bitmask("FLAG");
+  o->division.dp_bytes = NULL;
+  o->division.level = 1;
+  o->division.object = -1;
+  o->division.powergroups = NULL;
+  o->warnings = 0;
+  o->modification_time = o->creation_time = mudtime;
+  o->attrcount = 0;
+  o->lastmod = NULL;
+#ifdef RPMODE_SYS
+  o->rplog.bufferq = NULL;
+  o->rplog.status = 0;
+#endif /* RPMODE_SYS */
+  if (current_state.garbage)
+    current_state.garbage--;
+  return newobj;
+}
+
+/** Output a long int to a file.
+ * \param f file pointer to write to.
+ * \param ref value to write.
+ */
+void
+putref(FILE * f, long int ref)
+{
+  OUTPUT(fprintf(f, "%ld\n", ref));
+}
+
+/** Output a string to a file.
+ * This function writes a string to a file, double-quoted, 
+ * appropriately escaping quotes and backslashes (the escape character).
+ * \param f file pointer to write to.
+ * \param s value to write.
+ */
+void
+putstring(FILE * f, const char *s)
+{
+  OUTPUT(putc('"', f));
+  while (*s) {
+    switch (*s) {
+    case '\\':
+    case '"':
+      OUTPUT(putc('\\', f));
+      /* FALL THROUGH */
+    default:
+      OUTPUT(putc(*s, f));
+    }
+    s++;
+  }
+  OUTPUT(putc('"', f));
+  OUTPUT(putc('\n', f));
+}
+
+/** Read a labeled entry from a database.
+ * Labeled entries look like 'label entry', and are used
+ * extensively in the current database format, and to a lesser
+ * extent in older versions.
+ * \param f the file to read from
+ * \param label pointer to update to the address of a static
+ * buffer containing the label that was read.
+ * \param value pointer to update to the address of a static
+ * buffer containing the value that was read.
+ */
+void
+db_read_labeled_string(FILE * f, char **label, char **value)
+{
+  static char lbuf[BUFFER_LEN], vbuf[BUFFER_LEN];
+  int c;
+  char *p;
+
+  *label = lbuf;
+  *value = vbuf;
+
+  /* invariant: we start at the beginning of a line. */
+
+  dbline++;
+
+  do {
+    c = getc(f);
+    while (isspace(c)) {
+      if (c == '\n')
+       dbline++;
+      c = getc(f);
+    }
+    if (c == '#') {
+      while ((c = getc(f)) != '\n' && c != EOF) {
+       /* nothing */
+      }
+      if (c == '\n')
+       dbline++;
+    }
+  } while (c != EOF && isspace(c));
+
+  if (c == EOF) {
+    do_rawlog(LT_ERR, T("DB: Unexpected EOF at line %d"), dbline);
+    longjmp(db_err, 1);
+  }
+
+  /* invariant: we should have the first character of a label in 'c'. */
+
+  p = lbuf;
+  do {
+    if (c != '_' && c != '-' && c != '!' && c != '.' && c != '>' && c != '<' && c != '#' &&    /* these really should only be first time */
+       !isalnum(c)) {
+      do_rawlog(LT_ERR, "DB: Illegal character '%c'(%d) in label, line %d",
+               c, c, dbline);
+      longjmp(db_err, 1);
+    }
+    safe_chr(c, lbuf, &p);
+    c = getc(f);
+  } while (c != EOF && !isspace(c));
+  *p++ = '\0';
+  if (p >= lbuf + BUFFER_LEN)
+    do_rawlog(LT_ERR, "DB: warning: very long label, line %d", dbline);
+
+  /* suck up separating whitespace */
+  while (c != '\n' && c != EOF && isspace(c))
+    c = getc(f);
+
+  /* check for presence of a value, which we must have. */
+  if (c == EOF || c == '\n') {
+    if (c == EOF)
+      do_rawlog(LT_ERR, T("DB: Unexpected EOF at line %d"), dbline);
+    else
+      do_rawlog(LT_ERR, T("DB: Missing value for '%s' at line %d"), lbuf,
+               dbline);
+    longjmp(db_err, 1);
+  }
+
+  /* invariant: we should have the first character of a value in 'c'. */
+
+  p = vbuf;
+  if (c == '"') {
+    /* quoted string */
+    int sline;
+    sline = dbline;
+    for (;;) {
+      c = getc(f);
+      if (c == '"')
+       break;
+      if (c == '\\')
+       c = getc(f);
+      if (c == EOF) {
+       do_rawlog(LT_ERR, "DB: Unclosed quoted string starting on line %d",
+                 sline);
+       longjmp(db_err, 1);
+      }
+      if (c == '\0')
+       do_rawlog(LT_ERR,
+                 "DB: warning: null in quoted string, remainder lost, line %d",
+                 dbline);
+      if (c == '\n')
+       dbline++;
+      safe_chr(c, vbuf, &p);
+    }
+    do {
+      c = getc(f);
+      if (c != EOF && !isspace(c)) {
+       do_rawlog(LT_ERR, "DB: Garbage after quoted string, line %d", dbline);
+       longjmp(db_err, 1);
+      }
+    } while (c != '\n' && c != EOF);
+  } else {
+    /* non-quoted value */
+    do {
+      if (c != '_' && c != '-' && c != '!' && c != '.' &&
+         c != '#' && !isalnum(c) && !isspace(c)) {
+       do_rawlog(LT_ERR, "DB: Illegal character '%c'(%d) in value, line %d",
+                 c, c, dbline);
+       longjmp(db_err, 1);
+      }
+      safe_chr(c, vbuf, &p);
+      c = getc(f);
+    } while (c != EOF && c != '\n');
+    if (c == '\n' && (p - vbuf >= 2) && (*(p - 2) == '\r')) {
+      /* Oops, we read in \r\n at the end of this value. Drop the \r */
+      p--;
+      *(p - 1) = '\n';
+    }
+  }
+  *p++ = '\0';
+  if (p >= vbuf + BUFFER_LEN)
+    do_rawlog(LT_ERR, "DB: warning: very long value, line %d", dbline);
+
+  /* note no line increment for final newline because of initial increment */
+}
+
+/** Read a string with a given label.
+ * If the label read is different than the one being checked, the 
+ * database load will abort with an error.
+ * \param f the file to read from.
+ * \param label the label that should be read.
+ * \param value pointer to update to the address of a static
+ * buffer containing the value that was read.
+ */
+void
+db_read_this_labeled_string(FILE * f, const char *label, char **value)
+{
+  char *readlabel;
+
+  db_read_labeled_string(f, &readlabel, value);
+
+  if (strcmp(readlabel, label)) {
+    do_rawlog(LT_ERR,
+             T("DB: error: Got label '%s', expected label '%s' at line %d"),
+             readlabel, label, dbline);
+    longjmp(db_err, 1);
+  }
+}
+
+/** Read an integer with a given label.
+ * If the label read is different than the one being checked, the 
+ * database load will abort with an error.
+ * \param f the file to read from.
+ * \param label the label that should be read.
+ * \param value pointer to update to the number that was read.
+ */
+void
+db_read_this_labeled_number(FILE * f, const char *label, int *value)
+{
+  char *readlabel;
+  char *readvalue;
+
+  db_read_labeled_string(f, &readlabel, &readvalue);
+
+  if (strcmp(readlabel, label)) {
+    do_rawlog(LT_ERR,
+             T("DB: error: Got label '%s', expected label '%s' at line %d"),
+             readlabel, label, dbline);
+    longjmp(db_err, 1);
+  }
+
+  *value = parse_integer(readvalue);
+}
+
+/** Read an integer and label.
+ * \param f the file to read from.
+ * \param label pointer to update to the address of a static
+ * buffer containing the label that was read.
+ * \param value pointer to update to the number that was read.
+ */
+void
+db_read_labeled_number(FILE * f, char **label, int *value)
+{
+  char *readvalue;
+  db_read_labeled_string(f, label, &readvalue);
+  *value = parse_integer(readvalue);
+}
+
+/** Read a dbref with a given label.
+ * If the label read is different than the one being checked, the 
+ * database load will abort with an error.
+ * \param f the file to read from.
+ * \param label the label that should be read.
+ * \param value pointer to update to the dbref that was read.
+ */
+void
+db_read_this_labeled_dbref(FILE * f, const char *label, dbref *val)
+{
+  char *readlabel;
+  char *readvalue;
+
+  db_read_labeled_string(f, &readlabel, &readvalue);
+
+  if (strcmp(readlabel, label)) {
+    do_rawlog(LT_ERR,
+             T("DB: error: Got label '%s', expected label '%s' at line %d"),
+             readlabel, label, dbline);
+    longjmp(db_err, 1);
+  }
+  *val = qparse_dbref(readvalue);
+}
+
+/** Read a dbref and label.
+ * \param f the file to read from.
+ * \param label pointer to update to the address of a static
+ * buffer containing the label that was read.
+ * \param value pointer to update to the dbref that was read.
+ */
+void
+db_read_labeled_dbref(FILE * f, char **label, dbref *val)
+{
+  char *readvalue;
+  db_read_labeled_string(f, label, &readvalue);
+  *val = qparse_dbref(readvalue);
+}
+
+static void
+db_write_label(FILE * f, char const *l)
+{
+  OUTPUT(fputs(l, f));
+  OUTPUT(putc(' ', f));
+}
+
+void
+db_write_labeled_string(FILE * f, char const *label, char const *value)
+{
+  db_write_label(f, label);
+  putstring(f, value);
+}
+
+void
+db_write_labeled_number(FILE * f, char const *label, int value)
+{
+  OUTPUT(fprintf(f, "%s %d\n", label, value));
+}
+
+void
+db_write_labeled_dbref(FILE * f, char const *label, dbref value)
+{
+  OUTPUT(fprintf(f, "%s #%d\n", label, value));
+}
+
+/** Write a boolexp to a file in unparsed (text) form.
+ * \param f file pointer to write to.
+ * \param b pointer to boolexp to write.
+ */
+void
+putboolexp(FILE * f, boolexp b)
+{
+  db_write_labeled_string(f, "  key", unparse_boolexp(GOD, b, UB_DBREF));
+}
+
+/** Write a list of locks to a file.
+ * \param f file pointer to write to.
+ * \param l pointer to lock_list to write.
+ */
+void
+putlocks(FILE * f, lock_list *l)
+{
+  lock_list *ll;
+  int count = 0;
+  for (ll = l; ll; ll = ll->next)
+    count++;
+  db_write_labeled_number(f, "lockcount", count);
+  for (ll = l; ll; ll = ll->next) {
+    db_write_labeled_string(f, " type", ll->type);
+    db_write_labeled_dbref(f, "  creator", L_CREATOR(ll));
+    db_write_labeled_string(f, "  flags", lock_flags_long(ll));
+    db_write_labeled_number(f, "  derefs", chunk_derefs(L_KEY(ll)));
+    putboolexp(f, ll->key);
+    /* putboolexp adds a '\n', so we won't. */
+  }
+}
+
+
+/** Write out the basics of an object.
+ * This function writes out the basic information associated with an
+ * object - just about everything but the attributes.
+ * \param f file pointer to write to.
+ * \param i dbref of object to write.
+ * \param o pointer to object to write.
+ */
+static void
+db_write_obj_basic(FILE * f, dbref i, struct object *o)
+{
+  db_write_labeled_string(f, "name", o->name);
+  db_write_labeled_dbref(f, "location", o->location);
+  db_write_labeled_dbref(f, "contents", o->contents);
+  db_write_labeled_dbref(f, "exits", o->exits);
+  db_write_labeled_dbref(f, "next", o->next);
+  db_write_labeled_dbref(f, "parent", o->parent);
+  putlocks(f, Locks(i));
+  db_write_labeled_dbref(f, "owner", o->owner);
+  db_write_labeled_dbref(f, "zone", o->zone);
+  db_write_labeled_dbref(f, "division_object",  o->division.object);
+  db_write_labeled_number(f, "level", o->division.level);
+  db_write_labeled_number(f, "pennies", Pennies(i));
+  db_write_labeled_number(f, "type", Typeof(i));
+  db_write_labeled_string(f, "powergroup", (const char *) powergroups_list_on(i, 0));
+  db_write_labeled_string(f, "powers", division_list_powerz(i, 0));
+  db_write_labeled_string(f, "flags",
+                       bits_to_string("FLAG", o->flags, GOD, NOTHING));
+  db_write_labeled_string(f, "warnings", unparse_warnings(o->warnings));
+  db_write_labeled_number(f, "created", (int) o->creation_time);
+  db_write_labeled_number(f, "modified", (int) o->modification_time);
+  if(o->lastmod && *o->lastmod)
+   db_write_labeled_string(f, "lastmod", o->lastmod);
+  else
+    db_write_labeled_string(f, "lastmod", (const char *)"NONE");
+
+}
+
+/** Write out an object.
+ * This function writes a single object out to a file.
+ * \param f file pointer to write to.
+ * \param i dbref of object to write.
+ */
+int
+db_write_object(FILE * f, dbref i)
+{
+  struct object *o;
+  ALIST *list;
+  int count = 0;
+
+  o = db + i;
+  db_write_obj_basic(f, i, o);
+
+  /* write the attribute list */
+
+  /* Don't trust AttrCount(thing) for number of attributes to write. */
+  for (list = o->list; list; list = AL_NEXT(list)) {
+    if (AF_Nodump(list))
+      continue;
+    count++;
+  }
+  db_write_labeled_number(f, "attrcount", count);
+
+  for (list = o->list; list; list = AL_NEXT(list)) {
+    if (AF_Nodump(list))
+      continue;
+    db_write_labeled_string(f, " name", AL_NAME(list));
+    db_write_labeled_dbref(f, "  owner", Owner(AL_CREATOR(list)));
+    db_write_labeled_string(f, "  flags", atrflag_to_string(AL_FLAGS(list)));
+    db_write_labeled_number(f, "  derefs", AL_DEREFS(list));
+    db_write_labeled_string(f, "  writelock", unparse_boolexp(GOD, AL_WLock(list), UB_DBREF));
+    db_write_labeled_number(f, "     derefs", chunk_derefs(AL_WLock(list)));
+    db_write_labeled_string(f, "  readlock", unparse_boolexp(GOD, AL_RLock(list), UB_DBREF));
+    db_write_labeled_number(f, "     derefs", chunk_derefs(AL_RLock(list)));
+    db_write_labeled_string(f, "  value", atr_value(list));
+  }
+  return 0;
+}
+
+/** Write out the object database to disk.
+ * \verbatim
+ * This function writes the databsae out to disk. The database
+ * structure currently looks something like this:
+ * +V<header line>
+ * +FLAGS LIST
+ * <flag data>
+ * +POWERS LIST
+ * <flag data>
+ * ~<number of objects>
+ * <object data>
+ * \endverbatim
+ * \param f file pointer to write to.
+ * \param flag 0 for normal dump, DBF_PANIC for panic dumps.
+ * \return the number of objects in the database (db_top)
+ */
+dbref
+db_write(FILE * f, int flag)
+{
+  dbref i;
+  int dbflag;
+
+  /* print a header line to make a later conversion to 2.0 easier to do.
+   * the odd choice of numbers is based on 256*x + 2 offset
+   * The original PennMUSH had x=5 (chat) or x=6 (nochat), and Tiny expects
+   * to deal with that. We need to use some extra flags as well, so
+   * we may be adding to 5/6 as needed, using successive binary numbers.
+   */
+  dbflag = 5 + flag;
+  dbflag += DBF_NO_CHAT_SYSTEM;
+  dbflag += DBF_WARNINGS;
+  dbflag += DBF_CREATION_TIMES;
+  dbflag += DBF_SPIFFY_LOCKS;
+  dbflag += DBF_NEW_STRINGS;
+  /* Removed because CobraMUSH 0.7 and after assume DBF_TYPE_GARBAGE implies
+   * a PennMUSH database that needs to be converted. */
+  /* dbflag += DBF_TYPE_GARBAGE; */
+  dbflag += DBF_SPLIT_IMMORTAL;
+  dbflag += DBF_NO_TEMPLE;
+  dbflag += DBF_LESS_GARBAGE;
+  dbflag += DBF_AF_VISUAL;
+  dbflag += DBF_VALUE_IS_COST;
+  dbflag += DBF_LINK_ANYWHERE;
+  dbflag += DBF_NO_STARTUP_FLAG;
+  dbflag += DBF_AF_NODUMP;
+  dbflag += DBF_NEW_FLAGS;
+  dbflag += DBF_DIVISIONS;
+  dbflag += DBF_LABELS;
+  dbflag += DBF_NEW_ATR_LOCK;
+
+  OUTPUT(fprintf(f, "+V%d\n", dbflag * 256 + 2));
+  db_write_labeled_string(f, "savedtime", show_time(mudtime, 1));
+
+  OUTPUT(fprintf(f, "~%d\n", db_top));
+  for (i = 0; i < db_top; i++) {
+#ifdef WIN32
+#ifndef __MINGW32__
+    /* Keep the service manager happy */
+    if (shutdown_flag && (i & 0xFF) == 0)
+      shutdown_checkpoint();
+#endif
+#endif
+    if (IsGarbage(i))
+      continue;
+    OUTPUT(fprintf(f, "!%d\n", i));
+    db_write_object(f, i);
+  }
+  OUTPUT(fputs(EOD, f));
+  return db_top;
+}
+
+static void
+db_write_flags(FILE * f)
+{
+  OUTPUT(fprintf(f, "+FLAGS LIST\n"));
+  flag_write_all(f, "FLAG");
+}
+
+static void
+db_write_powers(FILE *f) {
+  OUTPUT(fprintf(f, "+POWERS LIST\n"));
+  power_write_all(f);
+}
+
+
+/** Write out an object, in paranoid fashion.
+ * This function writes a single object out to a file in paranoid
+ * mode, which warns about several potential types of corruption,
+ * and can fix some of them.
+ * \param f file pointer to write to.
+ * \param i dbref of object to write.
+ * \param flag 1 = debug, 0 = normal
+ */
+int
+db_paranoid_write_object(FILE * f, dbref i, int flag)
+{
+  struct object *o;
+  ALIST *list, *next;
+  char name[BUFFER_LEN];
+  char tbuf1[BUFFER_LEN];
+  int err = 0;
+  char *p;
+  char lastp;
+  dbref owner;
+  int flags;
+  int fixmemdb = 0;
+  int count = 0;
+  int attrcount = 0;
+
+  o = db + i;
+  db_write_obj_basic(f, i, o);
+  /* fflush(f); */
+
+  /* write the attribute list, scanning */
+  for (list = o->list; list; list = AL_NEXT(list)) {
+    if (AF_Nodump(list))
+      continue;
+    attrcount++;
+  }
+
+  db_write_labeled_number(f, "attrcount", count);
+
+  for (list = o->list; list; list = next) {
+    next = AL_NEXT(list);
+    if (AF_Nodump(list))
+      continue;
+    fixmemdb = err = 0;
+    /* smash unprintable characters in the name, replace with ! */
+    strcpy(name, AL_NAME(list));
+    for (p = name; *p; p++) {
+      if (!isprint((unsigned char) *p) || isspace((unsigned char) *p)) {
+       *p = '!';
+       fixmemdb = err = 1;
+      }
+    }
+    if (err) {
+      /* If name already exists on this object, try adding a
+       * number to the end. Give up if we can't find one < 10000
+       */
+      if (atr_get_noparent(i, name)) {
+       count = 0;
+       do {
+         name[BUFFER_LEN - 6] = '\0';
+         sprintf(tbuf1, "%s%d", name, count);
+         count++;
+       } while (count < 10000 && atr_get_noparent(i, tbuf1));
+       strcpy(name, tbuf1);
+      }
+      do_rawlog(LT_CHECK,
+               T(" * Bad attribute name on #%d. Changing name to %s.\n"),
+               i, name);
+      err = 0;
+    }
+    /* check the owner */
+    owner = AL_CREATOR(list);
+    if (!GoodObject(owner)) {
+      do_rawlog(LT_CHECK, T(" * Bad owner on attribute %s on #%d.\n"), name, i);
+      owner = GOD;
+      fixmemdb = 1;
+    } else {
+      owner = Owner(owner);
+    }
+
+    /* write that info out */
+    db_write_labeled_string(f, "name", name);
+    db_write_labeled_dbref(f, "owner", owner);
+    db_write_labeled_string(f, "flags", atrflag_to_string(AL_FLAGS(list)));
+    db_write_labeled_number(f, "derefs", AL_DEREFS(list));
+
+    /* now check the attribute */
+    strcpy(tbuf1, atr_value(list));
+    /* get rid of unprintables and hard newlines */
+    lastp = '\0';
+    for (p = tbuf1; *p; p++) {
+      if (!isprint((unsigned char) *p)) {
+       if (!isspace((unsigned char) *p)) {
+         *p = '!';
+         err = 1;
+       }
+      }
+      lastp = *p;
+    }
+    if (err) {
+      fixmemdb = 1;
+      do_rawlog(LT_CHECK,
+               T(" * Bad text in attribute %s on #%d. Changed to:\n"), name,
+               i);
+      do_rawlog(LT_CHECK, "%s\n", tbuf1);
+    }
+    db_write_labeled_string(f, "value", tbuf1);
+    if (flag && fixmemdb) {
+      /* Fix the db in memory */
+      flags = AL_FLAGS(list);
+      atr_clr(i, AL_NAME(list), owner);
+      (void) atr_add(i, name, tbuf1, owner, flags);
+      list = atr_get_noparent(i, name);
+      AL_FLAGS(list) = flags;
+    }
+  }
+  return 0;
+}
+
+
+
+/** Write out the object database to disk, in paranoid mode.
+ * \verbatim
+ * This function writes the databsae out to disk, in paranoid mode. 
+ * The database structure currently looks something like this:
+ * +V<header line>
+ * +FLAGS LIST
+ * <flag data>
+ * ~<number of objects>
+ * <object data>
+ * \endverbatim
+ * \param f file pointer to write to.
+ * \param flag 0 for normal paranoid dump, 1 for debug paranoid dump.
+ * \return the number of objects in the database (db_top)
+ */
+dbref
+db_paranoid_write(FILE * f, int flag)
+{
+  dbref i;
+  int dbflag;
+
+/* print a header line to make a later conversion to 2.0 easier to do.
+ * the odd choice of numbers is based on 256*x + 2 offset
+ */
+  dbflag = 5;
+  dbflag += DBF_NO_CHAT_SYSTEM;
+  dbflag += DBF_WARNINGS;
+  dbflag += DBF_CREATION_TIMES;
+  dbflag += DBF_SPIFFY_LOCKS;
+  dbflag += DBF_NEW_STRINGS;
+  /* Removed because CobraMUSH 0.7 and after assume DBF_TYPE_GARBAGE implies
+   * a PennMUSH database that needs to be converted. */
+  /* dbflag += DBF_TYPE_GARBAGE; */
+  dbflag += DBF_SPLIT_IMMORTAL;
+  dbflag += DBF_NO_TEMPLE;
+  dbflag += DBF_LESS_GARBAGE;
+  dbflag += DBF_AF_VISUAL;
+  dbflag += DBF_VALUE_IS_COST;
+  dbflag += DBF_LINK_ANYWHERE;
+  dbflag += DBF_NO_STARTUP_FLAG;
+  dbflag += DBF_AF_NODUMP;
+  dbflag += DBF_NEW_FLAGS;
+  dbflag += DBF_DIVISIONS;
+  dbflag += DBF_LABELS;
+
+  do_rawlog(LT_CHECK, "PARANOID WRITE BEGINNING...\n");
+
+  OUTPUT(fprintf(f, "+V%d\n", dbflag * 256 + 2));
+
+  db_write_labeled_string(f, "savedtime", show_time(mudtime, 1));
+  db_write_flags(f);
+  db_write_powers(f);
+  OUTPUT(fprintf(f, "~%d\n", db_top));
+
+  /* write out each object */
+  for (i = 0; i < db_top; i++) {
+#ifdef WIN32SERVICES
+    /* Keep the service manager happy */
+    if (shutdown_flag && (i & 0xFF) == 0)
+      shutdown_checkpoint();
+#endif
+    if (IsGarbage(i))
+      continue;
+    OUTPUT(fprintf(f, "!%d\n", i));
+    db_paranoid_write_object(f, i, flag);
+    /* print out a message every so many objects */
+    if (i % paranoid_checkpt == 0)
+      do_rawlog(LT_CHECK, T("\t...wrote up to object #%d\n"), i);
+  }
+  OUTPUT(fputs(EOD, f));
+  do_rawlog(LT_CHECK, T("\t...finished at object #%d\n"), i - 1);
+  do_rawlog(LT_CHECK, "END OF PARANOID WRITE.\n");
+  return db_top;
+}
+
+
+/** Read in a long int.
+ * \param f file pointer to read from.
+ * \return long int read.
+ */
+long int
+getref(FILE * f)
+{
+  static char buf[BUFFER_LEN];
+  if (!fgets(buf, sizeof(buf), f)) {
+    do_rawlog(LT_ERR, T("Unexpected EOF at line %d"), dbline);
+    longjmp(db_err, 1);
+  }
+  dbline++;
+  return strtol(buf, NULL, 10);
+}
+
+
+/** Read in a string, into a static buffer.
+ * This function reads a double-quoted escaped string of the form
+ * written by putstring. The string is read into a static buffer
+ * that is not allocated, so the return value must usually be copied
+ * elsewhere.
+ * \param f file pointer to read from.
+ * \return pointer to static buffer containing string read.
+ */
+const char *
+getstring_noalloc(FILE * f)
+{
+  static char buf[BUFFER_LEN];
+  char *p;
+  int c;
+
+  p = buf;
+  c = fgetc(f);
+  if (c == EOF) {
+    do_rawlog(LT_ERR, T("Unexpected EOF at line %d"), dbline);
+    longjmp(db_err, 1);
+  } else if (c != '"') {
+    for (;;) {
+      if ((c == '\0') || (c == EOF) ||
+         ((c == '\n') && ((p == buf) || (p[-1] != '\r')))) {
+       *p = '\0';
+       if (c == '\n')
+         dbline++;
+       return buf;
+      }
+      safe_chr(c, buf, &p);
+      c = fgetc(f);
+    }
+  } else {
+    for (;;) {
+      c = fgetc(f);
+      if (c == '"') {
+       /* It's a closing quote if it's followed by \r or \n */
+       c = fgetc(f);
+       if (c == '\r') {
+         /* Get a possible \n, too */
+         if ((c = fgetc(f)) != '\n')
+           ungetc(c, f);
+         else
+           dbline++;
+       } else if (c != '\n')
+         ungetc(c, f);
+       *p = '\0';
+       return buf;
+      } else if (c == '\\') {
+       c = fgetc(f);
+      }
+      if ((c == '\0') || (c == EOF)) {
+       *p = '\0';
+       return buf;
+      }
+      safe_chr(c, buf, &p);
+    }
+  }
+}
+
+/** Read a boolexp from a file.
+ * This function reads a boolexp from a file. It expects the format that
+ * put_boolexp writes out.
+ * \param f file pointer to read from.
+ * \param type pointer to lock type being read.
+ * \return pointer to boolexp read.
+ */
+boolexp
+getboolexp(FILE * f, const char *type)
+{
+  char *val;
+  db_read_this_labeled_string(f, "key", &val);
+  return parse_boolexp(GOD, val, type);
+}
+
+extern PRIV lock_privs[];
+
+/** Read locks for an object.
+ * This function is used for DBF_SPIFFY_LOCKS to read a whole list
+ * of locks from an object and set them.
+ * \param i dbref of the object.
+ * \param f file pointer to read from.
+ * \param c number of locks, or -1 if not yet known.
+ */
+void
+get_new_locks(dbref i, FILE * f, int c)
+{
+  char *val, *key;
+  dbref creator;
+  int flags;
+  char type[BUFFER_LEN];
+  boolexp b;
+  int count = c, n, derefs = 0;
+
+  if (c < 0) {
+    db_read_this_labeled_string(f, "lockcount", &val);
+    count = parse_integer(val);
+  }
+
+  for (n = 0; n < count; n++) {
+    /* Name of the lock */
+    db_read_this_labeled_string(f, "type", &val);
+    strcpy(type, val);
+    if(indb_flags & DBF_LABELS) {
+      db_read_this_labeled_dbref(f, "creator", &creator);
+      db_read_this_labeled_string(f, "flags", &val);
+      flags = string_to_privs(lock_privs, val, 0);
+      db_read_this_labeled_number(f, "derefs", &derefs);
+    } else {
+      db_read_this_labeled_number(f, "creator", &creator);
+      db_read_this_labeled_number(f, "flags", &flags);
+    }
+    /* boolexp */
+    db_read_this_labeled_string(f, "key", &key);
+    b = parse_boolexp_d(GOD, key, type, derefs);
+    add_lock_raw(creator, i, type, b, flags);
+  }
+}
+
+
+/** Read locks for an object.
+ * This function is used for DBF_NEW_LOCKS to read a whole list
+ * of locks from an object and set them. DBF_NEW_LOCKS aren't really
+ * new any more, and get_new_locks() is probably being used instead of
+ * this function.
+ * \param i dbref of the object.
+ * \param f file pointer to read from.
+ */
+void
+getlocks(dbref i, FILE * f)
+{
+  /* Assumes it begins at the beginning of a line. */
+  int c;
+  boolexp b;
+  char buf[BUFFER_LEN], *p;
+  while ((c = getc(f)), c != EOF && c == '_') {
+    p = buf;
+    while ((c = getc(f)), c != EOF && c != '|') {
+      *p++ = c;
+    }
+    *p = '\0';
+    if (c == EOF || (p - buf == 0)) {
+      do_rawlog(LT_ERR, T("ERROR: Invalid lock format on object #%d"), i);
+      return;
+    }
+    b = getboolexp(f, buf);    /* Which will clobber a '\n' */
+    if (b == TRUE_BOOLEXP) {
+      /* getboolexp() would already have complained. */
+      return;
+    } else {
+      add_lock_raw(Owner(i), i, buf, b, -1);
+    }
+  }
+  ungetc(c, f);
+  return;
+}
+
+void
+putbytes(FILE * f, unsigned char *lbytes, int byte_limit) {
+  int i;
+
+  OUTPUT(putc('\"', f));
+
+  for(i = 0; i < byte_limit; ++i) {
+    switch(lbytes[i]) {
+      case '\"':
+      case '\\':
+        OUTPUT(putc('\\', f));
+      default:
+        OUTPUT(putc(lbytes[i], f));
+    }
+  }
+
+  OUTPUT(putc('\"', f));
+  OUTPUT(putc('\n', f));
+}
+
+unsigned char *
+getbytes(FILE * f) {
+  static unsigned char byte_buf[BUFFER_LEN];
+  unsigned char *bp;
+  int i;
+
+  memset(byte_buf, 0, BUFFER_LEN);
+  bp = byte_buf;
+  (void) fgetc(f);     /* Skip the leading " */
+  for(;;) {
+    i = fgetc(f);
+    if(i == '\"') {
+      if((i = fgetc(f)) != '\n')
+        ungetc(i, f);
+      break;
+    } else if(i == '\\') {
+      i = fgetc(f);
+    }
+    if(i == EOF)
+      break;
+    safe_chr(i, byte_buf, &bp);
+  }
+  return byte_buf;
+}
+
+/** Free the entire database.
+ * This function frees the name, attributes, and locks on every object
+ * in the database, and then free the entire database structure and
+ * resets db_top.
+ */
+void
+db_free(void)
+{
+  dbref i;
+
+  if (db) {
+
+    for (i = 0; i < db_top; i++) {
+      set_name(i, NULL);
+      set_lmod(i, NULL);
+      atr_free(i);
+      free_locks(Locks(i));
+    }
+
+    free((char *) db);
+    db = NULL;
+    db_init = db_top = 0;
+  }
+}
+
+/** Read an attribute list for an object from a file
+ * \param f file pointer to read from.
+ * \param i dbref for the attribute list.
+ */
+int
+get_list(FILE * f, dbref i)
+{
+  int c;
+  char *p, *q;
+  char tbuf1[BUFFER_LEN + 150];
+  int flags;
+  int count = 0;
+  unsigned char derefs;
+
+  List(i) = NULL;
+  tbuf1[0] = '\0';
+  while (1)
+    switch (c = getc(f)) {
+    case ']':                  /* new style attribs, read name then value */
+      /* Using getstring_noalloc here will cause problems with attribute
+         names starting with ". This is probably a better fix than just
+         disallowing " in attribute names. */
+      fgets(tbuf1, BUFFER_LEN + 150, f);
+      if (!(p = strchr(tbuf1, '^'))) {
+       do_rawlog(LT_ERR, T("ERROR: Bad format on new attributes. object #%d"),
+                 i);
+       return -1;
+      }
+      *p++ = '\0';
+      if (!(q = strchr(p, '^'))) {
+       do_rawlog(LT_ERR,
+                 T("ERROR: Bad format on new attribute %s. object #%d"),
+                 tbuf1, i);
+       return -1;
+      }
+      *q++ = '\0';
+      flags = atoi(q);
+      /* Remove obsolete AF_NUKED flag and AF_STATIC, just in case */
+      flags &= ~AF_NUKED;
+      flags &= ~AF_STATIC;
+      if (!(indb_flags & DBF_AF_VISUAL)) {
+       /* Remove AF_ODARK flag. If it wasn't there, set AF_VISUAL */
+       if (!(flags & AF_ODARK))
+         flags |= AF_VISUAL;
+       flags &= ~AF_ODARK;
+      }
+      /* Read in the deref count for the attribute, or set it to 0 if not
+         present. */
+      q = strchr(q, '^');
+      if (q++)
+       derefs = atoi(q);
+      else
+       derefs = 0;
+      /* We add the attribute assuming that atoi(p) is an ok dbref
+       * since we haven't loaded the whole db and can't really tell
+       * if it is or not. We'll fix this up at the end of the load 
+       */
+      atr_new_add(i, tbuf1, getstring_noalloc(f), atoi(p), flags, derefs, TRUE_BOOLEXP, TRUE_BOOLEXP);
+      count++;
+      /* Check removed for atoi(q) == 0  (which results in NOTHING for that
+       * parameter, and thus no flags), since this eliminates 'visual'
+       * attributes (which, if not built-in attrs, have a flag val of 0.)
+       */
+      break;
+    case '>':                  /* old style attribs, die noisily */
+      do_rawlog(LT_ERR, T("ERROR: old-style attribute format in object %d"), i);
+      return -1;
+      break;
+    case '<':                  /* end of list */
+      if ('\n' != getc(f)) {
+       do_rawlog(LT_ERR, T("ERROR: no line feed after < on object %d"), i);
+       return -1;
+      }
+      return count;
+    default:
+      if (c == EOF) {
+       do_rawlog(LT_ERR, T("ERROR: Unexpected EOF on file."));
+       return -1;
+      }
+      do_rawlog(LT_ERR,
+               T
+               ("ERROR: Bad character %c (%d) in attribute list on object %d"),
+               c, c, i);
+      do_rawlog(LT_ERR,
+               T("  (expecting ], >, or < as first character of the line.)"));
+      if (*tbuf1)
+       do_rawlog(LT_ERR, T("  Last attribute read was: %s"), tbuf1);
+      else
+       do_rawlog(LT_ERR, T("  No attributes had been read yet."));
+      return -1;
+    }
+}
+
+extern PRIV attr_privs[];
+
+void
+db_read_attrs(FILE * f, dbref i, int count)
+{
+   char name[ATTRIBUTE_NAME_LIMIT + 1];
+   char l_key[BUFFER_LEN];
+   char value[BUFFER_LEN + 1];
+   boolexp w_lock, r_lock;
+   dbref owner;
+   int derefs = 0, lock_derefs = 0;
+   int flags;
+   char *tmp;
+
+   List(i) = NULL;
+
+   for (; count > 0; count--) {
+     db_read_this_labeled_string(f, "name", &tmp);
+     strcpy(name, tmp);
+     db_read_this_labeled_dbref(f, "owner", &owner);
+     db_read_this_labeled_string(f, "flags", &tmp);
+     flags = string_to_privs(attr_privs, tmp, 0);
+     db_read_this_labeled_number(f, "derefs", &derefs);
+
+     if(HAS_COBRADBFLAG(indb_flags,DBF_NEW_ATR_LOCK)) {
+       db_read_this_labeled_string(f, "writelock", &tmp);
+       strcpy(l_key, tmp);
+       db_read_this_labeled_number(f, "derefs", &lock_derefs);
+       w_lock = parse_boolexp_d(GOD, l_key, (char *) "ATTR", lock_derefs);
+       
+       db_read_this_labeled_string(f, "readlock", &tmp);
+       strcpy(l_key, tmp);
+       db_read_this_labeled_number(f, "derefs", &lock_derefs);
+       r_lock = parse_boolexp_d(GOD, l_key, (char *) "ATTR", lock_derefs);
+
+     }  else {
+       w_lock = TRUE_BOOLEXP;
+       r_lock = TRUE_BOOLEXP;
+     }
+     
+
+     db_read_this_labeled_string(f, "value", &tmp);
+     strcpy(value, tmp);
+     atr_new_add(i, name, value, owner, flags, derefs, w_lock, r_lock);
+   }
+}
+
+/* Load Seperate Flag Database
+ */
+
+int load_flag_db(FILE *f) {
+  int c;
+  char *tmp;
+
+  loading_db = 1;
+  
+  if((c = fgetc(f)) != '+' && (c = fgetc(f)) != '+') {
+    do_rawlog(LT_ERR, T("Flag database does not start with a version string"));
+    return -1;
+  }
+
+  flagdb_flags = ((getref(f) - 2) / 256) - 5;
+  db_read_this_labeled_string(f, "savedtime", &tmp); /* We don't use this info anywhere 'yet' */
+  do_rawlog(LT_ERR, T("Loading flag databased saved on %s UTC"), tmp);
+
+  while((c = fgetc(f)) != EOF) {
+    if(c == '+') {
+       c = fgetc(f);
+        if(c == 'F') {
+          (void) getstring_noalloc(f);
+          flag_read_all(f, "FLAG");
+        } else if(c == 'P'){
+          (void) getstring_noalloc(f);
+          powers_read_all(f);
+        }else {
+          do_rawlog(LT_ERR, T("Unrecgonized database format"));
+          return -1;
+        }
+    } else if(c == '*'){
+      char buff[80];
+      ungetc('*', f);
+      fgets(buff, sizeof buff, f);
+      if(strcmp(buff, EOD) != 0){
+       do_rawlog(LT_ERR, T("ERROR: No end of dump entry."));
+       return -1;
+      } else {
+       do_rawlog(LT_ERR, T("READING: done"));
+       break;
+      }
+    } else {
+      do_rawlog(LT_ERR, T("Unrecognized database format"));
+      return -1;
+    }
+  }
+  loading_db = 0;
+  return 0;
+}
+
+void db_write_flag_db(FILE *f) {
+  int flags;
+
+  /* Write out db flags */
+  flags = 5;
+  flags += FLAG_DBF_CQUOTA_RENAME;
+  /* */
+
+  OUTPUT(fprintf(f, "+V%ld\n", flagdb_flags * 256 + 2));
+  db_write_labeled_string(f, "savedtime", show_time(mudtime, 1));
+  db_write_flags(f);
+  db_write_powers(f);
+  OUTPUT(fputs(EOD, f));
+}
+
+
+
+/** Read a non-labeled database from a file.
+ * \param f the file to read from
+ * \return number of objects in the database
+ */
+
+static dbref
+db_read_oldstyle(FILE * f)
+{
+  int c, opbits;
+  dbref i;
+  struct object *o;
+  int temp = 0;
+  time_t temp_time = 0;
+  div_pbits old_dp_bytes;
+
+  for (i = 0;; i++) {
+    /* Loop invariant: we always begin at the beginning of a line. */
+    errobj = i;
+    c = getc(f);
+    switch (c) {
+      /* make sure database is at least this big *1.5 */
+    case '~':
+      db_init = (getref(f) * 3) / 2;
+      init_objdata_htab(db_init);
+      break;
+      /* Use the MUSH 2.0 header stuff to see what's in this db */
+    case '+':
+      c = getc(f);             /* Skip the V */
+      if (c == 'F') {
+       (void) getstring_noalloc(f);
+       flag_read_all(f, "FLAG");
+      } else {
+       do_rawlog(LT_ERR, T("Unrecognized database format!"));
+       return -1;
+      }
+      break;
+      /* old fashioned database */
+    case '#':
+    case '&':                  /* zone oriented database */
+      do_rawlog(LT_ERR, T("ERROR: old style database."));
+      return -1;
+      break;
+      /* new database */
+    case '!':                  /* non-zone oriented database */
+      /* make space */
+      i = getref(f);
+      db_grow(i + 1);
+      /* read it in */
+      o = db + i;
+      set_name(i, getstring_noalloc(f));
+      o->location = getref(f);
+      o->contents = getref(f);
+      o->exits = getref(f);
+      o->next = getref(f);
+      o->parent = getref(f);
+      o->locks = NULL;
+      get_new_locks(i, f, -1);
+      o->owner = getref(f);
+      o->zone = getref(f);
+      if (indb_flags & DBF_DIVISIONS) {
+        o->division.object = getref(f);
+        o->division.level = getref(f);
+       /* This is the spot for poweres.. read it to NULL ville */
+       getref(f);
+       /* Read Old Style and convert */
+        old_dp_bytes = getbytes(f);
+       o->division.dp_bytes = (div_pbits) convert_old_cobra_powers(old_dp_bytes);
+      } else {
+        o->division.object = -1;
+        o->division.level = 1;
+        o->division.dp_bytes = NULL;
+      }
+      s_Pennies(i, getref(f));
+      if (indb_flags & DBF_NEW_FLAGS) {
+       o->type = getref(f);
+       o->flags = string_to_bits("FLAG", getstring_noalloc(f));
+      } else {
+       int old_flags, old_toggles;
+       old_flags = getref(f);
+       old_toggles = getref(f);
+       if ((o->type = type_from_old_flags(old_flags)) < 0) {
+         do_rawlog(LT_ERR, T("Unable to determine type of #%d\n"), i);
+         return -1;
+       }
+       o->flags =
+         flags_from_old_flags(old_flags, old_toggles, o->type);
+      }
+
+      /* We need to have flags in order to do this right, which is why
+       * we waited until now
+       */
+      switch (Typeof(i)) {
+      case TYPE_PLAYER:
+       current_state.players++;
+       current_state.garbage--;
+       break;
+      case TYPE_THING:
+       current_state.things++;
+       current_state.garbage--;
+       break;
+      case TYPE_EXIT:
+       current_state.exits++;
+       current_state.garbage--;
+       break;
+      case TYPE_ROOM:
+       current_state.rooms++;
+       current_state.garbage--;
+       break;
+      case TYPE_DIVISION:
+        current_state.divisions++;
+        current_state.garbage--;
+        break;
+      }
+
+      if (IsPlayer(i) && (strlen(o->name) > (Size_t) PLAYER_NAME_LIMIT)) {
+       char buff[BUFFER_LEN + 1];      /* The name plus a NUL */
+       strncpy(buff, o->name, PLAYER_NAME_LIMIT);
+       buff[PLAYER_NAME_LIMIT] = '\0';
+       set_name(i, buff);
+       do_rawlog(LT_CHECK,
+                 T(" * Name of #%d is longer than the maximum, truncating.\n"),
+                 i);
+      } else if (!IsPlayer(i) && (strlen(o->name) > OBJECT_NAME_LIMIT)) {
+       char buff[OBJECT_NAME_LIMIT + 1];       /* The name plus a NUL */
+       strncpy(buff, o->name, OBJECT_NAME_LIMIT);
+       buff[OBJECT_NAME_LIMIT] = '\0';
+       set_name(i, buff);
+       do_rawlog(LT_CHECK,
+                 T(" * Name of #%d is longer than the maximum, truncating.\n"),
+                 i);
+      }
+
+      if (!(indb_flags & DBF_VALUE_IS_COST) && IsThing(i))
+       s_Pennies(i, (Pennies(i) + 1) * 5);
+      if(!(indb_flags & DBF_DIVISIONS)) {
+        opbits = getref(f);    /* save old pows to c to pass to conversion after object is read*/
+       convert_object_powers(i, opbits);
+      }
+
+      /* Remove the STARTUP and ACCESSED flags */
+      if (!(indb_flags & DBF_NO_STARTUP_FLAG)) {
+       clear_flag_internal(i, "STARTUP");
+       clear_flag_internal(i, "ACCESSED");
+      }
+
+      /* Clear the GOING flags. If it was scheduled for destruction
+       * when the db was saved, it gets a reprieve.
+       */
+      if(!Guest(i)) {
+        clear_flag_internal(i, "GOING");
+        clear_flag_internal(i, "GOING_TWICE");
+      }
+
+      /* If there are channels in the db, read 'em in */
+      /* We don't support this anymore, so we just discard them */
+      if (!(indb_flags & DBF_NO_CHAT_SYSTEM))
+       temp = getref(f);
+      else
+       temp = 0;
+
+      /* If there are warnings in the db, read 'em in */
+      temp = MAYBE_GET(f, DBF_WARNINGS);
+      o->warnings = temp;
+      /* If there are creation times in the db, read 'em in */
+      temp_time = MAYBE_GET(f, DBF_CREATION_TIMES);
+      if (temp_time)
+       o->creation_time = (time_t) temp_time;
+      else
+       o->creation_time = mudtime;
+      temp_time = MAYBE_GET(f, DBF_CREATION_TIMES);
+      if (temp_time || IsPlayer(i))
+       o->modification_time = (time_t) temp_time;
+      else
+       o->modification_time = o->creation_time;
+
+      /* read attribute list for item */
+      if ((o->attrcount = get_list(f, i)) < 0) {
+       do_rawlog(LT_ERR, T("ERROR: bad attribute list object %d"), i);
+       return -1;
+      }
+      if (!(indb_flags & DBF_AF_NODUMP)) {
+       /* Clear QUEUE and SEMAPHORE attributes */
+       atr_clr(i, "QUEUE", GOD);
+       atr_clr(i, "SEMAPHORE", GOD);
+      }
+      /* check to see if it's a player */
+      if (IsPlayer(i)) {
+       add_player(i, NULL);
+       clear_flag_internal(i, "CONNECTED");
+      }
+      break;
+
+    case '*':
+      {
+       char buff[80];
+       ungetc('*', f);
+       fgets(buff, sizeof buff, f);
+       if (strcmp(buff, EOD) != 0) {
+         do_rawlog(LT_ERR, T("ERROR: No end of dump after object #%d"), i - 1);
+         return -1;
+       } else {
+         if(!(indb_flags & DBF_DIVISIONS) || (indb_flags & DBF_TYPE_GARBAGE)) {
+           dbref master_division;
+             /* Final Step of DB Conversion
+              * We're gonna first Create the Master Division
+              * Second we're gonna loop through the database
+              * set all players except unregistered players to the master division
+              * and set all levels appropriate
+              **/
+           i = GOD;
+           master_division = new_object();
+           set_name(master_division, "Master Division");
+           Type(master_division) = TYPE_DIVISION;
+           PUSH(master_division, Contents(i));
+           Owner(master_division) = i;
+           CreTime(master_division) = ModTime(master_division) = mudtime;
+           atr_new_add(master_division, "DESCRIBE",
+               "This is the master division that comes before all divisions.", i,
+               AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH, 1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+           current_state.divisions++;
+           SLEVEL(master_division) =  LEVEL_DIRECTOR;
+           SLEVEL(i) = LEVEL_GOD;
+           /* Division & God Setup now run through the database & set everyitng else up */
+           for(i = 0; i < db_top; i++)
+           {
+             if(has_flag_by_name(i, "UNREGISTERED", TYPE_PLAYER)){
+               SLEVEL(i) = LEVEL_UNREGISTERED;
+               clear_flag_internal(i, "UNREGISTERED");
+             } else if(i != master_division) {
+               SDIV(i).object = master_division;
+               if(!has_flag_by_name(i, "WIZARD", NOTYPE) &&
+                   !has_flag_by_name(i, "ROYALTY", NOTYPE) && SLEVEL(i) != -500) {
+                 SLEVEL(i) = LEVEL_PLAYER;
+               } else if(SLEVEL(i) == -500) {
+                 SLEVEL(i) = LEVEL_GUEST;
+               } else { /* They look like wizard or royalty.. Set 'em to 29 */
+                 SLEVEL(i) = LEVEL_DIRECTOR;
+               }
+             }
+           }
+               do_rawlog(LT_ERR, T("DB Conversion complete"));
+         }
+         do_rawlog(LT_ERR, "READING: done");
+         loading_db = 0;
+         fix_free_list();
+         dbck();
+         log_mem_check();
+         return db_top;
+       }
+      }
+    default:
+      do_rawlog(LT_ERR, T("ERROR: failed object %d"), i);
+      return -1;
+    }
+  }
+}
+
+/** Read the object database from a file.
+ * This function reads the entire database from a file. See db_write()
+ * for some notes about the expected format.
+ * \param f file pointer to read from.
+ * \return number of objects in the database.
+ */
+dbref
+db_read(FILE * f)
+{
+  int c;
+  dbref i = 0;
+  char *tmp;
+  struct object *o;
+  div_pbits div_powers;
+  /* Changed because CobraMUSH 0.7 and after assume DBF_TYPE_GARBAGE implies
+   * a PennMUSH database that needs to be converted. */
+  /* int minimum_flags =
+    DBF_NEW_STRINGS | DBF_TYPE_GARBAGE | DBF_SPLIT_IMMORTAL | DBF_NO_TEMPLE |
+    DBF_SPIFFY_LOCKS; */
+
+  int minimum_flags =
+    DBF_NEW_STRINGS | DBF_SPLIT_IMMORTAL | DBF_NO_TEMPLE | DBF_SPIFFY_LOCKS;
+
+  log_mem_check();
+
+  loading_db = 1;
+
+  clear_players();
+  db_free();
+  indb_flags = 1;
+
+  c = fgetc(f);
+  if (c != '+') {
+    do_rawlog(LT_ERR, T("Database does not start with a version string"));
+    return -1;
+  }
+  c = fgetc(f);
+  if (c != 'V') {
+    do_rawlog(LT_ERR, T("Database does not start with a version string"));
+    return -1;
+  }
+  indb_flags = ((getref(f) - 2) / 256) - 5;
+  /* if you want to read in an old-style database, use an earlier
+   * patchlevel to upgrade.
+   */
+  if (((indb_flags & minimum_flags) != minimum_flags) ||
+      (indb_flags & DBF_NO_POWERS)) {
+    do_rawlog(LT_ERR, T("ERROR: Old database without required dbflags."));
+    return -1;
+  }
+
+  if (!(indb_flags & DBF_LABELS))
+    return db_read_oldstyle(f);
+
+  db_read_this_labeled_string(f, "savedtime", &tmp);
+  strcpy(db_timestamp, tmp);
+
+  do_rawlog(LT_ERR, T("Loading database saved on %s UTC"), db_timestamp);
+
+  while ((c = fgetc(f)) != EOF) {
+    switch (c) {
+    case '+':
+      c = fgetc(f);
+      if (c == 'F') {
+       (void) getstring_noalloc(f);
+       flag_read_all(f, "FLAG");
+      } else if (c == 'P') {
+       (void) getstring_noalloc(f);
+       if(!(indb_flags & DBF_TYPE_GARBAGE))
+         powers_read_all(f);
+       else {
+         if(ps_tab._Read_Powers_ == 0)
+           init_powers();
+         flag_read_all(f, NULL);
+       }
+      } else {
+       do_rawlog(LT_ERR, T("Unrecognized database format!"));
+       return -1;
+      }
+      break;
+    case '~':
+      db_init = (getref(f) * 3) / 2;
+      init_objdata_htab(db_init);
+      break;
+    case '!':
+      /* Read an object */
+      {
+       char *label, *value;
+       /* Thre should be an entry in the enum and following table and
+          switch for each top-level label associated with an
+          object. Not finding a label is not an error; the default
+          set in new_object() is used. Finding a label not listed
+          below is an error. */
+       enum known_labels {
+         LBL_NAME, LBL_LOCATION, LBL_CONTENTS, LBL_EXITS,
+         LBL_NEXT, LBL_PARENT, LBL_LOCKS, LBL_OWNER, LBL_ZONE,
+         LBL_PENNIES, LBL_TYPE, LBL_FLAGS, LBL_POWERGROUPS, LBL_POWERS, LBL_WARNINGS,
+         LBL_CREATED, LBL_MODIFIED, LBL_ATTRS, LBL_ERROR, LBL_LEVEL,
+         LBL_DIVOBJ, LBL_PWRLVL, LBL_LMOD
+       };
+       struct label_table {
+         const char *label;
+         enum known_labels tag;
+       };
+       struct label_table fields[] = {
+         {"name", LBL_NAME},
+         {"location", LBL_LOCATION},
+         {"contents", LBL_CONTENTS},
+         {"exits", LBL_EXITS},
+         {"next", LBL_NEXT},
+         {"parent", LBL_PARENT},
+         {"lockcount", LBL_LOCKS},
+         {"owner", LBL_OWNER},
+         {"zone", LBL_ZONE},
+         {"pennies", LBL_PENNIES},
+         {"type", LBL_TYPE},
+         {"flags", LBL_FLAGS},
+         {"powers", LBL_POWERS},
+         {"warnings", LBL_WARNINGS},
+         {"created", LBL_CREATED},
+         {"modified", LBL_MODIFIED},
+         {"attrcount", LBL_ATTRS},
+         {"division_object", LBL_DIVOBJ},
+         {"level", LBL_LEVEL},
+         {"powerlevel", LBL_PWRLVL},
+         {"powergroup", LBL_POWERGROUPS},
+         {"lastmod", LBL_LMOD},
+          /* Add new label types here. */
+         {NULL, LBL_ERROR}
+       }, *entry;
+       enum known_labels the_label;
+
+       i = getref(f);
+       db_grow(i + 1);
+       o = db + i;
+       while (1) {
+         c = fgetc(f);
+         ungetc(c, f);
+         /* At the start of another object or the EOD marker */
+         if (c == '!' || c == '*')
+           break;
+         db_read_labeled_string(f, &label, &value);
+         the_label = LBL_ERROR;
+         /* Look up the right enum value in the label table */
+         for (entry = fields; entry->label; entry++) {
+           if (strcmp(entry->label, label) == 0) {
+             the_label = entry->tag;
+             break;
+           }
+         }
+         switch (the_label) {
+         case LBL_NAME:
+           set_name(i, value);
+           break;
+         case LBL_LOCATION:
+           o->location = qparse_dbref(value);
+           break;
+         case LBL_CONTENTS:
+           o->contents = qparse_dbref(value);
+           break;
+         case LBL_EXITS:
+           o->exits = qparse_dbref(value);
+           break;
+         case LBL_NEXT:
+           o->next = qparse_dbref(value);
+           break;
+         case LBL_PARENT:
+           o->parent = qparse_dbref(value);
+           break;
+         case LBL_LOCKS:
+           get_new_locks(i, f, parse_integer(value));
+           break;
+         case LBL_LEVEL:
+           o->division.level = parse_integer(value); 
+           break;
+         case LBL_PWRLVL:
+           /* Do nothing with this value */
+           break;
+         case LBL_DIVOBJ:
+           o->division.object = qparse_dbref(value);
+           break;
+         case LBL_OWNER:
+           o->owner = qparse_dbref(value);
+           break;
+         case LBL_ZONE:
+           o->zone = qparse_dbref(value);
+           break;
+         case LBL_PENNIES:
+           s_Pennies(i, parse_integer(value));
+           break;
+         case LBL_TYPE:
+           o->type = parse_integer(value);
+           switch (Typeof(i)) {
+           case TYPE_PLAYER:
+             current_state.players++;
+             current_state.garbage--;
+             break;
+           case TYPE_DIVISION:
+             current_state.divisions++;
+             current_state.garbage--;
+             break;
+           case TYPE_THING:
+             current_state.things++;
+             current_state.garbage--;
+             break;
+           case TYPE_EXIT:
+             current_state.exits++;
+             current_state.garbage--;
+             break;
+           case TYPE_ROOM:
+             current_state.rooms++;
+             current_state.garbage--;
+             break;
+           }
+           break;
+         case LBL_FLAGS:
+           o->flags = string_to_bits("FLAG", value);
+           /* Clear the GOING flags. If it was scheduled for destruction
+            * when the db was saved, it gets a reprieve.
+            */
+           clear_flag_internal(i, "GOING");
+           clear_flag_internal(i, "GOING_TWICE");
+           break;
+         case LBL_POWERS:
+             div_powers = string_to_dpbits(value);
+             if(power_is_zero(div_powers, DP_BYTES) == 0) {
+               o->division.dp_bytes = NULL;
+               mush_free(div_powers, "POWER_SPOT");
+             } else o->division.dp_bytes =  div_powers;
+           break;
+         case LBL_POWERGROUPS:
+           powergroup_db_set(NOTHING, i, value, 0);
+           break;
+         case LBL_WARNINGS:
+           o->warnings = parse_warnings(NOTHING, value);
+           break;
+         case LBL_CREATED:
+           o->creation_time = (time_t) parse_integer(value);
+           break;
+         case LBL_MODIFIED:
+           o->modification_time = (time_t) parse_integer(value);
+           break;
+         case LBL_LMOD:
+           db[i].lastmod = NULL;
+           set_lmod(i, value);
+           break;
+         case LBL_ATTRS:
+           {
+             int attrcount = parse_integer(value);
+             db_read_attrs(f, i, attrcount);
+           }
+           break;
+         case LBL_ERROR:
+         default:
+           do_rawlog(LT_ERR, T("Unrecognized field '%s' in object #%d"),
+                     label, i);
+           return -1;
+         }
+       }
+       if (IsPlayer(i) && (strlen(o->name) > (size_t) PLAYER_NAME_LIMIT)) {
+         char buff[BUFFER_LEN + 1];    /* The name plus a NUL */
+         strncpy(buff, o->name, PLAYER_NAME_LIMIT);
+         buff[PLAYER_NAME_LIMIT] = '\0';
+         set_name(i, buff);
+         do_rawlog(LT_CHECK,
+                   T
+                   (" * Name of #%d is longer than the maximum, truncating.\n"),
+                   i);
+       } else if (!IsPlayer(i) && (strlen(o->name) > OBJECT_NAME_LIMIT)) {
+         char buff[OBJECT_NAME_LIMIT + 1];     /* The name plus a NUL */
+         strncpy(buff, o->name, OBJECT_NAME_LIMIT);
+         buff[OBJECT_NAME_LIMIT] = '\0';
+         set_name(i, buff);
+         do_rawlog(LT_CHECK,
+                   T
+                   (" * Name of #%d is longer than the maximum, truncating.\n"),
+                   i);
+       }
+       if (IsPlayer(i)) {
+         add_player(i, NULL);
+         clear_flag_internal(i, "CONNECTED");
+       }
+      }
+      break;
+    case '*':
+      {
+       char buff[80];
+       ungetc('*', f);
+       fgets(buff, sizeof buff, f);
+       if (strcmp(buff, EOD) != 0) {
+         do_rawlog(LT_ERR, T("ERROR: No end of dump after object #%d"), i - 1);
+         return -1;
+       } else {
+         loading_db = 0;
+         log_mem_check();
+         fix_free_list();
+         dbck();
+         log_mem_check();
+         do_rawlog(LT_ERR, "READING: done");
+         return db_top;
+       }
+      }
+    default:
+      do_rawlog(LT_ERR, T("ERROR: failed object %d"), i);
+      return -1;
+    }
+  }
+  return -1;
+}
+
+static void
+init_objdata_htab(int size)
+{
+  hashinit(&htab_objdata, size, 4);
+  hashinit(&htab_objdata_keys, 8, 32);
+}
+
+extern time_t first_start_time;
+
+void init_postconvert() {
+  dbref master_division, i;
+
+         if((!(indb_flags & DBF_DIVISIONS) || (indb_flags & DBF_TYPE_GARBAGE)) && CreTime(0) != first_start_time ) {
+           do_rawlog(LT_ERR, "Beginning Pennmush to CobraMUSH database conversion process.");
+             /* Final Step of DB Conversion
+              * We're gonna first Create the Master Division
+              * Second we're gonna loop through the database
+              * set all players except unregistered players to the master division
+              * and set all levels appropriate
+              **/
+           i = GOD;
+           master_division = new_object();
+           set_name(master_division, "Master Division");
+           Type(master_division) = TYPE_DIVISION;
+           PUSH(master_division, Contents(i));
+           Owner(master_division) = i;
+           CreTime(master_division) = ModTime(master_division) = mudtime;
+           Location(master_division) = i;
+           atr_new_add(master_division, "DESCRIBE",
+               "This is the master division that comes before all divisions.", i,
+               AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH, 1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+           current_state.divisions++;
+           SLEVEL(master_division) =  LEVEL_DIRECTOR;
+           SLEVEL(i) = LEVEL_GOD;
+           /* Division & God Setup now run through the database & set everyitng else up */
+           for(i = 0; i < db_top; i++)
+           {
+             if(IsGarbage(i))
+               continue;
+             if(has_flag_by_name(i, "UNREGISTERED", TYPE_PLAYER)){
+               SLEVEL(i) = LEVEL_UNREGISTERED;
+               clear_flag_internal(i, "UNREGISTERED");
+             } else if(i != master_division) {
+               SDIV(i).object = master_division;
+               if(!has_flag_by_name(i, "WIZARD", NOTYPE) &&
+                   !has_flag_by_name(i, "ROYALTY", NOTYPE) && SLEVEL(i) != -500) {
+                 SLEVEL(i) = LEVEL_PLAYER;
+               } else if(SLEVEL(i) == -500) {
+                 SLEVEL(i) = LEVEL_GUEST;
+               } else if(has_flag_by_name(i, "WIZARD", NOTYPE)) { /* They look like wizard or royalty.. Set 'em up right */
+                 SLEVEL(i) = LEVEL_DIRECTOR;
+                 powergroup_db_set(GOD, i, "WIZARD", 1);
+               } else if(has_flag_by_name(i, "ROYALTY", NOTYPE)) {
+                 SLEVEL(i) = LEVEL_ADMIN;
+                 powergroup_db_set(GOD, i, "ROYALTY", 1);
+               }
+             }
+           }
+               do_rawlog(LT_ERR, T("DB Conversion complete"));
+         }
+
+}
+
+/** Add data to the object data hashtable. 
+ * This hash table is typically used to store transient object data
+ * that is built at database load and isn't saved to disk, but it
+ * can be used for other purposes as well - it's a good general
+ * tool for hackers who want to add their own data to objects.
+ * This function adds data to the hashtable.
+ * \param thing dbref of object to associate the data with.
+ * \param keybase base string for type of data.
+ * \param data pointer to the data to store.
+ * \return data passed in.
+ */
+void *
+set_objdata(dbref thing, const char *keybase, void *data)
+{
+  hashdelete(tprintf("%s_#%d", keybase, thing), &htab_objdata);
+  if (data) {
+    if (hashadd(tprintf("%s_#%d", keybase, thing), data, &htab_objdata) < 0)
+      return NULL;
+    if (hash_find(&htab_objdata_keys, keybase) == NULL) {
+      char *newkey = strdup(keybase);
+      hashadd(keybase, (void *) &newkey, &htab_objdata_keys);
+    }
+  }
+  return data;
+}
+
+/** Retrieve data from the object data hashtable.
+ * \param thing dbref of object data is associated with.
+ * \param keybase base string for type of data.
+ * \return data stored for that object and keybase, or NULL.
+ */
+void *
+get_objdata(dbref thing, const char *keybase)
+{
+  return hashfind(tprintf("%s_#%d", keybase, thing), &htab_objdata);
+}
+
+/** Clear all of an object's data from the object data hashtable.
+ * This function clears any data associated with a given object
+ * that's in the object data hashtable (under any keybase).
+ * It's used before we free the object.
+ * \param thing dbref of object data is associated with.
+ */
+void
+clear_objdata(dbref thing)
+{
+  char *p;
+  for (p = (char *) hash_firstentry(&htab_objdata_keys);
+       p; p = (char *) hash_nextentry(&htab_objdata_keys)) {
+    set_objdata(thing, p, NULL);
+  }
+}
+
+/** Create a basic 3-object (Start Room, God, Master Room) database. */
+void
+create_minimal_db(void)
+{
+  dbref start_room, god, master_room, master_division;
+
+  int desc_flags = AF_VISUAL | AF_NOPROG | AF_PREFIXMATCH;
+
+  start_room = new_object();   /* #0 */
+  god = new_object();          /* #1 */
+  master_room = new_object();  /* #2 */
+  master_division = new_object(); /* #3 */
+
+  init_objdata_htab(DB_INITIAL_SIZE);
+
+  set_name(start_room, "Room Zero");
+  Type(start_room) = TYPE_ROOM;
+  Flags(start_room) = string_to_bits("FLAG", "LINK_OK");
+  atr_new_add(start_room, "DESCRIBE", "You are in Room Zero.", GOD, desc_flags,
+             1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+  CreTime(start_room) = ModTime(start_room) = mudtime;
+  current_state.rooms++;
+
+  set_name(god, "One");
+  Type(god) = TYPE_PLAYER;
+  Location(god) = start_room;
+  Home(god) = start_room;
+  Owner(god) = god;
+  CreTime(god) = mudtime;
+  ModTime(god) = (time_t) 0;
+  SDIV(god).object = master_division;
+  add_lock(god, god, Basic_Lock, parse_boolexp(god, "=me", Basic_Lock), -1);
+  add_lock(god, god, Enter_Lock, parse_boolexp(god, "=me", Enter_Lock), -1);
+  add_lock(god, god, Use_Lock, parse_boolexp(god, "=me", Use_Lock), -1);
+  atr_new_add(god, "DESCRIBE", "You see Number One.", god, desc_flags, 1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+#ifdef USE_MAILER
+  atr_new_add(god, "MAILCURF", "0", god, AF_LOCKED | AF_NOPROG, 1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+  add_folder_name(god, 0, "inbox");
+#endif
+  PUSH(god, Contents(start_room));
+  add_player(god, NULL);
+  s_Pennies(god, START_BONUS);
+  local_data_create(god);
+  current_state.players++;
+
+  set_name(master_room, "Master Room");
+  Type(master_room) = TYPE_ROOM;
+  Flags(master_room) = string_to_bits("FLAG", "FLOATING");
+  Owner(master_room) = god;
+  CreTime(master_room) = ModTime(master_room) = mudtime;
+  atr_new_add(master_room, "DESCRIBE",
+             "This is the master room. Any exit in here is considered global. The same is true for objects with $-commands placed here.",
+             god, desc_flags, 1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+  current_state.rooms++;
+/* Master Division */
+  Location(master_division) = god;
+  Home(master_division) = god;
+  set_name(master_division, "Master Division");
+  Type(master_division) = TYPE_DIVISION; 
+  PUSH(master_division, Contents(god));
+  Owner(master_division) = god;
+  CreTime(master_division) = ModTime(master_division) = mudtime; 
+  atr_new_add(master_division, "DESCRIBE", 
+      "This is the master division that comes before all divisions.", god, desc_flags, 1, TRUE_BOOLEXP, TRUE_BOOLEXP);
+  current_state.divisions++;
+  /* division stuff */
+  SDIV(start_room).object = master_division;
+  SDIV(master_room).object = master_division;
+  SLEVEL(god) = LEVEL_GOD;
+  SLEVEL(start_room) = LEVEL_SYSBUILDER;
+  SLEVEL(master_room) = LEVEL_SYSCODER;
+  SLEVEL(master_division) = LEVEL_DIRECTOR;
+}
diff --git a/src/destroy.c b/src/destroy.c
new file mode 100644 (file)
index 0000000..e5a5e8f
--- /dev/null
@@ -0,0 +1,1530 @@
+/*
+ * \file destroy.c
+ *
+ * \brief Destroying objects and consistency checking.
+ *
+ * This file has two main parts. One part is the functions for destroying
+ * objects and getting objects off of the free list. The major public
+ * functions here are do_destroy(), free_get(), and purge().
+ *
+ * The other part is functions for checking the consistency of the
+ * database, and repairing any inconsistencies that are found. The
+ * major function in this group is dbck().
+ * 
+ *
+ * These lengthy comments are by Ralph Melton, December 1995.
+ *
+ * First, a discourse on the theory of how we handle destruction.
+ *
+ * We want to maintain the following invariants:
+ * 1. All destroyed objects are on the free list. (linked through the next
+ *    fields.)
+ * 2. All objects on the free list are destroyed objects.
+ * 3. No undestroyed object has its next, contents, location, or home
+ *    field pointing to a destroyed object.
+ * 4. No object's zone or parent is a destroyed object.
+ * 5. No object's owner is a destroyed object.
+ * 
+ * For the sake of efficiency, we allow indirect locks and other locks to
+ * refer to destroyed objects; boolexp.c had better be able to cope with
+ * these.
+ *
+ *
+ * There are three logically distinct parts to destroying an object:
+ *
+ * Part 1: we do all the permissions checks, check for the SAFE flag
+ * and the override switch, and decide that yes, we are going to destroy
+ * this object.
+ *
+ * Part 2: we eliminate all the links from other objects in the database
+ * to this object. This processing may depend on the object's type.
+ *
+ * Part 3: (logically concurrent with part 2, and must happen together
+ * with part 2) Remove any commands the object may have in the queue,
+ * free all the storage associated with the object, set the name to
+ * 'Garbage', and set this object to be a destroyed object, and put it
+ * on the free list. This process is independent of object type.
+ *
+ * Note that phases 2 and 3 do not have to happen immediately after Phase 1.
+ * To allow some delay, we set the object GOING, and then process it on
+ * the check that happens every ten minutes. 
+ *
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <assert.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "copyrite.h"
+#include "conf.h"
+#include "mushdb.h"
+#include "match.h"
+#include "externs.h"
+#include "log.h"
+#include "game.h"
+#include "extmail.h"
+#include "malias.h"
+#include "attrib.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "confmagic.h"
+
+
+
+dbref first_free = NOTHING;   /**< Object at top of free list */
+
+static dbref what_to_destroy(dbref player, char *name, int confirm);
+void pre_destroy(dbref player, dbref thing);
+void free_object(dbref thing);
+static void empty_contents(dbref thing);
+static void clear_thing(dbref thing);
+static void clear_player(dbref thing);
+static void clear_room(dbref thing);
+static void clear_exit(dbref thing);
+
+static void check_fields(void);
+static void check_connected_rooms(void);
+static void mark_connected(dbref loc);
+static void check_connected_marks(void);
+static void mark_contents(dbref loc);
+static void check_contents(void);
+static void check_locations(void);
+static void check_zones(void);
+static void check_divisions(void);
+static int attribute_owner_helper
+  (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr, void *args);
+
+#ifdef CHAT_SYSTEM
+extern void remove_all_obj_chan(dbref thing);
+extern void chan_chownall(dbref old, dbref new);
+#endif /* CHAT_SYSTEM */
+
+extern struct db_stat_info current_state;
+
+/** Mark an object */
+#define SetMarked(x)    Type(x) |= TYPE_MARKED
+/** Unmark an object */
+#define ClearMarked(x)  Type(x) &= ~TYPE_MARKED
+
+
+/* Section I: do_destroy() and related functions. This section is where
+ * the human interface of do_destroy() should largely be determined.
+ */
+
+/* Methinks it's time to consider human interfaces criteria for destruction
+ * as well as to consider the invariants that need to be maintained.
+ *
+ * My major criteria are these (with no implied ranking, since I haven't
+ * decided how they balance out):
+ * 
+ * 1) It's easy to destroy things you intend to destroy.
+ * 
+ * 2) It's easy to correct from destroying things that you don't intend
+ * to destroy. This includes both typos and realizing that you didn't mean
+ * to destroy that. This principle requires two sub-principles:
+ *      a) The player gets notified when something 'important' gets
+ *         marked to be destroyed--and gets told .what. is marked.
+ *      b) The actual destruction of the important thing is delayed
+ *         long enough that you can recover from it.
+ *
+ * 3) You can't destroy something you don't have the proper privileges to
+ * destroy. (Obvious, but still worth writing down.)
+ *
+ * To try to achieve a reasonable balance between items 1) and 2), we
+ * have the following design:
+ * Everything is finally destroyed on the second purge after the @destroy
+ * command is done, unless it is set !GOING in the meantime.
+ * @destroying an object while it is set GOING destroys it immediately.
+ *
+ * Let me introduce a little jargon for this discussion:
+ *      pre-destroying an object == setting it GOING, running the @adestroy.
+ *              (Pre-destroying corresponds to phase 1 above.)
+ *      purging an object == actually irrevocably making it vanish.
+ *              (This corresponds to phases 2 and 3 above.)
+ *      undestroying an object == setting it !GOING, etc.
+ *
+ * We would also like to have an @adestroy attribute that contains
+ * code to be executed when the object is destroyed. This is
+ * complicated by the fact that the object is going to be
+ * destroyed. To work around this, we run the @adestroy when the
+ * object is pre-destroyed, not when it's actually purged. This
+ * introduces the possibility that the adestroy may be invoked for
+ * something that is then undestroyed. To compensate for that, we run
+ * the @startup attribute when something is undestroyed.
+ *
+ * Another issue is how to run the @adestroy for objects that are
+ * destroyed as a consequence of other objects being destroyed. For
+ * example, when rooms are destroyed, any exits leading from those
+ * rooms are also destroyed, and when a player is destroyed, !SAFE
+ * objects they own may also be destroyed.
+ * 
+ * To handle this, we do the following:
+ * pre-destroying a room pre-destroys all its exits.
+ * pre-destroying a player pre-destroys all the objects that will be purged
+ * when that player is purged.
+ *
+ * This requires the following about undestroys:
+ * undestroying an exit undestroys its source room.
+ * undestroying any object requires undestroying its owner.
+ *
+ * But it also seems to require the following in order to make '@destroy
+ * foo; @undestroy foo' a no-op for all foo:
+ * undestroying a room undestroys all its exits.
+ * undestroying a player undestroys all its GOING things.
+ * 
+ * Now, consider this scenario:
+ * Player A owns room #1. Player B owns exit #2, whose source is room #1.
+ * Player B owns thing #3. Player A and player B are both pre-destroyed;
+ * none of the objects are set SAFE. Thing #3 is then undestroyed.
+ * 
+ * If you trace through the dependencies, you find that this involves
+ * undestroying all the objects, including both players! Is that what
+ * we want? It seems to me that it would be very surprising in practice.
+ * 
+ * To reconcile this, we introduce the following compromise.
+ * undestroying a room undestroys all exits in the room that are not owned
+ *      by a GOING player or set SAFE..
+ * undestroying a player undestroys all objects he owns that are not exits
+ *      in a GOING room that he does not own.
+ * 
+ * In this way, the propagation of previous scenario would die out at exit
+ * #2, which would stay GOING. Metaphorically, there are two 'votes' for
+ * its destruction: the destruction of room #1, and the destruction of
+ * player B. Undestroying player B by undestroying thing #3 removes one
+ * of the 'votes' for exit #2's destruction, but there would still be
+ * the vote from room #1.  
+ */
+
+
+/** Determine what object to destroy and if we're allowed.
+ * Do all matching and permissions checking. Returns the object to be
+ * destroyed if all the permissions checks are successful, otherwise
+ * return NOTHING.
+ */
+static dbref
+what_to_destroy(dbref player, char *name, int confirm)
+{
+  dbref thing;
+
+  thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
+  if (thing == NOTHING)
+    return NOTHING;
+
+  if (IsGarbage(thing)) {
+    notify(player, T("Destroying that again is hardly necessary."));
+    return NOTHING;
+  }
+  if (God(thing)) {
+    notify(player, T("Destroying God would be blasphemous."));
+    return NOTHING;
+  }
+  /* To destroy, you must either:
+   * 1. Control it
+   * 2. Control its source or destination if it's an exit
+   * 3. Be dealing with a dest-ok thing and pass its lock/destroy
+   */
+  if (!controls(player, thing) &&
+      !(IsExit(thing) &&
+       (controls(player, Destination(thing)) ||
+        controls(player, Source(thing)))) &&
+      !(DestOk(thing) && eval_lock(player, thing, Destroy_Lock))) {
+    notify(player, T("Permission denied."));
+    return NOTHING;
+  }
+  if (thing == PLAYER_START || thing == MASTER_ROOM || thing == BASE_ROOM ||
+      thing == DEFAULT_HOME || God(thing)) {
+    notify(player, T("That is too special to be destroyed."));
+    return NOTHING;
+  }
+  if (REALLY_SAFE) {
+    if (Safe(thing) && !DestOk(thing)) {
+      notify(player,
+            T
+            ("That object is set SAFE. You must set it !SAFE before destroying it."));
+      return NOTHING;
+    }
+  } else {                     /* REALLY_SAFE */
+    if (Safe(thing) && !DestOk(thing) && !confirm) {
+      notify(player, T("That object is marked SAFE. Use @nuke to destroy it."));
+      return NOTHING;
+    }
+  }
+  /* check to make sure there's no accidental destruction */
+  if (!confirm && !Owns(player, thing) && !DestOk(thing)) {
+    notify(player,
+          T("That object does not belong to you. Use @nuke to destroy it."));
+    return NOTHING;
+  }
+  /* what kind of thing we are destroying? */
+  switch (Typeof(thing)) {
+  case TYPE_PLAYER:
+    if (!IsPlayer(player)) { notify(player, T("Programs don't kill people; people kill people!"));
+      return NOTHING;
+    }
+    /* The only player a player can own() is themselves...
+     * If they somehow manage to own() another player, they can't
+     * nuke that one either...which seems like a good plan, although
+     * the error message is a bit confusing. -DTC
+     */
+    if (thing == player) {
+      notify(player, T("Sorry, no suicide allowed."));
+      return NOTHING;
+    }
+    if (Connected(thing)) {
+      notify(player,
+            T("How gruesome. You may not destroy players who are connected."));
+      return NOTHING;
+    }
+    if (!confirm) {
+      notify(player, T("You must use @nuke to destroy a player."));
+      return NOTHING;
+    }
+    if(!CanNuke(player, thing)) {
+      notify(player, "Permission denied.");
+      return NOTHING;
+    }
+    break;
+  case TYPE_DIVISION:
+    if (thing == Home(player) && !IsExit(player)) {
+      notify(player, T("No home-wrecking allowed! Relink first."));
+      return NOTHING;
+    }
+    if (!OOREF(player,div_powover(player, player, "Division"), div_powover(ooref, ooref, "Division"))) {
+      notify(player, T("You need the Division power to destroy a division."));
+      return NOTHING;
+    }
+    if (thing == SDIV(player).object) {
+      notify(player, T("You can't destroy your own division."));
+      return NOTHING;
+    }
+    break;
+  case TYPE_THING:
+    break;
+  case TYPE_ROOM:
+    break;
+  case TYPE_EXIT:
+    break;
+  }
+  return thing;
+
+}
+
+
+/** User interface to destroy an object.
+ * \verbatim
+ * This is the top-level function for @destroy.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of object to destroy.
+ * \param confirm if 1, called with /override (or nuke).
+ */
+void
+do_destroy(dbref player, char *name, int confirm)
+{
+  dbref thing;
+  thing = what_to_destroy(player, name, confirm);
+  if (!GoodObject(thing))
+    return;
+
+  /* If thing has already been marked for destruction, go ahead and
+   * destroy immediately.
+   */
+  if (Going(thing)) {
+    if(IsDivision(thing))
+      do_log(LT_WIZ, player, thing, "Division object destroyed!");
+    free_object(thing);
+    notify(player, T("Destroyed."));
+    return;
+  }
+  /* Present informative messages. */
+  if (!REALLY_SAFE && Safe(thing))
+    notify(player,
+          T
+          ("Warning: Target is set SAFE, but scheduling for destruction anyway."));
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    /* wait until dbck */
+    notify_except(Contents(thing), NOTHING,
+                 T("The room shakes and begins to crumble."), 0);
+    if (Owns(player, thing))
+      notify_format(player,
+                   T("You will be rewarded shortly for %s."),
+                   object_header(player, thing));
+    else {
+      notify_format(player,
+                   T
+                   ("The wrecking ball is on its way for %s's %s and its exits."),
+                   Name(Owner(thing)), object_header(player, thing));
+      notify_format(Owner(thing),
+                   T("%s has scheduled your room %s to be destroyed."),
+                   Name(player), object_header(Owner(thing), thing));
+    }
+    break;
+  case TYPE_PLAYER:
+    /* wait until dbck */
+    notify_format(player,
+                 (DESTROY_POSSESSIONS ?
+                  (REALLY_SAFE ?
+                   T
+                   ("%s and all their (non-SAFE) objects are scheduled to be destroyed.")
+                   :
+                   T
+                   ("%s and all their objects are scheduled to be destroyed."))
+                  : T("%s is scheduled to be destroyed.")),
+                 object_header(player, thing));
+    break;
+  case TYPE_DIVISION:
+    do_log(LT_WIZ, player, thing, "Division object scheduled for destruction.");
+  case TYPE_THING:
+    if (!Owns(player, thing)) {
+      notify_format(player, T("%s's %s is scheduled to be destroyed."),
+                   Name(Owner(thing)), object_header(player, thing));
+      if (!DestOk(thing))
+       notify_format(Owner(thing),
+                     T("%s has scheduled your %s for destruction."),
+                     Name(player), object_header(Owner(thing), thing));
+    } else {
+      notify_format(player, T("%s is scheduled to be destroyed."),
+                   object_header(player, thing));
+    }
+    break;
+  case TYPE_EXIT:
+    if (!Owns(player, thing)) {
+      notify_format(Owner(thing),
+                   T("%s has scheduled your %s for destruction."),
+                   Name(player), object_header(Owner(thing), thing));
+      notify_format(player,
+                   T("%s's %s is scheduled to be destroyed."),
+                   Name(Owner(thing)), object_header(player, thing));
+    } else
+      notify_format(player,
+                   T("%s is scheduled to be destroyed."),
+                   object_header(player, thing));
+    break;
+  default:
+    do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in do_destroy."));
+    return;
+  }
+
+  pre_destroy(player, thing);
+  return;
+}
+
+
+/** Spare an object from slated destruction.
+ * \verbatim
+ * This is the top-level function for @undestroy.
+ * Not undestroy, quite--it's actually 'remove it from its status as about
+ * to be destroyed.'
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of object to be spared.
+ */
+void
+do_undestroy(dbref player, char *name)
+{
+  dbref thing;
+  thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
+  if (!GoodObject(thing)) {
+    return;
+  }
+  if (!controls(player, thing)) {
+    notify(player, T("Alas, your efforts of mercy are in vain."));
+    return;
+  }
+  if(undestroy(player, thing)) {
+  if (IsDivision(thing))
+    do_log(LT_WIZ, player, thing, "Division object destruction canceled.");
+
+  notify_format(Owner(thing),
+               T("Your %s has been spared from destruction."),
+               object_header(Owner(thing), thing));
+  if (player != Owner(thing)) {
+    notify_format(player,
+                 T("%s's %s has been spared from destruction."),
+                 Name(Owner(thing)), object_header(player, thing));
+  }
+  } else {
+         notify(player, T("That can't be undestroyed."));
+  }
+}
+
+
+
+/* Section II: Functions that manage the actual work of destroying
+ * Objects.
+ */
+
+/* Schedule something to be destroyed, run @adestroy, etc. */
+void
+pre_destroy(dbref player, dbref thing)
+{
+  dbref tmp;
+  if (Going(thing) || IsGarbage(thing)) {
+    /* we've already covered this thing. No need to do so again. */
+    return;
+  }
+  set_flag_internal(thing, "GOING");
+  clear_flag_internal(thing, "GOING_TWICE");
+
+  /* Present informative messages, and do recursive destruction. */
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    DOLIST(tmp, Exits(thing)) {
+      pre_destroy(player, tmp);
+    }
+    break;
+  case TYPE_PLAYER:
+    if (DESTROY_POSSESSIONS) {
+      for (tmp = 0; tmp < db_top; tmp++) {
+       if (Owner(tmp) == thing &&
+           (tmp != thing) && (!REALLY_SAFE || !Safe(thing))) {
+         pre_destroy(player, tmp);
+       }
+      }
+    }
+    break;
+  case TYPE_DIVISION:
+  case TYPE_THING:
+    break;
+  case TYPE_EXIT:
+    /* This is the only case in which we might end up destroying something
+     * whose owner hasn't already been notified. */
+    if ((Owner(thing) != Owner(Source(thing))) && Going(Source(thing))) {
+      if (!Owns(player, thing)) {
+       notify_format(Owner(thing),
+                     T("%s has scheduled your %s for destruction."),
+                     Name(player), object_header(Owner(thing), thing));
+      }
+    }
+    break;
+  default:
+    do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in pre_destroy."));
+    return;
+  }
+
+  if (ADESTROY_ATTR)
+    did_it(player, thing, NULL, NULL, NULL, NULL, "ADESTROY", NOTHING);
+
+  return;
+
+}
+
+
+/** Spare an object from destruction.
+ * Not undestroy, quite--it's actually 'remove it from its status as about
+ * to be destroyed.' This is the internal function used in hardcode.
+ * \param player the enactor.
+ * \param thing dbref of object to be spared.
+ * \return 1 successful undestruction.
+ * \return 0 thing is not a valid object to undestroy.
+ */
+int
+undestroy(dbref player, dbref thing)
+{
+  dbref tmp;
+  if (!Going(thing) || IsGarbage(thing)) {
+    return 0;
+  }
+  clear_flag_internal(thing, "GOING");
+  clear_flag_internal(thing, "GOING_TWICE");
+  if (!Halted(thing))
+    (void) queue_attribute_noparent(thing, "STARTUP", thing);
+  /* undestroy owner, if need be. */
+  if (Going(Owner(thing))) {
+    if (Owner(thing) != player) {
+      notify_format(player,
+                   T("%s has been spared from destruction."),
+                   object_header(player, Owner(thing)));
+      notify_format(Owner(thing),
+                   T("You have been spared from destruction by %s."),
+                   Name(player));
+    } else {
+      notify(player, T("You have been spared from destruction."));
+    }
+    (void) undestroy(player, Owner(thing));
+  }
+  switch (Typeof(thing)) {
+  case TYPE_PLAYER:
+    if (DESTROY_POSSESSIONS)
+      /* Undestroy all objects owned by players, except exits that are in
+       * rooms owned by other players that are set GOING, since those will
+       * be purged when the room is purged.
+       */
+      for (tmp = 0; tmp < db_top; tmp++) {
+       if (Owns(thing, tmp) &&
+           (tmp != thing) &&
+           !(IsExit(tmp) && !Owns(thing, Source(tmp)) && Going(Source(tmp)))) {
+         (void) undestroy(player, tmp);
+       }
+      }
+    break;
+  case TYPE_DIVISION:
+  case TYPE_THING:
+    break;
+  case TYPE_EXIT:
+    /* undestroy containing room. */
+    if (Going(Source(thing))) {
+      (void) undestroy(player, Source(thing));
+      notify_format(player,
+                   T("The room %s has been spared from destruction."),
+                   object_header(player, Source(thing)));
+      if (Owner(Source(thing)) != player) {
+       notify_format(Owner(Source(thing)),
+                     T("The room %s has been spared from destruction by %s."),
+                     object_header(Owner(Source(thing)), Source(thing)),
+                     Name(player));
+      }
+    }
+    break;
+  case TYPE_ROOM:
+    /* undestroy exits in this room, except exits that are going to be
+     * destroyed anyway due to a GOING player.
+     */
+    DOLIST(tmp, Exits(thing)) {
+      if (DESTROY_POSSESSIONS ? (!Going(Owner(tmp)) || Safe(tmp)) : 1) {
+       (void) undestroy(player, tmp);
+      }
+    }
+    break;
+  default:
+    do_log(LT_ERR, NOTHING, NOTHING, T("Surprising type in un_destroy."));
+    return 0;
+  }
+  return 1;
+}
+
+
+/* Does the real work of freeing all the memory and unlinking an object.
+ * This is going to have to be very tightly coupled with the implementation;
+ * if the database format changes, this will likely have to change too.
+ */
+void
+free_object(dbref thing)
+{
+  dbref i, loc;
+  if (!GoodObject(thing))
+    return;
+  local_data_free(thing);
+
+  switch (Typeof(thing)) {
+  case TYPE_DIVISION:
+    clear_division(thing);
+    clear_thing(thing);
+    break;
+  case TYPE_THING:
+    clear_thing(thing); 
+    change_quota(thing, QUOTA_COST);
+    break;
+  case TYPE_PLAYER:
+    clear_player(thing);
+    break;
+  case TYPE_EXIT:
+    clear_exit(thing);
+    change_quota(thing, QUOTA_COST);
+    break;
+  case TYPE_ROOM:
+    clear_room(thing);
+    change_quota(thing, QUOTA_COST);
+    break;
+  default:
+    do_log(LT_ERR, NOTHING, NOTHING, T("Unknown type on #%d in free_object."),
+          thing);
+    return;
+  }
+
+  do_halt(thing, "", thing);
+  /* The equivalent of an @drain/any/all: */
+  dequeue_semaphores(thing, NULL, INT_MAX, 1, 1);
+
+  /* if something is zoned or parented or linked or chained or located
+   * to/in destroyed object, undo */
+  for (i = 0; i < db_top; i++) {
+    if (Zone(i) == thing) {
+      Zone(i) = NOTHING;
+    }
+    if (Parent(i) == thing) {
+      Parent(i) = NOTHING;
+    }
+    if (Home(i) == thing) {
+      switch (Typeof(i)) {
+      case TYPE_DIVISION:
+      case TYPE_PLAYER:
+      case TYPE_THING:
+       Home(i) = DEFAULT_HOME;
+       break;
+      case TYPE_EXIT:
+       /* Huh.  An exit that claims to be from here, but wasn't linked
+        * in properly. */
+       do_rawlog(LT_ERR,
+                 T("ERROR: Exit %s leading from invalid room #%d destroyed."),
+                 unparse_object(GOD, i), thing);
+       free_object(i);
+       break;
+      case TYPE_ROOM:
+       /* Hrm.  It claims we're an exit from it, but we didn't agree.
+        * Clean it up anyway. */
+       do_log(LT_ERR, NOTHING, NOTHING,
+              T("Found a destroyed exit #%d in room #%d"), thing, i);
+       break;
+      }
+    }
+    /* The location check MUST be done AFTER the home check. */
+    if (Location(i) == thing) {
+      switch (Typeof(i)) {
+      case TYPE_DIVISION:
+      case TYPE_PLAYER:
+      case TYPE_THING:
+       /* Huh.  It thought it was here, but we didn't agree. */
+       enter_room(i, Home(i), 0);
+       break;
+      case TYPE_EXIT:
+       /* If our destination is destroyed, then we relink to the
+        * source room (so that the exit can't be stolen). Yes, it's
+        * inconsistent with the treatment of exits leading from
+        * destroyed rooms, but it's a lot better than turning exits
+        * into nasty limbo exits.
+        */
+       Destination(i) = Source(i);
+       break;
+      case TYPE_ROOM:
+       /* Just remove a dropto. */
+       Location(i) = NOTHING;
+       break;
+      }
+    }
+    if (Next(i) == thing) {
+      Next(i) = NOTHING;
+    }
+  }
+
+  /* chomp chomp */
+  atr_free(thing);
+  List(thing) = NULL;
+  /* don't eat name otherwise examine will crash */
+
+  free_locks(Locks(thing));
+  Locks(thing) = NULL;
+
+  s_Pennies(thing, 0);
+  Owner(thing) = GOD;
+  Parent(thing) = NOTHING;
+  Zone(thing) = NOTHING;
+#ifdef CHAT_SYSTEM
+  remove_all_obj_chan(thing);
+#endif /* CHAT_SYSTEM */
+
+  switch (Typeof(thing)) {
+    /* Make absolutely sure we are removed from Location's content or
+       exit list. If we are in a room we own and destroy_possessions
+       is yes, this can happen, causing much ickyness: All garbage
+       items would be in DEFAULT_HOME. */
+  case TYPE_DIVISION:
+  case TYPE_PLAYER:
+  case TYPE_THING:
+    loc = Location(thing);
+    if (GoodObject(loc))
+      Contents(loc) = remove_first(Contents(loc), thing);
+    if (Typeof(thing) == TYPE_THING)
+      current_state.things--;
+    else if (Typeof(thing) == TYPE_DIVISION)
+      current_state.divisions--;
+    else
+      current_state.players--;
+    break;
+  case TYPE_EXIT:              /* This probably won't be needed, but lets make sure */
+    loc = Source(thing);
+    if (GoodObject(loc))
+      Exits(loc) = remove_first(Exits(loc), thing);
+    current_state.exits--;
+    break;
+  case TYPE_ROOM:
+    current_state.rooms--;
+    break;
+  default:
+    /* Do nothing for rooms. */
+    break;
+  }
+
+  Type(thing) = TYPE_GARBAGE;
+  destroy_flag_bitmask(Flags(thing));
+  Flags(thing) = NULL;
+  if(DPBITS(thing)) {
+     mush_free(DPBITS(thing), "POWER_SPOT");
+     DPBITS(thing) = NULL;
+  }
+  Location(thing) = NOTHING;
+  set_name(thing, "Garbage");
+  Exits(thing) = NOTHING;
+  Home(thing) = NOTHING;
+  set_lmod(thing, NULL);
+
+  clear_objdata(thing);
+
+  Next(thing) = first_free;
+  first_free = thing;
+
+  current_state.garbage++;
+
+#ifdef HAS_ASSERT
+  assert(IsGarbage(thing));
+#endif
+}
+
+static void
+empty_contents(dbref thing)
+{
+  /* Destroy any exits they may be carrying, send everything else home. */
+  dbref first;
+  dbref rest;
+  dbref target;
+  notify_except(Contents(thing), NOTHING,
+               T
+               ("The floor disappears under your feet, you fall through NOTHINGness and then:"),
+               0);
+  first = Contents(thing);
+  Contents(thing) = NOTHING;
+  /* send all objects to nowhere */
+  DOLIST(rest, first) {
+    Location(rest) = NOTHING;
+  }
+  /* now send them home */
+  while (first != NOTHING) {
+    rest = Next(first);
+    /* if home is in thing set it to limbo */
+    switch (Typeof(first)) {
+    case TYPE_EXIT:            /* if holding exits, destroy it */
+      free_object(first);
+      break;
+    case TYPE_THING:           /* move to home */
+    case TYPE_PLAYER:
+      /* Make sure the home is a reasonable object. */
+      if (!GoodObject(Home(first)) || IsExit(Home(first)) ||
+         Home(first) == thing)
+       Home(first) = DEFAULT_HOME;
+      target = Home(first);
+      /* If home isn't a good place to send it, send it to DEFAULT_HOME. */
+      if (!GoodObject(target) || recursive_member(target, first, 0))
+       target = DEFAULT_HOME;
+      if (target != NOTHING) {
+       /* Use enter_room() on everything so that AENTER and such
+        * are all triggered properly. */
+       enter_room(first, target, 0);
+      }
+      break;
+    }
+    first = rest;
+  }
+}
+
+static void
+clear_thing(dbref thing)
+{
+  dbref loc;
+  int a;
+  /* Remove object from room's contents */
+  loc = Location(thing);
+  if (loc != NOTHING) {
+    Contents(loc) = remove_first(Contents(loc), thing);
+  }
+  /* Remove object from any following chains */
+  clear_followers(thing, 0);
+  clear_following(thing, 0);
+  /* give player money back */
+  giveto(Owner(thing), (a = Pennies(thing)));
+  empty_contents(thing);
+  clear_flag_internal(thing, "PUPPET");
+  if (!Quiet(thing) && !Quiet(Owner(thing)))
+    notify_format(Owner(thing),
+                 T("You get your %d %s deposit back for %s."),
+                 a, ((a == 1) ? MONEY : MONIES),
+                 object_header(Owner(thing), thing));
+}
+
+static void
+clear_player(dbref thing)
+{
+  dbref i;
+  ATTR *atemp;
+  char alias[BUFFER_LEN + 1];
+
+  /* Clear out mail. */
+  do_mail_clear(thing, NULL);
+  do_mail_purge(thing);
+  malias_cleanup(thing);
+
+#ifdef CHAT_SYSTEM
+  /* Chown any chat channels they own to God */
+  chan_chownall(thing, GOD);
+#endif /* CHAT_SYSTEM */
+
+  /* Clear out names from the player list */
+  delete_player(thing, NULL);
+  if ((atemp = atr_get_noparent(thing, "ALIAS")) != NULL) {
+    strcpy(alias, atr_value(atemp));
+    delete_player(thing, alias);
+  }
+  /* Do all the thing-esque manipulations. */
+  clear_thing(thing);
+
+  /* Deal with objects owned by the player. */
+  for (i = 0; i < db_top; i++) {
+    if (Owner(i) == thing && i != thing) {
+      if (DESTROY_POSSESSIONS ? (REALLY_SAFE ? Safe(i) : 0) : 1) {
+       chown_object(GOD, i, GOD, 0);
+      } else {
+       free_object(i);
+      }
+    }
+  }
+}
+
+static void
+clear_room(dbref thing)
+{
+  dbref first, rest;
+#ifdef RPMODE_SYS
+  /* Clear RPLOGging crapp */
+  if(has_flag_by_name(thing, "ICFUNCS", TYPE_THING))
+    rplog_shutdown_room(thing);
+#endif /* RPMODE_SYS */
+  /* give player money back */
+  giveto(Owner(thing), ROOM_COST);
+  empty_contents(thing);
+  /* Remove exits */
+  first = Exits(thing);
+  Source(thing) = NOTHING;
+  /* set destination of all exits to nothing */
+  DOLIST(rest, first) {
+    Destination(rest) = NOTHING;
+  }
+  /* Clear all exits out of exit list */
+  while (first != NOTHING) {
+    rest = Next(first);
+    if (IsExit(first)) {
+      free_object(first);
+    }
+    first = rest;
+  }
+}
+
+
+static void
+clear_exit(dbref thing)
+{
+  dbref loc;
+  loc = Source(thing);
+  if (GoodObject(loc)) {
+    Exits(loc) = remove_first(Exits(loc), thing);
+  };
+  giveto(Owner(thing), EXIT_COST);
+}
+
+
+/** Return a cleaned up object off the free list or NOTHING.
+ * \return a garbage object or NOTHING.
+ */
+dbref
+free_get(void)
+{
+  dbref newobj;
+  if (first_free == NOTHING)
+    return (NOTHING);
+  newobj = first_free;
+  first_free = Next(first_free);
+  /* Make sure this object really should be in free list */
+  if (!IsGarbage(newobj)) {
+    static int nrecur = 0;
+    dbref temp;
+    if (nrecur++ == 20) {
+      first_free = NOTHING;
+      report();
+      do_rawlog(LT_ERR, T("ERROR: Removed free list and continued\n"));
+      return (NOTHING);
+    }
+    report();
+    do_rawlog(LT_TRACE, T("ERROR: Object #%d should not be free\n"), newobj);
+    do_rawlog(LT_TRACE, T("ERROR: Corrupt free list, fixing\n"));
+    fix_free_list();
+    temp = free_get();
+    nrecur--;
+    return (temp);
+  }
+  /* free object name */
+  set_name(newobj, NULL);
+  return (newobj);
+}
+
+/** Build the free list with a sledgehammer. 
+ * Only do this when it's actually necessary.
+ * Since we only do it if things are corrupted, we do not free any memory.
+ * Presumably, this will only waste a reasonable amount of memory, since
+ * it's only called in exceptional cases.
+ */
+void
+fix_free_list(void)
+{
+  dbref thing;
+  first_free = NOTHING;
+  for (thing = 0; thing < db_top; thing++) {
+    if (IsGarbage(thing)) {
+      Next(thing) = first_free;
+      first_free = thing;
+    }
+  }
+}
+
+
+
+/** Destroy all the objects we said we would destroy later. */
+void
+purge(void)
+{
+  dbref thing;
+  for (thing = 0; thing < db_top; thing++) {
+    if (IsGarbage(thing)) {
+      continue;
+    } else if (Going(thing)) {
+      if (Going_Twice(thing)) {
+       free_object(thing);
+      } else {
+       set_flag_internal(thing, "GOING_TWICE");
+      }
+    } else {
+      continue;
+    }
+  }
+}
+
+
+/** Destroy objects slated for destruction.
+ * \verbatim
+ * This is the top-level function for @purge.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_purge(dbref player)
+{
+  purge();
+  notify(player, T("Purge complete."));
+}
+
+
+
+/* Section III: dbck() and related functions. */
+
+/** The complete db checkup.
+ */
+void
+dbck(void)
+{
+  check_fields();
+  check_contents();
+  check_locations();
+  check_connected_rooms();
+  check_zones();
+  check_divisions();
+  local_dbck();
+}
+
+/* Do division integrity checks */
+
+static void
+check_divisions(void) {
+  int i, n;
+  int check;
+
+  for(i = 0; i < db_top ; i++)
+    if(IsDivision(i)) {
+       /* first, let's do some smart division setting. if it's not set. 
+       * Just incase some weird corruption hits us
+       */
+      if(Division(i) == -1 && IsDivision(i) && IsDivision(Location(i))) {
+       do_rawlog(LT_ERR, T("Auto-Divisioning #%d to #%d"), i, Location(i));
+       Division(i) = Location(i);
+       Parent(i) = Location(i);
+      }
+      /* make sure their division is set correctly. */
+      if(SDIV(i).object != -1 && SDIV(i).object == i) {
+       do_rawlog(LT_ERR, T("ERROR: Bad Master-Division."));
+        SDIV(i).object = -1;   
+      }
+      /* make sure their division is a valid object */
+      if((!GoodObject(Division(i)) && Division(i) != NOTHING) || IsGarbage(Division(i))) {
+       Division(i) = -1;
+       do_rawlog(LT_ERR, T("ERROR: Bad Division(#%d) set on object #%d"), Division(i), i);
+      }
+      /* now check parent tree */
+      if(Division(i) != -1)
+       Parent(i) = Division(i);
+      else 
+       for(n = 0, check = i; n < MAX_PARENTS && check != NOTHING; n++, check = Parent(check))
+         if(IsDivision(Parent(check))) {
+           do_rawlog(LT_ERR, T("ERROR: Bad Division Parent Structure."));
+           Parent(check) = NOTHING;
+         }
+    }
+}
+
+/* Do sanity checks on non-destroyed objects. */
+static void
+check_fields(void)
+{
+  dbref thing;
+  for (thing = 0; thing < db_top; thing++) {
+    if (IsGarbage(thing)) {
+      /* The only relevant thing is that the Next field ought to be pointing
+       * to a destroyed object.
+       */
+      dbref next;
+      next = Next(thing);
+      if ((!GoodObject(next) || !IsGarbage(next)) && (next != NOTHING)) {
+       do_rawlog(LT_ERR, T("ERROR: Invalid next pointer #%d from object %s"),
+                 next, unparse_object(GOD, thing));
+       Next(thing) = NOTHING;
+       fix_free_list();
+      }
+      continue;
+    } else {
+      /* Do sanity checks on non-destroyed objects */
+      dbref zone, loc, parent, home, owner, next;
+      zone = Zone(thing);
+      if (GoodObject(zone) && IsGarbage(zone))
+       Zone(thing) = NOTHING;
+      parent = Parent(thing);
+      if (GoodObject(parent) && IsGarbage(parent))
+       Parent(thing) = NOTHING;
+      owner = Owner(thing);
+      if (!GoodObject(owner) || IsGarbage(owner) || !IsPlayer(owner)) {
+       do_rawlog(LT_ERR, T("ERROR: Invalid object owner on %s(%d)"),
+                 Name(thing), thing);
+       report();
+       Owner(thing) = GOD;
+      }
+      next = Next(thing);
+      if ((!GoodObject(next) || IsGarbage(next)) && (next != NOTHING)) {
+       do_rawlog(LT_ERR, T("ERROR: Invalid next pointer #%d from object %s"),
+                 next, unparse_object(GOD, thing));
+       Next(thing) = NOTHING;
+      }
+      if((next == thing)) /* its saying itself is next? */
+       Next(thing) = NOTHING;
+      /* This next bit has to be type-specific because of different uses
+       * of the home and location fields.
+       */
+      home = Home(thing);
+      loc = Location(thing);
+      switch (Typeof(thing)) {
+      case TYPE_DIVISION:
+      case TYPE_PLAYER:
+      case TYPE_THING:
+       if (!GoodObject(home) || IsGarbage(home) || IsExit(home))
+         Home(thing) = DEFAULT_HOME;
+       if (!GoodObject(loc) || IsGarbage(loc) || IsExit(loc))
+         enter_room(thing, Home(thing), 0);
+       break;
+      case TYPE_EXIT:
+       if (Contents(thing) != NOTHING) {
+         /* Eww.. Exits can't have contents. Bad news */
+         Contents(thing) = NOTHING;
+         do_rawlog(LT_ERR,
+                   T("ERROR: Exit %s has a contents list. Wiping it out."),
+                   unparse_object(GOD, thing));
+       }
+       if (!GoodObject(loc)
+           && !((loc == NOTHING) || (loc == AMBIGUOUS) || (loc == HOME))) {
+         /* Bad news. We're linked to a really impossible object.
+          * Relink to our source
+          */
+         Destination(thing) = Source(thing);
+         do_rawlog(LT_ERR,
+                   T
+                   ("ERROR: Exit %s leading to invalid room #%d relinked to its source room."),
+                   unparse_object(GOD, thing), home);
+       } else if (GoodObject(loc) && IsGarbage(loc)) {
+         /* If our destination is destroyed, then we relink to the
+          * source room (so that the exit can't be stolen). Yes, it's
+          * inconsistent with the treatment of exits leading from
+          * destroyed rooms, but it's a lot better than turning exits
+          * into nasty limbo exits.
+          */
+         Destination(thing) = Source(thing);
+         do_rawlog(LT_ERR,
+                   T
+                   ("ERROR: Exit %s leading to garbage room #%d relinked to its source room."),
+                   unparse_object(GOD, thing), home);
+       }
+       /* This must come last */
+       if (!GoodObject(home) || !IsRoom(home)) {
+         /* If our source is destroyed, just destroy the exit. */
+         do_rawlog(LT_ERR,
+                   T
+                   ("ERROR: Exit %s leading from invalid room #%d destroyed."),
+                   unparse_object(GOD, thing), home);
+         free_object(thing);
+       }
+       break;
+      case TYPE_ROOM:
+       if (GoodObject(home) && IsGarbage(home)) {
+         /* Eww. Destroyed exit. This isn't supposed to happen. */
+         do_log(LT_ERR, NOTHING, NOTHING,
+                T("Found a destroyed exit #%d in room #%d"), home, thing);
+       }
+       if (GoodObject(loc) && (IsGarbage(loc) || IsExit(loc))) {
+         /* Just remove a dropto. */
+         Location(thing) = NOTHING;
+       }
+       break;
+      }
+      /* Check if this guy isn't in a division, if he's not.. Then we need fix their level & zap
+       * powers
+       */
+      if(!IsDivision(thing) && !IsDivision(Division(thing))) {
+       if(LEVEL(thing) > LEVEL_UNREGISTERED) {
+         do_log(LT_ERR, NOTHING, NOTHING, T("Found' #%d' at Level '%d' without a valid division, setting to Unregistered level '%d'"), 
+             thing, LEVEL(thing), LEVEL_UNREGISTERED);
+         SLEVEL(thing) = LEVEL_UNREGISTERED;
+       }
+       if(!!DPBITS(thing)) { /* Zap powers */
+         do_log(LT_ERR, NOTHING, NOTHING, T("Found '#%d' with no division and powers set, removing."), thing);
+         mush_free(DPBITS(thing), "POWER_SPOT");
+         DPBITS(thing) = NULL;
+       }
+      }
+      /* Zap power allocation if they don't have any */
+      if(!!DPBITS(thing) && (power_is_zero(DPBITS(thing), DP_BYTES) == 0)) {
+       mush_free(DPBITS(thing), "POWER_SPOT");
+        DPBITS(thing) = NULL;
+      }
+      /* Check attribute ownership. If the attribute is owned by
+       * an invalid dbref, change its ownership to powerless.
+       */
+      if (!IsGarbage(thing))
+       atr_iter_get(options.powerless, thing, "**", 0, attribute_owner_helper, NULL);
+    }
+  }
+}
+
+static int
+attribute_owner_helper(dbref player __attribute__ ((__unused__)),
+                      dbref thing __attribute__ ((__unused__)),
+                      dbref parent __attribute__ ((__unused__)),
+                      char const *pattern
+                      __attribute__ ((__unused__)), ATTR *atr, void *args
+                      __attribute__ ((__unused__)))
+{
+  if (!GoodObject(AL_CREATOR(atr)))
+    AL_CREATOR(atr) = options.powerless; /* set to a powerless object so twinchecks don't backfire */
+  return 0;
+}
+
+static void
+check_connected_rooms(void)
+{
+  dbref room;
+  for (room = 0; room < db_top; ++room)
+    if ((room == BASE_ROOM) || (IsRoom(room) && Floating(room)))
+      mark_connected(room);
+  check_connected_marks();
+}
+
+static void
+mark_connected(dbref loc)
+{
+  dbref thing;
+  if (!GoodObject(loc) || !IsRoom(loc) || Marked(loc))
+    return;
+  SetMarked(loc);
+  /* recursively trace */
+  for (thing = Exits(loc); thing != NOTHING; thing = Next(thing))
+    mark_connected(Destination(thing));
+}
+
+static void
+check_connected_marks(void)
+{
+  dbref loc;
+  for (loc = 0; loc < db_top; loc++)
+    if (!IsGarbage(loc) && Marked(loc))
+      ClearMarked(loc);
+    else if (IsRoom(loc)) {
+      if (!Name(loc)) {
+       do_log(LT_ERR, NOTHING, NOTHING, T("ERROR: no name for room #%d."),
+              loc);
+       set_name(loc, "XXXX");
+      }
+      if (!Going(loc) && !Floating(loc) && !NoWarnable(loc) &&
+         (!EXITS_CONNECT_ROOMS || (Exits(loc) == NOTHING))) {
+       notify_format(Owner(loc), T("You own a disconnected room, %s"),
+                     object_header(Owner(loc), loc));
+      }
+    }
+}
+
+/* Warn about objects without @lock/zone used as zones */
+static void
+check_zones(void)
+{
+  dbref n, zone = NOTHING, tmp;
+
+  for (n = 0; n < db_top; n++) {
+    if (IsGarbage(n))
+      continue;
+    zone = Zone(n);
+    if (!GoodObject(zone))
+      continue;
+    if (ZONE_CONTROL_ZMP && !IsPlayer(zone))
+      continue;
+    if (zone != n)             /* Objects can be zoned to themselves */
+      for (tmp = Zone(zone); GoodObject(tmp); tmp = Zone(tmp)) {
+       if (tmp == n) {
+         notify_format(Owner(n),
+                       T("You own an object in a circular zone chain: %s"),
+                       object_header(Owner(n), n));
+         break;
+       }
+       if (tmp == Zone(tmp))   /* Object zoned to itself */
+         break;
+      }
+
+    if (Marked(zone))
+      continue;
+    if (getlock(zone, Zone_Lock) == TRUE_BOOLEXP)
+      SetMarked(zone);
+  }
+
+  for (n = 0; n < db_top; n++) {
+    if (!IsGarbage(n) && Marked(n)) {
+      ClearMarked(n);
+      notify_format(Owner(n),
+                   T
+                   ("You own an object without a @lock/zone being used as a zone: %s"),
+                   object_header(Owner(n), n));
+    }
+  }
+}
+
+/** In this macro, field must be an lvalue whose evaluation has
+ * no side effects and results in a dbref to be checked.
+ * All hell will break loose if this is not so.
+ */
+#define CHECK(field)            \
+  if ((field) != NOTHING) { \
+     if (!GoodObject(field) || IsGarbage(field)) { \
+       do_rawlog(LT_ERR, "Bad reference #%d from %s severed.", \
+                 (field), unparse_object(GOD, thing)); \
+       (field) = NOTHING; \
+     } else if (IsRoom(field)) { \
+       do_rawlog(LT_ERR, "Reference to room #%d from %s severed.", \
+                 (field), unparse_object(GOD, thing)); \
+       (field) = NOTHING; \
+     } else if (Marked(field)) {  \
+       do_rawlog(LT_ERR, "Multiple references to %s. Reference from #%d severed.", \
+                 unparse_object(GOD, (field)), thing); \
+       (field) = NOTHING; \
+     } else { \
+       SetMarked(field); \
+       mark_contents(field); \
+     } \
+  }
+
+/* An auxiliary function for check_contents. */
+static void
+mark_contents(dbref thing)
+{
+  if (!GoodObject(thing) || IsGarbage(thing))
+    return;
+
+  SetMarked(thing);
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    CHECK(Exits(thing));
+    CHECK(Contents(thing));
+    break;
+  case TYPE_DIVISION:
+  case TYPE_PLAYER:
+  case TYPE_THING:
+    CHECK(Contents(thing));
+    CHECK(Next(thing));
+    break;
+  case TYPE_EXIT:
+    CHECK(Next(thing));
+    break;
+  default:
+    do_rawlog(LT_ERR, T("Bad object type found for %s in mark_contents"),
+             unparse_object(GOD, thing));
+    break;
+  }
+}
+
+#undef CHECK
+
+/* Check that for every thing, player, and exit, you can trace exactly one
+ * path to that object from a room by following the exits field of rooms,
+ * the next field of non-rooms, and the contents field of non-exits.
+ */
+static void
+check_contents(void)
+{
+  dbref thing;
+  for (thing = 0; thing < db_top; thing++) {
+    if (IsRoom(thing)) {
+      mark_contents(thing);
+    }
+  }
+  for (thing = 0; thing < db_top; thing++) {
+    if (!IsRoom(thing) && !IsGarbage(thing) && !Marked(thing)) {
+      do_rawlog(LT_ERR, T("Object %s not pointed to by anything."),
+               unparse_object(GOD, thing));
+      notify_format(Owner(thing),
+                   T("You own an object %s that was \'orphaned\'."),
+                   object_header(Owner(thing), thing));
+      /* We try to fix this by trying to send players and things to
+       * their current location, to their home, or to DEFAULT_HOME, in
+       * that order, and relinking exits to their source.
+       */
+      Next(thing) = NOTHING;
+      switch (Typeof(thing)) {
+      case TYPE_PLAYER:
+      case TYPE_THING:
+       if (GoodObject(Location(thing)) &&
+           !IsGarbage(Location(thing)) && Marked(Location(thing))) {
+         PUSH(thing, Contents(Location(thing)));
+       } else if (GoodObject(Home(thing)) &&
+                  !IsGarbage(Home(thing)) && Marked(Home(thing))) {
+         Contents(Location(thing)) =
+           remove_first(Contents(Location(thing)), thing);
+         PUSH(thing, Contents(Home(thing)));
+         Location(thing) = Home(thing);
+       } else {
+         Contents(Location(thing)) =
+           remove_first(Contents(Location(thing)), thing);
+         PUSH(thing, Contents(DEFAULT_HOME));
+         Location(thing) = DEFAULT_HOME;
+       }
+       enter_room(thing, Location(thing), 0);
+       /* If we've managed to reconnect it, then we've reconnected
+        * its contents. */
+       mark_contents(Contents(thing));
+       notify_format(Owner(thing), T("It was moved to %s."),
+                     object_header(Owner(thing), Location(thing)));
+       do_rawlog(LT_ERR, T("Moved to %s."),
+                 unparse_object(GOD, Location(thing)));
+       break;
+      case TYPE_EXIT:
+       if (GoodObject(Source(thing)) && IsRoom(Source(thing))) {
+         PUSH(thing, Exits(Source(thing)));
+         notify_format(Owner(thing), T("It was moved to %s."),
+                       object_header(Owner(thing), Source(thing)));
+         do_rawlog(LT_ERR, T("Moved to %s."),
+                   unparse_object(GOD, Source(thing)));
+       } else {
+         /* Just destroy the exit. */
+         Source(thing) = NOTHING;
+         notify(Owner(thing), T("It was destroyed."));
+         do_rawlog(LT_ERR, T("Orphaned exit destroyed."));
+         free_object(thing);
+       }
+       break;
+      case TYPE_ROOM:
+       /* We should never get here. */
+       do_log(LT_ERR, NOTHING, NOTHING, T("Disconnected room. So what?"));
+       break;
+      default:
+       do_log(LT_ERR, NOTHING, NOTHING,
+              T("Surprising type on #%d found in check_cycles."), thing);
+       break;
+      }
+    }
+  }
+  for (thing = 0; thing < db_top; thing++) {
+    if (!IsGarbage(thing))
+      ClearMarked(thing);
+  }
+}
+
+
+/* Check that every player and thing occurs in the contents list of its
+ * location, and that every exit occurs in the exit list of its source.
+ */
+static void
+check_locations(void)
+{
+  dbref thing;
+  dbref loc;
+  for (loc = 0; loc < db_top; loc++) {
+    if (!IsExit(loc)) {
+      for (thing = Contents(loc); thing != NOTHING; thing = Next(thing)) {
+       if (!Mobile(thing)) {
+         do_rawlog(LT_ERR,
+                   T
+                   ("ERROR: Contents of object %d corrupt at object %d cleared"),
+                   loc, thing);
+         /* Remove this from the list and start over. */
+         Contents(loc) = remove_first(Contents(loc), thing);
+         thing = Contents(loc);
+         continue;
+       } else if (Location(thing) != loc) {
+         /* Well, it would fit here, and it can't be elsewhere because
+          * we've done a check_contents already, so let's just put it
+          * here.
+          */
+         do_rawlog(LT_ERR,
+                   T("Incorrect location on object %s. Reset to #%d."),
+                   unparse_object(GOD, thing), loc);
+         Location(thing) = loc;
+       }
+       SetMarked(thing);
+      }
+    }
+    if (IsRoom(loc)) {
+      for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
+       if (!IsExit(thing)) {
+         do_rawlog(LT_ERR,
+                   T("ERROR: Exits of room %d corrupt at object %d cleared"),
+                   loc, thing);
+         /* Remove this from the list and start over. */
+         Exits(loc) = remove_first(Exits(loc), thing);
+         thing = Exits(loc);
+         continue;
+       } else if (Source(thing) != loc) {
+         do_rawlog(LT_ERR,
+                   T("Incorrect source on exit %s. Reset to #%d."),
+                   unparse_object(GOD, thing), loc);
+       }
+      }
+    }
+  }
+
+
+  for (thing = 0; thing < db_top; thing++)
+    if (!IsGarbage(thing) && Marked(thing))
+      ClearMarked(thing);
+    else if (Mobile(thing)) {
+      do_rawlog(LT_ERR, T("ERROR DBCK: Moved object %d"), thing);
+      moveto(thing, DEFAULT_HOME);
+    }
+}
+
+
+/** Database checkup, user interface.
+ * \verbatim
+ * This is the top-level function for @dbck. Automatic checks should
+ * call dbck(), not this.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_dbck(dbref player)
+{
+  notify(player, T("GAME: Performing database consistency check."));
+  do_log(LT_WIZ, player, NOTHING, T("DBCK done."));
+  dbck();
+  notify(player, T("GAME: Database consistency check complete."));
+}
diff --git a/src/division.c b/src/division.c
new file mode 100644 (file)
index 0000000..f73f446
--- /dev/null
@@ -0,0 +1,2673 @@
+/* division.c - RLB 3/11/02
+ *             Part of the Division system for PennMUSH
+ *        4/12/02 - RLB - Rewrite level set, much more readable now =)
+ *        4/17/02 - RLB - Code StreamLining
+ *        1/18/04 - RLB - began work on power conversion code
+ *        1/23/04 - RLB - completed power conversion code
+ *        3/26/04 - RLB - Added translation wrappers to messages.
+ *        7/27/05 - RLB - DB Powers Completed
+ */
+
+/* Required Includes {{{1 */
+#include "copyrite.h"
+#include "config.h"
+#ifdef I_STRING
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include <ctype.h>
+#include "conf.h"
+#include "externs.h"
+#include "boolexp.h"
+#include "command.h"
+#include "division.h"
+#include "parse.h"
+#include "htab.h"
+#include "cmds.h"
+#include "confmagic.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "dbio.h"
+#include "match.h"
+#include "log.h"
+#include "boolexp.h"
+#include "attrib.h"
+#include "ansi.h"
+/* }}}1 */
+
+/* Static Function Prototypes {{{1 */
+static void adjust_levels(dbref, int);
+static void adjust_divisions(dbref, dbref, dbref);
+static void power_add(POWER * pow);
+static POWER *new_power(void);
+static void realloc_object_powers(int);
+static void power_add_additional();
+static void power_read(FILE *);
+static void powergroup_read(FILE *);
+static void power_read_alias(FILE *);
+static void clear_all_powers();
+static int power_delete(const char *);
+static int check_old_power_ycode(int, unsigned char *);
+static int new_power_bit(int);
+static int power_goodname(char *);
+
+/* External Accessible Function Prototypes {{{1 */
+void division_set(dbref, dbref, const char *);
+void division_empower(dbref, dbref, const char *);
+const char *division_list_powerz(dbref, int);
+const char *list_dp_powerz(div_pbits, int);
+dbref create_div(dbref, const char *);
+int division_level(dbref, dbref, int);
+int div_inscope(dbref, dbref);
+int div_powover(dbref, dbref, const char *);
+void clear_division(dbref);
+int yescode_i(char *);
+char *yescode_str(int);
+int can_give_power(dbref, dbref, POWER *, int);
+int can_have_power(dbref, POWER *, int);
+int can_have_power_at(dbref, POWER *);
+POWER *add_power_type(const char *name, const char *type);
+void init_powers();
+div_pbits new_power_bitmask();
+void powers_read_all(FILE * in);
+POWER *has_power(dbref, const char *);
+int can_have_pg(dbref object, POWERGROUP * pgrp);
+void do_list_powers(dbref player, const char *name);
+char *list_all_powers(dbref player, const char *name);
+void add_to_div_exit_path(dbref player, dbref div_obj);
+
+/* PowerGroup Related Functions {{{2
+ */
+char *powergroups_list(dbref, char flag);
+char *powergroups_list_on(dbref, char);
+char powergroup_delete(dbref, const char *);
+POWERGROUP *powergroup_add(const char *);
+int powergroup_empower(dbref, POWERGROUP *, const char *, char);
+void powergroup_db_set(dbref, dbref, const char *, char);
+void rem_pg_from_player(dbref player, POWERGROUP * powergroup);
+void add_pg_to_player(dbref player, POWERGROUP * powergroup);
+int powergroup_has(dbref, POWERGROUP *);
+int can_receive_power_at(dbref player, POWER * power);
+
+/* Pre-Defined Tables {{{1
+ */
+/* Pre-Defined Power Aliases {{{2 */
+static struct power_alias power_alias_tab[] = {
+  {"Functions", "GFuncs"},
+  {"@Cemit", "Cemit"},
+  {"CQuota", "SetQuotas"},
+  {"chat_privs", "Chat"},
+  {"link_anywhere", "Link"},
+  {"long_fingers", "Remote"},
+  {"Pemit_All", "Pemit"},
+  {"Open_Anywhere", "Open"},
+  {"Tport_Anything", "Tel_Thing"},
+  {"Tel_Where", "Tel_Place"},
+  {"Tport_Anywhere", "Tel_Place"},
+  {"@wall", "Announce"},
+  {"wall", "Announce"},
+  {NULL, NULL}
+};
+
+/* Power Types {{{2 */
+
+static struct division_power_entry_t powc_list[] = {
+  {"self", &powc_self, YES, YES, YES},
+  {"levchk", &powc_levchk, YES, YESLTE, YESLT},
+  {"levchk_lte", &powc_levchk_lte, YES, YES, YES},
+  {"levset", &powc_levset, YES, YESLTE, YESLT},
+  {"bcreate", &powc_bcreate, YES, YES, YES},
+  {NULL, NULL, -1, -1, -1},
+};
+
+/* Pre-Defined Power-Groups {{{2 */
+
+static struct powergroup_text_t predefined_powergroups[] = {
+
+  /* PowerGroup Name */
+  {"Wizard",
+   /* Max Powers */
+   "Announce Attach BCreate Boot Builder Can_NsPemit Cemit Chat Chown Combat Cron DAnnounce Division EAnnounce Economy Empire Empower GFuncs Halt Hide Idle Join Level Link Login MailAdmin Many_Attribs Modify:LTE Newpass:LTE NoPay NoQuota Nukte:LTE Open Pass_Locks PCreate PEmit Poll Privilege PrivWho ProgLock Program Powergroup:lt Pueblo_Send Queue Quota RCACHE Remote RPChat RPEmit RPTel Search See_All See_Queue SetQuotas Site SQL_Ok Summon Tel_Place Tel_Thing @SU:LTE",
+   /* Auto Powers */
+   "Announce Attach BCreate Boot Builder Can_NsPemit Cemit Chat Chown Combat Cron DAnnounce Division EAnnounce Empire Empower GFuncs Halt Hide Idle Join Level Link Login MailAdmin Many_Attribs Modify:LTE Newpass:LTE NoPay NoQuota Nukte:LTE Open PCreate PEmit Poll Powergroup:lt Privilege PrivWho ProgLock Program Pueblo_Send Queue Quota RCACHE Remote RPChat RPEmit RPTel Search See_All See_Queue SetQuotas Site SQL_Ok Summon Tel_Place Tel_Thing @SU:LTE"},
+
+  /* PowerGroup Name */
+  {"Royalty",
+   /* Max Powers */
+   "See_All NoPay NoQuota Queue See_Queue Remote Chat Builder Announce Boot PrivWho Login PEmit Hide Privilege Tel_Place Tel_Thing",
+   /* Auto Powers */
+   "See_All NoPay NoQuota Queue See_Queue Remote Chat Builder Announce Boot PrivWho Login PEmit Hide Privilege Tel_Place Tel_Thing"},
+
+  /* PowerGroup Name */
+  {"Director",
+   /* Max Powers */
+   "@SU:LTE Announce Attach BCreate Boot Builder Can_NsPemit Cemit Chat Chown Combat Cron DAnnounce Division EAnnounce Empire Empower GFuncs Halt Hide Idle Join Level Link Login MailAdmin Many_Attribs Modify Newpass:2 NoPay NoQuota Nuke:LTE Open:2 Pass_Locks PCreate PEmit Poll PowerGroup:LTE Privilege PrivWho Program ProgLock:lte Pueblo_Send Quota Queue RCACHE Remote RPChat RPEmit RPTel Search See_All See_Queue SetQuotas  Site SQL_Ok Summon Tel_Thing Tel_Place",
+   /* Auto Powers */
+   "Announce Attach BCreate Boot:2 Builder Can_NsPemit Cemit Chat Chown:2 Combat Cron DAnnounce Division EAnnounce Economy Empire Empower:2 GFuncs Halt Hide Idle Join Level:1 Link Login MailAdmin:1 Many_Attribs Modify:2 Newpass:1 NoPay NoQuota Nuke:LT Open:2 Pass_Locks PCreate PEmit Poll PowerGroup:LTE Privilege PrivWho Program ProgLock:1 Pueblo_Send Quota Queue RCACHE Remote RPChat RPEmit RPTel Search:LTE See_All Search:lte See_Queue SetQuotas Site SQL_Ok Summon Tel_Thing Tel_Place"},
+
+  /* PowerGroup Name */
+  {"Admin",
+   /* Max Powers */
+   "Announce Attach BCreate Boot:1 Builder Can_NsPemit Cemit Chat Chown(LTE) Combat Cron DAnnounce Division EAnnounce Economy Empire Empower GFuncs Halt Hide Idle Join Level Link Login MailAdmin:2 Many_Attribs Modify Newpass:2 NoPay Nuke:LTE NoQuota Open:2 Pass_Locks:2 PCreate PEmit Poll Powergroup:lte Privilege PrivWho Program ProgLock:2 Pueblo_Send Quota Queue RCACHE Remote RPChat RPEmit RPTel Search:LTE See_All See_Queue SetQuotas SQL_Ok Summon Tel_Thing Tel_Place",
+   /* Auto Powers */
+   "Announce Attach:1 BCreate Builder Can_NsPemit Cemit Chat Combat Cron DAnnounce Division EAnnounce Economy Empire Empower:2 GFuncs Halt:2 Hide Idle Join Level:1 Link:2 Login Modify:2 Newpass:1 NoPay Nuke:LT NoQuota Open:2 PCreate PEmit Poll PowerGroup:LTE Program Pueblo_Send Quota Queue RCACHE Remote RPChat RPEmit RPTel Search:2 See_All:2 See_Queue Summon:1 Tel_Thing:1 Tel_Place:2 "},
+
+  /* PowerGroup Name */
+  {"EmpireHead",
+   /* Max Powers */
+   "Announce Attach:1 BCreate Boot:1 Builder Can_NsPemit Cemit Chat Combat Cron DAnnounce Division EAnnounce Economy Empower GFuncs Halt Hide Idle Join Level Link Login Many_Attribs Modify:2 Newpass:1 NoPay NoQuota Nuke:LT Open:2 Pass_Locks:2 PCreate PEmit Poll PowerGroup:LT Privilege PrivWho Program:2 Pueblo_Send Quota Queue RCACHE Remote RPChat RPEmit RPTel Search:2 See_All:2 See_Queue SetQuotas:LTE SQL_Ok Summon Tel_Thing:2 Tel_Place:1",
+   /* Auto Powers */
+   "Announce Attach:1 BCreate Boot:1 Builder Cemit Chat DAnnounce Division EAnnounce Empower:1 Halt:1 Hide Idle Join:2 Level:1 Link:1 Login Modify:2 Newpass:1 NoPay NoQuota Open:2 Pass_Locks PCreate PEmit:1 Poll PowerGroup:LT PrivWho Program:2 Pueblo_Send Quota Queue Remote RPChat RPEmit Search:1 See_Queue SetQuotas:1 Summon:1 Tel_Thing:1 Tel_Place:2 "},
+
+  /* PowerGroup Name */
+  {"EmpireAdmin",
+   /* Max Powers */
+   "",
+   /* Auto Powers */
+   ""},
+  /* PowerGroup Name */
+  {"Builder",
+   /* Max Powers */
+   "Builder Cemit Chat Combat DAnnounce Division EAnnounce Economy Empower GFuncs Halt Hide Idle Join Level Link Login Many_Attribs Modify:2 NoPay NoQuota Open:2 PCreate PEmit Poll PrivWho Program:2 Pueblo_Send Quota Queue RCACHE Remote RPChat RPEmit RPTel Search:2 See_All:2 See_Queue Space Summon Tel_Thing:2 Tel_Place:2",
+   /* Auto Powers */
+   "Builder Hide Idle Join:2 Level:LT Login NoPay NoQuota Open:LTE PEmit:LT Poll Program:LTE Pueblo_Send Quota Queue RPChat RPEmit See_Queue Tel_Thing:LT Tel_Place:2"},
+
+  /* PowerGroup Name */
+  {"Player",
+   /* Max Powers */
+   "Builder Chat Combat Hide Idle Login NoPay PEmit Poll RPChat RPEmit Summon Join",
+   /* Auto Powers */
+   "RPChat RPEmit"},
+  /* end of powers */
+  {NULL, NULL, NULL}
+};
+
+/* Pre-Defined Powers List {{{2 */
+static struct new_division_power_entry_t new_power_list[] = {
+  {"Announce", "self"},
+  {"Attach", "levchk"},
+  {"BCreate", "bcreate"},
+  {"Boot", "levchk"},
+  {"Builder", "self"},
+  {"Can_NsPemit", "levchk"},
+  {"Cemit", "self"},
+  {"Chat", "self"},
+  {"Chown", "levchk"},
+  {"Combat", "levchk"},
+  {"Cron", "self"},
+  {"DAnnounce", "self"},
+  {"Division", "self"},
+  {"EAnnounce", "self"},
+  {"Empire", "self"},
+  {"Empower", "levchk_lte"},
+  {"GFuncs", "self"},
+  {"Halt", "levchk"},
+  {"Hide", "self"},
+  {"Idle", "self"},
+  {"Join", "levchk"},
+  {"Level", "levset"},
+  {"Link", "levchk"},
+  {"Login", "self"},
+  {"MailAdmin", "levchk"},
+  {"Many_Attribs", "self"},
+  {"Modify", "levchk"},
+  {"Newpass", "levchk"},
+  {"NoPay", "self"},
+  {"NoQuota", "self"},
+  {"Nuke", "levchk"},
+  {"Open", "levchk"},
+  {"Pass_Locks", "levchk"},
+  {"PCreate", "self"},
+  {"PEmit", "levchk"},
+  {"Poll", "self"},
+  {"Powergroup", "levchk"},
+  {"Privilege", "self"},
+  {"PrivWho", "self"},
+  {"Program", "levchk"},
+  {"ProgLock", "levchk"},
+  {"Pueblo_Send", "self"},
+  {"Quota", "self"},
+  {"Queue", "self"},
+  {"RCACHE", "self"},
+  {"Remote", "levchk"},
+  {"RPChat", "self"},
+  {"RPEmit", "self"},
+  {"RPTel", "self"},
+  {"Search", "levchk"},
+  {"See_All", "levchk"},
+  {"See_Queue", "levchk"},
+  {"SetQuotas", "levchk"},
+  {"Site", "self"},
+  {"SQL_Ok", "self"},
+  {"Summon", "levchk"},
+  {"@SU", "levchk"},
+  {"Tel_Thing", "levchk"},
+  {"Tel_Place", "levchk"},
+  {NULL, NULL}
+};
+
+/* Old CobraMUSH Powers Conversion Table {{{2 */
+static struct old_division_power_entry_t old_cobra_conv_t[] = {
+  /* Power, Yes, YesLTE, YesLT */
+  {"Announce", POW_ANNOUNCE, POW_ANNOUNCE, POW_ANNOUNCE},
+  {"Attach", POW_ATTACH, POW_ATTACH, POW_ATTACH_LT},
+  {"BCreate", POW_BCREATE, POW_BCREATE, POW_BCREATE},
+  {"Boot", POW_BOOT, POW_BOOT_LTE, POW_BOOT_LT},
+  {"Builder", POW_BUILD, POW_BUILD, POW_BUILD},
+  {"Can_NsPemit", POW_NSPEMIT, POW_NSPEMIT_LTE, POW_NSPEMIT_LT},
+  {"Cemit", POW_CEMIT, POW_CEMIT, POW_CEMIT},
+  {"Chat", POW_CPRIV, POW_CPRIV, POW_CPRIV},
+  {"Chown", POW_CHOWN, POW_CHOWN_LTE, POW_CHOWN_LT},
+  {"Combat", POW_COMBAT, POW_COMBAT_LTE, POW_COMBAT_LT},
+  {"CQuota", POW_CQUOTAS, POW_CQUOTAS_LTE, POW_CQUOTAS_LT},
+  {"DAnnounce", POW_DANNOUNCE, POW_DANNOUNCE, POW_DANNOUNCE},
+  {"Division", POW_DIVISION, POW_DIVISION, POW_DIVISION},
+  {"EAnnounce", POW_EANNOUNCE, POW_EANNOUNCE, POW_EANNOUNCE},
+  {"Empire", POW_EMPIRE, POW_EMPIRE, POW_EMPIRE},
+  {"Empower", POW_EMPOWER, POW_EMPOWER, POW_EMPOWER},
+  {"GFuncs", POW_GFUNCS, POW_GFUNCS, POW_GFUNCS},
+  {"Halt", POW_HALT, POW_HALT_LTE, POW_HALT_LT},
+  {"Hide", POW_HIDE, POW_HIDE, POW_HIDE},
+  {"Idle", POW_IDLE, POW_IDLE, POW_IDLE},
+  {"Join", POW_JOIN, POW_JOIN, POW_JOIN_LT},
+  {"Level", POW_RERANK, POW_RERANK, POW_RERANK_LT},
+  {"Link", POW_LANY, POW_LANY_LTE, POW_LANY_LT},
+  {"Login", POW_LOGIN, POW_LOGIN, POW_LOGIN},
+  {"MailAdmin", POW_MAIL, POW_MAIL_LTE, POW_MAIL_LT},
+  {"Many_Attribs", POW_MANY_ATTRIBS, POW_MANY_ATTRIBS, POW_MANY_ATTRIBS},
+  {"Modify", POW_MODIFY, POW_MODIFY_LTE, POW_MODIFY_LT},
+  {"Newpass", POW_NEWPASS, POW_NEWPASS_LTE, POW_NEWPASS_LT},
+  {"NoPay", POW_NOPAY, POW_NOPAY, POW_NOPAY},
+  {"NoQuota", POW_NOQUOTA, POW_NOQUOTA, POW_NOQUOTA},
+  {"Open", POW_OANY, POW_OANY_LTE, POW_OANY_LT},
+  {"Pass_Locks", POW_PASS_LOCKS, POW_PASS_LOCKS_LTE, POW_PASS_LOCKS_LT},
+  {"PCreate", POW_PCREATE, POW_PCREATE, POW_PCREATE},
+  {"PEmit", POW_PEMIT, POW_PEMIT_LTE, POW_PEMIT_LT},
+  {"Poll", POW_POLL, POW_POLL, POW_POLL},
+  {"PowerLevel", POW_POWLSET, POW_POWLSET_LTE, POW_POWLSET_LT},
+  {"Privilege", POW_PRIVILEGE, POW_PRIVILEGE, POW_PRIVILEGE},
+  {"PrivWho", POW_PWHO, POW_PWHO, POW_PWHO},
+  {"Program", POW_PROG, POW_PROG_LTE, POW_PROG_LT},
+  {"ProgLock", POW_PROGL, POW_PROGL_LTE, POW_PROGL_LT},
+  {"Pueblo_Send", POW_PUEBLO_SEND, POW_PUEBLO_SEND, POW_PUEBLO_SEND},
+  {"Quota", POW_VQUOTAS, POW_VQUOTAS_LTE, POW_VQUOTAS_LT},
+  {"Queue", POW_QUEUE, POW_QUEUE, POW_QUEUE},
+  {"RCACHE", POW_RCACHE, POW_RCACHE, POW_RCACHE},
+  {"Remote", POW_LFINGERS, POW_LFINGERS_LTE, POW_LFINGERS_LT},
+  {"RPChat", POW_RPCHAT, POW_RPCHAT, POW_RPCHAT},
+  {"RPEmit", POW_RPEMIT, POW_RPEMIT, POW_RPEMIT},
+  {"RPTel", POW_RPTEL, POW_RPTEL, POW_RPTEL},
+  {"Search", POW_SEARCH, POW_SEARCH_LTE, POW_SEARCH_LT},
+  {"See_All", POW_SEE_ALL, POW_SEE_ALL_LTE, POW_SEE_ALL_LT},
+  {"See_Queue", POW_PS, POW_PS_LTE, POW_PS_LT},
+  {"Site", POW_MANAGEMENT, POW_MANAGEMENT, POW_MANAGEMENT},
+  {"SQL_Ok", POW_SQL, POW_SQL, POW_SQL},
+  {"Summon", POW_SUMMON, POW_SUMMON, POW_SUMMON_LT},
+  {"Tel_Thing", POW_TPORT_T, POW_TPORT_T_LTE, POW_TPORT_T_LT},
+  {"Tel_Place", POW_TPORT_O, POW_TPORT_O_LTE, POW_TPORT_O_LT},
+  {NULL, -1, -1, -1}
+};
+
+/* PennMUSH Power Conversion Table  {{{2 */
+
+static struct convold_ptab pconv_ptab[] = {
+  {"Announce", CAN_WALL},
+  {"Boot", CAN_BOOT},
+  {"Builder", CAN_BUILD},
+  {"Cemit", CEMIT},
+  {"Chat", CHAT_PRIVS},
+  {"GFuncs", GLOBAL_FUNCS},
+  {"Halt", HALT_ANYTHING},
+  {"Hide", CAN_HIDE},
+  {"Idle", UNLIMITED_IDLE},
+  {"Link", LINK_ANYWHERE},
+  {"Login", LOGIN_ANYTIME},
+  {"Remote", LONG_FINGERS},
+  {"NoPay", NO_PAY},
+  {"NoQuota", NO_QUOTA},
+  {"Open", OPEN_ANYWHERE},
+  {"PEmit", PEMIT_ALL},
+  {"PCreate", CREATE_PLAYER},
+  {"Poll", SET_POLL},
+  {"Queue", HUGE_QUEUE},
+  {"See_All", SEE_ALL},
+  {"See_Queue", PS_ALL},
+  {"Tel_Thing", TEL_OTHER},
+  {"Tel_Where", TEL_ANYWHERE},
+  {NULL, 0}
+};
+
+/* 1}}} */
+
+/* File Variable Declarations {{{1
+ */
+extern struct db_stat_info current_state;
+extern long flagdb_flags;
+POWERSPACE ps_tab = { NULL, NULL, 0, 0, NULL };
+PTAB power_ptab;
+PTAB powergroup_ptab;
+
+/* }}}1 */
+
+/* Division and Power System Functions {{{1 */
+/* Database Functions {{{2 */
+/* powers_read_all() {{{3 */
+void
+powers_read_all(FILE * in)
+{
+  int count;
+
+  clear_all_powers();
+  /* Allocate initially big enough for 1 */
+  ps_tab.bits_taken = mush_malloc(1, "BITS_TAKEN");
+  if (!ps_tab.bits_taken)
+    mush_panic("Error! Bits Taken couldn't allocate!\n");
+  ps_tab.powers = &power_ptab;
+  ps_tab.powergroups = &powergroup_ptab;
+  ps_tab.powerbits = 0;
+  ptab_init(ps_tab.powers);
+  ptab_init(ps_tab.powergroups);
+
+
+  db_read_this_labeled_number(in, "powercount", &count);
+
+  for (; count > 0; count--)
+    power_read(in);
+  db_read_this_labeled_number(in, "aliascount", &count);
+  for (; count > 0; count--)
+    power_read_alias(in);
+
+  db_read_this_labeled_number(in, "powergroupcount", &count);
+  for (; count > 0; count--)
+    powergroup_read(in);
+
+  ps_tab._Read_Powers_ = 1;
+  power_add_additional();
+}
+
+/* init_powers() {{{3 */
+void
+init_powers()
+{
+  do_rawlog(LT_ERR,
+            "ERROR: Powers failed to read from database.  Loading default power list!");
+  clear_all_powers();
+  ps_tab.powers = &power_ptab;
+  ps_tab.powergroups = &powergroup_ptab;
+  ps_tab.powerbits = 0;
+  /* Allocate this initially big enough for 1 */
+  ps_tab.bits_taken = mush_malloc(1, "BITS_TAKEN");
+  if (!ps_tab.bits_taken)
+    mush_panic("Error! Bits Taken couldn't allocate!\n");
+  ptab_init(ps_tab.powers);
+  ptab_init(ps_tab.powergroups);
+  ps_tab._Read_Powers_ = 1;
+  power_add_additional();
+}
+
+/* powers_read() {{{3 - database power read routine */
+static void
+power_read(FILE * in)
+{
+  char power_name[BUFFER_LEN];
+  char *power_type;
+
+  db_read_this_labeled_string(in, "powername", &power_type);
+
+  if(!(flagdb_flags & FLAG_DBF_CQUOTA_RENAME) && !strcmp(power_type, "CQuota"))
+    strcpy(power_name, "SetQuotas");
+  else
+    strcpy(power_name, power_type);
+
+  db_read_this_labeled_string(in, "type", &power_type);
+
+  add_power_type(power_name, power_type);
+}
+
+/* powergroup_read() {{{3 - database powergroup read routine */
+static void
+powergroup_read(FILE * in)
+{
+  POWERGROUP *pgrp;
+  div_pbits dbits;
+  char *value;
+
+  db_read_this_labeled_string(in, "powergroupname", &value);
+  pgrp = powergroup_add(value);
+  db_read_this_labeled_string(in, "maxpowers", &value);
+  dbits = string_to_dpbits(value);
+
+  if (power_is_zero(dbits, DP_BYTES) == 0) {
+    mush_free(dbits, "POWER_SPOT");
+    pgrp->max_powers = NULL;
+  } else
+    pgrp->max_powers = dbits;
+
+  db_read_this_labeled_string(in, "autopowers", &value);
+  dbits = string_to_dpbits(value);
+
+  if (power_is_zero(dbits, DP_BYTES) == 0) {
+    mush_free(dbits, "POWER_SPOT");
+    pgrp->auto_powers = NULL;
+  } else
+    pgrp->auto_powers = dbits;
+}
+
+/* power_read_alias() {{{3 */
+static void
+power_read_alias(FILE * in)
+{
+  POWER *power;
+  char alias_name[BUFFER_LEN];
+  char *power_pointer;
+
+  db_read_this_labeled_string(in, "aliasname", &power_pointer);
+  strcpy(alias_name, power_pointer);
+  db_read_this_labeled_string(in, "power", &power_pointer);
+
+  /* Make sure the alias doesn't exist */
+  if ((power = find_power(alias_name)))
+    return;
+
+  /* Make sure the power exists */
+  if (!(power = find_power(power_pointer)))
+    return;
+
+  ptab_start_inserts(ps_tab.powers);
+  ptab_insert(ps_tab.powers, alias_name, power);
+  ptab_end_inserts(ps_tab.powers);
+}
+
+/* power_write_all() {{{3 */
+void
+power_write_all(FILE * file)
+{
+  POWER *power;
+  POWERGROUP *pgrp;
+  char pname[BUFFER_LEN];
+  int i, pc_l;
+  int num_powers = 0;
+
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name))
+      num_powers++;
+
+  db_write_labeled_number(file, "powercount", num_powers);
+
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name)) {
+      db_write_labeled_string(file, "  powername", power->name);
+      for (pc_l = 0; powc_list[pc_l].name; pc_l++)
+        if (powc_list[pc_l].powc_chk == power->powc_chk)
+          break;
+      /* We will assume this /does/ match. */
+      db_write_labeled_string(file, "       type", powc_list[pc_l].name);
+    }
+  for (i = 0, power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (strcmp(pname, power->name))
+      i++;
+
+  db_write_labeled_number(file, "aliascount", i);
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (strcmp(pname, power->name)) {
+      db_write_labeled_string(file, "  aliasname", pname);
+      db_write_labeled_string(file, "    power", power->name);
+    }
+  for (num_powers = 0, pgrp =
+       ptab_firstentry_new(ps_tab.powergroups, pname); pgrp;
+       pgrp = ptab_nextentry_new(ps_tab.powergroups, pname))
+    num_powers++;
+  db_write_labeled_number(file, "powergroupcount", num_powers);
+  for (pgrp = ptab_firstentry_new(ps_tab.powergroups, pname); pgrp;
+       pgrp = ptab_nextentry_new(ps_tab.powergroups, pname)) {
+    db_write_labeled_string(file, "  powergroupname", pgrp->name);
+    db_write_labeled_string(file, "    maxpowers",
+                            list_dp_powerz(pgrp->max_powers, 0));
+    db_write_labeled_string(file, "    autopowers",
+                            list_dp_powerz(pgrp->auto_powers, 0));
+  }
+
+}
+
+/* End of Database Functions }}}2 */
+/* PowerGroup Functions {{{2  */
+/* powergroup_db_set() {{{3 - Set or Reset powergroup on Player */
+/* Supplying executor as NOTHING, will assume the game is doing this internally and bypass
+ * security checks
+ */
+void
+powergroup_db_set(dbref executor, dbref player, const char *powergroup,
+                  char autoset)
+{
+  int pg_cnt, pg_indx;
+  char tbuf[BUFFER_LEN], *p_buf[BUFFER_LEN / 2];
+  char pname[BUFFER_LEN];
+  char *p;
+  POWERGROUP *pgrp;
+  POWER *power;
+  int ycode, reset = 0;
+  int po_c;
+
+  if (executor != NOTHING && Owner(executor) != Owner(player)
+      && !(po_c = div_powover(executor, player, "POWERGROUP"))) {
+    notify(executor, "Permission denied.");
+    return;
+  }
+
+
+  if (!powergroup || !*powergroup) {
+    notify(executor, "Must provide powergroup arguments.");
+    return;
+  }
+
+  strcpy(tbuf, powergroup);
+
+  pg_cnt = list2arr(p_buf, BUFFER_LEN / 2, tbuf, ' ');
+  if (!pg_cnt) {
+    notify(executor, "Must supply powers.");
+    return;
+  }
+  for (pg_indx = 0; pg_indx < pg_cnt; reset = 0, pg_indx++) {
+    p = p_buf[pg_indx];
+    if (*p == '!') {
+      reset++;
+      p++;
+    }
+
+    if (!(pgrp = powergroup_find(p))) {
+      notify_format(executor, "No such powergroup to %s.",
+                    reset ? "remove" : "add");
+      continue;
+    }
+
+    if (GoodObject(executor) && !God(executor)
+        && !can_have_pg(player, pgrp)) {
+      notify_format(executor, "%s can not receive the '%s' powergroup.",
+                    unparse_object(executor, player), pgrp->name);
+      continue;
+    }
+
+    /* See if 'executor' can actually add/remove this stuff to 'player */
+    /* In order do this stuff. Executor must have all the 'Max' powers in the
+     * powergroup & have the PowerGroup power. 
+     *                    OR
+     * They own the object and the powergroup exists on them.
+     */
+    if (executor != NOTHING) {
+      if (!(Owns(executor, player) && powergroup_has(executor, pgrp)))
+        for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+             power = ptab_nextentry_new(ps_tab.powers, pname))
+          if (!strcmp(pname, power->name)
+              && (God(executor) ? YES :
+                  check_power_yescode(DPBITS(executor), power))
+              < check_power_yescode(pgrp->max_powers, power)) {
+            notify_format(executor,
+                          "You must possess '%s' power to set the '%s' powergroup.",
+                          power->name, pgrp->name);
+            goto pgset_recurse;
+          }
+    }
+
+
+    if (reset) {
+      if (!powergroup_has(player, pgrp)) {
+        /* TODO: Better message */
+        notify(executor,
+               "They don't have the powergroup in the first place.");
+        continue;
+      }
+      /* Remove */
+      rem_pg_from_player(player, pgrp);
+      notify_format(executor, "You remove powergroup '%s' from %s.",
+                    pgrp->name, object_header(executor, player));
+      notify_format(player, "%s removed powergroup '%s' from you.",
+                    object_header(player, executor), pgrp->name);
+    } else {
+      if (powergroup_has(player, pgrp)) {
+        notify(executor,
+               "They already have that powergroup.  Don't add it twice.");
+        continue;
+      }
+      /* Add */
+      add_pg_to_player(player, pgrp);
+      if (autoset
+          && ((executor == NOTHING)
+              || div_powover(executor, player, "EMPOWER"))) {
+        for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+             power = ptab_nextentry_new(ps_tab.powers, pname))
+          if (!strcmp(pname, power->name)
+              && (ycode =
+                  check_power_yescode(pgrp->auto_powers,
+                                      power)) >
+              (executor ==
+               NOTHING ? YES : check_power_yescode(DPBITS(player),
+                                                   power))) {
+            switch (ycode) {
+            case YES:
+              GIVE_DPBIT(player, power->flag_yes);
+              break;
+            case YESLTE:
+              GIVE_DPBIT(player, power->flag_lte);
+              break;
+            case YESLT:
+              GIVE_DPBIT(player, power->flag_lt);
+              break;
+            }
+          }
+      }
+      if (executor != NOTHING) {
+        memset(tbuf, '\0', 128);
+        strncpy(tbuf, object_header(executor, player), 127);
+        notify_format(executor, "Added Powergroup '%s' to %s.", pgrp->name,
+                      tbuf);
+        memset(tbuf, '\0', 128);
+        strncpy(tbuf, object_header(player, executor), 127);
+        notify_format(player, "You received Powergroup '%s' from %s.",
+                      pgrp->name, tbuf);
+      }
+    }
+  pgset_recurse:
+    /* Continue is here to calm down gcc warning on pgset_recurse 
+     * is at the end of the for loop with nothing under it */
+    continue;
+  }
+}
+
+/* powergroup_empower() {{{3 */
+/* Player Can be NOTHING which will bypass all security checks */
+/* This is basically a powergroup version of division_empower()
+ * for setting powers onto powergroups
+ */
+int
+powergroup_empower(dbref executor, POWERGROUP * pgrp, const char *powers,
+                   char autoset)
+{
+  POWER *power;
+  div_pbits *pbits;
+  int power_count, power_indx;
+  char *p_buf[BUFFER_LEN / 2];
+  char tbuf[BUFFER_LEN];
+  char msg_buf[BUFFER_LEN], *mbp;
+  int flag, pg_lscope;
+  int good_object;
+  char *p, *t;
+
+  good_object = GoodObject(executor);
+
+  if (!powers || !*powers) {
+    if (good_object)
+      notify(executor, "Must supply a power.");
+    return 0;
+  }
+
+  pbits = autoset ? &pgrp->auto_powers : &pgrp->max_powers;
+
+  strcpy(tbuf, powers);
+  power_count = list2arr(p_buf, BUFFER_LEN / 2, tbuf, ' ');
+  if (!power_count) {
+    if (good_object)
+      notify(executor, "Must supply powers.");
+    return 0;
+  }
+
+  mbp = msg_buf;
+  *mbp = '\0';
+
+  for (power_indx = 0; power_indx < power_count; power_indx++) {
+    t = p_buf[power_indx];
+    if (*t == NOT_TOKEN) {
+      t++;
+      flag = NO;
+    } else if ((p = strchr(t, ':'))) {
+      *p++ = '\0';
+      while (*p == ' ')
+        p++;
+      flag = yescode_i(p);
+    } else
+      flag = YES;
+    power = find_power(t);
+    if (!power) {
+      if (good_object)
+        notify_format(executor, T("No such power '%s'."), t);
+      continue;
+    }
+
+    /* Check if A) They're Giving it
+     *              1) Their power rating of that power must be better than it has
+     *              2) If its the auto list its giving it to, it must exist in the max list
+     *                 if not.. Attempt to give it to the max list first. If that fails. Fail
+     *                 this.
+     *          B) They're taking it away
+     *              1) Their power rating of that power must equal to or better than it has
+     *              2) If they're taking it away from the max list, make it disappear from the
+     *                 auto list if it has it.
+     */
+    pg_lscope =
+        (!good_object
+         || God(executor)) ? YES : check_power_yescode(DPBITS(executor),
+                                                       power);
+    if (flag) {
+      if (check_power_yescode(*pbits, power) == flag) {
+        if (good_object)
+          notify_format(executor,
+                        "%s(%s) already exists on that powergroup.",
+                        power->name, yescode_str(flag));
+        continue;
+      } else if ((flag > pg_lscope) && good_object) {
+        notify_format(executor,
+                      "You must posses %s(%s) to give it to a powergroup.",
+                      power->name, yescode_str(flag));
+        continue;
+      }
+      if (autoset) {
+        /* Alrite.. they should already be able to do this.. now just check the level of
+         * the power on the manual spot.  If it needs to be higher, upgrade it.
+         */
+        if (check_power_yescode(pgrp->max_powers, power) < flag) {
+          if (!pgrp->max_powers)
+            pgrp->max_powers = new_power_bitmask();
+          RESET_POWER_DP(pgrp->max_powers, power);
+          switch (flag) {
+          case YES:
+            DPBIT_SET(pgrp->max_powers, power->flag_yes);
+            break;
+          case YESLTE:
+            DPBIT_SET(pgrp->max_powers, power->flag_lte);
+            break;
+          case YESLT:
+            DPBIT_SET(pgrp->max_powers, power->flag_lt);
+            break;
+          }
+          if (good_object)
+            notify_format(executor, "Added '%s(%s)' as a max power.",
+                          power->name, yescode_str(flag));
+        }
+      }
+    } else {
+      if (good_object && (pg_lscope < check_power_yescode(*pbits, power))) {
+        notify(executor,
+               "You must posses that power to take it away from a powergroup");
+        continue;
+      }
+      if (!autoset) {
+        /* If the power is on their auto list, remove it cause its no longer allowed to exist
+         * there
+         */
+        if (check_power_yescode(pgrp->auto_powers, power) > NO) {
+          RESET_POWER_DP(pgrp->auto_powers, power);
+        }
+        if (pgrp->auto_powers
+            && power_is_zero(pgrp->auto_powers, DP_BYTES) == 0) {
+          mush_free(pgrp->auto_powers, "POWER_SPOT");
+          pgrp->auto_powers = NULL;
+        }
+        if (good_object)
+          notify_format(executor, "Removed '%s' as an auto power.",
+                        power->name);
+      }
+    }
+
+    if (flag > NO && !*pbits)
+      *pbits = new_power_bitmask();
+
+    RESET_POWER_DP((*pbits), power);
+
+    switch (flag) {
+    case YES:
+      DPBIT_SET((*pbits), power->flag_yes);
+      break;
+    case YESLTE:
+      DPBIT_SET((*pbits), power->flag_lte);
+      break;
+    case YESLT:
+      DPBIT_SET((*pbits), power->flag_lt);
+      break;
+    case NO:
+      /* the power should already be clared, just check to see if we're zeroed out 
+       * & then free the spot 
+       */
+      if (*pbits && power_is_zero(*pbits, DP_BYTES) == 0) {
+        mush_free(*pbits, "POWER_SPOT");
+        *pbits = NULL;
+      }
+    }
+
+    if (good_object)
+      safe_format(msg_buf, &mbp, "%s%s(%s)", *msg_buf != '\0' ? ", " : "",
+                  power->name, yescode_str(flag));
+  }
+  *mbp = '\0';
+  if (good_object && *msg_buf)
+    notify_format(executor, T("PowerGroup '%s' Empowered: %s"), pgrp->name,
+                  msg_buf);
+  return 1;
+}
+
+/* powergroups_list() {{{3 - List powergroups able to set from 'players' point of view */
+char *
+powergroups_list(dbref player, char flag)
+{
+  POWERGROUP *pgrp;
+  char pg_name[BUFFER_LEN];
+  static char st_buf[BUFFER_LEN];
+  char *st_bp;
+  /* For right now this should list all powergroups period */
+
+  st_bp = st_buf;
+  *st_bp = '\0';
+
+  for (pgrp = ptab_firstentry_new(ps_tab.powergroups, pg_name); pgrp;
+       pgrp = ptab_nextentry_new(ps_tab.powergroups, pg_name)) {
+    if (*st_buf != '\0') {
+      if (flag)
+        safe_str(", ", st_buf, &st_bp);
+      else
+        safe_chr(' ', st_buf, &st_bp);
+    }
+    safe_str(pg_name, st_buf, &st_bp);
+  }
+  *st_bp = '\0';
+  return st_buf;
+
+}
+
+/* powergroups_list_on() {{{3 - List powergroups on 'player' */
+char *
+powergroups_list_on(dbref player, char commaize)
+{
+  static char st_buf[BUFFER_LEN];
+  char *st_bp;
+  struct power_group_list *pg_l;
+
+  st_bp = st_buf;
+  *st_bp = '\0';
+  if ((pg_l = SDIV(player).powergroups)) {
+    for (; pg_l; pg_l = pg_l->next) {
+      if (*st_buf != '\0')
+        safe_str(commaize ? ", " : " ", st_buf, &st_bp);
+      safe_str(pg_l->power_group->name, st_buf, &st_bp);
+    }
+    *st_bp = '\0';
+  } else
+    return (char *) "None";
+  return st_buf;
+}
+
+/* powergroup_delete() {{{3 - delete powergroup */
+char
+powergroup_delete(dbref player, const char *key)
+{
+  POWERGROUP *pgrp;
+  int i;
+
+  if (!God(player)) {
+    notify(player, "Permission denied.");
+    return 0;
+  }
+
+  if (!(pgrp = (POWERGROUP *) ptab_find_exact(ps_tab.powergroups, key)))
+    return 0;
+
+  for (i = 0; i < db_top; i++)
+    if (powergroup_has(i, pgrp))
+      rem_pg_from_player(i, pgrp);
+
+  /* Delete & Free stuff */
+  mush_free((Malloc_t) pgrp->name, "PG_NAME");
+  if (pgrp->auto_powers)
+    mush_free((Malloc_t) pgrp->auto_powers, "POWER_SPOT");
+  if (pgrp->max_powers)
+    mush_free((Malloc_t) pgrp->max_powers, "POWER_SPOT");
+  mush_free((Malloc_t) pgrp, "POWERGROUP");
+  ptab_delete(ps_tab.powergroups, key);
+  return 1;
+}
+
+/* add_pg_to_player() {{{3 */
+/* This function only adds the powergroup actually onto teh players
+ * powergroup linked list.  Nothing more
+ */
+void
+add_pg_to_player(dbref player, POWERGROUP * powergroup)
+{
+  struct power_group_list *pgl;
+  struct power_group_list *newpgl;
+
+  pgl = SDIV(player).powergroups;
+
+  /* Is this their first power group? */
+  if (pgl == NULL) {
+    newpgl =
+        (struct power_group_list *)
+        mush_malloc(sizeof(struct power_group_list), "PG_LIST");
+    newpgl->power_group = powergroup;
+    newpgl->next = NULL;
+    SDIV(player).powergroups = newpgl;
+    return;
+  }
+
+  /* They already have it, so cancel the operation - this catches the first
+   * one only */
+  if (pgl->power_group == powergroup)
+    return;
+
+  /* Should it be the first one? */
+  if (strcasecmp(pgl->power_group->name, powergroup->name) > 0) {
+    newpgl =
+        (struct power_group_list *)
+        mush_malloc(sizeof(struct power_group_list), "PG_LIST");
+    newpgl->power_group = powergroup;
+    newpgl->next = pgl;
+    SDIV(player).powergroups = newpgl;
+    return;
+  }
+
+  for (; pgl->next; pgl = pgl->next) {
+    /* They already have it, so cancel the operation */
+    if (pgl->next->power_group == powergroup)
+      return;
+
+    /* Keep power groups sorted by name */
+    if (strcasecmp(pgl->next->power_group->name, powergroup->name) > 0)
+      break;
+  }
+
+  newpgl =
+      (struct power_group_list *)
+      mush_malloc(sizeof(struct power_group_list), "PG_LIST");
+  newpgl->power_group = powergroup;
+
+  newpgl->next = pgl->next;
+  pgl->next = newpgl;
+}
+
+/* powergroup_has() {{{3 - Check to see if player has the powergroup on 'em */
+int
+powergroup_has(dbref player, POWERGROUP * pgrp)
+{
+  struct power_group_list *pg_list;
+
+  for (pg_list = SDIV(player).powergroups; pg_list;
+       pg_list = pg_list->next)
+    if (pg_list->power_group == pgrp)
+      return 1;
+  return 0;
+}
+
+/* rem_pg_from_player() {{{3 - Remove Power Group from object */
+void
+rem_pg_from_player(dbref player, POWERGROUP * powergroup)
+{
+  POWER *power;
+  char pname[BUFFER_LEN];
+  struct power_group_list *pgl, *prev;
+  int ycode;
+
+  prev = NULL;
+  for (pgl = SDIV(player).powergroups; pgl; prev = pgl, pgl = pgl->next)
+    if (pgl->power_group == powergroup)
+      break;
+
+  /* Did they even have it to begin with? */
+  if (!pgl)
+    return;
+
+  /* Was it the first one? */
+  if (!prev) {
+    SDIV(player).powergroups = pgl->next;
+    mush_free((Malloc_t) pgl, "PG_LIST"); 
+    SDIV(player).powergroups = NULL;
+} else {
+    prev->next = pgl->next;
+   mush_free((Malloc_t) pgl, "PG_LIST");
+ }
+
+  /* After its all removed and shit.  Make sure they can still have 
+   * all those happy little fuckin powers */
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name))
+      if (!can_have_power
+          (player, power, check_power_yescode(DPBITS(player), power))) {
+        RESET_POWER(player, power);
+        /* If they can have the power but at a lower level. just downgrade the power */
+        ycode = can_receive_power_at(player, power);
+        switch (ycode) {
+        case YES:
+          GIVE_DPBIT(player, power->flag_yes);
+          break;
+        case YESLTE:
+          GIVE_DPBIT(player, power->flag_lte);
+          break;
+        case YESLT:
+          GIVE_DPBIT(player, power->flag_lt);
+          break;
+        }
+      }
+
+}
+
+/* powergroup_add() {{{3 */
+POWERGROUP *
+powergroup_add(const char *pg_name)
+{
+  POWERGROUP *pgrp;
+
+  if (!pg_name || !*pg_name)
+    return NULL;
+
+  /* Don't add if it already exists */
+  if (powergroup_find(pg_name))
+    return NULL;
+
+  ptab_start_inserts(ps_tab.powergroups);
+
+  /* Initialize Power group */
+  pgrp = (POWERGROUP *) mush_malloc(sizeof(POWERGROUP), "POWERGROUP");
+  pgrp->name = mush_strdup(pg_name, "PG_NAME");
+  pgrp->max_powers = NULL;
+  pgrp->auto_powers = NULL;
+
+  /* Insert it into the ptab */
+  ptab_insert(ps_tab.powergroups, pg_name, pgrp);
+  ptab_end_inserts(ps_tab.powergroups);
+
+  return pgrp;
+}
+
+
+/* Power Control Checks {{{2  */
+/* powc_levchk() {{{3 * Normal Power Over checking */
+int
+powc_levchk(int plev, dbref who, dbref what)
+{
+
+  /* quick check if its themself */
+  if (plev > NO && who == what)
+    return 1;
+  switch (plev) {
+  case YES:
+    return 1;
+  case YESLTE:
+    if (LEVEL(who) >= LEVEL(what))
+      return 1;
+    break;
+  case YESLT:
+    if ((LEVEL(who) > LEVEL(what))
+        || (Owner(who) == Owner(what) && LEVEL(who) == LEVEL(what)))
+      return 1;
+  }
+  return 0;
+}
+
+/* powc_levchk() {{{3  - special check for level setting */
+int
+powc_levset(int plev, dbref who, dbref what)
+{
+  int i;
+  i = powc_levchk(plev, who, what);
+  if (IsDivision(what) && !div_powover(who, who, "Division"))
+    return 0;
+  return i;
+}
+
+/* powc_self() {{{3 - Can only use it over yourself Check */
+int
+powc_self(int plev __attribute__ ((__unused__)), dbref p1, dbref p2)
+{
+  return (p1 == p2);
+}
+
+/* powc_bcreate() {{{3 - Bcreate check.. dude using it over must be a builder (used for newpass check) */
+int
+powc_bcreate(int plev __attribute__ ((__unused__)), dbref who, dbref what)
+{
+  if (!div_powover(who, who, "Builder"))
+    return 0;
+  return (LEVEL(who) >= LEVEL(what));
+}
+
+/* powc_levchk_lte()  {{{3 - FullYes power(like BCreate) but can only be used LTE */
+int
+powc_levchk_lte(int plev
+                __attribute__ ((__unused__)), dbref who, dbref what)
+{
+  return (LEVEL(who) >= LEVEL(what) || (plev > NO && who == what));
+}
+
+/* End of POWC Checks }}}2 */
+
+/* COMMAND(cmd_power) {{{2 */
+/* 
+ * @power/list
+ * - List Powers
+ *   
+ *   @power/add Power=Type
+ *   - Add Power
+ *
+ *   @power/Delete Power
+ *
+ *   @power <power>
+ *   - Power Info
+ *
+ *   @power <player>=<power>
+ *   - Empower Equivalent
+ *
+ *   @power/alias <power to point to>=<alias>
+ *   - Alias Power
+ */
+
+COMMAND(cmd_power)
+{
+  POWER *power, *alias;
+  char tbuf[BUFFER_LEN], pname[BUFFER_LEN];
+  char *tbp;
+  dbref target;
+  int i;
+
+  tbp = tbuf;
+  *tbp = '\0';
+
+  if (SW_ISSET(sw, SWITCH_LIST)) {
+    do_list_powers(player, arg_left);
+  } else if (SW_ISSET(sw, SWITCH_ADD) && Site(player)) {
+    /* Add power */
+    if (!arg_left || !arg_right) {
+      notify(player, "Must Supply Power Name & Type");
+      return;
+    }
+    power = find_power(arg_left);
+    if (!power) {
+      /* Make sure its a good power name */
+      switch (power_goodname(arg_left)) {
+      case 0:
+        notify(player, "Must Supply power name.");
+        return;
+      case -1:
+        notify(player, "Must be longer than one character.");
+        return;
+      case -2:
+        notify(player, "Power may not contain spaces.");
+        return;
+      default:
+        break;
+      }
+      /* Make sure type exists */
+      for (i = 0; powc_list[i].name; i++)
+        if (!strcasecmp(powc_list[i].name, arg_right))
+          break;
+      if (powc_list[i].name) {
+        power = add_power_type(arg_left, powc_list[i].name);
+        notify_format(player, "Added power '%s' as a '%s' type power.",
+                      power->name, powc_list[i].name);
+      } else
+        notify(player, "Invalid Type.");
+    } else
+      notify(player, "No point adding a power that already exists.");
+  } else if (SW_ISSET(sw, SWITCH_DELETE) && Site(player)) {
+    /* This should handle deleting both aliases & powers */
+    if (arg_left && *arg_left) {
+      power = (POWER *) ptab_find_exact(ps_tab.powers, arg_left);
+      if (power) {
+        if (strcasecmp(power->name, arg_left)) {
+          /* This is an alias, this one is real easy. */
+          ptab_delete(ps_tab.powers, arg_left);
+          notify_format(player, "Power Alias '%s' removed.", arg_left);
+        } else {
+          /* This is a real power */
+          if (power_delete(arg_left))
+            notify_format(player, "Power '%s' deleted.", arg_left);
+          else
+            notify(player, "No such power.");
+        }
+      } else
+        notify(player, "No such power or alias.");
+    } else
+      notify(player, "Must specify a power or alias to delete.");
+  } else if (SW_ISSET(sw, SWITCH_ALIAS) && Site(player)) {
+    if (arg_left && *arg_left && arg_right && *arg_right) {
+      /* Make sure alias doesn't already exist */
+      power = find_power(arg_right);
+      if (!power) {
+        power = find_power(arg_left);
+        if (!power) {
+          notify(player, "That power does not exist.");
+          return;
+        }
+        ptab_start_inserts(ps_tab.powers);
+        ptab_insert(ps_tab.powers, arg_right, power);
+        ptab_end_inserts(ps_tab.powers);
+        notify_format(player, "Power alias '%s' added pointing to '%s'.",
+                      arg_right, power->name);
+      } else if (strcasecmp(power->name, arg_left)) {
+        /* Attempt to re-alias the power, by deleting the alias & repointing it */
+        power = find_power(arg_left);
+        if (power) {
+          /* delete */
+          ptab_delete(ps_tab.powers, arg_right);
+          /* re-add */
+          ptab_start_inserts(ps_tab.powers);
+          ptab_insert(ps_tab.powers, arg_right, power);
+          ptab_end_inserts(ps_tab.powers);
+          notify_format(player, "Power alias '%s' added pointing to '%s'.",
+                        arg_right, power->name);
+        } else
+          notify(player, "That power does not exist.");
+      } else
+        notify(player, "Can not overwrite a real power with an alias.");
+    } else
+      notify(player, "Must supply alias and power to alias it to.");
+  } else {
+    if (arg_left && arg_right && *arg_left && *arg_right) {
+      /* Empower Alias Code */
+      target =
+          noisy_match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
+      if (target == NOTHING)
+        return;
+      division_empower(player, target, arg_right);
+    } else if (arg_left && *arg_left) {
+      /* Power Info Code */
+      power = find_power(arg_left);
+      if (power) {
+        /* Find Power Type */
+        for (i = 0; powc_list[i].name; i++)
+          if (powc_list[i].powc_chk == power->powc_chk)
+            break;
+        for (alias = ptab_firstentry_new(ps_tab.powers, pname); alias;
+             alias = ptab_nextentry_new(ps_tab.powers, pname))
+          if (strcasecmp(pname, alias->name) && alias == power) {
+            if (!*tbuf)
+              safe_str(pname, tbuf, &tbp);
+            else
+              safe_format(tbuf, &tbp, ", %s", pname);
+          }
+        *tbp = '\0';
+
+        notify_format(player, "Power Name    : %s", power->name);
+        notify_format(player, "Power Aliases : %s", tbuf ? tbuf : "");
+        notify_format(player, "Power Type    : %s", powc_list[i].name);
+      } else
+        notify(player, "No such power.");
+    }
+  }
+}
+
+/* COMMAND(cmd_powergroup) {{{2 */
+/* @powergroup - syntaxes
+ * 
+ * @powergroup/add <powergroup>          - Add a powergroup into the system
+ * @powergroup/delete <powergroup>       - Remove a powergroup from a user
+ * @powergroup <powergroup>              - View powergroup Info
+ * @powergroup <powergroup>=<power>      - Alias to @powergroup/max
+ * @powergroup <user>=<powergroup>       - Assign a powergroup to a user
+ * @powergroup/auto <powergroup>=<power> - Assign a power as an autoset for the powergroup
+ * @powergroup/max <powergroup>=<power>  - Assign a power to the maxset for the powergroup
+ * @powergroup/list                      - List all available powergroups
+ * @powergroup/list <player>            - List powergroups set on a object
+ * @powergroup/raw <user>=<powergroup>   - Assign a powergroup to a user without assigning auto powers
+ *
+ */
+
+COMMAND(cmd_powergroup)
+{
+  POWERGROUP *pgrp;
+  dbref match;
+
+
+  if (SW_ISSET(sw, SWITCH_LIST)) {
+    if (!has_power(player, "POWERGROUP")) {
+      notify(player, "Permission denied.");
+      return;
+    }
+
+    if (arg_left && *arg_left) {
+      match = match_thing(player, arg_left);
+      if (!GoodObject(match))
+        return;
+      notify_format(player, "Powergroups on %s: %s",
+                    object_header(player, match),
+                    powergroups_list_on(match, 1));
+    } else
+      notify_format(player,
+                    "PowerGroups Available to Set: %s",
+                    powergroups_list(player, 1));
+  } else if (SW_ISSET(sw, SWITCH_ADD)) {
+    if (!has_power(player, "POWERGROUP")) {
+      notify(player, "Permission denied.");
+      return;
+    }
+
+    switch (power_goodname(arg_left)) {
+    case 0:
+      notify(player, "Must supply powergroup name.");
+      return;
+    case -1:
+      notify(player, "Powergroup name must be longer than one character.");
+      return;
+    case -2:
+      notify(player, "Powergroup name may not contain spaces in it.");
+      return;
+    default:
+      break;
+    }
+    if (powergroup_add(arg_left))
+      notify(player, "Added powergroup.");
+    else
+      notify(player, "Couldn't add powergroup.");
+  } else if (SW_ISSET(sw, SWITCH_DELETE)) {
+    if (powergroup_delete(player, arg_left))
+      notify(player, "Deleted powergroup.");
+    else
+      notify(player, "Couldn't delete powergroup.");
+  } else if (SW_ISSET(sw, SWITCH_AUTO) || SW_ISSET(sw, SWITCH_MAX)) {
+    if (!has_power(player, "POWERGROUP")) {
+      notify(player, "Permission denied.");
+      return;
+    }
+
+    /* Assign auto powers */
+    if (arg_left && *arg_left) {
+      if ((pgrp = powergroup_find(arg_left))) {
+        powergroup_empower(player, pgrp, arg_right,
+                           !!SW_ISSET(sw, SWITCH_AUTO));
+      } else
+        notify(player, "No such powergroup.");
+
+    } else
+      notify(player, "Must provide a powergroup argument.");
+  } else if (SW_ISSET(sw, SWITCH_RAW)) {
+    if (arg_left && *arg_left && arg_right && *arg_right) {
+      match = match_thing(player, arg_left);
+      if (GoodObject(match))
+        powergroup_db_set(player, match, arg_right, 0);
+    } else
+      notify(player, "Must supply a player and powergroup argument.");
+  } else {
+    if (!arg_left || !*arg_left) {
+      notify(player, "Must supply an argument.");
+      return;
+    } else if (arg_right && *arg_right) {
+      match = match_result(player, arg_left, NOTYPE, MAT_EVERYTHING);
+      if (GoodObject(match)) {
+        powergroup_db_set(player, match, arg_right, 1);
+      } else {
+        /* They're assigning a power to a powergroup */
+        if ((pgrp = powergroup_find(arg_left))
+            && has_power(player, "POWERGROUP"))
+          (void) powergroup_empower(player, pgrp, arg_right, 0);
+        else                    /* Give can't find that error message as if we're failing on GoodObject */
+          notify(player, "I can't see that here.");
+        return;
+      }
+    } else {
+      if (!has_power(player, "POWERGROUP")) {
+        notify(player, "Permission denied.");
+        return;
+      }
+
+      if ((pgrp = powergroup_find(arg_left))) {
+        /* They're wanting info on the powergroup */
+        notify_format(player, "%sPowerGroup Name - %s%s", ANSI_HILITE,
+                      pgrp->name, ANSI_NORMAL);
+        notify_format(player, "%sAutoSet Powers -%s %s", ANSI_HILITE,
+                      ANSI_NORMAL, list_dp_powerz(pgrp->auto_powers, 1));
+        notify_format(player, "%sMaxSet Powers  -%s %s", ANSI_HILITE,
+                      ANSI_NORMAL, list_dp_powerz(pgrp->max_powers, 1));
+      } else
+        notify(player, "No such powergroup.");
+    }
+  }
+}
+
+/* list_dp_powers() {{{2 */
+const char *
+list_dp_powerz(div_pbits pbits, int flag)
+{
+  POWER *power;
+  char pname[BUFFER_LEN];
+  static char buf1[BUFFER_LEN], *tbp;
+  int yescode;
+
+  tbp = buf1;
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name)) {
+      yescode = check_power_yescode(pbits, power);
+      if (yescode > NO) {
+        if (tbp != buf1)
+          safe_chr(' ', buf1, &tbp);
+        safe_str(pname, buf1, &tbp);
+        if (flag) {
+          if (yescode < YES)
+            safe_format(buf1, &tbp, "(%s)", yescode_str(yescode));
+        } else
+          safe_format(buf1, &tbp, ":%d", yescode);
+
+      }
+    }
+  *tbp = '\0';
+  return buf1;
+}
+
+/* division_set() {{{2 */
+void
+division_set(dbref exec, dbref target, const char *arg2)
+{
+/* @div/set player=division, @div/set player=none for reset */
+  ATTR *divrcd;
+  char *p_buf[BUFFER_LEN / 2];
+  char buf[BUFFER_LEN], *bp;
+  dbref divi;
+  struct power_group_list *pg_l;
+  int cnt;
+
+  /* Lets make sure they're completely logged out if they're in @sd/division */
+  divrcd = atr_get(target, "XYXX_DIVRCD");
+  
+  if(divrcd != NULL && (cnt = list2arr(p_buf, BUFFER_LEN / 2, safe_atr_value(divrcd), ' ')) > 0) {
+    notify(exec, "Player must log out of every occurance of @sd/division before they may be redivisioned.");
+    return;
+  }
+
+
+  if (!div_powover(exec, target, "Attach")) {
+    notify(exec, T(e_perm));
+    return;
+  }
+
+  if (!strcasecmp(arg2, "NONE")) {
+    notify(exec, T("Division reset."));
+    notify(target, T("GAME: Division reset."));
+
+    if (Typeof(target) == TYPE_PLAYER)
+      adjust_divisions(target, Division(target), NOTHING);
+    else {
+      for (pg_l = SDIV(target).powergroups; pg_l;
+           pg_l = SDIV(target).powergroups)
+        rem_pg_from_player(target, pg_l->power_group);
+      if (DPBITS(target))
+        mush_free(DPBITS(target), "POWER_SPOT");
+      DPBITS(target) = NULL;
+    }
+
+    return;
+  }
+  divi = match_result(exec, arg2, TYPE_THING, MAT_EVERYTHING);
+  if (divi == NOTHING || !GoodObject(divi)) {
+    notify(exec, T("That is not a valid division."));
+    return;
+  }
+  if (!IsDivision(divi)) {
+    notify(exec, T("That is not a valid division."));
+    return;
+  }
+  if (!div_inscope(exec, divi)) {
+    notify(exec, T("That division is not in your scope."));
+    return;
+  }
+
+  if (divi == target) {
+    notify(exec, T("Can't division something to itself."));
+    return;
+  }
+
+  if (Typeof(target) == TYPE_PLAYER)
+    adjust_divisions(target, Division(target), divi);
+  else
+    SDIV(target).object = divi;
+  if (IsDivision(target) && GoodObject(SDIV(target).object)) {
+    Parent(target) = divi;
+    moveto(target, divi);
+  }
+  /* kludged this in, because notify_format won't work right 
+   * with 2 object_header's in a row 
+   */
+  bp = buf;
+  safe_str(T("Division for "), buf, &bp);
+  safe_str(object_header(exec, target), buf, &bp);
+  safe_str(T(" set to "), buf, &bp);
+  safe_str(object_header(exec, divi), buf, &bp);
+  safe_chr('.', buf, &bp);
+  *bp = '\0';
+  notify(exec, buf);
+  notify_format(target, T("GAME: Division set to %s."),
+                object_header(target, divi));
+}
+
+/* create_div() {{{2 - Create New Division with name & return the dbref */
+dbref
+create_div(dbref owner, const char *name)
+{
+  char buf[BUFFER_LEN];
+  dbref obj, loc;
+
+  if (!div_powover(owner, owner, "Division")) {
+    notify(owner, T(e_perm));
+    return NOTHING;
+  } else if (*name == '\0') {
+    notify(owner, T("Create what division?"));
+    return NOTHING;
+  } else if (!ok_name(name)) {
+    notify(owner, T("Not a valid name for a division."));
+    return NOTHING;
+  }
+  obj = new_object();
+  set_name(obj, name);
+  Owner(obj) = owner;
+  Zone(obj) = NOTHING;
+  Type(obj) = TYPE_DIVISION;
+  if ((loc = Location(owner)) != NOTHING &&
+      (controls(owner, loc) || Abode(loc))) {
+    Home(obj) = loc;            /* home */
+  } else {
+    Home(obj) = Home(owner);    /* home */
+  }
+  SDIV(obj).object = SDIV(owner).object;
+  /* if they belong to a division set it there */
+  if (SDIV(obj).object != NOTHING) {
+    Location(obj) = SDIV(obj).object;
+    PUSH(obj, Contents(SDIV(obj).object));
+  } else {
+    Location(obj) = owner;
+    PUSH(obj, Contents(owner));
+  }
+  Parent(obj) = SDIV(owner).object;
+  current_state.divisions++;
+  sprintf(buf, object_header(owner, obj));
+  notify_format(owner, T("Division created: %s  Parent division: %s"),
+                buf, object_header(owner, SDIV(obj).object));
+  return obj;
+}
+
+/* check_power_yescode() {{{2 - check integer power yescode of said POWER * */
+int
+check_power_yescode(div_pbits pbits, POWER * power)
+{
+  if (!pbits)
+    return NO;
+  if (DPBIT_ISSET(pbits, power->flag_yes))
+    return YES;
+  else if (DPBIT_ISSET(pbits, power->flag_lte))
+    return YESLTE;
+  else if (DPBIT_ISSET(pbits, power->flag_lt))
+    return YESLT;
+  else
+    return NO;
+}
+
+/* div_powover() {{{2 -  check if 'who' can use power 'name' over 'what' */
+int
+div_powover(dbref who, dbref what, const char *name)
+{
+  POWER *power;
+  int inscope, ycode, pcheck;
+
+  if (God(who))                 /* god is almighty */
+    return 1;
+
+  inscope = div_inscope(who, what);
+  power = find_power(name);
+  if (!power)
+    return 0;
+  if (God(who))
+    ycode = YES;
+  else
+    ycode = check_power_yescode(DPBITS(who), power);
+  if (ycode == NO)
+    pcheck = 0;
+  else
+    pcheck = power->powc_chk(ycode, who, what);
+
+  if (inscope && pcheck)
+    return 1;
+
+  if (Owner(who) != who && Inherit_Powers(who)) {
+    /* We don't let God's objects inherit his powers, for safety.
+     */
+
+    if (God(Owner(who)))
+      return 0;
+
+    inscope = div_inscope(Owner(who), what);
+    ycode = check_power_yescode(DPBITS(Owner(who)), power);
+
+    if (ycode == NO)
+      pcheck = 0;
+    else
+      pcheck = power->powc_chk(ycode, who, what);
+    if (inscope && pcheck)
+      return 1;
+  }
+
+  return 0;
+}
+
+/* div_inscope() {{{2 - check if scopee is in divscope of scoper */
+int
+div_inscope(dbref scoper, dbref scopee)
+{
+/* check if scopee is in the divscope of scoper */
+  dbref div1, div2;
+
+  if (!GoodObject(scopee))
+    return 0;
+  if (God(scoper))
+    return 1;
+
+  /* For the odd case they aren't in a division.. They always in their own divscope */
+  if (scoper == scopee)
+    return 1;
+
+  if (IsDivision(scoper))
+    div1 = scoper;
+  else
+    div1 = SDIV(scoper).object;
+
+  if (IsDivision(scopee))
+    div2 = scopee;
+  else
+    div2 = SDIV(scopee).object;
+
+  if (div1 == NOTHING)          /* can't match to nothing */
+    return 0;
+  if (div2 == NOTHING)          /*  they're automatically at the bottom of the divtree */
+    return 1;
+
+  for (; div1 != div2; div2 = SDIV(div2).object)
+    if (div2 == NOTHING)        /* went off the tree */
+      return 0;
+    else if (div2 == SDIV(div2).object) {       /* detect & fix bad division tree */
+      do_log(LT_ERR, scoper, scopee,
+             T("Bad Master Division(#%d).  Corrected."), div2);
+      SDIV(div2).object = NOTHING;
+    }
+  return 1;
+}
+
+/* division_empower() {{{2 */
+void
+division_empower(dbref exec, dbref target, const char *arg2)
+{
+  POWER *power;
+  int power_count, power_indx;
+  char *p_buf[BUFFER_LEN / 2];
+  char tbuf[BUFFER_LEN];
+  char msg_buf[BUFFER_LEN], *mbp;
+  int flag, cont;
+  char *p, *t;
+
+  if (!div_powover(exec, target, "Empower")) {
+    notify(exec, T(e_perm));
+    return;
+  }
+
+  if (!arg2 || !*arg2) {
+    notify(exec, "Must supply a power.");
+    return;
+  }
+
+  strcpy(tbuf, arg2);
+  power_count = list2arr(p_buf, BUFFER_LEN / 2, tbuf, ' ');
+  if (!power_count) {
+    notify(exec, "Must supply powers.");
+    return;
+  }
+
+  mbp = msg_buf;
+  *mbp = '\0';
+
+  for (cont = power_indx = 0; power_indx < power_count;
+       cont = 0, power_indx++) {
+    t = p_buf[power_indx];
+    if (*t == NOT_TOKEN) {
+      t++;
+      flag = NO;
+    } else if ((p = strchr(t, ':'))) {
+      *p++ = '\0';
+      while (*p == ' ')
+        p++;
+      flag = yescode_i(p);
+    } else
+      flag = YES;
+    power = find_power(t);
+    if (!power) {
+      notify_format(exec, T("No such power '%s'."), t);
+      continue;
+    }
+
+    switch (can_give_power(exec, target, power, flag)) {
+    case 0:
+      notify(exec, T("You can't give powers you don't have."));
+      cont = 1;
+      break;
+    case -1:
+      notify(exec, T("You can't take away powers you don't have."));
+      cont = 1;
+      break;
+    case -2:
+      notify_format(exec, T("Target player can't have power: '%s'"),
+                    power->name);
+      cont = 1;
+      break;
+    }
+
+    if (cont)
+      continue;
+
+
+    /* Reset power regardless, it'll be set back */
+    RESET_POWER(target, power);
+
+    switch (flag) {
+    case YES:
+      GIVE_DPBIT(target, power->flag_yes);
+      break;
+    case YESLTE:
+      GIVE_DPBIT(target, power->flag_lte);
+      break;
+    case YESLT:
+      GIVE_DPBIT(target, power->flag_lt);
+      break;
+    case NO:
+      /* Upon getting rid of a power.. check to see if we're zeroed out & then free the spot */
+      if (DPBITS(target) && power_is_zero(DPBITS(target), DP_BYTES) == 0) {
+        mush_free(DPBITS(target), "POWER_SPOT");
+        DPBITS(target) = NULL;
+      }
+    }
+
+    safe_format(msg_buf, &mbp, "%s%s(%s)", *msg_buf != '\0' ? ", " : "",
+                power->name, yescode_str(flag));
+  }
+  *mbp = '\0';
+  if (*msg_buf) {
+    notify_format(exec, T("%s : %s."), object_header(exec, target),
+                  msg_buf);
+    notify_format(target, T("You have been empowered '%s' by %s."),
+                  msg_buf, object_header(target, exec));
+    do_log(LT_WIZ, exec, target, T("POWER SET: %s"), msg_buf);
+  }
+}
+
+/* can_give_power() {{{2 - check if can give power to said object */
+/* giver-> guy givin power, receiver-> guy reciving power, power->pointer to power,
+ * pow_lev-> powerscope level
+ * Return Values: 1->Yes, 
+ * No Vals:  0 -> Giver  doesn't have it
+ *         -1 -> Giver power conflict in lowering pow_lev
+ *         -2 -> Receiver can't receive it due to powergroup conflict
+ */
+int
+can_give_power(dbref giver, dbref receiver, POWER * power, int pow_lev)
+{
+  int g_level;
+  int r_level;
+
+  if (God(giver))               /* God is almighty */
+    return 1;
+
+  if (God(giver))
+    g_level = YES;
+  else
+    g_level = check_power_yescode(DPBITS(giver), power);
+  if (g_level < pow_lev) {
+    if (Inherit_Powers(giver))
+      return can_give_power(Owner(giver), receiver, power, pow_lev);
+
+    return 0;
+  }
+
+  /* They can only lower their powerscope if they're GTE */
+  if (God(receiver))
+    r_level = YES;
+  else
+    r_level = check_power_yescode(DPBITS(receiver), power);
+
+  if (!can_have_power(receiver, power, pow_lev))
+    return -2;
+
+  if (pow_lev < r_level && g_level < r_level) {
+    if (Inherit_Powers(giver)
+        && can_give_power(Owner(giver), receiver, power, pow_lev) == 1)
+      return 1;
+
+    return -1;
+  }
+
+  return 1;
+}
+
+/* can_have_power() {{{2 - check if object can have power at said plevel*/
+int
+can_have_power(dbref player, POWER * power, int level)
+{
+  struct power_group_list *pg_l;
+
+  if (God(player))
+    return 1;
+
+  for (pg_l = SDIV(player).powergroups; pg_l; pg_l = pg_l->next)
+    if (check_power_yescode(pg_l->power_group->max_powers, power) >= level)
+      return 1;
+  return 0;
+}
+
+/* can_receive_power_at() {{{2 - return what plevel object can have power at  */
+/* Returns what yescode player can receive 'power' at */
+int
+can_receive_power_at(dbref player, POWER * power)
+{
+  struct power_group_list *pg_l;
+  int ycode = NO;
+  int cur_ycode;
+
+  if (God(player))
+    return YES;
+
+  for (pg_l = SDIV(player).powergroups; pg_l; pg_l = pg_l->next) {
+    cur_ycode = check_power_yescode(pg_l->power_group->max_powers, power);
+    if (cur_ycode > ycode)
+      ycode = cur_ycode;
+  }
+  return ycode;
+}
+
+/* division_level() {{{2 */
+int
+division_level(dbref exec, dbref target, int level)
+{
+  POWER *power;
+  int plev, nl;
+
+  if (level < 1)
+    return 0;
+
+  power = find_power("Level");
+  if (!div_powover(exec, target, "Level")) {
+    if (Owner(target) == exec) {        /* if they own it.. let 'em have it */
+      if (!power->powc_chk(exec, target, YESLTE))
+        return 0;
+    } else
+      return 0;
+  }
+
+  plev = God(exec) ? YES : check_power_yescode(DPBITS(exec), power);
+
+  if (level < LEVEL(exec) && exec == target)    /* stupidity safeguard */
+    return 0;
+  else if (plev < YESLTE && exec == Owner(target))
+    plev = YESLTE;
+
+  /* First make sure they can be this 'level' in the 
+   * division, if not cap it down to max level. 
+   */
+  if (God(exec))
+    nl = level > MAX_LEVEL ? MAX_LEVEL : level;
+  else {
+    if (GoodObject(SDIV(target).object)) {
+      nl = (level >
+            LEVEL(SDIV(target).object) ? LEVEL(SDIV(target).
+                                               object) : level);
+    } else
+      nl = LEVEL_UNREGISTERED;  /* Not on the divtree? Only unregistered peeps! */
+  }
+
+  /* do proper checking for each rank level */
+  switch (plev) {
+  case YES:
+  case YESLTE:
+    if (nl > LEVEL(exec))
+      return 0;
+    break;
+  case YESLT:
+    if (nl >= LEVEL(exec))
+      return 0;
+    break;
+  case NO:
+    return 0;
+  }
+
+  do_log(LT_WIZ, exec, target, T("Level set to '%d'"), nl);
+
+  if (Typeof(target) == TYPE_PLAYER) {
+    SLEVEL(target) = nl;
+    adjust_levels(target, nl);  /* security for the owner. */
+  } else
+    SLEVEL(target) = nl;
+  return 1;
+}
+
+/* division_list_powerz() {{{2 - return string of powers on target */
+const char *
+division_list_powerz(dbref target, int flag)
+{
+  POWER *power;
+  char pname[BUFFER_LEN];
+  static char buf1[BUFFER_LEN], *tbp;
+  int yescode;
+
+  tbp = buf1;
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name)) {
+      yescode =
+          God(target) ? YES : check_power_yescode(DPBITS(target), power);
+      if (yescode > NO) {
+        if (tbp != buf1)
+          safe_chr(' ', buf1, &tbp);
+        safe_str(pname, buf1, &tbp);
+        if (flag) {
+          if (yescode < YES)
+            safe_format(buf1, &tbp, "(%s)", yescode_str(yescode));
+        } else
+          safe_format(buf1, &tbp, ":%d", yescode);
+
+      }
+    }
+  *tbp = '\0';
+  return buf1;
+}
+
+/* adjust_powers() {{{2 - adjust powers on 'obj' to what max 'to' can have them at */
+/* adjust powers to the level of 'to' */
+void
+adjust_powers(dbref obj, dbref to)
+{
+  POWER *power;
+  char pname[BUFFER_LEN];
+  int plev;
+
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name)) {
+      if ((God(obj) ? YES : check_power_yescode(DPBITS(obj), power)) >
+          (plev =
+           (God(to) ? YES : check_power_yescode(DPBITS(to), power)))) {
+        RESET_POWER(obj, power);
+        switch (plev) {
+        case YESLTE:
+          GIVE_DPBIT(obj, power->flag_lte);
+          break;
+        case YESLT:
+          GIVE_DPBIT(obj, power->flag_lt);
+          break;
+        }
+      }
+    }
+}
+
+/* adjust_levels() {{{2 - cap levels of 'owner's object to 'level'  */
+static void
+adjust_levels(dbref owner, int level)
+{
+  dbref cur_obj;
+  for (cur_obj = 0; cur_obj < db_top; cur_obj++)
+    if ((Owner(cur_obj) == owner) && !IsDivision(cur_obj))
+      if (SLEVEL(cur_obj) > level)
+        SLEVEL(cur_obj) = level;
+}
+
+/* adjust_divisions() {{{2 */
+static void
+adjust_divisions(dbref owner, dbref from_division, dbref to_division)
+{
+  int cur_obj;
+  struct power_group_list *pg_l;
+  struct power_group_list *next;
+
+  for (cur_obj = 0; cur_obj < db_top; cur_obj++) {
+    if ((Owner(cur_obj) == owner) && !IsDivision(cur_obj) && Division(cur_obj) == from_division) {
+      Division(cur_obj) = to_division;
+      for (pg_l = SDIV(cur_obj).powergroups; pg_l; pg_l = next) {
+        next = pg_l->next;
+        if (!can_have_pg(cur_obj, pg_l->power_group))
+          rem_pg_from_player(cur_obj, pg_l->power_group);
+      }
+    }
+  }
+}
+
+int
+can_have_pg(dbref object, POWERGROUP * pgrp)
+{
+  POWER *power;
+  char pname[BUFFER_LEN];
+  int deny = 0;
+
+  /* Check The Division of the object.. make sure they can have the powergroup
+   */
+
+  /* If they're a player, check to see if its the default powergroup */
+
+  if (IsPlayer(object) && !strcasecmp(pgrp->name, PLAYER_DEF_POWERGROUP))
+    return 1;
+
+
+  /* Otherwise If they have no division, they can't have any powergroups */
+
+  if (!GoodObject(SDIV(object).object))
+    return 0;
+
+  /* Check to see if they're division has the powergroup, if so grant it */
+
+  if (powergroup_has(SDIV(object).object, pgrp))
+    return 1;
+
+  /* Check to see if the division can obtain the powergroup, if so grant it */
+
+  if (DPBITS(SDIV(object).object))
+    for (deny = 1, power = ptab_firstentry_new(ps_tab.powers, pname);
+         power; power = ptab_nextentry_new(ps_tab.powers, pname))
+      if (!strcmp(pname, power->name)
+          && check_power_yescode(DPBITS(SDIV(object).object),
+                                 power) <
+          check_power_yescode(pgrp->max_powers, power)) {
+        deny = 0;
+        break;
+      }
+
+  return deny;
+}
+
+/* clear_division() {{{2 */
+void
+clear_division(dbref divi)
+{
+  int cur_obj;
+  struct power_group_list *pg_l;
+
+  for (cur_obj = 0; cur_obj < db_top; cur_obj++)
+    if ((SDIV(cur_obj).object == divi) && Typeof(cur_obj) == TYPE_DIVISION) {
+      /* If we run across a division pass it up the divtree */
+      SDIV(cur_obj).object = SDIV(divi).object;
+      Parent(cur_obj) = SDIV(divi).object;
+      if (GoodObject(Location(SDIV(divi).object)))
+        moveto(cur_obj, SDIV(divi).object);
+    } else if (SDIV(cur_obj).object == divi) {
+      notify_format(cur_obj, T("GAME: Division '%s' has been cleared."),
+                    object_header(cur_obj, divi));
+      SDIV(cur_obj).object = NOTHING;
+      if (Typeof(cur_obj) == TYPE_PLAYER && !God(cur_obj))      /* if player.. fix level & powerlevel */
+        division_level(1, cur_obj, 2);
+      /* Zap powergroups & powers as need be */
+      for (pg_l = SDIV(cur_obj).powergroups; pg_l;
+           pg_l = SDIV(cur_obj).powergroups)
+        rem_pg_from_player(cur_obj, pg_l->power_group);
+      if (DPBITS(cur_obj))
+        mush_free(DPBITS(cur_obj), "POWER_SPOT");
+      DPBITS(cur_obj) = NULL;
+    }
+}
+
+/* yescode_str() {{{2 - return string of integer ycode */
+char *
+yescode_str(int ycode)
+{
+  static char buf[8];
+  memset(buf, '\0', 8);
+  switch (ycode) {
+  case YES:
+    strncpy(buf, "Yes", 7);
+    break;
+  case YESLTE:
+    strncpy(buf, "YesLTE", 7);
+    break;
+  case YESLT:
+    strncpy(buf, "YesLT", 7);
+    break;
+  default:
+    strncpy(buf, "No", 7);
+  }
+  return buf;
+}
+
+/* yescode_i() {{{2 - return integer of string ycode */
+/* all possibile types of yescodes */
+int
+yescode_i(char *str)
+{
+  if (!strcasecmp(str, "YES"))
+    return YES;
+  else if (!strcasecmp(str, "YESLTE"))
+    return YESLTE;
+  else if (!strcasecmp(str, "YESLT"))
+    return YESLT;
+  else if (!strcasecmp(str, "LTE"))
+    return YESLTE;
+  else if (!strcasecmp(str, "LT"))
+    return YESLT;
+  else if (!strcmp(str, "1"))
+    return YESLT;
+  else if (!strcmp(str, "2"))
+    return YESLTE;
+  else if (!strcmp(str, "3"))
+    return YES;
+  else
+    return NO;
+}
+
+/* dpbit_match() {{{2 */
+int
+dpbit_match(div_pbits p1, div_pbits p2)
+{
+  int i;
+
+  if (!p1 || !p2)
+    return 0;
+
+  for (i = 0; i < (DP_BYTES * 8); i++)
+    if (DPBIT_ISSET(p1, i) && DPBIT_ISSET(p2, i))
+      return 1;
+  /* if p1 is bzero'd let 'em slide */
+  for (i = 0; i < DP_BYTES; i++)
+    if (p1[i] != 0)
+      return 0;
+  return 1;
+}
+
+/* string_to_dpbits() {{{2 */
+div_pbits
+string_to_dpbits(const char *powers)
+{
+  div_pbits powc;
+  POWER *power;
+  char *p_buff[BUFFER_LEN / 2];
+  char buff[BUFFER_LEN];
+  char *p, *t;
+  int power_count, power_indx, plevel;
+
+  /* create our spot */
+  powc = new_power_bitmask();
+  if (!powers || !powers[0])
+    return powc;
+  strcpy(buff, powers);
+  power_count = list2arr(p_buff, BUFFER_LEN / 2, buff, ' ');
+  if (!power_count)
+    return NULL;
+  for (power_indx = 0; power_indx < power_count; power_indx++) {
+    t = p_buff[power_indx];
+    if ((p = strchr(t, ':'))) {
+      *p++ = '\0';
+      plevel = yescode_i(p);
+    } else
+      plevel = YES;
+    power = find_power(t);
+    if (!power)
+      continue;
+    else {                      /* Set bits accordingly */
+      switch (plevel) {
+      case 3:
+        DPBIT_SET(powc, power->flag_yes);
+      case 2:
+        DPBIT_SET(powc, power->flag_lte);
+      case 1:
+        DPBIT_SET(powc, power->flag_lt);
+        break;
+      default:
+        /* Make sure nothing is set. */
+        RESET_POWER_DP(powc, power);
+        break;
+      }
+    }
+  }
+  return powc;
+}
+
+/* convert_object_powers() {{{2 - Convert old powers to new powers & guest power will assign them guest level
+ */
+
+void
+convert_object_powers(dbref dbnum, int opbts)
+{
+  POWER *power;
+  int i;
+
+  /* First Convert All their powerz to new powerz */
+
+  for (i = 0; pconv_ptab[i].Name != NULL; i++)
+    if (opbts & pconv_ptab[i].Op) {     /* give them the power in its new 'full yes' form */
+      power = find_power(pconv_ptab[i].Name);
+      GIVE_DPBIT(dbnum, power->flag_yes);
+    }
+
+  if (opbts & IS_GUEST) {
+    SLEVEL(dbnum) = -500;
+  }
+  if (has_flag_by_name(dbnum, "WIZARD", NOTYPE)) {
+    powergroup_db_set(NOTHING, dbnum, "WIZARD", 1);
+  } else if (has_flag_by_name(dbnum, "ROYALTY", NOTYPE)) {
+    powergroup_db_set(NOTHING, dbnum, "ROYALTY", 1);
+  }
+}
+
+/* decompile_powers() {{{2 - Do Division & Powers Decompilation */
+void
+decompile_powers(dbref player, dbref thing, const char *name)
+{
+
+  notify_format(player, "@level %s = %d", name, LEVEL(thing));
+  /* Add powergroups and extra division crapp to this */
+
+  if (DPBITS(thing) && power_is_zero(DPBITS(thing), DP_BYTES) != 0)
+    notify_format(player, "@empower %s = %s", name,
+                  division_list_powerz(thing, 0));
+}
+
+/* power_add() {{{2 - Add Power Allocation to power table */
+static void
+power_add(POWER * pow)
+{
+  ptab_start_inserts(ps_tab.powers);
+  ptab_insert(ps_tab.powers, pow->name, pow);
+  ptab_end_inserts(ps_tab.powers);
+
+  /* Set flag_lt cause it should be our biggest bit */
+  if (pow->flag_lt > ps_tab.powerbits) {
+    /* Crossed bite boundary.. make objects power spot bigger */
+    if ((pow->flag_lt >> 3) > (ps_tab.powerbits >> 3))
+      realloc_object_powers((pow->flag_lt >> 3) + 1);
+    ps_tab.powerbits = pow->flag_lt;
+  }
+
+  /* Make sure all these bits are set on bits_taken */
+  DPBIT_SET(ps_tab.bits_taken, pow->flag_yes);
+  DPBIT_SET(ps_tab.bits_taken, pow->flag_lte);
+  DPBIT_SET(ps_tab.bits_taken, pow->flag_lt);
+}
+
+/* realloc_object_powers() {{{2 - reallocate all objects in database powerbit allocation size */
+static void
+realloc_object_powers(int size)
+{
+  POWERGROUP *pgrp;
+  char pg_name[BUFFER_LEN];
+  int cur_obj;
+
+  ps_tab.bits_taken = (div_pbits) realloc(ps_tab.bits_taken, size);
+  *(ps_tab.bits_taken + size - 1) = 0;
+
+  for (cur_obj = 0; cur_obj < db_top; cur_obj++)
+    /* Check for */
+    if (DPBITS(cur_obj)) {
+      DPBITS(cur_obj) = (div_pbits) realloc(DPBITS(cur_obj), size);
+      *(DPBITS(cur_obj) + size - 1) = 0;
+    }
+  /*  Check Powergroups */
+  for (pgrp = ptab_firstentry_new(ps_tab.powergroups, pg_name); pgrp;
+       pgrp = ptab_nextentry_new(ps_tab.powergroups, pg_name)) {
+    if (pgrp->auto_powers) {
+      pgrp->auto_powers = (div_pbits) realloc(pgrp->auto_powers, size);
+      *(pgrp->auto_powers + size - 1) = 0;
+    }
+    if (pgrp->max_powers) {
+      pgrp->max_powers = (div_pbits) realloc(pgrp->max_powers, size);
+      *(pgrp->max_powers + size - 1) = 0;
+    }
+  }
+}
+
+/* new_power_bitmask() {{{2 - create new power bitmask allocation */
+div_pbits
+new_power_bitmask()
+{
+  div_pbits power_spot;
+
+  power_spot = (div_pbits) mush_malloc(DP_BYTES, "POWER_SPOT");
+  if (!power_spot)
+    mush_panic("DPBYTES Power_Spot couldn't allocate!");
+
+  DPBIT_ZERO(power_spot);
+  return power_spot;
+}
+
+/* new_power() {{{2 - create new power allocation */
+static POWER *
+new_power(void)
+{
+  POWER *p = (POWER *) mush_malloc(sizeof(POWER), "power");
+  if (!p)
+    mush_panic("Unable to allocate memory for a new flag!\n");
+  return p;
+}
+
+/* has_power() {{{2 - This function checks if a player has a power at all. 
+ * Returns the power pointer if so
+ */
+POWER *
+has_power(dbref object, const char *name)
+{
+  POWER *power;
+
+  power = find_power(name);
+
+  if (God(object))
+    return power;
+
+  if (check_power_yescode(DPBITS(object), power) > NO)
+    return power;
+  else
+    return NULL;
+}
+
+/* power_is_zero() {{{2 - check to make sure there are no powers set. */
+char
+power_is_zero(div_pbits pbits, int bytes)
+{
+  int spot = 0;
+
+  while (spot < bytes) {
+    if (pbits[spot] != 0)
+      return pbits[spot];
+    spot++;
+  }
+  return 0;
+}
+
+/* add_power_type() {{{2 - sugar coated routine to add a power */
+POWER *
+add_power_type(const char *name, const char *type)
+{
+  POWER *power;
+  int lastbit;
+  int PowType_i;
+
+  /* Make sure its not already there */
+  if ((power = find_power(name)) != NULL)
+    return power;
+  lastbit = new_power_bit(0);
+
+  for (PowType_i = 0; powc_list[PowType_i].name; PowType_i++)
+    if (!strcmp(type, powc_list[PowType_i].name))
+      break;
+  if (!powc_list[PowType_i].name)
+    return NULL;
+  power = new_power();
+  power->name = mush_strdup(name, "power name");
+  power->powc_chk = powc_list[PowType_i].powc_chk;
+  /* Figure out Our Yes/Lte/Lt Values */
+  power->flag_yes = lastbit;
+  if (powc_list[PowType_i].flag_lte != powc_list[PowType_i].flag_yes)
+    lastbit = new_power_bit(lastbit);
+  power->flag_lte = lastbit;
+  if (powc_list[PowType_i].flag_lt != powc_list[PowType_i].flag_lte)
+    lastbit = new_power_bit(lastbit);
+  power->flag_lt = lastbit;
+
+  power_add(power);
+
+  return power;
+}
+
+/* new_power_bit() {{{2 - Find the next available free bit to use */
+static int
+new_power_bit(int bit)
+{
+  int c;
+
+  /* A real simple way out.. */
+  if (bit >= ps_tab.powerbits)
+    return bit + 1;
+
+  c = bit == 0 ? 1 : bit;
+
+  if (c <= ps_tab.powerbits)
+    c++;
+  while (c <= ps_tab.powerbits) {
+    if (!DPBIT_ISSET(ps_tab.bits_taken, c)) {
+      return c;
+    }
+    c++;
+  }
+  return c;
+}
+
+/* clear_all_powers() {{{2 */
+static void
+clear_all_powers()
+{
+  POWER *power;
+  char pname[BUFFER_LEN];
+
+  if (ps_tab.powers) {
+    for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+         power = ptab_nextentry_new(ps_tab.powers, pname))
+      if (!strcmp(pname, power->name)) {
+        mush_free((Malloc_t) power->name, "power name");
+        mush_free(power, "POWER_SPOT");
+        return;
+      }
+    ptab_free(ps_tab.powers);
+  }
+  ps_tab.powerbits = 0;
+}
+
+/* power_delete() {{{2 */
+/* 1 Success, 0 Power Didn't Exist */
+static int
+power_delete(const char *key)
+{
+  POWERGROUP *pgrp;
+  POWER *power, *alias;
+  char pname[BUFFER_LEN];
+  int i;
+
+  power = find_power(key);
+
+  if (!power)
+    return 0;
+
+  /* Step 1, Remove Aliases */
+  for (alias = ptab_firstentry_new(ps_tab.powers, pname); alias;
+       alias = ptab_nextentry_new(ps_tab.powers, pname))
+    if (strcmp(pname, alias->name))
+      ptab_delete(ps_tab.powers, pname);
+
+  /* Step 2, Unset bits on bits_taken */
+  RESET_POWER_DP(ps_tab.bits_taken, power);
+
+  /* Step 3, Unset power on every object in db */
+  for (i = 0; i < db_top; i++) {
+    RESET_POWER(i, power);
+  }
+
+  /* Step 4: Unset power on every powergroup */
+  for (pgrp = ptab_firstentry_new(ps_tab.powergroups, pname); pgrp;
+       pgrp = ptab_nextentry_new(ps_tab.powergroups, pname)) {
+    RESET_POWER_DP(pgrp->auto_powers, power);
+    RESET_POWER_DP(pgrp->max_powers, power);
+  }
+
+
+  /* Step 5, Remove Power */
+  ptab_delete(ps_tab.powers, power->name);
+  mush_free((Malloc_t) power->name, "power name");
+  mush_free(power, "POWER_SPOT");
+
+  return 1;
+}
+
+/* power_add_additional() {{{2 - Add pre-defined powers */
+static void
+power_add_additional(void)
+{
+  POWERGROUP *pgrp;
+  POWER *power;
+  int i;
+
+  for (i = 0; new_power_list[i].name; i++)
+    (void) add_power_type(new_power_list[i].name, new_power_list[i].type);
+
+  for (i = 0; power_alias_tab[i].alias; i++) {
+    /* First Make sure the alias doesn't exist in there */
+    power = find_power(power_alias_tab[i].alias);
+    if (power)
+      continue;
+    /* Now make sure the power actually exists */
+    power = find_power(power_alias_tab[i].realname);
+    if (!power)
+      continue;
+    ptab_start_inserts(ps_tab.powers);
+    ptab_insert(ps_tab.powers, power_alias_tab[i].alias, power);
+    ptab_end_inserts(ps_tab.powers);
+  }
+
+  /* Load pre-defined powergroups as well */
+  for (i = 0; predefined_powergroups[i].name; i++)
+    if ((pgrp = powergroup_add(predefined_powergroups[i].name))) {
+      powergroup_empower(NOTHING, pgrp,
+                         predefined_powergroups[i].max_powers, 0);
+      powergroup_empower(NOTHING, pgrp,
+                         predefined_powergroups[i].auto_powers, 1);
+    }
+
+
+}
+
+/* convert_old_cobra_powers() {{{2 */
+div_pbits
+convert_old_cobra_powers(unsigned char *dp_bytes)
+{
+  POWER *power;
+  div_pbits pbits;
+  int pcheck;
+  int i;
+
+  pbits = NULL;
+
+  for (i = 0; old_cobra_conv_t[i].name; i++) {
+    power = find_power(old_cobra_conv_t[i].name);
+    if (!power)
+      continue;
+    pcheck = check_old_power_ycode(i, dp_bytes);
+    if (pcheck > NO && pbits == NULL)
+      pbits = new_power_bitmask();
+    switch (pcheck) {
+    case YES:
+      DPBIT_SET(pbits, power->flag_yes);
+      break;
+    case YESLTE:
+      DPBIT_SET(pbits, power->flag_lte);
+      break;
+    case YESLT:
+      DPBIT_SET(pbits, power->flag_lt);
+      break;
+    }
+  }
+  return pbits;
+}
+
+/* power_goodname() {{{2 -  check if its a good power name
+ * 1 - good power
+ * 0 - provide flag name
+ * -1 - be longer than one char
+ * -2 - no spaces
+ */
+int
+power_goodname(char *str)
+{
+  if (!str || !*str)
+    return 0;
+  if (strlen(str) == 1)
+    return -1;
+  if (strchr(str, ' '))
+    return -2;
+  return 1;
+}
+
+/* check_old_power_ycode() {{{2 */
+/* Return Yescode Appropriate with old power */
+static int
+check_old_power_ycode(int pnum, unsigned char *dp_bytes)
+{
+  if (DPBIT_ISSET(dp_bytes, old_cobra_conv_t[pnum].flag_yes))
+    return YES;
+  else if (DPBIT_ISSET(dp_bytes, old_cobra_conv_t[pnum].flag_lte))
+    return YESLTE;
+  else if (DPBIT_ISSET(dp_bytes, old_cobra_conv_t[pnum].flag_lt))
+    return YESLT;
+  return NO;
+}
+
+/* add_to_div_exit_path() {{{2 add division to @sd exit path */
+void add_to_div_exit_path(dbref player, dbref div_obj) {
+  ATTR *divrcd;
+  char *tp_buf[BUFFER_LEN / 2], *tbp, tbuf[BUFFER_LEN];
+  int cnt, i;
+
+  divrcd = (ATTR *) atr_get(player, (const char *) "XYXX_DIVRCD");
+  tbp = tbuf;
+
+  if(divrcd == NULL) {
+    /* simple one, just add a one liner to this guy */
+    atr_add(player, "XYXX_DIVRCD", unparse_number(div_obj), GOD, NOTHING);
+  } else {
+    /* Ok.. First we're gonna have to list2arr to get our boys */
+    cnt = list2arr(tp_buf, BUFFER_LEN / 2 , (char *) safe_atr_value(divrcd), ' '); 
+    /* Now lets put us back into the list and set it back on the guy */
+    safe_str(tp_buf[0], tbuf, &tbp);
+    for(i = 1; i < cnt ; i++) {
+      safe_chr(' ', tbuf, &tbp);
+      safe_str(tp_buf[i], tbuf, &tbp);
+    }
+    /* Tac the new one on there */
+    safe_chr(' ', tbuf, &tbp);
+    safe_str(unparse_number(div_obj), tbuf, &tbp);
+    *tbp = '\0';
+    (void) atr_add(player, "XYXX_DIVRCD", tbuf, GOD, NOTHING);
+  }
+
+}
+
+/* do_list_powers() {{{2 list all powers */
+void do_list_powers(dbref player, const char *name) {
+  POWER *power;
+  char tbuf[BUFFER_LEN], pname[BUFFER_LEN];
+  char *tbp;
+
+  tbp = tbuf;
+  *tbp = '\0';
+    /* List Powers */
+    for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+         power = ptab_nextentry_new(ps_tab.powers, pname))
+      if (!strcmp(power->name, pname) && (!name || !*name || quick_wild(name, power->name))) {
+        if (!*tbuf)
+          safe_str(pname, tbuf, &tbp);
+        else
+          safe_format(tbuf, &tbp, ", %s", pname);
+      }
+    *tbp = '\0';
+    notify_format(player, "Powers List: %s", tbuf ? tbuf : "None.");
+}
+
+char *list_all_powers(dbref player, const char *name) {
+  POWER *power;
+  static char buff[BUFFER_LEN];
+  char pname[BUFFER_LEN];
+  char *bp;
+
+  bp = buff;
+
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname)) {
+    if (!strcmp(power->name, pname)
+        && (!name || !*name || quick_wild(name, power->name))) {
+      if (bp != buff)
+        safe_str(", ", buff, &bp);
+      safe_str(pname, buff, &bp);
+    }
+  }
+
+  *bp = '\0';
+
+  return buff;
+}
+
+
+/* Vim Folds Hint
+ * vim:ts=18  fdm=marker 
+ */
diff --git a/src/extchat.c b/src/extchat.c
new file mode 100644 (file)
index 0000000..2b20fce
--- /dev/null
@@ -0,0 +1,3671 @@
+/**
+ * \file extchat.c
+ *
+ * \brief The PennMUSH chat system
+ *
+ *
+ */
+#include "copyrite.h"
+#include "config.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <stdarg.h>
+#include "conf.h"
+#include "externs.h"
+#include "attrib.h"
+#include "mushdb.h"
+#include "match.h"
+#include "flags.h"
+#include "extchat.h"
+#include "ansi.h"
+#include "privtab.h"
+#include "mymalloc.h"
+#include "pueblo.h"
+#include "parse.h"
+#include "lock.h"
+#include "log.h"
+#include "game.h"
+#include "dbdefs.h"
+#include "function.h"
+#include "command.h"
+#include "dbio.h"
+#include "confmagic.h"
+
+#ifdef CHAT_SYSTEM
+
+static struct {
+  unsigned oldchanflags, newchanflags;
+} penn_conversion_table[] =
+{
+  {0x800, CHANNEL_INTERACT},
+  {0, 0}
+};
+
+static struct {
+  int dbflags;
+  unsigned oldchanflags, newchanflags;
+} dbflag_conversion_table[] =
+{
+  {0, 0, 0}
+};
+
+extern jmp_buf db_err;
+
+/** Evaluate a function and jump on error. */
+#define OUTPUT(fun) do { if ((fun) < 0) longjmp(db_err, 1); } while (0)
+#define chan_cmd_match(x,y,z)  (void)atr_comm_match(x, y, '$', ':', z, 0, NULL, NULL, NULL)
+
+static CHAN *new_channel(void);
+static CHANLIST *new_chanlist(void);
+static CHANUSER *new_user(dbref who);
+static char *nv_eval(dbref thing, const char *code);
+static void do_set_cobj _((dbref player, const char *name, const char *obj));
+static void do_reset_cobj _((dbref player, const char *name));
+static void free_channel(CHAN *c);
+static void free_chanlist(CHANLIST *cl);
+static void free_user(CHANUSER *u);
+static int load_chatdb_oldstyle(FILE *fp);
+static int load_channel(FILE * fp, CHAN *ch);
+static int load_chanusers(FILE * fp, CHAN *ch);
+static int load_labeled_channel(FILE *fp, CHAN *ch, int iscobra, int dbflags);
+static int load_labeled_chanusers(FILE *fp, CHAN *ch);
+static void insert_channel(CHAN **ch);
+static void remove_channel(CHAN *ch);
+static void insert_obj_chan(dbref who, CHAN **ch);
+static void remove_obj_chan(dbref who, CHAN *ch);
+void remove_all_obj_chan(dbref thing);
+static void chan_chown(CHAN *c, dbref victim);
+void chan_chownall(dbref old, dbref new);
+static int insert_user(CHANUSER *user, CHAN *ch);
+static int remove_user(CHANUSER *u, CHAN *ch);
+static int save_channel(FILE * fp, CHAN *ch);
+static int save_chanuser(FILE * fp, CHANUSER *user);
+static void channel_wipe(dbref player, CHAN *chan);
+static int yesno(const char *str);
+static int canstilladd(dbref player);
+static enum cmatch_type find_channel_partial_on(const char *name, CHAN **chan,
+                                               dbref player);
+static enum cmatch_type find_channel_partial_off(const char *name, CHAN **chan,
+                                                dbref player);
+static char *list_cflags(CHAN *c);
+static char *list_cuflags(CHANUSER *u);
+static void channel_join_self(dbref player, const char *name);
+static void channel_leave_self(dbref player, const char *name);
+static void do_channel_who(dbref player, CHAN *chan);
+void chat_player_announce(dbref player, char *msg, int ungag);
+static int ok_channel_name(const char *n);
+static void format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim,
+                                    int flags, const char *msg,
+                                    const char *extra);
+static void list_partial_matches(dbref player, const char *name,
+                                enum chan_match_type type);
+
+const char *chan_speak_lock = "ChanSpeakLock"; /**< Name of speak lock */
+const char *chan_join_lock = "ChanJoinLock";   /**< Name of join lock */
+const char *chan_mod_lock = "ChanModLock";     /**< Name of modify lock */
+const char *chan_see_lock = "ChanSeeLock";     /**< Name of see lock */
+const char *chan_hide_lock = "ChanHideLock";   /**< Name of hide lock */
+
+#define CYES 1   /**< An affirmative. */
+#define CNO 0    /**< A negative. */
+#define ERR -1   /**< An error. Clever, eh? */
+
+/** Wrapper for insert_user() that generates a new CHANUSER and inserts it */
+#define insert_user_by_dbref(who,chan) \
+        insert_user(new_user(who),chan)
+/** Wrapper for remove_user() that searches for the CHANUSER to remove */
+#define remove_user_by_dbref(who,chan) \
+        remove_user(onchannel(who,chan),chan)
+#define OnChannel(who,chan) ((ChanObj(chan) == who) || onchannel(who,chan))
+
+int num_channels;  /**< Number of channels defined */
+
+CHAN *channels;           /**< Pointer to channel list */
+
+static PRIV priv_table[] = {
+  {"Disabled", 'D', CHANNEL_DISABLED, CHANNEL_DISABLED},
+  {"Admin", 'A', CHANNEL_ADMIN | CHANNEL_PLAYER, CHANNEL_ADMIN},
+  {"Director", 'W', CHANNEL_DIRECTOR | CHANNEL_PLAYER, CHANNEL_DIRECTOR},
+  {"Player", 'P', CHANNEL_PLAYER, CHANNEL_PLAYER},
+  {"Object", 'O', CHANNEL_OBJECT, CHANNEL_OBJECT},
+  {"Quiet", 'Q', CHANNEL_QUIET, CHANNEL_QUIET},
+  {"Open", 'o', CHANNEL_OPEN, CHANNEL_OPEN},
+  {"Hide_Ok", 'H', CHANNEL_CANHIDE, CHANNEL_CANHIDE},
+  {"NoTitles", 'T', CHANNEL_NOTITLES, CHANNEL_NOTITLES},
+  {"NoNames", 'N', CHANNEL_NONAMES, CHANNEL_NONAMES},
+  {"NoCemit", 'C', CHANNEL_NOCEMIT, CHANNEL_NOCEMIT},
+  {"Interact", 'I', CHANNEL_INTERACT, CHANNEL_INTERACT},
+  {"ChanObj", 'Z', CHANNEL_COBJ, CHANNEL_COBJ},
+  {NULL, '\0', 0, 0}
+};
+
+
+/** Get a player's CHANUSER entry if they're on a channel.
+ * This function checks to see if a given player is on a given channel.
+ * If so, it returns a pointer to their CHANUSER structure. If not,
+ * returns NULL.
+ * \param who player to test channel membership of.
+ * \param ch pointer to channel to test membership on.
+ * \return player's CHANUSER entry on the channel, or NULL.
+ */
+CHANUSER *
+onchannel(dbref who, CHAN *ch)
+{
+  static CHANUSER *u;
+  for (u = ChanUsers(ch); u; u = u->next) {
+    if (CUdbref(u) == who) {
+      return u;
+    }
+  }
+  return NULL;
+}
+
+/** A macro to test if a channel exists and, if not, to notify. */
+#define test_channel(player,name,chan) \
+   do { \
+    chan = NULL; \
+    switch (find_channel(name,&chan,player)) { \
+    case CMATCH_NONE: \
+      notify(player, T ("CHAT: I don't recognize that channel.")); \
+      return; \
+    case CMATCH_AMBIG: \
+      notify(player, T("CHAT: I don't know which channel you mean.")); \
+      list_partial_matches(player, name, PMATCH_ALL); \
+      return; \
+    default: \
+      break; \
+     } \
+    } while (0)
+
+/*----------------------------------------------------------
+ * Loading and saving the chatdb
+ * The chatdb's format is pretty straightforward
+ * Return 1 on success, 0 on failure
+ */
+
+/** Initialize the chat database .*/
+void
+init_chatdb(void)
+{
+  num_channels = 0;
+  channels = NULL;
+}
+
+/** Load the chat database from a file.
+ * \param fp pointer to file to read from.
+ * \retval 1 success
+ * \retval 0 failure
+ */
+static int
+load_chatdb_oldstyle(FILE * fp)
+{
+  int i;
+  CHAN *ch;
+  char buff[20];
+
+  /* How many channels? */
+  num_channels = getref(fp);
+  if (num_channels > MAX_CHANNELS)
+    return 0;
+
+  /* Load all channels */
+  for (i = 0; i < num_channels; i++) {
+    if (feof(fp))
+      break;
+    ch = new_channel();
+    if (!ch)
+      return 0;
+    if (!load_channel(fp, ch)) {
+      do_rawlog(LT_ERR, T("Unable to load channel %d."), i);
+      free_channel(ch);
+      return 0;
+    }
+    insert_channel(&ch);
+  }
+  num_channels = i;
+
+  /* Check for **END OF DUMP*** */
+  fgets(buff, sizeof buff, fp);
+  if (!buff)
+    do_rawlog(LT_ERR, T("CHAT: No end-of-dump marker in the chat database."));
+  else if (strcmp(buff, EOD) != 0)
+    do_rawlog(LT_ERR, T("CHAT: Trailing garbage in the chat database."));
+
+  return 1;
+}
+
+extern char db_timestamp[];
+
+/** Load the chat database from a file.
+ * \param fp pointer to file to read from.
+ * \retval 1 success
+ * \retval 0 failure
+ */
+int
+load_chatdb(FILE * fp)
+{
+  int i, flags, iscobra;
+  CHAN *ch;
+  char buff[20];
+  char *chat_timestamp;
+
+  i = fgetc(fp);
+  if (i == EOF) {
+    do_rawlog(LT_ERR, T("CHAT: Invalid database format!"));
+    longjmp(db_err, 1);
+  } else if (i != '+') {
+    ungetc(i, fp);
+    return load_chatdb_oldstyle(fp);
+  }
+
+  i = fgetc(fp);
+
+  if (i == 'F') {
+    iscobra = 1;
+  } else if (i == 'V') {
+    iscobra = 0;
+  } else {
+    do_rawlog(LT_ERR, T("CHAT: Invalid database format!"));
+    longjmp(db_err, 1);
+  }
+
+  flags = getref(fp);
+
+  db_read_this_labeled_string(fp, "savedtime", &chat_timestamp);
+
+  if (strcmp(chat_timestamp, db_timestamp))
+    do_rawlog(LT_ERR,
+             T
+             ("CHAT: warning: chatdb and game db were saved at different times!"));
+
+  /* How many channels? */
+  db_read_this_labeled_number(fp, "channels", &num_channels);
+  if (num_channels > MAX_CHANNELS)
+    return 0;
+
+  /* Load all channels */
+  for (i = 0; i < num_channels; i++) {
+    ch = new_channel();
+    if (!ch)
+      return 0;
+    if (!load_labeled_channel(fp, ch, iscobra, flags)) {
+      do_rawlog(LT_ERR, T("Unable to load channel %d."), i);
+      free_channel(ch);
+      return 0;
+    }
+    insert_channel(&ch);
+  }
+  num_channels = i;
+
+  /* Check for **END OF DUMP*** */
+  fgets(buff, sizeof buff, fp);
+  if (!buff)
+    do_rawlog(LT_ERR, T("CHAT: No end-of-dump marker in the chat database."));
+  else if (strcmp(buff, EOD) != 0)
+    do_rawlog(LT_ERR, T("CHAT: Trailing garbage in the chat database."));
+
+  return 1;
+}
+
+
+/* Malloc memory for a new channel, and initialize it */
+static CHAN *
+new_channel(void)
+{
+  CHAN *ch;
+
+  ch = (CHAN *) mush_malloc(sizeof(CHAN), "CHAN");
+  if (!ch)
+    return NULL;
+  ch->name[0] = '\0';
+  ch->title[0] = '\0';
+  ChanType(ch) = CHANNEL_DEFAULT_FLAGS;
+  ChanCreator(ch) = NOTHING;
+  ChanObj(ch) = NOTHING;
+  ChanCost(ch) = CHANNEL_COST;
+  ChanNext(ch) = NULL;
+  ChanNumMsgs(ch) = 0;
+  /* By default channels are public but mod-lock'd to the creator */
+  ChanJoinLock(ch) = TRUE_BOOLEXP;
+  ChanSpeakLock(ch) = TRUE_BOOLEXP;
+  ChanSeeLock(ch) = TRUE_BOOLEXP;
+  ChanHideLock(ch) = TRUE_BOOLEXP;
+  ChanModLock(ch) = TRUE_BOOLEXP;
+  ChanNumUsers(ch) = 0;
+  ChanMaxUsers(ch) = 0;
+  ChanUsers(ch) = NULL;
+  ChanBufferQ(ch) = NULL;
+  return ch;
+}
+
+
+
+/* Malloc memory for a new user, and initialize it */
+static CHANUSER *
+new_user(dbref who)
+{
+  CHANUSER *u;
+  u = (CHANUSER *) mush_malloc(sizeof(CHANUSER), "CHANUSER");
+  if (!u)
+    mush_panic("Couldn't allocate memory in new_user in extchat.c");
+  CUdbref(u) = who;
+  CUtype(u) = CU_DEFAULT_FLAGS;
+  u->title[0] = '\0';
+  CUnext(u) = NULL;
+  return u;
+}
+
+/* Free memory from a channel */
+static void
+free_channel(CHAN *c)
+{
+  CHANUSER *u, *unext;
+  if (!c)
+    return;
+  free_boolexp(ChanJoinLock(c));
+  free_boolexp(ChanSpeakLock(c));
+  free_boolexp(ChanHideLock(c));
+  free_boolexp(ChanSeeLock(c));
+  free_boolexp(ChanModLock(c));
+  u = ChanUsers(c);
+  while (u) {
+    unext = u->next;
+    free_user(u);
+    u = unext;
+  }
+  return;
+}
+
+/* Free memory from a channel user */
+static void
+free_user(CHANUSER *u)
+{
+  if (u)
+    mush_free(u, "CHANUSER");
+}
+
+/* Load in a single channel into position i. Return 1 if
+ * successful, 0 otherwise.
+ */
+static int
+load_channel(FILE * fp, CHAN *ch)
+{
+  strcpy(ChanName(ch), getstring_noalloc(fp));
+  if (feof(fp))
+    return 0;
+  strcpy(ChanTitle(ch), getstring_noalloc(fp));
+  ChanType(ch) = getref(fp);
+  ChanCreator(ch) = getref(fp);
+  ChanObj(ch) = NOTHING;
+  ChanCost(ch) = getref(fp);
+  ChanNumMsgs(ch) = 0;
+  ChanJoinLock(ch) = getboolexp(fp, chan_join_lock);
+  ChanSpeakLock(ch) = getboolexp(fp, chan_speak_lock);
+  ChanModLock(ch) = getboolexp(fp, chan_mod_lock);
+  ChanSeeLock(ch) = getboolexp(fp, chan_see_lock);
+  ChanHideLock(ch) = getboolexp(fp, chan_hide_lock);
+  ChanNumUsers(ch) = getref(fp);
+  ChanMaxUsers(ch) = ChanNumUsers(ch);
+  ChanUsers(ch) = NULL;
+  if (ChanNumUsers(ch) > 0)
+    ChanNumUsers(ch) = load_chanusers(fp, ch);
+  return 1;
+}
+
+/* Load in a single channel into position i. Return 1 if
+ * successful, 0 otherwise.
+ */
+static int
+load_labeled_channel(FILE * fp, CHAN *ch, int iscobra, int dbflags)
+{
+  char *label, *value, c;
+  int i;
+
+  enum known_labels { LBL_NAME, LBL_DESC, LBL_CFLAGS,LBL_CREATOR, LBL_COST, 
+    LBL_LOCK, LBL_USERS, LBL_COBJ, LBL_JOIN, LBL_SPEAK, LBL_MODIFY, LBL_SEE, 
+    LBL_HIDE, LBL_ERROR };
+  struct label_table {
+    const char *label;
+    enum known_labels tag;
+  };
+  struct subfield_t {
+    const char *subfield;
+    enum known_labels tag;
+    enum known_labels stag;
+  };
+  struct label_table field[] = {
+    {"name", LBL_NAME},
+    {"description", LBL_DESC},
+    {"flags", LBL_CFLAGS},
+    {"creator", LBL_CREATOR},
+    {"cost", LBL_COST},
+    {"lock", LBL_LOCK},
+    {"users", LBL_USERS},
+    {"cobj", LBL_COBJ},
+    {NULL, LBL_ERROR}
+  }, *entry;
+  struct subfield_t subfield[] = {
+    {"join", LBL_LOCK, LBL_JOIN},
+    {"speak", LBL_LOCK, LBL_SPEAK},
+    {"modify", LBL_LOCK, LBL_MODIFY},
+    {"see", LBL_LOCK, LBL_SEE},
+    {"hide", LBL_LOCK, LBL_HIDE},
+    {NULL, LBL_ERROR}
+  }, *sfptr;
+  enum known_labels the_label, the_subfield;
+
+  ChanNumMsgs(ch) = 0;
+  while(1) {
+    c = fgetc(fp);
+    ungetc(c, fp);
+    if(c == '*') /* End of Dump Checking */
+      break;
+    db_read_labeled_string(fp, &label, &value);
+    the_label = LBL_ERROR;
+    for(entry = field; entry->label; entry++)
+      if(!strcasecmp(entry->label, label)) {
+       the_label = entry->tag;
+       break;
+      }
+    switch(the_label) {
+      case LBL_NAME:
+       strcpy(ChanName(ch), value);
+       break;
+      case LBL_DESC:
+       strcpy(ChanTitle(ch), value);
+       break;
+      case LBL_CFLAGS:
+       ChanType(ch) = parse_integer(value);
+
+        if (!iscobra)
+          for (i = 0; penn_conversion_table[i].oldchanflags; i++)
+            if ((ChanType(ch) & penn_conversion_table[i].oldchanflags)
+               == penn_conversion_table[i].oldchanflags)
+              ChanType(ch) = (ChanType(ch)
+                               & ~penn_conversion_table[i].oldchanflags)
+                               | penn_conversion_table[i].newchanflags;
+
+        for (i = 0; dbflag_conversion_table[i].dbflags; i++)
+          if (!(dbflags & dbflag_conversion_table[i].dbflags))
+            if ((ChanType(ch) & dbflag_conversion_table[i].oldchanflags)
+               == dbflag_conversion_table[i].oldchanflags)
+              ChanType(ch) = (ChanType(ch)
+                               & ~dbflag_conversion_table[i].oldchanflags)
+                               | dbflag_conversion_table[i].newchanflags;
+
+       break;
+      case LBL_CREATOR:
+       ChanCreator(ch) = qparse_dbref(value);
+       break;
+      case LBL_COBJ:
+       ChanObj(ch) = qparse_dbref(value);
+       break;
+      case LBL_COST:
+       ChanCost(ch) = parse_integer(value);
+       break;
+      case LBL_LOCK:
+       goto subfield_checks;
+       break;
+      case LBL_USERS:
+       ChanMaxUsers(ch) = ChanNumUsers(ch) = parse_integer(value);
+       ChanUsers(ch) = NULL;
+       if(ChanNumUsers(ch) > 0)
+         ChanNumUsers(ch) = load_labeled_chanusers(fp, ch);
+       return 1;
+       break;
+      case LBL_ERROR:
+      default:
+       do_rawlog(LT_ERR, T("Unrecgonized field '%s' in chatdatabase"), label);
+       return 0;
+    };
+    continue;
+subfield_checks:
+    for( sfptr = subfield; sfptr->subfield; sfptr++)
+      if((entry->tag == sfptr->tag) && !strcasecmp(sfptr->subfield, value) ) {
+       the_subfield = sfptr->stag;
+       break;
+      }
+    switch(the_subfield) {
+      case LBL_SPEAK:
+       ChanSpeakLock(ch) = getboolexp(fp, chan_speak_lock);
+       break;
+      case LBL_JOIN:
+       ChanJoinLock(ch) = getboolexp(fp, chan_join_lock);
+       break;
+      case LBL_MODIFY:
+       ChanModLock(ch) = getboolexp(fp, chan_mod_lock);
+       break;
+      case LBL_SEE:
+       ChanSeeLock(ch) = getboolexp(fp, chan_see_lock);
+       break;
+      case LBL_HIDE:
+       ChanHideLock(ch) = getboolexp(fp, chan_hide_lock);
+       break;
+      case LBL_ERROR:
+      default:
+       do_rawlog(LT_ERR, T("Unrecgonized lock subfield '%s' in chat database"), value);
+       break;
+    };
+  }
+  return 1;
+}
+
+
+/* Load the *channel's user list. Return number of users on success, or 0 */
+static int
+load_chanusers(FILE * fp, CHAN *ch)
+{
+  int i, num = 0;
+  CHANUSER *user;
+  dbref player;
+  for (i = 0; i < ChanNumUsers(ch); i++) {
+    player = getref(fp);
+    /* Don't bother if the player isn't a valid dbref or the wrong type */
+    if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
+      user = new_user(player);
+      CUtype(user) = getref(fp);
+      strcpy(CUtitle(user), getstring_noalloc(fp));
+      CUnext(user) = NULL;
+      if (insert_user(user, ch))
+       num++;
+    } else {
+      /* But be sure to read (and discard) the player's info */
+      do_log(LT_ERR, 0, 0, T("Bad object #%d removed from channel %s"),
+            player, ChanName(ch));
+      (void) getref(fp);
+      (void) getstring_noalloc(fp);
+    }
+  }
+  return num;
+}
+
+/* Load the *channel's user list. Return number of users on success, or 0 */
+static int
+load_labeled_chanusers(FILE * fp, CHAN *ch)
+{
+  int i, num = 0, n;
+  char *tmp;
+  CHANUSER *user;
+  dbref player;
+  for (i = 0; i < ChanNumUsers(ch); i++) {
+    db_read_this_labeled_dbref(fp, "dbref", &player);
+    /* Don't bother if the player isn't a valid dbref or the wrong type */
+    if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
+      user = new_user(player);
+      db_read_this_labeled_number(fp, "flags", &n);
+      CUtype(user) = n;
+      db_read_this_labeled_string(fp, "title", &tmp);
+      strcpy(CUtitle(user), tmp);
+      CUnext(user) = NULL;
+      if (insert_user(user, ch))
+       num++;
+    } else {
+      /* But be sure to read (and discard) the player's info */
+      do_log(LT_ERR, 0, 0, T("Bad object #%d removed from channel %s"),
+            player, ChanName(ch));
+      db_read_this_labeled_number(fp, "flags", &n);
+      db_read_this_labeled_string(fp, "title", &tmp);
+    }
+  }
+  return num;
+}
+
+
+/* Insert the channel onto the list of channels, sorted by name */
+static void
+insert_channel(CHAN **ch)
+{
+  CHAN *p;
+  char cleanname[CHAN_NAME_LEN];
+  char cleanp[CHAN_NAME_LEN];
+
+  if (!ch || !*ch)
+    return;
+
+  /* If there's no users on the list, or if the first user is already
+   * alphabetically greater, user should be the first entry on the list */
+  /* No channels? */
+  if (!channels) {
+    channels = *ch;
+    channels->next = NULL;
+    return;
+  }
+  p = channels;
+  /* First channel? */
+  strcpy(cleanp, remove_markup(ChanName(p), NULL));
+  strcpy(cleanname, remove_markup(ChanName(*ch), NULL));
+  if (strcasecoll(cleanp, cleanname) > 0) {
+    channels = *ch;
+    channels->next = p;
+    return;
+  }
+  /* Otherwise, find which user this user should be inserted after */
+  while (p->next) {
+    strcpy(cleanp, remove_markup(ChanName(p->next), NULL));
+    if (strcasecoll(cleanp, cleanname) > 0)
+      break;
+    p = p->next;
+  }
+  (*ch)->next = p->next;
+  p->next = *ch;
+  return;
+}
+
+/* Remove a channel from the list, but don't free it */
+static void
+remove_channel(CHAN *ch)
+{
+  CHAN *p;
+
+  if (!ch)
+    return;
+  if (!channels)
+    return;
+  if (channels == ch) {
+    /* First channel */
+    channels = ch->next;
+    return;
+  }
+  /* Otherwise, find the channel before this one */
+  for (p = channels; p->next && (p->next != ch); p = p->next) ;
+
+  if (p->next) {
+    p->next = ch->next;
+  }
+  return;
+}
+
+/* Insert the channel onto the list of channels on a given object,
+ * sorted by name
+ */
+static void
+insert_obj_chan(dbref who, CHAN **ch)
+{
+  CHANLIST *p;
+  CHANLIST *tmp;
+
+  if (!ch || !*ch)
+    return;
+
+  tmp = new_chanlist();
+  if (!tmp)
+    return;
+  tmp->chan = *ch;
+  /* If there's no channels on the list, or if the first channel is already
+   * alphabetically greater, chan should be the first entry on the list */
+  /* No channels? */
+  if (!Chanlist(who)) {
+    tmp->next = NULL;
+    s_Chanlist(who, tmp);
+    return;
+  }
+  p = Chanlist(who);
+  /* First channel? */
+  if (strcasecoll(ChanName(p->chan), ChanName(*ch)) > 0) {
+    tmp->next = p;
+    s_Chanlist(who, tmp);
+    return;
+  } else if (!strcasecmp(ChanName(p->chan), ChanName(*ch))) {
+    /* Don't add the same channel twice! */
+    free_chanlist(tmp);
+  } else {
+    /* Otherwise, find which channel this channel should be inserted after */
+    for (;
+        p->next
+        && (strcasecoll(ChanName(p->next->chan), ChanName(*ch)) < 0);
+        p = p->next) ;
+    if (p->next && !strcasecmp(ChanName(p->next->chan), ChanName(*ch))) {
+      /* Don't add the same channel twice! */
+      free_chanlist(tmp);
+    } else {
+      tmp->next = p->next;
+      p->next = tmp;
+    }
+  }
+  return;
+}
+
+/* Remove a channel from the obj's chanlist, and free the chanlist ptr */
+static void
+remove_obj_chan(dbref who, CHAN *ch)
+{
+  CHANLIST *p, *q;
+
+  if (!ch)
+    return;
+  p = Chanlist(who);
+  if (!p)
+    return;
+  if (p->chan == ch) {
+    /* First channel */
+    s_Chanlist(who, p->next);
+    free_chanlist(p);
+    return;
+  }
+  /* Otherwise, find the channel before this one */
+  for (; p->next && (p->next->chan != ch); p = p->next) ;
+
+  if (p->next) {
+    q = p->next;
+    p->next = p->next->next;
+    free_chanlist(q);
+  }
+  return;
+}
+
+/** Remove all channels from the obj's chanlist, freeing them.
+ * \param thing object to have channels removed from.
+ */
+void
+remove_all_obj_chan(dbref thing)
+{
+  CHANLIST *p, *nextp;
+  for (p = Chanlist(thing); p; p = nextp) {
+    nextp = p->next;
+    remove_user_by_dbref(thing, p->chan);
+  }
+  return;
+}
+
+
+static CHANLIST *
+new_chanlist(void)
+{
+  CHANLIST *c;
+  c = (CHANLIST *) mush_malloc(sizeof(CHANLIST), "CHANLIST");
+  if (!c)
+    return NULL;
+  c->chan = NULL;
+  c->next = NULL;
+  return c;
+}
+
+static void
+free_chanlist(CHANLIST *cl)
+{
+  mush_free(cl, "CHANLIST");
+}
+
+
+/* Insert the user onto the channel's list, sorted by the user's name */
+static int
+insert_user(CHANUSER *user, CHAN *ch)
+{
+  CHANUSER *p;
+
+  if (!user || !ch)
+    return 0;
+
+  /* If there's no users on the list, or if the first user is already
+   * alphabetically greater, user should be the first entry on the list */
+  p = ChanUsers(ch);
+  if (!p || (strcasecoll(Name(CUdbref(p)), Name(CUdbref(user))) > 0)) {
+    user->next = ChanUsers(ch);
+    ChanUsers(ch) = user;
+  } else {
+    /* Otherwise, find which user this user should be inserted after */
+    for (;
+        p->next
+        && (strcasecoll(Name(CUdbref(p->next)), Name(CUdbref(user))) <= 0);
+        p = p->next) ;
+    if (CUdbref(p) == CUdbref(user)) {
+      /* Don't add the same user twice! */
+      mush_free((Malloc_t) user, "CHANUSER");
+      return 0;
+    } else {
+      user->next = p->next;
+      p->next = user;
+    }
+  }
+  insert_obj_chan(CUdbref(user), &ch);
+  return 1;
+}
+
+/* Remove a user from a channel list, and free it */
+static int
+remove_user(CHANUSER *u, CHAN *ch)
+{
+  CHANUSER *p;
+  dbref who;
+
+  if (!ch || !u)
+    return 0;
+  p = ChanUsers(ch);
+  if (!p)
+    return 0;
+  who = CUdbref(u);
+  if (p == u) {
+    /* First user */
+    ChanUsers(ch) = p->next;
+    free_user(u);
+  } else {
+    /* Otherwise, find the user before this one */
+    for (; p->next && (p->next != u); p = p->next) ;
+
+    if (p->next) {
+      p->next = u->next;
+      free_user(u);
+    } else
+      return 0;
+  }
+
+  /* Now remove the channel from the user's chanlist */
+  remove_obj_chan(who, ch);
+  ChanNumUsers(ch)--;
+  return 1;
+}
+
+
+/** Write the chat database to disk.
+ * \param fp pointer to file to write to.
+ * \retval 1 success
+ * \retval 0 failure
+ */
+int
+save_chatdb(FILE * fp)
+{
+  CHAN *ch;
+  int default_flags = 0;
+
+  /* How many channels? */
+  OUTPUT(fprintf(fp, "+F%d\n", default_flags));
+  db_write_labeled_string(fp, "savedtime", show_time(mudtime, 1));
+  db_write_labeled_number(fp, "channels", num_channels);
+  for (ch = channels; ch; ch = ch->next) {
+    save_channel(fp, ch);
+  }
+  OUTPUT(fputs(EOD, fp));
+  return 1;
+}
+
+/* Save a single channel. Return 1 if  successful, 0 otherwise.
+ */
+static int
+save_channel(FILE * fp, CHAN *ch)
+{
+  CHANUSER *cu;
+
+  db_write_labeled_string(fp, " name", ChanName(ch));
+  db_write_labeled_string(fp, "  description", ChanTitle(ch));
+  db_write_labeled_number(fp, "  flags", ChanType(ch));
+  db_write_labeled_dbref(fp, "  creator", ChanCreator(ch));
+  db_write_labeled_dbref(fp, "  cobj", ChanObj(ch));
+  db_write_labeled_number(fp, "  cost", ChanCost(ch));
+  db_write_labeled_string(fp, "  lock", "join");
+  putboolexp(fp, ChanJoinLock(ch));
+  db_write_labeled_string(fp, "  lock", "speak");
+  putboolexp(fp, ChanSpeakLock(ch));
+  db_write_labeled_string(fp, "  lock", "modify");
+  putboolexp(fp, ChanModLock(ch));
+  db_write_labeled_string(fp, "  lock", "see");
+  putboolexp(fp, ChanSeeLock(ch));
+  db_write_labeled_string(fp, "  lock", "hide");
+  putboolexp(fp, ChanHideLock(ch));
+  db_write_labeled_number(fp, "  users", ChanNumUsers(ch));
+  for (cu = ChanUsers(ch); cu; cu = cu->next)
+    save_chanuser(fp, cu);
+  return 1;
+}
+
+/* Save the channel's user list. Return 1 on success, 0 on failure */
+static int
+save_chanuser(FILE * fp, CHANUSER *user)
+{
+  db_write_labeled_dbref(fp, "   dbref", CUdbref(user));
+  db_write_labeled_number(fp, "    flags", CUtype(user));
+  db_write_labeled_string(fp, "    title", CUtitle(user));
+  return 1;
+}
+
+/*-------------------------------------------------------------*
+ * Some utility functions:
+ *  find_channel - given a name and a player, return a channel
+ *  find_channel_partial - given a name and a player, return
+ *    the first channel that matches name 
+ *  find_channel_partial_on - given a name and a player, return
+ *    the first channel that matches name that player is on.
+ *  onchannel - is player on channel?
+ */
+
+/** Removes markup and <>'s in channel names.
+ * \param name The name to normalize.
+ * \retval a pointer to a static buffer with the normalized name. 
+ */
+static char *
+normalize_channel_name(const char *name)
+{
+  static char cleanname[BUFFER_LEN];
+  size_t len;
+
+  cleanname[0] = '\0';
+
+  if (!name || !*name)
+    return cleanname;
+
+  strcpy(cleanname, remove_markup(name, &len));
+  len--;
+
+  if (!*cleanname)
+    return cleanname;
+
+  if (cleanname[0] == '<' && cleanname[len - 1] == '>') {
+    cleanname[len - 1] = '\0';
+    return cleanname + 1;
+  } else
+    return cleanname;
+}
+
+/** Attempt to match a channel name for a player.
+ * Given name and a chan pointer, set chan pointer to point to
+ * channel if found (NULL otherwise), and return an indication
+ * of how good the match was. If the player is not able to see
+ * the channel, fail to match.
+ * \param name name of channel to find.
+ * \param chan pointer to address of channel structure to return.
+ * \param player dbref to use for permission checks.
+ * \retval CMATCH_EXACT exact match of channel name.
+ * \retval CMATCH_PARTIAL partial match of channel name.
+ * \retval CMATCH_AMBIG multiple match of channel name.
+ * \retval CMATCH_NONE no match for channel name.
+ */
+enum cmatch_type
+find_channel(const char *name, CHAN **chan, dbref player)
+{
+  CHAN *p;
+  int count = 0;
+  char *cleanname;
+  char cleanp[CHAN_NAME_LEN];
+
+  *chan = NULL;
+  if (!name || !*name)
+    return CMATCH_NONE;
+
+  cleanname = normalize_channel_name(name);
+  for (p = channels; p; p = p->next) {
+    strcpy(cleanp, remove_markup(ChanName(p), NULL));
+    if (!strcasecmp(cleanname, cleanp)) {
+      *chan = p;
+      if (Chan_Can_See(*chan, player) || OnChannel(player, *chan))
+       return CMATCH_EXACT;
+      else
+       return CMATCH_NONE;
+    }
+    if (string_prefix(cleanp, name)) {
+      /* Keep the alphabetically first channel if we've got one */
+      if (Chan_Can_See(p, player) || OnChannel(player, p)) {
+       if (!*chan)
+         *chan = p;
+       count++;
+      }
+    }
+  }
+  switch (count) {
+  case 0:
+    return CMATCH_NONE;
+  case 1:
+    return CMATCH_PARTIAL;
+  }
+  return CMATCH_AMBIG;
+}
+
+
+/** Attempt to match a channel name for a player.
+ * Given name and a chan pointer, set chan pointer to point to
+ * channel if found (NULL otherwise), and return an indication
+ * of how good the match was. If the player is not able to see
+ * the channel, fail to match. If the match is ambiguous, return
+ * the first channel matched.
+ * \param name name of channel to find.
+ * \param chan pointer to address of channel structure to return.
+ * \param player dbref to use for permission checks.
+ * \retval CMATCH_EXACT exact match of channel name.
+ * \retval CMATCH_PARTIAL partial match of channel name.
+ * \retval CMATCH_AMBIG multiple match of channel name.
+ * \retval CMATCH_NONE no match for channel name.
+ */
+enum cmatch_type
+find_channel_partial(const char *name, CHAN **chan, dbref player)
+{
+  CHAN *p;
+  int count = 0;
+  char *cleanname;
+  char cleanp[CHAN_NAME_LEN];
+
+  *chan = NULL;
+  if (!name || !*name)
+    return CMATCH_NONE;
+  cleanname = normalize_channel_name(name);
+  for (p = channels; p; p = p->next) {
+    strcpy(cleanp, remove_markup(ChanName(p), NULL));
+    if (!strcasecmp(cleanname, cleanp)) {
+      *chan = p;
+      return CMATCH_EXACT;
+    }
+    if (string_prefix(cleanp, cleanname)) {
+      /* If we've already found an ambiguous match that the
+       * player is on, keep using that one. Otherwise, this is
+       * our best candidate so far.
+       */
+      if (!*chan || (!OnChannel(player, *chan) && OnChannel(player, p)))
+       *chan = p;
+      count++;
+    }
+  }
+  switch (count) {
+  case 0:
+    return CMATCH_NONE;
+  case 1:
+    return CMATCH_PARTIAL;
+  }
+  return CMATCH_AMBIG;
+}
+
+static void
+list_partial_matches(dbref player, const char *name, enum chan_match_type type)
+{
+  CHAN *p;
+  char *cleanname;
+  char cleanp[CHAN_NAME_LEN];
+  char buff[BUFFER_LEN], *bp;
+  bp = buff;
+
+  if (!name || !*name)
+    return;
+
+  safe_str(T("CHAT: Partial matches are:"), buff, &bp);
+  cleanname = normalize_channel_name(name);
+  for (p = channels; p; p = p->next) {
+    if (!Chan_Can_See(p, player))
+      continue;
+    if ((type == PMATCH_ALL) || ((type == PMATCH_ON)
+                                ? (int) (void *) OnChannel(player, p)
+                                : !OnChannel(player, p))) {
+      strcpy(cleanp, remove_markup(ChanName(p), NULL));
+      if (string_prefix(cleanp, cleanname)) {
+       safe_chr(' ', buff, &bp);
+       safe_str(ChanName(p), buff, &bp);
+      }
+    }
+  }
+
+  safe_chr('\0', buff, &bp);
+  notify(player, buff);
+
+}
+
+
+
+
+/** Attempt to match a channel name for a player.
+ * Given name and a chan pointer, set chan pointer to point to
+ * channel if found and player is on the channel (NULL otherwise), 
+ * and return an indication of how good the match was. If the player is 
+ * not able to see the channel, fail to match. If the match is ambiguous, 
+ * return the first channel matched.
+ * \param name name of channel to find.
+ * \param chan pointer to address of channel structure to return.
+ * \param player dbref to use for permission checks.
+ * \retval CMATCH_EXACT exact match of channel name.
+ * \retval CMATCH_PARTIAL partial match of channel name.
+ * \retval CMATCH_AMBIG multiple match of channel name.
+ * \retval CMATCH_NONE no match for channel name.
+ */
+static enum cmatch_type
+find_channel_partial_on(const char *name, CHAN **chan, dbref player)
+{
+  CHAN *p;
+  int count = 0;
+  char *cleanname;
+  char cleanp[CHAN_NAME_LEN];
+
+  *chan = NULL;
+  if (!name || !*name)
+    return CMATCH_NONE;
+  cleanname = normalize_channel_name(name);
+  for (p = channels; p; p = p->next) {
+    if (OnChannel(player, p)) {
+      strcpy(cleanp, remove_markup(ChanName(p), NULL));
+      if (!strcasecmp(cleanname, cleanp)) {
+       *chan = p;
+       return CMATCH_EXACT;
+      }
+      if (string_prefix(cleanp, cleanname) && OnChannel(player, p)) {
+       if (!*chan)
+         *chan = p;
+       count++;
+      }
+    }
+  }
+  switch (count) {
+  case 0:
+    return CMATCH_NONE;
+  case 1:
+    return CMATCH_PARTIAL;
+  }
+  return CMATCH_AMBIG;
+}
+
+/** Attempt to match a channel name for a player.
+ * Given name and a chan pointer, set chan pointer to point to
+ * channel if found and player is NOT on the channel (NULL otherwise), 
+ * and return an indication of how good the match was. If the player is 
+ * not able to see the channel, fail to match. If the match is ambiguous, 
+ * return the first channel matched.
+ * \param name name of channel to find.
+ * \param chan pointer to address of channel structure to return.
+ * \param player dbref to use for permission checks.
+ * \retval CMATCH_EXACT exact match of channel name.
+ * \retval CMATCH_PARTIAL partial match of channel name.
+ * \retval CMATCH_AMBIG multiple match of channel name.
+ * \retval CMATCH_NONE no match for channel name.
+ */
+static enum cmatch_type
+find_channel_partial_off(const char *name, CHAN **chan, dbref player)
+{
+  CHAN *p;
+  int count = 0;
+  char *cleanname;
+  char cleanp[CHAN_NAME_LEN];
+
+  *chan = NULL;
+  if (!name || !*name)
+    return CMATCH_NONE;
+  cleanname = normalize_channel_name(name);
+  for (p = channels; p; p = p->next) {
+    if (!OnChannel(player, p)) {
+      strcpy(cleanp, remove_markup(ChanName(p), NULL));
+      if (!strcasecmp(cleanname, cleanp)) {
+       *chan = p;
+       return CMATCH_EXACT;
+      }
+      if (string_prefix(cleanp, cleanname)) {
+       if (!*chan)
+         *chan = p;
+       count++;
+      }
+    }
+  }
+  switch (count) {
+  case 0:
+    return CMATCH_NONE;
+  case 1:
+    return CMATCH_PARTIAL;
+  }
+  return CMATCH_AMBIG;
+}
+
+/*--------------------------------------------------------------*
+ * User commands:
+ *  do_channel - @channel/on,off,who
+ *  do_chan_admin - @channel/add,delete,name,priv,quiet
+ *  do_chan_desc
+ *  do_chan_title
+ *  do_chan_lock
+ *  do_chan_boot
+ *  do_chan_wipe
+ */
+
+/** User interface to channels.
+ * \verbatim
+ * This is one of the top-level functions for @channel.
+ * It handles the /on, /off and /who switches. It also
+ * parses and handles the older @channel <channel>=<command>
+ * format, for the on, off, who, and wipe commands.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of channel.
+ * \param target name of player to add/remove (NULL for self)
+ * \param com channel command switch.
+ */
+void
+do_channel(dbref player, const char *name, const char *target, const char *com)
+{
+  CHAN *chan = NULL;
+  CHANUSER *u;
+  dbref victim;
+
+  if (!name || !*name) {
+    notify(player, T("You need to specify a channel."));
+    return;
+  }
+  if (!com || !*com) {
+    notify(player, T("What do you want to do with that channel?"));
+    return;
+  }
+
+  /* Quickly catch two common cases and handle separately */
+  if (!target || !*target) {
+    if (!strcasecmp(com, "on") || !strcasecmp(com, "join")) {
+      channel_join_self(player, name);
+      return;
+    } else if (!strcasecmp(com, "off") || !strcasecmp(com, "leave")) {
+      channel_leave_self(player, name);
+      return;
+    }
+  }
+
+  test_channel(player, name, chan);
+  if (!Chan_Can_See(chan, player)) {
+    if (OnChannel(player, chan))
+      notify_format(player,
+                   T("CHAT: You can't do that with channel <%s>."),
+                   ChanName(chan));
+    else
+      notify(player, T("CHAT: I don't recognize that channel."));
+    return;
+  }
+  if (!strcasecmp(com, "who")) {
+    do_channel_who(player, chan);
+    return;
+  } else if (!strcasecmp(com, "wipe")) {
+    channel_wipe(player, chan);
+    return;
+  }
+  /* It's on or off now */
+  /* Determine who is getting added or deleted. If we don't have
+   * an argument, we return, because we should've caught those above,
+   * and this shouldn't happen.
+   */
+  if (!target || !*target) {
+    notify(player, T("I don't understand what you want to do."));
+    return;
+  } else if ((victim = lookup_player(target)) != NOTHING) ;
+  else if (Channel_Object(chan))
+    victim = match_result(player, target, TYPE_THING, MAT_OBJECTS);
+  else
+    victim = NOTHING;
+  if (!GoodObject(victim)) {
+    notify(player, T("Invalid target."));
+    return;
+  }
+  if (!strcasecmp("on", com) || !strcasecmp("join", com)) {
+    if (!Chan_Ok_Type(chan, victim)) {
+      notify_format(player,
+                   T("Sorry, wrong type of thing for channel <%s>."),
+                   ChanName(chan));
+      return;
+    }
+    if (Guest(player)) {
+      notify(player, T("Guests are not allowed to join channels."));
+      return;
+    }
+    if (!controls(player, victim)) {
+      notify(player, T("Invalid target."));
+      return;
+    }
+    /* Is victim already on the channel? */
+    if (OnChannel(victim, chan)) {
+      notify_format(player,
+                   T("%s is already on channel <%s>."), Name(victim),
+                   ChanName(chan));
+      return;
+    }
+    /* Does victim pass the joinlock? */
+    if (!Chan_Can_Join(chan, victim)) {
+      if (Director(player)) {
+       /* Directors can override join locks */
+       notify(player,
+              T
+              ("CHAT: Warning: Target does not meet channel join permissions (joining anyway)"));
+      } else {
+       notify(player, T("Permission to join denied."));
+       return;
+      }
+    }
+    if (insert_user_by_dbref(victim, chan)) {
+      notify_format(victim,
+                   T("CHAT: %s joins you to channel <%s>."), Name(player),
+                   ChanName(chan));
+      notify_format(player,
+                   T("CHAT: You join %s to channel <%s>."), Name(victim),
+                   ChanName(chan));
+      u = onchannel(victim, chan);
+      if (!Channel_Quiet(chan) && !DarkLegal(victim)) {
+       format_channel_broadcast(chan, u, victim, CB_CHECKQUIET | CB_PRESENCE,
+                                T("%s %s has joined this channel."), NULL);
+      }
+      ChanNumUsers(chan)++;
+    } else {
+      notify_format(player,
+                   T("%s is already on channel <%s>."), Name(victim),
+                   ChanName(chan));
+    }
+    return;
+  } else if (!strcasecmp("off", com) || !strcasecmp("leave", com)) {
+    char title[CU_TITLE_LEN];
+    /* You must control either the victim or the channel */
+    if (!controls(player, victim) && !Chan_Can_Modify(chan, player)) {
+      notify(player, T("Invalid target."));
+      return;
+    }
+    if (Guest(player)) {
+      notify(player, T("Guests may not leave channels."));
+      return;
+    }
+    u = onchannel(victim, chan);
+    strcpy(title, (u &&CUtitle(u)) ? CUtitle(u) : "");
+    if (remove_user(u, chan)) {
+      if (!Channel_Quiet(chan) && !DarkLegal(victim)) {
+       format_channel_broadcast(chan, NULL, victim,
+                                CB_CHECKQUIET | CB_PRESENCE,
+                                T("%s %s has left this channel."), title);
+      }
+      notify_format(victim,
+                   T("CHAT: %s removes you from channel <%s>."),
+                   Name(player), ChanName(chan));
+      notify_format(player,
+                   T("CHAT: You remove %s from channel <%s>."),
+                   Name(victim), ChanName(chan));
+    } else {
+      notify_format(player, T("%s is not on channel <%s>."), Name(victim),
+                   ChanName(chan));
+    }
+    return;
+  } else {
+    notify(player, T("I don't understand what you want to do."));
+    return;
+  }
+}
+
+static void
+channel_join_self(dbref player, const char *name)
+{
+  CHAN *chan = NULL;
+  CHANUSER *u;
+
+  if (Guest(player)) {
+    notify(player, T("Guests are not allowed to join channels."));
+    return;
+  }
+
+  switch (find_channel_partial_off(name, &chan, player)) {
+  case CMATCH_NONE:
+    if (find_channel_partial_on(name, &chan, player))
+      notify_format(player, T("CHAT: You are already on channel <%s>"),
+                   ChanName(chan));
+    else
+      notify(player, T("CHAT: I don't recognize that channel."));
+    return;
+  case CMATCH_AMBIG:
+    notify(player, T("CHAT: I don't know which channel you mean."));
+    list_partial_matches(player, name, PMATCH_OFF);
+    return;
+  default:
+    break;
+  }
+  if (!Chan_Can_See(chan, player)) {
+    notify(player, T("CHAT: I don't recognize that channel."));
+    return;
+  }
+  if (!Chan_Ok_Type(chan, player)) {
+    notify_format(player,
+                 T("Sorry, wrong type of thing for channel <%s>."),
+                 ChanName(chan));
+    return;
+  }
+  /* Does victim pass the joinlock? */
+  if (!Chan_Can_Join(chan, player)) {
+    if (Director(player)) {
+      /* Directors can override join locks */
+      notify(player,
+            T
+            ("CHAT: Warning: You don't meet channel join permissions (joining anyway)"));
+    } else {
+      notify(player, T("Permission to join denied."));
+      return;
+    }
+  }
+  if (insert_user_by_dbref(player, chan)) {
+    notify_format(player, T("CHAT: You join channel %s."), ChanObjName(chan));
+    u = onchannel(player, chan);
+    if (!Channel_Quiet(chan) && !DarkLegal(player))
+      format_channel_broadcast(chan, u, player, CB_CHECKQUIET | CB_PRESENCE,
+                              T("%s %s has joined this channel."), NULL);
+    ChanNumUsers(chan)++;
+  } else {
+    /* Should never happen */
+    notify_format(player,
+                 T("%s is already on channel <%s>."), Name(player),
+                 ChanName(chan));
+  }
+}
+
+static void
+channel_leave_self(dbref player, const char *name)
+{
+  CHAN *chan = NULL;
+  CHANUSER *u;
+  char title[CU_TITLE_LEN];
+
+  if (Guest(player)) {
+    notify(player, T("Guests are not allowed to leave channels."));
+    return;
+  }
+  switch (find_channel_partial_on(name, &chan, player)) {
+  case CMATCH_NONE:
+    if (find_channel_partial_off(name, &chan, player)
+       && Chan_Can_See(chan, player))
+      notify_format(player, T("CHAT: You are not on channel <%s>"),
+                   ChanName(chan));
+    else
+      notify(player, T("CHAT: I don't recognize that channel."));
+    return;
+  case CMATCH_AMBIG:
+    notify(player, T("CHAT: I don't know which channel you mean."));
+    list_partial_matches(player, name, PMATCH_ON);
+    return;
+  default:
+    break;
+  }
+  u = onchannel(player, chan);
+  strcpy(title, (u &&CUtitle(u)) ? CUtitle(u) : "");
+  if (remove_user(u, chan)) {
+    if (!Channel_Quiet(chan) && !DarkLegal(player))
+      format_channel_broadcast(chan, NULL, player, CB_CHECKQUIET | CB_PRESENCE,
+                              T("%s %s has left this channel."), title);
+    notify_format(player, T("CHAT: You leave channel <%s>."), ChanName(chan));
+  } else {
+    /* Should never happen */
+    notify_format(player, T("%s is not on channel <%s>."), Name(player),
+                 ChanName(chan));
+  }
+}
+
+/** Parse a chat token command, but don't chat with it.
+ * \verbatim
+ * This function hacks up something of the form "+<channel> <message>",
+ * finding the two args, and returns 1 if the channel exists,
+ * otherwise 0.
+ * \endverbatim
+ * \param player the enactor.
+ * \param command the command to parse.
+ * \retval 1 channel exists
+ * \retval 0 chat failed (no such channel, etc.)
+ */
+int
+parse_chat(dbref player, char *command)
+{
+  char *arg1;
+  char *arg2;
+  char *s;
+  char ch;
+  CHAN *c;
+
+  s = command;
+  arg1 = s;
+  while (*s && !isspace((unsigned char) *s))
+    s++;
+
+  if (!*s)
+    return 0;
+
+  ch = *s;
+  arg2 = s;
+  *arg2++ = '\0';
+  while (*arg2 && isspace((unsigned char) *arg2))
+    arg2++;
+
+  /* arg1 is channel name, arg2 is text. */
+  switch (find_channel_partial_on(arg1, &c, player)) {
+  case CMATCH_AMBIG:
+  case CMATCH_EXACT:
+  case CMATCH_PARTIAL:
+    *s = '=';
+    return 1;
+  default:
+    *s = ch;
+    return 0;
+  }
+}
+
+
+/** Chat on a channel, given its name.
+ * \verbatim
+ * This function parses a channel name and then calls do_chat()
+ * to do the actual work of chatting. If it was called through
+ * @chat, it fails noisily, but if it was called through +channel,
+ * it fails silently so that the command can be matched against $commands.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of channel to speak on.
+ * \param msg message to send to channel.
+ * \param source 0 if from +channel, 1 if from the chat command.
+ * \retval 1 successful chat.
+ * \retval 0 failed chat.
+ */
+int
+do_chat_by_name(dbref player, const char *name, const char *msg, int source)
+{
+  CHAN *c;
+  c = NULL;
+  if (!msg || !*msg) {
+    if (source)
+      notify(player, T("Don't you have anything to say?"));
+    return 0;
+  }
+  /* First try to find a channel that the player's on. If that fails,
+   * look for one that the player's not on.
+   */
+  switch (find_channel_partial_on(name, &c, player)) {
+  case CMATCH_AMBIG:
+    if (!ChanUseFirstMatch(player)) {
+      notify(player, T("CHAT: I don't know which channel you mean."));
+      list_partial_matches(player, name, PMATCH_ON);
+      return 1;
+    }
+  case CMATCH_EXACT:
+  case CMATCH_PARTIAL:
+    do_chat(player, c, msg);
+    return 1;
+  case CMATCH_NONE:
+    if (find_channel(name, &c, player) == CMATCH_NONE) {
+      if (source)
+       notify(player, T("CHAT: No such channel."));
+      return 0;
+    }
+  }
+  return 0;
+}
+
+/** Send a message to a channel.
+ * This function does the real work of putting together a message
+ * to send to a chat channel (which it then does via channel_broadcast()).
+ * \param player the enactor.
+ * \param chan pointer to the channel to speak on.
+ * \param arg1 message to send.
+ */
+void
+do_chat(dbref player, CHAN *chan, const char *arg1)
+{
+  CHANUSER *u;
+  const char *gap;
+  const char *someone = "Someone";
+  char *title;
+  const char *name;
+  int canhear;
+
+  if (!Chan_Ok_Type(chan, player)) {
+    notify_format(player,
+                 T
+                 ("Sorry, you're not the right type to be on channel <%s>."),
+                 ChanName(chan));
+    return;
+  }
+  if (!Chan_Can_Speak(chan, player)) {
+    if (Chan_Can_See(chan, player))
+      notify_format(player,
+                   T("Sorry, you're not allowed to speak on channel <%s>."),
+                   ChanName(chan));
+    else
+      notify(player, T("No such channel."));
+    return;
+  }
+  u = onchannel(player, chan);
+  canhear = u ? !Chanuser_Gag(u) : 0;
+  /* If the channel isn't open, you must hear it in order to speak */
+  if (!Channel_Open(chan) && ChanObj(chan) != player) {
+    if (!u) {
+      notify(player, T("You must be on that channel to speak on it."));
+      return;
+    } else if (!canhear) {
+      notify(player, T("You must stop gagging that channel to speak on it."));
+      return;
+#ifdef RPMODE_SYS
+    } else if(RPMODE(player) && !Can_RPCHAT(player)) {
+           notify(player, T("You can't do that in RPMODE."));
+           return;
+#endif
+    }
+  }
+
+  if (!*arg1) {
+    notify(player, T("What do you want to say to that channel?"));
+    return;
+  }
+
+  if (!Channel_NoTitles(chan) && u &&CUtitle(u) && *CUtitle(u))
+     title = CUtitle(u);
+  else
+    title = NULL;
+  if (Channel_NoNames(chan))
+    name = NULL;
+  else
+    name = accented_name(player);
+  if (!title && !name)
+    name = someone;
+
+  /* figure out what kind of message we have */
+  gap = " ";
+  switch (*arg1) {
+  case SEMI_POSE_TOKEN:
+    gap = "";
+    /* FALLTHRU */
+  case POSE_TOKEN:
+    arg1 = arg1 + 1;
+    channel_broadcast(chan, player, 0, "%s %s%s%s%s%s%s", ChanObjName(chan),
+                     title ? title : "", title ? ANSI_NORMAL : "",
+                     (title && name) ? " " : "", name ? name : "", gap, arg1);
+    if (!canhear)
+      notify_format(player, T("To channel %s: %s%s%s%s%s%s"), ChanName(chan),
+                   title ? title : "", title ? ANSI_NORMAL : "",
+                   (title && name) ? " " : "", name ? name : "", gap, arg1);
+    break;
+  default:
+    if (CHAT_STRIP_QUOTE && (*arg1 == SAY_TOKEN))
+      arg1 = arg1 + 1;
+    channel_broadcast(chan, player, 0, T("%s %s%s%s%s says, \"%s\""),
+                     ChanObjName(chan), title ? title : "",
+                     title ? ANSI_NORMAL : "", (title && name) ? " " : "",
+                     name ? name : "", arg1);
+    if (!canhear)
+      notify_format(player,
+                   T("To channel %s: %s%s%s%s says, \"%s\""),
+                   ChanName(chan), title ? title : "",
+                   title ? ANSI_NORMAL : "", (title && name) ? " " : "",
+                   name ? name : "", arg1);
+    break;
+  }
+
+   if(ChanObjCheck(chan) == 1 && player != ChanObj(chan))
+     chan_cmd_match(ChanObj(chan), player, arg1);
+
+  ChanNumMsgs(chan)++;
+}
+
+/** Emit on a channel.
+ * \verbatim
+ * This is the top-level function for @cemit.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the channel.
+ * \param msg message to emit.
+ * \param flags PEMIT_* flags.
+ */
+void
+do_cemit(dbref player, const char *name, const char *msg, int flags)
+{
+  CHAN *chan = NULL;
+  CHANUSER *u;
+  int canhear;
+
+  if (!name || !*name) {
+    notify(player, T("That is not a valid channel."));
+    return;
+  }
+  switch (find_channel(name, &chan, player)) {
+  case CMATCH_NONE:
+    notify(player, T("I don't recognize that channel."));
+    return;
+  case CMATCH_AMBIG:
+    notify(player, T("I don't know which channel you mean."));
+    list_partial_matches(player, name, PMATCH_ALL);
+    return;
+  }
+  if (!Chan_Can_See(chan, player)) {
+    notify(player, T("CHAT: I don't recognize that channel."));
+    return;
+  }
+  if (!Chan_Ok_Type(chan, player)) {
+    notify_format(player,
+                 T
+                 ("Sorry, you're not the right type to be on channel <%s>."),
+                 ChanName(chan));
+    return;
+  }
+  if (!Chan_Can_Cemit(chan, player)) {
+    notify_format(player,
+                 T("Sorry, you're not allowed to @cemit on channel <%s>."),
+                 ChanName(chan));
+    return;
+  }
+  u = onchannel(player, chan);
+  canhear = u ? !Chanuser_Gag(u) : 0;
+  /* If the channel isn't open, you must hear it in order to speak */
+  if (!Channel_Open(chan)) {
+    if (!u) {
+      notify(player, T("You must be on that channel to speak on it."));
+      return;
+    } else if (!canhear) {
+      notify(player, T("You must stop gagging that channel to speak on it."));
+      return;
+#ifdef RPMODE_SYS
+    } else if(RPMODE(player) && !Can_RPCHAT(player)) {
+           notify(player, T("You can't do that in RPMODE."));
+           return;
+#endif
+    }
+  }
+
+  if (!msg || !*msg) {
+    notify(player, T("What do you want to emit?"));
+    return;
+  }
+  if (!(flags & PEMIT_SILENT))
+    channel_broadcast(chan, player, (flags & PEMIT_SPOOF) ? 0 : CB_NOSPOOF,
+                     "%s %s", ChanObjName(chan), msg);
+  else
+    channel_broadcast(chan, player, (flags & PEMIT_SPOOF) ? 0 : CB_NOSPOOF,
+                     "%s", msg);
+  if (!canhear)
+    notify_format(player, T("Cemit to channel %s: %s"), ChanName(chan), msg);
+  ChanNumMsgs(chan)++;
+  return;
+}
+
+/** Administrative channel commands.
+ * \verbatim
+ * This is one of top-level functions for @channel. This one handles
+ * the /add, /delete, /rename, and /priv switches.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the channel.
+ * \param perms the permissions to set on an added/priv'd channel, the newname for a renamed channel, or on/off for a quieted channel.
+ * \param flag switch indicator: 0=add, 1=delete, 2=rename, 3=priv
+ */
+void
+do_chan_admin(dbref player, char *name, const char *perms, int flag)
+{
+  CHAN *chan = NULL, *temp = NULL;
+  long int type;
+  int res;
+  boolexp key;
+  char old[CHAN_NAME_LEN];
+
+  if (!name || !*name) {
+    notify(player, T("You must specify a channel."));
+    return;
+  }
+  if (Guest(player)) {
+    notify(player, T("Guests may not modify channels."));
+    return;
+  }
+  if ((flag > 1) && (!perms || !*perms)) {
+    notify(player, T("What do you want to do with the channel?"));
+    return;
+  }
+  /* Make sure we've got a unique channel name unless we're
+   * adding a channel */
+  if (flag)
+    test_channel(player, name, chan);
+  switch (flag) {
+  case 0:
+    /* add a channel */
+    if (num_channels == MAX_CHANNELS) {
+      notify(player, T("No more room for channels."));
+      return;
+    }
+    if (strlen(name) > CHAN_NAME_LEN - 1) {
+      notify(player, T("The channel needs a shorter name."));
+      return;
+    }
+    if (!ok_channel_name(name)) {
+      notify(player, T("Invalid name for a channel."));
+      return;
+    }
+    if (!Admin(player) && !canstilladd(player)) {
+      notify(player, T("You already own too many channels."));
+      return;
+    }
+    res = find_channel(name, &chan, GOD);
+    if (res != CMATCH_NONE) {
+      notify(player, T("CHAT: The channel needs a more unique name."));
+      return;
+    }
+    /* get the permissions. Invalid specs default to the default */
+    if (!perms || !*perms)
+      type = string_to_privs(priv_table, options.channel_flags, 0);
+    else
+      type = string_to_privs(priv_table, perms, 0);
+    if (!Chan_Can(player, type)) {
+      notify(player, T("You can't create channels of that type."));
+      return;
+    }
+    if (type & CHANNEL_DISABLED)
+      notify(player, T("Warning: channel will be created disabled."));
+
+    /* Check if they're trying to create it as a chanobj channel, if so reject them */
+    if(type & CHANNEL_COBJ) {
+      notify(player,"CHAT: Channels can not be created with chanobj type.");
+      return;
+    }
+
+    /* Can the player afford it? There's a cost */
+    if (!payfor(Owner(player), CHANNEL_COST)) {
+      notify_format(player, T("You can't afford the %d %s."), CHANNEL_COST,
+                   MONIES);
+      return;
+    }
+    /* Ok, let's do it */
+    chan = new_channel();
+    if (!chan) {
+      notify(player, T("CHAT: No more memory for channels!"));
+      giveto(Owner(player), CHANNEL_COST);
+      return;
+    }
+    key = parse_boolexp(player, tprintf("=#%d", player), chan_mod_lock);
+    if (!key) {
+      mush_free(chan, "CHAN");
+      notify(player, T("CHAT: No more memory for channels!"));
+      giveto(Owner(player), CHANNEL_COST);
+      return;
+    }
+    ChanModLock(chan) = key;
+    num_channels++;
+    if (type)
+      ChanType(chan) = type;
+    ChanCreator(chan) = Owner(player);
+    strcpy(ChanName(chan), name);
+    insert_channel(&chan);
+    notify_format(player, T("CHAT: Channel <%s> created."), ChanName(chan));
+    break;
+  case 1:
+    /* remove a channel */
+    /* Check permissions. Directors and owners can remove */
+    if (!Chan_Can_Nuke(chan, player)) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    /* remove everyone from the channel */
+    channel_wipe(player, chan);
+    /* refund the owner's money */
+    giveto(ChanCreator(chan), ChanCost(chan));
+    /* zap the channel */
+    remove_channel(chan);
+    free_channel(chan);
+    num_channels--;
+    notify(player, T("Channel removed."));
+    break;
+  case 2:
+    /* rename a channel */
+    /* Can the player do this? */
+    if (!Chan_Can_Modify(chan, player)) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    /* make sure the channel name is unique */
+    if (find_channel(perms, &temp, GOD)) {
+      /* But allow renaming a channel to a differently-cased version of
+       * itself 
+       */
+      if (temp != chan) {
+       notify(player, T("The channel needs a more unique new name."));
+       return;
+      }
+    }
+    if (strlen(perms) > CHAN_NAME_LEN - 1) {
+      notify(player, T("That name is too long."));
+      return;
+    }
+    /* When we rename a channel, we actually remove it and re-insert it */
+    strcpy(old, ChanName(chan));
+    remove_channel(chan);
+    strcpy(ChanName(chan), perms);
+    insert_channel(&chan);
+    channel_broadcast(chan, player, 0,
+                     "<%s> %s has renamed channel %s to %s.",
+                     ChanName(chan), Name(player), old, ChanName(chan));
+    notify(player, T("Channel renamed."));
+    break;
+  case 3:
+    /* change the permissions on a channel */
+    if (!Chan_Can_Modify(chan, player)) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    /* get the permissions. Invalid specs default to no change */
+    type = string_to_privs(priv_table, perms, ChanType(chan));
+    if (!Chan_Can_Priv(player, type, ChanType(chan))) {
+      notify(player, T("You can't make channels that type."));
+      return;
+    } 
+
+    if (type & CHANNEL_DISABLED)
+      notify(player, T("Warning: channel will be disabled."));
+    if (type == ChanType(chan)) {
+      notify_format(player,
+                   T
+                   ("Invalid or same permissions on channel <%s>. No changes made."),
+                   ChanName(chan));
+    } else {
+      ChanType(chan) = type;
+      notify_format(player,
+                   T("Permissions on channel <%s> changed."), ChanName(chan));
+    }
+    break;
+  }
+}
+
+static int
+ok_channel_name(const char *n)
+{
+  /* is name valid for a channel? */
+  const char *p;
+  char name[BUFFER_LEN];
+
+  strcpy(name, remove_markup(n, NULL));
+
+  if (!name || !*name)
+    return 0;
+
+  /* No leading spaces */
+  if (isspace((unsigned char) *name))
+    return 0;
+
+  /* only printable characters */
+  for (p = name; p && *p; p++) {
+    if (!isprint((unsigned char) *p))
+      return 0;
+  }
+
+  /* No trailing spaces */
+  p--;
+  if (isspace((unsigned char) *p))
+    return 0;
+
+  return 1;
+}
+
+
+/** Modify a user's settings on a channel.
+ * \verbatim
+ * This is one of top-level functions for @channel. This one
+ * handles the /mute, /hide, and /gag switches, which control an
+ * individual user's settings on a single channel.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the channel.
+ * \param isyn a yes/no string.
+ * \param flag switch indicator: 0=mute, 1=hide, 2=gag
+ * \param silent if 1, no notification of actions.
+ */
+void
+do_chan_user_flags(dbref player, char *name, const char *isyn, int flag,
+                  int silent)
+{
+  CHAN *c = NULL;
+  CHANUSER *u;
+  CHANLIST *p;
+  int setting = abs(yesno(isyn));
+  p = NULL;
+
+  if (!name || !*name) {
+    p = Chanlist(player);
+    if (!p) {
+      notify(player, T("You are not on any channels."));
+      return;
+    }
+    silent = 1;
+    switch (flag) {
+    case 0:
+      notify(player, setting ? T("All channels have been muted.")
+            : T("All channels have been unmuted."));
+      break;
+    case 1:
+      notify(player, setting ? T("You hide on all the channels you can.")
+            : T("You unhide on all channels."));
+      break;
+    case 2:
+      notify(player, setting ? T("All channels have been gagged.")
+            : T("All channels have been ungagged."));
+      break;
+    }
+  } else {
+    test_channel(player, name, c);
+  }
+
+  /* channel loop */
+  do {
+    /* If we have a channel list at the start, 
+     * that means they didn't gave us a channel name,
+     * so we now figure out c. */
+    if (p != NULL) {
+      c = p->chan;
+      p = p->next;
+    }
+
+    u = onchannel(player, c);
+    if (!u) {
+      /* This should only happen if they gave us a bad name */
+      if (!silent)
+       notify_format(player, T("You are not on channel <%s>."), ChanName(c));
+      return;
+    }
+
+    switch (flag) {
+    case 0:
+      /* Mute */
+      if (setting) {
+       CUtype(u) |= CU_QUIET;
+       if (!silent)
+         notify_format(player,
+                       T
+                       ("You will no longer hear connection messages on channel <%s>."),
+                       ChanName(c));
+      } else {
+       CUtype(u) &= ~CU_QUIET;
+       if (!silent)
+         notify_format(player,
+                       T
+                       ("You will now hear connection messages on channel <%s>."),
+                       ChanName(c));
+      }
+      break;
+
+    case 1:
+      /* Hide */
+      if (setting) {
+       if (!Chan_Can_Hide(c, player) && !Director(player)) {
+         if (!silent)
+           notify_format(player,
+                         T("You are not permitted to hide on channel <%s>."),
+                         ChanName(c));
+       } else {
+         CUtype(u) |= CU_HIDE;
+         if (!silent)
+           notify_format(player,
+                         T
+                         ("You no longer appear on channel <%s>'s who list."),
+                         ChanName(c));
+       }
+      } else {
+       CUtype(u) &= ~CU_HIDE;
+       if (!silent)
+         notify_format(player,
+                       T("You now appear on channel <%s>'s who list."),
+                       ChanName(c));
+      }
+      break;
+    case 2:
+      /* Gag */
+      if (setting) {
+       CUtype(u) |= CU_GAG;
+       if (!silent)
+         notify_format(player,
+                       T
+                       ("You will no longer hear messages on channel <%s>."),
+                       ChanName(c));
+      } else {
+       CUtype(u) &= ~CU_GAG;
+       if (!silent)
+         notify_format(player,
+                       T("You will now hear messages on channel <%s>."),
+                       ChanName(c));
+      }
+      break;
+    }
+  } while (p != NULL);
+
+  return;
+}
+
+/** Set a user's title for the channel.
+ * \verbatim
+ * This is one of the top-level functions for @channel. It handles
+ * the /title switch.
+ * \param player the enactor.
+ * \param name the name of the channel.
+ * \param title the player's new channel title.
+ */
+void
+do_chan_title(dbref player, const char *name, const char *title)
+{
+  CHAN *c = NULL;
+  CHANUSER *u;
+  const char *scan;
+
+  if (!name || !*name) {
+    notify(player, T("You must specify a channel."));
+    return;
+  }
+  if (strlen(title) >= CU_TITLE_LEN) {
+    notify(player, T("Title too long."));
+    return;
+  }
+  /* Stomp newlines and other weird whitespace */
+  for (scan = title; *scan; scan++) {
+    if ((isspace((unsigned char) *scan) && (*scan != ' '))
+       || (*scan == BEEP_CHAR)) {
+      notify(player, T("Invalid character in title."));
+      return;
+    }
+  }
+  test_channel(player, name, c);
+  u = onchannel(player, c);
+  if (!u) {
+    notify_format(player, T("You are not on channel <%s>."), ChanName(c));
+    return;
+  }
+  strcpy(CUtitle(u), title);
+  if (!Quiet(player))
+    notify_format(player, T("Title %s for %schannel %s."),
+                 *title ? T("set") : T("cleared"),
+                 Channel_NoTitles(c) ? "(NoTitles) " : "", ChanObjName(c));
+  return;
+}
+
+/** List all the channels and their flags.
+ * \verbatim
+ * This is one of the top-level functions for @channel. It handles the
+ * /list switch.
+ * \endverbatim
+ * \param player the enactor.
+ * \param partname a partial channel name to match.
+ */
+void
+do_channel_list(dbref player, const char *partname)
+{
+  CHAN *c;
+  CHANUSER *u;
+  char numusers[BUFFER_LEN];
+  char cleanname[CHAN_NAME_LEN];
+  const char thirtyblanks[31] = "                              ";
+  char blanks[31];
+  int numblanks;
+
+  if (SUPPORT_PUEBLO)
+    notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
+  notify_format(player, "%-30s %-5s %8s %-16s %-8s %-3s",
+               "Name", "Users", "Msgs", T("Chan Type"), "Status", "Buf");
+  for (c = channels; c; c = c->next) {
+    strcpy(cleanname, remove_markup(ChanName(c), NULL));
+    if (Chan_Can_See(c, player) && string_prefix(cleanname, partname)) {
+      u = onchannel(player, c);
+      if (SUPPORT_PUEBLO)
+       sprintf(numusers,
+               "%cA XCH_CMD=\"@channel/who %s\" XCH_HINT=\"See who's on this channel now\"%c%5ld%c/A%c",
+               TAG_START, cleanname, TAG_END, ChanNumUsers(c),
+               TAG_START, TAG_END);
+      else
+       sprintf(numusers, "%5ld", ChanNumUsers(c));
+      numblanks = strlen(ChanName(c)) - strlen(cleanname);
+      if (numblanks > 0 && numblanks < 31) {
+       strcpy(blanks, thirtyblanks);
+       blanks[numblanks] = '\0';
+      } else {
+       blanks[0] = '\0';
+      }
+      notify_format(player,
+                   "%-30s%s %s %8ld [%c%c%c%c%c%c%c%c %c%c%c%c%c%c] [%-3s %c%c] %3d",
+                   ChanName(c), blanks, numusers, ChanNumMsgs(c),
+                   Channel_Disabled(c) ? 'D' : '-',
+                   Channel_Player(c) ? 'P' : '-',
+                   Channel_Object(c) ? 'O' : '-',
+                   Channel_Admin(c) ? 'A' : (Channel_Director(c) ? 'W' : '-'),
+                   Channel_Quiet(c) ? 'Q' : '-',
+                   Channel_CanHide(c) ? 'H' : '-', Channel_Open(c) ? 'o' : '-',
+                   (ChanType(c) & CHANNEL_COBJ) ? 'Z' : '-',
+                   /* Locks */
+                   ChanJoinLock(c) != TRUE_BOOLEXP ? 'j' : '-',
+                   ChanSpeakLock(c) != TRUE_BOOLEXP ? 's' : '-',
+                   ChanModLock(c) != TRUE_BOOLEXP ? 'm' : '-',
+                   ChanSeeLock(c) != TRUE_BOOLEXP ? 'v' : '-',
+                   ChanHideLock(c) != TRUE_BOOLEXP ? 'h' : '-',
+                   /* Does the player own it? */
+                   ChanCreator(c) == player ? '*' : '-',
+                   /* User status */
+                   u ? (Chanuser_Gag(u) ? "Gag" : "On") : "Off",
+                   (u &&Chanuser_Quiet(u)) ? 'Q' : ' ',
+                   (u &&Chanuser_Hide(u)) ? 'H' : ' ',
+                   bufferq_lines(ChanBufferQ(c)));
+    }
+  }
+  if (SUPPORT_PUEBLO)
+    notify_noenter(player, tprintf("%c/SAMP%c", TAG_START, TAG_END));
+}
+
+static char *
+list_cflags(CHAN *c)
+{
+  static char tbuf1[BUFFER_LEN];
+  char *bp;
+
+  bp = tbuf1;
+  if (Channel_Disabled(c))
+    safe_chr('D', tbuf1, &bp);
+  if (Channel_Player(c))
+    safe_chr('P', tbuf1, &bp);
+  if (Channel_Object(c))
+    safe_chr('O', tbuf1, &bp);
+  if (Channel_Admin(c))
+    safe_chr('A', tbuf1, &bp);
+  if (Channel_Director(c))
+    safe_chr('W', tbuf1, &bp);
+  if (Channel_Quiet(c))
+    safe_chr('Q', tbuf1, &bp);
+  if (Channel_CanHide(c))
+    safe_chr('H', tbuf1, &bp);
+  if (Channel_Open(c))
+    safe_chr('o', tbuf1, &bp);
+  if (Channel_NoTitles(c))
+    safe_chr('T', tbuf1, &bp);
+  if (Channel_NoNames(c))
+    safe_chr('N', tbuf1, &bp);
+  if (Channel_NoCemit(c))
+    safe_chr('C', tbuf1, &bp);
+  if (Channel_Interact(c))
+    safe_chr('I', tbuf1, &bp);
+  if(ChanType(c) & CHANNEL_COBJ)
+    safe_chr('Z', tbuf1, &bp);
+  *bp = '\0';
+  return tbuf1;
+}
+
+static char *
+list_cuflags(CHANUSER *u)
+{
+  static char tbuf1[BUFFER_LEN];
+  char *bp;
+
+  bp = tbuf1;
+  if (Chanuser_Gag(u))
+     safe_chr('G', tbuf1, &bp);
+  if (Chanuser_Quiet(u))
+     safe_chr('Q', tbuf1, &bp);
+  if (Chanuser_Hide(u))
+     safe_chr('H', tbuf1, &bp);
+  *bp = '\0';
+  return tbuf1;
+}
+
+FUNCTION(fun_cobj) {
+CHAN *c;
+
+  switch(find_channel(args[0], &c, executor)) {
+    case CMATCH_NONE:
+      safe_str("#-1 NO SUCH CHANNEL", buff, bp);
+      return;
+    case CMATCH_AMBIG:
+      safe_str("#-2 AMBIGUOUS CHANNEL MATCH", buff, bp);
+      return;
+  }
+
+  if(!ChanObjCheck(c)) {
+    safe_str("#-1 NO CHANOBJ SET", buff, bp);
+    return;
+   }
+  if((executor == ChanCreator(c)) || Director(executor) || 
+      Visual(ChanObj(c)) || controls(executor, Owner(ChanCreator(c)))) {
+
+  safe_str(tprintf("#%ld", ChanObj(c)), buff, bp);
+  return;
+   }
+   else
+   {
+  safe_str("#-1 PERMISSION DENIED", buff, bp);
+  return;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cflags)
+{
+  /* With one channel arg, returns list of set flags, as per 
+   * do_channel_list. Sample output: PQ, Oo, etc.
+   * With two args (channel,object) return channel-user flags
+   * for that object on that channel (a subset of GQH).
+   * You must pass channel's @clock/see, and, in second case,
+   * must be able to examine the object.
+   */
+  CHAN *c;
+  CHANUSER *u;
+  dbref thing;
+
+  if (!args[0] || !*args[0]) {
+    safe_str(T("#-1 NO CHANNEL GIVEN"), buff, bp);
+    return;
+  }
+  switch (find_channel(args[0], &c, executor)) {
+  case CMATCH_NONE:
+    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
+    return;
+  case CMATCH_AMBIG:
+    safe_str(T("#-1 AMBIGUOUS CHANNEL NAME"), buff, bp);
+    return;
+  default:
+    if (!Chan_Can_See(c, executor)) {
+      safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
+      return;
+    }
+    if (nargs == 1) {
+      safe_str(list_cflags(c), buff, bp);
+      return;
+    }
+    thing = match_thing(executor, args[1]);
+    if (thing == NOTHING) {
+      safe_str(T(e_match), buff, bp);
+      return;
+    }
+    if (!Can_Examine(executor, thing)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    u = onchannel(thing, c);
+    if (!u) {
+      safe_str(T("#-1 NOT ON CHANNEL"), buff, bp);
+      return;
+    }
+    safe_str(list_cuflags(u), buff, bp);
+    break;
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_ctitle)
+{
+  /* ctitle(<channel>,<object>) returns the object's chantitle on that chan.
+   * You must pass the channel's see-lock, and
+   * either you must either be able to examine <object>, or
+   * <object> must not be hidden, and either
+   *   a) You must be on <channel>, or
+   *   b) You must pass the join-lock 
+   */
+  CHAN *c;
+  CHANUSER *u;
+  dbref thing;
+  int ok;
+  int can_ex;
+
+  if (!args[0] || !*args[0]) {
+    safe_str(T("#-1 NO CHANNEL GIVEN"), buff, bp);
+    return;
+  }
+  switch (find_channel(args[0], &c, executor)) {
+  case CMATCH_NONE:
+    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
+    return;
+  case CMATCH_AMBIG:
+    safe_str(T("#-1 AMBIGUOUS CHANNEL NAME"), buff, bp);
+    return;
+  default:
+    thing = match_thing(executor, args[1]);
+    if (thing == NOTHING) {
+      safe_str(T(e_match), buff, bp);
+      return;
+    }
+    if (!Chan_Can_See(c, executor)) {
+      safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
+      return;
+    }
+    can_ex = Can_Examine(executor, thing);
+    ok = (onchannel(executor, c) || Chan_Can_Join(c, executor));
+    u = onchannel(thing, c);
+    if (!u) {
+      if (can_ex || ok)
+       safe_str(T("#-1 NOT ON CHANNEL"), buff, bp);
+      else
+       safe_str(T("#-1 PERMISSION DENIED"), buff, bp);
+      return;
+    }
+    ok &= !Chanuser_Hide(u);
+    if (!(can_ex || ok)) {
+      safe_str(T("#-1 PERMISSION DENIED"), buff, bp);
+      return;
+    }
+    if (CUtitle(u))
+       safe_str(CUtitle(u), buff, bp);
+    break;
+  }
+}
+
+FUNCTION(fun_cowner)
+{
+  /* Return the dbref of the owner of a channel. */
+  CHAN *c;
+
+  if (!args[0] || !*args[0]) {
+    safe_str(T("#-1 NO CHANNEL GIVEN"), buff, bp);
+    return;
+  }
+  switch (find_channel(args[0], &c, executor)) {
+  case CMATCH_NONE:
+    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
+    break;
+  case CMATCH_AMBIG:
+    safe_str(T("#-1 AMBIGUOUS CHANNEL NAME"), buff, bp);
+    break;
+  default:
+    safe_dbref(ChanCreator(c), buff, bp);
+  }
+
+}
+
+/* Remove all players from a channel, notifying them. This is the 
+ * utility routine for handling it. The command @channel/wipe
+ * calls do_chan_wipe, below
+ */
+static void
+channel_wipe(dbref player, CHAN *chan)
+{
+  CHANUSER *u, *nextu;
+  dbref victim;
+  /* This is easy. Just call remove_user on each user in the list */
+  if (!chan)
+    return;
+  for (u = ChanUsers(chan); u; u = nextu) {
+    nextu = u->next;
+    victim = CUdbref(u);
+    if (remove_user(u, chan))
+       notify_format(victim, T("CHAT: %s has removed all users from <%s>."),
+                    Name(player), ChanName(chan));
+  }
+  ChanNumUsers(chan) = 0;
+  return;
+}
+
+/** Remove all players from a channel.
+ * \verbatim
+ * This is the top-level function for @channel/wipe, which removes all
+ * players from a channel.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of channel to wipe.
+ */
+void
+do_chan_wipe(dbref player, const char *name)
+{
+  CHAN *c;
+  /* Find the channel */
+  test_channel(player, name, c);
+  /* Check permissions */
+  if (!Chan_Can_Modify(c, player)) {
+    notify(player, T("CHAT: Wipe that silly grin off your face instead."));
+    return;
+  }
+  /* Wipe it */
+  channel_wipe(player, c);
+  notify_format(player, T("CHAT: Channel <%s> wiped."), ChanName(c));
+  return;
+}
+
+/** Change the owner of a channel.
+ * \verbatim
+ * This is the top-level function for @channel/chown, which changes
+ * ownership of a channel.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the channel.
+ * \param newowner name of the new owner for the channel.
+ */
+void
+do_chan_chown(dbref player, const char *name, const char *newowner)
+{
+  CHAN *c;
+  dbref victim;
+  /* Only a Director can do this */
+  if (!Director(player)) {
+    notify(player, T("CHAT: Only a Director can do that."));
+    return;
+  }
+  /* Find the channel */
+  test_channel(player, name, c);
+  /* Find the victim */
+  if ((victim = lookup_player(newowner)) == NOTHING) {
+    notify(player, T("CHAT: Invalid owner."));
+    return;
+  }
+  /* We refund the original owner's money, but don't charge the
+   * new owner. 
+   */
+  chan_chown(c, victim);
+  notify_format(player,
+               T("CHAT: Channel <%s> now owned by %s."), ChanName(c),
+               Name(ChanCreator(c)));
+  return;
+}
+
+/** Chown all of a player's channels. 
+ * This function changes ownership of all of a player's channels. It's
+ * usually used before destroying the player.
+ * \param old dbref of old channel owner.
+ * \param newowner dbref of new channel owner.
+ */
+void
+chan_chownall(dbref old, dbref newowner)
+{
+  CHAN *c;
+
+  /* Run the channel list. If a channel is owned by old, chown it
+     silently to newowner */
+  for (c = channels; c; c = c->next) {
+    if (ChanCreator(c) == old)
+      chan_chown(c, newowner);
+  }
+}
+
+/* The actual chowning of a channel */
+static void
+chan_chown(CHAN *c, dbref victim)
+{
+  giveto(ChanCreator(c), ChanCost(c));
+  ChanCreator(c) = victim;
+  ChanCost(c) = 0;
+  return;
+}
+
+/** Lock one of the channel's locks.
+ * \verbatim
+ * This is the top-level function for @clock.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the channel.
+ * \param lockstr string representation of the lock value.
+ * \param whichlock which lock is to be set.
+ */
+void
+do_chan_lock(dbref player, const char *name, const char *lockstr, int whichlock)
+{
+  CHAN *c;
+  boolexp key;
+  const char *ltype;
+
+  /* Make sure the channel exists */
+  test_channel(player, name, c);
+  /* Make sure the player has permission */
+  if (!Chan_Can_Modify(c, player)) {
+    notify_format(player, T("CHAT: Channel %s resists."), ChanObjName(c));
+    return;
+  }
+  /* Ok, let's do it */
+  switch (whichlock) {
+  case CL_JOIN:
+    ltype = chan_join_lock;
+    break;
+  case CL_MOD:
+    ltype = chan_mod_lock;
+    break;
+  case CL_SEE:
+    ltype = chan_see_lock;
+    break;
+  case CL_HIDE:
+    ltype = chan_hide_lock;
+    break;
+  case CL_SPEAK:
+    ltype = chan_speak_lock;
+    break;
+  default:
+    ltype = "ChanUnknownLock";
+  }
+
+  if (!lockstr || !*lockstr) {
+    /* Unlock it */
+    key = TRUE_BOOLEXP;
+  } else {
+    key = parse_boolexp(player, lockstr, ltype);
+    if (key == TRUE_BOOLEXP) {
+      notify(player, T("CHAT: I don't understand that key."));
+      return;
+    }
+  }
+  switch (whichlock) {
+  case CL_JOIN:
+    free_boolexp(ChanJoinLock(c));
+    ChanJoinLock(c) = key;
+    notify_format(player, (key == TRUE_BOOLEXP) ?
+                 T("CHAT: Joinlock on <%s> reset.") :
+                 T("CHAT: Joinlock on <%s> set."), ChanName(c));
+    break;
+  case CL_SPEAK:
+    free_boolexp(ChanSpeakLock(c));
+    ChanSpeakLock(c) = key;
+    notify_format(player, (key == TRUE_BOOLEXP) ?
+                 T("CHAT: Speaklock on <%s> reset.") :
+                 T("CHAT: Speaklock on <%s> set."), ChanName(c));
+    break;
+  case CL_SEE:
+    free_boolexp(ChanSeeLock(c));
+    ChanSeeLock(c) = key;
+    notify_format(player, (key == TRUE_BOOLEXP) ?
+                 T("CHAT: Seelock on <%s> reset.") :
+                 T("CHAT: Seelock on <%s> set."), ChanName(c));
+    break;
+  case CL_HIDE:
+    free_boolexp(ChanHideLock(c));
+    ChanHideLock(c) = key;
+    notify_format(player, (key == TRUE_BOOLEXP) ?
+                 T("CHAT: Hidelock on <%s> reset.") :
+                 T("CHAT: Hidelock on <%s> set."), ChanName(c));
+    break;
+  case CL_MOD:
+    free_boolexp(ChanModLock(c));
+    ChanModLock(c) = key;
+    notify_format(player, (key == TRUE_BOOLEXP) ?
+                 T("CHAT: Modlock on <%s> reset.") :
+                 T("CHAT: Modlock on <%s> set."), ChanName(c));
+    break;
+  }
+  return;
+}
+
+
+/** A channel list with names and descriptions only.
+ * \verbatim
+ * This is the top-level function for @channel/what.
+ * \endverbatim
+ * \param player the enactor.
+ * \param partname a partial name of channels to match.
+ */
+void
+do_chan_what(dbref player, const char *partname)
+{
+  CHAN *c;
+  int found = 0;
+  char *cleanname;
+  char cleanp[CHAN_NAME_LEN];
+
+  cleanname = normalize_channel_name(partname);
+  for (c = channels; c; c = c->next) {
+    strcpy(cleanp, remove_markup(ChanName(c), NULL));
+    if (Chan_Can_See(c, player) && string_prefix(cleanp, cleanname)) {
+      notify(player, ChanName(c));
+      notify_format(player, T("Description: %s"), ChanTitle(c));
+      notify_format(player, T("Owner: %s"), Name(ChanCreator(c)));
+      notify_format(player, T("Flags: %s"),
+                   privs_to_string(priv_table, ChanType(c))); 
+      if(ChanType(c) & CHANNEL_COBJ) {
+       if(player == ChanCreator(c) || Director(player)
+               || Visual(ChanObj(c))
+               || controls(player, Owner(ChanCreator(c))))
+          notify_format(player, "Channel object: %s",
+                               object_header(player, ChanObj(c)));
+      }
+
+      if (ChanBufferQ(c))
+       notify_format(player,
+                     T("Recall buffer: %dk, can hold %d."),
+                     BufferQSize(ChanBufferQ(c)), bufferq_lines(ChanBufferQ(c)));
+      found++;
+    }
+  }
+  if (!found)
+    notify(player, T("CHAT: I don't recognize that channel."));
+}
+
+
+/** A decompile of a channel.
+ * \verbatim
+ * This is the top-level function for @channel/decompile, which attempts
+ * to produce all the MUSHcode necessary to recreate a channel and its
+ * membership.
+ * \param player the enactor.
+ * \param name name of the channel.
+ * \param brief if 1, don't include channel membership.
+ */
+void
+do_chan_decompile(dbref player, const char *name, int brief)
+{
+  CHAN *c;
+  CHANUSER *u;
+  int found;
+  char cleanname[BUFFER_LEN];
+  char cleanp[CHAN_NAME_LEN];
+
+  found = 0;
+  strcpy(cleanname, remove_markup(name, NULL));
+  for (c = channels; c; c = c->next) {
+    strcpy(cleanp, remove_markup(ChanName(c), NULL));
+    if (string_prefix(cleanp, cleanname)) {
+      found++;
+      if (!(See_All(player) || Chan_Can_Modify(c, player)
+           || (ChanCreator(c) == player))) {
+       if (Chan_Can_See(c, player))
+         notify_format(player, T("CHAT: No permission to decompile <%s>"),
+                       ChanName(c));
+       continue;
+      }
+      notify_format(player, "@channel/add %s = %s", ChanName(c),
+                   privs_to_string(priv_table, ChanType(c)));
+      notify_format(player, "@channel/chown %s = %s", ChanName(c),
+                   Name(ChanCreator(c)));
+      if(ChanObj(c) != NOTHING)
+       notify_format(player, "@cobj %s=%s", ChanName(c), unparse_dbref(ChanObj(c)));
+      if (ChanModLock(c) != TRUE_BOOLEXP)
+       notify_format(player, "@clock/mod %s = %s", ChanName(c),
+                     unparse_boolexp(player, ChanModLock(c), UB_MEREF));
+      if (ChanHideLock(c) != TRUE_BOOLEXP)
+       notify_format(player, "@clock/hide %s = %s", ChanName(c),
+                     unparse_boolexp(player, ChanHideLock(c), UB_MEREF));
+      if (ChanJoinLock(c) != TRUE_BOOLEXP)
+       notify_format(player, "@clock/join %s = %s", ChanName(c),
+                     unparse_boolexp(player, ChanJoinLock(c), UB_MEREF));
+      if (ChanSpeakLock(c) != TRUE_BOOLEXP)
+       notify_format(player, "@clock/speak %s = %s", ChanName(c),
+                     unparse_boolexp(player, ChanSpeakLock(c), UB_MEREF));
+      if (ChanSeeLock(c) != TRUE_BOOLEXP)
+       notify_format(player, "@clock/see %s = %s", ChanName(c),
+                     unparse_boolexp(player, ChanSeeLock(c), UB_MEREF));
+      if (ChanTitle(c))
+       notify_format(player, "@channel/desc %s = %s", ChanName(c),
+                     ChanTitle(c));
+      if (ChanBufferQ(c))
+       notify_format(player, "@channel/buffer %s = %d", ChanName(c),
+                     bufferq_lines(ChanBufferQ(c)));
+      if (!brief) {
+       for (u = ChanUsers(c); u; u = u->next) {
+         if (!Chanuser_Hide(u) || Priv_Who(player))
+
+            notify_format(player, "@channel/on %s = %s", ChanName(c),
+                          Name(CUdbref(u)));
+       }
+      }
+    }
+  }
+  if (!found)
+    notify(player, T("CHAT: No channel matches that string."));
+}
+
+static void
+do_channel_who(dbref player, CHAN *chan)
+{
+  char tbuf1[BUFFER_LEN];
+  char *bp;
+  CHANUSER *u;
+  dbref who;
+  int sf, i = 0;
+  bp = tbuf1;
+  for (u = ChanUsers(chan); u; u = u->next) {
+    who = CUdbref(u);
+    if ((IsThing(who) || Connected(who)) &&
+       (!Chanuser_Hide(u) || Priv_Who(player))) {
+      i++;
+      safe_itemizer(i, !(u->next), ",", T("and"), " ", tbuf1, &bp);
+      safe_str(Name(who), tbuf1, &bp);
+      if (IsThing(who))
+       safe_format(tbuf1, &bp, "(#%d)", who);
+      sf = 0;
+      if (Chanuser_Hide(u) || Chanuser_Gag(u) || (RPMODE(u->who) && !Can_RPCHAT(u->who))) {
+             safe_str(" (", tbuf1, &bp);
+             sf++;
+      }
+      if (Chanuser_Hide(u)) {
+        safe_str("hidden", tbuf1, &bp);
+        sf++;
+      }
+      if(Chanuser_Gag(u)) {
+        if(sf > 1) safe_chr(',', tbuf1, &bp);
+        safe_str("gagging", tbuf1, &bp);
+        sf++;
+      }
+      if(RPMODE(u->who) && !Can_RPCHAT(u->who)) {
+             if(sf > 1) safe_chr(',', tbuf1, &bp);
+             safe_str("rpgag", tbuf1, &bp);
+             sf++;
+      }
+      if(sf > 0)
+             safe_chr(')', tbuf1, &bp);
+    }
+  }
+  *bp = '\0';
+  if (!*tbuf1)
+    notify(player, T("There are no connected players on that channel."));
+  else {
+    notify_format(player, T("Members of channel <%s> are:"), ChanName(chan));
+    notify(player, tbuf1);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cwho)
+{
+  int first = 1;
+  CHAN *chan = NULL;
+  CHANUSER *u;
+  dbref who;
+
+  switch (find_channel(args[0], &chan, executor)) {
+  case CMATCH_NONE:
+    notify(executor, T("No such channel."));
+    return;
+  case CMATCH_AMBIG:
+    notify(executor, T("I can't tell which channel you mean."));
+    return;
+  default:
+    break;
+  }
+
+  /* Feh. We need to do some sort of privilege checking, so that
+   * if mortals can't do '@channel/who wizard', they can't do
+   * 'think cwho(wizard)' either. The first approach that comes to
+   * mind is the following:
+   * if (!ChannelPermit(privs,chan)) ...
+   * Unfortunately, we also want objects to be able to check cwho()
+   * on channels.
+   * So, we check the owner, instead, because most uses of cwho()
+   * are either in the Master Room owned by a wizard, or on people's
+   * quicktypers.
+   */
+
+  if (!Chan_Can_See(chan, Owner(executor))
+      && !Chan_Can_See(chan, executor)) {
+    safe_str(T("#-1 NO PERMISSIONS FOR CHANNEL"), buff, bp);
+    return;
+  }
+  for (u = ChanUsers(chan); u; u = u->next) {
+    who = CUdbref(u);
+    if ((IsThing(who) || Connected(who)) &&
+       (!Chanuser_Hide(u) || Priv_Who(executor))) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_dbref(who, buff, bp);
+    }
+  }
+}
+
+
+/** Modify a channel's description.
+ * \verbatim
+ * This is the top-level function for @channel/desc, which sets a channel's
+ * description.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the channel.
+ * \param title description of the channel.
+ */
+void
+do_chan_desc(dbref player, const char *name, const char *title)
+{
+  CHAN *c;
+  /* Check new title length */
+  if (title && strlen(title) > CHAN_TITLE_LEN - 1) {
+    notify(player, T("CHAT: New description too long."));
+    return;
+  }
+  /* Make sure the channel exists */
+  test_channel(player, name, c);
+  /* Make sure the player has permission */
+  if (!Chan_Can_Modify(c, player)) {
+    notify(player, "CHAT: Yeah, right.");
+    return;
+  }
+  /* Ok, let's do it */
+  if (!title || !*title) {
+    ChanTitle(c)[0] = '\0';
+    notify_format(player, T("CHAT: Channel <%s> description cleared."),
+                 ChanName(c));
+  } else {
+    strcpy(ChanTitle(c), title);
+    notify_format(player, T("CHAT: Channel <%s> description set."),
+                 ChanName(c));
+  }
+}
+
+
+
+static int
+yesno(const char *str)
+{
+  if (!str || !*str)
+    return ERR;
+  switch (str[0]) {
+  case 'y':
+  case 'Y':
+    return CYES;
+  case 'n':
+  case 'N':
+    return CNO;
+  case 'o':
+  case 'O':
+    switch (str[1]) {
+    case 'n':
+    case 'N':
+      return CYES;
+    case 'f':
+    case 'F':
+      return CNO;
+    default:
+      return ERR;
+    }
+  default:
+    return ERR;
+  }
+}
+
+/* Can this player still add channels, or have they created their
+ * limit already?
+ */
+static int
+canstilladd(dbref player)
+{
+  CHAN *c;
+  int num = 0;
+  for (c = channels; c; c = c->next) {
+    if (ChanCreator(c) == player)
+      num++;
+  }
+  return (num < MAX_PLAYER_CHANS);
+}
+
+
+
+/** Tell players on a channel when someone connects or disconnects.
+ * \param player player that is connecting or disconnecting.
+ * \param msg message to announce.
+ * \param ungag if 1, remove any channel gags the player has.
+ */
+void
+chat_player_announce(dbref player, char *msg, int ungag)
+{
+  CHAN *c;
+  CHANUSER *u;
+  char buff[BUFFER_LEN], *bp;
+
+  for (c = channels; c; c = c->next) {
+    u = onchannel(player, c);
+    if (u) {
+      if (!Channel_Quiet(c) && (Channel_Admin(c) || Channel_Director(c)
+                               || (!Chanuser_Hide(u) && !Dark(player)))) {
+       bp = buff;
+
+       safe_format(buff, &bp, "%s %s", "%s", msg);
+       *bp = '\0';
+       format_channel_broadcast(c, u, player, CB_CHECKQUIET | CB_PRESENCE,
+                                buff, NULL);
+      }
+      if (ungag)
+       CUtype(u) &= ~CU_GAG;
+    }
+  }
+}
+
+/** Return a list of channels that the player is on.
+ * \param player player whose channels are to be shown.
+ * \return string listing player's channels, prefixed with Channels:
+ */
+const char *
+channel_description(dbref player)
+{
+  static char buf[BUFFER_LEN];
+  CHANLIST *c;
+
+  *buf = '\0';
+
+  if (Chanlist(player)) {
+    strcpy(buf, T("Channels:"));
+    for (c = Chanlist(player); c; c = c->next)
+      sprintf(buf, "%s %s", buf, ChanName(c->chan));
+  } else if (IsPlayer(player))
+    strcpy(buf, T("Channels: *NONE*"));
+  return buf;
+}
+
+
+FUNCTION(fun_channels)
+{
+  dbref it;
+  char sep = ' ';
+  CHAN *c;
+  CHANLIST *cl;
+  CHANUSER *u;
+  int can_ex;
+
+  /* There are these possibilities:
+   *  no args - just a list of all channels
+   *   2 args - object, delimiter
+   *   1 arg - either object or delimiter. If it's longer than 1 char,
+   *           we treat it as an object.
+   * You can see an object's channels if you can examine it.
+   * Otherwise you can see only channels that you share with
+   * it where it's not hidden.
+   */
+  if (nargs >= 1) {
+    /* Given an argument, return list of channels it's on */
+    it = match_result(executor, args[0], NOTYPE, MAT_EVERYTHING);
+    if (GoodObject(it)) {
+      int first = 1;
+      if (!delim_check(buff, bp, nargs, args, 2, &sep))
+       return;
+      can_ex = Can_Examine(executor, it);
+      for (cl = Chanlist(it); cl; cl = cl->next) {
+       if (can_ex || ((u = onchannel(it, cl->chan)) &&!Chanuser_Hide(u)
+                      && onchannel(executor, cl->chan))) {
+         if (!first)
+           safe_chr(sep, buff, bp);
+         safe_str(ChanName(cl->chan), buff, bp);
+         first = 0;
+       }
+      }
+      return;
+    } else {
+      /* args[0] didn't match. Maybe it's a delimiter? */
+      if (arglens[0] > 1) {
+       if (it == NOTHING)
+         notify(executor, T("I can't see that here."));
+       else if (it == AMBIGUOUS)
+         notify(executor, T("I don't know which thing you mean."));
+       return;
+      } else if (!delim_check(buff, bp, nargs, args, 1, &sep))
+       return;
+    }
+  }
+  /* No arguments (except maybe delimiter) - return list of all channels */
+  for (c = channels; c; c = c->next) {
+    if (Chan_Can_See(c, executor)) {
+      if (c != channels)
+       safe_chr(sep, buff, bp);
+      safe_str(ChanName(c), buff, bp);
+    }
+  }
+  return;
+}
+
+FUNCTION(fun_clock)
+{
+  CHAN *c = NULL;
+  char *p = NULL;
+  boolexp lock_ptr = TRUE_BOOLEXP;
+  int which_lock = 0;
+
+  if ((p = strchr(args[0], '/'))) {
+    *p++ = '\0';
+  } else {
+    p = (char *) "JOIN";
+  }
+
+  switch (find_channel(args[0], &c, executor)) {
+  case CMATCH_NONE:
+    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
+    return;
+  case CMATCH_AMBIG:
+    safe_str("#-2 AMBIGUOUS CHANNEL MATCH", buff, bp);
+    return;
+  }
+
+  if (!strcasecmp(p, "JOIN")) {
+    which_lock = CL_JOIN;
+    lock_ptr = ChanJoinLock(c);
+  } else if (!strcasecmp(p, "SPEAK")) {
+    which_lock = CL_SPEAK;
+    lock_ptr = ChanSpeakLock(c);
+  } else if (!strcasecmp(p, "MOD")) {
+    which_lock = CL_MOD;
+    lock_ptr = ChanModLock(c);
+  } else if (!strcasecmp(p, "SEE")) {
+    which_lock = CL_SEE;
+    lock_ptr = ChanSeeLock(c);
+  } else if (!strcasecmp(p, "HIDE")) {
+    which_lock = CL_HIDE;
+    lock_ptr = ChanHideLock(c);
+  } else {
+    safe_str(T("#-1 NO SUCH LOCK TYPE"), buff, bp);
+    return;
+  }
+
+  if (nargs == 2) {
+    if (FUNCTION_SIDE_EFFECTS) {
+      if (!command_check_byname(executor, "@clock") || fun->flags & FN_NOSIDEFX) {
+       safe_str(T(e_perm), buff, bp);
+       return;
+      }
+      do_chan_lock(executor, args[0], args[1], which_lock);
+      return;
+    } else {
+      safe_str(T(e_disabled), buff, bp);
+    }
+  }
+
+  if (Chan_Can_Decomp(c, executor)) {
+    safe_str(unparse_boolexp(executor, lock_ptr, 1), buff, bp);
+    return;
+  } else {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cemit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = PEMIT_SILENT;
+  flags |= (ns ? PEMIT_SPOOF : 0);
+  if (!command_check_byname(executor, ns ? "@nscemit" : "@cemit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if (nargs == 3 && parse_boolean(args[2]))
+    flags &= ~PEMIT_SILENT;
+  orator = executor;
+  do_cemit(executor, args[0], args[1], flags);
+}
+
+COMMAND (cmd_cemit) {
+  int spflags = !strcmp(cmd->name, "@NSCEMIT") ? PEMIT_SPOOF : 0;
+  SPOOF(player, cause, sw);
+  if (!SW_ISSET(sw, SWITCH_NOISY))
+    spflags |= PEMIT_SILENT;
+  do_cemit(player, arg_left, arg_right, spflags);
+}
+
+COMMAND (cmd_channel) {
+  if (switches)
+    do_channel(player, arg_left, args_right[1], switches);
+  else if (SW_ISSET(sw, SWITCH_LIST))
+    do_channel_list(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_ADD))
+    do_chan_admin(player, arg_left, args_right[1], 0);
+  else if (SW_ISSET(sw, SWITCH_DELETE))
+    do_chan_admin(player, arg_left, args_right[1], 1);
+  else if (SW_ISSET(sw, SWITCH_NAME))
+    do_chan_admin(player, arg_left, args_right[1], 2);
+  else if (SW_ISSET(sw, SWITCH_RENAME))
+    do_chan_admin(player, arg_left, args_right[1], 2);
+  else if (SW_ISSET(sw, SWITCH_PRIVS))
+    do_chan_admin(player, arg_left, args_right[1], 3);
+  else if (SW_ISSET(sw, SWITCH_RECALL))
+    do_chan_recall(player, arg_left, args_right, SW_ISSET(sw, SWITCH_QUIET));
+  else if (SW_ISSET(sw, SWITCH_DECOMPILE))
+    do_chan_decompile(player, arg_left, SW_ISSET(sw, SWITCH_BRIEF));
+  else if (SW_ISSET(sw, SWITCH_DESCRIBE))
+    do_chan_desc(player, arg_left, args_right[1]);
+  else if (SW_ISSET(sw, SWITCH_TITLE))
+    do_chan_title(player, arg_left, args_right[1]);
+  else if (SW_ISSET(sw, SWITCH_CHOWN))
+    do_chan_chown(player, arg_left, args_right[1]);
+  else if (SW_ISSET(sw, SWITCH_WIPE))
+    do_chan_wipe(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_MUTE))
+    do_chan_user_flags(player, arg_left, args_right[1], 0, 0);
+  else if (SW_ISSET(sw, SWITCH_UNMUTE))
+    do_chan_user_flags(player, arg_left, "n", 0, 0);
+  else if (SW_ISSET(sw, SWITCH_HIDE))
+    do_chan_user_flags(player, arg_left, args_right[1], 1, 0);
+  else if (SW_ISSET(sw, SWITCH_UNHIDE))
+    do_chan_user_flags(player, arg_left, "n", 1, 0);
+  else if (SW_ISSET(sw, SWITCH_GAG))
+    do_chan_user_flags(player, arg_left, args_right[1], 2, 0);
+  else if (SW_ISSET(sw, SWITCH_UNGAG))
+    do_chan_user_flags(player, arg_left, "n", 2, 0);
+  else if (SW_ISSET(sw, SWITCH_WHAT))
+    do_chan_what(player, arg_left);
+  else if (SW_ISSET(sw, SWITCH_BUFFER))
+    do_chan_buffer(player, arg_left, args_right[1]);
+  else
+    do_channel(player, arg_left, NULL, args_right[1]);
+}
+
+COMMAND (cmd_chat) {
+  do_chat_by_name(player, arg_left, arg_right, 1);
+}
+
+COMMAND(cmd_cobj) {
+  if(SW_ISSET(sw, SWITCH_RESET)) {
+      do_reset_cobj(player,arg_left);
+      return;
+      }
+  do_set_cobj(player, arg_left, arg_right);
+}
+
+
+COMMAND (cmd_clock) {
+  if (SW_ISSET(sw, SWITCH_JOIN))
+    do_chan_lock(player, arg_left, arg_right, CL_JOIN);
+  else if (SW_ISSET(sw, SWITCH_SPEAK))
+    do_chan_lock(player, arg_left, arg_right, CL_SPEAK);
+  else if (SW_ISSET(sw, SWITCH_MOD))
+    do_chan_lock(player, arg_left, arg_right, CL_MOD);
+  else if (SW_ISSET(sw, SWITCH_SEE))
+    do_chan_lock(player, arg_left, arg_right, CL_SEE);
+  else if (SW_ISSET(sw, SWITCH_HIDE))
+    do_chan_lock(player, arg_left, arg_right, CL_HIDE);
+  else
+    notify(player, T("You must specify a type of lock"));
+}
+
+/** Find the next player on a channel to notify.
+ * This function is a helper for notify_anything that is used to
+ * notify all players on a channel.
+ * \param current next dbref to notify (not used).
+ * \param data pointer to structure containing channel and chanuser data.
+ * \return next dbref to notify.
+ */
+dbref
+na_channel(dbref current, void *data)
+{
+  struct na_cpass *nac = data;
+  CHANUSER *u, *nu;
+  int cont;
+
+  nu = nac->u;
+  do {
+    u = nu;
+    if (!u)
+      return NOTHING;
+    current = CUdbref(u);
+    nu = u->next;
+    cont = (!GoodObject(current) ||
+           (nac->checkquiet && Chanuser_Quiet(u)) ||
+           Chanuser_Gag(u) ||  (RPMODE(current) && !Can_RPCHAT(current))
+           || (IsPlayer(current) && !Connected(current)));
+  } while (cont);
+  nac->u = nu;
+  return current;
+}
+
+/** Broadcast a message to a channel.
+ * \param channel pointer to channel to broadcast to.
+ * \param player message speaker.
+ * \param flags broadcast flag mask (see CB_* constants in extchat.h)
+ * \param fmt message format string.
+ */
+void WIN32_CDECL
+channel_broadcast(CHAN *channel, dbref player, int flags, const char *fmt, ...)
+/* flags: 0x1 = checkquiet, 0x2 = nospoof */
+{
+  va_list args;
+#ifdef HAS_VSNPRINTF
+  char tbuf1[BUFFER_LEN];
+#else
+  char tbuf1[BUFFER_LEN * 2];  /* Safety margin as per tprintf */
+#endif
+  struct na_cpass nac;
+  int na_flags = NA_INTER_LOCK;
+
+  /* Make sure we can write to the channel before doing so */
+  if (Channel_Disabled(channel))
+    return;
+
+  va_start(args, fmt);
+
+#ifdef HAS_VSNPRINTF
+  (void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
+#else
+  (void) vsprintf(tbuf1, fmt, args);
+#endif
+  va_end(args);
+  tbuf1[BUFFER_LEN - 1] = '\0';
+
+  nac.u = ChanUsers(channel);
+  nac.checkquiet = (flags & CB_CHECKQUIET) ? 1 : 0;
+  if (Channel_Interact(channel))
+    na_flags |= (flags & CB_PRESENCE) ? NA_INTER_PRESENCE : NA_INTER_HEAR;
+  notify_anything(player, na_channel, &nac, ns_esnotify,
+                 na_flags | ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), tbuf1);
+  if (ChanBufferQ(channel))
+    add_to_bufferq(ChanBufferQ(channel), 0,
+                  (flags & CB_NOSPOOF) ? player : NOTHING, tbuf1);
+}
+
+
+/** Recall past lines from the channel's buffer.
+ * We try to recall no more lines that are requested by the player,
+ * but sometimes we may have fewer to recall.
+ * \verbatim
+ * This is the top-level function for @chan/recall.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the channel.
+ * \param lineinfo pointer to array containing lines, optional start
+ * \param quiet if true, don't show timestamps.
+ */
+void
+do_chan_recall(dbref player, const char *name, char *lineinfo[], int quiet)
+{
+  CHAN *chan;
+  CHANUSER *u;
+  const char *lines;
+  const char *startpos;
+  int num_lines = 10;          /* Default if none is given */
+  int start = -1;
+  int all;
+  char *p = NULL, *buf;
+  time_t timestamp;
+  char *stamp;
+  dbref speaker;
+  int type;
+
+  if (!name || !*name) {
+    notify(player, T("You need to specify a channel."));
+    return;
+  }
+  lines = lineinfo[1];
+  startpos = lineinfo[2];
+  if (startpos && *startpos) {
+    if (!is_integer(startpos)) {
+      notify(player, T("Which line do you want to start recall from?"));
+      return;
+    }
+    start = parse_integer(startpos) - 1;
+  }
+  if (lines && *lines) {
+    if (is_integer(lines)) {
+      num_lines = parse_integer(lines);
+      if (num_lines == 0)
+       num_lines = INT_MAX;
+    } else {
+      notify(player, T("How many lines did you want to recall?"));
+      return;
+    }
+  }
+  if (num_lines < 1) {
+    notify(player, T("How many lines did you want to recall?"));
+    return;
+  }
+
+  test_channel(player, name, chan);
+  if (!Chan_Can_See(chan, player)) {
+    if (OnChannel(player, chan))
+      notify_format(player,
+                   T("CHAT: You can't do that with channel <%s>."),
+                   ChanName(chan));
+    else
+      notify(player, T("CHAT: I don't recognize that channel."));
+    return;
+  }
+  u = onchannel(player, chan);
+  if (!u &&!Chan_Can_Access(chan, player)) {
+    notify(player, T("CHAT: You must join a channel to recall from it."));
+    return;
+  }
+  if (!ChanBufferQ(chan)) {
+    notify(player, T("CHAT: That channel doesn't have a recall buffer."));
+    return;
+  }
+  if (start < 0)
+    start = BufferQNum(ChanBufferQ(chan)) - num_lines;
+  if (isempty_bufferq(ChanBufferQ(chan))
+      || (BufferQNum(ChanBufferQ(chan)) <= start)) {
+    notify(player, T("CHAT: Nothing to recall."));
+    return;
+  }
+
+  all = (start <= 0 && num_lines >= BufferQNum(ChanBufferQ(chan)));
+  notify_format(player, T("CHAT: Recall from channel <%s>"), ChanName(chan));
+  while (start > 0) {
+    buf = iter_bufferq(ChanBufferQ(chan), &p, &speaker, &type, &timestamp);
+    start--;
+  }
+  while ((buf = iter_bufferq(ChanBufferQ(chan), &p, &speaker, &type,
+                            &timestamp)) && num_lines > 0) {
+    if (Nospoof(player) && GoodObject(speaker)) {
+      char *nsmsg = ns_esnotify(speaker, na_one, &player,
+                               Paranoid(player) ? 1 : 0);
+      if (quiet)
+       notify_format(player, T("%s %s"), nsmsg, buf);
+      else {
+       stamp = show_time(timestamp, 0);
+       notify_format(player, T("[%s] %s %s"), stamp, nsmsg, buf);
+      }
+      mush_free(nsmsg, "string");
+    } else {
+      if (quiet)
+       notify(player, buf);
+      else {
+       stamp = show_time(timestamp, 0);
+       notify_format(player, T("[%s] %s"), stamp, buf);
+      }
+    }
+    num_lines--;
+  }
+  notify(player, T("CHAT: End recall"));
+  if (!all)
+    notify_format(player,
+                 T
+                 ("CHAT: To recall the entire buffer, use @chan/recall %s=0"),
+                 ChanName(chan));
+}
+
+/** Set the size of a channel's buffer in maximum lines.
+ * \verbatim
+ * This is the top-level function for @chan/buffer.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the channel.
+ * \param lines a string given the number of lines to buffer.
+ */
+void
+do_chan_buffer(dbref player, const char *name, const char *lines)
+{
+  CHAN *chan;
+  int size;
+
+  if (!name || !*name) {
+    notify(player, T("You need to specify a channel."));
+    return;
+  }
+  if (!lines || !*lines || !is_integer(lines)) {
+    notify(player, T("You need to specify the number of lines to buffer."));
+    return;
+  }
+  size = parse_integer(lines);
+  if (size < 0 || size > 10) {
+    notify(player, T("Invalid buffer size."));
+    return;
+  }
+  test_channel(player, name, chan);
+  if (!Chan_Can_Modify(chan, player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (!size) {
+    /* Remove a channel's buffer */
+    if (ChanBufferQ(chan)) {
+      free_bufferq(ChanBufferQ(chan));
+      ChanBufferQ(chan) = NULL;
+      notify_format(player,
+                   T("CHAT: Channel buffering disabled for channel <%s>."),
+                   ChanName(chan));
+    } else {
+      notify_format(player,
+                   T
+                   ("CHAT: Channel buffering already disabled for channel <%s>."),
+                   ChanName(chan));
+    }
+  } else {
+    if (ChanBufferQ(chan)) {
+      /* Resize a buffer */
+      ChanBufferQ(chan) = reallocate_bufferq(ChanBufferQ(chan), size);
+      notify_format(player, T("CHAT: Resizing buffer of channel <%s>"),
+                   ChanName(chan));
+    } else {
+      /* Start a new buffer */
+      ChanBufferQ(chan) = allocate_bufferq(size);
+      notify_format(player,
+                   T("CHAT: Buffering enabled on channel <%s>."),
+                   ChanName(chan));
+    }
+  }
+}
+
+static void
+format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim, int flags,
+                        const char *msg, const char *extra)
+{
+  const char *title = NULL;
+  if (extra && *extra)
+    title = extra;
+  else if (u &&CUtitle(u))
+     title = CUtitle(u);
+
+  if (Channel_NoNames(chan)) {
+    if (Channel_NoTitles(chan) || !title)
+      channel_broadcast(chan, victim, flags, msg, ChanObjName(chan), "Someone");
+    else
+      channel_broadcast(chan, victim, flags, msg, ChanObjName(chan), title);
+  } else
+    channel_broadcast(chan, victim, flags, msg, ChanObjName(chan), Name(victim));
+}
+
+
+static void
+do_reset_cobj(player, name)
+      dbref player;
+      const char *name;
+{
+  CHAN *c;
+  /* Test Channel */
+   test_channel(player, name, c);
+  if(!Chan_Can_Modify(c, player)) {
+     notify(player, "CHAT: Oh come on.");
+     return;
+    }
+   /* Now lets reset teh chan obj stuff on the channel */
+    ChanType(c) &= ~CHANNEL_COBJ;
+    ChanObj(c) = -1;
+    notify(player,"ChanObj Reset.");
+}
+
+    
+static void
+do_set_cobj(player, name, obj) 
+        dbref player;
+       const char *name;
+       const char *obj;
+{
+  CHAN *c;
+  dbref cobj;
+  /* Test the channel */
+    test_channel(player, name, c);
+    
+    if(!Chan_Can_Modify(c, player)) {
+      notify(player, "CHAT: Oh come on.");
+      return;
+      }
+      
+     /* Not sure if all this shit works right, so be careful */
+
+     cobj = match_result(player, obj, TYPE_THING, MAT_ABSOLUTE);
+              
+     if(cobj == -1) {
+        notify(player, "Invalid Object");
+        return;
+       }
+     
+     
+     if(cobj == -2) {
+         notify(player, "Ambigious Object");
+         return; 
+          }
+
+     if(!controls(player,cobj)) {
+         notify(player,"You must own that object first");
+        return;
+        }
+       
+     if(Typeof(cobj) != TYPE_THING) {
+         notify(player, "Must be an object");
+         return;
+     }
+
+     ChanType(c) |= CHANNEL_COBJ;
+     ChanObj(c) = cobj;
+
+     notify(player,tprintf("Channel object for %s is now #%ld", ChanName(c),
+                 ChanObj(c)) );
+
+}
+
+int ChanObjCheck(CHAN *c)
+ {
+ if(ChanType(c) & CHANNEL_COBJ) {
+  if(Typeof(ChanObj(c)) == TYPE_GARBAGE) {
+    ChanType(c) &= ~CHANNEL_COBJ;
+    ChanObj(c) = -1;
+    return 0;
+    } else return 1;
+   } else return 0;    
+}
+
+const char *
+ChanObjName(CHAN *c)
+  {
+    ATTR *nm;
+    static char buff[BUFFER_LEN];
+    static char tbuf[BUFFER_LEN];
+    
+    if(!c)
+      return NULL;
+   
+   if(ChanType(c) & CHANNEL_COBJ) {
+      if(Typeof(ChanObj(c)) == TYPE_GARBAGE) {
+         ChanType(c) &= ~(CHANNEL_COBJ);
+         ChanObj(c) = -1;
+         strcpy(buff,tprintf("<%s>", ChanName(c)));
+         return buff;    
+       }
+     nm = atr_get_noparent(ChanObj(c), "CHANNAME");
+       if(nm) {
+          strcpy(tbuf,atr_value(nm));
+         strcpy(buff,(char *) nv_eval(ChanObj(c), tbuf));
+         }
+         else {
+          strcpy(buff,tprintf("<%s>", ChanName(c)));
+          }
+   }
+    else {
+        strcpy(buff,tprintf("<%s>", ChanName(c))); }
+
+   return buff; 
+}
+
+static char *nv_eval(dbref thing, const char *code) {
+  static char my_buff[BUFFER_LEN * 3];
+  char *my_bp = my_buff;
+  char *tbuf = (char *) mush_malloc(BUFFER_LEN, "nv_eval");
+  char *tbuf_ptr = tbuf;
+
+  strcpy(tbuf, code);
+  process_expression(my_buff, &my_bp, (const char **) &tbuf,
+                        thing, thing, thing, PE_DEFAULT, PT_DEFAULT,
+                        (PE_Info *) NULL);
+
+  *my_bp = '\0';
+
+  mush_free((Malloc_t) tbuf_ptr, "nv_eval");
+  return my_buff;
+}
+
+
+/* Evaluate a channel lock with %0 set to the channel name.
+ * \param c the channel to test.
+ * \param p the object trying to pass the lock.
+ * \param type the type of channel lock to test.
+ * \return true or false
+ */
+int
+eval_chan_lock(CHAN *c, dbref p, enum clock_type type)
+{
+  char *oldenv[10];
+  boolexp b = TRUE_BOOLEXP;
+  int retval, n;
+
+  if (!c || !GoodObject(p))
+    return 0;
+
+  switch (type) {
+  case CLOCK_SEE:
+    b = ChanSeeLock(c);
+    break;
+  case CLOCK_JOIN:
+    b = ChanJoinLock(c);
+    break;
+  case CLOCK_SPEAK:
+    b = ChanSpeakLock(c);
+    break;
+  case CLOCK_HIDE:
+    b = ChanHideLock(c);
+    break;
+  case CLOCK_MOD:
+    b = ChanModLock(c);
+  }
+
+  save_global_env("eval_chan_lock", oldenv);
+  global_eval_context.wenv[0] = ChanName(c);
+  for (n = 1; n < 10; n++)
+    global_eval_context.wenv[n] = NULL;
+  retval = eval_boolexp(p, b, p, NULL);
+  restore_global_env("eval_chan_lock", oldenv);
+
+  return retval;
+
+}
+
+#endif /* CHAT_SYSTEM */
diff --git a/src/extmail.c b/src/extmail.c
new file mode 100644 (file)
index 0000000..65e3291
--- /dev/null
@@ -0,0 +1,2786 @@
+/**
+ * \file extmail.c
+ *
+ * \brief The PennMUSH built-in mail system.
+ *
+ * \verbatim
+ *---------------------------------------------------------------
+ * extmail.c - Javelin's improved @mail system
+ * Based on Amberyl's linked list mailer
+ *
+ * Summary of mail command syntax:
+ * Sending:
+ *   @mail[/sendswitch] player-list = message
+ *     sendswitches: /silent, /urgent
+ *     player-list is a space-separated list of players, aliases, or msg#'s 
+ *     to reply to. Players can be names or dbrefs. Aliases start with *
+ * Reading/Handling:
+ *   @mail[/readswitch] [msg-list [= target]]
+ *     With no readswitch, @mail reads msg-list (same as /read)
+ *     With no readswitch and no msg-list, @mail lists all messages (/list)
+ *     readswitches: /list, /read, /fwd (requires target list of players
+ *      to forward messages to), /file (requires target folder to file
+ *      to), /tag, /untag, /clear, /unclear, /purge (no msg-list),
+ *      /count
+ *     Assumes messages in current folder, set by @folder or @mail/folder
+ *     msg-list can be one of: a message number, a message range,
+ *     (2-3, 4-, -6), sender references (*player), date comparisons
+ *     (~0, >2, <5), or the strings "urgent", "tagged", "cleared",
+ *     "read", "unread", "all", or "folder"
+ *     You can also use 1:2 (folder 1, message 2) and 1:2-3 for ranges.
+ * Admin stuff:
+ *   @mail[/switch] [player]
+ *     Switches include: nuke (used to be "purge"), [efd]stats, debug
+ *
+ * THEORY OF OPERATION:
+ *  Prior to pl11, mail was an unsorted linked list. When mail was sent,
+ * it was added onto the end. To read mail, you scanned the whole list.
+ * This is still how origmail.c works.
+ *  As of pl11, extmail.c maintains mail as a sorted linked list, sorted
+ * by recipient and order of receipt. This makes sending mail less
+ * efficient (because you have to scan the list to figure out where to
+ * insert), but reading/checking/deleting more efficient, 
+ * because once you've found where the player's mail starts, you just
+ * read from there. 
+ *  That wouldn't be so exciting unless there was a fast way to find
+ * where a player's mail chain started. Fortunately, there is. We
+ * record that information for connected players when they connect,
+ * on their descriptor. So, when connected players do reading/etc,
+ * it's O(1). Sending to a connected player is O(1). Sending to an
+ * unconnected player still requires scanning (O(n)), but you send once, 
+ * and read/list/delete etc, multiple times.
+ *  And just to make the sending to disconnected players faster, 
+ * instead of scanning the whole maildb to find the insertion point,
+ * we start the scan from the chain of the connected player with the
+ * closest db# to the target player. This scales up very well.
+ *--------------------------------------------------------------------
+ * \endverbatim
+ */
+
+#include "config.h"
+#include "copyrite.h"
+
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <ctype.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <string.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "extmail.h"
+#include "malias.h"
+#include "attrib.h"
+#include "parse.h"
+#include "mymalloc.h"
+#include "pueblo.h"
+#include "flags.h"
+#include "log.h"
+#include "lock.h"
+#include "command.h"
+#include "dbio.h"
+#include "confmagic.h"
+
+#ifdef USE_MAILER
+
+extern int do_convtime(const char *str, struct tm *ttm);       /* funtime.c */
+
+static void do_mail_flags
+  (dbref player, const char *msglist, mail_flag flag, int negate);
+static char *mail_list_time(const char *the_time, int flag);
+static MAIL *mail_fetch(dbref player, int num);
+static MAIL *real_mail_fetch(dbref player, int num, int folder);
+static MAIL *mailfun_fetch(dbref player, int nargs, char *arg1, char *arg2);
+static void count_mail(dbref player,
+                      int folder, int *rcount, int *ucount, int *ccount);
+static int real_send_mail(dbref player,
+                         dbref target, char *subject, char *message,
+                         mail_flag flags, int silent, int nosig);
+static void send_mail(dbref player,
+                     dbref target, char *subject, char *message,
+                     mail_flag flags, int silent, int nosig);
+static int send_mail_alias(dbref player,
+                          char *aname, char *subject,
+                          char *message, mail_flag flags, int silent,
+                          int nosig);
+static void filter_mail(dbref from, dbref player, char *subject,
+                       char *message, int mailnumber, mail_flag flags);
+static MAIL *find_insertion_point(dbref player);
+static int get_folder_number(dbref player, char *name);
+static char *get_folder_name(dbref player, int fld);
+static int player_folder(dbref player);
+static int parse_folder(dbref player, char *folder_string);
+static int mail_match(dbref player, MAIL *mp, struct mail_selector ms, int num);
+static int parse_msglist
+  (const char *msglist, struct mail_selector *ms, dbref player);
+static int parse_message_spec
+  (dbref player, const char *s, int *msglow, int *msghigh, int *folder);
+static char *status_chars(MAIL *mp);
+static char *status_string(MAIL *mp);
+static int sign(int x);
+static char *get_message(MAIL *mp);
+static char *get_compressed_message(MAIL *mp);
+static char *get_subject(MAIL *mp);
+static char *get_sender(MAIL *mp, int full);
+static int was_sender(dbref player, MAIL *mp);
+
+MAIL *maildb;           /**< The head of the mail list */
+MAIL *tail_ptr;                 /**< The end of the mail list */
+
+#define HEAD  maildb    /**< The head of the mail list */
+#define TAIL  tail_ptr  /**< The end of the mail list */
+
+/** A line of...dashes! */
+#define DASH_LINE  \
+  "-----------------------------------------------------------------------------"
+
+int mdb_top = 0;               /**< total number of messages in mail db */
+
+/*-------------------------------------------------------------------------*
+ *   User mail functions (these are called from game.c)
+ *
+ * do_mail - cases without a /switch.
+ * do_mail_send - sending mail
+ * do_mail_read - read messages
+ * do_mail_list - list messages
+ * do_mail_flags - tagging, untagging, clearing, unclearing of messages
+ * do_mail_file - files messages into a new folder
+ * do_mail_fwd - forward messages to another player(s)
+ * do_mail_count - count messages
+ * do_mail_purge - purge cleared messages
+ * do_mail_change_folder - change current folder
+ * do_mail_unfolder - remove a folder name from MAILFOLDERS
+ * do_mail_subject - set the current mail subject
+ *-------------------------------------------------------------------------*/
+
+/* Return the uncompressed text of a @mail in a static buffer */
+static char *
+get_message(MAIL *mp)
+{
+  static char text[BUFFER_LEN * 2];
+  unsigned char tbuf[BUFFER_LEN * 2];
+
+  if (!mp)
+    return NULL;
+
+  chunk_fetch(mp->msgid, tbuf, sizeof tbuf);
+  strcpy(text, uncompress(tbuf));
+  return text;
+}
+
+/* Return the compressed text of a @mail in a static buffer */
+static char *
+get_compressed_message(MAIL *mp)
+{
+  unsigned static char text[BUFFER_LEN * 2];
+
+  if (!mp)
+    return NULL;
+
+  chunk_fetch(mp->msgid, text, sizeof text);
+  return (char *) text;
+}
+
+/* Return the subject of a mail message, or (no subject) */
+static char *
+get_subject(MAIL *mp)
+{
+  static char sbuf[SUBJECT_LEN + 1];
+  char *p;
+  if (mp->subject) {
+    strncpy(sbuf, uncompress(mp->subject), SUBJECT_LEN);
+    sbuf[SUBJECT_LEN] = '\0';
+    /* Stop at a return or a tab */
+    for (p = sbuf; *p; p++) {
+      if ((*p == '\r') || (*p == '\n') || (*p == '\t')) {
+       *p = '\0';
+       break;
+      }
+      if (!isprint((unsigned char) *p)) {
+       *p = ' ';
+      }
+    }
+  } else
+    strcpy(sbuf, T("(no subject)"));
+  return sbuf;
+}
+
+/* Return the name of the mail sender. */
+static char *
+get_sender(MAIL *mp, int full)
+{
+  static char tbuf1[BUFFER_LEN], *bp;
+  bp = tbuf1;
+  if (!GoodObject(mp->from))
+    safe_str("!Purged!", tbuf1, &bp);
+  else if (!was_sender(mp->from, mp))
+    safe_str("!Purged!", tbuf1, &bp);
+  else if (IsPlayer(mp->from) || !full)
+    safe_str(Name(mp->from), tbuf1, &bp);
+  else
+    safe_format(tbuf1, &bp, "%s (owner: %s)", Name(mp->from),
+               Name(Owner(mp->from)));
+  *bp = '\0';
+  return tbuf1;
+}
+
+/* Was this player the sender of this message? */
+static int
+was_sender(dbref player, MAIL *mp)
+{
+  /* If the dbrefs don't match, fail. */
+  if (mp->from != player)
+    return 0;
+  /* If we don't know the creation time of the sender, succeed. */
+  if (!mp->from_ctime)
+    return 1;
+  /* Succeed if and only if the creation times match. */
+  return (mp->from_ctime == CreTime(player));
+}
+
+/** Change folders or rename a folder.
+ * \verbatim
+ * This implements @mail/folder
+ * \endverbatim
+ * \param player the enactor.
+ * \param fld string containing folder number or name.
+ * \param newname string containing folder name, if renaming.
+ */
+void
+do_mail_change_folder(dbref player, char *fld, char *newname)
+{
+  int pfld;
+  char *p;
+
+  if (!fld || !*fld) {
+    /* Check mail in all folders */
+    for (pfld = MAX_FOLDERS; pfld >= 0; pfld--)
+      check_mail(player, pfld, 1);
+    pfld = player_folder(player);
+    notify_format(player,
+                 T("MAIL: Current folder is %d [%s]."), pfld,
+                 get_folder_name(player, pfld));
+    return;
+  }
+  pfld = parse_folder(player, fld);
+  if (pfld < 0) {
+    notify(player, T("MAIL: What folder is that?"));
+    return;
+  }
+  if (newname && *newname) {
+    /* We're changing a folder name here */
+    if (strlen(newname) > FOLDER_NAME_LEN) {
+      notify(player, T("MAIL: Folder name too long"));
+      return;
+    }
+    for (p = newname; p && *p; p++) {
+      if (!isdigit((unsigned char) *p) && !isalpha((unsigned char) *p)) {
+       notify(player, T("MAIL: Illegal folder name"));
+       return;
+      }
+    }
+    add_folder_name(player, pfld, newname);
+    notify_format(player, T("MAIL: Folder %d now named '%s'"), pfld, newname);
+  } else {
+    /* Set a new folder */
+    set_player_folder(player, pfld);
+    notify_format(player,
+                 T("MAIL: Current folder set to %d [%s]."), pfld,
+                 get_folder_name(player, pfld));
+  }
+}
+
+/** Remove a folder name.
+ * \verbatim
+ * This implements @mail/unfolder
+ * \endverbatim
+ * \param player the enactor.
+ * \param fld string containing folder number or name.
+ */
+void
+do_mail_unfolder(dbref player, char *fld)
+{
+  int pfld;
+
+  if (!fld || !*fld) {
+    notify(player, T("MAIL: You must specify a folder name or number"));
+    return;
+  }
+  pfld = parse_folder(player, fld);
+  if (pfld < 0) {
+    notify(player, T("MAIL: What folder is that?"));
+    return;
+  }
+  add_folder_name(player, pfld, NULL);
+  notify_format(player, T("MAIL: Folder %d now has no name"), pfld);
+}
+
+
+/** Tag a set of mail messages.
+ * \param player the enactor.
+ * \param msglist string specifying messages to tag.
+ */
+void
+do_mail_tag(dbref player, const char *msglist)
+{
+  do_mail_flags(player, msglist, M_TAG, 0);
+}
+
+/** Clear a set of mail messages.
+ * \param player the enactor.
+ * \param msglist string specifying messages to clear.
+ */
+void
+do_mail_clear(dbref player, const char *msglist)
+{
+  do_mail_flags(player, msglist, M_CLEARED, 0);
+}
+
+/** Untag a set of mail messages.
+ * \param player the enactor.
+ * \param msglist string specifying messages to untag.
+ */
+void
+do_mail_untag(dbref player, const char *msglist)
+{
+  do_mail_flags(player, msglist, M_TAG, 1);
+}
+
+/** Unclear a set of mail messages.
+ * \param player the enactor.
+ * \param msglist string specifying messages to unclear.
+ */
+void
+do_mail_unclear(dbref player, const char *msglist)
+{
+  do_mail_flags(player, msglist, M_CLEARED, 1);
+}
+
+
+/** Set or clear a flag on a set of messages.
+ * \param player the enactor.
+ * \param msglist string representing list of messages to operate on.
+ * \param flag flag to set or clear.
+ * \param negate if 1, clear the flag; if 0, set the flag.
+ */
+static void
+do_mail_flags(dbref player, const char *msglist, mail_flag flag, int negate)
+{
+  MAIL *mp;
+  struct mail_selector ms;
+  int j, folder;
+  folder_array i;
+  int notified = 0;
+
+  if (!parse_msglist(msglist, &ms, player)) {
+    return;
+  }
+  FA_Init(i, j);
+  j = 0;
+  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
+  for (mp = find_exact_starting_point(player);
+       mp && (mp->to == player); mp = mp->next) {
+    if ((mp->to == player) && (All(ms) || (Folder(mp) == folder))) {
+      i[Folder(mp)]++;
+      if (mail_match(player, mp, ms, i[Folder(mp)])) {
+       j++;
+       if (negate) {
+         mp->read &= ~flag;
+       } else {
+         mp->read |= flag;
+       }
+       switch (flag) {
+       case M_TAG:
+         if (All(ms)) {
+           if (!notified) {
+             notify_format(player,
+                           T("MAIL: All messages in all folders %s."),
+                           negate ? "untagged" : "tagged");
+             notified++;
+           }
+         } else
+           notify_format(player,
+                         "MAIL: Msg #%d:%d %s.", Folder(mp),
+                         i[Folder(mp)], negate ? "untagged" : "tagged");
+         break;
+       case M_CLEARED:
+         if (All(ms)) {
+           if (!notified) {
+             notify_format(player,
+                           T("MAIL: All messages in all folders %s."),
+                           negate ? "uncleared" : "cleared");
+             notified++;
+           }
+         } else {
+           if (Unread(mp) && !negate) {
+             notify_format(player,
+                           T
+                           ("MAIL: Unread Msg #%d:%d cleared! Use @mail/unclear %d:%d to recover."),
+                           Folder(mp), i[Folder(mp)], Folder(mp),
+                           i[Folder(mp)]);
+           } else {
+             notify_format(player,
+                           (negate ? T("MAIL: Msg #%d:%d uncleared.") :
+                            T("MAIL: Msg #%d:%d cleared.")), Folder(mp),
+                           i[Folder(mp)]);
+           }
+         }
+         break;
+       }
+      }
+    }
+  }
+  if (!j) {
+    /* ran off the end of the list without finding anything */
+    notify(player, T("MAIL: You don't have any matching messages!"));
+  }
+  return;
+}
+
+/** File messages into a folder.
+ * \verbatim
+ * This implements @mail/file.
+ * \endverbatim
+ * \param player the enactor.
+ * \param msglist list of messages to file.
+ * \param folder name or number of folder to put messages in.
+ */
+void
+do_mail_file(dbref player, char *msglist, char *folder)
+{
+  MAIL *mp;
+  struct mail_selector ms;
+  int j, foldernum, origfold;
+  folder_array i;
+  int notified = 0;
+
+  if (!parse_msglist(msglist, &ms, player)) {
+    return;
+  }
+  if ((foldernum = parse_folder(player, folder)) == -1) {
+    notify(player, T("MAIL: Invalid folder specification"));
+    return;
+  }
+  FA_Init(i, j);
+  j = 0;
+  origfold = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
+  for (mp = find_exact_starting_point(player);
+       mp && (mp->to == player); mp = mp->next) {
+    if ((mp->to == player) && (All(ms) || (Folder(mp) == origfold))) {
+      i[Folder(mp)]++;
+      if (mail_match(player, mp, ms, i[Folder(mp)])) {
+       j++;
+       mp->read &= M_FMASK;    /* Clear the folder */
+       mp->read &= ~M_CLEARED; /* Unclear it if it was marked cleared */
+       mp->read |= FolderBit(foldernum);
+       if (All(ms)) {
+         if (!notified) {
+           notify_format(player,
+                         T("MAIL: All messages filed in folder %d [%s]"),
+                         foldernum, get_folder_name(player, foldernum));
+           notified++;
+         }
+       } else
+         notify_format(player,
+                       T("MAIL: Msg %d:%d filed in folder %d [%s]"),
+                       origfold, i[origfold], foldernum,
+                       get_folder_name(player, foldernum));
+      }
+    }
+  }
+  if (!j) {
+    /* ran off the end of the list without finding anything */
+    notify(player, T("MAIL: You don't have any matching messages!"));
+  }
+  return;
+}
+
+/** Read mail messages.
+ * This displays the contents of a set of mail messages.
+ * \param player the enactor.
+ * \param msglist list of messages to read.
+ */
+void
+do_mail_read(dbref player, char *msglist)
+{
+  MAIL *mp;
+  char tbuf1[BUFFER_LEN];
+  char folderheader[BUFFER_LEN];
+  struct mail_selector ms;
+  int j, folder;
+  folder_array i;
+
+  if (!parse_msglist(msglist, &ms, player)) {
+    return;
+  }
+  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
+  FA_Init(i, j);
+  j = 0;
+  for (mp = find_exact_starting_point(player);
+       mp && (mp->to == player); mp = mp->next) {
+    if ((mp->to == player) && (All(ms) || Folder(mp) == folder)) {
+      i[Folder(mp)]++;
+      if (mail_match(player, mp, ms, i[Folder(mp)])) {
+       /* Read it */
+       j++;
+       if (SUPPORT_PUEBLO) {
+         notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
+         sprintf(folderheader,
+                 "%cA XCH_HINT=\"List messages in this folder\" XCH_CMD=\"@mail/list %d:1-\"%c%s%c/A%c",
+                 TAG_START, Folder(mp), TAG_END, T("Folder:"), TAG_START,
+                 TAG_END);
+       } else
+         strcpy(folderheader, T("Folder:"));
+       notify(player, DASH_LINE);
+       strcpy(tbuf1, get_sender(mp, 1));
+       notify_format(player,
+                     T
+                     ("From: %-55s %s\nDate: %-25s   %s %2d   Message: %d\nStatus: %s"),
+                     tbuf1, ((*tbuf1 != '!') && IsPlayer(mp->from)
+                             && Connected(mp->from)
+                             && (!hidden(mp->from)
+                                 || Priv_Who(player))) ? " (Conn)" : "      ",
+                     show_time(mp->time, 0), folderheader, Folder(mp),
+                     i[Folder(mp)], status_string(mp));
+       notify_format(player, T("Subject: %s"), get_subject(mp));
+       notify(player, DASH_LINE);
+       if (SUPPORT_PUEBLO)
+         notify_noenter(player, tprintf("%c/SAMP%c", TAG_START, TAG_END));
+       strcpy(tbuf1, get_message(mp));
+       notify(player, tbuf1);
+       if (SUPPORT_PUEBLO)
+         notify_format(player, "%cSAMP%c%s%c/SAMP%c", TAG_START, TAG_END,
+                       DASH_LINE, TAG_START, TAG_END);
+       else
+         notify(player, DASH_LINE);
+       if (Unread(mp))
+         mp->read |= M_MSGREAD;        /* mark message as read */
+      }
+    }
+  }
+  if (!j) {
+    /* ran off the end of the list without finding anything */
+    notify(player, T("MAIL: You don't have that many matching messages!"));
+  }
+  return;
+}
+
+
+/** List the flags, number, sender, subject, and date of messages in a
+ * concise format.
+ * \param player the enactor.
+ * \param msglist list of messages to list.
+ */
+void
+do_mail_list(dbref player, const char *msglist)
+{
+  char subj[30];
+  char sender[30];
+  MAIL *mp;
+  struct mail_selector ms;
+  int j, folder;
+  folder_array i;
+
+  if (!parse_msglist(msglist, &ms, player)) {
+    return;
+  }
+  FA_Init(i, j);
+  j = 0;
+  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
+  if (SUPPORT_PUEBLO)
+    notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
+  notify_format(player,
+               T
+               ("---------------------------  MAIL (folder %2d)  ------------------------------"),
+               folder);
+  for (mp = find_exact_starting_point(player); mp && (mp->to == player);
+       mp = mp->next) {
+    if ((mp->to == player) && (All(ms) || Folder(mp) == folder)) {
+      i[Folder(mp)]++;
+      if (mail_match(player, mp, ms, i[Folder(mp)])) {
+       /* list it */
+       if (SUPPORT_PUEBLO)
+         notify_noenter(player,
+                        tprintf
+                        ("%cA XCH_CMD=\"@mail/read %d:%d\" XCH_HINT=\"Read message %d in folder %d\"%c",
+                         TAG_START, Folder(mp), i[Folder(mp)],
+                         i[Folder(mp)], Folder(mp), TAG_END));
+       strcpy(subj, chopstr(get_subject(mp), 28));
+       strcpy(sender, chopstr(get_sender(mp, 0), 12));
+       notify_format(player, "[%s] %2d:%-3d %c%-12s  %-*s %s",
+                     status_chars(mp), Folder(mp), i[Folder(mp)],
+                     ((*sender != '!') && (Connected(mp->from) &&
+                                           (!hidden(mp->from)
+                                            || Priv_Who(player)))
+                      ? '*' : ' '), sender, 30, subj,
+                     mail_list_time(show_time(mp->time, 0), 1));
+       if (SUPPORT_PUEBLO)
+         notify_noenter(player, tprintf("%c/A%c", TAG_START, TAG_END));
+      }
+    }
+  }
+  notify(player, DASH_LINE);
+  if (SUPPORT_PUEBLO)
+    notify_format(player, "%c/SAMP%c", TAG_START, TAG_END);
+  return;
+}
+
+static char *
+mail_list_time(const char *the_time, int flag /* 1 for no year */ )
+{
+  static char newtime[BUFFER_LEN];
+  const char *p;
+  char *q;
+  int i;
+  p = the_time;
+  q = newtime;
+  if (!p || !*p)
+    return NULL;
+  /* Format of the_time is: day mon dd hh:mm:ss yyyy */
+  /* Chop out :ss */
+  for (i = 0; i < 16; i++) {
+    if (*p)
+      *q++ = *p++;
+  }
+  if (!flag) {
+    for (i = 0; i < 3; i++) {
+      if (*p)
+       p++;
+    }
+    for (i = 0; i < 5; i++) {
+      if (*p)
+       *q++ = *p++;
+    }
+  }
+  *q = '\0';
+  return newtime;
+}
+
+
+/** Expunge mail that's marked for deletion.
+ * \verbatim
+ * This implements @mail/purge.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_mail_purge(dbref player)
+{
+  MAIL *mp, *nextp;
+
+  /* Go through player's mail, and remove anything marked cleared */
+  for (mp = find_exact_starting_point(player);
+       mp && (mp->to == player); mp = nextp) {
+    if ((mp->to == player) && Cleared(mp)) {
+      /* Delete this one */
+      /* head and tail of the list are special */
+      if (mp == HEAD)
+       HEAD = mp->next;
+      else if (mp == TAIL)
+       TAIL = mp->prev;
+      /* relink the list */
+      if (mp->prev != NULL)
+       mp->prev->next = mp->next;
+      if (mp->next != NULL)
+       mp->next->prev = mp->prev;
+      /* save the pointer */
+      nextp = mp->next;
+      /* then wipe */
+      mdb_top--;
+      free(mp->subject);
+      chunk_delete(mp->msgid);
+      mush_free((Malloc_t) mp, "mail");
+    } else {
+      nextp = mp->next;
+    }
+  }
+  /* Clean up the player's mailp */
+  if (Connected(player)) {
+    desc_mail_set(player, NULL);
+    desc_mail_set(player, find_exact_starting_point(player));
+  }
+  notify(player, T("MAIL: Mailbox purged."));
+  return;
+}
+
+/** Forward mail messages to someone(s) else.
+ * \verbatim
+ * This implements @mail/forward.
+ * \endverbatim
+ * \param player the enactor.
+ * \param msglist list of messages to forward.
+ * \param tolist list of recipients to forwared to.
+ */
+void
+do_mail_fwd(dbref player, char *msglist, char *tolist)
+{
+  MAIL *mp;
+  MAIL *last;
+  struct mail_selector ms;
+  int j, num, folder;
+  folder_array i;
+  const char *head;
+  MAIL *temp;
+  dbref target;
+  int num_recpts = 0;
+  const char **start;
+  char *current;
+  start = &head;
+
+  if (!parse_msglist(msglist, &ms, player)) {
+    return;
+  }
+  if (!tolist || !*tolist) {
+    notify(player, T("MAIL: To whom should I forward?"));
+    return;
+  }
+  folder = MSFolder(ms) ? MSFolder(ms) : player_folder(player);
+  /* Mark the player's last message. This prevents a loop if
+   * the forwarding command happens to forward a message back
+   * to the player itself 
+   */
+  last = mp = find_exact_starting_point(player);
+  if (!last) {
+    notify(player, T("MAIL: You have no messages to forward."));
+    return;
+  }
+  while (last->next && (last->next->to == player))
+    last = last->next;
+
+  FA_Init(i, j);
+  while (mp && (mp->to == player) && (mp != last->next)) {
+    if ((mp->to == player) && (All(ms) || (Folder(mp) == folder))) {
+      i[Folder(mp)]++;
+      if (mail_match(player, mp, ms, i[Folder(mp)])) {
+       /* forward it to all players listed */
+       head = tolist;
+       while (head && *head) {
+         current = next_in_list(start);
+         /* Now locate a target */
+         num = atoi(current);
+         if (num) {
+           /* reply to a mail message */
+           temp = mail_fetch(player, num);
+           if (!temp) {
+             notify(player, T("MAIL: You can't reply to nonexistant mail."));
+           } else {
+             char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
+             strcpy(tbuf1, uncompress(mp->subject));
+             strcpy(tbuf2, get_compressed_message(mp));
+             send_mail(player, temp->from, tbuf1, tbuf2, M_FORWARD | M_REPLY,
+                       1, 0);
+             num_recpts++;
+           }
+         } else {
+           /* forwarding to a player */
+           target =
+             match_result(player, current, TYPE_PLAYER,
+                          MAT_ME | MAT_ABSOLUTE | MAT_PLAYER);
+           if (!GoodObject(target))
+             target = lookup_player(current);
+           if (!GoodObject(target))
+             target = short_page(current);
+           if (!GoodObject(target) || !IsPlayer(target)) {
+             notify_format(player, T("No such unique player: %s."), current);
+           } else {
+             char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
+             strcpy(tbuf1, uncompress(mp->subject));
+             strcpy(tbuf2, get_compressed_message(mp));
+             send_mail(player, target, tbuf1, tbuf2, M_FORWARD, 1, 0);
+             num_recpts++;
+           }
+         }
+       }
+      }
+    }
+    mp = mp->next;
+  }
+  notify_format(player, T("MAIL: %d messages forwarded."), num_recpts);
+}
+
+/** Send a mail message.
+ * \param player the enactor.
+ * \param tolist list of recipients.
+ * \param message message text.
+ * \param flags flags to apply to the message.
+ * \param silent if 1, don't notify sender for each message sent.
+ * \param nosig if 1, don't apply sender's MAILSIGNATURE.
+ */
+void
+do_mail_send(dbref player, char *tolist, char *message, mail_flag flags,
+            int silent, int nosig)
+{
+  const char *head;
+  int num;
+  dbref target;
+  int mail_flags;
+  char sbuf[SUBJECT_LEN + 1], *sb, *mb;
+  int i = 0, subject_given = 0;
+  const char **start;
+  char *current;
+  start = &head;
+
+  if (!tolist || !*tolist) {
+    notify(player, T("MAIL: I can't figure out who you want to mail to."));
+    return;
+  }
+  if (!message || !*message) {
+    notify(player, T("MAIL: I can't figure out what you want to send."));
+    return;
+  }
+  sb = sbuf;
+  mb = message;                        /* Save the message pointer */
+  while (*message && (i < SUBJECT_LEN) && *message != SUBJECT_COOKIE) {
+    *sb++ = *message++;
+    i++;
+  }
+  *sb = '\0';
+  if (*message && (*message == SUBJECT_COOKIE)) {
+    message++;
+    subject_given = 1;
+  } else
+    message = mb;              /* Rewind the pointer to the beginning */
+#ifdef ALLOW_NOSUBJECT
+  if (!subject_given)
+    strcpy(sbuf, T("(no subject)"));
+#endif
+  /* Parse the player list */
+  head = tolist;
+  while (head && *head) {
+    mail_flags = flags;
+    current = next_in_list(start);
+    /* Now locate a target */
+    if (is_strict_integer(current)) {
+      /* reply to a mail message */
+      MAIL *temp;
+
+      num = parse_integer(current);
+
+      temp = mail_fetch(player, num);
+      if (!temp) {
+       notify(player, T("MAIL: You can't reply to nonexistent mail."));
+       return;
+      }
+      if (subject_given)
+       send_mail(player, temp->from, sbuf, message, mail_flags, silent, nosig);
+      else
+       send_mail(player, temp->from, uncompress(temp->subject), message,
+                 mail_flags | M_REPLY, silent, nosig);
+    } else {
+      /* send a new mail message */
+      target =
+       match_result(player, current, TYPE_PLAYER,
+                    MAT_ME | MAT_ABSOLUTE | MAT_PLAYER);
+      if (!GoodObject(target))
+       target = lookup_player(current);
+      if (!GoodObject(target))
+       target = short_page(current);
+      if (!GoodObject(target) || !IsPlayer(target)) {
+       if (!send_mail_alias
+           (player, current, sbuf, message, mail_flags, silent, nosig))
+         notify_format(player, T("No such unique player: %s."), current);
+      } else
+       send_mail(player, target, sbuf, message, mail_flags, silent, nosig);
+    }
+  }
+}
+
+/*-------------------------------------------------------------------------*
+ *   Admin mail functions
+ *
+ * do_mail_nuke - clear & purge mail for a player, or all mail in db.
+ * do_mail_stat - stats on mail for a player, or for all db.
+ * do_mail_debug - fix mail with a sledgehammer
+ *-------------------------------------------------------------------------*/
+
+/*-------------------------------------------------------------------------*
+ *   Basic mail functions
+ *-------------------------------------------------------------------------*/
+static MAIL *
+mail_fetch(dbref player, int num)
+{
+  /* get an arbitrary mail message in the current folder */
+  return real_mail_fetch(player, num, player_folder(player));
+}
+
+static MAIL *
+real_mail_fetch(dbref player, int num, int folder)
+{
+  MAIL *mp;
+  int i = 0;
+
+  for (mp = find_exact_starting_point(player); mp != NULL; mp = mp->next) {
+    if (mp->to > player)
+      break;
+    if ((mp->to == player) && ((folder < 0) || (Folder(mp) == folder)))
+      i++;
+    if (i == num)
+      return mp;
+  }
+  return NULL;
+}
+
+
+static void
+count_mail(dbref player, int folder, int *rcount, int *ucount, int *ccount)
+{
+  /* returns count of read, unread, & cleared messages as rcount, ucount,
+   * ccount. folder=-1 returns for all folders */
+
+  MAIL *mp;
+  int rc, uc, cc;
+
+  cc = rc = uc = 0;
+  for (mp = find_exact_starting_point(player);
+       mp && (mp->to == player); mp = mp->next) {
+    if ((mp->to == player) && ((folder == -1) || (Folder(mp) == folder))) {
+      if (Cleared(mp))
+       cc++;
+      else if (Read(mp))
+       rc++;
+      else
+       uc++;
+    }
+  }
+  *rcount = rc;
+  *ucount = uc;
+  *ccount = cc;
+}
+
+
+static void
+send_mail(dbref player, dbref target, char *subject, char *message,
+         mail_flag flags, int silent, int nosig)
+{
+  /* send a message to a target, consulting the target's mailforward.
+   * If mailforward isn't set, just deliver to targt.
+   * If mailforward is set, deliver to the list if we're allowed
+   *  (but don't check mailforward further!)
+   */
+  ATTR *a;
+  int good = 0;
+
+  a = atr_get_noparent(target, "MAILFORWARDLIST");
+  if (!a) {
+    /* Easy, no forwarding */
+    good =
+      real_send_mail(player, target, subject, message, flags, silent, nosig);
+    return;
+  } else {
+    /* We have a forward list. Run through it. */
+    char *fwdstr, *orig, *curr;
+    dbref fwd;
+    orig = safe_atr_value(a);
+    fwdstr = trim_space_sep(orig, ' ');
+    while ((curr = split_token(&fwdstr, ' ')) != NULL) {
+      if (is_objid(curr)) {
+       fwd = parse_objid(curr);
+       if (GoodObject(fwd) && Can_MailForward(target, fwd)) {
+         good +=
+           real_send_mail(player, fwd, subject, message, flags, 1, nosig);
+       } else
+         notify_format(target, T("Failed attempt to forward @mail to #%d"),
+                       fwd);
+      }
+    }
+    free((Malloc_t) orig);
+  }
+  if (!silent) {
+    if (good)
+      notify_format(player,
+                   T("MAIL: You sent your message to %s."), Name(target));
+    else
+      notify_format(player,
+                   T
+                   ("MAIL: Your message was not sent to %s due to a mail forwarding problem."),
+                   Name(target));
+  }
+}
+
+static int
+real_send_mail(dbref player, dbref target, char *subject, char *message,
+             mail_flag flags, int silent, int nosig)
+{
+  /* deliver a mail message to a target, period */
+
+  MAIL *newp, *mp;
+  int rc, uc, cc;
+  char *newmsg, *nm, *buff, *bp;
+  char const *ms;
+  char *mailsig;
+  char sbuf[BUFFER_LEN];
+  ATTR *a;
+
+  if (!IsPlayer(target)) {
+    notify(player, T("MAIL: You cannot send mail to non-existent people."));
+    return 0;
+  }
+  if (!strcasecmp(message, "clear")) {
+    notify(player,
+          T("MAIL: You probably don't wanna send mail saying 'clear'."));
+    return 0;
+  }
+  if (!(Admin(player) || eval_lock(player, target, Mail_Lock))) {
+    notify_format(player,
+                 T("MAIL: %s is not accepting mail from you right now."),
+                 Name(target));
+    return 0;
+  }
+  count_mail(target, 0, &rc, &uc, &cc);
+  if ((rc + uc + cc) >= MAIL_LIMIT) {
+    notify_format(player, T("MAIL: %s's mailbox is full. Can't send."),
+                 Name(target));
+    return 0;
+  }
+
+  /* initialize the appropriate fields */
+  newp = (MAIL *) mush_malloc(sizeof(struct mail), "mail");
+  newp->to = target;
+  newp->from = player;
+  newp->from_ctime = CreTime(player);
+  /* Deal with the subject */
+  if (subject)
+    strcpy(sbuf, subject);
+  else
+    strcpy(sbuf, T("(no subject)"));
+  if ((flags & M_FORWARD) && !string_prefix(sbuf, "Fwd:"))
+    newp->subject = compress(chopstr(tprintf("Fwd: %s", sbuf), SUBJECT_LEN));
+  else if ((flags & M_REPLY) && !string_prefix(sbuf, "Re:"))
+    newp->subject = compress(chopstr(tprintf("Re: %s", sbuf), SUBJECT_LEN));
+  else if ((a = atr_get_noparent(player, "MAILSUBJECT")) != NULL)
+    /* Don't bother to uncompress a->value */
+    newp->subject = u_strdup(AL_STR(a));
+  else
+    newp->subject = compress(sbuf);
+  if (flags & M_FORWARD) {
+    /* Forwarding passes the message already compressed */
+    int len = strlen(message) + 1;
+    newp->msgid = chunk_create((unsigned char *) message, len, 1);
+  } else {
+    int len;
+    unsigned char *text;
+    newmsg = (char *) mush_malloc(BUFFER_LEN, "string");
+    if (!newmsg)
+      mush_panic(T("Failed to allocate string in send_mail"));
+    nm = newmsg;
+    safe_str(message, newmsg, &nm);
+    if (!nosig && ((a = atr_get_noparent(player, "MAILSIGNATURE")) != NULL)) {
+      /* Append the MAILSIGNATURE to the mail - Cordin@Dune's idea */
+      buff = (char *) mush_malloc(BUFFER_LEN, "string");
+      if (!buff)
+       mush_panic(T("Failed to allocate string in send_mail"));
+      ms = mailsig = safe_atr_value(a);
+      bp = buff;
+      process_expression(buff, &bp, &ms, player, player, player,
+                        PE_DEFAULT, PT_DEFAULT, NULL);
+      *bp = '\0';
+      free(mailsig);
+      safe_str(buff, newmsg, &nm);
+      mush_free((Malloc_t) buff, "string");
+    }
+    *nm = '\0';
+    text = compress(newmsg);
+    len = strlen((char *) text) + 1;
+    newp->msgid = chunk_create(text, len, 1);
+    free(text);
+    mush_free((Malloc_t) newmsg, "string");
+  }
+
+  newp->time = mudtime;
+  newp->read = flags & M_FMASK;        /* Send to folder 0 */
+
+  /* Where do we insert it? After mp, wherever that is.
+   * This can return NULL if there are no messages or
+   * if we insert at the head of the list 
+   */
+  mp = find_insertion_point(target);
+
+  if (mp) {
+    newp->prev = mp;
+    newp->next = mp->next;
+    if (mp == TAIL)
+      TAIL = newp;
+    else
+      mp->next->prev = newp;
+    mp->next = newp;
+  } else {
+    if (HEAD) {
+      /* Insert at the front */
+      newp->next = HEAD;
+      newp->prev = NULL;
+      HEAD->prev = newp;
+      HEAD = newp;
+    } else {
+      /* This is the first message in the maildb */
+      HEAD = newp;
+      TAIL = newp;
+      newp->prev = NULL;
+      newp->next = NULL;
+    }
+  }
+
+  /* If the target's mailp isn't pointing to their list, we'd better
+   * set it
+   */
+  if (Connected(target))
+    desc_mail_set(target, find_exact_starting_point(target));
+
+  mdb_top++;
+
+  /* notify people */
+  if (!silent)
+    notify_format(player,
+                 T("MAIL: You sent your message to %s."), Name(target));
+  notify_format(target,
+               T("MAIL: You have a new message (%d) from %s."),
+               rc + uc + cc + 1, Name(player));
+
+  /* Check @mailfilter */
+  filter_mail(player, target, subject, message, rc + uc + cc + 1, flags);
+
+  if (AMAIL_ATTR && (atr_get_noparent(target, "AMAIL"))
+      && (player != target) && Admin(target))
+    did_it(player, target, NULL, NULL, NULL, NULL, "AMAIL", NOTHING);
+
+  return 1;
+}
+
+
+/** Wipe the entire maildb.
+ * \param player the enactor.
+ */
+void
+do_mail_nuke(dbref player)
+{
+  MAIL *mp, *nextp;
+
+  if (!God(player)) {
+    notify(player, T("The postal service issues a warrant for your arrest."));
+    return;
+  }
+  /* walk the list */
+  for (mp = HEAD; mp != NULL; mp = nextp) {
+    nextp = mp->next;
+    if (mp->subject)
+      free(mp->subject);
+    chunk_delete(mp->msgid);
+    mush_free((Malloc_t) mp, "mail");
+  }
+
+  HEAD = TAIL = NULL;
+  mdb_top = 0;
+  desc_mail_clear();
+
+  do_log(LT_ERR, 0, 0, T("** MAIL PURGE ** done by %s(#%d)."),
+        Name(player), player);
+  notify(player, T("You annihilate the post office. All messages cleared."));
+}
+
+/** Low-level mail sanity checking or debugging.
+ * "how to fix mail with a sledgehammer".
+ * \param player the enactor.
+ * \param action action to take (sanity, clear, fix)
+ * \param victim name of player whose mail is to be checked (NULL to check all)
+ */
+void
+do_mail_debug(dbref player, const char *action, const char *victim)
+{
+  dbref target;
+  MAIL *mp, *nextp;
+  int i;
+
+  if (!MailAdministrator(player)) {
+    notify(player, T("Go get some bugspray."));
+    return;
+  }
+  if (string_prefix("clear", action)) {
+    target = lookup_player(victim);
+    if (target == NOTHING) {
+      target = match_result(player, victim, NOTYPE, MAT_ABSOLUTE);
+    }
+    if (target == NOTHING) {
+      notify_format(player, T("%s: No such player."), victim);
+      return;
+    }
+    if (!MailAdmin(player, target)) {
+      notify(player, T("Go get some bugspray."));
+      return;
+    }
+    do_mail_clear(target, "ALL");
+    do_mail_purge(target);
+    notify_format(player, T("Mail cleared for %s(#%d)."), Name(target), target);
+    return;
+  } else if (string_prefix("sanity", action)) {
+    for (i = 0, mp = HEAD; mp != NULL; i++, mp = mp->next) {
+      if (!GoodObject(mp->to))
+       notify_format(player, T("Bad object #%d has mail."), mp->to);
+      else if (!IsPlayer(mp->to))
+       notify_format(player, T("%s(#%d) has mail but is not a player."),
+                     Name(mp->to), mp->to);
+    }
+    if (i != mdb_top) {
+      notify_format(player,
+                   T
+                   ("Mail database top is %d, actual message count is %d. Fixing."),
+                   mdb_top, i);
+      mdb_top = i;
+    }
+    notify(player, T("Mail sanity check completed."));
+  } else if (string_prefix("fix", action)) {
+    for (i = 0, mp = HEAD; mp != NULL; i++, mp = nextp) {
+      if (!GoodObject(mp->to) || !IsPlayer(mp->to)) {
+       notify_format(player, T("Fixing mail for #%d."), mp->to);
+       /* Delete this one */
+       /* head and tail of the list are special */
+       if (mp == HEAD)
+         HEAD = mp->next;
+       else if (mp == TAIL)
+         TAIL = mp->prev;
+       /* relink the list */
+       if (mp->prev != NULL)
+         mp->prev->next = mp->next;
+       if (mp->next != NULL)
+         mp->next->prev = mp->prev;
+       /* save the pointer */
+       nextp = mp->next;
+       /* then wipe */
+       mdb_top--;
+       if (mp->subject)
+         free(mp->subject);
+       chunk_delete(mp->msgid);
+       mush_free((Malloc_t) mp, "mail");
+      } else if (!GoodObject(mp->from)) {
+       /* Oops, it's from a player whose dbref is out of range!
+        * We'll make it appear to be from #0 instead because there's 
+        * no really good choice
+        */
+       mp->from = 0;
+       nextp = mp->next;
+      } else {
+       nextp = mp->next;
+      }
+    }
+    notify(player, T("Mail sanity fix completed."));
+  } else {
+    notify(player, T("That is not a debugging option."));
+    return;
+  }
+}
+
+/** Display mail database statistics.
+ * \verbatim
+ * This implements @mail/stat, @mail/dstat, @mail/fstat.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of player whose mail stats are to be checked (NULL for all)
+ * \param full level of verbosity: MSTATS_COUNT = just count messages,
+ * MSTATS_READ = break down by read/unread, MSTATS_SIZE = include
+ * space usage.
+ */
+void
+do_mail_stats(dbref player, char *name, enum mail_stats_type full)
+{
+  dbref target;
+  int fc, fr, fu, tc, tr, tu, fchars, tchars, cchars;
+  char last[50];
+  MAIL *mp;
+
+  fc = fr = fu = tc = tr = tu = cchars = fchars = tchars = 0;
+
+  /* find player */
+
+  if (*name == '\0') {
+    target = AMBIGUOUS;
+  } else if (*name == NUMBER_TOKEN) {
+    target = atoi(&name[1]);
+    if (!GoodObject(target) || !IsPlayer(target))
+      target = NOTHING;
+  } else if (!strcasecmp(name, "me")) {
+    target = player;
+  } else {
+    target = lookup_player(name);
+  }
+
+  /* Don't use GoodObject here! */
+  if (target == NOTHING) {
+    target = match_result(player, name, NOTYPE, MAT_ABSOLUTE);
+  }
+  if ((target == NOTHING) || ((target == AMBIGUOUS) && !MailAdministrator(player))) {
+    notify_format(player, T("%s: No such player."), name);
+    return;
+  }
+  if (!MailAdmin(player, target) && (target != player && target != AMBIGUOUS)) {
+    notify(player, T("The post office protects privacy!"));
+    return;
+  }
+  /* this comand is computationally expensive */
+
+  if (!payfor(player, FIND_COST)) {
+    notify_format(player, T("Finding mail stats costs %d %s."), FIND_COST,
+                 (FIND_COST == 1) ? MONEY : MONIES);
+    return;
+  }
+  if (target == AMBIGUOUS) {   /* stats for all */
+    if (full == MSTATS_COUNT) {
+      notify_format(player,
+                   T("There are %d messages in the mail spool."), mdb_top);
+      return;
+    } else if (full == MSTATS_READ) {
+      for (mp = HEAD; mp != NULL; mp = mp->next) {
+       if (Cleared(mp))
+         fc++;
+       else if (Read(mp))
+         fr++;
+       else
+         fu++;
+      }
+      notify_format(player,
+                   T
+                   ("MAIL: There are %d msgs in the mail spool, %d unread, %d cleared."),
+                   fc + fr + fu, fu, fc);
+      return;
+    } else {
+      for (mp = HEAD; mp != NULL; mp = mp->next) {
+       if (Cleared(mp)) {
+         fc++;
+         cchars += strlen(get_message(mp));
+       } else if (Read(mp)) {
+         fr++;
+         fchars += strlen(get_message(mp));
+       } else {
+         fu++;
+         tchars += strlen(get_message(mp));
+       }
+      }
+      notify_format(player,
+                   T
+                   ("MAIL: There are %d old msgs in the mail spool, totalling %d characters."),
+                   fr, fchars);
+      notify_format(player,
+                   T
+                   ("MAIL: There are %d new msgs in the mail spool, totalling %d characters."),
+                   fu, tchars);
+      notify_format(player,
+                   ("MAIL: There are %d cleared msgs in the mail spool, totalling %d characters."),
+                   fc, cchars);
+      return;
+    }
+  }
+  /* individual stats */
+
+  if (full == MSTATS_COUNT) {
+    /* just count number of messages */
+    for (mp = HEAD; mp != NULL; mp = mp->next) {
+      if (was_sender(target, mp))
+       fr++;
+      if (mp->to == target)
+       tr++;
+    }
+    notify_format(player, T("%s sent %d messages."), Name(target), fr);
+    notify_format(player, T("%s has %d messages."), Name(target), tr);
+    return;
+  }
+  /* more detailed message count */
+  for (mp = HEAD; mp != NULL; mp = mp->next) {
+    if (was_sender(target, mp)) {
+      if (Cleared(mp))
+       fc++;
+      else if (Read(mp))
+       fr++;
+      else
+       fu++;
+      if (full == MSTATS_SIZE)
+       fchars += strlen(get_message(mp));
+    }
+    if (mp->to == target) {
+      if (!tr && !tu)
+       strcpy(last, show_time(mp->time, 0));
+      if (Cleared(mp))
+       tc++;
+      else if (Read(mp))
+       tr++;
+      else
+       tu++;
+      if (full == MSTATS_SIZE)
+       tchars += strlen(get_message(mp));
+    }
+  }
+
+  notify_format(player, T("Mail statistics for %s:"), Name(target));
+
+  if (full == MSTATS_READ) {
+    notify_format(player, T("%d messages sent, %d unread, %d cleared."),
+                 fc + fr + fu, fu, fc);
+    notify_format(player, T("%d messages received, %d unread, %d cleared."),
+                 tc + tr + tu, tu, tc);
+  } else {
+    notify_format(player,
+                 T
+                 ("%d messages sent, %d unread, %d cleared, totalling %d characters."),
+                 fc + fr + fu, fu, fc, fchars);
+    notify_format(player,
+                 T
+                 ("%d messages received, %d unread, %d cleared, totalling %d characters."),
+                 tc + tr + tu, tu, tc, tchars);
+  }
+
+  if (tc + tr + tu > 0)
+    notify_format(player, T("Last is dated %s"), last);
+  return;
+}
+
+
+/** Main mail wrapper.
+ * \verbatim
+ * This implements the @mail command when called with no switches.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 left-hand argument (before the =)
+ * \param arg2 right-hand argument (after the =)
+ */
+void
+do_mail(dbref player, char *arg1, char *arg2)
+{
+  dbref sender;
+  /* Force player to be a real player, but keep track of the
+   * enactor in case we're sending mail, which objects can do
+   */
+  sender = player;
+  player = Owner(player);
+  if (!arg1 || !*arg1) {
+    if (arg2 && *arg2) {
+      notify(player, T("MAIL: Invalid mail command."));
+      return;
+    }
+    /* just the "@mail" command */
+    do_mail_list(player, "");
+    return;
+  }
+  /* purge a player's mailbox */
+  if (!strcasecmp(arg1, "purge")) {
+    do_mail_purge(player);
+    return;
+  }
+  /* clear message */
+  if (!strcasecmp(arg1, "clear")) {
+    do_mail_clear(player, arg2);
+    return;
+  }
+  if (!strcasecmp(arg1, "unclear")) {
+    do_mail_unclear(player, arg2);
+    return;
+  }
+  if (arg2 && *arg2) {
+    /* Sending mail */
+    if (Gagged(sender))
+      notify(sender, T("You cannot do that while gagged."));
+    else
+      do_mail_send(sender, arg1, arg2, 0, 0, 0);
+  } else {
+    /* Must be reading or listing mail - no arg2 */
+    if (isdigit((unsigned char) *arg1) && !strchr(arg1, '-'))
+      do_mail_read(player, arg1);
+    else
+      do_mail_list(player, arg1);
+  }
+  return;
+}
+
+/*-------------------------------------------------------------------------*
+ *   Auxiliary functions
+ *-------------------------------------------------------------------------*/
+/* ARGSUSED */
+FUNCTION(fun_folderstats)
+{
+  /* This function can take one of four formats:
+   * folderstats() -> returns stats for my current folder
+   * folderstats(folder#) -> returns stats for my folder folder#
+   * folderstats(player) -> returns stats for player's current folder
+   * folderstats(player,folder#) -> returns stats for player's folder folder#
+   */
+  dbref player;
+  int rc, uc, cc;
+
+  switch (nargs) {
+  case 0:
+    count_mail(executor, player_folder(executor), &rc, &uc, &cc);
+    break;
+  case 1:
+    if (!is_integer(args[0])) {
+      /* handle the case of wanting to count the number of messages */
+      if ((player =
+          noisy_match_result(executor, args[0], TYPE_PLAYER,
+                             MAT_ME | MAT_ABSOLUTE | MAT_PLAYER)) == NOTHING) {
+       safe_str(T("#-1 NO SUCH PLAYER"), buff, bp);
+       return;
+      } else if ((executor != player) && !MailAdmin(executor, player)) {
+       safe_str(T(e_perm), buff, bp);
+       return;
+      } else {
+       count_mail(player, player_folder(player), &rc, &uc, &cc);
+      }
+    } else {
+      count_mail(executor, parse_integer(args[0]), &rc, &uc, &cc);
+    }
+    break;
+  case 2:
+    if ((player =
+        noisy_match_result(executor, args[0], TYPE_PLAYER,
+                           MAT_ME | MAT_ABSOLUTE | MAT_PLAYER)) == NOTHING) {
+      safe_str(T("#-1 NO SUCH PLAYER"), buff, bp);
+      return;
+    } else if ((executor != player) && !MailAdmin(executor, player)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (!is_integer(args[1])) {
+      safe_str(T("#-1 FOLDER MUST BE INTEGER"), buff, bp);
+      return;
+    }
+    count_mail(player, parse_integer(args[1]), &rc, &uc, &cc);
+    break;
+  default:
+    /* This should never happen */
+    return;
+  }
+
+  safe_integer(rc, buff, bp);
+  safe_chr(' ', buff, bp);
+  safe_integer(uc, buff, bp);
+  safe_chr(' ', buff, bp);
+  safe_integer(cc, buff, bp);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_mail)
+{
+  /* mail([<player>,] [<folder #>:]<message #>)
+   * mail() --> return total # of messages for executor
+   * mail(<player>) --> return total # of messages for player
+   */
+
+  MAIL *mp;
+  dbref player;
+  int rc, uc, cc;
+
+  if (nargs == 0) {
+    count_mail(executor, -1, &rc, &uc, &cc);
+    safe_integer(rc + uc + cc, buff, bp);
+    return;
+  }
+  /* Try mail(<player>) */
+  if (nargs == 1) {
+    player =
+      match_result(executor, args[0], TYPE_PLAYER,
+                  MAT_ME | MAT_ABSOLUTE | MAT_PLAYER);
+    if (GoodObject(player)) {
+      if ((executor != player) && !MailAdmin(executor, player)) {
+       safe_str(T(e_perm), buff, bp);
+      } else {
+       count_mail(player, -1, &rc, &uc, &cc);
+       safe_integer(rc, buff, bp);
+       safe_chr(' ', buff, bp);
+       safe_integer(uc, buff, bp);
+       safe_chr(' ', buff, bp);
+       safe_integer(cc, buff, bp);
+      }
+      return;
+    }
+  }
+  /* That didn't work. Ok, try mailfun_fetch */
+  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
+  if (mp) {
+    safe_str(get_message(mp), buff, bp);
+    return;
+  }
+  safe_str(T("#-1 INVALID MESSAGE OR PLAYER"), buff, bp);
+  return;
+}
+
+/* A helper routine used by all the mail*() functions
+ * We parse the following format:
+ *  func([<player>,] [<folder #>:]<message #>)
+ * and return the matching message or NULL
+ */
+static MAIL *
+mailfun_fetch(dbref player, int nargs, char *arg1, char *arg2)
+{
+  dbref target;
+  int msg;
+  int folder;
+
+  if (nargs == 1) {
+    /* Simply a message number or folder:message */
+    if (parse_message_spec(player, arg1, &msg, NULL, &folder))
+      return real_mail_fetch(player, msg, folder);
+    else {
+      return NULL;
+    }
+  } else {
+    /* Both a target and a message */
+    if ((target =
+        noisy_match_result(player, arg1, TYPE_PLAYER,
+                           MAT_ME | MAT_ABSOLUTE | MAT_PLAYER)) == NOTHING) {
+      return NULL;
+    } else if ((player != target) && !MailAdmin(player, target)) {
+      notify(player, T("Permission denied"));
+      return NULL;
+    }
+    if (parse_message_spec(target, arg2, &msg, NULL, &folder))
+      return real_mail_fetch(target, msg, folder);
+    else {
+      notify(player, T("Invalid message specification"));
+      return NULL;
+    }
+  }
+  /* NOTREACHED */
+  return NULL;
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_mailfrom)
+{
+  MAIL *mp;
+
+  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
+  if (!mp)
+    safe_str("#-1", buff, bp);
+  else if (!was_sender(mp->from, mp))
+    safe_str("#-1", buff, bp);
+  else
+    safe_dbref(mp->from, buff, bp);
+  return;
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_mailstats)
+{
+  /* Copied from extmail.c: do_mail_stats with changes to refer to
+   * args[0] rather than name, etc
+   *
+   * Todo: Change it so it is just mailstats and depending on what
+   * it is called as, it will change if it is doing a fstats, dstats,
+   * or stats. Looks like stats -> full=0, dstats -> full=1, fstats ->
+   * full=2
+   */
+
+  /* mail database statistics */
+
+  dbref target;
+  int fc, fr, fu, tc, tr, tu, fchars, tchars, cchars;
+  char last[50];
+  MAIL *mp;
+  int full;
+
+  /* Figure out how we were called */
+  if (string_prefix(called_as, "mailstats")) {
+    full = 0;
+  } else if (string_prefix(called_as, "maildstats")) {
+    full = 1;
+  } else if (string_prefix(called_as, "mailfstats")) {
+    full = 2;
+  } else {
+    safe_str(T("#-? fun_mailstats called with invalid called_as!"), buff, bp);
+    return;
+  }
+
+  fc = fr = fu = tc = tr = tu = cchars = fchars = tchars = 0;
+
+  /* find player */
+  if (*args[0] == '\0') {
+    target = AMBIGUOUS;
+  } else if (*args[0] == NUMBER_TOKEN) {
+    target = atoi(&args[0][1]);
+    if (!GoodObject(target) || !IsPlayer(target))
+      target = NOTHING;
+  } else if (!strcasecmp(args[0], "me")) {
+    target = executor;
+  } else {
+    target = lookup_player(args[0]);
+  }
+
+  if (!GoodObject(target)) {
+    target = match_result(executor, args[0], NOTYPE, MAT_ABSOLUTE);
+  }
+  if (!GoodObject(target) || !IsPlayer(target)) {
+    notify_format(executor, T("%s: No such player."), args[0]);
+    return;
+  }
+  if ((executor != target) && !MailAdmin(executor, target)) {
+    notify(executor, T("The post office protects privacy!"));
+    return;
+  }
+  /* this comand is computationally expensive */
+
+  if (!payfor(executor, FIND_COST)) {
+    notify_format(executor, T("Finding mail stats costs %d %s."), FIND_COST,
+                 (FIND_COST == 1) ? MONEY : MONIES);
+    return;
+  }
+  if (target == AMBIGUOUS) {   /* stats for all */
+    if (full == 0) {
+      /* FORMAT
+       * total mail
+       */
+      safe_integer(mdb_top, buff, bp);
+      return;
+    } else if (full == 1) {
+      for (mp = HEAD; mp != NULL; mp = mp->next) {
+       if (Cleared(mp))
+         fc++;
+       else if (Read(mp))
+         fr++;
+       else
+         fu++;
+      }
+      /* FORMAT
+       * sent, sent_unread, sent_cleared
+       */
+      safe_format(buff, bp, "%d %d %d", fc + fr + fu, fu, fc);
+    } else {
+      for (mp = HEAD; mp != NULL; mp = mp->next) {
+       if (Cleared(mp)) {
+         fc++;
+         cchars += strlen(get_message(mp));
+       } else if (Read(mp)) {
+         fr++;
+         fchars += strlen(get_message(mp));
+       } else {
+         fu++;
+         tchars += strlen(get_message(mp));
+       }
+      }
+      /* FORMAT
+       * sent_read, sent_read_characters,
+       * sent_unread, sent_unread_characters,
+       * sent_clear, sent_clear_characters
+       */
+      safe_format(buff, bp, "%d %d %d %d %d %d",
+                 fr, fchars, fu, tchars, fc, cchars);
+      return;
+    }
+  }
+
+  /* individual stats */
+
+  if (full == 0) {
+    /* just count number of messages */
+    for (mp = HEAD; mp != NULL; mp = mp->next) {
+      if (was_sender(target, mp))
+       fr++;
+      if (mp->to == target)
+       tr++;
+    }
+    /* FORMAT
+     * sent, received
+     */
+    safe_format(buff, bp, "%d %d", fr, tr);
+    return;
+  }
+  /* more detailed message count */
+  for (mp = HEAD; mp != NULL; mp = mp->next) {
+    if (was_sender(target, mp)) {
+      if (Cleared(mp))
+       fc++;
+      else if (Read(mp))
+       fr++;
+      else
+       fu++;
+      if (full == 2)
+       fchars += strlen(get_message(mp));
+    }
+    if (mp->to == target) {
+      if (!tr && !tu)
+       strcpy(last, show_time(mp->time, 0));
+      if (Cleared(mp))
+       tc++;
+      else if (Read(mp))
+       tr++;
+      else
+       tu++;
+      if (full == 2)
+       tchars += strlen(get_message(mp));
+    }
+  }
+
+  if (full == 1) {
+    /* FORMAT
+     * sent, sent_unread, sent_cleared
+     * received, rec_unread, rec_cleared
+     */
+    safe_format(buff, bp, "%d %d %d %d %d %d",
+               fc + fr + fu, fu, fc, tc + tr + tu, tu, tc);
+  } else {
+    /* FORMAT
+     * sent, sent_unread, sent_cleared, sent_bytes
+     * received, rec_unread, rec_cleared, rec_bytes
+     */
+    safe_format(buff, bp, "%d %d %d %d %d %d %d %d",
+               fc + fr + fu, fu, fc, fchars, tc + tr + tu, tu, tc, tchars);
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_mailtime)
+{
+  MAIL *mp;
+
+  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
+  if (!mp)
+    safe_str("#-1", buff, bp);
+  else
+    safe_str(show_time(mp->time, 0), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_mailstatus)
+{
+  MAIL *mp;
+
+  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
+  if (!mp)
+    safe_str("#-1", buff, bp);
+  else
+    safe_str(status_chars(mp), buff, bp);
+  return;
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_mailsubject)
+{
+  MAIL *mp;
+
+  mp = mailfun_fetch(executor, nargs, args[0], args[1]);
+  if (!mp)
+    safe_str("#-1", buff, bp);
+  else
+    safe_str(uncompress(mp->subject), buff, bp);
+  return;
+}
+
+/** Save mail to disk.
+ * \param fp pointer to filehandle to save mail to.
+ * \return number of mail messages saved.
+ */
+int
+dump_mail(FILE * fp)
+{
+  MAIL *mp;
+  int count = 0;
+  int mail_flags = 0;
+
+  mail_flags += MDBF_SUBJECT;
+  mail_flags += MDBF_ALIASES;
+  mail_flags += MDBF_NEW_EOD;
+  mail_flags += MDBF_SENDERCTIME;
+
+  if (mail_flags)
+    fprintf(fp, "+%d\n", mail_flags);
+
+  save_malias(fp);
+
+  OUTPUT(fprintf(fp, "%d\n", mdb_top));
+
+  for (mp = HEAD; mp != NULL; mp = mp->next) {
+    putref(fp, mp->to);
+    putref(fp, mp->from);
+    putref(fp, mp->from_ctime);
+    putstring(fp, show_time(mp->time, 0));
+    if (mp->subject)
+      putstring(fp, uncompress(mp->subject));
+    else
+      putstring(fp, "");
+    putstring(fp, get_message(mp));
+    putref(fp, mp->read);
+    count++;
+  }
+
+  OUTPUT(fputs(EOD, fp));
+  fflush(fp);
+
+  if (count != mdb_top) {
+    do_log(LT_ERR, 0, 0, T("MAIL: Count of messages is %d, mdb_top is %d."),
+          count, mdb_top);
+    mdb_top = count;           /* Doesn't help if we forked, but oh well */
+  }
+  return (count);
+}
+
+
+
+/** Find the first message in a player's mail chain, or NULL if none.
+ * We first try to find a good place to start by looking for a connected
+ * player with a dbref nearest to our target player. Then it's a linear
+ * search.
+ * \param player the player to search for.
+ * \return pointer to first message in their mail chain, or NULL.
+ */
+MAIL *
+find_exact_starting_point(dbref player)
+{
+  static MAIL *mp;
+
+  if (!HEAD)
+    return NULL;
+  mp = desc_mail(player);
+  if (!mp) {
+    /* Player is connected and has no mail, or nobody's connected who
+     * has mail - we have to scan the maildb.
+     */
+    if (HEAD->to > player)
+      return NULL;             /* No mail chain */
+    for (mp = HEAD; mp && (mp->to < player); mp = mp->next) ;
+  } else {
+    while (mp && (mp->to >= player))
+      mp = mp->prev;
+    if (!mp)
+      mp = HEAD;
+    while (mp && (mp->to < player))
+      mp = mp->next;
+  }
+  if (mp && (mp->to == player))
+    return mp;
+  return NULL;
+}
+
+
+/* Find the place where new mail to this player should go (after):
+ *  1. The last message in the player's mail chain, or
+ *  2. The last message before where the player's chain should start, or
+ *  3. NULL (meaning TAIL)
+ */
+static MAIL *
+find_insertion_point(dbref player)
+{
+  static MAIL *mp;
+
+  if (!HEAD)
+    return NULL;
+  mp = desc_mail(player);
+  if (!mp) {
+    /* Player is connected and has no mail, or nobody's connected who
+     * has mail - we have to scan the maildb.
+     */
+    if (HEAD->to > player)
+      return NULL;             /* No mail chain */
+    for (mp = TAIL; mp && (mp->to > player); mp = mp->prev) ;
+  } else {
+    while (mp && (mp->to <= player))
+      mp = mp->next;
+    if (!mp)
+      mp = TAIL;
+    while (mp && (mp->to > player))
+      mp = mp->prev;
+  }
+  return mp;
+}
+
+
+/** Initialize the mail database pointers */
+void
+mail_init(void)
+{
+  mdb_top = 0;
+  HEAD = NULL;
+  TAIL = NULL;
+}
+
+/** Load mail from disk.
+ * \param fp pointer to filehandle from which to load mail.
+ */
+int
+load_mail(FILE * fp)
+{
+  char nbuf1[8];
+  unsigned char *tbuf = NULL;
+  unsigned char *text;
+  int len;
+  int mail_top = 0;
+  int mail_flags = 0;
+  int i = 0;
+  MAIL *mp, *tmpmp;
+  int done = 0;
+  char sbuf[BUFFER_LEN];
+  struct tm ttm;
+
+  /* find out how many messages we should be loading */
+  fgets(nbuf1, sizeof(nbuf1), fp);
+  /* If it starts with +, it's telling us the mail db flags */
+  if (*nbuf1 == '+') {
+    mail_flags = atoi(nbuf1 + 1);
+    /* If the flags indicates aliases, we'll read them now */
+    if (mail_flags & MDBF_ALIASES) {
+      load_malias(fp);
+    }
+    fgets(nbuf1, sizeof(nbuf1), fp);
+  }
+  mail_top = atoi(nbuf1);
+  if (!mail_top) {
+    /* mail_top could be 0 from an error or actually be 0. */
+    if (nbuf1[0] == '0' && nbuf1[1] == '\n') {
+      char buff[20];
+      fgets(buff, sizeof buff, fp);
+      if (!buff)
+       do_rawlog(LT_ERR,
+                 T("MAIL: Missing end-of-dump marker in mail database."));
+      else if (strcmp(buff, (mail_flags & MDBF_NEW_EOD)
+                     ? "***END OF DUMP***\n" : "*** END OF DUMP ***\n") == 0)
+       return 1;
+      else
+       do_rawlog(LT_ERR, T("MAIL: Trailing garbage in the mail database."));
+    }
+    return 0;
+  }
+  /* first one is a special case */
+  mp = (MAIL *) mush_malloc(sizeof(struct mail), "mail");
+  mp->to = getref(fp);
+  mp->from = getref(fp);
+  if (mail_flags & MDBF_SENDERCTIME)
+    mp->from_ctime = getref(fp);
+  else
+    mp->from_ctime = 0;                /* No one will have this creation time */
+
+  if (do_convtime(getstring_noalloc(fp), &ttm))
+    mp->time = mktime(&ttm);
+  else                                /* do_convtime failed. Odd. */
+    mp->time = mudtime;
+
+  if (mail_flags & MDBF_SUBJECT) {
+    tbuf = compress(getstring_noalloc(fp));
+  }
+  text = compress(getstring_noalloc(fp));
+  len = strlen((char *) text) + 1;
+  mp->msgid = chunk_create(text, len, 1);
+  free(text);
+  if (mail_flags & MDBF_SUBJECT)
+    mp->subject = tbuf;
+  else {
+    strcpy(sbuf, get_message(mp));
+    mp->subject = compress(chopstr(sbuf, SUBJECT_LEN));
+  }
+  mp->read = getref(fp);
+  mp->next = NULL;
+  mp->prev = NULL;
+  HEAD = mp;
+  TAIL = mp;
+  i++;
+
+  /* now loop through */
+  for (; i < mail_top; i++) {
+    mp = (MAIL *) mush_malloc(sizeof(struct mail), "mail");
+    mp->to = getref(fp);
+    mp->from = getref(fp);
+    if (mail_flags & MDBF_SENDERCTIME)
+      mp->from_ctime = getref(fp);
+    else
+      mp->from_ctime = 0;      /* No one will have this creation time */
+    if (do_convtime(getstring_noalloc(fp), &ttm))
+      mp->time = mktime(&ttm);
+    else                      /* do_convtime failed. Odd. */
+      mp->time = mudtime;
+    if (mail_flags & MDBF_SUBJECT)
+      tbuf = compress(getstring_noalloc(fp));
+    else
+      tbuf = NULL;
+    text = compress(getstring_noalloc(fp));
+    len = strlen((char *) text) + 1;
+    mp->msgid = chunk_create(text, len, 1);
+    free(text);
+    if (tbuf)
+      mp->subject = tbuf;
+    else {
+      strcpy(sbuf, get_message(mp));
+      mp->subject = compress(chopstr(sbuf, SUBJECT_LEN));
+    }
+    mp->read = getref(fp);
+
+    /* We now to a sorted insertion, sorted by recipient db# */
+    if (mp->to >= TAIL->to) {
+      /* Pop it onto the end */
+      mp->next = NULL;
+      mp->prev = TAIL;
+      TAIL->next = mp;
+      TAIL = mp;
+    } else {
+      /* Search for where to put it */
+      mp->prev = NULL;
+      for (done = 0, tmpmp = HEAD; tmpmp && !done; tmpmp = tmpmp->next) {
+       if (tmpmp->to > mp->to) {
+         /* Insert before tmpmp */
+         mp->next = tmpmp;
+         mp->prev = tmpmp->prev;
+         if (tmpmp->prev) {
+           /* tmpmp isn't HEAD */
+           tmpmp->prev->next = mp;
+         } else {
+           /* tmpmp is HEAD */
+           HEAD = mp;
+         }
+         tmpmp->prev = mp;
+         done = 1;
+       }
+      }
+      if (!done) {
+       /* This is bad */
+       do_rawlog(LT_ERR, T("MAIL: bad code."));
+      }
+    }
+  }
+
+  mdb_top = i;
+
+  if (i != mail_top) {
+    do_rawlog(LT_ERR, T("MAIL: mail_top is %d, only read in %d messages."),
+             mail_top, i);
+  }
+  {
+    char buff[20];
+    fgets(buff, sizeof buff, fp);
+    if (!buff)
+      do_rawlog(LT_ERR,
+               T("MAIL: Missing end-of-dump marker in mail database."));
+    else if (strcmp(buff, (mail_flags & MDBF_NEW_EOD)
+                   ? EOD : "*** END OF DUMP ***\n") != 0)
+      /* There's still stuff. Icky. */
+      do_rawlog(LT_ERR, T("MAIL: Trailing garbage in the mail database."));
+  }
+
+  do_mail_debug(GOD, "fix", "");
+  return (mdb_top);
+}
+
+static int
+get_folder_number(dbref player, char *name)
+{
+  ATTR *a;
+  char str[BUFFER_LEN], pat[BUFFER_LEN], *res, *p;
+  /* Look up a folder name and return the appopriate number */
+  a = (ATTR *) atr_get_noparent(player, "MAILFOLDERS");
+  if (!a)
+    return -1;
+  strcpy(str, atr_value(a));
+  sprintf(pat, ":%s:", strupper(name));
+  res = strstr(str, pat);
+  if (!res)
+    return -1;
+  res += 2 + strlen(name);
+  p = res;
+  while (isdigit((unsigned char) *p))
+    p++;
+  *p = '\0';
+  return atoi(res);
+}
+
+static char *
+get_folder_name(dbref player, int fld)
+{
+  static char str[BUFFER_LEN];
+  char pat[BUFFER_LEN];
+  static char *old;
+  char *r;
+  ATTR *a;
+
+  /* Get the name of the folder, or "nameless" */
+  sprintf(pat, "%d:", fld);
+  old = NULL;
+  a = (ATTR *) atr_get_noparent(player, "MAILFOLDERS");
+  if (!a) {
+    strcpy(str, "unnamed");
+    return str;
+  }
+  strcpy(str, atr_value(a));
+  old = (char *) string_match(str, pat);
+  if (old) {
+    r = old + strlen(pat);
+    while (*r != ':')
+      r++;
+    *r = '\0';
+    return old + strlen(pat);
+  } else {
+    strcpy(str, "unnamed");
+    return str;
+  }
+}
+
+/** Assign a name to a folder.
+ * \param player dbref of player whose folder is to be named.
+ * \param fld folder number.
+ * \param name new name for folder.
+ */
+void
+add_folder_name(dbref player, int fld, const char *name)
+{
+  char *old, *res, *r;
+  char *new, *pat, *str, *tbuf;
+  ATTR *a;
+
+  /* Muck with the player's MAILFOLDERS attrib to add a string of the form:
+   * number:name:number to it, replacing any such string with a matching
+   * number.
+   */
+  new = (char *) mush_malloc(BUFFER_LEN, "string");
+  pat = (char *) mush_malloc(BUFFER_LEN, "string");
+  str = (char *) mush_malloc(BUFFER_LEN, "string");
+  tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!new || !pat || !str || !tbuf)
+    mush_panic(T("Failed to allocate strings in add_folder_name"));
+
+  if (name && *name)
+    sprintf(new, "%d:%s:%d ", fld, strupper(name), fld);
+  else
+    strcpy(new, " ");
+  sprintf(pat, "%d:", fld);
+  /* get the attrib and the old string, if any */
+  old = NULL;
+  a = (ATTR *) atr_get_noparent(player, "MAILFOLDERS");
+  if (a) {
+    strcpy(str, atr_value(a));
+    old = (char *) string_match(str, pat);
+  }
+  if (old && *old) {
+    strcpy(tbuf, str);
+    r = old;
+    while (!isspace((unsigned char) *r))
+      r++;
+    *r = '\0';
+    res = replace_string(old, new, tbuf);      /* mallocs mem! */
+  } else {
+    r = res = (char *) mush_malloc(BUFFER_LEN + 1, "replace_string.buff");
+    if (a)
+      safe_str(str, res, &r);
+    safe_str(new, res, &r);
+    *r = '\0';
+  }
+  /* put the attrib back */
+  (void) atr_add(player, "MAILFOLDERS", res, GOD,
+                AF_NOPROG | AF_LOCKED);
+  mush_free((Malloc_t) res, "replace_string.buff");
+  mush_free((Malloc_t) new, "string");
+  mush_free((Malloc_t) pat, "string");
+  mush_free((Malloc_t) str, "string");
+  mush_free((Malloc_t) tbuf, "string");
+}
+
+static int
+player_folder(dbref player)
+{
+  /* Return the player's current folder number. If they don't have one, set
+   * it to 0 */
+  ATTR *a;
+
+  a = (ATTR *) atr_get_noparent(player, "MAILCURF");
+  if (!a) {
+    set_player_folder(player, 0);
+    return 0;
+  }
+  return atoi(atr_value(a));
+}
+
+/** Set a player's current mail folder to a given folder.
+ * \param player the player whose current folder is to be set.
+ * \param fnum folder number to make current.
+ */
+void
+set_player_folder(dbref player, int fnum)
+{
+  /* Set a player's folder to fnum */
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+
+  sprintf(tbuf1, "%d", fnum);
+  a = (ATTR *) atr_match("MAILCURF");
+  if (a)
+    (void) atr_add(player, a->name, tbuf1, GOD, a->flags);
+  else                         /* Shouldn't happen, but... */
+    (void) atr_add(player, "MAILCURF", tbuf1, GOD,
+                  AF_NOPROG | AF_LOCKED);
+}
+
+static int
+parse_folder(dbref player, char *folder_string)
+{
+  int fnum;
+
+  /* Given a string, return a folder #, or -1 The string is just a number,
+   * for now. Later, this will be where named folders are handled */
+  if (!folder_string || !*folder_string)
+    return -1;
+  if (isdigit((unsigned char) *folder_string)) {
+    fnum = atoi(folder_string);
+    if ((fnum < 0) || (fnum > MAX_FOLDERS))
+      return -1;
+    else
+      return fnum;
+  }
+  /* Handle named folders here */
+  return get_folder_number(player, folder_string);
+}
+
+static int
+mail_match(dbref player, MAIL *mp, struct mail_selector ms, int num)
+{
+  int diffdays;
+  mail_flag mpflag;
+
+  /* Does a piece of mail match the mail_selector? */
+  if (ms.low && num < ms.low)
+    return 0;
+  if (ms.high && num > ms.high)
+    return 0;
+  if (ms.player && !was_sender(ms.player, mp))
+    return 0;
+  if (AllInFolder(ms) && (Folder(mp) == player_folder(player)))
+    return 1;
+  mpflag = Read(mp) ? mp->read : (mp->read | M_MSUNREAD);
+  if (!All(ms) && !(ms.flags & mpflag))
+    return 0;
+  if (ms.days != -1) {
+    /* Get the time now, subtract mp->time, and compare the results with
+     * ms.days (in manner of ms.day_comp) */
+    diffdays = difftime(mudtime, mp->time) / 86400;
+    if (sign(diffdays - ms.days) != ms.day_comp)
+      return 0;
+    else
+      return 1;
+  }
+  return 1;
+}
+
+static int
+parse_msglist(const char *msglist, struct mail_selector *ms, dbref player)
+{
+  char *p;
+  char tbuf1[BUFFER_LEN];
+  int folder;
+
+  /* Take a message list, and return the appropriate mail_selector setup. For
+   * now, msglists are quite restricted. That'll change once all this is
+   * working. Returns 0 if couldn't parse, and also notifys player of why. */
+  /* Initialize the mail selector - this matches all messages */
+  ms->low = 0;
+  ms->high = 0;
+  ms->flags = 0x00FF | M_MSUNREAD;
+  ms->player = 0;
+  ms->days = -1;
+  ms->day_comp = 0;
+  /* Now, parse the message list */
+  if (!msglist || !*msglist) {
+    /* All messages */
+    return 1;
+  }
+  /* Don't mess with msglist itself */
+  strncpy(tbuf1, msglist, BUFFER_LEN - 1);
+  p = tbuf1;
+  while (p && *p && isspace((unsigned char) *p))
+    p++;
+  if (!p || !*p) {
+    ms->flags |= M_FOLDER;
+    return 1;                  /* all messages in current folder */
+  }
+  if (isdigit((unsigned char) *p) || *p == '-') {
+    if (!parse_message_spec(player, p, &ms->low, &ms->high, &folder)) {
+      notify(player, T("MAIL: Invalid message specification"));
+      return 0;
+    }
+    ms->flags |= FolderBit(folder);
+  } else if (*p == '~') {
+    /* exact # of days old */
+    p++;
+    if (!p || !*p) {
+      notify(player, T("MAIL: Invalid age"));
+      return 0;
+    }
+    if (!is_integer(p)) {
+      notify(player, T("MAIL: Message ages must be integers"));
+      return 0;
+    }
+    ms->day_comp = 0;
+    ms->days = atoi(p);
+    if (ms->days < 0) {
+      notify(player, T("MAIL: Invalid age"));
+      return 0;
+    }
+  } else if (*p == '<') {
+    /* less than # of days old */
+    p++;
+    if (!p || !*p) {
+      notify(player, T("MAIL: Invalid age"));
+      return 0;
+    }
+    if (!is_integer(p)) {
+      notify(player, T("MAIL: Message ages must be integers"));
+      return 0;
+    }
+    ms->day_comp = -1;
+    ms->days = atoi(p);
+    if (ms->days < 0) {
+      notify(player, T("MAIL: Invalid age"));
+      return 0;
+    }
+  } else if (*p == '>') {
+    /* greater than # of days old */
+    p++;
+    if (!p || !*p) {
+      notify(player, T("MAIL: Invalid age"));
+      return 0;
+    }
+    if (!is_integer(p)) {
+      notify(player, T("MAIL: Message ages must be integers"));
+      return 0;
+    }
+    ms->day_comp = 1;
+    ms->days = atoi(p);
+    if (ms->days < 0) {
+      notify(player, T("MAIL: Invalid age"));
+      return 0;
+    }
+  } else if (*p == '#') {
+    /* From db# */
+    if (!is_dbref(p)) {
+      notify(player, T("MAIL: Invalid dbref #"));
+      return 0;
+    }
+    ms->player = parse_dbref(p);
+    if (!GoodObject(ms->player) || !(ms->player)) {
+      notify(player, T("MAIL: Invalid dbref #"));
+      return 0;
+    }
+  } else if (*p == '*') {
+    /* From player name */
+    p++;
+    if (!p || !*p) {
+      notify(player, T("MAIL: Invalid player"));
+      return 0;
+    }
+    ms->player = lookup_player(p);
+    if (ms->player == NOTHING) {
+      notify(player, T("MAIL: Invalid player"));
+      return 0;
+    }
+  } else if (!strcasecmp(p, "all")) {
+    ms->flags = M_ALL;
+  } else if (!strcasecmp(p, "folder")) {
+    ms->flags = M_FOLDER;
+  } else if (!strcasecmp(p, "urgent")) {
+    ms->flags = M_URGENT;
+  } else if (!strcasecmp(p, "unread")) {
+    ms->flags = M_MSUNREAD;
+  } else if (!strcasecmp(p, "read")) {
+    ms->flags = M_MSGREAD;
+  } else if (!strcasecmp(p, "clear") || !strcasecmp(p, "cleared")) {
+    ms->flags = M_CLEARED;
+  } else if (!strcasecmp(p, "tag") || !strcasecmp(p, "tagged")) {
+    ms->flags = M_TAG;
+  } else if (!strcasecmp(p, "mass")) {
+    ms->flags = M_MASS;
+  } else if (!strcasecmp(p, "me")) {
+    ms->player = player;
+  } else {
+    notify(player, T("MAIL: Invalid message specification"));
+    return 0;
+  }
+  return 1;
+}
+
+static char *
+status_chars(MAIL *mp)
+{
+  /* Return a short description of message flags */
+  static char res[10];
+  char *p;
+
+  res[0] = '\0';
+  p = res;
+  *p++ = Read(mp) ? '-' : 'N';
+  *p++ = Cleared(mp) ? 'C' : '-';
+  *p++ = Urgent(mp) ? 'U' : '-';
+  /* *p++ = Mass(mp) ? 'M' : '-'; */
+  *p++ = Forward(mp) ? 'F' : '-';
+  *p++ = Tagged(mp) ? '+' : '-';
+  *p = '\0';
+  return res;
+}
+
+static char *
+status_string(MAIL *mp)
+{
+  /* Return a longer description of message flags */
+  static char tbuf1[BUFFER_LEN];
+
+  tbuf1[0] = '\0';
+  if (Read(mp))
+    strcat(tbuf1, T("Read "));
+  else
+    strcat(tbuf1, T("Unread "));
+  if (Cleared(mp))
+    strcat(tbuf1, T("Cleared "));
+  if (Urgent(mp))
+    strcat(tbuf1, T("Urgent "));
+  if (Mass(mp))
+    strcat(tbuf1, T("Mass "));
+  if (Forward(mp))
+    strcat(tbuf1, T("Fwd "));
+  if (Tagged(mp))
+    strcat(tbuf1, T("Tagged"));
+  return tbuf1;
+}
+
+/** Check for new mail.
+ * \param player player to check for new mail (and to notify).
+ * \param folder folder number to check.
+ * \param silent if 1, don't tell player if they have no mail.
+ */
+void
+check_mail(dbref player, int folder, int silent)
+{
+  int rc;                      /* read messages */
+  int uc;                      /* unread messages */
+  int cc;                      /* cleared messages */
+  int total;
+
+  /* just count messages */
+  count_mail(player, folder, &rc, &uc, &cc);
+  total = rc + uc + cc;
+  if (total > 0)
+    notify_format(player,
+                 T
+                 ("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared)."),
+                 total, folder, get_folder_name(player, folder), uc, cc);
+  else if (!silent)
+    notify(player, T("\nMAIL: You have no mail.\n"));
+  if ((folder == 0) && (total + 5 > MAIL_LIMIT))
+    notify_format(player, T("MAIL: Warning! Limit on inbox messages is %d!"),
+                 MAIL_LIMIT);
+  return;
+}
+
+static int
+sign(int x)
+{
+  if (x == 0) {
+    return 0;
+  } else if (x < 0) {
+    return -1;
+  } else {
+    return 1;
+  }
+}
+
+/* See if we've been given something of the form [f:]m1[-m2]
+ * If so, return 1 and set f and mlow and mhigh
+ * If not, return 0
+ * If msghigh is given as NULL, don't allow ranges 
+ * Used in parse_msglist, fun_mail and relatives.
+ */
+static int
+parse_message_spec(dbref player, const char *s, int *msglow, int *msghigh,
+                  int *folder)
+{
+  char buf[BUFFER_LEN];
+  char *p, *q;
+  if (!s || !*s)
+    return 0;
+  strcpy(buf, s);
+  if ((p = strchr(buf, ':'))) {
+    *p++ = '\0';
+    if (!is_integer(buf))
+      return 0;
+    *folder = parse_integer(buf);
+    if (msghigh && (q = strchr(p, '-'))) {
+      /* f:low-high */
+      *q++ = '\0';
+      if (!*p)
+       *msglow = 0;
+      else if (!is_integer(p))
+       return 0;
+      else {
+       *msglow = parse_integer(p);
+       if (*msglow == 0)
+         *msglow = -1;
+      }
+      if (!*q)
+       *msghigh = 0;
+      else if (!is_integer(q))
+       return 0;
+      else {
+       *msghigh = parse_integer(q);
+       if (*msghigh == 0)
+         *msghigh = -1;
+      }
+    } else {
+      /* f:m */
+      if (!is_integer(p))
+       return 0;
+      *msglow = parse_integer(p);
+      if (*msglow == 0)
+       *msglow = -1;
+      if (msghigh)
+       *msghigh = *msglow;
+    }
+    if (*msglow < 0 || (msghigh && *msghigh < 0) || *folder < 0
+       || *folder > MAX_FOLDERS)
+      return 0;
+  } else {
+    /* No folder spec */
+    *folder = player_folder(player);
+    if (msghigh && (q = strchr(buf, '-'))) {
+      /* low-high */
+      *q++ = '\0';
+      if (!*buf)
+       *msglow = 0;
+      else if (!is_integer(buf))
+       return 0;
+      else {
+       *msglow = parse_integer(buf);
+       if (*msglow == 0)
+         *msglow = -1;
+      }
+      if (!*q)
+       *msghigh = 0;
+      else if (!is_integer(q))
+       return 0;
+      else {
+       *msghigh = parse_integer(q);
+       if (*msghigh == 0)
+         *msghigh = -1;
+      }
+    } else {
+      /* m */
+      if (!is_integer(buf))
+       return 0;
+      *msglow = parse_integer(buf);
+      if (*msglow == 0)
+       *msglow = -1;
+      if (msghigh)
+       *msghigh = *msglow;
+    }
+    if (*msglow < 0 || (msghigh && *msghigh < 0))
+      return 0;
+  }
+  return 1;
+}
+
+static int
+send_mail_alias(dbref player, char *aname, char *subject, char *message,
+               mail_flag flags, int silent, int nosig)
+{
+  struct mail_alias *m;
+  int i;
+
+  /* send a mail message to each player on an alias */
+  /* We return 0 if this wasn't an alias */
+  m = get_malias(player, aname);
+  if (!m)
+    return 0;
+  /* Is it an alias they can use? */
+  if (!((m->owner == player) || (m->nflags == 0) ||
+       (Admin(player)) ||
+       ((m->nflags & ALIAS_MEMBERS) && ismember(m, player))))
+    return 0;
+
+  /* If they are not allowed to see the people on the alias, then
+   * we must treat this as a case of silent mailing.
+   */
+  if (!((m->owner == player) || (m->mflags == 0) ||
+       (Admin(player)) ||
+       ((m->mflags & ALIAS_MEMBERS) && ismember(m, player)))) {
+    silent = 1;
+    notify_format(player,
+                 T("You sent your message to the '%s' alias"), m->name);
+  }
+
+  for (i = 0; i < m->size; i++) {
+    send_mail(player, m->members[i], subject, message, flags, silent, nosig);
+  }
+  return 1;                    /* Success */
+}
+
+/** Event for @mailfilter.
+ * \param from the @mailing player.
+ * \param player the player to act on.
+ * \param subject the subject of the @mail.
+ * \param message the body of the @mail.
+ * \param mailnumber the number of the @mail to file.
+ * \param flags the flags of the @mail.
+ */
+void
+filter_mail(dbref from, dbref player, char *subject,
+           char *message, int mailnumber, mail_flag flags)
+{
+  ATTR *f;
+  char buff[BUFFER_LEN], *bp, *asave;
+  char buf[FOLDER_NAME_LEN + 1];
+  char *wsave[10], *rsave[NUMQ];
+  char *arg, *arg2, *arg3, *arg4;
+  int j;
+  char const *ap;
+  static char tbuf1[6];
+
+  /* Does the player have a @mailfilter? */
+  f = atr_get(player, "MAILFILTER");
+  if (!f)
+    return;
+
+  /* Handle this now so it doesn't clutter code */
+  tbuf1[0] = '\0';
+  if (flags & M_URGENT)
+    strcat(tbuf1, "U");
+  if (flags & M_FORWARD)
+    strcat(tbuf1, "F");
+  if (flags & M_REPLY)
+    strcat(tbuf1, "R");
+
+  arg = (char *) mush_malloc(BUFFER_LEN, "string");
+  arg2 = (char *) mush_malloc(BUFFER_LEN, "string");
+  arg3 = (char *) mush_malloc(BUFFER_LEN, "string");
+  arg4 = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!arg4)
+    mush_panic("Unable to allocate memory in mailfilter");
+  save_global_regs("filter_mail", rsave);
+  save_global_env("filter_mail", wsave);
+  for (j = 0; j < 10; j++)
+    global_eval_context.wenv[j] = NULL;
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.renv[j][0] = '\0';
+  strcpy(arg, unparse_dbref(from));
+  global_eval_context.wenv[0] = arg;
+  strcpy(arg2, subject);
+  global_eval_context.wenv[1] = arg2;
+  strcpy(arg3, message);
+  global_eval_context.wenv[2] = arg3;
+  strcpy(arg4, tbuf1);
+  global_eval_context.wenv[3] = arg4;
+
+  ap = asave = safe_atr_value(f);
+  bp = buff;
+  process_expression(buff, &bp, &ap, player, player, player,
+                   PE_DEFAULT, PT_DEFAULT, NULL);
+  *bp = '\0';
+  free((Malloc_t) asave);
+  if (*buff) {
+    sprintf(buf, "0:%d", mailnumber);
+    do_mail_file(player, buf, buff);
+  }
+
+  mush_free((Malloc_t) arg, "string");
+  mush_free((Malloc_t) arg2, "string");
+  mush_free((Malloc_t) arg3, "string");
+  mush_free((Malloc_t) arg4, "string");
+  restore_global_env("filter_mail", wsave);
+  restore_global_regs("filter_mail", rsave);
+}
+
+#endif                         /* USE_MAILER */
diff --git a/src/filecopy.c b/src/filecopy.c
new file mode 100644 (file)
index 0000000..14ed4f4
--- /dev/null
@@ -0,0 +1,275 @@
+/* Win32 services routines */  \r
+  \r
+/* Author: Nick Gammon */ \r
+  \r
+#ifdef WIN32\r
+  \r
+#include "copyrite.h"\r
+#include "config.h"\r
+  \r
+#include <windows.h>            /* for find first */\r
+  \r
+#include <stdlib.h>\r
+#include <process.h>\r
+#include <direct.h>\r
+  \r
+#include "conf.h"\r
+#include "mushdb.h"\r
+#include "match.h"\r
+#include "externs.h"\r
+#include "mymalloc.h"\r
+#include "log.h"\r
+#include "confmagic.h"\r
+  \r
+/* This is bad, but only for WIN32, which is bad anyway... */ \r
+#define EMBEDDED_MKINDX\r
+\rstatic char buff[1024];
+\r\rBOOL \r ConcatenateFiles(const char *path, const char *outputfile) \r
+{
+  \rHANDLE filscan;
+  \rWIN32_FIND_DATA fildata;
+  \rBOOL filflag;
+  \rDWORD status;
+  \rFILE * fo = NULL;
+  \rFILE * f = NULL;
+  \rsize_t bytes_in, bytes_out;
+  \rlong total_bytes = 0;
+  \rint total_files = 0;
+  \rchar directory[MAX_PATH];
+  \rchar fullname[MAX_PATH];
+  \r\rchar *p;
+  \r\r
+    /* If outputfile is an empty string, forget it. */ \r
+    if (!outputfile || !*outputfile)
+    \rreturn FALSE;
+  \r\r
+/* extract the directory from the path name */ \r
+    \rstrcpy(directory, path);
+  \rp = strrchr(directory, '\\');
+  \rif (p)
+    \rp[1] = 0;
+  \r
+  else {
+    \rp = strrchr(directory, '/');
+    \rif (p)
+      \rp[1] = 0;
+  \r}
+  \r\r
+/* Open output file */ \r
+    \rfo = fopen(outputfile, "wb");
+  \r\rif (!fo) {
+    \rdo_rawlog(LT_ERR, T("Unable to open file: %s"), outputfile);
+    \rreturn FALSE;
+  \r}
+  \rdo_rawlog(LT_ERR, T("Creating file: %s"), outputfile);
+  \r\r
+/* Find first file matching the wildcard */ \r
+    \rfilscan = FindFirstFile(path, &fildata);
+  \rif (filscan == INVALID_HANDLE_VALUE) {
+    \rstatus = GetLastError();
+    \r\rfclose(fo);
+    \r\rdo_rawlog(LT_ERR, "**** No files matching: \"%s\" found.", path);
+    \r\rif (status == ERROR_NO_MORE_FILES)
+      \rreturn TRUE;
+    \r
+    else
+      \rreturn FALSE;
+  \r}
+  \r
+/*\r
+   Now enter the concatenation loop.\r
+ */ \r
+    \r
+  do {
+    \rif (!(fildata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+      \r\rdo_rawlog(LT_ERR, "%s: %s, %ld %s", \rT("    Copying file"),
+                 \rfildata.cFileName, \rfildata.nFileSizeLow,
+                 \rfildata.nFileSizeLow == 1 ? T("byte") : T("bytes"));
+      \r\rstrcpy(fullname, directory);
+      \rstrcat(fullname, fildata.cFileName);
+      \r\r
+/* Open the input file */ \r
+       \rf = fopen(fullname, "rb");
+      \r\rif (!f)
+       \rdo_rawlog(LT_ERR, T("    ** Unable to open file: %s"), fullname);
+      \r
+      else {
+       \r\rtotal_files++;
+       \r\r
+         /* do the copy loop */ \r
+         \rwhile (!feof(f)) {
+         \rbytes_in = fread(buff, 1, sizeof(buff), f);
+         \rif (bytes_in <= 0)
+           \rbreak;
+         \r\rbytes_out = fwrite(buff, 1, bytes_in, fo);
+         \rtotal_bytes += bytes_out;
+         \rif (bytes_in != bytes_out) {
+           \rdo_rawlog(LT_ERR, T("Unable to write to file: %s"), outputfile);
+           \rfclose(f);
+           \rbreak;
+         \r}
+       \r}                      /* end of copy loop */
+       \r\r\rfclose(f);
+      \r}                       /* end of being able to open file */
+    \r\r}
+    \r
+      /* end of not being a directory */ \r
+      /* get next file matching the wildcard */ \r
+      filflag = FindNextFile(filscan, &fildata);
+  \r} while (filflag);
+  \r\rstatus = GetLastError();
+  \r\rFindClose(filscan);
+  \r\rfclose(fo);
+  \r\rdo_rawlog(LT_ERR, T("Copied %i %s, %ld %s"), \rtotal_files,
+             \rtotal_files == 1 ? T("file") : T("files"), \rtotal_bytes,
+             total_bytes == 1 ? T("byte") : T("bytes"));
+  \r\rif (status == ERROR_NO_MORE_FILES)
+    \rreturn TRUE;
+  \r
+  else
+    \rreturn FALSE;
+\r\r}
+\r\rint \r
+CheckDatabase(const char *path, FILETIME * modified, long *filesize) \r
+{
+  \rHANDLE filscan;
+  \rWIN32_FIND_DATA fildata;
+  \rSYSTEMTIME st;
+  \rstatic char *months[] =
+    { \r">!<", \r"Jan", "Feb", "Mar", "Apr", "May", "Jun", \r"Jul", "Aug", "Sep",
+    "Oct", "Nov", "Dec" \r
+  };
+  \rFILE * f;
+  \rsize_t bytes;
+  \r\rfilscan = FindFirstFile(path, &fildata);
+  \rif (filscan == INVALID_HANDLE_VALUE) {
+    \rdo_rawlog(LT_ERR, "File \"%s\" not found.", path);
+    \rreturn FALSE;
+  \r}
+  \r*modified = fildata.ftLastWriteTime;
+  \r*filesize = fildata.nFileSizeLow;
+  \r\rFindClose(filscan);
+  \r\rFileTimeToSystemTime(&fildata.ftLastWriteTime, &st);
+  \r\rif (st.wMonth < 1 || st.wMonth > 12)
+    \rst.wMonth = 0;
+  \r\rdo_rawlog(LT_ERR, \r\r
+             ("File \"%s\" found, size %ld %s, modified on %02d %s %04d %02d:%02d:%02d"),
+             \rpath, fildata.nFileSizeLow,
+             \rfildata.nFileSizeLow == 1 ? T("byte") : T("bytes"), st.wDay,
+             \rmonths[st.wMonth], st.wYear, st.wHour, st.wMinute, st.wSecond);
+  \r\rif (fildata.nFileSizeHigh == 0 && fildata.nFileSizeLow < 80) {
+    \rdo_rawlog(LT_ERR, T("File is too small to be a MUSH database."));
+    \rreturn FALSE;
+  \r}
+  \r
+/* check file for validity */ \r
+    \rf = fopen(path, "rb");
+  \r\rif (!f) {
+    \rdo_rawlog(LT_ERR, T("Unable to open file %s"), path);
+    \rreturn FALSE;
+  \r}
+  \rif (fseek(f, -80, SEEK_END) != 0) {
+    \rdo_rawlog(LT_ERR, T("Unable to check file %s"), path);
+    \rfclose(f);
+    \rreturn FALSE;
+  \r}
+  \rbytes = fread(buff, 1, 80, f);
+  \r\rfclose(f);
+  \r\rif (bytes != 80) {
+    \rdo_rawlog(LT_ERR, T("Unable to read last part of file %s"), path);
+    \rreturn FALSE;
+  \r}
+  \rif (strstr(buff, "***END OF DUMP***") == 0) {
+    \rdo_rawlog(LT_ERR, T("Database not terminated correctly, file %s"), path);
+    \rreturn FALSE;
+  \r}
+  \rreturn TRUE;
+\r\r}                            /* end of  CheckDatabase */
+\r\rvoid \r
+Win32MUSH_setup(void) \r
+{
+  \r\rint indb_OK, outdb_OK, panicdb_OK;
+  \r\rFILETIME indb_time, outdb_time, panicdb_time;
+  \r\rlong indb_size, outdb_size, panicdb_size;
+  \r\r
+#ifndef _DEBUG\r
+  \r\rchar FileName[256];
+  \rif (GetModuleFileName(NULL, FileName, 256) != 0) {
+    \rif (!strcasecmp(rindex(FileName, '\\') + 1, "pennmush.exe")) {
+      \rif (CopyFile("pennmush.exe", "pennmush_run.exe", FALSE)) {
+       \rdo_rawlog(LT_ERR, "Successfully copied executable, starting copy.");
+       \rexecl("pennmush_run.exe", "pennmush_run.exe", "/run", NULL);
+      \r}
+    \r}
+  \r}
+  \r\r
+#endif                         /* \r */
+    \rConcatenateFiles("txt\\hlp\\*.hlp", "txt\\help.txt");
+  \rConcatenateFiles("txt\\nws\\*.nws", "txt\\news.txt");
+  \rConcatenateFiles("txt\\evt\\*.evt", "txt\\events.txt");
+  \rConcatenateFiles("txt\\rul\\*.rul", "txt\\rules.txt");
+  \rConcatenateFiles("txt\\idx\\*.idx", "txt\\index.txt");
+  \r\rindb_OK = CheckDatabase(options.input_db, &indb_time, &indb_size);
+  \routdb_OK = CheckDatabase(options.output_db, &outdb_time, &outdb_size);
+  \rpanicdb_OK = CheckDatabase(options.crash_db, &panicdb_time, &panicdb_size);
+  \r\rif (indb_OK) {             /* Look at outdb */
+    \rif (outdb_OK) {           /* Look at panicdb */
+      \rif (panicdb_OK) {       /* outdb or panicdb or indb */
+       \rif (CompareFileTime(&panicdb_time, &outdb_time) > 0) { /* panicdb or indb */
+         \r\rif (CompareFileTime(&panicdb_time, &indb_time) > 0) {       /* panicdb */
+           \r\rConcatenateFiles(options.crash_db, options.input_db);
+         \r} else {             /* indb */
+         \r}
+       \r} else {               /* outdb or indb */
+         \rif (CompareFileTime(&outdb_time, &indb_time) > 0) {  /* outdb */
+           \r\rConcatenateFiles(options.output_db, options.input_db);
+         \r} else {             /* indb */
+         \r}
+       \r}
+      \r} else {                        /* outdb or indb */
+       \rif (CompareFileTime(&outdb_time, &indb_time) > 0) {    /* outdb */
+         \r\rConcatenateFiles(options.output_db, options.input_db);
+       \r} else {               /* indb */
+       \r}
+      \r}
+    \r} else {                  /* outdb not OK */
+      \rif (panicdb_OK) {       /* panicdb or indb */
+       \rif (CompareFileTime(&panicdb_time, &indb_time) > 0) {  /* panicdb */
+         \r\rConcatenateFiles(options.crash_db, options.input_db);
+       \r} else {               /* indb */
+       \r}
+      \r} else {                        /* indb */
+      \r}
+    \r}
+  \r} else {                    /* indb not OK */
+    \rif (outdb_OK) {           /* look at panicdb */
+      \rif (panicdb_OK) {       /* out or panic */
+       \rif (CompareFileTime(&panicdb_time, &outdb_time) > 0) { /* panicdb */
+         \r\rConcatenateFiles(options.crash_db, options.input_db);
+       \r} else {               /* outdb */
+         \r\rConcatenateFiles(options.output_db, options.input_db);
+       \r}
+      \r} else {                        /* outdb */
+       \rConcatenateFiles(options.output_db, options.input_db);
+      \r}
+    \r} else {                  /* outdb not OK */
+      \rif (panicdb_OK) {       /* panicdb */
+       \rConcatenateFiles(options.crash_db, options.input_db);
+      \r} else {                        /* NOTHING */
+       \rreturn;
+      \r}
+    \r}
+  \r}
+  \r
+/* Final failsafe - input database SHOULD still be OK. */ \r
+    do_rawlog(LT_ERR, T("Verifying selected database."));
+  \rif (!CheckDatabase(options.input_db, &indb_time, &indb_size)) {
+    \rdo_rawlog(LT_ERR, T("File corrupted during selection process."));
+    \rexit(-1);
+  \r} else {
+    \rdo_rawlog(LT_ERR, T("Input database verified. Proceeding to analysis."));
+  \r}
+\r}
+
+\r
+#endif /* WIN32 */\r
diff --git a/src/flaglocal.dst b/src/flaglocal.dst
new file mode 100644 (file)
index 0000000..dc87986
--- /dev/null
@@ -0,0 +1,41 @@
+/*-----------------------------------------------------------------
+ * Local flags
+ *
+ * This file is reserved for local flags that you may wish
+ * to hack into PennMUSH.
+ * This file will not be overwritten when you update
+ * to a new distribution, so it's preferable to add new flags
+ * here and leave flag.c alone.
+ *
+ * YOU ARE RESPONSIBLE FOR SEEING THAT YOU DO THIS RIGHT - 
+ * It's probably smarter to have God @flag/add rather than
+ * do it here, as @flag/add does extensive checks for safety,
+ * and add_flag() doesn't! Remember that flags are saved in the
+ * database, so flags added here won't overwrite flags that
+ * are already defined. When in doubt, use @flag.
+ *
+ * It is explicitly safe to try to add_flag() an existing flag.
+ * It won't do anything, but it won't be harmful.
+ *
+ */
+
+/* Here are some includes you're likely to need or want.
+ */
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "flags.h"
+#include "confmagic.h"
+
+void local_flags(void);
+
+
+void
+local_flags()
+{
+#ifdef EXAMPLE
+  add_flag("BIG", 'B', TYPE_PLAYER | TYPE_THING, F_ANY, F_ANY);
+#endif
+}
diff --git a/src/flags.c b/src/flags.c
new file mode 100644 (file)
index 0000000..c956d48
--- /dev/null
@@ -0,0 +1,2548 @@
+/**
+ * \file flags.c
+ *
+ * \brief Flags and powers (and sometimes object types) in PennMUSH
+ *
+ *
+ * Functions to cope with flags and powers (and also object types,
+ * in some cases).
+ *
+ * Flag functions actually involve with several related entities:
+ *  Flag definitions (FLAG objects)
+ *  Bitmasks representing sets of flags (object_flag_type's). The
+ *    bits involved may differ between dbs.
+ *  Strings of space-separated flag names. This is a string representation
+ *    of a bitmask, suitable for display and storage
+ *  Strings of flag characters
+ *
+ */
+
+#include "config.h"
+
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "boolexp.h"
+#include "command.h"
+#include "attrib.h"
+#include "mushdb.h"
+#include "parse.h"
+#include "match.h"
+#include "ptab.h"
+#include "htab.h"
+#include "privtab.h"
+#include "game.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "lock.h"
+#include "log.h"
+#include "dbio.h"
+#include "oldflags.h"
+#include "division.h"
+#include "ansi.h"
+#include "confmagic.h"
+
+
+
+static int can_set_flag(dbref player, dbref thing, FLAG *flagp, int negate);
+static FLAG *letter_to_flagptr(FLAGSPACE * n, char c, int type);
+static void flag_add(FLAGSPACE * n, const char *name, FLAG *f);
+static int has_flag(dbref thing, FLAG *f);
+
+static FLAG *flag_read(FILE * in);
+static FLAG *flag_read_oldstyle(FILE * in);
+static void flag_read_all_oldstyle(FILE * in, const char *ns);
+static void flag_write(FILE * out, FLAG *f, const char *name);
+static FLAG *flag_hash_lookup(FLAGSPACE * n, const char *name, int type);
+static FLAG *clone_flag(FLAG *f);
+static FLAG *new_flag(void);
+static void flag_add_additional(void);
+static char *list_aliases(FLAGSPACE * n, FLAG *given);
+static void realloc_object_flag_bitmasks(int numbytes);
+static FLAG *match_flag_ns(FLAGSPACE * n, const char *name);
+static void flag_fake_read(FILE *);
+
+PTAB ptab_flag;                        /**< Table of flags by name, inc. aliases */
+HASHTAB htab_flagspaces;               /**< Hash of flagspaces */
+
+extern PTAB ptab_command;      /* Uses flag bitmasks */
+extern long flagdb_flags;
+
+/** Attempt to find a flagspace from its name */
+#define Flagspace_Lookup(n,ns)  if (!(n = (FLAGSPACE *)hashfind(ns,&htab_flagspaces))) mush_panic("Unable to locate flagspace");
+
+/** This is the old default flag table. We still use it when we have to
+ * convert old dbs, but once you have a converted db, it's the flag
+ * table in the db that counts, not this one.
+ */
+/* Name     Letter   Type(s)   Flag   Perms   Negate_Perm */
+FLAG flag_table[] = {
+  {"CHOWN_OK", 'C', NOTYPE, CHOWN_OK, F_ANY, F_ANY},
+  {"DARK", 'D', NOTYPE, DARK, F_ANY, F_ANY},
+  {"GOING", 'G', NOTYPE, GOING, F_INTERNAL, F_ANY},
+  {"HAVEN", 'H', NOTYPE, HAVEN, F_ANY, F_ANY},
+  {"LINK_OK", 'L', NOTYPE, LINK_OK, F_ANY, F_ANY},
+  {"OPAQUE", 'O', NOTYPE, LOOK_OPAQUE, F_ANY, F_ANY},
+  {"QUIET", 'Q', NOTYPE, QUIET, F_ANY, F_ANY},
+  {"STICKY", 'S', NOTYPE, STICKY, F_ANY, F_ANY},
+  {"UNFINDABLE", 'U', NOTYPE, UNFIND, F_ANY, F_ANY},
+  {"VISUAL", 'V', NOTYPE, VISUAL, F_ANY, F_ANY},
+  {"SAFE", 'X', NOTYPE, SAFE, F_ANY, F_ANY},
+  {"AUDIBLE", 'a', NOTYPE, AUDIBLE, F_ANY, F_ANY},
+  {"DEBUG", 'b', NOTYPE, DEBUGGING, F_ANY, F_ANY},
+  {"NO_WARN", 'w', NOTYPE, NOWARN, F_ANY, F_ANY},
+  {"ENTER_OK", 'e', NOTYPE, ENTER_OK, F_ANY, F_ANY},
+  {"HALT", 'h', NOTYPE, HALT, F_ANY, F_ANY},
+  {"NO_COMMAND", 'n', NOTYPE, NO_COMMAND, F_ANY, F_ANY},
+  {"LIGHT", 'l', NOTYPE, LIGHT, F_ANY, F_ANY},
+  {"TRANSPARENT", 't', NOTYPE, TRANSPARENTED, F_ANY, F_ANY},
+  {"VERBOSE", 'v', NOTYPE, VERBOSE, F_ANY, F_ANY},
+  {"ANSI", 'A', TYPE_PLAYER, PLAYER_ANSI, F_ANY, F_ANY},
+  {"COLOR", 'C', TYPE_PLAYER, PLAYER_COLOR, F_ANY, F_ANY},
+  {"MONITOR", 'M', TYPE_PLAYER | TYPE_ROOM | TYPE_THING | TYPE_DIVISION, 0, F_ANY, F_ANY},
+  {"NOSPOOF", '"', TYPE_PLAYER, PLAYER_NOSPOOF, F_ANY | F_ODARK,
+   F_ANY | F_ODARK},
+  {"SHARED", 'Z', TYPE_PLAYER, PLAYER_ZONE, F_ANY, F_ANY},
+  {"TRACK_MONEY", '\0', TYPE_PLAYER, 0, F_ANY, F_ANY},
+  {"CONNECTED", 'c', TYPE_PLAYER, PLAYER_CONNECT, F_INTERNAL | F_MDARK,
+   F_INTERNAL | F_MDARK},
+  {"GAGGED", 'g', TYPE_PLAYER, PLAYER_GAGGED, F_PRIVILEGE, F_PRIVILEGE},
+  {"MYOPIC", 'm', TYPE_PLAYER, PLAYER_MYOPIC, F_ANY, F_ANY},
+  {"TERSE", 'x', TYPE_PLAYER | TYPE_THING, PLAYER_TERSE, F_ANY, F_ANY},
+  {"FIXED", 'F', TYPE_PLAYER, PLAYER_FIXED, F_PRIVILEGE, F_PRIVILEGE},
+#ifdef VACATION_FLAG
+  {"ON-VACATION", 'o', TYPE_PLAYER, PLAYER_VACATION, F_ANY, F_ANY},
+#endif
+  {"SUSPECT", 's', TYPE_PLAYER, PLAYER_SUSPECT, F_PRIVILEGE | F_DARK,
+   F_PRIVILEGE | F_DARK},
+  {"PARANOID", '\0', TYPE_PLAYER, PLAYER_PARANOID, F_ANY | F_ODARK,
+   F_ANY | F_ODARK},
+  {"INPROGRAM", '\0', TYPE_PLAYER, PLAYER_PROG, F_INTERNAL, F_INTERNAL},
+  {"NOACCENTS", '~', TYPE_PLAYER, PLAYER_NOACCENTS, F_ANY, F_ANY},
+  {"DESTROY_OK", 'd', TYPE_THING, THING_DEST_OK, F_ANY, F_ANY},
+  {"PUPPET", 'p', TYPE_THING | TYPE_DIVISION, THING_PUPPET, F_ANY, F_ANY},
+  {"NO_LEAVE", 'N', TYPE_THING, THING_NOLEAVE, F_ANY, F_ANY},
+  {"LISTEN_PARENT", '^', TYPE_THING | TYPE_ROOM | TYPE_DIVISION, 0, F_ANY, F_ANY},
+  {"Z_TEL", 'Z', TYPE_THING | TYPE_ROOM, 0, F_ANY, F_ANY},
+  {"ABODE", 'A', TYPE_ROOM, ROOM_ABODE, F_ANY, F_ANY},
+  {"FLOATING", 'F', TYPE_ROOM, ROOM_FLOATING, F_ANY, F_ANY},
+  {"JUMP_OK", 'J', TYPE_ROOM, ROOM_JUMP_OK, F_ANY, F_ANY},
+  {"NO_TEL", 'N', TYPE_ROOM, ROOM_NO_TEL, F_ANY, F_ANY},
+#ifdef UNINSPECTED_FLAG
+  {"UNINSPECTED", 'u', TYPE_ROOM, ROOM_UNINSPECT, F_PRIVILEGE, F_PRIVILEGE},
+#endif
+  {"CLOUDY", 'x', TYPE_EXIT, EXIT_CLOUDY, F_ANY, F_ANY},
+  {"GOING_TWICE", '\0', NOTYPE, GOING_TWICE, F_INTERNAL | F_DARK,
+   F_INTERNAL | F_DARK},
+  {NULL, '\0', 0, 0, 0, 0}
+};
+
+/** The old table to kludge multi-type toggles. Now used only 
+ * for conversion.
+ */
+static FLAG hack_table[] = {
+  {"MONITOR", 'M', TYPE_PLAYER, PLAYER_MONITOR, F_PRIVILEGE, F_PRIVILEGE},
+  {"MONITOR", 'M', TYPE_THING, THING_LISTEN, F_ANY, F_ANY},
+  {"MONITOR", 'M', TYPE_ROOM, ROOM_LISTEN, F_ANY, F_ANY},
+  {"LISTEN_PARENT", '^', TYPE_THING, THING_INHEARIT, F_ANY, F_ANY},
+  {"LISTEN_PARENT", '^', TYPE_ROOM, ROOM_INHEARIT, F_ANY, F_ANY},
+  {"Z_TEL", 'Z', TYPE_THING, THING_Z_TEL, F_ANY, F_ANY},
+  {"Z_TEL", 'Z', TYPE_ROOM, ROOM_Z_TEL, F_ANY, F_ANY},
+  {NULL, '\0', 0, 0, 0, 0}
+};
+
+
+/** A table of types, as if they were flags. Some functions that
+ * expect flags also accept, for historical reasons, types.
+ */
+static FLAG type_table[] = {
+  {"PLAYER", 'P', TYPE_PLAYER, TYPE_PLAYER, F_INTERNAL, F_INTERNAL},
+  {"ROOM", 'R', TYPE_ROOM, TYPE_ROOM, F_INTERNAL, F_INTERNAL},
+  {"EXIT", 'E', TYPE_EXIT, TYPE_EXIT, F_INTERNAL, F_INTERNAL},
+  {"THING", 'T', TYPE_THING, TYPE_THING, F_INTERNAL, F_INTERNAL},
+  {"DIVISION", 'D', TYPE_DIVISION, TYPE_DIVISION, F_INTERNAL, F_INTERNAL},
+  {NULL, '\0', 0, 0, 0, 0}
+};
+
+/** A table of types, as privileges. */
+static PRIV type_privs[] = {
+  {"PLAYER", 'P', TYPE_PLAYER, TYPE_PLAYER},
+  {"ROOM", 'R', TYPE_ROOM, TYPE_ROOM},
+  {"EXIT", 'E', TYPE_EXIT, TYPE_EXIT},
+  {"THING", 'T', TYPE_THING, TYPE_THING},
+  {"DIVISION", 'D', TYPE_DIVISION, TYPE_DIVISION},
+  {NULL, '\0', 0, 0}
+};
+
+/** The old default aliases for flags. This table is only used in conversion
+ * of old databases. Once a database is converted, the alias list in the
+ * database is what counts.
+ */
+static FLAG_ALIAS flag_alias_tab[] = {
+  {"TRACE", "DEBUG"},
+  {"NOWARN", "NO_WARN"},
+  {"NOCOMMAND", "NO_COMMAND"},
+  {"LISTENER", "MONITOR"},
+  {"WATCHER", "MONITOR"},
+  {"ZONE", "SHARED"},
+  {"COLOUR", "COLOR"},
+#ifdef VACATION_FLAG
+  {"VACATION", "ON-VACATION"},
+#endif
+  {"DEST_OK", "DESTROY_OK"},
+  {"NOLEAVE", "NO_LEAVE"},
+  {"TEL_OK", "JUMP_OK"},
+  {"TELOK", "JUMP_OK"},
+  {"TEL-OK", "JUMP_OK"},
+  {"^", "LISTEN_PARENT"},
+
+  {NULL, NULL}
+};
+
+/** The table of flag privilege bits. */
+static PRIV flag_privs[] = {
+  {"owned", '\0', F_OWNED, F_OWNED},
+  {"privilege", '\0', F_PRIVILEGE, F_PRIVILEGE},
+  {"god", '\0', F_GOD, F_GOD},
+  {"internal", '\0', F_INTERNAL, F_INTERNAL},
+  {"dark", '\0', F_DARK, F_DARK},
+  {"mdark", '\0', F_MDARK, F_MDARK},
+  {"odark", '\0', F_ODARK, F_ODARK},
+  {"disabled", '\0', F_DISABLED, F_DISABLED},
+  {NULL, '\0', 0, 0}
+};
+
+
+
+/*---------------------------------------------------------------------------
+ * Flag definition functions, including flag hash table handlers 
+ */
+
+/** Convenience function to return a pointer to a flag struct
+ * given the name.
+ * \param name name of flag to find.
+ * \return poiner to flag structure, or NULL.
+ */
+FLAG *
+match_flag(const char *name)
+{
+  return (FLAG *) match_flag_ns(hashfind("FLAG", &htab_flagspaces), name);
+}
+
+/** Convenience function to return a pointer to a flag struct
+ * given the name.
+ * \param name name of flag to find.
+ * \return poiner to flag structure, or NULL.
+ */
+static FLAG *
+match_flag_ns(FLAGSPACE * n, const char *name)
+{
+  return (FLAG *) ptab_find(n->tab, name);
+}
+
+/** Given a flag name and mask of types, return a pointer to a flag struct.
+ * This function first attempts to match the flag name to a flag of the
+ * right type. If that fails, it tries to match flag characters if the
+ * name is a single character. If all else fails, it tries to match
+ * against an object type name.
+ * \param n pointer to flagspace to search.
+ * \param name name of flag to find.
+ * \param type mask of desired flag object types.
+ * \return pointer to flag structure, or NULL.
+ */
+static FLAG *
+flag_hash_lookup(FLAGSPACE * n, const char *name, int type)
+{
+  FLAG *f;
+
+  f = match_flag_ns(n, name);
+  if (f && !(f->perms & F_DISABLED)) {
+    if (f->type & type)
+      return f;
+    return NULL;
+  }
+
+  /* If the name is a single character, search the flag characters */
+  if (name && *name && !*(name + 1)) {
+    if ((f = letter_to_flagptr(n, *name, type)))
+      return f;
+  }
+
+  /* provided for backwards compatibility: type flag checking */
+  if (n->flag_table == flag_table) {
+    for (f = type_table; f->name != NULL; f++)
+      if (string_prefix(name, f->name))
+       return f;
+  }
+
+  return NULL;
+}
+
+/* Allocate a new FLAG definition */
+static FLAG *
+new_flag(void)
+{
+  FLAG *f = (FLAG *) mush_malloc(sizeof(FLAG), "flag");
+  if (!f)
+    mush_panic("Unable to allocate memory for a new flag!\n");
+  return f;
+}
+
+/* Deallocate all flag-related memory */
+static void
+clear_all_flags(FLAGSPACE * n)
+{
+  ptab_free(n->tab);
+  /* Finally, the flags array */
+  if (n->flags)
+    free(n->flags);
+  n->flags = NULL;
+  n->flagbits = 0;
+}
+
+static FLAG *
+clone_flag(FLAG *f)
+{
+  FLAG *clone = new_flag();
+  clone->name = mush_strdup(f->name, "flag name");
+  clone->letter = f->letter;
+  clone->type = f->type;
+  clone->bitpos = f->bitpos;
+  clone->perms = f->perms;
+  clone->negate_perms = f->negate_perms;
+  return clone;
+}
+
+/* This is a stub function to add a flag. It performs no error-checking,
+ * so it's up to you to be sure you're adding a flag that's properly
+ * set up and that'll work ok. If called with autopos == 0, this
+ * auto-allocates the next bitpos. Otherwise, bitpos is ignored and
+ * f->bitpos is used.
+ */
+static void
+flag_add(FLAGSPACE * n, const char *name, FLAG *f)
+{
+  /* If this flag has no bitpos assigned, assign it the next one.
+   * We could improve this algorithm to use the next available
+   * slot after deletions, too, but this will do for now.
+   */
+  if (f->bitpos < 0)
+    f->bitpos = n->flagbits;
+
+  /* Insert the flag in the ptab by the given name (maybe an alias) */
+  ptab_start_inserts(n->tab);
+  ptab_insert(n->tab, name, f);
+  ptab_end_inserts(n->tab);
+
+  /* Is this a canonical flag (as opposed to an alias?)
+   * If it's an alias, we're done.
+   * A canonical flag has either been given a new bitpos
+   * or has not yet been stored in the flags array.
+   * (An alias would have a previously used bitpos that's already 
+   * indexing a flag in the flags array)
+   */
+  if ((f->bitpos >= n->flagbits) || (n->flags[f->bitpos] == NULL)) {
+    /* It's a canonical flag */
+    int i;
+    if (f->bitpos >= n->flagbits) {
+      /* Oops, we need a bigger array */
+      if (n->flagbits == 0)
+       n->flags = (FLAG **) malloc(sizeof(FLAG *));
+      else {
+       n->flags =
+         (FLAG **) realloc(n->flags, (f->bitpos + 1) * sizeof(FLAG *));
+       if (!n->flags)
+         mush_panic("Unable to reallocate flags array!\n");
+      }
+      /* Make sure the new space is full of NULLs */
+      for (i = n->flagbits; i <= f->bitpos; i++)
+       n->flags[i] = NULL;
+    }
+    /* Put the canonical flag in the flags array */
+    n->flags[f->bitpos] = f;
+    n->flagbits = f->bitpos + 1;
+    if (n->flagbits % 8 == 1) {
+      /* We've crossed over a byte boundary, so we need to realloc
+       * all the flags on all our objects to get them an additional
+       * byte.
+       */
+      realloc_object_flag_bitmasks((n->flagbits + 7) / 8);
+    }
+  }
+}
+
+/* Eventually, this need to compute numbytes itself from the
+ * total bytes required for flags + powers
+ */
+static void
+realloc_object_flag_bitmasks(int numbytes)
+{
+  dbref it;
+  object_flag_type p;
+
+  for (it = 0; it < db_top; it++) {
+    Flags(it) = (object_flag_type) realloc(Flags(it), numbytes);
+    /* Zero them out */
+    p = Flags(it) + numbytes - 1;
+    memset(p, 0, 1);
+  }
+}
+
+
+/* Read in a flag from a file and return it */
+static FLAG *
+flag_read_oldstyle(FILE * in)
+{
+  FLAG *f;
+  char *c;
+  c = mush_strdup(getstring_noalloc(in), "flag name");
+  if (!strcmp(c, "FLAG ALIASES")) {
+    mush_free(c, "flag name");
+    return NULL;               /* We're done */
+  }
+  f = new_flag();
+  f->name = c;
+  c = (char *) getstring_noalloc(in);
+  f->letter = *c;
+  f->bitpos = -1;
+  f->type = getref(in);
+  f->perms = getref(in);
+  f->negate_perms = getref(in);
+  return f;
+}
+
+static FLAG *
+flag_alias_read_oldstyle(FILE * in, char *alias)
+{
+  FLAG *f;
+  char *c;
+  /* Real name first */
+  c = mush_strdup(getstring_noalloc(in), "flag alias");
+  if (!strcmp(c, "END OF FLAGS")) {
+    mush_free(c, "flag alias");
+    return NULL;               /* We're done */
+  }
+  f = match_flag(c);
+  if (!f) {
+    /* Corrupt db. Recover as well as we can. */
+    do_rawlog(LT_ERR,
+             T
+             ("FLAG READ: flag alias %s matches no known flag. Skipping aliases."),
+             c);
+    mush_free(c, "flag alias");
+    do {
+      c = (char *) getstring_noalloc(in);
+    } while (strcmp(c, "END OF FLAGS"));
+    return NULL;
+  } else
+    mush_free(c, "flag alias");
+
+  /* Get the alias name */
+  strcpy(alias, getstring_noalloc(in));
+  return f;
+}
+
+/** Read flags and aliases from the database. This function expects
+ * to receive file pointer that's already reading in a database file
+ * and pointing at the start of the flag table. It reads the flags,
+ * reads the aliases, and then does any additional flag adding that
+ * needs to happen.
+ * \param in file pointer to read from.
+ * \param ns name of namespace to search.
+ */
+static void
+flag_read_all_oldstyle(FILE * in, const char *ns)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  char alias[BUFFER_LEN];
+
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG READ: Unable to locate flagspace %s."), ns);
+    return;
+  }
+  /* If we are reading flags from the db, they are definitive. */
+  clear_all_flags(n);
+  while ((f = flag_read_oldstyle(in))) {
+    flag_add(n, f->name, f);
+  }
+  /* Assumes we'll always have at least one alias */
+  while ((f = flag_alias_read_oldstyle(in, alias))) {
+    flag_add(n, alias, f);
+  }
+  flag_add_additional();
+}
+
+/* Read in a flag from a file and return it */
+static FLAG *
+flag_read(FILE * in)
+{
+  FLAG *f;
+  char *c;
+  char *tmp;
+
+  db_read_this_labeled_string(in, "name", &tmp);
+  c = mush_strdup(tmp, "flag name");
+  f = new_flag();
+  f->name = c;
+  db_read_this_labeled_string(in, "letter", &tmp);
+  f->letter = *tmp;
+  f->bitpos = -1;
+  db_read_this_labeled_string(in, "type", &tmp);
+  if(!strcmp(tmp, "ANY"))
+    f->type = ALLTYPES;
+  else
+    f->type = string_to_privs(type_privs, tmp, 0);
+  db_read_this_labeled_string(in, "perms", &tmp);
+  f->perms = string_to_privs(flag_privs, tmp, 0);
+  db_read_this_labeled_string(in, "negate_perms", &tmp);
+  f->negate_perms = string_to_privs(flag_privs, tmp, 0);
+  return f;
+}
+
+static FLAG *
+flag_alias_read(FILE * in, char *alias, FLAGSPACE * n)
+{
+  FLAG *f;
+  char *c;
+  char *tmp;
+  /* Real name first */
+  db_read_this_labeled_string(in, "name", &tmp);
+  c = mush_strdup(tmp, "flag alias");
+  f = match_flag_ns(n, c);
+  if (!f) {
+    /* Corrupt db. Recover as well as we can. */
+    do_rawlog(LT_ERR,
+             T
+             ("FLAG READ: flag alias %s matches no known flag. Skipping this alias."),
+             c);
+    mush_free(c, "flag alias");
+    (void) getstring_noalloc(in);
+    return NULL;
+  } else
+    mush_free(c, "flag alias");
+  /* Get the alias name */
+  db_read_this_labeled_string(in, "alias", &tmp);
+  strcpy(alias, tmp);
+  return f;
+}
+/** Read flags and aliases from the database. This function expects
+ * to receive file pointer that's already reading in a database file
+ * and pointing at the start of the flag table. It reads the flags,
+ * reads the aliases, and then does any additional flag adding that
+ * needs to happen.
+ * \param in file pointer to read from.
+ * \param ns name of namespace to search.
+ */
+void
+flag_read_all(FILE * in, const char *ns)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  char alias[BUFFER_LEN];
+  int count;
+  if (!(flagdb_flags & DBF_LABELS)) {
+    flag_read_all_oldstyle(in, ns);
+    return;
+  }
+
+  if(ns) {
+    if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+      do_rawlog(LT_ERR, T("FLAG READ: Unable to locate flagspace %s."), ns);
+      return;
+    }
+  } else {
+    do_rawlog(LT_ERR, T("FLAG READ: Fake Powers List loading.") );
+    flag_fake_read(in);
+    return;
+  }
+
+  /* If we are reading flags from the db, they are definitive. */
+  clear_all_flags(n);
+  db_read_this_labeled_number(in, "flagcount", &count);
+  for (; count > 0; count--) {
+    if ((f = flag_read(in))) 
+     flag_add(n, f->name, f);
+  }
+ /* Assumes we'll always have at least one alias */
+  db_read_this_labeled_number(in, "flagaliascount", &count);
+  for (; count > 0; count--) {
+    if ((f = flag_alias_read(in, alias, n)))
+      flag_add(n, alias, f);
+  }
+  flag_add_additional();
+}
+
+static void flag_fake_read(FILE *f) {
+  int count;
+
+  /* Read shit into NULL-ville */
+  db_read_this_labeled_number(f, "flagcount", &count);
+
+  for(; count > 0; count--) {
+    getstring_noalloc(f);
+    getstring_noalloc(f);
+    getstring_noalloc(f);
+    getstring_noalloc(f);
+    getstring_noalloc(f);
+  }
+
+  db_read_this_labeled_number(f, "flagaliascount", &count);
+  for(; count > 0; count--) {
+    getstring_noalloc(f);
+    getstring_noalloc(f);
+  }
+}
+
+/* Write a flag out to a file */
+static void
+flag_write(FILE * out, FLAG *f, const char *name)
+{
+  db_write_labeled_string(out, " name", name);
+ db_write_labeled_string(out, "  letter", tprintf("%c", f->letter));
+ db_write_labeled_string(out, "  type", f->type == ALLTYPES ? "ANY"  :privs_to_string(type_privs, f->type));
+ db_write_labeled_string(out, "  perms",
+                        privs_to_string(flag_privs, f->perms));
+ db_write_labeled_string(out, "  negate_perms",
+                        privs_to_string(flag_privs, f->negate_perms));
+}
+
+
+/* Write a flag alias out to a file */
+static void
+flag_alias_write(FILE * out, FLAG *f, const char *name)
+{
+  db_write_labeled_string(out, " name", f->name);
+  db_write_labeled_string(out, "  alias", name);
+}
+
+/** Write flags and aliases to the database. This function expects
+ * to receive file pointer that's already writing in a database file.
+ * It writes the flags, writes the aliases.
+ * \param out file pointer to write to.
+ */
+void
+flag_write_all(FILE * out, const char *ns)
+{
+  int i, count;
+  FLAG *f;
+  FLAGSPACE *n;
+  char flagname[BUFFER_LEN];
+
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG WRITE: Unable to locate flagspace %s."), ns);
+    return;
+  }
+  /* Write out canonical flags first */
+  count = 0;
+  for (i = 0; i < n->flagbits; i++) {
+    if (n->flags[i])
+      count++;
+  }
+  db_write_labeled_number(out, "flagcount", count);
+  for (i = 0; i < n->flagbits; i++) {
+    if (n->flags[i])
+      flag_write(out, n->flags[i], n->flags[i]->name);
+  }
+  /* Now write out aliases. An alias is a flag in the ptab whose
+   * name isn't the same as the name of the canonical flag in its
+   * bit position
+   */
+  count = 0;
+  f = ptab_firstentry_new(n->tab, flagname);
+  while (f) {
+    if (strcmp(n->flags[f->bitpos]->name, flagname))
+      count++;
+    f = ptab_nextentry_new(n->tab, flagname);
+  }
+  db_write_labeled_number(out, "flagaliascount", count);
+  f = ptab_firstentry_new(n->tab, flagname);
+  while (f) {
+    if (strcmp(n->flags[f->bitpos]->name, flagname)) {
+      /* This is an alias! */
+      flag_alias_write(out, f, flagname);
+    }
+    f = ptab_nextentry_new(n->tab, flagname);
+  }
+}
+
+/** Initialize the flagspaces.
+ */
+void
+init_flagspaces(void)
+{
+  FLAGSPACE *flags;
+
+  hashinit(&htab_flagspaces, 4, sizeof(FLAGSPACE));
+  flags = (FLAGSPACE *) mush_malloc(sizeof(FLAGSPACE), "flagspace");
+  flags->tab = &ptab_flag;
+  flags->flagbits = 0;
+  flags->flags = NULL;
+  flags->flag_table = flag_table;
+  flags->flag_alias_table = flag_alias_tab;
+  hashadd("FLAG", (void *) flags, &htab_flagspaces);
+}
+
+
+/** Initialize a flag table with defaults.
+ * This function loads the standard flags as a baseline 
+ * (and for dbs that haven't yet converted).
+ * \param ns name of flagspace to initialize.
+ */
+void
+init_flag_table(const char *ns)
+{
+  FLAG *f, *cf;
+  FLAG_ALIAS *a;
+  FLAGSPACE *n;
+
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG INIT: Unable to locate flagspace %s."), ns);
+    return;
+  }
+
+  ptab_init(n->tab);
+  /* do regular flags first */
+  for (f = n->flag_table; f->name; f++) {
+    cf = clone_flag(f);
+    cf->bitpos = -1;
+    flag_add(n, cf->name, cf);
+  }
+  /* now add in the aliases */
+  for (a = n->flag_alias_table; a->alias; a++) {
+    if ((f = match_flag_ns(n, a->realname)))
+      flag_add(n, a->alias, f);
+    else
+      do_rawlog(LT_ERR,
+               T("FLAG INIT: flag alias %s matches no known flag."), a->alias);
+  }
+  flag_add_additional();
+}
+
+/* This is where the developers will put flag_add statements to create
+ * new flags in future penn versions. Hackers should avoid this,
+ * and use local_flags() in flaglocal.c instead.
+ */
+static void
+flag_add_additional(void)
+{
+  FLAG *f;
+#ifdef RPMODE_SYS
+  add_flag("ICFUNCS", '\0', TYPE_ROOM, F_PRIVILEGE, F_PRIVILEGE);
+#endif
+  add_flag("EMPIRE", 'E', TYPE_DIVISION, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("INHERIT", 'I', TYPE_THING | TYPE_EXIT | TYPE_ROOM, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("INHERITABLE", 'I', TYPE_PLAYER, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("ZCLONE_OK", '\0', TYPE_THING, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("WEIRDSITE", '\0', TYPE_PLAYER, F_GOD | F_DARK, F_GOD | F_DARK);
+  add_flag("MISTRUST", 'm', TYPE_THING | TYPE_EXIT | TYPE_ROOM, F_PRIVILEGE,
+          F_PRIVILEGE);
+  add_flag("ORPHAN", 'i', NOTYPE, F_ANY, F_ANY);
+  add_flag("TRACK_MONEY", '\0', TYPE_PLAYER, F_ANY, F_ANY);
+  add_flag("INPROGRAM", '\0', TYPE_PLAYER, F_INTERNAL, F_INTERNAL);
+  add_flag("BUILDER", '\0', TYPE_PLAYER, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("AUTH_PARENT", '\0', NOTYPE, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("MUTE", '\0', TYPE_PLAYER, F_PRIVILEGE, F_PRIVILEGE);
+#ifdef RPMODE_SYS
+  add_flag("RPMODE", '\0', TYPE_PLAYER, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("BLIND", '\0', TYPE_PLAYER, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("PARALYZED", '\0', TYPE_PLAYER, F_PRIVILEGE, F_PRIVILEGE);
+  add_flag("RPAPPROVED", '\0', TYPE_THING, F_PRIVILEGE, F_PRIVILEGE);
+#endif
+  if ((f = match_flag("TERSE")))
+    f->type |= TYPE_THING;
+  if ((f = match_flag("PUPPET")))
+    f->type |= TYPE_ROOM | TYPE_DIVISION;
+  if ((f = match_flag("NOSPOOF"))) {
+    f->type = NOTYPE;
+    f->letter = '"';
+  }
+  if ((f = match_flag("PARANOID"))) {
+    f->type = NOTYPE;
+    f->letter = '\0';
+  }
+  f = add_flag("CHAN_USEFIRSTMATCH", '\0', NOTYPE, F_PRIVILEGE, F_PRIVILEGE);
+  if (!match_flag("CHAN_FIRSTMATCH"))
+    flag_add(hashfind("FLAG", &htab_flagspaces), "CHAN_FIRSTMATCH", f);
+  if (!match_flag("CHAN_MATCHFIRST"))
+    flag_add(hashfind("FLAG", &htab_flagspaces), "CHAN_MATCHFIRST", f);
+
+  local_flags();
+}
+
+/** Extract object type from old-style flag value.
+ * Before 1.7.7p5, object types were stored in the lowest 3 bits of the
+ * flag value. Now they get their own place in the object structure,
+ * but if we're reading an older database, we need to extract the types
+ * from the old flag value.
+ * \param old_flags an old-style flag bitmask.
+ * \return a type bitflag.
+ */
+int
+type_from_old_flags(long old_flags)
+{
+  switch (old_flags & OLD_TYPE_MASK) {
+  case OLD_TYPE_PLAYER:
+    return TYPE_PLAYER;
+  case OLD_TYPE_ROOM:
+    return TYPE_ROOM;
+  case OLD_TYPE_EXIT:
+    return TYPE_EXIT;
+  case OLD_TYPE_THING:
+    return TYPE_THING;
+  case OLD_TYPE_GARBAGE:
+    return TYPE_GARBAGE;
+  case OLD_TYPE_DIVISION:
+    return TYPE_DIVISION;
+  }
+  /* If we get here, we're in trouble. */
+  return -1;
+}
+
+/** Extract flags from old-style flag and toggle values.
+ * This function takes the flag and toggle bitfields from older databases,
+ * allocates a new flag bitmask, and populates it appropriately
+ * by looking up each flag/toggle value in the old flag table.
+ * \param old_flags an old-style flag bitmask.
+ * \param old_toggles an old-style toggle bitmask.
+ * \param type the object type.
+ * \return a newly allocated flag bitmask representing the flags and toggles.
+ */
+object_flag_type
+flags_from_old_flags(long old_flags, long old_toggles, int type)
+{
+  FLAG *f, *newf;
+  object_flag_type bitmask = new_flag_bitmask("FLAG");
+
+  for (f = flag_table; f->name; f++) {
+    if (f->type == NOTYPE) {
+      if (f->bitpos & old_flags) {
+       newf = match_flag(f->name);
+       set_flag_bitmask(bitmask, newf->bitpos);
+      }
+    } else if (f->type & type) {
+      if (f->bitpos & old_toggles) {
+       newf = match_flag(f->name);
+       set_flag_bitmask(bitmask, newf->bitpos);
+      }
+    }
+  }
+  for (f = hack_table; f->name; f++) {
+    if ((f->type & type) && (f->bitpos & old_toggles)) {
+      newf = match_flag(f->name);
+      set_flag_bitmask(bitmask, newf->bitpos);
+    }
+  }
+  return bitmask;
+}
+
+/** Macro to detrmine if flag f's name is n */
+#define is_flag(f,n)    (!strcmp(f->name,n))
+
+/* Given a single character, return the matching flag definition */
+static FLAG *
+letter_to_flagptr(FLAGSPACE * n, char c, int type)
+{
+  FLAG *f;
+  int i;
+  for (i = 0; i < n->flagbits; i++)
+    if ((f = n->flags[i])) {
+      /* Doh! Kludge-city. We'll ignore the CHOWN_OK flag on players, because
+       * it's more useful to check 'C' as COLOR. Argle.
+       */
+      if (!(is_flag(f, "CHOWN_OK") && (type == TYPE_PLAYER)) &&
+         ((f->letter == c) && (f->type & type)))
+       return f;
+    }
+  /* Do we need to do this? */
+  return NULL;
+}
+
+
+/*----------------------------------------------------------------------
+ * Functions for managing bitmasks
+ */
+
+/** Locate a specific byte given a bit position */
+#define FlagByte(x) (x / 8)
+/** Locate a specific bit within a byte given a bit position */
+#define FlagBit(x) (7 - (x % 8))
+/** How many bytes do we need for a flag bitmask? */
+#define FlagBytes(n)  ((size_t)((n->flagbits + 7) / 8))
+
+
+/** Allocate a new flag bitmask.
+ * This function allocates a new flag bitmask of sufficient length
+ * to include the current number of flags. It zeroes out the entire
+ * bitmask and returns it.
+ * \return a newly allocated zeroed flag bitmask.
+ */
+object_flag_type
+new_flag_bitmask(const char *ns)
+{
+  object_flag_type bitmask;
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, ns);
+  bitmask = (object_flag_type) mush_malloc(FlagBytes(n), "flag_bitmask");
+  if (!bitmask)
+    mush_panic("Unable to allocate memory for flag bitmask");
+  memset(bitmask, 0, FlagBytes(n));
+  return bitmask;
+}
+
+/** Copy flag bitmask, allocating a new bitmask for the copy.
+ * This function allocates a new flag bitmask of sufficient length
+ * to include the current number of flags, and copies all the flags
+ * from a given bitmask into the new bitmask.
+ * \param ns name of namespace to search.
+ * \param given a flag bitmask.
+ * \return a newly allocated clone of the given bitmask.
+ */
+object_flag_type
+clone_flag_bitmask(const char *ns, object_flag_type given)
+{
+  object_flag_type bitmask;
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, ns);
+  bitmask = (object_flag_type) mush_malloc(FlagBytes(n), "flag_bitmask");
+  if (!bitmask)
+    mush_panic("Unable to allocate memory for flag bitmask");
+  memcpy(bitmask, given, FlagBytes(n));
+  return bitmask;
+}
+
+/** Copy one flag bitmask into another (already allocated).
+ * This is a convenience function - it's memcpy for flag bitmasks.
+ * \param ns name of namespace to search.
+ * \param dest destination bitmask for the given data.
+ * \param given a flag bitmask to copy.
+ */
+/* Copy a given bitmask to an already-allocated destination bitmask */
+void
+copy_flag_bitmask(const char *ns, object_flag_type dest, object_flag_type given)
+{
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, ns);
+  memcpy((void *) dest, (void *) given, FlagBytes(n));
+}
+
+/** Deallocate a flag bitmask.
+ * \param bitmask a flag bitmask to free.
+ */
+void
+destroy_flag_bitmask(object_flag_type bitmask)
+{
+  mush_free((Malloc_t) bitmask, "flag_bitmask");
+}
+
+/** Add a bit into a bitmask.
+ * This function sets a particular bit in a bitmask (e.g. bit 42), 
+ * by computing the appropriate byte, and the appropriate bit within the byte,
+ * and setting it.
+ * \param bitmask a flag bitmask.
+ * \param bit the bit to set.
+ */
+void
+set_flag_bitmask(object_flag_type bitmask, int bit)
+{
+  int bytepos = FlagByte(bit);
+  int bitpos = FlagBit(bit);
+  if (!bitmask)
+    return;
+  *(bitmask + bytepos) |= (1 << bitpos);
+}
+
+/** Add a bit into a bitmask.
+ * This function clears a particular bit in a bitmask (e.g. bit 42), 
+ * by computing the appropriate byte, and the appropriate bit within the byte,
+ * and clearing it.
+ * \param bitmask a flag bitmask.
+ * \param bit the bit to clear.
+ */
+void
+clear_flag_bitmask(object_flag_type bitmask, int bit)
+{
+  int bytepos = FlagByte(bit);
+  int bitpos = FlagBit(bit);
+  if (!bitmask)
+    return;
+  *(bitmask + bytepos) &= ~(1 << bitpos);
+}
+
+/** Test a bit in a bitmask.
+ * This function tests a particular bit in a bitmask (e.g. bit 42), 
+ * by computing the appropriate byte, and the appropriate bit within the byte,
+ * and testing it.
+ * \param bitmask a flag bitmask.
+ * \param bitpos the bit to test.
+ * \retval 1 bit is set.
+ * \retval 0 bit is not set.
+ */
+int
+has_bit(object_flag_type bitmask, int bitpos)
+{
+  int bytepos, bits_in_byte;
+  /* Garbage objects, for example, have no bits set */
+  if (!bitmask)
+    return 0;
+  bytepos = FlagByte(bitpos);
+  bits_in_byte = FlagBit(bitpos);
+  return *(bitmask + bytepos) & (1 << bits_in_byte);
+}
+
+/** Test a set of bits in one bitmask against all those in another.
+ * This function determines if one bitmask contains (at least)
+ * all of the bits set in another bitmask.
+ * \param ns name of namespace to search.
+ * \param source the bitmask to test.
+ * \param bitmask the bitmask containing the bits to look for.
+ * \retval 1 all bits in bitmask are set in source.
+ * \retval 0 at least one bit in bitmask is not set in source.
+ */
+int
+has_all_bits(const char *ns, object_flag_type source, object_flag_type bitmask)
+{
+  unsigned int i;
+  int ok = 1;
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, ns);
+  for (i = 0; i < FlagBytes(n); i++)
+    ok &= ((*(bitmask + i) & *(source + i)) == *(bitmask + i));
+  return ok;
+}
+
+/** Test to see if a bitmask is entirely 0 bits.
+ * \param ns name of namespace to search.
+ * \param source the bitmask to test.
+ * \retval 1 all bits in bitmask are 0.
+ * \retval 0 at least one bit in bitmask is 1.
+ */
+int
+null_flagmask(const char *ns, object_flag_type source)
+{
+  unsigned int i;
+  int bad = 0;
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, ns);
+  for (i = 0; i < FlagBytes(n); i++)
+    bad |= *(source + i);
+  return (!bad);
+}
+
+/** Test a set of bits in one bitmask against any of those in another.
+ * This function determines if one bitmask contains any
+ * of the bits set in another bitmask.
+ * \param ns name of namespace to search.
+ * \param source the bitmask to test.
+ * \param bitmask the bitmask containing the bits to look for.
+ * \retval 1 at least one bit in bitmask is set in source.
+ * \retval 0 no bits in bitmask are set in source.
+ */
+int
+has_any_bits(const char *ns, object_flag_type source, object_flag_type bitmask)
+{
+  unsigned int i;
+  int ok = 0;
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, ns);
+  for (i = 0; i < FlagBytes(n); i++)
+    ok |= (*(bitmask + i) & *(source + i));
+  return ok;
+}
+
+/** Produce a space-separated list of flag names, given a bitmask.
+ * This function returns the string representation of a flag bitmask.
+ * \param ns name of namespace to search.
+ * \param bitmask a flag bitmask.
+ * \param privs dbref for privilege checking for flag visibility.
+ * \param thing object for which bitmask is the flag bitmask.
+ * \return string representation of bitmask (list of flags).
+ */
+const char *
+bits_to_string(const char *ns, object_flag_type bitmask, dbref privs,
+              dbref thing)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  int i;
+  int first = 1;
+  static char buf[BUFFER_LEN];
+  char *bp;
+
+  Flagspace_Lookup(n, ns);
+  bp = buf;
+  for (i = 0; i < n->flagbits; i++) {
+    if ((f = n->flags[i])) {
+      if (has_bit(bitmask, f->bitpos) &&
+         (!GoodObject(thing) || Can_See_Flag(privs, thing, f))) {
+       if (!first)
+         safe_chr(' ', buf, &bp);
+       safe_str(f->name, buf, &bp);
+       first = 0;
+      }
+    }
+  }
+  *bp = '\0';
+  return buf;
+}
+
+/** Convert a flag list string to a bitmask.
+ * Given a space-separated list of flag names, convert them to
+ * a bitmask array (which we malloc) and return it.
+ * \param ns name of namespace to search.
+ * \param str list of flag names.
+ * \return a newly allocated flag bitmask.
+ */
+object_flag_type
+string_to_bits(const char *ns, const char *str)
+{
+  object_flag_type bitmask;
+  char *copy, *s, *sp;
+  FLAG *f;
+  FLAGSPACE *n;
+
+  bitmask = new_flag_bitmask(ns);
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG: Unable to locate flagspace %s."), ns);
+    return bitmask;
+  }
+  if (!str)
+    return bitmask;            /* We're done, then */
+  copy = mush_strdup(str, "flagstring");
+  s = trim_space_sep(copy, ' ');
+  while (s) {
+    sp = split_token(&s, ' ');
+    if (!(f = match_flag_ns(n, sp)))
+      /* Now what do we do? Ignore it? */
+      continue;
+    set_flag_bitmask(bitmask, f->bitpos);
+  }
+  mush_free(copy, "flagstring");
+  return bitmask;
+}
+
+
+/*----------------------------------------------------------------------
+ * Functions for working with flags on objects
+ */
+
+
+/** Check an object for a flag.
+ * This function tests to see if an object has a flag. It is the
+ * function to use for this purpose from outside of this file.
+ * \param thing object to check.
+ * \param flag name of flag to check for (a string).
+ * \param type allowed types of flags to check for.
+ * \retval 1 object has the flag.
+ * \retval 0 object does not have the flag.
+ */
+int
+has_flag_by_name(dbref thing, const char *flag, int type)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  n = hashfind("FLAG", &htab_flagspaces);
+  f = flag_hash_lookup(n, flag, type);
+  if (!f)
+    return 0;
+  return has_flag(thing, f);
+}
+
+static int
+has_flag(dbref thing, FLAG *f)
+{
+  if (!GoodObject(thing) || IsGarbage(thing))
+    return 0;
+  return has_bit(Flags(thing), f->bitpos);
+}
+
+static int
+can_set_flag(dbref player, dbref thing, FLAG *flagp, int negate)
+{
+  /* returns 1 if player can set a flag on thing. */
+  int myperms;
+  if (!flagp || !GoodObject(player) || !GoodObject(thing))
+    return 0;
+  myperms = negate ? flagp->negate_perms : flagp->perms;
+  if ((myperms & F_INTERNAL) || (myperms & F_DISABLED))
+    return 0;
+  if (!(flagp->type & Typeof(thing)))
+    return 0;
+  /* K.. we've passed internal and type checks.. if we're god we can do anything */
+  if(God(player))
+    return 1;
+  /* You've got to *own* something (or be Director) to set it
+   * chown_ok or dest_ok. This prevents subversion of the
+   * zone-restriction on @chown and @dest
+   */
+  if (is_flag(flagp, "CHOWN_OK") || is_flag(flagp, "DESTROY_OK")) {
+    if (!Owns(player, thing) && !Director(player))
+      return 0;
+    else
+      return 1;
+  }
+
+  /* You must be privileged to set/clear the MONITOR flag on a player */
+  if (IsPlayer(thing) && is_flag(flagp, "MONITOR") && !Admin(player))
+    return 0;
+  /* Checking for the ZONE flag. If you set this, the player had
+   * better be zone-locked! 
+   */
+  if (!negate && is_flag(flagp, "ZONE") &&
+      (getlock(thing, Zone_Lock) == TRUE_BOOLEXP)) {
+    notify(player, T("You must @lock/zone before you can set a player ZONE"));
+    return 0;
+  }
+  /* Privilege, permissions work if a) they hav priv power & b) they control the thing */
+  if ((myperms & F_PRIVILEGE) && !(div_powover(player, player, "Privilege") && controls(player,thing)))
+    return 0;
+  else if ((myperms & F_GOD) && !God(player))
+    return 0;
+  if (Director(thing) && is_flag(flagp, "GAGGED"))
+    return 0;                  /* can't gag directors/God */
+  /* Check if its wiz or roy flag.. Make sure they're in a division before they can do 'em */
+  if ((is_flag(flagp, "WIZARD") || is_flag(flagp, "ROYALTY")) 
+      && (SDIV(thing).object == NOTHING))
+    return 0;
+  if (God(player))             /* God can do (almost) anything) */
+    return 1;
+  /* Make sure we don't accidentally permission-check toggles when
+   * checking priv bits.
+   */
+  /* A wiz can set things he owns WIZ, but nothing else. */
+  if (is_flag(flagp, "WIZARD") && !negate)
+    return (has_flag_by_name(player, "WIZARD", NOTYPE) && Owns(player, thing) && !IsPlayer(thing));
+  /* A wiz can unset the WIZ bit on any non-player */
+  if (is_flag(flagp, "WIZARD") && negate)
+    return (has_flag_by_name(player, "WIZARD", NOTYPE) && !IsPlayer(thing));
+  /* Wizards can set or unset anything royalty. Royalty can set anything
+   * they own royalty, but cannot reset their own bits. */
+  if (is_flag(flagp, "ROYALTY")) {
+    return (!Guest(thing) && (has_flag_by_name(player, "WIZARD", NOTYPE) || (has_flag_by_name(player, "ROYALTY", NOTYPE) &&
+                                                 Owns(player, thing)
+                                                 && !IsPlayer(thing))));
+  }
+  return 1;
+}
+
+/** Return a list of flag symbols that one object can see on another.
+ * \param thing object to list flag symbols for.
+ * \param player looker, for permission checking.
+ * \return a string containing all the visible type and flag symbols.
+ */
+const char *
+unparse_flags(dbref thing, dbref player)
+{
+  /* print out the flag symbols (letters) */
+  static char buf[BUFFER_LEN];
+  char *p;
+  FLAG *f;
+  int i;
+  FLAGSPACE *n;
+
+  Flagspace_Lookup(n, "FLAG");
+  p = buf;
+  switch (Typeof(thing)) {
+  case TYPE_GARBAGE:
+    *p = '\0';
+    return buf;
+  case TYPE_ROOM:
+    *p++ = 'R';
+    break;
+  case TYPE_EXIT:
+    *p++ = 'E';
+    break;
+  case TYPE_THING:
+    *p++ = 'T';
+    break;
+  case TYPE_PLAYER:
+    *p++ = 'P';
+    break;
+  case TYPE_DIVISION:
+    *p++ = 'D';
+    break;
+  }
+  for (i = 0; i < n->flagbits; i++) {
+    if ((f = n->flags[i])) {
+      if (has_flag(thing, f) && Can_See_Flag(player, thing, f))
+       *p++ = f->letter;
+    }
+  }
+  *p = '\0';
+  return buf;
+}
+
+/** Return the object's type and its flag list for examine.
+ * \param thing object to list flag symbols for.
+ * \param player looker, for permission checking.
+ * \return a string containing all the visible type and flag symbols.
+ */
+const char *
+flag_description(dbref player, dbref thing)
+{
+  static char buf[BUFFER_LEN];
+  char *bp;
+  bp = buf;
+  safe_str(T("Type: "), buf, &bp);
+  safe_str(privs_to_string(type_privs, Typeof(thing)), buf, &bp);
+  safe_str(T(" Flags: "), buf, &bp);
+  safe_str(bits_to_string("FLAG", Flags(thing), player, thing), buf, &bp);
+  *bp = '\0';
+  return buf;
+}
+
+
+
+/** Print out the flags for a decompile.
+ * \param player looker, for permission checking.
+ * \param thing object being decompiled.
+ * \param name name by which object is referred to in the decompile.
+ * \param ns name of namespace to search.
+ * \param command name of command used to set the 'flag'.
+ */
+void
+decompile_flags(dbref player, dbref thing, const char *name)
+{
+  FLAG *f;
+  int i;
+  FLAGSPACE *n;
+  Flagspace_Lookup(n, "FLAG");
+  for (i = 0; i < n->flagbits; i++)
+    if ((f = n->flags[i])) {
+      if (has_flag(thing, f) && Can_See_Flag(player, thing, f))
+       notify_format(player, "@set %s = %s", name, f->name);
+    }
+}
+
+
+/** Set or clear flags on an object, without permissions/hear checking.
+ * This function is for server internal use, only, when a flag should
+ * be set or cleared unequivocally.
+ * \param ns name of namespace to search.
+ * \param thing object on which to set or clear flag.
+ * \param flag name of flag to set or clear.
+ * \param negate if 1, clear the flag, if 0, set the flag.
+ */
+void
+twiddle_flag_internal(const char *ns, dbref thing, const char *flag, int negate)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  if (IsGarbage(thing))
+    return;
+  n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces);
+  if ((f = flag_hash_lookup(n, flag, Typeof(thing)))) {
+    if (n->flag_table == flag_table)
+      twiddle_flag(thing, f, negate);
+  }
+}
+
+
+/** Set or clear flags on an object, with full permissions/hear checking.
+ * \verbatim
+ * This function is used to set and clear flags through @set and the like.
+ * It does permission checking and handles the "is now listening" messages.
+ * \endverbatim
+ * \param player the enactor.
+ * \param thing object on which to set or clear flag.
+ * \param flag name of flag to set or clear.
+ * \param negate if 1, clear the flag, if 0, set the flag.
+ * \param hear 1 if object is a hearer.
+ * \param listener 1 if object is a listener.
+ */
+void
+set_flag(dbref player, dbref thing, const char *flag, int negate,
+        int hear, int listener)
+{
+  FLAG *f;
+  char tbuf1[BUFFER_LEN];
+  char *tp;
+#ifdef RPMODE_SYS
+  char  icloc_buf[BUFFER_LEN];
+  char *tp2, *tp3;
+  ATTR *a, *icloc_ptr;
+  dbref icloc, absroom;
+#endif /* RPMODE_SYS */
+  FLAGSPACE *n;
+
+  n = (FLAGSPACE *) hashfind("FLAG", &htab_flagspaces);
+  if ((f = flag_hash_lookup(n, flag, Typeof(thing))) == NULL) {
+    notify_format(player, T("%s - I don't recognize that flag."), flag);
+    return;
+  }
+
+  if (!OOREF(player,can_set_flag(player, thing, f, negate), can_set_flag(ooref, thing, f, negate))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* The only players who can be Dark are directors. */
+  if (is_flag(f, "DARK") && !negate && Alive(thing) && !Admin(thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  if(is_flag(f, "BUILDER") && !OOREF(player,div_powover(player,player, "BCreate"), 
+       div_powover(ooref, ooref, "BCreate"))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+    
+#ifdef RPMODE_SYS
+  if(is_flag(f, "ICFUNCS") && IsRoom(thing)) {
+    if(negate)
+      rplog_shutdown_room(thing);
+    else
+      rplog_init_room(thing);
+  }
+  if(is_flag(f, "RPMODE")) {
+       if(atr_get(thing, "RPLOCK") != NULL) {
+        notify_format(player, "That player is currently locked %s RPMode.", RPMODE(thing) ? "into" : "out of");
+        return;
+       }
+         if(negate) {
+                 if(!RPMODE(thing)) {
+                         notify(player, "That player isn't currently in RPMODE.");
+                         return;
+                 }
+                 icloc = GoodObject((absroom = absolute_room(thing))) ? absroom : thing;
+                 if(GoodObject(icloc) && has_flag_by_name(icloc, "ICFUNCS" , NOTYPE) 
+                         && !Going(icloc)) {
+                                 (void) atr_add(thing, "INF_RPLOC", unparse_dbref(Location(thing)), GOD, NOTHING); 
+                                 icloc = Home(thing);
+                         } else {
+                                 notify(player, "That player is not an IC Location, therefore can't go OOC.");
+                                 return;
+                         }
+         } else {
+                 if(RPMODE(thing)) {
+                         notify(player, "That player is already in RPMODE.");
+                         return;
+                 }
+                 if(InProg(thing)) {
+                   notify(player, "Permission denied. Can not go RPMODE while in a @PROGRAM.");
+                   return;
+                 }
+                 /* First make sure there truerace is valid */
+                 icloc_ptr = atr_get(thing, "TRUERACE");
+                 if(icloc_ptr) 
+                   strncpy(icloc_buf, atr_value(icloc_ptr), BUFFER_LEN-1);
+                 if(!icloc_buf) {
+                         if(player != thing)
+                                 notify(player, "That player does not have a valid TRUERACE set.");
+                         notify(thing, "You do not have a valid TRUERACE set.  Contact an administrator.");
+                         return;
+                 } else if(parse_dbref(icloc_buf) == NOTHING) {
+                         if(player != thing)
+                                 notify(player, "That player has an invalid TRUERACE.");
+                         notify(thing, "You have an invalid TRUERACE.  Contact an administrator.");
+                         return;
+                 }
+                 icloc_ptr = atr_get(thing, "INF_RPLOC");
+                 memset(icloc_buf, '\0', BUFFER_LEN);
+                 if(icloc_ptr)
+                         strncpy(icloc_buf, atr_value(icloc_ptr), BUFFER_LEN-1);
+                 if(!icloc_buf) {
+                         if(player != thing)
+                           notify(player, "That player has an invalid RP location set."); 
+                         notify(thing, "You have an invalid RP location set.  Contact an administrator.");
+                         return;
+                 }
+                 
+                 icloc = parse_dbref(icloc_buf);
+                 icloc = GoodObject((absroom = absolute_room(icloc))) ? absroom : icloc;
+
+                 if(!GoodObject(icloc) ||  !has_flag_by_name(icloc, "ICFUNCS", NOTYPE) || Going(icloc)) {
+                         if(player != thing)
+                           notify(player, "That player has an invalid RP location set.");
+                         notify(thing, "You have an invalid RP location set.  Contact an administrator.");
+                         return;
+                 }
+                 /* Ok.. everythings good, move 'em to the IC world */
+         }
+  }
+#endif /* RPMODE_SYS */
+
+  twiddle_flag(thing, f, negate);
+
+#ifdef RPMODE_SYS
+  if(is_flag(f, "RPMODE")) {
+       dbref oldloc;
+       oldloc = Location(thing);
+       enter_room(thing, icloc, 1);
+       if(!negate) {
+               a = atr_get( ANCESTOR_PLAYER, "AIC");
+               if(a) {
+                 strncpy(tbuf1, atr_value(a), BUFFER_LEN-1);
+                 tbuf1[strlen(tbuf1)+1] = '\0';
+                 tp = mush_strdup(unparse_dbref(thing), "rpsys.thing");
+                 tp2 = mush_strdup(unparse_dbref(oldloc), "rpsys.oldloc");
+                 tp3 = mush_strdup(unparse_dbref(icloc), "rpsys.icloc");
+                 global_eval_context.wnxt[0] = tp;
+                 global_eval_context.wnxt[1] = tp2;
+                 global_eval_context.wnxt[2] = tp3;
+                 global_eval_context.wnxt[3] = NULL;
+                 parse_que(ANCESTOR_PLAYER, tbuf1, player);
+                 if(tp)
+                   mush_free(tp, "rpsys.thing");
+                 if(tp2)
+                   mush_free(tp2, "rpsys.oldloc");
+                 if(tp3)
+                   mush_free(tp3, "rpsys.icloc");
+               }
+                notify_format(thing, "You wake up from a deep sleep.");
+               if(oldloc != icloc)
+                       notify_except(Contents(oldloc), NOTHING, tprintf("%s%sGAME%s%s:%s%s %s has entered RPMODE%s.", 
+                                       ANSI_HILITE, ANSI_CYAN, ANSI_NORMAL, ANSI_RED, ANSI_HILITE, ANSI_YELLOW,
+                                       Name(thing), ANSI_NORMAL), 0);
+               if(Connected(thing))
+                 notify_except(Contents(icloc), thing, tprintf("%s wakes up from a deep sleep.",
+                               spname(thing)), NA_INTER_HEAR);
+       } else  {
+                a = atr_get( ANCESTOR_PLAYER, "AOOC");
+                if(a) {
+                  strncpy(tbuf1, atr_value(a), BUFFER_LEN-1);
+                  tbuf1[strlen(tbuf1)+1] = '\0';
+                  tp = mush_strdup(unparse_dbref(thing), "rpsys.thing");
+                  tp2 = mush_strdup(unparse_dbref(oldloc), "rpsys.oldloc");
+                  tp3 = mush_strdup(unparse_dbref(icloc), "rpsys.icloc");
+                  global_eval_context.wnxt[0] = tp;
+                  global_eval_context.wnxt[1] = tp2;
+                  global_eval_context.wnxt[2] = tp3;
+                  global_eval_context.wnxt[3] = NULL;
+                  parse_que(ANCESTOR_PLAYER, tbuf1, player);
+                  if(tp)
+                    mush_free(tp, "rpsys.thing");
+                  if(tp2)
+                    mush_free(tp2, "rpsys.oldloc");
+                  if(tp3)
+                    mush_free(tp3, "rpsys.icloc");
+                }
+
+                notify_format(thing, "You fall into a deep sleep.");
+
+               notify_except(Contents(icloc), thing, tprintf("%s%sGAME%s%s:%s%s %s has arrived from leaving RPMODE%s.",                                                       ANSI_HILITE, ANSI_CYAN, ANSI_NORMAL, ANSI_RED, ANSI_HILITE, ANSI_YELLOW,
+                                        Name(thing), ANSI_NORMAL), 0);
+               if(Connected(thing))
+                 notify_anything(orator, na_loc, &Contents(oldloc), NULL, NA_INTER_HEAR,
+                               tprintf("%s falls into a deep sleep.", spname(thing)));
+       }
+  }
+#endif /* RPMODE_SYS */
+  if (negate) {
+    /* log if necessary */
+    if (is_flag(f, "WIZARD")) {
+      do_log(LT_WIZ, player, thing, "WIZFLAG RESET");
+      /* Perform Marker Resets  & Check if Royalty flag is on them for special conditions */
+      powergroup_db_set(player, thing, "!WIZARD", 1);
+      if(has_flag_by_name(thing, "ROYALTY", NOTYPE))
+       SLEVEL(thing) = 28;
+      else
+       SLEVEL(thing) = 3;
+    } else if (is_flag(f, "ROYALTY")) {
+      do_log(LT_WIZ, player, thing, "ROYAL FLAG RESET");
+      /* Perform Marker Resets */
+      powergroup_db_set(player, thing, "!ROYALTY", 1);
+      if(!has_flag_by_name(thing, "WIZARD", NOTYPE))
+       SLEVEL(thing) = 3;
+    }
+    if (is_flag(f, "SUSPECT"))
+      do_log(LT_WIZ, player, thing, "SUSPECT FLAG RESET");
+    /* those who unDARK return to the WHO */
+    if (is_flag(f, "DARK") && IsPlayer(thing))
+      hide_player(thing, 0);
+    /* notify the area if something stops listening, but only if it
+       wasn't listening before */
+    if (!IsPlayer(thing) && (hear || listener) &&
+       !Hearer(thing) && !Listener(thing)) {
+      tp = tbuf1;
+      safe_format(tbuf1, &tp, T("%s is no longer listening."), Name(thing));
+      *tp = '\0';
+      if (GoodObject(Location(thing)))
+       notify_except(Contents(Location(thing)), NOTHING, tbuf1,
+                     NA_INTER_PRESENCE);
+      notify_except(Contents(thing), NOTHING, tbuf1, 0);
+    }
+    if (IsRoom(thing) && is_flag(f, "MONITOR") && !hear && !Listener(thing)) {
+      tp = tbuf1;
+      safe_format(tbuf1, &tp, T("%s is no longer listening."), Name(thing));
+      *tp = '\0';
+      notify_except(Contents(thing), NOTHING, tbuf1, NA_INTER_PRESENCE);
+    }
+    if (is_flag(f, "AUDIBLE")) {
+      switch (Typeof(thing)) {
+      case TYPE_EXIT:
+       if (Audible(Source(thing))) {
+         tp = tbuf1;
+         safe_format(tbuf1, &tp, T("Exit %s is no longer broadcasting."),
+                     Name(thing));
+         *tp = '\0';
+         notify_except(Contents(Source(thing)), NOTHING, tbuf1, 0);
+       }
+       break;
+      case TYPE_ROOM:
+       notify_except(Contents(thing), NOTHING,
+                     T("Audible exits in this room have been deactivated."),
+                     0);
+       break;
+      case TYPE_THING:
+      case TYPE_PLAYER:
+       notify_except(Contents(thing), thing,
+                     T("This room is no longer broadcasting."), 0);
+       notify(thing, T("Your contents can no longer be heard from outside."));
+       break;
+      }
+    }
+    if (is_flag(f, "QUIET") || (!AreQuiet(player, thing))) {
+      tp = tbuf1;
+      safe_str(Name(thing), tbuf1, &tp);
+      safe_str(" - ", tbuf1, &tp);
+      safe_str(f->name, tbuf1, &tp);
+      safe_str(T(" reset."), tbuf1, &tp);
+      *tp = '\0';
+      notify(player, tbuf1);
+    }
+  } else {
+
+    /* log if necessary */
+    if (is_flag(f, "WIZARD")) {
+      do_log(LT_WIZ, player, thing, "WIZFLAG SET");
+      /* marker powers */
+      powergroup_db_set(player, thing, "WIZARD", 1);
+      SLEVEL(thing) = 29;
+    } else if (is_flag(f, "ROYALTY")) {
+      do_log(LT_WIZ, player, thing, "ROYAL FLAG SET");
+      /* marker powers */
+      powergroup_db_set(player, thing, "ROYALTY", 1);
+      if(!has_flag_by_name(thing, "WIZARD", NOTYPE))
+       SLEVEL(thing) = 28;
+    }
+    if (is_flag(f, "SUSPECT"))
+      do_log(LT_WIZ, player, thing, "SUSPECT FLAG SET");
+    if (is_flag(f, "SHARED"))
+        check_zone_lock(player, thing, 1);
+    /* DARK players should be treated as logged out */
+    if (is_flag(f, "DARK") && IsPlayer(thing))
+      hide_player(thing, 1);
+    /* notify area if something starts listening */
+    if (!IsPlayer(thing) &&
+       (is_flag(f, "PUPPET") || is_flag(f, "MONITOR")) && !hear && !listener) {
+      tp = tbuf1;
+      safe_format(tbuf1, &tp, T("%s is now listening."), Name(thing));
+      *tp = '\0';
+      if (GoodObject(Location(thing)))
+       notify_except(Contents(Location(thing)), NOTHING, tbuf1,
+                     NA_INTER_PRESENCE);
+      notify_except(Contents(thing), NOTHING, tbuf1, 0);
+    }
+    if (IsRoom(thing) && is_flag(f, "MONITOR") && !hear && !listener) {
+      tp = tbuf1;
+      safe_format(tbuf1, &tp, T("%s is now listening."), Name(thing));
+      *tp = '\0';
+      notify_except(Contents(thing), NOTHING, tbuf1, 0);
+    }
+    /* notify for audible exits */
+    if (is_flag(f, "AUDIBLE")) {
+      switch (Typeof(thing)) {
+      case TYPE_EXIT:
+       if (Audible(Source(thing))) {
+         tp = tbuf1;
+         safe_format(tbuf1, &tp, T("Exit %s is now broadcasting."),
+                     Name(thing));
+         *tp = '\0';
+         notify_except(Contents(Source(thing)), NOTHING, tbuf1, 0);
+       }
+       break;
+      case TYPE_ROOM:
+       notify_except(Contents(thing), NOTHING,
+                     T("Audible exits in this room have been activated."), 0);
+       break;
+      case TYPE_PLAYER:
+      case TYPE_THING:
+       notify_except(Contents(thing), thing,
+                     T("This room is now broadcasting."), 0);
+       notify(thing, T("Your contents can now be heard from outside."));
+       break;
+      }
+    }
+    if (is_flag(f, "QUIET") || (!AreQuiet(player, thing))) {
+      tp = tbuf1;
+      safe_str(Name(thing), tbuf1, &tp);
+      safe_str(" - ", tbuf1, &tp);
+      safe_str(f->name, tbuf1, &tp);
+      safe_str(T(" set."), tbuf1, &tp);
+      *tp = '\0';
+      notify(player, tbuf1);
+    }
+  }
+}
+
+
+/** Check if an object has one or all of a list of flag characters.
+ * This function is used by orflags and andflags to check to see
+ * if an object has one or all of the flags signified by a list
+ * of flag characters.
+ * \param ns name of namespace to search.
+ * \param player the object checking, for permissions
+ * \param it the object on which to check for flags.
+ * \param fstr string of flag characters to check for.
+ * \param type 0=orflags, 1=andflags.
+ * \retval 1 object has any (or all) flags.
+ * \retval 0 object has no (or not all) flags.
+ */
+int
+flaglist_check(const char *ns, dbref player, dbref it, const char *fstr,
+              int type)
+{
+  char *s;
+  FLAG *fp;
+  int negate, temp;
+  int ret = type;
+  FLAGSPACE *n;
+
+  negate = temp = 0;
+  if (!GoodObject(it))
+    return 0;
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG: Unable to locate flagspace %s"), ns);
+    return 0;
+  }
+  for (s = (char *) fstr; *s; s++) {
+    /* Check for a negation sign. If we find it, we note it and 
+     * increment the pointer to the next character.
+     */
+    if (*s == '!') {
+      negate = 1;
+      s++;
+    } else {
+      negate = 0;              /* It's important to clear this at appropriate times;
+                                * else !Dc means (!D && !c), instead of (!D && c). */
+    }
+    if (!*s)
+      /* We got a '!' that wasn't followed by a letter.
+       * Fail the check. */
+      return (type == 1) ? 0 : ret;
+    /* Find the flag. */
+    if ((fp = letter_to_flagptr(n, *s, Typeof(it))) == NULL) {
+      /* Maybe *s is a type specifier (P, T, E, R). These aren't really
+       * flags, but we grandfather them in to preserve old code
+       */
+      if ((*s == 'T') || (*s == 'R') || (*s == 'E') || (*s == 'P')) {
+       temp = (*s == 'T') ? (Typeof(it) == TYPE_THING) :
+         ((*s == 'R') ? (Typeof(it) == TYPE_ROOM) :
+          ((*s == 'E') ? (Typeof(it) == TYPE_EXIT) :
+           (Typeof(it) == TYPE_PLAYER)));
+       if ((type == 1) && ((negate && temp) || (!negate && !temp)))
+         return 0;
+       else if ((type == 0) && ((!negate && temp) || (negate && !temp)))
+         ret |= 1;
+      } else {
+       /* Either we got a '!' that wasn't followed by a letter, or
+        * we couldn't find that flag. For AND, since we've failed
+        * a check, we can return false. Otherwise we just go on.
+        */
+       if (type == 1)
+         return 0;
+       else
+         continue;
+      }
+    } else {
+      /* does the object have this flag? */
+      temp = (has_flag(it, fp) && Can_See_Flag(player, it, fp));
+      if ((type == 1) && ((negate && temp) || (!negate && !temp))) {
+       /* Too bad there's no NXOR function...
+        * At this point we've either got a flag and we don't want
+        * it, or we don't have a flag and we want it. Since it's
+        * AND, we return false.
+        */
+       return 0;
+      } else if ((type == 0) && ((!negate && temp) || (negate && !temp))) {
+       /* We've found something we want, in an OR. We OR a
+        * true with the current value.
+        */
+       ret |= 1;
+      }
+      /* Otherwise, we don't need to do anything. */
+    }
+  }
+  return ret;
+}
+
+/** Check if an object has one or all of a list of flag names.
+ * This function is used by orlflags and andlflags to check to see
+ * if an object has one or all of the flags signified by a list
+ * of flag names.
+ * \param ns name of namespace to search.
+ * \param player the object checking, for permissions
+ * \param it the object on which to check for flags.
+ * \param fstr string of flag names, space-separated, to check for.
+ * \param type 0=orlflags, 1=andlflags.
+ * \retval 1 object has any (or all) flags.
+ * \retval 0 object has no (or not all) flags.
+ */
+int
+flaglist_check_long(const char *ns, dbref player, dbref it, const char *fstr,
+                   int type)
+{
+  char *s, *copy, *sp;
+  FLAG *fp;
+  int negate, temp;
+  int ret = type;
+  FLAGSPACE *n;
+  negate = temp = 0;
+  if (!GoodObject(it))
+    return 0;
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG: Unable to locate flagspace %s"), ns);
+    return 0;
+  }
+  copy = mush_strdup(fstr, "flaglistlong");
+  sp = trim_space_sep(copy, ' ');
+  while (sp) {
+    s = split_token(&sp, ' ');
+    /* Check for a negation sign. If we find it, we note it and 
+     * increment the pointer to the next character.
+     */
+    if (*s == '!') {
+      negate = 1;
+      s++;
+    } else {
+      negate = 0;              /* It's important to clear this at appropriate times;
+                                * else !D c means (!D && !c), instead of (!D && c). */
+    }
+    if (!*s) {
+      /* We got a '!' that wasn't followed by a string.
+       * Fail the check. */
+      if (type == 1)
+       ret = 0;
+      break;
+    }
+    /* Find the flag. */
+    if (!(fp = flag_hash_lookup(n, s, Typeof(it)))) {
+      /* Either we got a '!' that wasn't followed by a letter, or
+       * we couldn't find that flag. For AND, since we've failed
+       * a check, we can return false. Otherwise we just go on.
+       */
+      if (type == 1) {
+       ret = 0;
+       break;
+      } else
+       continue;
+    } else {
+      /* does the object have this flag? There's a special case
+       * here, as we want (for consistency with flaglist_check)
+       * to allow types to match as well
+       */
+      if (!strcmp(fp->name, "PLAYER"))
+       temp = IsPlayer(it);
+      else if (!strcmp(fp->name, "THING"))
+       temp = IsThing(it);
+      else if (!strcmp(fp->name, "ROOM"))
+       temp = IsRoom(it);
+      else if (!strcmp(fp->name, "EXIT"))
+       temp = IsExit(it);
+      else
+       temp = (has_flag(it, fp) && Can_See_Flag(player, it, fp));
+      if ((type == 1) && ((negate && temp) || (!negate && !temp))) {
+       /* Too bad there's no NXOR function...
+        * At this point we've either got a flag and we don't want
+        * it, or we don't have a flag and we want it. Since it's
+        * AND, we return false.
+        */
+       ret = 0;
+       break;
+      } else if ((type == 0) && ((!negate && temp) || (negate && !temp))) {
+       /* We've found something we want, in an OR. We OR a
+        * true with the current value.
+        */
+       ret |= 1;
+      }
+      /* Otherwise, we don't need to do anything. */
+    }
+  }
+  mush_free(copy, "flaglistlong");
+  return ret;
+}
+
+
+/** Can a player see a flag?
+ * \param privs looker.
+ * \param thing object on which to look for flag.
+ * \param name name of flag to look for.
+ * \retval 1 object has the flag and looker can see it.
+ * \retval 0 looker can not see flag on object.
+ */
+int
+sees_flag(dbref privs, dbref thing, const char *name)
+{
+  /* Does thing have the flag named name && can privs see it? */
+  FLAG *f;
+  FLAGSPACE *n;
+  n = hashfind("FLAG", &htab_flagspaces);
+  if ((f = flag_hash_lookup(n, name, Typeof(thing))) == NULL)
+    return 0;
+  return has_flag(thing, f) && Can_See_Flag(privs, thing, f);
+}
+
+
+/** A hacker interface for adding a flag.
+ * \verbatim
+ * This function is used to add a new flag to the game. It's
+ * called by @flag (via do_flag_add()), and is the right function to
+ * call in flaglocal.c if you're writing a hardcoded system patch that
+ * needs to add its own flags. It will not add the same flag twice.
+ * \endverbatim
+ * \param ns name of namespace to add flag to.
+ * \param name flag name.
+ * \param letter flag character (or ascii 0)
+ * \param type mask of object types to which the flag applies.
+ * \param perms mask of permissions to see/set the flag.
+ * \param negate_perms mask of permissions to clear the flag.
+ */
+FLAG *
+add_flag(const char *name, const char letter, int type,
+        int perms, int negate_perms)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  n = hashfind("FLAG", &htab_flagspaces);
+  /* Don't double-add */
+  if ((f = match_flag_ns(n, strupper(name))))
+    return f;
+  f = new_flag();
+  f->name = mush_strdup(strupper(name), "flag name");
+  f->letter = letter;
+  f->type = type;
+  f->perms = perms;
+  f->negate_perms = negate_perms;
+  f->bitpos = -1;
+  flag_add(n, f->name, f);
+  return f;
+}
+
+
+/*--------------------------------------------------------------------------
+ * MUSHcode interface
+ */
+
+/** User interface to list flags.
+ * \verbatim
+ * This function implements @flag/list.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg wildcard pattern of flag names to list, or NULL for all.
+ * \param lc if 1, list flags in lowercase.
+ */
+void
+do_list_flags(dbref player, const char *arg, int lc)
+{
+  char *b = list_all_flags("FLAG", arg, player, 0x3);
+  notify_format(player, T("Flags: %s"), lc ? strlower(b) : b);
+}
+
+/** User interface to show flag detail.
+ * \verbatim
+ * This function implements @flag <flag>.
+ * \endverbatim
+ * \param ns name of namespace to search.
+ * \param player the enactor.
+ * \param name name of the flag to describe.
+ */
+void
+do_flag_info(const char *ns, dbref player, const char *name)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  /* Find the flagspace */
+  if (!(n = (FLAGSPACE *) hashfind(ns, &htab_flagspaces))) {
+    do_rawlog(LT_ERR, T("FLAG: Unable to locate flagspace %s"), ns);
+    return;
+  }
+
+  /* Find the flag */
+  f = flag_hash_lookup(n, name, NOTYPE);
+  if (!f && God(player))
+    f = match_flag_ns(n, name);
+  if (!f) {
+    notify_format(player, T("No such %s."), strlower(ns));
+    return;
+  }
+  notify_format(player, T("Flag name: %s"), f->name);
+  notify_format(player, T("Flag char: %c"), f->letter);
+  notify_format(player, "  Aliases: %s", list_aliases(n, f));
+  notify_format(player, "  Type(s): %s", f->type == ALLTYPES ? "ANY" : privs_to_string(type_privs, f->type));
+  notify_format(player, "    Perms: %s", privs_to_string(flag_privs, f->perms));
+  notify_format(player, "ResetPrms: %s",
+               privs_to_string(flag_privs, f->negate_perms));
+}
+
+/** Change the permissions on a flag. 
+ * \verbatim
+ * This is the user-interface to @flag/restrict, which uses this syntax:
+ *
+ * @flag/restrict <flag> = <perms>, <negate_perms> 
+ *
+ * If no comma is given, use <perms> for both.
+ * \endverbatim
+ * \param ns name of namespace to search.
+ * \param player the enactor.
+ * \param name name of flag to modify.
+ * \param args_right array of arguments on the right of the equal sign.
+ */
+void
+do_flag_restrict(dbref player, const char *name, char *args_right[])
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  int perms, negate_perms;
+
+  if (!Director(player)) {
+    notify(player, T("You don't have enough magic for that."));
+    return;
+  }
+  n = hashfind("FLAG", &htab_flagspaces);
+  if (!(f = flag_hash_lookup(n, name, NOTYPE))) {
+    notify_format(player, T("No such flag."));
+    return;
+  }
+  if (!args_right[1] || !*args_right[1]) {
+    notify_format(player, T("How do you want to restrict that flag?"));
+    return;
+  }
+  if (!strcasecmp(args_right[1], "any")) {
+    perms = F_ANY;
+  } else {
+    perms = string_to_privs(flag_privs, args_right[1], 0);
+    if ((!perms) || (perms & (F_INTERNAL | F_DISABLED))) {
+      notify(player, T("I don't understand those permissions."));
+      return;
+    }
+  }
+  if (args_right[2] && *args_right[2]) {
+    if (!strcasecmp(args_right[2], "any")) {
+      negate_perms = F_ANY;
+    } else {
+      negate_perms = string_to_privs(flag_privs, args_right[2], 0);
+      if ((!negate_perms) || (negate_perms & (F_INTERNAL | F_DISABLED))) {
+       notify(player, T("I don't understand those permissions."));
+       return;
+      }
+    }
+  } else {
+    negate_perms = string_to_privs(flag_privs, args_right[1], 0);
+  }
+  f->perms = perms;
+  f->negate_perms = negate_perms;
+  notify_format(player, T("Permissions on %s flag set."), f->name);
+}
+
+
+/** Change the type of a flag.
+ * \verbatim
+ * This is the user-interface to @flag/type, which uses this syntax:
+ *
+ * @flag/type <flag> = <type(s)>
+ *
+ * We refuse to change the type of a flag if there are objects
+ * with the flag that would no longer be of the correct type.
+ * \endverbatim
+ * \param ns name of the flagspace to use.
+ * \param player the enactor.
+ * \param name name of flag to modify.
+ * \param type_string list of types.
+ */
+void
+do_flag_type(const char *ns, dbref player, const char *name, char *type_string)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  int type;
+  dbref it;
+
+  if (!God(player)) {
+    notify(player, T("You don't have enough magic for that."));
+    return;
+  }
+  n = hashfind(ns, &htab_flagspaces);
+  if (!(f = flag_hash_lookup(n, name, NOTYPE))) {
+    notify_format(player, T("No such %s."), strlower(ns));
+    return;
+  }
+  if (!type_string || !*type_string) {
+    notify_format(player, T("What type do you want to make that %s?"),
+                 strlower(ns));
+    return;
+  }
+  if (!strcasecmp(type_string, "any")) {
+    type = NOTYPE;
+  } else {
+    type = string_to_privs(type_privs, type_string, 0);
+    if (!type) {
+      notify(player, T("I don't understand the list of types."));
+      return;
+    }
+    /* Are there any objects with the flag that don't match these
+     * types?
+     */
+    for (it = 0; it < db_top; it++) {
+      if (!(type & Typeof(it)) && has_flag(it, f)) {
+      notify(player,
+             T
+             ("Objects of other types already have this flag set. Search for them and remove the flag first."));
+      return;
+      }
+    }
+  }
+  f->type = type;
+  notify_format(player, T("Type of %s flag set."), f->name);
+}
+
+
+
+/** Add a new flag
+ * \verbatim
+ * This function implements @flag/add, which uses this syntax:
+ *
+ * @flag/add <flag> = <letter>, <type(s)>, <perms>, <negate_perms> 
+ *
+ * <letter> defaults to none. If given, it must not match an existing
+ *   flag character that could apply to the given type
+ * <type(s)> defaults to NOTYPE
+ * <perms> defaults to any
+ * <negate_perms> defaults to <perms>
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the flag to add.
+ * \param args_right array of arguments on the right side of the equal sign.
+ */
+void
+do_flag_add(dbref player, const char *name, char *args_right[])
+{
+  char letter = '\0';
+  int type = NOTYPE;
+  int perms = F_ANY;
+  int negate_perms = F_ANY;
+  FLAG *f;
+  FLAGSPACE *n;
+
+  if (!Director(player)) {
+    notify(player, T("You don't have enough magic for that."));
+    return;
+  }
+  if (!name || !*name) {
+    notify_format(player, T("You must provide a name for the flag."));
+    return;
+  }
+  if (strlen(name) == 1) {
+    notify_format(player, T("Flag names must be longer than one character."));
+    return;
+  }
+  if (strchr(name, ' ')) {
+    notify_format(player, T("Flag names may not contain spaces."));
+    return;
+  }
+  Flagspace_Lookup(n, "FLAG");
+  /* Do we have a letter? */
+  if (args_right[1]) {
+    if (strlen(args_right[1]) > 1) {
+      notify_format(player, T("Flag characters must be single characters."));
+      return;
+    }
+    letter = *args_right[1];
+    /* Do we have a type? */
+    if (args_right[2]) {
+      if (*args_right[2] && strcasecmp(args_right[2], "any"))
+       type = string_to_privs(type_privs, args_right[2], 0);
+      if (!type) {
+       notify(player, T("I don't understand the list of types."));
+       return;
+      }
+    }
+    /* Is this letter already in use for this type? */
+    if (*args_right[1]) {
+      if ((f = letter_to_flagptr(n, *args_right[1], type))) {
+       notify_format(player, T("Letter conflicts with the %s flag."), f->name);
+       return;
+      }
+    }
+    /* Do we have perms? */
+    if (args_right[3] && *args_right[3]) {
+      if (!strcasecmp(args_right[3], "any")) {
+       perms = F_ANY;
+      } else {
+       perms = string_to_privs(flag_privs, args_right[3], 0);
+       if ((!perms) || (perms & (F_INTERNAL | F_DISABLED))) {
+         notify(player, T("I don't understand those permissions."));
+         return;
+       }
+      }
+    }
+    if (args_right[4] && *args_right[4]) {
+      if (!strcasecmp(args_right[4], "any")) {
+       negate_perms = F_ANY;
+      } else {
+       negate_perms = string_to_privs(flag_privs, args_right[4], 0);
+       if ((!negate_perms) || (negate_perms & (F_INTERNAL | F_DISABLED))) {
+         notify(player, T("I don't understand those permissions."));
+         return;
+       }
+      }
+    } else
+      negate_perms = perms;
+  }
+  /* Ok, let's do it. */
+  add_flag(name, letter, type, perms, negate_perms);
+  /* Did it work? */
+  if ((f = match_flag(name)))
+    do_flag_info("FLAG", player, name);
+  else
+    notify_format(player, T("Unknown failure adding flag."));
+}
+
+/** Alias a flag.
+ * \verbatim
+ * This function implements the @flag/alias commmand.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the flag to alias.
+ * \param alias name of the alias.
+ */
+void
+do_flag_alias(dbref player, const char *name, const char *alias)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+  if (!Director(player)) {
+    notify(player, T("You don't look like God."));
+    return;
+  }
+  if (!alias || !*alias) {
+    notify(player, T("You must provide a name for the alias."));
+    return;
+  }
+  if (strlen(alias) == 1) {
+    notify(player, T("Flag aliases must be longer than one character."));
+    return;
+  }
+  if (strchr(alias, ' ')) {
+    notify(player, T("Flag aliases may not contain spaces."));
+    return;
+  }
+  n = hashfind("FLAG", &htab_flagspaces);
+  f = match_flag_ns(n, alias);
+  if (f) {
+    notify_format(player, T("That alias already matches the %s flag."),
+                 f->name);
+    return;
+  }
+  f = match_flag_ns(n, name);
+  if (!f) {
+    notify(player, T("I don't know that flag."));
+    return;
+  }
+  if (f->perms & F_DISABLED) {
+    notify(player, T("That flag is disabled."));
+    return;
+  }
+  /* Insert the flag in the ptab by the given alias */
+  ptab_start_inserts(n->tab);
+  ptab_insert(n->tab, alias, f);
+  ptab_end_inserts(n->tab);
+  if ((f = match_flag_ns(n, alias)))
+    do_flag_info("FLAG", player, alias);
+  else
+    notify(player, T("Unknown failure adding alias."));
+}
+
+/** Change a flag's alias. 
+ * \param player the enactor.
+ * \param name name of the flag.
+ * \param letter The new alias, or an empty string to remove the alias.
+ */
+void
+do_flag_letter(dbref player, const char *name, const char *letter)
+{
+  FLAG *f;
+  FLAGSPACE *n;
+
+  if (!Director(player)) {
+    notify(player, T("You don't look like God."));
+    return;
+  }
+  Flagspace_Lookup(n, "FLAG");
+  f = match_flag_ns(n, name);
+  if (!f) {
+    notify_format(player, T("I don't know that flag."));
+    return;
+  }
+  if (letter && *letter) {
+    FLAG *other;
+
+    if (strlen(letter) > 1) {
+      notify_format(player, T("Flag characters must be single characters."));
+      return;
+    }
+
+    if ((other = letter_to_flagptr(n, *letter, NOTYPE))) {
+      notify_format(player, T("Letter conflicts with the %s flag."),
+                   other->name);
+      return;
+    }
+
+    f->letter = *letter;
+    notify_format(player, T("Letter for flag %s set to '%c'."),
+                  f->name, *letter);
+  } else {                     /* Clear a flag */
+    f->letter = '\0';
+    notify_format(player, T("Letter for flag %s cleared."), f->name);
+  }
+}
+
+
+/** Disable a flag. 
+ * \verbatim
+ * This function implements @flag/disable.
+ * Only God can do this, and it makes the flag effectively
+ * unusuable - it's invisible to all but God, and can't be set, cleared,
+ * or tested against.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the flag to disable.
+ */
+void
+do_flag_disable(dbref player, const char *name)
+{
+  FLAG *f;
+  if (!Director(player)) {
+    notify(player, T("You don't look like God."));
+    return;
+  }
+  f = match_flag(name);
+  if (!f) {
+    notify_format(player, T("I don't know that."));
+    return;
+  }
+  if (f->perms & F_DISABLED) {
+    notify_format(player, T("That flag is already disabled."));
+    return;
+  }
+  /* Do it. */
+  f->perms |= F_DISABLED;
+  notify_format(player, T("Flag %s disabled."), f->name);
+}
+
+/** Delete a flag. 
+ * \verbatim
+ * This function implements @flag/delete.
+ * Only God can do this, and clears the flag on everyone
+ * and then removes it and its aliases from the tables.
+ * Danger, Will Robinson!
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the flag to delete.
+ */
+void
+do_flag_delete(dbref player, const char *name)
+{
+  FLAG *f, *tmpf;
+  char flagname[BUFFER_LEN];
+  dbref i;
+  int got_one;
+  FLAGSPACE *n;
+
+  if (!God(player)) {
+    notify(player, T("You don't look like God."));
+    return;
+  }
+  n = (FLAGSPACE *) hashfind("FLAG", &htab_flagspaces);
+  f = ptab_find_exact(n->tab, name);
+  if (!f) {
+    notify_format(player, T("I don't know that flag."));
+    return;
+  }
+  if (f->perms & F_INTERNAL) {
+    notify(player, T("There are probably easier ways to crash your MUSH."));
+    return;
+  }
+  /* Remove aliases. Convoluted because ptab_delete probably trashes
+   * the firstentry/nextentry stuff
+   */
+  do {
+    got_one = 0;
+    tmpf = ptab_firstentry_new(n->tab, flagname);
+    while (tmpf) {
+      if (!strcmp(tmpf->name, f->name) &&
+         strcmp(n->flags[f->bitpos]->name, flagname)) {
+       ptab_delete(n->tab, flagname);
+       got_one = 1;
+       break;
+      }
+      tmpf = ptab_nextentry_new(n->tab, flagname);
+    }
+  } while (got_one);
+  /* Reset the flag on all objects */
+  for (i = 0; i < db_top; i++)
+    twiddle_flag(i, f, 1);
+  /* Remove the flag's entry in flags */
+  n->flags[f->bitpos] = NULL;
+  /* Remove the flag from the ptab */
+  ptab_delete(n->tab, f->name);
+  notify_format(player, T("Flag %s deleted."), f->name);
+  /* Free the flag. */
+  mush_free((char *) f->name, "flag name");
+  mush_free(f, "flag");
+}
+
+/** Enable a disabled flag.
+ * \verbatim
+ * This function implements @flag/enable.
+ * Only God can do this, and it reverses /disable.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the flag to enable.
+ */
+void
+do_flag_enable(dbref player, const char *name)
+{
+  FLAG *f;
+  if (!Director(player)) {
+    notify(player, T("You don't look like God."));
+    return;
+  }
+  f = match_flag(name);
+  if (!f) {
+    notify_format(player, T("I don't know that flag."));
+    return;
+  }
+  if (!(f->perms & F_DISABLED)) {
+    notify_format(player, T("That flag is not disabled."));
+    return;
+  }
+  /* Do it. */
+  f->perms &= ~F_DISABLED;
+  notify_format(player, T("Flag %s enabled."), f->name);
+}
+
+
+static char *
+list_aliases(FLAGSPACE * n, FLAG *given)
+{
+  FLAG *f;
+  static char buf[BUFFER_LEN];
+  char *bp;
+  char flagname[BUFFER_LEN];
+  int first = 1;
+  bp = buf;
+  f = ptab_firstentry_new(n->tab, flagname);
+  while (f) {
+    if (!strcmp(given->name, f->name) &&
+       strcmp(n->flags[f->bitpos]->name, flagname)) {
+      /* This is an alias! */
+      if (!first)
+       safe_chr(' ', buf, &bp);
+      first = 0;
+      safe_str(flagname, buf, &bp);
+    }
+    f = ptab_nextentry_new(n->tab, flagname);
+  }
+  *bp = '\0';
+  return buf;
+}
+
+
+/** Return a list of all flags.
+ * \param ns name of namespace to search.
+ * \param name wildcard to match against flag names, or NULL for all.
+ * \param privs the looker, for permission checking.
+ * \param which a bitmask of 0x1 (flag chars) and 0x2 (flag names).
+ */
+char *
+list_all_flags(const char *ns, const char *name, dbref privs, int which)
+{
+  FLAG *f;
+  char **ptrs;
+  int i, numptrs = 0;
+  static char buf[BUFFER_LEN];
+  char *bp;
+  int disallowed;
+  FLAGSPACE *n;
+
+  Flagspace_Lookup(n, ns);
+  disallowed = God(privs) ? F_INTERNAL : (F_INTERNAL | F_DISABLED);
+  if (!Admin(privs))
+    disallowed |= (F_DARK | F_MDARK);
+  ptrs = (char **) malloc(n->flagbits * sizeof(char *));
+  for (i = 0; i < n->flagbits; i++) {
+    if ((f = n->flags[i]) && !(f->perms & disallowed)) {
+      if (!name || !*name || quick_wild(name, f->name))
+       ptrs[numptrs++] = (char *) f->name;
+    }
+  }
+  do_gensort(ptrs, numptrs, ALPHANUM_LIST);
+  bp = buf;
+  for (i = 0; i < numptrs; i++) {
+    switch (which) {
+    case 0x3:
+      if (i)
+       safe_strl(", ", 2, buf, &bp);
+      safe_str(ptrs[i], buf, &bp);
+      f = match_flag_ns(n, ptrs[i]);
+      if (!f)
+       break;
+      if (f->letter != '\0')
+       safe_format(buf, &bp, " (%c)", f->letter);
+      if (f->perms & F_DISABLED)
+       safe_str(T(" (disabled)"), buf, &bp);
+      break;
+    case 0x2:
+      if (i)
+       safe_chr(' ', buf, &bp);
+      safe_str(ptrs[i], buf, &bp);
+      break;
+    case 0x1:
+      f = match_flag_ns(n, ptrs[i]);
+      if (f && (f->letter != '\0'))
+       safe_chr(f->letter, buf, &bp);
+      break;
+    }
+  }
+  *bp = '\0';
+  free(ptrs);
+  return buf;
+}
+
+
+
+
+/** Show the flags and powers associated with a command.
+ * \param flagmask the command's flagmask.
+ * \param powers the command's power mask.
+ * \return string output of powers and flags.
+ */
+const char *
+show_command_flags(object_flag_type flagmask, div_pbits powers)
+{
+  static char fbuf[BUFFER_LEN];
+  char *bp;
+  bp = fbuf;
+
+  /* do generic flags */
+  if (flagmask) {
+    if (powers)
+      safe_chr('\n', fbuf, &bp);
+    safe_str("Flags      : ", fbuf, &bp);
+    safe_str(bits_to_string("FLAG", flagmask, GOD, NOTHING), fbuf, &bp);
+  }
+
+  *bp = '\0';
+  return fbuf;
+}
diff --git a/src/funcrypt.c b/src/funcrypt.c
new file mode 100644 (file)
index 0000000..52e02a0
--- /dev/null
@@ -0,0 +1,204 @@
+/**
+ * \file funcrypt.c
+ *
+ * \brief Functions for cryptographic stuff in softcode
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include "conf.h"
+#include "case.h"
+#include "externs.h"
+#include "version.h"
+#include "extchat.h"
+#include "htab.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "parse.h"
+#include "function.h"
+#include "command.h"
+#include "game.h"
+#include "attrib.h"
+#include "ansi.h"
+#include "match.h"
+#ifdef HAS_OPENSSL
+#include <openssl/sha.h>
+#include <openssl/evp.h>
+#else
+#include "shs.h"
+#endif
+#include "confmagic.h"
+
+
+static char *crunch_code _((char *code));
+static char *crypt_code _((char *code, char *text, int type));
+
+#ifdef HAS_OPENSSL
+static void safe_hexchar(unsigned char c, char *buff, char **bp);
+#endif
+
+/* Copy over only alphanumeric chars */
+static char *
+crunch_code(char *code)
+{
+  char *in;
+  char *out;
+  static char output[BUFFER_LEN];
+
+  out = output;
+  in = code;
+  while (*in) {
+    while (*in == ESC_CHAR) {
+      while (*in && *in != 'm')
+       in++;
+      in++;                    /* skip 'm' */
+    }
+    if ((*in >= 32) && (*in <= 126)) {
+      *out++ = *in;
+    }
+    in++;
+  }
+  *out = '\0';
+  return output;
+}
+
+static char *
+crypt_code(char *code, char *text, int type)
+{
+  static char textbuff[BUFFER_LEN];
+  char codebuff[BUFFER_LEN];
+  int start = 32;
+  int end = 126;
+  int mod = end - start + 1;
+  char *p, *q, *r;
+
+  if (!text || !*text)
+    return (char *) "";
+  if (!code || !*code)
+    return text;
+  strcpy(codebuff, crunch_code(code));
+  if (!*codebuff)
+    return text;
+  textbuff[0] = '\0';
+
+  p = text;
+  q = codebuff;
+  r = textbuff;
+  /* Encryption: Simply go through each character of the text, get its ascii
+   * value, subtract start, add the ascii value (less start) of the
+   * code, mod the result, add start. Continue  */
+  while (*p) {
+    if ((*p < start) || (*p > end)) {
+      p++;
+      continue;
+    }
+    if (type)
+      *r++ = (((*p++ - start) + (*q++ - start)) % mod) + start;
+    else
+      *r++ = (((*p++ - *q++) + 2 * mod) % mod) + start;
+    if (!*q)
+      q = codebuff;
+  }
+  *r = '\0';
+  return textbuff;
+}
+
+FUNCTION(fun_encrypt)
+{
+  safe_str(crypt_code(args[1], args[0], 1), buff, bp);
+}
+
+FUNCTION(fun_decrypt)
+{
+  safe_str(crypt_code(args[1], args[0], 0), buff, bp);
+}
+
+FUNCTION(fun_checkpass)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!(GoodObject(it) && IsPlayer(it))) {
+    safe_str(T("#-1 NO SUCH PLAYER"), buff, bp);
+    return;
+  }
+  safe_boolean(password_check(it, args[1]), buff, bp);
+}
+
+FUNCTION(fun_sha0)
+{
+#ifdef HAS_OPENSSL
+  unsigned char hash[SHA_DIGEST_LENGTH];
+  int n;
+
+  SHA((unsigned char *) args[0], arglens[0], hash);
+
+  for (n = 0; n < SHA_DIGEST_LENGTH; n++)
+    safe_hexchar(hash[n], buff, bp);
+#else
+  SHS_INFO shsInfo;
+  shsInfo.reverse_wanted = (BYTE) options.reverse_shs;
+  shsInit(&shsInfo);
+  shsUpdate(&shsInfo, (const BYTE *) args[0], arglens[0]);
+  shsFinal(&shsInfo);
+  safe_format(buff, bp, "%0lx%0lx%0lx%0lx%0lx", shsInfo.digest[0],
+             shsInfo.digest[1], shsInfo.digest[2], shsInfo.digest[3],
+             shsInfo.digest[4]);
+#endif
+}
+
+FUNCTION(fun_digest)
+{
+#ifdef HAS_OPENSSL
+  EVP_MD_CTX ctx;
+  const EVP_MD *mp;
+  unsigned char md[EVP_MAX_MD_SIZE];
+  size_t n, len = 0;
+
+  if ((mp = EVP_get_digestbyname(args[0])) == NULL) {
+    safe_str(T("#-1 UNSUPPORTED DIGEST TYPE"), buff, bp);
+    return;
+  }
+
+  EVP_DigestInit(&ctx, mp);
+  EVP_DigestUpdate(&ctx, args[1], arglens[1]);
+  EVP_DigestFinal(&ctx, md, &len);
+
+  for (n = 0; n < len; n++) {
+    safe_hexchar(md[n], buff, bp);
+  }
+
+#else
+  if (strcmp(args[0], "sha") == 0) {
+    SHS_INFO shsInfo;
+    shsInfo.reverse_wanted = (BYTE) options.reverse_shs;
+    shsInit(&shsInfo);
+    shsUpdate(&shsInfo, (const BYTE *) args[0], arglens[0]);
+    shsFinal(&shsInfo);
+    safe_format(buff, bp, "%0lx%0lx%0lx%0lx%0lx", shsInfo.digest[0],
+               shsInfo.digest[1], shsInfo.digest[2], shsInfo.digest[3],
+               shsInfo.digest[4]);
+  } else {
+    safe_str(T("#-1 UNSUPPORTED DIGEST TYPE"), buff, bp);
+  }
+#endif
+}
+
+#ifdef HAS_OPENSSL
+static void
+safe_hexchar(unsigned char c, char *buff, char **bp)
+{
+  const char *digits = "0123456789abcdef";
+  if (*bp - buff < BUFFER_LEN - 1) {
+    **bp = digits[c >> 4];
+    (*bp)++;
+  }
+  if (*bp - buff < BUFFER_LEN - 1) {
+    **bp = digits[c & 0x0F];
+    (*bp)++;
+  }
+}
+#endif
diff --git a/src/function.c b/src/function.c
new file mode 100644 (file)
index 0000000..c9246c2
--- /dev/null
@@ -0,0 +1,1578 @@
+/**
+ * \file function.c
+ *
+ * \brief The function parser.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "conf.h"
+#include "externs.h"
+#include "attrib.h"
+#include "dbdefs.h"
+#include "mushdb.h"
+#include "function.h"
+#include "match.h"
+#include "htab.h"
+#include "parse.h"
+#include "lock.h"
+#include "flags.h"
+#include "game.h"
+#include "mymalloc.h"
+#include "funs.h"
+#include "confmagic.h"
+
+static void func_hash_insert(const char *name, FUN *func);
+extern void local_functions(void);
+static int apply_restrictions(unsigned int result, const char *restriction);
+
+USERFN_ENTRY *userfn_tab;   /**< Table of user-defined functions */
+HASHTAB htab_function;     /**< Function hash table */
+HASHTAB htab_user_function; /**< User-defined function hash table */
+
+/* -------------------------------------------------------------------------*
+ * Utilities.
+ */
+
+/** Save a copy of the q-registers.
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preserve pointer to array to store the q-registers in.
+ */
+void
+save_global_regs(const char *funcname, char *preserve[])
+{
+  int i;
+
+  for (i = 0; i < NUMQ; i++) {
+    if (!global_eval_context.renv[i][0])
+      preserve[i] = NULL;
+    else {
+      preserve[i] = (char *) mush_malloc(BUFFER_LEN, funcname);
+      strcpy(preserve[i], global_eval_context.renv[i]);
+    }
+  }
+}
+
+/** Restore the q-registers, freeing the storage array.
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preserve pointer to array to restore the q-registers from.
+ */
+void
+restore_global_regs(const char *funcname, char *preserve[])
+{
+  int i;
+  for (i = 0; i < NUMQ; i++) {
+    if (preserve[i]) {
+      strcpy(global_eval_context.renv[i], preserve[i]);
+      mush_free(preserve[i], funcname);
+      preserve[i] = NULL;
+    } else {
+      global_eval_context.renv[i][0] = '\0';
+    }
+  }
+}
+
+/** Free the storage array for the q-registers, without restoring
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preserve pointer to array to free q-registers from.
+ */
+void
+free_global_regs(const char *funcname, char *preserve[])
+{
+  int i;
+  for (i = 0; i < NUMQ; i++) {
+    if (preserve[i])
+      mush_free(preserve[i], funcname);
+  }
+}
+
+/** Initilalize an array for the q-registers, setting all NULL.
+ * \param preserve pointer to array to free q-registers from.
+ */
+void
+init_global_regs(char *preserve[])
+{
+  int i;
+  for (i = 0; i < NUMQ; i++) {
+    preserve[i] = NULL;
+  }
+}
+
+/** Restore the q-registers, without freeing the storage array.
+ * \param preserve pointer to array to restore the q-registers from.
+ */
+void
+load_global_regs(char *preserve[])
+{
+  int i;
+  for (i = 0; i < NUMQ; i++) {
+    if (preserve[i]) {
+      strcpy(global_eval_context.renv[i], preserve[i]);
+    } else {
+      global_eval_context.renv[i][0] = '\0';
+    }
+  }
+}
+
+/** Save a copy of the environment (%0-%9)
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preserve pointer to array to store %0-%9 in.
+ */
+void
+save_global_env(const char *funcname __attribute__ ((__unused__)),
+               char *preserve[])
+{
+  int i;
+  for (i = 0; i < 10; i++)
+    preserve[i] = global_eval_context.wenv[i];
+}
+
+/** Restore the environment (%0-%9)
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preserve pointer to array to restore %0-%9 from.
+ */
+void
+restore_global_env(const char *funcname __attribute__ ((__unused__)),
+                  char *preserve[])
+{
+  int i;
+  for (i = 0; i < 10; i++)
+    global_eval_context.wenv[i] = preserve[i];
+}
+
+/** Save a copy of the wnxt and rnxt state
+ * This function must deal with both the addresses and the values
+ * of these variables, because they get modified in all sorts of
+ * nasty ways that we may not account for.
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preservew pointer to array to store the wnxt address in.
+ * \param preserver pointer to array to store the rnxt address in.
+ * \param valw pointer to array to store the wnxt value in.
+ * \param valr pointer to array to store the rnxt value in.
+ */
+void
+save_global_nxt(const char *funcname, char *preservew[], char *preserver[],
+               char *valw[], char *valr[])
+{
+  int i;
+  for (i = 0; i < NUMQ; i++) {
+    preserver[i] = global_eval_context.rnxt[i];
+    if (!global_eval_context.rnxt[i])
+      valr[i] = NULL;
+    else {
+      valr[i] = (char *) mush_malloc(BUFFER_LEN, funcname);
+      strcpy(valr[i], global_eval_context.rnxt[i]);
+    }
+  }
+  for (i = 0; i < 10; i++) {
+    preservew[i] = global_eval_context.wnxt[i];
+    if (!global_eval_context.wnxt[i])
+      valw[i] = NULL;
+    else {
+      valw[i] = (char *) mush_malloc(BUFFER_LEN, funcname);
+      strcpy(valw[i], global_eval_context.wnxt[i]);
+    }
+  }
+}
+
+/** Restore a copy of the wnxt and rnxt state
+ * \param funcname name of function calling (for memory leak testing)
+ * \param preservew pointer to array to restore the wnxt address from.
+ * \param preserver pointer to array to restore the rnxt address from.
+ * \param valw pointer to array to restore the wnxt value from.
+ * \param valr pointer to array to restore the rnxt value from.
+ */
+void
+restore_global_nxt(const char *funcname, char *preservew[], char *preserver[],
+                   char *valw[], char *valr[])
+{
+  int i;
+  for (i = 0; i < NUMQ; i++) {
+    global_eval_context.rnxt[i] = preserver[i];
+    if (preserver[i]) {
+      /* There was a former address, so we can restore to it */
+      strcpy(global_eval_context.rnxt[i], valr[i]);
+      mush_free(valr[i], funcname);
+      valr[i] = NULL;
+    }
+  }
+  for (i = 0; i < 10; i++) {
+    global_eval_context.wnxt[i] = preservew[i];
+    if (preservew[i]) {
+      /* There was a former address, so we can restore to it */
+      strcpy(global_eval_context.wnxt[i], valw[i]);
+      mush_free(valw[i], funcname);
+      valw[i] = NULL;
+    }
+  }
+}
+
+/** Check for a delimiter in an argument of a function call.
+ * This function checks a given argument of a function call and sees
+ * if it could be used as a delimiter. A delimiter must be a single
+ * character. If the argument isn't present or is null, we return
+ * the default delimiter, a space.
+ * \param buff unused.
+ * \param bp unused.
+ * \param nfargs number of arguments to the function.
+ * \param fargs array of function arguments.
+ * \param sep_arg index of the argument to check for a delimiter.
+ * \param sep pointer to separator character, used to return separator.
+ * \retval 0 illegal separator argument.
+ * \retval 1 successfully returned a separator (maybe the default one).
+ */
+int
+delim_check(char *buff, char **bp, int nfargs, char *fargs[], int sep_arg,
+           char *sep)
+{
+  /* Find a delimiter. */
+
+  if (nfargs >= sep_arg) {
+    if (!*fargs[sep_arg - 1])
+      *sep = ' ';
+    else if (strlen(fargs[sep_arg - 1]) != 1) {
+      safe_str(T("#-1 SEPARATOR MUST BE ONE CHARACTER"), buff, bp);
+      return 0;
+    } else
+      *sep = *fargs[sep_arg - 1];
+  } else
+    *sep = ' ';
+
+  return 1;
+}
+
+/* --------------------------------------------------------------------------
+ * The actual function handlers
+ */
+
+/** An entry in the function table.
+ * This structure represents a function's entry in the function table.
+ */
+typedef struct fun_tab {
+  const char *name;    /**< Name of the function, uppercase. */
+  function_func fun;   /**< Pointer to code to call for this function. */
+  int minargs; /**< Minimum args required. */
+  int maxargs; /**< Maximum args, or INT_MAX. If <0, last arg may have commas */
+  int flags;   /**< Flags to control how the function is parsed. */
+} FUNTAB;
+
+
+/** The function table. Functions can also be added at runtime with
+ * add_function().
+ */
+FUNTAB flist[] = {
+  {"@@", fun_atat, 1, -1, FN_NOPARSE},
+  {"ABS", fun_abs, 1, 1, FN_REG},
+  {"ACCENT", fun_accent, 2, 2, FN_REG},
+  {"ACCNAME", fun_accname, 1, 1, FN_REG},
+  {"ADD", fun_add, 2, INT_MAX, FN_REG},
+  {"AFTER", fun_after, 2, 2, FN_REG},
+  {"ALIGN", fun_align, 2, INT_MAX, FN_REG},
+  {"ALLOF", fun_allof, 2, INT_MAX, FN_NOPARSE},
+  {"ALPHAMAX", fun_alphamax, 1, INT_MAX, FN_REG},
+  {"ALPHAMIN", fun_alphamin, 1, INT_MAX, FN_REG},
+  {"AND", fun_and, 2, INT_MAX, FN_REG},
+  {"ANDFLAGS", fun_andflags, 2, 2, FN_REG},
+  {"ANDLFLAGS", fun_andlflags, 2, 2, FN_REG},
+  {"ANDLPOWERS", fun_andlflags, 2, 2, FN_REG},
+  {"ANDPOWERS", fun_andflags, 2, 2, FN_REG},
+  {"ANSI", fun_ansi, 2, -2, FN_NOPARSE},
+  {"APOSS", fun_aposs, 1, 1, FN_REG},
+  {"ARABIC2ROMAN", fun_arabictoroman, 1, 1, FN_REG},
+  {"ART", fun_art, 1, 1, FN_REG},
+  {"ATRLOCK", fun_atrlock, 1, 2, FN_REG},
+  {"BAND", fun_band, 1, INT_MAX, FN_REG},
+  {"BASECONV", fun_baseconv, 3, 3, FN_REG},
+  {"BEEP", fun_beep, 0, 1, FN_REG},
+  {"BEFORE", fun_before, 2, 2, FN_REG},
+  {"BNAND", fun_bnand, 2, 2, FN_REG},
+  {"BNOT", fun_bnot, 1, 1, FN_REG},
+  {"BOR", fun_bor, 1, INT_MAX, FN_REG},
+  {"BOUND", fun_bound, 2, 3, FN_REG},
+  {"BRACKETS", fun_brackets, 1, 1, FN_REG},
+  {"BREAK", fun_break, 1, 1, FN_REG},
+  {"BXOR", fun_bxor, 1, INT_MAX, FN_REG},
+  {"CAND", fun_cand, 2, INT_MAX, FN_NOPARSE},
+  {"CAPSTR", fun_capstr, 1, -1, FN_REG},
+  {"CASE", fun_switch, 3, INT_MAX, FN_NOPARSE},
+  {"CASEALL", fun_switch, 3, INT_MAX, FN_NOPARSE},
+  {"CAT", fun_cat, 1, INT_MAX, FN_REG},
+#ifdef CHAT_SYSTEM
+  {"CEMIT", fun_cemit, 2, 3, FN_REG},
+  {"CFLAGS", fun_cflags, 1, 2, FN_REG},
+  {"CHANNELS", fun_channels, 0, 2, FN_REG},
+  {"CLOCK", fun_clock, 1, 2, FN_REG},
+  {"COWNER", fun_cowner, 1, 1, FN_REG},
+  {"CTITLE", fun_ctitle, 2, 2, FN_REG},
+  {"CWHO", fun_cwho, 1, 1, FN_REG},
+#endif /* CHAT_SYSTEM */
+  {"CENTER", fun_center, 2, 3, FN_REG},
+  {"CHILDREN", fun_lsearch, 1, 1, FN_REG},
+  {"CHR", fun_chr, 1, 1, FN_REG},
+  {"CHECKPASS", fun_checkpass, 2, 2, FN_REG | FN_DIRECTOR},
+  {"CLONE", fun_clone, 1, 1, FN_REG},
+  {"CMDS", fun_cmds, 1, 1, FN_REG},
+#ifdef CHAT_SYSTEM
+  {"COBJ", fun_cobj, 1, 1, FN_REG},
+#endif /* CHAT_SYSTEM */
+  {"COMP", fun_comp, 2, 3, FN_REG},
+  {"CON", fun_con, 1, 1, FN_REG},
+  {"CONFIG", fun_config, 1, 1, FN_REG},
+  {"CONN", fun_conn, 1, 1, FN_REG},
+  {"CONTROLS", fun_controls, 2, 2, FN_REG},
+  {"CONVSECS", fun_convsecs, 1, 2, FN_REG},
+  {"CONVUTCSECS", fun_convsecs, 1, 1, FN_REG},
+  {"CONVTIME", fun_convtime, 1, 1, FN_REG},
+  {"COR", fun_cor, 2, INT_MAX, FN_NOPARSE},
+  {"CREATE", fun_create, 1, 2, FN_REG},
+#ifdef RPMODE_SYS
+  {"CRPLOG", fun_crplog, 0, 1, FN_REG},
+#endif
+  {"CTIME", fun_ctime, 1, 1, FN_REG},
+  {"DEC", fun_dec, 1, 1, FN_REG},
+  {"DECRYPT", fun_decrypt, 2, 2, FN_REG},
+  {"DEFAULT", fun_default, 2, 2, FN_NOPARSE},
+  {"DELETE", fun_delete, 3, 3, FN_REG},
+  {"DIE", fun_die, 2, 3, FN_REG},
+  {"DIG", fun_dig, 1, 3, FN_REG},
+  {"DIGEST", fun_digest, 2, -2, FN_REG},
+  {"DIST2D", fun_dist2d, 4, 4, FN_REG},
+  {"DIST3D", fun_dist3d, 6, 6, FN_REG},
+  {"DIV", fun_div, 2, 2, FN_REG},
+  {"DOING", fun_doing, 1, 1, FN_REG},
+  {"EDEFAULT", fun_edefault, 2, 2, FN_NOPARSE},
+  {"EDIT", fun_edit, 3, INT_MAX, FN_REG},
+  {"ELEMENT", fun_element, 3, 3, FN_REG},
+  {"ELEMENTS", fun_elements, 2, 4, FN_REG},
+  {"ELIST", fun_itemize, 1, 5, FN_REG},
+  {"ELOCK", fun_elock, 2, 2, FN_REG},
+  {"EMIT", fun_emit, 1, -1, FN_REG},
+  {"ENCRYPT", fun_encrypt, 2, 2, FN_REG},
+  {"EMPOWER", fun_empower, 2, 2, FN_REG},
+  {"ENTRANCES", fun_entrances, 0, 4, FN_REG},
+  {"ETIMEFMT", fun_etimefmt, 2, 2, FN_REG},
+  {"EQ", fun_eq, 2, 2, FN_REG},
+  {"EVAL", fun_eval, 2, 2, FN_REG},
+  {"ESCAPE", fun_escape, 1, -1, FN_REG},
+  {"EXIT", fun_exit, 1, 1, FN_REG},
+  {"EXTRACT", fun_extract, 3, 4, FN_REG},
+  {"FILTER", fun_filter, 2, 4, FN_REG},
+  {"FILTERBOOL", fun_filter, 2, 4, FN_REG},
+  {"FINDABLE", fun_findable, 2, 2, FN_REG},
+  {"FIRST", fun_first, 1, 2, FN_REG},
+  {"FIRSTOF", fun_firstof, 0, INT_MAX, FN_NOPARSE},
+  {"FLAGS", fun_flags, 0, 1, FN_REG},
+  {"FLIP", fun_flip, 1, 1, FN_REG},
+  {"FLOORDIV", fun_floordiv, 2, 2, FN_REG},
+  {"FOLD", fun_fold, 2, 4, FN_REG},
+  {"FOLDERSTATS", fun_folderstats, 0, 2, FN_REG},
+  {"FOLLOWERS", fun_followers, 1, 1, FN_REG},
+  {"FOLLOWING", fun_following, 1, 1, FN_REG},
+  {"FOREACH", fun_foreach, 2, 4, FN_REG},
+  {"FRACTION", fun_fraction, 1, 1, FN_REG},
+  {"FUNCTIONS", fun_functions, 0, 0, FN_REG},
+  {"FULLNAME", fun_fullname, 1, 1, FN_REG},
+  {"GET", fun_get, 1, 1, FN_REG},
+  {"GET_EVAL", fun_get_eval, 1, 1, FN_REG},
+  {"GRAB", fun_grab, 2, 3, FN_REG},
+  {"GRABALL", fun_graball, 2, 4, FN_REG},
+  {"GREP", fun_grep, 3, 3, FN_REG},
+  {"GREPI", fun_grep, 3, 3, FN_REG},
+  {"GT", fun_gt, 2, 2, FN_REG},
+  {"GTE", fun_gte, 2, 2, FN_REG},
+  {"HASATTR", fun_hasattr, 2, 2, FN_REG},
+  {"HASATTRP", fun_hasattr, 2, 2, FN_REG},
+  {"HASATTRPVAL", fun_hasattr, 2, 2, FN_REG},
+  {"HASATTRVAL", fun_hasattr, 2, 2, FN_REG},
+  {"HASFLAG", fun_hasflag, 2, 2, FN_REG},
+  {"HASPOWER", fun_hasdivpower, 2, 2, FN_REG},
+  {"HASPOWERGROUP", fun_haspowergroup, 2, 2, FN_REG},
+  {"HASTYPE", fun_hastype, 2, 2, FN_REG},
+  {"HEIGHT", fun_height, 1, 1, FN_REG},
+  {"HIDDEN", fun_hidden, 1, 1, FN_REG},
+  {"HOME", fun_home, 1, 1, FN_REG},
+  {"HOST", fun_hostname, 1, 1, FN_REG},
+  {"HOSTNAME", fun_hostname, 1, 1, FN_REG},
+  {"IDLE_AVERAGE", fun_idle_average, 1, 1, FN_REG},
+  {"IDLE_TOTAL", fun_idle_total, 1, 1, FN_REG},
+  {"IDLE", fun_idlesecs, 1, 1, FN_REG},
+  {"IDLESECS", fun_idlesecs, 1, 1, FN_REG},
+  {"IF", fun_if, 2, 3, FN_NOPARSE},
+  {"IFELSE", fun_if, 3, 3, FN_NOPARSE},
+  {"ILEV", fun_ilev, 0, 0, FN_REG},
+  {"INAME", fun_iname, 1, 1, FN_REG},
+  {"INC", fun_inc, 1, 1, FN_REG},
+  {"INDEX", fun_index, 4, 4, FN_REG},
+  {"INSERT", fun_insert, 3, 4, FN_REG},
+  {"INUM", fun_inum, 1, 1, FN_REG},
+  {"IPADDR", fun_ipaddr, 1, 1, FN_REG},
+  {"ISDAYLIGHT", fun_isdaylight, 0, 0, FN_REG},
+  {"ISDBREF", fun_isdbref, 1, 1, FN_REG},
+  {"ISINT", fun_isint, 1, 1, FN_REG},
+  {"ISNUM", fun_isnum, 1, 1, FN_REG},
+  {"ISWORD", fun_isword, 1, 1, FN_REG},
+  {"ITER", fun_iter, 2, 4, FN_NOPARSE},
+  {"ITEMS", fun_items, 2, 2, FN_REG},
+  {"ITEMIZE", fun_itemize, 1, 4, FN_REG},
+  {"ITEXT", fun_itext, 1, 1, FN_REG},
+  {"LAST", fun_last, 1, 2, FN_REG},
+  {"LATTR", fun_lattr, 1, 1, FN_REG},
+  {"LCON", fun_dbwalker, 1, 1, FN_REG},
+  {"LCSTR", fun_lcstr, 1, -1, FN_REG},
+  {"LDELETE", fun_ldelete, 2, 3, FN_REG},
+  {"LDIVISIONS", fun_dbwalker, 1, 1, FN_REG},
+  {"LEFT", fun_left, 2, 2, FN_REG},
+  {"LEMIT", fun_lemit, 1, -1, FN_REG},
+  {"LEXITS", fun_dbwalker, 1, 1, FN_REG},
+  {"LFLAGS", fun_lflags, 0, 1, FN_REG},
+  {"LINK", fun_link, 2, 3, FN_REG},
+  {"LIST", fun_list, 1, 1, FN_REG},
+  {"LIT", fun_lit, 1, -1, FN_LITERAL},
+  {"LJUST", fun_ljust, 2, 3, FN_REG},
+  {"LMATH", fun_lmath, 2, 3, FN_REG},
+  {"LNUM", fun_lnum, 1, 3, FN_REG},
+  {"LOC", fun_loc, 1, 1, FN_REG},
+  {"LOCTREE", fun_loctree, 1, 1, FN_REG},
+  {"LOCALIZE", fun_localize, 1, 1, FN_NOPARSE},
+  {"LOCATE", fun_locate, 3, 3, FN_REG},
+  {"LOCK", fun_lock, 1, 2, FN_REG},
+  {"LPARENT", fun_lparent, 1, 1, FN_REG},
+  {"LPLAYERS", fun_dbwalker, 1, 1, FN_REG},
+  {"LPORTS", fun_lports, 0, 0, FN_REG},
+  {"LPOS", fun_lpos, 2, 2, FN_REG},
+  {"LTHINGS", fun_dbwalker, 1, 1, FN_REG},
+  {"LSEARCH", fun_lsearch, 1, 5, FN_REG},
+  {"LSEARCHR", fun_lsearch, 1, 5, FN_REG},
+  {"LSTATS", fun_lstats, 0, 1, FN_REG},
+  {"LT", fun_lt, 2, 2, FN_REG},
+  {"LTE", fun_lte, 2, 2, FN_REG},
+  {"LVCON", fun_dbwalker, 1, 1, FN_REG},
+  {"LVEXITS", fun_dbwalker, 1, 1, FN_REG},
+  {"LVPLAYERS", fun_dbwalker, 1, 1, FN_REG},
+  {"LVTHINGS", fun_dbwalker, 1, 1, FN_REG},
+  {"LWHO", fun_lwho, 0, 1, FN_REG},
+  {"MAIL", fun_mail, 0, 2, FN_REG},
+  {"MAILFROM", fun_mailfrom, 1, 2, FN_REG},
+  {"MAILSTATS", fun_mailstats, 1, 1, FN_REG},
+  {"MAILDSTATS", fun_mailstats, 1, 1, FN_REG},
+  {"MAILFSTATS", fun_mailstats, 1, 1, FN_REG},
+  {"MAILSTATUS", fun_mailstatus, 1, 2, FN_REG},
+  {"MAILSUBJECT", fun_mailsubject, 1, 2, FN_REG},
+  {"MAILTIME", fun_mailtime, 1, 2, FN_REG},
+  {"MAP", fun_map, 2, 4, FN_REG},
+  {"MATCH", fun_match, 2, 3, FN_REG},
+  {"MATCHALL", fun_matchall, 2, 4, FN_REG},
+  {"MAX", fun_max, 1, INT_MAX, FN_REG},
+  {"MEAN", fun_mean, 1, INT_MAX, FN_REG},
+  {"MEDIAN", fun_median, 1, INT_MAX, FN_REG},
+  {"MEMBER", fun_member, 2, 3, FN_REG},
+  {"MERGE", fun_merge, 3, 3, FN_REG},
+  {"MID", fun_mid, 3, 3, FN_REG},
+  {"MIN", fun_min, 1, INT_MAX, FN_REG},
+  {"MIX", fun_mix, 3, 12, FN_REG},
+  {"MODULO", fun_modulo, 2, 2, FN_REG},
+  {"MONEY", fun_money, 1, 1, FN_REG},
+  {"MTIME", fun_mtime, 1, 1, FN_REG},
+  {"MUDNAME", fun_mudname, 0, 0, FN_REG},
+  {"MUL", fun_mul, 2, INT_MAX, FN_REG},
+  {"MUNGE", fun_munge, 3, 5, FN_REG},
+  {"MWHO", fun_lwho, 0, 0, FN_REG},
+  {"NAME", fun_name, 0, 2, FN_REG},
+  {"NAND", fun_nand, 1, INT_MAX, FN_REG},
+  {"NATTR", fun_nattr, 1, 1, FN_REG},
+  {"NCON", fun_dbwalker, 1, 1, FN_REG},
+  {"NEXITS", fun_dbwalker, 1, 1, FN_REG},
+  {"NPLAYERS", fun_dbwalker, 1, 1, FN_REG},
+  {"NTHINGS", fun_dbwalker, 1, 1, FN_REG},
+  {"NVCON", fun_dbwalker, 1, 1, FN_REG},
+  {"NVEXITS", fun_dbwalker, 1, 1, FN_REG},
+  {"NVPLAYERS", fun_dbwalker, 1, 1, FN_REG},
+  {"NVTHINGS", fun_dbwalker, 1, 1, FN_REG},
+  {"NEARBY", fun_nearby, 2, 2, FN_REG},
+  {"NEQ", fun_neq, 2, 2, FN_REG},
+  {"NEXT", fun_next, 1, 1, FN_REG},
+  {"NOR", fun_nor, 1, INT_MAX, FN_REG},
+  {"NOT", fun_not, 1, 1, FN_REG},
+#ifdef CHAT_SYSTEM 
+  {"NSCEMIT", fun_cemit, 2, 3, FN_REG},
+#endif /* CHAT_SYSTEM */
+  {"NSEMIT", fun_emit, 1, -1, FN_REG},
+  {"NSLEMIT", fun_lemit, 1, -1, FN_REG},
+  {"NSOEMIT", fun_oemit, 2, -2, FN_REG},
+  {"NSPEMIT", fun_pemit, 2, -2, FN_REG},
+  {"NSREMIT", fun_remit, 2, -2, FN_REG},
+  {"NSZEMIT", fun_zemit, 2, -2, FN_REG},
+  {"NUM", fun_num, 1, 1, FN_REG},
+  {"NULL", fun_null, 1, INT_MAX, FN_REG},
+  {"OBJ", fun_obj, 1, 1, FN_REG},
+  {"OBJEVAL", fun_objeval, 2, -2, FN_NOPARSE},
+  {"OBJID", fun_objid, 1, 1, FN_REG},
+  {"OBJMEM", fun_objmem, 1, 1, FN_REG},
+  {"OEMIT", fun_oemit, 2, -2, FN_REG},
+  {"OPEN", fun_open, 2, 2, FN_REG},
+  {"OR", fun_or, 2, INT_MAX, FN_REG},
+  {"ORD", fun_ord, 1, 1, FN_REG},
+  {"ORFLAGS", fun_orflags, 2, 2, FN_REG},
+  {"ORLFLAGS", fun_orlflags, 2, 2, FN_REG},
+  {"ORLPOWERS", fun_orlflags, 2, 2, FN_REG},
+  {"ORPOWERS", fun_orflags, 2, 2, FN_REG},
+  {"OOREF", fun_ooref, 0, 0, FN_REG},
+  {"OWNER", fun_owner, 1, 1, FN_REG},
+  {"PARENT", fun_parent, 1, 2, FN_REG},
+  {"PCREATE", fun_pcreate, 2, 2, FN_REG},
+  {"PEMIT", fun_pemit, 2, -2, FN_REG},
+  {"PGHASPOWER", fun_pghaspower, 3, 4, FN_REG},
+  {"PGPOWERS", fun_pgpowers, 2, 2, FN_REG},
+  {"PLAYERMEM", fun_playermem, 1, 1, FN_REG},
+  {"PMATCH", fun_pmatch, 1, 1, FN_REG},
+  {"POLL", fun_poll, 0, 0, FN_REG},
+  {"PORTS", fun_ports, 1, 1, FN_REG},
+  {"POS", fun_pos, 2, 2, FN_REG},
+  {"POSS", fun_poss, 1, 1, FN_REG},
+  {"POWERS", fun_powers, 1, 2, FN_REG},
+  {"PRIMARYDIVISION", fun_primary_division, 1, 1, FN_REG},
+  {"PROGRAM", fun_prog, 3, 5, FN_REG},
+  {"PROMPT", fun_prompt, 1, 2, FN_REG},
+  {"PUEBLO", fun_pueblo, 1, 1, FN_REG},
+  {"QUITPROG", fun_quitprog, 1, 1, FN_REG},
+  {"QUOTA", fun_quota, 1, 1, FN_REG},
+  {"R", fun_r, 1, 1, FN_REG},
+  {"RAND", fun_rand, 1, 2, FN_REG},
+  {"RANDWORD", fun_randword, 1, 2, FN_REG},
+  {"RECV", fun_recv, 1, 1, FN_REG},
+  {"REGEDIT", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
+  {"REGEDITALL", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
+  {"REGEDITALLI", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
+  {"REGEDITI", fun_regreplace, 3, INT_MAX, FN_NOPARSE},
+  {"REGMATCH", fun_regmatch, 2, 3, FN_REG},
+  {"REGMATCHI", fun_regmatch, 2, 3, FN_REG},
+  {"REGRAB", fun_regrab, 2, 4, FN_REG},
+  {"REGRABALL", fun_regrab, 2, 4, FN_REG},
+  {"REGRABALLI", fun_regrab, 2, 3, FN_REG},
+  {"REGRABI", fun_regrab, 2, 3, FN_REG},
+  {"REGREP", fun_regrep, 3, 3, FN_REG},
+  {"REGREPI", fun_regrep, 3, 3, FN_REG},
+  {"RESWITCH", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
+  {"RESWITCHALL", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
+  {"RESWITCHALLI", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
+  {"RESWITCHI", fun_reswitch, 3, INT_MAX, FN_NOPARSE},
+  {"REMAINDER", fun_remainder, 2, 2, FN_REG},
+  {"REMIT", fun_remit, 2, -2, FN_REG},
+  {"REMOVE", fun_remove, 2, 3, FN_REG},
+  {"REPEAT", fun_repeat, 2, 2, FN_REG},
+  {"REPLACE", fun_replace, 3, 4, FN_REG},
+  {"REST", fun_rest, 1, 2, FN_REG},
+  {"RESTARTS", fun_restarts, 0, 0, FN_REG},
+  {"RESTARTTIME", fun_restarttime, 0, 0, FN_REG},
+  {"REVERSE", fun_flip, 1, 1, FN_REG},
+  {"REVWORDS", fun_revwords, 1, 3, FN_REG},
+  {"RIGHT", fun_right, 2, 2, FN_REG},
+  {"RJUST", fun_rjust, 2, 3, FN_REG},
+  {"RLOC", fun_rloc, 2, 2, FN_REG},
+  {"RNUM", fun_rnum, 2, 2, FN_REG},
+  {"ROMAN2ARABIC",  fun_romantoarabic, 1, 1, FN_REG},
+  {"ROOM", fun_room, 1, 1, FN_REG},
+  {"ROOT", fun_root, 2, 2, FN_REG},
+  {"S", fun_s, 1, -1, FN_REG},
+  {"SCAN", fun_scan, 1, -2, FN_REG},
+  {"SCRAMBLE", fun_scramble, 1, -1, FN_REG},
+  {"SECS", fun_secs, 0, 0, FN_REG},
+  {"SECURE", fun_secure, 1, -1, FN_REG},
+  {"SENT", fun_sent, 1, 1, FN_REG},
+  {"SET", fun_set, 2, 2, FN_REG},
+  {"SETQ", fun_setq, 2, 2, FN_REG},
+  {"SETR", fun_setq, 2, 2, FN_REG},
+  {"SETDIFF", fun_setdiff, 2, 5, FN_REG},
+  {"SETINTER", fun_setinter, 2, 5, FN_REG},
+  {"SETUNION", fun_setunion, 2, 5, FN_REG},
+  {"SHA0", fun_sha0, 1, 1, FN_REG},
+  {"SHL", fun_shl, 2, 2, FN_REG},
+  {"SHR", fun_shr, 2, 2, FN_REG},
+  {"SHUFFLE", fun_shuffle, 1, 3, FN_REG},
+  {"SIGN", fun_sign, 1, 1, FN_REG},
+  {"SIGNAL", fun_signal, 2, 3, FN_REG},
+  {"SORT", fun_sort, 1, 4, FN_REG},
+  {"SORTBY", fun_sortby, 2, 4, FN_REG},
+  {"SOUNDEX", fun_soundex, 1, 1, FN_REG},
+  {"SOUNDSLIKE", fun_soundlike, 2, 2, FN_REG},
+  {"SPACE", fun_space, 1, 1, FN_REG},
+  {"SPELLNUM", fun_spellnum, 1, 1, FN_REG},
+  {"SPLICE", fun_splice, 3, 4, FN_REG},
+#ifdef _SWMP_
+  {"SQ_RESPOND", fun_sq_respond, 1, 1, FN_REG},
+#endif
+  {"SQL", fun_sql, 1, 3, FN_REG},
+  {"SQLESCAPE", fun_sql_escape, 1, 1, FN_REG},
+  {"SQUISH", fun_squish, 1, 2, FN_REG},
+  {"SSL", fun_ssl, 1, 1, FN_REG},
+  {"STARTTIME", fun_starttime, 0, 0, FN_REG},
+  {"STEP", fun_step, 3, 5, FN_REG},
+  {"STRCAT", fun_strcat, 1, INT_MAX, FN_REG},
+  {"STRINSERT", fun_strinsert, 3, -3, FN_REG},
+  {"STRIPACCENTS", fun_stripaccents, 1, 1, FN_REG},
+  {"STRIPANSI", fun_stripansi, 1, -1, FN_REG},
+  {"STRLEN", fun_strlen, 1, -1, FN_REG},
+  {"STRMATCH", fun_strmatch, 2, 2, FN_REG},
+  {"STRREPLACE", fun_strreplace, 4, 4, FN_REG},
+  {"SUB", fun_sub, 2, 2, FN_REG},
+  {"SUBJ", fun_subj, 1, 1, FN_REG},
+  {"SWITCH", fun_switch, 3, INT_MAX, FN_NOPARSE},
+  {"SWITCHALL", fun_switch, 3, INT_MAX, FN_NOPARSE},
+  {"T", fun_t, 1, 1, FN_REG},
+  {"TABLE", fun_table, 1, 5, FN_REG},
+  {"TEL", fun_tel, 2, 4, FN_REG},
+  {"TERMINFO", fun_terminfo, 1, 1, FN_REG},
+  {"TEXTFILE", fun_textfile, 2, 2, FN_REG},
+  {"TIME", fun_time, 0, 1, FN_REG},
+  {"TIMEFMT", fun_timefmt, 1, 2, FN_REG},
+  {"TIMESTRING", fun_timestring, 1, 2, FN_REG},
+  {"TR", fun_tr, 3, 3, FN_REG},
+  {"TRIGGER", fun_trigger, 1, 11, FN_REG},
+  {"TRIM", fun_trim, 1, 3, FN_REG},
+  {"TRIMPENN", fun_trim, 1, 3, FN_REG},
+  {"TRIMTINY", fun_trim, 1, 3, FN_REG},
+  {"TRUNC", fun_trunc, 1, 1, FN_REG},
+  {"TYPE", fun_type, 1, 1, FN_REG},
+  {"UCSTR", fun_ucstr, 1, -1, FN_REG},
+  {"UDEFAULT", fun_uldefault, 2, 12, FN_NOPARSE},
+  {"UFUN", fun_ufun, 1, 11, FN_REG},
+  {"ULDEFAULT", fun_uldefault, 1, 12, FN_NOPARSE},
+  {"ULOCAL", fun_ulocal, 1, 11, FN_REG},
+  {"IDLE_TIMES", fun_idle_times, 1, 1, FN_REG},
+  {"UTCTIME", fun_time, 0, 0, FN_REG},
+  {"U", fun_ufun, 1, 11, FN_REG},
+  {"V", fun_v, 1, 1, FN_REG},
+  {"VALID", fun_valid, 2, 2, FN_REG},
+  {"VERSION", fun_version, 0, 0, FN_REG},
+  {"VISIBLE", fun_visible, 2, 2, FN_REG},
+  {"WAIT", fun_wait, 2, 2, FN_NOPARSE},
+  {"WHERE", fun_where, 1, 1, FN_REG},
+  {"WIDTH", fun_width, 1, 1, FN_REG},
+  {"WIPE", fun_wipe, 1, 1, FN_REG},
+  {"WORDPOS", fun_wordpos, 2, 3, FN_REG},
+  {"WORDS", fun_words, 1, 2, FN_REG},
+  {"WRAP", fun_wrap, 2, 4, FN_REG},
+  {"XGET", fun_xget, 2, 2, FN_REG},
+  {"XOR", fun_xor, 2, INT_MAX, FN_REG},
+  {"XCON", fun_dbwalker, 3, 3, FN_REG},
+  {"XEXITS", fun_dbwalker, 3, 3, FN_REG},
+  {"XPLAYERS", fun_dbwalker, 3, 3, FN_REG},
+  {"XTHINGS", fun_dbwalker, 3, 3, FN_REG},
+  {"XVCON", fun_dbwalker, 3, 3, FN_REG},
+  {"XVEXITS", fun_dbwalker, 3, 3, FN_REG},
+  {"XVPLAYERS", fun_dbwalker, 3, 3, FN_REG},
+  {"XVTHINGS", fun_dbwalker, 3, 3, FN_REG},
+  {"ZEMIT", fun_zemit, 2, -2, FN_REG},
+  {"ZFUN", fun_zfun, 1, 11, FN_REG},
+  {"ZONE", fun_zone, 1, 2, FN_REG},
+  {"ZMWHO", fun_zwho, 1, 1, FN_REG},
+  {"ZWHO", fun_zwho, 1, 2, FN_REG},
+  {"VADD", fun_vadd, 2, 3, FN_REG},
+  {"VCROSS", fun_vcross, 2, 3, FN_REG},
+  {"VSUB", fun_vsub, 2, 3, FN_REG},
+  {"VMAX", fun_vmax, 2, 3, FN_REG},
+  {"VMIN", fun_vmin, 2, 3, FN_REG},
+  {"VMUL", fun_vmul, 2, 3, FN_REG},
+  {"VDOT", fun_vdot, 2, 3, FN_REG},
+  {"VMAG", fun_vmag, 1, 2, FN_REG},
+  {"VDIM", fun_words, 1, 2, FN_REG},
+  {"VUNIT", fun_vunit, 1, 2, FN_REG},
+  {"ACOS", fun_acos, 1, 2, FN_REG},
+  {"ASIN", fun_asin, 1, 2, FN_REG},
+  {"ATAN", fun_atan, 1, 2, FN_REG},
+  {"ATAN2", fun_atan2, 2, 3, FN_REG},
+  {"CEIL", fun_ceil, 1, 1, FN_REG},
+  {"COS", fun_cos, 1, 2, FN_REG},
+  {"CTU", fun_ctu, 3, 3, FN_REG},
+  {"E", fun_e, 0, 0, FN_REG},
+  {"EXP", fun_exp, 1, 1, FN_REG},
+  {"FDIV", fun_fdiv, 2, 2, FN_REG},
+  {"FMOD", fun_fmod, 2, 2, FN_REG},
+  {"FLOOR", fun_floor, 1, 1, FN_REG},
+  {"LOG", fun_log, 1, 2, FN_REG},
+  {"LN", fun_ln, 1, 1, FN_REG},
+  {"PI", fun_pi, 0, 0, FN_REG},
+  {"POWER", fun_power, 2, 2, FN_REG},
+  {"ROUND", fun_round, 2, 2, FN_REG},
+  {"SIN", fun_sin, 1, 2, FN_REG},
+  {"SQRT", fun_sqrt, 1, 1, FN_REG},
+  {"STDDEV", fun_stddev, 1, INT_MAX, FN_REG},
+  {"TAN", fun_tan, 1, 2, FN_REG},
+  {"HTML", fun_html, 1, 1, FN_REG},
+  {"TAG", fun_tag, 1, INT_MAX, FN_REG},
+  {"ENDTAG", fun_endtag, 1, 1, FN_REG},
+  {"TAGWRAP", fun_tagwrap, 2, 3, FN_REG},
+  {"LEVEL", fun_level, 1, 2, FN_REG},
+  {"DIVISION", fun_division, 1, 1, FN_REG},
+  {"DIVSCOPE", fun_divscope, 1, 2, FN_REG},
+  {"HASDIVPOWER", fun_hasdivpower, 2, 3, FN_REG},
+  {"UPDIV", fun_updiv, 1, 1, FN_REG},
+  {"DOWNDIV", fun_indivall, 1, 1, FN_REG},
+  {"EMPIRE", fun_empire, 1, 2, FN_REG},
+  {"INDIV", fun_indiv, 1, 2, FN_REG},
+  {"INDIVALL", fun_indivall, 1, 2, FN_REG},
+  {"POWOVER", fun_powover, 3, 3, FN_REG},
+  {"CANSEE", fun_visible, 2, 2, FN_REG},
+  {"POWERGROUPS", fun_powergroups, 0, 1, FN_REG},
+  {NULL, NULL, 0, 0, 0}
+};
+
+/** List all functions.
+ * \verbatim
+ * This is the mail interface to @list functions.
+ * \endverbatim
+ * \param player the enactor.
+ * \param lc if 1, return functions in lowercase.
+ */
+void
+do_list_functions(dbref player, int lc)
+{
+  /* lists all built-in functions. */
+  char *b = list_functions();
+  notify_format(player, "Functions: %s", lc ? strlower(b) : b);
+}
+
+/** Return a list of function names.
+ * This function returns the list of function names as a string.
+ * \return list of function names as a static string.
+ */
+char *
+list_functions(void)
+{
+  FUN *fp;
+  const char *ptrs[BUFFER_LEN / 2];
+  static char buff[BUFFER_LEN];
+  char *bp;
+  int nptrs = 0, i;
+  for (fp = (FUN *) hash_firstentry(&htab_function);
+       fp; fp = (FUN *) hash_nextentry(&htab_function)) {
+    if (fp->flags & FN_OVERRIDE)
+      continue;
+    ptrs[nptrs++] = fp->name;
+  }
+  fp = (FUN *) hash_firstentry(&htab_user_function);
+  while (fp) {
+    ptrs[nptrs++] = fp->name;
+    fp = (FUN *) hash_nextentry(&htab_user_function);
+  }
+  do_gensort((char **) ptrs, nptrs, 0);
+  bp = buff;
+  safe_str(ptrs[0], buff, &bp);
+  for (i = 1; i < nptrs; i++) {
+    safe_chr(' ', buff, &bp);
+    safe_str(ptrs[i], buff, &bp);
+  }
+  *bp = '\0';
+  return buff;
+}
+
+/*---------------------------------------------------------------------------
+ * Hashed function table stuff
+ */
+
+
+/** Look up a function by name.
+ * \param name name of function to look up.
+ * \return pointer to function data, or NULL.
+ */
+FUN *
+func_hash_lookup(const char *name)
+{
+  FUN *f;
+  f = (FUN *) hashfind(strupper(name), &htab_function);
+  if (!f || f->flags & FN_OVERRIDE)
+    f = (FUN *) hashfind(strupper(name), &htab_user_function);
+  return f;
+}
+
+static void
+func_hash_insert(const char *name, FUN *func)
+{
+  hashadd(name, (void *) func, &htab_function);
+}
+
+/** Initialize the function hash table.
+ */
+void
+init_func_hashtab(void)
+{
+  FUNTAB *ftp;
+
+  hashinit(&htab_function, 512, sizeof(FUN));
+  hashinit(&htab_user_function, 32, sizeof(FUN));
+  for (ftp = flist; ftp->name; ftp++) {
+    function_add(ftp->name, ftp->fun, ftp->minargs, ftp->maxargs, ftp->flags);
+  }
+  local_functions();
+}
+
+/** Function initization to perform after reading the config file.
+ * This function performs post-config initialization. Specifically,
+ * we need the max_globals value from the config file before we
+ * can allocate the global user function table here.
+ */
+void
+function_init_postconfig(void)
+{
+  userfn_tab =
+    (USERFN_ENTRY *) mush_malloc(MAX_GLOBAL_FNS * sizeof(USERFN_ENTRY),
+                                "userfn_tab");
+}
+
+/** Check permissions to run a function.
+ * \param player the executor.
+ * \param fp pointer to function data.
+ * \retval 1 executor may use the function.
+ * \retval 0 permission denied.
+ */
+int
+check_func(dbref player, FUN *fp)
+{
+  if (!fp)
+    return 0;
+  if ((fp->flags & (~FN_ARG_MASK)) == 0)
+    return 1;
+  if (fp->flags & FN_DISABLED)
+    return 0;
+  if ((fp->flags & FN_GOD) && !God(player))
+    return 0;
+  if ((fp->flags & FN_DIRECTOR) && !Director(player))
+    return 0;
+  if ((fp->flags & FN_ADMIN) && !Admin(player))
+    return 0;
+  if ((fp->flags & FN_NOGAGGED) && Gagged(player))
+    return 0;
+  if ((fp->flags & FN_NOFIXED) && Fixed(player))
+    return 0;
+  if ((fp->flags & FN_NOGUEST) && Guest(player))
+    return 0;
+  if((fp->flags & FN_NORP) && RPMODE(player))
+    return 0;
+  return 1;
+}
+
+/** Add an alias to a function.
+ * This function adds an alias to a function in the hash table.
+ * \param function name of function to alias.
+ * \param alias name of the alias to add.
+ * \retval 0 failure (alias exists, or function doesn't, or is a user fun).
+ * \retval 1 success.
+ */
+int
+alias_function(const char *function, const char *alias)
+{
+  FUN *fp;
+
+  /* Make sure the alias doesn't exist already */
+  if (func_hash_lookup(alias))
+    return 0;
+
+  /* Look up the original */
+  fp = func_hash_lookup(function);
+  if (!fp)
+    return 0;
+
+  /* We can't alias @functions. Just use another @function for these */
+  if (!(fp->flags & FN_BUILTIN))
+    return 0;
+
+  function_add(strdup(strupper(alias)), fp->where.fun,
+              fp->minargs, fp->maxargs, fp->flags);
+  return 1;
+}
+
+/** Add a function.
+ * \param name name of the function to add.
+ * \param fun pointer to compiled function code.
+ * \param minargs minimum arguments to function.
+ * \param maxargs maximum arguments to function.
+ * \param ftype function evaluation flags.
+ */
+void
+function_add(const char *name, function_func fun, int minargs, int maxargs,
+            int ftype)
+{
+  FUN *fp;
+  fp = (FUN *) mush_malloc(sizeof(FUN), "function");
+  memset(fp, 0, sizeof(FUN));
+  fp->name = name;
+  fp->where.fun = fun;
+  fp->minargs = minargs;
+  fp->maxargs = maxargs;
+  fp->flags = FN_BUILTIN | ftype;
+  func_hash_insert(name, fp);
+}
+
+/*-------------------------------------------------------------------------
+ * Function handlers and the other good stuff. Almost all this code is
+ * a modification of TinyMUSH 2.0 code.
+ */
+
+/** Strip a level of braces.
+ * this is a hack which just strips a level of braces. It malloc()s memory
+ * which must be free()d later.
+ * \param str string to strip braces from.
+ * \return newly allocated string with the first level of braces stripped.
+ */
+char *
+strip_braces(const char *str)
+{
+  char *buff;
+  char *bufc;
+
+  buff = (char *) mush_malloc(BUFFER_LEN, "strip_braces.buff");
+  bufc = buff;
+
+  while (isspace((unsigned char) *str))        /* eat spaces at the beginning */
+    str++;
+
+  switch (*str) {
+  case '{':
+    str++;
+    process_expression(buff, &bufc, &str, 0, 0, 0, PE_NOTHING, PT_BRACE, NULL);
+    *bufc = '\0';
+    return buff;
+    break;                     /* NOT REACHED */
+  default:
+    strcpy(buff, str);
+    return buff;
+  }
+}
+
+/*------------------------------------------------------------------------
+ * User-defined global function handlers 
+ */
+
+
+static Size_t userfn_count = 0;
+
+static int
+apply_restrictions(unsigned int result, const char *restriction)
+{
+  int flag, clear = 0;
+  char *tp;
+  while (restriction && *restriction) {
+    if ((tp = strchr(restriction, ' ')))
+      *tp++ = '\0';
+    if (*restriction == '!') {
+      restriction++;
+      clear = 1;
+    }
+    flag = 0;
+    if (!strcasecmp(restriction, "nobody")) {
+      flag = FN_DISABLED;
+    } else if (string_prefix(restriction, "nogag")) {
+      flag = FN_NOGAGGED;
+    } else if (string_prefix(restriction, "nofix")) {
+      flag = FN_NOFIXED;
+    } else if (string_prefix(restriction, "norp")) {
+           flag = FN_NORP;
+    } else if (!strcasecmp(restriction, "noguest")) {
+      flag = FN_NOGUEST;
+    } else if (!strcasecmp(restriction, "admin")) {
+      flag = FN_ADMIN;
+    } else if(!strcasecmp(restriction, "wizard")) {
+      flag = FN_DIRECTOR;
+    } else if (!strcasecmp(restriction, "director")) {
+      flag = FN_DIRECTOR;
+    } else if (!strcasecmp(restriction, "god")) {
+      flag = FN_GOD;
+    } else if (!strcasecmp(restriction, "nosidefx")) {
+      flag = FN_NOSIDEFX;
+    } else if (!strcasecmp(restriction, "logargs")) {
+      flag = FN_LOGARGS;
+    } else if (!strcasecmp(restriction, "logname")) {
+      flag = FN_LOGNAME;
+    } else if (!strcasecmp(restriction, "noparse")) {
+      flag = FN_NOPARSE;
+    } else if(!strcasecmp(restriction, "ulocal")) {
+      flag = FN_ULOCAL;
+    }
+    if (clear)
+      result &= ~flag;
+    else
+      result |= flag;
+    restriction = tp;
+  }
+  return result;
+}
+
+
+/** Given a function name and a restriction, apply the restriction to the
+ * function in addition to whatever its usual restrictions are.
+ * This is used by the configuration file startup in conf.c
+ * \verbatim
+ * Valid restrictions are:
+ *   nobody     disable the command
+ *   nogagged   can't be used by gagged players
+ *   nofixed    can't be used by fixed players
+ *   noguest    can't be used by guests
+ *   admin      can only be used by admins
+ *   director   can only be used by directors
+ *   god        can only be used by god
+ *   noplayer   can't be used by players, just objects/rooms/exits
+ *   nosidefx   can't be used to do side-effect thingies
+ * \endverbatim
+ * \param name name of function to restrict.
+ * \param restriction name of restriction to apply to function.
+ * \retval 1 success.
+ * \retval 0 failure (invalid function or restriction name).
+ */
+int
+restrict_function(const char *name, const char *restriction)
+{
+  FUN *fp;
+
+  if (!name || !*name)
+    return 0;
+  fp = func_hash_lookup(name);
+  if (!fp)
+    return 0;
+  fp->flags = apply_restrictions(fp->flags, restriction);
+  return 1;
+}
+
+/** Softcode interface to restrict a function.
+ * \verbatim
+ * This is the implementation of @function/restrict.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of function to restrict.
+ * \param restriction name of restriction to add.
+ */
+void
+do_function_restrict(dbref player, const char *name, const char *restriction)
+{
+  if (!Global_Funcs(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (!name) {
+    notify(player, T("Restrict what function?"));
+    return;
+  }
+  if (!restriction) {
+    notify(player, T("Do what with the function?"));
+    return;
+  }
+  if (restrict_function(name, restriction))
+    notify(player, T("Restrictions modified."));
+  else
+    notify(player, T("Restrict attempt failed."));
+}
+
+
+/** Add a user-defined function.
+ * \verbatim
+ * This is the implementation of the @function command. If no arguments
+ * are given, it lists all @functions defined. Otherwise, this adds
+ * an @function.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of function to add.
+ * \param argv array of arguments.
+ */
+void
+do_function(dbref player, char *name, char *argv[])
+{
+  char tbuf1[BUFFER_LEN];
+  char *bp = tbuf1;
+  dbref thing;
+  FUN *fp;
+
+  /* if no arguments, just give the list of user functions, by walking
+   * the function hash table, and looking up all functions marked
+   * as user-defined.
+   */
+
+  if (!name || !*name) {
+    if (userfn_count == 0) {
+      notify(player, T("No global user-defined functions exist."));
+      return;
+    }
+    if (Global_Funcs(player)) {
+      /* if the player is privileged, display user-def'ed functions
+       * with corresponding dbref number of thing and attribute name.
+       */
+      notify(player, T("Function Name                   Dbref #    Attrib"));
+      for (fp = (FUN *) hash_firstentry(&htab_user_function);
+          fp; fp = (FUN *) hash_nextentry(&htab_user_function)) {
+       notify_format(player,
+                     "%-32s %6d    %s", fp->name,
+                     userfn_tab[fp->where.offset].thing,
+                     userfn_tab[fp->where.offset].name);
+      }
+    } else {
+      /* just print out the list of available functions */
+      safe_str(T("User functions:"), tbuf1, &bp);
+      for (fp = (FUN *) hash_firstentry(&htab_user_function);
+          fp; fp = (FUN *) hash_nextentry(&htab_user_function)) {
+       safe_chr(' ', tbuf1, &bp);
+       safe_str(fp->name, tbuf1, &bp);
+      }
+      *bp = '\0';
+      notify(player, tbuf1);
+    }
+    return;
+  }
+  /* otherwise, we are adding a user function. 
+   * Only those with the Global_Funcs power may add stuff.
+   * If you add a function that is already a user-defined function,
+   * the old function gets over-written.
+   */
+
+  if (!Global_Funcs(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (!argv[1] || !*argv[1] || !argv[2] || !*argv[2]) {
+    notify(player, T("You must specify an object and an attribute."));
+    return;
+  }
+  /* make sure the function name length is okay */
+  if (strlen(name) >= SBUF_LEN) {
+    notify(player, T("Function name too long."));
+    return;
+  }
+  /* find the object. For some measure of security, the player must
+   * be able to examine it.
+   */
+  if ((thing = noisy_match_result(player, argv[1], NOTYPE, MAT_EVERYTHING))
+      == NOTHING)
+    return;
+  if (!Can_Examine(player, thing)) {
+    notify(player, T("No permission to examine object."));
+    return;
+  }
+  /* we don't need to check if the attribute exists. If it doesn't,
+   * it's not our problem - it's the user's responsibility to make
+   * sure that the attribute exists (if it doesn't, invoking the
+   * function will return a #-1 NO SUCH ATTRIBUTE error).
+   * We do, however, need to make sure that the user isn't trying
+   * to replace a built-in function.
+   */
+
+  fp = func_hash_lookup(upcasestr(name));
+  if (!fp) {
+    if (userfn_count >= (Size_t) MAX_GLOBAL_FNS) {
+      notify(player, T("Function table full."));
+      return;
+    }
+    if (argv[6] && *argv[6]) {
+      notify(player, T("Expected between 1 and 5 arguments."));
+      return;
+    }
+    /* a completely new entry. First, insert it into general hash table */
+    fp = (FUN *) mush_malloc(sizeof(FUN), "func_hash.FUN");
+    fp->name = mush_strdup(name, "func_hash.name");
+    fp->where.offset = userfn_count;
+    if (argv[3] && *argv[3]) {
+      fp->minargs = parse_integer(argv[3]);
+      if (fp->minargs < 0)
+       fp->minargs = 0;
+      else if (fp->minargs > 10)
+       fp->minargs = 10;
+    } else
+      fp->minargs = 0;
+
+    if (argv[4] && *argv[4]) {
+      fp->maxargs = parse_integer(argv[4]);
+      if (fp->maxargs < -10)
+       fp->maxargs = -10;
+      else if (fp->maxargs > 10)
+       fp->maxargs = 10;
+    } else
+      fp->maxargs = 10;
+    if (argv[5] && *argv[5])
+      fp->flags = apply_restrictions(0, argv[5]);
+    else
+      fp->flags = 0;
+    hashadd(name, fp, &htab_user_function);
+
+    /* now add it to the user function table */
+    userfn_tab[userfn_count].thing = thing;
+    userfn_tab[userfn_count].name =
+      mush_strdup(upcasestr(argv[2]), "userfn_tab.name");
+    userfn_tab[userfn_count].fn = mush_strdup(name, "usrfn_tab.fn");
+    userfn_count++;
+
+    notify(player, T("Function added."));
+    return;
+  } else {
+
+    /* we are modifying an old entry */
+    if ((fp->flags & FN_BUILTIN) && !(fp->flags & FN_OVERRIDE)) {
+      notify(player, T("You cannot change that built-in function."));
+      return;
+    }
+    if (fp->flags & FN_BUILTIN) {      /* Overriding a built in function */
+      if (userfn_count >= (Size_t) MAX_GLOBAL_FNS) {
+       notify(player, T("Function table full."));
+       return;
+      }
+      fp = (FUN *) mush_malloc(sizeof(FUN), "func_hash.FUN");
+      fp->name = mush_strdup(name, "func_hash.name");
+      fp->where.offset = userfn_count;
+      fp->flags = 0;
+      userfn_count++;
+      hashadd(name, fp, &htab_user_function);
+    }
+    userfn_tab[fp->where.offset].thing = thing;
+    if (userfn_tab[fp->where.offset].name)
+      mush_free((Malloc_t) userfn_tab[fp->where.offset].name,
+               "userfn_tab.name");
+    userfn_tab[fp->where.offset].name =
+      mush_strdup(upcasestr(argv[2]), "userfn_tab.name");
+    if (argv[3] && *argv[3]) {
+      fp->minargs = parse_integer(argv[3]);
+      if (fp->minargs < 0)
+       fp->minargs = 0;
+      else if (fp->minargs > 10)
+       fp->minargs = 10;
+    } else
+      fp->minargs = 0;
+
+    if (argv[4] && *argv[4]) {
+      fp->maxargs = parse_integer(argv[4]);
+      if (fp->maxargs < -10)
+       fp->maxargs = -10;
+      else if (fp->maxargs > 10)
+       fp->maxargs = 10;
+    } else
+      fp->maxargs = 10;
+
+    if (argv[5] && *argv[5])
+      fp->flags = apply_restrictions(0, argv[5]);
+    else
+      fp->flags = 0;
+
+    notify(player, T("Function updated."));
+  }
+}
+
+
+/** Restore an overridden built-in function.
+ * \verbatim
+ * If a built-in function is deleted with @function/delete, it can be
+ * restored with @function/restore. This implements @function/restore.
+ * If a user-defined function has been added, it will be removed by
+ * this function.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of function to restore.
+ */
+void
+do_function_restore(dbref player, const char *name)
+{
+  FUN *fp;
+  Size_t table_index, i;
+
+  if (!Global_Funcs(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  if (!name || !*name) {
+    notify(player, T("Restore what?"));
+    return;
+  }
+
+  fp = (FUN *) hashfind(strupper(name), &htab_function);
+
+  if (!fp) {
+    notify(player, T("That's not a builtin function."));
+    return;
+  }
+
+  if (!(fp->flags & FN_OVERRIDE)) {
+    notify(player, T("That function isn't deleted!"));
+    return;
+  }
+
+  fp->flags &= ~FN_OVERRIDE;
+  notify(player, T("Restored."));
+
+  /* Delete any @function with the same name */
+  fp = (FUN *) hashfind(strupper(name), &htab_user_function);
+  if (!fp)
+    return;
+  /* Remove it from the hash table */
+  hashdelete(fp->name, &htab_user_function);
+  /* Free its memory */
+  table_index = fp->where.offset;
+  mush_free((void *) fp->name, "func_hash.name");
+  mush_free(fp, "func_hash.FUN");
+  /* Fix up the user function table. Expensive, but how often will
+   * we need to delete an @function anyway?
+   */
+  mush_free((Malloc_t) userfn_tab[table_index].name, "userfn_tab.name");
+  mush_free((Malloc_t) userfn_tab[table_index].fn, "userfn_tab.fn");
+  userfn_count--;
+  for (i = table_index; i < userfn_count; i++) {
+    fp = (FUN *) hashfind(userfn_tab[i + 1].fn, &htab_user_function);
+    fp->where.offset = i;
+    userfn_tab[i].thing = userfn_tab[i + 1].thing;
+    userfn_tab[i].name = mush_strdup(userfn_tab[i + 1].name, "userfn_tab.name");
+    mush_free((Malloc_t) userfn_tab[i + 1].name, "userfn_tab.name");
+    userfn_tab[i].fn = mush_strdup(userfn_tab[i + 1].fn, "userfn_tab.fn");
+    mush_free((Malloc_t) userfn_tab[i + 1].fn, "userfn_tab.fn");
+  }
+}
+
+/** Delete a function.
+ * \verbatim
+ * This code implements @function/delete, which deletes a function -
+ * either a built-in or a user-defined one.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the function to delete.
+ */
+void
+do_function_delete(dbref player, char *name)
+{
+  /* Deletes a user-defined function. 
+   * For security, you must control the object the function uses
+   * to delete the function.
+   */
+  Size_t table_index, i;
+  FUN *fp;
+
+  if (!Global_Funcs(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  fp = func_hash_lookup(name);
+  if (!fp) {
+    notify(player, T("No such function."));
+    return;
+  }
+  if (fp->flags & FN_BUILTIN) {
+    if (!Global_Funcs(player)) {
+      notify(player, T("You can't delete that @function."));
+      return;
+    }
+    fp->flags |= FN_OVERRIDE;
+    notify(player, T("Function deleted."));
+    return;
+  }
+
+  table_index = fp->where.offset;
+  if (!controls(player, userfn_tab[table_index].thing)) {
+    notify(player, T("You can't delete that @function."));
+    return;
+  }
+  /* Remove it from the hash table */
+  hashdelete(fp->name, &htab_user_function);
+  /* Free its memory */
+  mush_free((void *) fp->name, "func_hash.name");
+  mush_free(fp, "func_hash.FUN");
+  /* Fix up the user function table. Expensive, but how often will
+   * we need to delete an @function anyway?
+   */
+  mush_free((Malloc_t) userfn_tab[table_index].name, "userfn_tab.name");
+  mush_free((Malloc_t) userfn_tab[table_index].fn, "userfn_tab.fn");
+  userfn_count--;
+  for (i = table_index; i < userfn_count; i++) {
+    fp = (FUN *) hashfind(userfn_tab[i + 1].fn, &htab_user_function);
+    fp->where.offset = i;
+    userfn_tab[i].thing = userfn_tab[i + 1].thing;
+    userfn_tab[i].name = mush_strdup(userfn_tab[i + 1].name, "userfn_tab.name");
+    mush_free((Malloc_t) userfn_tab[i + 1].name, "userfn_tab.name");
+    userfn_tab[i].fn = mush_strdup(userfn_tab[i + 1].fn, "userfn_tab.fn");
+    mush_free((Malloc_t) userfn_tab[i + 1].fn, "userfn_tab.fn");
+  }
+  notify(player, T("Function deleted."));
+}
+
+/** Enable or disable a function.
+ * \verbatim
+ * This implements @function/disable and @function/enable.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the function to enable or disable.
+ * \param toggle if 1, enable; if 0, disable.
+ */
+void
+do_function_toggle(dbref player, char *name, int toggle)
+{
+  FUN *fp;
+
+  if (!Global_Funcs(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  fp = func_hash_lookup(name);
+  if (!fp) {
+    notify(player, T("No such function."));
+    return;
+  }
+
+  if (toggle) {
+    fp->flags &= ~FN_DISABLED;
+    notify(player, T("Enabled."));
+  } else {
+    fp->flags |= FN_DISABLED;
+    notify(player, T("Disabled."));
+  }
+}
+
+/** Get information about a function.
+ * \verbatim
+ * This implements the @function <function> command, which reports function
+ * details to the enactor.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the function.
+ */
+void
+do_function_report(dbref player, char *name)
+{
+  FUN *fp;
+  char tbuf[BUFFER_LEN];
+  char *tp;
+  const char *state, *state2;
+  int first = 1;
+  int maxargs;
+
+  fp = func_hash_lookup(name);
+  if (!fp) {
+    notify(player, T("No such function."));
+    return;
+  }
+
+  if (fp->flags & FN_BUILTIN)
+    state2 = "";
+  else
+    state2 = " @function";
+
+  if (fp->flags & FN_DISABLED)
+    state = "Disabled";
+  else
+    state = "Enabled";
+
+  notify_format(player, T("Name      : %s() (%s%s)"), fp->name, state, state2);
+
+  tp = tbuf;
+  tbuf[0] = '\0';
+  if (fp->flags & FN_NOPARSE) {
+    safe_str("Noparse", tbuf, &tp);
+    if (first)
+      first = 0;
+  }
+
+  if(fp->flags & FN_ULOCAL) {
+         safe_str("Ulocal", tbuf, &tp);
+         if(first)
+                 first = 0;
+  }
+
+  if (fp->flags & FN_LITERAL) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Literal", tbuf, &tp);
+    first = 0;
+  }
+
+  if (fp->flags & FN_NOSIDEFX) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Nosidefx", tbuf, &tp);
+    first = 0;
+  }
+
+  if (fp->flags & FN_LOGARGS) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("LogArgs", tbuf, &tp);
+    first = 0;
+  } else if (fp->flags & FN_LOGNAME) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("LogName", tbuf, &tp);
+    first = 0;
+  }
+
+
+  if (fp->flags & FN_NOGAGGED) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Nogagged", tbuf, &tp);
+    first = 0;
+  }
+
+  if (fp->flags & FN_NOGUEST) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Noguest", tbuf, &tp);
+    first = 0;
+  }
+
+  if (fp->flags & FN_NOFIXED) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Nofixed", tbuf, &tp);
+    first = 0;
+  }
+
+  if(fp->flags & FN_NORP) {
+         if(first == 0)
+                 safe_strl(", ", 2, tbuf, &tp);
+         safe_str("NoRPMODE", tbuf, &tp);
+         first =0;
+  }
+
+  if (fp->flags & FN_DIRECTOR) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Director", tbuf, &tp);
+    first = 0;
+  }
+
+  if (fp->flags & FN_ADMIN) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("Admin", tbuf, &tp);
+    first = 0;
+  }
+
+  if (fp->flags & FN_GOD) {
+    if (first == 0)
+      safe_strl(", ", 2, tbuf, &tp);
+    safe_str("God", tbuf, &tp);
+    first = 0;
+  }
+
+  *tp = '\0';
+  notify_format(player, T("Flags     : %s"), tbuf);
+
+  if (!(fp->flags & FN_BUILTIN) && Global_Funcs(player)) {
+    notify_format(player, T("Location  : #%d/%s"),
+                 userfn_tab[fp->where.offset].thing,
+                 userfn_tab[fp->where.offset].name);
+  }
+
+  maxargs = abs(fp->maxargs);
+
+  tp = tbuf;
+
+  if (fp->maxargs < 0) {
+    safe_str(T("(Commas okay in last argument)"), tbuf, &tp);
+    *tp = '\0';
+  } else
+    tbuf[0] = '\0';
+
+  if (fp->minargs == maxargs)
+    notify_format(player, T("Arguments : %d %s"), fp->minargs, tbuf);
+  else if (fp->maxargs == INT_MAX)
+    notify_format(player, T("Arguments : At least %d %s"), fp->minargs, tbuf);
+  else
+    notify_format(player,
+                 T("Arguments : %d to %d %s"), fp->minargs, maxargs, tbuf);
+}
diff --git a/src/fundb.c b/src/fundb.c
new file mode 100644 (file)
index 0000000..d6214fe
--- /dev/null
@@ -0,0 +1,2001 @@
+/**
+ * \file fundb.c
+ *
+ * \brief Dbref-related functions for mushcode.
+ *
+ *
+ */
+
+#include "copyrite.h"
+
+#include "config.h"
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "dbdefs.h"
+#include "flags.h"
+
+#include "match.h"
+#include "parse.h"
+#include "boolexp.h"
+#include "command.h"
+#include "game.h"
+#include "mushdb.h"
+#include "privtab.h"
+#include "lock.h"
+#include "log.h"
+#include "attrib.h"
+#include "function.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#pragma warning( disable : 4761)       /* NJG: disable warning re conversion */
+#endif
+
+extern PRIV attr_privs[];
+static lock_type get_locktype(char *str);
+extern struct db_stat_info *get_stats(dbref owner);
+static int lattr_helper(dbref player, dbref thing, dbref parent, char const *pattern,
+                       ATTR *atr, void *args);
+
+static dbref
+ dbwalk(char *buff, char **bp, dbref executor, dbref enactor,
+             int type, dbref loc, dbref after, int skipdark,
+                    int start, int count, int *retcount);
+
+
+static const char *
+do_get_attrib(dbref executor, dbref thing, const char *attrib)
+{
+  ATTR *a;
+  char const *value;
+
+  a = atr_get(thing, strupper(attrib));
+  if (a) {
+    if (Can_Read_Attr(executor, thing, a)) {
+      if (strlen(value = atr_value(a)) < BUFFER_LEN)
+       return value;
+      else
+       return T("#-1 ATTRIBUTE LENGTH TOO LONG");
+    }
+    return T(e_atrperm);
+  }
+  a = atr_match(attrib);
+  if (a) {
+    if (Can_Read_Attr(executor, thing, a))
+      return "";
+    return T(e_atrperm);
+  }
+  if (!Can_Examine(executor, thing))
+    return T(e_atrperm);
+  return "";
+}
+
+/** Structure containing arguments for lattr_helper */
+struct lh_args {
+  int first;           /**< Is this is the first attribute, or later? */
+  char *buff;          /**< Buffer to store output */
+  char **bp;           /**< Pointer to address of insertion point in buff */
+};
+
+/* this function produces a space-separated list of attributes that are
+ * on an object.
+ */
+/* ARGSUSED */
+static int
+lattr_helper(dbref player __attribute__ ((__unused__)),
+            dbref thing __attribute__ ((__unused__)),
+            dbref parent __attribute__ ((__unused__)),
+            char const *pattern __attribute__ ((__unused__)),
+            ATTR *atr, void *args)
+{
+  struct lh_args *lh = args;
+  if (lh->first)
+    lh->first = 0;
+  else
+    safe_chr(' ', lh->buff, lh->bp);
+  safe_str(AL_NAME(atr), lh->buff, lh->bp);
+  return 1;
+}
+
+
+FUNCTION(fun_ooref) {
+       safe_dbref(ooref, buff, bp);
+}
+/* ARGSUSED */
+FUNCTION(fun_lattr)
+{
+  dbref thing;
+  char *pattern;
+  struct lh_args lh;
+
+  pattern = strchr(args[0], '/');
+  if (pattern)
+    *pattern++ = '\0';
+  else
+    pattern = (char *) "*";    /* match anything */
+
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  if (!Can_Examine(executor, thing)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  lh.first = 1;
+  lh.buff = buff;
+  lh.bp = bp;
+  (void) atr_iter_get(executor, thing, pattern, 0, lattr_helper, &lh);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_nattr)
+{
+  dbref thing;
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  if (!Can_Examine(executor, thing)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_integer(AttrCount(thing), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_hasattr)
+{
+  dbref thing;
+  ATTR *a;
+
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  if (strchr(called_as, 'P'))
+    a = atr_get(thing, upcasestr(args[1]));
+  else
+    a = atr_get_noparent(thing, upcasestr(args[1]));
+  if (a && Can_Read_Attr(executor, thing, a)) {
+    if (strchr(called_as, 'V'))
+      safe_chr(*AL_STR(a) ? '1' : '0', buff, bp);
+    else
+      safe_chr('1', buff, bp);
+    return;
+  } else if (a || !Can_Examine(executor, thing)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_chr('0', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_get)
+{
+  dbref thing;
+  char *s;
+
+  s = strchr(args[0], '/');
+  if (!s) {
+    safe_str(T("#-1 BAD ARGUMENT FORMAT TO GET"), buff, bp);
+    return;
+  }
+  *s++ = '\0';
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  safe_str(do_get_attrib(executor, thing, s), buff, bp);
+}
+
+/* Functions like get, but uses the standard way of passing arguments */
+/* to a function, and thus doesn't choke on nested functions within.  */
+
+/* ARGSUSED */
+FUNCTION(fun_xget)
+{
+  dbref thing;
+
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  safe_str(do_get_attrib(executor, thing, args[1]), buff, bp);
+}
+
+/* Functions like get, but includes a default response if the
+ * attribute isn't present or is null
+ */
+/* ARGSUSED */
+FUNCTION(fun_default)
+{
+  dbref thing;
+  ATTR *attrib;
+  char *dp;
+  const char *sp;
+  char mstr[BUFFER_LEN];
+  /* find our object and attribute */
+  dp = mstr;
+  sp = args[0];
+  process_expression(mstr, &dp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *dp = '\0';
+  parse_attrib(executor, mstr, &thing, &attrib);
+  if (GoodObject(thing) && attrib && Can_Read_Attr(executor, thing, attrib)) {
+    /* Ok, we've got it */
+    dp = safe_atr_value(attrib);
+    safe_str(dp, buff, bp);
+    free(dp);
+    return;
+  }
+  /* We couldn't get it. Evaluate args[1] and return it */
+  sp = args[1];
+  process_expression(buff, bp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_eval)
+{
+  /* like xget, except pronoun substitution is done */
+
+  dbref thing;
+  char *tbuf;
+  char const *tp;
+  ATTR *a;
+
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  a = atr_get(thing, upcasestr(args[1]));
+  if (a && Can_Read_Attr(executor, thing, a)) {
+    if (!CanEvalAttr(executor, thing, a)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    tp = tbuf = safe_atr_value(a);
+    add_check("fun_eval.attr_value");
+    process_expression(buff, bp, &tp, thing, executor, executor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    mush_free((Malloc_t) tbuf, "fun_eval.attr_value");
+    return;
+  } else if (a || !Can_Examine(executor, thing)) {
+    safe_str(T(e_atrperm), buff, bp);
+    return;
+  }
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_get_eval)
+{
+  /* like eval, except uses obj/attr syntax. 2.x compatibility */
+
+  dbref thing;
+  char *tbuf, *s;
+  char const *tp;
+  ATTR *a;
+
+  s = strchr(args[0], '/');
+  if (!s) {
+    safe_str(T("#-1 BAD ARGUMENT FORMAT TO GET_EVAL"), buff, bp);
+    return;
+  }
+  *s++ = '\0';
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  a = atr_get(thing, upcasestr(s));
+  if (a && Can_Read_Attr(executor, thing, a)) {
+    if (!CanEvalAttr(executor, thing, a)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    tp = tbuf = safe_atr_value(a);
+    add_check("fun_eval.attr_value");
+    process_expression(buff, bp, &tp, thing, executor, executor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    mush_free((Malloc_t) tbuf, "fun_eval.attr_value");
+    return;
+  } else if (a || !Can_Examine(executor, thing)) {
+    safe_str(T(e_atrperm), buff, bp);
+    return;
+  }
+  return;
+}
+
+/* Functions like eval, but includes a default response if the
+ * attribute isn't present or is null
+ */
+/* ARGSUSED */
+FUNCTION(fun_edefault)
+{
+  dbref thing;
+  ATTR *attrib;
+  char *dp, *sbuf;
+  char const *sp;
+  char mstr[BUFFER_LEN];
+  /* find our object and attribute */
+  dp = mstr;
+  sp = args[0];
+  process_expression(mstr, &dp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *dp = '\0';
+  parse_attrib(executor, mstr, &thing, &attrib);
+  if (GoodObject(thing) && attrib && Can_Read_Attr(executor, thing, attrib)) {
+    if (!CanEvalAttr(executor, thing, attrib)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    /* Ok, we've got it */
+    sp = sbuf = safe_atr_value(attrib);
+    add_check("fun_edefault.attr_value");
+    process_expression(buff, bp, &sp, thing, executor, executor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    mush_free((Malloc_t) sbuf, "fun_edefault.attr_value");
+    return;
+  }
+  /* We couldn't get it. Evaluate args[1] and return it */
+  sp = args[1];
+  process_expression(buff, bp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_v)
+{
+  /* handle 0-9, va-vz, n, l, # */
+
+  int c;
+
+  if (args[0][0] && !args[0][1]) {
+    switch (c = args[0][0]) {
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      if (global_eval_context.wenv[c - '0'])
+       safe_str(global_eval_context.wenv[c - '0'], buff, bp);
+      return;
+    case '#':
+      /* enactor dbref */
+      safe_dbref(enactor, buff, bp);
+      return;
+    case '@':
+      /* caller dbref */
+      safe_dbref(caller, buff, bp);
+      return;
+    case '!':
+      /* executor dbref */
+      safe_dbref(executor, buff, bp);
+      return;
+    case 'n':
+    case 'N':
+      /* enactor name */
+      safe_str(Name(enactor), buff, bp);
+      return;
+    case 'l':
+    case 'L':
+      /* giving the location does not violate security,
+       * since the object is the enactor.  */
+      safe_dbref(Location(enactor), buff, bp);
+      return;
+    case 'c':
+    case 'C':
+      safe_str(global_eval_context.ccom, buff, bp);
+      return;
+    }
+  }
+  safe_str(do_get_attrib(executor, executor, args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_flags)
+{
+  dbref thing;
+  char *p;
+  ATTR *a;
+  if (nargs == 0) {
+    safe_str(list_all_flags("FLAG", NULL, executor, 0x1), buff, bp);
+    return;
+  }
+  if ((p = strchr(args[0], '/')))
+    *p++ = '\0';
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  if (p) {
+    /* Attribute flags, you must be able to read the attribute */
+    a = atr_get_noparent(thing, upcasestr(p));
+    if (!a || !Can_Read_Attr(executor, thing, a)) {
+      safe_str("#-1", buff, bp);
+      return;
+    }
+    safe_str(privs_to_letters(attr_privs, AL_FLAGS(a)), buff, bp);
+    if (atr_sub_branch(a))
+      safe_chr('`', buff, bp);
+  } else {
+    /* Object flags, visible to all */
+    safe_str(unparse_flags(thing, executor), buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lflags)
+{
+  dbref thing;
+  char *p;
+  ATTR *a;
+  if (nargs == 0) {
+    safe_str(list_all_flags("FLAG", NULL, executor, 0x2), buff, bp);
+    return;
+  }
+  if ((p = strchr(args[0], '/')))
+    *p++ = '\0';
+  thing = match_thing(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  if (p) {
+    /* Attribute flags, you must be able to read the attribute */
+    a = atr_get_noparent(thing, upcasestr(p));
+    if (!a || !Can_Read_Attr(executor, thing, a)) {
+      safe_str("#-1", buff, bp);
+      return;
+    }
+    safe_str(privs_to_string(attr_privs, AL_FLAGS(a)), buff, bp);
+  } else {
+    /* Object flags, visible to all */
+    safe_str(bits_to_string("FLAG", Flags(thing), executor, thing), buff, bp);
+  }
+}
+
+#ifdef WIN32
+#pragma warning( default : 4761)       /* Re-enable conversion warning */
+#endif
+
+/* ARGSUSED */
+FUNCTION(fun_num)
+{
+  safe_dbref(match_thing(executor, args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_rnum)
+{
+  dbref place = match_thing(executor, args[0]);
+  char *name = args[1];
+  dbref thing;
+  if ((place != NOTHING) &&
+      (Can_Examine(executor, place) || (Location(executor) == place) ||
+       (enactor == place))) {
+    switch (thing = match_result(place, name, NOTYPE, MAT_REMOTE)) {
+    case NOTHING:
+      safe_str(T(e_match), buff, bp);
+      break;
+    case AMBIGUOUS:
+      safe_str(T("#-1 AMBIGUOUS MATCH"), buff, bp);
+      break;
+    default:
+      safe_dbref(thing, buff, bp);
+    }
+  } else
+    safe_str("#-1", buff, bp);
+}
+
+/*
+ * fun_lcon, fun_lexits, fun_con, fun_exit, fun_next were all
+ * re-coded by d'mike, 7/12/91.
+  *
+ * The function behavior was changed by Amberyl, to remove what she saw
+ * as a security problem, since mortals could get the contents of rooms
+ * they didn't control, thus (if they were willing to go through the trouble)
+ * they could build a scanner to locate anything they wanted.
+ *
+ * The functions were completely reorganized and largely unified by Talek,
+ * in September 2001, to finally cure persistent problems with next() and
+ * con() and exit() returning different results/having different permissions
+ * than lcon() and lexits().
+ *
+ * You can get the complete contents of any room you may examine, regardless
+ * of whether or not objects are dark.  You can get the partial contents
+ * (obeying DARK/LIGHT/etc.) of your current location or the enactor.
+ * You CANNOT get the contents of anything else, regardless of whether
+ * or not you have objects in it.
+ *
+ * The same behavior is true for exits.
+ *
+ * The lvcon/lvexits/lvplayers forms work the same way, but never return
+ * a dark object or disconnected player, and are useful for parent rooms.
+ */
+
+/* Valid types for this function:
+ * TYPE_EXIT    (lexits, lvexits, exit, next)
+ * TYPE_THING   (lcon, lvcon, con, next) - really means 'things and players'
+ * TYPE_PLAYER  (lplayers, lvplayers)
+ */
+
+static dbref
+dbwalk(char *buff, char **bp, dbref executor, dbref enactor,
+       int type, dbref loc, dbref after, int skipdark,
+       int start, int count, int *retcount)
+{
+  dbref result;
+  int first;
+  int nthing;
+  dbref thing;
+  int validloc;
+  dbref startdb;
+
+  nthing = 0;
+
+  if (!GoodObject(loc)) {
+    if (buff)
+      safe_str("#-1", buff, bp);
+    return NOTHING;
+  }
+  if (type == TYPE_EXIT) {
+    validloc = IsRoom(loc);
+    startdb = Exits(loc);
+  } else {
+    validloc = !IsExit(loc);
+    startdb = Contents(loc);
+  }
+
+  result = NOTHING;
+  if (GoodObject(loc) && validloc &&
+      (Can_Examine(executor, loc) || (Location(executor) == loc) ||
+       (enactor == loc))) {
+    first = 1;
+    DOLIST_VISIBLE(thing, startdb, executor) {
+      /* Skip if:
+       * - We're not checking this type
+       * - We can't interact with this thing
+       * - We're only listing visual objects, and it's dark
+       * - It's a player, not connected, and skipdark is true
+       *   Funkily, lvcon() shows unconnected players, so we
+       *   use type == TYPE_PLAYER for this check. :-/.
+       */
+      if (!(Typeof(thing) & type) ||
+         !can_interact(thing, executor, INTERACT_SEE) ||
+         (skipdark && Dark(thing) && !Light(thing) && !Light(loc)) ||
+         ((type == TYPE_PLAYER) && skipdark && !Connected(thing)))
+       continue;
+      nthing += 1;
+      if (count < 1 || (nthing >= start && nthing < start + count)) {
+       if (buff) {
+         if (first)
+           first = 0;
+         else
+           safe_chr(' ', buff, bp);
+         safe_dbref(thing, buff, bp);
+       }
+      }
+      if (result == NOTHING) {
+       if (after == NOTHING)
+         result = thing;
+       if (after == thing)
+         after = NOTHING;
+      }
+      if (retcount)
+       *retcount = nthing;
+    }
+  } else if (buff)
+    safe_str("#-1", buff, bp);
+  return result;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_dbwalker)
+{
+  dbref loc = match_thing(executor, args[0]);
+  int start, count;
+  int vis = 0;
+  int type = 0;
+  int result = 0;
+  const char *ptr = called_as;
+  char *buffptr = buff;
+  char **bptr = bp;
+
+  start = count = 0;
+  buffptr = buff;
+  bptr = bp;
+
+  switch (*(ptr++)) {
+  case 'X':
+    if (!is_strict_integer(args[1]) || !is_strict_integer(args[2])) {
+      safe_str(T(e_int), buff, bp);
+      return;
+    }
+    start = parse_integer(args[1]);
+    count = parse_integer(args[2]);
+    if (start < 1 || count < 1) {
+      safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
+      return;
+    }
+    break;
+  case 'N':
+    buffptr = NULL;
+    bptr = NULL;
+    break;
+  }
+
+  if (*ptr == 'V') {
+    vis = 1;
+    ptr++;
+  }
+
+  switch (*ptr) {
+  case 'D':
+    type = TYPE_DIVISION; /* divisions */
+    break;
+  case 'C':                    /* con */
+    type = TYPE_THING | TYPE_PLAYER;
+    break;
+  case 'T':                    /* things */
+    type = TYPE_THING;
+    break;
+  case 'P':                    /* players */
+    type = TYPE_PLAYER;
+    break;
+  case 'E':                    /* exits */
+    type = TYPE_EXIT;
+    break;
+  default:
+    /* This should never be reached ... */
+    type = TYPE_THING | TYPE_PLAYER;
+  }
+
+  dbwalk(buffptr, bptr, executor, enactor, type, loc, NOTHING,
+        vis, start, count, &result);
+
+  if (!buffptr) {
+    safe_integer(result, buff, bp);
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_con)
+{
+  dbref loc = match_thing(executor, args[0]);
+  safe_dbref(dbwalk (NULL, NULL, executor, enactor, TYPE_THING | TYPE_PLAYER, loc, 
+       NOTHING, 0, 0, 0, NULL), buff, bp);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_exit)
+{
+  dbref loc = match_thing(executor, args[0]);
+    safe_dbref(dbwalk (NULL, NULL, executor, enactor, TYPE_EXIT, loc, NOTHING, 0, 0, 0,
+                                   NULL), buff, bp);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_next)
+{
+  dbref it = match_thing(executor, args[0]);
+
+  if (GoodObject(it)) {
+    switch (Typeof(it)) {
+    case TYPE_EXIT:
+      safe_dbref(dbwalk
+                 (NULL, NULL, executor, enactor, TYPE_EXIT, Source(it), it, 0,
+                  0, 0, NULL), buff, bp);
+      break;
+    case TYPE_THING:
+    case TYPE_PLAYER:
+      safe_dbref(dbwalk
+                 (NULL, NULL, executor, enactor, TYPE_THING | TYPE_PLAYER,
+                  Location(it), it, 0, 0, 0, NULL), buff, bp);
+      break;
+    default:
+      safe_str("#-1", buff, bp);
+      break;
+    }
+  } else
+    safe_str("#-1", buff, bp);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_entrances)
+{
+  /* All args are optional.
+   * The first argument is the dbref to check (default = this room)
+   * The second argument to this function is a set of characters:
+   * (a)ll (default), (e)xits, (t)hings, (p)layers, (r)ooms
+   * The third and fourth args limit the range of dbrefs (default=0,db_top)
+   */
+  dbref where = Location(executor);
+  dbref low = 0;
+  dbref high = db_top - 1;
+  dbref counter;
+  dbref entrance;
+  int found;
+  int exd, td, pd, rd;         /* what we're looking for */
+  char *p;
+
+  if (!command_check_byname(executor, "@entrances")) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (nargs > 0)
+    where = match_result(executor, args[0], NOTYPE, MAT_EVERYTHING);
+  if (!GoodObject(where)) {
+    safe_str(T("#-1 INVALID LOCATION"), buff, bp);
+    return;
+  }
+  exd = td = pd = rd = 0;
+  if (nargs > 1) {
+    if (!args[1] || !*args[1]) {
+      safe_str(T("#-1 INVALID SECOND ARGUMENT"), buff, bp);
+      return;
+    }
+    p = args[1];
+    while (*p) {
+      switch (*p) {
+      case 'a':
+      case 'A':
+       exd = td = pd = rd = 1;
+       break;
+      case 'e':
+      case 'E':
+       exd = 1;
+       break;
+      case 't':
+      case 'T':
+       td = 1;
+       break;
+      case 'p':
+      case 'P':
+       pd = 1;
+       break;
+      case 'r':
+      case 'R':
+       rd = 1;
+       break;
+      default:
+       safe_str(T("#-1 INVALID SECOND ARGUMENT"), buff, bp);
+       return;
+      }
+      p++;
+    }
+  }
+  if (!exd && !td && !pd && !rd) {
+    exd = td = pd = rd = 1;
+  }
+  if (nargs > 2) {
+    if (is_integer(args[2])) {
+      low = parse_integer(args[2]);
+    } else if (is_dbref(args[2])) {
+      low = parse_dbref(args[2]);
+    } else {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+  }
+  if (nargs > 3) {
+    if (is_integer(args[3])) {
+      high = parse_integer(args[3]);
+    } else if (is_dbref(args[3])) {
+      high = parse_dbref(args[3]);
+    } else {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+  }
+  if (!GoodObject(low))
+    low = 0;
+  if (!GoodObject(high))
+    high = db_top - 1;
+
+  if (!controls(executor, where) && !CanSearch(executor, where)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if (!payfor(executor, FIND_COST)) {
+    notify_format(executor, T("You don't have %d %s to do that."),
+                 FIND_COST, ((FIND_COST == 1) ? MONEY : MONIES));
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  /* Ok, do the real work */
+  found = 0;
+  for (counter = low; counter <= high; counter++) {
+    if (controls(executor, where) || controls(executor, counter)) {
+      if ((exd && IsExit(counter)) ||
+         (td && IsThing(counter)) ||
+         (pd && IsPlayer(counter)) || (rd && IsRoom(counter))) {
+       if (Mobile(counter))
+         entrance = Home(counter);
+       else
+         entrance = Location(counter);
+       if (entrance == where) {
+         if (!found)
+           found = 1;
+         else
+           safe_chr(' ', buff, bp);
+         safe_dbref(counter, buff, bp);
+       }
+      }
+    }
+  }
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_nearby)
+{
+  dbref obj1 = match_thing(executor, args[0]);
+  dbref obj2 = match_thing(executor, args[1]);
+
+  if (!controls(executor, obj1) && !controls(executor, obj2)
+      && !(CanSee(executor, obj1) && CanSee(executor, obj2))
+      && !nearby(executor, obj1) && !nearby(executor, obj2)) {
+    safe_str(T("#-1 NO OBJECTS CONTROLLED"), buff, bp);
+    return;
+  }
+  if (!GoodObject(obj1) || !GoodObject(obj2)) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  safe_chr(nearby(obj1, obj2) ? '1' : '0', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_controls)
+{
+  dbref it = match_thing(executor, args[0]);
+  dbref thing = match_thing(executor, args[1]);
+
+  if (!GoodObject(it))
+    safe_str(T("#-1 ARG1 NOT FOUND"), buff, bp);
+  else if (!GoodObject(thing))
+    safe_str(T("#-1 ARG2 NOT FOUND"), buff, bp);
+  else if (!(controls(executor, it) || controls(executor, thing)
+            || CanSee(executor, it) || CanSee(executor, thing)))
+    safe_str(T(e_perm), buff, bp);
+  else
+    safe_chr(controls(it, thing) ? '1' : '0', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_visible)
+{
+  /* check to see if we have an object-attribute pair. If we don't,
+   * then we want to know about the whole object; otherwise, we're
+   * just interested in a single attribute.
+   * If we encounter an error, we return 0 rather than an error
+   * code, since if it doesn't exist, it obviously can't see 
+   * anything or be seen.
+   */
+
+  dbref it = match_thing(executor, args[0]);
+  dbref thing;
+  char *name;
+  ATTR *a;
+
+  if (!GoodObject(it)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  if ((name = strchr(args[1], '/')))
+    *name++ = '\0';
+  thing = match_thing(executor, args[1]);
+  if (!GoodObject(thing)) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+  if (name) {
+    a = atr_get(thing, upcasestr(name));
+    safe_chr((a && Can_Read_Attr(it, thing, a)) ? '1' : '0', buff, bp);
+  } else {
+    safe_boolean(Can_Examine(it, thing), buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_type)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!GoodObject(it)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  switch (Typeof(it)) {
+  case TYPE_PLAYER:
+    safe_str("PLAYER", buff, bp);
+    break;
+  case TYPE_THING:
+    safe_str("THING", buff, bp);
+    break;
+  case TYPE_EXIT:
+    safe_str("EXIT", buff, bp);
+    break;
+  case TYPE_ROOM:
+    safe_str("ROOM", buff, bp);
+    break;
+  case TYPE_DIVISION:
+    safe_str("DIVISION", buff, bp);
+    break;
+  case TYPE_GARBAGE:
+    safe_str("GARBAGE", buff, bp);
+    break;
+  default:
+    safe_str("WEIRD OBJECT", buff, bp);
+    do_rawlog(LT_ERR, T("WARNING: Weird object #%d (type %d)\n"), it,
+             Typeof(it));
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_hasflag)
+{
+  dbref thing;
+  ATTR *attrib;
+  int f;
+
+  if (strchr(args[0], '/')) {
+    parse_attrib(executor, args[0], &thing, &attrib);
+    if (!attrib)
+      safe_str("#-1", buff, bp);
+    else if ((f = string_to_atrflag(executor, args[1])) < 0)
+      safe_str("#-1", buff, bp);
+    else
+      safe_boolean(AL_FLAGS(attrib) & f, buff, bp);
+  } else {
+    thing = match_thing(executor, args[0]);
+    if (!GoodObject(thing))
+      safe_str(T(e_notvis), buff, bp);
+    else
+      safe_boolean(sees_flag(executor, thing, args[1]), buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_hastype)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!GoodObject(it)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  switch (*args[1]) {
+  case 'r':
+  case 'R':
+    safe_boolean(IsRoom(it), buff, bp);
+    break;
+  case 'e':
+  case 'E':
+    safe_boolean(IsExit(it), buff, bp);
+    break;
+  case 'p':
+  case 'P':
+    safe_boolean(IsPlayer(it), buff, bp);
+    break;
+  case 't':
+  case 'T':
+    safe_boolean(IsThing(it), buff, bp);
+    break;
+  case 'd':
+  case 'D':
+    safe_boolean(IsDivision(it), buff, bp);
+    break;
+  case 'g':
+  case 'G':
+    safe_boolean(IsGarbage(it), buff, bp);
+    break;
+  default:
+    safe_str(T("#-1 NO SUCH TYPE"), buff, bp);
+    break;
+  };
+}
+
+/* ARGSUSED */
+FUNCTION(fun_orflags)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!strcmp(called_as, "ORPOWERS"))
+    safe_boolean(flaglist_check("POWER", executor, it, args[1], 0), buff, bp);
+  else
+    safe_boolean(flaglist_check("FLAG", executor, it, args[1], 0), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_andflags)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!strcmp(called_as, "ANDPOWERS"))
+    safe_boolean(flaglist_check("POWER", executor, it, args[1], 1), buff, bp);
+  else
+    safe_boolean(flaglist_check("FLAG", executor, it, args[1], 1), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_orlflags)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!strcmp(called_as, "ORLPOWERS"))
+    safe_boolean(flaglist_check_long("POWER", executor, it, args[1], 0), buff,
+                bp);
+  else
+    safe_boolean(flaglist_check_long("FLAG", executor, it, args[1], 0), buff,
+                bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_andlflags)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!strcmp(called_as, "ANDLPOWERS"))
+    safe_boolean(flaglist_check_long("POWER", executor, it, args[1], 1), buff,
+                bp);
+  else
+    safe_boolean(flaglist_check_long("FLAG", executor, it, args[1], 1), buff,
+                bp);
+}
+
+static lock_type
+get_locktype(str)
+    char *str;
+{
+  /* figure out a lock type */
+
+  if (!str || !*str)
+    return Basic_Lock;
+  return upcasestr(str);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lock)
+{
+  dbref it;
+  char *p;
+  lock_type ltype;
+
+  if ((p = strchr(args[0], '/')))
+    *p++ = '\0';
+
+  it = match_thing(executor, args[0]);
+  ltype = get_locktype(p);
+
+  if (nargs == 2) {
+    if (!command_check_byname(executor, "@lock") || fun->flags & FN_NOSIDEFX) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (FUNCTION_SIDE_EFFECTS)
+      do_lock(executor, args[0], args[1], ltype);
+    else {
+      safe_str(T(e_disabled), buff, bp);
+      return;
+    }
+  }
+  if (GoodObject(it) && (ltype != NULL)
+      && Can_Read_Lock(executor, it, ltype)) {
+    safe_str(unparse_boolexp(executor, getlock(it, ltype), UB_DBREF), buff, bp);
+    return;
+  }
+  safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_elock)
+{
+  char *p;
+  lock_type ltype;
+  dbref it;
+  dbref victim = match_thing(executor, args[1]);
+
+  p = strchr(args[0], '/');
+  if (p)
+    *p++ = '\0';
+
+  it = match_thing(executor, args[0]);
+  ltype = get_locktype(p);
+
+  if (!GoodObject(it) || (ltype == NULL) || !Can_Read_Lock(executor, it, ltype)) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  if (Can_Locate(executor, victim))
+    safe_boolean(eval_lock(victim, it, ltype), buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_findable)
+{
+  dbref obj = match_thing(executor, args[0]);
+  dbref victim = match_thing(executor, args[1]);
+
+  if (!GoodObject(obj))
+    safe_str(T("#-1 ARG1 NOT FOUND"), buff, bp);
+  else if (!GoodObject(victim))
+    safe_str(T("#-1 ARG2 NOT FOUND"), buff, bp);
+  else if (!CanSee(executor, obj) && !controls(executor, obj) &&
+          !controls(executor, victim))
+    safe_str(T(e_perm), buff, bp);
+  else
+    safe_boolean(Can_Locate(obj, victim), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_loc)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (GoodObject(it) && Can_Locate(executor, it))
+    safe_dbref(Location(it), buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+FUNCTION(fun_loctree) {
+  dbref it = match_thing(executor, args[0]);
+  dbref loc;
+
+  if (GoodObject(it) && Can_Locate(executor, it)) {
+        for(loc = it; GoodObject(Location(loc)) ; loc = Location(loc)) {
+         if(loc != it)
+           safe_chr(' ', buff, bp);
+         safe_dbref(Location(loc), buff, bp);
+       }
+  }
+   else safe_str("#-1", buff, bp);
+       
+}
+
+/* ARGSUSED */
+FUNCTION(fun_objid)
+{
+  dbref it = match_thing(executor, args[0]);
+
+  if (GoodObject(it)) {
+    safe_dbref(it, buff, bp);
+    safe_chr(':', buff, bp);
+    safe_integer(CreTime(it), buff, bp);
+  } else
+    safe_str(T(e_notvis), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ctime)
+{
+  dbref it = match_thing(executor, args[0]);
+
+  if (GoodObject(it))
+    safe_str(show_time(CreTime(it), 0), buff, bp);
+  else
+    safe_str(T(e_notvis), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_mtime)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!GoodObject(it))
+    safe_str(T(e_notvis), buff, bp);
+  else if (!Can_Examine(executor, it) || IsPlayer(it))
+    safe_str(T(e_perm), buff, bp);
+  else
+    safe_str(show_time(ModTime(it), 0), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_where)
+{
+  /* finds the "real" location of an object */
+
+  dbref it = match_thing(executor, args[0]);
+  if (GoodObject(it) && Can_Locate(executor, it))
+    safe_dbref(where_is(it), buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_room)
+{
+  dbref it = match_thing(executor, args[0]);
+
+  if (!GoodObject(it))
+    safe_str(T(e_notvis), buff, bp);
+  else if (!Can_Locate(executor, it))
+    safe_str(T(e_perm), buff, bp);
+  else {
+    dbref room = absolute_room(it);
+    if (!GoodObject(room)) {
+      safe_strl("#-1", 3, buff, bp);
+      return;
+    }
+    safe_dbref(room, buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_rloc)
+{
+  int i;
+  int deep = parse_integer(args[1]);
+  dbref it = match_thing(executor, args[0]);
+
+  if (deep > 20)
+    deep = 20;
+  if (deep < 0)
+    deep = 0;
+
+  if (!GoodObject(it))
+    safe_str(T(e_notvis), buff, bp);
+  else if (!Can_Locate(executor, it))
+    safe_str(T(e_perm), buff, bp);
+  else {
+    for (i = 0; i < deep; i++) {
+      if (!GoodObject(it) || IsRoom(it))
+       break;
+      it = Location(it);
+    }
+    safe_dbref(it, buff, bp);
+    return;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_zone)
+{
+  dbref it;
+
+  if (nargs == 2) {
+    if (!command_check_byname(executor, "@chzone") || fun->flags & FN_NOSIDEFX) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (FUNCTION_SIDE_EFFECTS)
+      (void) do_chzone(executor, args[0], args[1], 1);
+    else {
+      safe_str(T(e_disabled), buff, bp);
+      return;
+    }
+  }
+  it = match_thing(executor, args[0]);
+  if (!GoodObject(it))
+    safe_str(T(e_notvis), buff, bp);
+  else if (!Can_Examine(executor, it))
+    safe_str(T(e_perm), buff, bp);
+  else
+    safe_dbref(Zone(it), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_parent)
+{
+  dbref it;
+
+  if (nargs == 2) {
+    if (!command_check_byname(executor, "@parent") || fun->flags & FN_NOSIDEFX) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (FUNCTION_SIDE_EFFECTS)
+      do_parent(executor, args[0], args[1]);
+    else {
+      safe_str(T(e_disabled), buff, bp);
+      return;
+    }
+  }
+  it = match_thing(executor, args[0]);
+  if (!GoodObject(it))
+    safe_str(T(e_notvis), buff, bp);
+  else if (!Can_Examine(executor, it))
+    safe_str(T(e_perm), buff, bp);
+  else
+    safe_dbref(Parent(it), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lparent)
+{
+  dbref it;
+  dbref par;
+
+  it = match_thing(executor, args[0]);
+  if (!GoodObject(it)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  safe_dbref(it, buff, bp);
+  par = Parent(it);
+  while (GoodObject(par) && Can_Examine(executor, it)) {
+    if (safe_chr(' ', buff, bp))
+      break;
+    safe_dbref(par, buff, bp);
+    it = par;
+    par = Parent(par);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_home)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (!GoodObject(it))
+    safe_str(T(e_notvis), buff, bp);
+  else if (!Can_Examine(executor, it))
+    safe_str(T(e_perm), buff, bp);
+  else if (IsExit(it))
+    safe_dbref(Source(it), buff, bp);
+  else if (IsRoom(it))
+    safe_dbref(Location(it), buff, bp);
+  else
+    safe_dbref(Home(it), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_money)
+{
+  /* Are we asking about something's money? */
+  dbref it = match_result(executor, args[0], NOTYPE, MAT_EVERYTHING);
+
+  if (!GoodObject(it)) {
+    /* Well, are we asking for the plural/singular for some amount? */
+    if (is_integer(args[0])) {
+      int a = parse_integer(args[0]);
+      if (abs(a) == 1)
+       safe_str(MONEY, buff, bp);
+      else
+       safe_str(MONIES, buff, bp);
+    } else {
+      /* Guess we're just making a typo or something. */
+      safe_str("#-1", buff, bp);
+    }
+    return;
+  } else if (!GoodObject(it)) {
+    /* Catch ambiguous matches */
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  /* If the thing in question has unlimited money, respond with the
+   * max money possible. We don't use the NoPay macro, though, because
+   * we want to return the amount of money stored in an object, even
+   * if its owner is no_pay. Softcode can check money(owner(XX)) if 
+   * they want to allow objects to pay like their owners.
+   */
+  if (Admin(it) || (NoPay(it)))
+    safe_integer(MAX_PENNIES, buff, bp);
+  else
+    safe_integer(Pennies(it), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_owner)
+{
+  dbref thing;
+  ATTR *attrib;
+
+  if (strchr(args[0], '/')) {
+    parse_attrib(executor, args[0], &thing, &attrib);
+    if (!GoodObject(thing) || !attrib
+       || !Can_Read_Attr(executor, thing, attrib))
+      safe_str("#-1", buff, bp);
+    else
+      safe_dbref(attrib->creator, buff, bp);
+  } else {
+    thing = match_thing(executor, args[0]);
+    if (!GoodObject(thing))
+      safe_str(T(e_notvis), buff, bp);
+    else
+      safe_dbref(Owner(thing), buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_name)
+{
+  dbref it;
+
+  /* Special case for backward compatibility */
+  if (nargs == 0)
+    return;
+  if (nargs == 2) {
+    if (!command_check_byname(executor, "@name") || fun->flags & FN_NOSIDEFX) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (FUNCTION_SIDE_EFFECTS)
+      do_name(executor, args[0], args[1]);
+    else
+      safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  it = match_thing(executor, args[0]);
+  if (GoodObject(it))
+         /* Quick Fix for most code dealing with know system */
+         /* All code.. when the enactor is IC & so is the person they're
+          * trying to gt the name of, they will get their 'know name' variant.
+          * Only exceptions are A) Code called from the master room
+          *                     B) Code called from a division object
+          *                     C) The enactor has see_all over the other player & are over the empire levels
+          **/
+    safe_str(
+                   shortname(it)
+                   , buff, bp);
+  else
+    safe_str(T(e_notvis), buff, bp);
+}
+
+#ifdef KNOW_SYS
+FUNCTION(fun_checkknow) {
+       dbref who;
+       dbref what;
+
+       who = match_thing(executor, args[0]);
+       what = match_thing(executor, args[1]);
+
+       if(!IsPlayer(who) || !IsPlayer(what)) {
+               safe_str((e_notvis), buff, bp);
+               return;
+       }
+
+       safe_chr(check_know(who,what) ? '1' : '0' , buff, bp); 
+}
+#endif
+
+/* ARGSUSED */
+FUNCTION(fun_fullname)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (GoodObject(it))
+    safe_str(Name(it), buff, bp);
+  else
+    safe_str(T(e_notvis), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_accname)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (GoodObject(it))
+    safe_str(accented_name(it), buff, bp);
+  else
+    safe_str(T(e_notvis), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_iname)
+{
+  dbref it = match_thing(executor, args[0]);
+  char tbuf1[BUFFER_LEN];
+
+  if (GoodObject(it)) {
+    /* You must either be see_all, control it, or be inside it */
+    if (!(controls(executor, it) || CanSee(executor, it) ||
+         (Location(executor) == it))) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (nameformat(executor, it, tbuf1))
+      safe_str(tbuf1, buff, bp);
+    else if (IsExit(it))
+      safe_str(shortname(it), buff, bp);
+    else
+      safe_str(accented_name(it), buff, bp);
+  } else
+    safe_str(T(e_notvis), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_pmatch)
+{
+  dbref target;
+
+  if (*args[0] == '*')
+    target = lookup_player(args[0] + 1);
+  else if (*args[0] == NUMBER_TOKEN) {
+    target = parse_objid(args[0]);
+    if (!(GoodObject(target) && IsPlayer(target))) {
+      notify(executor, T("No match."));
+      safe_str("#-1", buff, bp);
+      return;
+    } else {
+      safe_dbref(target, buff, bp);
+      return;
+    }
+  } else
+    target = lookup_player(args[0]);
+  if (target == NOTHING) {
+    if (*args[0] == '*')
+      target = visible_short_page(executor, args[0] + 1);
+    else
+      target = visible_short_page(executor, args[0]);
+  }
+  switch (target) {
+  case NOTHING:
+    notify(executor, T("No match."));
+    safe_str("#-1", buff, bp);
+    break;
+  case AMBIGUOUS:
+    notify(executor, T("I'm not sure who you mean."));
+    safe_str("#-2", buff, bp);
+    break;
+  default:
+    safe_dbref(target, buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_locate)
+{
+  dbref looker;
+  int pref_type;
+  dbref item, loc;
+  char *p;
+  int keys = 0;
+  int ambig_ok = 0;
+  int force_type = 0;
+  long match_flags = 0;
+
+  /* find out what we're matching in relation to */
+  looker = match_thing(executor, args[0]);
+  if (!GoodObject(looker)) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  if (!CanSee(executor, looker) && !controls(executor, looker)) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+
+  /* find out our preferred match type and flags */
+  pref_type = NOTYPE;
+  for (p = args[2]; *p; p++) {
+    switch (*p) {
+    case 'N':
+      pref_type = NOTYPE;
+      break;
+    case 'E':
+      pref_type = TYPE_EXIT;
+      break;
+    case 'P':
+      pref_type = TYPE_PLAYER;
+      break;
+    case 'R':
+      pref_type = TYPE_ROOM;
+      break;
+    case 'T':
+      pref_type = TYPE_THING;
+      break;
+    case 'D':
+      pref_type = TYPE_DIVISION;
+      break;
+    case 'L':
+      keys = 1;
+      break;
+    case 'F':
+      force_type = 1;
+      break;
+    case '*':
+      match_flags |= MAT_EVERYTHING;
+      break;
+    case 'a':
+      match_flags |= MAT_ABSOLUTE;
+      break;
+    case 'c':
+      match_flags |= MAT_CARRIED_EXIT;
+      break;
+    case 'e':
+      match_flags |= MAT_EXIT;
+      break;
+    case 'h':
+      match_flags |= MAT_HERE;
+      break;
+    case 'i':
+      match_flags |= MAT_POSSESSION;
+      break;
+    case 'l':
+      match_flags |= MAT_CONTAINER;
+      break;
+    case 'm':
+      match_flags |= MAT_ME;
+      break;
+    case 'n':
+      match_flags |= MAT_NEIGHBOR;
+      break;
+    case 'p':
+      match_flags |= MAT_PLAYER;
+      break;
+    case 'z':
+      match_flags |= MAT_ENGLISH;
+      break;
+    case 'X':
+      ambig_ok = 1;            /* okay to pick last match */
+      break;
+    default:
+      notify_format(executor, T("I don't understand switch '%c'."), *p);
+      break;
+    }
+  }
+
+  if (keys)
+    match_flags = MAT_CHECK_KEYS;
+
+  /* report the results */
+  if (!ambig_ok)
+    item = match_result(looker, args[1], pref_type, match_flags);
+  else
+    item = last_match_result(looker, args[1], pref_type, match_flags);
+
+  if (!GoodObject(item)) {
+    safe_dbref(item, buff, bp);
+    return;
+  }
+
+  if (force_type && pref_type != NOTYPE && !(Typeof(item) == pref_type)) {
+    safe_dbref(NOTHING, buff, bp);
+    return;
+  }
+
+  /* To locate it, you must either be able to examine its location
+   * or be able to see the item.
+   */
+  loc = Location(item);
+  if (GoodObject(loc)) {
+    if (Can_Examine(executor, loc))
+      safe_dbref(item, buff, bp);
+    else if (can_interact(item, executor, INTERACT_SEE)
+            && (!DarkLegal(item) || Light(loc) || Light(item)))
+      safe_dbref(item, buff, bp);
+    else
+      safe_dbref(NOTHING, buff, bp);
+  } else {
+    if (can_interact(item, executor, INTERACT_SEE)
+       && (See_All(executor) || !DarkLegal(item) || Light(item)))
+      safe_dbref(item, buff, bp);
+    else
+      safe_dbref(NOTHING, buff, bp);
+  }
+  return;
+}
+
+
+/* --------------------------------------------------------------------------
+ * Creation functions: CREATE, PCREATE, OPEN, DIG
+ */
+
+/* ARGSUSED */
+FUNCTION(fun_create)
+{
+  int cost;
+
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+
+  if (!command_check_byname(executor, "@create") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if (nargs == 2)
+    cost = parse_integer(args[1]);
+  else
+    cost = OBJECT_COST;
+  safe_dbref(do_create(executor, args[0], cost), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_pcreate)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@pcreate") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_dbref(do_pcreate(executor, args[0], args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_open)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@open") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_dbref(do_real_open(executor, args[0], args[1], NOTHING), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_dig)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@dig") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_dbref(do_dig(executor, args[0], args, 0), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_clone)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@clone") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_dbref(do_clone(executor, args[0], NULL, 0), buff, bp);
+}
+
+
+/* --------------------------------------------------------------------------
+ * Attribute functions: LINK, SET
+ */
+
+/* ARGSUSED */
+FUNCTION(fun_link)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@link") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  do_link(executor, args[0], args[1], args[2] && args[2] != '\0' ? parse_boolean(args[2]) : 0 );
+}
+
+/* ARGSUSED */
+FUNCTION(fun_set)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@set") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  do_set(executor, args[0], args[1]);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_wipe)
+{
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@wipe") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  do_wipe(executor, args[0]);
+}
+
+
+/* --------------------------------------------------------------------------
+ * Misc functions: TEL
+ */
+
+/* ARGSUSED */
+FUNCTION(fun_tel)
+{
+  int silent = 0;
+  int inside = 0;
+  if (!FUNCTION_SIDE_EFFECTS) {
+    safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+  if (!command_check_byname(executor, "@tel") || fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if (nargs > 2)
+    silent = parse_boolean(args[2]);
+  if (nargs > 3)
+    silent = parse_boolean(args[3]);
+  do_teleport(executor, args[0], args[1], silent, inside);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_isdbref)
+{
+  safe_boolean(parse_dbref(args[0]) != NOTHING, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_grep)
+{
+  char *tp;
+
+  dbref it = match_thing(executor, args[0]);
+  if (!GoodObject(it)) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  /* make sure there's an attribute and a pattern */
+  if (!*args[1]) {
+    safe_str(T("#-1 NO SUCH ATTRIBUTE"), buff, bp);
+    return;
+  }
+  if (!*args[2]) {
+    safe_str(T("#-1 INVALID GREP PATTERN"), buff, bp);
+    return;
+  }
+  tp = grep_util(executor, it, args[1], args[2], arglens[2],
+                strcmp(called_as, "GREP"));
+  add_check("fun_grep.attr_list");
+  safe_str(tp, buff, bp);
+  mush_free((Malloc_t) tp, "fun_grep.attr_list");
+}
+
+/* Get database size statistics */
+/* ARGSUSED */
+FUNCTION(fun_lstats)
+{
+  dbref who;
+  struct db_stat_info *si;
+
+  if ((!args[0]) || !*args[0] || !strcasecmp(args[0], "all")) {
+    who = ANY_OWNER;
+  } else if (!strcasecmp(args[0], "me")) {
+    who = executor;
+  } else {
+    who = lookup_player(args[0]);
+    if (who == NOTHING) {
+      safe_str(T(e_notvis), buff, bp);
+      return;
+    }
+  }
+  if (!CanSearch(executor, who)) {
+    if (who != ANY_OWNER && !controls(executor, who)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+  }
+  si = get_stats(who);
+  if (who != ANY_OWNER) {
+    safe_format(buff, bp, "%d %d %d %d %d %d", si->total - si->garbage, si->rooms,
+               si->exits, si->things, si->players, si->divisions);
+  } else {
+    safe_format(buff, bp, "%d %d %d %d %d %d %d", si->total, si->rooms, si->exits,
+               si->things, si->players, si->divisions, si->garbage);
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_atrlock)
+{
+  dbref thing;
+  char *p;
+  ATTR *ptr;
+  int status;
+
+  if (nargs == 1)
+    status = 0;
+  else
+    status = 1;
+
+  if (status == 1) {
+    if (FUNCTION_SIDE_EFFECTS) {
+      if (!command_check_byname(executor, "@atrlock")
+         || fun->flags & FN_NOSIDEFX) {
+       safe_str(T(e_perm), buff, bp);
+       return;
+      }
+      do_atrlock(executor, args[0], args[1], 0);
+      return;
+    } else
+      safe_str(T(e_disabled), buff, bp);
+    return;
+  }
+
+  if (!args[0] || !*args[0]) {
+    safe_str(T("#-1 ARGUMENT MUST BE OBJ/ATTR"), buff, bp);
+    return;
+  }
+  if (!(p = strchr(args[0], '/')) || !(*(p + 1))) {
+    safe_str(T("#-1 ARGUMENT MUST BE OBJ/ATTR"), buff, bp);
+    return;
+  }
+  *p++ = '\0';
+
+  if ((thing =
+       noisy_match_result(executor, args[0], NOTYPE,
+                         MAT_EVERYTHING)) == NOTHING) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+
+  ptr = atr_get_noparent(thing, strupper(p));
+  if (ptr && Can_Read_Attr(executor, thing, ptr))
+    safe_boolean(AF_Locked(ptr), buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_followers)
+{
+  dbref thing;
+  ATTR *a;
+  char *s;
+  char *res;
+
+  thing = match_controlled(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T("#-1 INVALID OBJECT"), buff, bp);
+    return;
+  }
+  a = atr_get_noparent(thing, "FOLLOWERS");
+  if (!a)
+    return;
+  s = atr_value(a);
+  res = trim_space_sep(s, ' ');
+  safe_str(res, buff, bp);
+  return;;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_following)
+{
+  dbref thing;
+  ATTR *a;
+  char *s;
+  char *res;
+
+  thing = match_controlled(executor, args[0]);
+  if (!GoodObject(thing)) {
+    safe_str(T("#-1 INVALID OBJECT"), buff, bp);
+    return;
+  }
+  a = atr_get_noparent(thing, "FOLLOWING");
+  if (!a)
+    return;
+  s = atr_value(a);
+  res = trim_space_sep(s, ' ');
+  safe_str(res, buff, bp);
+  return;;
+}
diff --git a/src/fundiv.c b/src/fundiv.c
new file mode 100644 (file)
index 0000000..4471e4f
--- /dev/null
@@ -0,0 +1,508 @@
+/* fundiv.c - 3/19/02 RLB created to give division functions their own place
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#ifdef I_STRING
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include "conf.h"
+#include "externs.h"
+#include "division.h"
+#include "dbdefs.h"
+#include "flags.h"
+
+#include "match.h"
+#include "parse.h"
+#include "boolexp.h"
+#include "command.h"
+#include "game.h"
+#include "mushdb.h"
+#include "privtab.h"
+#ifdef MEM_CHECK
+#include "memcheck.h"
+#endif
+#include "lock.h"
+#include "log.h"
+#include "attrib.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#pragma warning( disable : 4761)        /* NJG: disable warning re conversion */
+#endif
+
+#ifdef CHAT_SYSTEM
+#include "extchat.h"
+extern CHAN *channels;
+#endif
+
+/* From division.c */
+extern int powergroup_has(dbref, POWERGROUP *);
+
+extern POWERSPACE ps_tab;
+
+FUNCTION(fun_level)
+{
+  dbref obj;
+  int l;
+
+  obj = match_thing(executor, args[0]);
+
+  if (obj == NOTHING) {
+    safe_str("#-1 BAD OBJECT", buff, bp);
+    return;
+  }
+
+  if (args[1]) {
+    l = division_level(executor, obj, atoi(args[1]));
+    if (l > 0)
+      safe_format(buff, bp, "%d", LEVEL(obj));
+    else
+      safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  }
+  safe_integer(LEVEL(obj), buff, bp);
+}
+
+
+/* Side-Effect, returns nothing */
+FUNCTION(fun_empower)
+{
+  dbref target;
+
+  target = match_thing(executor, args[0]);
+
+  if (target == NOTHING)
+    return;
+
+  division_empower(executor, target, args[1]);
+}
+
+FUNCTION(fun_divscope)
+{
+  dbref obj1, obj2;
+/* check if arg[1] is in divscope of arg[0] */
+  obj1 = match_thing(executor, args[0]);
+  if (obj1 == NOTHING) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  if (nargs > 1) {
+    obj2 = match_thing(executor, args[1]);
+    if (obj2 == NOTHING) {
+      safe_str("#-1", buff, bp);
+      return;
+    }
+    safe_format(buff, bp, "%d", div_inscope(obj1, obj2));
+  } else
+    safe_format(buff, bp, "%d", div_inscope(executor, obj1));
+}
+
+FUNCTION(fun_division)
+{
+  dbref obj1;
+
+  obj1 = match_thing(executor, args[0]);
+  if (obj1 == NOTHING) {
+    safe_str(unparse_dbref(obj1), buff, bp);
+  } else {
+    if (!IsDivision(SDIV(obj1).object))
+      SDIV(obj1).object = -1;
+    safe_str(unparse_dbref(SDIV(obj1).object), buff, bp);
+  }
+}
+
+FUNCTION(fun_hasdivpower)
+{
+  dbref obj1;
+  POWER *power;
+  int level, levc;
+
+  obj1 = match_thing(executor, args[0]);
+
+  if (obj1 == NOTHING) {
+    safe_str("#-1 NO SUCH OBJECT", buff, bp);
+    return;
+  }
+  if (!Can_Examine(executor, obj1)) {
+    safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  }
+
+  power = find_power(args[1]);
+  if (!power) {
+    safe_str("#-1 NO SUCH POWER", buff, bp);
+    return;
+  }
+  level = God(obj1) ? YES : check_power_yescode(DPBITS(obj1), power);
+  if (nargs > 2) {
+    levc = yescode_i(args[2]);
+    if (level == levc)
+      safe_chr('1', buff, bp);
+    else
+      safe_chr('0', buff, bp);
+    return;
+  }
+  if (!strcasecmp(called_as, "haspower"))       /* for compatibility */
+    safe_chr(level > NO ? '1' : '0', buff, bp);
+  else
+    safe_format(buff, bp, "%d", level);
+
+}
+
+/* Check if a powergroup has a power */
+/* syntax: pghaspower(powergroup,auto/max,power[,level]) */
+FUNCTION(fun_pghaspower)
+{
+  POWERGROUP *pgrp;
+  POWER *power;
+  int level, levc;
+  int auto_chk = 0;
+
+  if (!strcasecmp(args[1], "auto"))
+    auto_chk = 1;
+  else if (!!strcasecmp(args[1], "max")) {
+    safe_str("#-1 POWERFIELD NOT SPECIFIED", buff, bp);
+    return;
+  }
+
+  pgrp = powergroup_find(args[0]);
+
+  if (!pgrp) {
+    safe_str("#-1 NO SUCH POWERGROUP", buff, bp);
+    return;
+  }
+
+  power = find_power(args[2]);
+  if (!power) {
+    safe_str("#-1 NO SUCH POWER", buff, bp);
+    return;
+  }
+  level =
+      check_power_yescode(auto_chk ? pgrp->auto_powers : pgrp->max_powers,
+                          power);
+  if (nargs > 3) {
+    levc = yescode_i(args[3]);
+    if (level == levc)
+      safe_chr('1', buff, bp);
+    else
+      safe_chr('0', buff, bp);
+    return;
+  }
+
+  safe_format(buff, bp, "%d", level);
+}
+
+/* Syntax: pgpowers(powergroup,auto/max) */
+FUNCTION(fun_pgpowers)
+{
+  POWERGROUP *pgrp;
+  POWER *power;
+  char pname[BUFFER_LEN];
+  char *tbp;
+  int yescode;
+  int auto_chk = 0;
+
+  if (!strcasecmp(args[1], "auto"))
+    auto_chk = 1;
+  else if (!!strcasecmp(args[1], "max")) {
+    safe_str("#-1 POWERFIELD NOT SPECIFIED", buff, bp);
+    return;
+  }
+
+  pgrp = powergroup_find(args[0]);
+
+  if (!pgrp) {
+    safe_str("#-1 NO SUCH POWERGROUP", buff, bp);
+    return;
+  }
+
+
+  tbp = *bp;
+  for (power = ptab_firstentry_new(ps_tab.powers, pname); power;
+       power = ptab_nextentry_new(ps_tab.powers, pname))
+    if (!strcmp(pname, power->name)) {
+      yescode =
+          check_power_yescode((auto_chk ? pgrp->auto_powers : pgrp->
+                               max_powers), power);
+      if (yescode > NO) {
+        if (*tbp != **bp)
+          safe_chr(' ', buff, bp);
+        safe_str(pname, buff, bp);
+        safe_format(buff, bp, ":%d", yescode);
+
+      }
+    }
+}
+
+
+/* Syntax: haspowergroup(<object> , <powergroup>) -> check to see if object has powergroup
+ */
+FUNCTION(fun_haspowergroup)
+{
+  POWERGROUP *pgrp;
+  dbref obj;
+
+  obj = match_thing(executor, args[0]);
+
+  if (!GoodObject(obj) || IsGarbage(obj))
+    safe_str(T(e_notvis), buff, bp);
+  else {
+    pgrp = powergroup_find(args[1]);
+    if (!pgrp)
+      safe_str("#-1 NO SUCH POWERGROUP", buff, bp);
+    else {
+      if (!powergroup_has(obj, pgrp))
+        safe_chr('0', buff, bp);
+      else
+        safe_chr('1', buff, bp);
+    }
+  }
+}
+
+/* Syntax: powergroups(<object>) -> return powergroups on object
+ *         powergroups()  -> return all powergroups
+ */
+
+FUNCTION(fun_powergroups)
+{
+  dbref obj;
+
+  if (nargs > 0) {
+    obj = match_thing(executor, args[0]);
+
+    if (!GoodObject(obj) || IsGarbage(obj))
+      safe_str(T(e_notvis), buff, bp);
+    else {
+      safe_str(powergroups_list_on(obj, 0), buff, bp);
+    }
+  } else {
+    safe_str((const char *) powergroups_list(executor, 0), buff, bp);
+  }
+}
+
+FUNCTION(fun_empire)
+{
+  dbref start;
+  dbref end;
+  dbref last;
+
+  start = match_thing(executor, args[0]);
+
+  if (!GoodObject(start)) {
+    safe_str("#-1 BAD OBJECT", buff, bp);
+    return;
+  }
+  if (!IsDivision(start)) {
+    if (!IsDivision(SDIV(start).object)) {
+      safe_str("#-1 INVALID DIVISION", buff, bp);
+      return;
+    }
+    start = SDIV(start).object;
+  }
+
+  for (last = end = args[1] ? SDIV(start).object : start;
+       GoodObject(end) && IsDivision(end)
+       && !has_flag_by_name(end, "EMPIRE", TYPE_DIVISION);
+       last = end, end = SDIV(end).object);
+  if (!has_flag_by_name(end, "EMPIRE", TYPE_DIVISION)) {
+    if (end == NOTHING && IsDivision(last))
+      safe_str(unparse_dbref(last), buff, bp);
+    else
+      safe_str(unparse_dbref(start), buff, bp);
+  } else
+    safe_str(unparse_dbref(end), buff, bp);
+}
+
+FUNCTION(fun_updiv)
+{
+  dbref obj1, divi;
+  int space = 1;
+
+
+
+  obj1 = match_thing(executor, args[0]);
+  if (obj1 == NOTHING) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  if (IsDivision(obj1))
+    safe_str(unparse_dbref(obj1), buff, bp);
+  else
+    space = 0;
+  divi = SDIV(obj1).object;
+  while (divi != NOTHING && GoodObject(divi) && IsDivision(divi)) {
+    if (space)
+      safe_chr(' ', buff, bp);
+    else
+      space = 1;
+    safe_str(unparse_dbref(divi), buff, bp);
+    divi = SDIV(divi).object;
+  }
+}
+
+FUNCTION(fun_indivall)
+{
+  dbref cur_obj, obj;
+  int type, space = 0;
+
+  obj = match_thing(executor, args[0]);
+  if (!GoodObject(obj) || !IsDivision(obj)) {
+    safe_str("#-1 INVALID DIVISION", buff, bp);
+    return;
+  }
+  if (nargs > 1) {
+    switch (*args[1]) {
+    case 'R':
+    case 'r':
+      type = TYPE_ROOM;
+      break;
+    case 'T':
+    case 't':
+      type = TYPE_THING;
+      break;
+    case 'P':
+    case 'p':
+      type = TYPE_PLAYER;
+      break;
+    case 'D':
+    case 'd':
+      type = TYPE_DIVISION;
+      break;
+    case 'E':
+    case 'e':
+      type = TYPE_EXIT;
+      break;
+    case 'N':
+    case 'n':
+      type = NOTYPE;
+      break;
+    default:
+      type = TYPE_PLAYER;
+    }
+  } else {
+    if (!strcmp(called_as, "DOWNDIV"))
+      type = TYPE_DIVISION;
+    else
+      type = TYPE_PLAYER;
+  }
+  for (cur_obj = 0; cur_obj < db_top; cur_obj++)
+    if ((Typeof(cur_obj) == type || type == NOTYPE)
+        && (div_inscope(obj, cur_obj) && SDIV(cur_obj).object != -1)) {
+      if (space)
+        safe_chr(' ', buff, bp);
+      else
+        space = 1;
+      safe_str(unparse_dbref(cur_obj), buff, bp);
+    }
+}
+
+FUNCTION(fun_indiv)
+{
+  dbref cur_obj, obj;
+  int type;
+  int space = 0;
+
+  obj = match_thing(executor, args[0]);
+  if (!GoodObject(obj) || !IsDivision(obj)) {
+    safe_str("#-1 INVALID DIVISION", buff, bp);
+    return;
+  }
+
+  if (nargs > 1) {
+    switch (*args[1]) {
+    case 'R':
+    case 'r':
+      type = TYPE_ROOM;
+      break;
+    case 'T':
+    case 't':
+      type = TYPE_THING;
+      break;
+    case 'P':
+    case 'p':
+      type = TYPE_PLAYER;
+      break;
+    case 'D':
+    case 'd':
+      type = TYPE_DIVISION;
+      break;
+    case 'E':
+    case 'e':
+      type = TYPE_EXIT;
+      break;
+    case 'N':
+    case 'n':
+      type = NOTYPE;
+      break;
+    default:
+      type = TYPE_PLAYER;
+    }
+  } else
+    type = TYPE_PLAYER;
+  for (cur_obj = 0; cur_obj < db_top; cur_obj++)
+    if ((Typeof(cur_obj) == type || type == NOTYPE)
+        && (SDIV(cur_obj).object == obj)) {
+      if (space)
+        safe_chr(' ', buff, bp);
+      else
+        space = 1;
+      safe_str(unparse_dbref(cur_obj), buff, bp);
+    }
+}
+
+FUNCTION(fun_powover)
+{
+  dbref obj1, obj2;
+
+  obj1 = match_thing(executor, args[0]);
+  obj2 = match_thing(executor, args[1]);
+
+  if (!GoodObject(obj1) || !GoodObject(obj2)) {
+    safe_str("#-1 BAD OBJECT", buff, bp);
+    return;
+  }
+  safe_chr(div_powover(obj1, obj2, args[2]) ? '1' : '0', buff, bp);
+}
+
+FUNCTION(fun_powers)
+{
+  dbref it;
+
+  it = match_thing(executor, args[0]);
+  if (it == NOTHING) {
+    safe_str("#-1 UNFINDABLE", buff, bp);
+    return;
+  }
+  if (!Can_Examine(executor, it)) {
+    safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  }
+  safe_str(division_list_powerz(it, 0), buff, bp);
+
+}
+
+/* Retrieve What their primary division is.. If they have XYXX_DIVRCD Set its the first entry, otherwise its simple
+ * Division() :)
+ */
+FUNCTION(fun_primary_division) {
+   ATTR *divrcd;
+   dbref target;
+   int cnt;
+   char *tbuf[BUFFER_LEN / 2];
+
+
+   /* Find Guy First.. */
+   target = match_thing(executor, args[0]);
+   /* Fetch divrcd */
+   divrcd = (ATTR *) atr_get(target, (const char *) "XYXX_DIVRCD");
+
+   if(divrcd == NULL) { /* Its just division() */
+      safe_dbref(Division(target), buff, bp);
+   } else {
+     cnt = list2arr(tbuf, BUFFER_LEN / 2, (char *) safe_atr_value(divrcd), ' ');
+     safe_dbref(parse_number(tbuf[0]), buff, bp);
+   }
+}
diff --git a/src/funlist.c b/src/funlist.c
new file mode 100644 (file)
index 0000000..701733e
--- /dev/null
@@ -0,0 +1,3194 @@
+/**
+ * \file funlist.c
+ *
+ * \brief List-handling functions for mushcode.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <string.h>
+#include <ctype.h>
+#include "conf.h"
+#include "case.h"
+#include "externs.h"
+#include "parse.h"
+#include "boolexp.h"
+#include "function.h"
+#include "mymalloc.h"
+#include "pcre.h"
+#include "match.h"
+#include "attrib.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "mushdb.h"
+#include "lock.h"
+#include "confmagic.h"
+
+#define MAX_SORTSIZE (BUFFER_LEN / 2)  /**< Maximum number of elements to sort */
+
+static char *next_token(char *str, char sep);
+static list_type autodetect_list(char **ptrs, int nptrs);
+static list_type get_list_type(char **args, int nargs,
+                              int type_pos, char **ptrs, int nptrs);
+static list_type get_list_type_noauto(char **args, int nargs, int type_pos);
+static int a_comp(const void *s1, const void *s2);
+static int ai_comp(const void *s1, const void *s2);
+static int i_comp(const void *s1, const void *s2);
+static int f_comp(const void *s1, const void *s2);
+static int u_comp(const void *s1, const void *s2);
+static int regrep_helper(dbref who, dbref what, dbref parent,
+                        char const *name, ATTR *atr, void *args);
+/** Type definition for a qsort comparison function */
+typedef int (*comp_func) (const void *, const void *);
+static void sane_qsort(void **array, int left, int right, comp_func compare);
+static void do_itemfuns(char *buff, char **bp, char *str, char *num,
+                       char *word, char *sep, int flag);
+
+char *iter_rep[MAX_ITERS];  /**< itext values */
+int iter_place[MAX_ITERS];  /**< inum numbers */
+int inum = 0;              /**< iter depth */
+int inum_limit = 0;        /**< limit of iter depth */
+int iter_break = 0; /**< iter break */
+extern const unsigned char *tables;
+
+static char *
+next_token(char *str, char sep)
+{
+  /* move pointer to start of the next token */
+
+  while (*str && (*str != sep))
+    str++;
+  if (!*str)
+    return NULL;
+  str++;
+  if (sep == ' ') {
+    while (*str == sep)
+      str++;
+  }
+  return str;
+}
+
+/** Convert list to array.
+ * Chops up a list of words into an array of words. The list is
+ * destructively modified.
+ * \param r pointer to array to store words.
+ * \param max maximum number of words to split out.
+ * \param list list of words as a string.
+ * \param sep separator character between list items.
+ * \return number of words split out.
+ */
+int
+list2arr(char *r[], int max, char *list, char sep) {
+  char *p, *lp;
+  int i;
+  int first;
+  ansi_string *as;
+  char *aptr;
+
+  as = parse_ansi_string(list);
+  aptr = as->text;
+
+  aptr = trim_space_sep(aptr, sep);
+
+  lp = list;
+  p = split_token(&aptr, sep);
+  first = 0;
+  for (i = 0; p && (i < max); i++, p = split_token(&aptr, sep)) {
+    r[i] = lp;
+    safe_ansi_string(as, p - (as->text), strlen(p), list, &lp);
+    *(lp++) = '\0';
+  }
+  free_ansi_string(as);
+  return i;
+}
+
+/* convert a character into an array and acknowledge a seperator character that may be escaped */
+/* String is not destruviely modified like list2arr does */
+int elist2arr(char *r[], int max, char *list, char sep) {
+   static char tbuf[BUFFER_LEN];
+   char tbuf2[BUFFER_LEN];
+   char *lp, *p, *bp, *cbufp;
+   int i;
+
+   memset(tbuf, '\0', BUFFER_LEN);
+   cbufp = bp = tbuf;
+   strcpy(tbuf2, list);
+   lp = p = tbuf2;
+   i = 0;
+
+   /* Do first */
+   while(p && *p && (i < max)) {
+     if(*p == '\\' && (p + 1)) {
+       /* Remove This Char & let the next char in */
+       *p++ = '\0';
+       safe_str(lp, cbufp, &bp);
+       lp = p;
+     }
+     if(*p == sep) {
+       *p = '\0';
+       safe_str(lp, cbufp, &bp);
+       *bp++ = '\0';
+       r[i++] = tbuf;
+       lp = ++p;
+       continue;
+     }
+     p++;
+   }
+
+     
+  return i;
+}
+
+
+
+/** Convert array to list.
+ * Takes an array of words and concatenates them into a string,
+ * using our safe string functions.
+ * \param r pointer to array of words.
+ * \param max maximum number of words to concatenate.
+ * \param list string to fill with word list.
+ * \param lp pointer into end of list.
+ * \param sep string to use as separator between words.
+ */
+void
+arr2list(char *r[], int max, char *list, char **lp, char *sep)
+{
+  int i;
+  int seplen = 0;
+
+  if (!max)
+    return;
+
+  if (sep && *sep)
+    seplen = strlen(sep);
+
+  safe_str(r[0], list, lp);
+  for (i = 1; i < max; i++) {
+    safe_strl(sep, seplen, list, lp);
+    safe_str(r[i], list, lp);
+  }
+  **lp = '\0';
+}
+
+/* ARGSUSED */
+FUNCTION(fun_munge)
+{
+  /* This is a function which takes three arguments. The first is
+   * an obj-attr pair referencing a u-function to be called. The
+   * other two arguments are lists. The first list is passed to the
+   * u-function.  The second list is then rearranged to match the
+   * order of the first list as returned from the u-function.
+   * This rearranged list is returned by MUNGE.
+   * A fourth argument (separator) is optional.
+   */
+
+  char list1[BUFFER_LEN], *lp, rlist[BUFFER_LEN], *rp;
+  char **ptrs1, **ptrs2, **results;
+  int i, j, nptrs1, nptrs2, nresults;
+  dbref thing;
+  ATTR *attrib;
+  char sep, isep[2] = { '\0', '\0' }, *osep, osepd[2] = {
+  '\0', '\0'};
+  int first;
+  char *uargs[2];
+
+  if (!delim_check(buff, bp, nargs, args, 4, &sep))
+    return;
+
+  isep[0] = sep;
+  if (nargs == 5)
+    osep = args[4];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+
+  /* Copy the first list, since we need to pass it to two destructive
+   * routines.
+   */
+
+  strcpy(list1, args[1]);
+
+  /* Break up the two lists into their respective elements. */
+
+  ptrs1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  ptrs2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  if (!ptrs1 || !ptrs2)
+    mush_panic("Unable to allocate memory in fun_munge");
+  nptrs1 = list2arr(ptrs1, MAX_SORTSIZE, args[1], sep);
+  nptrs2 = list2arr(ptrs2, MAX_SORTSIZE, args[2], sep);
+
+  if (nptrs1 != nptrs2) {
+    safe_str(T("#-1 LISTS MUST BE OF EQUAL SIZE"), buff, bp);
+    mush_free((Malloc_t) ptrs1, "ptrarray");
+    mush_free((Malloc_t) ptrs2, "ptrarray");
+    free_anon_attrib(attrib);
+    return;
+  }
+  /* Call the user function */
+
+  lp = list1;
+  rp = rlist;
+  uargs[0] = lp;
+  uargs[1] = isep;
+  do_userfn(rlist, &rp, thing, attrib, 2, uargs,
+           executor, caller, enactor, pe_info);
+  *rp = '\0';
+
+  /* Now that we have our result, put it back into array form. Search
+   * through list1 until we find the element position, then copy the
+   * corresponding element from list2.  Mark used elements with
+   * NULL to handle duplicates
+   */
+  results = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  if (!results)
+    mush_panic("Unable to allocate memory in fun_munge");
+  nresults = list2arr(results, MAX_SORTSIZE, rlist, sep);
+
+  first = 1;
+  for (i = 0; i < nresults; i++) {
+    for (j = 0; j < nptrs1; j++) {
+      if (ptrs2[j] && !strcmp(results[i], ptrs1[j])) {
+       if (first)
+         first = 0;
+       else
+         safe_str(osep, buff, bp);
+       safe_str(ptrs2[j], buff, bp);
+       ptrs2[j] = NULL;
+       break;
+      }
+    }
+  }
+  mush_free((Malloc_t) ptrs1, "ptrarray");
+  mush_free((Malloc_t) ptrs2, "ptrarray");
+  mush_free((Malloc_t) results, "ptrarray");
+  free_anon_attrib(attrib);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_elements)
+{
+  /* Given a list and a list of numbers, return the corresponding
+   * elements of the list. elements(ack bar eep foof yay,2 4) = bar foof
+   * A separator for the first list is allowed.
+   * This code modified slightly from the Tiny 2.2.1 distribution
+   */
+  int nwords, cur;
+  char **ptrs;
+  char *wordlist;
+  char *s, *r, sep;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  ptrs = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  wordlist = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!ptrs || !wordlist)
+    mush_panic("Unable to allocate memory in fun_elements");
+
+  /* Turn the first list into an array. */
+  strcpy(wordlist, args[0]);
+  nwords = list2arr(ptrs, MAX_SORTSIZE, wordlist, sep);
+
+  s = trim_space_sep(args[1], ' ');
+
+  /* Go through the second list, grabbing the numbers and finding the
+   * corresponding elements.
+   */
+  r = split_token(&s, ' ');
+  cur = atoi(r) - 1;
+  if ((cur >= 0) && (cur < nwords) && ptrs[cur]) {
+    safe_str(ptrs[cur], buff, bp);
+  }
+  while (s) {
+    r = split_token(&s, ' ');
+    cur = atoi(r) - 1;
+    if ((cur >= 0) && (cur < nwords) && ptrs[cur]) {
+      safe_str(osep, buff, bp);
+      safe_str(ptrs[cur], buff, bp);
+    }
+  }
+  mush_free((Malloc_t) ptrs, "ptrarray");
+  mush_free((Malloc_t) wordlist, "string");
+}
+
+/* ARGSUSED */
+FUNCTION(fun_matchall)
+{
+  /* Check each word individually, returning the word number of all
+   * that match. If none match, return an empty string.
+   */
+
+  int wcount;
+  char *r, *s, *b, sep;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  wcount = 1;
+  s = trim_space_sep(args[0], sep);
+  b = *bp;
+  do {
+    r = split_token(&s, sep);
+    if (quick_wild(args[1], r)) {
+      if (*bp != b)
+       safe_str(osep, buff, bp);
+      safe_integer(wcount, buff, bp);
+    }
+    wcount++;
+  } while (s);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_graball)
+{
+  /* Check each word individually, returning all that match.
+   * If none match, return an empty string.  This is to grab()
+   * what matchall() is to match().
+   */
+
+  char *r, *s, *b, sep;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  s = trim_space_sep(args[0], sep);
+  b = *bp;
+  do {
+    r = split_token(&s, sep);
+    if (quick_wild(args[1], r)) {
+      if (*bp != b)
+       safe_str(osep, buff, bp);
+      safe_str(r, buff, bp);
+    }
+  } while (s);
+}
+
+
+
+/* ARGSUSED */
+FUNCTION(fun_fold)
+{
+  /* iteratively evaluates an attribute with a list of arguments and
+   * optional base case. With no base case, the first list element is
+   * passed as %0, and the second as %1. The attribute is then evaluated
+   * with these args. The result is then used as %0, and the next arg as
+   * %1. Repeat until no elements are left in the list. The base case 
+   * can provide a starting point.
+   */
+
+  dbref thing;
+  ATTR *attrib;
+  char const *ap;
+  char *abuf, *result, *rp, *rsave;
+  char *cp;
+  char *tptr[2];
+  char sep;
+  int funccount, per;
+  int pe_flags = PE_DEFAULT;
+
+  if (!delim_check(buff, bp, nargs, args, 4, &sep))
+    return;
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+
+  /* Now we can go to work */
+  if (AF_Debug(attrib))
+    pe_flags |= PE_DEBUG;
+  result = (char *) mush_malloc(BUFFER_LEN, "string");
+  rsave = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!result || !rsave)
+    mush_panic("Unable to allocate memory in fun_fold");
+
+  abuf = safe_atr_value(attrib);
+
+  /* save our stack */
+  tptr[0] = global_eval_context.wenv[0];
+  tptr[1] = global_eval_context.wenv[1];
+
+  cp = args[1];
+
+  /* If we have three or more arguments, the third one is the base case */
+  if (nargs >= 3) {
+    global_eval_context.wenv[0] = args[2];
+    global_eval_context.wenv[1] = split_token(&cp, sep);
+  } else {
+    global_eval_context.wenv[0] = split_token(&cp, sep);
+    global_eval_context.wenv[1] = split_token(&cp, sep);
+  }
+  rp = result;
+  ap = abuf;
+  process_expression(result, &rp, &ap, thing, executor, enactor,
+                    pe_flags, PT_DEFAULT, pe_info);
+  *rp = '\0';
+  strcpy(rsave, result);
+  funccount = pe_info->fun_invocations;
+
+  /* handle the rest of the cases */
+  while (cp && *cp) {
+    global_eval_context.wenv[0] = rsave;
+    global_eval_context.wenv[1] = split_token(&cp, sep);
+    rp = result;
+    ap = abuf;
+    per = process_expression(result, &rp, &ap, thing, executor, enactor,
+                            pe_flags, PT_DEFAULT, pe_info);
+    *rp = '\0';
+    if (per || (pe_info->fun_invocations >= FUNCTION_LIMIT &&
+               pe_info->fun_invocations == funccount &&
+               !strcmp(rsave, result)))
+      break;
+    funccount = pe_info->fun_invocations;
+    strcpy(rsave, result);
+  }
+  safe_str(rsave, buff, bp);
+
+  /* restore the stack */
+  global_eval_context.wenv[0] = tptr[0];
+  global_eval_context.wenv[1] = tptr[1];
+
+  free((Malloc_t) abuf);
+  mush_free((Malloc_t) result, "string");
+  mush_free((Malloc_t) rsave, "string");
+  free_anon_attrib(attrib);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_itemize)
+{
+  /* Called in one of two ways:
+   * itemize(<list>[,<delim>[,<conjunction>[,<punctuation>]]])
+   * elist(<list>[,<conjunction> [,<delim> [,<output delim> [,<punctuation>]]]])
+   * Either way, it takes the elements of list and:
+   *  If there's just one, returns it.
+   *  If there's two, returns <e1> <conjunction> <e2>
+   *  If there's >2, returns <e1><punc> <e2><punc> ... <conjunction> <en>
+   * Default <conjunction> is "and", default punctuation is ","
+   */
+  const char *outsep = " ";
+  char sep = ' ';
+  const char *lconj = "and";
+  const char *punc = ",";
+  char *cp;
+  char *word, *nextword;
+  int pos;
+
+  if (strcmp(called_as, "ELIST") == 0) {
+    /* elist ordering */
+    if (!delim_check(buff, bp, nargs, args, 3, &sep))
+      return;
+    if (nargs > 1)
+      lconj = args[1];
+    if (nargs > 3)
+      outsep = args[3];
+    if (nargs > 4)
+      punc = args[4];
+  } else {
+    /* itemize ordering */
+    if (!delim_check(buff, bp, nargs, args, 2, &sep))
+      return;
+    if (nargs > 2)
+      lconj = args[2];
+    if (nargs > 3)
+      punc = args[3];
+  }
+  cp = trim_space_sep(args[0], sep);
+  pos = 1;
+  word = split_token(&cp, sep);
+  while (word) {
+    nextword = split_token(&cp, sep);
+    safe_itemizer(pos, !(nextword), punc, lconj, outsep, buff, bp);
+    safe_str(word, buff, bp);
+    pos++;
+    word = nextword;
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_filter)
+{
+  /* take a user-def function and a list, and return only those elements
+   * of the list for which the function evaluates to 1.
+   */
+
+  dbref thing;
+  ATTR *attrib;
+  char const *ap;
+  char *abuf, result[BUFFER_LEN], *rp;
+  char *cp;
+  char *tptr;
+  char sep;
+  int first;
+  int check_bool = 0;
+  int funccount;
+  char *osep, osepd[2] = { '\0', '\0' };
+  int pe_flags = PE_DEFAULT;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  if (strcmp(called_as, "FILTERBOOL") == 0)
+    check_bool = 1;
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+
+  if (AF_Debug(attrib))
+    pe_flags |= PE_DEBUG;
+
+  abuf = safe_atr_value(attrib);
+
+  tptr = global_eval_context.wenv[0];
+
+  cp = trim_space_sep(args[1], sep);
+  first = 1;
+  funccount = pe_info->fun_invocations;
+  while (cp && *cp) {
+    global_eval_context.wenv[0] = split_token(&cp, sep);
+    ap = abuf;
+    rp = result;
+    if (process_expression(result, &rp, &ap, thing, executor, enactor,
+                          pe_flags, PT_DEFAULT, pe_info))
+      break;
+    *rp = '\0';
+    if ((check_bool == 0)
+       ? (*result == '1' && *(result + 1) == '\0')
+       : parse_boolean(result)) {
+      if (first)
+       first = 0;
+      else
+       safe_str(osep, buff, bp);
+      safe_str(global_eval_context.wenv[0], buff, bp);
+    }
+    /* Can't do *bp == oldbp like in all the others, because bp might not
+     * move even when not full, if one of the list elements is null and
+     * we have a null separator. */
+    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
+      break;
+    funccount = pe_info->fun_invocations;
+  }
+
+  global_eval_context.wenv[0] = tptr;
+
+  free((Malloc_t) abuf);
+  free_anon_attrib(attrib);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_shuffle)
+{
+  /* given a list of words, randomize the order of words. 
+   * We do this by taking each element, and swapping it with another
+   * element with a greater array index (thus, words[0] can be swapped
+   * with anything up to words[n], words[5] with anything between
+   * itself and words[n], etc.
+   * This is relatively fast - linear time - and reasonably random.
+   * Will take an optional delimiter argument.
+   */
+
+  char *words[BUFFER_LEN / 2];
+  int n, i, j;
+  char sep;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  if (nargs == 3)
+    osep = args[2];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  /* split the list up, or return if the list is empty */
+  if (!*args[0])
+    return;
+  n = list2arr(words, BUFFER_LEN / 2, args[0], sep);
+
+  /* shuffle it */
+  for (i = 0; i < n; i++) {
+    char *tmp;
+    j = get_random_long(i, n - 1);
+    tmp = words[j];
+    words[j] = words[i];
+    words[i] = tmp;
+  }
+
+  arr2list(words, n, buff, bp, osep);
+}
+
+static list_type
+autodetect_list(char *ptrs[], int nptrs)
+{
+  list_type sort_type;
+  int i;
+
+  sort_type = NUMERIC_LIST;
+
+  for (i = 0; i < nptrs; i++) {
+    switch (sort_type) {
+    case NUMERIC_LIST:
+      if (!is_strict_integer(ptrs[i])) {
+       /* If it's not an integer, see if it's a floating-point number */
+       if (is_strict_number(ptrs[i])) {
+         sort_type = FLOAT_LIST;
+       } else if (i == 0) {
+
+         /* If we get something non-numeric, switch to an
+          * alphanumeric guess, unless this is the first
+          * element and we have a dbref.
+          */
+         if (is_objid(ptrs[i]))
+           sort_type = DBREF_LIST;
+         else
+           return ALPHANUM_LIST;
+       }
+      }
+      break;
+    case FLOAT_LIST:
+      if (!is_strict_number(ptrs[i]))
+       return ALPHANUM_LIST;
+      break;
+    case DBREF_LIST:
+      if (!is_objid(ptrs[i]))
+       return ALPHANUM_LIST;
+      break;
+    default:
+      return ALPHANUM_LIST;
+    }
+  }
+  return sort_type;
+}
+
+static list_type
+get_list_type(char *args[], int nargs, int type_pos, char *ptrs[], int nptrs)
+{
+  if (nargs >= type_pos) {
+    switch (*args[type_pos - 1]) {
+    case 'A':
+    case 'a':
+      return ALPHANUM_LIST;
+    case 'I':
+    case 'i':
+      return INSENS_ALPHANUM_LIST;
+    case 'D':
+    case 'd':
+      return DBREF_LIST;
+    case 'N':
+    case 'n':
+      return NUMERIC_LIST;
+    case 'F':
+    case 'f':
+      return FLOAT_LIST;
+    case '\0':
+      return autodetect_list(ptrs, nptrs);
+    default:
+      return ALPHANUM_LIST;
+    }
+  }
+  return autodetect_list(ptrs, nptrs);
+}
+
+static list_type
+get_list_type_noauto(char *args[], int nargs, int type_pos)
+{
+  if (nargs >= type_pos) {
+    switch (*args[type_pos - 1]) {
+    case 'A':
+    case 'a':
+      return ALPHANUM_LIST;
+    case 'I':
+    case 'i':
+      return INSENS_ALPHANUM_LIST;
+    case 'D':
+    case 'd':
+      return DBREF_LIST;
+    case 'N':
+    case 'n':
+      return NUMERIC_LIST;
+    case 'F':
+    case 'f':
+      return FLOAT_LIST;
+    default:
+      return UNKNOWN_LIST;
+    }
+  }
+  return UNKNOWN_LIST;
+}
+
+static int
+a_comp(const void *s1, const void *s2)
+{
+  return strcoll(*(char *const *) s1, *(char *const *) s2);
+}
+
+static int
+ai_comp(const void *s1, const void *s2)
+{
+  return strcasecoll(*(char *const *) s1, *(char *const *) s2);
+}
+
+/** An integer, for sorting purposes. We store both the string and
+ * the int forms to make some things more efficient.
+ */
+typedef struct i_record {
+  char *str;   /**< string representation */
+  int num;     /**< integer representation */
+} i_rec;
+
+/** Integer comparison routine for sorts.
+ * \param s1 void pointer to an i_record.
+ * \param s2 void pointer to an i_record.
+ * \retval <0 s1's integer is < s2's integer.
+ * \retval 0 s1's integer = s2's integer.
+ * \retval >0 s1's integer is > s2's integer.
+ */
+static int
+i_comp(const void *s1, const void *s2)
+{
+  if (((const i_rec *) s1)->num > ((const i_rec *) s2)->num)
+    return 1;
+  if (((const i_rec *) s1)->num < ((const i_rec *) s2)->num)
+    return -1;
+  return 0;
+}
+
+/** A double, for sorting purposes. We store both the string and
+ * the NVAL forms to make some things more efficient.
+ */
+typedef struct f_record {
+  char *str;   /**< string representation */
+  NVAL num;    /**< numeric representation */
+} f_rec;
+
+static int
+f_comp(const void *s1, const void *s2)
+{
+  if (((const f_rec *) s1)->num > ((const f_rec *) s2)->num)
+    return 1;
+  if (((const f_rec *) s1)->num < ((const f_rec *) s2)->num)
+    return -1;
+  return 0;
+}
+
+static dbref ucomp_executor, ucomp_caller, ucomp_enactor;
+static char ucomp_buff[BUFFER_LEN];
+static PE_Info *ucomp_pe_info;
+
+static int
+u_comp(const void *s1, const void *s2)
+{
+  char result[BUFFER_LEN], *rp;
+  char const *tbuf;
+  int n;
+
+  /* Our two arguments are passed as %0 and %1 to the sortby u-function. */
+
+  /* Note that this function is for use in conjunction with our own
+   * sane_qsort routine, NOT with the standard library qsort!
+   */
+  global_eval_context.wenv[0] = (char *) s1;
+  global_eval_context.wenv[1] = (char *) s2;
+
+  /* Run the u-function, which should return a number. */
+
+  tbuf = ucomp_buff;
+  rp = result;
+  if (process_expression(result, &rp, &tbuf,
+                        ucomp_executor, ucomp_caller, ucomp_enactor,
+                        PE_DEFAULT, PT_DEFAULT, ucomp_pe_info))
+    return 0;
+  n = parse_integer(result);
+
+  return n;
+}
+
+/** Compare two values based on sort_type.
+ * \param a one value.
+ * \param b another value.
+ * \param sort_type how to compare the values.
+ * \retval <0 a < b
+ * \retval 0  a = b
+ * \retval >0 a > b
+ */
+int
+gencomp(char *a, char *b, list_type sort_type)
+{
+  switch (sort_type) {
+  case NUMERIC_LIST:
+    {
+      int na, nb;
+      na = parse_integer(a);
+      nb = parse_integer(b);
+      if (na < nb)
+       return -1;
+      if (na > nb)
+       return 1;
+      return 0;
+    }
+  case DBREF_LIST:
+    {
+      int dga, dgb;
+      dga = parse_objid(a);
+      dgb = parse_objid(b);
+      if (dga < dgb)
+       return -1;
+      if (dga > dgb)
+       return 1;
+      return 0;
+    }
+  case FLOAT_LIST:
+    {
+      NVAL na, nb;
+      na = parse_number(a);
+      nb = parse_number(b);
+      if (na < nb)
+       return -1;
+      if (na > nb)
+       return 1;
+      return 0;
+    }
+  case INSENS_ALPHANUM_LIST:
+    {
+      int result;
+      result = strcasecoll(a, b);
+      if (result < 0)
+       return -1;
+      if (result > 0)
+       return 1;
+      return 0;
+    }
+  case ALPHANUM_LIST:          /* Falls through */
+  default:
+    {
+      int result;
+      result = strcoll(a, b);
+      if (result < 0)
+       return -1;
+      if (result > 0)
+       return 1;
+      return 0;
+    }
+  }
+}
+
+/** A generic sort routine to sort an array in place.
+ * \param s the array to sort.
+ * \param n number of elements in array s.
+ * \param sort_type type of sort.
+ */
+void
+do_gensort(char *s[], int n, list_type sort_type)
+{
+  int i;
+  f_rec *fp;
+  i_rec *ip;
+
+  switch (sort_type) {
+  case ALPHANUM_LIST:
+  case UNKNOWN_LIST:
+    qsort(s, n, sizeof(char *), a_comp);
+    break;
+  case INSENS_ALPHANUM_LIST:
+    qsort(s, n, sizeof(char *), ai_comp);
+    break;
+  case NUMERIC_LIST:
+    ip = (i_rec *) mush_malloc(n * sizeof(i_rec), "do_gensort.int_list");
+    for (i = 0; i < n; i++) {
+      ip[i].str = s[i];
+      ip[i].num = parse_integer(s[i]);
+    }
+    qsort((void *) ip, n, sizeof(i_rec), i_comp);
+    for (i = 0; i < n; i++)
+      s[i] = ip[i].str;
+    mush_free((Malloc_t) ip, "do_gensort.int_list");
+    break;
+  case DBREF_LIST:
+    ip = (i_rec *) mush_malloc(n * sizeof(i_rec), "do_gensort.dbref_list");
+    for (i = 0; i < n; i++) {
+      ip[i].str = s[i];
+      ip[i].num = qparse_dbref(s[i]);
+    }
+    qsort((void *) ip, n, sizeof(i_rec), i_comp);
+    for (i = 0; i < n; i++)
+      s[i] = ip[i].str;
+    mush_free((Malloc_t) ip, "do_gensort.dbref_list");
+    break;
+  case FLOAT_LIST:
+    fp = (f_rec *) mush_malloc(n * sizeof(f_rec), "do_gensort.num_list");
+    for (i = 0; i < n; i++) {
+      fp[i].str = s[i];
+      fp[i].num = parse_number(s[i]);
+    }
+    qsort((void *) fp, n, sizeof(f_rec), f_comp);
+    for (i = 0; i < n; i++)
+      s[i] = fp[i].str;
+    mush_free((Malloc_t) fp, "do_gensort.num_list");
+    break;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sort)
+{
+  char *ptrs[MAX_SORTSIZE];
+  int nptrs;
+  list_type sort_type;
+  char sep;
+  char outsep[BUFFER_LEN];
+
+  if (!nargs || !*args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs < 4) {
+    outsep[0] = sep;
+    outsep[1] = '\0';
+  } else
+    strcpy(outsep, args[3]);
+
+  nptrs = list2arr(ptrs, MAX_SORTSIZE, args[0], sep);
+  sort_type = get_list_type(args, nargs, 2, ptrs, nptrs);
+  do_gensort(ptrs, nptrs, sort_type);
+  arr2list(ptrs, nptrs, buff, bp, outsep);
+}
+
+static void
+sane_qsort(void *array[], int left, int right, comp_func compare)
+{
+  /* Andrew Molitor's qsort, which doesn't require transitivity between
+   * comparisons (essential for preventing crashes due to boneheads
+   * who write comparison functions where a > b doesn't mean b < a).
+   */
+  /* Actually, this sort doesn't require commutivity.
+   * Sorting doesn't make sense without transitivity...
+   */
+
+  int i, last;
+  void *tmp;
+
+loop:
+  if (left >= right)
+    return;
+
+  /* Pick something at random at swap it into the leftmost slot   */
+  /* This is the pivot, we'll put it back in the right spot later */
+
+  i = get_random_long(left, right);
+  tmp = array[i];
+  array[i] = array[left];
+  array[left] = tmp;
+
+  last = left;
+  for (i = left + 1; i <= right; i++) {
+
+    /* Walk the array, looking for stuff that's less than our */
+    /* pivot. If it is, swap it with the next thing along     */
+
+    if (compare(array[i], array[left]) < 0) {
+      last++;
+      if (last == i)
+       continue;
+
+      tmp = array[last];
+      array[last] = array[i];
+      array[i] = tmp;
+    }
+  }
+
+  /* Now we put the pivot back, it's now in the right spot, we never */
+  /* need to look at it again, trust me.                             */
+
+  tmp = array[last];
+  array[last] = array[left];
+  array[left] = tmp;
+
+  /* At this point everything underneath the 'last' index is < the */
+  /* entry at 'last' and everything above it is not < it.          */
+
+  if ((last - left) < (right - last)) {
+    sane_qsort(array, left, last - 1, compare);
+    left = last + 1;
+    goto loop;
+  } else {
+    sane_qsort(array, last + 1, right, compare);
+    right = last - 1;
+    goto loop;
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_sortby)
+{
+  char *ptrs[MAX_SORTSIZE], *tptr[10];
+  char *up, sep;
+  int nptrs;
+  dbref thing;
+  ATTR *attrib;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!nargs || !*args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  /* Find object and attribute to get sortby function from. */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  up = ucomp_buff;
+  safe_str(atr_value(attrib), ucomp_buff, &up);
+  *up = '\0';
+
+  ucomp_executor = thing;
+  ucomp_caller = executor;
+  ucomp_enactor = enactor;
+  ucomp_pe_info = pe_info;
+
+  save_global_env("sortby", tptr);
+
+  /* Split up the list, sort it, reconstruct it. */
+  nptrs = list2arr(ptrs, MAX_SORTSIZE, args[1], sep);
+  if (nptrs > 1)               /* pointless to sort less than 2 elements */
+    sane_qsort((void *) ptrs, 0, nptrs - 1, u_comp);
+
+  arr2list(ptrs, nptrs, buff, bp, osep);
+
+  restore_global_env("sortby", tptr);
+  free_anon_attrib(attrib);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_setinter)
+{
+  char sep;
+  char **a1, **a2;
+  int n1, n2, x1, x2, val;
+  list_type sort_type = ALPHANUM_LIST;
+  int osepl = 0;
+  char *osep = NULL, osepd[2] = { '\0', '\0' };
+
+  /* if no lists, then no work */
+  if (!*args[0] && !*args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  a1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  a2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  if (!a1 || !a2)
+    mush_panic("Unable to allocate memory in fun_setinter");
+
+  /* make arrays out of the lists */
+  n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep);
+  n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep);
+
+  if (nargs < 4) {
+    osepd[0] = sep;
+    osep = osepd;
+    if (sep)
+      osepl = 1;
+  } else if (nargs == 4) {
+    sort_type = get_list_type_noauto(args, nargs, 4);
+    if (sort_type == UNKNOWN_LIST) {
+      sort_type = ALPHANUM_LIST;
+      osep = args[3];
+      osepl = arglens[3];
+    } else {
+      osepd[0] = sep;
+      osep = osepd;
+      if (sep)
+       osepl = 1;
+    }
+  } else if (nargs == 5) {
+    sort_type = get_list_type(args, nargs, 4, a1, n1);
+    osep = args[4];
+    osepl = arglens[4];
+  }
+  /* sort each array */
+  do_gensort(a1, n1, sort_type);
+  do_gensort(a2, n2, sort_type);
+
+  /* get the first value for the intersection, removing duplicates */
+  x1 = x2 = 0;
+  while ((val = gencomp(a1[x1], a2[x2], sort_type))) {
+    if (val < 0) {
+      x1++;
+      if (x1 >= n1) {
+       mush_free((Malloc_t) a1, "ptrarray");
+       mush_free((Malloc_t) a2, "ptrarray");
+       return;
+      }
+    } else {
+      x2++;
+      if (x2 >= n2) {
+       mush_free((Malloc_t) a1, "ptrarray");
+       mush_free((Malloc_t) a2, "ptrarray");
+       return;
+      }
+    }
+  }
+  safe_str(a1[x1], buff, bp);
+  while (!gencomp(a1[x1], a2[x2], sort_type)) {
+    x1++;
+    if (x1 >= n1) {
+      mush_free((Malloc_t) a1, "ptrarray");
+      mush_free((Malloc_t) a2, "ptrarray");
+      return;
+    }
+  }
+
+  /* get values for the intersection, until at least one list is empty */
+  while ((x1 < n1) && (x2 < n2)) {
+    while ((val = gencomp(a1[x1], a2[x2], sort_type))) {
+      if (val < 0) {
+       x1++;
+       if (x1 >= n1) {
+         mush_free((Malloc_t) a1, "ptrarray");
+         mush_free((Malloc_t) a2, "ptrarray");
+         return;
+       }
+      } else {
+       x2++;
+       if (x2 >= n2) {
+         mush_free((Malloc_t) a1, "ptrarray");
+         mush_free((Malloc_t) a2, "ptrarray");
+         return;
+       }
+      }
+    }
+    safe_strl(osep, osepl, buff, bp);
+    safe_str(a1[x1], buff, bp);
+    while (!gencomp(a1[x1], a2[x2], sort_type)) {
+      x1++;
+      if (x1 >= n1) {
+       mush_free((Malloc_t) a1, "ptrarray");
+       mush_free((Malloc_t) a2, "ptrarray");
+       return;
+      }
+    }
+  }
+  mush_free((Malloc_t) a1, "ptrarray");
+  mush_free((Malloc_t) a2, "ptrarray");
+}
+
+/* ARGSUSED */
+FUNCTION(fun_setunion)
+{
+  char sep;
+  char **a1, **a2;
+  int n1, n2, x1, x2, val;
+  int lastx1, lastx2, found;
+  list_type sort_type = ALPHANUM_LIST;
+  int osepl = 0;
+  char *osep = NULL, osepd[2] = { '\0', '\0' };
+
+  /* if no lists, then no work */
+  if (!*args[0] && !*args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  a1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  a2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  if (!a1 || !a2)
+    mush_panic("Unable to allocate memory in fun_setdiff");
+
+  /* make arrays out of the lists */
+  n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep);
+  n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep);
+
+  if (nargs < 4) {
+    osepd[0] = sep;
+    osep = osepd;
+    if (sep)
+      osepl = 1;
+  } else if (nargs == 4) {
+    sort_type = get_list_type_noauto(args, nargs, 4);
+    if (sort_type == UNKNOWN_LIST) {
+      sort_type = ALPHANUM_LIST;
+      osep = args[3];
+      osepl = arglens[3];
+    } else {
+      osepd[0] = sep;
+      osep = osepd;
+      if (sep)
+       osepl = 1;
+    }
+  } else if (nargs == 5) {
+    sort_type = get_list_type(args, nargs, 4, a1, n1);
+    osep = args[4];
+    osepl = arglens[4];
+  }
+
+  /* sort each array */
+  do_gensort(a1, n1, sort_type);
+  do_gensort(a2, n2, sort_type);
+
+  /* get values for the union, in order, skipping duplicates */
+  lastx1 = lastx2 = -1;
+  found = x1 = x2 = 0;
+  if (n1 == 1 && !*a1[0])
+    n1 = 0;
+  if (n2 == 1 && !*a2[0])
+    n2 = 0;
+  while ((x1 < n1) || (x2 < n2)) {
+    /* If we've already copied off something from a1, and our current
+     * look at a1 is the same element, or we've copied from a2 and
+     * our current look at a1 is the same element, skip forward in a1.
+     */
+    if (x1 < n1 && lastx1 >= 0) {
+      val = gencomp(a1[lastx1], a1[x1], sort_type);
+      if (val == 0) {
+       x1++;
+       continue;
+      }
+    }
+    if (x1 < n1 && lastx2 >= 0) {
+      val = gencomp(a2[lastx2], a1[x1], sort_type);
+      if (val == 0) {
+       x1++;
+       continue;
+      }
+    }
+    if (x2 < n2 && lastx1 >= 0) {
+      val = gencomp(a1[lastx1], a2[x2], sort_type);
+      if (val == 0) {
+       x2++;
+       continue;
+      }
+    }
+    if (x2 < n2 && lastx2 >= 0) {
+      val = gencomp(a2[lastx2], a2[x2], sort_type);
+      if (val == 0) {
+       x2++;
+       continue;
+      }
+    }
+    if (x1 >= n1) {
+      /* Just copy off the rest of a2 */
+      if (x2 < n2) {
+       if (found)
+         safe_strl(osep, osepl, buff, bp);
+       safe_str(a2[x2], buff, bp);
+       lastx2 = x2;
+       x2++;
+       found = 1;
+      }
+    } else if (x2 >= n2) {
+      /* Just copy off the rest of a1 */
+      if (x1 < n1) {
+       if (found)
+         safe_strl(osep, osepl, buff, bp);
+       safe_str(a1[x1], buff, bp);
+       lastx1 = x1;
+       x1++;
+       found = 1;
+      }
+    } else {
+      /* At this point, we're merging. Take the lower of the two. */
+      val = gencomp(a1[x1], a2[x2], sort_type);
+      if (val <= 0) {
+       if (found)
+         safe_strl(osep, osepl, buff, bp);
+       safe_str(a1[x1], buff, bp);
+       lastx1 = x1;
+       x1++;
+       found = 1;
+      } else {
+       if (found)
+         safe_strl(osep, osepl, buff, bp);
+       safe_str(a2[x2], buff, bp);
+       lastx2 = x2;
+       x2++;
+       found = 1;
+      }
+    }
+  }
+  mush_free((Malloc_t) a1, "ptrarray");
+  mush_free((Malloc_t) a2, "ptrarray");
+}
+
+/* ARGSUSED */
+FUNCTION(fun_setdiff)
+{
+  char sep;
+  char **a1, **a2;
+  int n1, n2, x1, x2, val;
+  list_type sort_type = ALPHANUM_LIST;
+  int osepl = 0;
+  char *osep = NULL, osepd[2] = { '\0', '\0' };
+
+  /* if no lists, then no work */
+  if (!*args[0] && !*args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  a1 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  a2 = (char **) mush_malloc(MAX_SORTSIZE * sizeof(char *), "ptrarray");
+  if (!a1 || !a2)
+    mush_panic("Unable to allocate memory in fun_setdiff");
+
+  /* make arrays out of the lists */
+  n1 = list2arr(a1, MAX_SORTSIZE, args[0], sep);
+  n2 = list2arr(a2, MAX_SORTSIZE, args[1], sep);
+
+  if (nargs < 4) {
+    osepd[0] = sep;
+    osep = osepd;
+    if (sep)
+      osepl = 1;
+  } else if (nargs == 4) {
+    sort_type = get_list_type_noauto(args, nargs, 4);
+    if (sort_type == UNKNOWN_LIST) {
+      sort_type = ALPHANUM_LIST;
+      osep = args[3];
+      osepl = arglens[3];
+    } else {
+      osepd[0] = sep;
+      osep = osepd;
+      if (sep)
+      osepl = 1;
+    }
+  } else if (nargs == 5) {
+    sort_type = get_list_type(args, nargs, 4, a1, n1);
+    osep = args[4];
+    osepl = arglens[4];
+  }
+
+  /* sort each array */
+  do_gensort(a1, n1, sort_type);
+  do_gensort(a2, n2, sort_type);
+
+  /* get the first value for the difference, removing duplicates */
+  x1 = x2 = 0;
+  while ((val = gencomp(a1[x1], a2[x2], sort_type)) >= 0) {
+    if (val > 0) {
+      x2++;
+      if (x2 >= n2)
+       break;
+    }
+    if (!val) {
+      x1++;
+      if (x1 >= n1) {
+       mush_free((Malloc_t) a1, "ptrarray");
+       mush_free((Malloc_t) a2, "ptrarray");
+       return;
+      }
+    }
+  }
+  safe_str(a1[x1], buff, bp);
+  do {
+    x1++;
+    if (x1 >= n1) {
+      mush_free((Malloc_t) a1, "ptrarray");
+      mush_free((Malloc_t) a2, "ptrarray");
+      return;
+    }
+  } while (!gencomp(a1[x1], a1[x1 - 1], sort_type));
+
+  /* get values for the difference, until at least one list is empty */
+  while (x2 < n2) {
+    if ((val = gencomp(a1[x1], a2[x2], sort_type)) < 0) {
+      safe_strl(osep, osepl, buff, bp);
+      safe_str(a1[x1], buff, bp);
+    }
+    if (val <= 0) {
+      do {
+       x1++;
+       if (x1 >= n1) {
+         mush_free((Malloc_t) a1, "ptrarray");
+         mush_free((Malloc_t) a2, "ptrarray");
+         return;
+       }
+      } while (!gencomp(a1[x1], a1[x1 - 1], sort_type));
+    }
+    if (val >= 0)
+      x2++;
+  }
+
+  /* empty out remaining values, still removing duplicates */
+  while (x1 < n1) {
+    safe_strl(osep, osepl, buff, bp);
+    safe_str(a1[x1], buff, bp);
+    do {
+      x1++;
+    } while ((x1 < n1) && !gencomp(a1[x1], a1[x1 - 1], sort_type));
+  }
+  mush_free((Malloc_t) a1, "ptrarray");
+  mush_free((Malloc_t) a2, "ptrarray");
+}
+
+#define CACHE_SIZE 8  /**< Maximum size of the lnum cache */
+
+/* ARGSUSED */
+FUNCTION(fun_lnum)
+{
+  NVAL j;
+  NVAL start;
+  NVAL end;
+  int istart, iend, k;
+  char const *osep = " ";
+  static NVAL cstart[CACHE_SIZE];
+  static NVAL cend[CACHE_SIZE];
+  static char csep[CACHE_SIZE][BUFFER_LEN];
+  static char cresult[CACHE_SIZE][BUFFER_LEN];
+  static int cpos;
+  char *cp;
+
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  end = parse_number(args[0]);
+  if (nargs > 1) {
+    if (!is_number(args[1])) {
+      safe_str(T(e_num), buff, bp);
+      return;
+    }
+    start = end;
+    end = parse_number(args[1]);
+    if ((start == 0) && (end == 0)) {
+      safe_str("0", buff, bp); /* Special case - lnum(0,0) -> 0 */
+      return;
+    }
+  } else {
+    if (end == 0)
+      return;                  /* Special case - lnum(0) -> blank string */
+    else if (end == 1) {
+      safe_str("0", buff, bp); /* Special case - lnum(1) -> 0 */
+      return;
+    }
+    end--;
+    if (end < 0) {
+      safe_str(T("#-1 NUMBER OUT OF RANGE"), buff, bp);
+      return;
+    }
+    start = 0;
+  }
+  if (nargs > 2) {
+    osep = args[2];
+  }
+  for (k = 0; k < CACHE_SIZE; k++) {
+    if (cstart[k] == start && cend[k] == end && !strcmp(csep[k], osep)) {
+      safe_str(cresult[k], buff, bp);
+      return;
+    }
+  }
+  cpos = (cpos + 1) % CACHE_SIZE;
+  cstart[cpos] = start;
+  cend[cpos] = end;
+  strcpy(csep[cpos], osep);
+  cp = cresult[cpos];
+
+  istart = (int) start;
+  iend = (int) end;
+  if (istart == start && iend == end) {
+    safe_integer(istart, cresult[cpos], &cp);
+    if (istart <= iend) {
+      for (k = istart + 1; k <= iend; k++) {
+       safe_str(osep, cresult[cpos], &cp);
+       if (safe_integer(k, cresult[cpos], &cp))
+         break;
+      }
+    } else {
+      for (k = istart - 1; k >= iend; k--) {
+       safe_str(osep, cresult[cpos], &cp);
+       if (safe_integer(k, cresult[cpos], &cp))
+         break;
+      }
+    }
+  } else {
+    safe_number(start, cresult[cpos], &cp);
+    if (start <= end) {
+      for (j = start + 1; j <= end; j++) {
+       safe_str(osep, cresult[cpos], &cp);
+       if (safe_number(j, cresult[cpos], &cp))
+         break;
+      }
+    } else {
+      for (j = start - 1; j >= end; j--) {
+       safe_str(osep, cresult[cpos], &cp);
+       if (safe_number(j, cresult[cpos], &cp))
+         break;
+      }
+    }
+  }
+  *cp = '\0';
+
+  safe_str(cresult[cpos], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_first)
+{
+  /* read first word from a string */
+
+  char *p;
+  char sep;
+
+  if (!*args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  p = trim_space_sep(args[0], sep);
+  safe_str(split_token(&p, sep), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_randword)
+{
+  char *s, *r;
+  char sep;
+  int word_count, word_index;
+
+  if (!*args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  s = trim_space_sep(args[0], sep);
+  word_count = do_wordcount(s, sep);
+  word_index = get_random_long(0, word_count - 1);
+
+  /* Go to the start of the token we're interested in. */
+  while (word_index && s) {
+    s = next_token(s, sep);
+    word_index--;
+  }
+
+  if (!s || !*s)               /* ran off the end of the string */
+    return;
+
+  /* Chop off the end, and copy. No length checking needed. */
+  r = s;
+  if (s && *s)
+    (void) split_token(&s, sep);
+  safe_str(r, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_rest)
+{
+  char *p;
+  char sep;
+
+  if (!*args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  p = trim_space_sep(args[0], sep);
+  (void) split_token(&p, sep);
+  safe_str(p, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_last)
+{
+  /* read last word from a string */
+
+  char *p, *r;
+  char sep;
+
+  if (!*args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  p = trim_space_sep(args[0], sep);
+  if (!(r = strrchr(p, sep)))
+    r = p;
+  else
+    r++;
+  safe_str(r, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_grab)
+{
+  /* compares two strings with possible wildcards, returns the
+   * word matched. Based on the 2.2 version of this function.
+   */
+
+  char *r, *s, sep;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  /* Walk the wordstring, until we find the word we want. */
+  s = trim_space_sep(args[0], sep);
+  do {
+    r = split_token(&s, sep);
+    if (quick_wild(args[1], r)) {
+      safe_str(r, buff, bp);
+      return;
+    }
+  } while (s);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_match)
+{
+  /* compares two strings with possible wildcards, returns the
+   * word position of the match. Based on the 2.0 version of this
+   * function.
+   */
+
+  char *s, *r;
+  char sep;
+  int wcount = 1;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  /* Walk the wordstring, until we find the word we want. */
+  s = trim_space_sep(args[0], sep);
+  do {
+    r = split_token(&s, sep);
+    if (quick_wild(args[1], r)) {
+      safe_integer(wcount, buff, bp);
+      return;
+    }
+    wcount++;
+  } while (s);
+  safe_chr('0', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_wordpos)
+{
+  int charpos, i;
+  char *cp, *tp, *xp;
+  char sep;
+
+  if (!is_integer(args[1])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  charpos = parse_integer(args[1]);
+  cp = args[0];
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if ((charpos <= 0) || ((size_t) charpos > strlen(cp))) {
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  tp = cp + charpos - 1;
+  cp = trim_space_sep(cp, sep);
+  xp = split_token(&cp, sep);
+  for (i = 1; xp; i++) {
+    if (tp < (xp + strlen(xp)))
+      break;
+    xp = split_token(&cp, sep);
+  }
+  safe_integer(i, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_extract)
+{
+  char sep;
+  int start, len;
+  char *s, *r;
+
+  if (!is_integer(args[1]) || !is_integer(args[2])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+  s = args[0];
+  start = parse_integer(args[1]);
+  len = parse_integer(args[2]);
+  if (!delim_check(buff, bp, nargs, args, 4, &sep))
+    return;
+
+  if ((start < 1) || (len < 1))
+    return;
+
+  /* Go to the start of the token we're interested in. */
+  start--;
+  s = trim_space_sep(s, sep);
+  while (start && s) {
+    s = next_token(s, sep);
+    start--;
+  }
+
+  if (!s || !*s)               /* ran off the end of the string */
+    return;
+
+  /* Find the end of the string that we want. */
+  r = s;
+  len--;
+  while (len && s) {
+    s = next_token(s, sep);
+    len--;
+  }
+
+  /* Chop off the end, and copy. No length checking needed. */
+  if (s && *s)
+    (void) split_token(&s, sep);
+  safe_str(r, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cat)
+{
+  int i;
+
+  safe_strl(args[0], arglens[0], buff, bp);
+  for (i = 1; i < nargs; i++) {
+    safe_chr(' ', buff, bp);
+    safe_strl(args[i], arglens[i], buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_remove)
+{
+  char sep;
+
+  /* zap word from string */
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  if (strchr(args[1], sep)) {
+    safe_str(T("#-1 CAN ONLY DELETE ONE ELEMENT"), buff, bp);
+    return;
+  }
+  safe_str(remove_word(args[0], args[1], sep), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_items)
+{
+  /* the equivalent of WORDS for an arbitrary separator */
+  /* This differs from WORDS in its treatment of the space
+   * separator.
+   */
+
+  char *s = args[0];
+  char c = *args[1];
+  int count = 1;
+
+  if (c == '\0')
+    c = ' ';
+
+  while ((s = strchr(s, c))) {
+    count++;
+    s++;
+  }
+
+  safe_integer(count, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_element)
+{
+  /* the equivalent of MEMBER for an arbitrary separator */
+  /* This differs from MEMBER in its use of quick_wild()
+   * instead of strcmp().
+   */
+
+  char *s, *t;
+  char c;
+  int el;
+
+  c = *args[2];
+
+  if (c == '\0')
+    c = ' ';
+  if (strchr(args[1], c)) {
+    safe_str(T("#-1 CAN ONLY TEST ONE ELEMENT"), buff, bp);
+    return;
+  }
+  s = args[0];
+  el = 1;
+
+  do {
+    t = s;
+    s = seek_char(t, c);
+    if (*s)
+      *s++ = '\0';
+    if (quick_wild(args[1], t)) {
+      safe_integer(el, buff, bp);
+      return;
+    }
+    el++;
+  } while (*s);
+
+  safe_chr('0', buff, bp);     /* no match */
+}
+
+/* ARGSUSED */
+FUNCTION(fun_index)
+{
+  /* more or less the equivalent of EXTRACT for an arbitrary separator */
+  /* This differs from EXTRACT in its handling of space separators. */
+
+  int start, end;
+  char c;
+  char *s, *p;
+
+  if (!is_integer(args[2]) || !is_integer(args[3])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+  s = args[0];
+  c = *args[1];
+  if (!c)
+    c = ' ';
+
+  start = parse_integer(args[2]);
+  end = parse_integer(args[3]);
+
+  if ((start < 1) || (end < 1) || (*s == '\0'))
+    return;
+
+  /* move s to the start of the item we want */
+  while (--start) {
+    if (!(s = strchr(s, c)))
+      return;
+    s++;
+  }
+
+  /* skip just spaces, not tabs or newlines, since people may MUSHcode things
+   * like "%r%tPolgara %r%tDurnik %r%tJavelin"
+   */
+  while (*s == ' ')
+    s++;
+  if (!*s)
+    return;
+
+  /* now figure out where to end the string */
+  p = s + 1;
+  /* we may already be pointing to a separator */
+  if (*s == c)
+    end--;
+  while (end--)
+    if (!(p = strchr(p, c)))
+      break;
+    else
+      p++;
+
+  if (p)
+    p--;
+  else
+    p = s + strlen(s);
+
+  /* trim trailing spaces (just true spaces) */
+  while ((p > s) && (p[-1] == ' '))
+    p--;
+  *p = '\0';
+
+  safe_str(s, buff, bp);
+}
+
+/** Functions that operate on items - delete, replace, insert.
+ * \param buff return buffer.
+ * \param bp pointer to insertion point in buff.
+ * \param str original string.
+ * \param num string containing the element number to operate on.
+ * \param word string to insert/delete/replace.
+ * \param sep separator string.
+ * \param flag operation to perform: 0 - delete, 1 - replace, 2 - insert
+ */
+static void
+do_itemfuns(char *buff, char **bp, char *str, char *num, char *word,
+           char *sep, int flag)
+{
+  char c;
+  int el, count, len = -1;
+  char *sptr, *eptr;
+
+  if (!is_integer(num)) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  el = parse_integer(num);
+
+  /* figure out the separator character */
+  if (sep && *sep)
+    c = *sep;
+  else
+    c = ' ';
+
+  /* we can't remove anything before the first position */
+  if ((el < 1 && flag != 2) || el == 0) {
+    safe_str(str, buff, bp);
+    return;
+  }
+  if (el < 0) {
+    sptr = str + strlen(str);
+    eptr = sptr;
+  } else {
+    sptr = str;
+    eptr = strchr(sptr, c);
+  }
+  count = 1;
+
+  /* go to the correct item in the string */
+  if (el < 0) {                        /* if using insert() with a negative insertion param */
+    /* count keeps track of the number of words from the right
+     * of the string.  When count equals the correct position, then
+     * sptr will point to the count'th word from the right, or
+     * a null string if the  word being added will be at the end of
+     * the string.
+     * eptr is just a helper.  */
+    for (len = strlen(str); len >= 0 && count < abs(el); len--, eptr--) {
+      if (*eptr == c)
+       count++;
+      if (count == abs(el)) {
+       sptr = eptr + 1;
+       break;
+      }
+    }
+  } else {
+    /* Loop invariant: if sptr and eptr are not NULL, eptr points to
+     * the count'th instance of c in str, and sptr is the beginning of
+     * the count'th item. */
+    while (eptr && (count < el)) {
+      sptr = eptr + 1;
+      eptr = strchr(sptr, c);
+      count++;
+    }
+  }
+
+  if ((!eptr || len < 0) && (count < abs(el))) {
+    /* we've run off the end of the string without finding anything */
+    safe_str(str, buff, bp);
+    return;
+  }
+  /* now find the end of that element */
+  if ((el < 0 && *eptr) || (el > 0 && sptr != str))
+    sptr[-1] = '\0';
+
+  switch (flag) {
+  case 0:
+    /* deletion */
+    if (!eptr) {               /* last element in the string */
+      if (el != 1)
+       safe_str(str, buff, bp);
+    } else if (sptr == str) {  /* first element in the string */
+      eptr++;                  /* chop leading separator */
+      safe_str(eptr, buff, bp);
+    } else {
+      safe_str(str, buff, bp);
+      safe_str(eptr, buff, bp);
+    }
+    break;
+  case 1:
+    /* replacing */
+    if (!eptr) {               /* last element in string */
+      if (el != 1) {
+       safe_str(str, buff, bp);
+       safe_chr(c, buff, bp);
+      }
+      safe_str(word, buff, bp);
+    } else if (sptr == str) {  /* first element in string */
+      safe_str(word, buff, bp);
+      safe_str(eptr, buff, bp);
+    } else {
+      safe_str(str, buff, bp);
+      safe_chr(c, buff, bp);
+      safe_str(word, buff, bp);
+      safe_str(eptr, buff, bp);
+    }
+    break;
+  case 2:
+    /* insertion */
+    if (sptr == str) {         /* first element in string */
+      safe_str(word, buff, bp);
+      safe_chr(c, buff, bp);
+      safe_str(str, buff, bp);
+    } else {
+      safe_str(str, buff, bp);
+      safe_chr(c, buff, bp);
+      safe_str(word, buff, bp);
+      safe_chr(c, buff, bp);
+      safe_str(sptr, buff, bp);
+    }
+    break;
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_ldelete)
+{
+  /* delete a word at position X of a list */
+
+  do_itemfuns(buff, bp, args[0], args[1], NULL, args[2], 0);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_replace)
+{
+  /* replace a word at position X of a list */
+
+  do_itemfuns(buff, bp, args[0], args[1], args[2], args[3], 1);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_insert)
+{
+  /* insert a word at position X of a list */
+
+  do_itemfuns(buff, bp, args[0], args[1], args[2], args[3], 2);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_member)
+{
+  char *s, *t;
+  char sep;
+  int el;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (strchr(args[1], sep)) {
+    safe_str(T("#-1 CAN ONLY TEST ONE ELEMENT"), buff, bp);
+    return;
+  }
+  s = trim_space_sep(args[0], sep);
+  el = 1;
+
+  do {
+    t = split_token(&s, sep);
+    if (!strcmp(args[1], t)) {
+      safe_integer(el, buff, bp);
+      return;
+    }
+    el++;
+  } while (s);
+
+  safe_chr('0', buff, bp);     /* not found */
+}
+
+/* ARGSUSED */
+FUNCTION(fun_before)
+{
+  char *p;
+
+  if (!*args[1])
+    p = strchr(args[0], ' ');
+  else
+    p = strstr(args[0], args[1]);
+  if (p) {
+    safe_strl(args[0], p - args[0], buff, bp);
+  } else
+    safe_strl(args[0], arglens[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_after)
+{
+  char *p;
+
+  if (!*args[1]) {
+    args[1][0] = ' ';
+    args[1][1] = '\0';
+    arglens[1] = 1;
+  }
+  p = strstr(args[0], args[1]);
+  if (p)
+    safe_str(p + arglens[1], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_revwords)
+{
+  char **words;
+  int count;
+  char sep;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  if (nargs == 3)
+    osep = args[2];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  words = (char **) mush_malloc(sizeof(char *) * BUFFER_LEN, "wordlist");
+
+  count = list2arr(words, BUFFER_LEN, args[0], sep);
+  if (count == 0) {
+    mush_free((Malloc_t) words, "wordlist");
+    return;
+  }
+
+  safe_str(words[--count], buff, bp);
+  while (count) {
+    safe_str(osep, buff, bp);
+    safe_str(words[--count], buff, bp);
+  }
+  mush_free((Malloc_t) words, "wordlist");
+}
+
+/* ARGSUSED */
+FUNCTION(fun_words)
+{
+  char sep;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+  safe_integer(do_wordcount(trim_space_sep(args[0], sep), sep), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_splice)
+{
+  /* like MERGE(), but does it for a word */
+
+  char *s0, *s1, *s2;
+  char *p0, *p1;
+  char sep;
+
+  if (!delim_check(buff, bp, nargs, args, 4, &sep))
+    return;
+
+  s0 = trim_space_sep(args[0], sep);
+  s1 = trim_space_sep(args[1], sep);
+  s2 = trim_space_sep(args[2], sep);
+
+  /* length checks */
+  if (!*args[2]) {
+    safe_str(T("#-1 NEED A WORD"), buff, bp);
+    return;
+  }
+  if (do_wordcount(s2, sep) != 1) {
+    safe_str(T("#-1 TOO MANY WORDS"), buff, bp);
+    return;
+  }
+  if (do_wordcount(s0, sep) != do_wordcount(s1, sep)) {
+    safe_str(T("#-1 NUMBER OF WORDS MUST BE EQUAL"), buff, bp);
+    return;
+  }
+  /* loop through the two lists */
+  p0 = split_token(&s0, sep);
+  p1 = split_token(&s1, sep);
+  safe_str(strcmp(p0, s2) ? p0 : p1, buff, bp);
+  while (s0) {
+    p0 = split_token(&s0, sep);
+    p1 = split_token(&s1, sep);
+    safe_chr(sep, buff, bp);
+    safe_str(strcmp(p0, s2) ? p0 : p1, buff, bp);
+  }
+}
+
+
+FUNCTION(fun_break) {
+  int i;
+
+  if(!args[0] || !*args[0])
+       i = 0;
+
+  if(i != 0 && !is_strict_integer(args[0])) {
+       safe_str(T(e_int), buff, bp);
+       return;
+   }
+
+   if(i != 0) i = parse_integer(args[0]);
+
+   if(i < 0 || i >= inum || (inum - i) <= inum_limit) {
+       safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
+       return;
+   }
+
+   iter_break = i+1;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_iter)
+{
+  /* Based on the TinyMUSH 2.0 code for this function. Please note that
+   * arguments to this function are passed _unparsed_.
+   */
+  /* Actually, this code has changed so much that the above comment
+   * isn't really true anymore. - Talek, 18 Oct 2000
+   */
+
+  char sep;
+  char *outsep, *list;
+  char *tbuf1, *tbuf2, *lp;
+  char const *sp;
+  int *place;
+  int funccount;
+  char *oldbp;
+  const char *replace[2];
+
+
+  if (inum >= MAX_ITERS) {
+    safe_str(T("#-1 TOO MANY ITERS"), buff, bp);
+    return;
+  }
+
+  if (nargs >= 3) {
+    /* We have a delimiter. We've got to parse the third arg in place */
+    char insep[BUFFER_LEN];
+    char *isep = insep;
+    const char *arg3 = args[2];
+    process_expression(insep, &isep, &arg3, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *isep = '\0';
+    strcpy(args[2], insep);
+  }
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  outsep = (char *) mush_malloc(BUFFER_LEN, "string");
+  list = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!outsep || !list)
+    mush_panic("Unable to allocate memory in fun_iter");
+  if (nargs < 4)
+    strcpy(outsep, " ");
+  else {
+    const char *arg4 = args[3];
+    char *osep = outsep;
+    process_expression(outsep, &osep, &arg4, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *osep = '\0';
+  }
+  lp = list;
+  sp = args[0];
+  process_expression(list, &lp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *lp = '\0';
+  lp = trim_space_sep(list, sep);
+  if (!*lp) {
+    mush_free((Malloc_t) outsep, "string");
+    mush_free((Malloc_t) list, "string");
+    return;
+  }
+
+  inum++;
+  place = &iter_place[inum];
+  *place = 0;
+  funccount = pe_info->fun_invocations;
+  oldbp = *bp;
+  while (lp) {
+    if (*place) {
+      safe_str(outsep, buff, bp);
+    }
+    *place = *place + 1;
+    iter_rep[inum] = tbuf1 = split_token(&lp, sep);
+    replace[0] = tbuf1;
+    replace[1] = unparse_integer(*place);
+    tbuf2 = replace_string2(standard_tokens, replace, args[1]);
+    sp = tbuf2;
+    if (process_expression(buff, bp, &sp, executor, caller, enactor,
+                          PE_DEFAULT, PT_DEFAULT, pe_info))
+      break;
+    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
+      break;
+    funccount = pe_info->fun_invocations;
+    oldbp = *bp;
+    mush_free((Malloc_t) tbuf2, "replace_string.buff");
+    if(iter_break > 0) { 
+      iter_break--;
+      break;
+    }
+  }
+  *place = 0;
+  iter_rep[inum] = NULL;
+  inum--;
+  mush_free((Malloc_t) outsep, "string");
+  mush_free((Malloc_t) list, "string");
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ilev)
+{
+  safe_integer(inum - 1, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_itext)
+{
+  int i;
+
+  if (!is_strict_integer(args[0])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  i = parse_integer(args[0]);
+
+  if (i < 0 || i >= inum || (inum - i) <= inum_limit) {
+    safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
+    return;
+  }
+
+  safe_str(iter_rep[inum - i], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_inum)
+{
+  int i;
+
+  if (!is_strict_integer(args[0])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  i = parse_integer(args[0]);
+
+  if (i < 0 || i >= inum || (inum - i) <= inum_limit) {
+    safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
+    return;
+  }
+
+  safe_number(iter_place[inum - i], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_step)
+{
+  /* Like map, but passes up to 10 elements from the list at a time in %0-%9
+   * If the attribute is not found, null is returned, NOT an error.
+   * This function takes delimiters.
+   */
+
+  dbref thing;
+  ATTR *attrib;
+  char *preserve[10];
+  char const *ap;
+  char *asave, *lp;
+  char sep;
+  int n;
+  int step;
+  int funccount;
+  char *oldbp;
+  char *osep, osepd[2] = { '\0', '\0' };
+  int pe_flags = PE_DEFAULT;
+
+  if (!is_integer(args[2])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+
+  step = parse_integer(args[2]);
+
+  if (step < 1 || step > 10) {
+    safe_str(T("#-1 STEP OUT OF RANGE"), buff, bp);
+    return;
+  }
+
+  if (!delim_check(buff, bp, nargs, args, 4, &sep))
+    return;
+
+  if (nargs == 5)
+    osep = args[4];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  lp = trim_space_sep(args[1], sep);
+  if (!*lp)
+    return;
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (AF_Debug(attrib))
+    pe_flags |= PE_DEBUG;
+
+  asave = safe_atr_value(attrib);
+
+  /* save our stack */
+  save_global_env("step", preserve);
+
+  for (n = 0; n < step; n++) {
+    global_eval_context.wenv[n] = split_token(&lp, sep);
+    if (!lp) {
+      n++;
+      break;
+    }
+  }
+  for (; n < 10; n++)
+    global_eval_context.wenv[n] = NULL;
+
+  ap = asave;
+  process_expression(buff, bp, &ap, thing, executor, enactor,
+                    pe_flags, PT_DEFAULT, pe_info);
+  oldbp = *bp;
+  funccount = pe_info->fun_invocations;
+  while (lp) {
+    safe_str(osep, buff, bp);
+    for (n = 0; n < step; n++) {
+      global_eval_context.wenv[n] = split_token(&lp, sep);
+      if (!lp) {
+       n++;
+       break;
+      }
+    }
+    for (; n < 10; n++)
+      global_eval_context.wenv[n] = NULL;
+    ap = asave;
+    if (process_expression(buff, bp, &ap, thing, executor, enactor,
+                          pe_flags, PT_DEFAULT, pe_info))
+      break;
+    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
+      break;
+    oldbp = *bp;
+    funccount = pe_info->fun_invocations;
+  }
+
+  free((Malloc_t) asave);
+  free_anon_attrib(attrib);
+  restore_global_env("step", preserve);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_map)
+{
+  /* Like iter(), but calls an attribute with list elements as %0 instead.
+   * If the attribute is not found, null is returned, NOT an error.
+   * This function takes delimiters.
+   */
+
+  dbref thing;
+  ATTR *attrib;
+  char const *ap;
+  char *asave, *lp;
+  char *tptr[2];
+  char place[16];
+  int placenr = 1;
+  char sep;
+  int funccount;
+  char *oldbp;
+  char *osep, osepd[2] = { '\0', '\0' };
+  int pe_flags = PE_DEFAULT;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  lp = trim_space_sep(args[1], sep);
+  if (!*lp)
+    return;
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (AF_Debug(attrib))
+    pe_flags |= PE_DEBUG;
+
+  strcpy(place, "1");
+  asave = safe_atr_value(attrib);
+
+  /* save our stack */
+  tptr[0] = global_eval_context.wenv[0];
+  tptr[1] = global_eval_context.wenv[1];
+  global_eval_context.wenv[1] = place;
+
+  global_eval_context.wenv[0] = split_token(&lp, sep);
+  ap = asave;
+  process_expression(buff, bp, &ap, thing, executor, enactor,
+                    pe_flags, PT_DEFAULT, pe_info);
+  oldbp = *bp;
+  funccount = pe_info->fun_invocations;
+  while (lp) {
+    safe_str(osep, buff, bp);
+    strcpy(place, unparse_integer(++placenr));
+    global_eval_context.wenv[0] = split_token(&lp, sep);
+    ap = asave;
+    if (process_expression(buff, bp, &ap, thing, executor, enactor,
+                          pe_flags, PT_DEFAULT, pe_info))
+      break;
+    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
+      break;
+    oldbp = *bp;
+    funccount = pe_info->fun_invocations;
+  }
+
+  free((Malloc_t) asave);
+  free_anon_attrib(attrib);
+  global_eval_context.wenv[0] = tptr[0];
+  global_eval_context.wenv[1] = tptr[1];
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_mix)
+{
+  /* Like map(), but goes through lists, passing them as %0 and %1.. %9.
+   * If the attribute is not found, null is returned, NOT an error.
+   * This function takes delimiters.
+   */
+
+  dbref thing;
+  ATTR *attrib;
+  char const *ap;
+  char *asave, *lp[10];
+  char *tptr[10];
+  char sep;
+  int funccount;
+  int n;
+  int lists, words;
+  char *oldbp;
+  int pe_flags = PE_DEFAULT;
+
+  if (nargs > 3) {             /* Last arg must be the delimiter */
+    n = nargs;
+    lists = nargs - 2;
+  } else {
+    n = 4;
+    lists = 2;
+  }
+
+  if (!delim_check(buff, bp, nargs, args, n, &sep))
+    return;
+
+  for (n = 0; n < lists; n++)
+    lp[n] = trim_space_sep(args[n + 1], sep);
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (!CanEvalAttr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  if (AF_Debug(attrib))
+    pe_flags |= PE_DEBUG;
+
+  asave = safe_atr_value(attrib);
+
+  /* save our stack */
+  save_global_env("fun_mix", tptr);
+
+  words = 0;
+  for (n = 0; n < 10; n++) {
+    if ((n < lists) && lp[n] && *lp[n]) {
+      global_eval_context.wenv[n] = split_token(&lp[n], sep);
+      if (global_eval_context.wenv[n])
+       words++;
+    } else
+      global_eval_context.wenv[n] = NULL;
+  }
+  if (words == 0) {
+    restore_global_env("fun_mix", tptr);
+    free((Malloc_t) asave);
+    free_anon_attrib(attrib);
+    return;
+  }
+  ap = asave;
+  process_expression(buff, bp, &ap, thing, executor, enactor,
+                    pe_flags, PT_DEFAULT, pe_info);
+  oldbp = *bp;
+  funccount = pe_info->fun_invocations;
+  while (1) {
+    words = 0;
+    for (n = 0; n < 10; n++) {
+      if ((n < lists) && lp[n] && *lp[n]) {
+       global_eval_context.wenv[n] = split_token(&lp[n], sep);
+       if (global_eval_context.wenv[n])
+         words++;
+      } else
+       global_eval_context.wenv[n] = NULL;
+    }
+    if (words == 0)
+      break;
+    safe_chr(sep, buff, bp);
+    ap = asave;
+    if (process_expression(buff, bp, &ap, thing, executor, enactor,
+                          pe_flags, PT_DEFAULT, pe_info))
+      break;
+    if (*bp == (buff + BUFFER_LEN - 1) && pe_info->fun_invocations == funccount)
+      break;
+    oldbp = *bp;
+    funccount = pe_info->fun_invocations;
+  }
+
+  free((Malloc_t) asave);
+  free_anon_attrib(attrib);
+  restore_global_env("fun_mix", tptr);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_table)
+{
+  /* TABLE(list, field_width, line_length, delimiter, output sep)
+   * Given a list, produce a table (a column'd list)
+   * Optional parameters: field width, line length, delimiter, output sep
+   * Number of columns = line length / (field width+1)
+   */
+  size_t line_length = 78;
+  size_t field_width = 10;
+  size_t col = 0;
+  size_t offset, col_len;
+  char sep, osep, *cp, *t;
+  ansi_string *as;
+
+  if (!delim_check(buff, bp, nargs, args, 5, &osep))
+    return;
+  if ((nargs == 5) && !*args[4])
+    osep = 0;
+
+  if (!delim_check(buff, bp, nargs, args, 4, &sep))
+    return;
+
+  if (nargs > 2) {
+    if (!is_integer(args[2])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    line_length = parse_integer(args[2]);
+    if (line_length < 2)
+      line_length = 2;
+  }
+  if (nargs > 1) {
+    if (!is_integer(args[1])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    field_width = parse_integer(args[1]);
+    if (field_width < 1)
+      field_width = 1;
+    if (field_width >= BUFFER_LEN)
+      field_width = BUFFER_LEN - 1;
+  }
+  if (field_width >= line_length)
+    field_width = line_length - 1;
+
+  /* Split out each token, truncate/pad it to field_width, and pack
+   * it onto the line. When the line would go over line_length,
+   * send a return
+   */
+
+  as = parse_ansi_string(args[0]);
+
+  cp = trim_space_sep(as->text, sep);
+  if (!*cp) {
+    free_ansi_string(as);
+    return;
+  }
+
+  t = split_token(&cp, sep);
+  offset = t - &as->text[0];
+  col_len = strlen(t);
+  if (col_len > field_width)
+    col_len = field_width;
+  safe_ansi_string(as, offset, col_len, buff, bp);
+  if (safe_fill(' ', field_width - col_len, buff, bp)) {
+    free_ansi_string(as);
+    return;
+  }
+  col = field_width + !!osep;
+  while (cp) {
+    col += field_width + !!osep;
+    if (col > line_length) {
+      if (NEWLINE_ONE_CHAR)
+       safe_str("\n", buff, bp);
+      else
+       safe_str("\r\n", buff, bp);
+      col = field_width + !!osep;
+    } else {
+      if (osep)
+       safe_chr(osep, buff, bp);
+    }
+    t = split_token(&cp, sep);
+    if (!t)
+      break;
+    offset = t - &as->text[0];
+    col_len = strlen(t);
+    if (col_len > field_width)
+      col_len = field_width;
+    safe_ansi_string(as, offset, col_len, buff, bp);
+    if (safe_fill(' ', field_width - col_len, buff, bp))
+      break;
+  }
+  free_ansi_string(as);
+}
+
+/* In the following regexp functions, we use pcre_study to potentially
+ * make pcre_exec faster. If pcre_study() can't help, it returns right
+ * away, and if it can, the savings in the actual matching are usually
+ * worth it.  Ideally, all compiled regexps and their study patterns
+ * should be cached somewhere. Especially nice for patterns in the
+ * master room. Just need to come up with a good caching algorithm to
+ * use. Easiest would be a hashtable that's just cleared every
+ * dbck_interval seconds. Except some benchmarking showed that compiling
+ * patterns is faster than I thought it'd be, so this is low priority.
+ */
+
+/* string, regexp, replacement string. Acts like sed or perl's s///g,
+//with an ig version */
+int re_subpatterns = -1;  /**< Number of subpatterns in regexp */
+int *re_offsets;         /**< Array of offsets to subpatterns */
+char *re_from = NULL;    /**< Pointer to last match position */
+FUNCTION(fun_regreplace)
+{
+  pcre *re;
+  pcre_extra *study = NULL;
+  const char *errptr;
+  int subpatterns;
+  int offsets[99];
+  int erroffset;
+  const char *r, *obp;
+  char *start, *oldbp;
+  char tbuf[BUFFER_LEN], *tbp;
+  char abuf[BUFFER_LEN], *abp;
+  char prebuf[BUFFER_LEN], *prep;
+  char postbuf[BUFFER_LEN], *postp;
+
+  int flags = 0, all = 0, match_offset = 0, len, funccount;
+  int i;
+
+  if (called_as[strlen(called_as) - 1] == 'I')
+    flags = PCRE_CASELESS;
+
+  if (string_prefix(called_as, "REGEDITALL"))
+    all = 1;
+
+  abp = abuf;
+  r = args[0];
+  process_expression(abuf, &abp, &r, executor, caller, enactor, PE_DEFAULT,
+                    PT_DEFAULT, pe_info);
+  *abp = '\0';
+
+  postp = postbuf;
+  safe_str(abuf, postbuf, &postp);
+  *postp = '\0';
+
+  for (i = 1; i < nargs - 1; i += 2) {
+    /* old postbuf is new prebuf */
+    prep = prebuf;
+    safe_str(postbuf, prebuf, &prep);
+    *prep = '\0';
+    postp = postbuf;
+    *postp = '\0';
+    tbp = tbuf;
+    r = args[i];
+    process_expression(tbuf, &tbp, &r, executor, caller, enactor, PE_DEFAULT,
+                      PT_DEFAULT, pe_info);
+    *tbp = '\0';
+
+    if ((re = pcre_compile(tbuf, flags, &errptr, &erroffset, tables)) == NULL) {
+      /* Matching error. */
+      safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+      safe_str(errptr, buff, bp);
+      return;
+    }
+    add_check("pcre");
+    /* If we're going to match the pattern multiple times, let's
+       study it. */
+    if (all) {
+      study = pcre_study(re, 0, &errptr);
+      if (errptr != NULL) {
+       mush_free((Malloc_t) re, "pcre");
+       safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+       safe_str(errptr, buff, bp);
+       return;
+      }
+      if (study != NULL)
+       add_check("pcre.extra");
+    }
+    len = strlen(prebuf);
+    start = prebuf;
+    subpatterns = pcre_exec(re, study, prebuf, len, 0, 0, offsets, 99);
+
+    /* Match wasn't found... we're done */
+    if (subpatterns < 0) {
+      safe_str(prebuf, postbuf, &postp);
+      mush_free((Malloc_t) re, "pcre");
+      if (study)
+       mush_free((Malloc_t) study, "pcre.extra");
+      continue;
+    }
+
+    funccount = pe_info->fun_invocations;
+    oldbp = postp;
+
+    do {
+      /* Copy up to the start of the matched area */
+      char tmp = prebuf[offsets[0]];
+      prebuf[offsets[0]] = '\0';
+      safe_str(start, postbuf, &postp);
+      prebuf[offsets[0]] = tmp;
+
+      /* Now copy in the replacement, putting in captured sub-expressions */
+      obp = args[i + 1];
+      re_from = prebuf;
+      re_offsets = offsets;
+      re_subpatterns = subpatterns;
+      process_expression(postbuf, &postp, &obp, executor, caller, enactor,
+                        PE_DEFAULT | PE_DOLLAR, PT_DEFAULT, pe_info);
+      if ((*bp == (buff + BUFFER_LEN - 1))
+         && (pe_info->fun_invocations == funccount))
+       break;
+
+      oldbp = postp;
+      funccount = pe_info->fun_invocations;
+
+      start = prebuf + offsets[1];
+      match_offset = offsets[1];
+      /* Make sure we advance at least 1 char */
+      if (offsets[0] == match_offset)
+       match_offset++;
+    } while (all && match_offset < len && (subpatterns =
+                                          pcre_exec(re, study, prebuf, len,
+                                                    match_offset, 0, offsets,
+                                                    99)) >= 0);
+
+
+    /* Now copy everything after the matched bit */
+    safe_str(start, postbuf, &postp);
+    *postp = '\0';
+    mush_free((Malloc_t) re, "pcre");
+    if (study)
+      mush_free((Malloc_t) study, "pcre.extra");
+
+    re_offsets = NULL;
+    re_subpatterns = -1;
+    re_from = NULL;
+  }
+
+  safe_str(postbuf, buff, bp);
+}
+
+/** array of indexes for %q registers during regexp matching */
+extern signed char qreg_indexes[UCHAR_MAX + 1];
+
+FUNCTION(fun_regmatch)
+{
+/* ---------------------------------------------------------------------------
+ * fun_regmatch Return 0 or 1 depending on whether or not a regular
+ * expression matches a string. If a third argument is specified, dump
+ * the results of a regexp pattern match into a set of r()-registers.
+ *
+ * regmatch(string, pattern, list of registers)
+ * If the number of matches exceeds the registers, those bits are tossed
+ * out.
+ * If -1 is specified as a register number, the matching bit is tossed.
+ * Therefore, if the list is "-1 0 3 5", the regexp $0 is tossed, and
+ * the regexp $1, $2, and $3 become r(0), r(3), and r(5), respectively.
+ *
+ */
+  int i, nqregs, curq;
+  char *qregs[NUMQ];
+  pcre *re;
+  const char *errptr;
+  int erroffset;
+  int offsets[99];
+  int subpatterns;
+  int flags = 0;
+  int qindex;
+
+  if (strcmp(called_as, "REGMATCHI") == 0)
+    flags = PCRE_CASELESS;
+
+  if (nargs == 2) {            /* Don't care about saving sub expressions */
+    safe_boolean(quick_regexp_match(args[1], args[0], flags ? 0 : 1), buff, bp);
+    return;
+  }
+
+  if ((re = pcre_compile(args[1], flags, &errptr, &erroffset, tables)) == NULL) {
+    /* Matching error. */
+    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+    safe_str(errptr, buff, bp);
+    return;
+  }
+  add_check("pcre");
+  subpatterns = pcre_exec(re, NULL, args[0], arglens[0], 0, 0, offsets, 99);
+  safe_integer(subpatterns >= 0, buff, bp);
+
+  /* We need to parse the list of registers.  Anything that we don't parse
+   * is assumed to be -1.  If we didn't match, or the match went wonky,
+   * then set the register to empty.  Otherwise, fill the register with
+   * the subexpression.
+   */
+  if (subpatterns == 0)
+    subpatterns = 33;
+  nqregs = list2arr(qregs, NUMQ, args[2], ' ');
+  for (i = 0; i < nqregs; i++) {
+    if (qregs[i] && qregs[i][0] && !qregs[i][1] &&
+       ((qindex = qreg_indexes[(unsigned char) qregs[i][0]]) != -1))
+      curq = qindex;
+    else
+      curq = -1;
+    if (curq < 0 || curq >= NUMQ)
+      continue;
+    if (subpatterns < 0)
+      global_eval_context.renv[curq][0] = '\0';
+    else
+      pcre_copy_substring(args[0], offsets, subpatterns, i, global_eval_context.renv[curq],
+                         BUFFER_LEN);
+  }
+  mush_free((Malloc_t) re, "pcre");
+}
+
+
+/** Structure to hold data for regrep */
+struct regrep_data {
+  pcre *re;            /**< Pointer to compiled regular expression */
+  pcre_extra *study;   /**< Pointer to studied data about re */
+  char *buff;          /**< Buffer to store regrep results */
+  char **bp;           /**< Pointer to address of insertion point in buff */
+  int first;           /**< Is this the first match or a later match? */
+};
+
+/* Like grep(), but using a regexp pattern. This same function handles
+ *  both regrep and regrepi. */
+FUNCTION(fun_regrep)
+{
+  struct regrep_data reharg;
+  const char *errptr;
+  int erroffset;
+  int flags = 0;
+
+  dbref it = match_thing(executor, args[0]);
+  reharg.first = 0;
+  if (it == NOTHING || it == AMBIGUOUS) {
+    safe_str(T(e_notvis), buff, bp);
+    return;
+  }
+  /* make sure there's an attribute and a pattern */
+  if (!*args[1]) {
+    safe_str(T("#-1 NO SUCH ATTRIBUTE"), buff, bp);
+    return;
+  }
+  if (!*args[2]) {
+    safe_str(T("#-1 INVALID GREP PATTERN"), buff, bp);
+    return;
+  }
+
+  if (strcmp(called_as, "REGREPI") == 0)
+    flags = PCRE_CASELESS;
+
+  if ((reharg.re = pcre_compile(args[2], flags,
+                               &errptr, &erroffset, tables)) == NULL) {
+    /* Matching error. */
+    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+    safe_str(errptr, buff, bp);
+    return;
+  }
+  add_check("pcre");
+
+  reharg.study = pcre_study(reharg.re, 0, &errptr);
+  if (errptr != NULL) {
+    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+    safe_str(errptr, buff, bp);
+    mush_free(reharg.re, "pcre");
+    return;
+  }
+  if (reharg.study)
+    add_check("pcre.extra");
+
+  reharg.buff = buff;
+  reharg.bp = bp;
+
+  atr_iter_get(executor, it, args[1], 0, regrep_helper, (void *) &reharg);
+  mush_free(reharg.re, "pcre");
+  if (reharg.study)
+    mush_free(reharg.study, "pcre.extra");
+}
+
+static int
+regrep_helper(dbref who __attribute__ ((__unused__)),
+             dbref what __attribute__ ((__unused__)),
+             dbref parent __attribute__ ((__unused__)),
+             char const *name __attribute__ ((__unused__)),
+             ATTR *atr, void *args)
+{
+  struct regrep_data *reharg = args;
+  char const *str;
+  int offsets[99];
+
+  str = atr_value(atr);
+  if (pcre_exec(reharg->re, reharg->study, str, strlen(str), 0, 0, offsets, 99)
+      >= 0) {
+    if (reharg->first != 0)
+      safe_chr(' ', reharg->buff, reharg->bp);
+    else
+      reharg->first = 1;
+    safe_str(AL_NAME(atr), reharg->buff, reharg->bp);
+    return 1;
+  } else
+    return 0;
+}
+
+/* Like grab, but with a regexp pattern. This same function handles
+ *  regrab(), regraball(), and the case-insenstive versions. */
+FUNCTION(fun_regrab)
+{
+  char *r, *s, *b, sep;
+  pcre *re;
+  pcre_extra *study;
+  const char *errptr;
+  int erroffset;
+  int offsets[99];
+  int flags = 0, all = 0;
+  char *osep, osepd[2] = { '\0', '\0' };
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  if (nargs == 4)
+    osep = args[3];
+  else {
+    osepd[0] = sep;
+    osep = osepd;
+  }
+
+  s = trim_space_sep(args[0], sep);
+  b = *bp;
+
+  if (strrchr(called_as, 'I'))
+    flags = PCRE_CASELESS;
+
+  if (string_prefix(called_as, "REGRABALL"))
+    all = 1;
+
+  if ((re = pcre_compile(args[1], flags, &errptr, &erroffset, tables)) == NULL) {
+    /* Matching error. */
+    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+    safe_str(errptr, buff, bp);
+    return;
+  }
+  add_check("pcre");
+
+  study = pcre_study(re, 0, &errptr);
+  if (errptr != NULL) {
+    safe_str(T("#-1 REGEXP ERROR: "), buff, bp);
+    safe_str(errptr, buff, bp);
+    mush_free(re, "pcre");
+    return;
+  }
+  if (study)
+    add_check("pcre.extra");
+
+  do {
+    r = split_token(&s, sep);
+    if (pcre_exec(re, study, r, strlen(r), 0, 0, offsets, 99) >= 0) {
+      if (all && *bp != b)
+       safe_str(osep, buff, bp);
+      safe_str(r, buff, bp);
+      if (!all)
+       break;
+    }
+  } while (s);
+
+  mush_free((Malloc_t) re, "pcre");
+  if (study)
+    mush_free((Malloc_t) study, "pcre.extra");
+}
diff --git a/src/funlocal.dst b/src/funlocal.dst
new file mode 100644 (file)
index 0000000..53c780a
--- /dev/null
@@ -0,0 +1,45 @@
+/*-----------------------------------------------------------------
+ * Local functions
+ *
+ * This file is reserved for local functions that you may wish
+ * to hack into PennMUSH. Read parse.h for information on adding
+ * functions. This file will not be overwritten when you update
+ * to a new distribution, so it's preferable to add new functions
+ * here and leave the other fun*.c files alone.
+ *
+ */
+
+/* Here are some includes you're likely to need or want.
+ * If your functions are doing math, include <math.h>, too.
+ */
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "parse.h"
+#include "confmagic.h"
+#include "boolexp.h"
+#include "function.h"
+
+void local_functions(void);
+
+/* Here you can use the new add_function instead of hacking into function.c
+ * Example included :)
+ */
+
+#ifdef EXAMPLE
+FUNCTION(local_fun_silly)
+{
+  safe_format(buff, bp, "Silly%sSilly", args[0]);
+}
+
+#endif
+
+void
+local_functions()
+{
+#ifdef EXAMPLE
+  function_add("SILLY", local_fun_silly, 1, 1, FN_REG);
+#endif
+}
diff --git a/src/funmath.c b/src/funmath.c
new file mode 100644 (file)
index 0000000..c6a134a
--- /dev/null
@@ -0,0 +1,2518 @@
+/**
+ * \file funmath.c
+ *
+ * \brief Mathematical functions for mushcode.
+ * 
+ *
+ */
+
+#include "copyrite.h"
+
+#include "config.h"
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+#include "conf.h"
+#include "externs.h"
+
+#include "parse.h"
+#include "htab.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#pragma warning( disable : 4761)        /* NJG: disable warning re conversion */
+#endif
+
+#ifndef M_PI
+/** The ratio of the circumference of a circle to its denominator. */
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+static void do_spellnum(char *num, unsigned int len, char **buff,
+                        char ***bp);
+static int nval_sort(const void *, const void *);
+static NVAL find_median(NVAL *, int);
+
+/** Declaration macro for math functions */
+#define MATH_FUNC(func) static void func(char **ptr, int nptr, char *buff, char **bp)
+
+/** Prototype macro for math functions */
+#define MATH_PROTO(func) static void func (char **ptr, int nptr, char *buff, char **bp)
+
+HASHTAB htab_math;   /**< Math function hash table */
+
+/** A math function. */
+typedef struct {
+  const char *name;     /**< Name of the function. */
+  void (*func) (char **, int, char *, char **); /**< Pointer to function code. */
+} MATH;
+
+static void math_hash_insert(const char *, MATH *);
+static MATH *math_hash_lookup(char *);
+static NVAL angle_to_rad(NVAL angle, const char *from);
+static NVAL rad_to_angle(NVAL angle, const char *to);
+static double frac(double v, int *RESTRICT n, int *RESTRICT d,
+                   double error);
+void init_math_hashtab(void);
+
+extern int format_long(long n, char *buff, char **bp, int maxlen,
+                       int base);
+extern int roman_numeral_table[256];
+
+MATH_PROTO(math_add);
+MATH_PROTO(math_sub);
+MATH_PROTO(math_mul);
+MATH_PROTO(math_div);
+MATH_PROTO(math_floordiv);
+MATH_PROTO(math_remainder);
+MATH_PROTO(math_modulo);
+MATH_PROTO(math_min);
+MATH_PROTO(math_max);
+MATH_PROTO(math_and);
+MATH_PROTO(math_nand);
+MATH_PROTO(math_or);
+MATH_PROTO(math_nor);
+MATH_PROTO(math_xor);
+MATH_PROTO(math_band);
+MATH_PROTO(math_bor);
+MATH_PROTO(math_bxor);
+MATH_PROTO(math_fdiv);
+MATH_PROTO(math_mean);
+MATH_PROTO(math_median);
+MATH_PROTO(math_stddev);
+
+
+/* ARGSUSED */
+FUNCTION(fun_romantoarabic) {
+  int arabic;
+
+  arabic = RomanToArabic(args[0]);
+
+  if(arabic != -1)
+    safe_number(arabic, buff, bp);
+  else
+    safe_str(T(e_range), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_arabictoroman) {
+  char *roman_num;
+  
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+
+  roman_num = ArabicToRoman(parse_integer(args[0]));
+
+  if(roman_num == NULL)
+    safe_str(T(e_range), buff, bp);
+  else
+    safe_str(roman_num, buff, bp);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ctu)
+{
+  NVAL angle;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+
+  if (!args[1] || !args[2]) {
+    safe_str(T("#-1 INVALID ANGLE TYPE"), buff, bp);
+    return;
+  }
+  angle = angle_to_rad(parse_number(args[0]), args[1]);
+  safe_number(rad_to_angle(angle, args[2]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_add)
+{
+  math_add(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sub)
+{
+  math_sub(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_mul)
+{
+  math_mul(args, nargs, buff, bp);
+}
+
+/* TO-DO: I have better code for comparing floating-point numbers
+   lying around somewhere. The idea is that numbers that are very
+   close can be 'close enough' to be equal or whatever, without having
+   to be exactly the same. */
+
+/* ARGSUSED */
+FUNCTION(fun_gt)
+{
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  safe_boolean(parse_number(args[0]) > parse_number(args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_gte)
+{
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  safe_boolean(parse_number(args[0]) >= parse_number(args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lt)
+{
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  safe_boolean(parse_number(args[0]) < parse_number(args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lte)
+{
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  safe_boolean(parse_number(args[0]) <= parse_number(args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_eq)
+{
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  safe_boolean(parse_number(args[0]) == parse_number(args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_neq)
+{
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  safe_boolean(parse_number(args[0]) != parse_number(args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_max)
+{
+  math_max(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_min)
+{
+  math_min(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sign)
+{
+  NVAL x;
+
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  x = parse_number(args[0]);
+  if (x == 0)
+    safe_chr('0', buff, bp);
+  else if (x > 0)
+    safe_chr('1', buff, bp);
+  else
+    safe_str("-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_shl)
+{
+  if (!is_uinteger(args[0]) || !is_uinteger(args[1])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+  safe_uinteger(parse_uinteger(args[0]) << parse_uinteger(args[1]), buff,
+                bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_shr)
+{
+  if (!is_uinteger(args[0]) || !is_uinteger(args[1])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+  safe_uinteger(parse_uinteger(args[0]) >> parse_uinteger(args[1]), buff,
+                bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_inc)
+{
+  int num;
+  char *p;
+  /* Handle the case of a pure number */
+  if (is_strict_integer(args[0])) {
+    safe_integer(parse_integer(args[0]) + 1, buff, bp);
+    return;
+  }
+  /* Handle a null string */
+  if (!*args[0]) {
+    safe_str(NULL_EQ_ZERO ? "1" : T("#-1 ARGUMENT MUST END IN AN INTEGER"),
+             buff, bp);
+    return;
+  }
+  p = args[0] + arglens[0] - 1;
+  if (!isdigit((unsigned char) *p)) {
+    if (NULL_EQ_ZERO) {
+      safe_str(args[0], buff, bp);
+      safe_str("1", buff, bp);
+    } else
+      safe_str(T("#-1 ARGUMENT MUST END IN AN INTEGER"), buff, bp);
+    return;
+  }
+  while ((isdigit((unsigned char) *p) || (*p == '-')) && p != args[0]) {
+    if (*p == '-') {
+      p--;
+      break;
+    }
+    p--;
+  }
+  /* p now points to the last non-numeric character in the string
+   * Move it to the first numeric character
+   */
+  p++;
+  num = parse_integer(p) + 1;
+  *p = '\0';
+  safe_str(args[0], buff, bp);
+  safe_integer(num, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_dec)
+{
+  int num;
+  char *p;
+  /* Handle the case of a pure number */
+  if (is_strict_integer(args[0])) {
+    safe_integer(parse_integer(args[0]) - 1, buff, bp);
+    return;
+  }
+  /* Handle a null string */
+  if (!*args[0]) {
+    safe_str(NULL_EQ_ZERO ? "-1" :
+             T("#-1 ARGUMENT MUST END IN AN INTEGER"), buff, bp);
+    return;
+  }
+  p = args[0] + arglens[0] - 1;
+  if (!isdigit((unsigned char) *p)) {
+    if (NULL_EQ_ZERO) {
+      safe_str(args[0], buff, bp);
+      safe_str("-1", buff, bp);
+    } else
+      safe_str(T("#-1 ARGUMENT MUST END IN AN INTEGER"), buff, bp);
+    return;
+  }
+  while ((isdigit((unsigned char) *p) || (*p == '-')) && p != args[0]) {
+    if (*p == '-') {
+      p--;
+      break;
+    }
+    p--;
+  }
+  /* p now points to the last non-numeric character in the string
+   * Move it to the first numeric character
+   */
+  p++;
+  num = parse_integer(p) - 1;
+  *p = '\0';
+  safe_str(args[0], buff, bp);
+  safe_integer(num, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_trunc)
+{
+  /* This function does not have the non-number check because
+   * the help file explicitly states that this function can
+   * be used to turn "101dalmations" into "101".
+   */
+  safe_integer(parse_integer(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_div)
+{
+  math_div(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_floordiv)
+{
+  math_floordiv(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_modulo)
+{
+  math_modulo(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_remainder)
+{
+  math_remainder(args, nargs, buff, bp);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_abs)
+{
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  safe_number(fabs(parse_number(args[0])), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_dist2d)
+{
+  NVAL d1, d2, r;
+
+  if (!is_number(args[0]) || !is_number(args[1]) ||
+      !is_number(args[2]) || !is_number(args[3])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  d1 = parse_number(args[0]) - parse_number(args[2]);
+  d2 = parse_number(args[1]) - parse_number(args[3]);
+  r = d1 * d1 + d2 * d2;
+#ifndef HAS_IEEE_MATH
+  /* You can overflow, which is bad. */
+  if (r < 0) {
+    safe_str(T("#-1 OVERFLOW ERROR"), buff, bp);
+    return;
+  }
+#endif
+  safe_number(sqrt(r), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_dist3d)
+{
+  NVAL d1, d2, d3, r;
+
+  if (!is_number(args[0]) || !is_number(args[1]) ||
+      !is_number(args[2]) || !is_number(args[3]) ||
+      !is_number(args[4]) || !is_number(args[5])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  d1 = parse_number(args[0]) - parse_number(args[3]);
+  d2 = parse_number(args[1]) - parse_number(args[4]);
+  d3 = parse_number(args[2]) - parse_number(args[5]);
+  r = d1 * d1 + d2 * d2 + d3 * d3;
+#ifndef HAS_IEEE_MATH
+  /* You can overflow, which is bad. */
+  if (r < 0) {
+    safe_str(T("#-1 OVERFLOW ERROR"), buff, bp);
+    return;
+  }
+#endif
+  safe_number(sqrt(r), buff, bp);
+}
+
+/* ------------------------------------------------------------------------
+ * Dune's vector functions: VADD, VSUB, VMUL, VCROSS, VMAG, VUNIT, VDIM
+ *  VCRAMER?
+ * Vectors are space-separated numbers.
+ */
+
+/* ARGSUSED */
+FUNCTION(fun_vmax)
+{
+  char *p1, *p2;
+  char *start;
+  char sep;
+  NVAL a, b;
+
+  /* return if a list is empty */
+  if (!args[0] || !args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+  p2 = trim_space_sep(args[1], sep);
+
+  /* return if a list is empty */
+  if (!*p1 || !*p2)
+    return;
+
+  /* max the vectors */
+  start = *bp;
+  a = parse_number(split_token(&p1, sep));
+  b = parse_number(split_token(&p2, sep));
+  safe_number((a > b) ? a : b, buff, bp);
+
+  while (p1 && p2) {
+    safe_chr(sep, buff, bp);
+    a = parse_number(split_token(&p1, sep));
+    b = parse_number(split_token(&p2, sep));
+    safe_number((a > b) ? a : b, buff, bp);
+  }
+
+  /* make sure vectors were the same length */
+  if (p1 || p2) {
+    *bp = start;
+    safe_str(T("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bp);
+    return;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_vmin)
+{
+  char *p1, *p2;
+  char *start;
+  char sep;
+  NVAL a, b;
+
+  /* return if a list is empty */
+  if (!args[0] || !args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+  p2 = trim_space_sep(args[1], sep);
+
+  /* return if a list is empty */
+  if (!*p1 || !*p2)
+    return;
+
+  /* max the vectors */
+  start = *bp;
+  a = parse_number(split_token(&p1, sep));
+  b = parse_number(split_token(&p2, sep));
+  safe_number((a < b) ? a : b, buff, bp);
+
+  while (p1 && p2) {
+    safe_chr(sep, buff, bp);
+    a = parse_number(split_token(&p1, sep));
+    b = parse_number(split_token(&p2, sep));
+    safe_number((a < b) ? a : b, buff, bp);
+  }
+
+  /* make sure vectors were the same length */
+  if (p1 || p2) {
+    *bp = start;
+    safe_str(T("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bp);
+    return;
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_vadd)
+{
+  char *p1, *p2;
+  char *start;
+  char sep;
+
+  /* return if a list is empty */
+  if (!args[0] || !args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+  p2 = trim_space_sep(args[1], sep);
+
+  /* return if a list is empty */
+  if (!*p1 || !*p2)
+    return;
+
+  /* add the vectors */
+  start = *bp;
+  safe_number(parse_number(split_token(&p1, sep)) +
+              parse_number(split_token(&p2, sep)), buff, bp);
+  while (p1 && p2) {
+    safe_chr(sep, buff, bp);
+    safe_number(parse_number(split_token(&p1, sep)) +
+                parse_number(split_token(&p2, sep)), buff, bp);
+  }
+
+  /* make sure vectors were the same length */
+  if (p1 || p2) {
+    *bp = start;
+    safe_str(T("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bp);
+    return;
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_vsub)
+{
+  char *p1, *p2;
+  char *start;
+  char sep;
+
+  /* return if a list is empty */
+  if (!args[0] || !args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+  p2 = trim_space_sep(args[1], sep);
+
+  /* return if a list is empty */
+  if (!*p1 || !*p2)
+    return;
+
+  /* subtract the vectors */
+  start = *bp;
+  safe_number(parse_number(split_token(&p1, sep)) -
+              parse_number(split_token(&p2, sep)), buff, bp);
+  while (p1 && p2) {
+    safe_chr(sep, buff, bp);
+    safe_number(parse_number(split_token(&p1, sep)) -
+                parse_number(split_token(&p2, sep)), buff, bp);
+  }
+
+  /* make sure vectors were the same length */
+  if (p1 || p2) {
+    *bp = start;
+    safe_str(T("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bp);
+    return;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_vmul)
+{
+  NVAL e1, e2;
+  char *p1, *p2;
+  char *start;
+  char sep;
+
+  /* return if a list is empty */
+  if (!args[0] || !args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+  p2 = trim_space_sep(args[1], sep);
+
+  /* return if a list is empty */
+  if (!*p1 || !*p2)
+    return;
+
+  /* multiply the vectors */
+  start = *bp;
+  e1 = parse_number(split_token(&p1, sep));
+  e2 = parse_number(split_token(&p2, sep));
+  if (!p1) {
+    /* scalar * vector */
+    safe_number(e1 * e2, buff, bp);
+    while (p2) {
+      safe_chr(sep, buff, bp);
+      safe_number(e1 * parse_number(split_token(&p2, sep)), buff, bp);
+    }
+  } else if (!p2) {
+    /* vector * scalar */
+    safe_number(e1 * e2, buff, bp);
+    while (p1) {
+      safe_chr(sep, buff, bp);
+      safe_number(parse_number(split_token(&p1, sep)) * e2, buff, bp);
+    }
+  } else {
+    /* vector * vector elementwise product */
+    safe_number(e1 * e2, buff, bp);
+    while (p1 && p2) {
+      safe_chr(sep, buff, bp);
+      safe_number
+          (parse_number(split_token(&p1, sep)) *
+           parse_number(split_token(&p2, sep)), buff, bp);
+    }
+    /* make sure vectors were the same length */
+    if (p1 || p2) {
+      *bp = start;
+      safe_str(T("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bp);
+      return;
+    }
+  }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_vdot)
+{
+  NVAL product;
+  char *p1, *p2;
+  char sep;
+
+  /* return if a list is empty */
+  if (!args[0] || !args[1])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+  p2 = trim_space_sep(args[1], sep);
+
+  /* return if a list is empty */
+  if (!*p1 || !*p2)
+    return;
+
+  /* multiply the vectors */
+  product = 0;
+  while (p1 && p2) {
+    product += parse_number(split_token(&p1, sep)) *
+        parse_number(split_token(&p2, sep));
+  }
+  if (p1 || p2) {
+    safe_str(T("#-1 VECTORS MUST BE SAME DIMENSIONS"), buff, bp);
+    return;
+  }
+  safe_number(product, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_vmag)
+{
+  NVAL num, sum;
+  char *p1;
+  char sep;
+
+  /* return if a list is empty */
+  if (!args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+
+  /* return if a list is empty */
+  if (!*p1)
+    return;
+
+  /* sum the squares */
+  num = parse_number(split_token(&p1, sep));
+  sum = num * num;
+  while (p1) {
+    num = parse_number(split_token(&p1, sep));
+    sum += num * num;
+  }
+
+  safe_number(sqrt(sum), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_vunit)
+{
+  NVAL num, sum;
+  char tbuf[BUFFER_LEN];
+  char *p1;
+  char sep;
+
+  /* return if a list is empty */
+  if (!args[0])
+    return;
+
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+  p1 = trim_space_sep(args[0], sep);
+
+  /* return if a list is empty */
+  if (!*p1)
+    return;
+
+  /* copy the vector, since we have to walk it twice... */
+  strcpy(tbuf, p1);
+
+  /* find the magnitude */
+  num = parse_number(split_token(&p1, sep));
+  sum = num * num;
+  while (p1) {
+    num = parse_number(split_token(&p1, sep));
+    sum += num * num;
+  }
+  sum = sqrt(sum);
+
+  if (!sum) {
+    /* zero vector */
+    p1 = tbuf;
+    safe_chr('0', buff, bp);
+    while (split_token(&p1, sep), p1) {
+      safe_chr(sep, buff, bp);
+      safe_chr('0', buff, bp);
+    }
+    return;
+  }
+  /* now make the unit vector */
+  p1 = tbuf;
+  safe_number(parse_number(split_token(&p1, sep)) / sum, buff, bp);
+  while (p1) {
+    safe_chr(sep, buff, bp);
+    safe_number(parse_number(split_token(&p1, sep)) / sum, buff, bp);
+  }
+}
+
+FUNCTION(fun_vcross)
+{
+  char sep = ' ';
+  char *v1[BUFFER_LEN / 2], *v2[BUFFER_LEN / 2];
+  int v1len, v2len, n;
+  NVAL vec1[3], vec2[3], cross[3];
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  v1len = list2arr(v1, BUFFER_LEN / 2, args[0], sep);
+  v2len = list2arr(v2, BUFFER_LEN / 2, args[1], sep);
+
+  if (v1len != 3 || v2len != 3) {
+    safe_str(T("#-1 VECTORS MUST BE THREE-DIMENSIONAL"), buff, bp);
+    return;
+  }
+
+  for (n = 0; n < 3; n++) {
+    vec1[n] = parse_number(v1[n]);
+    vec2[n] = parse_number(v2[n]);
+  }
+
+  cross[0] = vec1[1] * vec2[2] - vec2[1] * vec1[2];
+  cross[1] = vec1[2] * vec2[0] - vec2[2] * vec1[0];
+  cross[2] = vec1[0] * vec2[1] - vec2[0] * vec1[1];
+
+  safe_number(cross[0], buff, bp);
+  safe_chr(sep, buff, bp);
+  safe_number(cross[1], buff, bp);
+  safe_chr(sep, buff, bp);
+  safe_number(cross[2], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_fdiv)
+{
+  math_fdiv(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_fmod)
+{
+  NVAL x, y;
+  if (!is_strict_number(args[0]) || !is_strict_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  y = parse_number(args[1]);
+  if (y == 0) {
+    safe_str(T("#-1 DIVISION BY ZERO"), buff, bp);
+    return;
+  }
+  x = parse_number(args[0]);
+  safe_number(fmod(x, y), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_floor)
+{
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  safe_number(floor(parse_number(args[0])), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ceil)
+{
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  safe_number(ceil(parse_number(args[0])), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_round)
+{
+  char temp[BUFFER_LEN];
+  int places;
+
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  if (nargs == 2) {
+    if (!is_integer(args[1])) {
+      safe_str(T(e_int), buff, bp);
+      return;
+    }
+    places = parse_integer(args[1]);
+  } else
+    places = 0;
+
+  if (places < 0)
+    places = 0;
+  else if (places > FLOAT_PRECISION)
+    places = FLOAT_PRECISION;
+
+  /* The 0.0000001 is a kludge because .15 gets represented as .149999...
+   * on many systems, and rounds down to .1. Lame. */
+  sprintf(temp, "%.*f", places, parse_number(args[0]) + 0.0000001);
+
+  /* Handle the bizarre "-0" sprintf problem. */
+  if (!strcmp(temp, "-0"))
+    safe_chr('0', buff, bp);
+  else
+    safe_str(temp, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_pi)
+{
+  safe_number(M_PI, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_e)
+{
+  safe_number(2.71828182845904523536, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sin)
+{
+  NVAL angle;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  angle = angle_to_rad(parse_number(args[0]), args[1]);
+  safe_number(sin(angle), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_asin)
+{
+  NVAL num;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  num = parse_number(args[0]);
+  if ((num < -1) || (num > 1)) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+  safe_number(rad_to_angle(asin(num), args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cos)
+{
+  NVAL angle;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  angle = angle_to_rad(parse_number(args[0]), args[1]);
+  safe_number(cos(angle), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_acos)
+{
+  NVAL num;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  num = parse_number(args[0]);
+  if ((num < -1) || (num > 1)) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+  safe_number(rad_to_angle(acos(num), args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_tan)
+{
+  NVAL angle;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  angle = angle_to_rad(parse_number(args[0]), args[1]);
+  safe_number(tan(angle), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_atan)
+{
+  NVAL angle;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  angle = parse_number(args[0]);
+  safe_number(rad_to_angle(atan(angle), args[1]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_atan2)
+{
+  NVAL x, y;
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  x = parse_number(args[0]);
+  y = parse_number(args[1]);
+  safe_number(rad_to_angle(atan2(x, y), args[2]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_exp)
+{
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  safe_number(exp(parse_number(args[0])), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_power)
+{
+  NVAL num;
+  NVAL m;
+
+  if (!is_number(args[0]) || !is_number(args[1])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  num = parse_number(args[0]);
+  m = parse_number(args[1]);
+  if (num < 0 && (m != (int) m)) {
+    safe_str(T("#-1 FRACTIONAL POWER OF NEGATIVE"), buff, bp);
+    return;
+  }
+#ifndef HAS_IEEE_MATH
+  if ((num > 100) || (m > 100)) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+#endif
+  safe_number(pow(num, m), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ln)
+{
+  NVAL num;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  num = parse_number(args[0]);
+#ifndef HAS_IEEE_MATH
+  /* log(0) is bad for you */
+  if (num == 0) {
+    safe_str(T("#-1 INFINITY"), buff, bp);
+    return;
+  }
+#endif
+  if (num < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+  safe_number(log(num), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_log)
+{
+  NVAL num, base;
+  if (!is_number(args[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  num = parse_number(args[0]);
+#ifndef HAS_IEEE_MATH
+  /* log(0) is bad for you */
+  if (num == 0) {
+    safe_str(T("#-1 INFINITY"), buff, bp);
+    return;
+  }
+#endif
+  if (num < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+  if (nargs == 2) {
+    if (!is_number(args[1])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    base = parse_number(args[1]);
+
+    if (base <= 1) {
+      safe_str(T("#-1 BASE OUT OF RANGE"), buff, bp);
+      return;
+    }
+    safe_number(log(num) / log(base), buff, bp);
+  } else
+    safe_number(log10(num), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_sqrt)
+{
+  NVAL num;
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  num = parse_number(args[0]);
+  if (num < 0) {
+    safe_str(T("#-1 IMAGINARY NUMBER"), buff, bp);
+    return;
+  }
+  safe_number(sqrt(num), buff, bp);
+}
+
+FUNCTION(fun_root)
+{
+  NVAL root, x;
+  int n;
+  int sign = 0;
+
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+  if (!is_integer(args[1])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  x = parse_number(args[0]);
+  n = parse_integer(args[1]);
+
+  if (n < 0) {
+    safe_str(T("#-1 ROOT OUT OF RANGE"), buff, bp);
+    return;
+  }
+
+  if (x < 0) {
+    if (n & 1) {                /* Odd root */
+      sign = 1;
+      x = fabs(x);
+    } else {                    /* Even */
+      safe_str(T("#-1 IMAGINARY NUMBER"), buff, bp);
+      return;
+    }
+  }
+
+  switch (n) {
+  case 0:
+    root = 0;
+    break;
+  case 1:
+    root = x;
+    break;
+  case 2:
+    root = sqrt(x);
+    break;
+  case 3:
+    /* TO-DO: Configure check for cbrt()? */
+  default:
+    /* Spiffy logarithm trick */
+    root = exp(log(x) / n);
+  }
+
+  if (sign)
+    root = -root;
+
+  safe_number(root, buff, bp);
+}
+
+
+/** Calculates the numerator and denominator for a fraction representing
+ *  a floating point number. Only works for positive numbers!
+ * \param v the number 
+ * \param n pointer to the numerator
+ * \param d pointer to the denominator
+ * \param error accuracy to which the fraction should represent the original number
+ * \return -1.0 if (v < MIN || v > MAX || error < 0.0) | (v - n/d) / v | otherwise.
+ */
+static double
+frac(double v, int *RESTRICT n, int *RESTRICT d, double error)
+{
+
+/* Based on a routine found in netlib (http://www.netlib.org) by
+   
+                        Robert J. Craig
+                        AT&T Bell Laboratories
+                        1200 East Naperville Road
+                        Naperville, IL 60566-7045
+
+ though I have no idea if that address is still valid.
+
+ Rewritten by Raevnos to use modern C, and get rid of stupid gotos.
+
+    reference:  Jerome Spanier and Keith B. Oldham, "An Atlas
+    of Functions," Springer-Verlag, 1987, pp. 665-7.
+  */
+
+
+  int D, N, t;
+  int first = 1;
+  double epsilon, r = 0.0, m;
+
+  if (v < 0 || error < 0.0)
+    return -1.0;
+  *d = D = 1;
+  *n = (int) v;
+  N = *n + 1;
+
+  do {
+    if (!first) {
+      if (r <= 1.0)
+        r = 1.0 / r;
+      N += *n * (int) r;
+      D += *d * (int) r;
+      *n += N;
+      *d += D;
+    } else
+      first = 0;
+    r = 0.0;
+    if (v * (*d) != *n) {
+      r = (N - v * D) / (v * (*d) - *n);
+      if (r <= 1.0) {
+        t = N;
+        N = *n;
+        *n = t;
+        t = D;
+        D = *d;
+        *d = t;
+      }
+    }
+    epsilon = fabs(1.0 - *n / (v * (*d)));
+    if (epsilon <= error)
+      return epsilon;
+    m = 1.0;
+    do {
+      m *= 10.0;
+    } while (m * epsilon < 1.0);
+    epsilon = 1.0 / m * ((int) (0.5 + m * epsilon));
+    if (epsilon <= error)
+      return epsilon;
+  } while (r != 0.0);
+  return epsilon;
+}
+
+FUNCTION(fun_fraction)
+{
+  int num = 0, denom = 0;
+  NVAL n;
+  int sign = 0;
+
+  if (!is_number(args[0])) {
+    safe_str(T(e_num), buff, bp);
+    return;
+  }
+
+  n = parse_number(args[0]);
+
+  if (n < 0) {
+    n = fabs(n);
+    sign = 1;
+  } else if (n == 0) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  frac(n, &num, &denom, 1.0e-10);
+
+  if (sign)
+    safe_chr('-', buff, bp);
+
+  if (denom == 1)
+    safe_integer(num, buff, bp);
+  else
+    safe_format(buff, bp, "%d/%d", num, denom);
+}
+
+FUNCTION(fun_isint)
+{
+  safe_boolean(is_strict_integer(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_isnum)
+{
+  safe_boolean(is_strict_number(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_and)
+{
+  math_and(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_or)
+{
+  math_or(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cand)
+{
+  int j;
+  char tbuf[BUFFER_LEN], *tp;
+  char const *sp;
+
+  for (j = 0; j < nargs; j++) {
+    tp = tbuf;
+    sp = args[j];
+    process_expression(tbuf, &tp, &sp, executor, caller, enactor,
+                       PE_DEFAULT, PT_DEFAULT, pe_info);
+    *tp = '\0';
+    if (!parse_boolean(tbuf)) {
+      safe_chr('0', buff, bp);
+      return;
+    }
+  }
+  safe_chr('1', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_cor)
+{
+  int j;
+  char tbuf[BUFFER_LEN], *tp;
+  char const *sp;
+
+  for (j = 0; j < nargs; j++) {
+    tp = tbuf;
+    sp = args[j];
+    process_expression(tbuf, &tp, &sp, executor, caller, enactor,
+                       PE_DEFAULT, PT_DEFAULT, pe_info);
+    *tp = '\0';
+    if (parse_boolean(tbuf)) {
+      safe_chr('1', buff, bp);
+      return;
+    }
+  }
+  safe_chr('0', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_not)
+{
+  safe_boolean(!parse_boolean(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_t)
+{
+  safe_boolean(parse_boolean(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_xor)
+{
+  math_xor(args, nargs, buff, bp);
+}
+
+/** Return the spelled-out version of an integer.
+ * \param num string containing an integer to spell out.
+ * \param len length of num, which must be a multiple of 3, max 15.
+ * \param buff pointer to address of output buffer.
+ * \param bp pointer to pointer to insertion point in buff.
+ */
+static void
+do_spellnum(char *num, unsigned int len, char **buff, char ***bp)
+{
+  static const char *bigones[] =
+      { "", "thousand", "million", "billion", "trillion" };
+  static const char *singles[] = { "", "one", "two", "three", "four",
+    "five", "six", "seven", "eight", "nine"
+  };
+  static const char *special[] =
+      { "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
+    "sixteen",
+    "seventeen", "eighteen", "nineteen"
+  };
+  static const char *tens[] = { "", " ", "twenty", "thirty", "forty",
+    "fifty", "sixty", "seventy", "eighty", "ninety"
+  };
+  unsigned int x0, x1, x2;
+  int pos = len / 3;
+  int started = 0;
+
+  while (pos > 0) {
+
+    pos--;
+
+    x0 = num[0] - '0';
+    x1 = num[1] - '0';
+    x2 = num[2] - '0';
+
+    if (x0) {
+      if (started)
+        safe_chr(' ', *buff, *bp);
+      safe_format(*buff, *bp, "%s %s", singles[x0], "hundred");
+      started = 1;
+    }
+
+    if (x1 == 1) {
+      if (started)
+        safe_chr(' ', *buff, *bp);
+      safe_str(special[x2], *buff, *bp);
+      started = 1;
+    } else if (x1 || x2) {
+      if (started)
+        safe_chr(' ', *buff, *bp);
+      if (x1) {
+        safe_str(tens[x1], *buff, *bp);
+        if (x2)
+          safe_chr('-', *buff, *bp);
+      }
+      safe_str(singles[x2], *buff, *bp);
+      started = 1;
+    }
+
+    if (pos && (x0 || x1 || x2)) {
+      if (started)
+        safe_chr(' ', *buff, *bp);
+      safe_str(bigones[pos], *buff, *bp);
+      started = 1;
+    }
+
+    num += 3;
+  }
+}                               /* do_spellnum */
+
+/** adds zeros to the beginning of the string, untill its length is 
+ * a multiple of 3.
+ */
+#define add_zeros(p) \
+   m = strlen(p) % 3; \
+   switch (m) { \
+    case 0: strcpy(num, p); break; \
+    case 1: num[0] = '0'; num[1] = '0'; strcpy(num + 2, p); break; \
+    case 2: num[0] = '0'; strcpy(num + 1, p); break; \
+   } \
+   p = num;
+
+/* ARGSUSED */
+FUNCTION(fun_spellnum)
+{
+  static const char *tail[] = { "", "tenth", "hundredth", "thousandth",
+    "ten-thousandth", "hundred-thousandth", "millionth",
+    "ten-millionth", "hundred-millionth", "billionth",
+    "ten-billionth", "hundred-billionth", "trillionth",
+    "ten-trillionth", "hundred-trillionth"
+  };
+
+  char num[BUFFER_LEN];
+  char *number,                 /* the whole number (without -/+ sign and leading zeros) */
+  *pnumber = args[0], *pnum1, *pnum2 = NULL;    /* part 1 and 2 of the number respectively */
+  unsigned int len, m, minus = 0,       /* is the number negative? */
+      dot = 0, len1, len2;      /* length of part 1 and 2 respectively */
+
+  pnumber = trim_space_sep(args[0], ' ');
+
+  /* Is the number negative? */
+  if (pnumber[0] == '-') {
+    minus = 1;
+    pnumber++;
+  } else if (pnumber[0] == '+') {
+    pnumber++;
+  }
+
+  /* remove leading zeros */
+  while (*pnumber == '0')
+    pnumber++;
+
+  pnum1 = number = pnumber;
+
+  /* Is it a number? 
+   * If so, devide the number in two parts: pnum1.pnum2 
+   */
+  len = strlen(number);
+  for (m = 0; m < len; m++) {
+
+    if (*pnumber == '.') {
+      if (dot) {
+        safe_str(T(e_num), buff, bp);
+        return;
+      }
+      dot = 1;                  /* allow only 1 dot in a number */
+      *pnumber = '\0';          /* devide the string */
+      pnum2 = pnumber + 1;
+    } else if (!isdigit((unsigned char) *pnumber)) {
+      safe_str(T(e_num), buff, bp);
+      return;
+    }
+    pnumber++;
+  }
+
+  add_zeros(pnum1);
+
+  len1 = strlen(pnum1);
+  len2 = (pnum2 == NULL ? 0 : strlen(pnum2));
+
+  /* Max number is 999,999,999,999,999.999,999,999,999 */
+  if (len1 > 15 || len2 > 14) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  /* before the . */
+
+  /* zero is special */
+  if (*pnum1 == '\0') {
+    if (len2 == 0)
+      safe_str("zero", buff, bp);
+  } else {
+    if (minus)
+      safe_str("negative ", buff, bp);
+    do_spellnum(pnum1, len1, &buff, &bp);
+  }
+
+  if (len2 > 0) {
+    /* after the . */
+
+    /* remove leading zeros */
+    while (*pnum2 == '0')
+      pnum2++;
+
+    add_zeros(pnum2);
+    pnumber = num;
+
+    len = strlen(pnumber);
+
+    if (len1 > 0)
+      safe_str(" and ", buff, bp);
+    else if (minus && len)
+      safe_str("negative ", buff, bp);
+
+    /* zero is special */
+    if (!len) {
+      safe_str("zero ", buff, bp);
+      safe_format(buff, bp, "%ss", tail[len2]);
+    } else
+      /* one is special too */
+    if (len == 3 && parse_integer(pnumber) == 1) {
+      safe_format(buff, bp, "one %s", tail[len2]);
+    } else {
+      /* rest is normal */
+      do_spellnum(pnum1, len, &buff, &bp);
+      safe_format(buff, bp, " %ss", tail[len2]);
+    }
+
+  }
+}
+
+#undef add_zeros
+
+
+FUNCTION(fun_bound)
+{
+  if (!is_number(args[0]) ||
+      !is_number(args[1]) || (nargs == 3 && !is_number(args[2]))) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+
+  if (parse_number(args[0]) < parse_number(args[1]))
+    safe_strl(args[1], arglens[1], buff, bp);
+  else if (nargs == 3 && parse_number(args[0]) > parse_number(args[2]))
+    safe_strl(args[2], arglens[2], buff, bp);
+  else
+    safe_strl(args[0], arglens[0], buff, bp);
+}
+
+FUNCTION(fun_band)
+{
+  math_band(args, nargs, buff, bp);
+}
+
+FUNCTION(fun_bnand)
+{
+  unsigned int retval;
+  if (!is_uinteger(args[0]) || !is_uinteger(args[1])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+  retval = parse_uinteger(args[0]) & (~parse_uinteger(args[1]));
+  safe_uinteger(retval, buff, bp);
+}
+
+FUNCTION(fun_bor)
+{
+  math_bor(args, nargs, buff, bp);
+}
+
+FUNCTION(fun_bxor)
+{
+  math_bxor(args, nargs, buff, bp);
+}
+
+FUNCTION(fun_bnot)
+{
+  if (!is_uinteger(args[0])) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+  safe_uinteger(~parse_uinteger(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_nand)
+{
+  math_nand(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_nor)
+{
+  math_nor(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lmath)
+{
+/* lmath(<op>, <list>[, <sep>])
+* is equivalant to
+*
+* &op me=<op>(%0, %1)
+* fold(me/op, <list>, <sep>)
+* 
+* but a lot more efficient. The Tiny l-OP functions
+* can be simulated with @function if needed.
+*/
+
+  int nptr;
+  char sep;
+  char **ptr;
+  MATH *op;
+
+  /* Allocate memory */
+  ptr = (char **) mush_malloc(BUFFER_LEN, "string");
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep)) {
+    mush_free((Malloc_t) ptr, "string");
+    return;
+  }
+
+  nptr = list2arr(ptr, BUFFER_LEN, args[1], sep);
+
+  op = math_hash_lookup(args[0]);
+
+  if (!op) {
+    safe_str(T("#-1 UNKNOWN OPERATION"), buff, bp);
+    mush_free((Malloc_t) ptr, "string");
+    return;
+  }
+  op->func(ptr, nptr, buff, bp);
+
+  mush_free((Malloc_t) ptr, "string");
+}
+
+FUNCTION(fun_baseconv)
+{
+  long n;
+  int from, to;
+  char *end;
+
+  if (!(is_integer(args[1]) && is_integer(args[2]))) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+
+  from = parse_integer(args[1]);
+  to = parse_integer(args[2]);
+
+  if (from < 2 || from > 36) {
+    safe_str(T("#-1 FROM BASE OUT OF RANGE"), buff, bp);
+    return;
+  }
+
+  if (to < 2 || to > 36) {
+    safe_str(T("#-1 TO BASE OUT OF RANGE"), buff, bp);
+    return;
+  }
+
+  n = strtol(trim_space_sep(args[0], ' '), &end, from);
+
+  if (*end != '\0') {
+    safe_str(T("#-1 MALFORMED NUMBER"), buff, bp);
+    return;
+  }
+
+  format_long(n, buff, bp, BUFFER_LEN, to);
+}
+
+MATH_FUNC(math_add)
+{
+  NVAL result = 0;
+  int n;
+
+  for (n = 0; n < nptr; n++) {
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    result += parse_number(ptr[n]);
+  }
+
+  safe_number(result, buff, bp);
+}
+
+MATH_FUNC(math_and)
+{
+  int n;
+
+  for (n = 0; n < nptr; n++) {
+    if (!parse_boolean(ptr[n])) {
+      safe_chr('0', buff, bp);
+      return;
+    }
+  }
+
+  safe_chr('1', buff, bp);
+  return;
+}
+
+MATH_FUNC(math_sub)
+{
+/* Subtraction */
+  NVAL result;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_number(ptr[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+
+  result = parse_number(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    result -= parse_number(ptr[n]);
+  }
+  safe_number(result, buff, bp);
+}
+
+MATH_FUNC(math_mul)
+{
+  NVAL result;
+  int n;
+/* Multiplication */
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_number(ptr[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  result = parse_number(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    result *= parse_number(ptr[n]);
+  }
+  safe_number(result, buff, bp);
+}
+
+
+MATH_FUNC(math_min)
+{
+  NVAL result;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_number(ptr[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  result = parse_number(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    NVAL test;
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    test = parse_number(ptr[n]);
+    result = (result > test) ? test : result;
+  }
+  safe_number(result, buff, bp);
+}
+
+MATH_FUNC(math_max)
+{
+  NVAL result;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_number(ptr[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  result = parse_number(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    NVAL test;
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    test = parse_number(ptr[n]);
+    result = (result > test) ? result : test;
+  }
+  safe_number(result, buff, bp);
+}
+
+MATH_FUNC(math_mean)
+{
+  NVAL result = 0, count = 0;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  for (n = 0; n < nptr; n++) {
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    result += parse_number(ptr[n]);
+    count++;
+  }
+
+  safe_number(result / count, buff, bp);
+}
+
+MATH_FUNC(math_div)
+{
+/* Division, truncating to match remainder */
+  int divresult, n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_integer(ptr[0])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+  divresult = parse_integer(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    int temp;
+    if (!is_integer(ptr[n])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    temp = parse_integer(ptr[n]);
+
+    if (temp == 0) {
+      safe_str(T("#-1 DIVISION BY ZERO"), buff, bp);
+      return;
+    }
+
+    if (divresult < 0) {
+      if (temp < 0)
+        divresult = -divresult / -temp;
+      else
+        divresult = -(-divresult / temp);
+    } else {
+      if (temp < 0)
+        divresult = -(divresult / -temp);
+      else
+        divresult = divresult / temp;
+    }
+  }
+  safe_integer(divresult, buff, bp);
+}
+
+MATH_FUNC(math_floordiv)
+{
+/* Division taking the floor, to match modulo */
+  int divresult, n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_integer(ptr[0])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+  divresult = parse_integer(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    int temp;
+    if (!is_integer(ptr[n])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    temp = parse_integer(ptr[n]);
+
+    if (temp == 0) {
+      safe_str(T("#-1 DIVISION BY ZERO"), buff, bp);
+      return;
+    }
+
+    if (divresult < 0) {
+      if (temp < 0)
+        divresult = -divresult / -temp;
+      else
+        divresult = -((-divresult + temp - 1) / temp);
+    } else {
+      if (temp < 0)
+        divresult = -((divresult - temp - 1) / -temp);
+      else
+        divresult = divresult / temp;
+    }
+  }
+  safe_integer(divresult, buff, bp);
+}
+
+MATH_FUNC(math_fdiv)
+{
+  NVAL result;
+  int n;
+/* Floating-point division */
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_number(ptr[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  result = parse_number(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    NVAL temp;
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    temp = parse_number(ptr[n]);
+
+    if (temp == 0) {
+      safe_str(T("#-1 DIVISION BY ZERO"), buff, bp);
+      return;
+    }
+
+    result /= temp;
+  }
+  safe_number(result, buff, bp);
+}
+
+MATH_FUNC(math_modulo)
+{
+/* Modulo */
+  int divresult, n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_integer(ptr[0])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+  divresult = parse_integer(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    int temp;
+    if (!is_integer(ptr[n])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    temp = parse_integer(ptr[n]);
+
+    if (temp == 0) {
+      safe_str(T("#-1 DIVISION BY ZERO"), buff, bp);
+      return;
+    }
+
+    if (divresult < 0) {
+      if (temp < 0)
+        divresult = -(-divresult % -temp);
+      else
+        divresult = (temp - (-divresult % temp)) % temp;
+    } else {
+      if (temp < 0)
+        divresult = -((-temp - (divresult % -temp)) % -temp);
+      else
+        divresult = divresult % temp;
+    }
+  }
+  safe_integer(divresult, buff, bp);
+}
+
+MATH_FUNC(math_remainder)
+{
+/* Remainder */
+  int divresult, n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_integer(ptr[0])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+  divresult = parse_integer(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    int temp;
+    if (!is_integer(ptr[n])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    temp = parse_integer(ptr[n]);
+
+    if (temp == 0) {
+      safe_str(T("#-1 DIVISION BY ZERO"), buff, bp);
+      return;
+    }
+
+    if (divresult < 0) {
+      if (temp < 0)
+        divresult = -(-divresult % -temp);
+      else
+        divresult = -(-divresult % temp);
+    } else {
+      if (temp < 0)
+        divresult = divresult % -temp;
+      else
+        divresult = divresult % temp;
+    }
+  }
+  safe_integer(divresult, buff, bp);
+}
+
+MATH_FUNC(math_band)
+{
+  unsigned int bretval;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_uinteger(ptr[0])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+
+  bretval = parse_uinteger(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    if (!is_uinteger(ptr[n])) {
+      safe_str(T(e_uints), buff, bp);
+      return;
+    }
+    bretval &= parse_uinteger(ptr[n]);
+  }
+  safe_uinteger(bretval, buff, bp);
+}
+
+MATH_FUNC(math_bor)
+{
+  unsigned int bretval;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_uinteger(ptr[0])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+
+  bretval = parse_uinteger(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    if (!is_uinteger(ptr[n])) {
+      safe_str(T(e_uints), buff, bp);
+      return;
+    }
+    bretval |= parse_uinteger(ptr[n]);
+  }
+  safe_uinteger(bretval, buff, bp);
+}
+
+MATH_FUNC(math_bxor)
+{
+  unsigned int bretval;
+  int n;
+
+  if (nptr < 1) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+
+  if (!is_uinteger(ptr[0])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+
+  bretval = parse_uinteger(ptr[0]);
+
+  for (n = 1; n < nptr; n++) {
+    if (!is_uinteger(ptr[n])) {
+      safe_str(T(e_uints), buff, bp);
+      return;
+    }
+    bretval ^= parse_uinteger(ptr[n]);
+  }
+  safe_uinteger(bretval, buff, bp);
+}
+
+MATH_FUNC(math_or)
+{
+  int n;
+/* Or */
+  for (n = 0; n < nptr; n++) {
+    if (parse_boolean(ptr[n])) {
+      safe_chr('1', buff, bp);
+      return;
+    }
+  }
+  safe_chr('0', buff, bp);
+}
+
+MATH_FUNC(math_nor)
+{
+  int n;
+/* nor */
+
+  for (n = 0; n < nptr; n++) {
+    if (parse_boolean(ptr[n])) {
+      safe_chr('0', buff, bp);
+      return;
+    }
+  }
+  safe_chr('1', buff, bp);
+}
+
+MATH_FUNC(math_nand)
+{
+  int n;
+
+  for (n = 0; n < nptr; n++) {
+    if (!parse_boolean(ptr[n])) {
+      safe_chr('1', buff, bp);
+      return;
+    }
+  }
+  safe_chr('0', buff, bp);
+}
+
+MATH_FUNC(math_xor)
+{
+  int found = 0, n;
+
+  for (n = 0; n < nptr; n++) {
+    if (parse_boolean(ptr[n])) {
+      if (found == 0) {
+        found = 1;
+      } else {
+        safe_chr('0', buff, bp);
+        return;
+      }
+    }
+  }
+  if (found)
+    safe_chr('1', buff, bp);
+  else
+    safe_chr('0', buff, bp);
+}
+
+
+static int
+nval_sort(const void *a, const void *b)
+{
+  const NVAL *x = a, *y = b;
+  const double epsilon = pow(10, -FLOAT_PRECISION);
+  int eq = (fabs(*x - *y) <= (epsilon * fabs(*x)));
+  return eq ? 0 : (*x > *y ? 1 : -1);
+}
+
+
+static NVAL
+find_median(NVAL * numbers, int nargs)
+{
+  if (nargs == 0)
+    return 0;
+  if (nargs == 1)
+    return numbers[0];
+
+  qsort(numbers, nargs, sizeof(NVAL), nval_sort);
+
+  if ((nargs % 2) == 1)         /* Odd # of items */
+    return numbers[nargs / 2];
+  else
+    return (numbers[(nargs / 2) - 1] + numbers[nargs / 2]) / (NVAL) 2;
+}
+
+FUNCTION(fun_median)
+{
+  math_median(args, nargs, buff, bp);
+}
+
+FUNCTION(fun_mean)
+{
+  math_mean(args, nargs, buff, bp);
+}
+
+FUNCTION(fun_stddev)
+{
+  math_stddev(args, nargs, buff, bp);
+}
+
+/* ARGSUSED */
+MATH_FUNC(math_median)
+{
+  NVAL median;
+  NVAL *numbers;
+  int n;
+
+  numbers = mush_malloc(nptr * sizeof(NVAL), "number_array");
+
+  for (n = 0; n < nptr; n++) {
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      mush_free(numbers, "number_array");
+      return;
+    }
+    numbers[n] = parse_number(ptr[n]);
+  }
+
+  median = find_median(numbers, nptr);
+  mush_free(numbers, "number_array");
+  safe_number(median, buff, bp);
+}
+
+MATH_FUNC(math_stddev)
+{
+  NVAL m, om, s, os, v;
+  int n;
+
+  if (nptr < 2) {
+    safe_number(0, buff, bp);
+    return;
+  }
+  if (!is_number(ptr[0])) {
+    safe_str(T(e_nums), buff, bp);
+    return;
+  }
+  m = parse_number(ptr[0]);
+  s = 0;
+  for (n = 1; n < nptr; n++) {
+    om = m;
+    os = s;
+    if (!is_number(ptr[n])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    v = parse_number(ptr[n]);
+    m = om + (v - om) / (n + 1);
+    s = os + (v - om) * (v - m);
+  }
+
+  safe_number(sqrt(s / (nptr - 1)), buff, bp);
+}
+
+/** A list of MATH_FUNCs that are suitable for lmath() */
+MATH mlist[] = {
+  {"ADD", math_add}
+  ,
+  {"SUB", math_sub}
+  ,
+  {"MUL", math_mul}
+  ,
+  {"DIV", math_div}
+  ,
+  {"FLOORDIV", math_floordiv}
+  ,
+  {"MOD", math_modulo}
+  ,
+  {"MODULO", math_modulo}
+  ,
+  {"MODULUS", math_modulo}
+  ,
+  {"REMAINDER", math_remainder}
+  ,
+  {"MIN", math_min}
+  ,
+  {"MAX", math_max}
+  ,
+  {"AND", math_and}
+  ,
+  {"NAND", math_nand}
+  ,
+  {"OR", math_or}
+  ,
+  {"NOR", math_nor}
+  ,
+  {"XOR", math_xor}
+  ,
+  {"BAND", math_band}
+  ,
+  {"BOR", math_bor}
+  ,
+  {"BXOR", math_bxor}
+  ,
+  {"FDIV", math_fdiv}
+  ,
+  {"MEAN", math_mean}
+  ,
+  {"MEDIAN", math_median}
+  ,
+  {"STDDEV", math_stddev}
+  ,
+  {NULL, NULL}
+};
+
+static MATH *
+math_hash_lookup(char *name)
+{
+  return (MATH *) hashfind(strupper(name), &htab_math);
+}
+
+
+static void
+math_hash_insert(const char *name, MATH * func)
+{
+  hashadd(name, (void *) func, &htab_math);
+}
+
+/** Initialize the math function hash table. */
+void
+init_math_hashtab(void)
+{
+  MATH *fp;
+
+  hashinit(&htab_math, 32, sizeof(MATH));
+  for (fp = mlist; fp->name; fp++)
+    math_hash_insert(fp->name, (MATH *) fp);
+}
+
+static NVAL
+angle_to_rad(NVAL angle, const char *from)
+{
+  if (!from)
+    return angle;
+  switch (*from) {
+  case 'r':
+  case 'R':
+    return angle;
+  case 'd':
+  case 'D':
+    return angle * (M_PI / 180.0);
+  case 'g':
+  case 'G':
+    return angle * (M_PI / 200.0);
+  default:
+    return angle;
+  }
+}
+
+static NVAL
+rad_to_angle(NVAL angle, const char *to)
+{
+  if (!to)
+    return angle;
+  switch (*to) {
+  case 'r':
+  case 'R':
+    return angle;
+  case 'd':
+  case 'D':
+    return angle * (180.0 / M_PI);
+  case 'g':
+  case 'G':
+    return angle * (200.0 / M_PI);
+  default:
+    return angle;
+  }
+}
+
+/* Take a normal number & make it roman */
+#define ROMAN_CDOWN(num,rstr)   while((i - num) >= 0) { \
+                                  safe_format(roman, &roman_bp, "%s", rstr );     \
+                                  i -= num; \
+                                 }
+/* Returns roman numeral or NULL for out of range */
+char * ArabicToRoman(int arabic) {
+  static char roman[BUFFER_LEN];
+  char *roman_bp;
+  int i = arabic;
+
+
+  /* Check Range */
+  if (i > 3999999 || i < 1)
+    return NULL;
+
+  memset(roman, '\0', BUFFER_LEN);
+  roman_bp = roman;
+
+  ROMAN_CDOWN(1000000, "m");
+
+  ROMAN_CDOWN(900000, "cm");
+
+  ROMAN_CDOWN(500000, "d");
+
+  ROMAN_CDOWN(400000, "cd");
+
+  ROMAN_CDOWN(100000, "c");
+
+  ROMAN_CDOWN(90000, "xc");
+
+  ROMAN_CDOWN(50000, "l");
+
+  ROMAN_CDOWN(40000, "xl");
+
+  ROMAN_CDOWN(10000, "x");
+
+  ROMAN_CDOWN(9000, "Mx");
+
+  ROMAN_CDOWN(5000, "v");
+
+  ROMAN_CDOWN(4000, "Mv");
+
+  ROMAN_CDOWN(1000, "M");
+
+  ROMAN_CDOWN(900, "CM");
+
+  ROMAN_CDOWN(500, "D");
+
+  ROMAN_CDOWN(400, "CD");
+
+  ROMAN_CDOWN(100, "C");
+
+  ROMAN_CDOWN(90, "XC");
+
+  ROMAN_CDOWN(50, "L");
+
+  ROMAN_CDOWN(40, "XL");
+
+  ROMAN_CDOWN(10, "X");
+
+  ROMAN_CDOWN(9, "IX");
+
+  ROMAN_CDOWN(5, "V");
+
+  ROMAN_CDOWN(4, "IV");
+
+  ROMAN_CDOWN(1, "I");
+
+  return (roman);
+}
+
+
+/* Convert Roman Numeral to Arabic Number */
+int
+RomanToArabic(char *roman_value)
+{
+  unsigned char c1, c2;
+  char *p;
+  int value1 = 0;
+  int value2 = 0;
+  int arabic_total = 0;
+
+  for (p = roman_value; *p; p++) {
+    c1 = *p;
+    value1 = roman_numeral_table[c1];
+    if (!value1)                /* Out of Range */
+      return -1;
+    if (*(p + 1))
+      c2 = *(p + 1);
+    else
+      c2 = '!';
+    value2 = roman_numeral_table[c2];
+    if (value2 > value1) {
+      arabic_total += value2 - value1;
+      p++;
+    } else
+      arabic_total += value1;
+  }
+  if (arabic_total < 4000000)
+    return arabic_total;
+  else
+    return -1; /* Out of Range */
+
+}
diff --git a/src/funmisc.c b/src/funmisc.c
new file mode 100644 (file)
index 0000000..81b415b
--- /dev/null
@@ -0,0 +1,748 @@
+/**
+ * \file funmisc.c
+ *
+ * \brief Miscellaneous functions for mushcode.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <time.h>
+#include <string.h>
+#include <ctype.h>
+#include "conf.h"
+#include "case.h"
+#include "externs.h"
+#include "version.h"
+#include "htab.h"
+#include "flags.h"
+#include "lock.h"
+#include "match.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "parse.h"
+#include "boolexp.h"
+#include "function.h"
+#include "boolexp.h"
+#include "command.h"
+#include "game.h"
+#include "attrib.h"
+#include "confmagic.h"
+
+#ifdef WIN32
+#pragma warning( disable : 4761)       /* NJG: disable warning re conversion */
+#endif
+
+extern time_t start_time, first_start_time;
+extern int reboot_count;
+extern FUN flist[];
+static char *soundex(char *str);
+extern char cf_motd_msg[BUFFER_LEN],
+  cf_downmotd_msg[BUFFER_LEN], cf_fullmotd_msg[BUFFER_LEN];
+extern HASHTAB htab_function;
+
+/* ARGSUSED */
+FUNCTION(fun_valid)
+{
+  /* Checks to see if a given <something> is valid as a parameter of a
+   * given type (such as an object name.)
+   */
+
+  if (!args[0] || !*args[0])
+    safe_str("#-1", buff, bp);
+  else if (!strcasecmp(args[0], "name"))
+    safe_boolean(ok_name(args[1]), buff, bp);
+  else if (!strcasecmp(args[0], "attrname"))
+    safe_boolean(good_atr_name(upcasestr(args[1])), buff, bp);
+  else if (!strcasecmp(args[0], "playername"))
+    safe_boolean(ok_player_name(args[1], executor), buff, bp);
+  else if (!strcasecmp(args[0], "password"))
+    safe_boolean(ok_password(args[1]), buff, bp);
+  else if (!strcasecmp(args[0], "command"))
+    safe_boolean(ok_command_name(upcasestr(args[1])), buff, bp);
+  else if (!strcasecmp(args[0], "function"))
+    safe_boolean(ok_command_name(upcasestr(args[1])), buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_pemit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = PEMIT_LIST;
+  if (!command_check_byname(executor, ns ? "@nspemit" : "@pemit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  orator = executor;
+  if (ns)
+    flags |= PEMIT_SPOOF;
+  do_pemit_list(executor, args[0], args[1], flags);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_oemit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = ns ? PEMIT_SPOOF : 0;
+  if (!command_check_byname(executor, ns ? "@nsoemit" : "@oemit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  orator = executor;
+  do_oemit_list(executor, args[0], args[1], flags);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_emit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = ns ? PEMIT_SPOOF : 0;
+  if (!command_check_byname(executor, ns ? "@nsemit" : "@emit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  orator = executor;
+  do_emit(executor, args[0], flags);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_remit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = ns ? PEMIT_SPOOF : 0;
+  if (!command_check_byname(executor, ns ? "@nsremit" : "@remit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  orator = executor;
+  do_remit(executor, args[0], args[1], flags);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lemit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = ns ? PEMIT_SPOOF : 0;
+  if (!command_check_byname(executor, ns ? "@nslemit" : "@lemit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  orator = executor;
+  do_lemit(executor, args[0], flags);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_zemit)
+{
+  int ns = string_prefix(called_as, "NS");
+  int flags = ns ? PEMIT_SPOOF : 0;
+  if (!command_check_byname(executor, ns ? "@nszemit" : "@zemit") ||
+      fun->flags & FN_NOSIDEFX) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  orator = executor;
+  do_zemit(executor, args[0], args[1], flags);
+}
+
+
+extern signed char qreg_indexes[UCHAR_MAX + 1];
+/* ARGSUSED */
+FUNCTION(fun_setq)
+{
+  /* sets a variable into a local register */
+  int qindex;
+
+  if (*args[0] && (*(args[0] + 1) == '\0') &&
+      ((qindex = qreg_indexes[(unsigned char) args[0][0]]) != -1)
+      && global_eval_context.renv[qindex]) {
+    strcpy(global_eval_context.renv[qindex], args[1]);
+    if (!strcmp(called_as, "SETR"))
+      safe_strl(args[1], arglens[1], buff, bp);
+  } else
+    safe_str(T("#-1 REGISTER OUT OF RANGE"), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_r)
+{
+  /* returns a local register */
+  int qindex;
+
+  if (*args[0] && (*(args[0] + 1) == '\0') &&
+      ((qindex = qreg_indexes[(unsigned char) args[0][0]]) != -1)
+      && global_eval_context.renv[qindex])
+    safe_str(global_eval_context.renv[qindex], buff, bp);
+  else
+    safe_str(T("#-1 REGISTER OUT OF RANGE"), buff, bp);
+}
+
+/* --------------------------------------------------------------------------
+ * Utility functions: RAND, DIE, SECURE, SPACE, BEEP, SWITCH, EDIT,
+ *      ESCAPE, SQUISH, ENCRYPT, DECRYPT, LIT
+ */
+
+/* ARGSUSED */
+FUNCTION(fun_rand)
+{
+  /*
+   * Uses Sh'dow's random number generator, found in utils.c.  Better
+   * distribution than original, w/ minimal speed losses.
+   */
+  int low, high;
+  if (!is_integer(args[0])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  if (nargs == 1) {
+    low = 0;
+    high = parse_integer(args[0]) - 1;
+  } else {
+    if (!is_integer(args[1])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    low = parse_integer(args[0]);
+    high = parse_integer(args[1]);
+  }
+
+  if (low > high) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  safe_integer(get_random_long(low, high), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_die)
+{
+  unsigned int n;
+  unsigned int die;
+  unsigned int count;
+  unsigned int total = 0;
+  int show_all = 0, first = 1;
+
+  if (!is_uinteger(args[0]) || !is_uinteger(args[1])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+  n = parse_uinteger(args[0]);
+  die = parse_uinteger(args[1]);
+  if (nargs == 3)
+    show_all = parse_boolean(args[2]);
+
+  if (n == 0 || n > 20) {
+    safe_str(T("#-1 NUMBER OUT OF RANGE"), buff, bp);
+    return;
+  }
+  if (show_all) {
+    for (count = 0; count < n; count++) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_uinteger(get_random_long(1, die), buff, bp);
+    }
+  } else {
+    for (count = 0; count < n; count++)
+      total += get_random_long(1, die);
+
+    safe_uinteger(total, buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_switch)
+{
+  /* this works a bit like the @switch command: it returns the string
+   * appropriate to the match. It picks the first match, like @select
+   * does, though.
+   * Args to this function are passed unparsed. Args are not evaluated
+   * until they are needed.
+   */
+
+  int j, per;
+  char mstr[BUFFER_LEN], pstr[BUFFER_LEN], *dp;
+  char const *sp;
+  char *tbuf1;
+  int first = 1, found = 0, exact = 0;
+
+  if (strstr(called_as, "ALL"))
+    first = 0;
+
+  if (string_prefix(called_as, "CASE"))
+    exact = 1;
+
+  dp = mstr;
+  sp = args[0];
+  process_expression(mstr, &dp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *dp = '\0';
+
+  /* try matching, return match immediately when found */
+
+  for (j = 1; j < (nargs - 1); j += 2) {
+    dp = pstr;
+    sp = args[j];
+    process_expression(pstr, &dp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *dp = '\0';
+
+    if ((!exact)
+       ? local_wild_match(pstr, mstr)
+       : (strcmp(pstr, mstr) == 0)) {
+      /* If there's a #$ in a switch's action-part, replace it with
+       * the value of the conditional (mstr) before evaluating it.
+       */
+      if (!exact)
+       tbuf1 = replace_string("#$", mstr, args[j + 1]);
+      else
+       tbuf1 = args[j + 1];
+
+      sp = tbuf1;
+
+      per = process_expression(buff, bp, &sp,
+                              executor, caller, enactor,
+                              PE_DEFAULT, PT_DEFAULT, pe_info);
+      if (!exact)
+       mush_free((Malloc_t) tbuf1, "replace_string.buff");
+      found = 1;
+      if (per || first)
+       return;
+    }
+  }
+
+  if (!(nargs & 1) && !found) {
+    /* Default case */
+    tbuf1 = replace_string("#$", mstr, args[nargs - 1]);
+    sp = tbuf1;
+    process_expression(buff, bp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    mush_free((Malloc_t) tbuf1, "replace_string.buff");
+  }
+}
+
+FUNCTION(fun_reswitch)
+{
+  /* this works a bit like the @switch/regexp command */
+
+  int j, per;
+  char mstr[BUFFER_LEN], pstr[BUFFER_LEN], *dp;
+  char const *sp;
+  char *tbuf1;
+  int first = 1, found = 0, cs = 1;
+
+  if (strstr(called_as, "ALL"))
+    first = 0;
+
+  if (strcmp(called_as, "RESWITCHI") == 0
+      || strcmp(called_as, "RESWITCHALLI") == 0)
+    cs = 0;
+
+  dp = mstr;
+  sp = args[0];
+  process_expression(mstr, &dp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *dp = '\0';
+
+  /* try matching, return match immediately when found */
+
+  for (j = 1; j < (nargs - 1); j += 2) {
+    dp = pstr;
+    sp = args[j];
+    process_expression(pstr, &dp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *dp = '\0';
+
+    if (quick_regexp_match(pstr, mstr, cs)) {
+      /* If there's a #$ in a switch's action-part, replace it with
+       * the value of the conditional (mstr) before evaluating it.
+       */
+      tbuf1 = replace_string("#$", mstr, args[j + 1]);
+
+      sp = tbuf1;
+
+      per = process_expression(buff, bp, &sp,
+                              executor, caller, enactor,
+                              PE_DEFAULT, PT_DEFAULT, pe_info);
+      mush_free((Malloc_t) tbuf1, "replace_string.buff");
+      found = 1;
+      if (per || first)
+       return;
+    }
+  }
+
+  if (!(nargs & 1) && !found) {
+    /* Default case */
+    tbuf1 = replace_string("#$", mstr, args[nargs - 1]);
+    sp = tbuf1;
+    process_expression(buff, bp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    mush_free((Malloc_t) tbuf1, "replace_string.buff");
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_if)
+{
+  char tbuf[BUFFER_LEN], *tp;
+  char const *sp;
+
+  tp = tbuf;
+  sp = args[0];
+  process_expression(tbuf, &tp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *tp = '\0';
+  if (parse_boolean(tbuf)) {
+    sp = args[1];
+    process_expression(buff, bp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+  } else if (nargs > 2) {
+    sp = args[2];
+    process_expression(buff, bp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_mudname)
+{
+  safe_str(MUDNAME, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_version)
+{
+  safe_format(buff, bp, "CobraMUSH v%s [%s]", VERSION, VBRANCH);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_starttime)
+{
+  safe_str(show_time(first_start_time, 0), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_restarttime)
+{
+  safe_str(show_time(start_time, 0), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_restarts)
+{
+  safe_integer(reboot_count, buff, bp);
+}
+
+/* Data for soundex functions */
+static char soundex_val[26] = {
+  0, 1, 2, 3, 0, 1, 2, 0, 0,
+  2, 2, 4, 5, 5, 0, 1, 2, 6,
+  2, 3, 0, 1, 0, 2, 0, 2
+};
+
+/* The actual soundex routine */
+static char *
+soundex(str)
+    char *str;
+{
+  static char tbuf1[BUFFER_LEN];
+  char *p, *q;
+
+  tbuf1[0] = '\0';
+  tbuf1[1] = '\0';
+  tbuf1[2] = '\0';
+  tbuf1[3] = '\0';
+
+  p = tbuf1;
+  q = upcasestr(remove_markup(str, NULL));
+  /* First character is just copied */
+  *p = *q++;
+  /* Special case for PH->F */
+  if ((*p == 'P') && *q && (*q == 'H')) {
+    *p = 'F';
+    q++;
+  }
+  p++;
+  /* Convert letters to soundex values, squash duplicates */
+  while (*q) {
+    if (!isalpha((unsigned char) *q) || !isascii((unsigned char) *q)) {
+      q++;
+      continue;
+    }
+    *p = soundex_val[*q++ - 'A'] + '0';
+    if (*p != *(p - 1))
+      p++;
+  }
+  *p = '\0';
+  /* Remove zeros */
+  p = q = tbuf1;
+  while (*q) {
+    if (*q != '0')
+      *p++ = *q;
+    q++;
+  }
+  *p = '\0';
+  /* Pad/truncate to 4 chars */
+  if (tbuf1[1] == '\0')
+    tbuf1[1] = '0';
+  if (tbuf1[2] == '\0')
+    tbuf1[2] = '0';
+  if (tbuf1[3] == '\0')
+    tbuf1[3] = '0';
+  tbuf1[4] = '\0';
+  return tbuf1;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_soundex)
+{
+  /* Returns the soundex code for a word. This 4-letter code is:
+   * 1. The first letter of the word (exception: ph -> f)
+   * 2. Replace each letter with a numeric code from the soundex table
+   * 3. Remove consecutive numbers that are the same
+   * 4. Remove 0's
+   * 5. Truncate to 4 characters or pad with 0's.
+   * It's actually a bit messier than that to make it faster.
+   */
+  if (!args[0] || !*args[0] || !isalpha((unsigned char) *args[0])
+      || strchr(args[0], ' ')) {
+    safe_str(T("#-1 FUNCTION (SOUNDEX) REQUIRES A SINGLE WORD ARGUMENT"), buff,
+            bp);
+    return;
+  }
+  safe_str(soundex(args[0]), buff, bp);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_soundlike)
+{
+  /* Return 1 if the two arguments have the same soundex.
+   * This can be optimized to go character-by-character, but
+   * I deem the modularity to be more important. So there.
+   */
+  char tbuf1[5];
+  if (!*args[0] || !*args[1] || !isalpha((unsigned char) *args[0])
+      || !isalpha((unsigned char) *args[1]) || strchr(args[0], ' ')
+      || strchr(args[1], ' ')) {
+    safe_str(T("#-1 FUNCTION (SOUNDLIKE) REQUIRES TWO ONE-WORD ARGUMENTS"),
+            buff, bp);
+    return;
+  }
+  /* soundex uses a static buffer, so we need to save it */
+  strcpy(tbuf1, soundex(args[0]));
+  safe_boolean(!strcmp(tbuf1, soundex(args[1])), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_functions)
+{
+  safe_str(list_functions(), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_null)
+{
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_atat)
+{
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_list)
+{
+  if (!args[0] || !*args[0])
+    safe_str("#-1", buff, bp);
+  else if (string_prefix("motd", args[0]))
+    safe_str(cf_motd_msg, buff, bp);
+  else if (string_prefix("downmotd", args[0]) && Admin(executor))
+    safe_str(cf_downmotd_msg, buff, bp);
+  else if (string_prefix("fullmotd", args[0]) && Admin(executor))
+    safe_str(cf_fullmotd_msg, buff, bp);
+  else if (string_prefix("functions", args[0]))
+    safe_str(list_functions(), buff, bp);
+  else if (string_prefix("commands", args[0]))
+    safe_str(list_commands(), buff, bp);
+  else if (string_prefix("attribs", args[0]))
+    safe_str(list_attribs(), buff, bp);
+  else if (string_prefix("locks", args[0]))
+    list_locks(buff, bp, NULL);
+  else if (string_prefix("flags", args[0]))
+    safe_str(list_all_flags("FLAG", "", executor, 0x3), buff, bp);
+  else if (string_prefix("powers", args[0]))
+    safe_str(list_all_powers(executor, ""), buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_scan)
+{
+  dbref thing;
+  char save_ccom[BUFFER_LEN];
+  char *cmdptr;
+
+  if (nargs == 1) {
+    thing = executor;
+    cmdptr = args[0];
+  } else {
+    thing = match_thing(executor, args[0]);
+    if (!GoodObject(thing)) {
+      safe_str(T(e_notvis), buff, bp);
+      return;
+    }
+    if (!See_All(executor) && !controls(executor, thing)) {
+      notify(executor, T("Permission denied."));
+      safe_str("#-1", buff, bp);
+      return;
+    }
+    cmdptr = args[1];
+  }
+  strcpy(save_ccom, global_eval_context.ccom);
+  strncpy(global_eval_context.ccom, cmdptr, BUFFER_LEN);
+  global_eval_context.ccom[BUFFER_LEN - 1] = '\0';
+  safe_str(scan_list(thing, cmdptr), buff, bp);
+  strcpy(global_eval_context.ccom, save_ccom);
+}
+
+
+enum whichof_t { DO_FIRSTOF, DO_ALLOF };
+static void
+do_whichof(char *args[], int nargs, enum whichof_t flag, char *buff, char **bp,
+          dbref executor, dbref caller, dbref enactor, PE_Info * pe_info)
+{
+  int j;
+  char tbuf[BUFFER_LEN], *tp;
+  char const *sp;
+  char sep = ' ';
+  int first = 1;
+
+  tbuf[0] = '\0';
+
+  if (flag == DO_ALLOF) {
+    /* The last arg is a delimiter. Parse it in place. */
+    char insep[BUFFER_LEN];
+    char *isep = insep;
+    const char *arglast = args[nargs - 1];
+    process_expression(insep, &isep, &arglast, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *isep = '\0';
+    strcpy(args[nargs - 1], insep);
+
+    if (!delim_check(buff, bp, nargs, args, nargs, &sep))
+      return;
+    nargs--;
+  }
+
+  for (j = 0; j < nargs; j++) {
+    tp = tbuf;
+    sp = args[j];
+    process_expression(tbuf, &tp, &sp, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *tp = '\0';
+    if (parse_boolean(tbuf)) {
+      if (!first) {
+       safe_chr(sep, buff, bp);
+      } else
+       first = 0;
+
+      safe_str(tbuf, buff, bp);
+
+      if (flag == DO_FIRSTOF)
+       return;
+    }
+  }
+  if (flag == DO_FIRSTOF)
+    safe_str(tbuf, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_firstof)
+{
+  do_whichof(args, nargs, DO_FIRSTOF, buff, bp, executor,
+            caller, enactor, pe_info);
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_allof)
+{
+  do_whichof(args, nargs, DO_ALLOF, buff, bp, executor,
+            caller, enactor, pe_info);
+}
+
+/* Signal Shit */
+FUNCTION(fun_signal) {
+       enum qid_flags qsig = QID_FALSE;
+       int signal_r;
+
+       if(!*args[0] || !*args[1])
+               return;
+       /* find out which signal we're using */
+       if(string_prefix("kill", args[1]))
+               qsig = QID_KILL;
+       else if(string_prefix("freeze", args[1]))
+               qsig = QID_FREEZE;
+       else if(string_prefix("continue", args[1]))
+               qsig = QID_CONT;
+       else if(string_prefix("time", args[1]))
+               qsig = QID_TIME;
+       else if(string_prefix("query_t", args[1]))
+               qsig = QID_QUERY_T;
+       if(qsig == QID_FALSE) {
+               safe_str("#-1 INVALID SIGNAL", buff, bp);
+               return;
+       } else if(qsig == QID_TIME && (!*args[2] || atoi(args[2]) < 0)) {
+               safe_str("#-1 INVALID TIME ARGUMENT", buff, bp);
+               return;
+       }
+
+       switch((signal_r = do_signal_qid(executor, atoi(args[0]), qsig, qsig == QID_TIME ? atoi(args[2]) : -1))) {
+               case 0:
+                       safe_str("#-1 INVALID TIME ARGUMENT", buff, bp);
+                       break;
+               case -1:
+                       safe_str("#-1 INVALID QID", buff, bp);
+                       break;
+               case -2: /* we shouldn't be getting this */
+                       safe_str("#-1 INVALID SIGNAL", buff, bp);
+                       break;
+               case -3:
+                       safe_str("#-1 PERMISSION DENIED", buff, bp);
+               default:
+                       safe_integer(signal_r > -1 ? signal_r : 0, buff, bp);
+                       break;
+       }
+               
+}
+
+FUNCTION(fun_trigger) {
+
+       if(!args[0] || !*args[0]) {
+               safe_str("#-1 INVALID ARGUMENTS", buff, bp);
+               return;
+       }
+       if(!command_check_byname(executor, "@trigger")){
+               safe_str("#-1 PERMISSION DENIED", buff, bp);
+               return;
+       }
+
+       do_trigger(executor, args[0], args);
+}
+
diff --git a/src/funstr.c b/src/funstr.c
new file mode 100644 (file)
index 0000000..e2e26f9
--- /dev/null
@@ -0,0 +1,2037 @@
+/**
+ * \file funstr.c
+ *
+ * \brief String functions for mushcode.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <locale.h>
+#include "conf.h"
+#include "ansi.h"
+#include "externs.h"
+#include "case.h"
+#include "match.h"
+#include "parse.h"
+#include "pueblo.h"
+#include "attrib.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "mushdb.h"
+#include "htab.h"
+#include "lock.h"
+#include "confmagic.h"
+
+
+#ifdef WIN32
+#define LC_MESSAGES 6
+#pragma warning( disable : 4761)       /* NJG: disable warning re conversion */
+#endif
+
+#ifdef __APPLE__
+#define LC_MESSAGES 6
+#endif
+
+HASHTAB htab_tag;  /**< Hash table of safe html tags */
+
+#define MAX_COLS 32  /**< Maximum number of columns for align() */
+static int wraplen(char *str, int maxlen);
+static int align_one_line(char *buff, char **bp, int ncols,
+                         int cols[MAX_COLS], int calign[MAX_COLS],
+                         char *ptrs[MAX_COLS], ansi_string *as[MAX_COLS],
+                         int linenum, char *fieldsep, int fslen, char *linesep,
+                         int lslen, char filler);
+void init_tag_hashtab(void);
+void init_pronouns(void);
+
+/** Return an indicator of a player's gender.
+ * \param player player whose gender is to be checked.
+ * \retval 0 neuter.
+ * \retval 1 female.
+ * \retval 2 male.
+ * \retval 3 plural.
+ */
+int
+get_gender(dbref player)
+{
+  ATTR *a;
+
+  a = atr_get(player, "SEX");
+
+  if (!a)
+    return 0;
+
+  switch (*atr_value(a)) {
+  case 'T':
+  case 't':
+  case 'P':
+  case 'p':
+    return 3;
+  case 'M':
+  case 'm':
+    return 2;
+  case 'F':
+  case 'f':
+  case 'W':
+  case 'w':
+    return 1;
+  default:
+    return 0;
+  }
+}
+
+char *subj[4]; /**< Subjective pronouns */
+char *poss[4]; /**< Possessive pronouns */
+char *obj[4];  /**< Objective pronouns */
+char *absp[4]; /**< Absolute possessive pronouns */
+
+/** Macro to set a pronoun entry based on whether we're translating or not */
+#define SET_PRONOUN(p,v,u)  p = strdup((translate) ? (v) : (u))
+
+/** Initialize the pronoun translation strings.
+ * This function sets up the values of the arrays of subjective,
+ * possessive, objective, and absolute possessive pronouns with
+ * locale-appropriate values.
+ */
+void
+init_pronouns(void)
+{
+  int translate = 0;
+#ifdef HAS_SETLOCALE
+  char *loc;
+  if ((loc = setlocale(LC_MESSAGES, NULL))) {
+    if (strcmp(loc, "C") && strncmp(loc, "en", 2))
+      translate = 1;
+  }
+#endif
+  SET_PRONOUN(subj[0], T("pronoun:neuter,subjective"), "it");
+  SET_PRONOUN(subj[1], T("pronoun:feminine,subjective"), "she");
+  SET_PRONOUN(subj[2], T("pronoun:masculine,subjective"), "he");
+  SET_PRONOUN(subj[3], T("pronoun:plural,subjective"), "they");
+  SET_PRONOUN(poss[0], T("pronoun:neuter,possessive"), "its");
+  SET_PRONOUN(poss[1], T("pronoun:feminine,possessive"), "her");
+  SET_PRONOUN(poss[2], T("pronoun:masculine,possessive"), "his");
+  SET_PRONOUN(poss[3], T("pronoun:plural,possessive"), "their");
+  SET_PRONOUN(obj[0], T("pronoun:neuter,objective"), "it");
+  SET_PRONOUN(obj[1], T("pronoun:feminine,objective"), "her");
+  SET_PRONOUN(obj[2], T("pronoun:masculine,objective"), "him");
+  SET_PRONOUN(obj[3], T("pronoun:plural,objective"), "them");
+  SET_PRONOUN(absp[0], T("pronoun:neuter,absolute possessive"), "its");
+  SET_PRONOUN(absp[1], T("pronoun:feminine,absolute possessive"), "hers");
+  SET_PRONOUN(absp[2], T("pronoun:masculine,absolute possessive"), "his");
+  SET_PRONOUN(absp[3], T("pronoun:plural,absolute possessive "), "theirs");
+}
+
+#undef SET_PRONOUN
+
+/* ARGSUSED */
+FUNCTION(fun_isword)
+{
+  /* is every character a letter? */
+  char *p;
+  if (!args[0] || !*args[0]) {
+    safe_chr('0', buff, bp);
+    return;
+  }
+  for (p = args[0]; *p; p++) {
+    if (!isalpha((unsigned char) *p)) {
+      safe_chr('0', buff, bp);
+      return;
+    }
+  }
+  safe_chr('1', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_capstr)
+{
+  char *p;
+  p = skip_leading_ansi(args[0]);
+  if (!p) {
+    safe_strl(args[0], arglens[0], buff, bp);
+    return;
+  } else if (p != args[0]) {
+    char x = *p;
+    *p = '\0';
+    safe_strl(args[0], p - args[0], buff, bp);
+    *p = x;
+  }
+  if (*p) {
+    safe_chr(UPCASE(*p), buff, bp);
+    p++;
+  }
+  if (*p)
+    safe_str(p, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_art)
+{
+  /* checks a word and returns the appropriate article, "a" or "an" */
+  char c;
+  char *p = skip_leading_ansi(args[0]);
+
+  if (!p) {
+    safe_chr('a', buff, bp);
+    return;
+  }
+  c = tolower(*p);
+  if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u')
+    safe_str("an", buff, bp);
+  else
+    safe_chr('a', buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_subj)
+{
+  dbref thing;
+
+  thing = match_thing(executor, args[0]);
+  if (thing == NOTHING) {
+    safe_str(T(e_match), buff, bp);
+    return;
+  }
+  safe_str(subj[get_gender(thing)], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_poss)
+{
+  dbref thing;
+
+  thing = match_thing(executor, args[0]);
+  if (thing == NOTHING) {
+    safe_str(T(e_match), buff, bp);
+    return;
+  }
+  safe_str(poss[get_gender(thing)], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_obj)
+{
+  dbref thing;
+
+  thing = match_thing(executor, args[0]);
+  if (thing == NOTHING) {
+    safe_str(T(e_match), buff, bp);
+    return;
+  }
+  safe_str(obj[get_gender(thing)], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_aposs)
+{
+  dbref thing;
+
+  thing = match_thing(executor, args[0]);
+  if (thing == NOTHING) {
+    safe_str(T(e_match), buff, bp);
+    return;
+  }
+  safe_str(absp[get_gender(thing)], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_alphamax)
+{
+  char amax[BUFFER_LEN];
+  char *c;
+  int j, m = 0;
+  size_t len;
+
+  c = remove_markup(args[0], &len);
+  memcpy(amax, c, len);
+  for (j = 1; j < nargs; j++) {
+    c = remove_markup(args[j], &len);
+    if (strcoll(amax, c) < 0) {
+      memcpy(amax, c, len);
+      m = j;
+    }
+  }
+  safe_strl(args[m], arglens[m], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_alphamin)
+{
+  char amin[BUFFER_LEN];
+  char *c;
+  int j, m = 0;
+  size_t len;
+
+  c = remove_markup(args[0], &len);
+  memcpy(amin, c, len);
+  for (j = 1; j < nargs; j++) {
+    c = remove_markup(args[j], &len);
+    if (strcoll(amin, c) > 0) {
+      memcpy(amin, c, len);
+      m = j;
+    }
+  }
+  safe_strl(args[m], arglens[m], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_strlen)
+{
+  safe_integer(ansi_strlen(args[0]), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_mid)
+{
+  ansi_string *as;
+  int pos, len;
+
+  if (!is_integer(args[1]) || !is_integer(args[2])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+  pos = parse_integer(args[1]);
+  len = parse_integer(args[2]);
+
+  if ((pos < 0) || (len < 0)) {
+    safe_str(T(e_range), buff, bp);
+    free_ansi_string(as);
+    return;
+  }
+
+  safe_ansi_string(as, pos, len, buff, bp);
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_left)
+{
+  int len;
+  ansi_string *as;
+
+  if (!is_integer(args[1])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  len = parse_integer(args[1]);
+
+  if (len < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+  safe_ansi_string(as, 0, len, buff, bp);
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_right)
+{
+  int len;
+  ansi_string *as;
+
+  if (!is_integer(args[1])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  len = parse_integer(args[1]);
+
+  if (len < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+  if ((size_t) len > as->len)
+    safe_strl(args[0], arglens[0], buff, bp);
+  else
+    safe_ansi_string(as, as->len - len, as->len, buff, bp);
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_strinsert)
+{
+  /* Insert a string into another */
+  ansi_string *as;
+  int pos;
+
+  if (!is_integer(args[1])) {
+    safe_str(e_int, buff, bp);
+    return;
+  }
+
+  pos = parse_integer(args[1]);
+  if (pos < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+
+  if ((size_t) pos > as->len) {
+    /* Fast special case - concatenate args[2] to args[0] */
+    safe_strl(args[0], arglens[0], buff, bp);
+    safe_strl(args[2], arglens[0], buff, bp);
+    free_ansi_string(as);
+    return;
+  }
+
+  safe_ansi_string(as, 0, pos, buff, bp);
+  safe_strl(args[2], arglens[2], buff, bp);
+  safe_ansi_string(as, pos, as->len, buff, bp);
+  free_ansi_string(as);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_delete)
+{
+  ansi_string *as;
+  int pos, num;
+
+
+  if (!is_integer(args[1]) || !is_integer(args[2])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+
+  pos = parse_integer(args[1]);
+  num = parse_integer(args[2]);
+
+  if (pos < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+
+  if ((size_t) pos > as->len || num <= 0) {
+    safe_strl(args[0], arglens[0], buff, bp);
+    free_ansi_string(as);
+    return;
+  }
+
+  safe_ansi_string(as, 0, pos, buff, bp);
+  safe_ansi_string(as, pos + num, as->len, buff, bp);
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_strreplace)
+{
+  ansi_string *as, *anew;
+  int start, len, end;
+
+  if (!is_integer(args[1]) || !is_integer(args[2])) {
+    safe_str(T(e_ints), buff, bp);
+    return;
+  }
+
+  start = parse_integer(args[1]);
+  len = parse_integer(args[2]);
+
+  if (start < 0 || len < 0) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+  anew = parse_ansi_string(args[3]);
+
+  safe_ansi_string(as, 0, start, buff, bp);
+  safe_ansi_string(anew, 0, anew->len, buff, bp);
+
+  end = start + len;
+
+  if ((size_t) end < as->len)
+    safe_ansi_string(as, end, as->len - end, buff, bp);
+
+  free_ansi_string(as);
+  free_ansi_string(anew);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_comp)
+{
+  char type = 'A';
+
+  if (nargs == 3 && !(args[2] && *args[2])) {
+    safe_str(T("#-1 INVALID THIRD ARGUMENT"), buff, bp);
+    return;
+  } else if (nargs == 3) {
+    type = toupper(*args[2]);
+  }
+
+  switch (type) {
+  case 'A':                    /* Case-sensitive lexicographic */
+    {
+      char left[BUFFER_LEN], right[BUFFER_LEN], *l, *r;
+      size_t llen, rlen;
+      l = remove_markup(args[0], &llen);
+      memcpy(left, l, llen);
+      r = remove_markup(args[1], &rlen);
+      memcpy(right, r, rlen);
+      safe_integer(gencomp(left, right, ALPHANUM_LIST), buff, bp);
+      return;
+    }
+  case 'I':                    /* Case-insensitive lexicographic */
+    {
+      char left[BUFFER_LEN], right[BUFFER_LEN], *l, *r;
+      size_t llen, rlen;
+      l = remove_markup(args[0], &llen);
+      memcpy(left, l, llen);
+      r = remove_markup(args[1], &rlen);
+      memcpy(right, r, rlen);
+      safe_integer(gencomp(left, right, INSENS_ALPHANUM_LIST), buff, bp);
+      return;
+    }
+  case 'N':                    /* Integers */
+    if (!is_strict_integer(args[0]) || !is_strict_integer(args[1])) {
+      safe_str(T(e_ints), buff, bp);
+      return;
+    }
+    safe_integer(gencomp(args[0], args[1], NUMERIC_LIST), buff, bp);
+    return;
+  case 'F':
+    if (!is_strict_number(args[0]) || !is_strict_number(args[1])) {
+      safe_str(T(e_nums), buff, bp);
+      return;
+    }
+    safe_integer(gencomp(args[0], args[1], FLOAT_LIST), buff, bp);
+    return;
+  case 'D':
+    {
+      dbref a, b;
+      a = parse_objid(args[0]);
+      b = parse_objid(args[1]);
+      if (a == NOTHING || b == NOTHING) {
+       safe_str(T("#-1 INVALID DBREF"), buff, bp);
+       return;
+      }
+      safe_integer(gencomp(args[0], args[1], DBREF_LIST), buff, bp);
+      return;
+    }
+  default:
+    safe_str(T("#-1 INVALID THIRD ARGUMENT"), buff, bp);
+    return;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_pos)
+{
+  char tbuf[BUFFER_LEN];
+  char *pos;
+  size_t len;
+
+  pos = remove_markup(args[1], &len);
+  memcpy(tbuf, pos, len);
+  pos = strstr(tbuf, remove_markup(args[0], NULL));
+  if (pos)
+    safe_integer(pos - tbuf + 1, buff, bp);
+  else
+    safe_str("#-1", buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lpos)
+{
+  char *pos;
+  char c = ' ';
+  size_t n, len;
+  int first = 1;
+
+  if (args[1][0])
+    c = args[1][0];
+
+  pos = remove_markup(args[0], &len);
+  for (n = 0; n < len; n++)
+    if (pos[n] == c) {
+      if (first)
+       first = 0;
+      else
+       safe_chr(' ', buff, bp);
+      safe_integer(n, buff, bp);
+    }
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_strmatch)
+{
+  char tbuf[BUFFER_LEN];
+  char *t;
+  size_t len;
+  /* matches a wildcard pattern for an _entire_ string */
+
+  t = remove_markup(args[0], &len);
+  memcpy(tbuf, t, len);
+  safe_boolean(quick_wild(remove_markup(args[1], NULL), tbuf), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_strcat)
+{
+  int j;
+
+  for (j = 0; j < nargs; j++)
+    safe_strl(args[j], arglens[j], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_flip)
+{
+  ansi_string *as;
+  int p, n;
+
+  as = parse_ansi_string(args[0]);
+  populate_codes(as);
+
+  for (p = 0, n = as->len - 1; p < n; p++, n--) {
+    char *tcode;
+    char t;
+
+    tcode = as->codes[p];
+    t = as->text[p];
+    as->codes[p] = as->codes[n];
+    as->text[p] = as->text[n];
+    as->codes[n] = tcode;
+    as->text[n] = t;
+  }
+
+  safe_ansi_string(as, 0, as->len, buff, bp);
+
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_merge)
+{
+  /* given s1, s2, and a list of characters, for each character in s1,
+   * if the char is in the list, replace it with the corresponding
+   * char in s2.
+   */
+
+  char *str, *rep;
+  char matched[UCHAR_MAX + 1];
+
+  /* do length checks first */
+  if (arglens[0] != arglens[1]) {
+    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
+    return;
+  }
+
+  memset(matched, 0, sizeof matched);
+
+  /* find the characters to look for */
+  if (!args[2] || !*args[2])
+    matched[(unsigned char) ' '] = 1;
+  else {
+    unsigned char *p;
+    for (p = (unsigned char *) args[2]; p && *p; p++)
+      matched[*p] = 1;
+  }
+
+  /* walk strings, copy from the appropriate string */
+  for (str = args[0], rep = args[1]; *str && *rep; str++, rep++) {
+    *str = matched[(unsigned char) *str] ? *rep : *str;
+  }
+  safe_str(args[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_tr)
+{
+  /* given str, s1, s2, for each character in str, if the char
+   * is in s1, replace it with the char at the same index in s2.
+   */
+
+  char charmap[256];
+  char instr[BUFFER_LEN], outstr[BUFFER_LEN];
+  char rawstr[BUFFER_LEN];
+  char *ip, *op;
+  size_t i, len;
+  char *c;
+  ansi_string *as;
+
+  /* No ansi allowed in find or replace lists */
+  c = remove_markup(args[1], &len);
+  memcpy(rawstr, c, len);
+
+  /* do length checks first */
+
+  for (i = 0; i < 256; i++) {
+    charmap[i] = (char) i;
+  }
+
+  ip = instr;
+  op = outstr;
+
+  for (i = 0; i < len; i++) {
+    safe_chr(rawstr[i], instr, &ip);
+    /* Handle a range of characters */
+    if (i != len - 1 && rawstr[i + 1] == '-' && i != len - 2) {
+      int dir, sentinel, cur;
+
+      if (rawstr[i] < rawstr[i + 2])
+       dir = 1;
+      else
+       dir = -1;
+
+      sentinel = rawstr[i + 2] + dir;
+      cur = rawstr[i] + dir;
+
+      while (cur != sentinel) {
+       safe_chr((char) cur, instr, &ip);
+       cur += dir;
+      }
+      i += 2;
+    }
+  }
+
+  c = remove_markup(args[2], &len);
+  memcpy(rawstr, c, len);
+  for (i = 0; i < len; i++) {
+    safe_chr(rawstr[i], outstr, &op);
+    /* Handle a range of characters */
+    if (i != len - 1 && rawstr[i + 1] == '-' && i != len - 2) {
+      int dir, sentinel, cur;
+
+      if (rawstr[i] < rawstr[i + 2])
+       dir = 1;
+      else
+       dir = -1;
+
+      sentinel = rawstr[i + 2] + dir;
+      cur = rawstr[i] + dir;
+
+      while (cur != sentinel) {
+       safe_chr((char) cur, outstr, &op);
+       cur += dir;
+      }
+      i += 2;
+    }
+  }
+
+  if ((ip - instr) != (op - outstr)) {
+    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
+    return;
+  }
+
+  len = ip - instr;
+
+  for (i = 0; i < len; i++)
+    charmap[(unsigned char) instr[i]] = outstr[i];
+
+  /* walk the string, translating characters */
+  as = parse_ansi_string(args[0]);
+  populate_codes(as);
+  len = as->len;
+  for (i = 0; i < len; i++) {
+    as->text[i] = charmap[(unsigned char) as->text[i]];
+  }
+  safe_ansi_string(as, 0, as->len, buff, bp);
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lcstr)
+{
+  char *p, *y;
+  p = args[0];
+  while (*p) {
+    y = skip_leading_ansi(p);
+    if (y != p) {
+      char t;
+      t = *y;
+      *y = '\0';
+      safe_str(p, buff, bp);
+      *y = t;
+      p = y;
+    }
+    if (*p) {
+      safe_chr(DOWNCASE(*p), buff, bp);
+      p++;
+    }
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ucstr)
+{
+  char *p, *y;
+  p = args[0];
+  while (*p) {
+    y = skip_leading_ansi(p);
+    if (y != p) {
+      char t;
+      t = *y;
+      *y = '\0';
+      safe_str(p, buff, bp);
+      *y = t;
+      p = y;
+    }
+    if (*p) {
+      safe_chr(UPCASE(*p), buff, bp);
+      p++;
+    }
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_repeat)
+{
+  int times;
+  char *ap;
+
+  if (!is_integer(args[1])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  times = parse_integer(args[1]);
+  if (times < 0) {
+    safe_str(T("#-1 ARGUMENT MUST BE NON-NEGATIVE INTEGER"), buff, bp);
+    return;
+  }
+  if (!*args[0])
+    return;
+
+  /* Special-case repeating one character */
+  if (arglens[0] == 1) {
+    safe_fill(args[0][0], times, buff, bp);
+    return;
+  }
+
+  /* Do the repeat in O(lg n) time. */
+  /* This takes advantage of the fact that we're given a BUFFER_LEN
+   * buffer for args[0] that we are free to trash.  Huzzah! */
+  ap = args[0] + arglens[0];
+  while (times) {
+    if (times & 1) {
+      if (safe_strl(args[0], arglens[0], buff, bp) != 0)
+       break;
+    }
+    safe_str(args[0], args[0], &ap);
+    *ap = '\0';
+    arglens[0] = strlen(args[0]);
+    times = times >> 1;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_scramble)
+{
+  int n, i, j;
+  ansi_string *as;
+
+  if (!*args[0])
+    return;
+
+  as = parse_ansi_string(args[0]);
+  populate_codes(as);
+  n = as->len;
+  for (i = 0; i < n; i++) {
+    char t, *tcode;
+    j = get_random_long(i, n - 1);
+    t = as->text[j];
+    as->text[j] = as->text[i];
+    as->text[i] = t;
+    tcode = as->codes[j];
+    as->codes[j] = as->codes[i];
+    as->codes[i] = tcode;
+  }
+  safe_ansi_string(as, 0, as->len, buff, bp);
+  free_ansi_string(as);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ljust)
+{
+  /* pads a string with trailing blanks (or other fill character) */
+
+  size_t spaces, len;
+  char sep;
+
+  if (!is_uinteger(args[1])) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+  len = ansi_strlen(args[0]);
+  spaces = parse_uinteger(args[1]);
+  if (spaces >= BUFFER_LEN)
+    spaces = BUFFER_LEN - 1;
+
+  if (len >= spaces) {
+    safe_strl(args[0], arglens[0], buff, bp);
+    return;
+  }
+  spaces -= len;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  safe_strl(args[0], arglens[0], buff, bp);
+  safe_fill(sep, spaces, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_rjust)
+{
+  /* pads a string with leading blanks (or other fill character) */
+
+  size_t spaces, len;
+  char sep;
+
+  if (!is_uinteger(args[1])) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+  len = ansi_strlen(args[0]);
+  spaces = parse_uinteger(args[1]);
+  if (spaces >= BUFFER_LEN)
+    spaces = BUFFER_LEN - 1;
+
+  if (len >= spaces) {
+    safe_strl(args[0], arglens[0], buff, bp);
+    return;
+  }
+  spaces -= len;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  safe_fill(sep, spaces, buff, bp);
+  safe_strl(args[0], arglens[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_center)
+{
+  /* pads a string with leading blanks (or other fill character) */
+
+  size_t width, len, lsp, rsp;
+  char sep;
+
+  if (!is_uinteger(args[1])) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+  width = parse_uinteger(args[1]);
+  len = ansi_strlen(args[0]);
+  if (len >= width) {
+    safe_strl(args[0], arglens[0], buff, bp);
+    return;
+  }
+  rsp = width - len;
+  lsp = rsp / 2;
+  rsp -= lsp;
+  if (lsp >= BUFFER_LEN)
+    lsp = BUFFER_LEN - 1;
+  if (rsp >= BUFFER_LEN)
+    rsp = BUFFER_LEN - 1;
+
+  if (!delim_check(buff, bp, nargs, args, 3, &sep))
+    return;
+
+  safe_fill(sep, lsp, buff, bp);
+  safe_strl(args[0], arglens[0], buff, bp);
+  safe_fill(sep, rsp, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_foreach)
+{
+  /* Like map(), but it operates on a string, rather than on a list,
+   * calling a user-defined function for each character in the string.
+   * No delimiter is inserted between the results.
+   */
+
+  dbref thing;
+  ATTR *attrib;
+  char const *ap, *lp;
+  char *asave, cbuf[2];
+  char *tptr[2];
+  char place[SBUF_LEN];
+  int placenr = 0;
+  int funccount;
+  char *oldbp;
+  char start, end;
+  char letters[BUFFER_LEN];
+  size_t len;
+
+  if (nargs >= 3) {
+    if (!delim_check(buff, bp, nargs, args, 3, &start))
+      return;
+  }
+
+  if (nargs == 4) {
+    if (!delim_check(buff, bp, nargs, args, 4, &end))
+      return;
+  } else {
+    end = '\0';
+  }
+
+  /* find our object and attribute */
+  parse_anon_attrib(executor, args[0], &thing, &attrib);
+  if (!GoodObject(thing) || !attrib || !Can_Read_Attr(executor, thing, attrib)) {
+    free_anon_attrib(attrib);
+    return;
+  }
+  strcpy(place, "0");
+  asave = safe_atr_value(attrib);
+
+  /* save our stack */
+  tptr[0] = global_eval_context.wenv[0];
+  tptr[1] = global_eval_context.wenv[1];
+  global_eval_context.wenv[1] = place;
+
+  ap = remove_markup(args[1], &len);
+  memcpy(letters, ap, len);
+
+  lp = trim_space_sep(letters, ' ');
+  if (nargs >= 3) {
+    char *tmp = strchr(lp, start);
+
+    if (!tmp) {
+      safe_str(lp, buff, bp);
+      free((Malloc_t) asave);
+      free_anon_attrib(attrib);
+      global_eval_context.wenv[1] = tptr[1];
+      return;
+    }
+    oldbp = place;
+    placenr = (tmp + 1) - lp;
+    safe_integer_sbuf(placenr, place, &oldbp);
+    oldbp = *bp;
+
+    *tmp = '\0';
+    safe_str(lp, buff, bp);
+    lp = tmp + 1;
+  }
+
+  cbuf[1] = '\0';
+  global_eval_context.wenv[0] = cbuf;
+  oldbp = *bp;
+  funccount = pe_info->fun_invocations;
+  while (*lp && *lp != end) {
+    *cbuf = *lp++;
+    ap = asave;
+    if (process_expression(buff, bp, &ap, thing, executor, enactor,
+                          PE_DEFAULT, PT_DEFAULT, pe_info))
+      break;
+    if (*bp == oldbp && pe_info->fun_invocations == funccount)
+      break;
+    oldbp = place;
+    safe_integer_sbuf(++placenr, place, &oldbp);
+    *oldbp = '\0';
+    oldbp = *bp;
+    funccount = pe_info->fun_invocations;
+  }
+  if (*lp)
+    safe_str(lp + 1, buff, bp);
+  free((Malloc_t) asave);
+  free_anon_attrib(attrib);
+  global_eval_context.wenv[0] = tptr[0];
+  global_eval_context.wenv[1] = tptr[1];
+}
+
+extern char escaped_chars[UCHAR_MAX + 1];
+extern char escaped_chars_s[UCHAR_MAX +1];
+
+/* ARGSUSED */
+FUNCTION(fun_secure)
+{
+  /* this function smashes all occurences of "unsafe" characters in a string.
+   * "unsafe" characters are defined by the escaped_chars table.
+   * these characters get replaced by spaces
+   */
+  unsigned char *p;
+
+  for (p = (unsigned char *) args[0]; *p; p++)
+    if (escaped_chars_s[*p])
+      *p = ' ';
+
+  safe_strl(args[0], arglens[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_escape)
+{
+  unsigned char *s;
+
+  if (arglens[0]) {
+    safe_chr('\\', buff, bp);
+    for (s = (unsigned char *) args[0]; *s; s++) {
+      if ((s != (unsigned char *) args[0]) && escaped_chars[*s])
+       safe_chr('\\', buff, bp);
+      safe_chr(*s, buff, bp);
+    }
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_trim)
+{
+  /* Similar to squish() but it doesn't trim spaces in the center, and
+   * takes a delimiter argument and trim style.
+   */
+
+  char *p, *q, sep;
+  int trim;
+  int trim_style_arg, trim_char_arg;
+
+  /* Alas, PennMUSH and TinyMUSH used different orders for the arguments.
+   * We'll give the users an option about it
+   */
+  if (!strcmp(called_as, "TRIMTINY")) {
+    trim_style_arg = 2;
+    trim_char_arg = 3;
+  } else if (!strcmp(called_as, "TRIMPENN")) {
+    trim_style_arg = 3;
+    trim_char_arg = 2;
+  } else if (TINY_TRIM_FUN) {
+    trim_style_arg = 2;
+    trim_char_arg = 3;
+  } else {
+    trim_style_arg = 3;
+    trim_char_arg = 2;
+  }
+
+  if (!delim_check(buff, bp, nargs, args, trim_char_arg, &sep))
+    return;
+
+  /* If a trim style is provided, it must be the third argument. */
+  if (nargs >= trim_style_arg) {
+    switch (DOWNCASE(*args[trim_style_arg - 1])) {
+    case 'l':
+      trim = 1;
+      break;
+    case 'r':
+      trim = 2;
+      break;
+    default:
+      trim = 3;
+      break;
+    }
+  } else
+    trim = 3;
+
+  /* We will never need to check for buffer length overrunning, since
+   * we will always get a smaller string. Thus, we can copy at the
+   * same time we skip stuff.
+   */
+
+  /* If necessary, skip over the leading stuff. */
+  p = args[0];
+  if (trim != 2) {
+    while (*p == sep)
+      p++;
+  }
+  /* Cut off the trailing stuff, if appropriate. */
+  if ((trim != 1) && (*p != '\0')) {
+    q = args[0] + arglens[0] - 1;
+    while ((q > p) && (*q == sep))
+      q--;
+    q[1] = '\0';
+  }
+  safe_str(p, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_lit)
+{
+  /* Just returns the argument, literally */
+  safe_strl(args[0], arglens[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_squish)
+{
+  /* zaps leading and trailing spaces, and reduces other spaces to a single
+   * space. This only applies to the literal space character, and not to
+   * tabs, newlines, etc.
+   * We do not need to check for buffer length overflows, since we're
+   * never going to end up with a longer string.
+   */
+
+  char *tp;
+  char sep;
+
+  /* Figure out the character to squish */
+  if (!delim_check(buff, bp, nargs, args, 2, &sep))
+    return;
+
+  /* get rid of trailing spaces first, so we don't have to worry about
+   * them later.
+   */
+  tp = args[0] + arglens[0] - 1;
+  while ((tp > args[0]) && (*tp == sep))
+    tp--;
+  tp[1] = '\0';
+
+  for (tp = args[0]; *tp == sep; tp++) /* skip leading spaces */
+    ;
+
+  while (*tp) {
+    safe_chr(*tp, buff, bp);
+    if (*tp == sep)
+      while (*tp == sep)
+       tp++;
+    else
+      tp++;
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_space)
+{
+  int s;
+
+  if (!is_uinteger(args[0])) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+  s = parse_integer(args[0]);
+  safe_fill(' ', s, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_beep)
+{
+  int k;
+
+  /* this function prints 1 to 5 beeps. The alert character '\a' is
+   * an ANSI C invention; non-ANSI-compliant implementations may ignore
+   * the '\' character and just print an 'a', or do something else nasty,
+   * so we define it to be something reasonable in ansi.h.
+   */
+
+  if (nargs) {
+    if (!is_integer(args[0])) {
+      safe_str(T(e_int), buff, bp);
+      return;
+    }
+    k = parse_integer(args[0]);
+  } else
+    k = 1;
+
+  if (!Admin(executor) || (k <= 0) || (k > 5)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_fill(BEEP_CHAR, k, buff, bp);
+}
+
+/** Initialize the html tag hash table with all the safe tags from HTML 4.0 */
+void
+init_tag_hashtab(void)
+{
+  static char dummy = 1;
+  int i = 0;
+  char tbuf[32];
+  char *tbp;
+  static const char *safetags[] = { "A", "B", "I", "U", "STRONG", "EM",
+    "ADDRESS", "BLOCKQUOTE", "CENTER", "DEL", "DIV",
+    "H1", "H2", "H3", "H4", "H5", "H6", "HR", "INS",
+    "P", "PRE", "DIR", "DL", "DT", "DD", "LI", "MENU", "OL", "UL",
+    "TABLE", "CAPTION", "COLGROUP", "COL", "THEAD", "TFOOT",
+    "TBODY", "TR", "TD", "TH",
+    "BR", "FONT", "IMG", "SPAN", "SUB", "SUP",
+    "ABBR", "ACRONYM", "CITE", "CODE", "DFN", "KBD", "SAMP", "VAR",
+    "BIG", "S", "SMALL", "STRIKE", "TT",
+    NULL
+  };
+  hashinit(&htab_tag, 64, 1);
+
+  while(safetags[i]) {
+    memset(tbuf, '\0', 32);
+    tbp = tbuf;
+    hashadd(safetags[i], (void *) &dummy, &htab_tag);
+    safe_format( tbuf, &tbp, "/%s", safetags[i] );
+    hashadd(tbuf, (void *) &dummy, &htab_tag);
+    i++;
+  }
+}
+
+FUNCTION(fun_ord)
+{
+  char *m;
+  size_t len = 0;
+  if (!args[0] || !args[0][0]) {
+    safe_str(T("#-1 FUNCTION EXPECTS ONE CHARACTER"), buff, bp);
+    return;
+  }
+  m = remove_markup(args[0], &len);
+
+  if (len != 2)                        /* len includes trailing nul */
+    safe_str(T("#-1 FUNCTION EXPECTS ONE CHARACTER"), buff, bp);
+  else if (isprint((unsigned char) *m))
+    safe_integer((unsigned char) *m, buff, bp);
+  else
+    safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);
+}
+
+FUNCTION(fun_chr)
+{
+  int c;
+
+  if (!is_integer(args[0])) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+  c = parse_integer(args[0]);
+  if (c < 0 || c > UCHAR_MAX)
+    safe_str(T("#-1 THIS ISN'T UNICODE"), buff, bp);
+  else if (isprint(c))
+    safe_chr(c, buff, bp);
+  else
+    safe_str(T("#-1 UNPRINTABLE CHARACTER"), buff, bp);
+
+}
+
+FUNCTION(fun_accent)
+{
+  if (arglens[0] != arglens[1]) {
+    safe_str(T("#-1 STRING LENGTHS MUST BE EQUAL"), buff, bp);
+    return;
+  }
+  safe_accent(args[0], args[1], arglens[0], buff, bp);
+}
+
+FUNCTION(fun_stripaccents)
+{
+  int n;
+  for (n = 0; n < arglens[0]; n++) {
+    if (accent_table[(unsigned char) args[0][n]].base)
+      safe_str(accent_table[(unsigned char) args[0][n]].base, buff, bp);
+    else
+      safe_chr(args[0][n], buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_html)
+{
+  if (!Site(executor))
+    safe_str(T(e_perm), buff, bp);
+  else
+    safe_tag(args[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_tag)
+{
+  int i;
+  if (!Site(executor) && !hash_find(&htab_tag, strupper(args[0])))
+    safe_str("#-1", buff, bp);
+  else {
+    safe_chr(TAG_START, buff, bp);
+    safe_strl(args[0], arglens[0], buff, bp);
+    for (i = 1; i < nargs; i++) {
+      if (ok_tag_attribute(executor, args[i])) {
+       safe_chr(' ', buff, bp);
+       safe_strl(args[i], arglens[i], buff, bp);
+      }
+    }
+    safe_chr(TAG_END, buff, bp);
+  }
+}
+
+/* ARGSUSED */
+FUNCTION(fun_endtag)
+{
+  if (!Site(executor) && !hash_find(&htab_tag, strupper(args[0])))
+    safe_str("#-1", buff, bp);
+  else
+    safe_tag_cancel(args[0], buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_tagwrap)
+{
+  if (!Site(executor) && !hash_find(&htab_tag, strupper(args[0])))
+    safe_str("#-1", buff, bp);
+  else {
+    if (nargs == 2)
+      safe_tag_wrap(args[0], NULL, args[1], buff, bp, executor);
+    else
+      safe_tag_wrap(args[0], args[1], args[2], buff, bp, executor);
+  }
+}
+
+#define COL_FLASH       (1)    /**< ANSI flash attribute bit */
+#define COL_HILITE      (2)    /**< ANSI hilite attribute bit */
+#define COL_INVERT      (4)    /**< ANSI inverse attribute bit */
+#define COL_UNDERSCORE  (8)    /**< ANSI underscore attribute bit */
+
+#define VAL_FLASH       (5)    /**< ANSI flag attribute value */
+#define VAL_HILITE      (1)    /**< ANSI hilite attribute value */
+#define VAL_INVERT      (7)    /**< ANSI inverse attribute value */
+#define VAL_UNDERSCORE  (4)    /**< ANSI underscore attribute value */
+
+#define COL_BLACK       (30)   /**< ANSI color black */
+#define COL_RED         (31)   /**< ANSI color red */
+#define COL_GREEN       (32)   /**< ANSI color green */
+#define COL_YELLOW      (33)   /**< ANSI color yellow */
+#define COL_BLUE        (34)   /**< ANSI color blue */
+#define COL_MAGENTA     (35)   /**< ANSI color magenta */
+#define COL_CYAN        (36)   /**< ANSI color cyan */
+#define COL_WHITE       (37)   /**< ANSI color white */
+
+/** The ansi attributes associated with a character. */
+typedef struct {
+  char flags;          /**< Ansi text attributes */
+  char fore;           /**< Ansi foreground color */
+  char back;           /**< Ansi background color */
+} ansi_data;
+
+static void dump_ansi_codes(ansi_data * ad, char *buff, char **bp);
+
+/** If we're adding y to x, do we need to add z as well? */
+#define EDGE_UP(x,y,z)  (((y) & (z)) && !((x) & (z)))
+
+static void
+dump_ansi_codes(ansi_data * ad, char *buff, char **bp)
+{
+  static ansi_data old_ad = { 0, 0, 0 };
+  int f = 0;
+
+  if ((old_ad.fore && !ad->fore)
+      || (old_ad.back && !ad->back)
+      || ((old_ad.flags & ad->flags) != old_ad.flags)) {
+    safe_str(ANSI_NORMAL, buff, bp);
+    old_ad.flags = 0;
+    old_ad.fore = 0;
+    old_ad.back = 0;
+  }
+
+  if ((old_ad.fore == ad->fore)
+      && (old_ad.back == ad->back)
+      && (old_ad.flags == ad->flags))
+    /* If nothing has changed, don't bother doing anything.
+     * This stops the entirely pointless \e[m being generated. */
+    return;
+
+  safe_str(ANSI_BEGIN, buff, bp);
+
+  if (EDGE_UP(old_ad.flags, ad->flags, COL_FLASH)) {
+    if (f++)
+      safe_chr(';', buff, bp);
+    safe_integer(VAL_FLASH, buff, bp);
+  }
+
+  if (EDGE_UP(old_ad.flags, ad->flags, COL_HILITE)) {
+    if (f++)
+      safe_chr(';', buff, bp);
+    safe_integer(VAL_HILITE, buff, bp);
+  }
+
+  if (EDGE_UP(old_ad.flags, ad->flags, COL_INVERT)) {
+    if (f++)
+      safe_chr(';', buff, bp);
+    safe_integer(VAL_INVERT, buff, bp);
+  }
+
+  if (EDGE_UP(old_ad.flags, ad->flags, COL_UNDERSCORE)) {
+    if (f++)
+      safe_chr(';', buff, bp);
+    safe_integer(VAL_UNDERSCORE, buff, bp);
+  }
+
+  if (ad->fore != old_ad.fore) {
+    if (f++)
+      safe_chr(';', buff, bp);
+    safe_integer(ad->fore, buff, bp);
+  }
+
+  if (ad->back != old_ad.back) {
+    if (f++)
+      safe_chr(';', buff, bp);
+    safe_integer(ad->back + 10, buff, bp);
+  }
+
+  safe_str(ANSI_END, buff, bp);
+
+  old_ad = *ad;
+
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_ansi)
+{
+  static char tbuff[BUFFER_LEN];
+  static ansi_data stack[1024] = { {0, 0, 0} }, *sp = stack;
+  char const *arg0, *arg1;
+  char *tbp;
+
+  tbp = tbuff;
+  arg0 = args[0];
+  process_expression(tbuff, &tbp, &arg0, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *tbp = '\0';
+
+  sp[1] = sp[0];
+  sp++;
+
+  for (tbp = tbuff; *tbp; tbp++) {
+    switch (*tbp) {
+    case 'n':                  /* normal */
+      sp->flags = 0;
+      sp->fore = 0;
+      sp->back = 0;
+      break;
+    case 'f':                  /* flash */
+      sp->flags |= COL_FLASH;
+      break;
+    case 'h':                  /* hilite */
+      sp->flags |= COL_HILITE;
+      break;
+    case 'i':                  /* inverse */
+      sp->flags |= COL_INVERT;
+      break;
+    case 'u':                  /* underscore */
+      sp->flags |= COL_UNDERSCORE;
+      break;
+    case 'F':                  /* flash */
+      sp->flags &= ~COL_FLASH;
+      break;
+    case 'H':                  /* hilite */
+      sp->flags &= ~COL_HILITE;
+      break;
+    case 'I':                  /* inverse */
+      sp->flags &= ~COL_INVERT;
+      break;
+    case 'U':                  /* underscore */
+      sp->flags &= ~COL_UNDERSCORE;
+      break;
+    case 'b':                  /* blue fg */
+      sp->fore = COL_BLUE;
+      break;
+    case 'c':                  /* cyan fg */
+      sp->fore = COL_CYAN;
+      break;
+    case 'g':                  /* green fg */
+      sp->fore = COL_GREEN;
+      break;
+    case 'm':                  /* magenta fg */
+      sp->fore = COL_MAGENTA;
+      break;
+    case 'r':                  /* red fg */
+      sp->fore = COL_RED;
+      break;
+    case 'w':                  /* white fg */
+      sp->fore = COL_WHITE;
+      break;
+    case 'x':                  /* black fg */
+      sp->fore = COL_BLACK;
+      break;
+    case 'y':                  /* yellow fg */
+      sp->fore = COL_YELLOW;
+      break;
+    case 'B':                  /* blue bg */
+      sp->back = COL_BLUE;
+      break;
+    case 'C':                  /* cyan bg */
+      sp->back = COL_CYAN;
+      break;
+    case 'G':                  /* green bg */
+      sp->back = COL_GREEN;
+      break;
+    case 'M':                  /* magenta bg */
+      sp->back = COL_MAGENTA;
+      break;
+    case 'R':                  /* red bg */
+      sp->back = COL_RED;
+      break;
+    case 'W':                  /* white bg */
+      sp->back = COL_WHITE;
+      break;
+    case 'X':                  /* black bg */
+      sp->back = COL_BLACK;
+      break;
+    case 'Y':                  /* yellow bg */
+      sp->back = COL_YELLOW;
+      break;
+    }
+  }
+
+  dump_ansi_codes(sp, buff, bp);
+
+  arg1 = args[1];
+  process_expression(buff, bp, &arg1, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+
+  dump_ansi_codes(--sp, buff, bp);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_stripansi)
+{
+  /* Strips ANSI codes away from a given string of text. Starts by
+   * finding the '\x' character and stripping until it hits an 'm'.
+   */
+
+  char *cp;
+
+  cp = remove_markup(args[0], NULL);
+  safe_str(cp, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_edit)
+{
+  int i;
+  char *f, *r, *raw;
+  ansi_string *prebuf;
+  char postbuf[BUFFER_LEN], lastbuf[BUFFER_LEN], *postp;
+  size_t rlen, flen;
+
+  prebuf = parse_ansi_string(args[0]);
+  raw = args[0];
+  for (i = 1; i < nargs - 1; i += 2) {
+
+    postp = postbuf;
+    f = args[i];               /* find this */
+    r = args[i + 1];           /* replace it with this */
+    flen = arglens[i];
+    rlen = arglens[i + 1];
+
+    /* Check for nothing to avoid infinite loop */
+    if (!*f && !*r)
+      continue;
+
+    if (flen == 1 && *f == '$') {
+      /* append */
+      safe_str(raw, postbuf, &postp);
+      safe_strl(r, rlen, postbuf, &postp);
+    } else if (flen == 1 && *f == '^') {
+      /* prepend */
+      safe_strl(r, rlen, postbuf, &postp);
+      safe_str(raw, postbuf, &postp);
+    } else if (!*f) {
+      /* insert between every character */
+      size_t last;
+      safe_strl(r, rlen, postbuf, &postp);
+      for (last = 0; last < prebuf->len; last++) {
+       safe_ansi_string(prebuf, last, 1, postbuf, &postp);
+       safe_strl(r, rlen, postbuf, &postp);
+      }
+    } else {
+      char *p;
+      size_t last = 0;
+
+      while (last < prebuf->len && (p = strstr(prebuf->text + last, f)) != NULL) {
+       safe_ansi_string(prebuf, last, p - (prebuf->text + last),
+                        postbuf, &postp);
+       safe_strl(r, rlen, postbuf, &postp);
+       last = p - prebuf->text + flen;
+      }
+      if (last < prebuf->len)
+       safe_ansi_string(prebuf, last, prebuf->len, postbuf, &postp);
+    }
+    *postp = '\0';
+    free_ansi_string(prebuf);
+    prebuf = parse_ansi_string(postbuf);
+    strcpy(lastbuf, postbuf);
+    raw = lastbuf;
+  }
+  safe_ansi_string(prebuf, 0, prebuf->len, buff, bp);
+  free_ansi_string(prebuf);
+}
+
+FUNCTION(fun_brackets)
+{
+  char *str;
+  int rbrack, lbrack, rbrace, lbrace, lcurl, rcurl;
+
+  lcurl = rcurl = rbrack = lbrack = rbrace = lbrace = 0;
+  str = args[0];               /* The string to count the brackets in */
+  while (*str) {
+    switch (*str) {
+    case '[':
+      lbrack++;
+      break;
+    case ']':
+      rbrack++;
+      break;
+    case '(':
+      lbrace++;
+      break;
+    case ')':
+      rbrace++;
+      break;
+    case '{':
+      lcurl++;
+      break;
+    case '}':
+      rcurl++;
+      break;
+    default:
+      break;
+    }
+    str++;
+  }
+  safe_format(buff, bp, "%d %d %d %d %d %d", lbrack, rbrack,
+             lbrace, rbrace, lcurl, rcurl);
+}
+
+
+/* Returns the length of str up to the first return character, 
+ * or else the last space, or else 0.
+ */
+static int
+wraplen(char *str, int maxlen)
+{
+  const int length = strlen(str);
+  int i = 0;
+
+  if (length <= maxlen) {
+    /* Find the first return char
+     * so %r will not mess with any alignment
+     * functions.
+     */
+    while (i < length) {
+      if ((str[i] == '\n') || (str[i] == '\r'))
+       return i;
+      i++;
+    }
+    return length;
+  }
+
+  /* Find the first return char
+   * so %r will not mess with any alignment
+   * functions.
+   */
+  while (i <= maxlen + 1) {
+    if ((str[i] == '\n') || (str[i] == '\r'))
+      return i;
+    i++;
+  }
+
+  /* No return char was found. Now 
+   * find the last space in str.
+   */
+  while (str[maxlen] != ' ' && maxlen > 0)
+    maxlen--;
+
+  return (maxlen ? maxlen : -1);
+}
+
+/** The integer in string a will be stored in v, 
+ * if a is not an integer then d (efault) is stored in v. 
+ */
+#define initint(a, v, d) \
+  do \
+   if (arglens[a] == 0) { \
+      v = d; \
+   } else { \
+     if (!is_integer(args[a])) { \
+        safe_str(T(e_int), buff, bp); \
+        return; \
+     } \
+     v = parse_integer(args[a]); \
+  } \
+ while (0)
+
+FUNCTION(fun_wrap)
+{
+/*  args[0]  =  text to be wrapped (required)
+ *  args[1]  =  line width (width) (required)
+ *  args[2]  =  width of first line (width1st)
+ *  args[3]  =  output delimiter (btwn lines)
+ */
+
+  char *pstr;                  /* start of string */
+  ansi_string *as;
+  const char *pend;            /* end of string */
+  int linewidth, width1st, width;
+  int linenr = 0;
+  const char *linesep;
+  int ansiwidth, ansilen;
+
+  if (!args[0] || !*args[0])
+    return;
+
+  initint(1, width, 72);
+  width1st = width;
+  if (nargs > 2)
+    initint(2, width1st, width);
+  if (nargs > 3)
+    linesep = args[3];
+  else if (NEWLINE_ONE_CHAR)
+    linesep = "\n";
+  else
+    linesep = "\r\n";
+
+  if (width < 2 || width1st < 2) {
+    safe_str(T("#-1 WIDTH TOO SMALL"), buff, bp);
+    return;
+  }
+
+  as = parse_ansi_string(args[0]);
+  pstr = as->text;
+  pend = as->text + as->len;
+
+  linewidth = width1st;
+  while (pstr < pend) {
+    if (linenr++ == 1)
+      linewidth = width;
+    if ((linenr > 1) && linesep && *linesep)
+      safe_str(linesep, buff, bp);
+
+    ansiwidth = ansi_strnlen(pstr, linewidth);
+    ansilen = wraplen(pstr, ansiwidth);
+
+    if (ansilen < 0) {
+      /* word doesn't fit on one line, so cut it */
+      safe_ansi_string2(as, pstr - as->text, ansiwidth - 1, buff, bp);
+      safe_chr('-', buff, bp);
+      pstr += ansiwidth - 1;   /* move to start of next line */
+    } else {
+      /* normal line */
+      safe_ansi_string2(as, pstr - as->text, ansilen, buff, bp);
+      if (pstr[ansilen] == '\r')
+       ++ansilen;
+      pstr += ansilen + 1;     /* move to start of next line */
+    }
+  }
+  free_ansi_string(as);
+}
+
+#undef initint
+
+#define AL_LEFT 1    /**< Align left */
+#define AL_RIGHT 2   /**< Align right */
+#define AL_CENTER 3  /**< Align center */
+#define AL_REPEAT 4  /**< Repeat column */
+
+static int
+align_one_line(char *buff, char **bp, int ncols,
+              int cols[MAX_COLS], int calign[MAX_COLS], char *ptrs[MAX_COLS],
+              ansi_string *as[MAX_COLS],
+              int linenum, char *fieldsep, int fslen,
+              char *linesep, int lslen, char filler)
+{
+  static char line[BUFFER_LEN];
+  static char segment[BUFFER_LEN];
+  char *sp;
+  char *ptr, *tptr;
+  char *lp;
+  char *lastspace;
+  int i, j;
+  int len;
+  int cols_done;
+  int skipspace;
+
+  lp = line;
+  memset(line, filler, BUFFER_LEN);
+  cols_done = 0;
+  for (i = 0; i < ncols; i++) {
+    if (!ptrs[i] || !*ptrs[i]) {
+      if (calign[i] & AL_REPEAT) {
+       ptrs[i] = as[i]->text;
+      } else {
+       lp += cols[i];
+       if (i < (ncols - 1) && fslen)
+         safe_str(fieldsep, line, &lp);
+       cols_done++;
+       continue;
+      }
+    }
+    if (calign[i] & AL_REPEAT) {
+      cols_done++;
+    }
+    for (len = 0, ptr = ptrs[i], lastspace = NULL; len < cols[i]; ptr++, len++) {
+      if ((!*ptr) || (*ptr == '\n'))
+       break;
+      if (isspace((unsigned char) *ptr)) {
+       lastspace = ptr;
+      }
+    }
+    skipspace = 0;
+    sp = segment;
+    if (!*ptr) {
+      if (len > 0) {
+       safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
+      }
+      ptrs[i] = ptr;
+    } else if (*ptr == '\n') {
+      for (tptr = ptr;
+          *tptr && tptr >= ptrs[i] && isspace((unsigned char) *tptr); tptr--) ;
+      len = (tptr - ptrs[i]) + 1;
+      if (len > 0) {
+       safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
+      }
+      ptrs[i] = ptr + 1;
+      ptr = tptr;
+    } else if (lastspace) {
+      ptr = lastspace;
+      skipspace = 1;
+      for (tptr = ptr;
+          *tptr && tptr >= ptrs[i] && isspace((unsigned char) *tptr); tptr--) ;
+      len = (tptr - ptrs[i]) + 1;
+      if (len > 0) {
+       safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
+      }
+      ptrs[i] = lastspace;
+    } else {
+      if (len > 0) {
+       safe_ansi_string2(as[i], ptrs[i] - (as[i]->text), len, segment, &sp);
+      }
+      ptrs[i] = ptr;
+    }
+    *sp = '\0';
+
+    if ((calign[i] & 3) == AL_LEFT) {
+      safe_str(segment, line, &lp);
+      lp += cols[i] - len;
+    } else if ((calign[i] & 3) == AL_RIGHT) {
+      lp += cols[i] - len;
+      safe_str(segment, line, &lp);
+    } else if ((calign[i] & 3) == AL_CENTER) {
+      j = cols[i] - len;
+      lp += j >> 1;
+      safe_str(segment, line, &lp);
+      lp += (j >> 1) + (j & 1);
+    }
+    if ((lp - line) > BUFFER_LEN)
+      lp = (line + BUFFER_LEN - 1);
+    if (i < (ncols - 1) && fslen)
+      safe_str(fieldsep, line, &lp);
+    if (skipspace)
+      for (;
+          *ptrs[i] && (*ptrs[i] != '\n') && isspace((unsigned char) *ptrs[i]);
+          ptrs[i]++) ;
+  }
+  if (cols_done == ncols)
+    return 0;
+  *lp = '\0';
+  if (linenum > 0 && lslen > 0)
+    safe_str(linesep, buff, bp);
+  safe_str(line, buff, bp);
+  return 1;
+}
+
+
+FUNCTION(fun_align)
+{
+  int nline;
+  char *ptr;
+  int ncols;
+  int i;
+  static int cols[MAX_COLS];
+  static int calign[MAX_COLS];
+  static ansi_string *as[MAX_COLS];
+  static char *ptrs[MAX_COLS];
+  char filler;
+  char *fieldsep;
+  int fslen;
+  char *linesep;
+  int lslen;
+
+  filler = ' ';
+  fieldsep = (char *) " ";
+  linesep = (char *) "\n";
+
+  /* Get column widths */
+  ncols = 0;
+  for (ptr = args[0]; *ptr; ptr++) {
+    while (isspace((unsigned char) *ptr))
+      ptr++;
+    if (*ptr == '>') {
+      calign[ncols] = AL_RIGHT;
+      ptr++;
+    } else if (*ptr == '-') {
+      calign[ncols] = AL_CENTER;
+      ptr++;
+    } else if (*ptr == '<') {
+      calign[ncols] = AL_LEFT;
+      ptr++;
+    } else if (isdigit((unsigned char) *ptr)) {
+      calign[ncols] = AL_LEFT;
+    } else {
+      safe_str(T("#-1 INVALID ALIGN STRING"), buff, bp);
+      return;
+    }
+    for (i = 0; *ptr && isdigit((unsigned char) *ptr); ptr++) {
+      i *= 10;
+      i += *ptr - '0';
+    }
+    if (*ptr == '.') {
+      calign[ncols] |= AL_REPEAT;
+      ptr++;
+    }
+    cols[ncols++] = i;
+    if (!*ptr)
+      break;
+  }
+
+  for (i = 0; i < ncols; i++) {
+    if (cols[i] < 1) {
+      safe_str(T("#-1 CANNOT HAVE COLUMN OF SIZE 0"), buff, bp);
+      return;
+    }
+  }
+
+  if (ncols < 1) {
+    safe_str(T("#-1 NOT ENOUGH COLUMNS FOR ALIGN"), buff, bp);
+    return;
+  }
+  if (ncols > MAX_COLS) {
+    safe_str(T("#-1 TOO MANY COLUMNS FOR ALIGN"), buff, bp);
+    return;
+  }
+  if (nargs < (ncols + 1) || nargs > (ncols + 4)) {
+    safe_str(T("#-1 INVALID NUMBER OF ARGUMENTS TO ALIGN"), buff, bp);
+    return;
+  }
+  if (nargs >= (ncols + 2)) {
+    if (!args[ncols + 1] || strlen(args[ncols + 1]) != 1) {
+      safe_str(T("#-1 FILLER MUST BE ONE CHARACTER"), buff, bp);
+      return;
+    }
+    filler = *(args[ncols + 1]);
+  }
+  if (nargs >= (ncols + 3)) {
+    fieldsep = args[ncols + 2];
+  }
+  if (nargs >= (ncols + 4)) {
+    linesep = args[ncols + 3];
+  }
+
+  fslen = strlen(fieldsep);
+  lslen = strlen(linesep);
+
+  for (i = 0; i < MAX_COLS; i++) {
+    as[i] = NULL;
+  }
+  for (i = 0; i < ncols; i++) {
+    as[i] = parse_ansi_string(args[i + 1]);
+    ptrs[i] = as[i]->text;
+  }
+
+  nline = 0;
+  while (1) {
+    if (!align_one_line(buff, bp, ncols, cols, calign, ptrs,
+                       as, nline++, fieldsep, fslen, linesep, lslen, filler))
+      break;
+  }
+  **bp = '\0';
+  for (i = 0; i < ncols; i++) {
+    free_ansi_string(as[i]);
+    ptrs[i] = as[i]->text;
+  }
+  return;
+}
diff --git a/src/funtime.c b/src/funtime.c
new file mode 100644 (file)
index 0000000..54c3a61
--- /dev/null
@@ -0,0 +1,596 @@
+/**
+ * \file funtime.c
+ *
+ * \brief Time functions for mushcode.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+#include <string.h>
+#include <ctype.h>
+#if  defined(__GNUC__) || defined(__LCC__)
+/* Required to get the getdate() prototype on glibc. */
+#define __USE_XOPEN_EXTENDED
+#endif
+#include <time.h>
+#include <errno.h>
+#include "conf.h"
+#include "externs.h"
+#include "parse.h"
+#include "dbdefs.h"
+#include "log.h"
+#include "match.h"
+#include "attrib.h"
+#include "confmagic.h"
+
+int do_convtime(const char *str, struct tm *ttm);
+void do_timestring(char *buff, char **bp, const char *format,
+                  unsigned long secs);
+
+extern char valid_timefmt_codes[256];
+
+FUNCTION(fun_timefmt)
+{
+  char s[BUFFER_LEN];
+  struct tm *ttm;
+  time_t tt;
+  int len, n;
+
+  if (!args[0] || !*args[0])
+    return;                    /* No field? Bad user. */
+
+  if (nargs == 2) {
+    /* This is silly, but time_t is signed on several platforms,
+     * so we can't assign an unsigned int to it safely
+     */
+    if (!is_integer(args[1])) {
+      safe_str(T(e_uint), buff, bp);
+      return;
+    }
+    tt = parse_integer(args[1]);
+    if (errno == ERANGE) {
+      safe_str(T(e_range), buff, bp);
+      return;
+    }
+    if (tt < 0) {
+      safe_str(T(e_uint), buff, bp);
+      return;
+    }
+  } else
+    tt = mudtime;
+
+  ttm = localtime(&tt);
+  len = arglens[0];
+  for (n = 0; n < len; n++) {
+    if (args[0][n] == '%')
+      args[0][n] = 0x5;
+    else if (args[0][n] == '$') {
+      args[0][n] = '%';
+      n++;
+      if (args[0][n] == '$')
+       args[0][n] = '%';
+      else if (!valid_timefmt_codes[(unsigned char) args[0][n]]) {
+       safe_format(buff, bp, T("#-1 INVALID ESCAPE CODE '$%c'"),
+                   args[0][n] ? args[0][n] : ' ');
+       return;
+      }
+    }
+  }
+  len = strftime(s, BUFFER_LEN, args[0], ttm);
+  if (len == 0) {
+    /* Problem. Either the output from strftime would be over
+     * BUFFER_LEN characters, or there wasn't any output at all.
+     * In the former case, what's in s is indeterminate. Instead of
+     * trying to figure out which of the two cases happened, just
+     * return an empty string.
+     */
+    return;
+  }
+  for (n = 0; n < len; n++)
+    if (s[n] == '%')
+      s[n] = '$';
+    else if (s[n] == 0x5)
+      s[n] = '%';
+  safe_str(s, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_time)
+{
+  int utc = 0;
+  int mytime;
+  double tz = 0;
+  mytime = mudtime;
+  /* TODO: Make win32 version of timezone code */
+
+  if (nargs == 1) {
+    if (!strcasecmp("UTC", args[0])) {
+      setenv("TZ", "0", 1);
+    } else if (args[0] && *args[0]) {
+      dbref thing;
+      ATTR *a;
+      char *ptr;
+      thing = match_result(executor, args[0], NOTYPE, MAT_EVERYTHING);
+      if (!GoodObject(thing)) {
+       /* Assume this is a manul timezone set */
+       if(is_strict_number(args[0])) {
+         utc = 1;
+         tz = strtod(args[0], NULL);
+         if (tz < -24.0 || tz > 24.0) {
+           safe_str("#-1 INVALID TIME ZONE", buff, bp);
+           return;
+         }
+         mytime += (int) (tz * 3600);
+       } else setenv("TZ", args[0], 1);
+      } else {
+      /* Always make time(player) return a time,
+       * even if player's TZ is unset or wonky */
+       a = atr_get(thing, "TZ");
+       if (a) { 
+         ptr = atr_value(a);
+         if(is_strict_number(ptr)) {
+           utc = 1;
+           tz = strtod(ptr, NULL);
+           if (tz >= -24.0 || tz <= 24.0) {
+             mytime += (int) (tz * 3600);
+           }
+         } else setenv("TZ", ptr, 1);
+       } 
+      }
+    }
+  } else if (!strcmp("UTCTIME", called_as)) {
+    setenv("TZ", "UTC", 1);
+  }
+  tzset();
+  safe_str(show_time(mytime, utc), buff, bp);
+  unsetenv("TZ");
+}
+
+/* ARGSUSED */
+FUNCTION(fun_secs)
+{
+  safe_integer(mudtime, buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_convsecs)
+{
+  /* converts seconds to a time string */
+
+  time_t tt;
+  struct tm *ttm;
+  int utc = 0;
+
+  if (strcmp(called_as, "CONVUTCSECS") == 0 ||
+      (nargs == 2 && strcasecmp("UTC", args[1]) == 0))
+    utc = 1;
+
+  if (!is_integer(args[0])) {
+    safe_str(T(e_int), buff, bp);
+    return;
+  }
+  tt = parse_integer(args[0]);
+  if (errno == ERANGE) {
+    safe_str(T(e_range), buff, bp);
+    return;
+  }
+  if (tt < 0) {
+    safe_str(T(e_uint), buff, bp);
+    return;
+  }
+
+  if (utc)
+    ttm = gmtime(&tt);
+  else
+    ttm = localtime(&tt);
+
+  safe_str(show_tm(ttm), buff, bp);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_etimefmt)
+{
+  unsigned long secs;
+
+  if (!is_uinteger(args[1])) {
+    safe_str(e_uint, buff, bp);
+    return;
+  }
+
+  secs = parse_uinteger(args[1]);
+
+  do_timestring(buff, bp, args[0], secs);
+
+}
+
+/* ARGSUSED */
+FUNCTION(fun_timestring)
+{
+  /* Convert seconds to #d #h #m #s
+   * If pad > 0, pad with 0's (i.e. 0d 0h 5m 1s)
+   * If pad > 1, force all #'s to be 2 digits
+   */
+  unsigned int secs, pad;
+  unsigned int days, hours, mins;
+
+  if (!is_uinteger(args[0])) {
+    safe_str(T(e_uints), buff, bp);
+    return;
+  }
+  if (nargs == 1)
+    pad = 0;
+  else {
+    if (!is_uinteger(args[1])) {
+      safe_str(T(e_uints), buff, bp);
+      return;
+    }
+    pad = parse_uinteger(args[1]);
+  }
+
+  secs = parse_uinteger(args[0]);
+
+  days = secs / 86400;
+  secs %= 86400;
+  hours = secs / 3600;
+  secs %= 3600;
+  mins = secs / 60;
+  secs %= 60;
+  if (pad || (days > 0)) {
+    if (pad == 2)
+      safe_format(buff, bp, "%02ud %02uh %02um %02us", days, hours, mins, secs);
+    else
+      safe_format(buff, bp, "%ud %2uh %2um %2us", days, hours, mins, secs);
+  } else if (hours > 0)
+    safe_format(buff, bp, "%2uh %2um %2us", hours, mins, secs);
+  else if (mins > 0)
+    safe_format(buff, bp, "%2um %2us", mins, secs);
+  else
+    safe_format(buff, bp, "%2us", secs);
+}
+
+#ifdef HAS_GETDATE
+int do_convtime_gd(const char *str, struct tm *ttm);
+/** Convert a time string to a struct tm using getdate().
+ * Formats for the time string are taken from the file referenced in
+ * the DATEMSK environment variable.
+ * \param str a time string.
+ * \param ttm pointer to a struct tm to fill.
+ * \retval 1 success.
+ * \retval 0 failure.
+ */
+int
+do_convtime_gd(const char *str, struct tm *ttm)
+{
+  /* converts time string to a struct tm. Returns 1 on success, 0 on fail.
+   * Formats of the time string are taken from the file listed in the
+   * DATEMSK env variable 
+   */
+  struct tm *tc;
+
+  tc = getdate(str);
+
+  if (tc == NULL) {
+#ifdef NEVER
+    if (getdate_err <= 7)
+      do_rawlog(LT_ERR, "getdate returned error code %d for %s", getdate_err,
+               str);
+#endif
+    return 0;
+  }
+
+  memcpy(ttm, tc, sizeof(struct tm));
+  ttm->tm_isdst = -1;
+
+  return 1;
+}
+
+#endif
+/* do_convtime for systems without getdate(). Will probably break if in
+         a non en_US locale */
+static const char *month_table[] = {
+  "Jan",
+  "Feb",
+  "Mar",
+  "Apr",
+  "May",
+  "Jun",
+  "Jul",
+  "Aug",
+  "Sep",
+  "Oct",
+  "Nov",
+  "Dec",
+};
+
+/** Convert a time string to a struct tm, without getdate().
+ * \param mystr a time string.
+ * \param ttm pointer to a struct tm to fill.
+ * \retval 1 success.
+ * \retval 0 failure.
+ */
+int
+do_convtime(const char *mystr, struct tm *ttm)
+{
+  /* converts time string to a struct tm. Returns 1 on success, 0 on fail.
+   * Time string format is always 24 characters long, in format
+   * Ddd Mmm DD HH:MM:SS YYYY
+   */
+
+  char *p, *q;
+  char str[25];
+  int i;
+
+  if (strlen(mystr) != 24)
+    return 0;
+  strcpy(str, mystr);
+
+  /* move over the day of week and truncate. Will always be 3 chars.
+   * we don't need this, so we can ignore it.
+   */
+  if (!(p = strchr(str, ' ')))
+    return 0;
+  *p++ = '\0';
+  if (strlen(str) != 3)
+    return 0;
+
+  /* get the month (3 chars), and convert it to a number */
+  if (!(q = strchr(p, ' ')))
+    return 0;
+  *q++ = '\0';
+  if (strlen(p) != 3)
+    return 0;
+  for (i = 0; (i < 12) && strcmp(month_table[i], p); i++) ;
+  if (i == 12)                 /* not found */
+    return 0;
+  else
+    ttm->tm_mon = i;
+
+  /* get the day of month */
+  p = q;
+  while (isspace((unsigned char) *p))  /* skip leading space */
+    p++;
+  if (!(q = strchr(p, ' ')))
+    return 0;
+  *q++ = '\0';
+  ttm->tm_mday = atoi(p);
+
+  /* get hours */
+  if (!(p = strchr(q, ':')))
+    return 0;
+  *p++ = '\0';
+  ttm->tm_hour = atoi(q);
+
+  /* get minutes */
+  if (!(q = strchr(p, ':')))
+    return 0;
+  *q++ = '\0';
+  ttm->tm_min = atoi(p);
+
+  /* get seconds */
+  if (!(p = strchr(q, ' ')))
+    return 0;
+  *p++ = '\0';
+  ttm->tm_sec = atoi(q);
+
+  /* get year */
+  ttm->tm_year = atoi(p) - 1900;
+
+  ttm->tm_isdst = -1;
+
+  return 1;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_convtime)
+{
+  /* converts time string to seconds */
+
+  struct tm ttm;
+
+  if (do_convtime(args[0], &ttm)
+#ifdef HAS_GETDATE
+      || do_convtime_gd(args[0], &ttm)
+#endif
+    ) {
+#ifdef SUN_OS
+    safe_integer(timelocal(&ttm), buff, bp);
+#else
+    safe_integer(mktime(&ttm), buff, bp);
+#endif                         /* SUN_OS */
+  } else {
+    safe_str("-1", buff, bp);
+  }
+}
+
+#ifdef WIN32
+#pragma warning( disable : 4761)       /* NJG: disable warning re conversion */
+#endif
+/* ARGSUSED */
+FUNCTION(fun_isdaylight)
+{
+  struct tm *ltime;
+
+  ltime = localtime(&mudtime);
+
+  safe_boolean(ltime->tm_isdst > 0, buff, bp);
+}
+
+/** Convert seconds to a formatted time string.
+ * \verbatim
+ * Format codes:
+ *       $s - Seconds. $S - Seconds, force 2 digits.
+ *       $m - Minutes. $M - Minutes, force 2 digits.
+ *       $h - Hours.   $H - Hours, force 2 digits.
+ *       $d - Days.    $D - Days, force 2 digits.
+ * $$ - Literal $.
+ * \endverbatim
+ * \param buff string to store the result in.
+ * \param bp pointer into end of buff.
+ * \param format format code string.
+ * \param secs seconds to convert.
+ */
+void
+do_timestring(char *buff, char **bp, const char *format, unsigned long secs)
+{
+  int days, hours, mins;
+  int pad = 0;
+  int width;
+  const char *c;
+  char *w;
+  int include_suffix, even_if_0, in_format_flags;
+
+  days = secs / 86400;
+  secs %= 86400;
+  hours = secs / 3600;
+  secs %= 3600;
+  mins = secs / 60;
+  secs %= 60;
+
+  for (c = format; c && *c; c++) {
+    if (*c == '$') {
+      c++;
+      width = strtol(c, &w, 10);
+      if (c == w)
+       pad = 0;
+      else
+       pad = 1;
+      if (width < 0)
+       width = 0;
+      else if (width >= BUFFER_LEN)
+       width = BUFFER_LEN - 1;
+      even_if_0 = in_format_flags = 1;
+      include_suffix = 0;
+      while (in_format_flags) {
+       switch (*w) {
+       case 'x':
+       case 'X':
+         include_suffix = 1;
+         w++;
+         break;
+       case 'z':
+       case 'Z':
+         even_if_0 = 0;
+         w++;
+         break;
+       case '$':
+         in_format_flags = 0;
+         if (pad)
+           safe_format(buff, bp, "%*c", width, '$');
+         else
+           safe_chr('$', buff, bp);
+         break;
+       case 's':
+         in_format_flags = 0;
+         if (secs || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%*lu", width, secs);
+           else
+             safe_uinteger(secs, buff, bp);
+           if (include_suffix)
+             safe_chr('s', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'S':
+         in_format_flags = 0;
+         if (secs || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%0*lu", width, secs);
+           else
+             safe_format(buff, bp, "%0lu", secs);
+           if (include_suffix)
+             safe_chr('s', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'm':
+         in_format_flags = 0;
+         if (mins || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%*d", width, mins);
+           else
+             safe_integer(mins, buff, bp);
+           if (include_suffix)
+             safe_chr('m', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'M':
+         in_format_flags = 0;
+         if (mins || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%0*d", width, mins);
+           else
+             safe_format(buff, bp, "%0d", mins);
+           if (include_suffix)
+             safe_chr('m', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'h':
+         in_format_flags = 0;
+         if (hours || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%*d", width, hours);
+           else
+             safe_integer(hours, buff, bp);
+           if (include_suffix)
+             safe_chr('h', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'H':
+         in_format_flags = 0;
+         if (hours || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%0*d", width, hours);
+           else
+             safe_format(buff, bp, "%0d", hours);
+           if (include_suffix)
+             safe_chr('h', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'd':
+         in_format_flags = 0;
+         if (days || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%*d", width, days);
+           else
+             safe_integer(days, buff, bp);
+           if (include_suffix)
+             safe_chr('d', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       case 'D':
+         in_format_flags = 0;
+         if (days || even_if_0) {
+           if (pad)
+             safe_format(buff, bp, "%0*d", width, days);
+           else
+             safe_format(buff, bp, "%0d", days);
+           if (include_suffix)
+             safe_chr('d', buff, bp);
+         } else if (pad)
+           safe_fill(' ', width + (include_suffix ? 1 : 0), buff, bp);
+         break;
+       default:
+         in_format_flags = 0;
+         safe_chr('$', buff, bp);
+         for (; c != w; c++)
+           safe_chr(*c, buff, bp);
+         safe_chr(*c, buff, bp);
+       }
+      }
+      c = w;
+    } else
+      safe_chr(*c, buff, bp);
+  }
+}
+
+#ifdef WIN32
+#pragma warning( default : 4761)       /* NJG: enable warning re conversion */
+#endif
diff --git a/src/funufun.c b/src/funufun.c
new file mode 100644 (file)
index 0000000..15321f0
--- /dev/null
@@ -0,0 +1,320 @@
+/**
+ * \file funufun.c
+ *
+ * \brief Evaluation and user-function functions for mushcode.
+ * 
+ *
+ */
+
+#include "copyrite.h"
+
+#include "config.h"
+#include "conf.h"
+#include "externs.h"
+#include "match.h"
+#include "parse.h"
+#include "mymalloc.h"
+#include "attrib.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "confmagic.h"
+
+void do_userfn(char *buff, char **bp, dbref obj, ATTR *attrib, int nargs,
+              char **args, dbref executor, dbref caller, dbref enactor,
+              PE_Info * pe_info);
+
+/* ARGSUSED */
+FUNCTION(fun_s)
+{
+  char const *p;
+  p = args[0];
+  process_expression(buff, bp, &p, executor, caller, enactor, PE_DEFAULT,
+                    PT_DEFAULT, pe_info);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_localize)
+{
+  char const *p;
+  char *saver[NUMQ];
+
+  save_global_regs("localize", saver);
+
+  p = args[0];
+  process_expression(buff, bp, &p, executor, caller, enactor, PE_DEFAULT,
+                    PT_DEFAULT, pe_info);
+
+  restore_global_regs("localize", saver);
+}
+
+/* ARGSUSED */
+FUNCTION(fun_objeval)
+{
+  char name[BUFFER_LEN];
+  char *s;
+  char const *p;
+  dbref obj;
+
+  BEGINOOREF_L
+
+  /* First, we evaluate our first argument so people can use 
+   * functions on it.
+   */
+  s = name;
+  p = args[0];
+  process_expression(name, &s, &p, executor, caller, enactor, PE_DEFAULT,
+                    PT_DEFAULT, pe_info);
+  *s = '\0';
+
+  if (FUNCTION_SIDE_EFFECTS) {
+    /* The security hole created by function side effects is too great
+     * to allow a see_all player to evaluate functions from someone else's
+     * standpoint. We require control.
+     */
+    if (((obj = match_thing(executor, name)) == NOTHING)
+      || !controls(executor, obj))
+      obj = executor;
+  } else {
+    /* In order to evaluate from something else's viewpoint, you
+     * must control it, or be able to see_all.
+     */
+    if (((obj = match_thing(executor, name)) == NOTHING)
+      || (!controls(executor, obj) && !CanSee(executor, obj)))
+      obj = executor;
+  }
+
+  p = args[1];
+  process_expression(buff, bp, &p, obj, executor, enactor, PE_DEFAULT,
+                    PT_DEFAULT, pe_info);
+  ENDOOREF_L
+}
+
+/** Helper function for ufun and family.
+ * \param buff string to store result of evaluation.
+ * \param bp pointer into end of buff.
+ * \param obj object on which the ufun is stored.
+ * \param attrib pointer to attribute on which the ufun is stored.
+ * \param nargs number of arguments passed to the ufun.
+ * \param args array of arguments passed to the ufun.
+ * \param executor executor.
+ * \param caller caller (unused).
+ * \param enactor enactor.
+ * \param pe_info pointer to structure for process_expression data.
+ */
+void
+do_userfn(char *buff, char **bp, dbref obj, ATTR *attrib, int nargs,
+         char **args, dbref executor, dbref caller
+         __attribute__ ((__unused__)), dbref enactor, PE_Info * pe_info)
+{
+  int j;
+  char *tptr[10];
+  char *tbuf;
+  char const *tp;
+  int pe_flags = PE_DEFAULT;
+
+  /* save our stack */
+  for (j = 0; j < 10; j++)
+    tptr[j] = global_eval_context.wenv[j];
+
+  /* copy the appropriate args into the stack */
+  if (nargs > 10)
+    nargs = 10;                        /* maximum ten args */
+  for (j = 0; j < nargs; j++)
+    global_eval_context.wenv[j] = args[j];
+  for (; j < 10; j++)
+    global_eval_context.wenv[j] = NULL;
+
+  tp = tbuf = safe_atr_value(attrib);
+  if (attrib->flags & AF_DEBUG)
+    pe_flags |= PE_DEBUG;
+  process_expression(buff, bp, &tp, obj, executor, enactor, pe_flags,
+                    PT_DEFAULT, pe_info);
+  free(tbuf);
+
+  /* restore the stack */
+  for (j = 0; j < 10; j++)
+    global_eval_context.wenv[j] = tptr[j];
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ufun)
+{
+  ATTR *attrib;
+  dbref obj;
+
+  BEGINOOREF_L
+
+  /* find the user function attribute */
+  parse_attrib(executor, args[0], &obj, &attrib);
+  if (!GoodObject(obj)) {
+    safe_str(T("#-1 INVALID OBJECT"), buff, bp);
+    goto ufun_done;
+  }
+  if (attrib && Can_Read_Attr(executor, obj, attrib)) {
+    if (!CanEvalAttr(executor, obj, attrib)) {
+      safe_str(T(e_perm), buff, bp);
+      goto ufun_done;
+    }
+    do_userfn(buff, bp, obj, attrib, nargs - 1, args + 1, executor, caller,
+             enactor, pe_info);
+    goto ufun_done;
+  } else if (attrib || !Can_Examine(executor, obj)) {
+    safe_str(T(e_atrperm), buff, bp);
+    goto ufun_done;
+  }
+ufun_done:
+  ENDOOREF_L
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_ulocal)
+{
+  /* Like fun_ufun, but saves the state of the q0-q9 registers
+   * when called
+   */
+  ATTR *attrib;
+  dbref obj;
+  char *preserve[NUMQ];
+
+  BEGINOOREF_L
+
+  /* find the user function attribute */
+  parse_attrib(executor, args[0], &obj, &attrib);
+  if (!GoodObject(obj)) {
+    safe_str(T("#-1 INVALID OBJECT"), buff, bp);
+    ENDOOREF_L
+    return;
+  }
+  if (attrib && Can_Read_Attr(executor, obj, attrib)) {
+    if (!CanEvalAttr(executor, obj, attrib)) {
+      safe_str(T(e_perm), buff, bp);
+      ENDOOREF_L
+      return;
+    }
+    save_global_regs("ulocal.save", preserve);
+    do_userfn(buff, bp, obj, attrib, nargs - 1, args + 1, executor, caller,
+             enactor, pe_info);
+    restore_global_regs("ulocal.save", preserve);
+    ENDOOREF_L
+    return;
+  } else if (attrib || !Can_Examine(executor, obj)) {
+    safe_str(T(e_atrperm), buff, bp);
+    ENDOOREF_L
+    return;
+  }
+  ENDOOREF_L
+  return;
+}
+
+/* Like fun_ufun, but takes as second argument a default message
+ * to use if the attribute isn't there.  If called as uldefault,
+ * then preserve registers, too.
+ */
+/* ARGSUSED */
+FUNCTION(fun_uldefault)
+{
+  dbref thing;
+  ATTR *attrib;
+  char *dp;
+  char const *sp;
+  char mstr[BUFFER_LEN];
+  char **xargs;
+  int i;
+  char *preserve[NUMQ];
+
+  BEGINOOREF_L
+
+  /* find our object and attribute */
+  dp = mstr;
+  sp = args[0];
+  process_expression(mstr, &dp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  *dp = '\0';
+  parse_attrib(executor, mstr, &thing, &attrib);
+  if (GoodObject(thing) && attrib && CanEvalAttr(executor, thing, attrib)
+      && Can_Read_Attr(executor, thing, attrib)) {
+    /* Ok, we've got it */
+    /* We must now evaluate all the arguments from args[2] on and
+     * pass them to the function */
+    xargs = NULL;
+    if (nargs > 2) {
+      xargs =
+       (char **) mush_malloc((nargs - 2) * sizeof(char *), "udefault.xargs");
+      for (i = 0; i < nargs - 2; i++) {
+       xargs[i] = (char *) mush_malloc(BUFFER_LEN, "udefault");
+       dp = xargs[i];
+       sp = args[i + 2];
+       process_expression(xargs[i], &dp, &sp, executor, caller, enactor,
+                          PE_DEFAULT, PT_DEFAULT, pe_info);
+       *dp = '\0';
+      }
+    }
+    if (called_as[1] == 'L')
+      save_global_regs("uldefault.save", preserve);
+    do_userfn(buff, bp, thing, attrib, nargs - 2, xargs,
+             executor, caller, enactor, pe_info);
+    if (called_as[1] == 'L')
+      restore_global_regs("uldefault.save", preserve);
+
+    /* Free the xargs */
+    if (nargs > 2) {
+      for (i = 0; i < nargs - 2; i++)
+       mush_free(xargs[i], "udefault");
+      mush_free(xargs, "udefault.xargs");
+    }
+    ENDOOREF_L
+    return;
+  }
+  /* We couldn't get it. Evaluate args[1] and return it */
+  sp = args[1];
+
+  if (called_as[1] == 'L')
+    save_global_regs("uldefault.save", preserve);
+  process_expression(buff, bp, &sp, executor, caller, enactor,
+                    PE_DEFAULT, PT_DEFAULT, pe_info);
+  if (called_as[1] == 'L')
+    restore_global_regs("uldefault.save", preserve);
+  ENDOOREF_L
+  return;
+}
+
+
+/* ARGSUSED */
+FUNCTION(fun_zfun)
+{
+  ATTR *attrib;
+  dbref zone;
+
+  BEGINOOREF_L
+
+  zone = Zone(executor);
+
+  if (zone == NOTHING) {
+    safe_str(T("#-1 INVALID ZONE"), buff, bp);
+    ENDOOREF_L
+    return;
+  }
+  /* find the user function attribute */
+  attrib = atr_get(zone, upcasestr(args[0]));
+  if (attrib && Can_Read_Attr(executor, zone, attrib)) {
+    if (!CanEvalAttr(executor, zone, attrib)) {
+      safe_str(T(e_perm), buff, bp);
+      ENDOOREF_L
+      return;
+    }
+    do_userfn(buff, bp, zone, attrib, nargs - 1, args + 1, executor, caller,
+             enactor, pe_info);
+    ENDOOREF_L
+    return;
+  } else if (attrib || !Can_Examine(executor, zone)) {
+    safe_str(T(e_atrperm), buff, bp);
+    ENDOOREF_L
+    return;
+  }
+  safe_str(T("#-1 NO SUCH USER FUNCTION"), buff, bp);
+  ENDOOREF_L
+  return;
+}
diff --git a/src/game.c b/src/game.c
new file mode 100644 (file)
index 0000000..3497ec4
--- /dev/null
@@ -0,0 +1,2448 @@
+/**
+ * \file game.c
+ *
+ * \brief The main game driver.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <string.h>
+#include <signal.h>
+#ifdef I_STDARG
+#include <stdarg.h>
+#endif
+#ifdef I_SYS_WAIT
+#include <sys/wait.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#ifdef WIN32
+#include <process.h>
+#include <windows.h>
+#undef OPAQUE                  /* Clashes with flags.h */
+void Win32MUSH_setup(void);
+#endif
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef HAS_GETRUSAGE
+#include <sys/resource.h>
+#endif
+#include <stdlib.h>
+#include <stdarg.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <errno.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "game.h"
+#include "attrib.h"
+#include "match.h"
+#include "case.h"
+#include "extmail.h"
+#ifdef CHAT_SYSTEM
+#include "extchat.h"
+#endif /* CHAT_SYSTEM */
+#ifdef HAS_OPENSSL
+#include "myssl.h"
+#endif
+#include "getpgsiz.h"
+#include "parse.h"
+#include "access.h"
+#include "version.h"
+#include "strtree.h"
+#include "command.h"
+#include "htab.h"
+#include "ptab.h"
+#include "log.h"
+#include "lock.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "function.h"
+#include "help.h"
+#include "dbio.h"
+
+#ifdef hpux
+#include <sys/syscall.h>
+#define getrusage(x,p)   syscall(SYS_GETRUSAGE,x,p)
+#endif                         /* fix to HP-UX getrusage() braindamage */
+
+#include "confmagic.h"
+#ifdef HAS_WAITPID
+/** Return value of wait* functions */
+#define WAIT_TYPE int
+#else
+#ifdef UNION_WAIT
+#define WAIT_TYPE union wait
+#else
+#define WAIT_TYPE int
+#endif
+#endif
+
+/* declarations */
+int database_loaded = 0;        /**< True after the database has been read. */
+char dumpfile[200];            /**< File name to dump database to */
+time_t start_time;             /**< MUSH start time (since process exec'd) */
+time_t first_start_time = 0;   /**< MUSH start time (since last shutdown) */
+time_t last_dump_time = 0;     /**< Time of last successful db save */
+int reboot_count = 0;          /**< Number of reboots so far */
+static int epoch = 0;
+static int reserved;                   /**< Reserved file descriptor */
+int depth = 0;                 /**< excessive recursion prevention */
+static dbref *errdblist = NULL;        /**< List of dbrefs to return errors from */
+static dbref *errdbtail;       /**< Pointer to end of errdblist */
+static int errdbsize = 4;      /**< Current size of errdblist array */
+extern int use_flagfile;
+
+static void errdb_grow(void);
+
+extern void initialize_mt(void);
+
+int paranoid_dump = 0;         /**< if paranoid, scan before dumping */
+int paranoid_checkpt = 0;      /**< write out an okay message every x objs */
+extern long indb_flags;
+extern void conf_default_set(void);
+static int dump_database_internal(void);
+static FILE *db_open(const char *filename);
+static FILE *db_open_write(const char *filename);
+static void db_close(FILE * f);
+static int fail_commands(dbref player);
+void do_readcache(dbref player);
+int check_alias(const char *command, const char *list);
+int list_check(dbref thing, dbref player, char type,
+              char end, char *str, int just_match);
+int alias_list_check(dbref thing, const char *command, const char *type);
+int loc_alias_check(dbref loc, const char *command, const char *type);
+void do_poor(dbref player, char *arg1);
+void do_writelog(dbref player, char *str, int ltype);
+void bind_and_queue(dbref player, dbref cause, char *action, const char *arg,
+                   const char *placestr);
+void do_scan(dbref player, char *command, int flag);
+void do_list(dbref player, char *arg, int lc);
+void do_dolist(dbref player, char *list, char *command,
+              dbref cause, unsigned int flags);
+void do_uptime(dbref player, int mortal);
+static char *make_new_epoch_file(const char *basename, int the_epoch);
+#ifdef HAS_GETRUSAGE
+void rusage_stats(void);
+#endif
+
+void do_list_memstats(dbref player);
+void st_stats_header(dbref player);
+void st_stats(dbref player, StrTree *root, const char *name);
+void do_timestring(char *buff, char **bp, const char *format,
+                  unsigned long secs);
+
+extern void create_minimal_db(void);   /* From db.c */
+
+dbref orator = NOTHING;         /**< Last dbref to issue a speech command */
+dbref ooref = NOTHING; /**< Origin Reference Object(used for twin checks) */
+dbref tooref = NOTHING; /**< temp store ooref variable */
+
+#ifdef COMP_STATS
+extern void compress_stats(long *entries,
+                          long *mem_used,
+                          long *total_uncompressed, long *total_compressed);
+#endif
+
+#ifdef MUSHCRON
+extern int start_cron(void);
+#endif /* MUSHCRON */
+
+
+Pid_t forked_dump_pid = -1;
+
+/** Open /dev/null to reserve a file descriptor that can be reused later. */
+void
+reserve_fd(void)
+{
+#ifndef WIN32
+  reserved = open("/dev/null", O_RDWR);
+#endif
+}
+
+/** Release the reserved file descriptor for other use. */
+void
+release_fd(void)
+{
+#ifndef WIN32
+  close(reserved);
+#endif
+}
+
+/** User command to dump the database.
+ * \verbatim
+ * This implements the @dump command.
+ * \endverbatim
+ * \param player the enactor, for permission checking.
+ * \param num checkpoint interval, as a string.
+ * \param flag type of dump.
+ */
+void
+do_dump(dbref player, char *num, enum dump_type flag)
+{
+  {
+#ifdef ALWAYS_PARANOID
+    if (1) {
+#else
+    if (flag != DUMP_NORMAL) {
+#endif
+      /* want to do a scan before dumping each object */
+      paranoid_dump = 1;
+      if (num && *num) {
+       /* checkpoint interval given */
+       paranoid_checkpt = atoi(num);
+       if ((paranoid_checkpt < 1) || (paranoid_checkpt >= db_top)) {
+         notify(player, T("Permission denied. Invalid checkpoint interval."));
+         paranoid_dump = 0;
+         return;
+       }
+      } else {
+       /* use a default interval */
+       paranoid_checkpt = db_top / 5;
+       if (paranoid_checkpt < 1)
+         paranoid_checkpt = 1;
+      }
+      if (flag == DUMP_PARANOID) {
+       notify_format(player, T("Paranoid dumping, checkpoint interval %d."),
+                     paranoid_checkpt);
+       do_rawlog(LT_CHECK,
+                 "*** PARANOID DUMP *** done by %s(#%d),\n",
+                 Name(player), player);
+      } else {
+       notify_format(player, T("Debug dumping, checkpoint interval %d."),
+                     paranoid_checkpt);
+       do_rawlog(LT_CHECK,
+                 "*** DEBUG DUMP *** done by %s(#%d),\n",
+                 Name(player), player);
+      }
+      do_rawlog(LT_CHECK, T("\tcheckpoint interval %d, at %s"),
+               paranoid_checkpt, show_time(mudtime, 0));
+    } else {
+      /* normal dump */
+      paranoid_dump = 0;       /* just to be safe */
+      notify(player, "Dumping...");
+      do_rawlog(LT_CHECK, "** DUMP ** done by %s(#%d) at %s",
+               Name(player), player, show_time(mudtime, 0));
+    }
+    fork_and_dump(1);
+    paranoid_dump = 0;
+  }
+}
+
+
+/** Print global variables to the trace log.
+ * This function is used for error reporting.
+ */
+void
+report(void)
+{
+  if (GoodObject(global_eval_context.cplr))
+    do_rawlog(LT_TRACE, T("TRACE: Cmd:%s\tdepth:%d\tby #%d at #%d"), global_eval_context.ccom,
+             depth, global_eval_context.cplr, Location(global_eval_context.cplr));
+  else
+    do_rawlog(LT_TRACE, "TRACE: Cmd:%s\tdepth:%d\tby #%d", global_eval_context.ccom, depth, global_eval_context.cplr);
+  notify_activity(NOTHING, 0, 1);
+}
+
+#ifdef HAS_GETRUSAGE
+/** Log process statistics to the error log.
+ */
+void
+rusage_stats(void)
+{
+  struct rusage usage;
+  int pid;
+  int psize;
+
+  pid = getpid();
+  psize = getpagesize();
+  getrusage(RUSAGE_SELF, &usage);
+
+  do_rawlog(LT_ERR, T("Process statistics:"));
+  do_rawlog(LT_ERR, T("Time used:   %10ld user   %10ld sys"),
+           usage.ru_utime.tv_sec, usage.ru_stime.tv_sec);
+  do_rawlog(LT_ERR, "Max res mem: %10ld pages  %10ld bytes",
+           usage.ru_maxrss, (usage.ru_maxrss * psize));
+  do_rawlog(LT_ERR, "Integral mem:%10ld shared %10ld private %10ld stack",
+           usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss);
+  do_rawlog(LT_ERR,
+           T("Page faults: %10ld hard   %10ld soft    %10ld swapouts"),
+           usage.ru_majflt, usage.ru_minflt, usage.ru_nswap);
+  do_rawlog(LT_ERR, T("Disk I/O:    %10ld reads  %10ld writes"),
+           usage.ru_inblock, usage.ru_oublock);
+  do_rawlog(LT_ERR, T("Network I/O: %10ld in     %10ld out"), usage.ru_msgrcv,
+           usage.ru_msgsnd);
+  do_rawlog(LT_ERR, T("Context swi: %10ld vol    %10ld forced"),
+           usage.ru_nvcsw, usage.ru_nivcsw);
+  do_rawlog(LT_ERR, "Signals:     %10ld", usage.ru_nsignals);
+}
+
+#endif                         /* HAS_GETRUSAGE */
+
+/** User interface to shut down the MUSH.
+ * \verbatim
+ * This implements the @shutdown command.
+ * \endverbatim
+ * \param player the enactor, for permission checking.
+ * \param flag type of shutdown to perform.
+ */
+void
+do_shutdown(dbref player, enum shutdown_type flag)
+{
+  if (flag == SHUT_PANIC && !God(player)) {
+    notify(player, T("It takes a God to make me panic."));
+    return;
+  }
+  {
+    flag_broadcast(0, 0, T("GAME: Shutdown by %s"), Name(player));
+    do_log(LT_ERR, player, NOTHING, T("SHUTDOWN by %s(%s)\n"),
+          Name(player), unparse_dbref(player));
+
+    /* This will create a file used to check if a restart should occur */
+#ifdef AUTORESTART
+    system("touch NORESTART");
+#endif
+
+    if (flag == SHUT_PANIC) {
+      mush_panic("@shutdown/panic");
+    } else {
+      if (flag == SHUT_PARANOID) {
+       paranoid_checkpt = db_top / 5;
+       if (paranoid_checkpt < 1)
+         paranoid_checkpt = 1;
+       paranoid_dump = 1;
+      }
+      shutdown_flag = 1;
+    }
+  }
+}
+
+jmp_buf db_err;
+
+static int
+dump_database_internal(void)
+{
+  char realdumpfile[2048];
+  char realtmpfl[2048];
+  char tmpfl[2048];
+  FILE *f = NULL;
+
+#ifndef PROFILING
+#ifndef WIN32
+  ignore_signal(SIGPROF);
+#endif
+#endif
+#ifdef I_SETJMP
+  if (setjmp(db_err)) {
+    /* The dump failed. Disk might be full or something went bad with the
+       compression slave. Boo! */
+    do_rawlog(LT_ERR, T("ERROR! Database save failed."));
+#ifndef PROFILING
+#ifdef HAS_ITIMER
+    install_sig_handler(SIGPROF, signal_cpu_limit);
+#endif
+#endif
+    return 1;
+  } else {
+    local_dump_database();
+
+#ifdef ALWAYS_PARANOID
+    paranoid_checkpt = db_top / 5;
+    if (paranoid_checkpt < 1)
+      paranoid_checkpt = 1;
+#endif
+    if(options.flagdb[0] != '\0') {
+      sprintf(realdumpfile, "%s%s", options.flagdb, options.compresssuff);
+      strcpy(tmpfl, make_new_epoch_file(options.flagdb, epoch));
+      sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
+      if((f = db_open_write(tmpfl)) != NULL) {
+       use_flagfile = 1;
+       db_write_flag_db(f);
+       db_close(f);
+#ifdef WIN32
+       unlink(realdumpfile);
+#endif
+       if(rename(realtmpfl, realdumpfile) < 0) {
+         perror(realtmpfl);
+         longjmp(db_err, 1);
+       }
+      } else {
+       perror(realtmpfl);
+       longjmp(db_err, 1);
+      }
+    }
+
+    sprintf(realdumpfile, "%s%s", dumpfile, options.compresssuff);
+    strcpy(tmpfl, make_new_epoch_file(dumpfile, epoch));
+    sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
+
+    if ((f = db_open_write(tmpfl)) != NULL) {
+      switch (paranoid_dump) {
+      case 0:
+#ifdef ALWAYS_PARANOID
+       db_paranoid_write(f, 0);
+#else
+       db_write(f, 0);
+#endif
+       break;
+      case 1:
+       db_paranoid_write(f, 0);
+       break;
+      case 2:
+       db_paranoid_write(f, 1);
+       break;
+      }
+      db_close(f);
+#ifdef WIN32
+      /* Win32 systems can't rename over an existing file, so unlink first */
+      unlink(realdumpfile);
+#endif
+      if (rename(realtmpfl, realdumpfile) < 0) {
+       perror(realtmpfl);
+       longjmp(db_err, 1);
+      }
+    } else {
+      perror(realtmpfl);
+      longjmp(db_err, 1);
+    }
+    sprintf(realdumpfile, "%s%s", options.mail_db, options.compresssuff);
+    strcpy(tmpfl, make_new_epoch_file(options.mail_db, epoch));
+    sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
+    if (mdb_top >= 0) {
+      if ((f = db_open_write(tmpfl)) != NULL) {
+       dump_mail(f);
+       db_close(f);
+#ifdef WIN32
+       unlink(realdumpfile);
+#endif
+       if (rename(realtmpfl, realdumpfile) < 0) {
+         perror(realtmpfl);
+         longjmp(db_err, 1);
+       }
+      } else {
+       perror(realtmpfl);
+       longjmp(db_err, 1);
+      }
+    }
+#ifdef CHAT_SYSTEM 
+    sprintf(realdumpfile, "%s%s", options.chatdb, options.compresssuff);
+    strcpy(tmpfl, make_new_epoch_file(options.chatdb, epoch));
+    sprintf(realtmpfl, "%s%s", tmpfl, options.compresssuff);
+    if ((f = db_open_write(tmpfl)) != NULL) {
+      save_chatdb(f);
+      db_close(f);
+#ifdef WIN32
+      unlink(realdumpfile);
+#endif
+      if (rename(realtmpfl, realdumpfile) < 0) {
+       perror(realtmpfl);
+       longjmp(db_err, 1);
+      }
+    } else {
+      perror(realtmpfl);
+      longjmp(db_err, 1);
+    }
+#endif /* CHAT_SYSTEM */
+    time(&last_dump_time);
+  }
+
+#endif
+#ifndef PROFILING
+#ifdef HAS_ITIMER
+  install_sig_handler(SIGPROF, signal_cpu_limit);
+#endif
+#endif
+
+  return 0;
+}
+
+/** Crash gracefully.
+ * This function is called when something disastrous happens - typically
+ * a failure to malloc memory or a signal like segfault.
+ * It logs the fault, does its best to dump a panic database, and
+ * exits abruptly. This function does not return.
+ * \param message message to log to the error log.
+ */
+void
+mush_panic(const char *message)
+{
+  const char *panicfile = options.crash_db;
+  FILE *f = NULL;
+  static int already_panicking = 0;
+
+  if (already_panicking) {
+    do_rawlog(LT_ERR,
+             T
+             ("PANIC: Attempted to panic because of '%s' while already panicking. Run in circles, scream and shout!"),
+             message);
+    _exit(133);
+  }
+
+  already_panicking = 1;
+  do_rawlog(LT_ERR, "PANIC: %s", message);
+  report();
+  flag_broadcast(0, 0, "EMERGENCY SHUTDOWN: %s", message);
+
+  /* turn off signals */
+  block_signals();
+
+  /* shut down interface */
+  emergency_shutdown();
+
+  /* dump panic file if we have a database read. */
+  if (database_loaded) {
+    if (setjmp(db_err)) {
+      /* Dump failed. We're in deep doo-doo */
+      do_rawlog(LT_ERR, T("CANNOT DUMP PANIC DB. OOPS."));
+      _exit(134);
+    } else {
+      if ((f = fopen(panicfile, FOPEN_WRITE)) == NULL) {
+       do_rawlog(LT_ERR, T("CANNOT OPEN PANIC FILE, YOU LOSE"));
+       _exit(135);
+      } else {
+       do_rawlog(LT_ERR, T("DUMPING: %s"), panicfile);
+       db_write(f, DBF_PANIC);
+       dump_mail(f);
+#ifdef CHAT_SYSTEM
+       save_chatdb(f);
+#endif /* CHAT_SYSTEM */
+       fclose(f);
+       do_rawlog(LT_ERR, T("DUMPING: %s (done)"), panicfile);
+      }
+    }
+  } else {
+    do_rawlog(LT_ERR, T("Skipping panic dump because database isn't loaded."));
+  }
+  _exit(136);
+}
+
+/** Crash gracefully.
+ * Calls mush_panic() with its arguments formatted.
+ * \param msg printf()-style format string.
+ */
+void
+mush_panicf(const char *msg, ...)
+{
+#ifdef HAS_VSNPRINTF
+  char c[BUFFER_LEN];
+#else
+  char c[BUFFER_LEN * 3];
+#endif
+  va_list args;
+
+  va_start(args, msg);
+
+#ifdef HAS_VSNPRINTF
+  vsnprintf(c, sizeof c, msg, args);
+#else
+  vsprintf(c, msg, args);
+#endif
+  c[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+
+  mush_panic(c);
+  _exit(136);          /* Not reached but kills warnings */
+}
+
+/** Dump the database.
+ * This function is a wrapper for dump_database_internal() that does
+ * a little logging before and after the dump.
+ */
+void
+dump_database(void)
+{
+  epoch++;
+
+  do_rawlog(LT_ERR, "DUMPING: %s.#%d#", dumpfile, epoch);
+  dump_database_internal();
+  do_rawlog(LT_ERR, "DUMPING: %s.#%d# (done)", dumpfile, epoch);
+}
+
+/** Dump a database, possibly by forking the process.
+ * This function calls dump_database_internal() to dump the MUSH
+ * databases. If we're configured to do so, it forks first, so that
+ * the child process can perform the dump while the parent continues
+ * to run the MUSH for the players. If we can't fork, this function
+ * warns players online that a dump is taking place and the game
+ * may pause.
+ * \param forking if 1, attempt a forking dump.
+ */
+void
+fork_and_dump(int forking)
+{
+  int child, nofork, status, split;
+  epoch++;
+#if (MALLOC_PACKAGE == 2)
+  FILE *memory_file;
+#endif
+
+#ifdef LOG_CHUNK_STATS
+  chunk_stats(NOTHING, 0);
+  chunk_stats(NOTHING, 1);
+#endif
+  do_rawlog(LT_CHECK, "CHECKPOINTING: %s.#%d#\n", dumpfile, epoch);
+  if (NO_FORK)
+    nofork = 1;
+  else
+    nofork = !forking || (paranoid_dump == 2); /* Don't fork for dump/debug */
+#ifdef WIN32
+  nofork = 1;
+#endif
+   split = 0;
+   if (!nofork && chunk_num_swapped()) {
+#ifndef WIN32
+    /* Try to clone the chunk swapfile. */
+    if (chunk_fork_file()) {
+     split = 1;
+    } else {
+      /* Ack, can't fork, 'cause we have stuff on disk... */
+      do_log(LT_ERR, 0, 0,
+          "fork_and_dump: Data are swapped to disk, so nonforking dumps will be used.");
+      nofork = 1;
+    }
+#endif
+ }
+
+  if (!nofork) {
+#ifndef WIN32
+    child = fork();
+    if (child < 0) {
+      /* Oops, fork failed. Let's do a nofork dump */
+      do_log(LT_ERR, 0, 0,
+            "fork_and_dump: fork() failed! Dumping nofork instead.");
+      if (DUMP_NOFORK_MESSAGE && *DUMP_NOFORK_MESSAGE)
+       flag_broadcast(0, 0, "%s", DUMP_NOFORK_MESSAGE);
+      child = 0;
+      nofork = 1;
+      if (split) {
+       split = 0;
+       chunk_fork_done();
+      }
+    } else if (child > 0) {
+      forked_dump_pid = child;
+      chunk_fork_parent();
+    } else {
+      chunk_fork_child();
+#ifdef HAS_SETPRIORITY
+      /* Lower the priority of the child to make parent more responsive */
+#ifdef HAS_GETPRIORITY
+      setpriority(PRIO_PROCESS, child, getpriority(PRIO_PROCESS, child) + 4);
+#else                          /* HAS_GETPRIORITY */
+      setpriority(PRIO_PROCESS, child, 8);
+#endif                         /* HAS_GETPRIORITY */
+#endif                         /* HAS_SETPRIORITY */
+    }
+#endif                         /* WIN32 */
+  } else {
+    if (DUMP_NOFORK_MESSAGE && *DUMP_NOFORK_MESSAGE)
+      flag_broadcast(0, 0, "%s", DUMP_NOFORK_MESSAGE);
+    child = 0;
+  }
+#if (MALLOC_PACKAGE == 2)
+   memory_file = fopen("log/memorydebug.log", "a");
+   mal_dumpleaktrace(memory_file);
+   mal_heapdump(memory_file);
+   mal_statsdump(memory_file);
+   fclose(memory_file);
+#endif
+  if (nofork || (!nofork && child == 0)) {
+    /* in the child */
+    release_fd();
+    status = dump_database_internal();
+#ifndef WIN32
+    if (split)
+      chunk_fork_done();
+#endif
+    if (!nofork) {
+      _exit(status);           /* !!! */
+    } else {
+      reserve_fd();
+      if (DUMP_NOFORK_COMPLETE && *DUMP_NOFORK_COMPLETE)
+       flag_broadcast(0, 0, "%s", DUMP_NOFORK_COMPLETE);
+    }
+  }
+#ifdef LOG_CHUNK_STATS
+  chunk_stats(NOTHING, 5);
+#endif
+}
+
+/** Start up the MUSH.
+ * This function does all of the work that's necessary to start up
+ * MUSH objects and code for the game. It sets up player aliases,
+ * fixes null object names, and triggers all object startups.
+ */
+void
+do_restart(void)
+{
+  dbref thing;
+  ATTR *s;
+  char buf[BUFFER_LEN];
+  char *bp;
+  int j;
+
+  /* Do stuff that needs to be done for players only: add stuff to the
+   * alias table, and refund money from queued commands at shutdown.
+   */
+  for (thing = 0; thing < db_top; thing++) {
+    if (IsPlayer(thing)) {
+      if ((s = atr_get_noparent(thing, "ALIAS")) != NULL) {
+       bp = buf;
+       safe_str(atr_value(s), buf, &bp);
+       *bp = '\0';
+       add_player(thing, buf);
+      }
+    }
+  }
+
+  /* Once we load all that, then we can trigger the startups and 
+   * begin queueing commands. Also, let's make sure that we get
+   * rid of null names.
+   */
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = NULL;
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = NULL;
+
+  for (thing = 0; thing < db_top; thing++) {
+    if (Name(thing) == NULL) {
+      if (IsGarbage(thing))
+       set_name(thing, "Garbage");
+      else {
+       do_log(LT_ERR, NOTHING, NOTHING, T("Null name on object #%d"), thing);
+       set_name(thing, "XXXX");
+      }
+    }
+    if (STARTUPS && !IsGarbage(thing) && !(Halted(thing))) {
+      (void) queue_attribute_noparent(thing, "STARTUP", thing);
+      do_top(5);
+    }
+  }
+}
+
+extern void init_names(void);
+extern void init_lmods(void);
+extern struct db_stat_info current_state;
+
+/** Initialize game structures and read the most of the configuration file.
+ * This function runs before we read in the databases. It is responsible
+ * for recording the MUSH start time, setting up all the hash and 
+ * prefix tables and similar structures, and reading the portions of the
+ * config file that don't require database load.
+ * \param conf file name of the configuration file.
+ */
+void
+init_game_config(const char *conf)
+{
+  int a;
+
+  global_eval_context.process_command_port = 0;
+  global_eval_context.break_called = 0;
+  global_eval_context.cplr = NOTHING;
+  strcpy(global_eval_context.ccom, "");
+
+  depth = 0;
+
+
+  for (a = 0; a < 10; a++) {
+    global_eval_context.wenv[a] = NULL;
+    global_eval_context.wnxt[a] = NULL;
+  }
+  for (a = 0; a < NUMQ; a++) {
+    global_eval_context.renv[a][0] = '\0';
+    global_eval_context.rnxt[a] = NULL;
+  }
+
+  /* set MUSH start time */
+  start_time = time((time_t *) 0);
+  if (!first_start_time)
+    first_start_time = start_time;
+
+  /* initialize all the hash and prefix tables */
+  init_flagspaces();
+  init_flag_table("FLAG");
+  init_func_hashtab();
+  init_math_hashtab();
+  init_tag_hashtab();
+  init_aname_table();
+  init_atr_name_tree();
+  init_locks();
+  init_names();
+  init_pronouns();
+  init_lmods();
+
+  memset(&current_state, 0, sizeof current_state);
+
+  /* Load all the config file stuff except restrict_* */
+  local_configs();
+  conf_default_set();
+  config_file_startup(conf, 0);
+  start_all_logs();
+  redirect_stderr();
+
+  /* Initialize the attribute chunk storage */
+  chunk_init();
+
+  do_rawlog(LT_ERR, "CobraMUSH v%s [%s]", VERSION, VBRANCH);
+  do_rawlog(LT_ERR, T("MUSH restarted, PID %d, at %s"),
+           (int) getpid(), show_time(start_time, 0));
+}
+
+/** Post-db-load configuration.
+ * This function contains code that should be run after dbs are loaded 
+ * (usually because we need to have the flag table loaded, or because they 
+ * run last). It reads in the portions of the config file that rely
+ * on flags being defined.
+ * \param conf file name of the configuration file.
+ */
+void
+init_game_postdb(const char *conf)
+{
+  /* access file stuff */
+  read_access_file();
+  /* initialize random number generator */
+  initialize_mt();
+  /* set up signal handlers for the timer */
+  init_timer();
+  init_postconvert();
+  /* Commands and functions require the flag table for restrictions */
+  command_init_preconfig();
+  command_init_postconfig();
+  function_init_postconfig();
+  attr_init_postconfig();
+  /* Load further restrictions from config file */
+  config_file_startup(conf, 1);
+#ifdef RPMODE_SYS
+  init_rplogs();
+#endif
+#ifdef MUSHCRON
+  start_cron(); /* Start the MUSH Cron Daemon */
+#endif /* CRON */
+  /* Call Local Startup */
+  local_startup();
+  /* everything else ok. Restart all objects. */
+  do_restart();
+#ifdef HAS_OPENSSL
+  /* Set up ssl */
+  if (!ssl_init()) {
+    fprintf(stderr, "SSL initialization failure\n");
+    options.ssl_port = 0;      /* Disable ssl */
+  }
+#endif
+#if (defined(HAS_MYSQL) && defined(_SWMP_))
+  sql_startup();
+#endif
+}
+
+extern int dbline;
+
+/** Read the game databases.
+ * This function reads in the object, mail, and chat databases.
+ * \retval -1 error.
+ * \retval 0 success.
+ */
+int
+init_game_dbs(void)
+{
+  FILE *f;
+  int c;
+  const char *infile, *outfile;
+  const char *mailfile;
+  const char *flag_file;
+  int panicdb;
+
+#ifdef WIN32
+  Win32MUSH_setup();           /* create index files, copy databases etc. */
+#endif
+
+  infile = restarting ? options.output_db : options.input_db;
+  outfile = options.output_db;
+  flag_file = options.flagdb;
+  mailfile = options.mail_db;
+  strcpy(dumpfile, outfile);
+
+  /* read small text files into cache */
+  fcache_init();
+
+  /* Read flagfile before main database */
+  f = db_open(flag_file);
+
+  if(f) {
+    if(setjmp(db_err) == 0){
+      use_flagfile = 1;
+      do_rawlog(LT_ERR, "LOADING: %s", flag_file);
+      if(load_flag_db(f) != 0)
+       use_flagfile = 0;
+      do_rawlog(LT_ERR, "LOADING: %s(done)", flag_file);
+      db_close(f);
+    }
+  } else use_flagfile = 0;
+
+  if(ps_tab._Read_Powers_ == 0)
+    init_powers();
+
+  f = db_open(infile);
+
+  if (!f) {
+    do_rawlog(LT_ERR, "Couldn't open %s! Creating minimal world.", infile);
+    init_compress(NULL);
+    create_minimal_db();
+    return 0;
+  }
+
+  c = getc(f);
+  if (c == EOF) {
+    do_rawlog(LT_ERR, "Couldn't read %s! Creating minimal world.", infile);
+    init_compress(NULL);
+    create_minimal_db();
+    return 0;
+  }
+
+  ungetc(c, f);
+
+  if (setjmp(db_err) == 0) {
+    /* ok, read it in */
+    do_rawlog(LT_ERR, "ANALYZING: %s", infile);
+    if (init_compress(f) < 0) {
+      do_rawlog(LT_ERR, "ERROR LOADING %s", infile);
+      return -1;
+    }
+    do_rawlog(LT_ERR, "ANALYZING: %s (done)", infile);
+
+    /* everything ok */
+    db_close(f);
+
+    f = db_open(infile);
+    if (!f)
+      return -1;
+
+    /* ok, read it in */
+    do_rawlog(LT_ERR, "LOADING: %s", infile);
+    dbline = 0;
+    if (db_read(f) < 0) {
+      do_rawlog(LT_ERR, "ERROR LOADING %s", infile);
+      db_close(f);
+      return -1;
+    }
+    do_rawlog(LT_ERR, "LOADING: %s (done)", infile);
+
+    /* If there's stuff at the end of the db, we may have a panic
+     * format db, with everything shoved together. In that case,
+     * don't close the file
+     */
+    panicdb = ((indb_flags & DBF_PANIC) && !feof(f));
+
+    if (!panicdb)
+      db_close(f);
+
+    /* complain about bad config options */
+    if (!GoodObject(PLAYER_START) || (!IsRoom(PLAYER_START)))
+      do_rawlog(LT_ERR, T("WARNING: Player_start (#%d) is NOT a room."),
+               PLAYER_START);
+    if (!GoodObject(MASTER_ROOM) || (!IsRoom(MASTER_ROOM)))
+      do_rawlog(LT_ERR, T("WARNING: Master room (#%d) is NOT a room."),
+               MASTER_ROOM);
+    if (!GoodObject(BASE_ROOM) || (!IsRoom(BASE_ROOM)))
+      do_rawlog(LT_ERR, T("WARNING: Base room (#%d) is NOT a room."),
+               BASE_ROOM);
+    if (!GoodObject(DEFAULT_HOME) || (!IsRoom(DEFAULT_HOME)))
+      do_rawlog(LT_ERR, T("WARNING: Default home (#%d) is NOT a room."),
+               DEFAULT_HOME);
+    if (!GoodObject(GOD) || (!IsPlayer(GOD)))
+      do_rawlog(LT_ERR, T("WARNING: God (#%d) is NOT a player."), GOD);
+
+    /* read mail database */
+    mail_init();
+
+    if (panicdb) {
+      do_rawlog(LT_ERR, T("LOADING: Trying to get mail from %s"), infile);
+      if (load_mail(f) <= 0) {
+        do_rawlog(LT_ERR, T("FAILED: Reverting to normal maildb"));
+        db_close(f);
+        panicdb = 0;
+      }
+    }
+
+    if (!panicdb) {
+      f = db_open(mailfile);
+      /* okay, read it in */
+      if (f) {
+        do_rawlog(LT_ERR, "LOADING: %s", mailfile);
+        dbline = 0;
+        load_mail(f);
+        do_rawlog(LT_ERR, "LOADING: %s (done)", mailfile);
+        db_close(f);
+      }
+    }
+
+#ifdef CHAT_SYSTEM
+    init_chatdb();
+
+    if (panicdb) {
+      do_rawlog(LT_ERR, T("LOADING: Trying to get chat from %s"), infile);
+      if (load_chatdb(f) <= 0) {
+        do_rawlog(LT_ERR, T("FAILED: Reverting to normal chatdb"));
+        db_close(f);
+        panicdb = 0;
+      }
+    }
+
+    if (!panicdb) {
+      f = db_open(options.chatdb);
+      if (f) {
+       do_rawlog(LT_ERR, "LOADING: %s", options.chatdb);
+       dbline = 0;
+       if (load_chatdb(f)) {
+         do_rawlog(LT_ERR, "LOADING: %s (done)", options.chatdb);
+       } else {
+         do_rawlog(LT_ERR, "ERROR LOADING %s", options.chatdb);
+         return -1;
+       }
+       db_close(f);
+      }
+    }
+#endif /* CHAT_SYSTEM */
+  } else {
+    do_rawlog(LT_ERR, "ERROR READING DATABASE!");
+    return -1;
+  }
+
+  return 0;
+}
+
+/** Read cached text files.
+ * \verbatim
+ * This implements the @readcache function.
+ * \endverbatim
+ * \param player the enactor, for permission checking.
+ */
+void
+do_readcache(dbref player)
+{
+  fcache_load(player);
+  help_reindex(player);
+}
+
+/** Check each attribute on each object in x for a $command matching cptr */
+#define list_match(x)        list_check(x, player, '$', ':', cptr, 0)
+/** Check each attribute on x for a $command matching cptr */
+#define cmd_match(x)         atr_comm_match(x, player, '$', ':', cptr, 0, NULL, NULL, &errdb)
+#define MAYBE_ADD_ERRDB(errdb)  \
+        do { \
+          if (GoodObject(errdb)) { \
+            if ((errdbtail - errdblist) >= errdbsize) \
+              errdb_grow(); \
+            if ((errdbtail - errdblist) < errdbsize) { \
+              *errdbtail = errdb; \
+              errdbtail++; \
+            } \
+            errdb = NOTHING; \
+          } \
+         } while(0)
+
+/** Attempt to match and execute a command.
+ * This function performs some sanity checks and then attempts to
+ * run a command. It checks, in order: home, built-in commands,
+ * enter aliases, leave aliases, $commands on neighboring objects or
+ * the player, $commands on the container, $commands on inventory,
+ * exits in the zone master room, $commands on objects in the ZMR,
+ * $commands on the ZMO, $commands on the player's zone, exits in the
+ * master room, and $commands on objectrs in the master room.
+ *
+ * When a command is directly input from a socket, we don't parse
+ * the value in attribute sets.
+ *
+ * \param player the enactor.
+ * \param command command to match and execute.
+ * \param cause object which caused the command to be executed.
+ * \param from_port if 1, the command was direct input from a socket.
+ */
+void
+process_command(dbref player, char *command, dbref cause, dbref realcause,  int from_port)
+{
+  int a;
+  char *p;                     /* utility */
+
+  char unp[BUFFER_LEN];                /* unparsed command */
+  /* general form command arg0=arg1,arg2...arg10 */
+  char temp[BUFFER_LEN];       /* utility */
+  int i;                       /* utility */
+  char *cptr;
+  dbref errdb;
+  dbref check_loc;
+
+  if (!errdblist)
+    errdblist = mush_malloc(errdbsize * sizeof(dbref), "errdblist");
+  errdbtail = errdblist;
+  errdb = NOTHING;
+  depth = 0;
+  if (!command) {
+    do_log(LT_ERR, NOTHING, NOTHING, T("ERROR: No command!!!"));
+    return;
+  }
+  /* robustify player */
+  if (!GoodObject(player)) {
+    do_log(LT_ERR, NOTHING, NOTHING, T("process_command bad player #%d"),
+          player);
+    return;
+  }
+
+  /* Destroyed objects shouldn't execute commands */
+  if (IsGarbage(player))
+    /* No message - nobody to tell, and it's too easy to do to log. */
+    return;
+  /* Halted objects can't execute commands */
+  /* And neither can halted players if the command isn't from_port */
+  if (Halted(player) && (!IsPlayer(player) || !from_port)) {
+    notify_format(Owner(player),
+                 T("Attempt to execute command by halted object #%d"), player);
+    return;
+  }
+  /* Players, things, and exits should not have invalid locations. This check
+   * must be done _after_ the destroyed-object check.
+   */
+  check_loc = IsExit(player) ? Source(player) : (IsRoom(player) ? player :
+                                                Location(player));
+  if (!GoodObject(check_loc) || IsGarbage(check_loc)) {
+    notify_format(Owner(player),
+                 T("Invalid location on command execution: %s(#%d)"),
+                 Name(player), player);
+    do_log(LT_ERR, NOTHING, NOTHING,
+          T("Command attempted by %s(#%d) in invalid location #%d."),
+          Name(player), player, Location(player));
+    if (Mobile(player))
+      moveto(player, PLAYER_START);    /* move it someplace valid */
+  }
+  orator = player;
+
+  log_activity(LA_CMD, player, command);
+  if (options.log_commands || Suspect(player))
+    do_log(LT_CMD, player, NOTHING, "%s", command);
+
+  if (Verbose(player))
+    raw_notify(Owner(player), tprintf("#%d] %s", player, command));
+
+  /* eat leading whitespace */
+  while (*command && isspace((unsigned char) *command))
+    command++;
+
+  /* eat trailing whitespace */
+  p = command + strlen(command) - 1;
+  while (isspace((unsigned char) *p) && (p >= command))
+    p--;
+  *++p = '\0';
+
+  /* ignore null commands that aren't from players */
+  if ((!command || !*command) && !from_port)
+    return;
+
+  /* important home checking comes first! */
+  if (strcmp(command, "home") == 0) {
+    if (!Mobile(player))
+      return;
+    if (Fixed(player))
+      notify(player, T("You can't do that IC!"));
+    else
+      do_move(player, command, 0);
+    return;
+  }
+  strcpy(unp, command);
+
+  cptr = command_parse(player, cause, realcause, command, from_port);
+  strcpy(global_eval_context.ucom, (cptr ? cptr : ""));
+  if (cptr) {
+    a = 0;
+    if (!Gagged(player)) {
+      if (Mobile(player)) {
+       /* if the "player" is an exit or room, no need to do these checks */
+       /* try matching enter aliases */
+       if (check_loc != NOTHING &&
+           (i = alias_list_check(Contents(check_loc), cptr, "EALIAS")) != -1) {
+
+         sprintf(temp, "#%d", i);
+         do_enter(player, temp);
+         goto done;
+       }
+       /* if that didn't work, try matching leave aliases */
+       if (!IsRoom(check_loc) && (loc_alias_check(check_loc, cptr, "LALIAS"))) {
+         do_leave(player);
+         goto done;
+       }
+      }
+
+      /* try matching user defined functions before chopping */
+
+      /* try objects in the player's location, the location itself,
+       * and objects in the player's inventory.
+       */
+      if (GoodObject(check_loc)) {
+       a += list_match(Contents(check_loc));
+       if (check_loc != player) {
+         a += cmd_match(check_loc);
+         MAYBE_ADD_ERRDB(errdb);
+       }
+      }
+      if (check_loc != player)
+       a += list_match(Contents(player));
+
+      /* now do check on zones */
+      if ((!a) && (Zone(check_loc) != NOTHING)) {
+       if (IsRoom(Zone(check_loc))) {
+         /* zone of player's location is a zone master room,
+          * so we check for exits and commands
+          */
+         /* check zone master room exits */
+         if (remote_exit(player, cptr)) {
+           if (!Mobile(player))
+             goto done;
+           else {
+             do_move(player, cptr, 2);
+             goto done;
+           }
+         } else
+           a += list_match(Contents(Zone(Location(player))));
+       } else {
+         a += cmd_match(Zone(Location(player)));
+         MAYBE_ADD_ERRDB(errdb);
+       }
+      }
+      /* if nothing matched with zone master room/zone object, try
+       * matching zone commands on the player's personal zone
+       */
+      if ((!a) && (Zone(player) != NOTHING) &&
+         (Zone(Location(check_loc)) != Zone(player))) {
+       if (IsRoom(Zone(player)))
+         /* Player's personal zone is a zone master room, so we
+          * also check commands on objects in that room
+          */
+         a += list_match(Contents(Zone(check_loc)));
+       else {
+         a += cmd_match(Zone(player));
+         MAYBE_ADD_ERRDB(errdb);
+       }
+      }
+      /* end of zone stuff */
+      /* check division object */
+      if(!a && GoodObject(SDIV(player).object) && IsDivision(SDIV(player).object))
+        a += atr_comm_divmatch(SDIV(player).object, player, '$', ':', cptr, 0, NULL, NULL, &errdb);
+
+      /* check global exits only if no other commands are matched */
+      if ((!a) && (check_loc != MASTER_ROOM)) {
+       if (global_exit(player, cptr)) {
+         if (!Mobile(player))
+           goto done;
+         else {
+           do_move(player, cptr, 1);
+           goto done;
+         }
+       } else
+         /* global user-defined commands checked if all else fails.
+          * May match more than one command in the master room.
+          */
+         a += list_match(Contents(MASTER_ROOM));
+      }
+      /* end of master room check */
+    }                          /* end of special checks */
+    if (!a) {
+      /* Do we have any error dbs queued up, and if so, do any
+       * have associated failure messages?
+       */
+      if ((errdblist == errdbtail) || (!fail_commands(player)))
+       generic_command_failure(player, cause, unp, from_port);
+    }
+  }
+
+  /* command has been executed. Free up memory. */
+
+done:
+  ;
+}
+
+
+COMMAND (cmd_with) {
+  dbref what;
+  char *cptr = arg_right;
+  dbref errdb;
+
+  what = noisy_match_result(player, arg_left, NOTYPE, MAT_NEARBY);
+  if (!GoodObject(what))
+    return;
+
+  errdbtail = errdblist;
+  errdb = NOTHING;
+  if (!SW_ISSET(sw, SWITCH_ROOM)) {
+    /* Run commands on a single object */
+    if (!cmd_match(what)) {
+      MAYBE_ADD_ERRDB(errdb);
+      notify(player, T("No matching command."));
+    }
+  } else {
+    /* Run commands on objects in a masterish room */
+
+    if (!IsRoom(what)) {
+      notify(player, T("Make room! Make room!"));
+      return;
+    }
+
+    if (!list_match(Contents(what)))
+      notify(player, T("No matching command."));
+  }
+}
+
+/* now undef everything that needs to be */
+#undef list_match
+#undef cmd_match
+
+/** Check to see if a string matches part of a semicolon-separated list.
+ * \param command string to match.
+ * \param list semicolon-separated list of aliases to match against.
+ * \retval 1 string matched an alias.
+ * \retval 0 string failed to match an alias.
+ */
+int
+check_alias(const char *command, const char *list)
+{
+  /* check if a string matches part of a semi-colon separated list */
+  const char *p;
+  while (*list) {
+    for (p = command; (*p && DOWNCASE(*p) == DOWNCASE(*list)
+                      && *list != EXIT_DELIMITER); p++, list++) ;
+    if (*p == '\0') {
+      while (isspace((unsigned char) *list))
+       list++;
+      if (*list == '\0' || *list == EXIT_DELIMITER)
+       return 1;               /* word matched */
+    }
+    /* didn't match. check next word in list */
+    while (*list && *list++ != EXIT_DELIMITER) ;
+    while (isspace((unsigned char) *list))
+      list++;
+  }
+  /* reached the end of the list without matching anything */
+  return 0;
+}
+
+/** Match a command or listen pattern against a list of things.
+ * This function iterates through a list of things (using the object
+ * Next pointer, so typically this is a list of contents of an object),
+ * and checks each for an attribute matching a command/listen pattern.
+ * \param thing first object on list.
+ * \param player the enactor.
+ * \param type type of attribute to match ('$' or '^')
+ * \param end character that signals the end of the matchable portion (':')
+ * \param str string to match against the attributes.
+ * \param just_match if 1, don't execute the command on match.
+ * \retval 1 a match was made.
+ * \retval 0 no match was made.
+ */
+int
+list_check(dbref thing, dbref player, char type, char end, char *str,
+          int just_match)
+{
+  int match = 0;
+  dbref errdb = NOTHING;
+
+  while (thing != NOTHING) {
+    if (atr_comm_match
+       (thing, player, type, end, str, just_match, NULL, NULL, &errdb))
+      match = 1;
+    else {
+      MAYBE_ADD_ERRDB(errdb);
+    }
+    thing = Next(thing);
+  }
+  return (match);
+}
+
+/** Match a command against an attribute of aliases.
+ * This function iterates through a list of things (using the object
+ * Next pointer, so typically this is a list of contents of an object),
+ * and checks each for an attribute of aliases that might be matched
+ * (as in EALIAS).
+ * \param thing first object on list.
+ * \param command command to attempt to match.
+ * \param type name of attribute of aliases to match against.
+ * \return dbref of first matching object, or -1 if none.
+ */
+int
+alias_list_check(dbref thing, const char *command, const char *type)
+{
+  ATTR *a;
+  char alias[BUFFER_LEN];
+
+  while (thing != NOTHING) {
+    a = atr_get_noparent(thing, type);
+    if (a) {
+      strcpy(alias, atr_value(a));
+      if (check_alias(command, alias) != 0)
+       return thing;           /* matched an alias */
+    }
+    thing = Next(thing);
+  }
+  return -1;
+}
+
+/** Check a command against a list of aliases on a location
+ * (as for LALIAS).
+ * \param loc location with attribute of aliases.
+ * \param command command to attempt to match.
+ * \param type name of attribute of aliases to match against.
+ * \retval 1 successful match.
+ * \retval 0 failure.
+ */
+int
+loc_alias_check(dbref loc, const char *command, const char *type)
+{
+  ATTR *a;
+  char alias[BUFFER_LEN];
+  a = atr_get_noparent(loc, type);
+  if (a) {
+    strcpy(alias, atr_value(a));
+    return (check_alias(command, alias));
+  } else
+    return 0;
+}
+
+/** Can an object hear?
+ * This function determines if a given object can hear. A Hearer is:
+ * a connected player, a puppet, an AUDIBLE object with a FORWARDLIST
+ * attribute, or an object with a LISTEN attribute.
+ * \param thing object to check.
+ * \retval 1 object can hear.
+ * \retval 0 object can't hear.
+ */
+int
+Hearer(dbref thing)
+{
+  ALIST *ptr;
+  int cmp;
+
+  if (Connected(thing) || Puppet(thing))
+    return 1;
+  for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
+    if (Audible(thing) && (strcmp(AL_NAME(ptr), "FORWARDLIST") == 0))
+      return 1;
+    cmp = strcoll(AL_NAME(ptr), "LISTEN");
+    if (cmp == 0)
+      return 1;
+    if (cmp > 0)
+      break;
+  }
+  return 0;
+}
+
+/** Might an object be responsive to commands?
+ * This function determines if a given object might pick up a $command.
+ * That is, if it has any attributes with $commands on them that are
+ * not set no_command.
+ * \param thing object to check.
+ * \retval 1 object responds to commands. 
+ * \retval 0 object doesn't respond to commands.
+ */
+int
+Commer(dbref thing)
+{
+  ALIST *ptr;
+
+  if(Halted(thing)||NoCommand(thing))
+         return 0;
+  for (ptr = List(thing); ptr; ptr = AL_NEXT(ptr)) {
+    if (AF_Command(ptr) && !AF_Noprog(ptr))
+      return (1);
+  }
+  return (0);
+}
+
+/** Is an object listening?
+ * This function determines if a given object is a Listener. A Listener
+ * is a thing or room that has the MONITOR flag set.
+ * \param thing object to check.
+ * \retval 1 object is a Listener.
+ * \retval 0 object isn't listening with ^patterns.
+ */
+int
+Listener(dbref thing)
+{
+  /* If a monitor flag is set on a room or thing, it's a listener.
+   * Otherwise not (even if ^patterns are present)
+   */
+  return (ThingListen(thing) || RoomListen(thing));
+}
+
+/** Reset all players' money.
+ * \verbatim
+ * This function implements the @poor command. It probably belongs in 
+ * rob.c, though.
+ * \endverbatim
+ * \param player the enactor, for permission checking.
+ * \param arg1 the amount of money to reset all players to.
+ */
+void
+do_poor(dbref player, char *arg1)
+{
+  int amt = atoi(arg1);
+  dbref a;
+  if (!God(player)) {
+    notify(player, T("Only God can cause financial ruin."));
+    return;
+  }
+  for (a = 0; a < db_top; a++)
+    if (IsPlayer(a))
+      s_Pennies(a, amt);
+  notify_format(player,
+               T
+               ("The money supply of all players has been reset to %d %s."),
+               amt, MONIES);
+  do_log(LT_WIZ, player, NOTHING,
+        T("** POOR done ** Money supply reset to %d %s."), amt, MONIES);
+}
+
+
+/** User interface to write a message to a log.
+ * \verbatim
+ * This function implements @log.
+ * \endverbatim
+ * \param player the enactor.
+ * \param str message to write to the log.
+ * \param ltype type of log to write to.
+ */
+void
+do_writelog(dbref player, char *str, int ltype)
+{
+  do_rawlog(ltype, "LOG: %s(#%d%s): %s", Name(player), player,
+           unparse_flags(player, GOD), str);
+
+  notify(player, "Logged.");
+}
+
+/** Bind occurences of '##' in "action" to "arg", then run "action".
+ * \param player the enactor.
+ * \param cause object that caused command to run.
+ * \param action command string which may contain tokens.
+ * \param arg value for ## token.
+ * \param placestr value for #@ token.
+ */
+void
+bind_and_queue(dbref player, dbref cause, char *action,
+              const char *arg, const char *placestr)
+{
+  char *repl, *command;
+  const char *replace[2];
+
+  replace[0] = arg;
+  replace[1] = placestr;
+
+  repl = replace_string2(standard_tokens, replace, action);
+
+  command = strip_braces(repl);
+
+  mush_free(repl, "replace_string.buff");
+
+  parse_que(player, command, cause);
+
+  mush_free(command, "strip_braces.buff");
+}
+
+/** Would the scan command find an matching attribute on x for player p? */
+#define ScanFind(p,x)  \
+  (Can_Examine(p,x) && \
+      ((num = atr_comm_match(x, p, '$', ':', command, 1, atrname, &ptr, NULL)) != 0))
+
+/** Scan for matches of $commands.
+ * This function scans for possible matches of user-def'd commands from the
+ * viewpoint of player, and return as a string.
+ * It assumes that atr_comm_match() returns atrname with a leading space.
+ * \param player the object from whose viewpoint to scan.
+ * \param command the command to scan for matches to.
+ * \return string of obj/attrib pairs with matching $commands.
+ */
+char *
+scan_list(dbref player, char *command)
+{
+  static char tbuf[BUFFER_LEN];
+  char *tp;
+  dbref thing;
+  char atrname[BUFFER_LEN];
+  char *ptr;
+  int num;
+
+  if (!GoodObject(Location(player))) {
+    strcpy(tbuf, T("#-1 INVALID LOCATION"));
+    return tbuf;
+  }
+  if (!command || !*command) {
+    strcpy(tbuf, T("#-1 NO COMMAND"));
+    return tbuf;
+  }
+  tp = tbuf;
+  ptr = atrname;
+  DOLIST(thing, Contents(Location(player))) {
+    if (ScanFind(player, thing)) {
+      *ptr = '\0';
+      safe_str(atrname, tbuf, &tp);
+      ptr = atrname;
+    }
+  }
+  ptr = atrname;
+  if (ScanFind(player, Location(player))) {
+    *ptr = '\0';
+    safe_str(atrname, tbuf, &tp);
+  }
+  ptr = atrname;
+  DOLIST(thing, Contents(player)) {
+    if (ScanFind(player, thing)) {
+      *ptr = '\0';
+      safe_str(atrname, tbuf, &tp);
+      ptr = atrname;
+    }
+  }
+  /* zone checks */
+  ptr = atrname;
+  if (Zone(Location(player)) != NOTHING) {
+    if (IsRoom(Zone(Location(player)))) {
+      /* zone of player's location is a zone master room */
+      if (Location(player) != Zone(player)) {
+       DOLIST(thing, Contents(Zone(Location(player)))) {
+         if (ScanFind(player, thing)) {
+           *ptr = '\0';
+           safe_str(atrname, tbuf, &tp);
+           ptr = atrname;
+         }
+       }
+      }
+    } else {
+      /* regular zone object */
+      if (ScanFind(player, Zone(Location(player)))) {
+       *ptr = '\0';
+       safe_str(atrname, tbuf, &tp);
+      }
+    }
+  }
+  ptr = atrname;
+  if ((Zone(player) != NOTHING)
+      && (Zone(player) != Zone(Location(player)))) {
+    /* check the player's personal zone */
+    if (IsRoom(Zone(player))) {
+      if (Location(player) != Zone(player)) {
+       DOLIST(thing, Contents(Zone(player))) {
+         if (ScanFind(player, thing)) {
+           *ptr = '\0';
+           safe_str(atrname, tbuf, &tp);
+           ptr = atrname;
+         }
+       }
+      }
+    } else if (ScanFind(player, Zone(player))) {
+      *ptr = '\0';
+      safe_str(atrname, tbuf, &tp);
+    }
+  }
+  ptr = atrname;
+  if ((Location(player) != MASTER_ROOM)
+      && (Zone(Location(player)) != MASTER_ROOM)
+      && (Zone(player) != MASTER_ROOM)) {
+    /* try Master Room stuff */
+    DOLIST(thing, Contents(MASTER_ROOM)) {
+      if (ScanFind(player, thing)) {
+       *ptr = '\0';
+       safe_str(atrname, tbuf, &tp);
+       ptr = atrname;
+      }
+    }
+  }
+  *tp = '\0';
+  if (*tbuf && *tbuf == ' ')
+    return tbuf + 1;           /* atrname comes with leading spaces */
+  return tbuf;
+}
+
+/** User interface to scan for $command matches.
+ * \verbatim
+ * This function implements @scan.
+ * \endverbatim
+ * \param player the enactor.
+ * \param command command to scan for matches to.
+ * \param flag bitflags for where to scan.
+ */
+void
+do_scan(dbref player, char *command, int flag)
+{
+  /* scan for possible matches of user-def'ed commands */
+  char atrname[BUFFER_LEN];
+  char *ptr;
+  dbref thing;
+  int num;
+  char save_ccom[BUFFER_LEN];
+
+  ptr = atrname;
+  if (!GoodObject(Location(player))) {
+    notify(player, T("Sorry, you are in an invalid location."));
+    return;
+  }
+  if (!command || !*command) {
+    notify(player, T("What command do you want to scan for?"));
+    return;
+  }
+  strcpy(save_ccom, global_eval_context.ccom);
+  memmove(global_eval_context.ccom, (char *) global_eval_context.ccom + 5, BUFFER_LEN - 5);
+  if (flag & CHECK_NEIGHBORS) {
+    notify(player, T("Matches on contents of this room:"));
+    DOLIST(thing, Contents(Location(player))) {
+      if (ScanFind(player, thing)) {
+       *ptr = '\0';
+       notify_format(player,
+                     "%s  [%d:%s]", unparse_object(player, thing),
+                     num, atrname);
+       ptr = atrname;
+      }
+    }
+  }
+  ptr = atrname;
+  if (flag & CHECK_HERE) {
+    if (ScanFind(player, Location(player))) {
+      *ptr = '\0';
+      notify_format(player, T("Matched here: %s  [%d:%s]"),
+                   unparse_object(player, Location(player)), num, atrname);
+    }
+  }
+  ptr = atrname;
+  if (flag & CHECK_INVENTORY) {
+    notify(player, T("Matches on carried objects:"));
+    DOLIST(thing, Contents(player)) {
+      if (ScanFind(player, thing)) {
+       *ptr = '\0';
+       notify_format(player, "%s  [%d:%s]",
+                     unparse_object(player, thing), num, atrname);
+       ptr = atrname;
+      }
+    }
+  }
+  ptr = atrname;
+  if (flag & CHECK_SELF) {
+    if (ScanFind(player, player)) {
+      *ptr = '\0';
+      notify_format(player, T("Matched self: %s  [%d:%s]"),
+                   unparse_object(player, player), num, atrname);
+    }
+  }
+  ptr = atrname;
+  if (flag & CHECK_ZONE) {
+    /* zone checks */
+    if (Zone(Location(player)) != NOTHING) {
+      if (IsRoom(Zone(Location(player)))) {
+       /* zone of player's location is a zone master room */
+       if (Location(player) != Zone(player)) {
+         notify(player, T("Matches on zone master room of location:"));
+         DOLIST(thing, Contents(Zone(Location(player)))) {
+           if (ScanFind(player, thing)) {
+             *ptr = '\0';
+             notify_format(player, "%s  [%d:%s]",
+                           unparse_object(player, thing), num, atrname);
+             ptr = atrname;
+           }
+         }
+       }
+      } else {
+       /* regular zone object */
+       if (ScanFind(player, Zone(Location(player)))) {
+         *ptr = '\0';
+         notify_format(player,
+                       T("Matched zone of location: %s  [%d:%s]"),
+                       unparse_object(player,
+                                      Zone(Location(player))), num, atrname);
+       }
+      }
+    }
+    ptr = atrname;
+    if ((Zone(player) != NOTHING)
+       && (Zone(player) != Zone(Location(player)))) {
+      /* check the player's personal zone */
+      if (IsRoom(Zone(player))) {
+       if (Location(player) != Zone(player)) {
+         notify(player, T("Matches on personal zone master room:"));
+         DOLIST(thing, Contents(Zone(player))) {
+           if (ScanFind(player, thing)) {
+             *ptr = '\0';
+             notify_format(player, "%s  [%d:%s]",
+                           unparse_object(player, thing), num, atrname);
+             ptr = atrname;
+           }
+         }
+       }
+      } else if (ScanFind(player, Zone(player))) {
+       *ptr = '\0';
+       notify_format(player, T("Matched personal zone: %s  [%d:%s]"),
+                     unparse_object(player, Zone(player)), num, atrname);
+      }
+    }
+  }
+  ptr = atrname;
+  if ((flag & CHECK_GLOBAL)
+      && (Location(player) != MASTER_ROOM)
+      && (Zone(Location(player)) != MASTER_ROOM)
+      && (Zone(player) != MASTER_ROOM)) {
+    /* try Master Room stuff */
+    notify(player, T("Matches on objects in the Master Room:"));
+    DOLIST(thing, Contents(MASTER_ROOM)) {
+      if (ScanFind(player, thing)) {
+       *ptr = '\0';
+       notify_format(player, "%s  [%d:%s]",
+                     unparse_object(player, thing), num, atrname);
+       ptr = atrname;
+      }
+    }
+  }
+  strcpy(global_eval_context.ccom, save_ccom);
+}
+
+#define DOL_MAP 1      /**< The map command */
+#define DOL_NOTIFY 2   /**< Add a notify after a dolist */
+#define DOL_DELIM 4    /**< Specify a delimiter to a dolist */
+
+/** Execute a command for each element of a list.
+ * \verbatim
+ * This function implements @dolist.
+ * \endverbatim
+ * \param player the enactor.
+ * \param list string containing the list to iterate over.
+ * \param command command to run for each list element.
+ * \param cause object which caused this command to be run.
+ * \param flags command switch flags.
+ */
+void
+do_dolist(dbref player, char *list, char *command, dbref cause,
+         unsigned int flags)
+{
+  char *curr, *objstring;
+  char outbuf[BUFFER_LEN];
+  char *bp;
+  int place;
+  char placestr[10];
+  int j;
+  char delim = ' ';
+  if (!command || !*command) {
+    notify(player, T("What do you want to do with the list?"));
+    if (flags & DOL_NOTIFY)
+      parse_que(player, "@notify me", cause);
+    return;
+  }
+
+  if (flags & DOL_DELIM) {
+    if (list[1] != ' ') {
+      notify(player, T("Separator must be one character."));
+      if (flags & DOL_NOTIFY)
+       parse_que(player, "@notify me", cause);
+      return;
+    }
+    delim = list[0];
+  }
+
+  /* set up environment for any spawned commands */
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = global_eval_context.wenv[j];
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = global_eval_context.renv[j];
+  bp = outbuf;
+  if (flags & DOL_DELIM)
+    list += 2;
+  place = 0;
+  objstring = trim_space_sep(list, delim);
+  if (objstring && !*objstring) {
+    /* Blank list */
+    if (flags & DOL_NOTIFY)
+      parse_que(player, "@notify me", cause);
+    return;
+  }
+
+  while (objstring) {
+    curr = split_token(&objstring, delim);
+    place++;
+    sprintf(placestr, "%d", place);
+    if (!(flags & DOL_MAP)) {
+      /* @dolist, queue command */
+      bind_and_queue(player, cause, command, curr, placestr);
+    } else {
+      const char *replace[2];
+      char *ebuf, *ebufptr;
+      /* it's @map, add to the output list */
+      if (bp != outbuf)
+       safe_chr(delim, outbuf, &bp);
+      replace[0] = curr;
+      replace[1] = placestr;
+      ebufptr = ebuf = replace_string2(standard_tokens, replace, command);
+      process_expression(outbuf, &bp, (char const **) &ebuf, player,
+                        cause, cause, PE_DEFAULT, PT_DEFAULT, NULL);
+      mush_free(ebufptr, "replace_string.buff");
+    }
+  }
+
+  *bp = '\0';
+  if (flags & DOL_MAP) {
+    /* if we're doing a @map, copy the list to an attribute */
+    (void) atr_add(player, "MAPLIST", outbuf, GOD, NOTHING);
+    notify(player, T("Function mapped onto list."));
+  }
+  if (flags & DOL_NOTIFY) {
+    /*  Execute a '@notify me' so the object knows we're done
+     *  with the list execution. We don't execute dequeue_semaphores()
+     *  directly, since we want the command to be queued
+     *  _after_ the list has executed.
+     */
+    parse_que(player, "@notify me", cause);
+  }
+}
+
+#ifdef linux
+static void linux_uptime(dbref player) __attribute__ ((__unused__));
+#elif defined(WIN32)
+static void win32_uptime(dbref player) __attribute__ ((__unused__));
+#else
+static void unix_uptime(dbref player) __attribute__ ((__unused__));
+#endif
+
+#ifdef linux
+static void
+linux_uptime(dbref player __attribute__ ((__unused__)))
+{
+  /* Use /proc files instead of calling the external uptime program on linux */
+  char tbuf1[BUFFER_LEN];
+  FILE *fp;
+  char line[128];              /* Overkill */
+  char *nl;
+  Pid_t pid;
+  int psize;
+#ifdef HAS_GETRUSAGE
+  struct rusage usage;
+#endif
+
+  /* Current time */
+  {
+    struct tm *t;
+    t = localtime(&mudtime);
+    strftime(tbuf1, sizeof tbuf1, "%I:%M%p ", t);
+    nl = tbuf1 + strlen(tbuf1);
+  }
+  /* System uptime */
+  fp = fopen("/proc/uptime", "r");
+  if (fp) {
+    time_t uptime;
+    const char *fmt;
+    if (fgets(line, sizeof line, fp)) {
+      /* First part of this line is uptime in seconds.milliseconds. We
+         only care about seconds. */
+      uptime = strtol(line, NULL, 10);
+      if (uptime > 86400)
+       fmt = "up $d days, $2h:$2M,";
+      else
+       fmt = "up $2h:$2M,";
+      do_timestring(tbuf1, &nl, fmt, uptime);
+    } else {
+      safe_str("Unknown uptime,", tbuf1, &nl);
+    }
+    fclose(fp);
+  } else {
+    safe_str("Unknown uptime,", tbuf1, &nl);
+  }
+
+  /* Now load averages */
+  fp = fopen("/proc/loadavg", "r");
+  if (fp) {
+    if (fgets(line, sizeof line, fp)) {
+      double load[3];
+      char *x, *l = line;
+      load[0] = strtod(l, &x);
+      l = x;
+      load[1] = strtod(l, &x);
+      l = x;
+      load[2] = strtod(l, NULL);
+      safe_format(tbuf1, &nl, " load average: %.2f, %.2f, %.2f",
+                 load[0], load[1], load[2]);
+    } else {
+      safe_str("Unknown load", tbuf1, &nl);
+    }
+    fclose(fp);
+  } else {
+    safe_str("Unknown load", tbuf1, &nl);
+  }
+
+  *nl = '\0';
+  notify(player, tbuf1);
+
+  /* do process stats */
+  (void) getcwd(tbuf1, BUFFER_LEN);
+  tbuf1[BUFFER_LEN-1] = '\0';
+  pid = getpid();
+  psize = getpagesize();
+  notify_format(player,
+               T("\nWorking directory:  %s\nProcess ID:  %10u        %10d bytes per page"),
+               tbuf1, pid, psize);
+
+  /* Linux's getrusage() is mostly unimplemented. Just has times, page faults
+     and swapouts. We use /proc/self/status */
+#ifdef HAS_GETRUSAGE
+  getrusage(RUSAGE_SELF, &usage);
+  notify_format(player, T("Time used:   %10ld user   %10ld sys"),
+               usage.ru_utime.tv_sec, usage.ru_stime.tv_sec);
+  notify_format(player,
+               T
+               ("Page faults: %10ld hard   %10ld soft    %10ld swapouts"),
+               usage.ru_majflt, usage.ru_minflt, usage.ru_nswap);
+#endif
+  fp = fopen("/proc/self/status", "r");
+  if (!fp)
+    return;
+  /* Skip lines we don't care about. */
+  while (fgets(line, sizeof line, fp) != NULL) {
+    static const char *fields[] = {
+      "VmSize:", "VmRSS:", "VmData:", "VmStk:", "VmExe:", "VmLib:",
+      "SigPnd:", "SigBlk:", "SigIgn:", "SigCgt:", NULL
+    };
+    int n;
+    for (n = 0; fields[n]; n++) {
+      size_t len = strlen(fields[n]);
+      if (strncmp(line, fields[n], len) == 0) {
+       if ((nl = strchr(line, '\n')) != NULL)
+         *nl = '\0';
+       notify(player, line);
+      }
+    }
+  }
+
+  fclose(fp);
+
+}
+#elif defined(WIN32)
+
+static void
+win32_uptime(dbref player __attribute__ ((__unused__)))
+{                              /* written by NJG */
+#ifdef WIN32
+  MEMORYSTATUS memstat;
+  double mem;
+  memstat.dwLength = sizeof(memstat);
+  GlobalMemoryStatus(&memstat);
+  notify(player, "---------- Windows memory usage ------------");
+  notify_format(player, "%10ld %% memory in use", memstat.dwMemoryLoad);
+  mem = memstat.dwAvailPhys / 1024.0 / 1024.0;
+  notify_format(player, "%10.3f Mb free physical memory", mem);
+  mem = memstat.dwTotalPhys / 1024.0 / 1024.0;
+  notify_format(player, "%10.3f Mb total physical memory", mem);
+  mem = memstat.dwAvailPageFile / 1024.0 / 1024.0;
+  notify_format(player, "%10.3f Mb available in the paging file ", mem);
+  mem = memstat.dwTotalPageFile / 1024.0 / 1024.0;
+  notify_format(player, "%10.3f Mb total paging file size", mem);
+#endif
+}
+#else /* else use unix uptime */
+static void
+unix_uptime(dbref player __attribute__ ((__unused__)))
+{
+#ifdef HAS_UPTIME
+  FILE *fp;
+  char c;
+  int i;
+#endif
+#ifdef HAS_GETRUSAGE
+  struct rusage usage;
+#endif
+#ifndef WIN32
+  char tbuf1[BUFFER_LEN];
+#endif
+  Pid_t pid;
+  int psize;
+
+#ifdef HAS_UPTIME
+  fp =
+#ifdef __LCC__
+    (FILE *)
+#endif
+    popen(UPTIME_PATH, "r");
+
+  /* just in case the system is screwy */
+  if (fp == NULL) {
+    notify(player, T("Error -- cannot execute uptime."));
+    do_rawlog(LT_ERR, T("** ERROR ** popen for @uptime returned NULL."));
+    return;
+  }
+  /* print system uptime */
+  for (i = 0; (c = getc(fp)) != '\n'; i++)
+    tbuf1[i] = c;
+  tbuf1[i] = '\0';
+  pclose(fp);
+
+  notify(player, tbuf1);
+#endif                         /* HAS_UPTIME */
+
+  /* do process stats */
+  (void) getcwd(tbuf1, BUFFER_LEN);
+  tbuf1[BUFFER_LEN-1] = '\0';
+  pid = getpid();
+  psize = getpagesize();
+  notify_format(player,
+               T("\nWorking directory:  %s\nProcess ID:  %10u        %10d bytes per page"),
+               tbuf1, pid, psize);
+
+
+#ifdef HAS_GETRUSAGE
+  getrusage(RUSAGE_SELF, &usage);
+  notify_format(player, T("Time used:   %10ld user   %10ld sys"),
+               usage.ru_utime.tv_sec, usage.ru_stime.tv_sec);
+  notify_format(player, "Max res mem: %10ld pages  %10ld bytes",
+               usage.ru_maxrss, (usage.ru_maxrss * psize));
+  notify_format(player,
+               "Integral mem:%10ld shared %10ld private %10ld stack",
+               usage.ru_ixrss, usage.ru_idrss, usage.ru_isrss);
+  notify_format(player,
+               T
+               ("Page faults: %10ld hard   %10ld soft    %10ld swapouts"),
+               usage.ru_majflt, usage.ru_minflt, usage.ru_nswap);
+  notify_format(player, T("Disk I/O:    %10ld reads  %10ld writes"),
+               usage.ru_inblock, usage.ru_oublock);
+  notify_format(player, T("Network I/O: %10ld in     %10ld out"),
+               usage.ru_msgrcv, usage.ru_msgsnd);
+  notify_format(player, T("Context swi: %10ld vol    %10ld forced"),
+               usage.ru_nvcsw, usage.ru_nivcsw);
+  notify_format(player, "Signals:     %10ld", usage.ru_nsignals);
+#endif                         /* HAS_GETRUSAGE */
+
+}
+#endif
+
+/** Report on server uptime.
+ * \verbatim
+ * This command implements @uptime.
+ * \endverbatim
+ * \param player the enactor.
+ * \param mortal if 1, show mortal display, even if player is privileged.
+ */
+void
+do_uptime(dbref player, int mortal)
+{
+  char tbuf1[BUFFER_LEN];
+  struct tm *when;
+
+  when = localtime(&first_start_time);
+  strftime(tbuf1, sizeof tbuf1, T("     Up since %a %b %d %X %Z %Y"), when);
+  notify(player, tbuf1);
+
+  when = localtime(&start_time);
+  strftime(tbuf1, sizeof tbuf1, T("  Last reboot: %a %b %d %X %Z %Y"), when);
+  notify(player, tbuf1);
+
+  notify_format(player, T("Total reboots: %d"), reboot_count);
+
+  when = localtime(&mudtime);
+  strftime(tbuf1, sizeof tbuf1, T("     Time now: %a %b %d %X %Z %Y"), when);
+  notify(player, tbuf1);
+
+  if (last_dump_time > 0) {
+    when = localtime(&last_dump_time);
+    strftime(tbuf1, sizeof tbuf1,
+            T("   Time of last database save: %a %b %d %X %Z %Y"), when);
+    notify(player, tbuf1);
+  }
+
+  /* calculate times until various events */
+  when = localtime(&options.dump_counter);
+  strftime(tbuf1, sizeof tbuf1, "%X", when);
+  notify_format(player,
+               T
+               ("Time until next database save: %ld minutes %ld seconds, at %s"),
+               (options.dump_counter - mudtime) / 60,
+               (options.dump_counter - mudtime) % 60, tbuf1);
+
+  when = localtime(&options.dbck_counter);
+  strftime(tbuf1, sizeof tbuf1, "%X", when);
+  notify_format(player,
+               T
+               ("   Time until next dbck check: %ld minutes %ld seconds, at %s."),
+               (options.dbck_counter - mudtime) / 60,
+               (options.dbck_counter - mudtime) % 60, tbuf1);
+
+  when = localtime(&options.purge_counter);
+  strftime(tbuf1, sizeof tbuf1, "%X", when);
+  notify_format(player,
+               T
+               ("        Time until next purge: %ld minutes %ld seconds, at %s."),
+               (options.purge_counter - mudtime) / 60,
+               (options.purge_counter - mudtime) % 60, tbuf1);
+
+  if (options.warn_interval) {
+    when = localtime(&options.warn_counter);
+    strftime(tbuf1, sizeof tbuf1, "%X", when);
+    notify_format(player,
+                 T
+                 ("    Time until next @warnings: %ld minutes %ld seconds, at %s."),
+                 (options.warn_counter - mudtime) / 60,
+                 (options.warn_counter - mudtime) % 60, tbuf1);
+  }
+
+  /* Mortals, go no further! */
+  if (!Site(player) || mortal)
+    return;
+#if defined(linux)
+  linux_uptime(player);
+#elif defined(WIN32)
+  win32_uptime(player);
+#else
+  unix_uptime(player);
+#endif
+
+  if (God(player))
+    notify_activity(player, 0, 0);
+}
+
+
+/* Open a db file, which may be compressed, and return a file pointer */
+static FILE *
+db_open(const char *filename)
+{
+  FILE *f;
+#ifndef WIN32
+  if (options.uncompressprog && *options.uncompressprog) {
+    /* We do this because on some machines (SGI Irix, for example),
+     * the popen will not return NULL if the mailfile isn't there.
+     */
+    f = fopen(tprintf("%s%s", filename, options.compresssuff), "r");
+    if (f) {
+      fclose(f);
+      f =
+#ifdef __LCC__
+       (FILE *)
+#endif
+       popen(tprintf
+             ("%s < %s%s", options.uncompressprog, filename,
+              options.compresssuff), "r");
+      /* Force the pipe to be fully buffered */
+      if (f)
+       setvbuf(f, NULL, _IOFBF, BUFSIZ);
+    }
+  } else
+#endif                         /* WIN32 */
+  {
+    f = fopen(filename, FOPEN_READ);
+  }
+  return f;
+}
+
+/* Open a file or pipe (if compressing) for writing */
+static FILE *
+db_open_write(const char *filename)
+{
+  FILE *f;
+  char workdir[BUFFER_LEN];
+
+  /* Be safe in case our game directory was removed and restored,
+   * in which case our inode is screwy
+   */
+#ifdef WIN32
+  if (GetCurrentDirectory(BUFFER_LEN, workdir)) {
+    if (SetCurrentDirectory(workdir) < 0)
+#else
+  if (getcwd(workdir, BUFFER_LEN)) {
+    if (chdir(workdir) < 0)
+#endif
+      fprintf(stderr,
+             "chdir to %s failed in db_open_write, errno %d (%s)\n",
+             workdir, errno, strerror(errno));
+  } else {
+    /* If this fails, we probably can't write to a log, either, though */
+    fprintf(stderr,
+           "getcwd failed during db_open_write, errno %d (%s)\n",
+           errno, strerror(errno));
+  }
+#ifndef WIN32
+  if (options.compressprog && *options.compressprog) {
+    f =
+#ifdef __LCC__
+      (FILE *)
+#endif
+      popen(tprintf
+           ("%s >%s%s", options.compressprog, filename,
+            options.compresssuff), "w");
+    /* Force the pipe to be fully buffered */
+    if (f)
+      setvbuf(f, NULL, _IOFBF, BUFSIZ);
+  } else
+#endif                         /* WIN32 */
+  {
+    f = fopen(filename, FOPEN_WRITE);
+  }
+  if (!f)
+    longjmp(db_err, 1);
+  return f;
+}
+
+
+/* Close a db file, which may really be a pipe */
+static void
+db_close(FILE * f)
+{
+#ifndef WIN32
+  if (options.compressprog && *options.compressprog) {
+    pclose(f);
+  } else
+#endif                         /* WIN32 */
+  {
+    fclose(f);
+  }
+}
+
+/** List various goodies.
+ * \verbatim
+ * This function implements @list.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg what to list.
+ * \param lc if 1, list in lowercase.
+ */
+void
+do_list(dbref player, char *arg, int lc)
+{
+  if (!arg || !*arg)
+    notify(player, T("I don't understand what you want to @list."));
+  else if (string_prefix("commands", arg))
+    do_list_commands(player, lc);
+  else if (string_prefix("functions", arg))
+    do_list_functions(player, lc);
+  else if (string_prefix("motd", arg))
+    do_motd(player, 3, "");
+  else if (string_prefix("attribs", arg))
+    do_list_attribs(player, lc);
+  else if (string_prefix("flags", arg))
+    do_list_flags(player, "", lc);
+  else if (string_prefix("powers", arg))
+    do_list_powers(player, "");
+  else
+    notify(player, T("I don't understand what you want to @list."));
+}
+
+extern HASHTAB htab_function;
+extern HASHTAB htab_user_function;
+extern HASHTAB htab_math;
+extern HASHTAB htab_tag;
+extern HASHTAB htab_player_list;
+extern HASHTAB htab_reserved_aliases;
+extern HASHTAB help_files;
+extern HASHTAB htab_objdata;
+extern StrTree atr_names;
+extern StrTree lock_names;
+extern StrTree object_names;
+extern PTAB ptab_command;
+extern PTAB ptab_attrib;
+extern PTAB ptab_flag;
+
+/** Reports stats on various in-memory data structures.
+ * \param player the enactor.
+ */
+void
+do_list_memstats(dbref player)
+{
+  notify(player, "Hash Tables:");
+  hash_stats_header(player);
+  hash_stats(player, &htab_function, "Functions");
+  hash_stats(player, &htab_user_function, "@Functions");
+  hash_stats(player, &htab_math, "Math funs");
+  hash_stats(player, &htab_tag, "HTML tags");
+  hash_stats(player, &htab_player_list, "Players");
+  hash_stats(player, &htab_reserved_aliases, "Aliases");
+  hash_stats(player, &help_files, "HelpFiles");
+  hash_stats(player, &htab_objdata, "ObjData");
+  notify(player, "Prefix Trees:");
+  ptab_stats_header(player);
+  ptab_stats(player, &ptab_attrib, "AttrPerms");
+  ptab_stats(player, &ptab_command, "Commands");
+  ptab_stats(player, &ptab_flag, "Flags");
+  notify(player, "String Trees:");
+  st_stats_header(player);
+  st_stats(player, &atr_names, "AttrNames");
+  st_stats(player, &object_names, "ObjNames");
+  st_stats(player, &lock_names, "LockNames");
+#if (COMPRESSION_TYPE >= 3) && defined(COMP_STATS)
+  if (Site(player)) {
+    long items, used, total_comp, total_uncomp;
+    double percent;
+    compress_stats(&items, &used, &total_uncomp, &total_comp);
+    notify(player, "---------- Internal attribute compression  ----------");
+    notify_format(player,
+                 "%10ld compression table items used, "
+                 "taking %ld bytes.", items, used);
+    notify_format(player, "%10ld bytes in text before compression. ",
+                 total_uncomp);
+    notify_format(player, "%10ld bytes in text AFTER  compression. ",
+                 total_comp);
+    percent = ((float) (total_comp)) / ((float) total_uncomp) * 100.0;
+    notify_format(player,
+                 "%10.0f %% text    compression ratio (lower is better). ",
+                 percent);
+    percent =
+      ((float) (total_comp + used + (32768L * sizeof(char *)))) /
+      ((float) total_uncomp) * 100.0;
+    notify_format(player,
+                 "%10.0f %% OVERALL compression ratio (lower is better). ",
+                 percent);
+    notify_format(player,
+                 T
+                 ("          (Includes table items, and table of words pointers of %ld bytes)"),
+                 32768L * sizeof(char *));
+    if (percent >= 100.0)
+      notify(player,
+            "          " "(Compression ratio improves with larger database)");
+  }
+#endif
+
+  if (Site(player))
+    list_mem_check(player);
+}
+
+static char *
+make_new_epoch_file(const char *basename, int the_epoch)
+{
+  static char result[BUFFER_LEN];      /* STATIC! */
+  /* Unlink the last the_epoch and create a new one */
+  sprintf(result, "%s.#%d#", basename, the_epoch - 1);
+  unlink(result);
+  sprintf(result, "%s.#%d#", basename, the_epoch);
+  return result;
+}
+
+
+/* Given a list of dbrefs on which a command has matched but been
+ * denied by a lock, queue up the COMMAND`*FAILURE attributes, if
+ * any.
+ */
+static int
+fail_commands(dbref player)
+{
+  dbref *obj = errdblist;
+  int size = errdbtail - errdblist;
+  int matched = 0;
+  while (size--) {
+    matched += fail_lock(player, *obj, Command_Lock, NULL, NOTHING);
+    obj++;
+  }
+  errdbtail = errdblist;
+  return (matched > 0);
+}
+
+/* Increase the size of the errdblist - up to some maximum */
+static void
+errdb_grow(void)
+{
+  if (errdbsize >= 50)
+    return;                    /* That's it, no more, forget it */
+  errdbsize++;
+  errdblist = realloc(errdblist, errdbsize * sizeof(dbref));
+  errdbtail = errdblist + errdbsize - 1;
+}
diff --git a/src/gmalloc.c b/src/gmalloc.c
new file mode 100644 (file)
index 0000000..2854954
--- /dev/null
@@ -0,0 +1,1295 @@
+/* DO NOT EDIT THIS FILE -- it is automagically generated.  -*- C -*- */
+
+#define _MALLOC_INTERNAL
+
+/* The malloc headers and source files from the C library follow here.  */
+
+/* Declarations for `malloc' and friends.
+   Copyright 1990, 1991, 1992, 1993, 1995 Free Software Foundation, Inc.
+   Written May 1989 by Mike Haertel.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.
+
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation.  */
+
+#ifndef _MALLOC_H
+
+#define _MALLOC_H       1
+
+#ifdef _MALLOC_INTERNAL
+
+#ifdef  HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#ifndef memset
+#define memset(s, zero, n)      bzero ((s), (n))
+#endif
+#ifndef memcpy
+#define memcpy(d, s, n)         bcopy ((s), (d), (n))
+#endif
+
+
+#if     defined (__GNU_LIBRARY__) || (defined (__STDC__) && __STDC__)
+#include <limits.h>
+#else
+#ifndef CHAR_BIT
+#define CHAR_BIT        8
+#endif
+#endif
+
+#ifdef  HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#endif                         /* _MALLOC_INTERNAL.  */
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#if defined (__cplusplus) || (defined (__STDC__) && __STDC__)
+#undef  __P
+#define __P(args)       args
+#undef  __ptr_t
+#define __ptr_t         void *
+#else                          /* Not C++ or ANSI C.  */
+#undef  __P
+#define __P(args)       ()
+#undef  const
+#define const
+#undef  __ptr_t
+#define __ptr_t         char *
+#endif                         /* C++ or ANSI C.  */
+
+#if defined (__STDC__) && __STDC__
+#include <stddef.h>
+#define __malloc_size_t         size_t
+#define __malloc_ptrdiff_t      ptrdiff_t
+#else
+#define __malloc_size_t         unsigned int
+#define __malloc_ptrdiff_t      int
+#endif
+
+#ifndef NULL
+#define NULL    0
+#endif
+
+
+/* Allocate SIZE bytes of memory.  */
+  extern __ptr_t malloc __P((__malloc_size_t __size));
+/* Re-allocate the previously allocated block
+   in __ptr_t, making the new block SIZE bytes long.  */
+  extern __ptr_t realloc __P((__ptr_t __ptr, __malloc_size_t __size));
+/* Allocate NMEMB elements of SIZE bytes each, all initialized to 0.  */
+  extern __ptr_t calloc __P((__malloc_size_t __nmemb, __malloc_size_t __size));
+/* Free a block allocated by `malloc', `realloc' or `calloc'.  */
+  extern void free __P((__ptr_t __ptr));
+
+/* Allocate SIZE bytes allocated to ALIGNMENT bytes.  */
+  extern __ptr_t memalign __P((__malloc_size_t __alignment,
+                              __malloc_size_t __size));
+
+/* Allocate SIZE bytes on a page boundary.  */
+  extern __ptr_t valloc __P((__malloc_size_t __size));
+
+
+#ifdef _MALLOC_INTERNAL
+
+/* The allocator divides the heap into blocks of fixed size; large
+   requests receive one or more whole blocks, and small requests
+   receive a fragment of a block.  Fragment sizes are powers of two,
+   and all fragments of a block are the same size.  When all the
+   fragments in a block have been freed, the block itself is freed.  */
+#define INT_BIT         (CHAR_BIT * sizeof(int))
+#define BLOCKLOG        (INT_BIT > 16 ? 12 : 9)
+#define BLOCKSIZE       (1 << BLOCKLOG)
+#define BLOCKIFY(SIZE)  (((SIZE) + BLOCKSIZE - 1) / BLOCKSIZE)
+
+/* Determine the amount of memory spanned by the initial heap table
+   (not an absolute limit).  */
+#define HEAP            (INT_BIT > 16 ? 4194304 : 65536)
+
+/* Number of contiguous free blocks allowed to build up at the end of
+   memory before they will be returned to the system.  */
+#define FINAL_FREE_BLOCKS       8
+
+/* Data structure giving per-block information.  */
+  typedef union {
+    /* Heap information for a busy block.  */
+    struct {
+      /* Zero for a large (multiblock) object, or positive giving the
+         logarithm to the base two of the fragment size.  */
+      int type;
+      union {
+       struct {
+         __malloc_size_t nfree;        /* Free frags in a fragmented block.  */
+         __malloc_size_t first;        /* First free fragment of the block.  */
+       } frag;
+       /* For a large object, in its first block, this has the number
+          of blocks in the object.  In the other blocks, this has a
+          negative number which says how far back the first block is.  */
+       __malloc_ptrdiff_t size;
+      } info;
+    } busy;
+    /* Heap information for a free block
+       (that may be the first of a free cluster).  */
+    struct {
+      __malloc_size_t size;    /* Size (in blocks) of a free cluster.  */
+      __malloc_size_t next;    /* Index of next free cluster.  */
+      __malloc_size_t prev;    /* Index of previous free cluster.  */
+    } free;
+  } malloc_info;
+
+/* Pointer to first block of the heap.  */
+  extern char *_heapbase;
+
+/* Table indexed by block number giving per-block information.  */
+  extern malloc_info *_heapinfo;
+
+/* Address to block number and vice versa.  */
+#define BLOCK(A)        (((char *) (A) - _heapbase) / BLOCKSIZE + 1)
+#define ADDRESS(B)      ((__ptr_t) (((B) - 1) * BLOCKSIZE + _heapbase))
+
+/* Current search index for the heap table.  */
+  extern __malloc_size_t _heapindex;
+
+/* Limit of valid info table indices.  */
+  extern __malloc_size_t _heaplimit;
+
+/* Doubly linked lists of free fragments.  */
+  struct list {
+    struct list *next;
+    struct list *prev;
+  };
+
+/* Free list headers for each fragment size.  */
+  extern struct list _fraghead[];
+
+/* List of blocks allocated with `memalign' (or `valloc').  */
+  struct alignlist {
+    struct alignlist *next;
+    __ptr_t aligned;           /* The address that memaligned returned.  */
+    __ptr_t exact;             /* The address that malloc returned.  */
+  };
+  extern struct alignlist *_aligned_blocks;
+
+/* Instrumentation.  */
+  extern __malloc_size_t _chunks_used;
+  extern __malloc_size_t _bytes_used;
+  extern __malloc_size_t _chunks_free;
+  extern __malloc_size_t _bytes_free;
+
+/* Internal version of `free' used in `morecore' (malloc.c). */
+  extern void _free_internal __P((__ptr_t __ptr));
+
+#endif                         /* _MALLOC_INTERNAL.  */
+
+/* Given an address in the middle of a malloc'd object,
+   return the address of the beginning of the object.  */
+  extern __ptr_t malloc_find_object_address __P((__ptr_t __ptr));
+
+/* Underlying allocation function; successive calls should
+   return contiguous pieces of memory.  */
+  extern __ptr_t (*__morecore) __P((__malloc_ptrdiff_t __size));
+
+/* Default value of `__morecore'.  */
+  extern __ptr_t __default_morecore __P((__malloc_ptrdiff_t __size));
+
+/* If not NULL, this function is called after each time
+   `__morecore' is called to increase the data size.  */
+  extern void (*__after_morecore_hook) __P((void));
+
+/* Nonzero if `malloc' has been called and done its initialization.  */
+  extern int __malloc_initialized;
+
+/* Hooks for debugging versions.  */
+  extern void (*__malloc_initialize_hook) __P((void));
+  extern void (*__free_hook) __P((__ptr_t __ptr));
+  extern __ptr_t (*__malloc_hook) __P((__malloc_size_t __size));
+  extern __ptr_t (*__realloc_hook)
+   __P((__ptr_t __ptr, __malloc_size_t __size));
+  extern __ptr_t (*__memalign_hook)
+   __P((__malloc_size_t __size, __malloc_size_t __alignment));
+
+/* Return values for `mprobe': these are the kinds of inconsistencies that
+   `mcheck' enables detection of.  */
+  enum mcheck_status {
+    MCHECK_DISABLED = -1,      /* Consistency checking is not turned on.  */
+    MCHECK_OK,                 /* Block is fine.  */
+    MCHECK_FREE,               /* Block freed twice.  */
+    MCHECK_HEAD,               /* Memory before the block was clobbered.  */
+    MCHECK_TAIL                        /* Memory after the block was clobbered.  */
+  };
+
+/* Activate a standard collection of debugging hooks.  This must be called
+   before `malloc' is ever called.  ABORTFUNC is called with an error code
+   (see enum above) when an inconsistency is detected.  If ABORTFUNC is
+   null, the standard function prints on stderr and then calls `abort'.  */
+  extern int mcheck __P((void (*__abortfunc) __P((enum mcheck_status))));
+
+/* Check for aberrations in a particular malloc'd block.  You must have
+   called `mcheck' already.  These are the same checks that `mcheck' does
+   when you free or reallocate a block.  */
+  extern enum mcheck_status mprobe __P((__ptr_t __ptr));
+
+/* Activate a standard collection of tracing hooks.  */
+  extern void mtrace __P((void));
+  extern void muntrace __P((void));
+
+/* Statistics available to the user.  */
+  struct mstats {
+    __malloc_size_t bytes_total;       /* Total size of the heap. */
+    __malloc_size_t chunks_used;       /* Chunks allocated by the user. */
+    __malloc_size_t bytes_used;        /* Byte total of user-allocated chunks. */
+    __malloc_size_t chunks_free;       /* Chunks in the free list. */
+    __malloc_size_t bytes_free;        /* Byte total of chunks in the free list. */
+  };
+
+/* Pick up the current statistics. */
+  extern struct mstats mstats __P((void));
+
+/* Call WARNFUN with a warning message when memory usage is high.  */
+  extern void memory_warnings __P((__ptr_t __start,
+                                  void (*__warnfun) __P((const char *))));
+
+
+/* Relocating allocator.  */
+
+/* Allocate SIZE bytes, and store the address in *HANDLEPTR.  */
+  extern __ptr_t r_alloc __P((__ptr_t *__handleptr, __malloc_size_t __size));
+
+/* Free the storage allocated in HANDLEPTR.  */
+  extern void r_alloc_free __P((__ptr_t *__handleptr));
+
+/* Adjust the block at HANDLEPTR to be SIZE bytes long.  */
+  extern __ptr_t r_re_alloc __P((__ptr_t *__handleptr, __malloc_size_t __size));
+
+
+#ifdef  __cplusplus
+}
+#endif
+#endif /* malloc.h  */                 /* Allocate memory on a page boundary.
+                                  Copyright (C) 1991, 1992, 1993, 1994 Free Software Foundation, Inc.
+
+                                  This library is free software; you can redistribute it and/or
+                                  modify it under the terms of the GNU Library General Public License as
+                                  published by the Free Software Foundation; either version 2 of the
+                                  License, or (at your option) any later version.
+
+                                  This library is distributed in the hope that it will be useful,
+                                  but WITHOUT ANY WARRANTY; without even the implied warranty of
+                                  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+                                  Library General Public License for more details.
+
+                                  You should have received a copy of the GNU Library General Public
+                                  License along with this library; see the file COPYING.LIB.  If
+                                  not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+                                  Cambridge, MA 02139, USA.
+
+                                  The author may be reached (Email) at the address mike@ai.mit.edu,
+                                  or (US mail) as Mike Haertel c/o Free Software Foundation.  */
+#if defined (__GNU_LIBRARY__) || defined (_LIBC)
+#include <stddef.h>
+#include <sys/cdefs.h>
+extern size_t __getpagesize __P((void));
+#else
+#include "getpgsiz.h"
+#define  __getpagesize()        getpagesize()
+#endif
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+static __malloc_size_t pagesize;
+
+__ptr_t
+valloc(size)
+    __malloc_size_t size;
+{
+  if (pagesize == 0)
+    pagesize = __getpagesize();
+
+  return memalign(pagesize, size);
+}
+
+/* Memory allocator `malloc'.
+   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
+   Written May 1989 by Mike Haertel.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.
+
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+/* How to really get more memory.  */
+__ptr_t (*__morecore) __P((ptrdiff_t __size)) = __default_morecore;
+
+/* Debugging hook for `malloc'.  */
+__ptr_t (*__malloc_hook) __P((__malloc_size_t __size));
+
+/* Pointer to the base of the first block.  */
+char *_heapbase;
+
+/* Block information table.  Allocated with align/__free (not malloc/free).  */
+malloc_info *_heapinfo;
+
+/* Number of info entries.  */
+static
+__malloc_size_t heapsize;
+
+/* Search index in the info table.  */
+__malloc_size_t _heapindex;
+
+/* Limit of valid info table indices.  */
+__malloc_size_t _heaplimit;
+
+/* Free lists for each fragment size.  */
+struct list
+ _fraghead[BLOCKLOG];
+
+/* Instrumentation.  */
+__malloc_size_t _chunks_used;
+__malloc_size_t _bytes_used;
+__malloc_size_t _chunks_free;
+__malloc_size_t _bytes_free;
+
+/* Are you experienced?  */
+int
+ __malloc_initialized;
+
+void (*__malloc_initialize_hook)
+ __P((void));
+void (*__after_morecore_hook)
+ __P((void));
+
+/* Aligned allocation.  */
+static
+__ptr_t align __P((__malloc_size_t));
+static
+  __ptr_t
+align(size)
+    __malloc_size_t size;
+{
+  __ptr_t result;
+  unsigned long int adj;
+
+  result = (*__morecore) (size);
+  adj = (unsigned long int) ((unsigned long int) ((char *) result -
+                                                 (char *) NULL)) % BLOCKSIZE;
+  if (adj != 0) {
+    adj = BLOCKSIZE - adj;
+    (void) (*__morecore) (adj);
+    result = (char *) result + adj;
+  }
+  if (__after_morecore_hook)
+    (*__after_morecore_hook) ();
+
+  return result;
+}
+
+/* Set everything up and remember that we have.  */
+static int initialize __P((void));
+static int
+initialize()
+{
+  if (__malloc_initialize_hook)
+    (*__malloc_initialize_hook) ();
+
+  heapsize = HEAP / BLOCKSIZE;
+  _heapinfo = (malloc_info *) align(heapsize * sizeof(malloc_info));
+  if (_heapinfo == NULL)
+    return 0;
+  memset(_heapinfo, 0, heapsize * sizeof(malloc_info));
+  _heapinfo[0].free.size = 0;
+  _heapinfo[0].free.next = _heapinfo[0].free.prev = 0;
+  _heapindex = 0;
+  _heapbase = (char *) _heapinfo;
+
+  /* Account for the _heapinfo block itself in the statistics.  */
+  _bytes_used = heapsize * sizeof(malloc_info);
+  _chunks_used = 1;
+
+  __malloc_initialized = 1;
+  return 1;
+}
+
+/* Get neatly aligned memory, initializing or
+   growing the heap info table as necessary. */
+static __ptr_t morecore __P((__malloc_size_t));
+static __ptr_t
+morecore(size)
+    __malloc_size_t size;
+{
+  __ptr_t result;
+  malloc_info *newinfo, *oldinfo;
+  __malloc_size_t newsize;
+
+  result = align(size);
+  if (result == NULL)
+    return NULL;
+
+  /* Check if we need to grow the info table.  */
+  if ((__malloc_size_t) BLOCK((char *) result + size) > heapsize) {
+    newsize = heapsize;
+    while ((__malloc_size_t) BLOCK((char *) result + size) > newsize)
+      newsize *= 2;
+    newinfo = (malloc_info *) align(newsize * sizeof(malloc_info));
+    if (newinfo == NULL) {
+      (*__morecore) (-size);
+      return NULL;
+    }
+    memcpy(newinfo, _heapinfo, heapsize * sizeof(malloc_info));
+    memset(&newinfo[heapsize], 0, (newsize - heapsize) * sizeof(malloc_info));
+    oldinfo = _heapinfo;
+    newinfo[BLOCK(oldinfo)].busy.type = 0;
+    newinfo[BLOCK(oldinfo)].busy.info.size
+      = BLOCKIFY(heapsize * sizeof(malloc_info));
+    _heapinfo = newinfo;
+    /* Account for the _heapinfo block itself in the statistics.  */
+    _bytes_used += newsize * sizeof(malloc_info);
+    ++_chunks_used;
+    _free_internal(oldinfo);
+    heapsize = newsize;
+  }
+  _heaplimit = BLOCK((char *) result + size);
+  return result;
+}
+
+/* Allocate memory from the heap.  */
+__ptr_t
+malloc(size)
+    __malloc_size_t size;
+{
+  __ptr_t result;
+  __malloc_size_t block, blocks, lastblocks, start;
+  register __malloc_size_t i;
+  struct list *next;
+
+  /* ANSI C allows `malloc (0)' to either return NULL, or to return a
+     valid address you can realloc and free (though not dereference).
+
+     It turns out that some extant code (sunrpc, at least Ultrix's version)
+     expects `malloc (0)' to return non-NULL and breaks otherwise.
+     Be compatible.  */
+
+#if     0
+  if (size == 0)
+    return NULL;
+#endif
+
+  if (__malloc_hook != NULL)
+    return (*__malloc_hook) (size);
+
+  if (!__malloc_initialized)
+    if (!initialize())
+      return NULL;
+
+  if (size < sizeof(struct list))
+    size = sizeof(struct list);
+
+#ifdef SUNOS_LOCALTIME_BUG
+  if (size < 16)
+    size = 16;
+#endif
+
+  /* Determine the allocation policy based on the request size.  */
+  if (size <= BLOCKSIZE / 2) {
+    /* Small allocation to receive a fragment of a block.
+       Determine the logarithm to base two of the fragment size. */
+    register __malloc_size_t log = 1;
+    --size;
+    while ((size /= 2) != 0)
+      ++log;
+
+    /* Look in the fragment lists for a
+       free fragment of the desired size. */
+    next = _fraghead[log].next;
+    if (next != NULL) {
+      /* There are free fragments of this size.
+         Pop a fragment out of the fragment list and return it.
+         Update the block's nfree and first counters. */
+      result = (__ptr_t) next;
+      next->prev->next = next->next;
+      if (next->next != NULL)
+       next->next->prev = next->prev;
+      block = BLOCK(result);
+      if (--_heapinfo[block].busy.info.frag.nfree != 0)
+       _heapinfo[block].busy.info.frag.first = (unsigned long int)
+         ((unsigned long int) ((char *) next->next - (char *) NULL)
+          % BLOCKSIZE) >> log;
+
+      /* Update the statistics.  */
+      ++_chunks_used;
+      _bytes_used += 1 << log;
+      --_chunks_free;
+      _bytes_free -= 1 << log;
+    } else {
+      /* No free fragments of the desired size, so get a new block
+         and break it into fragments, returning the first.  */
+      result = malloc(BLOCKSIZE);
+      if (result == NULL)
+       return NULL;
+
+      /* Link all fragments but the first into the free list.  */
+      for (i = 1; i < (__malloc_size_t) (BLOCKSIZE >> log); ++i) {
+       next = (struct list *) ((char *) result + (i << log));
+       next->next = _fraghead[log].next;
+       next->prev = &_fraghead[log];
+       next->prev->next = next;
+       if (next->next != NULL)
+         next->next->prev = next;
+      }
+
+      /* Initialize the nfree and first counters for this block.  */
+      block = BLOCK(result);
+      _heapinfo[block].busy.type = log;
+      _heapinfo[block].busy.info.frag.nfree = i - 1;
+      _heapinfo[block].busy.info.frag.first = i - 1;
+
+      _chunks_free += (BLOCKSIZE >> log) - 1;
+      _bytes_free += BLOCKSIZE - (1 << log);
+      _bytes_used -= BLOCKSIZE - (1 << log);
+    }
+  } else {
+    /* Large allocation to receive one or more blocks.
+       Search the free list in a circle starting at the last place visited.
+       If we loop completely around without finding a large enough
+       space we will have to get more memory from the system.  */
+    blocks = BLOCKIFY(size);
+    start = block = _heapindex;
+    while (_heapinfo[block].free.size < blocks) {
+      block = _heapinfo[block].free.next;
+      if (block == start) {
+       /* Need to get more from the system.  Check to see if
+          the new core will be contiguous with the final free
+          block; if so we don't need to get as much.  */
+       block = _heapinfo[0].free.prev;
+       lastblocks = _heapinfo[block].free.size;
+       if (_heaplimit != 0 && block + lastblocks == _heaplimit &&
+           (*__morecore) (0) == ADDRESS(block + lastblocks) &&
+           (morecore((blocks - lastblocks) * BLOCKSIZE)) != NULL) {
+         /* Which block we are extending (the `final free
+            block' referred to above) might have changed, if
+            it got combined with a freed info table.  */
+         block = _heapinfo[0].free.prev;
+         _heapinfo[block].free.size += (blocks - lastblocks);
+         _bytes_free += (blocks - lastblocks) * BLOCKSIZE;
+         continue;
+       }
+       result = morecore(blocks * BLOCKSIZE);
+       if (result == NULL)
+         return NULL;
+       block = BLOCK(result);
+       _heapinfo[block].busy.type = 0;
+       _heapinfo[block].busy.info.size = blocks;
+       ++_chunks_used;
+       _bytes_used += blocks * BLOCKSIZE;
+       return result;
+      }
+    }
+
+    /* At this point we have found a suitable free list entry.
+       Figure out how to remove what we need from the list. */
+    result = ADDRESS(block);
+    if (_heapinfo[block].free.size > blocks) {
+      /* The block we found has a bit left over,
+         so relink the tail end back into the free list. */
+      _heapinfo[block + blocks].free.size = _heapinfo[block].free.size - blocks;
+      _heapinfo[block + blocks].free.next = _heapinfo[block].free.next;
+      _heapinfo[block + blocks].free.prev = _heapinfo[block].free.prev;
+      _heapinfo[_heapinfo[block].free.prev].free.next
+       = _heapinfo[_heapinfo[block].free.next].free.prev
+       = _heapindex = block + blocks;
+    } else {
+      /* The block exactly matches our requirements,
+         so just remove it from the list. */
+      _heapinfo[_heapinfo[block].free.next].free.prev
+       = _heapinfo[block].free.prev;
+      _heapinfo[_heapinfo[block].free.prev].free.next
+       = _heapindex = _heapinfo[block].free.next;
+      --_chunks_free;
+    }
+
+    _heapinfo[block].busy.type = 0;
+    _heapinfo[block].busy.info.size = blocks;
+    ++_chunks_used;
+    _bytes_used += blocks * BLOCKSIZE;
+    _bytes_free -= blocks * BLOCKSIZE;
+
+    /* Mark all the blocks of the object just allocated except for the
+       first with a negative number so you can find the first block by
+       adding that adjustment.  */
+    while (--blocks > 0)
+      _heapinfo[block + blocks].busy.info.size = -blocks;
+  }
+
+  return result;
+}
+\f
+#ifndef _LIBC
+
+/* On some ANSI C systems, some libc functions call _malloc, _free
+   and _realloc.  Make them use the GNU functions.  */
+
+__ptr_t _malloc _((__malloc_size_t size));
+
+__ptr_t
+_malloc(size)
+    __malloc_size_t size;
+{
+  return malloc(size);
+}
+
+void _free _((__ptr_t ptr));
+
+void
+_free(ptr)
+    __ptr_t ptr;
+{
+  free(ptr);
+}
+
+__ptr_t _realloc _((__ptr_t ptr, size_t size));
+
+__ptr_t
+_realloc(ptr, size)
+    __ptr_t ptr;
+    __malloc_size_t size;
+{
+  return realloc(ptr, size);
+}
+
+#endif
+/* Free a block of memory allocated by `malloc'.
+   Copyright 1990, 1991, 1992, 1994 Free Software Foundation, Inc.
+   Written May 1989 by Mike Haertel.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.
+
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+/* Debugging hook for free.  */
+void (*__free_hook) __P((__ptr_t __ptr));
+
+/* List of blocks allocated by memalign.  */
+struct alignlist *_aligned_blocks = NULL;
+
+/* Return memory to the heap.
+   Like `free' but don't call a __free_hook if there is one.  */
+void
+_free_internal(ptr)
+    __ptr_t ptr;
+{
+  int type;
+  __malloc_size_t block, blocks;
+  register __malloc_size_t i;
+  struct list *prev, *next;
+
+  block = BLOCK(ptr);
+
+  type = _heapinfo[block].busy.type;
+  switch (type) {
+  case 0:
+    /* Get as many statistics as early as we can.  */
+    --_chunks_used;
+    _bytes_used -= _heapinfo[block].busy.info.size * BLOCKSIZE;
+    _bytes_free += _heapinfo[block].busy.info.size * BLOCKSIZE;
+
+    /* Find the free cluster previous to this one in the free list.
+       Start searching at the last block referenced; this may benefit
+       programs with locality of allocation.  */
+    i = _heapindex;
+    if (i > block)
+      while (i > block)
+       i = _heapinfo[i].free.prev;
+    else {
+      do
+       i = _heapinfo[i].free.next;
+      while (i > 0 && i < block);
+      i = _heapinfo[i].free.prev;
+    }
+
+    /* Determine how to link this block into the free list.  */
+    if (block == i + _heapinfo[i].free.size) {
+      /* Coalesce this block with its predecessor.  */
+      _heapinfo[i].free.size += _heapinfo[block].busy.info.size;
+      block = i;
+    } else {
+      /* Really link this block back into the free list.  */
+      _heapinfo[block].free.size = _heapinfo[block].busy.info.size;
+      _heapinfo[block].free.next = _heapinfo[i].free.next;
+      _heapinfo[block].free.prev = i;
+      _heapinfo[i].free.next = block;
+      _heapinfo[_heapinfo[block].free.next].free.prev = block;
+      ++_chunks_free;
+    }
+
+    /* Now that the block is linked in, see if we can coalesce it
+       with its successor (by deleting its successor from the list
+       and adding in its size).  */
+    if (block + _heapinfo[block].free.size == _heapinfo[block].free.next) {
+      _heapinfo[block].free.size
+       += _heapinfo[_heapinfo[block].free.next].free.size;
+      _heapinfo[block].free.next
+       = _heapinfo[_heapinfo[block].free.next].free.next;
+      _heapinfo[_heapinfo[block].free.next].free.prev = block;
+      --_chunks_free;
+    }
+    /* Now see if we can return stuff to the system.  */
+    blocks = _heapinfo[block].free.size;
+    if (blocks >= FINAL_FREE_BLOCKS && block + blocks == _heaplimit
+       && (*__morecore) (0) == ADDRESS(block + blocks)) {
+      register __malloc_size_t bytes = blocks * BLOCKSIZE;
+      _heaplimit -= blocks;
+      (*__morecore) (-bytes);
+      _heapinfo[_heapinfo[block].free.prev].free.next
+       = _heapinfo[block].free.next;
+      _heapinfo[_heapinfo[block].free.next].free.prev
+       = _heapinfo[block].free.prev;
+      block = _heapinfo[block].free.prev;
+      --_chunks_free;
+      _bytes_free -= bytes;
+    }
+    /* Set the next search to begin at this block.  */
+    _heapindex = block;
+    break;
+
+  default:
+    /* Do some of the statistics.  */
+    --_chunks_used;
+    _bytes_used -= 1 << type;
+    ++_chunks_free;
+    _bytes_free += 1 << type;
+
+    /* Get the address of the first free fragment in this block.  */
+    prev = (struct list *) ((char *) ADDRESS(block) +
+                           (_heapinfo[block].busy.info.frag.first << type));
+
+    if (_heapinfo[block].busy.info.frag.nfree ==
+       (__malloc_size_t) (BLOCKSIZE >> type) - 1) {
+      /* If all fragments of this block are free, remove them
+         from the fragment list and free the whole block.  */
+      next = prev;
+      for (i = 1; i < (__malloc_size_t) (BLOCKSIZE >> type); ++i)
+       next = next->next;
+      prev->prev->next = next;
+      if (next != NULL)
+       next->prev = prev->prev;
+      _heapinfo[block].busy.type = 0;
+      _heapinfo[block].busy.info.size = 1;
+
+      /* Keep the statistics accurate.  */
+      ++_chunks_used;
+      _bytes_used += BLOCKSIZE;
+      _chunks_free -= BLOCKSIZE >> type;
+      _bytes_free -= BLOCKSIZE;
+
+      free(ADDRESS(block));
+    } else if (_heapinfo[block].busy.info.frag.nfree != 0) {
+      /* If some fragments of this block are free, link this
+         fragment into the fragment list after the first free
+         fragment of this block. */
+      next = (struct list *) ptr;
+      next->next = prev->next;
+      next->prev = prev;
+      prev->next = next;
+      if (next->next != NULL)
+       next->next->prev = next;
+      ++_heapinfo[block].busy.info.frag.nfree;
+    } else {
+      /* No fragments of this block are free, so link this
+         fragment into the fragment list and announce that
+         it is the first free fragment of this block. */
+      prev = (struct list *) ptr;
+      _heapinfo[block].busy.info.frag.nfree = 1;
+      _heapinfo[block].busy.info.frag.first = (unsigned long int)
+       ((unsigned long int) ((char *) ptr - (char *) NULL)
+        % BLOCKSIZE >> type);
+      prev->next = _fraghead[type].next;
+      prev->prev = &_fraghead[type];
+      prev->prev->next = prev;
+      if (prev->next != NULL)
+       prev->next->prev = prev;
+    }
+    break;
+  }
+}
+
+/* Return memory to the heap.  */
+void
+free(ptr)
+    __ptr_t ptr;
+{
+  register struct alignlist *l;
+
+  if (ptr == NULL)
+    return;
+
+  for (l = _aligned_blocks; l != NULL; l = l->next)
+    if (l->aligned == ptr) {
+      l->aligned = NULL;       /* Mark the slot in the list as free.  */
+      ptr = l->exact;
+      break;
+    }
+  if (__free_hook != NULL)
+    (*__free_hook) (ptr);
+  else
+    _free_internal(ptr);
+}
+
+/* Copyright (C) 1991, 1993, 1994 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+#ifdef _LIBC
+
+#include <ansidecl.h>
+#include <gnu-stabs.h>
+
+#undef  cfree
+
+function_alias(cfree, free, void, (ptr), DEFUN(cfree, (ptr), PTR ptr))
+#else
+
+void cfree _((__ptr_t ptr));
+
+void
+cfree(ptr)
+    __ptr_t ptr;
+{
+  free(ptr);
+}
+
+#endif
+/* Change the size of a block allocated by `malloc'.
+   Copyright 1990, 1991, 1992, 1993, 1994 Free Software Foundation, Inc.
+   Written May 1989 by Mike Haertel.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.
+
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+#if  (defined (MEMMOVE_MISSING) || \
+      !defined(_LIBC) && !defined(STDC_HEADERS) && !defined(USG))
+
+/* Snarfed directly from Emacs src/dispnew.c:
+   XXX Should use system bcopy if it handles overlap.  */
+#ifndef emacs
+
+/* Like bcopy except never gets confused by overlap.  */
+
+static void
+safe_bcopy(from, to, size)
+    char *from, *to;
+    int size;
+{
+  if (size <= 0 || from == to)
+    return;
+
+  /* If the source and destination don't overlap, then bcopy can
+     handle it.  If they do overlap, but the destination is lower in
+     memory than the source, we'll assume bcopy can handle that.  */
+  if (to < from || from + size <= to)
+    bcopy(from, to, size);
+
+  /* Otherwise, we'll copy from the end.  */
+  else {
+    register char *endf = from + size;
+    register char *endt = to + size;
+
+    /* If TO - FROM is large, then we should break the copy into
+       nonoverlapping chunks of TO - FROM bytes each.  However, if
+       TO - FROM is small, then the bcopy function call overhead
+       makes this not worth it.  The crossover point could be about
+       anywhere.  Since I don't think the obvious copy loop is too
+       bad, I'm trying to err in its favor.  */
+    if (to - from < 64) {
+      do
+       *--endt = *--endf;
+      while (endf != from);
+    } else {
+      for (;;) {
+       endt -= (to - from);
+       endf -= (to - from);
+
+       if (endt < to)
+         break;
+
+       bcopy(endf, endt, to - from);
+      }
+
+      /* If SIZE wasn't a multiple of TO - FROM, there will be a
+         little left over.  The amount left over is
+         (endt + (to - from)) - to, which is endt - from.  */
+      bcopy(from, to, endt - from);
+    }
+  }
+}
+#endif                         /* Not emacs.  */
+
+#define memmove(to, from, size) safe_bcopy ((from), (to), (size))
+
+#endif
+
+
+#define min(A, B) ((A) < (B) ? (A) : (B))
+
+/* Debugging hook for realloc.  */
+__ptr_t (*__realloc_hook) __P((__ptr_t __ptr, __malloc_size_t __size));
+
+/* Resize the given region to the new size, returning a pointer
+   to the (possibly moved) region.  This is optimized for speed;
+   some benchmarks seem to indicate that greater compactness is
+   achieved by unconditionally allocating and copying to a
+   new region.  This module has incestuous knowledge of the
+   internals of both free and malloc. */
+__ptr_t
+realloc(ptr, size)
+    __ptr_t ptr;
+    __malloc_size_t size;
+{
+  __ptr_t result;
+  int type;
+  __malloc_size_t block, blocks, oldlimit;
+
+  if (size == 0) {
+    free(ptr);
+    return malloc(0);
+  } else if (ptr == NULL)
+    return malloc(size);
+
+  if (__realloc_hook != NULL)
+    return (*__realloc_hook) (ptr, size);
+
+  block = BLOCK(ptr);
+
+  type = _heapinfo[block].busy.type;
+  switch (type) {
+  case 0:
+    /* Maybe reallocate a large block to a small fragment.  */
+    if (size <= BLOCKSIZE / 2) {
+      result = malloc(size);
+      if (result != NULL) {
+       memcpy(result, ptr, size);
+       _free_internal(ptr);
+       return result;
+      }
+    }
+    /* The new size is a large allocation as well;
+       see if we can hold it in place. */
+    blocks = BLOCKIFY(size);
+    if ((__malloc_ptrdiff_t) blocks < _heapinfo[block].busy.info.size) {
+      /* The new size is smaller; return
+         excess memory to the free list. */
+      _heapinfo[block + blocks].busy.type = 0;
+      _heapinfo[block + blocks].busy.info.size
+       = _heapinfo[block].busy.info.size - blocks;
+      _heapinfo[block].busy.info.size = blocks;
+      /* We have just created a new chunk by splitting a chunk in two.
+         Now we will free this chunk; increment the statistics counter
+         so it doesn't become wrong when _free_internal decrements it.  */
+      ++_chunks_used;
+      _free_internal(ADDRESS(block + blocks));
+      result = ptr;
+    } else if ((__malloc_ptrdiff_t) blocks == _heapinfo[block].busy.info.size)
+      /* No size change necessary.  */
+      result = ptr;
+    else {
+      /* Won't fit, so allocate a new region that will.
+         Free the old region first in case there is sufficient
+         adjacent free space to grow without moving. */
+      blocks = _heapinfo[block].busy.info.size;
+      /* Prevent free from actually returning memory to the system.  */
+      oldlimit = _heaplimit;
+      _heaplimit = 0;
+      _free_internal(ptr);
+      _heaplimit = oldlimit;
+      result = malloc(size);
+      if (result == NULL) {
+       /* Now we're really in trouble.  We have to unfree
+          the thing we just freed.  Unfortunately it might
+          have been coalesced with its neighbors.  */
+       if (_heapindex == block)
+         (void) malloc(blocks * BLOCKSIZE);
+       else {
+         __ptr_t previous = malloc((block - _heapindex) * BLOCKSIZE);
+         (void) malloc(blocks * BLOCKSIZE);
+         _free_internal(previous);
+       }
+       return NULL;
+      }
+      if (ptr != result)
+       memmove(result, ptr, blocks * BLOCKSIZE);
+    }
+    break;
+
+  default:
+    /* Old size is a fragment; type is logarithm
+       to base two of the fragment size.  */
+    if (size > (__malloc_size_t) (1 << (type - 1)) &&
+       size <= (__malloc_size_t) (1 << type))
+      /* The new size is the same kind of fragment.  */
+      result = ptr;
+    else {
+      /* The new size is different; allocate a new space,
+         and copy the lesser of the new size and the old. */
+      result = malloc(size);
+      if (result == NULL)
+       return NULL;
+      memcpy(result, ptr, min(size, (__malloc_size_t) 1 << type));
+      free(ptr);
+    }
+    break;
+  }
+
+  return result;
+}
+
+/* Copyright (C) 1991, 1992, 1994 Free Software Foundation, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.
+
+   The author may be reached (Email) at the address mike@ai.mit.edu,
+   or (US mail) as Mike Haertel c/o Free Software Foundation.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+/* Allocate an array of NMEMB elements each SIZE bytes long.
+   The entire array is initialized to zeros.  */
+__ptr_t
+calloc(nmemb, size)
+    register __malloc_size_t nmemb;
+    register __malloc_size_t size;
+{
+  register __ptr_t result = malloc(nmemb * size);
+
+  if (result != NULL)
+    (void) memset(result, 0, nmemb * size);
+
+  return result;
+}
+
+/* Copyright (C) 1991, 1992, 1993, 1994 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with the GNU C Library; see the file COPYING.  If not, write to
+   the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+#ifndef __GNU_LIBRARY__
+#define __sbrk  sbrk
+#endif
+
+#ifdef __GNU_LIBRARY__
+/* It is best not to declare this and cast its result on foreign operating
+   systems with potentially hostile include files.  */
+extern __ptr_t __sbrk __P((int increment));
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+/* Allocate INCREMENT more bytes of data space,
+   and return the start of data space, or NULL on errors.
+   If INCREMENT is negative, shrink data space.  */
+__ptr_t
+__default_morecore(increment)
+#ifdef __STDC__
+    ptrdiff_t increment;
+#else
+    int
+     increment;
+#endif
+{
+  __ptr_t result = (__ptr_t) __sbrk((int) increment);
+  if (result == (__ptr_t) -1)
+    return NULL;
+  return result;
+}
+
+/* Copyright (C) 1991, 1992, 1993, 1994, 1995 Free Software Foundation, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with this library; see the file COPYING.LIB.  If
+   not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+   Cambridge, MA 02139, USA.  */
+
+#ifndef _MALLOC_INTERNAL
+#define _MALLOC_INTERNAL
+#include <malloc.h>
+#endif
+
+__ptr_t (*__memalign_hook) __P((size_t __size, size_t __alignment));
+
+__ptr_t
+memalign(alignment, size)
+    __malloc_size_t alignment;
+    __malloc_size_t size;
+{
+  __ptr_t result;
+  unsigned long int adj;
+
+  if (__memalign_hook)
+    return (*__memalign_hook) (alignment, size);
+
+  size = ((size + alignment - 1) / alignment) * alignment;
+
+  result = malloc(size);
+  if (result == NULL)
+    return NULL;
+  adj = (unsigned long int) ((unsigned long int) ((char *) result -
+                                                 (char *) NULL)) % alignment;
+  if (adj != 0) {
+    struct alignlist *l;
+    for (l = _aligned_blocks; l != NULL; l = l->next)
+      if (l->aligned == NULL)
+       /* This slot is free.  Use it.  */
+       break;
+    if (l == NULL) {
+      l = (struct alignlist *) malloc(sizeof(struct alignlist));
+      if (l == NULL) {
+       free(result);
+       return NULL;
+      }
+      l->next = _aligned_blocks;
+      _aligned_blocks = l;
+    }
+    l->exact = result;
+    result = l->aligned = (char *) result + alignment - adj;
+  }
+  return result;
+}
diff --git a/src/help.c b/src/help.c
new file mode 100644 (file)
index 0000000..200dd75
--- /dev/null
@@ -0,0 +1,545 @@
+/**
+ * \file help.c
+ *
+ * \brief The PennMUSH help system.
+ *
+ *
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdio.h>
+#include "conf.h"
+#include "externs.h"
+#include "boolexp.h"
+#include "command.h"
+#include "htab.h"
+#include "help.h"
+#include "log.h"
+#include "ansi.h"
+#include "parse.h"
+#include "pueblo.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+HASHTAB help_files;  /**< Help filenames hash table */
+
+static int help_init = 0;
+
+static void do_new_spitfile(dbref player, char *arg1, help_file *help_dat);
+static const char *string_spitfile(help_file *help_dat, char *arg1);
+static help_indx *help_find_entry(help_file *help_dat, const char *the_topic);
+static char *list_matching_entries(const char *pattern, help_file *help_dat);
+
+static void help_build_index(help_file *h, int restricted);
+
+/** Linked list of help topic names. */
+typedef struct TLIST {
+  char topic[TOPIC_NAME_LEN + 1];      /**< Name of topic */
+  struct TLIST *next;                  /**< Pointer to next list entry */
+} tlist;
+
+tlist *top = NULL;   /**< Pointer to top of linked list of topic names */
+
+help_indx *topics = NULL;  /**< Pointer to linked list of topic indexes */
+unsigned num_topics = 0;   /**< Number of topics loaded */
+unsigned top_topics = 0;   /**< Maximum number of topics loaded */
+
+static void write_topic(long int p);
+
+#define TRUE 1  /**< A true value */
+#define FALSE 0         /**< A false value */
+
+COMMAND (cmd_helpcmd) {
+  help_file *h;
+
+  h = hashfind(cmd->name, &help_files);
+
+  if (!h) {
+    notify(player, T("That command is unavailable."));
+    return;
+  }
+
+  if (h->admin && !Admin(player)) {
+    notify(player, T("You don't look like an admin to me."));
+    return;
+  }
+
+  if (wildcard(arg_left))
+    notify_format(player, T("Here are the entries which match '%s':\n%s"),
+                 arg_left, list_matching_entries(arg_left, h));
+  else
+    do_new_spitfile(player, arg_left, h);
+}
+
+/** Initialize the helpfile hashtable, which contains the names of the
+ * help files.
+ */
+void
+init_help_files(void)
+{
+  hash_init(&help_files, 8, sizeof(help_file));
+  help_init = 1;
+}
+
+/** Add new help command. This function is
+ * the basis for the help_command directive in mush.cnf. It creates
+ * a new help entry for the hash table, builds a help index,
+ * and adds the new command to the command table.
+ * \param command_name name of help command to add.
+ * \param filename name of the help file to use for this command.
+ * \param admin if 1, this command reads admin topics, rather than standard.
+ */
+void
+add_help_file(const char *command_name, const char *filename, int admin)
+{
+  help_file *h;
+  char newfilename[256] = "\0";
+
+  /* Must use a buffer for MacOS file path conversion */
+  strncpy(newfilename, filename, 256);
+
+  if (help_init == 0)
+    init_help_files();
+
+  if (!command_name || !filename || !*command_name || !*newfilename)
+    return;
+
+  /* If there's already an entry for it, complain */
+  h = hashfind(strupper(command_name), &help_files);
+  if (h) {
+    do_rawlog(LT_ERR, T("Duplicate help_command %s ignored."), command_name);
+    return;
+  }
+
+  h = mush_malloc(sizeof *h, "help_file.entry");
+  h->command = mush_strdup(strupper(command_name), "help_file.command");
+  h->file = mush_strdup(newfilename, "help_file.filename");
+  h->entries = 0;
+  h->indx = NULL;
+  h->admin = admin;
+  help_build_index(h, h->admin);
+  if (!h->indx) {
+    mush_free(h->command, "help_file.command");
+    mush_free(h->file, "help_file.filename");
+    mush_free(h, "help_file.entry");
+    return;
+  }
+  (void) command_add(h->command, CMD_T_ANY | CMD_T_NOPARSE, NULL, cmd_helpcmd, NULL);
+  hashadd(h->command, h, &help_files);
+}
+
+/** Rebuild a help file index.
+ * \verbatim
+ * This command implements @readcache.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+help_reindex(dbref player)
+{
+  help_file *curr;
+
+  for (curr = (help_file *) hash_firstentry(&help_files);
+       curr; curr = (help_file *) hash_nextentry(&help_files)) {
+    if (curr->indx) {
+      mush_free((Malloc_t) curr->indx, "help_index");
+      curr->entries = 0;
+    }
+    help_build_index(curr, curr->admin);
+  }
+  if (player != NOTHING) {
+    notify(player, T("Help files reindexed."));
+    do_rawlog(LT_WIZ, T("Help files reindexed by %s(#%d)"), Name(player),
+             player);
+  } else
+    do_rawlog(LT_WIZ, T("Help files reindexed."));
+}
+
+static void
+do_new_spitfile(dbref player, char *arg1, help_file *help_dat)
+{
+  help_indx *entry = NULL;
+  FILE *fp;
+  char *p, line[LINE_SIZE + 1];
+  char the_topic[LINE_SIZE + 2];
+  int default_topic = 0;
+  size_t n;
+
+  if (*arg1 == '\0') {
+    default_topic = 1;
+    arg1 = (char *) help_dat->command;
+  } else if (*arg1 == '&') {
+    notify(player, T("Help topics don't start with '&'."));
+    return;
+  }
+  if (strlen(arg1) > LINE_SIZE)
+    *(arg1 + LINE_SIZE) = '\0';
+
+  if(help_dat->admin) {
+    sprintf(the_topic, "&%s", arg1);
+  } else
+    strcpy(the_topic, arg1);
+
+  if (!help_dat->indx || help_dat->entries == 0) {
+    notify(player, T("Sorry, that command is temporarily unvailable."));
+    do_rawlog(LT_ERR, T("No index for %s."), help_dat->command);
+    return;
+  }
+
+  entry = help_find_entry(help_dat, the_topic);
+  if (!entry && default_topic)
+    entry = help_find_entry(help_dat, (help_dat->admin ? "&help" : "help"));
+
+  if (!entry) {
+    notify_format(player, T("No entry for '%s'."), arg1);
+    return;
+  }
+
+  if ((fp = fopen(help_dat->file, FOPEN_READ)) == NULL) {
+    notify(player, T("Sorry, that function is temporarily unavailable."));
+    do_log(LT_ERR, 0, 0, T("Can't open text file %s for reading"),
+          help_dat->file);
+    return;
+  }
+  if (fseek(fp, entry->pos, 0) < 0L) {
+    notify(player, T("Sorry, that function is temporarily unavailable."));
+    do_rawlog(LT_ERR, T("Seek error in file %s"), help_dat->file);
+    return;
+  }
+  strcpy(the_topic, strupper(entry->topic + (*entry->topic == '&')));
+  /* ANSI topics */
+  if (ShowAnsi(player)) {
+    char ansi_topic[LINE_SIZE + 10];
+    sprintf(ansi_topic, "%s%s%s", ANSI_HILITE, the_topic, ANSI_NORMAL);
+    notify(player, ansi_topic);
+  } else
+    notify(player, the_topic);
+
+  if (SUPPORT_PUEBLO)
+    notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
+  for (n = 0; n < BUFFER_LEN; n++) {
+    if (fgets(line, LINE_SIZE, fp) == NULL)
+      break;
+    if (line[0] == '&')
+      break;
+    if (line[0] == '\n') {
+      notify(player, " ");
+    } else {
+      for (p = line; *p != '\0'; p++)
+       if (*p == '\n')
+         *p = '\0';
+      notify(player, line);
+    }
+  }
+  if (SUPPORT_PUEBLO)
+    notify_format(player, "%c/SAMP%c", TAG_START, TAG_END);
+  fclose(fp);
+  if (n >= BUFFER_LEN)
+    notify_format(player, T("%s output truncated."), help_dat->command);
+}
+
+
+static help_indx *
+help_find_entry(help_file *help_dat, const char *the_topic)
+{
+  size_t n;
+  help_indx *entry = NULL;
+
+  if (help_dat->entries < 10) {        /* Just do a linear search for small files */
+    for (n = 0; n < help_dat->entries; n++) {
+      if (string_prefix(help_dat->indx[n].topic, the_topic)) {
+       entry = &help_dat->indx[n];
+       break;
+      }
+    }
+  } else {                     /* Binary search of the index */
+    int left = 0;
+    int cmp;
+    int right = help_dat->entries - 1;
+
+    while (1) {
+      n = (left + right) / 2;
+
+      if (left > right)
+       break;
+
+      cmp = strcasecmp(the_topic, help_dat->indx[n].topic);
+
+      if (cmp == 0) {
+       entry = &help_dat->indx[n];
+       break;
+      } else if (cmp < 0) {
+       /* We need to catch the first prefix */
+       if (string_prefix(help_dat->indx[n].topic, the_topic)) {
+         int m;
+         for (m = n - 1; m >= 0; m--) {
+           if (!string_prefix(help_dat->indx[m].topic, the_topic))
+             break;
+         }
+         entry = &help_dat->indx[m + 1];
+         break;
+       }
+       if (left == right)
+         break;
+       right = n - 1;
+      } else {                 /* cmp > 0 */
+       if (left == right)
+         break;
+       left = n + 1;
+      }
+    }
+  }
+  return entry;
+}
+
+static void
+write_topic(long int p)
+{
+  tlist *cur, *nextptr;
+  help_indx *temp;
+  for (cur = top; cur; cur = nextptr) {
+    nextptr = cur->next;
+    if (num_topics >= top_topics) {
+      top_topics += top_topics / 2 + 20;
+      if (topics)
+       topics = (help_indx *) realloc(topics, top_topics * sizeof(help_indx));
+      else
+       topics = (help_indx *) malloc(top_topics * sizeof(help_indx));
+      if (!topics) {
+       mush_panic(T("Out of memory"));
+      }
+    }
+    temp = &topics[num_topics++];
+    temp->pos = p;
+    strcpy(temp->topic, cur->topic);
+    free(cur);
+  }
+  top = NULL;
+}
+
+static int WIN32_CDECL topic_cmp(const void *s1, const void *s2);
+static int WIN32_CDECL
+topic_cmp(const void *s1, const void *s2)
+{
+  const help_indx *a = s1;
+  const help_indx *b = s2;
+
+  return strcasecmp(a->topic, b->topic);
+
+}
+
+static void
+help_build_index(help_file *h, int restricted)
+{
+  long bigpos, pos = 0;
+  int in_topic;
+  int i, n, lineno, ntopics;
+  char *s, *topic;
+  char the_topic[TOPIC_NAME_LEN + 1];
+  char line[LINE_SIZE + 1];
+  FILE *rfp;
+  tlist *cur;
+
+  /* Quietly ignore null values for the file */
+  if (!h || !h->file)
+    return;
+  if ((rfp = fopen(h->file, FOPEN_READ)) == NULL) {
+    do_rawlog(LT_ERR, T("Can't open %s for reading"), h->file);
+    return;
+  }
+
+  if (restricted)
+    do_rawlog(LT_WIZ, T("Indexing file %s (admin topics)"), h->file);
+  else
+    do_rawlog(LT_WIZ, T("Indexing file %s"), h->file);
+  topics = NULL;
+  num_topics = 0;
+  top_topics = 0;
+  bigpos = 0L;
+  lineno = 0;
+  ntopics = 0;
+
+  in_topic = 0;
+
+  while (fgets(line, LINE_SIZE, rfp) != NULL) {
+    ++lineno;
+    if (ntopics == 0) {
+      /* Looking for the first topic, but we'll ignore blank lines */
+      if (!line[0]) {
+       /* Someone's feeding us /dev/null? */
+       do_rawlog(LT_ERR, T("Malformed help file %s doesn't start with &"),
+                 h->file);
+       fclose(rfp);
+       return;
+      }
+      if (isspace((unsigned char) line[0]))
+       continue;
+      if (line[0] != '&') {
+       do_rawlog(LT_ERR, T("Malformed help file %s doesn't start with &"),
+                 h->file);
+       fclose(rfp);
+       return;
+      }
+    }
+    n = strlen(line);
+    if (line[n - 1] != '\n') {
+      do_rawlog(LT_ERR, T("Line %d of %s: line too long"), lineno, h->file);
+    }
+    if (line[0] == '&') {
+      ++ntopics;
+      if (!in_topic) {
+       /* Finish up last entry */
+       if (ntopics > 1) {
+         write_topic(pos);
+       }
+       in_topic = TRUE;
+      }
+      /* parse out the topic */
+      /* Get the beginning of the topic string */
+      for (topic = &line[1];
+          (*topic == ' ' || *topic == '\t') && *topic != '\0'; topic++) ;
+
+      /* Get the topic */
+      strcpy(the_topic, "");
+      for (i = -1, s = topic; *s != '\n' && *s != '\0'; s++) {
+       if (i >= TOPIC_NAME_LEN - 1)
+         break;
+       if (*s != ' ' || the_topic[i] != ' ')
+         the_topic[++i] = *s;
+      }
+      if ((restricted && the_topic[0] == '&')
+         || (!restricted && the_topic[0] != '&')) {
+       the_topic[++i] = '\0';
+       cur = (tlist *) malloc(sizeof(tlist));
+       strcpy(cur->topic, the_topic);
+       cur->next = top;
+       top = cur;
+      }
+    } else {
+      if (in_topic) {
+       pos = bigpos;
+      }
+      in_topic = FALSE;
+    }
+    bigpos = ftell(rfp);
+  }
+
+  /* Handle last topic */
+  write_topic(pos);
+  qsort(topics, num_topics, sizeof(help_indx), topic_cmp);
+  h->entries = num_topics;
+  h->indx = topics;
+  add_check("help_index");
+  fclose(rfp);
+  do_rawlog(LT_WIZ, T("%d topics indexed."), num_topics);
+  return;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_textfile)
+{
+  help_file *h;
+
+  h = hashfind(strupper(args[0]), &help_files);
+  if (!h) {
+    safe_str(T("#-1 NO SUCH FILE"), buff, bp);
+    return;
+  }
+  if (h->admin && !Admin(executor)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (wildcard(args[1]))
+    safe_str(list_matching_entries(args[1], h), buff, bp);
+  else
+    safe_str(string_spitfile(h, args[1]), buff, bp);
+}
+
+
+static const char *
+string_spitfile(help_file *help_dat, char *arg1)
+{
+  help_indx *entry = NULL;
+  FILE *fp;
+  char line[LINE_SIZE + 1];
+  char the_topic[LINE_SIZE + 2];
+  size_t n;
+  static char buff[BUFFER_LEN];
+  char *bp;
+
+  if (*arg1 == '\0')
+    arg1 = (char *) "help";
+  else if (*arg1 == '&')
+    return T("#-1 INVALID ENTRY");
+  if (strlen(arg1) > LINE_SIZE)
+    *(arg1 + LINE_SIZE) = '\0';
+
+  if (help_dat->admin)
+    sprintf(the_topic, "&%s", arg1);
+  else
+    strcpy(the_topic, arg1);
+
+  if (!help_dat->indx || help_dat->entries == 0)
+    return T("#-1 NO INDEX FOR FILE");
+
+  entry = help_find_entry(help_dat, the_topic);
+
+  if (!entry) {
+    return T("#-1 NO ENTRY");
+  }
+
+  if ((fp = fopen(help_dat->file, FOPEN_READ)) == NULL) {
+    return T("#-1 UNAVAILABLE");
+  }
+  if (fseek(fp, entry->pos, 0) < 0L) {
+    return T("#-1 UNAVAILABLE");
+  }
+  bp = buff;
+  for (n = 0; n < BUFFER_LEN; n++) {
+    if (fgets(line, LINE_SIZE, fp) == NULL)
+      break;
+    if (line[0] == '&')
+      break;
+    safe_str(line, buff, &bp);
+  }
+  *bp = '\0';
+  fclose(fp);
+  return buff;
+}
+
+/** Return a string with all help entries that match a pattern */
+static char *
+list_matching_entries(const char *pattern, help_file *help_dat)
+{
+  static char buff[BUFFER_LEN];
+  int offset;
+  char *bp;
+  size_t n;
+
+  bp = buff;
+
+  if (help_dat->admin)
+    offset = 1;                        /* To skip the leading & */
+  else
+    offset = 0;
+
+  for (n = 0; n < help_dat->entries; n++)
+    if (quick_wild(pattern, help_dat->indx[n].topic + offset)) {
+      safe_str(help_dat->indx[n].topic + offset, buff, &bp);
+      safe_strl(", ", 2, buff, &bp);
+    }
+
+  if (bp > buff)
+    *(bp - 2) = '\0';
+  else {
+    safe_str(T("No matching help topics."), buff, &bp);
+    *bp = '\0';
+  }
+
+  return buff;
+}
diff --git a/src/htab.c b/src/htab.c
new file mode 100644 (file)
index 0000000..28fa100
--- /dev/null
@@ -0,0 +1,578 @@
+/**
+ * \file htab.c
+ *
+ * \brief Hashtable routines.
+ * This code is largely ripped out of TinyMUSH 2.2.5, with tweaks
+ * to make it Penn-compatible by Trivian.
+ *
+ *
+ */
+
+#include "config.h"
+#include "copyrite.h"
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+
+#include "htab.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+HASHENT *hash_new(HASHTAB *htab, const char *key);
+static int hash_val(register const char *k, int mask);
+
+/* ---------------------------------------------------------------------------
+ * hash_val: Compute hash value of a string for a hash table.
+ */
+/*#define NEW_HASH_FUN /**/
+#ifdef NEW_HASH_FUN
+
+/* This hash function adapted from http://burtleburtle.net/bob/hash/evahash.html */
+
+typedef unsigned long int u4;  /**< unsigned 4-byte type */
+typedef unsigned char u1;      /**< unsigned 1-byte type */
+
+/* The mixing step */
+#define mix(a,b,c) \
+{ \
+  a=a-b;  a=a-c;  a=a^(c>>13); \
+  b=b-c;  b=b-a;  b=b^(a<<8);  \
+  c=c-a;  c=c-b;  c=c^(b>>13); \
+  a=a-b;  a=a-c;  a=a^(c>>12); \
+  b=b-c;  b=b-a;  b=b^(a<<16); \
+  c=c-a;  c=c-b;  c=c^(b>>5);  \
+  a=a-b;  a=a-c;  a=a^(c>>3);  \
+  b=b-c;  b=b-a;  b=b^(a<<10); \
+  c=c-a;  c=c-b;  c=c^(b>>15); \
+}
+
+/* The whole new hash function */
+static int
+hash_val(register const char *k, int mask)
+{
+  register u4 a, b, c;         /* the internal state */
+  u4 len, length;              /* how many key bytes still need mixing */
+  static u4 initval = 5432;    /* the previous hash, or an arbitrary value */
+
+  /* Set up the internal state */
+  length = len = strlen(k);
+  a = b = 0x9e3779b9;          /* the golden ratio; an arbitrary value */
+  c = initval;                 /* variable initialization of internal state */
+
+   /*---------------------------------------- handle most of the key */
+  while (len >= 12) {
+    a = a + (k[0] + ((u4) k[1] << 8) + ((u4) k[2] << 16) + ((u4) k[3] << 24));
+    b = b + (k[4] + ((u4) k[5] << 8) + ((u4) k[6] << 16) + ((u4) k[7] << 24));
+    c = c + (k[8] + ((u4) k[9] << 8) + ((u4) k[10] << 16) + ((u4) k[11] << 24));
+    mix(a, b, c);
+    k = k + 12;
+    len = len - 12;
+  }
+
+   /*------------------------------------- handle the last 11 bytes */
+  c = c + length;
+  switch (len) {               /* all the case statements fall through */
+  case 11:
+    c = c + ((u4) k[10] << 24);
+  case 10:
+    c = c + ((u4) k[9] << 16);
+  case 9:
+    c = c + ((u4) k[8] << 8);
+    /* the first byte of c is reserved for the length */
+  case 8:
+    b = b + ((u4) k[7] << 24);
+  case 7:
+    b = b + ((u4) k[6] << 16);
+  case 6:
+    b = b + ((u4) k[5] << 8);
+  case 5:
+    b = b + k[4];
+  case 4:
+    a = a + ((u4) k[3] << 24);
+  case 3:
+    a = a + ((u4) k[2] << 16);
+  case 2:
+    a = a + ((u4) k[1] << 8);
+  case 1:
+    a = a + k[0];
+    /* case 0: nothing left to add */
+  }
+  mix(a, b, c);
+   /*-------------------------------------------- report the result */
+  return c & mask;
+}
+
+
+#else                          /* NEW_HASH_FUN */
+/** Compute a hash value for mask-style hashing.
+ * Given a null key, return 0. Otherwise, add up the numeric value
+ * of all the characters and return the sum modulo the size of the
+ * hash table.
+ * \param key key to hash.
+ * \param hashmask hash table size to use as modulus.
+ * \return hash value.
+ */
+int
+hash_val(const char *key, int hashmask)
+{
+  int hash = 0;
+  const char *sp;
+
+  if (!key || !*key)
+    return 0;
+  for (sp = key; *sp; sp++)
+    hash = (hash << 5) + hash + *sp;
+  return (hash & hashmask);
+}
+#endif                         /* NEW_HASH_FUN */
+
+/* ----------------------------------------------------------------------
+ * hash_getmask: Get hash mask for mask-style hashing.
+ */
+
+/** Get the hash mask for mask-style hashing.
+ * Given the data size, return closest power-of-two less than that size.
+ * \param size data size.
+ * \return hash mask.
+ */
+int
+hash_getmask(int *size)
+{
+  int tsize;
+
+  if (!size || !*size)
+    return 0;
+
+  for (tsize = 1; tsize < *size; tsize = tsize << 1) ;
+  *size = tsize;
+  return tsize - 1;
+}
+
+/** Initialize a hashtable.
+ * \param htab pointer to hash table to initialize.
+ * \param size size of hashtable.
+ * \param data_size size of an individual datum to store in the table.
+ */
+void
+hash_init(HASHTAB *htab, int size, int data_size)
+{
+  int i;
+
+  htab->mask = get_hashmask(&size);
+  htab->hashsize = size;
+  htab->entries = 0;
+  htab->buckets = mush_malloc(size * sizeof(HASHENT *), "hash_buckets");
+  for (i = 0; i < size; i++)
+    htab->buckets[i] = NULL;
+
+  htab->entry_size = data_size;
+}
+
+/** Return a hashtable entry given a key.
+ * \param htab pointer to hash table to search.
+ * \param key key to look up in the table.
+ * \return pointer to hash table entry for given key.
+ */
+HASHENT *
+hash_find(HASHTAB *htab, const char *key)
+{
+  int hval, cmp;
+  HASHENT *hptr;
+
+  if (!htab->buckets)
+    return NULL;
+
+  hval = hash_val(key, htab->mask);
+  for (hptr = htab->buckets[hval]; hptr != NULL; hptr = hptr->next) {
+    cmp = strcmp(key, hptr->key);
+    if (cmp == 0) {
+      return hptr;
+    } else if (cmp < 0)
+      break;
+  }
+  return NULL;
+}
+
+/** Return the value stored in a hash entry.
+ * \param entry pointer to a hash table entry.
+ * \return generic pointer to the stored value.
+ */
+void *
+hash_value(HASHENT *entry)
+{
+  if (entry)
+    return entry->data;
+  else
+    return NULL;
+}
+
+/** Return the key stored in a hash entry.
+ * \param entry pointer to a hash table entry.
+ * \return pointer to the stored key.
+ */
+char *
+hash_key(HASHENT *entry)
+{
+  if (entry)
+    return entry->key;
+  else
+    return NULL;
+}
+
+/** Resize a hash table.
+ * \param htab pointer to hashtable.
+ * \param size new size.
+ */
+void
+hash_resize(HASHTAB *htab, int size)
+{
+  int i;
+  HASHENT **oldarr;
+  HASHENT **newarr;
+  HASHENT *hent, *nent, *curr, *old;
+  int hval;
+  int osize;
+  int mask;
+
+  /* We don't want hashes outside these limits */
+  if ((size < (1 << 4)) || (size > (1 << 20)))
+    return;
+
+  /* Save the old data we need */
+  osize = htab->hashsize;
+  oldarr = htab->buckets;
+
+  mask = htab->mask = get_hashmask(&size);
+
+  if (size == htab->hashsize)
+    return;
+
+  htab->hashsize = size;
+  newarr =
+    (HASHENT **) mush_malloc(size * sizeof(struct hashentry *), "hash_buckets");
+  htab->buckets = newarr;
+  for (i = 0; i < size; i++)
+    newarr[i] = NULL;
+
+  for (i = 0; i < osize; i++) {
+    hent = oldarr[i];
+    while (hent) {
+      nent = hent->next;
+      hval = hash_val(hent->key, mask);
+      for (curr = newarr[hval], old = NULL; curr; old = curr, curr = curr->next) {
+       if (strcmp(curr->key, hent->key) > 0)
+         break;
+      }
+      if (old) {
+       old->next = hent;
+       hent->next = curr;
+      } else {
+       hent->next = newarr[hval];
+       newarr[hval] = hent;
+      }
+      hent = nent;
+    }
+  }
+  mush_free(oldarr, "hash_buckets");
+
+  return;
+}
+
+HASHENT *
+hash_new(HASHTAB *htab, const char *key)
+{
+  int hval;
+  size_t keylen;
+  HASHENT *hptr, *curr, *old;
+
+  hptr = hash_find(htab, key);
+  if (hptr)
+    return hptr;
+
+  if (htab->entries > (htab->hashsize * HTAB_UPSCALE))
+    hash_resize(htab, htab->hashsize << 1);
+
+  hval = hash_val(key, htab->mask);
+  htab->entries++;
+  keylen = strlen(key) + 1;
+  hptr = (HASHENT *) mush_malloc(HASHENT_SIZE + keylen, "hash_entry");
+  memcpy(hptr->key, key, keylen);
+  hptr->data = NULL;
+
+  if (!htab->buckets[hval] || strcmp(key, htab->buckets[hval]->key) < 0) {
+    hptr->next = htab->buckets[hval];
+    htab->buckets[hval] = hptr;
+    return hptr;
+  }
+
+  /* Insert in sorted order. There's always at least one item in 
+     the chain already at this point. */
+  old = htab->buckets[hval];
+  for (curr = old->next; curr; old = curr, curr = curr->next) {
+    /* Comparison will never be 0 because hash_add checks to see
+       if the entry is already present. */
+    if (strcmp(key, curr->key) < 0) {  /* Insert before curr */
+      old->next = hptr;
+      hptr->next = curr;
+      return hptr;
+    }
+  }
+
+  /* If we get here, we reached the end of the chain */
+  old->next = hptr;
+  hptr->next = NULL;
+
+  return hptr;
+}
+
+/** Add an entry to a hash table.
+ * \param htab pointer to hash table.
+ * \param key key string to store data under.
+ * \param hashdata void pointer to data to be stored.
+ * \param extra_size unused.
+ * \retval -1 failure.
+ * \retval 0 success.
+ */
+int
+hash_add(HASHTAB *htab, const char *key, void *hashdata,
+        int extra_size __attribute__ ((__unused__)))
+{
+  HASHENT *hptr;
+
+  if (hash_find(htab, key) != NULL) {
+    return -1;
+  }
+
+  hptr = hash_new(htab, key);
+
+  if (!hptr)
+    return -1;
+
+  hptr->data = hashdata;
+  /*      hptr->extra_size = extra_size; */
+  return 0;
+}
+
+/** Delete an entry in a hash table.
+ * \param htab pointer to hash table.
+ * \param entry pointer to hash entry to delete (and free).
+ */
+void
+hash_delete(HASHTAB *htab, HASHENT *entry)
+{
+  int hval;
+  HASHENT *hptr, *last;
+
+  if (!entry)
+    return;
+
+  hval = hash_val(entry->key, htab->mask);
+  last = NULL;
+  for (hptr = htab->buckets[hval]; hptr; last = hptr, hptr = hptr->next) {
+    if (entry == hptr) {
+      if (last == NULL)
+       htab->buckets[hval] = hptr->next;
+      else
+       last->next = hptr->next;
+      mush_free(hptr, "hash_entry");
+      htab->entries--;
+      return;
+    }
+  }
+
+  if (htab->entries < (htab->hashsize * HTAB_DOWNSCALE))
+    hash_resize(htab, htab->hashsize >> 1);
+}
+
+/** Flush a hash table, freeing all entries.
+ * \param htab pointer to a hash table.
+ * \param size size of hash table.
+ */
+void
+hash_flush(HASHTAB *htab, int size)
+{
+  HASHENT *hent, *thent;
+  int i;
+
+  if (htab->buckets) {
+    for (i = 0; i < htab->hashsize; i++) {
+      hent = htab->buckets[i];
+      while (hent != NULL) {
+       thent = hent;
+       hent = hent->next;
+       mush_free(thent, "hash_entry");
+      }
+      htab->buckets[i] = NULL;
+    }
+  }
+  if (size == 0) {
+    mush_free(htab->buckets, "hash_buckets");
+    htab->buckets = NULL;
+  } else if (size != htab->hashsize) {
+    if (htab->buckets)
+      mush_free(htab->buckets, "hash_buckets");
+    hashinit(htab, size, htab->entry_size);
+  } else {
+    htab->entries = 0;
+  }
+}
+
+/** Return the first entry of a hash table.
+ * This function is used with hash_nextentry() to iterate through a 
+ * hash table.
+ * \param htab pointer to hash table.
+ * \return first hash table entry.
+ */
+void *
+hash_firstentry(HASHTAB *htab)
+{
+  int hval;
+
+  for (hval = 0; hval < htab->hashsize; hval++)
+    if (htab->buckets[hval]) {
+      htab->last_hval = hval;
+      htab->last_entry = htab->buckets[hval];
+      return htab->buckets[hval]->data;
+    }
+  return NULL;
+}
+
+/** Return the first key of a hash table.
+ * This function is used with hash_nextentry_key() to iterate through a 
+ * hash table.
+ * \param htab pointer to hash table.
+ * \return first hash table key.
+ */
+char *
+hash_firstentry_key(HASHTAB *htab)
+{
+  int hval;
+
+  for (hval = 0; hval < htab->hashsize; hval++)
+    if (htab->buckets[hval]) {
+      htab->last_hval = hval;
+      htab->last_entry = htab->buckets[hval];
+      return htab->buckets[hval]->key;
+    }
+  return NULL;
+}
+
+/** Return the next entry of a hash table.
+ * This function is used with hash_firstentry() to iterate through a 
+ * hash table. hash_firstentry() must be called before calling
+ * this function.
+ * \param htab pointer to hash table.
+ * \return next hash table entry.
+ */
+void *
+hash_nextentry(HASHTAB *htab)
+{
+  int hval;
+  HASHENT *hptr;
+
+  hval = htab->last_hval;
+  hptr = htab->last_entry;
+  if (hptr->next) {
+    htab->last_entry = hptr->next;
+    return hptr->next->data;
+  }
+  hval++;
+  while (hval < htab->hashsize) {
+    if (htab->buckets[hval]) {
+      htab->last_hval = hval;
+      htab->last_entry = htab->buckets[hval];
+      return htab->buckets[hval]->data;
+    }
+    hval++;
+  }
+  return NULL;
+}
+
+/** Return the next key of a hash table.
+ * This function is used with hash_firstentry{,_key}() to iterate through a 
+ * hash table. hash_firstentry{,_key}() must be called before calling
+ * this function.
+ * \param htab pointer to hash table.
+ * \return next hash table key.
+ */
+char *
+hash_nextentry_key(HASHTAB *htab)
+{
+  int hval;
+  HASHENT *hptr;
+
+  hval = htab->last_hval;
+  hptr = htab->last_entry;
+  if (hptr->next) {
+    htab->last_entry = hptr->next;
+    return hptr->next->key;
+  }
+  hval++;
+  while (hval < htab->hashsize) {
+    if (htab->buckets[hval]) {
+      htab->last_hval = hval;
+      htab->last_entry = htab->buckets[hval];
+      return htab->buckets[hval]->key;
+    }
+    hval++;
+  }
+  return NULL;
+}
+
+/** Display a header for a stats listing.
+ * \param player player to notify with header.
+ */
+void
+hash_stats_header(dbref player)
+{
+  notify_format(player,
+               "Table      Buckets Entries LChain  ECh  1Ch  2Ch  3Ch 4+Ch  AvgCh ~Memory");
+}
+
+/** Display stats on a hashtable.
+ * \param player player to notify with stats.
+ * \param htab pointer to the hash table.
+ * \param hname name of the hash table.
+ */
+void
+hash_stats(dbref player, HASHTAB *htab, const char *hname)
+{
+  int longest = 0, n;
+  int lengths[5];
+  double chainlens = 0.0;
+  double totchains = 0.0;
+  unsigned int bytes = 0;
+
+  if (!htab || !hname)
+    return;
+
+  for (n = 0; n < 5; n++)
+    lengths[n] = 0;
+  bytes += sizeof(HASHTAB);
+  bytes += htab->entry_size * htab->entries;
+  if (htab->buckets) {
+    bytes += HASHENT_SIZE * htab->hashsize;
+    for (n = 0; n < htab->hashsize; n++) {
+      int chain = 0;
+      HASHENT *b;
+      if (htab->buckets[n]) {
+       for (b = htab->buckets[n]; b; b = b->next) {
+         chain++;
+         bytes += strlen(b->key) + 1 /* + b->extra_size */ ;
+       }
+       if (chain > longest)
+         longest = chain;
+      }
+      lengths[(chain > 4) ? 4 : chain]++;
+      chainlens += chain;
+    }
+  }
+  for (n = 1; n < 5; n++)
+    totchains += lengths[n];
+
+  notify_format(player,
+               "%-10s %7d %7d %6d %4d %4d %4d %4d %4d %6.3f %7u", hname,
+               htab->hashsize, htab->entries, longest, lengths[0], lengths[1],
+               lengths[2], lengths[3], lengths[4],
+               totchains == 0.0 ? 0.0 : chainlens / totchains, bytes);
+}
diff --git a/src/ident.c b/src/ident.c
new file mode 100644 (file)
index 0000000..0ee9f58
--- /dev/null
@@ -0,0 +1,533 @@
+/**
+ * \file ident.c
+ *
+ * \brief High-level calls to the ident library.
+ *
+ * Author: Pär Emanuelsson <pell@lysator.liu.se>
+ * Hacked by: Peter Eriksson <pen@lysator.liu.se>
+ * 
+ * Many changes by Shawn Wagner to be protocol independent
+ * for PennMUSH
+ */
+
+#include "config.h"
+#ifdef NeXT3
+#include <libc.h>
+#endif
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <time.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#ifdef I_SYS_WAIT
+#include <sys/wait.h>
+#endif
+#include <errno.h>
+#ifndef WIN32
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#else
+#ifdef I_SYS_IN
+#include <sys/in.h>
+#endif
+#endif
+#ifdef I_ARPA_INET
+#include <arpa/inet.h>
+#endif
+#include <netdb.h>
+#endif                         /* WIN32 */
+
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+
+#include "conf.h"
+#include "externs.h"
+#include "attrib.h"
+#include "ident.h"
+#include "mymalloc.h"
+#include "mysocket.h"
+#include "confmagic.h"
+
+  /* Low-level calls and macros */
+
+/** Structure to track an ident connection. */
+typedef struct {
+  int fd;              /**< file descriptor to read from. */
+  char buf[IDBUFSIZE]; /**< buffer to hold ident data. */
+} ident_t;
+
+
+static ident_t *id_open(struct sockaddr *faddr,
+                       socklen_t flen,
+                       struct sockaddr *laddr, socklen_t llen, int *timeout);
+
+static int id_query(ident_t * id,
+                   struct sockaddr *laddr,
+                   socklen_t llen,
+                   struct sockaddr *faddr, socklen_t flen, int *timeout);
+
+static int id_close(ident_t * id);
+
+static int id_parse(ident_t * id, int *timeout, IDENT **ident);
+static IDENT *ident_lookup(int fd, int *timeout);
+
+
+/* Do a complete ident query and return result */
+static IDENT *
+ident_lookup(int fd, int *timeout)
+{
+  union sockaddr_u localaddr, remoteaddr;
+  socklen_t llen, rlen, len;
+
+  len = sizeof(remoteaddr);
+  if (getpeername(fd, (struct sockaddr *) remoteaddr.data, &len) < 0)
+    return 0;
+  llen = len;
+
+  len = sizeof(localaddr);
+  if (getsockname(fd, (struct sockaddr *) localaddr.data, &len) < 0)
+    return 0;
+  rlen = len;
+
+  return ident_query(&localaddr.addr, llen, &remoteaddr.addr, rlen, timeout);
+}
+
+/** Perform an ident query and return the result.
+ * \param laddr local socket address data.
+ * \param llen local socket address data length.
+ * \param raddr remote socket address data.
+ * \param rlen remote socket address data length.
+ * \param timeout pointer to timeout value for query.
+ * \return ident responses in IDENT pointer, or NULL.
+ */
+IDENT *
+ident_query(struct sockaddr *laddr, socklen_t llen,
+           struct sockaddr *raddr, socklen_t rlen, int *timeout)
+{
+  int res;
+  ident_t *id;
+  IDENT *ident = 0;
+
+  if (timeout && *timeout < 0)
+    *timeout = 0;
+
+  id = id_open(raddr, rlen, laddr, llen, timeout);
+
+  if (!id) {
+#ifndef WIN32
+    errno = EINVAL;
+#endif
+#ifdef DEBUG
+    fprintf(stderr, "id_open failed.\n");
+#endif
+    return 0;
+  }
+
+  res = id_query(id, raddr, rlen, laddr, llen, timeout);
+
+  if (res < 0) {
+    id_close(id);
+#ifdef DEBUG
+    fprintf(stderr, "id_query failed.\n");
+#endif
+    return 0;
+  }
+
+  res = id_parse(id, timeout, &ident);
+
+  if (res != 1) {
+    id_close(id);
+#ifdef DEBUG
+    fprintf(stderr, "id_parse failed.\n");
+#endif
+    return 0;
+  }
+  id_close(id);
+
+  return ident;                        /* At last! */
+}
+
+/** Perform an ident lookup and return the remote identifier as a
+ * newly allocated string. This function allocates memory that
+ * should be freed by the caller.
+ * \param fd socket to use for ident lookup.
+ * \param timeout pointer to timeout value for lookup.
+ * \return allocated string containing identifier, or NULL.
+ */
+char *
+ident_id(int fd, int *timeout)
+{
+  IDENT *ident;
+  char *id = NULL;
+  if (timeout && *timeout < 0)
+    *timeout = 0;
+  ident = ident_lookup(fd, timeout);
+  if (ident && ident->identifier && *ident->identifier)
+    id = strdup(ident->identifier);
+  ident_free(ident);
+  return id;
+}
+
+/** Free an IDENT structure and all elements.
+ * \param id pointer to IDENT structure to free.
+ */
+void
+ident_free(IDENT *id)
+{
+  if (!id)
+    return;
+  if (id->identifier)
+    free(id->identifier);
+  if (id->opsys)
+    free(id->opsys);
+  if (id->charset)
+    free(id->charset);
+  free(id);
+}
+
+/* id_open.c Establish/initiate a connection to an IDENT server
+   **
+   ** Author: Peter Eriksson <pen@lysator.liu.se>
+   ** Fixes: Pär Emanuelsson <pell@lysator.liu.se> */
+
+
+
+static ident_t *
+id_open(struct sockaddr *faddr, socklen_t flen,
+       struct sockaddr *laddr, socklen_t llen, int *timeout)
+{
+  ident_t *id;
+  char host[NI_MAXHOST];
+  union sockaddr_u myinterface;
+  fd_set rs, ws, es;
+  struct timeval to;
+  int res;
+#ifndef WIN32
+  int tmperrno;
+#endif
+
+  if ((id = (ident_t *) malloc(sizeof(*id))) == 0)
+    return 0;
+
+  memset(id, 0, sizeof(ident_t));
+
+  if (getnameinfo(faddr, flen, host, sizeof(host), NULL, 0,
+                 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+    free(id);
+    return 0;
+  }
+
+  /* Make sure we connect from the right interface. Changing the pointer
+     directly doesn't seem to work. So... */
+  memcpy(&myinterface, laddr, llen);
+  if (myinterface.addr.sa_family == AF_INET)
+    ((struct sockaddr_in *) &myinterface.addr)->sin_port = 0;
+#ifdef HAS_IPV6                        /* Bleah, I wanted to avoid stuff like this */
+  else if (myinterface.addr.sa_family == AF_INET6)
+    ((struct sockaddr_in6 *) &myinterface.addr)->sin6_port = 0;
+#endif
+
+  id->fd = make_socket_conn(host, &myinterface.addr, llen, IDPORT, timeout);
+
+  if (id->fd < 0)              /* Couldn't connect to an ident server */
+    goto ERROR_BRANCH;
+
+  if (timeout) {
+    time_t now, after;
+
+    FD_ZERO(&rs);
+    FD_ZERO(&ws);
+    FD_ZERO(&es);
+    FD_SET(id->fd, &rs);
+    FD_SET(id->fd, &ws);
+    FD_SET(id->fd, &es);
+    to.tv_sec = *timeout;
+    to.tv_usec = 0;
+    now = time(NULL);
+    if ((res = select(id->fd + 1, &rs, &ws, &es, &to)) < 0) {
+#ifdef DEBUG
+      perror("libident: select");
+#endif
+      goto ERROR_BRANCH;
+    }
+    after = time(NULL);
+    *timeout -= after - now;
+    *timeout = *timeout < 0 ? 0 : *timeout;
+
+    if (res == 0) {
+#ifndef WIN32
+      errno = ETIMEDOUT;
+#endif
+      goto ERROR_BRANCH;
+    }
+    if (FD_ISSET(id->fd, &es))
+      goto ERROR_BRANCH;
+
+    if (!FD_ISSET(id->fd, &rs) && !FD_ISSET(id->fd, &ws))
+      goto ERROR_BRANCH;
+  }
+  return id;
+
+ERROR_BRANCH:
+#ifndef WIN32
+  tmperrno = errno;            /* Save, so close() won't erase it */
+#endif
+  closesocket(id->fd);
+  free(id);
+#ifndef WIN32
+  errno = tmperrno;
+#endif
+  return 0;
+}
+
+
+/* id_close.c Close a connection to an IDENT server
+   **
+   ** Author: Peter Eriksson <pen@lysator.liu.se> */
+
+static int
+id_close(ident_t * id)
+{
+  int res;
+
+  res = closesocket(id->fd);
+  free(id);
+
+  return res;
+}
+
+
+/* id_query.c Transmit a query to an IDENT server
+   **
+   ** Author: Peter Eriksson <pen@lysator.liu.se>
+   ** Slight modifications by Alan Schwartz */
+
+
+static int
+id_query(ident_t * id, struct sockaddr *laddr, socklen_t llen,
+        struct sockaddr *faddr, socklen_t flen, int *timeout)
+{
+  int res;
+  char buf[80];
+  char port[NI_MAXSERV];
+  fd_set ws;
+  struct timeval to;
+
+  getnameinfo(laddr, llen, NULL, 0, port, sizeof(port),
+             NI_NUMERICHOST | NI_NUMERICSERV);
+  sprintf(buf, "%s , ", port);
+  getnameinfo(faddr, flen, NULL, 0, port, sizeof(port),
+             NI_NUMERICHOST | NI_NUMERICSERV);
+  strncat(buf, port, sizeof(buf));
+  strncat(buf, "\r\n", sizeof(buf));
+
+  if (timeout) {
+    time_t now, after;
+    FD_ZERO(&ws);
+    FD_SET(id->fd, &ws);
+    to.tv_sec = *timeout;
+    to.tv_usec = 0;
+    now = time(NULL);
+    if ((res = select(id->fd + 1, NULL, &ws, NULL, &to)) < 0)
+      return -1;
+    after = time(NULL);
+    *timeout -= after - now;
+    *timeout = *timeout < 0 ? 0 : *timeout;
+    if (res == 0) {
+#ifndef WIN32
+      errno = ETIMEDOUT;
+#endif
+      return -1;
+    }
+  }
+  /* Used to ignore SIGPIPE here, but we already ignore it anyways. */
+  res = send(id->fd, buf, strlen(buf), 0);
+
+  return res;
+}
+
+
+/* id_parse.c Receive and parse a reply from an IDENT server
+   **
+   ** Author: Peter Eriksson <pen@lysator.liu.se>
+   ** Fiddling: Pär Emanuelsson <pell@lysator.liu.se> */
+
+static char *
+xstrtok(char *RESTRICT cp, const char *RESTRICT cs, char *RESTRICT dc)
+{
+  static char *bp = 0;
+
+  if (cp)
+    bp = cp;
+
+  /*
+   ** No delimitor cs - return whole buffer and point at end
+   */
+  if (!cs) {
+    while (*bp)
+      bp++;
+    return NULL;
+  }
+  /*
+   ** Skip leading spaces
+   */
+  while (isspace((unsigned char) *bp))
+    bp++;
+
+  /*
+   ** No token found?
+   */
+  if (!*bp)
+    return NULL;
+
+  cp = bp;
+  bp += strcspn(bp, cs);
+  /*  while (*bp && !strchr(cs, *bp))
+     bp++;
+   */
+  /* Remove trailing spaces */
+  *dc = *bp;
+  for (dc = bp - 1; dc > cp && isspace((unsigned char) *dc); dc--) ;
+  *++dc = '\0';
+
+  bp++;
+
+  return cp;
+}
+
+
+static int
+id_parse(ident_t * id, int *timeout, IDENT **ident)
+{
+  char c, *cp, *tmp_charset;
+  fd_set rs;
+  int res = 0, lp, fp;
+  size_t pos;
+  struct timeval to;
+
+#ifndef WIN32
+  errno = 0;
+#endif
+
+  tmp_charset = 0;
+
+  if (!id || !ident)
+    return -1;
+
+  *ident = malloc(sizeof(IDENT));
+
+  if (!*ident)
+    return -1;
+
+  memset(*ident, 0, sizeof(IDENT));
+
+  pos = strlen(id->buf);
+
+  if (timeout) {
+    time_t now, after;
+    FD_ZERO(&rs);
+    FD_SET(id->fd, &rs);
+    to.tv_sec = *timeout;
+    to.tv_usec = 0;
+    now = time(NULL);
+    if ((res = select(id->fd + 1, &rs, NULL, NULL, &to)) < 0)
+      return -1;
+    after = time(NULL);
+    *timeout -= after - now;
+    *timeout = *timeout < 0 ? 0 : *timeout;
+    if (res == 0) {
+#ifndef WIN32
+      errno = ETIMEDOUT;
+#endif
+      return -1;
+    }
+  }
+  while (pos < sizeof(id->buf) &&
+        (res = recv(id->fd, id->buf + pos, 1, 0)) == 1 && id->buf[pos] != '\n')
+    pos++;
+  if (res < 0)
+    return -1;
+
+  if (res == 0) {
+#ifndef WIN32
+    errno = ENOTCONN;
+#endif
+    return -1;
+  }
+  if (id->buf[pos] != '\n') {
+    return 0;
+  }
+  id->buf[pos++] = '\0';
+
+  /* Get first field (<lport> , <fport>) */
+  cp = xstrtok(id->buf, ":", &c);
+  if (!cp) {
+    return -2;
+  }
+
+  if ((res = sscanf(cp, " %d , %d", &lp, &fp)) != 2) {
+    (*ident)->identifier = strdup(cp);
+    return -2;
+  }
+  /* Get second field (USERID or ERROR) */
+  cp = xstrtok(NULL, ":", &c);
+  if (!cp) {
+    return -2;
+  }
+  if (strcmp(cp, "ERROR") == 0) {
+    cp = xstrtok(NULL, "\n\r", &c);
+    if (!cp)
+      return -2;
+
+    (*ident)->identifier = strdup(cp);
+
+    return 2;
+  } else if (strcmp(cp, "USERID") == 0) {
+    /* Get first subfield of third field <opsys> */
+    cp = xstrtok(NULL, ",:", &c);
+    if (!cp) {
+      return -2;
+    }
+    (*ident)->opsys = strdup(cp);
+
+    /* We have a second subfield (<charset>) */
+    if (c == ',') {
+      cp = xstrtok(NULL, ":", &c);
+      if (!cp)
+       return -2;
+
+      tmp_charset = cp;
+
+      (*ident)->charset = strdup(cp);
+
+      /* We have even more subfields - ignore them */
+      if (c == ',')
+       xstrtok(NULL, ":", &c);
+    }
+    if (tmp_charset && strcmp(tmp_charset, "OCTET") == 0)
+      cp = xstrtok(NULL, NULL, &c);
+    else
+      cp = xstrtok(NULL, "\n\r", &c);
+
+    (*ident)->identifier = strdup(cp);
+    return 1;
+  } else {
+    (*ident)->identifier = strdup(cp);
+    return -3;
+  }
+}
diff --git a/src/info_slave.c b/src/info_slave.c
new file mode 100644 (file)
index 0000000..6b82690
--- /dev/null
@@ -0,0 +1,233 @@
+/**
+ * \file info_slave.c
+ *
+ * \brief The information slave process.
+ *
+ * When running PennMUSH under Unix, a second process (info_slave) is
+ * started and the server farms out DNS and ident lookups to the
+ * info_slave, and reads responses from the info_slave asynchronously. 
+ * Communication between server and slave is by means of a local socket.
+ *
+ */
+#include "copyrite.h"
+#include "config.h"
+
+#ifdef WIN32
+#error "info_slave is not currently supported on Windows"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#endif
+#include <netdb.h>
+#include <ctype.h>
+#include <string.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <sys/uio.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "ident.h"
+#include "mysocket.h"
+#include "confmagic.h"
+
+/* Duplicate these, rather than trying to include strutil.o... */
+/** Arguments for functions that call APPEND_TO_BUF */
+#define APPEND_ARGS int len, blen, clen
+/** Add string c to buffer buff of max length mlen */
+#define APPEND_TO_BUF(mlen) \
+  /* Trivial cases */  \
+  if (c[0] == '\0') \
+    return 0; \
+  /* The array is at least two characters long here */ \
+  if (c[1] == '\0') \
+    return safe_chr(c[0], buff, bp); \
+  len = strlen(c); \
+  blen = *bp - buff; \
+  if (blen > (mlen)) \
+    return len; \
+  if ((len + blen) <= (mlen)) \
+    clen = len; \
+  else \
+    clen = (mlen) - blen; \
+  memcpy(*bp, c, clen); \
+  *bp += clen; \
+  return len - clen
+
+#ifdef SAFE_CHR_FUNCTION
+int
+safe_chr(char c, char *buf, char **bufp)
+{
+  /* adds a character to a string, being careful not to overflow buffer */
+
+  if ((*bufp - buf >= BUFFER_LEN - 1))
+    return 1;
+
+  *(*bufp)++ = c;
+  return 0;
+}
+#endif
+
+int
+safe_str(const char *c, char *buff, char **bp)
+{
+  /* copies a string into a buffer, making sure there's no overflow. */
+  APPEND_ARGS;
+
+  if (!c || !*c)
+    return 0;
+
+  APPEND_TO_BUF(BUFFER_LEN);
+}
+
+#undef APPEND_ARGS
+#undef APPEND_TO_BUF
+
+
+int
+main(int argc, char *argv[])
+{
+  int mush;
+  int port;
+  int fd;
+  union sockaddr_u local, remote;
+  static char buf[BUFFER_LEN]; /* overkill */
+  char *bp;
+  int len, size;
+  IDENT *ident_result;
+  char host[NI_MAXHOST];
+  char lport[NI_MAXSERV];
+  int use_ident, use_dns, timeout;
+  socklen_t llen, rlen;
+  struct iovec dat[3];
+
+  if (argc < 2) {
+    fprintf(stderr, "info_slave needs a port number!\n");
+    return EXIT_FAILURE;
+  }
+  port = atoi(argv[1]);
+  use_ident = 1;
+  if (argc >= 3) {
+    /* The second argument is -1 if we don't want ident used.
+     * Anything else is the timeout. Default is 5 seconds.
+     */
+    use_ident = atoi(argv[2]);
+  } else
+    use_ident = 5;
+
+  if (argc >= 4) {
+    /* The third argument is 1 to do DNS lookups, 0 to not. */
+    use_dns = atoi(argv[3]);
+  } else
+    use_dns = 1;
+
+#ifdef HAS_SOCKETPAIR
+  mush = port;                 /* We inherit open file descriptions and sockets from parent */
+#else
+  mush = make_socket_conn("127.0.0.1", NULL, 0, port, NULL);
+  if (mush == -1) {            /* Couldn't connect */
+    fprintf(stderr, "Couldn't connect to mush!\n");
+    return EXIT_FAILURE;
+  }
+#endif
+  /* yes, we are _blocking_ */
+
+  for (;;) {
+    /* grab a request */
+    /* First, the address size. */
+
+    len = read(mush, &rlen, sizeof rlen);
+    if (len < (int) sizeof rlen) {
+      perror("info_slave reading remote size (Did the mush crash?)");
+      return EXIT_FAILURE;
+    }
+    /* Now the first address and len of the second. */
+    dat[0].iov_base = (char *) &remote.data;
+    dat[0].iov_len = rlen;
+    dat[1].iov_base = (char *) &llen;
+    dat[1].iov_len = sizeof llen;
+    size = rlen + sizeof llen;
+    len = readv(mush, dat, 2);
+    if (len < size) {
+      perror("info_slave reading remote sockaddr and local size");
+      return EXIT_FAILURE;
+    }
+
+    /* Now the second address and fd. */
+    dat[0].iov_base = (char *) &local.data;
+    dat[0].iov_len = llen;
+    dat[1].iov_base = (char *) &fd;
+    dat[1].iov_len = sizeof fd;
+    size = llen + sizeof fd;
+    len = readv(mush, dat, 2);
+    if (len < size) {
+      perror("info_slave reading local sockaddr and fd");
+      return EXIT_FAILURE;
+    }
+
+    if (!fd)
+      /* MUSH aborted query part way through or only wrote a partial
+       * packet */
+      continue;
+
+    bp = buf;
+    if (getnameinfo(&remote.addr, rlen, host, sizeof host, NULL, 0,
+                   NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+      safe_str("An error occured", buf, &bp);
+    else
+      safe_str(host, buf, &bp);
+    safe_chr('^', buf, &bp);
+    if (getnameinfo(&local.addr, llen, NULL, 0, lport, sizeof lport,
+                   NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+      safe_str("An error occured", buf, &bp);
+    else
+      safe_str(lport, buf, &bp);
+    safe_chr('^', buf, &bp);
+
+    if (use_ident > 0) {
+      timeout = use_ident;
+      ident_result =
+       ident_query(&local.addr, llen, &remote.addr, rlen, &timeout);
+      if (ident_result && ident_result->identifier) {
+       safe_str(ident_result->identifier, buf, &bp);
+       safe_chr('@', buf, &bp);
+      }
+      if (ident_result)
+       ident_free(ident_result);
+    }
+    if (use_dns) {
+      if (getnameinfo(&remote.addr, rlen, host, sizeof host, NULL, 0,
+                     NI_NUMERICSERV) != 0) {
+       safe_str("An error occured", buf, &bp);
+      } else {
+       safe_str(host, buf, &bp);
+      }
+    } else
+      safe_str(host, buf, &bp);
+    *bp = '\0';
+    size = strlen(buf);
+    dat[0].iov_base = (char *) &fd;
+    dat[0].iov_len = sizeof fd;
+    dat[1].iov_base = (char *) &size;
+    dat[1].iov_len = sizeof size;
+    dat[2].iov_base = buf;
+    dat[2].iov_len = size;
+    len = writev(mush, dat, 3);
+    size = dat[0].iov_len + dat[1].iov_len + dat[2].iov_len;
+    if (len < size) {
+      perror("info_slave write packet");
+      return EXIT_FAILURE;
+    }
+  }
+  return EXIT_SUCCESS;
+}
diff --git a/src/local.dst b/src/local.dst
new file mode 100644 (file)
index 0000000..120c07a
--- /dev/null
@@ -0,0 +1,267 @@
+/*-----------------------------------------------------------------
+ * Local stuff
+ *
+ * This file contains custom stuff, and some of the items here are
+ * called from within PennMUSH at specific times.
+ */
+
+/* Here are some includes you're likely to need or want.
+ */
+#include "copyrite.h"
+#include "config.h"
+#include <string.h>
+#include "conf.h"
+#include "dbio.h"
+#include "externs.h"
+#include "parse.h"
+#include "htab.h"
+#include "command.h"
+#include "confmagic.h"
+
+extern HASHTAB htab_reserved_aliases;
+
+/* Called after all MUSH init is done.
+ */
+void
+local_startup(void)
+{
+}
+
+/* Add you own runtime configuration options here, and you can set
+ * them in mush.cnf.
+ */
+void
+local_configs()
+{
+#ifdef EXAMPLE
+  /* For each config parameter you add, you should initialize it as a
+   * static variable here (or a global variable elsewhere in your
+   * code)
+   */
+  static int config_example = 1;
+  static char config_string[BUFFER_LEN];
+#endif
+
+  /* Initial size of this hashtable should be close to the number of
+   * add_config()'s you plan to do.
+   */
+  hashinit(&local_options, 4, sizeof(COBRA_CONF));
+
+#ifdef EXAMPLE
+  /* Call add_config for each config parameter you want to add.
+   * Note the use of &config_example for simple types (bool, int),
+   * but just config_string for strings.
+   */
+  add_config("use_example", cf_bool, &config_example, sizeof config_example,
+            "cosmetic");
+  add_config("some_string", cf_str, config_string, sizeof config_string,
+            "cosmetic");
+#endif
+}
+
+
+/* Wrap any calls to stdio functions in local_dump_database() and stuff
+ * it calls in the OUTPUT() macro, which does error checking so we can
+ * report problems with a save so it's caught before damage is
+ * done and you loose a db. . putref(), putstring(), and such don't need
+ * them.
+ * For example:
+ *
+ * putref(fp, 18);
+ * OUTPUT(putc('\n', fp));
+ */
+
+/* Called when the database will be saved
+ * This is called JUST before we dump the
+ * database to disk
+ * Use to save any in-memory structures
+ * back to disk
+ */
+void
+local_dump_database(void)
+{
+}
+
+/* Called when the MUSH is shutting down.
+ * The DB has been saved and descriptors closed
+ * The log files are still open though.
+ */
+void
+local_shutdown(void)
+{
+}
+
+/* Called when the MUSH is performing a dbck database check,
+ * at the end of the check. A good place to add any regular
+ * consistency checking you require.
+ */
+void
+local_dbck(void)
+{
+}
+
+/* This is called exactly once a second
+ * After the MUSH has done all it's stuff
+ */
+void
+local_timer(void)
+{
+}
+
+/* Called when a player connects. If this is a new creation,
+ * isnew will be true. num gives the number of connections by
+ * that player (so if num > 1, this is a multiple connect).
+ */
+void
+local_connect(dbref player __attribute__ ((__unused__)),
+             int isnew __attribute__ ((__unused__)),
+             int num __attribute__ ((__unused__)))
+{
+}
+
+/* Called when a player disconnects. If num > 1, this is
+ * a partial disconnect.
+ */
+void
+local_disconnect(dbref player __attribute__ ((__unused__)),
+                int num __attribute__ ((__unused__)))
+{
+}
+
+
+/* For serious hackers only */
+
+/* Those who are depraved enough to do so (Like me), can always 
+ * abuse this as a new and better way of Always Doing Stuff
+ * to objects.
+ * Like, say you want to put out a message on the admin
+ * channel every time an object is destroyed, do so in the
+ * local_data_destroy() routine.
+ */
+
+/* Called when a object is created with @create (or @dig, @link) 
+ * This is done AFTER object-specific setup, so the types
+ * etc will already be set, and object-specific initialization
+ * will be done.
+ * Note that the game will ALWAYS set the LocData to NULL before
+ * this routine is called.
+ */
+
+/* For a well-commented example of how to use this code,
+ * see: ftp://bimbo.hive.no/pub/PennMUSH/coins.tar.gz
+ */
+
+void
+local_data_create(dbref object __attribute__ ((__unused__)))
+{
+}
+
+/* Called when an object is cloned. Since clone is a rather
+ * specific form of creation, it has it's own function.
+ * Note that local_data_create() is NOT called for this object
+ * first, but the system will always set LocData to NULL first.
+ * Clone is the 'new' object, while source is the one it's
+ * being copied from.
+ */
+
+void
+local_data_clone(dbref clone __attribute__ ((__unused__)),
+                dbref source __attribute__ ((__unused__)))
+{
+}
+
+/* Called when a object is REALLY destroyed, not just set
+ * Going.
+ */
+
+void
+local_data_free(dbref object __attribute__ ((__unused__)))
+{
+}
+
+/* Initiation of objects after a reload or dumping to disk should
+ * be handled in local_dump_database() and local_startup().
+ */
+
+
+/* This function is called *before* most standard interaction checks,
+ * and can override them. You probably want to do as little as possible
+ * here and do most of the work in local_can_interact_last instead.
+ * If this returns NOTHING, it means 'go on to more checks'
+ */
+int
+local_can_interact_first(dbref from __attribute__ ((__unused__)),
+                        dbref to __attribute__ ((__unused__)), int type
+                        __attribute__ ((__unused__)))
+{
+
+  return NOTHING;
+}
+
+/* This one is called *after* most standard interaction checks. */
+int
+local_can_interact_last(dbref from __attribute__ ((__unused__)),
+                       dbref to __attribute__ ((__unused__)), int type
+                       __attribute__ ((__unused__)))
+{
+  /* from is where the message is coming from, in theory. It makes sense
+   * for sound, but think of it as light rays for visiblity or matching. 
+   * The rays come *from* someone standing in a room, and go *to* the person
+   * looking around.
+   */
+
+#ifdef NEVER
+  /* Various examples follow */
+
+  switch (type) {
+  case INTERACT_SEE:
+    /* Someone standing in a room, or doing
+     * @verb type stuff that's @bar, @obar, and @abar
+     */
+
+    /* Probably a good idea */
+    if (See_All(to))
+      return 1;
+
+    break;
+
+  case INTERACT_PRESENCE:
+    /* Someone arriving or leaving, connecting or disconnecting, 
+     * and (for objects) growing or losing ears.
+     */
+
+    /* To prevent spying, always notice presence */
+    return 1;
+
+    break;
+
+  case INTERACT_HEAR:
+    /* People talking */
+
+    /* Telepathy example. Players who can hear telepathy get @set
+     * HEAR_TELEPATHY,  players currently using telepathy should be
+     * @set USE_TELEPATHY. */
+
+    if (has_flag_by_name(from, "USE_TELEPATHY", NOTYPE))
+      return has_flag_by_name(to, "HEAR_TELEPATHY", NOTYPE);
+
+    break;
+
+  case INTERACT_MATCH:
+    /* Matching object names so you can pick them up, go through exits,
+       etc. */
+
+    break;
+  }
+
+  /* Splits the universe in half, half FOO and half not. */
+  return (has_flag_by_name(to, "FOO", NOTYPE) ==
+         has_flag_by_name(from, "FOO", NOTYPE));
+
+
+#endif                         /* NEVER */
+
+  /* You want to return NOTHING if you haven't made up your mind */
+  return NOTHING;
+
+}
diff --git a/src/lock.c b/src/lock.c
new file mode 100644 (file)
index 0000000..466934b
--- /dev/null
@@ -0,0 +1,970 @@
+/**
+ * \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 */
+const lock_type Take_Lock = "Take"; /**< Name of take lock */
+const lock_type Open_Lock = "Open"; /**< Name of Open lock */
+const lock_type MailForward_Lock = "MailForward"; /**< Name of mailforward 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 | LF_PRIVILEGE, 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},
+  {"Take", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
+  {"MailForward", TRUE_BOOLEXP, GOD, LF_PRIVATE, NULL},
+  /* Add new lock types just before this line. */
+  {NULL, NULL_CHUNK_REFERENCE, 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"},
+  {"Take", "TAKE", "TFAIL"},
+  {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},
+  {"privilege", 'p', LF_PRIVILEGE, LF_PRIVILEGE},
+  /*  {"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(dbref thing, lock_type type);
+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 all available locks
+ * \param buff the buffer
+ * \param bp a pointer to the current position in the buffer
+ */
+void
+list_locks(char *buff, char **bp, const char *name)
+{
+  char rbuff[BUFFER_LEN];
+  char *rp;
+  int first = 1;
+  const lock_list *ptr;
+  rp = rbuff;
+  for (ptr = lock_types; ptr->type != NULL; ptr++) {
+    /* Skip those that don't match */
+    if (name && !string_prefix(ptr->type, name))
+      continue;
+    if (first) {
+      first = 0;
+    } else {
+      safe_chr(' ', rbuff, &rp);
+    }
+    safe_str(ptr->type, rbuff, &rp);
+  }
+  *rp = '\0';
+  /* We strupper it for consistency with the other
+   * @list/foo and list(foo)s. */
+  safe_str(strupper(rbuff), buff, bp);
+}
+
+/** User interface to list locks.
+ * \verbatim
+ * This function implements @list/locks.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg wildcard pattern of flag names to list, or NULL for all.
+ * \param lc if 1, list flags in lowercase.
+ * \param label label to prefix to list.
+ */
+void
+do_list_locks(dbref player, const char *arg, int lc, const char *label)
+{
+  char buff[BUFFER_LEN];
+  char *bp = buff;
+  list_locks(buff, &bp, arg);
+  *bp = '\0';
+  notify_format(player, "%s: %s", label, lc ? strlower(buff) : buff);
+}
+
+/** 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));
+}
+
+/** 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(char const *p)
+{
+  int f;
+  f = string_to_privs(lock_privs, p, 0);
+  if (!f)
+    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 ((L_FLAGS(lock) & LF_PRIVILEGE)
+       && !Prived(player))
+    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);
+}
+
+static 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) {
+    notify(player, "Use @atrlock.");
+    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)) {
+          char lmbuf[1024];
+          ModTime(thing) = mudtime;
+          snprintf(lmbuf, 1023, "%s lock[#%d]", real_type, player);
+          lmbuf[strlen(lmbuf)+1] = '\0';
+          set_lmod(thing, lmbuf);
+       }
+      } 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, keyname, 0);
+    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)) {
+          char lmbuf[1024];
+          ModTime(thing) = mudtime;
+          snprintf(lmbuf, 1023, "%s lock[#%d]", real_type, player);
+          lmbuf[strlen(lmbuf)+1] = '\0';
+          set_lmod(thing, lmbuf);
+       }
+      } else
+       notify(player, T("Permission denied."));
+    }
+  }
+}
+
+/** 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));
+  if(Pass_Lock(player, thing) && IS_passlock_type(ltype))
+    return 1;
+  return eval_boolexp(player, getlock(thing, ltype), thing, NULL);
+}
+
+/** 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(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)) {
+          char lmbuf[1024];
+          ModTime(thing) = mudtime;
+          snprintf(lmbuf, 1023, "lflags - %s[#%d]", flags, player);
+          lmbuf[strlen(lmbuf)+1] = '\0';
+          set_lmod(thing, lmbuf);
+  }
+}
+
+/** 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));
+  }
+}
diff --git a/src/log.c b/src/log.c
new file mode 100644 (file)
index 0000000..06afea1
--- /dev/null
+++ b/src/log.c
@@ -0,0 +1,495 @@
+/**
+ * \file log.c
+ *
+ * \brief Logging for PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <stdarg.h>
+#ifdef I_STDLIB
+#include <stdlib.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <time.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+
+#include "conf.h"
+#include "externs.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "htab.h"
+#include "bufferq.h"
+#include "log.h"
+#include "confmagic.h"
+
+static char *quick_unparse(dbref object);
+static void start_log(FILE ** fp, const char *filename);
+static void end_log(const char *filename);
+
+BUFFERQ *activity_bq = NULL;
+
+HASHTAB htab_logfiles; /**< Hash table of logfile names and descriptors */
+
+/* log file pointers */
+FILE *connlog_fp;  /**< Connect log */
+FILE *checklog_fp; /**< Checkpoint log */
+FILE *wizlog_fp;   /**< Wizard log */
+FILE *tracelog_fp; /**< Trace log */
+FILE *cmdlog_fp;   /**< Command log */
+
+
+static char *
+quick_unparse(dbref object)
+{
+  static char buff[BUFFER_LEN], *bp;
+
+  switch (object) {
+  case NOTHING:
+    strcpy(buff, T("*NOTHING*"));
+    break;
+  case AMBIGUOUS:
+    strcpy(buff, T("*VARIABLE*"));
+    break;
+  case HOME:
+    strcpy(buff, T("*HOME*"));
+    break;
+  default:
+    bp = buff;
+    safe_format(buff, &bp, "%s(#%d%s)",
+               Name(object), object, unparse_flags(object, GOD));
+    *bp = '\0';
+  }
+
+  return buff;
+}
+
+static void
+start_log(FILE ** fp, const char *filename)
+{
+  char newfilename[256] = "\0";
+  static int ht_initialized = 0;
+  FILE *f;
+
+  if (!filename || !*filename) {
+    *fp = stderr;
+  } else {
+    if (!ht_initialized) {
+      hashinit(&htab_logfiles, 8, sizeof(FILE *));
+      ht_initialized = 1;
+    }
+    if ((f = (FILE *) hashfind(strupper(filename), &htab_logfiles))) {
+      /* We've already opened this file, so just use that pointer */
+      *fp = f;
+    } else {
+
+      /* Must use a buffer for MacOS file path conversion */
+      strncpy(newfilename, filename, 256);
+
+      *fp = fopen(newfilename, "a");
+      if (*fp == NULL) {
+       fprintf(stderr, T("WARNING: cannot open log %s\n"), newfilename);
+       *fp = stderr;
+      } else {
+       hashadd(strupper(filename), (void *) *fp, &htab_logfiles);
+       fprintf(*fp, "START OF LOG.\n");
+       fflush(*fp);
+      }
+    }
+  }
+}
+
+/** Open all logfiles.
+ */
+void
+start_all_logs(void)
+{
+  start_log(&connlog_fp, CONNLOG);
+  start_log(&checklog_fp, CHECKLOG);
+  start_log(&wizlog_fp, WIZLOG);
+  start_log(&tracelog_fp, TRACELOG);
+  start_log(&cmdlog_fp, CMDLOG);
+}
+
+/** Redirect stderr to a error log file. 
+ * Should be called after start_all_logs().
+ * \param log name of logfile to redirect stderr to.
+ */
+void
+redirect_stderr(void)
+{
+  FILE *errlog_fp;
+
+  fprintf(stderr, T("Redirecting stderr to %s\n"), ERRLOG);
+  errlog_fp = fopen(ERRLOG, "a");
+  if (!errlog_fp) {
+    fprintf(stderr, T("Unable to open %s. Error output to stderr.\n"), ERRLOG);
+  } else {
+
+    if (!freopen(ERRLOG, "a", stderr)) {
+      printf(T("Ack!  Failed reopening stderr!"));
+      exit(1);
+    }
+    setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
+    fclose(errlog_fp);
+  }
+}
+
+
+static void
+end_log(const char *filename)
+{
+  FILE *fp;
+  if (!filename || !*filename)
+    return;
+  if ((fp = (FILE *) hashfind(strupper(filename), &htab_logfiles))) {
+    fprintf(fp, "END OF LOG.\n");
+    fflush(fp);
+    fclose(fp);
+    hashdelete(strupper(filename), &htab_logfiles);
+  }
+}
+
+/** Close all logfiles.
+ */
+void
+end_all_logs(void)
+{
+  char *name, *next;
+  name = hash_firstentry_key(&htab_logfiles);
+  while (name) {
+    next = hash_nextentry_key(&htab_logfiles);
+    end_log(name);
+    name = next;
+  }
+}
+
+
+/** Log a raw message.
+ * take a log type and format list and args, write to appropriate logfile.
+ * log types are defined in log.h
+ * \param logtype type of log to print message to.
+ * \param fmt format string for message.
+ */
+void WIN32_CDECL
+do_rawlog(int logtype, const char *fmt, ...)
+{
+  struct tm *ttm;
+  char timebuf[18];
+  char tbuf1[BUFFER_LEN + 50];
+  va_list args;
+  FILE *f = NULL;
+  va_start(args, fmt);
+
+#ifdef HAS_VSNPRINTF
+  (void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
+#else
+  (void) vsprintf(tbuf1, fmt, args);
+#endif
+  tbuf1[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+
+  ttm = localtime(&mudtime);
+
+  strftime(timebuf, sizeof timebuf, "[%m/%d %H:%M:%S]", ttm);
+
+  switch (logtype) {
+  case LT_ERR:
+    f = stderr;
+    break;
+  case LT_HUH:
+  case LT_CMD:
+    start_log(&cmdlog_fp, CMDLOG);
+    f = cmdlog_fp;
+    break;
+  case LT_WIZ:
+    start_log(&wizlog_fp, WIZLOG);
+    f = wizlog_fp;
+    break;
+  case LT_CONN:
+    start_log(&connlog_fp, CONNLOG);
+    f = connlog_fp;
+    break;
+  case LT_TRACE:
+    start_log(&tracelog_fp, TRACELOG);
+    f = tracelog_fp;
+    break;
+  case LT_CHECK:
+    start_log(&checklog_fp, CHECKLOG);
+    f = checklog_fp;
+    break;
+  default:
+    f = stderr;
+    break;
+  }
+  fprintf(f, "%s %s\n", timebuf, tbuf1);
+  fflush(f);
+}
+
+/** Log a message, with useful information.
+ * take a log type and format list and args, write to appropriate logfile.
+ * log types are defined in log.h. Unlike do_rawlog, this version
+ * tags messages with prefixes, and uses dbref information passed to it.
+ * \param logtype type of log to print message to.
+ * \param player dbref that generated the log message.
+ * \param object second dbref involved in log message (e.g. force logs)
+ * \param fmt mesage format string.
+ */
+void WIN32_CDECL
+do_log(int logtype, dbref player, dbref object, const char *fmt, ...)
+{
+  /* tbuf1 had 50 extra chars because we might pass this function
+   * both a label string and a command which could be up to BUFFER_LEN
+   * in length - for example, when logging @forces
+   */
+  char tbuf1[BUFFER_LEN + 50];
+  va_list args;
+  char unp1[BUFFER_LEN], unp2[BUFFER_LEN];
+
+  va_start(args, fmt);
+
+#ifdef HAS_VSNPRINTF
+  (void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
+#else
+  (void) vsprintf(tbuf1, fmt, args);
+#endif
+  va_end(args);
+
+  switch (logtype) {
+  case LT_ERR:
+    do_rawlog(logtype, "RPT: %s", tbuf1);
+    break;
+  case LT_CMD:
+    strcpy(unp1, quick_unparse(player));
+    if (GoodObject(object)) {
+      strcpy(unp2, quick_unparse(object));
+      do_rawlog(logtype, T("CMD: %s %s / %s: %s"),
+               (Suspect(player) ? "SUSPECT" : ""), unp1, unp2, tbuf1);
+    } else {
+      strcpy(unp2, quick_unparse(Location(player)));
+      do_rawlog(logtype, T("CMD: %s %s in %s: %s"),
+               (Suspect(player) ? "SUSPECT" : ""), unp1, unp2, tbuf1);
+    }
+    break;
+  case LT_WIZ:
+    strcpy(unp1, quick_unparse(player));
+    if (GoodObject(object)) {
+      strcpy(unp2, quick_unparse(object));
+      do_rawlog(logtype, "WIZ: %s --> %s: %s", unp1, unp2, tbuf1);
+    } else {
+      do_rawlog(logtype, "WIZ: %s: %s", unp1, tbuf1);
+    }
+    break;
+  case LT_CONN:
+    do_rawlog(logtype, "NET: %s", tbuf1);
+    break;
+  case LT_TRACE:
+    do_rawlog(logtype, "TRC: %s", tbuf1);
+    break;
+  case LT_CHECK:
+    do_rawlog(logtype, "%s", tbuf1);
+    break;
+  case LT_HUH:
+    if (!controls(player, Location(player))) {
+      strcpy(unp1, quick_unparse(player));
+      strcpy(unp2, quick_unparse(Location(player)));
+      do_rawlog(logtype, T("HUH: %s in %s [%s]: %s"),
+               unp1, unp2,
+               (GoodObject(Location(player))) ?
+               Name(Owner(Location(player))) : T("bad object"), tbuf1);
+    }
+    break;
+  default:
+    do_rawlog(LT_ERR, "ERR: %s", tbuf1);
+  }
+}
+
+/** Wipe out a game log. This is intended for those emergencies where
+ * the log has grown out of bounds, overflowing the disk quota, etc.
+ * Because someone with the god password can use this command to wipe
+ * out 'intrusion' traces, we also require the log_wipe_passwd given
+ * in mush.cnf
+ * \param player the enactor.
+ * \param logtype type of log to wipe.
+ * \param str password for wiping logs.
+ */
+void
+do_logwipe(dbref player, int logtype, char *str)
+{
+  if (strcmp(str, LOG_WIPE_PASSWD)) {
+    const char *lname;
+    switch (logtype) {
+    case LT_CONN:
+      lname = "connection";
+      break;
+    case LT_CHECK:
+      lname = "checkpoint";
+      break;
+    case LT_CMD:
+      lname = "command";
+      break;
+    case LT_TRACE:
+      lname = "trace";
+      break;
+    case LT_WIZ:
+      lname = "wizard";
+      break;
+    default:
+      lname = "unspecified";
+    }
+    notify(player, T("Wrong password."));
+    do_log(LT_WIZ, player, NOTHING,
+          T("Invalid attempt to wipe the %s log, password %s"), lname, str);
+    return;
+  }
+  switch (logtype) {
+  case LT_CONN:
+    end_log(CONNLOG);
+    unlink(CONNLOG);
+    start_log(&connlog_fp, CONNLOG);
+    do_log(LT_ERR, player, NOTHING, T("Connect log wiped."));
+    break;
+  case LT_CHECK:
+    end_log(CHECKLOG);
+    unlink(CHECKLOG);
+    start_log(&checklog_fp, CHECKLOG);
+    do_log(LT_ERR, player, NOTHING, T("Checkpoint log wiped."));
+    break;
+  case LT_CMD:
+    end_log(CMDLOG);
+    unlink(CMDLOG);
+    start_log(&cmdlog_fp, CMDLOG);
+    do_log(LT_ERR, player, NOTHING, T("Command log wiped."));
+    break;
+  case LT_TRACE:
+    end_log(TRACELOG);
+    unlink(TRACELOG);
+    start_log(&tracelog_fp, TRACELOG);
+    do_log(LT_ERR, player, NOTHING, T("Trace log wiped."));
+    break;
+  case LT_WIZ:
+    end_log(WIZLOG);
+    unlink(WIZLOG);
+    start_log(&wizlog_fp, WIZLOG);
+    do_log(LT_ERR, player, NOTHING, T("Wizard log wiped."));
+    break;
+  default:
+    notify(player, T("That is not a valid log."));
+    return;
+  }
+  notify(player, T("Log wiped."));
+}
+
+
+/** Log a message to the activity log.
+ * \param type message type (an LA_* constant)
+ * \param player object responsible for the message.
+ * \param action message to log.
+ */
+void
+log_activity(int type, dbref player, const char *action)
+{
+  if (!activity_bq)
+    activity_bq = allocate_bufferq(ACTIVITY_LOG_SIZE);
+  add_to_bufferq(activity_bq, type, player, action);
+}
+
+/** Retrieve the last logged message from the activity log.
+ * \return last logged message or an empty string.
+ */
+const char *
+last_activity(void)
+{
+  if (!activity_bq)
+    return "";
+  else
+    return BufferQLast(activity_bq);
+}
+
+/** Retrieve the type of the last logged message from the activity log.
+ * \return last type of last logged message or -1.
+ */
+int
+last_activity_type(void)
+{
+  if (!activity_bq)
+    return -1;
+  else
+    return BufferQLastType(activity_bq);
+}
+
+
+/** Dump out (to a player or the error log) the activity buffer queue.
+ * \param player player to receive notification, if notifying.
+ * \param num_lines number of lines of buffer to dump (0 = all).
+ * \param dump if 1, dump to error log; if 0, notify player.
+ */
+void
+notify_activity(dbref player, int num_lines, int dump)
+{
+  int type;
+  dbref plr;
+  time_t timestamp;
+  char *buf;
+  char *p = NULL;
+  char *stamp;
+  int skip;
+  const char *typestr;
+
+  if (!activity_bq)
+    return;
+
+  if (dump || !num_lines)
+    num_lines = BufferQNum(activity_bq);
+  skip = BufferQNum(activity_bq) - num_lines;
+
+  if (dump)
+    do_rawlog(LT_ERR, "Dumping recent activity:");
+  else
+    notify(player, T("GAME: Recall from activity log"));
+
+  do {
+    buf = iter_bufferq(activity_bq, &p, &plr, &type, &timestamp);
+    if (skip <= 0) {
+      if (buf) {
+       stamp = show_time(timestamp, 0);
+       switch (type) {
+       case LA_CMD:
+         typestr = "CMD";
+         break;
+       case LA_PE:
+         typestr = "EXP";
+         break;
+       case LA_LOCK:
+         typestr = "LCK";
+         break;
+       default:
+         typestr = "???";
+         break;
+       }
+
+       if (dump)
+         do_rawlog(LT_ERR, "[%s/#%d/%s] %s", stamp, plr, typestr, buf);
+       else
+         notify_format(player, "[%s/#%d/%s] %s", stamp, plr, typestr, buf);
+      }
+    }
+    skip--;
+  } while (buf);
+
+
+  if (!dump)
+    notify(player, T("GAME: End recall"));
+}
diff --git a/src/look.c b/src/look.c
new file mode 100644 (file)
index 0000000..530e70f
--- /dev/null
@@ -0,0 +1,1657 @@
+/**
+ * \file look.c
+ *
+ * \brief Commands that look at things.
+ *
+ *
+ */
+
+#include "config.h"
+#include "copyrite.h"
+
+#include <string.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "attrib.h"
+#include "match.h"
+#include "ansi.h"
+#include "pueblo.h"
+#include "extchat.h"
+#include "game.h"
+#include "command.h"
+#include "parse.h"
+#include "privtab.h"
+#include "confmagic.h"
+#include "log.h"
+
+static void look_exits(dbref player, dbref loc, const char *exit_name);
+static void look_contents(dbref player, dbref loc, const char *contents_name);
+static void look_atrs(dbref player, dbref thing, const char *mstr, int all);
+static void mortal_look_atrs(dbref player, dbref thing, const char *mstr,
+                            int all);
+static void look_simple(dbref player, dbref thing);
+static void look_description(dbref player, dbref thing, const char *def,
+                            const char *descname, const char *descformatname);
+static int decompile_helper
+  (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr, void *args);
+static int look_helper
+  (dbref player, dbref thing, dbref parent, char const *pattern, ATTR *atr, void *args);
+static int look_helper_veiled
+  (dbref player, dbref thing, dbref target, char const *pattern, ATTR *atr, void *args);
+void decompile_atrs(dbref player, dbref thing, const char *name,
+                   const char *pattern, const char *prefix, int skipdef);
+void decompile_locks(dbref player, dbref thing, const char *name, int skipdef);
+
+extern PRIV attr_privs[];
+
+static void
+look_exits(dbref player, dbref loc, const char *exit_name)
+{
+  dbref thing, exec_target;
+  char *tbuf1, *tbuf2, *nbuf;
+  char *s1, *s2;
+  char *p;
+  int exit_count, this_exit, total_count;
+  ATTR *a;
+  int texits;
+  PUEBLOBUFF;
+
+  /* make sure location is a room */
+  if (!IsRoom(loc))
+    return;
+
+  tbuf1 = (char *) mush_malloc(BUFFER_LEN, "string");
+  tbuf2 = (char *) mush_malloc(BUFFER_LEN, "string");
+  nbuf = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!tbuf1 || !tbuf2 || !nbuf)
+    mush_panic("Unable to allocate memory in look_exits");
+  s1 = tbuf1;
+  s2 = tbuf2;
+  texits = exit_count = total_count = 0;
+  this_exit = 1;
+
+  a = atr_get(loc, "EXITFORMAT");
+  if (a) {
+    char *wsave[10], *rsave[NUMQ];
+    char *arg, *buff, *bp, *save;
+    char const *sp;
+    int j;
+
+    arg = (char *) mush_malloc(BUFFER_LEN, "string");
+    buff = (char *) mush_malloc(BUFFER_LEN, "string");
+    if (!arg || !buff)
+      mush_panic("Unable to allocate memory in look_exits");
+    save_global_regs("look_exits", rsave);
+    for (j = 0; j < 10; j++) {
+      wsave[j] = global_eval_context.wenv[j];
+      global_eval_context.wenv[j] = NULL;
+    }
+    for (j = 0; j < NUMQ; j++)
+      global_eval_context.renv[j][0] = '\0';
+    bp = arg;
+    DOLIST(thing, Exits(loc)) {
+      if (((Light(loc) || Light(thing)) || !(Dark(loc) || Dark(thing)))
+         && can_interact(thing, player, INTERACT_SEE)) {
+       if (bp != arg)
+         safe_chr(' ', arg, &bp);
+       safe_dbref(thing, arg, &bp);
+      }
+    }
+    *bp = '\0';
+    global_eval_context.wenv[0] = arg;
+    sp = save = safe_atr_value(a);
+    bp = buff;
+    if(AL_FLAGS(a) & AF_POWINHERIT)
+              exec_target = atr_on_obj;
+      else
+              exec_target = loc;
+
+    process_expression(buff, &bp, &sp, exec_target, ((AL_FLAGS(a) & AF_POWINHERIT ) ? loc : player), player,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    free((Malloc_t) save);
+    notify_by(loc, player, buff);
+    for (j = 0; j < 10; j++) {
+      global_eval_context.wenv[j] = wsave[j];
+    }
+    restore_global_regs("look_exits", rsave);
+    mush_free((Malloc_t) tbuf1, "string");
+    mush_free((Malloc_t) tbuf2, "string");
+    mush_free((Malloc_t) nbuf, "string");
+    mush_free((Malloc_t) arg, "string");
+    mush_free((Malloc_t) buff, "string");
+    return;
+  }
+  /* Scan the room and see if there are any visible exits */
+  if (Light(loc)) {
+    for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
+      total_count++;
+      if (!Transparented(loc) || Opaque(thing))
+       exit_count++;
+    }
+  } else if (Dark(loc)) {
+    for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
+      if (Light(thing) && can_interact(thing, player, INTERACT_SEE)) {
+       total_count++;
+       if (!Transparented(loc) || Opaque(thing))
+         exit_count++;
+      }
+    }
+  } else {
+    for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
+      if ((Light(thing) || !DarkLegal(thing)) &&
+         can_interact(thing, player, INTERACT_SEE)) {
+       total_count++;
+       if (!Transparented(loc) || Opaque(thing))
+         exit_count++;
+      }
+    }
+  }
+  if (total_count == 0) {
+    /* No visible exits. We are outta here */
+    mush_free((Malloc_t) tbuf1, "string");
+    mush_free((Malloc_t) tbuf2, "string");
+    mush_free((Malloc_t) nbuf, "string");
+    return;
+  }
+
+  PUSE;
+  tag_wrap("FONT", "SIZE=+1", exit_name);
+  PEND;
+  notify_by(loc, player, pbuff);
+
+  for (thing = Exits(loc); thing != NOTHING; thing = Next(thing)) {
+    if ((Light(loc) || Light(thing) || !(DarkLegal(thing) && !Dark(loc)))
+       && can_interact(thing, player, INTERACT_SEE)) {
+      strcpy(pbuff, Name(thing));
+      if ((p = strchr(pbuff, ';')))
+       *p = '\0';
+      p = nbuf;
+      safe_tag_wrap("A", tprintf("XCH_CMD=\"go #%d\"", thing), pbuff, nbuf, &p,
+                   NOTHING);
+      *p = '\0';
+      if (Transparented(loc) && !(Opaque(thing))) {
+       if (SUPPORT_PUEBLO && !texits) {
+         texits = 1;
+         notify_noenter_by(loc, player, tprintf("%cUL%c", TAG_START, TAG_END));
+       }
+       s1 = tbuf1;
+       safe_tag("LI", tbuf1, &s1);
+       safe_chr(' ', tbuf1, &s1);
+       if (Location(thing) == NOTHING)
+         safe_format(tbuf1, &s1, T("%s leads nowhere."), nbuf);
+       else if (Location(thing) == HOME)
+         safe_format(tbuf1, &s1, T("%s leads home."), nbuf);
+       else if (Location(thing) == AMBIGUOUS)
+         safe_format(tbuf1, &s1, T("%s leads to a variable location."), nbuf);
+       else if (!GoodObject(thing))
+         safe_format(tbuf1, &s1, T("%s is corrupt!"), nbuf);
+       else {
+         safe_format(tbuf1, &s1, T("%s leads to %s."), nbuf,
+                     Name(Location(thing)));
+       }
+       *s1 = '\0';
+       notify_nopenter_by(loc, player, tbuf1);
+      } else {
+       if (COMMA_EXIT_LIST) {
+         safe_itemizer(this_exit, (this_exit == exit_count),
+                       ",", T("and"), " ", tbuf2, &s2);
+         safe_str(nbuf, tbuf2, &s2);
+         this_exit++;
+       } else {
+         safe_str(nbuf, tbuf2, &s2);
+         safe_str("  ", tbuf2, &s2);
+       }
+      }
+    }
+  }
+  if (SUPPORT_PUEBLO && texits) {
+    PUSE;
+    tag_cancel("UL");
+    PEND;
+    notify_noenter_by(loc, player, pbuff);
+  }
+  *s2 = '\0';
+  notify_by(loc, player, tbuf2);
+  mush_free((Malloc_t) tbuf1, "string");
+  mush_free((Malloc_t) tbuf2, "string");
+  mush_free((Malloc_t) nbuf, "string");
+}
+
+
+static void
+look_contents(dbref player, dbref loc, const char *contents_name)
+{
+  dbref thing;
+  dbref can_see_loc;
+  dbref exec_target;
+  ATTR *a;
+  PUEBLOBUFF;
+
+  /* check to see if he can see the location */
+  /*
+   * patched so that player can't see in dark rooms even if owned by that
+   * player.  (he must use examine command)
+   */
+  can_see_loc = !Dark(loc);
+
+  a = atr_get(loc, "CONFORMAT");
+  if (a) {
+    char *wsave[10], *rsave[NUMQ];
+    char *arg, *buff, *bp, *save;
+    char *arg2, *bp2;
+    char const *sp;
+    int j;
+
+    arg = (char *) mush_malloc(BUFFER_LEN, "string");
+    arg2 = (char *) mush_malloc(BUFFER_LEN, "string");
+    buff = (char *) mush_malloc(BUFFER_LEN, "string");
+    if (!arg || !buff || !arg2)
+      mush_panic("Unable to allocate memory in look_contents");
+    save_global_regs("look_contents", rsave);
+    for (j = 0; j < 10; j++) {
+      wsave[j] = global_eval_context.wenv[j];
+      global_eval_context.wenv[j] = NULL;
+    }
+    for (j = 0; j < NUMQ; j++)
+      global_eval_context.renv[j][0] = '\0';
+    bp = arg;
+    bp2 = arg2;
+    DOLIST(thing, Contents(loc)) {
+      if (can_see(player, thing, can_see_loc)) {
+       if (bp != arg)
+         safe_chr(' ', arg, &bp);
+       safe_dbref(thing, arg, &bp);
+        if (bp2 != arg2)
+          safe_chr('|', arg2, &bp2);
+        safe_str(unparse_object_myopic(player, thing), arg2, &bp2);
+      }
+    }
+    *bp = '\0';
+    *bp2 = '\0';
+    global_eval_context.wenv[0] = arg;
+    global_eval_context.wenv[1] = arg2;
+    sp = save = safe_atr_value(a);
+    bp = buff;
+    if(AL_FLAGS(a) & AF_POWINHERIT)
+           exec_target = atr_on_obj; 
+    else
+           exec_target = loc;
+    process_expression(buff, &bp, &sp, exec_target, ((AL_FLAGS(a) & AF_POWINHERIT ) ? loc : player), player,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    free((Malloc_t) save);
+    notify_by(loc, player, buff);
+    for (j = 0; j < 10; j++) {
+      global_eval_context.wenv[j] = wsave[j];
+    }
+    restore_global_regs("look_contents", rsave);
+    mush_free((Malloc_t) arg, "string");
+    mush_free((Malloc_t) arg2, "string");
+    mush_free((Malloc_t) buff, "string");
+    return;
+  }
+  /* check to see if there is anything there */
+  DOLIST(thing, Contents(loc)) {
+    if (can_see(player, thing, can_see_loc)) {
+      /* something exists!  show him everything */
+      PUSE;
+      tag_wrap("FONT", "SIZE=+1", contents_name);
+      tag("UL");
+      PEND;
+      notify_nopenter_by(loc, player, pbuff);
+      DOLIST(thing, Contents(loc)) {
+       if (can_see(player, thing, can_see_loc)) {
+         PUSE;
+         tag("LI");
+         tag_wrap("A", tprintf("XCH_CMD=\"look #%d\"", thing),
+                  unparse_object_myopic(player, thing));
+         PEND;
+         notify_by(loc, player, pbuff);
+       }
+      }
+      PUSE;
+      tag_cancel("UL");
+      PEND;
+      notify_noenter_by(loc, player, pbuff);
+      break;                   /* we're done */
+    }
+  }
+}
+
+static int
+look_helper_veiled(dbref player, dbref thing __attribute__ ((__unused__)),
+                  dbref parent __attribute__ ((__unused__)),
+                  char const *pattern, ATTR *atr, void *args
+                  __attribute__ ((__unused__)))
+{
+  char fbuf[BUFFER_LEN];
+  char const *r;
+
+  if (EX_PUBLIC_ATTRIBS &&
+      !strcmp(AL_NAME(atr), "DESCRIBE") && !strcmp(pattern, "*"))
+    return 0;
+  strcpy(fbuf, privs_to_letters(attr_privs, AL_FLAGS(atr)));
+  if (atr_sub_branch(atr))
+    strcat(fbuf, "`");
+  if (AF_Veiled(atr)) {
+    if (ShowAnsi(player))
+      notify_format(player,
+                   "%s%s [#%d%s]%s is veiled", ANSI_HILITE, AL_NAME(atr),
+                   Owner(AL_CREATOR(atr)), fbuf, ANSI_NORMAL);
+    else
+      notify_format(player,
+                   "%s [#%d%s] is veiled", AL_NAME(atr),
+                   Owner(AL_CREATOR(atr)), fbuf);
+  } else {
+    r = safe_atr_value(atr);
+    if (ShowAnsi(player))
+      notify_format(player,
+                   "%s%s [#%d%s]:%s %s", ANSI_HILITE, AL_NAME(atr),
+                   Owner(AL_CREATOR(atr)), fbuf, ANSI_NORMAL, r);
+    else
+      notify_format(player, "%s [#%d%s]: %s", AL_NAME(atr),
+                   Owner(AL_CREATOR(atr)), fbuf, r);
+    /* Show the Lock if it has one */
+    if(!(AL_RLock(atr) == TRUE_BOOLEXP))
+      notify_format(player, "Readlock: %s", unparse_boolexp(player, AL_RLock(atr), UB_ALL ));
+    if(!(AL_WLock(atr) == TRUE_BOOLEXP))
+      notify_format(player, "Writelock: %s", unparse_boolexp(player, AL_WLock(atr), UB_ALL ));
+
+    free((Malloc_t) r);
+  }
+  return 1;
+}
+
+static int
+look_helper(dbref player, dbref thing __attribute__ ((__unused__)),
+            dbref parent __attribute__ ((__unused__)),
+           char const *pattern, ATTR *atr, void *args
+           __attribute__ ((__unused__)))
+{
+  char fbuf[BUFFER_LEN];
+  char const *r;
+
+  if (EX_PUBLIC_ATTRIBS &&
+      !strcmp(AL_NAME(atr), "DESCRIBE") && !strcmp(pattern, "*"))
+    return 0;
+  strcpy(fbuf, privs_to_letters(attr_privs, AL_FLAGS(atr)));
+  if (atr_sub_branch(atr))
+    strcat(fbuf, "`");
+  r = safe_atr_value(atr);
+  if (ShowAnsi(player))
+    notify_format(player,
+                 "%s%s [#%d%s]:%s %s", ANSI_HILITE, AL_NAME(atr),
+                 Owner(AL_CREATOR(atr)), fbuf, ANSI_NORMAL, r);
+  else
+    notify_format(player, "%s [#%d%s]: %s", AL_NAME(atr),
+                 Owner(AL_CREATOR(atr)), fbuf, r);
+  free((Malloc_t) r);
+  return 1;
+}
+
+static void
+look_atrs(dbref player, dbref thing, const char *mstr, int all)
+{
+  if (all || (mstr && *mstr && !wildcard(mstr))) {
+    if (!atr_iter_get(player, thing, mstr, 0, look_helper, NULL) && mstr)
+      notify(player, T("No matching attributes."));
+  } else {
+    if (!atr_iter_get(player, thing, mstr, 0, look_helper_veiled, NULL) && mstr)
+      notify(player, T("No matching attributes."));
+  }
+}
+
+static void
+mortal_look_atrs(dbref player, dbref thing, const char *mstr, int all)
+{
+  if (all || (mstr && *mstr && !wildcard(mstr))) {
+    if (!atr_iter_get(player, thing, mstr, 1, look_helper, NULL) && mstr)
+      notify(player, T("No matching attributes."));
+  } else {
+    if (!atr_iter_get(player, thing, mstr, 1, look_helper_veiled, NULL)
+       && mstr)
+      notify(player, T("No matching attributes."));
+  }
+}
+
+static void
+look_simple(dbref player, dbref thing)
+{
+  int flag = 0;
+  PUEBLOBUFF;
+
+  PUSE;
+  tag_wrap("FONT", "SIZE=+2", unparse_object_myopic(player, thing));
+  PEND;
+  notify(player, pbuff);
+  look_description(player, thing, T("You see nothing special."), "DESCRIBE",
+                  "DESCFORMAT");
+  did_it(player, thing, NULL, NULL, "ODESCRIBE", NULL, "ADESCRIBE", NOTHING);
+  if (IsExit(thing) && Transparented(thing)) {
+    if (Cloudy(thing))
+      flag = 3;
+    else
+      flag = 1;
+  } else if (Cloudy(thing))
+    flag = 4;
+  if (flag) {
+    if (Location(thing) == HOME)
+      look_room(player, Home(player), flag);
+    else if (GoodObject(thing) && GoodObject(Destination(thing)))
+      look_room(player, Destination(thing), flag);
+  }
+}
+
+/** Look at a room.
+ * The style parameter tells you what kind of look it is:
+ * LOOK_NORMAL (caused by "look"), LOOK_TRANS (look through a transparent
+ * exit), LOOK_AUTO (automatic look, by moving),
+ * LOOK_CLOUDY (look through a cloudy exit - contents only), LOOK_CLOUDYTRANS
+ * (look through a cloudy transparent exit - desc only).
+ * \param player the looker.
+ * \param loc room being looked at.
+ * \param style how the room is being looked at.
+ */
+void
+look_room(dbref player, dbref loc, enum look_type style)
+{
+
+  PUEBLOBUFF;
+  ATTR *a;
+
+  if (loc == NOTHING)
+    return;
+
+#ifdef RPMODE_SYS
+  if(Blind(player) && RPMODE(player) && ICRoom(loc)) {
+     notify(player, "You are temporarily blind.");
+     return;
+  }
+#endif
+
+
+  /* don't give the unparse if looking through Transparent exit */
+  if (style == LOOK_NORMAL || style == LOOK_AUTO) {
+    PUSE;
+    tag("XCH_PAGE CLEAR=\"LINKS PLUGINS\"");
+    if (SUPPORT_PUEBLO && style == LOOK_AUTO) {
+      a = atr_get(loc, "VRML_URL");
+      if (a) {
+       tag(tprintf("IMG XCH_GRAPH=LOAD HREF=\"%s\"", atr_value(a)));
+      } else {
+       tag("IMG XCH_GRAPH=HIDE");
+      }
+    }
+    tag("HR");
+    tag_wrap("FONT", "SIZE=+3", unparse_room(player, loc));
+    PEND;
+    notify_by(loc, player, pbuff);
+  }
+  if (!IsRoom(loc)) {
+    if (style != LOOK_AUTO || !Terse(player)) {
+      if (atr_get(loc, "IDESCRIBE")) {
+       look_description(player, loc, NULL, "IDESCRIBE", "IDESCFORMAT");
+       did_it(player, loc, NULL, NULL, "OIDESCRIBE", NULL,
+              "AIDESCRIBE", NOTHING);
+      } else if (atr_get(loc, "IDESCFORMAT")) {
+       look_description(player, loc, NULL, "DESCRIBE", "IDESCFORMAT");
+      } else
+       look_description(player, loc, NULL, "DESCRIBE", "DESCFORMAT");
+    }
+  }
+  /* tell him the description */
+  else {
+    if (style == LOOK_NORMAL || style == LOOK_AUTO) {
+      if (style == LOOK_NORMAL || !Terse(player)) {
+       look_description(player, loc, NULL, "DESCRIBE", "DESCFORMAT");
+       did_it(player, loc, NULL, NULL, "ODESCRIBE", NULL,
+              "ADESCRIBE", NOTHING);
+      } else
+       did_it(player, loc, NULL, NULL, "ODESCRIBE", NULL, "ADESCRIBE",
+              NOTHING);
+    } else if (style != LOOK_CLOUDY)
+      look_description(player, loc, NULL, "DESCRIBE", "DESCFORMAT");
+  }
+  /* tell him the appropriate messages if he has the key */
+  if (IsRoom(loc) && (style == LOOK_NORMAL || style == LOOK_AUTO)) {
+    if (style == LOOK_AUTO && Terse(player)) {
+      if (could_doit(player, loc))
+       did_it(player, loc, NULL, NULL, "OSUCCESS", NULL, "ASUCCESS", NOTHING);
+      else
+       did_it(player, loc, NULL, NULL, "OFAILURE", NULL, "AFAILURE", NOTHING);
+    } else if (could_doit(player, loc))
+      did_it(player, loc, "SUCCESS", NULL, "OSUCCESS", NULL, "ASUCCESS",
+            NOTHING);
+    else
+      fail_lock(player, loc, Basic_Lock, NULL, NOTHING);
+  }
+  /* tell him the contents */
+  if (style != LOOK_CLOUDYTRANS)
+    look_contents(player, loc, T("Contents:"));
+  if (style == LOOK_NORMAL || style == LOOK_AUTO) {
+    look_exits(player, loc, T("Obvious exits:"));
+  }
+}
+
+static void
+look_description(dbref player, dbref thing, const char *def,
+                const char *descname, const char *descformatname)
+{
+  /* Show thing's description to player, obeying DESCFORMAT if set */
+  ATTR *a, *f;
+  char *preserveq[NUMQ];
+  char *preserves[10];
+  char buff[BUFFER_LEN], fbuff[BUFFER_LEN];
+  char *bp, *fbp, *asave;
+  char const *ap;
+
+  if (!GoodObject(player) || !GoodObject(thing))
+    return;
+  save_global_regs("look_desc_save", preserveq);
+  save_global_env("look_desc_save", preserves);
+  a = atr_get(thing, descname);
+  if (a) {
+    /* We have a DESCRIBE, evaluate it into buff */
+    asave = safe_atr_value(a);
+    ap = asave;
+    bp = buff;
+    process_expression(buff, &bp, &ap, thing, player, player,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    free((Malloc_t) asave);
+  }
+  f = atr_get(thing, descformatname);
+  if (f) {
+    /* We have a DESCFORMAT, evaluate it into fbuff and use it */
+    /* If we have a DESCRIBE, pass the evaluated version as %0 */
+    global_eval_context.wenv[0] = a ? buff : NULL;
+    asave = safe_atr_value(f);
+    ap = asave;
+    fbp = fbuff;
+    process_expression(fbuff, &fbp, &ap, thing, player, player,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *fbp = '\0';
+    free((Malloc_t) asave);
+    notify_by(thing, player, fbuff);
+  } else if (a) {
+    /* DESCRIBE only */
+    notify_by(thing, player, buff);
+  } else if (def) {
+    /* Nothing, go with the default message */
+    notify_by(thing, player, def);
+  }
+  restore_global_regs("look_desc_save", preserveq);
+  restore_global_env("look_desc_save", preserves);
+}
+
+/** An automatic look (due to motion).
+ * \param player the looker.
+ */
+void
+do_look_around(dbref player)
+{
+  dbref loc;
+  if ((loc = Location(player)) == NOTHING)
+    return;
+  look_room(player, loc, LOOK_AUTO);   /* auto-look. Obey TERSE. */
+}
+
+/** Look at something.
+ * \param player the looker.
+ * \param name name of object to look at.
+ * \param key 0 for normal look, 1 for look/outside.
+ */
+void
+do_look_at(dbref player, const char *name, int key)
+{
+  dbref thing;
+  dbref loc;
+
+  if (!GoodObject(Location(player)))
+    return;
+
+  if (key) {                   /* look outside */
+    /* can't see through opaque objects */
+    if (IsRoom(Location(player)) || Opaque(Location(player))) {
+      notify(player, T("You can't see through that."));
+      return;
+    }
+    loc = Location(Location(player));
+
+    if (!GoodObject(loc))
+      return;
+
+    /* look at location of location */
+    if (*name == '\0') {
+      look_room(player, loc, LOOK_NORMAL);
+      return;
+    }
+    thing =
+      match_result(loc, name, NOTYPE,
+                  MAT_PLAYER | MAT_REMOTE_CONTENTS | MAT_EXIT | MAT_REMOTES);
+    if (thing == NOTHING) {
+      notify(player, T("I don't see that here."));
+      return;
+    } else if (thing == AMBIGUOUS) {
+      notify(player, T("I don't know which one you mean."));
+      return;
+    }
+  } else {                     /* regular look */
+    if (*name == '\0') {
+        look_room(player, Location(player), LOOK_NORMAL);
+      return;
+    }
+    /* look at a thing in location */
+    if ((thing = match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING) {
+      dbref box;
+      const char *boxname = name;
+      box = parse_match_possessor(player, &name);
+      if (box == NOTHING) {
+       notify(player, T("I don't see that here."));
+       return;
+      } else if (box == AMBIGUOUS) {
+       notify_format(player, T("I can't tell which %s."), boxname);
+       return;
+      }
+      thing = match_result(box, name, NOTYPE, MAT_POSSESSION);
+      if (thing == NOTHING) {
+       notify(player, T("I don't see that here."));
+       return;
+      } else if (thing == AMBIGUOUS) {
+       notify_format(player, T("I can't tell which %s."), name);
+       return;
+      }
+      if (Opaque(Location(thing)) &&
+         (!CanSee(player, Location(thing)) &&
+          !controls(player, thing) && !controls(player, Location(thing)))) {
+       notify(player, T("You can't look at that from here."));
+       return;
+      }
+    } else if (thing == AMBIGUOUS) {
+      notify(player, T("I can't tell which one you mean."));
+      return;
+    }
+  }
+
+  /* once we've determined the object to look at, it doesn't matter whether
+   * this is look or look/outside.
+   */
+
+  /* we need to check for the special case of a player doing 'look here'
+   * while inside an object.
+   */
+  if (Location(player) == thing) {
+    look_room(player, thing, LOOK_NORMAL);
+    return;
+  }
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    look_room(player, thing, LOOK_NORMAL);
+    break;
+  case TYPE_DIVISION:
+  case TYPE_THING:
+  case TYPE_PLAYER:
+#ifdef RPMODE_SYS
+    if(Blind(player) && RPMODE(player) && ICRoom(Location(player))
+       && ((IsPlayer(thing) && RPMODE(thing))
+               || (IsThing(thing) && RPAPPROVED(thing)))) {
+      notify(player, "You are temporarily blind.");
+      break;
+    }
+#endif
+    look_simple(player, thing);
+    if (!(Opaque(thing)))
+      look_contents(player, thing, "Carrying:");
+    break;
+  default:
+    look_simple(player, thing);
+    break;
+  }
+}
+
+
+/** Examine an object.
+ * \param player the enactor doing the examining.
+ * \param name name of object to examine.
+ * \param brief if 1, a brief examination. if 2, a mortal examination.
+ * \param all if 1, include veiled attributes.
+ */
+void
+do_examine(dbref player, const char *name, enum exam_type flag, int all)
+{
+  dbref thing;
+  ATTR *a;
+  char *r;
+  dbref content;
+  dbref exit_dbref;
+  dbref non_div;
+  const char *real_name = NULL;
+  char *attrib_name = NULL;
+  char *tp;
+  char *tbuf;
+  int ok = 0;
+  int listed = 0;
+  PUEBLOBUFF;
+
+  if (*name == '\0') {
+    if ((thing = Location(player)) == NOTHING)
+      return;
+    attrib_name = NULL;
+  } else {
+
+    if ((attrib_name = strchr(name, '/')) != NULL) {
+      *attrib_name = '\0';
+      attrib_name++;
+    }
+    real_name = name;
+    /* look it up */
+
+    if ((thing =
+        noisy_match_result(player, real_name, NOTYPE,
+                           MAT_EVERYTHING)) == NOTHING)
+      return;
+  }
+
+  /*  can't examine destructed objects  */
+  if (IsGarbage(thing)) {
+    notify(player, T("Garbage is garbage."));
+    return;
+  }
+  /*  only look at some of the attributes */
+  if (attrib_name && *attrib_name) {
+    look_atrs(player, thing, attrib_name, all);
+    return;
+  }
+  if (flag == EXAM_MORTAL) {
+    ok = 0;
+  } else {
+    ok = Can_Examine(player, thing);
+  }
+
+  tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!ok && (!EX_PUBLIC_ATTRIBS || !nearby(player, thing))) {
+    /* if it's not examinable and we're not near it, we can only get the
+     * name and the owner.
+     */
+    tp = tbuf;
+    safe_str(object_header(player, thing), tbuf, &tp);
+    safe_str(T(" is owned by "), tbuf, &tp);
+    safe_str(object_header(player, Owner(thing)), tbuf, &tp);
+    *tp = '\0';
+    notify(player, tbuf);
+    mush_free((Malloc_t) tbuf, "string");
+    return;
+  }
+  if (ok) {
+    PUSE;
+    tag_wrap("FONT", "SIZE=+3", object_header(player, thing));
+    PEND;
+    notify(player, pbuff);
+    if (FLAGS_ON_EXAMINE)
+      notify(player, flag_description(player, thing));
+  }
+  if (EX_PUBLIC_ATTRIBS && (flag != EXAM_BRIEF)) {
+    a = atr_get_noparent(thing, "DESCRIBE");
+    if (a) {
+      r = safe_atr_value(a);
+      notify(player, r);
+      free((Malloc_t) r);
+    }
+  }
+  if (ok) {
+    notify_format(player,
+                 T("Owner: %s  Zone: %s  %s: %d"),
+                 Name(Owner(thing)),
+                 object_header(player, Zone(thing)), MONIES, Pennies(thing));
+    notify_format(player, T("Division: %s  Level: %d"),
+                 object_header(player, SDIV(thing).object),
+                 LEVEL(thing));
+    notify_format(player,
+                 T("Parent: %s"), object_header(player, Parent(thing)));
+    {
+      struct lock_list *ll;
+      for (ll = Locks(thing); ll; ll = ll->next) {
+       notify_format(player, T("%s Lock [#%d%s]: %s"),
+                     L_TYPE(ll), L_CREATOR(ll), lock_flags(ll),
+                     unparse_boolexp(player, L_KEY(ll), UB_ALL));
+      }
+    }
+    notify_format(player, T("Powergroups: %s"), powergroups_list_on(thing, 0));
+    notify_format(player, T("Powers: %s"), division_list_powerz(thing, 1));
+#ifdef CHAT_SYSTEM
+    notify(player, channel_description(thing));
+#endif /* CHAT_SYSTEM */
+
+    notify_format(player, T("Warnings checked: %s"),
+                 unparse_warnings(Warnings(thing)));
+
+    notify_format(player, T("Created: %s"), show_time(CreTime(thing), 0));
+    if (!IsPlayer(thing)) {
+      notify_format(player, T("Last Modification: %s"),
+                   show_time(ModTime(thing), 0));
+      if((Owner(thing) == Owner(player) )|| Director(player))
+        notify_format(player, T("Modified: %s"), LastMod(thing));
+    }
+  }
+
+  /* show attributes */
+  switch (flag) {
+  case EXAM_NORMAL:            /* Standard */
+    if (EX_PUBLIC_ATTRIBS || ok)
+      look_atrs(player, thing, NULL, all);
+    break;
+  case EXAM_BRIEF:             /* Brief */
+    break;
+  case EXAM_MORTAL:            /* Mortal */
+    if (EX_PUBLIC_ATTRIBS)
+      mortal_look_atrs(player, thing, NULL, all);
+    break;
+  }
+
+  /* Show Sub-Divisions if its a division  */
+  non_div =  Contents(thing);
+  if((Contents(thing) != NOTHING) && IsDivision(thing)) {
+    non_div = NOTHING;
+    DOLIST_VISIBLE(content, Contents(thing), (ok) ? GOD : player) {
+      if(IsDivision(content)) {
+       if(!listed) {
+         listed = 1;
+         notify(player, T("Sub-Divisions:"));
+       }
+       notify(player, object_header(player, content));
+      } else non_div = content;
+    }
+    listed = 0;
+  }
+
+  /* show contents */
+  if ((non_div != NOTHING) &&
+      (ok || (!IsRoom(thing) && !(Location(thing) != NOTHING  && IsDivision(Location(thing)) && IsDivision(thing)) && !Opaque(thing)))) {
+    listed = 0;
+    DOLIST_VISIBLE(content, Contents(thing), (ok) ? GOD : player) {
+      if(IsDivision(content) && IsDivision(thing))
+       continue;
+      if (!listed) {
+       listed = 1;
+       if (IsPlayer(thing))
+         notify(player, T("Carrying:"));
+       else
+         notify(player, T("Contents:"));
+      }
+      notify(player, object_header(player, content));
+    }
+  }
+  if (!ok) {
+    /* if not examinable, just show obvious exits and name and owner */
+    if (IsRoom(thing))
+      look_exits(player, thing, T("Obvious exits:"));
+    tp = tbuf;
+    safe_str(object_header(player, thing), tbuf, &tp);
+    safe_str(T(" is owned by "), tbuf, &tp);
+    safe_str(object_header(player, Owner(thing)), tbuf, &tp);
+    *tp = '\0';
+    notify(player, tbuf);
+    mush_free((Malloc_t) tbuf, "string");
+    return;
+  }
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    /* tell him about exits */
+    if (Exits(thing) != NOTHING) {
+      notify(player, T("Exits:"));
+      DOLIST(exit_dbref, Exits(thing))
+       notify(player, object_header(player, exit_dbref));
+    } else
+      notify(player, T("No exits."));
+    /* print dropto if present */
+    if (Location(thing) != NOTHING) {
+      notify_format(player,
+                   T("Dropped objects go to: %s"),
+                   object_header(player, Location(thing)));
+    }
+    break;
+  case TYPE_DIVISION:
+  case TYPE_THING:
+  case TYPE_PLAYER:
+    /* print home */
+    notify_format(player, T("Home: %s"), object_header(player, Home(thing)));  /* home */
+    /* print location if player can link to it */
+    if (Location(thing) != NOTHING)
+      notify_format(player,
+                   T("Location: %s"), object_header(player, Location(thing)));
+    break;
+  case TYPE_EXIT:
+    /* print source */
+    switch (Source(thing)) {
+    case NOTHING:
+      do_rawlog(LT_ERR,
+               T
+               ("*** BLEAH *** Weird exit %s(#%d) in #%d with source NOTHING."),
+               Name(thing), thing, Destination(thing));
+      break;
+    case AMBIGUOUS:
+      do_rawlog(LT_ERR,
+               T("*** BLEAH *** Weird exit %s(#%d) in #%d with source AMBIG."),
+               Name(thing), thing, Destination(thing));
+      break;
+    case HOME:
+      do_rawlog(LT_ERR,
+               T("*** BLEAH *** Weird exit %s(#%d) in #%d with source HOME."),
+               Name(thing), thing, Destination(thing));
+      break;
+    default:
+      notify_format(player,
+                   T("Source: %s"), object_header(player, Source(thing)));
+      break;
+    }
+    /* print destination */
+    switch (Destination(thing)) {
+    case NOTHING:
+      notify(player, T("Destination: *UNLINKED*"));
+      break;
+    case HOME:
+      notify(player, T("Destination: *HOME*"));
+      break;
+    default:
+      notify_format(player,
+                   T("Destination: %s"),
+                   object_header(player, Destination(thing)));
+      break;
+    }
+    break;
+  default:
+    /* do nothing */
+    break;
+  }
+  mush_free((Malloc_t) tbuf, "string");
+}
+
+/** The score command: check a player's money.
+ * \param player the enactor.
+ */
+void
+do_score(dbref player)
+{
+
+  notify_format(player,
+               T("You have %d %s."),
+               Pennies(player), Pennies(player) == 1 ? MONEY : MONIES);
+}
+
+/** The inventory command.
+ * \param player the enactor.
+ */
+void
+do_inventory(dbref player)
+{
+  dbref thing;
+  dbref exec_target;
+  ATTR *invfmt; /* INVFORMAT */
+
+
+  invfmt = atr_get(player, "INVFORMAT");
+  
+  if(invfmt) {
+    /* %0 - Pennies
+     * %1 - Content List
+     */
+    char *wsave[10], *rsave[NUMQ];
+    char *arg, *buff, *bp, *save;
+    char *arg2, *bp2;
+    char const *sp;
+    int j;
+
+    arg = (char *) mush_malloc(BUFFER_LEN, "string");
+    arg2 = (char *) mush_malloc(BUFFER_LEN, "string");
+    buff = (char *) mush_malloc(BUFFER_LEN, "string");
+    if(!arg || !buff || !arg2)
+      mush_panic("Unable to allocate memory in do_inventory");
+    save_global_regs("do_inventory", rsave);
+    for(j = 0 ; j < 10 ; j++) {
+      wsave[j] = global_eval_context.wenv[j];
+      global_eval_context.wenv[j] = NULL;
+    }
+
+    for(j = 0; j < NUMQ ; j++)
+      global_eval_context.renv[j][0] = '\0';
+    bp = arg;
+    bp2 = arg2;
+    DOLIST(thing, Contents(player)) {
+      if (bp != arg)
+       safe_chr(' ', arg, &bp);
+      safe_dbref(thing, arg, &bp);
+      if (bp2 != arg2)
+       safe_chr('|', arg2, &bp2);
+      safe_str(unparse_object_myopic(player, thing), arg2, &bp2);
+    }
+    *bp = '\0';
+    *bp2 = '\0';
+    global_eval_context.wenv[0] = arg;
+    global_eval_context.wenv[1] = arg2;
+    sp = save = safe_atr_value(invfmt);
+    if(AL_FLAGS(invfmt) & AF_POWINHERIT)
+      exec_target = atr_on_obj;
+    else
+      exec_target = player;
+    bp = buff;
+    process_expression(buff, &bp, &sp, exec_target, player, player, PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    notify_by(player, player, buff);
+    free((Malloc_t) save);
+    for(j = 0 ; j < 10 ; j++)
+      global_eval_context.wenv[j]  = wsave[j];
+    restore_global_regs("do_inventory", rsave);
+    mush_free((Malloc_t) arg, "string");
+    mush_free((Malloc_t) arg2, "string");
+    mush_free((Malloc_t) buff, "string");
+    return;
+  } else {
+  if ((thing = Contents(player)) == NOTHING) {
+      notify(player, T("You aren't carrying anything."));
+  } else {
+    notify(player, T("You are carrying:"));
+    DOLIST(thing, thing) {
+      notify(player, unparse_object_myopic(player, thing));
+    }
+  }
+
+  do_score(player);
+  }
+}
+
+/** The find command.
+ * \param player the enactor.
+ * \param name name pattern to search for.
+ * \param argv array of additional arguments (for dbref ranges)
+ */
+void
+do_find(dbref player, const char *name, char *argv[])
+{
+  dbref i;
+  int count = 0;
+  int bot = 0;
+  int top = db_top;
+
+  if (!payfor(player, FIND_COST)) {
+    notify_format(player, T("Finds cost %d %s."), FIND_COST,
+                 ((FIND_COST == 1) ? MONEY : MONIES));
+    return;
+  }
+  /* determinte range */
+  if (argv[1] && *argv[1]) {
+    size_t offset = 0;
+    if (argv[1][0] == '#')
+      offset = 1;
+    bot = parse_integer(argv[1] + offset);
+    if (!GoodObject(bot)) {
+      notify(player, T("Invalid range argument"));
+      return;
+    }
+  }
+  if (argv[2] && *argv[2]) {
+    size_t offset = 0;
+    if (argv[2][0] == '#')
+      offset = 1;
+    top = parse_integer(argv[2] + offset);
+    if (!GoodObject(top)) {
+      notify(player, T("Invalid range argument"));
+      return;
+    }
+  }
+
+  for (i = bot; i < top; i++) {
+    if (!IsGarbage(i) && !IsExit(i) && controls(player, i) &&
+       (!*name || string_match(Name(i), name))) {
+      notify(player, object_header(player, i));
+      count++;
+    }
+  }
+  notify_format(player, T("*** %d objects found ***"), count);
+}
+
+/** Sweep the current location for bugs.
+ * \verbatim
+ * This implements @sweep.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 optional area to sweep.
+ */
+void
+do_sweep(dbref player, const char *arg1)
+{
+  char tbuf1[BUFFER_LEN];
+  char *p;
+  dbref here = Location(player);
+  int connect_flag = 0;
+  int here_flag = 0;
+  int inven_flag = 0;
+  int exit_flag = 0;
+
+  if (here == NOTHING)
+    return;
+
+  if (arg1 && *arg1) {
+    if (string_prefix(arg1, "connected"))
+      connect_flag = 1;
+    else if (string_prefix(arg1, "here"))
+      here_flag = 1;
+    else if (string_prefix(arg1, "inventory"))
+      inven_flag = 1;
+    else if (string_prefix(arg1, "exits"))
+      exit_flag = 1;
+    else {
+      notify(player, T("Invalid parameter."));
+      return;
+    }
+  }
+  if (!inven_flag && !exit_flag) {
+    notify(player, T("Listening in ROOM:"));
+
+    if (connect_flag) {
+      /* only worry about puppet and players who's owner's are connected */
+      if (Connected(here) || (Puppet(here) && Connected(Owner(here)))) {
+       if (IsPlayer(here)) {
+         notify_format(player, T("%s is listening."), Name(here));
+       } else {
+         notify_format(player, T("%s [owner: %s] is listening."),
+                       Name(here), Name(Owner(here)));
+       }
+      }
+    } else {
+      if (Hearer(here) || Listener(here)) {
+       if (Connected(here))
+         notify_format(player, T("%s (this room) [speech]. (connected)"),
+                       Name(here));
+       else
+         notify_format(player, T("%s (this room) [speech]."), Name(here));
+      }
+      if (Commer(here))
+       notify_format(player, T("%s (this room) [commands]."), Name(here));
+      if (Audible(here))
+       notify_format(player, T("%s (this room) [broadcasting]."), Name(here));
+    }
+
+    for (here = Contents(here); here != NOTHING; here = Next(here)) {
+      if (connect_flag) {
+       /* only worry about puppet and players who's owner's are connected */
+       if (Connected(here) || (Puppet(here) && Connected(Owner(here)))) {
+         if (IsPlayer(here)) {
+           notify_format(player, T("%s is listening."), Name(here));
+         } else {
+           notify_format(player, T("%s [owner: %s] is listening."),
+                         Name(here), Name(Owner(here)));
+         }
+       }
+      } else {
+       if (Hearer(here) || Listener(here)) {
+         if (Connected(here))
+           notify_format(player, "%s [speech]. (connected)", Name(here));
+         else
+           notify_format(player, "%s [speech].", Name(here));
+       }
+       if (Commer(here))
+         notify_format(player, "%s [commands].", Name(here));
+      }
+    }
+  }
+  if (!connect_flag && !inven_flag && IsRoom(Location(player))) {
+    notify(player, T("Listening EXITS:"));
+    if (Audible(Location(player))) {
+      /* listening exits only work if the room is AUDIBLE */
+      for (here = Exits(Location(player)); here != NOTHING; here = Next(here)) {
+       if (Audible(here)) {
+         strcpy(tbuf1, Name(here));
+         for (p = tbuf1; *p && (*p != ';'); p++) ;
+         *p = '\0';
+         notify_format(player, "%s [broadcasting].", tbuf1);
+       }
+      }
+    }
+  }
+  if (!here_flag && !exit_flag) {
+    notify(player, T("Listening in your INVENTORY:"));
+
+    for (here = Contents(player); here != NOTHING; here = Next(here)) {
+      if (connect_flag) {
+       /* only worry about puppet and players who's owner's are connected */
+       if (Connected(here) || (Puppet(here) && Connected(Owner(here)))) {
+         if (IsPlayer(here)) {
+           notify_format(player, T("%s is listening."), Name(here));
+         } else {
+           notify_format(player, T("%s [owner: %s] is listening."),
+                         Name(here), Name(Owner(here)));
+         }
+       }
+      } else {
+       if (Hearer(here) || Listener(here)) {
+         if (Connected(here))
+           notify_format(player, "%s [speech]. (connected)", Name(here));
+         else
+           notify_format(player, "%s [speech].", Name(here));
+       }
+       if (Commer(here))
+         notify_format(player, "%s [commands].", Name(here));
+      }
+    }
+  }
+}
+
+/** Locate a player.
+ * \verbatim
+ * This implements @whereis.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of player to locate.
+ */
+void
+do_whereis(dbref player, const char *name)
+{
+  dbref thing;
+  if (*name == '\0') {
+    notify(player, T("You must specify a valid player name."));
+    return;
+  }
+  if ((thing = lookup_player(name)) == NOTHING) {
+    notify(player, T("That player does not seem to exist."));
+    return;
+  }
+  if (!Can_Locate(player, thing)) {
+    notify(player, T("That player wishes to have some privacy."));
+    notify_format(thing, T("%s tried to locate you and failed."), Name(player));
+    return;
+  }
+  notify_format(player,
+               T("%s is at: %s."), Name(thing),
+               unparse_object(player, Location(thing)));
+  if (!CanSee(player, thing))
+    notify_format(thing, T("%s has just located your position."), Name(player));
+  return;
+
+}
+
+/** Find the entrances to a room.
+ * \verbatim
+ * This implements @entrances, which finds things linked to an object
+ * (typically exits, but can be any type).
+ * \endverbatim
+ * \param player the enactor.
+ * \param where name of object to find entrances on.
+ * \param argv array of arguments for dbref range limitation.
+ * \param val what type of 'entrances' to find.
+ */
+void
+do_entrances(dbref player, const char *where, char *argv[], enum ent_type val)
+{
+  dbref place;
+  dbref counter;
+  int exc, tc, pc, rc;         /* how many we've found */
+  int exd, td, pd, rd;         /* what we're looking for */
+  int bot = 0;
+  int top = db_top;
+
+  exc = tc = pc = rc = exd = td = pd = rd = 0;
+
+  if (!where || !*where) {
+    if ((place = Location(player)) == NOTHING)
+      return;
+  } else {
+    if ((place = noisy_match_result(player, where, NOTYPE, MAT_EVERYTHING))
+       == NOTHING)
+      return;
+  }
+
+  if (!controls(player, place) && !CanSearch(player, place)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (!payfor(player, FIND_COST)) {
+    notify_format(player, T("You don't have enough %d %s to do that."),
+                 FIND_COST, ((FIND_COST == 1) ? MONEY : MONIES));
+    return;
+  }
+  /* figure out what we're looking for */
+  switch (val) {
+  case ENT_EXITS:
+    exd = 1;
+    td = pd = rd = 0;
+    break;
+  case ENT_THINGS:
+    td = 1;
+    exd = pd = rd = 0;
+    break;
+  case ENT_PLAYERS:
+    pd = 1;
+    exd = td = rd = 0;
+    break;
+  case ENT_ROOMS:
+    rd = 1;
+    exd = td = pd = 0;
+    break;
+  case ENT_ALL:
+    exd = td = pd = rd = 1;
+  }
+
+  /* determine range */
+  if (argv[1] && *argv[1])
+    bot = atoi(argv[1]);
+  if (bot < 0)
+    bot = 0;
+  if (argv[2] && *argv[2])
+    top = atoi(argv[2]) + 1;
+  if (top > db_top)
+    top = db_top;
+
+  for (counter = bot; counter < top; counter++) {
+    if (controls(player, place) || controls(player, counter)) {
+      switch (Typeof(counter)) {
+      case TYPE_EXIT:
+       if (exd) {
+         if (Location(counter) == place) {
+           notify_format(player,
+                         "%s(#%d) [from: %s(#%d)]", Name(counter),
+                         counter, Name(Source(counter)), Source(counter));
+           exc++;
+         }
+       }
+       break;
+      case TYPE_ROOM:
+       if (rd) {
+         if (Location(counter) == place) {
+           notify_format(player, "%s(#%d) [dropto]", Name(counter), counter);
+           rc++;
+         }
+       }
+       break;
+      case TYPE_THING:
+       if (td) {
+         if (Home(counter) == place) {
+           notify_format(player, "%s(#%d) [home]", Name(counter), counter);
+           tc++;
+         }
+       }
+       break;
+      case TYPE_PLAYER:
+       if (pd) {
+         if (Home(counter) == place) {
+           notify_format(player, "%s(#%d) [home]", Name(counter), counter);
+           pc++;
+         }
+       }
+       break;
+      }
+    }
+  }
+
+  if (!exc && !tc && !pc && !rc) {
+    notify(player, T("Nothing found."));
+    return;
+  } else {
+    notify(player, T("----------  Entrances Done  ----------"));
+    notify_format(player,
+                 "Totals: Rooms...%d  Exits...%d  Objects...%d  Players...%d",
+                 rc, exc, tc, pc);
+    return;
+  }
+}
+
+/** Store arguments for decompile_helper() */
+struct dh_args {
+  char const *prefix;  /**< Decompile/tf prefix */
+  char const *name;    /**< Decompile object name */
+  int skipdef;         /**< Skip default flags on attributes if true */
+};
+
+static int
+decompile_helper(dbref player, dbref thing __attribute__ ((__unused__)),       
+                dbref parent __attribute__ ((__unused__)), 
+                const char *pattern __attribute__ ((__unused__)), 
+                ATTR *atr, void *args)
+{
+  struct dh_args *dh = args;
+  ATTR *ptr;
+  char msg[BUFFER_LEN];
+  char *bp;
+
+  if (AF_Nodump(atr))
+    return 0;
+
+  ptr = atr_match(AL_NAME(atr));
+  bp = msg;
+  safe_str(dh->prefix, msg, &bp);
+  if (ptr && !strcmp(AL_NAME(atr), AL_NAME(ptr)))
+    safe_chr('@', msg, &bp);
+  else {
+    ptr = NULL;                        /* To speed later checks */
+    safe_chr('&', msg, &bp);
+  }
+  safe_str(AL_NAME(atr), msg, &bp);
+  safe_chr(' ', msg, &bp);
+  safe_str(dh->name, msg, &bp);
+  safe_chr('=', msg, &bp);
+  safe_str(atr_value(atr), msg, &bp);
+  *bp = '\0';
+  notify(player, msg);
+  /* Now deal with attribute flags, if not FugueEditing */
+  if (!*dh->prefix) {
+    /* If skipdef is on, only show sets that aren't the defaults */
+    const char *privs = NULL;
+    if (dh->skipdef && ptr) {
+      /* Standard attribute. Get the default perms, if any. */
+      /* Are we different? If so, do as usual */
+      int npmflags = AL_FLAGS(ptr) & (~AF_PREFIXMATCH);
+      if (AL_FLAGS(atr) != AL_FLAGS(ptr) && AL_FLAGS(atr) != npmflags)
+       privs = privs_to_string(attr_privs, AL_FLAGS(atr));
+    } else {
+      privs = privs_to_string(attr_privs, AL_FLAGS(atr));
+    }
+    if (privs && *privs)
+      notify_format(player, "@set %s/%s=%s", dh->name, AL_NAME(atr), privs);
+  }
+  return 1;
+}
+
+/** Decompile attributes on an object.
+ * \param player the enactor.
+ * \param thing object with attributes to decompile.
+ * \param name name to refer to object by in decompile.
+ * \param pattern pattern to match attributes to decompile.
+ * \param prefix prefix to use for decompile/tf.
+ * \param skipdef if true, skip showing default attribute flags.
+ */
+void
+decompile_atrs(dbref player, dbref thing, const char *name, const char *pattern,
+              const char *prefix, int skipdef)
+{
+  struct dh_args dh;
+  dh.prefix = prefix;
+  dh.name = name;
+  dh.skipdef = skipdef;
+  /* Comment complaints if none are found */
+  if (!atr_iter_get(player, thing, pattern, 0, decompile_helper, &dh))
+    notify(player, T("@@ No attributes found. @@"));
+}
+
+/** Decompile locks on an object.
+ * \param player the enactor.
+ * \param thing object with attributes to decompile.
+ * \param name name to refer to object by in decompile.
+ * \param skipdef if true, skip showing default lock flags.
+ */
+void
+decompile_locks(dbref player, dbref thing, const char *name, int skipdef)
+{
+  lock_list *ll;
+  for (ll = Locks(thing); ll; ll = ll->next) {
+    const lock_list *p = get_lockproto(L_TYPE(ll));
+    if (p) {
+      notify_format(player, "@lock/%s %s=%s",
+                   L_TYPE(ll), name, unparse_boolexp(player, L_KEY(ll),
+                                                     UB_MEREF));
+      if (skipdef) {
+       if (p && L_FLAGS(ll) == L_FLAGS(p))
+         continue;
+      }
+      if (L_FLAGS(ll))
+       notify_format(player,
+                     "@lset %s/%s=%s", name, L_TYPE(ll), lock_flags_long(ll));
+      if ((L_FLAGS(p) & LF_PRIVATE) && !(L_FLAGS(ll) & LF_PRIVATE))
+       notify_format(player, "@lset %s/%s=!no_inherit", name, L_TYPE(ll));
+    } else {
+      notify_format(player, "@lock/user:%s %s=%s",
+                   ll->type, name, unparse_boolexp(player, ll->key, UB_MEREF));
+      if (L_FLAGS(ll))
+       notify_format(player,
+                     "@lset %s/%s=%s", name, L_TYPE(ll), lock_flags_long(ll));
+    }
+  }
+}
+
+/** Decompile.
+ * \verbatim
+ * This implements @decompile.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of object to decompile.
+ * \param dbflag flag for type of decompile to perform.
+ * \param skipdef if true, skip showing default flags on attributes/locks.
+ */
+void
+do_decompile(dbref player, const char *name, enum dec_type dbflag, int skipdef)
+{
+  dbref thing;
+  const char *object = NULL;
+  char *attrib;
+  ATTR *a;
+  char dbnum[40];
+
+  /* @decompile must always have an argument */
+  if (!name || !*name) {
+    notify(player, T("What do you want to @decompile?"));
+    return;
+  }
+  attrib = strchr(name, '/');
+  if (attrib)
+    *attrib++ = '\0';
+
+  /* find object */
+  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING)) ==
+      NOTHING)
+    return;
+
+  if (IsGarbage(thing)) {
+    notify(player, T("Garbage is garbage."));
+    return;
+  }
+  sprintf(dbnum, "#%d", thing);
+
+  /* if we have an attribute arg specified, wild match on it */
+  if (attrib && *attrib) {
+    switch (dbflag) {
+    case DEC_DB:
+      decompile_atrs(player, thing, dbnum, attrib, "", skipdef);
+      break;
+    case DEC_TF:
+      if (((a = atr_get_noparent(player, "TFPREFIX")) != NULL) &&
+         AL_STR(a) && *AL_STR(a)) {
+       decompile_atrs(player, thing, dbnum, attrib, atr_value(a), skipdef);
+      } else
+       decompile_atrs(player, thing, dbnum, attrib, "FugueEdit > ", skipdef);
+      break;
+    default:
+      if (IsRoom(thing))
+       decompile_atrs(player, thing, "here", attrib, "", skipdef);
+      else
+       decompile_atrs(player, thing, Name(thing), attrib, "", skipdef);
+      break;
+    }
+    return;
+  }
+  /* else we have a full decompile */
+  if (!Can_Examine(player, thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* determine creation and what we call the object */
+  switch (Typeof(thing)) {
+  case TYPE_PLAYER:
+    if (!strcasecmp(name, "me"))
+      object = "me";
+    else if (dbflag == DEC_DB)
+      object = dbnum;
+    else
+      object = Name(thing);
+    break;
+  case TYPE_THING:
+    if (dbflag == DEC_DB) {
+      object = dbnum;
+      break;
+    } else
+      object = Name(thing);
+    if (dbflag != DEC_ATTR)
+      notify_format(player, "@create %s", object);
+    break;
+  case TYPE_ROOM:
+    if (dbflag == DEC_DB) {
+      object = dbnum;
+      break;
+    } else
+      object = "here";
+    if (dbflag != DEC_ATTR)
+      notify_format(player, "@dig/teleport %s", Name(thing));
+    break;
+  case TYPE_EXIT:
+    if (dbflag == DEC_DB) {
+      object = dbnum;
+    } else {
+      object = shortname(thing);
+      if (dbflag != DEC_ATTR)
+       notify_format(player, "@open %s", Name(thing));
+    }
+    break;
+  case TYPE_DIVISION:
+    if (dbflag == DEC_DB) {
+      object = dbnum;
+    } else {
+      object = shortname(thing);
+    }
+    if (dbflag != DEC_ATTR)
+      notify_format(player, "@division/create %s", object);
+    break;
+  }
+
+  if (dbflag != DEC_ATTR) {
+    if (Mobile(thing)) {
+      if (GoodObject(Home(thing)))
+       notify_format(player, "@link %s = #%d", object, Home(thing));
+      else if (Home(thing) == HOME)
+       notify_format(player, "@link %s = HOME", object);
+    } else {
+      if (GoodObject(Destination(thing)))
+       notify_format(player, "@link %s = #%d", object, Destination(thing));
+      else if (Destination(thing) == AMBIGUOUS)
+       notify_format(player, "@link %s = VARIABLE", object);
+      else if (Destination(thing) == HOME)
+       notify_format(player, "@link %s = HOME", object);
+    }
+
+    if (GoodObject(Zone(thing)))
+      notify_format(player, "@chzone %s = #%d", object, Zone(thing));
+    if (GoodObject(Parent(thing)))
+      notify_format(player, "@parent %s = #%d", object, Parent(thing));
+    if (GoodObject(SDIV(thing).object) && IsDivision(SDIV(thing).object))
+      notify_format(player, "@division %s = #%d", object, SDIV(thing).object);
+
+    decompile_locks(player, thing, object, skipdef);
+    decompile_flags(player, thing, object);
+    decompile_powers(player, thing, object);
+  }
+  if (dbflag != DEC_FLAG) {
+    decompile_atrs(player, thing, object, "**", "", skipdef);
+  }
+}
diff --git a/src/malias.c b/src/malias.c
new file mode 100644 (file)
index 0000000..1c7ff47
--- /dev/null
@@ -0,0 +1,1066 @@
+/**
+ * \file malias.c
+ *
+ * \brief  Global mail aliases/lists
+ *
+ * \verbatim
+ *
+ * This code implements an extension to extended @mail which allows
+ * admin (and others who are so em@powered) to create mail aliases
+ * for the MUSH. Optionally, any player can be allowed to.
+ *
+ * Aliases are used by @mail'ing to !<alias name>
+ * Aliases have a name, a description, a list of members (dbrefs), an owner
+ * a size (how many members), and two kinds of flags. 
+ * nflags control who can use/see an alias name, and mflags 
+ * control who can see the alias members. The choices
+ * are everyone, alias members, owner, admin
+ * 
+ * Interface:
+ * @malias[/list]
+ * @malias/members !name
+ * @malias[/create] !name=list-of-members
+ * @malias/destroy !name
+ * @malias/add !name=list-of-members
+ * @malias/remove !name=list-of-members
+ * @malias/desc !name=description
+ * @malias/nameprivs !name=flags
+ * @malias/listprivs !name=flags
+ * @malias/stat
+ * @malias/chown !name=owner    (Admin only)
+ * @malias/nuke                 (Admin only)
+ *
+ * \endverbatim
+ */
+
+#define MA_INC 3       /**< How many maliases we malloc at a time */
+#include "config.h"
+#include "copyrite.h"
+
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <ctype.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <string.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "parse.h"
+#include "malias.h"
+#include "privtab.h"
+#include "mymalloc.h"
+#include "flags.h"
+#include "pueblo.h"
+#include "log.h"
+#include "dbio.h"
+#include "confmagic.h"
+
+
+
+int ma_size = 0;   /**< Number of maliases */
+int ma_top = 0;           /**< Top of alias array */
+struct mail_alias *malias; /**< Pointer to linked list of aliases */
+
+/** Privilege table for maliases. */
+static PRIV malias_priv_table[] = {
+  {"Admin", 'A', ALIAS_ADMIN, ALIAS_ADMIN},
+  {"Members", 'M', ALIAS_MEMBERS, ALIAS_MEMBERS},
+  {"Owner", 'O', ALIAS_OWNER, ALIAS_OWNER},
+  {NULL, '\0', 0, 0}
+};
+
+static const char *get_shortprivs(struct mail_alias *m);
+
+
+/***********************************************************
+***** User-commands *****
+***********************************************************/
+
+
+/** List or create a malias.
+ * \verbatim
+ * This implements the @malias command (with no switches).
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 name of malias to create or list, or NULL to list all.
+ * \param arg2 parameters for creation, or NULL to list.
+ */
+void
+do_malias(dbref player, char *arg1, char *arg2)
+{
+  if (!arg1 || !*arg1) {
+    if (arg2 && *arg2) {
+      notify(player, T("MAIL: Invalid malias command."));
+      return;
+    }
+    /* just the "@malias" command */
+    do_malias_list(player);
+    return;
+  }
+  if (arg2 && *arg2) {
+    /* Creating malias */
+    do_malias_create(player, arg1, arg2);
+  } else {
+    /* List specific alias - no arg2 */
+    do_malias_members(player, arg1);
+  }
+}
+
+
+/** Create a malias.
+ * \verbatim
+ * This implements the @malias/create command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of malias to create.
+ * \param tolist parameters for creation.
+ */
+void
+do_malias_create(dbref player, char *alias, char *tolist)
+{
+  char *head, *tail, spot;
+  struct mail_alias *m;
+  char *na;
+  const char *buff, *good, *scan;
+  int i = 0;
+  dbref target;
+  dbref alist[100];
+
+  if (!IsPlayer(player)) {
+    notify(player, T("MAIL: Only players may create mail aliases."));
+    return;
+  }
+  if (!alias || !*alias || !tolist || !*tolist) {
+    notify(player, T("MAIL: What alias do you want to create?."));
+    return;
+  }
+  if (*alias != MALIAS_TOKEN) {
+    notify_format(player,
+                 T("MAIL: All Mail aliases must begin with '%c'."),
+                 MALIAS_TOKEN);
+    return;
+  }
+  good = "`$_-.'";
+  /* Make sure that the name contains legal characters only */
+  for (scan = alias + 1; scan && *scan; scan++) {
+    if (isalpha((unsigned char) *scan) || isdigit((unsigned char) *scan))
+      continue;
+    if (!strchr(good, *scan)) {
+      notify(player, T("MAIL: Invalid character in mail alias."));
+      return;
+    }
+  }
+  m = get_malias(GOD, alias);  /* GOD can see all aliases */
+  if (m) {                     /* Ensures no duplicates!  */
+    notify_format(player, T("MAIL: Mail Alias '%s' already exists."), alias);
+    return;
+  }
+  if (!ma_size) {
+    ma_size = MA_INC;
+    malias =
+      (struct mail_alias *) mush_malloc(sizeof(struct mail_alias) *
+                                       ma_size, "malias_list");
+  } else if (ma_top >= ma_size) {
+    ma_size += MA_INC;
+    m =
+      (struct mail_alias *) mush_malloc(sizeof(struct mail_alias) *
+                                       (ma_size), "malias_list");
+    memcpy(m, malias, sizeof(struct mail_alias) * ma_top);
+    mush_free((Malloc_t) malias, "malias_list");
+    malias = m;
+  }
+  i = 0;
+
+  /*
+   * Parse the player list
+   */
+  head = (char *) tolist;
+  while (head && *head) {
+    while (*head == ' ')
+      head++;
+    tail = head;
+    while (*tail && (*tail != ' ')) {
+      if (*tail == '"') {
+       head++;
+       tail++;
+       while (*tail && (*tail != '"'))
+         tail++;
+      }
+      if (*tail)
+       tail++;
+    }
+    tail--;
+    if (*tail != '"')
+      tail++;
+    spot = *tail;
+    *tail = '\0';
+    /*
+     * Now locate a target
+     */
+    if (!strcasecmp(head, "me"))
+      target = player;
+    else if (*head == '#') {
+      target = atoi(head + 1);
+    } else
+      target = lookup_player(head);
+    if (!(GoodObject(target)) || (!IsPlayer(target))) {
+      notify_format(player, T("MAIL: No such player '%s'."), head);
+    } else {
+      buff = unparse_object(player, target);
+      notify_format(player, T("MAIL: %s added to alias %s"), buff, alias);
+      alist[i] = target;
+      i++;
+    }
+    /*
+     * Get the next recip
+     */
+    *tail = spot;
+    head = tail;
+    if (*head == '"')
+      head++;
+    if (i == 100)
+      break;
+  }
+
+  if (head && *head) {
+    notify(player, T("MAIL: Alias list is restricted to maximal 100 entries!"));
+  }
+  if (!i) {
+    notify(player, T("MAIL: No valid recipients for alias-list!"));
+    return;
+  }
+  m = &malias[ma_top];
+  m->members = (dbref *) mush_malloc(sizeof(dbref) * i, "malias_members");
+  memcpy(m->members, alist, sizeof(dbref) * i);
+
+  na = alias + 1;
+  m->size = i;
+  m->owner = player;
+  m->name = mush_strdup(na, "malias_name");
+  m->desc = compress(na);
+  add_check("malias_desc");
+  m->nflags = ALIAS_OWNER | ALIAS_MEMBERS;
+  m->mflags = ALIAS_OWNER;
+  ma_top++;
+
+
+  notify_format(player, T("MAIL: Alias set '%s' defined."), alias);
+}
+
+
+/** List maliases.
+ * \verbatim
+ * This function implements @malias/list.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_malias_list(dbref player)
+{
+  struct mail_alias *m;
+  int i = 0;
+  int notified = 0;
+
+  for (i = 0; i < ma_top; i++) {
+    m = &malias[i];
+    if ((m->owner == player) || (m->nflags == 0) ||
+       ((m->nflags & ALIAS_ADMIN) && Admin(player)) ||
+       ((m->nflags & ALIAS_MEMBERS) && ismember(m, player))) {
+      if (!notified) {
+       notify_format(player, "%-13s %-35s %s %-15s",
+                     T("Name"), T("Alias Description"), T("Use See"),
+                     T("Owner"));
+       notified++;
+      }
+      notify_format(player,
+                   "%c%-12.12s %-35.35s %s %-15.15s", MALIAS_TOKEN, m->name,
+                   uncompress((unsigned char *) (m->desc)), get_shortprivs(m),
+                   Name(m->owner));
+    }
+  }
+
+  notify(player, T("*****  End of Mail Aliases *****"));
+}
+
+/** List malias members.
+ * \verbatim
+ * This function implements @malias/members.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the alias to list members of.
+ */
+void
+do_malias_members(dbref player, char *alias)
+{
+  struct mail_alias *m;
+  int i = 0;
+  char buff[BUFFER_LEN];
+  char *bp;
+
+  m = get_malias(player, alias);
+
+  if (!m) {
+    notify_format(player, T("MAIL: Alias '%s' not found."), alias);
+    return;
+  }
+  if ((m->owner == player) || (m->mflags == 0) ||
+      (Admin(player)) ||
+      ((m->mflags & ALIAS_MEMBERS) && ismember(m, player))) {
+    /* Dummy to avoid having to invert the "if" above ;-) */
+  } else {
+    notify(player, T("MAIL: Permission denied."));
+    return;
+  }
+  bp = buff;
+  safe_format(buff, &bp, T("MAIL: Alias %c%s: "), MALIAS_TOKEN, m->name);
+  for (i = 0; i < m->size; i++) {
+    safe_str(Name(m->members[i]), buff, &bp);
+    safe_chr(' ', buff, &bp);
+    /* Attention if player names may contain spaces!! */
+  }
+  *bp = '\0';
+  notify(player, buff);
+}
+
+
+/** Describe a malias.
+ * \verbatim
+ * This implements the @malias/desc command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the malias to describe.
+ * \param desc description to set.
+ */
+void
+do_malias_desc(dbref player, char *alias, char *desc)
+{
+  struct mail_alias *m;
+
+  if (!(m = get_malias(player, alias))) {
+    notify_format(player, T("MAIL: Alias %s not found."), alias);
+    return;
+  } else if (MailAdmin(player, m->owner) || (player == m->owner)) {
+    if (m->desc)
+      free(m->desc);           /* No need to update MEM_CHECK records here */
+    m->desc = compress(desc);
+    notify(player, T("MAIL: Description changed."));
+  } else
+    notify(player, T("MAIL: Permission denied."));
+  return;
+}
+
+
+/** Change ownership of a malias.
+ * \verbatim
+ * This implements the @malias/chown command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the malias to chown.
+ * \param owner name of the new owner.
+ */
+void
+do_malias_chown(dbref player, char *alias, char *owner)
+{
+  struct mail_alias *m;
+  dbref no = NOTHING;
+
+  if (!(m = get_malias(player, alias))) {
+    notify_format(player, T("MAIL: Alias %s not found."), alias);
+    return;
+  } else {
+    if (!MailAdmin(player, m->owner)) {
+      notify(player, T("MAIL: You cannot do that!"));
+      return;
+    } else {
+      if ((no = lookup_player(owner)) == NOTHING) {
+       notify(player, T("MAIL: I cannot find that player."));
+       return;
+      }
+      m->owner = no;
+      notify(player, T("MAIL: Owner changed for alias."));
+    }
+  }
+}
+
+
+
+/** Change name of a malias.
+ * \verbatim
+ * This implements the @malias/rename command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the malias to rename.
+ * \param newname new name for the malias.
+ */
+void
+do_malias_rename(dbref player, char *alias, char *newname)
+{
+  struct mail_alias *m;
+
+  if ((m = get_malias(player, alias)) == NULL) {
+    notify(player, T("MAIL: I cannot find that alias!"));
+    return;
+  }
+  if (*newname != MALIAS_TOKEN) {
+    notify_format(player,
+                 T("MAIL: Bad alias. Aliases must start with '%c'."),
+                 MALIAS_TOKEN);
+    return;
+  }
+  if (get_malias(GOD, newname) != NULL) {
+    notify(player, T("MAIL: That name already exists!"));
+    return;
+  }
+  if (!MailAdmin(player, m->owner) && !(m->owner == player)) {
+    notify(player, T("MAIL: Permission denied."));
+    return;
+  }
+
+  free(m->name);               /* No need to update MEM_CHECK records here. */
+  m->name = strdup(newname + 1);
+
+  notify(player, T("MAIL: Mail Alias renamed."));
+}
+
+
+
+/** Delete a malias.
+ * \verbatim
+ * This implements the @malias/destroy command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the malias to destroy.
+ */
+void
+do_malias_destroy(dbref player, char *alias)
+{
+  struct mail_alias *m;
+  m = get_malias(player, alias);
+  if (!m) {
+    notify(player,
+          T
+          ("MAIL: Not a valid alias. Remember to prefix the alias name with *."));
+    return;
+  }
+  if (MailAdmin(player, m->owner) || (m->owner == player)) {
+    notify(player, T("MAIL: Alias Destroyed."));
+    if (m->members)
+      mush_free((Malloc_t) m->members, "malias_members");
+    if (m->name)
+      mush_free(m->name, "malias_name");
+    if (m->desc)
+      mush_free(m->desc, "malias_desc");
+    *m = malias[--ma_top];
+  } else {
+    notify(player, T("MAIL: Permission denied!"));
+  }
+}
+
+
+/** Set the membership list for a malias.
+ * \verbatim
+ * This implements the @malias/set command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the malias to set members for.
+ * \param tolist space-separated list of players to set as members.
+ */
+void
+do_malias_set(dbref player, char *alias, char *tolist)
+{
+  struct mail_alias *m;
+  int i = 0;
+  char *head, *tail, spot;
+  const char *buff;
+  dbref alist[100];
+  dbref target;
+
+  m = get_malias(player, alias);
+  if (!m) {
+    notify_format(player,
+                 T
+                 ("MAIL: Not a valid alias. Remember to prefix the alias name with %c."),
+                 MALIAS_TOKEN);
+    return;
+  }
+  if (!tolist || !*tolist) {
+    notify(player, T("MAIL: You must set the alias to a non-empty list."));
+    return;
+  }
+  if (!(MailAdmin(player, m->owner) || (m->owner == player))) {
+    notify(player, T("MAIL: Permission denied!"));
+    return;
+  }
+
+  /*
+   * Parse the player list
+   */
+  head = (char *) tolist;
+  while (head && *head) {
+    while (*head == ' ')
+      head++;
+    tail = head;
+    while (*tail && (*tail != ' ')) {
+      if (*tail == '"') {
+       head++;
+       tail++;
+       while (*tail && (*tail != '"'))
+         tail++;
+      }
+      if (*tail)
+       tail++;
+    }
+    tail--;
+    if (*tail != '"')
+      tail++;
+    spot = *tail;
+    *tail = '\0';
+    /*
+     * Now locate a target
+     */
+    if (!strcasecmp(head, "me"))
+      target = player;
+    else if (*head == '#') {
+      target = atoi(head + 1);
+    } else
+      target = lookup_player(head);
+    if (!(GoodObject(target)) || (!IsPlayer(target))) {
+      notify_format(player, T("MAIL: No such player '%s'."), head);
+    } else {
+      buff = unparse_object(player, target);
+      notify_format(player, T("MAIL: %s added to alias %s"), buff, alias);
+      alist[i] = target;
+      i++;
+    }
+    /*
+     * Get the next recip
+     */
+    *tail = spot;
+    head = tail;
+    if (*head == '"')
+      head++;
+    if (i == 100)
+      break;
+  }
+
+  if (head && *head) {
+    notify(player, T("MAIL: Alias list is restricted to maximal 100 entries!"));
+  }
+  if (!i) {
+    notify(player, T("MAIL: No valid recipients for alias-list!"));
+    return;
+  }
+  if (m->members)
+    mush_free((Malloc_t) m->members, "malias_members");
+  m->members = (dbref *) mush_malloc(sizeof(dbref) * i, "malias_members");
+  memcpy(m->members, alist, sizeof(dbref) * i);
+  m->size = i;
+  notify(player, T("MAIL: Alias list set."));
+}
+
+
+
+/** List all maliases.
+ * \verbatim
+ * This implements the @malias/list command.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_malias_all(dbref player)
+{
+  struct mail_alias *m;
+  int i;
+
+  if (!Admin(player)) {
+    do_malias_list(player);
+    return;
+  }
+  notify(player,
+        "Num   Name       Description                              Owner       Count");
+
+  for (i = 0; i < ma_top; i++) {
+    m = &malias[i];
+    notify_format(player, "#%-4d %c%-10.10s %-40.40s %-11.11s (%3d)",
+                 i, MALIAS_TOKEN, m->name,
+                 uncompress((unsigned char *) m->desc),
+                 Name(m->owner), m->size);
+  }
+
+  notify(player, T("***** End of Mail Aliases *****"));
+}
+
+
+
+
+/** Statistics on maliases.
+ * \verbatim
+ * This implements the @malias/stat command.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_malias_stats(dbref player)
+{
+  if (!Admin(player))
+    notify(player, T("MAIL: Permission denied."));
+  else {
+    notify_format(player,
+                 T("MAIL: Number of mail aliases defined: %d"), ma_top);
+    notify_format(player, T("MAIL: Allocated slots %d"), ma_size);
+  }
+}
+
+/** Remove all maliases.
+ * \verbatim
+ * This implements the @malias/nuke command.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_malias_nuke(dbref player)
+{
+  struct mail_alias *m;
+  int i;
+
+  if (!God(player)) {
+    notify(player, T("MAIL: Only god can do that!"));
+    return;
+  }
+  if (ma_size) {               /* aliases defined ? */
+    for (i = 0; i < ma_top; i++) {
+      m = &malias[i];
+      if (m->name)
+       mush_free(m->name, "malias_name");
+      if (m->desc)
+       mush_free(m->desc, "malias_desc");
+      if (m->members)
+       mush_free((Malloc_t) m->members, "malias_members");
+    }
+    mush_free((Malloc_t) malias, "malias_list");
+  }
+  ma_size = ma_top = 0;
+  notify(player, T("MAIL: All mail aliases destroyed!"));
+}
+
+
+/** Set permisions on maliases.
+ * \verbatim
+ * This implements @malias/use and @malias/see
+ * \endverbatim
+ * \param player the enactor.
+ * \param alias name of the malias.
+ * \param privs string of privs to set.
+ * \param type if 1, setting nprivs, if 0, mprivs.
+ */
+void
+do_malias_privs(dbref player, char *alias, char *privs, int type)
+{
+  struct mail_alias *m;
+  int *p;
+
+  if (!(m = get_malias(player, alias))) {
+    notify(player, T("MAIL: I cannot find that alias!"));
+    return;
+  }
+  if (!MailAdmin(player, m->owner) && (m->owner != player)) {
+    notify(player, T("MAIL: Permission denied."));
+    return;
+  }
+  p = type ? &m->mflags : &m->nflags;
+  *p = string_to_privs(malias_priv_table, privs, 0);
+  notify_format(player,
+               T("MAIL: Permission to see/use alias '%s' changed to %s"),
+               alias, privs_to_string(malias_priv_table, *p));
+}
+
+
+/** Add players to a malias.
+ * \param player dbref of enactor.
+ * \param alias name of malias.
+ * \param tolist string with list of players to add.
+ */
+void
+do_malias_add(dbref player, char *alias, char *tolist)
+{
+  char *head, *tail, spot;
+  struct mail_alias *m;
+  const char *buff;
+  int i = 0;
+  dbref target;
+  dbref alist[100];
+  dbref *members;
+
+  m = get_malias(player, alias);
+  if (!m) {
+    notify_format(player, T("MAIL: Mail Alias '%s' not found."), alias);
+    return;
+  }
+  if (!MailAdmin(player, m->owner) && (m->owner != player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  i = 0;
+
+  /*
+   * Parse the player list
+   */
+  head = (char *) tolist;
+  while (head && *head) {
+    while (*head == ' ')
+      head++;
+    tail = head;
+    while (*tail && (*tail != ' ')) {
+      if (*tail == '"') {
+       head++;
+       tail++;
+       while (*tail && (*tail != '"'))
+         tail++;
+      }
+      if (*tail)
+       tail++;
+    }
+    tail--;
+    if (*tail != '"')
+      tail++;
+    spot = *tail;
+    *tail = '\0';
+    /*
+     * Now locate a target
+     */
+    if (!strcasecmp(head, "me"))
+      target = player;
+    else if (*head == '#') {
+      target = atoi(head + 1);
+    } else
+      target = lookup_player(head);
+    if (!(GoodObject(target)) || (!IsPlayer(target))) {
+      notify_format(player, T("MAIL: No such player '%s'."), head);
+    } else {
+      if (ismember(m, target)) {
+       notify_format(player,
+                     T("MAIL: player '%s' exists already in alias %s."),
+                     head, alias);
+      } else {
+       buff = unparse_object(player, target);
+       notify_format(player, T("MAIL: %s added to alias %s"), buff, alias);
+       alist[i] = target;
+       i++;
+      }
+    }
+    /*
+     * Get the next recip
+     */
+    *tail = spot;
+    head = tail;
+    if (*head == '"')
+      head++;
+    if (i == 100)
+      break;
+  }
+
+  if (head && *head) {
+    notify(player, T("MAIL: Alias list is restricted to maximal 100 entries!"));
+  }
+  if (!i) {
+    notify(player, T("MAIL: No valid recipients for alias-list!"));
+    return;
+  }
+  members =
+    (dbref *) mush_malloc(sizeof(dbref) * (i + m->size), "malias_members");
+
+  memcpy(members, m->members, sizeof(dbref) * m->size);
+  memcpy(&members[m->size], alist, sizeof(dbref) * i);
+  mush_free((Malloc_t) m->members, "malias_members");
+  m->members = members;
+
+  m->size += i;
+
+  notify_format(player, T("MAIL: Alias set '%s' redefined."), alias);
+}
+
+
+
+
+
+/** Remove players from a malias.
+ * \param player dbref of enactor.
+ * \param alias name of malias.
+ * \param tolist string with list of players to remove.
+ */
+void
+do_malias_remove(dbref player, char *alias, char *tolist)
+{
+  char *head, *tail, spot;
+  struct mail_alias *m;
+  const char *buff;
+  int i = 0;
+  dbref target;
+
+  m = get_malias(player, alias);
+  if (!m) {
+    notify_format(player, T("MAIL: Mail Alias '%s' not found."), alias);
+    return;
+  }
+  if (!MailAdmin(player, m->owner) && (m->owner != player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  i = 0;
+
+  /*
+   * Parse the player list
+   */
+  head = (char *) tolist;
+  while (head && *head) {
+    while (*head == ' ')
+      head++;
+    tail = head;
+    while (*tail && (*tail != ' ')) {
+      if (*tail == '"') {
+       head++;
+       tail++;
+       while (*tail && (*tail != '"'))
+         tail++;
+      }
+      if (*tail)
+       tail++;
+    }
+    tail--;
+    if (*tail != '"')
+      tail++;
+    spot = *tail;
+    *tail = '\0';
+    /*
+     * Now locate a target
+     */
+    if (!strcasecmp(head, "me"))
+      target = player;
+    else if (*head == '#') {
+      target = atoi(head + 1);
+    } else
+      target = lookup_player(head);
+    if (!(GoodObject(target)) || (!IsPlayer(target))) {
+      notify_format(player, T("MAIL: No such player '%s'."), head);
+    } else {
+      if (!(i = ismember(m, target))) {
+       notify_format(player, T("MAIL: player '%s' is not in alias %s."),
+                     head, alias);
+      } else {
+       buff = unparse_object(player, target);
+       m->members[i - 1] = m->members[--m->size];
+       notify_format(player, T("MAIL: %s removed from alias %s"), buff, alias);
+      }
+    }
+    /*
+     * Get the next recip
+     */
+    *tail = spot;
+    head = tail;
+    if (*head == '"')
+      head++;
+  }
+
+  notify_format(player, T("MAIL: Alias set '%s' redefined."), alias);
+}
+
+
+
+
+
+
+/***********************************************************
+***** "Utility" functions *****
+***********************************************************/
+
+static const char *
+get_shortprivs(struct mail_alias *m)
+{
+  static char privs[10];
+  strcpy(privs, "--  -- ");
+
+  if (!m->nflags)
+    privs[0] = 'E';
+  else {
+    if (m->nflags & ALIAS_MEMBERS)
+      privs[0] = 'M';
+    if (m->nflags & ALIAS_ADMIN)
+      privs[1] = 'A';
+    if (!strncmp(privs, "--", 2))
+      privs[1] = 'O';
+  }
+
+  if (!m->mflags)
+    privs[4] = 'E';
+  else {
+    if (m->mflags & ALIAS_MEMBERS)
+      privs[4] = 'M';
+    if (m->mflags & ALIAS_ADMIN)
+      privs[5] = 'A';
+    if (!strncmp(privs + 4, "--", 2))
+      privs[5] = 'O';
+  }
+
+  return privs;
+}
+
+
+/** Is a player a member of a malias?
+ * \param m pointer to malias.
+ * \param player dbref of player.
+ * \retval 1 player is a member of the malias.
+ * \retval 0 player is not a member of the malias.
+ */
+int
+ismember(struct mail_alias *m, dbref player)
+{
+  int i;
+  for (i = 0; i < m->size; i++) {
+    if (player == m->members[i])
+      return (i + 1);          /* To avoid entry "0" */
+  }
+  return 0;
+}
+
+/** Remove a destroyed player from all maliases.
+ * \param player player to remove from maliases.
+ */
+void
+malias_cleanup(dbref player)
+{
+  struct mail_alias *m;
+  int n, i = 0;
+
+  for (n = 0; n < ma_top; n++) {
+    m = &malias[n];
+    if ((i = ismember(m, player)) != 0) {
+      do_rawlog(LT_ERR, "Removing #%d from malias %s", player, m->name);
+      m->members[i - 1] = m->members[--m->size];
+    }
+  }
+}
+
+/** Get a malias pointer with permission checking.
+ * \param player player dbref, for permission check.
+ * \param alias name of malias to retrieve.
+ * \return pointer to malias structure, or NULL if player can't see it.
+ */
+struct mail_alias *
+get_malias(dbref player, char *alias)
+{
+  const char *mal;
+  struct mail_alias *m;
+  int i = 0;
+
+  if (*alias != MALIAS_TOKEN)
+    return NULL;
+
+  mal = alias + 1;
+
+  for (i = 0; i < ma_top; i++) {
+    m = &malias[i];
+    if ((m->owner == player) || (m->nflags == 0) ||
+       /* ((m->nflags & ALIAS_ADMIN) && Admin(player)) || */
+       Admin(player) ||
+       ((m->nflags & ALIAS_MEMBERS) && ismember(m, player))) {
+
+      if (!strcasecmp(mal, m->name))
+       return m;
+    }
+  }
+  return NULL;
+}
+
+
+
+
+
+/***********************************************************
+***** Loading and saving of mail-aliases *****
+***********************************************************/
+
+/** Load maliases from the mail db.
+ * \param fp file pointer to read from.
+ */
+void
+load_malias(FILE * fp)
+{
+  int i, j;
+  char buffer[BUFFER_LEN];
+  struct mail_alias *m;
+  char *s;
+
+  ma_top = getref(fp);
+
+  ma_size = ma_top;
+
+  if (ma_top > 0)
+    malias =
+      (struct mail_alias *) mush_malloc(sizeof(struct mail_alias) *
+                                       ma_size, "malias_list");
+  else
+    malias = NULL;
+
+  for (i = 0; i < ma_top; i++) {
+    m = &malias[i];
+
+    m->owner = getref(fp);
+    m->name = mush_strdup(getstring_noalloc(fp), "malias_name");
+    m->desc = compress(getstring_noalloc(fp));
+    add_check("malias_desc");
+
+    m->nflags = getref(fp);
+    m->mflags = getref(fp);
+    m->size = getref(fp);
+
+    if (m->size > 0) {
+      m->members =
+       (dbref *) mush_malloc(m->size * sizeof(dbref), "malias_members");
+      for (j = 0; j < m->size; j++) {
+       m->members[j] = getref(fp);
+      }
+    } else {
+      m->members = NULL;
+    }
+  }
+  s = fgets(buffer, sizeof(buffer), fp);
+
+  if (!s || strcmp(buffer, "\"*** End of MALIAS ***\"\n") != 0) {
+    do_rawlog(LT_ERR, T("MAIL: Error reading MALIAS list"));
+  }
+}
+
+/** Write maliases to the maildb
+ * \param fp file pointer to write to.
+ */
+void
+save_malias(FILE * fp)
+{
+  int i, j;
+  struct mail_alias *m;
+
+  putref(fp, ma_top);
+
+  for (i = 0; i < ma_top; i++) {
+    m = &malias[i];
+    putref(fp, m->owner);
+    putstring(fp, (char *) (m->name));
+    putstring(fp, uncompress(m->desc));
+
+    putref(fp, m->nflags);
+    putref(fp, m->mflags);
+    putref(fp, m->size);
+
+    for (j = 0; j < m->size; j++)
+      putref(fp, m->members[j]);
+  }
+  putstring(fp, "*** End of MALIAS ***");
+}
diff --git a/src/match.c b/src/match.c
new file mode 100644 (file)
index 0000000..849a625
--- /dev/null
@@ -0,0 +1,690 @@
+/**
+ * \file match.c
+ *
+ * \brief Matching of object names.
+ *
+ * These are the PennMUSH name-matching routines, fully re-entrant.
+ *  match_result(who,name,type,flags) - return match, AMBIGUOUS, or NOTHING
+ *  noisy_match_result(who,name,type,flags) - return match or NOTHING,
+ *      and notify player on failures
+ *  last_match_result(who,name,type,flags) - return match or NOTHING,
+ *      and return the last match found in ambiguous situations
+ *
+ * who = dbref of player to match for
+ * name = string to match on
+ * type = preferred type of match (TYPE_THING, etc.) or NOTYPE
+ * flags = a set of bits indicating what kind of matching to do
+ *
+ * flags are defined in match.h, but here they are for reference:
+ * MAT_CHECK_KEYS       - check locks when matching
+ * MAT_GLOBAL           - match in master room
+ * MAT_REMOTES          - match things not nearby
+ * MAT_NEAR             - match things nearby
+ * MAT_CONTROL          - do a control check after matching
+ * MAT_ME               - match "me"
+ * MAT_HERE             - match "here"
+ * MAT_ABSOLUTE         - match "#dbref"
+ * MAT_PLAYER           - match a player's name
+ * MAT_NEIGHBOR         - match something in the same room
+ * MAT_POSSESSION       - match something I'm carrying
+ * MAT_EXIT             - match an exit
+ * MAT_CARRIED_EXIT     - match a carried exit (rare)
+ * MAT_CONTAINER        - match a container I'm in
+ * MAT_REMOTE_CONTENTS  - match the contents of a remote location
+ * MAT_ENGLISH          - match natural english 'my 2nd flower'
+ * MAT_EVERYTHING       - me,here,absolute,player,neighbor,possession,exit
+ * MAT_NEARBY           - everything near
+ * MAT_OBJECTS          - me,absolute,player,neigbor,possession
+ * MAT_NEAR_THINGS      - objects near
+ * MAT_REMOTE           - absolute,player,remote_contents,exit,remotes
+ * MAT_LIMITED          - absolute,player,neighbor
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "conf.h"
+#include "mushdb.h"
+#include "externs.h"
+#include "case.h"
+#include "match.h"
+#include "parse.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "confmagic.h"
+
+static dbref match_result_internal
+  (dbref who, const char *name, int type, long flags);
+static dbref simple_matches(dbref who, const char *name, long flags);
+static int parse_english(const char **name, long *flags);
+static dbref match_me(const dbref who, const char *name);
+static dbref match_here(const dbref who, const char *name);
+/** Convenience alias for parse_objid */
+#define match_absolute(name) parse_objid(name)
+static dbref match_player(const dbref matcher, const char *match_name);
+static dbref choose_thing(const dbref match_who, const int preferred_type,
+                         long int flags, dbref thing1, dbref thing2);
+extern int check_alias(const char *command, const char *list); /* game.c */
+
+
+/** A wrapper for returning a match, AMBIGUOUS, or NOTHING.
+ * This function attempts to match a name for who, and 
+ * can return the matched dbref, AMBIGUOUS, or NOTHING.
+ * \param who the looker.
+ * \param name name to try to match.
+ * \param type type of object to match.
+ * \param flags match flags.
+ * \return dbref of matched object, or AMBIGUOUS, or NOTHING.
+ */
+dbref
+match_result(const dbref who, const char *name, const int type,
+            const long flags)
+{
+  return match_result_internal(who, name, type, flags);
+}
+
+/** A noisy wrapper for returning a match or NOTHING.
+ * This function attempts to match a name for who, and 
+ * can return the matched dbref or NOTHING (in ambiguous cases,
+ * NOTHING is returned). If no match is made, the looker is notified
+ * of the failure to match or ambiguity.
+ * \param who the looker.
+ * \param name name to try to match.
+ * \param type type of object to match.
+ * \param flags match flags.
+ * \return dbref of matched object, or  NOTHING.
+ */
+dbref
+noisy_match_result(const dbref who, const char *name, const int type,
+                  const long flags)
+{
+  return match_result_internal(who, name, type, flags | MAT_NOISY);
+}
+
+/** A noisy wrapper for returning a match or NOTHING.
+ * This function attempts to match a name for who, and 
+ * can return the matched dbref or NOTHING. In ambiguous cases,
+ * the last matched thing is returned.
+ * \param who the looker.
+ * \param name name to try to match.
+ * \param type type of object to match.
+ * \param flags match flags.
+ * \return dbref of matched object, or  NOTHING.
+ */
+dbref
+last_match_result(const dbref who, const char *name, const int type,
+                 const long flags)
+{
+  return match_result_internal(who, name, type, flags | MAT_LAST);
+}
+
+/** Wrapper for a noisy match with control checks.
+ * This function performs a noisy_match_result() and then checks that
+ * the looker controls the matched object before returning it.
+ * If the control check fails, the looker is notified and NOTHING
+ * is returned.
+ * \param player the looker.
+ * \param name name to try to match.
+ * \return dbref of matched controlled object, or NOTHING.
+ */
+dbref
+match_controlled(dbref player, const char *name)
+{
+  dbref match;
+  match = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
+  if (GoodObject(match) && !controls(player, match)) {
+    notify(player, T("Permission denied."));
+    return NOTHING;
+  } else {
+    return match;
+  }
+}
+
+/* The real work. Here's the spec:
+ * str  --> "me"
+ *      --> "here"
+ *      --> "#dbref"
+ *      --> "*player"
+ *      --> adj-phrase name
+ *      --> name
+ * adj-phrase --> adj
+ *            --> adj count
+ *            --> count
+ * adj  --> "my", "me" (restrict match to inventory)
+ *      --> "here", "this", "this here" (restrict match to neighbor objects)
+ *      --> "toward" (restrict match to exits)
+ * count --> 1st, 21st, etc.
+ *       --> 2nd, 22nd, etc.
+ *       --> 3rd, 23rd, etc.
+ *       --> 4th, 10th, etc.
+ * name --> exit_alias
+ *      --> full_obj_name
+ *      --> partial_obj_name
+ *
+ * 1. Look for exact matches and return immediately:
+ *  a. "me" if requested
+ *  b. "here" if requested
+ *  c. #dbref, possibly with a control check
+ *  c. *player
+ * 2. Parse for adj-phrases and restrict further matching and/or
+ *    remember the object count
+ * 3. Look for matches (remote contents, neighbor, inventory, exits, 
+ *    containers, carried exits)
+ *  a. If we don't have an object count, collect the number of exact
+ *     and partial matches and the best partial match.
+ *  b. If we do have an object count, collect the nth exact match
+ *     and the nth match (exact or partial). number of matches is always
+ *     0 or 1.
+ * 4. Make decisions
+ *  a. If we got a single exact match, return it
+ *  b. If we got multiple exact matches, complain
+ *  c. If we got no exact matches, but a single partial match, return it
+ *  d. If we got multiple partial matches, complain
+ *  e. If we got no matches, complain
+ */
+
+#define MATCH_NONE      0x0    /**< No matches were found */
+#define MATCH_EXACT     0x1    /**< At least one exact match found */
+#define MATCH_PARTIAL   0x2    /**< At least one partial match found, no exact */
+/** Prototype for matching functions */
+#define MATCH_FUNC_PROTO(fun_name) \
+  /* ARGSUSED */ /* try to keep lint happy */ \
+  static int fun_name(const dbref who, const char *name, const int type, \
+      const long flags, dbref first, \
+      dbref *match, int *exact_matches_to_go, int *matches_to_go)
+/** Common declaration for matching functions */
+#define MATCH_FUNC(fun_name) \
+  static int fun_name(const dbref who, const char *name, const int type, \
+      const long flags, dbref first __attribute__ ((__unused__)), \
+      dbref *match, int *exact_matches_to_go, int *matches_to_go)
+/** Macro to execute matching and store some results */
+#define RUN_MATCH_FUNC(fun,first) \
+   { \
+    result = fun(who, name, type, flags, first, &match, \
+          &exact_matches_to_go, &matches_to_go); \
+    if (result == MATCH_EXACT) { \
+      exact_match = match; \
+      /* If it's the nth exact match, we're done */ \
+      if (matchnum && !exact_matches_to_go) \
+        goto finished; \
+      /* If it's the n'th match, remember it */ \
+      if (matchnum && !matches_to_go) \
+        last_match = match; \
+    } else if (result == MATCH_PARTIAL) { \
+      if (!matchnum || !matches_to_go) \
+        last_match = match; \
+    } \
+   }
+
+MATCH_FUNC_PROTO(match_possession);
+MATCH_FUNC_PROTO(match_neighbor);
+MATCH_FUNC_PROTO(match_exit);
+MATCH_FUNC_PROTO(match_exit_internal);
+MATCH_FUNC_PROTO(match_container);
+MATCH_FUNC_PROTO(match_list);
+
+static dbref
+match_result_internal(dbref who, const char *name, int type, long flags)
+{
+  dbref match = NOTHING, last_match = NOTHING, exact_match = NOTHING;
+  int exact_matches_to_go, matches_to_go;
+  int matchnum = 0;
+  int result;
+
+  /* The quick ones that can never be ambiguous */
+  match = simple_matches(who, name, flags);
+  if (GoodObject(match))
+    return match;
+
+  /* Check for adjective phrases */
+  matchnum = parse_english(&name, &flags);
+
+  /* Perform matching. We've already had flags restricted by any
+   * adjective phrases. If matchnum is set, collect the matchnum'th
+   * exact match (and stop) and the matchnum'th match (exact or partial, 
+   * and store this in case we don't get enough exact matches).
+   * If not, collect the number of exact and partial matches and the 
+   * last exact and partial matches.
+   */
+  exact_matches_to_go = matches_to_go = matchnum;
+  if (flags & MAT_POSSESSION)
+    RUN_MATCH_FUNC(match_possession, NOTHING);
+  if (flags & MAT_NEIGHBOR)
+    RUN_MATCH_FUNC(match_neighbor, NOTHING);
+  if (flags & MAT_REMOTE_CONTENTS)
+    RUN_MATCH_FUNC(match_possession, NOTHING);
+  if (flags & MAT_EXIT)
+    RUN_MATCH_FUNC(match_exit, NOTHING);
+  if (flags & MAT_CONTAINER)
+    RUN_MATCH_FUNC(match_container, NOTHING);
+  if (flags & MAT_CARRIED_EXIT)
+    RUN_MATCH_FUNC(match_exit_internal, who);
+
+finished:
+  /* Set up the default match_result behavior */
+  if (matchnum) {
+    /* nth exact match? */
+    if (!exact_matches_to_go)
+      match = exact_match;
+    else if (GoodObject(last_match))
+      match = last_match;      /* nth exact-or-partial match, or nothing? */
+    /* This shouldn't happen, but just in case we have a valid match,
+     * and an invalid last_match in the matchnum case, fall through and
+     * use the match.
+     */
+  } else if (GoodObject(exact_match)) {
+    /* How many exact matches? */
+    if (exact_matches_to_go == -1)
+      match = exact_match;     /* Good */
+    else if (flags & MAT_LAST)
+      match = exact_match;     /* Good enough */
+    else
+      match = AMBIGUOUS;
+  } else {
+    if (!matches_to_go)
+      match = NOTHING;         /* No matches */
+    else if (matches_to_go == -1)
+      match = last_match;      /* Good */
+    else if (flags & MAT_LAST)
+      match = last_match;      /* Good enough */
+    else
+      match = AMBIGUOUS;
+  }
+
+  /* Handle noisy_match_result */
+  if (flags & MAT_NOISY) {
+    switch (match) {
+    case NOTHING:
+      notify(who, T("I can't see that here."));
+      return NOTHING;
+    case AMBIGUOUS:
+      notify(who, T("I don't know which one you mean!"));
+      return NOTHING;
+    default:
+      return match;
+    }
+  }
+  return match;
+}
+
+static dbref
+simple_matches(dbref who, const char *name, long flags)
+{
+  dbref match = NOTHING;
+  if (flags & MAT_ME) {
+    match = match_me(who, name);
+    if (GoodObject(match))
+      return match;
+  }
+  if (flags & MAT_HERE) {
+    match = match_here(who, name);
+    if (GoodObject(match))
+      return match;
+  }
+  if (!(flags & MAT_NEAR) || Long_Fingers(who)) {
+    if (flags & MAT_ABSOLUTE) {
+      match = match_absolute(name);
+      if (GoodObject(match) && (flags & MAT_NEAR) && !CanRemote(who, match))
+        goto mat_near;
+      if (GoodObject(match)) {
+       if (flags & MAT_CONTROL) {
+         /* Check for control */
+         if (controls(who, match) || nearby(who, match))
+           return match;
+       } else {
+         return match;
+       }
+      }
+    }
+    if (flags & MAT_PLAYER) {
+      match = match_player(who, name);
+      if (GoodObject(match))
+       return match;
+    }
+  } else {
+    /* We're doing a nearby match and the player doesn't have
+     * long_fingers, so it's a controlled absolute
+     */
+mat_near:
+    match = match_absolute(name);
+    if (GoodObject(match) && (controls(who, match) || nearby(who, match)))
+      return match;
+  }
+  return NOTHING;
+}
+
+
+/* 
+ * adj-phrase --> adj
+ *            --> adj count
+ *            --> count
+ * adj  --> "my", "me" (restrict match to inventory)
+ *      --> "here", "this", "this here" (restrict match to neighbor objects)
+ *      --> "toward" (restrict match to exits)
+ * count --> 1st, 21st, etc.
+ *       --> 2nd, 22nd, etc.
+ *       --> 3rd, 23rd, etc.
+ *       --> 4th, 10th, etc.
+ *
+ * We return the count, we position the pointer at the end of the adj-phrase
+ * (or at the beginning, if we fail), and we modify the flags if there
+ * are restrictions
+ */
+static int
+parse_english(const char **name, long *flags)
+{
+  int saveflags = *flags;
+  const char *savename = *name;
+  char *mname;
+  char *e;
+  int count = 0;
+
+  /* Handle restriction adjectives first */
+  if (*flags & MAT_NEIGHBOR) {
+    if (!strncasecmp(*name, "this here ", 10)) {
+      *name += 10;
+      *flags &= ~(MAT_POSSESSION | MAT_EXIT);
+    } else if (!strncasecmp(*name, "here ", 5)
+              || !strncasecmp(*name, "this ", 5)) {
+      *name += 5;
+      *flags &=
+       ~(MAT_POSSESSION | MAT_EXIT | MAT_REMOTE_CONTENTS | MAT_CONTAINER);
+    }
+  }
+  if ((*flags & MAT_POSSESSION) && (!strncasecmp(*name, "my ", 3)
+                                   || !strncasecmp(*name, "me ", 3))) {
+    *name += 3;
+    *flags &= ~(MAT_NEIGHBOR | MAT_EXIT | MAT_CONTAINER | MAT_REMOTE_CONTENTS);
+  }
+  if ((*flags & MAT_EXIT) && (!strncasecmp(*name, "toward ", 7))) {
+    *name += 7;
+    *flags &=
+      ~(MAT_NEIGHBOR | MAT_POSSESSION | MAT_CONTAINER | MAT_REMOTE_CONTENTS);
+  }
+
+  while (**name == ' ')
+    (*name)++;
+
+  /* If the name was just 'toward' (with no object name), reset 
+   * everything and press on.
+   */
+  if (!**name) {
+    *name = savename;
+    *flags = saveflags;
+    return 0;
+  }
+
+  /* Handle count adjectives */
+  if (!isdigit((unsigned char) **name)) {
+    /* Quick exit */
+    return 0;
+  }
+  mname = strchr(*name, ' ');
+  if (!mname) {
+    /* Quick exit - count without a noun */
+    return 0;
+  }
+  /* Ok, let's see if we can get a count adjective */
+  savename = *name;
+  *mname = '\0';
+  count = strtoul(*name, &e, 10);
+  if (e && *e) {
+    if (count < 1) {
+      count = -1;
+    } else if ((count > 10) && (count < 14)) {
+      if (strcasecmp(e, "th"))
+       count = -1;
+    } else if ((count % 10) == 1) {
+      if (strcasecmp(e, "st"))
+       count = -1;
+    } else if ((count % 10) == 2) {
+      if (strcasecmp(e, "nd"))
+       count = -1;
+    } else if ((count % 10) == 3) {
+      if (strcasecmp(e, "rd"))
+       count = -1;
+    } else if (strcasecmp(e, "th")) {
+      count = -1;
+    }
+  }
+  *mname = ' ';
+  if (count < 0) {
+    /* An error (like '0th' or '12nd') - this wasn't really a count
+     * adjective. Reset and press on. */
+    *name = savename;
+    return 0;
+  }
+  /* We've got a count adjective */
+  *name = mname + 1;
+  while (**name == ' ')
+    (*name)++;
+  return count;
+}
+
+
+static dbref
+match_me(const dbref who, const char *name)
+{
+  return (!strcasecmp(name, "me")) ? who : NOTHING;
+}
+
+static dbref
+match_here(const dbref who, const char *name)
+{
+  return (!strcasecmp(name, "here") && GoodObject(Location(who))) ?
+    Location(who) : NOTHING;
+}
+
+
+static dbref
+match_player(const dbref matcher, const char *match_name)
+{
+  dbref match;
+  const char *p;
+
+  if (*match_name != LOOKUP_TOKEN)
+    return NOTHING;
+  for (p = match_name + 1; isspace((unsigned char) *p); p++) ;
+  /* If lookup_player fails, try a partial match on connected
+   * players, 2.0 style. That can return match, NOTHING, AMBIGUOUS
+   */
+  match = lookup_player(p);
+  return (match != NOTHING) ? match : visible_short_page(matcher, p);
+}
+
+/* Run down a contents list and try to match */
+MATCH_FUNC(match_list)
+{
+  dbref absolute;
+  dbref alias_match;
+  int match_type = MATCH_NONE;
+  int nth_match = (*exact_matches_to_go != 0);
+
+  /* If we were given an absolute dbref, remember it */
+  absolute = match_absolute(name);
+  /* If we were given a player name, remember it */
+  alias_match = lookup_player(name);
+
+  DOLIST(first, first) {
+    if (first == absolute) {
+      /* Got an absolute match, return it */
+      *match = first;
+      (*exact_matches_to_go)--;
+      (*matches_to_go)--;
+      return MATCH_EXACT;
+    } else if( can_interact(first, who, INTERACT_MATCH) && (!strcasecmp(Name(first), name) ||
+                           (GoodObject(alias_match) && (alias_match == first)))) {
+      /* An exact match, but there may be others */
+      (*exact_matches_to_go)--;
+      (*matches_to_go)--;
+      if (nth_match) {
+       if (!(*exact_matches_to_go)) {
+         /* We're done */
+         *match = first;
+         return MATCH_EXACT;
+       }
+      } else {
+       if (match_type == MATCH_EXACT)
+         *match = choose_thing(who, type, flags, *match, first);
+       else
+         *match = first;
+       match_type = MATCH_EXACT;
+      }
+    } else if ((match_type != MATCH_EXACT) 
+                   && string_match(Name(first), name)
+                   && can_interact(first, who, INTERACT_MATCH)) {
+      /* A partial match, and we haven't done an exact match yet */
+      (*matches_to_go)--;
+      if (nth_match) {
+       if (!(*matches_to_go))
+         *match = first;
+      } else if (match_type == MATCH_PARTIAL)
+       *match = choose_thing(who, type, flags, *match, first);
+      else
+       *match = first;
+      match_type = MATCH_PARTIAL;
+    }
+  }
+  /* If we've made the nth partial match in this round, there's none to go */
+  if (nth_match && *matches_to_go < 0)
+    *matches_to_go = 0;
+  return match_type;
+}
+
+/* We recursively search upwards trying to match exits */
+MATCH_FUNC(match_exit)
+{
+  dbref loc;
+  int mret;
+
+  loc = (IsRoom(who)) ? who : Location(who);
+  if (flags & MAT_REMOTES) {
+    if (GoodObject(loc))
+      return match_exit_internal(who, name, type, flags, Zone(loc),
+                                match, exact_matches_to_go, matches_to_go);
+    else
+      return NOTHING;
+  } else if (flags & MAT_GLOBAL)
+    return match_exit_internal(who, name, type, flags, MASTER_ROOM,
+                              match, exact_matches_to_go, matches_to_go);
+  /* Recursively search upwards in locations parent tree */
+  while(GoodObject(loc) && !IsGarbage(loc) && mret != MATCH_EXACT ) {
+    mret = match_exit_internal(who, name, type, flags, loc,
+                            match, exact_matches_to_go, matches_to_go);
+    loc = Parent(loc);
+  }
+  return mret;
+}
+
+MATCH_FUNC(match_exit_internal)
+{
+  dbref exit_tmp;
+  dbref absolute;
+  int match_type = MATCH_NONE;
+  int nth_match = (*exact_matches_to_go != 0);
+
+  if (!GoodObject(first) || !IsRoom(first) || !name || !*name)
+    return NOTHING;
+  /* Store an absolute dbref match if given */
+  absolute = match_absolute(name);
+  DOLIST(exit_tmp, Exits(first)) {
+
+    if ((exit_tmp == absolute) && (can_interact(exit_tmp, who, INTERACT_MATCH))) {
+      /* Absolute match. Return immediately */
+      *match = exit_tmp;
+      (*exact_matches_to_go)--;
+      (*matches_to_go)--;
+      return MATCH_EXACT;
+    } else if (check_alias(name, Name(exit_tmp))
+              && (can_interact(exit_tmp, who, INTERACT_MATCH))) {
+      /* Matched an exit alias, but there may be more */
+      (*exact_matches_to_go)--;
+      (*matches_to_go)--;
+      if (nth_match) {
+       if (!(*exact_matches_to_go)) {
+         /* We're done */
+         *match = exit_tmp;
+         return MATCH_EXACT;
+       }
+      } else {
+       if (match_type == MATCH_EXACT)
+         *match = choose_thing(who, type, flags, *match, exit_tmp);
+       else
+         *match = exit_tmp;
+       match_type = MATCH_EXACT;
+      }
+    }
+  }
+  /* If we've made the nth partial match in this round, there's none to go */
+  if (nth_match && *matches_to_go < 0)
+    *matches_to_go = 0;
+  return match_type;
+}
+
+
+MATCH_FUNC(match_possession)
+{
+  if (!GoodObject(who))
+    return NOTHING;
+  return match_list(who, name, type, flags, Contents(who), match,
+                   exact_matches_to_go, matches_to_go);
+}
+
+MATCH_FUNC(match_container)
+{
+  if (!GoodObject(who))
+    return NOTHING;
+  return match_list(who, name, type, flags, Location(who), match,
+                   exact_matches_to_go, matches_to_go);
+}
+
+MATCH_FUNC(match_neighbor)
+{
+  dbref loc;
+  if (!GoodObject(who))
+    return NOTHING;
+  loc = Location(who);
+  if (!GoodObject(loc))
+    return NOTHING;
+  return match_list(who, name, type, flags, Contents(loc), match,
+                   exact_matches_to_go, matches_to_go);
+}
+
+
+static dbref
+choose_thing(const dbref match_who, const int preferred_type, long flags,
+            dbref thing1, dbref thing2)
+{
+  int has1;
+  int has2;
+  /* If there's only one valid thing, return it */
+  /* (Apologies to Theodor Geisel) */
+  if (thing1 == NOTHING)
+    return thing2;
+  else if (thing2 == NOTHING)
+    return thing1;
+
+  /* If a type is given, and only one thing is of that type, return it */
+  if (preferred_type != NOTYPE) {
+    if (Typeof(thing1) == preferred_type) {
+      if (Typeof(thing2) != preferred_type)
+       return thing1;
+    } else if (Typeof(thing2) == preferred_type)
+      return thing2;
+  }
+
+  /* If we've asked for a basic lock check, and only one passes, use that */
+  if (flags & MAT_CHECK_KEYS) {
+    has1 = could_doit(match_who, thing1);
+    has2 = could_doit(match_who, thing2);
+    if (has1 && !has2)
+      return thing1;
+    else if (has2 && !has1)
+      return thing2;
+  }
+
+  /* No luck. Return the higher dbref */
+  return (thing1 > thing2 ? thing1 : thing2);
+}
diff --git a/src/memcheck.c b/src/memcheck.c
new file mode 100644 (file)
index 0000000..a151c95
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * \file memcheck.c
+ *
+ * \brief A simple memory allocation tracker for PennMUSH.
+ *
+ * This code isn't usually compiled in, but it's handy to debug
+ * memory leaks sometimes.
+ *
+ *
+ */
+#include "config.h"
+#include "conf.h"
+#include "copyrite.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+
+#include "externs.h"
+#include "dbdefs.h"
+#include "mymalloc.h"
+#include "log.h"
+#include "confmagic.h"
+
+typedef struct mem_check MEM;
+
+/** A linked list for storing memory allocation counts */
+struct mem_check {
+  int ref_count;               /**< Number of allocations of this type. */
+  MEM *next;                   /**< Pointer to next in linked list. */
+  char ref_name[BUFFER_LEN];   /**< Name of this allocation type. */
+};
+
+static MEM *my_check = NULL;
+
+/*** WARNING! DO NOT USE strcasecoll IN THESE FUNCTIONS OR YOU'LL CREATE
+ *** AN INFINITE LOOP. DANGER, WILL ROBINSON!
+ ***/
+
+/** Add an allocation check.
+ * \param ref type of allocation.
+ */
+void
+add_check(const char *ref)
+{
+  MEM *loop, *newcheck, *prev = NULL;
+  size_t reflen;
+  int cmp;
+
+  if (!options.mem_check)
+    return;
+
+  for (loop = my_check; loop; loop = loop->next) {
+    cmp = strcasecmp(ref, loop->ref_name);
+    if (cmp == 0) {
+      loop->ref_count++;
+      return;
+    } else if (cmp < 0)
+      break;
+    prev = loop;
+  }
+  reflen = strlen(ref) + 1;
+  newcheck = (MEM *) malloc(sizeof(MEM) - BUFFER_LEN + reflen);
+  memcpy(newcheck->ref_name, ref, reflen);
+  newcheck->ref_count = 1;
+  newcheck->next = loop;
+  if (prev)
+    prev->next = newcheck;
+  else
+    my_check = newcheck;
+  return;
+}
+
+/** Remove an allocation check.
+ * \param ref type of allocation to remove.
+ */
+void
+del_check(const char *ref)
+{
+  MEM *loop, *prev = NULL;
+  int cmp;
+
+  if (!options.mem_check)
+    return;
+
+  for (loop = my_check; loop; loop = loop->next) {
+    cmp = strcasecmp(ref, loop->ref_name);
+    if (cmp == 0) {
+      loop->ref_count--;
+      if (!loop->ref_count) {
+       if (!prev)
+         my_check = loop->next;
+       else
+         prev->next = loop->next;
+       free(loop);
+      }
+      return;
+    } else if (cmp < 0)
+      break;
+    prev = loop;
+  }
+  do_rawlog(LT_CHECK,
+           T("ERROR: Tried deleting a check that was never added! :%s\n"),
+           ref);
+}
+
+/** List allocations.
+ * \param player the enactor.
+ */
+void
+list_mem_check(dbref player)
+{
+  MEM *loop;
+
+  if (!options.mem_check)
+    return;
+  for (loop = my_check; loop; loop = loop->next) {
+    notify_format(player, "%s : %d", loop->ref_name, loop->ref_count);
+  }
+}
+
+/** Log all allocations.
+ */
+void
+log_mem_check(void)
+{
+  MEM *loop;
+
+  if (!options.mem_check)
+    return;
+  do_rawlog(LT_CHECK, "MEMCHECK dump starts");
+  for (loop = my_check; loop; loop = loop->next) {
+    do_rawlog(LT_CHECK, "%s : %d", loop->ref_name, loop->ref_count);
+  }
+  do_rawlog(LT_CHECK, "MEMCHECK dump ends");
+}
diff --git a/src/move.c b/src/move.c
new file mode 100644 (file)
index 0000000..97934f8
--- /dev/null
@@ -0,0 +1,1392 @@
+/**
+ * \file move.c
+ *
+ * \brief Movement commands for PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "match.h"
+#include "flags.h"
+#include "lock.h"
+#include "dbdefs.h"
+#include "parse.h"
+#include "log.h"
+#include "command.h"
+#include "cmds.h"
+#include "game.h"
+#include "confmagic.h"
+
+void moveit(dbref what, dbref where, int nomovemsgs);
+static void send_contents(dbref loc, dbref dest);
+static void maybe_dropto(dbref loc, dbref dropto);
+static dbref find_var_dest(dbref player, dbref exit_obj);
+static void add_follower(dbref leader, dbref follower);
+static void add_following(dbref follower, dbref leader);
+static void add_follow(dbref leader, dbref follower, int noisy);
+static void del_follower(dbref leader, dbref follower);
+static void del_following(dbref follower, dbref leader);
+static void del_follow(dbref follower, dbref leader, int noisy);
+static char *list_followers(dbref player);
+static char *list_following(dbref player);
+static int is_following(dbref follower, dbref leader);
+static void follower_command(dbref leader, dbref loc, const char *com);
+
+/** A convenience wrapper for enter_room().
+ * \param what object to move.
+ * \param where location to move it to.
+ */
+void
+moveto(dbref what, dbref where)
+{
+  enter_room(what, where, 0);
+}
+
+
+/** Send an object somewhere.
+ * \param what object to move.
+ * \param where location to move it to.
+ * \param nomovemsgs if 1, don't show movement messages.
+ */
+void
+moveit(dbref what, dbref where, int nomovemsgs)
+{
+  dbref loc, old;
+  dbref absloc, absold;
+
+  /* Don't move something into something it's holding */
+  if (recursive_member(where, what, 0))
+    return;
+
+  /* remove what from old loc */
+  absold = absolute_room(what);
+  if ((loc = old = Location(what)) != NOTHING) {
+    Contents(loc) = remove_first(Contents(loc), what);
+  }
+  /* test for special cases */
+  switch (where) {
+  case NOTHING:
+    Location(what) = NOTHING;
+    return;                    /* NOTHING doesn't have contents */
+  case HOME:
+    where = Home(what);                /* home */
+    safe_tel(what, where, nomovemsgs);
+    return;
+    /*NOTREACHED */
+    break;
+  }
+
+  /* now put what in where */
+  PUSH(what, Contents(where));
+
+  Location(what) = where;
+  absloc = absolute_room(what);
+  if (!DARK_NOAENTER || !DarkLegal(what))
+    if ((where != NOTHING) && (old != where)) {
+      did_it(what, what, NULL, NULL, "OXMOVE", NULL, NULL, old);
+      if (Hearer(what) && !nomovemsgs) {
+       did_it_interact(what, old, "LEAVE", NULL, "OLEAVE", T("has left."),
+                       "ALEAVE", old, NA_INTER_PRESENCE);
+       /* If the player is leaving a zone, do zone messages */
+       /* The tricky bit here is that we only care about the zone of
+        * the outermost contents */
+       if (GoodObject(absold) && GoodObject(Zone(absold))
+           && (Zone(absloc) != Zone(absold)))
+         did_it_interact(what, Zone(absold), "ZLEAVE", NULL, "OZLEAVE", NULL,
+                         "AZLEAVE", old, NA_INTER_SEE);
+       if (GoodObject(old) && !IsRoom(old))
+         did_it_interact(what, old, NULL, NULL, "OXLEAVE", NULL, NULL, where,
+                         NA_INTER_SEE);
+       if (!IsRoom(where))
+         did_it_interact(what, where, NULL, NULL, "OXENTER", NULL, NULL, old,
+                         NA_INTER_SEE);
+       /* If the player is entering a new zone, do zone messages */
+       if (!GoodObject(absold)
+           || (GoodObject(Zone(absloc)) && (Zone(absloc) != Zone(absold))))
+         did_it_interact(what, Zone(absloc), "ZENTER", NULL, "OZENTER", NULL,
+                         "AZENTER", where, NA_INTER_SEE);
+       did_it_interact(what, where, "ENTER", NULL, "OENTER", T("has arrived."),
+                       "AENTER", where, NA_INTER_PRESENCE);
+      } else {
+       /* non-listeners only trigger the actions not the messages */
+       did_it(what, old, NULL, NULL, NULL, NULL, "ALEAVE", old);
+       if (GoodObject(absold) && GoodObject(Zone(absold))
+           && (Zone(absloc) != Zone(absold)))
+         did_it(what, Zone(absold), NULL, NULL, NULL, NULL, "AZLEAVE", old);
+       if (!GoodObject(absold)
+           || (GoodObject(Zone(absloc)) && (Zone(absloc) != Zone(absold))))
+         did_it(what, Zone(absloc), NULL, NULL, NULL, NULL, "AZENTER", where);
+       did_it(what, where, NULL, NULL, NULL, NULL, "AENTER", where);
+      }
+    }
+  if (!nomovemsgs)
+    did_it_interact(what, what, "MOVE", NULL, "OMOVE", NULL, "AMOVE", where,
+                   NA_INTER_SEE);
+}
+
+/** A dropper is an object that can hear and has a connected owner */
+#define Dropper(thing) (Hearer(thing) && Connected(Owner(thing)))
+
+static void
+send_contents(dbref loc, dbref dest)
+{
+  dbref first;
+  dbref rest;
+  first = Contents(loc);
+  Contents(loc) = NOTHING;
+
+  /* blast locations of everything in list */
+  DOLIST(rest, first) {
+    Location(rest) = NOTHING;
+  }
+
+  while (first != NOTHING) {
+    rest = Next(first);
+    if (Dropper(first) || !eval_lock(first, loc, Dropto_Lock)) {
+      Location(first) = loc;
+      PUSH(first, Contents(loc));
+    } else
+      enter_room(first, Sticky(first) ? HOME : dest, 0);
+    first = rest;
+  }
+
+  Contents(loc) = reverse(Contents(loc));
+}
+
+static void
+maybe_dropto(dbref loc, dbref dropto)
+{
+  dbref thing;
+  if (loc == dropto)
+    return;                    /* bizarre special case */
+  if (!IsRoom(loc))
+    return;
+  /* check for players */
+  DOLIST(thing, Contents(loc)) {
+    if (Dropper(thing))
+      return;
+  }
+
+  /* no players, send everything to the dropto */
+  send_contents(loc, dropto);
+}
+
+/** Enter a container.
+ * \param player object entering the container.
+ * \param loc container to enter.
+ * \param nomovemsgs if 1, don't give movement messages.
+ */
+void
+enter_room(dbref player, dbref loc, int nomovemsgs)
+{
+  dbref old;
+  dbref dropto;
+  static int deep = 0;
+  if (deep++ > 15) {
+    deep--;
+    return;
+  }
+  if (!GoodObject(player)) {
+    deep--;
+    return;
+  }
+  /* check for room == HOME */
+  if (loc == HOME)
+    loc = Home(player);
+
+  if (!Mobile(player)) {
+    do_rawlog(LT_ERR, T("ERROR: Non object moved!! %d\n"), player);
+    deep--;
+    return;
+  }
+  if (IsExit(loc)) {
+    do_rawlog(LT_ERR, T("ERROR: Attempt to move %d to exit %d\n"), player, loc);
+    deep--;
+    return;
+  }
+  if (loc == player) {
+    do_rawlog(LT_ERR, T("ERROR: Attempt to move player %d into itself\n"),
+             player);
+    deep--;
+    return;
+  }
+  if (recursive_member(loc, player, 0)) {
+    do_rawlog(LT_ERR,
+             T("ERROR: Attempt to move player %d into carried object %d\n"),
+             player, loc);
+    deep--;
+    return;
+  }
+  /* get old location */
+  old = Location(player);
+
+  /* go there */
+  moveit(player, loc, nomovemsgs);
+
+  /* if old location has STICKY dropto, send stuff through it */
+
+  if ((loc != old) && Dropper(player) &&
+      (old != NOTHING) && (IsRoom(old)) &&
+      ((dropto = Location(old)) != NOTHING) && Sticky(old))
+    maybe_dropto(old, dropto);
+
+
+  /* autolook */
+  look_room(player, loc, 2);
+  deep--;
+}
+
+
+/** Teleport player to location while removing items they shouldn't take.
+ * \param player player to teleport.
+ * \param dest location to teleport player to.
+ * \param nomovemsgs if 1, don't show movement messages
+ */
+void
+safe_tel(dbref player, dbref dest, int nomovemsgs)
+{
+  dbref first;
+  dbref rest;
+  if (dest == HOME)
+    dest = Home(player);
+  if (Owner(Location(player)) == Owner(dest)) {
+    enter_room(player, dest, nomovemsgs);
+    return;
+  }
+  first = Contents(player);
+  Contents(player) = NOTHING;
+
+  /* blast locations of everything in list */
+  DOLIST(rest, first) {
+    Location(rest) = NOTHING;
+  }
+
+  while (first != NOTHING) {
+    rest = Next(first);
+    /* if thing is ok to take then move to player else send home.
+     * thing is not okay to move if it's STICKY and its home is not
+     * the player.
+     */
+    if (!controls(player, first)
+       && (Sticky(first)
+           && (Home(first) != player)))
+      enter_room(first, HOME, nomovemsgs);
+    else {
+      PUSH(first, Contents(player));
+      Location(first) = player;
+    }
+    first = rest;
+  }
+  Contents(player) = reverse(Contents(player));
+  enter_room(player, dest, nomovemsgs);
+}
+
+/** Can a player go in a given direction?
+ * This checks to see if there's a go-able direction. It doesn't
+ * check whether the GOTO command is restricted. That should be
+ * done by the command parser.
+ * \param player dbref of mover.
+ * \param direction name of direction to move.
+ */
+int
+can_move(dbref player, const char *direction)
+{
+  if (!strcasecmp(direction, "home") && !Fixed(player) && !RPMODE(player))
+    return 1;
+
+  /* otherwise match on exits - don't use GoodObject here! */
+  return (match_result(player, direction, TYPE_EXIT, MAT_ENGLISH | MAT_EXIT) !=
+         NOTHING);
+}
+
+static dbref
+find_var_dest(dbref player, dbref exit_obj)
+{
+  /* This is used to evaluate the u-function DESTINATION on an exit with
+   * a VARIABLE (ambiguous) link.
+   */
+
+  char const *abuf, *ap;
+  char buff[BUFFER_LEN], *bp;
+  ATTR *a;
+  dbref dest_room;
+  /* We'd like a DESTINATION attribute, but we'll settle for EXITTO,
+   * for portability
+   */
+  a = atr_get(exit_obj, "DESTINATION");
+  if (!a)
+    a = atr_get(exit_obj, "EXITTO");
+  if (!a)
+    return NOTHING;
+  abuf = safe_atr_value(a);
+  if (!abuf)
+    return NOTHING;
+  if (!*abuf) {
+    free((Malloc_t) abuf);
+    return NOTHING;
+  }
+  ap = abuf;
+  bp = buff;
+  process_expression(buff, &bp, &ap, exit_obj, player, player,
+                    PE_DEFAULT, PT_DEFAULT, NULL);
+  *bp = '\0';
+  dest_room = parse_objid(buff);
+  free((Malloc_t) abuf);
+  return (dest_room);
+}
+
+
+/** The move command.
+ * \param player the enactor.
+ * \param direction name of direction to move.
+ * \param type type of motion to check (global, zone, neither).
+ */
+void
+do_move(dbref player, const char *direction, enum move_type type)
+{
+  dbref exit_m, loc, var_dest;
+  if(!strcasecmp(direction, "home") && (RPMODE(player) || Fixed(player))) {
+         notify(player, "Not right now pal.");
+         return;
+  }
+#ifdef RPMODE_SYS
+  if(IsParalyzed(player)) {
+    notify(player, "You are temporarily paralyzed.");
+    return;
+  }
+#endif
+  if (!strcasecmp(direction, "home")) {
+    /* send him home */
+    /* but steal all his possessions */
+    if (!Mobile(player) || !GoodObject(Home(player)) ||
+       recursive_member(Home(player), player, 0)
+       || (player == Home(player))) {
+      notify(player, T("Bad destination."));
+      return;
+    }
+    if ((loc = Location(player)) != NOTHING && !Dark(player) && !Dark(loc)) {
+      /* tell everybody else */
+      notify_except(Contents(loc), player,
+                   tprintf(T("%s goes home."), Name(player)), NA_INTER_SEE);
+    }
+    /* give the player the messages */
+    notify(player, T("There's no place like home..."));
+    notify(player, T("There's no place like home..."));
+    notify(player, T("There's no place like home..."));
+    safe_tel(player, HOME, 0);
+  } else {
+    /* find the exit */
+    if (type == MOVE_GLOBAL)
+      exit_m =
+       match_result(player, direction, TYPE_EXIT,
+                    MAT_ENGLISH | MAT_EXIT | MAT_GLOBAL | MAT_CHECK_KEYS);
+    else if (type == MOVE_ZONE)
+      exit_m =
+       match_result(player, direction, TYPE_EXIT,
+                    MAT_ENGLISH | MAT_EXIT | MAT_REMOTES | MAT_CHECK_KEYS);
+    else
+      exit_m =
+       match_result(player, direction, TYPE_EXIT,
+                    MAT_ENGLISH | MAT_EXIT | MAT_CHECK_KEYS);
+    switch (exit_m) {
+    case NOTHING:
+      /* try to force the object */
+      notify(player, T("You can't go that way."));
+      break;
+    case AMBIGUOUS:
+      notify(player, T("I don't know which way you mean!"));
+      break;
+    default:
+      /* we got one */
+      /* check to see if we're allowed to pass */
+      if (!eval_lock(player, Location(player), Leave_Lock)) {
+       fail_lock(player, Location(player), Leave_Lock,
+                 T("You can't go that way."), NOTHING);
+       return;
+      }
+
+      if (could_doit(player, exit_m)) {
+       switch (Location(exit_m)) {
+       case HOME:
+         var_dest = Home(player);
+         break;
+       case AMBIGUOUS:
+         var_dest = find_var_dest(player, exit_m);
+         /* Only allowed if the owner of the exit could link to var_dest */
+         if (GoodObject(var_dest) && !can_link_to(exit_m, var_dest))
+           var_dest = NOTHING;
+         break;
+       default:
+         var_dest = Location(exit_m);
+       }
+
+       if (!GoodObject(var_dest)) {
+         do_rawlog(LT_ERR,
+                   T("Exit #%d destination became %d during move.\n"),
+                   exit_m, var_dest);
+         notify(player, T("Exit destination is invalid."));
+         return;
+       }
+       if (recursive_member(var_dest, player, 0)) {
+         notify(player, T("Exit destination is invalid."));
+         return;
+       }
+       did_it(player, exit_m, "SUCCESS", NULL, "OSUCCESS", NULL,
+              "ASUCCESS", NOTHING);
+       did_it(player, exit_m, "DROP", NULL, "ODROP", NULL, "ADROP", var_dest);
+       switch (Typeof(var_dest)) {
+       case TYPE_ROOM:
+         /* Remember the current room */
+         loc = Location(player);
+         /* Move the leader */
+         enter_room(player, var_dest, 0);
+         /* Move the followers if the leader is elsewhere */
+         if (Location(player) != loc)
+           follower_command(player, loc, tprintf("%s #%d", "move", exit_m));
+         break;
+        case TYPE_DIVISION:
+       case TYPE_PLAYER:
+       case TYPE_THING:
+         if (IsGarbage(var_dest)) {
+           notify(player, T("You can't go that way."));
+           return;
+         }
+         if (Location(var_dest) == NOTHING)
+           return;
+         /* Remember the current room */
+         loc = Location(player);
+         /* Move the leader */
+         safe_tel(player, var_dest, 0);
+         /* Move the followers if the leader is elsewhere */
+         if (Location(player) != loc)
+           follower_command(player, loc, tprintf("%s #%d", "move", exit_m));
+         break;
+       case TYPE_EXIT:
+         notify(player, T("This feature coming soon."));
+         break;
+       }
+      } else
+       fail_lock(player, exit_m, Basic_Lock, T("You can't go that way."),
+                 NOTHING);
+      break;
+    }
+  }
+}
+
+/** Move an exit to the first position in the room's exit list.
+ * \verbatim
+ * This implements @firstexit.
+ * \endverbatim
+ * \param player the enactor.
+ * \param what name of exit to promote.
+ */
+void
+do_firstexit(dbref player, const char *what)
+{
+  dbref thing;
+  dbref loc;
+  if ((thing =
+       noisy_match_result(player, what, TYPE_EXIT,
+                         MAT_ENGLISH | MAT_EXIT)) == NOTHING)
+    return;
+  loc = Home(thing);
+  if (!controls(player, loc)) {
+    notify(player, T("You cannot modify exits in that room."));
+    return;
+  }
+  Exits(loc) = remove_first(Exits(loc), thing);
+  Source(thing) = loc;
+  PUSH(thing, Exits(loc));
+  notify_format(player, T("%s is now the first exit."), Name(thing));
+}
+
+
+/** The get command.
+ * \param player the enactor.
+ * \param what name of object to get.
+ */
+void
+do_get(dbref player, const char *what)
+{
+  dbref loc = Location(player);
+  dbref thing;
+  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN], *tp;
+  long match_flags =
+    MAT_NEIGHBOR | MAT_ABSOLUTE | MAT_CHECK_KEYS | MAT_NEAR | MAT_ENGLISH;
+
+  if (!IsRoom(loc) && !EnterOk(loc) && !controls(player, loc)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (!Long_Fingers(player))
+    match_flags |= MAT_CONTROL;
+  if (match_result(player, what, TYPE_THING, match_flags) == NOTHING) {
+    if (POSSESSIVE_GET) {
+      dbref box;
+      const char *boxname = what;
+      /* take care of possessive get (stealing) */
+      box = parse_match_possessor(player, &what);
+      if (box == NOTHING) {
+         notify(player, T("I don't see that here."));
+       return;
+      } else if (box == AMBIGUOUS) {
+       notify_format(player, T("I can't tell which %s."), boxname);
+       return;
+      }
+      thing = match_result(box, what, NOTYPE, MAT_POSSESSION);
+      if (thing == NOTHING) {
+       notify(player, T("I don't see that here."));
+       return;
+      } else if (thing == AMBIGUOUS) {
+       notify_format(player, T("I can't tell which %s."), what);
+       return;
+      }
+      /* to steal something, you have to be able to get it, and the
+       * object must be ENTER_OK and not take-locked against you.
+       */
+      if (could_doit(player, thing) &&
+          (POSSGET_ON_DISCONNECTED ||
+           (!IsPlayer(Location(thing)) ||
+            Connected(Location(thing)))) &&
+         (controls(player, thing) ||
+          (EnterOk(Location(thing)) &&
+           eval_lock(player, Location(thing), Take_Lock)))) {
+       notify_format(Location(thing),
+                     T("%s was taken from you."), Name(thing));
+       notify_format(thing, T("%s took you."), Name(player));
+       tp = tbuf1;
+       safe_format(tbuf1, &tp, T("You take %s from %s."), Name(thing),
+                   Name(Location(thing)));
+       *tp = '\0';
+       tp = tbuf2;
+       safe_format(tbuf2, &tp, T("takes %s from %s."), Name(thing),
+                   Name(Location(thing)));
+       *tp = '\0';
+       moveto(thing, player);
+       did_it(player, thing, "SUCCESS", tbuf1, "OSUCCESS", tbuf2, "ASUCCESS",
+              NOTHING);
+       did_it_with(player, player, "RECEIVE", NULL, "ORECEIVE", NULL,
+                   "ARECEIVE", NOTHING, thing, NOTHING, NA_INTER_HEAR);
+      } else
+       fail_lock(player, thing, Basic_Lock,
+                 T("You can't take that from there."), NOTHING);
+    } else {
+       notify(player, T("I don't see that here."));
+    }
+    return;
+  } else {
+    if ((thing = noisy_match_result(player, what, TYPE_THING, match_flags))
+       != NOTHING) {
+      if (Location(thing) == player) {
+       notify(player, T("You already have that!"));
+       return;
+      }
+      if (Location(player) == thing) {
+       notify(player, T("It's all around you!"));
+       return;
+      }
+      if (recursive_member(player, thing, 0)) {
+       notify(player, T("Bad destination."));
+       return;
+      }
+      switch (Typeof(thing)) {
+      case TYPE_DIVISION:
+      case TYPE_PLAYER:
+      case TYPE_THING:
+       if (thing == player) {
+         notify(player, T("You cannot get yourself!"));
+         return;
+       }
+       if (!eval_lock(player, Location(thing), Take_Lock)) {
+         fail_lock(player, Location(thing), Take_Lock,
+                   T("You can't take that from there."), NOTHING);
+         return;
+       }
+       if (could_doit(player, thing)) {
+         moveto(thing, player);
+         notify_format(thing, T("%s took you."), Name(player));
+         tp = tbuf1;
+         safe_format(tbuf1, &tp, T("You take %s."), Name(thing));
+         *tp = '\0';
+         tp = tbuf2;
+         safe_format(tbuf2, &tp, T("takes %s."), Name(thing));
+         *tp = '\0';
+         did_it(player, thing, "SUCCESS", tbuf1, "OSUCCESS", tbuf2,
+                "ASUCCESS", NOTHING);
+         did_it_with(player, player, "RECEIVE", NULL, "ORECEIVE", NULL,
+                     "ARECEIVE", NOTHING, thing, NOTHING, NA_INTER_HEAR);
+       } else
+         fail_lock(player, thing, Basic_Lock, T("You can't pick that up."),
+                   NOTHING);
+       break;
+      case TYPE_EXIT:
+       notify(player, T("You can't pick up exits."));
+       return;
+      default:
+       notify(player, T("You can't take that!"));
+       break;
+      }
+    }
+  }
+}
+
+
+/** Drop an object.
+ * \param player the enactor.
+ * \param name name of object to drop.
+ */
+void
+do_drop(dbref player, const char *name)
+{
+  dbref loc;
+  dbref thing;
+  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN], *tp;
+  if ((loc = Location(player)) == NOTHING)
+    return;
+  switch (thing =
+         match_result(player, name, TYPE_THING,
+                      MAT_POSSESSION | MAT_ABSOLUTE | MAT_CONTROL |
+                      MAT_ENGLISH)) {
+  case NOTHING:
+      notify(player, T("You don't have that!"));
+    return;
+  case AMBIGUOUS:
+    notify(player, T("I don't know which you mean!"));
+    return;
+  default:
+    if (Location(thing) != player) {
+      /* Shouldn't ever happen. */
+      notify(player, T("You can't drop that."));
+      return;
+    } else if (IsExit(thing)) {
+      notify(player, T("Sorry you can't drop exits."));
+      return;
+    } else if (!eval_lock(player, thing, Drop_Lock)) {
+      notify(player, T("You can't seem to get rid of that."));
+      return;
+    } else if (IsRoom(loc) && !eval_lock(player, loc, Drop_Lock)) {
+      notify(player, T("You can't seem to drop things here."));
+      return;
+    } else if (Sticky(thing) && !Fixed(thing)) {
+      notify(thing, T("Dropped."));
+      safe_tel(thing, HOME, 0);
+    } else if ((Location(loc) != NOTHING) && IsRoom(loc) && !Sticky(loc)
+              && eval_lock(thing, loc, Dropto_Lock)) {
+      /* location has immediate dropto */
+      notify_format(thing, T("%s drops you."), Name(player));
+      moveto(thing, Location(loc));
+    } else {
+      notify_format(thing, T("%s drops you."), Name(player));
+      moveto(thing, loc);
+    }
+    break;
+  }
+  tp = tbuf1;
+  safe_format(tbuf1, &tp, T("You drop %s."), Name(thing));
+  *tp = '\0';
+  tp = tbuf2;
+  safe_format(tbuf2, &tp, T("drops %s."), Name(thing));
+  *tp = '\0';
+  did_it(player, thing, "DROP", tbuf1, "ODROP", tbuf2, "ADROP", NOTHING);
+}
+
+/** The empty command.
+ * This command causes the player to attempt to move everything in
+ * the thing to the location of the thing.
+ * Thing must be in player's inventory or in player's location.
+ * For each item in thing, movement is allowed if one of these is true:
+ * (a) thing is inside player, and player is allowed to get thing's item
+ * (b) thing is next to player, player is allowed to get thing's item,
+ *     and player is allowed to drop item in player's location.
+ * We do not consider the cases of forcing the object to drop the items,
+ * teleporting the items out, or forcing the items to leave; 
+ * 'empty' implies that the items pass through the player's hands.
+ *
+ * There is a choice to be made here with regard to locks - do we 
+ * check locks on the thing (e.g. enter locks) and its location
+ * (e.g. drop locks) once or each time? If we choose once, we break
+ * locks that might make decisions based on the number of items there.
+ * If we choose multiple, we risk running side effects more than once.
+ * We choose multiple, as that's what would happen if the
+ * player did it manually.
+ * \param player the enactor.
+ * \param what the name of the object to empty.
+ */
+void
+do_empty(dbref player, const char *what)
+{
+  dbref player_loc;
+  dbref thing, thing_loc;
+  dbref item;
+  int empty_ok;
+  int count = 0;
+  int next;
+
+  if ((player_loc = Location(player)) == NOTHING)
+    return;
+  thing =
+    noisy_match_result(player, what, TYPE_THING | TYPE_PLAYER,
+                      MAT_NEAR_THINGS | MAT_ENGLISH);
+  if (!GoodObject(thing))
+    return;
+  thing_loc = Location(thing);
+
+  /* Object to empty must be in player's inventory or location */
+  if ((thing_loc != player) && (thing_loc != player_loc)) {
+    notify(player, T("You can't empty that from here."));
+    return;
+  }
+  for (item = first_visible(player, Contents(thing)); GoodObject(item);
+       item = first_visible(player, next)) {
+    next = Next(item);
+    if (IsExit(item))
+      continue;                        /* No dropping exits */
+    empty_ok = 0;
+    if (player == thing) {
+      /* empty me: You don't need to get what's in your inventory already */
+      if (eval_lock(player, item, Drop_Lock) &&
+         (!IsRoom(thing_loc) || eval_lock(player, thing_loc, Drop_Lock)))
+       empty_ok = 1;
+    }
+    /* Check that player can get stuff from thing */
+    else if (controls(player, thing) ||
+            (EnterOk(thing) && eval_lock(player, thing, Enter_Lock))) {
+      /* Check that player can get item */
+      if (!could_doit(player, item)) {
+       /* Send failure message if set, otherwise be quiet */
+       fail_lock(player, thing, Basic_Lock, NULL, NOTHING);
+       continue;
+      }
+      /* Now check for dropping in the destination */
+      /* Thing is in player's inventory - sufficient */
+      if (thing_loc == player)
+       empty_ok = 1;
+      /* Thing is in player's location - player must also be able to drop */
+      else if (eval_lock(player, item, Drop_Lock) &&
+              (!IsRoom(thing_loc) || eval_lock(player, thing_loc, Drop_Lock)))
+       empty_ok = 1;
+    }
+    /* Now do the work, if we should. That includes triggering messages */
+    if (empty_ok) {
+      char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN], *tp;
+
+      count++;
+      /* Get messages */
+      if (thing != player) {
+       notify_format(thing, T("%s was taken from you."), Name(item));
+       notify_format(item, T("%s took you."), Name(player));
+       tp = tbuf1;
+       safe_format(tbuf1, &tp, T("You take %s from %s."), Name(item),
+                   Name(thing));
+       *tp = '\0';
+       tp = tbuf2;
+       safe_format(tbuf2, &tp, T("takes %s from %s."), Name(item),
+                   Name(thing));
+       *tp = '\0';
+       moveto(item, player);
+       did_it(player, item, "SUCCESS", tbuf1, "OSUCCESS", tbuf2, "ASUCCESS",
+              NOTHING);
+       did_it_with(player, player, "RECEIVE", NULL, "ORECEIVE", NULL,
+                   "ARECEIVE", NOTHING, item, NOTHING, NA_INTER_HEAR);
+      }
+      /* Drop messages */
+      if (thing_loc != player) {
+       if (Sticky(item) && !Fixed(item)) {
+         safe_tel(thing, HOME, 0);
+       } else if ((Location(thing_loc) != NOTHING) && IsRoom(thing_loc)
+                  && !Sticky(thing_loc)
+                  && eval_lock(item, thing_loc, Dropto_Lock)) {
+         /* location has immediate dropto */
+         notify_format(item, T("%s drops you."), Name(player));
+         moveto(item, Location(thing_loc));
+       } else {
+         notify_format(item, T("%s drops you."), Name(player));
+         moveto(item, thing_loc);
+       }
+       tp = tbuf1;
+       safe_format(tbuf1, &tp, T("You drop %s."), Name(item));
+       *tp = '\0';
+       tp = tbuf2;
+       safe_format(tbuf2, &tp, T("drops %s."), Name(item));
+       *tp = '\0';
+       did_it(player, item, "DROP", tbuf1, "ODROP", tbuf2, "ADROP", NOTHING);
+      }
+    }
+  }
+  notify_format(player, T("You remove %d object%s from %s."),
+               count, (count == 1) ? "" : "s", Name(thing));
+  return;
+}
+
+
+/** The enter command.
+ * \param player the enactor.
+ * \param what name of object to enter.
+ */
+void
+do_enter(dbref player, const char *what)
+{
+  dbref thing;
+  dbref loc;
+  long match_flags = MAT_CHECK_KEYS | MAT_NEIGHBOR | MAT_ENGLISH | MAT_ABSOLUTE;
+  if ((thing = noisy_match_result(player, what, TYPE_THING, match_flags))
+      == NOTHING)
+    return;
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+  case TYPE_EXIT:
+    notify(player, T("Permission denied."));
+    break;
+  default:
+    /* Remember the current room */
+    loc = Location(player);
+    /* Only privileged players may enter something remotely */
+    if ((Location(thing) != loc) && !Admin(player)) {
+      notify(player, T("I don't see that here."));
+      return;
+    }
+    /* the object must pass the lock. Also, the thing being entered */
+    /* has to be controlled, or must be enter_ok */
+    if (!((EnterOk(thing) || controls(player, thing)) &&
+         (eval_lock(player, thing, Enter_Lock))
+       )) {
+      fail_lock(player, thing, Enter_Lock, T("Permission denied."), NOTHING);
+      return;
+    }
+    if (thing == player) {
+      notify(player, T("Sorry, you must remain beside yourself!"));
+      return;
+    }
+    /* Move the leader */
+    safe_tel(player, thing, 0);
+    /* Move the followers if the leader is elsewhere */
+    if (Location(player) != loc)
+      follower_command(player, loc, tprintf("%s #%d", "enter", thing));
+    break;
+  }
+}
+
+/** The leave command.
+ * \param player the enactor.
+ */
+void
+do_leave(dbref player)
+{
+  dbref loc;
+  loc = Location(player);
+  if (IsRoom(loc) || IsGarbage(loc) || IsGarbage(Location(loc))
+      || NoLeave(loc)
+      || !eval_lock(player, loc, Leave_Lock)
+    ) {
+    fail_lock(player, loc, Leave_Lock, T("You can't leave."), NOTHING);
+    return;
+  }
+  enter_room(player, Location(loc), 0);
+  if (Location(player) != loc)
+    follower_command(player, loc, "leave");
+}
+
+/** Is direction a global exit?
+ * \param player looker.
+ * \param direction name of exit.
+ * \retval 1 direction is a global exit.
+ * \retval 0 direction is not a global exit.
+ */
+int
+global_exit(dbref player, const char *direction)
+{
+  return (GoodObject
+         (match_result(player, direction, TYPE_EXIT, MAT_GLOBAL | MAT_EXIT)));
+}
+
+/** Is direction a remote exit?
+ * \param player looker.
+ * \param direction name of exit.
+ * \retval 1 direction is a remote exit.
+ * \retval 0 direction is not a remote exit.
+ */
+int
+remote_exit(dbref player, const char *direction)
+{
+  return (GoodObject
+         (match_result(player, direction, TYPE_EXIT, MAT_REMOTES | MAT_EXIT)));
+}
+
+/** Wrapper for exit movement.
+ * We check local exit, then zone exit, then global. If nothing is
+ * matched, treat it as local so player will get an error message.
+ * \param player the mover.
+ * \param command direction to move.
+ */
+void
+move_wrapper(dbref player, const char *command)
+{
+  if (!Mobile(player))
+    return;
+  if (!strcasecmp(command, "home") && Fixed(player) && !RPMODE(player)) {
+    notify(player, T("You can't do that IC!"));
+    return;
+  }
+  if (can_move(player, command))
+    do_move(player, command, 0);
+  else if ((Zone(Location(player)) != NOTHING) && remote_exit(player, command))
+    do_move(player, command, 2);
+  else if ((Location(player) != MASTER_ROOM)
+          && global_exit(player, command))
+    do_move(player, command, 1);
+  else
+    do_move(player, command, 0);
+}
+
+/* Routines for dealing with the follow commands */
+
+/** The follow command.
+ * \verbatim
+ * follow <arg> tries to start following
+ * follow alone lists who you're following
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg name of object to follow.
+ */
+void
+do_follow(dbref player, const char *arg)
+{
+  dbref leader;
+  if (arg && *arg) {
+    /* Who do we want to follow? */
+    leader = match_result(player, arg, NOTYPE, MAT_NEARBY);
+    if (leader == AMBIGUOUS) {
+      notify(player, T("I can't tell which one to follow."));
+      return;
+    }
+    if (!GoodObject(leader) || !GoodObject(Location(player))
+       || (IsPlayer(leader) && !Connected(leader))
+       || ((DarkLegal(leader)
+            || (Dark(Location(player)) && !Light(leader)))
+           && !CanSee(player, leader))) {
+      notify(player, T("You don't see that here."));
+      return;
+    }
+    if (!Mobile(leader)) {
+      notify(player, T("You can only follow players and things."));
+      return;
+    }
+    if (leader == player) {
+      notify(player, T("You chase your tail for a while and feel silly."));
+      return;
+    }
+    /* Are we already following them? */
+    if (is_following(player, leader)) {
+      notify_format(player, T("You're already following %s."), Name(leader));
+      return;
+    }
+    /* Ok, are we allowed to follow them? */
+    if (!eval_lock(player, leader, Follow_Lock)) {
+      fail_lock(player, leader, Follow_Lock,
+               T("You're not alllowed to follow."), Location(player));
+      return;
+    }
+    /* Ok, looks good */
+    add_follow(leader, player, 1);
+  } else {
+    /* List followers */
+    notify_format(player, T("You are following: %s"), list_following(player));
+    notify_format(player, T("You are followed by: %s"), list_followers(player));
+  }
+}
+
+/** The unfollow command.
+ * \verbatim
+ * unfollow <arg> removes someone from your following list 
+ * unfollow alone removes everyone from your following list.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg object to stop following.
+ */
+void
+do_unfollow(dbref player, const char *arg)
+{
+  dbref leader;
+  if (arg && *arg) {
+    /* Who do we want to stop following? */
+    leader = match_result(player, arg, NOTYPE, MAT_OBJECTS);
+    if (leader == AMBIGUOUS) {
+      notify(player, T("I can't tell which one to stop following."));
+      return;
+    }
+    if (!GoodObject(leader)) {
+      notify(player, T("I don't see that here."));
+      return;
+    }
+    /* Are we following them? */
+    if (!is_following(player, leader)) {
+      notify_format(player, T("You're not following %s."), Name(leader));
+      return;
+    }
+    /* Ok, looks good */
+    del_follow(leader, player, 1);
+  } else {
+    /* Stop following everyone */
+    clear_following(player, 1);
+    notify(player, T("You stop following anyone."));
+  }
+}
+
+
+/** The dismiss command.
+ * \verbatim
+ * dismiss <arg> removes someone from your followers list 
+ * dismiss alone removes everyone from your followers list.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg name of object to dismiss.
+ */
+void
+do_dismiss(dbref player, const char *arg)
+{
+  dbref follower;
+  if (arg && *arg) {
+    /* Who do we want to stop leading? */
+    follower = match_result(player, arg, NOTYPE, MAT_OBJECTS);
+    if (!GoodObject(follower)) {
+      notify(player, T("I don't recognize who you want to dismiss."));
+      return;
+    }
+    /* Are we following them? */
+    if (!is_following(follower, player)) {
+      notify_format(player, T("%s isn't following you."), Name(follower));
+      return;
+    }
+    /* Ok, looks good */
+    del_follow(player, follower, 1);
+  } else {
+    /* Stop leading everyone */
+    clear_followers(player, 1);
+    notify(player, T("You dismiss all your followers."));
+  }
+}
+
+/** The desert command.
+ * \verbatim
+ * desert <arg> removes someone from your followers and following list 
+ * desert alone removes everyone from both lists
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg name of object to desert.
+ */
+void
+do_desert(dbref player, const char *arg)
+{
+  dbref who;
+  if (arg && *arg) {
+    /* Who do we want to stop leading? */
+    who = match_result(player, arg, NOTYPE, MAT_OBJECTS);
+    if (!GoodObject(who)) {
+      notify(player, T("I don't recognize who you want to desert."));
+      return;
+    }
+    /* Are we following or leading them? */
+    if (!is_following(who, player)
+       && !is_following(player, who)) {
+      notify_format(player,
+                   T("%s isn't following you, nor vice versa."), Name(who));
+      return;
+    }
+    /* Ok, looks good */
+    del_follow(player, who, 1);
+    del_follow(who, player, 1);
+  } else {
+    /* Stop leading everyone */
+    clear_followers(player, 1);
+    clear_following(player, 1);
+    notify(player, T("You desert everyone you're leading or following."));
+  }
+}
+
+/* Add someone to a player's FOLLOWERS attribute */
+static void
+add_follower(dbref leader, dbref follower)
+{
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+  char *bp;
+  a = atr_get_noparent(leader, "FOLLOWERS");
+  if (!a) {
+    (void) atr_add(leader, "FOLLOWERS", unparse_dbref(follower), GOD, NOTHING);
+  } else {
+    bp = tbuf1;
+    safe_str(atr_value(a), tbuf1, &bp);
+    safe_chr(' ', tbuf1, &bp);
+    safe_dbref(follower, tbuf1, &bp);
+    *bp = '\0';
+    (void) atr_add(leader, "FOLLOWERS", tbuf1, GOD, NOTHING);
+  }
+}
+
+/* Add someone to a player's FOLLOWING attribute */
+static void
+add_following(dbref follower, dbref leader)
+{
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+  char *bp;
+  a = atr_get_noparent(follower, "FOLLOWING");
+  if (!a) {
+    (void) atr_add(follower, "FOLLOWING", unparse_dbref(leader), GOD, NOTHING);
+  } else {
+    bp = tbuf1;
+    safe_str(atr_value(a), tbuf1, &bp);
+    safe_chr(' ', tbuf1, &bp);
+    safe_dbref(leader, tbuf1, &bp);
+    *bp = '\0';
+    (void) atr_add(follower, "FOLLOWING", tbuf1, GOD, NOTHING);
+  }
+}
+
+static void
+add_follow(dbref leader, dbref follower, int noisy)
+{
+  char msg[BUFFER_LEN];
+  add_follower(leader, follower);
+  add_following(follower, leader);
+  if (noisy) {
+    strcpy(msg, tprintf(T("You begin following %s."), Name(leader)));
+    notify_format(leader, T("%s begins following you."), Name(follower));
+    did_it(follower, leader, "FOLLOW", msg, "OFOLLOW", NULL,
+          "AFOLLOW", NOTHING);
+  }
+}
+
+
+/* Delete someone from a player's FOLLOWERS attribute */
+static void
+del_follower(dbref leader, dbref follower)
+{
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+  char flwr[BUFFER_LEN];
+  a = atr_get_noparent(leader, "FOLLOWERS");
+  if (!a)
+    return;                    /* No followers, so no deletion */
+  /* Let's take it apart and put it back together w/o follower */
+  strcpy(flwr, unparse_dbref(follower));
+  strcpy(tbuf1, atr_value(a));
+  (void) atr_add(leader, "FOLLOWERS",
+                remove_word(tbuf1, flwr, ' '), GOD, NOTHING);
+}
+
+/* Delete someone from a player's FOLLOWING attribute */
+static void
+del_following(dbref follower, dbref leader)
+{
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+  char ldr[BUFFER_LEN];
+  a = atr_get_noparent(follower, "FOLLOWING");
+  if (!a)
+    return;                    /* Not following, so no deletion */
+  /* Let's take it apart and put it back together w/o leader */
+  strcpy(ldr, unparse_dbref(leader));
+  strcpy(tbuf1, atr_value(a));
+  (void) atr_add(follower, "FOLLOWING",
+                remove_word(tbuf1, ldr, ' '), GOD, NOTHING);
+}
+
+static void
+del_follow(dbref leader, dbref follower, int noisy)
+{
+  char msg[BUFFER_LEN];
+  del_follower(leader, follower);
+  del_following(follower, leader);
+  if (noisy) {
+    strcpy(msg, tprintf(T("You stop following %s."), Name(leader)));
+    notify_format(leader, T("%s stops following you."), Name(follower));
+    did_it(follower, leader, "UNFOLLOW", msg, "OUNFOLLOW",
+          NULL, "AUNFOLLOW", NOTHING);
+  }
+}
+
+/* Return a list of names of players who are my followers, comma-separated */
+static char *
+list_followers(dbref player)
+{
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+  char *s, *sp;
+  static char buff[BUFFER_LEN];
+  char *bp;
+  dbref who;
+  int first = 1;
+  a = atr_get_noparent(player, "FOLLOWERS");
+  if (!a)
+    return (char *) "";
+  strcpy(tbuf1, atr_value(a));
+  bp = buff;
+  s = trim_space_sep(tbuf1, ' ');
+  while (s) {
+    sp = split_token(&s, ' ');
+    who = parse_dbref(sp);
+    if (GoodObject(who)) {
+      if (!first)
+       safe_str(", ", buff, &bp);
+      safe_str(Name(who), buff, &bp);
+      first = 0;
+    }
+  }
+  *bp = '\0';
+  return buff;
+}
+
+/* Return a list of names of players who I'm following, comma-separated */
+static char *
+list_following(dbref player)
+{
+  ATTR *a;
+  char tbuf1[BUFFER_LEN];
+  char *s, *sp;
+  static char buff[BUFFER_LEN];
+  char *bp;
+  dbref who;
+  int first = 1;
+  a = atr_get_noparent(player, "FOLLOWING");
+  if (!a)
+    return (char *) "";
+  strcpy(tbuf1, atr_value(a));
+  bp = buff;
+  s = trim_space_sep(tbuf1, ' ');
+  while (s) {
+    sp = split_token(&s, ' ');
+    who = parse_dbref(sp);
+    if (GoodObject(who)) {
+      if (!first)
+       safe_str(", ", buff, &bp);
+      safe_str(Name(who), buff, &bp);
+      first = 0;
+    }
+  }
+  *bp = '\0';
+  return buff;
+}
+
+/* Is follower following leader? */
+static int
+is_following(dbref follower, dbref leader)
+{
+  ATTR *a;
+  char *s, *sp;
+  char tbuf1[BUFFER_LEN];
+  /* There are probably fewer dbrefs on the follower's FOLLOWING list
+   * than the leader's FOLLOWERS list, so we check the former
+   */
+  a = atr_get_noparent(follower, "FOLLOWING");
+  if (!a)
+    return 0;                  /* Following no one */
+  strcpy(tbuf1, atr_value(a));
+  s = trim_space_sep(tbuf1, ' ');
+  while (s) {
+    sp = split_token(&s, ' ');
+    if (parse_dbref(sp) == leader)
+      return 1;
+  }
+  return 0;
+}
+
+/** Clear a player's followers list.
+ * \param leader dbref of player whose list is to be cleared.
+ * \param noisy if 1, notify the player.
+ */
+void
+clear_followers(dbref leader, int noisy)
+{
+  ATTR *a;
+  char *s, *sp;
+  char tbuf1[BUFFER_LEN];
+  dbref flwr;
+  a = atr_get_noparent(leader, "FOLLOWERS");
+  if (!a)
+    return;                    /* No one's following me */
+  strcpy(tbuf1, atr_value(a));
+  s = trim_space_sep(tbuf1, ' ');
+  while (s) {
+    sp = split_token(&s, ' ');
+    flwr = parse_dbref(sp);
+    if (GoodObject(flwr)) {
+      del_following(flwr, leader);
+      if (noisy)
+       notify_format(flwr, T("You stop following %s."), Name(leader));
+    }
+  }
+  (void) atr_clr(leader, "FOLLOWERS", GOD);
+}
+
+/** Clear a player's following list.
+ * \param follower dbref of player whose list is to be cleared.
+ * \param noisy if 1, notify the player.
+ */
+void
+clear_following(dbref follower, int noisy)
+{
+  ATTR *a;
+  char *s, *sp;
+  char tbuf1[BUFFER_LEN];
+  dbref ldr;
+  a = atr_get_noparent(follower, "FOLLOWING");
+  if (!a)
+    return;                    /* I'm not following anyone */
+  strcpy(tbuf1, atr_value(a));
+  s = trim_space_sep(tbuf1, ' ');
+  while (s) {
+    sp = split_token(&s, ' ');
+    ldr = parse_dbref(sp);
+    if (GoodObject(ldr)) {
+      del_follower(ldr, follower);
+      if (noisy)
+       notify_format(ldr, T("%s stops following you."), Name(follower));
+    }
+  }
+  (void) atr_clr(follower, "FOLLOWING", GOD);
+}
+
+/* For all of a leader's followers who are in the same room as the
+ * leader, run the same command the leader just ran.
+ */
+static void
+follower_command(dbref leader, dbref loc, const char *com)
+{
+  dbref follower;
+  ATTR *a;
+  char *s, *sp;
+  char tbuf1[BUFFER_LEN];
+  char combuf[BUFFER_LEN];
+  if (!com || !*com)
+    return;
+  strcpy(combuf, com);
+  a = atr_get_noparent(leader, "FOLLOWERS");
+  if (!a)
+    return;                    /* No followers */
+  strcpy(tbuf1, atr_value(a));
+  s = tbuf1;
+  while (s) {
+    sp = split_token(&s, ' ');
+    follower = parse_dbref(sp);
+    if (GoodObject(follower) && (Location(follower) == loc)
+       && (Connected(follower) || IsThing(follower))
+       && (!(DarkLegal(leader)
+             || (Dark(Location(follower)) && !Light(leader)))
+           || CanSee(follower, leader))) {
+      /* This is a follower who was in the room with the leader. Follow. */
+      notify_format(follower, T("You follow %s."), Name(leader));
+      process_command(follower, combuf, follower, follower, 0);
+    }
+  }
+}
diff --git a/src/mycrypt.c b/src/mycrypt.c
new file mode 100644 (file)
index 0000000..43e202a
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * \file mycrypt.c
+ *
+ * \brief Password encryption for PennMUSH
+ *
+ * This file defines the function mush_crypt(key) used for password
+ * encryption, depending on the system. Actually, we pretty much
+ * expect to use SHS.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+#include "conf.h"
+#ifdef HAS_OPENSSL
+#include <openssl/sha.h>
+#else
+#include "shs.h"
+#endif
+#include "confmagic.h"
+
+char *mush_crypt(const char *key);
+
+/** Encrypt a password and return ciphertext.
+ * \param key plaintext to encrypt.
+ * \return encrypted password.
+ */
+char *
+mush_crypt(const char *key)
+{
+  static char crypt_buff[70];
+
+#ifdef HAS_OPENSSL
+  unsigned char hash[SHA_DIGEST_LENGTH];
+  unsigned int a, b;
+
+  SHA((const unsigned char *) key, strlen(key), hash);
+
+  memcpy(&a, hash, sizeof a);
+  memcpy(&b, hash + sizeof a, sizeof b);
+
+  if (options.reverse_shs) {
+    int ra, rb;
+    ra = (a << 16) | (a >> 16);
+    rb = (b << 16) | (b >> 16);
+    a = ((ra & 0xFF00FF00L) >> 8) | ((ra & 0x00FF00FFL) << 8);
+    b = ((rb & 0xFF00FF00L) >> 8) | ((rb & 0x00FF00FFL) << 8);
+  }
+
+  sprintf(crypt_buff, "XX%u%u", a, b);
+
+#else
+  SHS_INFO shsInfo;
+
+  shsInfo.reverse_wanted = (BYTE) options.reverse_shs;
+  shsInit(&shsInfo);
+  shsUpdate(&shsInfo, (const BYTE *) key, strlen(key));
+  shsFinal(&shsInfo);
+  sprintf(crypt_buff, "XX%lu%lu", shsInfo.digest[0], shsInfo.digest[1]);
+#endif
+
+  return crypt_buff;
+}
diff --git a/src/mymalloc.c b/src/mymalloc.c
new file mode 100644 (file)
index 0000000..fab647e
--- /dev/null
@@ -0,0 +1,24 @@
+/**
+ * \file mymalloc.c
+ *
+ * \brief Malloc wrapper file.
+ *
+ * A wrapper for the various malloc package options. See options.h
+ * for descriptions of each.
+ * Each package's source code must include config.h, mymalloc.h,
+ * and confmagic.h.
+ */
+#include "config.h"
+#include "options.h"
+#include "confmagic.h"
+
+/* An undefined or illegal MALLOC_PACKAGE is treated as system 
+ * malloc, because we're such nice forgiving people
+ */
+#if (MALLOC_PACKAGE == 1)
+#include "csrimalloc.c"
+#elif (MALLOC_PACKAGE == 2)
+#include "csrimalloc.c"
+#elif (MALLOC_PACKAGE == 5)
+#include "gmalloc.c"
+#endif
diff --git a/src/mysocket.c b/src/mysocket.c
new file mode 100644 (file)
index 0000000..ceab173
--- /dev/null
@@ -0,0 +1,1561 @@
+/**
+ * \file mysocket.c
+ *
+ * \brief Socket routines for PennMUSH.
+ *
+ *
+ */
+#include "copyrite.h"
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+
+#ifdef I_NETINET_IN
+#ifdef WIN32
+#undef EINTR
+#endif
+#include <netinet/in.h>
+#else
+#ifdef I_SYS_IN
+#include <sys/in.h>
+#endif
+#endif
+
+#ifdef I_NETINET_TCP
+#include <netinet/tcp.h>
+#endif
+
+#ifdef I_ARPA_INET
+#include <arpa/inet.h>
+#endif
+
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+
+#ifndef HAS_GETADDRINFO
+#ifdef I_ARPA_NAMESER
+#include <arpa/nameser.h>
+#endif
+#ifndef WIN32
+#ifdef __APPLE__
+#ifdef __APPLE_CC__
+#include <nameser.h>
+#endif
+#endif
+#ifndef __CYGWIN__
+#include <resolv.h>
+#endif
+#endif
+#endif
+
+#ifdef I_NETDB
+#include <netdb.h>
+#endif
+
+#ifndef h_errno
+#ifndef __CYGWIN__
+extern int h_errno;
+#endif
+#endif
+
+#include <errno.h>
+
+#ifdef I_FCNTL
+#include <fcntl.h>
+#endif
+
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+
+#include <time.h>
+
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+
+#include "conf.h"
+#include "externs.h"
+#include "mymalloc.h"
+#include "mysocket.h"
+#include "ident.h"
+#include "confmagic.h"
+
+#ifndef __ptr_t
+#if defined (__cplusplus) || (defined (__STDC__) && __STDC__)
+#define __ptr_t void *
+#else
+#define __ptr_t char *
+#endif
+#endif
+
+
+#ifndef INFOSLAVE
+/** Given a sockaddr structure, try to look up and return hostname info.
+ * If we can't get a hostname from DNS (or if we're not using DNS),
+ * we settle for the IP address.
+ * \param host pointer to a sockaddr structure.
+ * \param len length of host structure.
+ * \return static hostname_info structure with hostname and port.
+ */
+struct hostname_info *
+hostname_convert(struct sockaddr *host, int len)
+{
+  static struct hostname_info hi;
+  static char hostname[NI_MAXHOST];
+  static char port[NI_MAXSERV];
+
+  if (getnameinfo(host, len, hostname, sizeof hostname, port, sizeof port,
+                 (USE_DNS ? 0 : NI_NUMERICHOST) | NI_NUMERICSERV) != 0) {
+    return NULL;
+  }
+  hi.hostname = hostname;
+  hi.port = port;
+  return &hi;
+}
+
+/** Given a sockaddr structure, try to look up and return ip address info.
+ * \param host pointer to a sockaddr structure.
+ * \param len length of host structure.
+ * \return static hostname_info structure with ip address and port.
+ */
+struct hostname_info *
+ip_convert(struct sockaddr *host, int len)
+{
+  static struct hostname_info hi;
+  static char hostname[NI_MAXHOST];
+  static char port[NI_MAXSERV];
+
+  if (getnameinfo(host, len, hostname, sizeof hostname, port, sizeof port,
+                 NI_NUMERICHOST | NI_NUMERICSERV) != 0) {
+    return NULL;
+  }
+  hi.hostname = hostname;
+  hi.port = port;
+  return &hi;
+}
+#endif                         /* INFOSLAVE */
+
+/** Open a tcp connection to a given host and port. Basically
+ * tcp_connect from UNPv1
+ * \param host hostname or ip address to connect to, as a string.
+ * \param myiterface pointer to sockaddr structure for specific interface.
+ * \param myilen length of myiterface
+ * \param port port to connect to.
+ * \param timeout pointer to timeout for connection.
+ * \return file descriptor for connected socket, or -1 for failure.
+ */
+int
+make_socket_conn(const char *host, struct sockaddr *myiterface,
+                socklen_t myilen, Port_t port, int *timeout)
+{
+  struct addrinfo hints, *server, *save;
+  char cport[NI_MAXSERV];
+  int s;
+  int res;
+  int err;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_flags = AI_PASSIVE;
+  hints.ai_family = AF_UNSPEC; /* Try to use IPv6 if available */
+  hints.ai_socktype = SOCK_STREAM;
+
+  sprintf(cport, "%hu", port);
+
+  if ((res = getaddrinfo(host, cport, &hints, &server)) != 0) {
+    fprintf(stderr, "In getaddrinfo: %s\n", gai_strerror(res));
+    fprintf(stderr, "Host: %s Port %hu\n", host, port);
+    fflush(stderr);
+    return -1;
+  }
+
+  if (!server) {
+    fprintf(stderr, "Couldn't get address for host %s port %hu\n", host, port);
+    fflush(stderr);
+    return -1;
+  }
+
+  save = server;
+
+  do {
+    s = socket(server->ai_family, server->ai_socktype, server->ai_protocol);
+    if (s < 0)
+      continue;
+
+    if (myiterface && myilen > 0) {
+      /* Bind to a specific interface. Needed for ident lookups */
+      if (bind(s, myiterface, myilen) < 0)
+       perror("bind failed (Possibly harmless)");
+    }
+
+    if ((err = connect_nonb(s, server->ai_addr, server->ai_addrlen, timeout)) ==
+       0)
+      break;
+
+#ifdef DEBUG
+    perror("connect failed (Probably harmless)");
+#endif
+
+    closesocket(s);
+
+  } while ((server = server->ai_next) != NULL);
+
+  freeaddrinfo(save);
+
+  if (server == NULL) {
+#ifndef DEBUG
+    /* Usually, we don't want to show this for failed connect to
+     * an ident server, as that just means they're not running one
+     */
+
+    if (port != IDPORT) {
+#endif
+      fprintf(stderr, "Couldn't connect to %s on port %hu\n", host, port);
+      fflush(stderr);
+#ifndef DEBUG
+    }
+#endif
+    return -1;
+  }
+  return s;
+}
+
+/** Start listening to TCP on a given port. Basically tcp_listen
+ * from UNPv1
+ * \param port port to listen on.
+ * \param addr pointer to sockaddr to copy address data to.
+ * \param len length of addr.
+ * \param host hostname or address to listen on, as a string.
+ * \return file descriptor of listening socket, or -1 for failure.
+ */
+int
+make_socket(Port_t port, union sockaddr_u *addr, socklen_t *len,
+           const char *host)
+{
+  int s, opt, ipv = 4;
+  /* Use getaddrinfo() to fill in the sockaddr fields. This
+   * effectively makes us IPv6 capable if the host is. Much of this is
+   * lifted from listing 11.9 (tcp_listen()) in W. Richard Steven's
+   * Unix Network Programming, vol 1.  If getaddrinfo() isn't
+   * present on the system, we'll use our own version, also from UNPv1. */
+  struct addrinfo *server, *save, hints;
+  char cport[NI_MAXSERV];
+  int res;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_flags = AI_PASSIVE;
+#ifdef FORCE_IPV4
+  hints.ai_family = AF_INET;   /* OpenBSD apparently doesn't properly
+                                  map IPv4 connections to IPv6 servers. */
+#else
+  hints.ai_family = AF_UNSPEC; /* Try to use IPv6 if available */
+#endif
+  hints.ai_socktype = SOCK_STREAM;
+
+  sprintf(cport, "%hu", port);
+
+  if (strlen(host) == 0)
+    host = NULL;
+
+  if ((res = getaddrinfo(host, cport, &hints, &server)) != 0) {
+    fprintf(stderr, "In getaddrinfo: %s\n", gai_strerror(res));
+    fprintf(stderr, "Host: %s Port %hu\n", host, port);
+    exit(3);
+  }
+
+  save = server;
+
+  if (!server) {
+    fprintf(stderr, "Couldn't get address for host %s port %hu\n", host, port);
+    exit(3);
+  }
+
+  do {
+    s = socket(server->ai_family, server->ai_socktype, server->ai_protocol);
+    if (s < 0)
+      continue;
+
+    opt = 1;
+    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)) < 0) {
+      perror("setsockopt (Possibly ignorable)");
+      continue;
+    }
+
+    if (bind(s, server->ai_addr, server->ai_addrlen) == 0)
+      break;                   /* Success */
+
+    perror("binding stream socket (Possibly ignorable)");
+    closesocket(s);
+  } while ((server = server->ai_next) != NULL);
+
+  if (server == NULL) {
+    fprintf(stderr, "Couldn't bind to port %d\n", port);
+    exit(4);
+  }
+
+  ipv = (server->ai_family == AF_INET) ? 4 : 6;
+  if (addr) {
+    memcpy(addr->data, server->ai_addr, server->ai_addrlen);
+    if (len)
+      *len = server->ai_addrlen;
+  }
+
+  freeaddrinfo(save);
+  fprintf(stderr, "Listening on port %d using IPv%d.\n", port, ipv);
+  fflush(stderr);
+  listen(s, 5);
+  return s;
+}
+
+
+/** Make a socket do nonblocking i/o.
+ * \param s file descriptor of socket.
+ */
+void
+make_nonblocking(int s)
+{
+#ifdef WIN32
+  unsigned long arg = 1;
+  if (ioctlsocket(s, FIONBIO, &arg) == -1) {
+#else
+  int flags;
+
+  if ((flags = fcntl(s, F_GETFL, 0)) == -1) {
+    perror("make_nonblocking: fcntl");
+#ifndef INFOSLAVE
+    mush_panic("F_GETFL fcntl failed");
+#else
+    exit(1);
+#endif
+  }
+
+  if (fcntl(s, F_SETFL, flags | O_NDELAY) == -1) {
+#endif                         /* WIN32 */
+    perror("make_nonblocking: fcntl");
+#ifndef INFOSLAVE
+    mush_panic("O_NDELAY fcntl failed");
+#else
+    exit(1);
+#endif
+  }
+}
+
+
+
+/** Enable TCP keepalive on the given socket if we can.
+ * \param s socket.
+ */
+/* ARGSUSED */
+void
+set_keepalive(int s __attribute__ ((__unused__)))
+{
+#ifdef CAN_KEEPALIVE
+  int keepalive = 1;
+#ifdef CAN_KEEPIDLE
+  int keepidle = 900;
+#endif
+
+  /* enable TCP keepalive */
+  if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+                (void *) &keepalive, sizeof(keepalive)) == -1)
+    fprintf(stderr, "[%d] could not set SO_KEEPALIVE: errno %d", s, errno);
+#ifdef CAN_KEEPIDLE
+  if (setsockopt(s, IPPROTO_TCP, TCP_KEEPIDLE,
+                (void *) &keepidle, sizeof(keepidle)) == -1)
+    fprintf(stderr, "[%d] could not set TCP_KEEPALIVE: errno %d", s, errno);
+#endif
+#endif
+  return;
+}
+
+
+
+/** Connect a socket, possibly making it nonblocking first.
+ * From UNP, with changes. If nsec > 0, we set the socket
+ * nonblocking and connect with timeout. The socket is STILL
+ * nonblocking after it returns. If nsec == 0, a normal blocking
+ * connect is done. 
+ * \param sockfd file descriptor of socket.
+ * \param saptr pointer to sockaddr structure with connection data.
+ * \param salen length of saptr.
+ * \param timeout pointer to timeout value. If given, nonblocking connect.
+ * \retval 0 success.
+ * \retval -1 failure.
+ */
+int
+connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen,
+            int *timeout)
+{
+  int n, error;
+  time_t start, end;
+  socklen_t len;
+  fd_set rset, wset;
+  struct timeval tval;
+
+  if (timeout && *timeout)
+    make_nonblocking(sockfd);
+
+  error = 0;
+  if ((n = connect(sockfd, (const struct sockaddr *) saptr, salen)) < 0)
+#ifdef WIN32
+    if (n == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK)
+#else
+    if (errno != EINPROGRESS)
+#endif
+      return -1;
+
+  /* connect completed immediately or we did a blocking connect  */
+  if (!timeout || !*timeout || n == 0)
+    goto done;
+
+  FD_ZERO(&rset);
+  FD_SET(sockfd, &rset);
+  wset = rset;
+  tval.tv_sec = *timeout;
+  tval.tv_usec = 0;
+  time(&start);
+
+  if ((n = select(sockfd + 1, &rset, &wset, NULL, &tval)) == 0) {
+    closesocket(sockfd);       /* timeout */
+    errno = ETIMEDOUT;
+    return -1;
+  }
+
+  time(&end);
+  *timeout -= end - start;
+  *timeout = *timeout < 0 ? 0 : *timeout;
+
+  if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
+    len = sizeof(error);
+    if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (__ptr_t) &error, &len) < 0)
+      return -1;               /* Solaris pending error */
+  } else
+    return -1;
+
+done:
+
+  if (error) {
+    closesocket(sockfd);       /* just in case */
+    errno = error;
+    return -1;
+  }
+  return 0;
+}
+
+
+/* The following functions are from W. Ridhard Steven's libgai,
+ * modified by Shawn Wagner for PennMUSH. These arn't full
+ * implementations- they don't handle unix-domain sockets or named
+ * services, because we don't need them for what we're doing.  */
+
+#ifdef AF_INET
+#define IPv4
+#endif
+#ifdef AF_INET6
+#define IPv6
+#endif
+
+#ifndef HAS_INET_PTON
+/* This is from the BIND 4.9.4 release, modified to compile by itself */
+
+/* Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+
+#define IN6ADDRSZ       16
+#define INADDRSZ         4
+#define INT16SZ          2
+
+#ifndef AF_INET6
+#define AF_INET6        AF_MAX+1       /* just to let this compile */
+#endif
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4.  sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+static const char *inet_ntop4(const unsigned char *src, char *dst, size_t size);
+static const char *inet_ntop6(const unsigned char *src, char *dst, size_t size);
+
+/* char *
+ * inet_ntop(af, src, dst, size)
+ *      convert a network format address to presentation format.
+ * return:
+ *      pointer to presentation format address (`dst'), or NULL (see errno).
+ * author:
+ *      Paul Vixie, 1996.
+ */
+const char *
+inet_ntop(int af, const void *src, char *dst, size_t size)
+{
+  switch (af) {
+  case AF_INET:
+    return (inet_ntop4(src, dst, size));
+  case AF_INET6:
+    return (inet_ntop6(src, dst, size));
+  default:
+    errno = EAFNOSUPPORT;
+    return (NULL);
+  }
+  /* NOTREACHED */
+}
+
+/* const char *
+ * inet_ntop4(src, dst, size)
+ *      format an IPv4 address, more or less like inet_ntoa()
+ * return:
+ *      `dst' (as a const)
+ * notes:
+ *      (1) uses no statics
+ *      (2) takes a unsigned char* not an in_addr as input
+ * author:
+ *      Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop4(const unsigned char *src, char *dst, size_t size)
+{
+  static const char fmt[] = "%u.%u.%u.%u";
+  char tmp[sizeof "255.255.255.255"];
+
+  sprintf(tmp, fmt, src[0], src[1], src[2], src[3]);
+  if (strlen(tmp) > size) {
+    errno = ENOSPC;
+    return (NULL);
+  }
+  strcpy(dst, tmp);
+  return (dst);
+}
+
+/* const char *
+ * inet_ntop6(src, dst, size)
+ *      convert IPv6 binary address into presentation (printable) format
+ * author:
+ *      Paul Vixie, 1996.
+ */
+static const char *
+inet_ntop6(const unsigned char *src, char *dst, size_t size)
+{
+  /*
+   * Note that int32_t and int16_t need only be "at least" large enough
+   * to contain a value of the specified size.  On some systems, like
+   * Crays, there is no such thing as an integer variable with 16 bits.
+   * Keep this in mind if you think this function should have been coded
+   * to use pointer overlays.  All the world's not a VAX.
+   */
+  char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
+  struct {
+    int base, len;
+  } best, cur;
+  unsigned int words[IN6ADDRSZ / INT16SZ];
+  int i;
+
+  /*
+   * Preprocess:
+   *    Copy the input (bytewise) array into a wordwise array.
+   *    Find the longest run of 0x00's in src[] for :: shorthanding.
+   */
+  memset(words, 0, sizeof words);
+  for (i = 0; i < IN6ADDRSZ; i++)
+    words[i / 2] |= (src[i] << ((1 - (i % 2)) << 3));
+  best.base = -1;
+  cur.base = -1;
+  for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
+    if (words[i] == 0) {
+      if (cur.base == -1)
+       cur.base = i, cur.len = 1;
+      else
+       cur.len++;
+    } else {
+      if (cur.base != -1) {
+       if (best.base == -1 || cur.len > best.len)
+         best = cur;
+       cur.base = -1;
+      }
+    }
+  }
+  if (cur.base != -1) {
+    if (best.base == -1 || cur.len > best.len)
+      best = cur;
+  }
+  if (best.base != -1 && best.len < 2)
+    best.base = -1;
+
+  /*
+   * Format the result.
+   */
+  tp = tmp;
+  for (i = 0; i < (IN6ADDRSZ / INT16SZ); i++) {
+    /* Are we inside the best run of 0x00's? */
+    if (best.base != -1 && i >= best.base && i < (best.base + best.len)) {
+      if (i == best.base)
+       *tp++ = ':';
+      continue;
+    }
+    /* Are we following an initial run of 0x00s or any real hex? */
+    if (i != 0)
+      *tp++ = ':';
+    /* Is this address an encapsulated IPv4? */
+    if (i == 6 && best.base == 0 &&
+       (best.len == 6 || (best.len == 5 && words[5] == 0xffff))) {
+      if (!inet_ntop4(src + 12, tp, sizeof tmp - (tp - tmp)))
+       return (NULL);
+      tp += strlen(tp);
+      break;
+    }
+    sprintf(tp, "%x", words[i]);
+    tp += strlen(tp);
+  }
+  /* Was it a trailing run of 0x00's? */
+  if (best.base != -1 && (best.base + best.len) == (IN6ADDRSZ / INT16SZ))
+    *tp++ = ':';
+  *tp++ = '\0';
+
+  /*
+   * Check for overflow, copy, and we're done.
+   */
+  if ((size_t) (tp - tmp) > size) {
+    errno = ENOSPC;
+    return (NULL);
+  }
+  strcpy(dst, tmp);
+  return (dst);
+}
+
+
+/* This is from the BIND 4.9.4 release, modified to compile by itself */
+
+/* Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+
+/*
+ * WARNING: Don't even consider trying to compile this on a system where
+ * sizeof(int) < 4.  sizeof(int) > 4 is fine; all the world's not a VAX.
+ */
+
+static int inet_pton4(const char *src, unsigned char *dst);
+static int inet_pton6(const char *src, unsigned char *dst);
+
+/* int
+ * inet_pton(af, src, dst)
+ *      convert from presentation format (which usually means ASCII printable)
+ *      to network format (which is usually some kind of binary format).
+ * return:
+ *      1 if the address was valid for the specified address family
+ *      0 if the address wasn't valid (`dst' is untouched in this case)
+ *      -1 if some other error occurred (`dst' is untouched in this case, too)
+ * author:
+ *      Paul Vixie, 1996.
+ */
+int
+inet_pton(int af, const char *src, void *dst)
+{
+  switch (af) {
+  case AF_INET:
+    return (inet_pton4(src, dst));
+  case AF_INET6:
+    return (inet_pton6(src, dst));
+  default:
+    errno = EAFNOSUPPORT;
+    return (-1);
+  }
+  /* NOTREACHED */
+}
+
+/* int
+ * inet_pton4(src, dst)
+ *      like inet_aton() but without all the hexadecimal and shorthand.
+ * return:
+ *      1 if `src' is a valid dotted quad, else 0.
+ * notice:
+ *      does not touch `dst' unless it's returning 1.
+ * author:
+ *      Paul Vixie, 1996.
+ */
+static int
+inet_pton4(const char *src, unsigned char *dst)
+{
+  static const char digits[] = "0123456789";
+  int saw_digit, octets, ch;
+  unsigned char tmp[INADDRSZ], *tp;
+
+  saw_digit = 0;
+  octets = 0;
+  *(tp = tmp) = 0;
+  while ((ch = *src++) != '\0') {
+    const char *pch;
+
+    if ((pch = strchr(digits, ch)) != NULL) {
+      unsigned int new = *tp * 10 + (pch - digits);
+
+      if (new > 255)
+       return (0);
+      *tp = new;
+      if (!saw_digit) {
+       if (++octets > 4)
+         return (0);
+       saw_digit = 1;
+      }
+    } else if (ch == '.' && saw_digit) {
+      if (octets == 4)
+       return (0);
+      *++tp = 0;
+      saw_digit = 0;
+    } else
+      return (0);
+  }
+  if (octets < 4)
+    return (0);
+  /* bcopy(tmp, dst, INADDRSZ); */
+  memcpy(dst, tmp, INADDRSZ);
+  return (1);
+}
+
+/* int
+ * inet_pton6(src, dst)
+ *      convert presentation level address to network order binary form.
+ * return:
+ *      1 if `src' is a valid [RFC1884 2.2] address, else 0.
+ * notice:
+ *      (1) does not touch `dst' unless it's returning 1.
+ *      (2) :: in a full address is silently ignored.
+ * credit:
+ *      inspired by Mark Andrews.
+ * author:
+ *      Paul Vixie, 1996.
+ */
+static int
+inet_pton6(const char *src, unsigned char *dst)
+{
+  static const char xdigits_l[] = "0123456789abcdef",
+    xdigits_u[] = "0123456789ABCDEF";
+  unsigned char tmp[IN6ADDRSZ], *tp, *endp, *colonp;
+  const char *xdigits, *curtok;
+  int ch, saw_xdigit;
+  unsigned int val;
+
+  memset((tp = tmp), 0, IN6ADDRSZ);
+  endp = tp + IN6ADDRSZ;
+  colonp = NULL;
+  /* Leading :: requires some special handling. */
+  if (*src == ':')
+    if (*++src != ':')
+      return (0);
+  curtok = src;
+  saw_xdigit = 0;
+  val = 0;
+  while ((ch = *src++) != '\0') {
+    const char *pch;
+
+    if ((pch = strchr((xdigits = xdigits_l), ch)) == NULL)
+      pch = strchr((xdigits = xdigits_u), ch);
+    if (pch != NULL) {
+      val <<= 4;
+      val |= (pch - xdigits);
+      if (val > 0xffff)
+       return (0);
+      saw_xdigit = 1;
+      continue;
+    }
+    if (ch == ':') {
+      curtok = src;
+      if (!saw_xdigit) {
+       if (colonp)
+         return (0);
+       colonp = tp;
+       continue;
+      }
+      if (tp + INT16SZ > endp)
+       return (0);
+      *tp++ = (unsigned char) (val >> 8) & 0xff;
+      *tp++ = (unsigned char) val & 0xff;
+      saw_xdigit = 0;
+      val = 0;
+      continue;
+    }
+    if (ch == '.' && ((tp + INADDRSZ) <= endp) && inet_pton4(curtok, tp) > 0) {
+      tp += INADDRSZ;
+      saw_xdigit = 0;
+      break;                   /* '\0' was seen by inet_pton4(). */
+    }
+    return (0);
+  }
+  if (saw_xdigit) {
+    if (tp + INT16SZ > endp)
+      return (0);
+    *tp++ = (unsigned char) (val >> 8) & 0xff;
+    *tp++ = (unsigned char) val & 0xff;
+  }
+  if (colonp != NULL) {
+    /*
+     * Since some memmove()'s erroneously fail to handle
+     * overlapping regions, we'll do the shift by hand.
+     */
+    const int n = tp - colonp;
+    int i;
+
+    for (i = 1; i <= n; i++) {
+      endp[-i] = colonp[n - i];
+      colonp[n - i] = 0;
+    }
+    tp = endp;
+  }
+  if (tp != endp)
+    return (0);
+  /* bcopy(tmp, dst, IN6ADDRSZ); */
+  memcpy(dst, tmp, IN6ADDRSZ);
+  return (1);
+}
+#endif
+
+#ifndef HAS_GETNAMEINFO
+static int gn_ipv46(char *, size_t, char *, size_t, void *, size_t,
+                   int, int, int);
+
+/*
+ * Handle either an IPv4 or an IPv6 address and port.
+ */
+
+/* include gn_ipv46 */
+static int
+gn_ipv46(char *host, size_t hostlen, char *serv, size_t servlen,
+        void *aptr, size_t alen, int family, int port, int flags)
+{
+  char *ptr;
+  struct hostent *hptr;
+
+  if (hostlen > 0) {
+    if (flags & NI_NUMERICHOST) {
+      if (inet_ntop(family, aptr, host, hostlen) == NULL)
+       return (1);
+    } else {
+      hptr = gethostbyaddr(aptr, alen, family);
+      if (hptr != NULL && hptr->h_name != NULL) {
+       if (flags & NI_NOFQDN) {
+         if ((ptr = strchr(hptr->h_name, '.')) != NULL)
+           *ptr = 0;           /* overwrite first dot */
+       }
+#ifdef HAS_SNPRINTF
+       snprintf(host, hostlen, "%s", hptr->h_name);
+#else
+       strncpy(host, hptr->h_name, hostlen);
+       host[hostlen - 1] = '\0';
+#endif
+      } else {
+       if (flags & NI_NAMEREQD)
+         return (1);
+       if (inet_ntop(family, aptr, host, hostlen) == NULL)
+         return (1);
+      }
+    }
+  }
+
+  if (servlen > 0) {
+    if (flags & NI_NUMERICSERV) {
+#ifdef HAS_SNPRINTF
+      snprintf(serv, servlen, "%hu", ntohs((unsigned short) port));
+#else
+      sprintf(serv, "%hu", ntohs((unsigned short) port));
+#endif
+
+    } else {
+      /* We're not bothering with getservbyport */
+#ifdef HAS_SNPRINTF
+      snprintf(serv, servlen, "%hu", ntohs((unsigned short) port));
+#else
+      sprintf(serv, "%hu", ntohs((unsigned short) port));
+#endif
+    }
+  }
+  return (0);
+}
+
+/* end gn_ipv46 */
+
+
+/* include getnameinfo */
+int
+getnameinfo(const struct sockaddr *sa, socklen_t salen,
+           char *host, size_t hostlen, char *serv, size_t servlen, int flags)
+{
+
+  switch (sa->sa_family) {
+#ifdef  IPv4
+  case AF_INET:{
+      struct sockaddr_in *sain = (struct sockaddr_in *) sa;
+
+      return (gn_ipv46(host, hostlen, serv, servlen,
+                      &sain->sin_addr, sizeof(struct in_addr),
+                      AF_INET, sain->sin_port, flags));
+    }
+#endif
+
+#ifdef  HAS_IPV6
+  case AF_INET6:{
+      struct sockaddr_in6 *sain = (struct sockaddr_in6 *) sa;
+
+      return (gn_ipv46(host, hostlen, serv, servlen,
+                      &sain->sin6_addr, sizeof(struct in6_addr),
+                      AF_INET6, sain->sin6_port, flags));
+    }
+#endif
+
+  default:
+    return (1);
+  }
+}
+
+/* end getnameinfo */
+
+#endif
+
+#ifndef HAS_GETADDRINFO
+
+/* include ga1 */
+struct search {
+  const char *host;            /* hostname or address string */
+  int family;                  /* AF_xxx */
+};
+
+static int ga_aistruct(struct addrinfo ***, const struct addrinfo *,
+                      const void *, int);
+static struct addrinfo *ga_clone(struct addrinfo *);
+static int ga_echeck(const char *, const char *, int, int, int, int);
+static int ga_nsearch(const char *, const struct addrinfo *, struct search *);
+static int ga_port(struct addrinfo *, int, int);
+static int ga_serv(struct addrinfo *, const struct addrinfo *, const char *);
+
+
+int
+getaddrinfo(const char *hostname, const char *servname,
+           const struct addrinfo *hintsp, struct addrinfo **result)
+{
+  int rc, error, nsearch;
+  char **ap, *canon;
+  struct hostent *hptr;
+  struct search search[3], *sptr;
+  struct addrinfo hints, *aihead, **aipnext;
+
+  /*
+   * If we encounter an error we want to free() any dynamic memory
+   * that we've allocated.  This is our hack to simplify the code.
+   */
+#define error(e) { error = (e); goto bad; }
+
+  aihead = NULL;               /* initialize automatic variables */
+  aipnext = &aihead;
+  canon = NULL;
+
+  if (hintsp == NULL) {
+    memset(&hints, 0, sizeof(hints));
+    hints.ai_family = AF_UNSPEC;
+  } else
+    hints = *hintsp;           /* struct copy */
+
+  /* 4first some basic error checking */
+  if ((rc = ga_echeck(hostname, servname, hints.ai_flags, hints.ai_family,
+                     hints.ai_socktype, hints.ai_protocol)) != 0)
+    error(rc);
+
+  /* end ga1 */
+
+  /* include ga3 */
+  /* 4remainder of function for IPv4/IPv6 */
+  nsearch = ga_nsearch(hostname, &hints, &search[0]);
+  for (sptr = &search[0]; sptr < &search[nsearch]; sptr++) {
+#ifdef  IPv4
+    /* 4check for an IPv4 dotted-decimal string */
+    if (isdigit((unsigned char) sptr->host[0])) {
+      struct in_addr inaddr;
+
+      if (inet_pton(AF_INET, sptr->host, &inaddr) == 1) {
+       if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET)
+         error(EAI_ADDRFAMILY);
+       if (sptr->family != AF_INET)
+         continue;             /* ignore */
+       rc = ga_aistruct(&aipnext, &hints, &inaddr, AF_INET);
+       if (rc != 0)
+         error(rc);
+       continue;
+      }
+    }
+#endif
+
+#ifdef  HAS_IPV6
+    /* 4check for an IPv6 hex string */
+    if ((isxdigit((unsigned char) sptr->host[0]) || sptr->host[0] == ':') &&
+       (strchr(sptr->host, ':') != NULL)) {
+      struct in6_addr in6addr;
+
+      if (inet_pton(AF_INET6, sptr->host, &in6addr) == 1) {
+       if (hints.ai_family != AF_UNSPEC && hints.ai_family != AF_INET6)
+         error(EAI_ADDRFAMILY);
+       if (sptr->family != AF_INET6)
+         continue;             /* ignore */
+       rc = ga_aistruct(&aipnext, &hints, &in6addr, AF_INET6);
+       if (rc != 0)
+         error(rc);
+       continue;
+      }
+    }
+#endif
+/* end ga3 */
+/* include ga4 */
+    /* 4remainder of for() to look up hostname */
+#ifdef  HAS_IPV6
+    if ((_res.options & RES_INIT) == 0)
+      res_init();              /* need this to set _res.options */
+#endif
+
+    if (nsearch == 2) {
+#ifdef  HAS_IPV6
+      _res.options &= ~RES_USE_INET6;
+#endif
+      hptr = gethostbyname2(sptr->host, sptr->family);
+    } else {
+#ifdef  HAS_IPV6
+      if (sptr->family == AF_INET6)
+       _res.options |= RES_USE_INET6;
+      else
+       _res.options &= ~RES_USE_INET6;
+#endif
+      hptr = gethostbyname(sptr->host);
+    }
+    if (hptr == NULL) {
+      if (nsearch == 2)
+       continue;               /* failure OK if multiple searches */
+
+      switch (h_errno) {
+      case HOST_NOT_FOUND:
+       error(EAI_NONAME);
+      case TRY_AGAIN:
+       error(EAI_AGAIN);
+      case NO_RECOVERY:
+       error(EAI_FAIL);
+      case NO_DATA:
+       error(EAI_NODATA);
+      default:
+       error(EAI_NONAME);
+      }
+    }
+
+    /* 4check for address family mismatch if one specified */
+    if (hints.ai_family != AF_UNSPEC && hints.ai_family != hptr->h_addrtype)
+      error(EAI_ADDRFAMILY);
+
+    /* 4save canonical name first time */
+    if (hostname != NULL && hostname[0] != '\0' &&
+       (hints.ai_flags & AI_CANONNAME) && canon == NULL) {
+      if ((canon = strdup(hptr->h_name)) == NULL)
+       error(EAI_MEMORY);
+    }
+
+    /* 4create one addrinfo{} for each returned address */
+    for (ap = hptr->h_addr_list; *ap != NULL; ap++) {
+      rc = ga_aistruct(&aipnext, &hints, *ap, hptr->h_addrtype);
+      if (rc != 0)
+       error(rc);
+    }
+  }
+  if (aihead == NULL)
+    error(EAI_NONAME);         /* nothing found */
+  /* end ga4 */
+
+/* include ga5 */
+  /* 4return canonical name */
+  if (hostname != NULL && hostname[0] != '\0' && hints.ai_flags & AI_CANONNAME) {
+    if (canon != NULL)
+      aihead->ai_canonname = canon;    /* strdup'ed earlier */
+    else {
+      if ((aihead->ai_canonname = strdup(search[0].host)) == NULL)
+       error(EAI_MEMORY);
+    }
+  }
+
+  /* 4now process the service name */
+  if (servname != NULL && servname[0] != '\0') {
+    if ((rc = ga_serv(aihead, &hints, servname)) != 0)
+      error(rc);
+  }
+
+  *result = aihead;            /* pointer to first structure in linked list */
+  return (0);
+
+bad:
+  freeaddrinfo(aihead);                /* free any alloc'ed memory */
+  return (error);
+}
+
+/* end ga5 */
+
+/*
+ * Basic error checking of flags, family, socket type, and protocol.
+ */
+
+/* include ga_echeck */
+static int
+ga_echeck(const char *hostname, const char *servname,
+         int flags, int family, int socktype, int protocol)
+{
+  if (flags & ~(AI_PASSIVE | AI_CANONNAME))
+    return (EAI_BADFLAGS);     /* unknown flag bits */
+
+  if (hostname == NULL || hostname[0] == '\0') {
+    if (servname == NULL || servname[0] == '\0')
+      return (EAI_NONAME);     /* host or service must be specified */
+  }
+
+  switch (family) {
+  case AF_UNSPEC:
+    break;
+#ifdef  IPv4
+  case AF_INET:
+    if (socktype != 0 &&
+       (socktype != SOCK_STREAM &&
+        socktype != SOCK_DGRAM && socktype != SOCK_RAW))
+      return (EAI_SOCKTYPE);   /* invalid socket type */
+    break;
+#endif
+#ifdef  HAS_IPV6
+  case AF_INET6:
+    if (socktype != 0 &&
+       (socktype != SOCK_STREAM &&
+        socktype != SOCK_DGRAM && socktype != SOCK_RAW))
+      return (EAI_SOCKTYPE);   /* invalid socket type */
+    break;
+#endif
+  default:
+    return (EAI_FAMILY);       /* unknown protocol family */
+  }
+  return (0);
+}
+
+/* end ga_echeck */
+
+
+/*
+ * Set up the search[] array with the hostnames and address families
+ * that we are to look up.
+ */
+
+/* include ga_nsearch1 */
+static int
+ga_nsearch(const char *hostname, const struct addrinfo *hintsp,
+          struct search *search)
+{
+  int nsearch = 0;
+
+  if (hostname == NULL || hostname[0] == '\0') {
+    if (hintsp->ai_flags & AI_PASSIVE) {
+      /* 4no hostname and AI_PASSIVE: implies wildcard bind */
+      switch (hintsp->ai_family) {
+#ifdef  IPv4
+      case AF_INET:
+       search[nsearch].host = "0.0.0.0";
+       search[nsearch].family = AF_INET;
+       nsearch++;
+       break;
+#endif
+#ifdef  HAS_IPV6
+      case AF_INET6:
+       search[nsearch].host = "0::0";
+       search[nsearch].family = AF_INET6;
+       nsearch++;
+       break;
+#endif
+      case AF_UNSPEC:
+#ifdef  HAS_IPV6
+       search[nsearch].host = "0::0";  /* IPv6 first, then IPv4 */
+       search[nsearch].family = AF_INET6;
+       nsearch++;
+#endif
+#ifdef  IPv4
+       search[nsearch].host = "0.0.0.0";
+       search[nsearch].family = AF_INET;
+       nsearch++;
+#endif
+       break;
+      }
+      /* end ga_nsearch1 */
+      /* include ga_nsearch2 */
+    } else {
+      /* 4no host and not AI_PASSIVE: connect to local host */
+      switch (hintsp->ai_family) {
+#ifdef  IPv4
+      case AF_INET:
+       search[nsearch].host = "localhost";     /* 127.0.0.1 */
+       search[nsearch].family = AF_INET;
+       nsearch++;
+       break;
+#endif
+#ifdef  HAS_IPV6
+      case AF_INET6:
+       search[nsearch].host = "0::1";
+       search[nsearch].family = AF_INET6;
+       nsearch++;
+       break;
+#endif
+      case AF_UNSPEC:
+#ifdef  HAS_IPV6
+       search[nsearch].host = "0::1";  /* IPv6 first, then IPv4 */
+       search[nsearch].family = AF_INET6;
+       nsearch++;
+#endif
+#ifdef  IPv4
+       search[nsearch].host = "localhost";
+       search[nsearch].family = AF_INET;
+       nsearch++;
+#endif
+       break;
+      }
+    }
+    /* end ga_nsearch2 */
+    /* include ga_nsearch3 */
+  } else {                     /* host is specified */
+    switch (hintsp->ai_family) {
+#ifdef  IPv4
+    case AF_INET:
+      search[nsearch].host = hostname;
+      search[nsearch].family = AF_INET;
+      nsearch++;
+      break;
+#endif
+#ifdef  HAS_IPV6
+    case AF_INET6:
+      search[nsearch].host = hostname;
+      search[nsearch].family = AF_INET6;
+      nsearch++;
+      break;
+#endif
+    case AF_UNSPEC:
+#ifdef  HAS_IPV6
+      search[nsearch].host = hostname;
+      search[nsearch].family = AF_INET6;       /* IPv6 first */
+      nsearch++;
+#endif
+#ifdef  IPv4
+      search[nsearch].host = hostname;
+      search[nsearch].family = AF_INET;        /* then IPv4 */
+      nsearch++;
+#endif
+      break;
+    }
+  }
+  if (nsearch < 1 || nsearch > 2)
+    return -1;
+  return (nsearch);
+}
+
+/* end ga_nsearch3 */
+
+
+/*
+ * Create and fill in an addrinfo{}.
+ */
+
+#define AI_CLONE 4
+
+/* include ga_aistruct1 */
+int
+ga_aistruct(struct addrinfo ***paipnext, const struct addrinfo *hintsp,
+           const void *addr, int family)
+{
+  struct addrinfo *ai;
+
+  if ((ai = calloc(1, sizeof(struct addrinfo))) == NULL)
+    return (EAI_MEMORY);
+  ai->ai_next = NULL;
+  ai->ai_canonname = NULL;
+  **paipnext = ai;
+  *paipnext = &ai->ai_next;
+
+  if ((ai->ai_socktype = hintsp->ai_socktype) == 0)
+    ai->ai_flags |= AI_CLONE;
+
+  ai->ai_protocol = hintsp->ai_protocol;
+/* end ga_aistruct1 */
+
+/* include ga_aistruct2 */
+  switch ((ai->ai_family = family)) {
+#ifdef  IPv4
+  case AF_INET:{
+      struct sockaddr_in *sinptr;
+
+      /* 4allocate sockaddr_in{} and fill in all but port */
+      if ((sinptr = calloc(1, sizeof(struct sockaddr_in))) == NULL)
+       return (EAI_MEMORY);
+#ifdef  HAVE_SOCKADDR_SA_LEN
+      sinptr->sin_len = sizeof(struct sockaddr_in);
+#endif
+      sinptr->sin_family = AF_INET;
+      memcpy(&sinptr->sin_addr, addr, sizeof(struct in_addr));
+      ai->ai_addr = (struct sockaddr *) sinptr;
+      ai->ai_addrlen = sizeof(struct sockaddr_in);
+      break;
+    }
+#endif                         /* IPV4 */
+#ifdef  HAS_IPV6
+  case AF_INET6:{
+      struct sockaddr_in6 *sin6ptr;
+
+      /* 4allocate sockaddr_in6{} and fill in all but port */
+      if ((sin6ptr = calloc(1, sizeof(struct sockaddr_in6))) == NULL)
+       return (EAI_MEMORY);
+#ifdef  HAVE_SOCKADDR_SA_LEN
+      sin6ptr->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+      sin6ptr->sin6_family = AF_INET6;
+      memcpy(&sin6ptr->sin6_addr, addr, sizeof(struct in6_addr));
+      ai->ai_addr = (struct sockaddr *) sin6ptr;
+      ai->ai_addrlen = sizeof(struct sockaddr_in6);
+      break;
+    }
+#endif                         /* IPV6 */
+
+  }
+  return (0);
+}
+
+/* end ga_aistruct2 */
+
+/*
+ * This function handles the service string.
+ */
+
+/* include ga_serv */
+int
+ga_serv(struct addrinfo *aihead, const struct addrinfo *hintsp,
+       const char *serv)
+{
+  int port, rc, nfound;
+
+  nfound = 0;
+  if (isdigit((unsigned char) serv[0])) {      /* check for port number string first */
+    port = (int) htons((unsigned short) atoi(serv));
+    if (hintsp->ai_socktype) {
+      /* 4caller specifies socket type */
+      if ((rc = ga_port(aihead, port, hintsp->ai_socktype)) < 0)
+       return (EAI_MEMORY);
+      nfound += rc;
+    } else {
+      /* 4caller does not specify socket type */
+      if ((rc = ga_port(aihead, port, SOCK_STREAM)) < 0)
+       return (EAI_MEMORY);
+      nfound += rc;
+      if ((rc = ga_port(aihead, port, SOCK_DGRAM)) < 0)
+       return (EAI_MEMORY);
+      nfound += rc;
+    }
+  }
+
+  if (nfound == 0) {
+    if (hintsp->ai_socktype == 0)
+      return (EAI_NONAME);     /* all calls to getservbyname() failed */
+    else
+      return (EAI_SERVICE);    /* service not supported for socket type */
+  }
+  return (0);
+}
+
+/* end ga_serv */
+
+
+/*
+ * Go through all the addrinfo structures, checking for a match of the
+ * socket type and filling in the socket type, and then the port number
+ * in the corresponding socket address structures.
+ *
+ * The AI_CLONE flag works as follows.  Consider a multihomed host with
+ * two IP addresses and no socket type specified by the caller.  After
+ * the "host" search there are two addrinfo structures, one per IP address.
+ * Assuming a service supported by both TCP and UDP (say the daytime
+ * service) we need to return *four* addrinfo structures:
+ *              IP#1, SOCK_STREAM, TCP port,
+ *              IP#1, SOCK_DGRAM, UDP port,
+ *              IP#2, SOCK_STREAM, TCP port,
+ *              IP#2, SOCK_DGRAM, UDP port.
+ * To do this, when the "host" loop creates an addrinfo structure, if the
+ * caller has not specified a socket type (hintsp->ai_socktype == 0), the
+ * AI_CLONE flag is set.  When the following function finds an entry like
+ * this it is handled as follows: If the entry's ai_socktype is still 0,
+ * this is the first use of the structure, and the ai_socktype field is set.
+ * But, if the entry's ai_socktype is nonzero, then we clone a new addrinfo
+ * structure and set it's ai_socktype to the new value.  Although we only
+ * need two socket types today (SOCK_STREAM and SOCK_DGRAM) this algorithm
+ * will handle any number.  Also notice that Posix.1g requires all socket
+ * types to be nonzero.
+ */
+
+/* include ga_port */
+int
+ga_port(struct addrinfo *aihead, int port, int socktype)
+               /* port must be in network byte order */
+{
+  int nfound = 0;
+  struct addrinfo *ai;
+
+  for (ai = aihead; ai != NULL; ai = ai->ai_next) {
+    if (ai->ai_flags & AI_CLONE) {
+      if (ai->ai_socktype != 0) {
+       if ((ai = ga_clone(ai)) == NULL)
+         return (-1);          /* memory allocation error */
+       /* ai points to newly cloned entry, which is what we want */
+      }
+    } else if (ai->ai_socktype != socktype)
+      continue;                        /* ignore if mismatch on socket type */
+
+    ai->ai_socktype = socktype;
+
+    switch (ai->ai_family) {
+#ifdef  IPv4
+    case AF_INET:
+      ((struct sockaddr_in *) ai->ai_addr)->sin_port = port;
+      nfound++;
+      break;
+#endif
+#ifdef  HAS_IPV6
+    case AF_INET6:
+      ((struct sockaddr_in6 *) ai->ai_addr)->sin6_port = port;
+      nfound++;
+      break;
+#endif
+    }
+  }
+  return (nfound);
+}
+
+/* end ga_port */
+
+/*
+ * Clone a new addrinfo structure from an existing one.
+ */
+
+/* include ga_clone */
+struct addrinfo *
+ga_clone(struct addrinfo *ai)
+{
+  struct addrinfo *new;
+
+  if ((new = calloc(1, sizeof(struct addrinfo))) == NULL)
+    return (NULL);
+
+  new->ai_next = ai->ai_next;
+  ai->ai_next = new;
+
+  new->ai_flags = 0;           /* make sure AI_CLONE is off */
+  new->ai_family = ai->ai_family;
+  new->ai_socktype = ai->ai_socktype;
+  new->ai_protocol = ai->ai_protocol;
+  new->ai_canonname = NULL;
+  new->ai_addrlen = ai->ai_addrlen;
+  if ((new->ai_addr = malloc(ai->ai_addrlen)) == NULL)
+    return (NULL);
+  memcpy(new->ai_addr, ai->ai_addr, ai->ai_addrlen);
+
+  return (new);
+}
+
+/* end ga_clone */
+#endif                         /* HAS_GETADDRINFO */
+
+/*
+ * Return a string containing some additional information after an
+ * error from getaddrinfo().
+ */
+#ifndef HAS_GAI_STRERROR
+
+const char *
+gai_strerror(int err)
+{
+  switch (err) {
+  case EAI_ADDRFAMILY:
+    return ("address family for host not supported");
+  case EAI_AGAIN:
+    return ("temporary failure in name resolution");
+  case EAI_BADFLAGS:
+    return ("invalid flags value");
+  case EAI_FAIL:
+    return ("non-recoverable failure in name resolution");
+  case EAI_FAMILY:
+    return ("address family not supported");
+  case EAI_MEMORY:
+    return ("memory allocation failure");
+  case EAI_NODATA:
+    return ("no address associated with host");
+  case EAI_NONAME:
+    return ("host nor service provided, or not known");
+  case EAI_SERVICE:
+    return ("service not supported for socket type");
+  case EAI_SOCKTYPE:
+    return ("socket type not supported");
+  case EAI_SYSTEM:
+    return ("system error");
+  default:
+    return ("unknown getaddrinfo() error");
+  }
+}
+#endif                         /* HAS_GAI_STRERROR */
+
+#ifndef HAS_GETADDRINFO
+
+/* include freeaddrinfo */
+void
+freeaddrinfo(struct addrinfo *aihead)
+{
+  struct addrinfo *ai, *ainext;
+
+  for (ai = aihead; ai != NULL; ai = ainext) {
+    if (ai->ai_addr != NULL)
+      free(ai->ai_addr);       /* socket address structure */
+
+    if (ai->ai_canonname != NULL)
+      free(ai->ai_canonname);
+
+    ainext = ai->ai_next;      /* can't fetch ai_next after free() */
+    free(ai);                  /* the addrinfo{} itself */
+  }
+}
+
+/* end freeaddrinfo */
+
+#endif                         /* HAS_GETADDRINFO */
diff --git a/src/myssl.c b/src/myssl.c
new file mode 100644 (file)
index 0000000..4dfa313
--- /dev/null
@@ -0,0 +1,592 @@
+/**
+ * \file myssl.c
+ *
+ * \brief Code to support SSL connections in PennMUSH.
+ *
+ * This file contains nearly all of the code that interacts with the
+ * OpenSSL libraries to suppose SSL connections in PennMUSH.
+ *
+ * Lots of stuff here taken from Eric Rescorla's 2001 Linux Journal
+ * articles "An Introduction to OpenSSL Programming"
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#ifdef HAS_OPENSSL
+#include <stdio.h>
+#include <stdarg.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef WIN32
+#define FD_SETSIZE 256
+#include <windows.h>
+#include <winsock.h>
+#include <io.h>
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#define MAXHOSTNAMELEN 32
+#define LC_MESSAGES 6
+void shutdown_checkpoint(void);
+#else                          /* !WIN32 */
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <sys/ioctl.h>
+#include <errno.h>
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#endif
+#ifdef I_NETDB
+#include <netdb.h>
+#endif
+#ifdef I_SYS_PARAM
+#include <sys/param.h>
+#endif
+#ifdef I_SYS_STAT
+#include <sys/stat.h>
+#endif
+#endif                         /* !WIN32 */
+#include <time.h>
+#ifdef I_SYS_WAIT
+#include <sys/wait.h>
+#endif
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef I_SYS_SELECT
+#include <sys/select.h>
+#endif
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/dh.h>
+#include <openssl/evp.h>
+
+#include "conf.h"
+#include "mysocket.h"
+#include "myssl.h"
+#include "log.h"
+#include "parse.h"
+#include "confmagic.h"
+
+#define MYSSL_RB        0x1    /**< Read blocked (on read) */
+#define MYSSL_WB        0x2    /**< Write blocked (on write) */
+#define MYSSL_RBOW      0x4    /**< Read blocked (on write) */
+#define MYSSL_WBOR      0x8    /**< Write blocked (on read) */
+#define MYSSL_ACCEPT    0x10   /**< We need to call SSL_accept (again) */
+#define MYSSL_VERIFIED  0x20   /**< This is an authenticated connection */
+#define MYSSL_HANDSHAKE 0x40   /**< We need to call SSL_do_handshake */
+
+#undef MYSSL_DEBUG
+#ifdef MYSSL_DEBUG
+#define ssl_debugdump(x) ssl_errordump(x)
+#else
+#define ssl_debugdump(x)
+#endif
+
+static void ssl_errordump(const char *msg);
+static int client_verify_callback(int preverify_ok, X509_STORE_CTX * x509_ctx);
+static DH *get_dh1024(void);
+
+
+static BIO *bio_err = NULL;
+static SSL_CTX *ctx = NULL;
+
+/** Initialize the SSL context.
+ * \return pointer to SSL context object.
+ */
+SSL_CTX *
+ssl_init(void)
+{
+  SSL_METHOD *meth;
+  unsigned char context[128];
+
+  if (!bio_err) {
+    if (!SSL_library_init())
+      return NULL;
+    SSL_load_error_strings();
+    /* Error write context */
+    bio_err = BIO_new_fp(stderr, BIO_NOCLOSE);
+  }
+#ifndef HAS_DEV_URANDOM
+  /* We need to seed the RNG with RAND_seed() or RAND_egd() here.
+   * Where are we going to get an unpredictable seed?
+   */
+#endif
+
+  /* Set up SIGPIPE handler here? */
+
+  /* Create context */
+  meth = SSLv23_server_method();
+  ctx = SSL_CTX_new(meth);
+
+  /* Load keys/certs */
+  if (*options.ssl_private_key_file) {
+    if (!SSL_CTX_use_certificate_chain_file(ctx, options.ssl_private_key_file)) {
+      ssl_errordump
+       ("Unable to load server certificate - only anonymous ciphers supported.");
+    }
+    if (!SSL_CTX_use_PrivateKey_file
+       (ctx, options.ssl_private_key_file, SSL_FILETYPE_PEM)) {
+      ssl_errordump
+       ("Unable to load private key - only anonymous ciphers supported.");
+    }
+  }
+
+  /* Load trusted CAs */
+  if (*options.ssl_ca_file) {
+    if (!SSL_CTX_load_verify_locations(ctx, options.ssl_ca_file, NULL)) {
+      ssl_errordump("Unable to load CA certificates");
+    } else {
+      if (options.ssl_require_client_cert)
+       SSL_CTX_set_verify(ctx,
+                          SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+                          client_verify_callback);
+      else
+       SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, client_verify_callback);
+#if (OPENSSL_VERSION_NUMBER < 0x0090600fL)
+      SSL_CTX_set_verify_depth(ctx, 1);
+#endif
+    }
+  }
+
+  SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE | SSL_OP_ALL);
+  SSL_CTX_set_mode(ctx,
+                  SSL_MODE_ENABLE_PARTIAL_WRITE |
+                  SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+  /* Set up DH callback */
+  SSL_CTX_set_tmp_dh(ctx, get_dh1024());
+
+  /* Set the cipher list to the usual default list, except that
+   * we'll allow anonymous diffie-hellman, too.
+   */
+  SSL_CTX_set_cipher_list(ctx, "ALL:ADH:RC4+RSA:+SSLv2:@STRENGTH");
+
+  /* Set up session cache if we can */
+  strncpy((char *) context, MUDNAME, 128);
+  SSL_CTX_set_session_id_context(ctx, (void *) &context, sizeof context);
+
+  /* Load hash algorithms */
+  OpenSSL_add_all_digests();
+
+  return ctx;
+}
+
+static int
+client_verify_callback(int preverify_ok, X509_STORE_CTX * x509_ctx)
+{
+  char buf[256];
+  X509 *err_cert;
+  int err, depth;
+
+  err_cert = X509_STORE_CTX_get_current_cert(x509_ctx);
+  err = X509_STORE_CTX_get_error(x509_ctx);
+  depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+
+  X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
+  if (!preverify_ok) {
+    do_log(LT_ERR, 0, 0, "verify error:num=%d:%s:depth=%d:%s\n", err,
+          X509_verify_cert_error_string(err), depth, buf);
+    if (err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) {
+      X509_NAME_oneline(X509_get_issuer_name(x509_ctx->current_cert), buf, 256);
+      do_log(LT_ERR, 0, 0, "issuer= %s\n", buf);
+    }
+    return preverify_ok;
+  }
+  /* They've passed the preverification */
+  /* if there are contents of the cert we wanted to verify, we'd do it here. 
+   */
+  return preverify_ok;
+}
+
+static DH *
+get_dh1024(void)
+{
+  static unsigned char dh1024_p[] = {
+    0xB6, 0xBC, 0x30, 0x5B, 0xB4, 0xE5, 0x96, 0x62, 0x3F, 0x85, 0x5B, 0x1F,
+    0x88, 0xD1, 0x12, 0xE1, 0x1D, 0x27, 0x69, 0x63, 0xAD, 0xB3, 0x4D, 0x23,
+    0xB8, 0x4B, 0x1A, 0x90, 0xA6, 0x89, 0xD8, 0x5D, 0xFA, 0xF5, 0x8F, 0xFF,
+    0xFF, 0xF4, 0x54, 0x3B, 0xCD, 0x5C, 0xAA, 0x79, 0x8B, 0x14, 0xBB, 0x84,
+    0xAC, 0xEE, 0x94, 0x47, 0x76, 0xEC, 0x46, 0x75, 0x26, 0x48, 0x8C, 0x06,
+    0x55, 0x27, 0x7F, 0xC0, 0xF1, 0xE8, 0x1F, 0xD2, 0xE4, 0x55, 0xAE, 0x78,
+    0x11, 0x6E, 0xF1, 0x3B, 0xCD, 0x55, 0xE8, 0x17, 0xE9, 0x15, 0x7B, 0x05,
+    0x91, 0x28, 0x9D, 0xD3, 0x40, 0x2E, 0x34, 0x03, 0x04, 0x2B, 0x2D, 0xC5,
+    0x5C, 0x67, 0xC5, 0xF4, 0x28, 0x8E, 0x16, 0xAC, 0xDD, 0x68, 0x43, 0x66,
+    0x51, 0xC1, 0x6F, 0x54, 0xB9, 0x22, 0xD8, 0x1A, 0x39, 0x6B, 0x0A, 0xC1,
+    0x20, 0x5A, 0x9D, 0x31, 0x30, 0xE4, 0x0B, 0xC3,
+  };
+  static unsigned char dh1024_g[] = {
+    0x02,
+  };
+  DH *dh;
+  if ((dh = DH_new()) == NULL)
+    return (NULL);
+  dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);
+  dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);
+  if ((dh->p == NULL) || (dh->g == NULL)) {
+    DH_free(dh);
+    return (NULL);
+  }
+  return (dh);
+}
+
+
+/** Associate an SSL object with a socket and return it.
+ * \param sock socket descriptor to associate with an SSL object.
+ * \return pointer to SSL object.
+ */
+SSL *
+ssl_setup_socket(int sock)
+{
+  SSL *ssl;
+  BIO *bio;
+
+  ssl = SSL_new(ctx);
+  bio = BIO_new_socket(sock, BIO_NOCLOSE);
+  BIO_set_nbio(bio, 1);
+  SSL_set_bio(ssl, bio, bio);
+  return ssl;
+}
+
+/** Close down an SSL connection and free the object.
+ * \param ssl pointer to SSL object to close down.
+ * Technically, this function sends a shutdown notification
+ * and then frees the object without waiting for acknowledgement
+ * of the shutdown. If there were a good way to do that, it would
+ * be better.
+ */
+void
+ssl_close_connection(SSL * ssl)
+{
+  SSL_shutdown(ssl);
+  SSL_free(ssl);
+}
+
+
+
+/** Given an accepted connection on the listening socket, set up SSL.
+ * \param sock an accepted socket (returned by accept())
+ * \param state pointer to place to return connection state.
+ * \return an SSL object to associate with the listen end of this connection.
+ */
+SSL *
+ssl_listen(int sock, int *state)
+{
+  SSL *ssl;
+  ssl = ssl_setup_socket(sock);
+  *state = ssl_accept(ssl);
+  return ssl;
+}
+
+/** Given an accepted connection on the listening socket, resume SSL.
+ * \param sock an accepted socket (returned by accept())
+ * \param state pointer to place to return connection state.
+ * \return an SSL object to associate with the listen end of this connection.
+ */
+SSL *
+ssl_resume(int sock, int *state)
+{
+  SSL *ssl;
+
+  ssl = ssl_setup_socket(sock);
+  SSL_set_accept_state(ssl);
+  *state = ssl_handshake(ssl);
+  return ssl;
+}
+
+
+/** Perform an SSL handshake.
+ * In some cases, a handshake may block, so we may have to call this
+ * function again. Accordingly, we return state information that
+ * tells us if we need to do that.
+ * \param ssl pointer to an SSL object.
+ * \return resulting state of the object.
+ */
+int
+ssl_handshake(SSL * ssl)
+{
+  int ret;
+  int state = 0;
+  int err;
+
+  if ((ret = SSL_do_handshake(ssl)) <= 0) {
+    switch (err = SSL_get_error(ssl, ret)) {
+    case SSL_ERROR_WANT_READ:
+      /* We must want for the socket to be readable, and then repeat
+       * the call.
+       */
+      ssl_debugdump("SSL_do_handshake wants read");
+      state = MYSSL_RB | MYSSL_HANDSHAKE;
+      break;
+    case SSL_ERROR_WANT_WRITE:
+      /* We must want for the socket to be writable, and then repeat
+       * the call.
+       */
+      ssl_debugdump("SSL_do_handshake wants write");
+      state = MYSSL_WB | MYSSL_HANDSHAKE;
+      break;
+    default:
+      /* Oops, don't know what's wrong */
+      ssl_errordump("Error in ssl_handshake");
+      state = -1;
+    }
+  } else {
+    state = ssl_accept(ssl);
+  }
+  return state;
+}
+
+/** Given connection state, determine if an SSL_accept needs to be 
+ * performed. This is a just a wrapper so we don't have to expose
+ * our internal state management stuff.
+ * \param state an ssl connection state.
+ * \return 0 if no ssl_accept is needed, non-zero otherwise.
+ */
+int
+ssl_need_accept(int state)
+{
+  return (state & MYSSL_ACCEPT);
+}
+
+/** Given connection state, determine if an SSL_handshake needs to be 
+ * performed. This is a just a wrapper so we don't have to expose
+ * our internal state management stuff.
+ * \param state an ssl connection state.
+ * \return 0 if no ssl_handshake is needed, non-zero otherwise.
+ */
+int
+ssl_need_handshake(int state)
+{
+  return (state & MYSSL_HANDSHAKE);
+}
+
+/** Given connection state, determine if it's blocked on write.
+ * This is a just a wrapper so we don't have to expose
+ * our internal state management stuff.
+ * \param state an ssl connection state.
+ * \return 0 if no ssl_handshake is needed, non-zero otherwise.
+ */
+int
+ssl_want_write(int state)
+{
+  return (state & MYSSL_WB);
+}
+
+/** Call SSL_accept and return the connection state.
+ * \param ssl pointer to an SSL object.
+ * \return ssl state flags indicating success, pending, or failure.
+ */
+int
+ssl_accept(SSL * ssl)
+{
+  int ret;
+  int state = 0;
+  X509 *peer;
+  char buf[256];
+
+  if ((ret = SSL_accept(ssl)) <= 0) {
+    switch (SSL_get_error(ssl, ret)) {
+    case SSL_ERROR_WANT_READ:
+      /* We must want for the socket to be readable, and then repeat
+       * the call.
+       */
+      ssl_debugdump("SSL_accept wants read");
+      state = MYSSL_RB | MYSSL_ACCEPT;
+      break;
+    case SSL_ERROR_WANT_WRITE:
+      /* We must want for the socket to be writable, and then repeat
+       * the call.
+       */
+      ssl_debugdump("SSL_accept wants write");
+      state = MYSSL_WB | MYSSL_ACCEPT;
+      break;
+    default:
+      /* Oops, don't know what's wrong */
+      ssl_errordump("Error accepting connection");
+      return -1;
+    }
+  } else {
+    /* Successful accept - report it */
+    if ((peer = SSL_get_peer_certificate(ssl))) {
+      if (SSL_get_verify_result(ssl) == X509_V_OK) {
+       /* The client sent a certificate which verified OK */
+       X509_NAME_oneline(X509_get_subject_name(peer), buf, 256);
+       do_log(LT_CONN, 0, 0, "SSL client certificate accepted: %s", buf);
+       state |= MYSSL_VERIFIED;
+      }
+    }
+  }
+
+  return state;
+}
+
+
+/** Given an SSL object and its last known state, attempt to read from it.
+ * \param ssl pointer to SSL object.
+ * \param state saved state of SSL object.
+ * \param net_read_ready 1 if the underlying socket is ready for read.
+ * \param net_write_ready 1 if the underlying socket is ready for write.
+ * \param buf buffer to read into.
+ * \param bufsize number of bytes to read.
+ * \param bytes_read pointer to return the number of bytes read.
+ * \return new state of SSL object, or -1 if the connection closed.
+ */
+int
+ssl_read(SSL * ssl, int state, int net_read_ready, int net_write_ready,
+        char *buf, int bufsize, int *bytes_read)
+{
+  if ((net_read_ready && !(state & MYSSL_WBOR)) ||
+      (net_write_ready && (state & MYSSL_RBOW))) {
+    do {
+      state &= ~(MYSSL_RB | MYSSL_RBOW);
+      *bytes_read = SSL_read(ssl, buf, bufsize);
+      switch (SSL_get_error(ssl, *bytes_read)) {
+      case SSL_ERROR_NONE:
+       /* Yay */
+       return state;
+      case SSL_ERROR_ZERO_RETURN:
+       /* End of data on this socket */
+       return -1;
+      case SSL_ERROR_WANT_READ:
+       /* More needs to be read from the underlying socket */
+       ssl_debugdump("SSL_read wants read");
+       state |= MYSSL_RB;
+       break;
+      case SSL_ERROR_WANT_WRITE:
+       /* More needs to be written to the underlying socket.
+        * This can happen during a rehandshake.
+        */
+       ssl_debugdump("SSL_read wants write");
+       state |= MYSSL_RBOW;
+       break;
+      default:
+       /* Should never happen */
+       ssl_errordump("Unknown ssl_read failure!");
+       return -1;
+      }
+    } while (SSL_pending(ssl) && !(state & MYSSL_RB));
+  }
+  return state;
+}
+
+/** Given an SSL object and its last known state, attempt to write to it.
+ * \param ssl pointer to SSL object.
+ * \param state saved state of SSL object.
+ * \param net_read_ready 1 if the underlying socket is ready for read.
+ * \param net_write_ready 1 if the underlying socket is ready for write.
+ * \param buf buffer to write.
+ * \param bufsize length of buffer to write.
+ * \param offset pointer to offset into buffer marking where to write next.
+ * \return new state of SSL object, or -1 if the connection closed.
+ */
+int
+ssl_write(SSL * ssl, int state, int net_read_ready, int net_write_ready,
+         unsigned char *buf, int bufsize, int *offset)
+{
+  int r;
+  if ((net_write_ready && bufsize) || (net_read_ready && !(state & MYSSL_WBOR))) {
+    state &= ~(MYSSL_WBOR | MYSSL_WB);
+    r = SSL_write(ssl, buf + *offset, bufsize);
+    switch (SSL_get_error(ssl, r)) {
+    case SSL_ERROR_NONE:
+      /* We wrote something, but maybe not all */
+      bufsize -= r;
+      *offset += r;
+      break;
+    case SSL_ERROR_WANT_WRITE:
+      /* Underlying socket isn't ready to be written to. */
+      ssl_debugdump("SSL_write wants write");
+      state |= MYSSL_WB;
+      break;
+    case SSL_ERROR_WANT_READ:
+      /* More needs to be read from the underlying socket first.
+       * This can happen during a rehandshake.
+       */
+      ssl_debugdump("SSL_write wants read");
+      state |= MYSSL_WBOR;
+      break;
+    default:
+      /* Should never happen */
+      ssl_errordump("Unknown ssl_write failure!");
+    }
+  }
+  return state;
+}
+
+
+static void
+ssl_errordump(const char *msg)
+{
+  fprintf(stderr, "%s\n", msg);
+  ERR_print_errors(bio_err);
+}
+
+#ifdef BROKEN
+
+/* The below were various attempts to serialize and save/restore 
+ * SSL objects. It ain't pretty, and it don't work.
+ */
+
+void
+ssl_write_session(FILE * fp, SSL * ssl)
+{
+  SSL_SESSION *s;
+  s = SSL_get_session(ssl);
+  PEM_write_SSL_SESSION(fp, s);
+}
+
+void
+ssl_read_session(FILE * fp)
+{
+  SSL_SESSION s;
+  PEM_read_SSL_SESSION(fp, &s, NULL, NULL);
+  SSL_CTX_add_session(ctx, &s);
+}
+
+void
+ssl_write_ssl(FILE * fp, SSL * ssl)
+{
+  BIO *bio;
+  SSL_CIPHER *cipher;
+  bio = SSL_get_rbio(ssl);
+  cipher = SSL_CIPHER_get_current_cipher(ssl);
+  fwrite(bio, sizeof(BIO), 1, fp);
+  fwrite(ssl->version, sizeof(ssl->version), 1, fp);
+  fwrite(ssl->type, sizeof(ssl->type), 1, fp);
+  fwrite(ssl->rwstate, sizeof(ssl->type), 1, fp);
+  fwrite(ssl->rstate, sizeof(ssl->type), 1, fp);
+  fwrite(ssl->state, sizeof(ssl->type), 1, fp);
+  fwrite(cipher, sizeof(cipher), 1, fp);
+}
+
+SSL *
+ssl_read_ssl(FILE * fp, int sock)
+{
+  SSL *ssl;
+  BIO *bio;
+
+  bio = BIO_new(BIO_s_socket());
+  fread(bio, sizeof(BIO), 1, fp);
+  ssl = SSL_new(ctx);
+  fread(ssl, sizeof(SSL), 1, fp);
+  SSL_set_ssl_method(ssl, SSLv23_server_method());
+  SSL_set_bio(ssl, bio, bio);
+  return ssl;
+}
+#endif                         /* BROKEN */
+
+
+#endif                         /* HAS_OPENSSL */
diff --git a/src/notify.c b/src/notify.c
new file mode 100644 (file)
index 0000000..ed1f09d
--- /dev/null
@@ -0,0 +1,1705 @@
+/**
+ * \file notify.c
+ *
+ * \brief Notification of objects with messages, for PennMUSH.
+ *
+ * The functions in this file are primarily concerned with maintaining
+ * queues of blocks of text to transmit to a player descriptor.
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef WIN32
+#include <windows.h>
+#include <winsock.h>
+#include <io.h>
+#else                          /* !WIN32 */
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#endif
+#include <sys/ioctl.h>
+#ifdef I_SYS_SOCKET
+#include <sys/socket.h>
+#endif
+#ifdef I_NETINET_IN
+#include <netinet/in.h>
+#endif
+#ifdef I_NETDB
+#include <netdb.h>
+#endif
+#ifdef I_SYS_PARAM
+#include <sys/param.h>
+#endif
+#ifdef I_SYS_STAT
+#include <sys/stat.h>
+#endif
+#endif                         /* !WIN32 */
+#include <time.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <limits.h>
+#ifdef I_FLOATINGPOINT
+#include <floatingpoint.h>
+#endif
+
+#include "conf.h"
+#include "mushdb.h"
+#include "externs.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "lock.h"
+#include "help.h"
+#include "match.h"
+#include "ansi.h"
+#include "pueblo.h"
+#include "parse.h"
+#include "access.h"
+#include "version.h"
+#include "patches.h"
+#include "mysocket.h"
+#include "ident.h"
+#include "strtree.h"
+#include "log.h"
+
+
+#include "mymalloc.h"
+
+#ifdef CHAT_SYSTEM
+#include "extchat.h"
+extern CHAN *channels;
+#endif /* CHAT_SYSTEM */
+#include "extmail.h"
+#include "attrib.h"
+#include "game.h"
+#include "confmagic.h"
+
+
+static int under_limit = 1;
+
+/** Default connection, nothing special */
+#define CONN_DEFAULT 0
+/** Using Pueblo, Smial, Mushclient, Simplemu, or some other
+ *  pueblo-style HTML aware client */
+#define CONN_HTML 0x1
+/** Using a client that understands telnet options */
+#define CONN_TELNET 0x2
+/** Send a telnet option to test client */
+#define CONN_TELNET_QUERY 0x4
+
+
+/* When the mush gets a new connection, it tries sending a telnet
+ * option negotiation code for setting client-side line-editing mode
+ * to it. If it gets a reply, a flag in the descriptor struct is
+ * turned on indicated telnet-awareness.
+ * 
+ * If the reply indicates that the client supports linemode, further
+ * instructions as to what linemode options are to be used is sent.
+ * Those options: Client-side line editing, and expanding literal
+ * client-side-entered tabs into spaces.
+ * 
+ * Option negotation requests sent by the client are processed,
+ * with the only one we confirm rather than refuse outright being
+ * suppress-go-ahead, since a number of telnet clients try it.
+ *
+ * The character 255 is the telnet option escape character, so when it
+ * is sent to a telnet-aware client by itself (Since it's also often y-umlaut)
+ * it must be doubled to escape it for the client. This is done automatically,
+ * and is the original purpose of adding telnet option support.
+ */
+
+/* Telnet codes */
+#define IAC 255                        /**< telnet: interpret as command */
+
+/** Iterate through a list of descriptors, and do something with those
+ * that are connected.
+ */
+#define DESC_ITER_CONN(d) \
+        for(d = descriptor_list;(d);d=(d)->next) \
+          if((d)->connected)
+
+#ifdef NT_TCP
+/* for Windows NT IO-completion-port method of TCP/IP - NJG */
+
+/* Windows NT TCP/IP routines written by Nick Gammon <nick@gammon.com.au> */
+
+#include <process.h>
+HANDLE CompletionPort;         /* IOs are queued up on this port */
+SOCKET MUDListenSocket;                /* for our listening thread */
+DWORD dwMUDListenThread;       /* thread handle for listening thread */
+SOCKADDR_IN saServer;          /* for listening thread */
+void __cdecl MUDListenThread(void *pVoid);     /* the listening thread */
+DWORD platform;                        /* which version of Windows are we using? */
+OVERLAPPED lpo_aborted;                /* special to indicate a player has finished TCP IOs */
+OVERLAPPED lpo_shutdown;       /* special to indicate a player should do a shutdown */
+void ProcessWindowsTCP(void);  /* handle NT-style IOs */
+CRITICAL_SECTION cs;           /* for thread synchronisation */
+#endif
+
+
+static const char *flushed_message = "\r\n<Output Flushed>\x1B[0m\r\n";
+
+extern DESC *descriptor_list;
+#ifdef WIN32
+static WSADATA wsadata;
+#endif
+
+static struct text_block *make_text_block(const unsigned char *s, int n);
+void free_text_block(struct text_block *t);
+void add_to_queue(struct text_queue *q, const unsigned char *b, int n);
+static int flush_queue(struct text_queue *q, int n);
+int queue_write(DESC *d, const unsigned char *b, int n);
+int queue_newwrite(DESC *d, const unsigned char *b, int n);
+int queue_string(DESC *d, const char *s);
+int queue_string_eol(DESC *d, const char *s);
+int queue_eol(DESC *d);
+void freeqs(DESC *d);
+int process_output(DESC *d);
+
+/** Types of text renderings we can do in notify_anything(). */
+enum na_type {
+  NA_ASCII = 0,                        /**< Plain old ascii. */
+  NA_ANSI,                     /**< ANSI flag */
+  NA_COLOR,                    /**< ANSI and COLOR flags */
+  NA_PUEBLO,                   /**< html */
+  NA_PASCII,                   /**< Player without any of the above */
+  NA_TANSI,                    /**< Like above with telnet-aware client */
+  NA_TCOLOR,                   /**< Like above with telnet-aware client */
+  NA_TPASCII,                  /**< Like above with telnet-aware client */
+  NA_NANSI,                    /**< ANSI and NOACCENTS */
+  NA_NCOLOR,                   /**< ANSI, COLOR, NOACCENTS */
+  NA_NPUEBLO,                  /**< html & NOACCENTS */
+  NA_NPASCII,                  /**< NOACCENTS */
+//  NA_EVALONCONTACT           /**< Evaluate on Contact */
+};
+
+/** Number of possible message text renderings */
+#define MESSAGE_TYPES 12
+
+#define TA_BGC 0       /**< Text attribute background color */
+#define TA_FGC 1       /**< Text attribute foreground color */
+#define TA_BOLD 2      /**< Text attribute bold/hilite */
+#define TA_REV 3       /**< Text attribute reverse/inverse */
+#define TA_BLINK 4     /**< Text attribute blinking/flashing */
+#define TA_ULINE 5     /**< Text attribute underline */
+
+static int na_depth = 0;
+
+/** A place to store a rendered message. */
+struct notify_strings {
+  unsigned char *message;      /**< The message text. */
+  size_t len;                  /**< Length of message. */
+  int made;                    /**< True if message has been rendered. */
+};
+
+static void fillstate(int state[], const unsigned char **f);
+static void ansi_change_state(char *t, char **o, int color, int *state,
+                             int *newstate);
+static enum na_type notify_type(DESC *d);
+static void free_strings(struct notify_strings messages[]);
+static void zero_strings(struct notify_strings messages[]);
+static unsigned char *notify_makestring(const char *message,
+                                       struct notify_strings messages[],
+                                       enum na_type type);
+
+
+static void
+fillstate(int state[6], const unsigned char **f)
+{
+  const unsigned char *p;
+  int i;
+  int n;
+  p = *f;
+  p++;
+  if (*p != '[') {
+    while (*p && *p != 'm')
+      p++;
+  } else {
+    p++;
+    while (*p && *p != 'm') {
+      if ((*p > '9') || (*p < '0')) {
+       /* Nada */
+      } else if (!(*(p + 1)) || (*(p + 1) == 'm') || (*(p + 1) == ';')) {
+       /* ShortCode */ ;
+       switch (*p) {
+       case '0':
+         for (i = 0; i < 6; i++)
+           state[i] = 0;
+         break;
+       case '1':
+         state[TA_BOLD] = 1;
+         break;
+       case '7':
+         state[TA_REV] = 1;
+         break;
+       case '5':
+         state[TA_BLINK] = 1;
+         break;
+       case '4':
+         state[TA_ULINE] = 1;
+         break;
+       }
+      } else {
+       n = (*p - '0') * 10;
+       p++;
+       n += (*p - '0');
+       if ((n >= 30) && (n <= 37))
+         state[TA_FGC] = n - 29;
+       else if ((n >= 40) && (n <= 47))
+         state[TA_BGC] = n - 39;
+      }
+      p++;
+    }
+  }
+  if ((p != *f) && (*p != 'm'))
+    p--;
+  *f = p;
+}
+
+/** Add an ansi tag if it's needed here */
+#define add_ansi_if(x,c) \
+  do { \
+    if (newstate[(x)] && (newstate[(x)]!=state[(x)])) { \
+      if (i) safe_chr(';',t,o); \
+      safe_str((c),t,o); \
+      i=1; \
+    } \
+  } while (0)
+
+static void
+ansi_change_state(char *t, char **o, int color, int *state, int *newstate)
+{
+  int i, n;
+  if ((state[TA_BOLD] && !newstate[TA_BOLD]) ||
+      (state[TA_REV] && !newstate[TA_REV]) ||
+      (state[TA_BLINK] && !newstate[TA_BLINK]) ||
+      (state[TA_ULINE] && !newstate[TA_ULINE]) ||
+      (color && state[TA_FGC] && !newstate[TA_FGC]) ||
+      (color && state[TA_BGC] && !newstate[TA_BGC])) {
+    for (n = 0; n < 6; n++)
+      state[n] = 0;
+    safe_str(ANSI_NORMAL, t, o);
+  }
+  if ((newstate[TA_BOLD] && (newstate[TA_BOLD] != state[TA_BOLD])) ||
+      (newstate[TA_REV] && (newstate[TA_REV] != state[TA_REV])) ||
+      (newstate[TA_BLINK] && (newstate[TA_BLINK] != state[TA_BLINK])) ||
+      (newstate[TA_ULINE] && (newstate[TA_ULINE] != state[TA_ULINE])) ||
+      (color && newstate[TA_FGC] && (newstate[TA_FGC] != state[TA_FGC])) ||
+      (color && newstate[TA_BGC] && (newstate[TA_BGC] != state[TA_BGC]))) {
+    safe_chr(ESC_CHAR, t, o);
+    safe_chr('[', t, o);
+    i = 0;
+    add_ansi_if(TA_BOLD, "1");
+    add_ansi_if(TA_REV, "7");
+    add_ansi_if(TA_BLINK, "5");
+    add_ansi_if(TA_ULINE, "4");
+    if (color) {
+      add_ansi_if(TA_FGC, unparse_integer(newstate[TA_FGC] + 29));
+      add_ansi_if(TA_BGC, unparse_integer(newstate[TA_BGC] + 39));
+    }
+    safe_chr('m', t, o);
+  }
+  for (n = 0; n < 6; n++)
+    state[n] = newstate[n];
+}
+
+#undef add_ansi_if
+
+static void
+zero_strings(struct notify_strings messages[])
+{
+  int n;
+  for (n = 0; n < MESSAGE_TYPES; n++) {
+    messages[n].message = NULL;
+    messages[n].len = 0;
+    messages[n].made = 0;
+  }
+}
+
+static void
+free_strings(struct notify_strings messages[])
+{
+  int n;
+  for (n = 0; n < MESSAGE_TYPES; n++)
+    if (messages[n].message)
+      mush_free(messages[n].message, "string");
+}
+
+static unsigned char *
+notify_makestring(const char *message, struct notify_strings messages[],
+                 enum na_type type)
+{
+  char *o;
+  const unsigned char *p;
+  char *t;
+  int state[6] = { 0, 0, 0, 0, 0, 0 };
+  int newstate[6] = { 0, 0, 0, 0, 0, 0 };
+  int changed = 0;
+  int color = 0;
+  int strip = 0;
+  int pueblo = 0;
+  static char tbuf[BUFFER_LEN];
+
+  if (messages[type].made)
+    return messages[type].message;
+  messages[type].made = 1;
+
+  p = (unsigned char *) message;
+  o = tbuf;
+  t = o;
+
+  /* Since well over 50% is this type, we do it quick */
+  switch (type) {
+  case NA_ASCII:
+    while (*p) {
+      switch (*p) {
+      case TAG_START:
+       while (*p && *p != TAG_END)
+         p++;
+       break;
+      case '\r':
+      case BEEP_CHAR:
+       break;
+      case ESC_CHAR:
+       while (*p && *p != 'm')
+         p++;
+       break;
+      default:
+       safe_chr(*p, t, &o);
+      }
+      p++;
+    }
+    *o = '\0';
+    messages[type].message = (unsigned char *) mush_strdup(tbuf, "string");
+    messages[type].len = o - tbuf;
+    return messages[type].message;
+  case NA_NPASCII:
+    strip = 1;
+  case NA_PASCII:
+  case NA_TPASCII:
+    /* PLAYER Ascii. Different output. \n is \r\n */
+    if (type == NA_NPASCII)
+      strip = 1;
+    while (*p) {
+      switch (*p) {
+      case IAC:
+       if (type == NA_TPASCII)
+         safe_str("\xFF\xFF", t, &o);
+       else if (strip)
+         safe_str(accent_table[IAC].base, t, &o);
+       else
+         safe_chr((char) IAC, t, &o);
+       break;
+      case TAG_START:
+       while (*p && *p != TAG_END)
+         p++;
+       break;
+      case ESC_CHAR:
+       while (*p && *p != 'm')
+         p++;
+       break;
+      case '\r':
+       break;
+      case '\n':
+       safe_str("\r\n", t, &o);
+       break;
+      default:
+       if (strip && accent_table[(unsigned char) *p].base)
+         safe_str(accent_table[(unsigned char) *p].base, t, &o);
+       else
+         safe_chr(*p, t, &o);
+      }
+      p++;
+    }
+    *o = '\0';
+    messages[type].message = (unsigned char *) mush_strdup(tbuf, "string");
+    messages[type].len = o - tbuf;
+    return messages[type].message;
+
+  case NA_PUEBLO:
+  case NA_NPUEBLO:
+    pueblo = 1;
+    /* FALLTHROUGH */
+  case NA_COLOR:
+  case NA_TCOLOR:
+  case NA_NCOLOR:
+    color = 1;
+    /* FALLTHROUGH */
+  case NA_ANSI:
+  case NA_TANSI:
+  case NA_NANSI:
+    if (type == NA_NCOLOR || type == NA_NANSI || type == NA_NPUEBLO)
+      strip = 1;
+    while (*p) {
+      switch ((unsigned char) *p) {
+      case IAC:
+       if (changed) {
+         changed = 0;
+         ansi_change_state(t, &o, color, state, newstate);
+       }
+       if (type == NA_TANSI || type == NA_TCOLOR)
+         safe_str("\xFF\xFF", t, &o);
+       else if (strip && accent_table[IAC].base)
+         safe_str(accent_table[IAC].base, t, &o);
+       else
+         safe_chr((char) IAC, t, &o);
+       break;
+      case TAG_START:
+       if (pueblo) {
+         safe_chr('<', t, &o);
+         p++;
+         while ((*p) && (*p != TAG_END)) {
+           safe_chr(*p, t, &o);
+           p++;
+         }
+         safe_chr('>', t, &o);
+       } else {
+         /* Non-pueblo */
+         while (*p && *p != TAG_END)
+           p++;
+       }
+       break;
+      case TAG_END:
+       /* Should never be seen alone */
+       break;
+      case '\r':
+       break;
+      case ESC_CHAR:
+       fillstate(newstate, &p);
+       changed = 1;
+       break;
+      default:
+       if (changed) {
+         changed = 0;
+         ansi_change_state(t, &o, color, state, newstate);
+       }
+       if (pueblo) {
+         if (strip) {
+           /* Even if we're NOACCENTS, we must still translate a few things */
+           switch ((unsigned char) *p) {
+           case '\n':
+           case '&':
+           case '<':
+           case '>':
+           case '"':
+             safe_str(accent_table[(unsigned char) *p].entity, t, &o);
+             break;
+           default:
+             if (accent_table[(unsigned char) *p].base)
+               safe_str(accent_table[(unsigned char) *p].base, t, &o);
+             else
+               safe_chr(*p, t, &o);
+             break;
+           }
+         } else if (accent_table[(unsigned char) *p].entity)
+           safe_str(accent_table[(unsigned char) *p].entity, t, &o);
+         else
+           safe_chr(*p, t, &o);
+       } else {
+         /* Non-pueblo */
+         if ((unsigned char) *p == '\n')
+           safe_str("\r\n", t, &o);
+         else if (strip && accent_table[(unsigned char) *p].base)
+           safe_str(accent_table[(unsigned char) *p].base, t, &o);
+         else
+           safe_chr(*p, t, &o);
+       }
+      }
+      p++;
+    }
+    if (state[TA_BOLD] || state[TA_REV] ||
+       state[TA_BLINK] || state[TA_ULINE] ||
+       (color && (state[TA_FGC] || state[TA_BGC])))
+      safe_str(ANSI_NORMAL, t, &o);
+
+    break;
+  }
+
+  *o = '\0';
+  messages[type].message = (unsigned char *) mush_strdup(tbuf, "string");
+  messages[type].len = o - tbuf;
+  return messages[type].message;
+}
+
+/*--------------------------------------------------------------
+ * Iterators for notify_anything.
+ * notify_anything calls these functions repeatedly to get the
+ * next object to notify, passing in the last object notified.
+ * On the first pass, it passes in NOTHING. When it finally
+ * receives NOTHING back, it stops.
+ */
+
+/** notify_anthing() iterator for a single dbref.
+ * \param current last dbref from iterator.
+ * \param data memory address containing first object in chain.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_one(dbref current, void *data)
+{
+  if (current == NOTHING)
+    return *((dbref *) data);
+  else
+    return NOTHING;
+}
+
+dbref na_jloc(dbref current, void *data) {
+       dbref player = *((dbref *) data);
+       if(current == NOTHING)
+               return player;
+       if(current == player)
+               return Location(player);
+       else
+               return NOTHING;
+}
+
+/** notify_anthing() iterator for following a contents/exit chain.
+ * \param current last dbref from iterator.
+ * \param data memory address containing first object in chain.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_next(dbref current, void *data)
+{
+  if (current == NOTHING)
+    return *((dbref *) data);
+  else
+    return Next(current);
+}
+
+/** notify_anthing() iterator for a location and its contents.
+ * \param current last dbref from iterator.
+ * \param data memory address containing dbref of location.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_loc(dbref current, void *data)
+{
+  dbref loc = *((dbref *) data);
+  if (current == NOTHING)
+    return loc;
+  else if (current == loc)
+    return Contents(current);
+  else
+    return Next(current);
+}
+
+/** notify_anthing() iterator for a contents/exit chain, with a dbref to skip.
+ * \param current last dbref from iterator.
+ * \param data memory address containing array of two dbrefs: the start of the chain and the dbref to skip.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_nextbut(dbref current, void *data)
+{
+  dbref *dbrefs = data;
+
+  do {
+    if (current == NOTHING)
+      current = dbrefs[0];
+    else
+      current = Next(current);
+  } while (current == dbrefs[1]);
+  return current;
+}
+
+/** notify_anthing() iterator for a location and its contents, with a dbref to skip.
+ * \param current last dbref from iterator.
+ * \param data memory address containing array of two dbrefs: the location and the dbref to skip.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_except(dbref current, void *data)
+{
+  dbref *dbrefs = data;
+
+  do {
+    if (current == NOTHING)
+      current = dbrefs[0];
+    else if (current == dbrefs[0])
+      current = Contents(current);
+    else
+      current = Next(current);
+  } while (current == dbrefs[1]);
+  return current;
+}
+
+/** notify_anthing() iterator for a location and its contents, with 2 dbrefs to skip.
+ * \param current last dbref from iterator.
+ * \param data memory address containing array of three dbrefs: the location and the dbrefs to skip.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_except2(dbref current, void *data)
+{
+  dbref *dbrefs = data;
+
+  do {
+    if (current == NOTHING)
+      current = dbrefs[0];
+    else if (current == dbrefs[0])
+      current = Contents(current);
+    else
+      current = Next(current);
+  } while ((current == dbrefs[1]) || (current == dbrefs[2]));
+  return current;
+}
+
+/** notify_anthing() iterator for a location and its contents, with N dbrefs to skip.
+ * \param current last dbref from iterator.
+ * \param data memory address containing array of three or more values: the number of dbrefs to skip, the location, and the dbrefs to skip.
+ * \return dbref of next object to notify, or NOTHING when done.
+ */
+dbref
+na_exceptN(dbref current, void *data)
+{
+  dbref *dbrefs = data;
+  int i, check;
+
+  do {
+    if (current == NOTHING)
+      current = dbrefs[1];
+    else if (current == dbrefs[1])
+      current = Contents(current);
+    else
+      current = Next(current);
+    check = 0;
+    for (i = 2; i < dbrefs[0] + 2; i++)
+      if (current == dbrefs[i])
+       check = 1;
+  } while (check);
+  return current;
+}
+
+
+static enum na_type
+notify_type(DESC *d)
+{
+  enum na_type poutput;
+  int strip;
+
+  if (!d->connected) {
+    /* These are the settings used at, e.g., the connect screen,
+     * when there's no connected player yet. If you want to use
+     * ansified connect screens, you'd probably change NA_NPASCII
+     * to NA_NCOLOR (for no accents) or NA_COLOR (for accents). 
+     * We don't recommend it. If you want to use accented characters,
+     * change NA_NPUEBLO and NA_NPASCII to NA_PUEBLO and NA_PASCII,
+     * respectively. That's not so bad.
+     */
+    return (d->conn_flags & CONN_HTML) ? NA_NPUEBLO : NA_NPASCII;
+  }
+
+  /* At this point, we have a connected player on the descriptor */
+  strip = IS(d->player, TYPE_PLAYER, "NOACCENTS");
+
+  if (d->conn_flags & CONN_HTML) {
+    poutput = strip ? NA_NPUEBLO : NA_PUEBLO;
+  } else if (ShowAnsi(d->player)) {
+    if (ShowAnsiColor(d->player)) {
+      if (strip)
+       poutput = NA_NCOLOR;
+      else
+       poutput = (d->conn_flags & CONN_TELNET) ? NA_TCOLOR : NA_COLOR;
+    } else {
+      if (strip)
+       poutput = NA_NANSI;
+      else
+       poutput = (d->conn_flags & CONN_TELNET) ? NA_TANSI : NA_ANSI;
+    }
+  } else {
+    if (strip)
+      poutput = NA_NPASCII;
+    else
+      poutput = (d->conn_flags & CONN_TELNET) ? NA_TPASCII : NA_PASCII;
+  }
+  return poutput;
+}
+
+/** Send a message to a series of dbrefs.
+ * This key function takes a speaker's utterance and looks up each
+ * object that should hear it. For each, it may need to render
+ * the utterance in a different fashion (with or without ansi, html,
+ * accents), but we cache each rendered version for efficiency.
+ * \param speaker dbref of object producing the message.
+ * \param func pointer to iterator function to look up each receiver.
+ * \param fdata initial data to pass to func.
+ * \param nsfunc function to call to do NOSPOOF formatting, or NULL.
+ * \param flags flags to pass in (such as NA_INTERACT)
+ * \param message message to render and transmit.
+ * \param loc location the message was sent to.
+ */
+void
+notify_anything_loc(dbref speaker, na_lookup func,
+                   void *fdata, char *(*nsfunc) (dbref, na_lookup func, void *,
+                                                 int), int flags,
+                   const char *message, dbref loc)
+{
+  dbref target;
+  dbref passalong[3];
+  struct notify_strings messages[MESSAGE_TYPES];
+  struct notify_strings nospoofs[MESSAGE_TYPES];
+  struct notify_strings paranoids[MESSAGE_TYPES];
+  int i, j;
+  DESC *d;
+  enum na_type poutput;
+  unsigned char *pstring;
+  size_t plen;
+  char *bp;
+  ATTR *a;
+  char *asave;
+  char const *ap;
+  char *preserve[NUMQ];
+  int havespoof = 0;
+  int havepara = 0;
+  char *wsave[10];
+  char *tbuf1 = NULL, *nospoof = NULL, *paranoid = NULL, *msgbuf;
+  char eocm[BUFFER_LEN];
+  static dbref puppet = NOTHING;
+  int nsflags;
+
+  if (!message || *message == '\0' || !func)
+    return;
+
+  /* Depth check */
+  if (na_depth > 7)
+    return;
+  na_depth++;
+
+  /* Only allocate these buffers when needed */
+  for (i = 0; i < MESSAGE_TYPES; i++) {
+    messages[i].message = NULL;
+    messages[i].made = 0;
+    nospoofs[i].message = NULL;
+    nospoofs[i].made = 0;
+    paranoids[i].message = NULL;
+    paranoids[i].made = 0;
+  }
+
+  msgbuf = mush_strdup(message, "string");
+
+  target = NOTHING;
+
+  while ((target = func(target, fdata)) != NOTHING) {
+    if ((flags & NA_PONLY) && !IsPlayer(target))
+      continue;
+
+    if (IsPlayer(target)) {
+      if (!Connected(target) && options.login_allow && under_limit)
+       continue;
+
+      if (flags & NA_INTERACTION) {
+       int pass_interact = 1;
+       if ((flags & NA_INTER_SEE) &&
+           !can_interact(speaker, target, INTERACT_SEE))
+         pass_interact = 0;
+       if (pass_interact && (flags & NA_INTER_PRESENCE) &&
+           !can_interact(speaker, target, INTERACT_PRESENCE))
+         pass_interact = 0;
+       if (pass_interact && (flags & NA_INTER_HEAR) &&
+           !can_interact(speaker, target, INTERACT_HEAR))
+         pass_interact = 0;
+       if (pass_interact && (flags & NA_INTER_LOCK) &&
+           !Pass_Interact_Lock(speaker, target))
+         pass_interact = 0;
+       if (!pass_interact)
+         continue;
+      }
+
+      /* Evaluate the On Contact Message */
+      if(flags & NA_EVALONCONTACT) {
+       wsave[j] = global_eval_context.wenv[j];
+       global_eval_context.wenv[0] = msgbuf;
+       for (j = 1; j < 10; j++)
+         global_eval_context.wenv[j] = NULL;
+       bp = eocm;
+       process_expression(eocm, &bp, (const char **) &msgbuf, target, speaker, speaker,
+           PE_DEFAULT, PT_DEFAULT, NULL);
+       *bp = 0;
+       for (j = 0; j < 10; j++)
+         global_eval_context.wenv[j] = wsave[j];
+      }
+
+      for (d = descriptor_list; d; d = d->next) {
+       if (d->connected && d->player == target) {
+         poutput = notify_type(d);
+
+         if ((flags & NA_PONLY) && (poutput != NA_PUEBLO))
+           continue;
+
+         if (!(flags & NA_SPOOF)
+             && (nsfunc && ((Nospoof(target) && (target != speaker))
+                            || (flags & NA_NOSPOOF)))) {
+           if (Paranoid(target) || (flags & NA_PARANOID)) {
+             if (!havepara) {
+               paranoid = nsfunc(speaker, func, fdata, 1);
+               havepara = 1;
+             }
+             pstring = notify_makestring(paranoid, paranoids, poutput);
+             plen = paranoids[poutput].len;
+           } else {
+             if (!havespoof) {
+               nospoof = nsfunc(speaker, func, fdata, 0);
+               havespoof = 1;
+             }
+             pstring = notify_makestring(nospoof, nospoofs, poutput);
+             plen = nospoofs[poutput].len;
+           }
+           queue_newwrite(d, pstring, plen);
+         }
+
+         pstring = notify_makestring((flags & NA_EVALONCONTACT) ? eocm : msgbuf, messages, poutput);
+         plen = messages[poutput].len;
+         if (pstring && *pstring)
+           queue_newwrite(d, pstring, plen);
+
+         if (!(flags & NA_NOENTER)) {
+           if ((poutput == NA_PUEBLO) || (poutput == NA_NPUEBLO)) {
+             if (flags & NA_NOPENTER)
+               queue_newwrite(d, (unsigned char *) "\n", 1);
+             else
+               queue_newwrite(d, (unsigned char *) "<BR>\n", 5);
+           } else {
+             queue_newwrite(d, (unsigned char *) "\r\n", 2);
+           }
+         }
+       }
+      }
+    } else if (Puppet(target) &&
+              ((Location(target) != Location(Owner(target))) ||
+               Verbose(target) ||
+               (flags & NA_MUST_PUPPET)) &&
+              ((flags & NA_PUPPET) || !(flags & NA_NORELAY))) {
+      dbref last = puppet;
+
+      if (flags & NA_INTERACTION) {
+       int pass_interact = 1;
+       if ((flags & NA_INTER_SEE) &&
+           !can_interact(speaker, target, INTERACT_SEE))
+         pass_interact = 0;
+       if (pass_interact && (flags & NA_INTER_PRESENCE) &&
+           !can_interact(speaker, target, INTERACT_PRESENCE))
+         pass_interact = 0;
+       if (pass_interact && (flags & NA_INTER_HEAR) &&
+           !can_interact(speaker, target, INTERACT_HEAR))
+         pass_interact = 0;
+       if (!pass_interact)
+         continue;
+      }
+
+      puppet = target;
+      if (!tbuf1)
+       tbuf1 = (char *) mush_malloc(BUFFER_LEN, "string");
+      bp = tbuf1;
+      safe_str(Name(target), tbuf1, &bp);
+      safe_str("> ", tbuf1, &bp);
+      *bp = '\0';
+      notify_anything(GOD, na_one, &Owner(target), NULL,
+                     NA_NOENTER | NA_PUPPET2 | NA_NORELAY | flags, tbuf1);
+
+      nsflags = 0;
+      if (!(flags & NA_SPOOF)) {
+       if (Nospoof(target))
+         nsflags |= NA_NOSPOOF;
+       if (Paranoid(target))
+         nsflags |= NA_PARANOID;
+      }
+      notify_anything(speaker, na_one, &Owner(target), ns_esnotify,
+                     flags | nsflags | NA_NORELAY | NA_PUPPET2, msgbuf);
+      puppet = last;
+    }
+    if ((flags & NA_NOLISTEN)
+       || (!PLAYER_LISTEN && IsPlayer(target))
+       || IsExit(target))
+      continue;
+
+#ifdef RPMODE_SYS
+    /* Do ICFUNCS related rplogging crapp */
+    if(has_flag_by_name(target, "ICFUNCS", TYPE_ROOM))
+      rplog_room(target, speaker, (char *) notify_makestring(msgbuf, messages, NA_ASCII));
+#endif /* RPMODE_SYS */
+
+    /* do @listen stuff */
+    a = atr_get_noparent(target, "LISTEN");
+    if (a) {
+      if (!tbuf1)
+       tbuf1 = (char *) mush_malloc(BUFFER_LEN, "string");
+      strcpy(tbuf1, atr_value(a));
+      if (AF_Regexp(a)
+         ? regexp_match_case(tbuf1,
+                             (char *) notify_makestring(msgbuf, messages,
+                                                        NA_ASCII), AF_Case(a))
+         : wild_match_case(tbuf1,
+                           (char *) notify_makestring(msgbuf, messages,
+                                                      NA_ASCII), AF_Case(a))) {
+       if (eval_lock(speaker, target, Listen_Lock))
+         if (PLAYER_AHEAR || (!IsPlayer(target))) {
+           if (speaker != target)
+             charge_action(speaker, target, "AHEAR");
+           else
+             charge_action(speaker, target, "AMHEAR");
+           charge_action(speaker, target, "AAHEAR");
+         }
+       if (!(flags & NA_NORELAY) && (loc != target) &&
+           !filter_found(target,
+                         (char *) notify_makestring(msgbuf, messages,
+                                                    NA_ASCII), 1)) {
+         passalong[0] = target;
+         passalong[1] = target;
+         passalong[2] = Owner(target);
+         a = atr_get(target, "INPREFIX");
+         if (a) {
+           for (j = 0; j < 10; j++)
+             wsave[j] = global_eval_context.wenv[j];
+           global_eval_context.wenv[0] = (char *) msgbuf;
+           for (j = 1; j < 10; j++)
+             global_eval_context.wenv[j] = NULL;
+           save_global_regs("inprefix_save", preserve);
+           asave = safe_atr_value(a);
+           ap = asave;
+           bp = tbuf1;
+           process_expression(tbuf1, &bp, &ap, target, speaker, speaker,
+                              PE_DEFAULT, PT_DEFAULT, NULL);
+           if (bp != tbuf1)
+             safe_chr(' ', tbuf1, &bp);
+           safe_str(msgbuf, tbuf1, &bp);
+           *bp = 0;
+           free(asave);
+           restore_global_regs("inprefix_save", preserve);
+           for (j = 0; j < 10; j++)
+             global_eval_context.wenv[j] = wsave[j];
+         }
+         notify_anything(speaker, Puppet(target) ? na_except2 : na_except,
+                         passalong, NULL, flags | NA_NORELAY | NA_PUPPET,
+                         (a) ? tbuf1 : msgbuf);
+       }
+      }
+    }
+    /* if object is flagged LISTENER, check for ^ listen patterns
+     *    * these are like AHEAR - object cannot trigger itself.
+     *    * unlike normal @listen, don't pass the message on.
+     *    */
+
+    if ((speaker != target) && (ThingListen(target) || RoomListen(target))
+       && eval_lock(speaker, target, Listen_Lock)
+      )
+      atr_comm_match(target, speaker, '^', ':',
+                    (char *) notify_makestring(msgbuf, messages, NA_ASCII), 0,
+                    NULL, NULL, NULL);
+
+    /* If object is flagged AUDIBLE and has a @FORWARDLIST, send
+     *  stuff on */
+    if ((!(flags & NA_NORELAY) || (flags & NA_PUPPET)) && Audible(target)
+       && ((a = atr_get_noparent(target, "FORWARDLIST")) != NULL)
+       && !filter_found(target, msgbuf, 0)) {
+      notify_list(speaker, target, "FORWARDLIST", msgbuf, flags);
+
+    }
+  }
+
+  for (i = 0; i < MESSAGE_TYPES; i++) {
+    if (messages[i].message)
+      mush_free((Malloc_t) messages[i].message, "string");
+    if (nospoofs[i].message)
+      mush_free((Malloc_t) nospoofs[i].message, "string");
+    if (paranoids[i].message)
+      mush_free((Malloc_t) paranoids[i].message, "string");
+  }
+  if (nospoof)
+    mush_free((Malloc_t) nospoof, "string");
+  if (paranoid)
+    mush_free((Malloc_t) paranoid, "string");
+  if (tbuf1)
+    mush_free((Malloc_t) tbuf1, "string");
+  mush_free((Malloc_t) msgbuf, "string");
+  na_depth--;
+}
+
+/** Send a message to a series of dbrefs.
+ * This key function takes a speaker's utterance and looks up each
+ * object that should hear it. For each, it may need to render
+ * the utterance in a different fashion (with or without ansi, html,
+ * accents), but we cache each rendered version for efficiency.
+ * \param speaker dbref of object producing the message.
+ * \param func pointer to iterator function to look up each receiver.
+ * \param fdata initial data to pass to func.
+ * \param nsfunc function to call to do NOSPOOF formatting, or NULL.
+ * \param flags flags to pass in (such as NA_INTERACT)
+ * \param message message to render and transmit.
+ */
+void
+notify_anything(dbref speaker, na_lookup func,
+               void *fdata, char *(*nsfunc) (dbref, na_lookup func, void *,
+                                             int), int flags,
+               const char *message)
+{
+  dbref loc;
+
+  if (GoodObject(speaker))
+    loc = Location(speaker);
+  else
+    loc = NOTHING;
+
+  notify_anything_loc(speaker, func, fdata, nsfunc, flags, message, loc);
+}
+
+/** Basic 'notify player with message */
+#define notify(p,m)           notify_anything(orator, na_one, &(p), NULL, 0, m)
+
+/** Notify a player with a formatted string, easy version.
+ * This is a safer replacement for notify(player, tprintf(fmt, ...))
+ * \param speaker dbref of object producing the message.
+ * \param func pointer to iterator function to look up each receiver.
+ * \param fdata initial data to pass to func.
+ * \param nsfunc function to call to do NOSPOOF formatting, or NULL.
+ * \param flags flags to pass in (such as NA_INTERACT)
+ * \param fmt format string.
+ */
+void WIN32_CDECL
+notify_format(dbref player, const char *fmt, ...)
+{
+#ifdef HAS_VSNPRINTF
+  char buff[BUFFER_LEN];
+#else
+  char buff[BUFFER_LEN * 3];
+#endif
+  va_list args;
+  va_start(args, fmt);
+#ifdef HAS_VSNPRINTF
+  vsnprintf(buff, sizeof buff, fmt, args);
+#else
+  vsprintf(buff, fmt, args);
+#endif
+  buff[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+  notify(player, buff);
+}
+
+
+/** Notify a player with a formatted string, full version.
+ * This is a safer replacement for notify(player, tprintf(fmt, ...))
+ * \param speaker dbref of object producing the message.
+ * \param func pointer to iterator function to look up each receiver.
+ * \param fdata initial data to pass to func.
+ * \param nsfunc function to call to do NOSPOOF formatting, or NULL.
+ * \param flags flags to pass in (such as NA_INTERACT)
+ * \param fmt format string.
+ */
+void WIN32_CDECL
+notify_anything_format(dbref speaker, na_lookup func,
+                      void *fdata, char *(*nsfunc) (dbref, na_lookup func,
+                                                    void *, int), int flags,
+                      const char *fmt, ...)
+{
+#ifdef HAS_VSNPRINTF
+  char buff[BUFFER_LEN];
+#else
+  char buff[BUFFER_LEN * 3];
+#endif
+  va_list args;
+  va_start(args, fmt);
+#ifdef HAS_VSNPRINTF
+  vsnprintf(buff, sizeof buff, fmt, args);
+#else
+  vsprintf(buff, fmt, args);
+#endif
+  buff[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+  notify_anything(speaker, func, fdata, nsfunc, flags, buff);
+}
+
+
+
+/** Send a message to a list of dbrefs on an attribute on an object.
+ * Be sure we don't send a message to the object itself!
+ * \param speaker message speaker
+ * \param thing object containing attribute with list of dbrefs
+ * \param atr attribute with list of dbrefs
+ * \param msg message to transmit
+ * \param flags bitmask of notification option flags
+ */
+void
+notify_list(dbref speaker, dbref thing, const char *atr, const char *msg,
+           int flags)
+{
+  char *fwdstr, *orig, *curr;
+  char tbuf1[BUFFER_LEN], *bp;
+  dbref fwd;
+  ATTR *a;
+  int nsflags;
+
+  a = atr_get(thing, atr);
+  if (!a)
+    return;
+  orig = safe_atr_value(a);
+  fwdstr = trim_space_sep(orig, ' ');
+
+  bp = tbuf1;
+  nsflags = 0;
+  if (!(flags & NA_NOPREFIX)) {
+    make_prefixstr(thing, msg, tbuf1);
+    if (!(flags & NA_SPOOF)) {
+      if (Nospoof(thing))
+       nsflags |= NA_NOSPOOF;
+      if (Paranoid(thing))
+       nsflags |= NA_PARANOID;
+    }
+  } else {
+    safe_str(msg, tbuf1, &bp);
+    *bp = 0;
+  }
+
+  while ((curr = split_token(&fwdstr, ' ')) != NULL) {
+    if (is_objid(curr)) {
+      fwd = parse_objid(curr);
+      if (GoodObject(fwd) && !IsGarbage(fwd) && (thing != fwd)
+         && (Can_Forward(thing, fwd) || Can_Forward(AL_CREATOR(a),fwd))) {
+       if (IsRoom(fwd)) {
+         notify_anything(speaker, na_loc, &fwd, ns_esnotify,
+                         flags | nsflags | NA_NORELAY, tbuf1);
+       } else {
+         notify_anything(speaker, na_one, &fwd, ns_esnotify,
+                         flags | nsflags | NA_NORELAY, tbuf1);
+       }
+      }
+    }
+  }
+  free((Malloc_t) orig);
+}
+
+/** Safely add a tag into a buffer.
+ * If we support pueblo, this function adds the tag start token,
+ * the tag, and the tag end token. If not, it does nothing.
+ * If we can't fit the tag in, we don't put any of it in.
+ * \param a_tag the html tag to add.
+ * \param buf the buffer to append to.
+ * \param bp pointer to address in buf to insert.
+ * \retval 0, successfully added.
+ * \retval 1, tag wouldn't fit in buffer.
+ */
+int
+safe_tag(char const *a_tag, char *buf, char **bp)
+{
+  int result = 0;
+  char *save = buf;
+
+  if (SUPPORT_PUEBLO) {
+    result = safe_chr(TAG_START, buf, bp);
+    result = safe_str(a_tag, buf, bp);
+    result = safe_chr(TAG_END, buf, bp);
+  }
+  /* If it didn't all fit, rewind. */
+  if (result)
+    *bp = save;
+
+  return result;
+}
+
+/** Safely add a closing tag into a buffer.
+ * If we support pueblo, this function adds the tag start token,
+ * a slash, the tag, and the tag end token. If not, it does nothing.
+ * If we can't fit the tag in, we don't put any of it in.
+ * \param a_tag the html tag to add.
+ * \param buf the buffer to append to.
+ * \param bp pointer to address in buf to insert.
+ * \retval 0, successfully added.
+ * \retval 1, tag wouldn't fit in buffer.
+ */
+int
+safe_tag_cancel(char const *a_tag, char *buf, char **bp)
+{
+  int result = 0;
+  char *save = buf;
+
+  if (SUPPORT_PUEBLO) {
+    result = safe_chr(TAG_START, buf, bp);
+    result = safe_chr('/', buf, bp);
+    result = safe_str(a_tag, buf, bp);
+    result = safe_chr(TAG_END, buf, bp);
+  }
+  /* If it didn't all fit, rewind. */
+  if (result)
+    *bp = save;
+
+  return result;
+}
+
+/** Safely add a tag, some text, and a matching closing tag into a buffer.
+ * If we can't fit the stuff, we don't put any of it in.
+ * \param a_tag the html tag to add.
+ * \param params tag parameters.
+ * \param data the text to wrap the tag around.
+ * \param buf the buffer to append to.
+ * \param bp pointer to address in buf to insert.
+ * \param player the player involved in all this, or NOTHING if internal.
+ * \retval 0, successfully added.
+ * \retval 1, tagged text wouldn't fit in buffer.
+ */
+int
+safe_tag_wrap(char const *a_tag, char const *params, char const *data,
+             char *buf, char **bp, dbref player)
+{
+  int result = 0;
+  char *save = buf;
+
+  if (SUPPORT_PUEBLO) {
+    result = safe_chr(TAG_START, buf, bp);
+    result = safe_str(a_tag, buf, bp);
+    if (params && *params && ok_tag_attribute(player, (char *) params)) {
+      result = safe_chr(' ', buf, bp);
+      result = safe_str(params, buf, bp);
+    }
+    result = safe_chr(TAG_END, buf, bp);
+  }
+  result = safe_str(data, buf, bp);
+  if (SUPPORT_PUEBLO) {
+    result = safe_tag_cancel(a_tag, buf, bp);
+  }
+  /* If it didn't all fit, rewind. */
+  if (result)
+    *bp = save;
+  return result;
+}
+
+/** Wrapper to notify a single player with a message, unconditionally.
+ * \param player player to notify.
+ * \param msg message to send.
+ */
+void
+raw_notify(dbref player, const char *msg)
+{
+  notify_anything(GOD, na_one, &player, NULL, NA_NOLISTEN, msg);
+}
+
+/** Notify all connected players with the given flag(s).
+ * If no flags are given, everyone is notified. If one flag list is given,
+ * all connected players with some flag in that list are notified. 
+ * If two flag lists are given, all connected players with at least one flag
+ * in each list are notified.
+ * \param flag1 first required flag list or NULL
+ * \param flag2 second required flag list or NULL
+ * \param fmt format string for message to notify.
+ */
+void WIN32_CDECL
+flag_broadcast(const char *flag1, const char *flag2, const char *fmt, ...)
+{
+  va_list args;
+  char tbuf1[BUFFER_LEN];
+  DESC *d;
+  int ok;
+
+  va_start(args, fmt);
+#ifdef HAS_VSNPRINTF
+  (void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
+#else
+  (void) vsprintf(tbuf1, fmt, args);
+#endif
+  va_end(args);
+  tbuf1[BUFFER_LEN - 1] = '\0';
+
+  DESC_ITER_CONN(d) {
+    ok = 1;
+    if (flag1)
+      ok = ok && flaglist_check_long("FLAG", GOD, d->player, flag1, 0);
+    if (flag2)
+      ok = ok && flaglist_check_long("FLAG", GOD, d->player, flag2, 0);
+    if (ok) {
+      queue_string_eol(d, tbuf1);
+      process_output(d);
+    }
+  }
+}
+
+
+
+static struct text_block *
+make_text_block(const unsigned char *s, int n)
+{
+  struct text_block *p;
+  p =
+    (struct text_block *) mush_malloc(sizeof(struct text_block), "text_block");
+  if (!p)
+    mush_panic("Out of memory");
+  p->buf =
+    (unsigned char *) mush_malloc(sizeof(unsigned char) * n, "text_block_buff");
+  if (!p->buf)
+    mush_panic("Out of memory");
+
+  memcpy(p->buf, s, n);
+  p->nchars = n;
+  p->start = p->buf;
+  p->nxt = 0;
+  return p;
+}
+
+/** Free a text_block structure.
+ * \param t pointer to text_block to free.
+ */
+void
+free_text_block(struct text_block *t)
+{
+  if (t) {
+    if (t->buf)
+      mush_free((Malloc_t) t->buf, "text_block_buff");
+    mush_free((Malloc_t) t, "text_block");
+  }
+}
+
+/** Add a new chunk of text to a player's output queue.
+ * \param q pointer to text_queue to add the chunk to.
+ * \param b text to add to the queue.
+ * \param n length of text to add.
+ */
+void
+add_to_queue(struct text_queue *q, const unsigned char *b, int n)
+{
+  struct text_block *p;
+
+  if (n == 0)
+    return;
+
+  p = make_text_block(b, n);
+  p->nxt = 0;
+  *q->tail = p;
+  q->tail = &p->nxt;
+}
+
+static int
+flush_queue(struct text_queue *q, int n)
+{
+  struct text_block *p;
+  int really_flushed = 0;
+  n += strlen(flushed_message);
+  while (n > 0 && (p = q->head)) {
+    n -= p->nchars;
+    really_flushed += p->nchars;
+    q->head = p->nxt;
+#ifdef DEBUG
+    do_rawlog(LT_ERR, "free_text_block(0x%x) at 1.", p);
+#endif                         /* DEBUG */
+    free_text_block(p);
+  }
+  p =
+    make_text_block((unsigned char *) flushed_message, strlen(flushed_message));
+  p->nxt = q->head;
+  q->head = p;
+  if (!p->nxt)
+    q->tail = &p->nxt;
+  really_flushed -= p->nchars;
+  return really_flushed;
+}
+
+#ifdef HAS_OPENSSL
+static int
+ssl_flush_queue(struct text_queue *q)
+{
+  struct text_block *p;
+  int n = strlen(flushed_message);
+  /* Remove all text blocks except the first one. */
+  if (q->head) {
+    while ((p = q->head->nxt)) {
+      q->head->nxt = p->nxt;
+#ifdef DEBUG
+      do_rawlog(LT_ERR, "free_text_block(0x%x) at 1.", p);
+#endif                         /* DEBUG */
+      free_text_block(p);
+    }
+  }
+  q->tail = &q->head->nxt;
+  /* Set up the flushed message if we can */
+  if (q->head->nchars + n < MAX_OUTPUT)
+    add_to_queue(q, (unsigned char *) flushed_message, n);
+  /* Return the total size of the message */
+  return q->head->nchars + n;
+}
+#endif
+
+/** Render and add text to the queue associated with a given descriptor.
+ * \param d pointer to descriptor to receive the text.
+ * \param b text to send.
+ * \param n length of b.
+ * \return number of characters added.
+ */
+int
+queue_write(DESC *d, const unsigned char *b, int n)
+{
+  char buff[BUFFER_LEN];
+  struct notify_strings messages[MESSAGE_TYPES];
+  unsigned char *s;
+  PUEBLOBUFF;
+  size_t len;
+
+  if ((n == 2) && (b[0] == '\r') && (b[1] == '\n')) {
+    if ((d->conn_flags & CONN_HTML))
+      queue_newwrite(d, (unsigned char *) "<BR>\n", 5);
+    else
+      queue_newwrite(d, b, 2);
+    return n;
+  }
+  if (n > BUFFER_LEN)
+    n = BUFFER_LEN;
+
+  memcpy(buff, b, n);
+  buff[n] = '\0';
+
+  zero_strings(messages);
+
+  if (d->conn_flags & CONN_HTML) {
+    PUSE;
+    tag_wrap("SAMP", NULL, buff);
+    PEND;
+    s = notify_makestring(pbuff, messages, NA_PUEBLO);
+    len = messages[NA_PUEBLO].len;
+  } else {
+    s = notify_makestring(buff, messages, notify_type(d));
+    len = messages[notify_type(d)].len;
+  }
+  queue_newwrite(d, s, len);
+  feed_snoop(d, (char *) s, 1);
+  free_strings(messages);
+  return n;
+}
+
+/** Add text to the queue associated with a given descriptor.
+ * This is the low-level function that works with already-rendered
+ * text.
+ * \param d pointer to descriptor to receive the text.
+ * \param b text to send.
+ * \param n length of b.
+ * \return number of characters added.
+ */
+int
+queue_newwrite(DESC *d, const unsigned char *b, int n)
+{
+  int space;
+
+#ifdef NT_TCP
+
+  /* queue up an asynchronous write if using Windows NT */
+  if (platform == VER_PLATFORM_WIN32_NT) {
+
+    struct text_block **qp, *cur;
+    DWORD nBytes;
+    BOOL bResult;
+
+    /* don't write if connection dropped */
+    if (d->bConnectionDropped)
+      return n;
+
+    /* add to queue - this makes sure output arrives in the right order */
+    add_to_queue(&d->output, b, n);
+    d->output_size += n;
+
+    /* if we are already writing, exit and wait for IO completion */
+    if (d->bWritePending)
+      return n;
+
+    /* pull head item out of queue - not necessarily the one we just put in */
+    qp = &d->output.head;
+
+    if ((cur = *qp) == NULL)
+      return n;                        /* nothing in queue - rather strange, but oh well. */
+
+
+    /* if buffer too long, write what we can and queue up the rest */
+
+    if (cur->nchars <= sizeof(d->output_buffer)) {
+      nBytes = cur->nchars;
+      memcpy(d->output_buffer, cur->start, nBytes);
+      if (!cur->nxt)
+       d->output.tail = qp;
+      *qp = cur->nxt;
+      free_text_block(cur);
+    }
+    /* end of able to write the lot */
+    else {
+      nBytes = sizeof(d->output_buffer);
+      memcpy(d->output_buffer, cur->start, nBytes);
+      cur->nchars -= nBytes;
+      cur->start += nBytes;
+    }                          /* end of buffer too long */
+
+    d->OutboundOverlapped.Offset = 0;
+    d->OutboundOverlapped.OffsetHigh = 0;
+
+    bResult = WriteFile((HANDLE) d->descriptor,
+                       d->output_buffer, nBytes, NULL, &d->OutboundOverlapped);
+
+    d->bWritePending = FALSE;
+
+    if (!bResult)
+      if (GetLastError() == ERROR_IO_PENDING)
+       d->bWritePending = TRUE;
+      else {
+       d->bConnectionDropped = TRUE;   /* do no more writes */
+       /* post a notification that the descriptor should be shutdown */
+       if (!PostQueuedCompletionStatus
+           (CompletionPort, 0, (DWORD) d, &lpo_shutdown)) {
+         char sMessage[200];
+         DWORD nError = GetLastError();
+         GetErrorMessage(nError, sMessage, sizeof sMessage);
+         printf
+           ("Error %ld (%s) on PostQueuedCompletionStatus in queue_newwrite\n",
+            nError, sMessage);
+         boot_desc(d);
+       }
+      }                                /* end of had write */
+    return n;
+
+  }                            /* end of NT TCP/IP way of doing it */
+#endif
+
+  space = MAX_OUTPUT - d->output_size - n;
+  if (space < SPILLOVER_THRESHOLD) {
+    process_output(d);
+    space = MAX_OUTPUT - d->output_size - n;
+    if (space < 0) {
+#ifdef HAS_OPENSSL
+      if (d->ssl) {
+       /* Now we have a problem, as SSL works in blocks and you can't
+        * just partially flush stuff.
+        */
+       d->output_size = ssl_flush_queue(&d->output);
+      } else
+#endif
+       d->output_size -= flush_queue(&d->output, -space);
+    }
+  }
+  add_to_queue(&d->output, b, n);
+  d->output_size += n;
+  feed_snoop(d, (char *) b, 1);
+  return n;
+}
+
+/** Add an end-of-line to a descriptor's text queue.
+ * \param d pointer to descriptor to send the eol to.
+ * \return number of characters queued.
+ */
+int
+queue_eol(DESC *d)
+{
+  if (SUPPORT_PUEBLO && (d->conn_flags & CONN_HTML))
+    return queue_newwrite(d, (unsigned char *) "<BR>\n", 5);
+  else
+    return queue_newwrite(d, (unsigned char *) "\r\n", 2);
+}
+
+/** Add a string and an end-of-line to a descriptor's text queue.
+ * \param d pointer to descriptor to send to.
+ * \param s string to queue.
+ * \return number of characters queued.
+ */
+int
+queue_string_eol(DESC *d, const char *s)
+{
+  queue_string(d, s);
+  return queue_eol(d);
+}
+
+/** Add a string to a descriptor's text queue.
+ * \param d pointer to descriptor to send to.
+ * \param s string to queue.
+ * \return number of characters queued.
+ */
+int
+queue_string(DESC *d, const char *s)
+{
+  unsigned char *n;
+  enum na_type poutput;
+  struct notify_strings messages[MESSAGE_TYPES];
+  dbref target;
+  int ret;
+
+  zero_strings(messages);
+
+  target = d->player;
+
+  poutput = notify_type(d);
+
+  n = notify_makestring(s, messages, poutput);
+  ret = queue_newwrite(d, n, messages[poutput].len);
+  free_strings(messages);
+  return ret;
+}
+
+
+/** Free all text queues associated with a descriptor.
+ * \param d pointer to descriptor.
+ */
+void
+freeqs(DESC *d)
+{
+  struct text_block *cur, *next;
+  cur = d->output.head;
+  while (cur) {
+    next = cur->nxt;
+#ifdef DEBUG
+    do_rawlog(LT_ERR, "free_text_block(0x%x) at 3.", cur);
+#endif                         /* DEBUG */
+    free_text_block(cur);
+    cur = next;
+  }
+  d->output.head = 0;
+  d->output.tail = &d->output.head;
+
+  cur = d->input.head;
+  while (cur) {
+    next = cur->nxt;
+#ifdef DEBUG
+    do_rawlog(LT_ERR, "free_text_block(0x%x) at 4.", cur);
+#endif                         /* DEBUG */
+    free_text_block(cur);
+    cur = next;
+  }
+  d->input.head = 0;
+  d->input.tail = &d->input.head;
+
+  if (d->raw_input) {
+    mush_free((Malloc_t) d->raw_input, "descriptor_raw_input");
+  }
+  d->raw_input = 0;
+  d->raw_input_at = 0;
+}
+
+/** A notify_anything function for formatting speaker data for NOSPOOF.
+ *  * \param speaker the speaker.
+ *   * \param func unused.
+ *    * \param fdata unused.
+ *     * \param para if 1, format for paranoid nospoof; if 0, normal nospoof.
+ *      * \return formatted string.
+ *       */
+char *
+ns_esnotify(dbref speaker, na_lookup func __attribute__ ((__unused__)),
+           void *fdata __attribute__ ((__unused__)), int para)
+{
+  char *dest, *bp;
+  bp = dest = mush_malloc(BUFFER_LEN, "string");
+
+  if (!GoodObject(speaker))
+    *dest = '\0';
+  else if (para) {
+    if (speaker == Owner(speaker))
+      safe_format(dest, &bp, "[%s(#%d)] ", Name(speaker), speaker);
+    else
+      safe_format(dest, &bp, "[%s(#%d)'s %s(#%d)] ", Name(Owner(speaker)),
+                 Owner(speaker), Name(speaker), speaker);
+  } else
+    safe_format(dest, &bp, "[%s:] ", spname(speaker));
+  *bp = '\0';
+  return dest;
+}
diff --git a/src/parse.c b/src/parse.c
new file mode 100644 (file)
index 0000000..30282fc
--- /dev/null
@@ -0,0 +1,1411 @@
+/**
+ * \file parse.c
+ *
+ * \brief The PennMUSH function/expression parser
+ *
+ * The most important function in this file is process_expression.
+ *
+ */
+
+#include "copyrite.h"
+
+#include "config.h"
+#include <math.h>
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+#include <signal.h>
+#include <errno.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "ansi.h"
+#include "dbdefs.h"
+#include "boolexp.h"
+#include "function.h"
+#include "case.h"
+#include "match.h"
+#include "mushdb.h"
+#include "parse.h"
+#include "attrib.h"
+#include "pcre.h"
+#include "flags.h"
+#include "log.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+extern char *absp[], *obj[], *poss[], *subj[]; /* fundb.c */
+extern int inum, inum_limit;
+extern char *iter_rep[];
+int global_fun_invocations;
+int global_fun_recursions;
+extern int re_subpatterns;
+extern int *re_offsets;
+extern char *re_from;
+extern sig_atomic_t cpu_time_limit_hit;
+extern int cpu_limit_warning_sent;
+extern int iter_break;
+
+/** Structure for storing DEBUG output in a linked list */
+struct debug_info {
+  char *string;                /**< A DEBUG string */
+  Debug_Info *prev;    /**< Previous node in the linked list */
+  Debug_Info *next;    /**< Next node in the linked list */
+};
+
+FUNCTION_PROTO(fun_gfun);
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+/* Common error messages */
+char e_int[] = "#-1 ARGUMENT MUST BE INTEGER";
+char e_ints[] = "#-1 ARGUMENTS MUST BE INTEGERS";
+char e_uint[] = "#-1 ARGUMENT MUST BE POSITIVE INTEGER";
+char e_uints[] = "#-1 ARGUMENTS MUST BE POSITIVE INTEGERS";
+char e_num[] = "#-1 ARGUMENT MUST BE NUMBER";
+char e_nums[] = "#-1 ARGUMENTS MUST BE NUMBERS";
+char e_invoke[] = "#-1 FUNCTION INVOCATION LIMIT EXCEEDED";
+char e_call[] = "#-1 CALL LIMIT EXCEEDED";
+char e_perm[] = "#-1 PERMISSION DENIED";
+char e_atrperm[] = "#-1 NO PERMISSION TO GET ATTRIBUTE";
+char e_match[] = "#-1 NO MATCH";
+char e_notvis[] = "#-1 NO SUCH OBJECT VISIBLE";
+char e_disabled[] = "#-1 FUNCTION DISABLED";
+char e_range[] = "#-1 OUT OF RANGE";
+
+#endif
+
+#if 0
+static void dummy_errors(void);
+
+static void
+dummy_errors()
+{
+  /* Just to make sure the error messages are in the translation
+     tables. */
+  char *temp;
+  temp = T("#-1 ARGUMENT MUST BE INTEGER");
+  temp = T("#-1 ARGUMENTS MUST BE INTEGERS");
+  temp = T("#-1 ARGUMENT MUST BE POSITIVE INTEGER");
+  temp = T("#-1 ARGUMENTS MUST BE POSITIVE INTEGERS");
+  temp = T("#-1 ARGUMENT MUST BE NUMBER");
+  temp = T("#-1 ARGUMENTS MUST BE NUMBERS");
+  temp = T("#-1 FUNCTION INVOCATION LIMIT EXCEEDED");
+  temp = T("#-1 CALL LIMIT EXCEEDED");
+  temp = T("#-1 PERMISSION DENIED");
+  temp = T("#-1 NO PERMISSION TO GET ATTRIBUTE");
+  temp = T("#-1 NO MATCH");
+  temp = T("#-1 NO SUCH OBJECT VISIBLE");
+  temp = T("#-1 FUNCTION DISABLED");
+  temp = T("#-1 OUT OF RANGE");
+}
+
+#endif
+
+/** Given a string, parse out a dbref.
+ * \param str string to parse.
+ * \return dbref contained in the string, or NOTHING if not a valid dbref.
+ */
+dbref
+parse_dbref(char const *str)
+{
+  /* Make sure string is strictly in format "#nnn".
+   * Otherwise, possesives will be fouled up.
+   */
+  char const *p;
+  dbref num;
+
+  if (!str || (*str != NUMBER_TOKEN) || !*(str + 1))
+    return NOTHING;
+  for (p = str + 1; isdigit((unsigned char) *p); p++) {
+  }
+  if (*p)
+    return NOTHING;
+
+  num = atoi(str + 1);
+  if (!GoodObject(num))
+    return NOTHING;
+  return num;
+}
+
+/** Version of parse_dbref() that doesn't do GoodObject checks */
+dbref
+qparse_dbref(const char *s)
+{
+
+  if (!s || (*s != NUMBER_TOKEN) || !*(s + 1))
+    return NOTHING;
+  return parse_integer(s + 1);
+}
+
+
+/** Given a string, parse out an object id or dbref.
+ * \param str string to parse.
+ * \return dbref of object referenced by string, or NOTHING if not a valid
+ * string or not an existing dbref.
+ */
+dbref
+parse_objid(char const *str)
+{
+  char *p;
+  if ((p = strchr(str, ':'))) {
+    char tbuf1[BUFFER_LEN];
+    dbref it;
+    /* A unique id, probably */
+    strncpy(tbuf1, str, (p - str));
+    tbuf1[p - str] = '\0';
+    it = parse_dbref(tbuf1);
+    if (GoodObject(it)) {
+      time_t matchtime;
+      p++;
+      if (!is_strict_integer(p))
+       return NOTHING;
+      matchtime = parse_integer(p);
+      return (CreTime(it) == matchtime) ? it : NOTHING;
+    } else
+      return NOTHING;
+  } else
+    return parse_dbref(str);
+}
+
+
+/** Given a string, parse out a boolean value.
+ * The meaning of boolean is fuzzy. To TinyMUSH, any string that begins with
+ * a non-zero number is true, and everything else is false.
+ * To PennMUSH, negative dbrefs are false, non-negative dbrefs are true,
+ * 0 is false, all other numbers are true, empty or blank strings are false,
+ * and all other strings are true. 
+ * \param str string to parse.
+ * \retval 1 str represents a true value.
+ * \retval 0 str represents a false value.
+ */
+int
+parse_boolean(char const *str)
+{
+  if (TINY_BOOLEANS) {
+    return (atoi(str) ? 1 : 0);
+  } else {
+    /* Turn a string into a boolean value.
+     * All negative dbrefs are false, all non-negative dbrefs are true.
+     * Zero is false, all other numbers are true.
+     * Empty (or space only) strings are false, all other strings are true.
+     */
+    /* Null strings are false */
+    if (!str || !*str)
+      return 0;
+    /* Negative dbrefs are false - actually anything starting #-,
+     * which will also cover our error messages. */
+    if (*str == '#' && *(str + 1) && (*(str + 1) == '-'))
+      return 0;
+    /* Non-zero numbers are true, zero is false */
+    if (is_strict_number(str))
+      return parse_number(str) != 0;   /* avoid rounding problems */
+    /* Skip blanks */
+    while (*str == ' ')
+      str++;
+    /* If there's any non-blanks left, it's true */
+    return *str != '\0';       /* force to 1 or 0 */
+  }
+}
+
+/** Is a string a boolean value?
+ * To TinyMUSH, any integer is a boolean value. To PennMUSH, any
+ * string at all is boolean.
+ * \param str string to check.
+ * \retval 1 string is a valid boolean.
+ * \retval 0 string is not a valid boolean.
+ */
+int
+is_boolean(char const *str)
+{
+  if (TINY_BOOLEANS)
+    return is_integer(str);
+  else
+    return 1;
+}
+
+/** Is a string a dbref?
+ * A dbref is a string starting with a #, optionally followed by a -,
+ * and then followed by at least one digit, and nothing else.
+ * \param str string to check.
+ * \retval 1 string is a dbref.
+ * \retval 0 string is not a dbref.
+ */
+int
+is_dbref(char const *str)
+{
+  if (!str || (*str != NUMBER_TOKEN) || !*(str + 1))
+    return 0;
+  if (*(str + 1) == '-') {
+    str++;
+  }
+  for (str++; isdigit((unsigned char) *str); str++) {
+  }
+  return !*str;
+}
+
+/** Is a string an objid?
+ * An objid is a string starting with a #, optionally followed by a -,
+ * and then followed by at least one digit, then optionally followed 
+ * by a : and at least one digit, and nothing else.
+ * In regex: ^#-?\d+(:\d+)?$
+ * \param str string to check.
+ * \retval 1 string is a dbref.
+ * \retval 0 string is not a dbref.
+ */
+int
+is_objid(char const *str)
+{
+  static pcre *re = NULL;
+  const char *errptr;
+  int erroffset;
+
+  if(!str)
+    return 0;
+  if (!re)
+    re = pcre_compile("^#-?\\d+(?::\\d+)?$", 0, &errptr, &erroffset, NULL);
+  return (pcre_exec(re, NULL, str, strlen(str), 0, 0, NULL, 0) >= 0);
+}
+
+/** Is string an integer?
+ * To TinyMUSH, any string is an integer. To PennMUSH, a string that
+ * passes strtol is an integer, and a blank string is an integer
+ * if NULL_EQ_ZERO is turned on.
+ * \param str string to check.
+ * \retval 1 string is an integer.
+ * \retval 0 string is not an integer.
+ */
+int
+is_integer(char const *str)
+{
+  char *end;
+
+  /* If we're emulating Tiny, anything is an integer */
+  if (TINY_MATH)
+    return 1;
+  if (!str)
+    return 0;
+  while (isspace((unsigned char) *str))
+    str++;
+  if (*str == '\0')
+    return NULL_EQ_ZERO;
+  errno = 0;
+  strtol(str, &end, 10);
+  if (errno == ERANGE || *end != '\0')
+    return 0;
+  return 1;
+}
+
+/** Is string an uinteger?
+ * To TinyMUSH, any string is an uinteger. To PennMUSH, a string that
+ * passes strtoul is an uinteger, and a blank string is an uinteger
+ * if NULL_EQ_ZERO is turned on.
+ * \param str string to check.
+ * \retval 1 string is an uinteger.
+ * \retval 0 string is not an uinteger.
+ */
+int
+is_uinteger(char const *str)
+{
+  char *end;
+
+  /* If we're emulating Tiny, anything is an integer */
+  if (TINY_MATH)
+    return 1;
+  if (!str)
+    return 0;
+  /* strtoul() accepts negative numbers, so we still have to do this check */
+  while (isspace((unsigned char) *str))
+    str++;
+  if (*str == '\0')
+    return NULL_EQ_ZERO;
+  if (!(isdigit((unsigned char) *str) || *str == '+'))
+    return 0;
+  errno = 0;
+  strtoul(str, &end, 10);
+  if (errno == ERANGE || *end != '\0')
+    return 0;
+  return 1;
+}
+
+/** Is string a number by the strict definition?
+ * A strict number is a non-null string that passes strtod.
+ * \param str string to check.
+ * \retval 1 string is a strict number.
+ * \retval 0 string is not a strict number.
+ */
+int
+is_strict_number(char const *str)
+{
+  char *end;
+  NVAL val;
+  if (!str)
+    return 0;
+  errno = 0;
+  val = strtod(str, &end);
+  if (errno == ERANGE || *end != '\0')
+    return 0;
+  return end > str;
+}
+
+/** Is string an integer by the strict definition?
+ * A strict integer is a non-null string that passes strtol.
+ * \param str string to check.
+ * \retval 1 string is a strict integer.
+ * \retval 0 string is not a strict integer.
+ */
+int
+is_strict_integer(char const *str)
+{
+  char *end;
+  int val;
+  if (!str)
+    return 0;
+  errno = 0;
+  val = strtol(str, &end, 10);
+  if (errno == ERANGE || *end != '\0')
+    return 0;
+  return end > str;
+}
+
+/** Is string a number?
+ * To TinyMUSH, any string is a number. To PennMUSH, a strict number is
+ * a number, and a blank string is a number if NULL_EQ_ZERO is turned on.
+ * \param str string to check.
+ * \retval 1 string is a number.
+ * \retval 0 string is not a number.
+ */
+int
+is_number(char const *str)
+{
+  /* If we're emulating Tiny, anything is a number */
+  if (TINY_MATH)
+    return 1;
+  while (isspace((unsigned char) *str))
+    str++;
+  if (*str == '\0')
+    return NULL_EQ_ZERO;
+  return is_strict_number(str);
+}
+
+/* Table of interesting characters for process_expression() */
+extern char active_table[UCHAR_MAX + 1];
+/* Indexes of valid q-regs into the global_eval_context.renv array. -1 is error. */
+extern signed char qreg_indexes[UCHAR_MAX + 1];
+
+#ifdef WIN32
+#pragma warning( disable : 4761)       /* NJG: disable warning re conversion */
+#endif
+
+
+/** Function and other substitution evaluation.
+ * This is the PennMUSH function/expression parser. Big stuff.
+ *
+ * All results are returned in buff, at the point *bp. bp is likely
+ * not equal to buff, so make no assumptions about writing at the
+ * start of the buffer.  *bp must be updated to point at the next
+ * place to be filled (ala safe_str() and safe_chr()).  Be very
+ * careful about not overflowing buff; use of safe_str() and safe_chr()
+ * for all writes into buff is highly recommended.
+ * 
+ * nargs is the count of the number of arguments passed to the function,
+ * and args is an array of pointers to them.  args will have at least
+ * nargs elements, or 10 elements, whichever is greater.  The first ten
+ * elements are initialized to NULL for ease of porting functions from
+ * the old style, but relying on such is considered bad form.
+ * The argument strings are stored in BUFFER_LEN buffers, but reliance
+ * on that size is also considered bad form.  The argument strings may
+ * be modified, but modifying the pointers to the argument strings will
+ * cause crashes. 
+ *
+ * executor corresponds to %!, the object invoking the function.
+ * caller   corresponds to %@, the last object to do a U() or similar.
+ * enactor  corresponds to %#, the object that started the whole mess.
+ * Note that fun_ufun() and similar must swap around these parameters
+ * in calling process_expression(); no checks are made in the parser
+ * itself to maintain these values.
+ *
+ * called_as contains a pointer to the name of the function called
+ * (taken from the function table).  This may be used to distinguish
+ * multiple functions which use the same C function for implementation.
+ *
+ * pe_info holds context information used by the parser.  It should
+ * be passed untouched to process_expression(), if it is called.
+ * pe_info should be treated as a black box; its structure and contents
+ * may change without notice.
+ *
+ * Normally, p_e() returns 0. It returns 1 upon hitting the CPU time limit.
+ *
+ * \param buff buffer to store returns of parsing.
+ * \param bp pointer to pointer into buff marking insert position.
+ * \param str string to parse.
+ * \param executor dbref of the object invoking the function.
+ * \param caller dbref of  the last object to use u()
+ * \param enactor dbref of the enactor.
+ * \param eflags flags to control what is evaluated.
+ * \param tflags flags to control what terminates an expression.
+ * \param pe_info pointer to parser context data.
+ * \retval 0 success.
+ * \retval 1 CPU time limit exceeded.
+ */
+int
+process_expression(char *buff, char **bp, char const **str,
+                  dbref executor, dbref caller, dbref enactor,
+                  int eflags, int tflags, PE_Info * pe_info)
+{
+  int debugging = 0, made_info = 0;
+  char *debugstr = NULL, *sourcestr = NULL;
+  char *realbuff = NULL, *realbp = NULL;
+  int gender = -1;
+  int inum_this;
+  char *startpos = *bp;
+  int had_space = 0;
+  char temp[3];
+  int temp_eflags;
+  int old_iter_limit;
+  int qindex;
+  int e_len;
+  int retval = 0;
+  const char *e_msg;
+  dbref local_ooref;
+
+  if (!buff || !bp || !str || !*str)
+    return 0;
+  if (cpu_time_limit_hit) {
+    if (!cpu_limit_warning_sent) {
+      cpu_limit_warning_sent = 1;
+      /* Can't just put #-1 CPU USAGE EXCEEDED in buff here, because
+       * it might never get displayed.
+       */
+      if (!Quiet(enactor))
+       notify(enactor, T("CPU usage exceeded."));
+      do_rawlog(LT_TRACE,
+               T
+               ("CPU time limit exceeded. enactor=#%d executor=#%d caller=#%d code=%s"),
+               enactor, executor, caller, *str);
+    }
+    return 1;
+  }
+  if (Halted(executor))
+    eflags = PE_NOTHING;
+  if (eflags & PE_COMPRESS_SPACES)
+    while (**str == ' ')
+      (*str)++;
+  if (!*str)
+    return 0;
+
+  if (!pe_info) {
+    old_iter_limit = inum_limit;
+    inum_limit = inum;
+    made_info = 1;
+    pe_info = (PE_Info *) mush_malloc(sizeof(PE_Info),
+                                     "process_expression.pe_info");
+    pe_info->fun_invocations = 0;
+    pe_info->fun_depth = 0;
+    pe_info->nest_depth = 0;
+    pe_info->call_depth = 0;
+    pe_info->debug_strings = NULL;
+  } else {
+    old_iter_limit = -1;
+  }
+
+  /* If we've been asked to evaluate, log the expression if:
+   * (a) the last thing we logged wasn't an expression, and
+   * (b) this expression isn't a substring of the last thing we logged
+   */
+  if ((eflags & PE_EVALUATE) &&
+      ((last_activity_type() != LA_PE) || !strstr(last_activity(), *str))) {
+    log_activity(LA_PE, executor, *str);
+  }
+
+  if (eflags != PE_NOTHING) {
+    if (((*bp) - buff) > (BUFFER_LEN - SBUF_LEN)) {
+      realbuff = buff;
+      realbp = *bp;
+      buff = (char *) mush_malloc(BUFFER_LEN,
+                                 "process_expression.buffer_extension");
+      *bp = buff;
+      startpos = buff;
+    }
+  }
+
+  if (CALL_LIMIT && (pe_info->call_depth++ > CALL_LIMIT)) {
+    e_msg = T(e_call);
+    e_len = strlen(e_msg);
+    if ((buff + e_len > *bp) || strcmp(e_msg, *bp - e_len))
+      safe_str(e_msg, buff, bp);
+    goto exit_sequence;
+  }
+
+
+  if (eflags != PE_NOTHING) {
+    debugging = (Debug(executor) || (eflags & PE_DEBUG))
+      && (Connected(Owner(executor)) || atr_get(executor, "DEBUGFORWARDLIST"));
+    if (debugging) {
+      int j;
+      char *debugp;
+      char const *mark;
+      Debug_Info *node;
+
+      debugstr = (char *) mush_malloc(BUFFER_LEN,
+                                     "process_expression.debug_source");
+      debugp = debugstr;
+      safe_dbref(executor, debugstr, &debugp);
+      safe_chr('!', debugstr, &debugp);
+      for (j = 0; j <= pe_info->nest_depth; j++)
+       safe_chr(' ', debugstr, &debugp);
+      sourcestr = debugp;
+      mark = *str;
+      process_expression(debugstr, &debugp, str,
+                        executor, caller, enactor,
+                        PE_NOTHING, tflags, pe_info);
+      *str = mark;
+      if (eflags & PE_COMPRESS_SPACES)
+       while ((debugp > sourcestr) && (debugp[-1] == ' '))
+         debugp--;
+      *debugp = '\0';
+      node = (Debug_Info *) mush_malloc(sizeof(Debug_Info),
+                                       "process_expression.debug_node");
+      node->string = debugstr;
+      node->prev = pe_info->debug_strings;
+      node->next = NULL;
+      if (node->prev)
+       node->prev->next = node;
+      pe_info->debug_strings = node;
+      pe_info->nest_depth++;
+    }
+  }
+
+  /* Only strip command braces if the first character is a brace. */
+  if (**str != '{')
+    eflags &= ~PE_COMMAND_BRACES;
+
+  for (;;) {
+     if(iter_break > 0) {
+       while(*str && **str)
+         (*str)++;
+       goto exit_sequence;
+     }
+    /* Find the first "interesting" character */
+    {
+      char const *pos;
+      int len, len2;
+      /* Inlined strcspn() equivalent, to save on overhead and portability */
+      pos = *str;
+      while (!active_table[*(unsigned char const *) *str])
+       (*str)++;
+      /* Inlined safe_str(), since the source string
+       * may not be null terminated */
+      len = *str - pos;
+      len2 = BUFFER_LEN - 1 - (*bp - buff);
+      if (len > len2)
+       len = len2;
+      if (len >= 0) {
+       memcpy(*bp, pos, len);
+       *bp += len;
+      }
+    }
+
+    switch (**str) {
+      /* Possible terminators */
+    case '}':
+      if (tflags & PT_BRACE)
+       goto exit_sequence;
+      break;
+    case ']':
+      if (tflags & PT_BRACKET)
+       goto exit_sequence;
+      break;
+    case ')':
+      if (tflags & PT_PAREN)
+       goto exit_sequence;
+      break;
+    case ',':
+      if (tflags & PT_COMMA)
+       goto exit_sequence;
+      break;
+    case ';':
+      if (tflags & PT_SEMI)
+       goto exit_sequence;
+      break;
+    case '=':
+      if (tflags & PT_EQUALS)
+       goto exit_sequence;
+      break;
+    case ' ':
+      if (tflags & PT_SPACE)
+       goto exit_sequence;
+      break;
+    case '\0':
+      goto exit_sequence;
+    }
+
+
+    switch (**str) {
+    case 0x1B:                 /* ANSI escapes. */
+      /* Skip over until the 'm' that matches the end. */
+      for (; *str && **str && **str != 'm'; (*str)++)
+       safe_chr(**str, buff, bp);
+      if (*str && **str) {
+       safe_chr(**str, buff, bp);
+       (*str)++;
+      }
+      break;
+    case '$':                  /* Dollar subs for regedit() */
+      if ((eflags & (PE_DOLLAR | PE_EVALUATE)) != (PE_DOLLAR | PE_EVALUATE)) {
+       safe_chr('$', buff, bp);
+       (*str)++;
+      } else {
+       char obuf[BUFFER_LEN];
+       int p = 0;
+
+       (*str)++;
+       /* Check the first two characters after the $ for a number */
+       if (isdigit((unsigned char) **str)) {
+         p = **str - '0';
+         (*str)++;
+         if (isdigit((unsigned char) **str)) {
+           p *= 10;
+           p += **str - '0';
+           (*str)++;
+         }
+       } else
+         break;
+
+       if (p >= re_subpatterns || re_offsets == NULL || re_from == NULL)
+         break;
+
+       pcre_copy_substring(re_from, re_offsets, re_subpatterns, p, obuf,
+                           BUFFER_LEN);
+       safe_str(obuf, buff, bp);
+      }
+      break;
+    case '%':                  /* Percent substitutions */
+      if (!(eflags & PE_EVALUATE) || (*bp - buff >= BUFFER_LEN - 1)) {
+       /* peak -- % escapes (at least) one character */
+       char savec;
+
+       safe_chr('%', buff, bp);
+       (*str)++;
+       savec = **str;
+       if (!savec)
+         goto exit_sequence;
+       safe_chr(savec, buff, bp);
+       (*str)++;
+       switch (savec) {
+       case 'Q':
+       case 'q':
+       case 'V':
+       case 'v':
+       case 'W':
+       case 'w':
+       case 'X':
+       case 'x':
+         /* These sequences escape two characters */
+         savec = **str;
+         if (!savec)
+           goto exit_sequence;
+         safe_chr(savec, buff, bp);
+         (*str)++;
+       }
+       break;
+      } else {
+       char savec, nextc;
+       char *savepos;
+       ATTR *attrib;
+
+       (*str)++;
+       savec = **str;
+       if (!savec)
+         goto exit_sequence;
+       savepos = *bp;
+       (*str)++;
+
+       switch (savec) {
+       case '%':               /* %% - a real % */
+         safe_chr('%', buff, bp);
+         break;
+       case '!':               /* executor dbref */
+         safe_dbref(executor, buff, bp);
+         break;
+       case '@':               /* caller dbref */
+         safe_dbref(caller, buff, bp);
+         break;
+       case '#':               /* enactor dbref */
+         safe_dbref(enactor, buff, bp);
+         break;
+       case ':':               /* enactor unique id */
+         safe_dbref(enactor, buff, bp);
+         safe_chr(':', buff, bp);
+         safe_integer(CreTime(enactor), buff, bp);
+         break;
+       case '?':               /* function limits */
+         if (pe_info) {
+           safe_integer(pe_info->fun_invocations, buff, bp);
+           safe_chr(' ', buff, bp);
+           safe_integer(pe_info->fun_depth, buff, bp);
+         } else {
+           safe_str("0 0", buff, bp);
+         }
+         break;
+       case '~':               /* enactor accented name */
+         safe_str(accented_name(enactor), buff, bp);
+         break;
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':               /* positional argument */
+         if (global_eval_context.wenv[savec - '0'])
+           safe_str(global_eval_context.wenv[savec - '0'], buff, bp);
+         break;
+       case 'A':
+       case 'a':               /* enactor absolute possessive pronoun */
+         if (gender < 0)
+           gender = get_gender(enactor);
+         safe_str(absp[gender], buff, bp);
+         break;
+       case 'B':
+       case 'b':               /* blank space */
+         safe_chr(' ', buff, bp);
+         break;
+       case 'C':
+       case 'c':               /* command line */
+         safe_str(global_eval_context.ccom, buff, bp);
+         break;
+       case 'I':
+       case 'i':
+         nextc = **str;
+         if (!nextc)
+           goto exit_sequence;
+         (*str)++;
+         if (!isdigit(nextc)) {
+           safe_str(T(e_int), buff, bp);
+           break;
+         }
+         inum_this = nextc - '0';
+         if (inum_this < 0 || inum_this >= inum
+             || (inum - inum_this) <= inum_limit) {
+           safe_str(T("#-1 ARGUMENT OUT OF RANGE"), buff, bp);
+         } else {
+           safe_str(iter_rep[inum - inum_this], buff, bp);
+         }
+         break;
+       case 'U':
+       case 'u':
+         safe_str(global_eval_context.ucom, buff, bp);
+         break;
+       case 'L':
+       case 'l':               /* enactor location dbref */
+         /* The security implications of this have
+          * already been talked to death.  Deal. */
+         safe_dbref(Location(enactor), buff, bp);
+         break;
+       case 'N':
+       case 'n':               /* enactor name */
+         safe_str(Name(enactor), buff, bp);
+         break;
+       case 'O':
+       case 'o':               /* enactor objective pronoun */
+         if (gender < 0)
+           gender = get_gender(enactor);
+         safe_str(obj[gender], buff, bp);
+         break;
+       case 'P':
+       case 'p':               /* enactor possessive pronoun */
+         if (gender < 0)
+           gender = get_gender(enactor);
+         safe_str(poss[gender], buff, bp);
+         break;
+       case 'Q':
+       case 'q':               /* temporary storage */
+         nextc = **str;
+         if (!nextc)
+           goto exit_sequence;
+         (*str)++;
+         if ((qindex = qreg_indexes[(unsigned char) nextc]) == -1)
+           break;
+         if (global_eval_context.renv[qindex])
+           safe_str(global_eval_context.renv[qindex], buff, bp);
+         break;
+       case 'R':
+       case 'r':               /* newline */
+         if (NEWLINE_ONE_CHAR)
+           safe_chr('\n', buff, bp);
+         else
+           safe_str("\r\n", buff, bp);
+         break;
+       case 'S':
+       case 's':               /* enactor subjective pronoun */
+         if (gender < 0)
+           gender = get_gender(enactor);
+         safe_str(subj[gender], buff, bp);
+         break;
+       case 'T':
+       case 't':               /* tab */
+         safe_chr('\t', buff, bp);
+         break;
+       case 'V':
+       case 'v':
+       case 'W':
+       case 'w':
+       case 'X':
+       case 'x':               /* attribute substitution */
+         nextc = **str;
+         if (!nextc)
+           goto exit_sequence;
+         (*str)++;
+         temp[0] = UPCASE(savec);
+         temp[1] = UPCASE(nextc);
+         temp[2] = '\0';
+         attrib = atr_get(executor, temp);
+         if (attrib)
+           safe_str(atr_value(attrib), buff, bp);
+         break;
+                case 'z':
+                case 'Z':
+                  nextc = **str;
+                  (*str)++;
+                  if (!nextc)
+                    {
+                      goto exit_sequence;
+                    }
+                  else
+                    {
+
+                      switch (nextc)
+                        {
+                        case 'h':       /* hilite */
+                          safe_str (ANSI_HILITE, buff, bp);
+                          break;
+                        case 'i':       /* inverse */
+                          safe_str (ANSI_INVERSE, buff, bp);
+                          break;
+                        case 'f':       /* flash */
+                          safe_str (ANSI_BLINK, buff, bp);
+                          break;
+                        case 'u':       /* underscore */
+                          safe_str (ANSI_UNDERSCORE, buff, bp);
+                          break;
+                        case 'n':       /* normal */
+                          safe_str (ANSI_NORMAL, buff, bp);
+                          break;
+                        case 'x':       /* black fg */
+                          safe_str (ANSI_BLACK, buff, bp);
+                          break;
+                        case 'r':       /* red fg */
+                          safe_str (ANSI_RED, buff, bp);
+                          break;
+                        case 'g':       /* green fg */
+                          safe_str (ANSI_GREEN, buff, bp);
+                          break;
+                        case 'y':       /* yellow fg */
+                          safe_str (ANSI_YELLOW, buff, bp);
+                          break;
+                        case 'b':       /* blue fg */
+                          safe_str (ANSI_BLUE, buff, bp);
+                          break;
+                        case 'm':       /* magenta fg */
+                          safe_str (ANSI_MAGENTA, buff, bp);
+                          break;
+                        case 'c':       /* cyan fg */
+                          safe_str (ANSI_CYAN, buff, bp);
+                          break;
+                        case 'w':       /* white fg */
+                          safe_str (ANSI_WHITE, buff, bp);
+                          break;
+                        case 'X':       /* black bg */
+                          safe_str (ANSI_BBLACK, buff, bp);
+                          break;
+                        case 'R':       /* red bg */
+                          safe_str (ANSI_BRED, buff, bp);
+                          break;
+                        case 'G':       /* green bg */
+                          safe_str (ANSI_BGREEN, buff, bp);
+                          break;
+                        case 'Y':       /* yellow bg */
+                          safe_str (ANSI_BYELLOW, buff, bp);
+                          break;
+                        case 'B':       /* blue bg */
+                          safe_str (ANSI_BBLUE, buff, bp);
+                          break;
+                        case 'M':       /* magenta bg */
+                          safe_str (ANSI_BMAGENTA, buff, bp);
+                          break;
+                        case 'C':       /* cyan bg */
+                          safe_str (ANSI_BCYAN, buff, bp);
+                          break;
+                        case 'W':       /* white bg */
+                          safe_str (ANSI_BWHITE, buff, bp);
+                          break;
+                        }
+                      break;
+                    }
+       default:                /* just copy */
+         safe_chr(savec, buff, bp);
+       }
+
+       if (isupper((unsigned char) savec))
+         *savepos = UPCASE(*savepos);
+      }
+      break;
+    case '{':                  /* "{}" parse group; recurse with no function check */
+      if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
+       (*str)++;
+       break;
+      }
+      if (eflags & PE_LITERAL) {
+       safe_chr('{', buff, bp);
+       (*str)++;
+       break;
+      }
+      if (!(eflags & (PE_STRIP_BRACES | PE_COMMAND_BRACES)))
+       safe_chr('{', buff, bp);
+      (*str)++;
+      if (process_expression(buff, bp, str,
+                            executor, caller, enactor,
+                            eflags & PE_COMMAND_BRACES
+                            ? (eflags & ~PE_COMMAND_BRACES)
+                            : (eflags &
+                               ~(PE_STRIP_BRACES | PE_FUNCTION_CHECK)),
+                            PT_BRACE, pe_info)) {
+       retval = 1;
+       break;
+      }
+
+      if (**str == '}') {
+       if (!(eflags & (PE_STRIP_BRACES | PE_COMMAND_BRACES)))
+         safe_chr('}', buff, bp);
+       (*str)++;
+      }
+      /* Only strip one set of braces for commands */
+      eflags &= ~PE_COMMAND_BRACES;
+      break;
+    case '[':                  /* "[]" parse group; recurse with mandatory function check */
+      if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
+       (*str)++;
+       break;
+      }
+      if (eflags & PE_LITERAL) {
+       safe_chr('[', buff, bp);
+       (*str)++;
+       break;
+      }
+      if (!(eflags & PE_EVALUATE)) {
+       safe_chr('[', buff, bp);
+       temp_eflags = eflags & ~PE_STRIP_BRACES;
+      } else
+       temp_eflags = eflags | PE_FUNCTION_CHECK | PE_FUNCTION_MANDATORY;
+      (*str)++;
+      if (process_expression(buff, bp, str,
+                            executor, caller, enactor,
+                            temp_eflags, PT_BRACKET, pe_info)) {
+       retval = 1;
+       break;
+      }
+      if (**str == ']') {
+       if (!(eflags & PE_EVALUATE))
+         safe_chr(']', buff, bp);
+       (*str)++;
+      }
+      break;
+    case '(':                  /* Function call */
+      if (CALL_LIMIT && (pe_info->call_depth > CALL_LIMIT)) {
+       (*str)++;
+       break;
+      }
+      (*str)++;
+      if (!(eflags & PE_EVALUATE) || !(eflags & PE_FUNCTION_CHECK)) {
+       safe_chr('(', buff, bp);
+       if (**str == ' ') {
+         safe_chr(**str, buff, bp);
+         (*str)++;
+       }
+       if (process_expression(buff, bp, str,
+                              executor, caller, enactor,
+                              eflags & ~PE_STRIP_BRACES, PT_PAREN, pe_info))
+         retval = 1;
+       if (**str == ')') {
+         if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
+           safe_chr(' ', buff, bp);
+         safe_chr(')', buff, bp);
+         (*str)++;
+       }
+       break;
+      } else {
+       char *sargs[10];
+       char **fargs;
+       int sarglens[10];
+       int *arglens;
+       int args_alloced;
+       int nfargs;
+       int j;
+       static char name[BUFFER_LEN];
+       char *sp, *tp;
+       FUN *fp;
+       int temp_tflags;
+       int denied;
+
+       fargs = sargs;
+       arglens = sarglens;
+       for (j = 0; j < 10; j++) {
+         fargs[j] = NULL;
+         arglens[j] = 0;
+       }
+       args_alloced = 10;
+       eflags &= ~PE_FUNCTION_CHECK;
+       /* Get the function name */
+       for (sp = startpos, tp = name; sp < *bp; sp++)
+         safe_chr(UPCASE(*sp), name, &tp);
+       *tp = '\0';
+       fp = func_hash_lookup(name);
+       if (!fp) {
+         if (eflags & PE_FUNCTION_MANDATORY) {
+           *bp = startpos;
+           safe_str(T("#-1 FUNCTION ("), buff, bp);
+           safe_str(name, buff, bp);
+           safe_str(") NOT FOUND", buff, bp);
+           if (process_expression(name, &tp, str,
+                                  executor, caller, enactor,
+                                  PE_NOTHING, PT_PAREN, pe_info))
+             retval = 1;
+           if (**str == ')')
+             (*str)++;
+           break;
+         }
+         safe_chr('(', buff, bp);
+         if (**str == ' ') {
+           safe_chr(**str, buff, bp);
+           (*str)++;
+         }
+         if (process_expression(buff, bp, str, executor, caller, enactor,
+                                eflags, PT_PAREN, pe_info)) {
+           retval = 1;
+           break;
+         }
+         if (**str == ')') {
+           if (eflags & PE_COMPRESS_SPACES && (*str)[-1] == ' ')
+             safe_chr(' ', buff, bp);
+           safe_chr(')', buff, bp);
+           (*str)++;
+         }
+         break;
+       }
+       *bp = startpos;
+
+       /* Check for the invocation limit */
+       if ((pe_info->fun_invocations >= FUNCTION_LIMIT) ||
+           (global_fun_invocations >= FUNCTION_LIMIT * 5)) {
+         e_msg = T(e_invoke);
+         e_len = strlen(e_msg);
+         if ((buff + e_len > *bp) || strcmp(e_msg, *bp - e_len))
+           safe_str(e_msg, buff, bp);
+         if (process_expression(name, &tp, str,
+                                executor, caller, enactor,
+                                PE_NOTHING, PT_PAREN, pe_info))
+           retval = 1;
+         if (**str == ')')
+           (*str)++;
+         break;
+       }
+       /* Check for the recursion limit */
+       if ((pe_info->fun_depth + 1 >= RECURSION_LIMIT) ||
+           (global_fun_recursions + 1 >= RECURSION_LIMIT * 5)) {
+         safe_str(T("#-1 FUNCTION RECURSION LIMIT EXCEEDED"), buff, bp);
+         if (process_expression(name, &tp, str,
+                                executor, caller, enactor,
+                                PE_NOTHING, PT_PAREN, pe_info))
+           retval = 1;
+         if (**str == ')')
+           (*str)++;
+         break;
+       }
+       /* Get the arguments */
+       temp_eflags = (eflags & ~PE_FUNCTION_MANDATORY)
+         | PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK;
+       switch (fp->flags & FN_ARG_MASK) {
+       case FN_LITERAL:
+         temp_eflags |= PE_LITERAL;
+         /* FALL THROUGH */
+       case FN_NOPARSE:
+         temp_eflags &= ~(PE_COMPRESS_SPACES | PE_EVALUATE |
+                          PE_FUNCTION_CHECK);
+         break;
+       }
+       denied = !check_func(executor, fp);
+       if (denied)
+         temp_eflags &=
+           ~(PE_COMPRESS_SPACES | PE_EVALUATE | PE_FUNCTION_CHECK);
+       temp_tflags = PT_COMMA | PT_PAREN;
+       nfargs = 0;
+       do {
+         char *argp;
+         if ((fp->maxargs < 0) && ((nfargs + 1) >= -fp->maxargs))
+           temp_tflags = PT_PAREN;
+         if (nfargs >= args_alloced) {
+           char **nargs;
+           int *narglens;
+           nargs = (char **) mush_malloc((nfargs + 10) * sizeof(char *),
+                                         "process_expression.function_arglist");
+           narglens = (int *) mush_malloc((nfargs + 10) * sizeof(int),
+                                          "process_expression.function_arglens");
+           for (j = 0; j < nfargs; j++) {
+             nargs[j] = fargs[j];
+             narglens[j] = arglens[j];
+           }
+           if (fargs != sargs)
+             mush_free((Malloc_t) fargs,
+                       "process_expression.function_arglist");
+           if (arglens != sarglens)
+             mush_free((Malloc_t) arglens,
+                       "process_expression.function_arglens");
+           fargs = nargs;
+           arglens = narglens;
+           args_alloced += 10;
+         }
+         fargs[nfargs] = (char *) mush_malloc(BUFFER_LEN,
+                                              "process_expression.function_argument");
+         argp = fargs[nfargs];
+         if (process_expression(fargs[nfargs], &argp, str,
+                                executor, caller, enactor,
+                                temp_eflags, temp_tflags, pe_info)) {
+           retval = 1;
+           nfargs++;
+           goto free_func_args;
+         }
+         *argp = '\0';
+         arglens[nfargs] = argp - fargs[nfargs];
+         (*str)++;
+         nfargs++;
+       } while ((*str)[-1] == ',');
+       if ((*str)[-1] != ')')
+         (*str)--;
+       /* See if this function is enabled */
+       /* Can't do this check earlier, because of possible side effects
+        * from the functions.  Bah. */
+       if (denied) {
+         if (fp->flags & FN_DISABLED)
+           safe_str(T(e_disabled), buff, bp);
+         else
+           safe_str(T(e_perm), buff, bp);
+         goto free_func_args;
+       } else {
+         /* If we have the right number of args, eval the function.
+          * Otherwise, return an error message.
+          * Special case: zero args is recognized as one null arg.
+          */
+         if ((fp->minargs == 0 || (fp->minargs == 1 && (fp->flags & FN_ONEARG))) && (nfargs == 1) && (!*fargs[0] || arglens[0]== 0)) {
+           mush_free((Malloc_t) fargs[0],
+                     "process_expression.function_argument");
+           fargs[0] = NULL;
+           arglens[0] = 0;
+           nfargs = 0;
+         }
+         if ((nfargs < fp->minargs) || (nfargs > abs(fp->maxargs))) {
+           safe_str(T("#-1 FUNCTION ("), buff, bp);
+           safe_str(fp->name, buff, bp);
+           safe_str(") EXPECTS ", buff, bp);
+           if (fp->minargs == abs(fp->maxargs)) {
+             safe_integer(fp->minargs, buff, bp);
+           } else if ((fp->minargs + 1) == abs(fp->maxargs)) {
+             safe_integer(fp->minargs, buff, bp);
+             safe_str(" OR ", buff, bp);
+             safe_integer(abs(fp->maxargs), buff, bp);
+           } else if (fp->maxargs == INT_MAX) {
+             safe_str("AT LEAST ", buff, bp);
+             safe_integer(fp->minargs, buff, bp);
+           } else {
+             safe_str("BETWEEN ", buff, bp);
+             safe_integer(fp->minargs, buff, bp);
+             safe_str(" AND ", buff, bp);
+             safe_integer(abs(fp->maxargs), buff, bp);
+           }
+           safe_str(" ARGUMENTS BUT GOT ", buff, bp);
+           safe_integer(nfargs, buff, bp);
+         } else {
+           global_fun_recursions++;
+           pe_info->fun_depth++;
+           if (fp->flags & FN_BUILTIN) {
+             global_fun_invocations++;
+             pe_info->fun_invocations++;
+             fp->where.fun(fp, buff, bp, nfargs, fargs, arglens, executor,
+                           caller, enactor, fp->name, pe_info);
+             if (fp->flags & FN_LOGARGS) {
+               char logstr[BUFFER_LEN];
+               char *logp;
+               int logi;
+               logp = logstr;
+               safe_str(fp->name, logstr, &logp);
+               safe_chr('(', logstr, &logp);
+               for (logi = 0; logi < nfargs; logi++) {
+                 safe_str(fargs[logi], logstr, &logp);
+                 if (logi + 1 < nfargs)
+                   safe_chr(',', logstr, &logp);
+               }
+               safe_chr(')', logstr, &logp);
+               *logp = '\0';
+               do_log(LT_CMD, executor, caller, "%s", logstr);
+             } else if (fp->flags & FN_LOGNAME)
+               do_log(LT_CMD, executor, caller, "%s()", fp->name);
+           } else {
+             dbref thing;
+             ATTR *attrib;
+             global_fun_invocations++;
+             pe_info->fun_invocations++;
+             thing = userfn_tab[fp->where.offset].thing;
+             attrib = atr_get(thing, userfn_tab[fp->where.offset].name);
+             if (!attrib) {
+               do_rawlog(LT_ERR,
+                         T("ERROR: @function (%s) without attribute (#%d/%s)"),
+                         fp->name, thing, userfn_tab[fp->where.offset].name);
+               safe_str("#-1 @FUNCTION (", buff, bp);
+               safe_str(fp->name, buff, bp);
+               safe_str(") MISSING ATTRIBUTE (", buff, bp);
+               safe_dbref(thing, buff, bp);
+               safe_chr('/', buff, bp);
+               safe_str(userfn_tab[fp->where.offset].name, buff, bp);
+               safe_chr(')', buff, bp);
+             } else { 
+               /* Open temporary ooref change exception */
+               /* local ooref changing should be safe here. 
+                * Cause we're going to the global func scope, any
+                * previous calling ooref shouldn't effect security here
+                */
+               local_ooref = ooref;
+               ooref = attrib->creator;
+               if(fp->flags & FN_ULOCAL) {
+                 char *preserve[NUMQ];
+                 save_global_regs("globalufun.save", preserve);
+                 do_userfn(buff, bp, thing, attrib, nfargs, fargs,
+                     executor, caller, enactor, pe_info);
+                 restore_global_regs("globalufun.save", preserve);
+               } else {
+                 do_userfn(buff, bp, thing, attrib, nfargs, fargs, executor, caller, enactor, pe_info);
+               }
+               /* Go back to normal ooref  status */
+               ooref = local_ooref;
+             }
+           }
+           pe_info->fun_depth--;
+           global_fun_recursions--;
+         }
+       }
+       /* Free up the space allocated for the args */
+      free_func_args:
+       for (j = 0; j < nfargs; j++)
+         if (fargs[j])
+           mush_free((Malloc_t) fargs[j],
+                     "process_expression.function_argument");
+       if (fargs != sargs)
+         mush_free((Malloc_t) fargs, "process_expression.function_arglist");
+       if (arglens != sarglens)
+         mush_free((Malloc_t) arglens, "process_expression.function_arglens");
+      }
+      break;
+      /* Space compression */
+    case ' ':
+      had_space = 1;
+      safe_chr(' ', buff, bp);
+      (*str)++;
+      if (eflags & PE_COMPRESS_SPACES) {
+       while (**str == ' ')
+         (*str)++;
+      } else
+       while (**str == ' ') {
+         safe_chr(' ', buff, bp);
+         (*str)++;
+       }
+      break;
+      /* Escape charater */
+    case '\\':
+      if (!(eflags & PE_EVALUATE))
+       safe_chr('\\', buff, bp);
+      (*str)++;
+      if (!**str)
+       goto exit_sequence;
+      /* FALL THROUGH */
+      /* Basic character */
+    default:
+      safe_chr(**str, buff, bp);
+      (*str)++;
+      break;
+    }
+  }
+
+exit_sequence:
+  if (eflags != PE_NOTHING) {
+    if ((eflags & PE_COMPRESS_SPACES) && had_space &&
+       ((*str)[-1] == ' ') && ((*bp)[-1] == ' '))
+      (*bp)--;
+    if (debugging) {
+      pe_info->nest_depth--;
+      **bp = '\0';
+      if (strcmp(sourcestr, startpos)) {
+       static char dbuf[BUFFER_LEN];
+       char *dbp;
+       if (pe_info->debug_strings) {
+         while (pe_info->debug_strings->prev)
+           pe_info->debug_strings = pe_info->debug_strings->prev;
+         while (pe_info->debug_strings->next) {
+           dbp = dbuf;
+           dbuf[0] = '\0';
+           safe_format(dbuf, &dbp, "%s :", pe_info->debug_strings->string);
+           *dbp = '\0';
+           if (Connected(Owner(executor)))
+             raw_notify(Owner(executor), dbuf);
+           notify_list(executor, executor, "DEBUGFORWARDLIST", dbuf,
+                       NA_NOLISTEN | NA_NOPREFIX);
+           pe_info->debug_strings = pe_info->debug_strings->next;
+           mush_free((Malloc_t) pe_info->debug_strings->prev,
+                     "process_expression.debug_node");
+         }
+         mush_free((Malloc_t) pe_info->debug_strings,
+                   "process_expression.debug_node");
+         pe_info->debug_strings = NULL;
+       }
+       dbp = dbuf;
+       dbuf[0] = '\0';
+       safe_format(dbuf, &dbp, "%s => %s", debugstr, startpos);
+       *dbp = '\0';
+       if (Connected(Owner(executor)))
+         raw_notify(Owner(executor), dbuf);
+       notify_list(executor, executor, "DEBUGFORWARDLIST", dbuf,
+                   NA_NOLISTEN | NA_NOPREFIX);
+      } else {
+       Debug_Info *node;
+       node = pe_info->debug_strings;
+       if (node) {
+         pe_info->debug_strings = node->prev;
+         if (node->prev)
+           node->prev->next = NULL;
+         mush_free((Malloc_t) node, "process_expression.debug_node");
+       }
+      }
+      mush_free((Malloc_t) debugstr, "process_expression.debug_source");
+    }
+    if (realbuff) {
+      **bp = '\0';
+      *bp = realbp;
+      safe_str(buff, realbuff, bp);
+      mush_free((Malloc_t) buff, "process_expression.buffer_extension");
+    }
+  }
+  /* Once we cross call limit, we stay in error */
+  if (pe_info && CALL_LIMIT && pe_info->call_depth <= CALL_LIMIT)
+    pe_info->call_depth--;
+  if (made_info)
+    mush_free((Malloc_t) pe_info, "process_expression.pe_info");
+  if (old_iter_limit != -1) {
+    inum_limit = old_iter_limit;
+  }
+  return retval;
+}
+
+#ifdef WIN32
+#pragma warning( default : 4761)       /* NJG: enable warning re conversion */
+#endif
diff --git a/src/pcre.c b/src/pcre.c
new file mode 100644 (file)
index 0000000..c5bd811
--- /dev/null
@@ -0,0 +1,7713 @@
+/*************************************************
+*      Perl-Compatible Regular Expressions       *
+*************************************************/
+
+
+/* This is a library of functions to support regular expressions whose syntax
+and semantics are as close as possible to those of the Perl 5 language. See
+the file Tech.Notes for some information on the internals.
+
+Written by: Philip Hazel <ph10@cam.ac.uk>
+
+           Copyright (c) 1997-2003 University of Cambridge
+
+-----------------------------------------------------------------------------
+Permission is granted to anyone to use this software for any purpose on any
+computer system, and to redistribute it freely, subject to the following
+restrictions:
+
+1. This software is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+2. The origin of this software must not be misrepresented, either by
+   explicit claim or by omission.
+
+3. Altered versions must be plainly marked as such, and must not be
+   misrepresented as being the original software.
+
+4. If PCRE is embedded in any software that is released under the GNU
+   General Purpose Licence (GPL), then the terms of that licence shall
+   supersede any condition above with which it is incompatible.
+-----------------------------------------------------------------------------
+*/
+
+/* Modified by Shawn Wagner for PennMUSH to fit in one file and remove
+   things we don't use, like a bunch of API functions and utf-8
+   support. If you want the full thing, see http://www.pcre.org. */
+/* Modified by Alan Schwartz for PennMUSH to change the use of
+ * 'isblank' as a variable (reported to Philip Hazel for pcre 4.5) */
+
+#include "config.h"
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include "pcre.h"
+#include "confmagic.h"
+#undef min
+#undef max
+
+/* Bits of PCRE's conf.h */
+#define NEWLINE '\n'
+#define LINK_SIZE   2
+#define MATCH_LIMIT 100000
+#define NO_RECURSE
+
+/* Bits of internal.h */
+/* This header contains definitions that are shared between the different
+modules, but which are not relevant to the outside. */
+
+#define PCRE_DEFINITION                /* Win32 __declspec(export) trigger for .dll */
+#define EXPORT
+
+/* PCRE keeps offsets in its compiled code as 2-byte quantities by default.
+These are used, for example, to link from the start of a subpattern to its
+alternatives and its end. The use of 2 bytes per offset limits the size of the
+compiled regex to around 64K, which is big enough for almost everybody.
+However, I received a request for an even bigger limit. For this reason, and
+also to make the code easier to maintain, the storing and loading of offsets
+from the byte string is now handled by the macros that are defined here.
+
+The macros are controlled by the value of LINK_SIZE. This defaults to 2 in
+the config.h file, but can be overridden by using -D on the command line. This
+is automated on Unix systems via the "configure" command. */
+
+#define PUT(a,n,d)   \
+  (a[n] = (d) >> 8), \
+  (a[(n)+1] = (d) & 255)
+
+#define GET(a,n) \
+  (((a)[n] << 8) | (a)[(n)+1])
+
+#define MAX_PATTERN_SIZE (1 << 16)
+
+
+/* Convenience macro defined in terms of the others */
+
+#define PUTINC(a,n,d)   PUT(a,n,d), a += LINK_SIZE
+
+
+/* PCRE uses some other 2-byte quantities that do not change when the size of
+offsets changes. There are used for repeat counts and for other things such as
+capturing parenthesis numbers in back references. */
+
+#define PUT2(a,n,d)   \
+  a[n] = (d) >> 8; \
+  a[(n)+1] = (d) & 255
+
+#define GET2(a,n) \
+  (((a)[n] << 8) | (a)[(n)+1])
+
+#define PUT2INC(a,n,d)  PUT2(a,n,d), a += 2
+
+/* These are the public options that can change during matching. */
+
+#define PCRE_IMS (PCRE_CASELESS|PCRE_MULTILINE|PCRE_DOTALL)
+
+/* Private options flags start at the most significant end of the four bytes,
+but skip the top bit so we can use ints for convenience without getting tangled
+with negative values. The public options defined in pcre.h start at the least
+significant end. Make sure they don't overlap, though now that we have expanded
+to four bytes there is plenty of space. */
+
+#define PCRE_FIRSTSET      0x40000000  /* first_byte is set */
+#define PCRE_REQCHSET      0x20000000  /* req_byte is set */
+#define PCRE_STARTLINE     0x10000000  /* start after \n for multiline */
+#define PCRE_ICHANGED      0x08000000  /* i option changes within regex */
+
+/* Options for the "extra" block produced by pcre_study(). */
+
+#define PCRE_STUDY_MAPPED   0x01       /* a map of starting chars exists */
+
+/* Masks for identifying the public options which are permitted at compile
+time, run time or study time, respectively. */
+
+#define PUBLIC_OPTIONS \
+  (PCRE_CASELESS|PCRE_EXTENDED|PCRE_ANCHORED|PCRE_MULTILINE| \
+   PCRE_DOTALL|PCRE_DOLLAR_ENDONLY|PCRE_EXTRA|PCRE_UNGREEDY|PCRE_UTF8| \
+   PCRE_NO_AUTO_CAPTURE|PCRE_NO_UTF8_CHECK)
+
+#define PUBLIC_EXEC_OPTIONS \
+  (PCRE_ANCHORED|PCRE_NOTBOL|PCRE_NOTEOL|PCRE_NOTEMPTY|PCRE_NO_UTF8_CHECK)
+
+#define PUBLIC_STUDY_OPTIONS 0 /* None defined */
+
+/* Magic number to provide a small check against being handed junk. */
+
+#define MAGIC_NUMBER  0x50435245UL     /* 'PCRE' */
+
+/* Negative values for the firstchar and reqchar variables */
+
+#define REQ_UNSET (-2)
+#define REQ_NONE  (-1)
+
+/* Flags added to firstbyte or reqbyte; a "non-literal" item is either a
+variable-length repeat, or a anything other than literal characters. */
+
+#define REQ_CASELESS 0x0100    /* indicates caselessness */
+#define REQ_VARY     0x0200    /* reqbyte followed non-literal item */
+
+/* Miscellaneous definitions */
+
+typedef int BOOL;
+
+#define FALSE   0
+#define TRUE    1
+
+/* Escape items that are just an encoding of a particular data value. Note that
+ESC_n is defined as yet another macro, which is set in config.h to either \n
+(the default) or \r (which some people want). */
+
+#ifndef ESC_e
+#define ESC_e 27
+#endif
+
+#ifndef ESC_f
+#define ESC_f '\f'
+#endif
+
+#ifndef ESC_n
+#define ESC_n NEWLINE
+#endif
+
+#ifndef ESC_r
+#define ESC_r '\r'
+#endif
+
+/* We can't officially use ESC_t because it is a POSIX reserved identifier
+(presumably because of all the others like size_t). */
+
+#ifndef ESC_tee
+#define ESC_tee '\t'
+#endif
+
+/* These are escaped items that aren't just an encoding of a particular data
+value such as \n. They must have non-zero values, as check_escape() returns
+their negation. Also, they must appear in the same order as in the opcode
+definitions below, up to ESC_z. There's a dummy for OP_ANY because it
+corresponds to "." rather than an escape sequence. The final one must be
+ESC_REF as subsequent values are used for \1, \2, \3, etc. There is are two
+tests in the code for an escape greater than ESC_b and less than ESC_Z to
+detect the types that may be repeated. These are the types that consume a
+character. If any new escapes are put in between that don't consume a
+character, that code will have to change. */
+
+enum { ESC_A = 1, ESC_G, ESC_B, ESC_b, ESC_D, ESC_d, ESC_S, ESC_s, ESC_W,
+  ESC_w, ESC_dum1, ESC_C, ESC_Z, ESC_z, ESC_E, ESC_Q, ESC_REF
+};
+
+/* Flag bits and data types for the extended class (OP_XCLASS) for classes that
+contain UTF-8 characters with values greater than 255. */
+
+#define XCL_NOT    0x01                /* Flag: this is a negative class */
+#define XCL_MAP    0x02                /* Flag: a 32-byte map is present */
+
+#define XCL_END       0                /* Marks end of individual items */
+#define XCL_SINGLE    1                /* Single item (one multibyte char) follows */
+#define XCL_RANGE     2                /* A range (two multibyte chars) follows */
+
+
+/* Opcode table: OP_BRA must be last, as all values >= it are used for brackets
+that extract substrings. Starting from 1 (i.e. after OP_END), the values up to
+OP_EOD must correspond in order to the list of escapes immediately above.
+Note that whenever this list is updated, the two macro definitions that follow
+must also be updated to match. */
+
+enum {
+  OP_END,                      /* 0 End of pattern */
+
+  /* Values corresponding to backslashed metacharacters */
+
+  OP_SOD,                      /* 1 Start of data: \A */
+  OP_SOM,                      /* 2 Start of match (subject + offset): \G */
+  OP_NOT_WORD_BOUNDARY,                /*  3 \B */
+  OP_WORD_BOUNDARY,            /*  4 \b */
+  OP_NOT_DIGIT,                        /*  5 \D */
+  OP_DIGIT,                    /*  6 \d */
+  OP_NOT_WHITESPACE,           /*  7 \S */
+  OP_WHITESPACE,               /*  8 \s */
+  OP_NOT_WORDCHAR,             /*  9 \W */
+  OP_WORDCHAR,                 /* 10 \w */
+  OP_ANY,                      /* 11 Match any character */
+  OP_ANYBYTE,                  /* 12 Match any byte (\C); different to OP_ANY for UTF-8 */
+  OP_EODN,                     /* 13 End of data or \n at end of data: \Z. */
+  OP_EOD,                      /* 14 End of data: \z */
+
+  OP_OPT,                      /* 15 Set runtime options */
+  OP_CIRC,                     /* 16 Start of line - varies with multiline switch */
+  OP_DOLL,                     /* 17 End of line - varies with multiline switch */
+  OP_CHARS,                    /* 18 Match string of characters */
+  OP_NOT,                      /* 19 Match anything but the following char */
+
+  OP_STAR,                     /* 20 The maximizing and minimizing versions of */
+  OP_MINSTAR,                  /* 21 all these opcodes must come in pairs, with */
+  OP_PLUS,                     /* 22 the minimizing one second. */
+  OP_MINPLUS,                  /* 23 This first set applies to single characters */
+  OP_QUERY,                    /* 24 */
+  OP_MINQUERY,                 /* 25 */
+  OP_UPTO,                     /* 26 From 0 to n matches */
+  OP_MINUPTO,                  /* 27 */
+  OP_EXACT,                    /* 28 Exactly n matches */
+
+  OP_NOTSTAR,                  /* 29 The maximizing and minimizing versions of */
+  OP_NOTMINSTAR,               /* 30 all these opcodes must come in pairs, with */
+  OP_NOTPLUS,                  /* 31 the minimizing one second. */
+  OP_NOTMINPLUS,               /* 32 This set applies to "not" single characters */
+  OP_NOTQUERY,                 /* 33 */
+  OP_NOTMINQUERY,              /* 34 */
+  OP_NOTUPTO,                  /* 35 From 0 to n matches */
+  OP_NOTMINUPTO,               /* 36 */
+  OP_NOTEXACT,                 /* 37 Exactly n matches */
+
+  OP_TYPESTAR,                 /* 38 The maximizing and minimizing versions of */
+  OP_TYPEMINSTAR,              /* 39 all these opcodes must come in pairs, with */
+  OP_TYPEPLUS,                 /* 40 the minimizing one second. These codes must */
+  OP_TYPEMINPLUS,              /* 41 be in exactly the same order as those above. */
+  OP_TYPEQUERY,                        /* 42 This set applies to character types such as \d */
+  OP_TYPEMINQUERY,             /* 43 */
+  OP_TYPEUPTO,                 /* 44 From 0 to n matches */
+  OP_TYPEMINUPTO,              /* 45 */
+  OP_TYPEEXACT,                        /* 46 Exactly n matches */
+
+  OP_CRSTAR,                   /* 47 The maximizing and minimizing versions of */
+  OP_CRMINSTAR,                        /* 48 all these opcodes must come in pairs, with */
+  OP_CRPLUS,                   /* 49 the minimizing one second. These codes must */
+  OP_CRMINPLUS,                        /* 50 be in exactly the same order as those above. */
+  OP_CRQUERY,                  /* 51 These are for character classes and back refs */
+  OP_CRMINQUERY,               /* 52 */
+  OP_CRRANGE,                  /* 53 These are different to the three seta above. */
+  OP_CRMINRANGE,               /* 54 */
+
+  OP_CLASS,                    /* 55 Match a character class, chars < 256 only */
+  OP_NCLASS,                   /* 56 Same, but the bitmap was created from a negative
+                                  class - the difference is relevant only when a UTF-8
+                                  character > 255 is encountered. */
+
+  OP_XCLASS,                   /* 57 Extended class for handling UTF-8 chars within the
+                                  class. This does both positive and negative. */
+
+  OP_REF,                      /* 58 Match a back reference */
+  OP_RECURSE,                  /* 59 Match a numbered subpattern (possibly recursive) */
+  OP_CALLOUT,                  /* 60 Call out to external function if provided */
+
+  OP_ALT,                      /* 61 Start of alternation */
+  OP_KET,                      /* 62 End of group that doesn't have an unbounded repeat */
+  OP_KETRMAX,                  /* 63 These two must remain together and in this */
+  OP_KETRMIN,                  /* 64 order. They are for groups the repeat for ever. */
+
+  /* The assertions must come before ONCE and COND */
+
+  OP_ASSERT,                   /* 65 Positive lookahead */
+  OP_ASSERT_NOT,               /* 66 Negative lookahead */
+  OP_ASSERTBACK,               /* 67 Positive lookbehind */
+  OP_ASSERTBACK_NOT,           /* 68 Negative lookbehind */
+  OP_REVERSE,                  /* 69 Move pointer back - used in lookbehind assertions */
+
+  /* ONCE and COND must come after the assertions, with ONCE first, as there's
+     a test for >= ONCE for a subpattern that isn't an assertion. */
+
+  OP_ONCE,                     /* 70 Once matched, don't back up into the subpattern */
+  OP_COND,                     /* 71 Conditional group */
+  OP_CREF,                     /* 72 Used to hold an extraction string number (cond ref) */
+
+  OP_BRAZERO,                  /* 73 These two must remain together and in this */
+  OP_BRAMINZERO,               /* 74 order. */
+
+  OP_BRANUMBER,                        /* 75 Used for extracting brackets whose number is greater
+                                  than can fit into an opcode. */
+
+  OP_BRA                       /* 76 This and greater values are used for brackets that
+                                  extract substrings up to a basic limit. After that,
+                                  use is made of OP_BRANUMBER. */
+};
+
+/* WARNING: There is an implicit assumption in study.c that all opcodes are
+less than 128 in value. This makes handling UTF-8 character sequences easier.
+*/
+
+
+/* This macro defines textual names for all the opcodes. There are used only
+for debugging, in pcre.c when DEBUG is defined, and also in pcretest.c. The
+macro is referenced only in printint.c. */
+
+#define OP_NAME_LIST \
+  "End", "\\A", "\\G", "\\B", "\\b", "\\D", "\\d",                \
+  "\\S", "\\s", "\\W", "\\w", "Any", "Anybyte", "\\Z", "\\z",     \
+  "Opt", "^", "$", "chars", "not",                                \
+  "*", "*?", "+", "+?", "?", "??", "{", "{", "{",                 \
+  "*", "*?", "+", "+?", "?", "??", "{", "{", "{",                 \
+  "*", "*?", "+", "+?", "?", "??", "{", "{", "{",                 \
+  "*", "*?", "+", "+?", "?", "??", "{", "{",                      \
+  "class", "nclass", "xclass", "Ref", "Recurse", "Callout",       \
+  "Alt", "Ket", "KetRmax", "KetRmin", "Assert", "Assert not",     \
+  "AssertB", "AssertB not", "Reverse", "Once", "Cond", "Cond ref",\
+  "Brazero", "Braminzero", "Branumber", "Bra"
+
+
+/* This macro defines the length of fixed length operations in the compiled
+regex. The lengths are used when searching for specific things, and also in the
+debugging printing of a compiled regex. We use a macro so that it can be
+incorporated both into pcre.c and pcretest.c without being publicly exposed.
+
+As things have been extended, some of these are no longer fixed lenths, but are
+minima instead. For example, the length of a single-character repeat may vary
+in UTF-8 mode. The code that uses this table must know about such things. */
+
+#define OP_LENGTHS \
+  1,                             /* End                                    */ \
+  1, 1, 1, 1, 1, 1, 1, 1, 1, 1,  /* \A, \G, \B, \B, \D, \d, \S, \s, \W, \w */ \
+  1, 1, 1, 1, 2, 1, 1,           /* Any, Anybyte, \Z, \z, Opt, ^, $        */ \
+  2,                             /* Chars - the minimum length             */ \
+  2,                             /* not                                    */ \
+  /* Positive single-char repeats                            ** These are  */ \
+  2, 2, 2, 2, 2, 2,              /* *, *?, +, +?, ?, ??      ** minima in  */ \
+  4, 4, 4,                       /* upto, minupto, exact     ** UTF-8 mode */ \
+  /* Negative single-char repeats - only for chars < 256                   */ \
+  2, 2, 2, 2, 2, 2,              /* NOT *, *?, +, +?, ?, ??                */ \
+  4, 4, 4,                       /* NOT upto, minupto, exact               */ \
+  /* Positive type repeats                                                 */ \
+  2, 2, 2, 2, 2, 2,              /* Type *, *?, +, +?, ?, ??               */ \
+  4, 4, 4,                       /* Type upto, minupto, exact              */ \
+  /* Character class & ref repeats                                         */ \
+  1, 1, 1, 1, 1, 1,              /* *, *?, +, +?, ?, ??                    */ \
+  5, 5,                          /* CRRANGE, CRMINRANGE                    */ \
+ 33,                             /* CLASS                                  */ \
+ 33,                             /* NCLASS                                 */ \
+  0,                             /* XCLASS - variable length               */ \
+  3,                             /* REF                                    */ \
+  1+LINK_SIZE,                   /* RECURSE                                */ \
+  2,                             /* CALLOUT                                */ \
+  1+LINK_SIZE,                   /* Alt                                    */ \
+  1+LINK_SIZE,                   /* Ket                                    */ \
+  1+LINK_SIZE,                   /* KetRmax                                */ \
+  1+LINK_SIZE,                   /* KetRmin                                */ \
+  1+LINK_SIZE,                   /* Assert                                 */ \
+  1+LINK_SIZE,                   /* Assert not                             */ \
+  1+LINK_SIZE,                   /* Assert behind                          */ \
+  1+LINK_SIZE,                   /* Assert behind not                      */ \
+  1+LINK_SIZE,                   /* Reverse                                */ \
+  1+LINK_SIZE,                   /* Once                                   */ \
+  1+LINK_SIZE,                   /* COND                                   */ \
+  3,                             /* CREF                                   */ \
+  1, 1,                          /* BRAZERO, BRAMINZERO                    */ \
+  3,                             /* BRANUMBER                              */ \
+  1+LINK_SIZE                    /* BRA                                    */ \
+
+
+/* The highest extraction number before we have to start using additional
+bytes. (Originally PCRE didn't have support for extraction counts highter than
+this number.) The value is limited by the number of opcodes left after OP_BRA,
+i.e. 255 - OP_BRA. We actually set it a bit lower to leave room for additional
+opcodes. */
+
+#define EXTRACT_BASIC_MAX  150
+
+/* A magic value for OP_CREF to indicate the "in recursion" condition. */
+
+#define CREF_RECURSE  0xffff
+
+/* The texts of compile-time error messages are defined as macros here so that
+they can be accessed by the POSIX wrapper and converted into error codes.  Yes,
+I could have used error codes in the first place, but didn't feel like changing
+just to accommodate the POSIX wrapper. */
+
+#define ERR1  "\\ at end of pattern"
+#define ERR2  "\\c at end of pattern"
+#define ERR3  "unrecognized character follows \\"
+#define ERR4  "numbers out of order in {} quantifier"
+#define ERR5  "number too big in {} quantifier"
+#define ERR6  "missing terminating ] for character class"
+#define ERR7  "invalid escape sequence in character class"
+#define ERR8  "range out of order in character class"
+#define ERR9  "nothing to repeat"
+#define ERR10 "operand of unlimited repeat could match the empty string"
+#define ERR11 "internal error: unexpected repeat"
+#define ERR12 "unrecognized character after (?"
+#define ERR13 "POSIX named classes are supported only within a class"
+#define ERR14 "missing )"
+#define ERR15 "reference to non-existent subpattern"
+#define ERR16 "erroffset passed as NULL"
+#define ERR17 "unknown option bit(s) set"
+#define ERR18 "missing ) after comment"
+#define ERR19 "parentheses nested too deeply"
+#define ERR20 "regular expression too large"
+#define ERR21 "failed to get memory"
+#define ERR22 "unmatched parentheses"
+#define ERR23 "internal error: code overflow"
+#define ERR24 "unrecognized character after (?<"
+#define ERR25 "lookbehind assertion is not fixed length"
+#define ERR26 "malformed number after (?("
+#define ERR27 "conditional group contains more than two branches"
+#define ERR28 "assertion expected after (?("
+#define ERR29 "(?R or (?digits must be followed by )"
+#define ERR30 "unknown POSIX class name"
+#define ERR31 "POSIX collating elements are not supported"
+#define ERR32 "this version of PCRE is not compiled with PCRE_UTF8 support"
+#define ERR33 "spare error"
+#define ERR34 "character value in \\x{...} sequence is too large"
+#define ERR35 "invalid condition (?(0)"
+#define ERR36 "\\C not allowed in lookbehind assertion"
+#define ERR37 "PCRE does not support \\L, \\l, \\N, \\P, \\p, \\U, \\u, or \\X"
+#define ERR38 "number after (?C is > 255"
+#define ERR39 "closing ) for (?C expected"
+#define ERR40 "recursive call could loop indefinitely"
+#define ERR41 "unrecognized character after (?P"
+#define ERR42 "syntax error after (?P"
+#define ERR43 "two named groups have the same name"
+#define ERR44 "invalid UTF-8 string"
+
+/* All character handling must be done as unsigned characters. Otherwise there
+are problems with top-bit-set characters and functions such as isspace().
+However, we leave the interface to the outside world as char *, because that
+should make things easier for callers. We define a short type for unsigned char
+to save lots of typing. I tried "uchar", but it causes problems on Digital
+Unix, where it is defined in sys/types, so use "uschar" instead. */
+
+typedef unsigned char uschar;
+
+/* The real format of the start of the pcre block; the index of names and the
+code vector run on as long as necessary after the end. */
+
+typedef struct real_pcre {
+  unsigned long int magic_number;
+  size_t size;                 /* Total that was malloced */
+  const unsigned char *tables; /* Pointer to tables */
+  unsigned long int options;
+  unsigned short int top_bracket;
+  unsigned short int top_backref;
+  unsigned short int first_byte;
+  unsigned short int req_byte;
+  unsigned short int name_entry_size;  /* Size of any name items; 0 => none */
+  unsigned short int name_count;       /* Number of name items */
+} real_pcre;
+
+/* The format of the block used to store data from pcre_study(). */
+
+typedef struct pcre_study_data {
+  size_t size;                 /* Total that was malloced */
+  uschar options;
+  uschar start_bits[32];
+} pcre_study_data;
+
+/* Structure for passing "static" information around between the functions
+doing the compiling, so that they are thread-safe. */
+
+typedef struct compile_data {
+  const uschar *lcc;           /* Points to lower casing table */
+  const uschar *fcc;           /* Points to case-flipping table */
+  const uschar *cbits;         /* Points to character type table */
+  const uschar *ctypes;                /* Points to table of type maps */
+  const uschar *start_code;    /* The start of the compiled code */
+  uschar *name_table;          /* The name/number table */
+  int names_found;             /* Number of entries so far */
+  int name_entry_size;         /* Size of each entry */
+  int top_backref;             /* Maximum back reference */
+  unsigned int backref_map;    /* Bitmap of low back refs */
+  int req_varyopt;             /* "After variable item" flag for reqbyte */
+} compile_data;
+
+/* Structure for maintaining a chain of pointers to the currently incomplete
+branches, for testing for left recursion. */
+
+typedef struct branch_chain {
+  struct branch_chain *outer;
+  uschar *current;
+} branch_chain;
+
+/* Structure for items in a linked list that represents an explicit recursive
+call within the pattern. */
+
+typedef struct recursion_info {
+  struct recursion_info *prevrec;      /* Previous recursion record (or NULL) */
+  int group_num;               /* Number of group that was called */
+  const uschar *after_call;    /* "Return value": points after the call in the expr */
+  const uschar *save_start;    /* Old value of md->start_match */
+  int *offset_save;            /* Pointer to start of saved offsets */
+  int saved_max;               /* Number of saved offsets */
+} recursion_info;
+
+/* When compiling in a mode that doesn't use recursive calls to match(),
+a structure is used to remember local variables on the heap. It is defined in
+pcre.c, close to the match() function, so that it is easy to keep it in step
+with any changes of local variable. However, the pointer to the current frame
+must be saved in some "static" place over a longjmp(). We declare the
+structure here so that we can put a pointer in the match_data structure.
+NOTE: This isn't used for a "normal" compilation of pcre. */
+
+struct heapframe;
+
+/* Structure for passing "static" information around between the functions
+doing the matching, so that they are thread-safe. */
+
+typedef struct match_data {
+  unsigned long int match_call_count;  /* As it says */
+  unsigned long int match_limit;       /* As it says */
+  int *offset_vector;          /* Offset vector */
+  int offset_end;              /* One past the end */
+  int offset_max;              /* The maximum usable for return data */
+  const uschar *lcc;           /* Points to lower casing table */
+  const uschar *ctypes;                /* Points to table of type maps */
+  BOOL offset_overflow;                /* Set if too many extractions */
+  BOOL notbol;                 /* NOTBOL flag */
+  BOOL noteol;                 /* NOTEOL flag */
+  BOOL utf8;                   /* UTF8 flag */
+  BOOL endonly;                        /* Dollar not before final \n */
+  BOOL notempty;               /* Empty string match not wanted */
+  const uschar *start_code;    /* For use when recursing */
+  const uschar *start_subject; /* Start of the subject string */
+  const uschar *end_subject;   /* End of the subject string */
+  const uschar *start_match;   /* Start of this match attempt */
+  const uschar *end_match_ptr; /* Subject position at end match */
+  int end_offset_top;          /* Highwater mark at end of match */
+  int capture_last;            /* Most recent capture number */
+  int start_offset;            /* The start offset value */
+  recursion_info *recursive;   /* Linked list of recursion data */
+  void *callout_data;          /* To pass back to callouts */
+  struct heapframe *thisframe; /* Used only when compiling for no recursion */
+} match_data;
+
+/* Bit definitions for entries in the pcre_ctypes table. */
+
+#define ctype_space   0x01
+#define ctype_letter  0x02
+#define ctype_digit   0x04
+#define ctype_xdigit  0x08
+#define ctype_word    0x10     /* alphameric or '_' */
+#define ctype_meta    0x80     /* regexp meta char or zero (end pattern) */
+
+/* Offsets for the bitmap tables in pcre_cbits. Each table contains a set
+of bits for a class map. Some classes are built by combining these tables. */
+
+#define cbit_space     0       /* [:space:] or \s */
+#define cbit_xdigit   32       /* [:xdigit:] */
+#define cbit_digit    64       /* [:digit:] or \d */
+#define cbit_upper    96       /* [:upper:] */
+#define cbit_lower   128       /* [:lower:] */
+#define cbit_word    160       /* [:word:] or \w */
+#define cbit_graph   192       /* [:graph:] */
+#define cbit_print   224       /* [:print:] */
+#define cbit_punct   256       /* [:punct:] */
+#define cbit_cntrl   288       /* [:cntrl:] */
+#define cbit_length  320       /* Length of the cbits table */
+
+/* Offsets of the various tables from the base tables pointer, and
+total length. */
+
+#define lcc_offset      0
+#define fcc_offset    256
+#define cbits_offset  512
+#define ctypes_offset (cbits_offset + cbit_length)
+#define tables_length (ctypes_offset + 256)
+
+/* End of internal.h */
+/* chartables.c */
+/*************************************************
+*      Perl-Compatible Regular Expressions       *
+*************************************************/
+
+/* This file is automatically written by the dftables auxiliary 
+program. If you edit it by hand, you might like to edit the Makefile to 
+prevent its ever being regenerated.
+
+This file is #included in the compilation of pcre.c to build the default
+character tables which are used when no tables are passed to the compile
+function. */
+
+static unsigned char pcre_default_tables[] = {
+
+/* This table is a lower casing table. */
+
+  0, 1, 2, 3, 4, 5, 6, 7,
+  8, 9, 10, 11, 12, 13, 14, 15,
+  16, 17, 18, 19, 20, 21, 22, 23,
+  24, 25, 26, 27, 28, 29, 30, 31,
+  32, 33, 34, 35, 36, 37, 38, 39,
+  40, 41, 42, 43, 44, 45, 46, 47,
+  48, 49, 50, 51, 52, 53, 54, 55,
+  56, 57, 58, 59, 60, 61, 62, 63,
+  64, 97, 98, 99, 100, 101, 102, 103,
+  104, 105, 106, 107, 108, 109, 110, 111,
+  112, 113, 114, 115, 116, 117, 118, 119,
+  120, 121, 122, 91, 92, 93, 94, 95,
+  96, 97, 98, 99, 100, 101, 102, 103,
+  104, 105, 106, 107, 108, 109, 110, 111,
+  112, 113, 114, 115, 116, 117, 118, 119,
+  120, 121, 122, 123, 124, 125, 126, 127,
+  128, 129, 130, 131, 132, 133, 134, 135,
+  136, 137, 138, 139, 140, 141, 142, 143,
+  144, 145, 146, 147, 148, 149, 150, 151,
+  152, 153, 154, 155, 156, 157, 158, 159,
+  160, 161, 162, 163, 164, 165, 166, 167,
+  168, 169, 170, 171, 172, 173, 174, 175,
+  176, 177, 178, 179, 180, 181, 182, 183,
+  184, 185, 186, 187, 188, 189, 190, 191,
+  192, 193, 194, 195, 196, 197, 198, 199,
+  200, 201, 202, 203, 204, 205, 206, 207,
+  208, 209, 210, 211, 212, 213, 214, 215,
+  216, 217, 218, 219, 220, 221, 222, 223,
+  224, 225, 226, 227, 228, 229, 230, 231,
+  232, 233, 234, 235, 236, 237, 238, 239,
+  240, 241, 242, 243, 244, 245, 246, 247,
+  248, 249, 250, 251, 252, 253, 254, 255,
+
+/* This table is a case flipping table. */
+
+  0, 1, 2, 3, 4, 5, 6, 7,
+  8, 9, 10, 11, 12, 13, 14, 15,
+  16, 17, 18, 19, 20, 21, 22, 23,
+  24, 25, 26, 27, 28, 29, 30, 31,
+  32, 33, 34, 35, 36, 37, 38, 39,
+  40, 41, 42, 43, 44, 45, 46, 47,
+  48, 49, 50, 51, 52, 53, 54, 55,
+  56, 57, 58, 59, 60, 61, 62, 63,
+  64, 97, 98, 99, 100, 101, 102, 103,
+  104, 105, 106, 107, 108, 109, 110, 111,
+  112, 113, 114, 115, 116, 117, 118, 119,
+  120, 121, 122, 91, 92, 93, 94, 95,
+  96, 65, 66, 67, 68, 69, 70, 71,
+  72, 73, 74, 75, 76, 77, 78, 79,
+  80, 81, 82, 83, 84, 85, 86, 87,
+  88, 89, 90, 123, 124, 125, 126, 127,
+  128, 129, 130, 131, 132, 133, 134, 135,
+  136, 137, 138, 139, 140, 141, 142, 143,
+  144, 145, 146, 147, 148, 149, 150, 151,
+  152, 153, 154, 155, 156, 157, 158, 159,
+  160, 161, 162, 163, 164, 165, 166, 167,
+  168, 169, 170, 171, 172, 173, 174, 175,
+  176, 177, 178, 179, 180, 181, 182, 183,
+  184, 185, 186, 187, 188, 189, 190, 191,
+  192, 193, 194, 195, 196, 197, 198, 199,
+  200, 201, 202, 203, 204, 205, 206, 207,
+  208, 209, 210, 211, 212, 213, 214, 215,
+  216, 217, 218, 219, 220, 221, 222, 223,
+  224, 225, 226, 227, 228, 229, 230, 231,
+  232, 233, 234, 235, 236, 237, 238, 239,
+  240, 241, 242, 243, 244, 245, 246, 247,
+  248, 249, 250, 251, 252, 253, 254, 255,
+
+/* This table contains bit maps for various character classes.
+Each map is 32 bytes long and the bits run from the least
+significant end of each byte. The classes that have their own
+maps are: space, xdigit, digit, upper, lower, word, graph
+print, punct, and cntrl. Other classes are built from combinations. */
+
+  0x00, 0x3e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03,
+  0x7e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0xfe, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0x07,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x03,
+  0xfe, 0xff, 0xff, 0x87, 0xfe, 0xff, 0xff, 0x07,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0x00, 0xfc,
+  0x01, 0x00, 0x00, 0xf8, 0x01, 0x00, 0x00, 0x78,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+  0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+/* This table identifies various classes of character by individual bits:
+  0x01   white space character
+  0x02   letter
+  0x04   decimal digit
+  0x08   hexadecimal digit
+  0x10   alphanumeric or '_'
+  0x80   regular expression metacharacter or binary zero
+*/
+
+  0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*   0-  7 */
+  0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x00,      /*   8- 15 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  16- 23 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  24- 31 */
+  0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,      /*    - '  */
+  0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x00,      /*  ( - /  */
+  0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,      /*  0 - 7  */
+  0x1c, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,      /*  8 - ?  */
+  0x00, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x12,      /*  @ - G  */
+  0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,      /*  H - O  */
+  0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,      /*  P - W  */
+  0x12, 0x12, 0x12, 0x80, 0x00, 0x00, 0x80, 0x10,      /*  X - _  */
+  0x00, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x12,      /*  ` - g  */
+  0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,      /*  h - o  */
+  0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12,      /*  p - w  */
+  0x12, 0x12, 0x12, 0x80, 0x80, 0x00, 0x00, 0x00,      /*  x -127 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 128-135 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 136-143 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 144-151 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 152-159 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 160-167 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 168-175 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 176-183 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 184-191 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 192-199 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 200-207 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 208-215 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 216-223 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 224-231 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 232-239 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 240-247 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};                             /* 248-255 */
+
+/* End of chartables.c */
+/* get.c */
+/* This module contains some convenience functions for extracting substrings
+from the subject string after a regex match has succeeded. The original idea
+for these functions came from Scott Wimer <scottw@cgibuilder.com>. */
+
+
+/*************************************************
+*      Copy captured string to given buffer      *
+*************************************************/
+
+/* This function copies a single captured substring into a given buffer.
+Note that we use memcpy() rather than strncpy() in case there are binary zeros
+in the string.
+
+Arguments:
+  subject        the subject string that was matched
+  ovector        pointer to the offsets table
+  stringcount    the number of substrings that were captured
+                   (i.e. the yield of the pcre_exec call, unless
+                   that was zero, in which case it should be 1/3
+                   of the offset table size)
+  stringnumber   the number of the required substring
+  buffer         where to put the substring
+  size           the size of the buffer
+
+Returns:         if successful:
+                   the length of the copied string, not including the zero
+                   that is put on the end; can be zero
+                 if not successful:
+                   PCRE_ERROR_NOMEMORY (-6) buffer too small
+                   PCRE_ERROR_NOSUBSTRING (-7) no such captured substring
+*/
+
+int
+pcre_copy_substring(const char *subject, int *ovector, int stringcount,
+                   int stringnumber, char *buffer, int size)
+{
+  int yield;
+  if (stringnumber < 0 || stringnumber >= stringcount)
+    return PCRE_ERROR_NOSUBSTRING;
+  stringnumber *= 2;
+  yield = ovector[stringnumber + 1] - ovector[stringnumber];
+  if (size < yield + 1)
+    return PCRE_ERROR_NOMEMORY;
+  memcpy(buffer, subject + ovector[stringnumber], yield);
+  buffer[yield] = 0;
+  return yield;
+}
+
+
+
+/* End of get.c */
+/* maketables.c */
+/*************************************************
+*           Create PCRE character tables         *
+*************************************************/
+
+/* This function builds a set of character tables for use by PCRE and returns
+a pointer to them. They are build using the ctype functions, and consequently
+their contents will depend upon the current locale setting. When compiled as
+part of the library, the store is obtained via pcre_malloc(), but when compiled
+inside dftables, use malloc().
+
+Arguments:   none
+Returns:     pointer to the contiguous block of data
+*/
+
+const unsigned char *
+pcre_maketables(void)
+{
+  unsigned char *yield, *p;
+  int i;
+
+#ifndef DFTABLES
+  yield = (unsigned char *) malloc(tables_length);
+#else
+  yield = (unsigned char *) malloc(tables_length);
+#endif
+
+  if (yield == NULL)
+    return NULL;
+  p = yield;
+
+/* First comes the lower casing table */
+
+  for (i = 0; i < 256; i++)
+    *p++ = tolower(i);
+
+/* Next the case-flipping table */
+
+  for (i = 0; i < 256; i++)
+    *p++ = islower(i) ? toupper(i) : tolower(i);
+
+/* Then the character class tables. Don't try to be clever and save effort
+on exclusive ones - in some locales things may be different. Note that the
+table for "space" includes everything "isspace" gives, including VT in the
+default locale. This makes it work for the POSIX class [:space:]. */
+
+  memset(p, 0, cbit_length);
+  for (i = 0; i < 256; i++) {
+    if (isdigit(i)) {
+      p[cbit_digit + i / 8] |= 1 << (i & 7);
+      p[cbit_word + i / 8] |= 1 << (i & 7);
+    }
+    if (isupper(i)) {
+      p[cbit_upper + i / 8] |= 1 << (i & 7);
+      p[cbit_word + i / 8] |= 1 << (i & 7);
+    }
+    if (islower(i)) {
+      p[cbit_lower + i / 8] |= 1 << (i & 7);
+      p[cbit_word + i / 8] |= 1 << (i & 7);
+    }
+    if (i == '_')
+      p[cbit_word + i / 8] |= 1 << (i & 7);
+    if (isspace(i))
+      p[cbit_space + i / 8] |= 1 << (i & 7);
+    if (isxdigit(i))
+      p[cbit_xdigit + i / 8] |= 1 << (i & 7);
+    if (isgraph(i))
+      p[cbit_graph + i / 8] |= 1 << (i & 7);
+    if (isprint(i))
+      p[cbit_print + i / 8] |= 1 << (i & 7);
+    if (ispunct(i))
+      p[cbit_punct + i / 8] |= 1 << (i & 7);
+    if (iscntrl(i))
+      p[cbit_cntrl + i / 8] |= 1 << (i & 7);
+  }
+  p += cbit_length;
+
+/* Finally, the character type table. In this, we exclude VT from the white
+space chars, because Perl doesn't recognize it as such for \s and for comments
+within regexes. */
+
+  for (i = 0; i < 256; i++) {
+    int x = 0;
+    if (i != 0x0b && isspace(i))
+      x += ctype_space;
+    if (isalpha(i))
+      x += ctype_letter;
+    if (isdigit(i))
+      x += ctype_digit;
+    if (isxdigit(i))
+      x += ctype_xdigit;
+    if (isalnum(i) || i == '_')
+      x += ctype_word;
+
+    /* Note: strchr includes the terminating zero in the characters it considers.
+       In this instance, that is ok because we want binary zero to be flagged as a
+       meta-character, which in this sense is any character that terminates a run
+       of data characters. */
+
+    if (strchr("*+?{^.$|()[", i) != 0)
+      x += ctype_meta;
+    *p++ = x;
+  }
+
+  return yield;
+}
+
+/* End of maketables.c */
+/* study.c */
+/*************************************************
+*      Set a bit and maybe its alternate case    *
+*************************************************/
+
+/* Given a character, set its bit in the table, and also the bit for the other
+version of a letter if we are caseless.
+
+Arguments:
+  start_bits    points to the bit map
+  c             is the character
+  caseless      the caseless flag
+  cd            the block with char table pointers
+
+Returns:        nothing
+*/
+
+static void
+set_bit(uschar * start_bits, int c, BOOL caseless, compile_data * cd)
+{
+  start_bits[c / 8] |= (1 << (c & 7));
+  if (caseless && (cd->ctypes[c] & ctype_letter) != 0)
+    start_bits[cd->fcc[c] / 8] |= (1 << (cd->fcc[c] & 7));
+}
+
+
+
+/*************************************************
+*          Create bitmap of starting chars       *
+*************************************************/
+
+/* This function scans a compiled unanchored expression and attempts to build a
+bitmap of the set of initial characters. If it can't, it returns FALSE. As time
+goes by, we may be able to get more clever at doing this.
+
+Arguments:
+  code         points to an expression
+  start_bits   points to a 32-byte table, initialized to 0
+  caseless     the current state of the caseless flag
+  utf8         TRUE if in UTF-8 mode
+  cd           the block with char table pointers
+
+Returns:       TRUE if table built, FALSE otherwise
+*/
+
+static BOOL
+set_start_bits(const uschar * code, uschar * start_bits, BOOL caseless,
+              BOOL utf8, compile_data * cd)
+{
+  register int c;
+
+/* This next statement and the later reference to dummy are here in order to
+trick the optimizer of the IBM C compiler for OS/2 into generating correct
+code. Apparently IBM isn't going to fix the problem, and we would rather not
+disable optimization (in this module it actually makes a big difference, and
+the pcre module can use all the optimization it can get). */
+
+  volatile int dummy;
+
+  do {
+    const uschar *tcode = code + 1 + LINK_SIZE;
+    BOOL try_next = TRUE;
+
+    while (try_next) {
+      /* If a branch starts with a bracket or a positive lookahead assertion,
+         recurse to set bits from within them. That's all for this branch. */
+
+      if ((int) *tcode >= OP_BRA || *tcode == OP_ASSERT) {
+       if (!set_start_bits(tcode, start_bits, caseless, utf8, cd))
+         return FALSE;
+       try_next = FALSE;
+      }
+
+      else
+       switch (*tcode) {
+       default:
+         return FALSE;
+
+         /* Skip over callout */
+
+       case OP_CALLOUT:
+         tcode += 2;
+         break;
+
+         /* Skip over extended extraction bracket number */
+
+       case OP_BRANUMBER:
+         tcode += 3;
+         break;
+
+         /* Skip over lookbehind and negative lookahead assertions */
+
+       case OP_ASSERT_NOT:
+       case OP_ASSERTBACK:
+       case OP_ASSERTBACK_NOT:
+         do
+           tcode += GET(tcode, 1);
+         while (*tcode == OP_ALT);
+         tcode += 1 + LINK_SIZE;
+         break;
+
+         /* Skip over an option setting, changing the caseless flag */
+
+       case OP_OPT:
+         caseless = (tcode[1] & PCRE_CASELESS) != 0;
+         tcode += 2;
+         break;
+
+         /* BRAZERO does the bracket, but carries on. */
+
+       case OP_BRAZERO:
+       case OP_BRAMINZERO:
+         if (!set_start_bits(++tcode, start_bits, caseless, utf8, cd))
+           return FALSE;
+         dummy = 1;
+         do
+           tcode += GET(tcode, 1);
+         while (*tcode == OP_ALT);
+         tcode += 1 + LINK_SIZE;
+         break;
+
+         /* Single-char * or ? sets the bit and tries the next item */
+
+       case OP_STAR:
+       case OP_MINSTAR:
+       case OP_QUERY:
+       case OP_MINQUERY:
+         set_bit(start_bits, tcode[1], caseless, cd);
+         tcode += 2;
+         break;
+
+         /* Single-char upto sets the bit and tries the next */
+
+       case OP_UPTO:
+       case OP_MINUPTO:
+         set_bit(start_bits, tcode[3], caseless, cd);
+         tcode += 4;
+         break;
+
+         /* At least one single char sets the bit and stops */
+
+       case OP_EXACT:          /* Fall through */
+         tcode++;
+
+       case OP_CHARS:          /* Fall through */
+         tcode++;
+
+       case OP_PLUS:
+       case OP_MINPLUS:
+         set_bit(start_bits, tcode[1], caseless, cd);
+         try_next = FALSE;
+         break;
+
+         /* Single character type sets the bits and stops */
+
+       case OP_NOT_DIGIT:
+         for (c = 0; c < 32; c++)
+           start_bits[c] |= ~cd->cbits[c + cbit_digit];
+         try_next = FALSE;
+         break;
+
+       case OP_DIGIT:
+         for (c = 0; c < 32; c++)
+           start_bits[c] |= cd->cbits[c + cbit_digit];
+         try_next = FALSE;
+         break;
+
+       case OP_NOT_WHITESPACE:
+         for (c = 0; c < 32; c++)
+           start_bits[c] |= ~cd->cbits[c + cbit_space];
+         try_next = FALSE;
+         break;
+
+       case OP_WHITESPACE:
+         for (c = 0; c < 32; c++)
+           start_bits[c] |= cd->cbits[c + cbit_space];
+         try_next = FALSE;
+         break;
+
+       case OP_NOT_WORDCHAR:
+         for (c = 0; c < 32; c++)
+           start_bits[c] |= ~cd->cbits[c + cbit_word];
+         try_next = FALSE;
+         break;
+
+       case OP_WORDCHAR:
+         for (c = 0; c < 32; c++)
+           start_bits[c] |= cd->cbits[c + cbit_word];
+         try_next = FALSE;
+         break;
+
+         /* One or more character type fudges the pointer and restarts, knowing
+            it will hit a single character type and stop there. */
+
+       case OP_TYPEPLUS:
+       case OP_TYPEMINPLUS:
+         tcode++;
+         break;
+
+       case OP_TYPEEXACT:
+         tcode += 3;
+         break;
+
+         /* Zero or more repeats of character types set the bits and then
+            try again. */
+
+       case OP_TYPEUPTO:
+       case OP_TYPEMINUPTO:
+         tcode += 2;           /* Fall through */
+
+       case OP_TYPESTAR:
+       case OP_TYPEMINSTAR:
+       case OP_TYPEQUERY:
+       case OP_TYPEMINQUERY:
+         switch (tcode[1]) {
+         case OP_ANY:
+           return FALSE;
+
+         case OP_NOT_DIGIT:
+           for (c = 0; c < 32; c++)
+             start_bits[c] |= ~cd->cbits[c + cbit_digit];
+           break;
+
+         case OP_DIGIT:
+           for (c = 0; c < 32; c++)
+             start_bits[c] |= cd->cbits[c + cbit_digit];
+           break;
+
+         case OP_NOT_WHITESPACE:
+           for (c = 0; c < 32; c++)
+             start_bits[c] |= ~cd->cbits[c + cbit_space];
+           break;
+
+         case OP_WHITESPACE:
+           for (c = 0; c < 32; c++)
+             start_bits[c] |= cd->cbits[c + cbit_space];
+           break;
+
+         case OP_NOT_WORDCHAR:
+           for (c = 0; c < 32; c++)
+             start_bits[c] |= ~cd->cbits[c + cbit_word];
+           break;
+
+         case OP_WORDCHAR:
+           for (c = 0; c < 32; c++)
+             start_bits[c] |= cd->cbits[c + cbit_word];
+           break;
+         }
+
+         tcode += 2;
+         break;
+
+         /* Character class where all the information is in a bit map: set the
+            bits and either carry on or not, according to the repeat count. If it was
+            a negative class, and we are operating with UTF-8 characters, any byte
+            with a value >= 0xc4 is a potentially valid starter because it starts a
+            character with a value > 255. */
+
+       case OP_NCLASS:
+         if (utf8) {
+           start_bits[24] |= 0xf0;     /* Bits for 0xc4 - 0xc8 */
+           memset(start_bits + 25, 0xff, 7);   /* Bits for 0xc9 - 0xff */
+         }
+         /* Fall through */
+
+       case OP_CLASS:
+         {
+           tcode++;
+
+           /* In UTF-8 mode, the bits in a bit map correspond to character
+              values, not to byte values. However, the bit map we are constructing is
+              for byte values. So we have to do a conversion for characters whose
+              value is > 127. In fact, there are only two possible starting bytes for
+              characters in the range 128 - 255. */
+
+           if (utf8) {
+             for (c = 0; c < 16; c++)
+               start_bits[c] |= tcode[c];
+             for (c = 128; c < 256; c++) {
+               if ((tcode[c / 8] && (1 << (c & 7))) != 0) {
+                 int d = (c >> 6) | 0xc0;      /* Set bit for this starter */
+                 start_bits[d / 8] |= (1 << (d & 7));  /* and then skip on to the */
+                 c = (c & 0xc0) + 0x40 - 1;    /* next relevant character. */
+               }
+             }
+           }
+
+           /* In non-UTF-8 mode, the two bit maps are completely compatible. */
+
+           else {
+             for (c = 0; c < 32; c++)
+               start_bits[c] |= tcode[c];
+           }
+
+           /* Advance past the bit map, and act on what follows */
+
+           tcode += 32;
+           switch (*tcode) {
+           case OP_CRSTAR:
+           case OP_CRMINSTAR:
+           case OP_CRQUERY:
+           case OP_CRMINQUERY:
+             tcode++;
+             break;
+
+           case OP_CRRANGE:
+           case OP_CRMINRANGE:
+             if (((tcode[1] << 8) + tcode[2]) == 0)
+               tcode += 5;
+             else
+               try_next = FALSE;
+             break;
+
+           default:
+             try_next = FALSE;
+             break;
+           }
+         }
+         break;                /* End of bitmap class handling */
+
+       }                       /* End of switch */
+    }                          /* End of try_next loop */
+
+    code += GET(code, 1);      /* Advance to next branch */
+  }
+  while (*code == OP_ALT);
+  return TRUE;
+}
+
+
+
+/*************************************************
+*          Study a compiled expression           *
+*************************************************/
+
+/* This function is handed a compiled expression that it must study to produce
+information that will speed up the matching. It returns a pcre_extra block
+which then gets handed back to pcre_exec().
+
+Arguments:
+  re        points to the compiled expression
+  options   contains option bits
+  errorptr  points to where to place error messages;
+            set NULL unless error
+
+Returns:    pointer to a pcre_extra block, with study_data filled in and the
+              appropriate flag set;
+            NULL on error or if no optimization possible
+*/
+
+EXPORT pcre_extra *
+pcre_study(const pcre * external_re, int options, const char **errorptr)
+{
+  uschar start_bits[32];
+  pcre_extra *extra;
+  pcre_study_data *study;
+  const real_pcre *re = (const real_pcre *) external_re;
+  uschar *code = (uschar *) re + sizeof(real_pcre) +
+    (re->name_count * re->name_entry_size);
+  compile_data compile_block;
+
+  *errorptr = NULL;
+
+  if (re == NULL || re->magic_number != MAGIC_NUMBER) {
+    *errorptr = "argument is not a compiled regular expression";
+    return NULL;
+  }
+
+  if ((options & ~PUBLIC_STUDY_OPTIONS) != 0) {
+    *errorptr = "unknown or incorrect option bit(s) set";
+    return NULL;
+  }
+
+/* For an anchored pattern, or an unanchored pattern that has a first char, or
+a multiline pattern that matches only at "line starts", no further processing
+at present. */
+
+  if ((re->options & (PCRE_ANCHORED | PCRE_FIRSTSET | PCRE_STARTLINE)) != 0)
+    return NULL;
+
+/* Set the character tables in the block which is passed around */
+
+  compile_block.lcc = re->tables + lcc_offset;
+  compile_block.fcc = re->tables + fcc_offset;
+  compile_block.cbits = re->tables + cbits_offset;
+  compile_block.ctypes = re->tables + ctypes_offset;
+
+/* See if we can find a fixed set of initial characters for the pattern. */
+
+  memset(start_bits, 0, 32 * sizeof(uschar));
+  if (!set_start_bits(code, start_bits, (re->options & PCRE_CASELESS) != 0,
+                     (re->options & PCRE_UTF8) != 0, &compile_block))
+    return NULL;
+
+/* Get a pcre_extra block and a pcre_study_data block. The study data is put in
+the latter, which is pointed to by the former, which may also get additional
+data set later by the calling program. At the moment, the size of
+pcre_study_data is fixed. We nevertheless save it in a field for returning via
+the pcre_fullinfo() function so that if it becomes variable in the future, we
+don't have to change that code. */
+
+  extra = (pcre_extra *) malloc(sizeof(pcre_extra) + sizeof(pcre_study_data));
+
+  if (extra == NULL) {
+    *errorptr = "failed to get memory";
+    return NULL;
+  }
+
+  study = (pcre_study_data *) ((char *) extra + sizeof(pcre_extra));
+  extra->flags = PCRE_EXTRA_STUDY_DATA;
+  extra->study_data = study;
+
+  study->size = sizeof(pcre_study_data);
+  study->options = PCRE_STUDY_MAPPED;
+  memcpy(study->start_bits, start_bits, sizeof(start_bits));
+
+  return extra;
+}
+
+/* End of study.c */
+/* pcre.c */
+#define DPRINTF(p)             /*nothing */
+
+/* Maximum number of items on the nested bracket stacks at compile time. This
+applies to the nesting of all kinds of parentheses. It does not limit
+un-nested, non-capturing parentheses. This number can be made bigger if
+necessary - it is used to dimension one int and one unsigned char vector at
+compile time. */
+
+#define BRASTACK_SIZE 200
+
+
+/* Maximum number of ints of offset to save on the stack for recursive calls.
+If the offset vector is bigger, malloc is used. This should be a multiple of 3,
+because the offset vector is always a multiple of 3 long. */
+
+#define REC_STACK_SAVE_MAX 30
+
+
+/* The number of bytes in a literal character string above which we can't add
+any more is set at 250 in order to allow for UTF-8 characters. (In theory it
+could be 255 when UTF-8 support is excluded, but that means that some of the
+test output would be different, which just complicates things.) */
+
+#define MAXLIT 250
+
+
+/* The maximum remaining length of subject we are prepared to search for a
+req_byte match. */
+
+#define REQ_BYTE_MAX 1000
+
+
+/* Table of sizes for the fixed-length opcodes. It's defined in a macro so that
+the definition is next to the definition of the opcodes in internal.h. */
+
+static const uschar OP_lengths[] = { OP_LENGTHS };
+
+/* Min and max values for the common repeats; for the maxima, 0 => infinity */
+
+static const char rep_min[] = { 0, 0, 1, 1, 0, 0 };
+static const char rep_max[] = { 0, 0, 0, 0, 1, 1 };
+
+/* Table for handling escaped characters in the range '0'-'z'. Positive returns
+are simple data values; negative values are for special things like \d and so
+on. Zero means further processing is needed (for things like \x), or the escape
+is invalid. */
+
+static const short int escapes[] = {
+  0, 0, 0, 0, 0, 0, 0, 0,      /* 0 - 7 */
+  0, 0, ':', ';', '<', '=', '>', '?',  /* 8 - ? */
+  '@', -ESC_A, -ESC_B, -ESC_C, -ESC_D, -ESC_E, 0, -ESC_G,      /* @ - G */
+  0, 0, 0, 0, 0, 0, 0, 0,      /* H - O */
+  0, -ESC_Q, 0, -ESC_S, 0, 0, 0, -ESC_W,       /* P - W */
+  0, 0, -ESC_Z, '[', '\\', ']', '^', '_',      /* X - _ */
+  '`', 7, -ESC_b, 0, -ESC_d, ESC_e, ESC_f, 0,  /* ` - g */
+  0, 0, 0, 0, 0, 0, ESC_n, 0,  /* h - o */
+  0, 0, ESC_r, -ESC_s, ESC_tee, 0, 0, -ESC_w,  /* p - w */
+  0, 0, -ESC_z                 /* x - z */
+};
+
+
+
+/* Tables of names of POSIX character classes and their lengths. The list is
+terminated by a zero length entry. The first three must be alpha, upper, lower,
+as this is assumed for handling case independence. */
+
+static const char *const posix_names[] = {
+  "alpha", "lower", "upper",
+  "alnum", "ascii", "blank", "cntrl", "digit", "graph",
+  "print", "punct", "space", "word", "xdigit"
+};
+
+static const uschar posix_name_lengths[] = {
+  5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 6, 0
+};
+
+/* Table of class bit maps for each POSIX class; up to three may be combined
+to form the class. The table for [:blank:] is dynamically modified to remove
+the vertical space characters. */
+
+static const int posix_class_maps[] = {
+  cbit_lower, cbit_upper, -1,  /* alpha */
+  cbit_lower, -1, -1,          /* lower */
+  cbit_upper, -1, -1,          /* upper */
+  cbit_digit, cbit_lower, cbit_upper,  /* alnum */
+  cbit_print, cbit_cntrl, -1,  /* ascii */
+  cbit_space, -1, -1,          /* blank - a GNU extension */
+  cbit_cntrl, -1, -1,          /* cntrl */
+  cbit_digit, -1, -1,          /* digit */
+  cbit_graph, -1, -1,          /* graph */
+  cbit_print, -1, -1,          /* print */
+  cbit_punct, -1, -1,          /* punct */
+  cbit_space, -1, -1,          /* space */
+  cbit_word, -1, -1,           /* word - a Perl extension */
+  cbit_xdigit, -1, -1          /* xdigit */
+};
+
+/* Table to identify digits and hex digits. This is used when compiling
+patterns. Note that the tables in chartables are dependent on the locale, and
+may mark arbitrary characters as digits - but the PCRE compiling code expects
+to handle only 0-9, a-z, and A-Z as digits when compiling. That is why we have
+a private table here. It costs 256 bytes, but it is a lot faster than doing
+character value tests (at least in some simple cases I timed), and in some
+applications one wants PCRE to compile efficiently as well as match
+efficiently.
+
+For convenience, we use the same bit definitions as in chartables:
+
+  0x04   decimal digit
+  0x08   hexadecimal digit
+
+Then we can use ctype_digit and ctype_xdigit in the code. */
+
+static const unsigned char digitab[] = {
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*   0-  7 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*   8- 15 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  16- 23 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  24- 31 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*    - '  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  ( - /  */
+  0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,      /*  0 - 7  */
+  0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  8 - ?  */
+  0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,      /*  @ - G  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  H - O  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  P - W  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  X - _  */
+  0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,      /*  ` - g  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  h - o  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  p - w  */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /*  x -127 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 128-135 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 136-143 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 144-151 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 152-159 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 160-167 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 168-175 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 176-183 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 184-191 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 192-199 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 200-207 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 208-215 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 216-223 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 224-231 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 232-239 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      /* 240-247 */
+  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};                             /* 248-255 */
+
+
+
+/* Definition to allow mutual recursion */
+
+static BOOL
+compile_regex(int, int, int *, uschar **, const uschar **, const char **,
+             BOOL, int, int *, int *, branch_chain *, compile_data *);
+
+/* Structure for building a chain of data that actually lives on the
+stack, for holding the values of the subject pointer at the start of each
+subpattern, so as to detect when an empty string has been matched by a
+subpattern - to break infinite loops. When NO_RECURSE is set, these blocks
+are on the heap, not on the stack. */
+
+typedef struct eptrblock {
+  struct eptrblock *epb_prev;
+  const uschar *epb_saved_eptr;
+} eptrblock;
+
+/* Flag bits for the match() function */
+
+#define match_condassert   0x01        /* Called to check a condition assertion */
+#define match_isgroup      0x02        /* Set if start of bracketed group */
+
+/* Non-error returns from the match() function. Error returns are externally
+defined PCRE_ERROR_xxx codes, which are all negative. */
+
+#define MATCH_MATCH        1
+#define MATCH_NOMATCH      0
+
+
+
+/*************************************************
+*               Global variables                 *
+*************************************************/
+
+/* PCRE is thread-clean and doesn't use any global variables in the normal
+sense. However, it calls memory allocation and free functions via the four
+indirections below, and it can optionally do callouts. These values can be
+changed by the caller, but are shared between all threads. However, when
+compiling for Virtual Pascal, things are done differently (see pcre.in). */
+
+#ifndef VPCOMPAT
+#ifdef __cplusplus
+extern "C" void *(*pcre_malloc) (size_t) = malloc;
+extern "C" void (*pcre_free) (void *) = free;
+extern "C" void *(*pcre_stack_malloc) (size_t) = malloc;
+extern "C" void (*pcre_stack_free) (void *) = free;
+extern "C" int (*pcre_callout) (pcre_callout_block *) = NULL;
+#else
+void *(*pcre_malloc) (size_t) = malloc;
+void (*pcre_free) (void *) = free;
+void *(*pcre_stack_malloc) (size_t) = malloc;
+void (*pcre_stack_free) (void *) = free;
+int (*pcre_callout) (pcre_callout_block *) = NULL;
+#endif
+#endif
+
+
+/*************************************************
+*    Macros and tables for character handling    *
+*************************************************/
+
+/* When UTF-8 encoding is being used, a character is no longer just a single
+byte. The macros for character handling generate simple sequences when used in
+byte-mode, and more complicated ones for UTF-8 characters. */
+
+#define GETCHAR(c, eptr) c = *eptr;
+#define GETCHARINC(c, eptr) c = *eptr++;
+#define GETCHARINCTEST(c, eptr) c = *eptr++;
+#define GETCHARLEN(c, eptr, len) c = *eptr;
+#define BACKCHAR(eptr)
+
+
+/*************************************************
+*            Handle escapes                      *
+*************************************************/
+
+/* This function is called when a \ has been encountered. It either returns a
+positive value for a simple escape such as \n, or a negative value which
+encodes one of the more complicated things such as \d. When UTF-8 is enabled,
+a positive value greater than 255 may be returned. On entry, ptr is pointing at
+the \. On exit, it is on the final character of the escape sequence.
+
+Arguments:
+  ptrptr     points to the pattern position pointer
+  errorptr   points to the pointer to the error message
+  bracount   number of previous extracting brackets
+  options    the options bits
+  isclass    TRUE if inside a character class
+
+Returns:     zero or positive => a data character
+             negative => a special escape sequence
+             on error, errorptr is set
+*/
+
+static int
+check_escape(const uschar ** ptrptr, const char **errorptr, int bracount,
+            int options, BOOL isclass)
+{
+  const uschar *ptr = *ptrptr;
+  int c, i;
+
+/* If backslash is at the end of the pattern, it's an error. */
+
+  c = *(++ptr);
+  if (c == 0)
+    *errorptr = ERR1;
+
+/* Non-alphamerics are literals. For digits or letters, do an initial lookup in
+a table. A non-zero result is something that can be returned immediately.
+Otherwise further processing may be required. */
+
+  else if (c < '0' || c > 'z') {
+  } /* Not alphameric */
+  else if ((i = escapes[c - '0']) != 0)
+    c = i;
+
+
+/* Escapes that need further processing, or are illegal. */
+
+  else {
+    const uschar *oldptr;
+    switch (c) {
+      /* A number of Perl escapes are not handled by PCRE. We give an explicit
+         error. */
+
+    case 'l':
+    case 'L':
+    case 'N':
+    case 'p':
+    case 'P':
+    case 'u':
+    case 'U':
+    case 'X':
+      *errorptr = ERR37;
+      break;
+
+      /* The handling of escape sequences consisting of a string of digits
+         starting with one that is not zero is not straightforward. By experiment,
+         the way Perl works seems to be as follows:
+
+         Outside a character class, the digits are read as a decimal number. If the
+         number is less than 10, or if there are that many previous extracting
+         left brackets, then it is a back reference. Otherwise, up to three octal
+         digits are read to form an escaped byte. Thus \123 is likely to be octal
+         123 (cf \0123, which is octal 012 followed by the literal 3). If the octal
+         value is greater than 377, the least significant 8 bits are taken. Inside a
+         character class, \ followed by a digit is always an octal number. */
+
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+
+      if (!isclass) {
+       oldptr = ptr;
+       c -= '0';
+       while ((digitab[ptr[1]] & ctype_digit) != 0)
+         c = c * 10 + *(++ptr) - '0';
+       if (c < 10 || c <= bracount) {
+         c = -(ESC_REF + c);
+         break;
+       }
+       ptr = oldptr;           /* Put the pointer back and fall through */
+      }
+
+      /* Handle an octal number following \. If the first digit is 8 or 9, Perl
+         generates a binary zero byte and treats the digit as a following literal.
+         Thus we have to pull back the pointer by one. */
+
+      if ((c = *ptr) >= '8') {
+       ptr--;
+       c = 0;
+       break;
+      }
+
+      /* \0 always starts an octal number, but we may drop through to here with a
+         larger first octal digit. */
+
+    case '0':
+      c -= '0';
+      while (i++ < 2 && ptr[1] >= '0' && ptr[1] <= '7')
+       c = c * 8 + *(++ptr) - '0';
+      c &= 255;                        /* Take least significant 8 bits */
+      break;
+
+      /* \x is complicated when UTF-8 is enabled. \x{ddd} is a character number
+         which can be greater than 0xff, but only if the ddd are hex digits. */
+
+    case 'x':
+
+      /* Read just a single hex char */
+
+      c = 0;
+      while (i++ < 2 && (digitab[ptr[1]] & ctype_xdigit) != 0) {
+       int cc;                 /* Some compilers don't like ++ */
+       cc = *(++ptr);          /* in initializers */
+       if (cc >= 'a')
+         cc -= 32;             /* Convert to upper case */
+       c = c * 16 + cc - ((cc < 'A') ? '0' : ('A' - 10));
+      }
+      break;
+
+      /* Other special escapes not starting with a digit are straightforward */
+
+    case 'c':
+      c = *(++ptr);
+      if (c == 0) {
+       *errorptr = ERR2;
+       return 0;
+      }
+
+      /* A letter is upper-cased; then the 0x40 bit is flipped. This coding
+         is ASCII-specific, but then the whole concept of \cx is ASCII-specific.
+         (However, an EBCDIC equivalent has now been added.) */
+
+      if (c >= 'a' && c <= 'z')
+       c -= 32;
+      c ^= 0x40;
+      break;
+
+      /* PCRE_EXTRA enables extensions to Perl in the matter of escapes. Any
+         other alphameric following \ is an error if PCRE_EXTRA was set; otherwise,
+         for Perl compatibility, it is a literal. This code looks a bit odd, but
+         there used to be some cases other than the default, and there may be again
+         in future, so I haven't "optimized" it. */
+
+    default:
+      if ((options & PCRE_EXTRA) != 0)
+       switch (c) {
+       default:
+         *errorptr = ERR3;
+         break;
+       }
+      break;
+    }
+  }
+
+  *ptrptr = ptr;
+  return c;
+}
+
+
+
+/*************************************************
+*            Check for counted repeat            *
+*************************************************/
+
+/* This function is called when a '{' is encountered in a place where it might
+start a quantifier. It looks ahead to see if it really is a quantifier or not.
+It is only a quantifier if it is one of the forms {ddd} {ddd,} or {ddd,ddd}
+where the ddds are digits.
+
+Arguments:
+  p         pointer to the first char after '{'
+
+Returns:    TRUE or FALSE
+*/
+
+static BOOL
+is_counted_repeat(const uschar * p)
+{
+  if ((digitab[*p++] & ctype_digit) == 0)
+    return FALSE;
+  while ((digitab[*p] & ctype_digit) != 0)
+    p++;
+  if (*p == '}')
+    return TRUE;
+
+  if (*p++ != ',')
+    return FALSE;
+  if (*p == '}')
+    return TRUE;
+
+  if ((digitab[*p++] & ctype_digit) == 0)
+    return FALSE;
+  while ((digitab[*p] & ctype_digit) != 0)
+    p++;
+
+  return (*p == '}');
+}
+
+
+
+/*************************************************
+*         Read repeat counts                     *
+*************************************************/
+
+/* Read an item of the form {n,m} and return the values. This is called only
+after is_counted_repeat() has confirmed that a repeat-count quantifier exists,
+so the syntax is guaranteed to be correct, but we need to check the values.
+
+Arguments:
+  p          pointer to first char after '{'
+  minp       pointer to int for min
+  maxp       pointer to int for max
+             returned as -1 if no max
+  errorptr   points to pointer to error message
+
+Returns:     pointer to '}' on success;
+             current ptr on error, with errorptr set
+*/
+
+static const uschar *
+read_repeat_counts(const uschar * p, int *minp, int *maxp,
+                  const char **errorptr)
+{
+  int min = 0;
+  int max = -1;
+
+  while ((digitab[*p] & ctype_digit) != 0)
+    min = min * 10 + *p++ - '0';
+
+  if (*p == '}')
+    max = min;
+  else {
+    if (*(++p) != '}') {
+      max = 0;
+      while ((digitab[*p] & ctype_digit) != 0)
+       max = max * 10 + *p++ - '0';
+      if (max < min) {
+       *errorptr = ERR4;
+       return p;
+      }
+    }
+  }
+
+/* Do paranoid checks, then fill in the required variables, and pass back the
+pointer to the terminating '}'. */
+
+  if (min > 65535 || max > 65535)
+    *errorptr = ERR5;
+  else {
+    *minp = min;
+    *maxp = max;
+  }
+  return p;
+}
+
+
+
+/*************************************************
+*      Find first significant op code            *
+*************************************************/
+
+/* This is called by several functions that scan a compiled expression looking
+for a fixed first character, or an anchoring op code etc. It skips over things
+that do not influence this. For some calls, a change of option is important.
+
+Arguments:
+  code       pointer to the start of the group
+  options    pointer to external options
+  optbit     the option bit whose changing is significant, or
+               zero if none are
+
+Returns:     pointer to the first significant opcode
+*/
+
+static const uschar *
+first_significant_code(const uschar * code, int *options, int optbit)
+{
+  for (;;) {
+    switch ((int) *code) {
+    case OP_OPT:
+      if (optbit > 0 && ((int) code[1] & optbit) != (*options & optbit))
+       *options = (int) code[1];
+      code += 2;
+      break;
+
+    case OP_ASSERT_NOT:
+    case OP_ASSERTBACK:
+    case OP_ASSERTBACK_NOT:
+      do
+       code += GET(code, 1);
+      while (*code == OP_ALT);
+      /* Fall through */
+
+    case OP_CALLOUT:
+    case OP_CREF:
+    case OP_BRANUMBER:
+    case OP_WORD_BOUNDARY:
+    case OP_NOT_WORD_BOUNDARY:
+      code += OP_lengths[*code];
+      break;
+
+    default:
+      return code;
+    }
+  }
+/* Control never reaches here */
+}
+
+
+
+
+/*************************************************
+*        Find the fixed length of a pattern      *
+*************************************************/
+
+/* Scan a pattern and compute the fixed length of subject that will match it,
+if the length is fixed. This is needed for dealing with backward assertions.
+In UTF8 mode, the result is in characters rather than bytes.
+
+Arguments:
+  code     points to the start of the pattern (the bracket)
+  options  the compiling options
+
+Returns:   the fixed length, or -1 if there is no fixed length,
+             or -2 if \C was encountered
+*/
+
+static int
+find_fixedlength(uschar * code, int options)
+{
+  int length = -1;
+
+  register int branchlength = 0;
+  register uschar *cc = code + 1 + LINK_SIZE;
+
+/* Scan along the opcodes for this branch. If we get to the end of the
+branch, check the length against that of the other branches. */
+
+  for (;;) {
+    int d;
+    register int op = *cc;
+    if (op >= OP_BRA)
+      op = OP_BRA;
+
+    switch (op) {
+    case OP_BRA:
+    case OP_ONCE:
+    case OP_COND:
+      d = find_fixedlength(cc, options);
+      if (d < 0)
+       return d;
+      branchlength += d;
+      do
+       cc += GET(cc, 1);
+      while (*cc == OP_ALT);
+      cc += 1 + LINK_SIZE;
+      break;
+
+      /* Reached end of a branch; if it's a ket it is the end of a nested
+         call. If it's ALT it is an alternation in a nested call. If it is
+         END it's the end of the outer call. All can be handled by the same code. */
+
+    case OP_ALT:
+    case OP_KET:
+    case OP_KETRMAX:
+    case OP_KETRMIN:
+    case OP_END:
+      if (length < 0)
+       length = branchlength;
+      else if (length != branchlength)
+       return -1;
+      if (*cc != OP_ALT)
+       return length;
+      cc += 1 + LINK_SIZE;
+      branchlength = 0;
+      break;
+
+      /* Skip over assertive subpatterns */
+
+    case OP_ASSERT:
+    case OP_ASSERT_NOT:
+    case OP_ASSERTBACK:
+    case OP_ASSERTBACK_NOT:
+      do
+       cc += GET(cc, 1);
+      while (*cc == OP_ALT);
+      /* Fall through */
+
+      /* Skip over things that don't match chars */
+
+    case OP_REVERSE:
+    case OP_BRANUMBER:
+    case OP_CREF:
+    case OP_OPT:
+    case OP_CALLOUT:
+    case OP_SOD:
+    case OP_SOM:
+    case OP_EOD:
+    case OP_EODN:
+    case OP_CIRC:
+    case OP_DOLL:
+    case OP_NOT_WORD_BOUNDARY:
+    case OP_WORD_BOUNDARY:
+      cc += OP_lengths[*cc];
+      break;
+
+      /* Handle char strings. In UTF-8 mode we must count characters, not bytes.
+         This requires a scan of the string, unfortunately. We assume valid UTF-8
+         strings, so all we do is reduce the length by one for every byte whose bits
+         are 10xxxxxx. */
+
+    case OP_CHARS:
+      branchlength += *(++cc);
+      cc += *cc + 1;
+      break;
+
+      /* Handle exact repetitions. The count is already in characters, but we
+         need to skip over a multibyte character in UTF8 mode.  */
+
+    case OP_EXACT:
+      branchlength += GET2(cc, 1);
+      cc += 4;
+      break;
+
+    case OP_TYPEEXACT:
+      branchlength += GET2(cc, 1);
+      cc += 4;
+      break;
+
+      /* Handle single-char matchers */
+
+    case OP_NOT_DIGIT:
+    case OP_DIGIT:
+    case OP_NOT_WHITESPACE:
+    case OP_WHITESPACE:
+    case OP_NOT_WORDCHAR:
+    case OP_WORDCHAR:
+    case OP_ANY:
+      branchlength++;
+      cc++;
+      break;
+
+      /* The single-byte matcher isn't allowed */
+
+    case OP_ANYBYTE:
+      return -2;
+
+      /* Check a class for variable quantification */
+
+
+    case OP_CLASS:
+    case OP_NCLASS:
+      cc += 33;
+
+      switch (*cc) {
+      case OP_CRSTAR:
+      case OP_CRMINSTAR:
+      case OP_CRQUERY:
+      case OP_CRMINQUERY:
+       return -1;
+
+      case OP_CRRANGE:
+      case OP_CRMINRANGE:
+       if (GET2(cc, 1) != GET2(cc, 3))
+         return -1;
+       branchlength += GET2(cc, 1);
+       cc += 5;
+       break;
+
+      default:
+       branchlength++;
+      }
+      break;
+
+      /* Anything else is variable length */
+
+    default:
+      return -1;
+    }
+  }
+/* Control never gets here */
+}
+
+
+
+
+/*************************************************
+*    Scan compiled regex for numbered bracket    *
+*************************************************/
+
+/* This little function scans through a compiled pattern until it finds a
+capturing bracket with the given number.
+
+Arguments:
+  code        points to start of expression
+  utf8        TRUE in UTF-8 mode
+  number      the required bracket number
+
+Returns:      pointer to the opcode for the bracket, or NULL if not found
+*/
+
+static const uschar *
+find_bracket(const uschar * code, BOOL utf8, int number)
+{
+  utf8 = utf8;                 /* Stop pedantic compilers complaining */
+
+  for (;;) {
+    register int c = *code;
+    if (c == OP_END)
+      return NULL;
+    else if (c == OP_CHARS)
+      code += code[1] + OP_lengths[c];
+    else if (c > OP_BRA) {
+      int n = c - OP_BRA;
+      if (n > EXTRACT_BASIC_MAX)
+       n = GET2(code, 2 + LINK_SIZE);
+      if (n == number)
+       return (uschar *) code;
+      code += OP_lengths[OP_BRA];
+    } else {
+      code += OP_lengths[c];
+
+    }
+  }
+}
+
+
+
+/*************************************************
+*   Scan compiled regex for recursion reference  *
+*************************************************/
+
+/* This little function scans through a compiled pattern until it finds an
+instance of OP_RECURSE.
+
+Arguments:
+  code        points to start of expression
+  utf8        TRUE in UTF-8 mode
+
+Returns:      pointer to the opcode for OP_RECURSE, or NULL if not found
+*/
+
+static const uschar *
+find_recurse(const uschar * code, BOOL utf8)
+{
+  utf8 = utf8;                 /* Stop pedantic compilers complaining */
+
+  for (;;) {
+    register int c = *code;
+    if (c == OP_END)
+      return NULL;
+    else if (c == OP_RECURSE)
+      return code;
+    else if (c == OP_CHARS)
+      code += code[1] + OP_lengths[c];
+    else if (c > OP_BRA) {
+      code += OP_lengths[OP_BRA];
+    } else {
+      code += OP_lengths[c];
+
+    }
+  }
+}
+
+
+
+/*************************************************
+*    Scan compiled branch for non-emptiness      *
+*************************************************/
+
+/* This function scans through a branch of a compiled pattern to see whether it
+can match the empty string or not. It is called only from could_be_empty()
+below. Note that first_significant_code() skips over assertions. If we hit an
+unclosed bracket, we return "empty" - this means we've struck an inner bracket
+whose current branch will already have been scanned.
+
+Arguments:
+  code        points to start of search
+  endcode     points to where to stop
+  utf8        TRUE if in UTF8 mode
+
+Returns:      TRUE if what is matched could be empty
+*/
+
+static BOOL
+could_be_empty_branch(const uschar * code, const uschar * endcode, BOOL utf8)
+{
+  register int c;
+  for (code = first_significant_code(code + 1 + LINK_SIZE, NULL, 0);
+       code < endcode;
+       code = first_significant_code(code + OP_lengths[c], NULL, 0)) {
+    const uschar *ccode;
+
+    c = *code;
+
+    if (c >= OP_BRA) {
+      BOOL empty_branch;
+      if (GET(code, 1) == 0)
+       return TRUE;            /* Hit unclosed bracket */
+
+      /* Scan a closed bracket */
+
+      empty_branch = FALSE;
+      do {
+       if (!empty_branch && could_be_empty_branch(code, endcode, utf8))
+         empty_branch = TRUE;
+       code += GET(code, 1);
+      }
+      while (*code == OP_ALT);
+      if (!empty_branch)
+       return FALSE;           /* All branches are non-empty */
+      code += 1 + LINK_SIZE;
+      c = *code;
+    }
+
+    else
+      switch (c) {
+       /* Check for quantifiers after a class */
+
+      case OP_CLASS:
+      case OP_NCLASS:
+       ccode = code + 33;
+
+       switch (*ccode) {
+       case OP_CRSTAR: /* These could be empty; continue */
+       case OP_CRMINSTAR:
+       case OP_CRQUERY:
+       case OP_CRMINQUERY:
+         break;
+
+       default:                /* Non-repeat => class must match */
+       case OP_CRPLUS: /* These repeats aren't empty */
+       case OP_CRMINPLUS:
+         return FALSE;
+
+       case OP_CRRANGE:
+       case OP_CRMINRANGE:
+         if (GET2(ccode, 1) > 0)
+           return FALSE;       /* Minimum > 0 */
+         break;
+       }
+       break;
+
+       /* Opcodes that must match a character */
+
+      case OP_NOT_DIGIT:
+      case OP_DIGIT:
+      case OP_NOT_WHITESPACE:
+      case OP_WHITESPACE:
+      case OP_NOT_WORDCHAR:
+      case OP_WORDCHAR:
+      case OP_ANY:
+      case OP_ANYBYTE:
+      case OP_CHARS:
+      case OP_NOT:
+      case OP_PLUS:
+      case OP_MINPLUS:
+      case OP_EXACT:
+      case OP_NOTPLUS:
+      case OP_NOTMINPLUS:
+      case OP_NOTEXACT:
+      case OP_TYPEPLUS:
+      case OP_TYPEMINPLUS:
+      case OP_TYPEEXACT:
+       return FALSE;
+
+       /* End of branch */
+
+      case OP_KET:
+      case OP_KETRMAX:
+      case OP_KETRMIN:
+      case OP_ALT:
+       return TRUE;
+
+       /* In UTF-8 mode, STAR, MINSTAR, QUERY, MINQUERY, UPTO, and MINUPTO  may be
+          followed by a multibyte character */
+
+      }
+  }
+
+  return TRUE;
+}
+
+
+
+/*************************************************
+*    Scan compiled regex for non-emptiness       *
+*************************************************/
+
+/* This function is called to check for left recursive calls. We want to check
+the current branch of the current pattern to see if it could match the empty
+string. If it could, we must look outwards for branches at other levels,
+stopping when we pass beyond the bracket which is the subject of the recursion.
+
+Arguments:
+  code        points to start of the recursion
+  endcode     points to where to stop (current RECURSE item)
+  bcptr       points to the chain of current (unclosed) branch starts
+  utf8        TRUE if in UTF-8 mode
+
+Returns:      TRUE if what is matched could be empty
+*/
+
+static BOOL
+could_be_empty(const uschar * code, const uschar * endcode,
+              branch_chain * bcptr, BOOL utf8)
+{
+  while (bcptr != NULL && bcptr->current >= code) {
+    if (!could_be_empty_branch(bcptr->current, endcode, utf8))
+      return FALSE;
+    bcptr = bcptr->outer;
+  }
+  return TRUE;
+}
+
+
+
+/*************************************************
+*           Check for POSIX class syntax         *
+*************************************************/
+
+/* This function is called when the sequence "[:" or "[." or "[=" is
+encountered in a character class. It checks whether this is followed by an
+optional ^ and then a sequence of letters, terminated by a matching ":]" or
+".]" or "=]".
+
+Argument:
+  ptr      pointer to the initial [
+  endptr   where to return the end pointer
+  cd       pointer to compile data
+
+Returns:   TRUE or FALSE
+*/
+
+static BOOL
+check_posix_syntax(const uschar * ptr, const uschar ** endptr,
+                  compile_data * cd)
+{
+  int terminator;              /* Don't combine these lines; the Solaris cc */
+  terminator = *(++ptr);       /* compiler warns about "non-constant" initializer. */
+  if (*(++ptr) == '^')
+    ptr++;
+  while ((cd->ctypes[*ptr] & ctype_letter) != 0)
+    ptr++;
+  if (*ptr == terminator && ptr[1] == ']') {
+    *endptr = ptr;
+    return TRUE;
+  }
+  return FALSE;
+}
+
+
+
+
+/*************************************************
+*          Check POSIX class name                *
+*************************************************/
+
+/* This function is called to check the name given in a POSIX-style class entry
+such as [:alnum:].
+
+Arguments:
+  ptr        points to the first letter
+  len        the length of the name
+
+Returns:     a value representing the name, or -1 if unknown
+*/
+
+static int
+check_posix_name(const uschar * ptr, int len)
+{
+  register int yield = 0;
+  while (posix_name_lengths[yield] != 0) {
+    if (len == posix_name_lengths[yield] &&
+       strncmp((const char *) ptr, posix_names[yield], len) == 0)
+      return yield;
+    yield++;
+  }
+  return -1;
+}
+
+
+/*************************************************
+*    Adjust OP_RECURSE items in repeated group   *
+*************************************************/
+
+/* OP_RECURSE items contain an offset from the start of the regex to the group
+that is referenced. This means that groups can be replicated for fixed
+repetition simply by copying (because the recursion is allowed to refer to
+earlier groups that are outside the current group). However, when a group is
+optional (i.e. the minimum quantifier is zero), OP_BRAZERO is inserted before
+it, after it has been compiled. This means that any OP_RECURSE items within it
+that refer to the group itself or any contained groups have to have their
+offsets adjusted. That is the job of this function. Before it is called, the
+partially compiled regex must be temporarily terminated with OP_END.
+
+Arguments:
+  group      points to the start of the group
+  adjust     the amount by which the group is to be moved
+  utf8       TRUE in UTF-8 mode
+  cd         contains pointers to tables etc.
+
+Returns:     nothing
+*/
+
+static void
+adjust_recurse(uschar * group, int adjust, BOOL utf8, compile_data * cd)
+{
+  uschar *ptr = group;
+  while ((ptr = (uschar *) find_recurse(ptr, utf8)) != NULL) {
+    int offset = GET(ptr, 1);
+    if (cd->start_code + offset >= group)
+      PUT(ptr, 1, offset + adjust);
+    ptr += 1 + LINK_SIZE;
+  }
+}
+
+
+
+/*************************************************
+*           Compile one branch                   *
+*************************************************/
+
+/* Scan the pattern, compiling it into the code vector. If the options are
+changed during the branch, the pointer is used to change the external options
+bits.
+
+Arguments:
+  optionsptr     pointer to the option bits
+  brackets       points to number of extracting brackets used
+  code           points to the pointer to the current code point
+  ptrptr         points to the current pattern pointer
+  errorptr       points to pointer to error message
+  firstbyteptr   set to initial literal character, or < 0 (REQ_UNSET, REQ_NONE)
+  reqbyteptr     set to the last literal character required, else < 0
+  bcptr          points to current branch chain
+  cd             contains pointers to tables etc.
+
+Returns:         TRUE on success
+                 FALSE, with *errorptr set on error
+*/
+
+static BOOL
+compile_branch(int *optionsptr, int *brackets, uschar ** codeptr,
+              const uschar ** ptrptr, const char **errorptr, int *firstbyteptr,
+              int *reqbyteptr, branch_chain * bcptr, compile_data * cd)
+{
+  int repeat_type, op_type;
+  int repeat_min = 0, repeat_max = 0;  /* To please picky compilers */
+  int bravalue = 0;
+  int length;
+  int greedy_default, greedy_non_default;
+  int firstbyte, reqbyte;
+  int zeroreqbyte, zerofirstbyte;
+  int req_caseopt, reqvary, tempreqvary;
+  int condcount = 0;
+  int options = *optionsptr;
+  register int c;
+  register uschar *code = *codeptr;
+  uschar *tempcode;
+  BOOL inescq = FALSE;
+  BOOL groupsetfirstbyte = FALSE;
+  const uschar *ptr = *ptrptr;
+  const uschar *tempptr;
+  uschar *previous = NULL;
+  uschar class[32];
+
+  BOOL utf8 = FALSE;
+
+/* Set up the default and non-default settings for greediness */
+
+  greedy_default = ((options & PCRE_UNGREEDY) != 0);
+  greedy_non_default = greedy_default ^ 1;
+
+/* Initialize no first char, no required char. REQ_UNSET means "no char
+matching encountered yet". It gets changed to REQ_NONE if we hit something that
+matches a non-fixed char first char; reqbyte just remains unset if we never
+find one.
+
+When we hit a repeat whose minimum is zero, we may have to adjust these values
+to take the zero repeat into account. This is implemented by setting them to
+zerofirstbyte and zeroreqbyte when such a repeat is encountered. The individual
+item types that can be repeated set these backoff variables appropriately. */
+
+  firstbyte = reqbyte = zerofirstbyte = zeroreqbyte = REQ_UNSET;
+
+/* The variable req_caseopt contains either the REQ_CASELESS value or zero,
+according to the current setting of the caseless flag. REQ_CASELESS is a bit
+value > 255. It is added into the firstbyte or reqbyte variables to record the
+case status of the value. */
+
+  req_caseopt = ((options & PCRE_CASELESS) != 0) ? REQ_CASELESS : 0;
+
+/* Switch on next character until the end of the branch */
+
+  for (;; ptr++) {
+    BOOL negate_class;
+    BOOL possessive_quantifier;
+    int class_charcount;
+    int class_lastchar;
+    int newoptions;
+    int recno;
+    int skipbytes;
+    int subreqbyte;
+    int subfirstbyte;
+
+    c = *ptr;
+    if (inescq && c != 0)
+      goto NORMAL_CHAR;
+
+    if ((options & PCRE_EXTENDED) != 0) {
+      if ((cd->ctypes[c] & ctype_space) != 0)
+       continue;
+      if (c == '#') {
+       /* The space before the ; is to avoid a warning on a silly compiler
+          on the Macintosh. */
+       while ((c = *(++ptr)) != 0 && c != NEWLINE) ;
+       if (c != 0)
+         continue;             /* Else fall through to handle end of string */
+      }
+    }
+
+    switch (c) {
+      /* The branch terminates at end of string, |, or ). */
+
+    case 0:
+    case '|':
+    case ')':
+      *firstbyteptr = firstbyte;
+      *reqbyteptr = reqbyte;
+      *codeptr = code;
+      *ptrptr = ptr;
+      return TRUE;
+
+      /* Handle single-character metacharacters. In multiline mode, ^ disables
+         the setting of any following char as a first character. */
+
+    case '^':
+      if ((options & PCRE_MULTILINE) != 0) {
+       if (firstbyte == REQ_UNSET)
+         firstbyte = REQ_NONE;
+      }
+      previous = NULL;
+      *code++ = OP_CIRC;
+      break;
+
+    case '$':
+      previous = NULL;
+      *code++ = OP_DOLL;
+      break;
+
+      /* There can never be a first char if '.' is first, whatever happens about
+         repeats. The value of reqbyte doesn't change either. */
+
+    case '.':
+      if (firstbyte == REQ_UNSET)
+       firstbyte = REQ_NONE;
+      zerofirstbyte = firstbyte;
+      zeroreqbyte = reqbyte;
+      previous = code;
+      *code++ = OP_ANY;
+      break;
+
+      /* Character classes. If the included characters are all < 255 in value, we
+         build a 32-byte bitmap of the permitted characters, except in the special
+         case where there is only one such character. For negated classes, we build
+         the map as usual, then invert it at the end. However, we use a different
+         opcode so that data characters > 255 can be handled correctly.
+
+         If the class contains characters outside the 0-255 range, a different
+         opcode is compiled. It may optionally have a bit map for characters < 256,
+         but those above are are explicitly listed afterwards. A flag byte tells
+         whether the bitmap is present, and whether this is a negated class or not.
+       */
+
+    case '[':
+      previous = code;
+
+      /* PCRE supports POSIX class stuff inside a class. Perl gives an error if
+         they are encountered at the top level, so we'll do that too. */
+
+      if ((ptr[1] == ':' || ptr[1] == '.' || ptr[1] == '=') &&
+         check_posix_syntax(ptr, &tempptr, cd)) {
+       *errorptr = (ptr[1] == ':') ? ERR13 : ERR31;
+       goto FAILED;
+      }
+
+      /* If the first character is '^', set the negation flag and skip it. */
+
+      if ((c = *(++ptr)) == '^') {
+       negate_class = TRUE;
+       c = *(++ptr);
+      } else {
+       negate_class = FALSE;
+      }
+
+      /* Keep a count of chars with values < 256 so that we can optimize the case
+         of just a single character (as long as it's < 256). For higher valued UTF-8
+         characters, we don't yet do any optimization. */
+
+      class_charcount = 0;
+      class_lastchar = -1;
+
+
+      /* Initialize the 32-char bit map to all zeros. We have to build the
+         map in a temporary bit of store, in case the class contains only 1
+         character (< 256), because in that case the compiled code doesn't use the
+         bit map. */
+
+      memset(class, 0, 32 * sizeof(uschar));
+
+      /* Process characters until ] is reached. By writing this as a "do" it
+         means that an initial ] is taken as a data character. The first pass
+         through the regex checked the overall syntax, so we don't need to be very
+         strict here. At the start of the loop, c contains the first byte of the
+         character. */
+
+      do {
+
+       /* Inside \Q...\E everything is literal except \E */
+
+       if (inescq) {
+         if (c == '\\' && ptr[1] == 'E') {
+           inescq = FALSE;
+           ptr++;
+           continue;
+         } else
+           goto LONE_SINGLE_CHARACTER;
+       }
+
+       /* Handle POSIX class names. Perl allows a negation extension of the
+          form [:^name:]. A square bracket that doesn't match the syntax is
+          treated as a literal. We also recognize the POSIX constructions
+          [.ch.] and [=ch=] ("collating elements") and fault them, as Perl
+          5.6 and 5.8 do. */
+
+       if (c == '[' &&
+           (ptr[1] == ':' || ptr[1] == '.' || ptr[1] == '=') &&
+           check_posix_syntax(ptr, &tempptr, cd)) {
+         BOOL local_negate = FALSE;
+         int posix_class, i;
+         register const uschar *cbits = cd->cbits;
+
+         if (ptr[1] != ':') {
+           *errorptr = ERR31;
+           goto FAILED;
+         }
+
+         ptr += 2;
+         if (*ptr == '^') {
+           local_negate = TRUE;
+           ptr++;
+         }
+
+         posix_class = check_posix_name(ptr, tempptr - ptr);
+         if (posix_class < 0) {
+           *errorptr = ERR30;
+           goto FAILED;
+         }
+
+         /* If matching is caseless, upper and lower are converted to
+            alpha. This relies on the fact that the class table starts with
+            alpha, lower, upper as the first 3 entries. */
+
+         if ((options & PCRE_CASELESS) != 0 && posix_class <= 2)
+           posix_class = 0;
+
+         /* Or into the map we are building up to 3 of the static class
+            tables, or their negations. The [:blank:] class sets up the same
+            chars as the [:space:] class (all white space). We remove the vertical
+            white space chars afterwards. */
+
+         posix_class *= 3;
+         for (i = 0; i < 3; i++) {
+           BOOL blankclass = strncmp((char *) ptr, "blank", 5) == 0;
+           int taboffset = posix_class_maps[posix_class + i];
+           if (taboffset < 0)
+             break;
+           if (local_negate) {
+             for (c = 0; c < 32; c++)
+               class[c] |= ~cbits[c + taboffset];
+             if (blankclass)
+               class[1] |= 0x3c;
+           } else {
+             for (c = 0; c < 32; c++)
+               class[c] |= cbits[c + taboffset];
+             if (blankclass)
+               class[1] &= ~0x3c;
+           }
+         }
+
+         ptr = tempptr + 1;
+         class_charcount = 10; /* Set > 1; assumes more than 1 per class */
+         continue;             /* End of POSIX syntax handling */
+       }
+
+       /* Backslash may introduce a single character, or it may introduce one
+          of the specials, which just set a flag. Escaped items are checked for
+          validity in the pre-compiling pass. The sequence \b is a special case.
+          Inside a class (and only there) it is treated as backspace. Elsewhere
+          it marks a word boundary. Other escapes have preset maps ready to
+          or into the one we are building. We assume they have more than one
+          character in them, so set class_charcount bigger than one. */
+
+       if (c == '\\') {
+         c = check_escape(&ptr, errorptr, *brackets, options, TRUE);
+         if (-c == ESC_b)
+           c = '\b';           /* \b is backslash in a class */
+
+         if (-c == ESC_Q) {    /* Handle start of quoted string */
+           if (ptr[1] == '\\' && ptr[2] == 'E') {
+             ptr += 2;         /* avoid empty string */
+           } else
+             inescq = TRUE;
+           continue;
+         }
+
+         else if (c < 0) {
+           register const uschar *cbits = cd->cbits;
+           class_charcount = 10;       /* Greater than 1 is what matters */
+           switch (-c) {
+           case ESC_d:
+             for (c = 0; c < 32; c++)
+               class[c] |= cbits[c + cbit_digit];
+             continue;
+
+           case ESC_D:
+             for (c = 0; c < 32; c++)
+               class[c] |= ~cbits[c + cbit_digit];
+             continue;
+
+           case ESC_w:
+             for (c = 0; c < 32; c++)
+               class[c] |= cbits[c + cbit_word];
+             continue;
+
+           case ESC_W:
+             for (c = 0; c < 32; c++)
+               class[c] |= ~cbits[c + cbit_word];
+             continue;
+
+           case ESC_s:
+             for (c = 0; c < 32; c++)
+               class[c] |= cbits[c + cbit_space];
+             class[1] &= ~0x08;        /* Perl 5.004 onwards omits VT from \s */
+             continue;
+
+           case ESC_S:
+             for (c = 0; c < 32; c++)
+               class[c] |= ~cbits[c + cbit_space];
+             class[1] |= 0x08; /* Perl 5.004 onwards omits VT from \s */
+             continue;
+
+             /* Unrecognized escapes are faulted if PCRE is running in its
+                strict mode. By default, for compatibility with Perl, they are
+                treated as literals. */
+
+           default:
+             if ((options & PCRE_EXTRA) != 0) {
+               *errorptr = ERR7;
+               goto FAILED;
+             }
+             c = *ptr;         /* The final character */
+           }
+         }
+
+         /* Fall through if we have a single character (c >= 0). This may be
+            > 256 in UTF-8 mode. */
+
+       }
+
+
+       /* End of backslash handling */
+       /* A single character may be followed by '-' to form a range. However,
+          Perl does not permit ']' to be the end of the range. A '-' character
+          here is treated as a literal. */
+       if (ptr[1] == '-' && ptr[2] != ']') {
+         int d;
+         ptr += 2;
+
+         d = *ptr;
+
+         /* The second part of a range can be a single-character escape, but
+            not any of the other escapes. Perl 5.6 treats a hyphen as a literal
+            in such circumstances. */
+
+         if (d == '\\') {
+           const uschar *oldptr = ptr;
+           d = check_escape(&ptr, errorptr, *brackets, options, TRUE);
+
+           /* \b is backslash; any other special means the '-' was literal */
+
+           if (d < 0) {
+             if (d == -ESC_b)
+               d = '\b';
+             else {
+               ptr = oldptr - 2;
+               goto LONE_SINGLE_CHARACTER;     /* A few lines below */
+             }
+           }
+         }
+
+         /* Check that the two values are in the correct order */
+
+         if (d < c) {
+           *errorptr = ERR8;
+           goto FAILED;
+         }
+
+         /* If d is greater than 255, we can't just use the bit map, so set up
+            for the UTF-8 supporting class type. If we are not caseless, we can
+            just set up a single range. If we are caseless, the characters < 256
+            are handled with a bitmap, in order to get the case-insensitive
+            handling. */
+
+         /* We use the bit map if the range is entirely < 255, or if part of it
+            is < 255 and matching is caseless. */
+
+         for (; c <= d; c++) {
+           class[c / 8] |= (1 << (c & 7));
+           if ((options & PCRE_CASELESS) != 0) {
+             int uc = cd->fcc[c];      /* flip case */
+             class[uc / 8] |= (1 << (uc & 7));
+           }
+           class_charcount++;  /* in case a one-char range */
+           class_lastchar = c;
+         }
+
+         continue;             /* Go get the next char in the class */
+       }
+
+       /* Handle a lone single character - we can get here for a normal
+          non-escape char, or after \ that introduces a single character. */
+
+      LONE_SINGLE_CHARACTER:
+
+       /* Handle a multibyte character */
+
+       /* Handle a single-byte character */
+       {
+         class[c / 8] |= (1 << (c & 7));
+         if ((options & PCRE_CASELESS) != 0) {
+           c = cd->fcc[c];     /* flip case */
+           class[c / 8] |= (1 << (c & 7));
+         }
+         class_charcount++;
+         class_lastchar = c;
+       }
+      }
+
+      /* Loop until ']' reached; the check for end of string happens inside the
+         loop. This "while" is the end of the "do" above. */
+
+      while ((c = *(++ptr)) != ']' || inescq);
+
+      /* If class_charcount is 1, we saw precisely one character with a value <
+         256. In UTF-8 mode, we can optimize if there were no characters >= 256 and
+         the one character is < 128. In non-UTF-8 mode we can always optimize.
+
+         The optimization throws away the bit map. We turn the item into a
+         1-character OP_CHARS if it's positive, or OP_NOT if it's negative. Note
+         that OP_NOT does not support multibyte characters. In the positive case, it
+         can cause firstbyte to be set. Otherwise, there can be no first char if
+         this item is first, whatever repeat count may follow. In the case of
+         reqbyte, save the previous value for reinstating. */
+
+      if (class_charcount == 1) {
+       zeroreqbyte = reqbyte;
+       if (negate_class) {
+         if (firstbyte == REQ_UNSET)
+           firstbyte = REQ_NONE;
+         zerofirstbyte = firstbyte;
+         *code++ = OP_NOT;
+       } else {
+         if (firstbyte == REQ_UNSET) {
+           zerofirstbyte = REQ_NONE;
+           firstbyte = class_lastchar | req_caseopt;
+         } else {
+           zerofirstbyte = firstbyte;
+           reqbyte = class_lastchar | req_caseopt | cd->req_varyopt;
+         }
+         *code++ = OP_CHARS;
+         *code++ = 1;
+       }
+       *code++ = class_lastchar;
+       break;                  /* End of class handling */
+      }
+
+
+      /* End of 1-byte optimization */
+      /* Otherwise, if this is the first thing in the branch, there can be no
+         first char setting, whatever the repeat count. Any reqbyte setting must
+         remain unchanged after any kind of repeat. */
+      if (firstbyte == REQ_UNSET)
+       firstbyte = REQ_NONE;
+      zerofirstbyte = firstbyte;
+      zeroreqbyte = reqbyte;
+
+      /* If there are characters with values > 255, we have to compile an
+         extended class, with its own opcode. If there are no characters < 256,
+         we can omit the bitmap. */
+
+
+      /* If there are no characters > 255, negate the 32-byte map if necessary,
+         and copy it into the code vector. If this is the first thing in the branch,
+         there can be no first char setting, whatever the repeat count. Any reqbyte
+         setting must remain unchanged after any kind of repeat. */
+
+      if (negate_class) {
+       *code++ = OP_NCLASS;
+       for (c = 0; c < 32; c++)
+         code[c] = ~class[c];
+      } else {
+       *code++ = OP_CLASS;
+       memcpy(code, class, 32);
+      }
+      code += 32;
+      break;
+
+      /* Various kinds of repeat */
+
+    case '{':
+      if (!is_counted_repeat(ptr + 1))
+       goto NORMAL_CHAR;
+      ptr = read_repeat_counts(ptr + 1, &repeat_min, &repeat_max, errorptr);
+      if (*errorptr != NULL)
+       goto FAILED;
+      goto REPEAT;
+
+    case '*':
+      repeat_min = 0;
+      repeat_max = -1;
+      goto REPEAT;
+
+    case '+':
+      repeat_min = 1;
+      repeat_max = -1;
+      goto REPEAT;
+
+    case '?':
+      repeat_min = 0;
+      repeat_max = 1;
+
+    REPEAT:
+      if (previous == NULL) {
+       *errorptr = ERR9;
+       goto FAILED;
+      }
+
+      if (repeat_min == 0) {
+       firstbyte = zerofirstbyte;      /* Adjust for zero repeat */
+       reqbyte = zeroreqbyte;  /* Ditto */
+      }
+
+      /* Remember whether this is a variable length repeat */
+
+      reqvary = (repeat_min == repeat_max) ? 0 : REQ_VARY;
+
+      op_type = 0;             /* Default single-char op codes */
+      possessive_quantifier = FALSE;   /* Default not possessive quantifier */
+
+      /* Save start of previous item, in case we have to move it up to make space
+         for an inserted OP_ONCE for the additional '+' extension. */
+
+      tempcode = previous;
+
+      /* If the next character is '+', we have a possessive quantifier. This
+         implies greediness, whatever the setting of the PCRE_UNGREEDY option.
+         If the next character is '?' this is a minimizing repeat, by default,
+         but if PCRE_UNGREEDY is set, it works the other way round. We change the
+         repeat type to the non-default. */
+
+      if (ptr[1] == '+') {
+       repeat_type = 0;        /* Force greedy */
+       possessive_quantifier = TRUE;
+       ptr++;
+      } else if (ptr[1] == '?') {
+       repeat_type = greedy_non_default;
+       ptr++;
+      } else
+       repeat_type = greedy_default;
+
+      /* If previous was a recursion, we need to wrap it inside brackets so that
+         it can be replicated if necessary. */
+
+      if (*previous == OP_RECURSE) {
+       memmove(previous + 1 + LINK_SIZE, previous, 1 + LINK_SIZE);
+       code += 1 + LINK_SIZE;
+       *previous = OP_BRA;
+       PUT(previous, 1, code - previous);
+       *code = OP_KET;
+       PUT(code, 1, code - previous);
+       code += 1 + LINK_SIZE;
+      }
+
+      /* If previous was a string of characters, chop off the last one and use it
+         as the subject of the repeat. If there was only one character, we can
+         abolish the previous item altogether. If a one-char item has a minumum of
+         more than one, ensure that it is set in reqbyte - it might not be if a
+         sequence such as x{3} is the first thing in a branch because the x will
+         have gone into firstbyte instead.  */
+
+      if (*previous == OP_CHARS) {
+       /* Deal with UTF-8 characters that take up more than one byte. It's
+          easier to write this out separately than try to macrify it. Use c to
+          hold the length of the character in bytes, plus 0x80 to flag that it's a
+          length rather than a small character. */
+
+
+       /* Handle the case of a single byte - either with no UTF8 support, or
+          with UTF-8 disabled, or for a UTF-8 character < 128. */
+
+       {
+         c = *(--code);
+         if (code == previous + 2) {   /* There was only one character */
+           code = previous;    /* Abolish the previous item */
+           if (repeat_min > 1)
+             reqbyte = c | req_caseopt | cd->req_varyopt;
+         } else {
+           previous[1]--;      /* adjust length */
+           tempcode = code;    /* Adjust position to be moved for '+' */
+         }
+       }
+
+       goto OUTPUT_SINGLE_REPEAT;      /* Code shared with single character types */
+      }
+
+      /* If previous was a single negated character ([^a] or similar), we use
+         one of the special opcodes, replacing it. The code is shared with single-
+         character repeats by setting opt_type to add a suitable offset into
+         repeat_type. OP_NOT is currently used only for single-byte chars. */
+
+      else if (*previous == OP_NOT) {
+       op_type = OP_NOTSTAR - OP_STAR; /* Use "not" opcodes */
+       c = previous[1];
+       code = previous;
+       goto OUTPUT_SINGLE_REPEAT;
+      }
+
+      /* If previous was a character type match (\d or similar), abolish it and
+         create a suitable repeat item. The code is shared with single-character
+         repeats by setting op_type to add a suitable offset into repeat_type. */
+
+      else if (*previous < OP_EODN) {
+       op_type = OP_TYPESTAR - OP_STAR;        /* Use type opcodes */
+       c = *previous;
+       code = previous;
+
+      OUTPUT_SINGLE_REPEAT:
+
+       /* If the maximum is zero then the minimum must also be zero; Perl allows
+          this case, so we do too - by simply omitting the item altogether. */
+
+       if (repeat_max == 0)
+         goto END_REPEAT;
+
+       /* Combine the op_type with the repeat_type */
+
+       repeat_type += op_type;
+
+       /* A minimum of zero is handled either as the special case * or ?, or as
+          an UPTO, with the maximum given. */
+
+       if (repeat_min == 0) {
+         if (repeat_max == -1)
+           *code++ = OP_STAR + repeat_type;
+         else if (repeat_max == 1)
+           *code++ = OP_QUERY + repeat_type;
+         else {
+           *code++ = OP_UPTO + repeat_type;
+           PUT2INC(code, 0, repeat_max);
+         }
+       }
+
+       /* The case {1,} is handled as the special case + */
+
+       else if (repeat_min == 1 && repeat_max == -1)
+         *code++ = OP_PLUS + repeat_type;
+
+       /* The case {n,n} is just an EXACT, while the general case {n,m} is
+          handled as an EXACT followed by an UPTO. An EXACT of 1 is optimized. */
+
+       else {
+         if (repeat_min != 1) {
+           *code++ = OP_EXACT + op_type;       /* NB EXACT doesn't have repeat_type */
+           PUT2INC(code, 0, repeat_min);
+         }
+
+         /* If the mininum is 1 and the previous item was a character string,
+            we either have to put back the item that got cancelled if the string
+            length was 1, or add the character back onto the end of a longer
+            string. For a character type nothing need be done; it will just get
+            put back naturally. Note that the final character is always going to
+            get added below, so we leave code ready for its insertion. */
+
+         else if (*previous == OP_CHARS) {
+           if (code == previous)
+             code += 2;
+           else
+             /* In UTF-8 mode, a multibyte char has its length in c, with the 0x80
+                bit set as a flag. The length will always be between 2 and 6. */
+
+             previous[1]++;
+         }
+
+         /*  For a single negated character we also have to put back the
+            item that got cancelled. At present this applies only to single byte
+            characters in any mode. */
+
+         else if (*previous == OP_NOT)
+           code++;
+
+         /* If the maximum is unlimited, insert an OP_STAR. Before doing so,
+            we have to insert the character for the previous code. In UTF-8 mode,
+            long characters have their length in c, with the 0x80 bit as a flag. */
+
+         if (repeat_max < 0) {
+           *code++ = c;
+           *code++ = OP_STAR + repeat_type;
+         }
+
+         /* Else insert an UPTO if the max is greater than the min, again
+            preceded by the character, for the previously inserted code. */
+
+         else if (repeat_max != repeat_min) {
+           *code++ = c;
+           repeat_max -= repeat_min;
+           *code++ = OP_UPTO + repeat_type;
+           PUT2INC(code, 0, repeat_max);
+         }
+       }
+
+       /* The character or character type itself comes last in all cases. */
+
+
+       *code++ = c;
+      }
+
+      /* If previous was a character class or a back reference, we put the repeat
+         stuff after it, but just skip the item if the repeat was {0,0}. */
+
+      else if (*previous == OP_CLASS ||
+              *previous == OP_NCLASS || *previous == OP_REF) {
+       if (repeat_max == 0) {
+         code = previous;
+         goto END_REPEAT;
+       }
+       if (repeat_min == 0 && repeat_max == -1)
+         *code++ = OP_CRSTAR + repeat_type;
+       else if (repeat_min == 1 && repeat_max == -1)
+         *code++ = OP_CRPLUS + repeat_type;
+       else if (repeat_min == 0 && repeat_max == 1)
+         *code++ = OP_CRQUERY + repeat_type;
+       else {
+         *code++ = OP_CRRANGE + repeat_type;
+         PUT2INC(code, 0, repeat_min);
+         if (repeat_max == -1)
+           repeat_max = 0;     /* 2-byte encoding for max */
+         PUT2INC(code, 0, repeat_max);
+       }
+      }
+
+      /* If previous was a bracket group, we may have to replicate it in certain
+         cases. */
+
+      else if (*previous >= OP_BRA || *previous == OP_ONCE ||
+              *previous == OP_COND) {
+       register int i;
+       int ketoffset = 0;
+       int len = code - previous;
+       uschar *bralink = NULL;
+
+       /* If the maximum repeat count is unlimited, find the end of the bracket
+          by scanning through from the start, and compute the offset back to it
+          from the current code pointer. There may be an OP_OPT setting following
+          the final KET, so we can't find the end just by going back from the code
+          pointer. */
+
+       if (repeat_max == -1) {
+         register uschar *ket = previous;
+         do
+           ket += GET(ket, 1);
+         while (*ket != OP_KET);
+         ketoffset = code - ket;
+       }
+
+       /* The case of a zero minimum is special because of the need to stick
+          OP_BRAZERO in front of it, and because the group appears once in the
+          data, whereas in other cases it appears the minimum number of times. For
+          this reason, it is simplest to treat this case separately, as otherwise
+          the code gets far too messy. There are several special subcases when the
+          minimum is zero. */
+
+       if (repeat_min == 0) {
+         /* If the maximum is also zero, we just omit the group from the output
+            altogether. */
+
+         if (repeat_max == 0) {
+           code = previous;
+           goto END_REPEAT;
+         }
+
+         /* If the maximum is 1 or unlimited, we just have to stick in the
+            BRAZERO and do no more at this point. However, we do need to adjust
+            any OP_RECURSE calls inside the group that refer to the group itself or
+            any internal group, because the offset is from the start of the whole
+            regex. Temporarily terminate the pattern while doing this. */
+
+         if (repeat_max <= 1) {
+           *code = OP_END;
+           adjust_recurse(previous, 1, utf8, cd);
+           memmove(previous + 1, previous, len);
+           code++;
+           *previous++ = OP_BRAZERO + repeat_type;
+         }
+
+         /* If the maximum is greater than 1 and limited, we have to replicate
+            in a nested fashion, sticking OP_BRAZERO before each set of brackets.
+            The first one has to be handled carefully because it's the original
+            copy, which has to be moved up. The remainder can be handled by code
+            that is common with the non-zero minimum case below. We have to
+            adjust the value or repeat_max, since one less copy is required. Once
+            again, we may have to adjust any OP_RECURSE calls inside the group. */
+
+         else {
+           int offset;
+           *code = OP_END;
+           adjust_recurse(previous, 2 + LINK_SIZE, utf8, cd);
+           memmove(previous + 2 + LINK_SIZE, previous, len);
+           code += 2 + LINK_SIZE;
+           *previous++ = OP_BRAZERO + repeat_type;
+           *previous++ = OP_BRA;
+
+           /* We chain together the bracket offset fields that have to be
+              filled in later when the ends of the brackets are reached. */
+
+           offset = (bralink == NULL) ? 0 : previous - bralink;
+           bralink = previous;
+           PUTINC(previous, 0, offset);
+         }
+
+         repeat_max--;
+       }
+
+       /* If the minimum is greater than zero, replicate the group as many
+          times as necessary, and adjust the maximum to the number of subsequent
+          copies that we need. If we set a first char from the group, and didn't
+          set a required char, copy the latter from the former. */
+
+       else {
+         if (repeat_min > 1) {
+           if (groupsetfirstbyte && reqbyte < 0)
+             reqbyte = firstbyte;
+           for (i = 1; i < repeat_min; i++) {
+             memcpy(code, previous, len);
+             code += len;
+           }
+         }
+         if (repeat_max > 0)
+           repeat_max -= repeat_min;
+       }
+
+       /* This code is common to both the zero and non-zero minimum cases. If
+          the maximum is limited, it replicates the group in a nested fashion,
+          remembering the bracket starts on a stack. In the case of a zero minimum,
+          the first one was set up above. In all cases the repeat_max now specifies
+          the number of additional copies needed. */
+
+       if (repeat_max >= 0) {
+         for (i = repeat_max - 1; i >= 0; i--) {
+           *code++ = OP_BRAZERO + repeat_type;
+
+           /* All but the final copy start a new nesting, maintaining the
+              chain of brackets outstanding. */
+
+           if (i != 0) {
+             int offset;
+             *code++ = OP_BRA;
+             offset = (bralink == NULL) ? 0 : code - bralink;
+             bralink = code;
+             PUTINC(code, 0, offset);
+           }
+
+           memcpy(code, previous, len);
+           code += len;
+         }
+
+         /* Now chain through the pending brackets, and fill in their length
+            fields (which are holding the chain links pro tem). */
+
+         while (bralink != NULL) {
+           int oldlinkoffset;
+           int offset = code - bralink + 1;
+           uschar *bra = code - offset;
+           oldlinkoffset = GET(bra, 1);
+           bralink = (oldlinkoffset == 0) ? NULL : bralink - oldlinkoffset;
+           *code++ = OP_KET;
+           PUTINC(code, 0, offset);
+           PUT(bra, 1, offset);
+         }
+       }
+
+       /* If the maximum is unlimited, set a repeater in the final copy. We
+          can't just offset backwards from the current code point, because we
+          don't know if there's been an options resetting after the ket. The
+          correct offset was computed above. */
+
+       else
+         code[-ketoffset] = OP_KETRMAX + repeat_type;
+      }
+
+      /* Else there's some kind of shambles */
+
+      else {
+       *errorptr = ERR11;
+       goto FAILED;
+      }
+
+      /* If the character following a repeat is '+', we wrap the entire repeated
+         item inside OP_ONCE brackets. This is just syntactic sugar, taken from
+         Sun's Java package. The repeated item starts at tempcode, not at previous,
+         which might be the first part of a string whose (former) last char we
+         repeated. However, we don't support '+' after a greediness '?'. */
+
+      if (possessive_quantifier) {
+       int len = code - tempcode;
+       memmove(tempcode + 1 + LINK_SIZE, tempcode, len);
+       code += 1 + LINK_SIZE;
+       len += 1 + LINK_SIZE;
+       tempcode[0] = OP_ONCE;
+       *code++ = OP_KET;
+       PUTINC(code, 0, len);
+       PUT(tempcode, 1, len);
+      }
+
+      /* In all case we no longer have a previous item. We also set the
+         "follows varying string" flag for subsequently encountered reqbytes if
+         it isn't already set and we have just passed a varying length item. */
+
+    END_REPEAT:
+      previous = NULL;
+      cd->req_varyopt |= reqvary;
+      break;
+
+
+      /* Start of nested bracket sub-expression, or comment or lookahead or
+         lookbehind or option setting or condition. First deal with special things
+         that can come after a bracket; all are introduced by ?, and the appearance
+         of any of them means that this is not a referencing group. They were
+         checked for validity in the first pass over the string, so we don't have to
+         check for syntax errors here.  */
+
+    case '(':
+      newoptions = options;
+      skipbytes = 0;
+
+      if (*(++ptr) == '?') {
+       int set, unset;
+       int *optset;
+
+       switch (*(++ptr)) {
+       case '#':               /* Comment; skip to ket */
+         ptr++;
+         while (*ptr != ')')
+           ptr++;
+         continue;
+
+       case ':':               /* Non-extracting bracket */
+         bravalue = OP_BRA;
+         ptr++;
+         break;
+
+       case '(':
+         bravalue = OP_COND;   /* Conditional group */
+
+         /* Condition to test for recursion */
+
+         if (ptr[1] == 'R') {
+           code[1 + LINK_SIZE] = OP_CREF;
+           PUT2(code, 2 + LINK_SIZE, CREF_RECURSE);
+           skipbytes = 3;
+           ptr += 3;
+         }
+
+         /* Condition to test for a numbered subpattern match. We know that
+            if a digit follows ( then there will just be digits until ) because
+            the syntax was checked in the first pass. */
+
+         else if ((digitab[ptr[1]] && ctype_digit) != 0) {
+           int condref;        /* Don't amalgamate; some compilers */
+           condref = *(++ptr) - '0';   /* grumble at autoincrement in declaration */
+           while (*(++ptr) != ')')
+             condref = condref * 10 + *ptr - '0';
+           if (condref == 0) {
+             *errorptr = ERR35;
+             goto FAILED;
+           }
+           ptr++;
+           code[1 + LINK_SIZE] = OP_CREF;
+           PUT2(code, 2 + LINK_SIZE, condref);
+           skipbytes = 3;
+         }
+         /* For conditions that are assertions, we just fall through, having
+            set bravalue above. */
+         break;
+
+       case '=':               /* Positive lookahead */
+         bravalue = OP_ASSERT;
+         ptr++;
+         break;
+
+       case '!':               /* Negative lookahead */
+         bravalue = OP_ASSERT_NOT;
+         ptr++;
+         break;
+
+       case '<':               /* Lookbehinds */
+         switch (*(++ptr)) {
+         case '=':             /* Positive lookbehind */
+           bravalue = OP_ASSERTBACK;
+           ptr++;
+           break;
+
+         case '!':             /* Negative lookbehind */
+           bravalue = OP_ASSERTBACK_NOT;
+           ptr++;
+           break;
+         }
+         break;
+
+       case '>':               /* One-time brackets */
+         bravalue = OP_ONCE;
+         ptr++;
+         break;
+
+       case 'C':               /* Callout - may be followed by digits */
+         *code++ = OP_CALLOUT;
+         {
+           int n = 0;
+           while ((digitab[*(++ptr)] & ctype_digit) != 0)
+             n = n * 10 + *ptr - '0';
+           if (n > 255) {
+             *errorptr = ERR38;
+             goto FAILED;
+           }
+           *code++ = n;
+         }
+         previous = NULL;
+         continue;
+
+       case 'P':               /* Named subpattern handling */
+         if (*(++ptr) == '<') {        /* Definition */
+           int i, namelen;
+           uschar *slot = cd->name_table;
+           const uschar *name; /* Don't amalgamate; some compilers */
+           name = ++ptr;       /* grumble at autoincrement in declaration */
+
+           while (*ptr++ != '>') ;
+           namelen = ptr - name - 1;
+
+           for (i = 0; i < cd->names_found; i++) {
+             int crc = memcmp(name, slot + 2, namelen);
+             if (crc == 0) {
+               if (slot[2 + namelen] == 0) {
+                 *errorptr = ERR43;
+                 goto FAILED;
+               }
+               crc = -1;       /* Current name is substring */
+             }
+             if (crc < 0) {
+               memmove(slot + cd->name_entry_size, slot,
+                       (cd->names_found - i) * cd->name_entry_size);
+               break;
+             }
+             slot += cd->name_entry_size;
+           }
+
+           PUT2(slot, 0, *brackets + 1);
+           memcpy(slot + 2, name, namelen);
+           slot[2 + namelen] = 0;
+           cd->names_found++;
+           goto NUMBERED_GROUP;
+         }
+
+         if (*ptr == '=' || *ptr == '>') {     /* Reference or recursion */
+           int i, namelen;
+           int type = *ptr++;
+           const uschar *name = ptr;
+           uschar *slot = cd->name_table;
+
+           while (*ptr != ')')
+             ptr++;
+           namelen = ptr - name;
+
+           for (i = 0; i < cd->names_found; i++) {
+             if (strncmp((char *) name, (char *) slot + 2, namelen) == 0)
+               break;
+             slot += cd->name_entry_size;
+           }
+           if (i >= cd->names_found) {
+             *errorptr = ERR15;
+             goto FAILED;
+           }
+
+           recno = GET2(slot, 0);
+
+           if (type == '>')
+             goto HANDLE_RECURSION;    /* A few lines below */
+
+           /* Back reference */
+
+           previous = code;
+           *code++ = OP_REF;
+           PUT2INC(code, 0, recno);
+           cd->backref_map |= (recno < 32) ? (1 << recno) : 1;
+           if (recno > cd->top_backref)
+             cd->top_backref = recno;
+           continue;
+         }
+
+         /* Should never happen */
+         break;
+
+       case 'R':               /* Pattern recursion */
+         ptr++;                /* Same as (?0)      */
+         /* Fall through */
+
+         /* Recursion or "subroutine" call */
+
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         {
+           const uschar *called;
+           recno = 0;
+           while ((digitab[*ptr] & ctype_digit) != 0)
+             recno = recno * 10 + *ptr++ - '0';
+
+           /* Come here from code above that handles a named recursion */
+
+         HANDLE_RECURSION:
+
+           previous = code;
+
+           /* Find the bracket that is being referenced. Temporarily end the
+              regex in case it doesn't exist. */
+
+           *code = OP_END;
+           called = (recno == 0) ?
+             cd->start_code : find_bracket(cd->start_code, utf8, recno);
+
+           if (called == NULL) {
+             *errorptr = ERR15;
+             goto FAILED;
+           }
+
+           /* If the subpattern is still open, this is a recursive call. We
+              check to see if this is a left recursion that could loop for ever,
+              and diagnose that case. */
+
+           if (GET(called, 1) == 0
+               && could_be_empty(called, code, bcptr, utf8)) {
+             *errorptr = ERR40;
+             goto FAILED;
+           }
+
+           /* Insert the recursion/subroutine item */
+
+           *code = OP_RECURSE;
+           PUT(code, 1, called - cd->start_code);
+           code += 1 + LINK_SIZE;
+         }
+         continue;
+
+         /* Character after (? not specially recognized */
+
+       default:                /* Option setting */
+         set = unset = 0;
+         optset = &set;
+
+         while (*ptr != ')' && *ptr != ':') {
+           switch (*ptr++) {
+           case '-':
+             optset = &unset;
+             break;
+
+           case 'i':
+             *optset |= PCRE_CASELESS;
+             break;
+           case 'm':
+             *optset |= PCRE_MULTILINE;
+             break;
+           case 's':
+             *optset |= PCRE_DOTALL;
+             break;
+           case 'x':
+             *optset |= PCRE_EXTENDED;
+             break;
+           case 'U':
+             *optset |= PCRE_UNGREEDY;
+             break;
+           case 'X':
+             *optset |= PCRE_EXTRA;
+             break;
+           }
+         }
+
+         /* Set up the changed option bits, but don't change anything yet. */
+
+         newoptions = (options | set) & (~unset);
+
+         /* If the options ended with ')' this is not the start of a nested
+            group with option changes, so the options change at this level. Compile
+            code to change the ims options if this setting actually changes any of
+            them. We also pass the new setting back so that it can be put at the
+            start of any following branches, and when this group ends (if we are in
+            a group), a resetting item can be compiled.
+
+            Note that if this item is right at the start of the pattern, the
+            options will have been abstracted and made global, so there will be no
+            change to compile. */
+
+         if (*ptr == ')') {
+           if ((options & PCRE_IMS) != (newoptions & PCRE_IMS)) {
+             *code++ = OP_OPT;
+             *code++ = newoptions & PCRE_IMS;
+           }
+
+           /* Change options at this level, and pass them back for use
+              in subsequent branches. Reset the greedy defaults and the case
+              value for firstbyte and reqbyte. */
+
+           *optionsptr = options = newoptions;
+           greedy_default = ((newoptions & PCRE_UNGREEDY) != 0);
+           greedy_non_default = greedy_default ^ 1;
+           req_caseopt = ((options & PCRE_CASELESS) != 0) ? REQ_CASELESS : 0;
+
+           previous = NULL;    /* This item can't be repeated */
+           continue;           /* It is complete */
+         }
+
+         /* If the options ended with ':' we are heading into a nested group
+            with possible change of options. Such groups are non-capturing and are
+            not assertions of any kind. All we need to do is skip over the ':';
+            the newoptions value is handled below. */
+
+         bravalue = OP_BRA;
+         ptr++;
+       }
+      }
+
+      /* If PCRE_NO_AUTO_CAPTURE is set, all unadorned brackets become
+         non-capturing and behave like (?:...) brackets */
+
+      else if ((options & PCRE_NO_AUTO_CAPTURE) != 0) {
+       bravalue = OP_BRA;
+      }
+
+      /* Else we have a referencing group; adjust the opcode. If the bracket
+         number is greater than EXTRACT_BASIC_MAX, we set the opcode one higher, and
+         arrange for the true number to follow later, in an OP_BRANUMBER item. */
+
+      else {
+      NUMBERED_GROUP:
+       if (++(*brackets) > EXTRACT_BASIC_MAX) {
+         bravalue = OP_BRA + EXTRACT_BASIC_MAX + 1;
+         code[1 + LINK_SIZE] = OP_BRANUMBER;
+         PUT2(code, 2 + LINK_SIZE, *brackets);
+         skipbytes = 3;
+       } else
+         bravalue = OP_BRA + *brackets;
+      }
+
+      /* Process nested bracketed re. Assertions may not be repeated, but other
+         kinds can be. We copy code into a non-register variable in order to be able
+         to pass its address because some compilers complain otherwise. Pass in a
+         new setting for the ims options if they have changed. */
+
+      previous = (bravalue >= OP_ONCE) ? code : NULL;
+      *code = bravalue;
+      tempcode = code;
+      tempreqvary = cd->req_varyopt;   /* Save value before bracket */
+
+      if (!compile_regex(newoptions,   /* The complete new option state */
+                        options & PCRE_IMS,    /* The previous ims option state */
+                        brackets,      /* Extracting bracket count */
+                        &tempcode,     /* Where to put code (updated) */
+                        &ptr,  /* Input pointer (updated) */
+                        errorptr,      /* Where to put an error message */
+                        (bravalue == OP_ASSERTBACK || bravalue == OP_ASSERTBACK_NOT),  /* TRUE if back assert */
+                        skipbytes,     /* Skip over OP_COND/OP_BRANUMBER */
+                        &subfirstbyte, /* For possible first char */
+                        &subreqbyte,   /* For possible last char */
+                        bcptr, /* Current branch chain */
+                        cd))   /* Tables block */
+       goto FAILED;
+
+      /* At the end of compiling, code is still pointing to the start of the
+         group, while tempcode has been updated to point past the end of the group
+         and any option resetting that may follow it. The pattern pointer (ptr)
+         is on the bracket. */
+
+      /* If this is a conditional bracket, check that there are no more than
+         two branches in the group. */
+
+      else if (bravalue == OP_COND) {
+       uschar *tc = code;
+       condcount = 0;
+
+       do {
+         condcount++;
+         tc += GET(tc, 1);
+       }
+       while (*tc != OP_KET);
+
+       if (condcount > 2) {
+         *errorptr = ERR27;
+         goto FAILED;
+       }
+
+       /* If there is just one branch, we must not make use of its firstbyte or
+          reqbyte, because this is equivalent to an empty second branch. */
+
+       if (condcount == 1)
+         subfirstbyte = subreqbyte = REQ_NONE;
+      }
+
+      /* Handle updating of the required and first characters. Update for normal
+         brackets of all kinds, and conditions with two branches (see code above).
+         If the bracket is followed by a quantifier with zero repeat, we have to
+         back off. Hence the definition of zeroreqbyte and zerofirstbyte outside the
+         main loop so that they can be accessed for the back off. */
+
+      zeroreqbyte = reqbyte;
+      zerofirstbyte = firstbyte;
+      groupsetfirstbyte = FALSE;
+
+      if (bravalue >= OP_BRA || bravalue == OP_ONCE || bravalue == OP_COND) {
+       /* If we have not yet set a firstbyte in this branch, take it from the
+          subpattern, remembering that it was set here so that a repeat of more
+          than one can replicate it as reqbyte if necessary. If the subpattern has
+          no firstbyte, set "none" for the whole branch. In both cases, a zero
+          repeat forces firstbyte to "none". */
+
+       if (firstbyte == REQ_UNSET) {
+         if (subfirstbyte >= 0) {
+           firstbyte = subfirstbyte;
+           groupsetfirstbyte = TRUE;
+         } else
+           firstbyte = REQ_NONE;
+         zerofirstbyte = REQ_NONE;
+       }
+
+       /* If firstbyte was previously set, convert the subpattern's firstbyte
+          into reqbyte if there wasn't one, using the vary flag that was in
+          existence beforehand. */
+
+       else if (subfirstbyte >= 0 && subreqbyte < 0)
+         subreqbyte = subfirstbyte | tempreqvary;
+
+       /* If the subpattern set a required byte (or set a first byte that isn't
+          really the first byte - see above), set it. */
+
+       if (subreqbyte >= 0)
+         reqbyte = subreqbyte;
+      }
+
+      /* For a forward assertion, we take the reqbyte, if set. This can be
+         helpful if the pattern that follows the assertion doesn't set a different
+         char. For example, it's useful for /(?=abcde).+/. We can't set firstbyte
+         for an assertion, however because it leads to incorrect effect for patterns
+         such as /(?=a)a.+/ when the "real" "a" would then become a reqbyte instead
+         of a firstbyte. This is overcome by a scan at the end if there's no
+         firstbyte, looking for an asserted first char. */
+
+      else if (bravalue == OP_ASSERT && subreqbyte >= 0)
+       reqbyte = subreqbyte;
+
+      /* Now update the main code pointer to the end of the group. */
+
+      code = tempcode;
+
+      /* Error if hit end of pattern */
+
+      if (*ptr != ')') {
+       *errorptr = ERR14;
+       goto FAILED;
+      }
+      break;
+
+      /* Check \ for being a real metacharacter; if not, fall through and handle
+         it as a data character at the start of a string. Escape items are checked
+         for validity in the pre-compiling pass. */
+
+    case '\\':
+      tempptr = ptr;
+      c = check_escape(&ptr, errorptr, *brackets, options, FALSE);
+
+      /* Handle metacharacters introduced by \. For ones like \d, the ESC_ values
+         are arranged to be the negation of the corresponding OP_values. For the
+         back references, the values are ESC_REF plus the reference number. Only
+         back references and those types that consume a character may be repeated.
+         We can test for values between ESC_b and ESC_Z for the latter; this may
+         have to change if any new ones are ever created. */
+
+      if (c < 0) {
+       if (-c == ESC_Q) {      /* Handle start of quoted string */
+         if (ptr[1] == '\\' && ptr[2] == 'E')
+           ptr += 2;           /* avoid empty string */
+         else
+           inescq = TRUE;
+         continue;
+       }
+
+       /* For metasequences that actually match a character, we disable the
+          setting of a first character if it hasn't already been set. */
+
+       if (firstbyte == REQ_UNSET && -c > ESC_b && -c < ESC_Z)
+         firstbyte = REQ_NONE;
+
+       /* Set values to reset to if this is followed by a zero repeat. */
+
+       zerofirstbyte = firstbyte;
+       zeroreqbyte = reqbyte;
+
+       /* Back references are handled specially */
+
+       if (-c >= ESC_REF) {
+         int number = -c - ESC_REF;
+         previous = code;
+         *code++ = OP_REF;
+         PUT2INC(code, 0, number);
+       } else {
+         previous = (-c > ESC_b && -c < ESC_Z) ? code : NULL;
+         *code++ = -c;
+       }
+       continue;
+      }
+
+      /* Data character: reset and fall through */
+
+      ptr = tempptr;
+      c = '\\';
+
+      /* Handle a run of data characters until a metacharacter is encountered.
+         The first character is guaranteed not to be whitespace or # when the
+         extended flag is set. */
+
+    NORMAL_CHAR:
+    default:
+      previous = code;
+      *code = OP_CHARS;
+      code += 2;
+      length = 0;
+
+      do {
+       /* If in \Q...\E, check for the end; if not, we always have a literal */
+
+       if (inescq) {
+         if (c == '\\' && ptr[1] == 'E') {
+           inescq = FALSE;
+           ptr++;
+         } else {
+           *code++ = c;
+           length++;
+         }
+         continue;
+       }
+
+       /* Skip white space and comments for /x patterns */
+
+       if ((options & PCRE_EXTENDED) != 0) {
+         if ((cd->ctypes[c] & ctype_space) != 0)
+           continue;
+         if (c == '#') {
+           /* The space before the ; is to avoid a warning on a silly compiler
+              on the Macintosh. */
+           while ((c = *(++ptr)) != 0 && c != NEWLINE) ;
+           if (c == 0)
+             break;
+           continue;
+         }
+       }
+
+       /* Backslash may introduce a data char or a metacharacter. Escaped items
+          are checked for validity in the pre-compiling pass. Stop the string
+          before a metaitem. */
+
+       if (c == '\\') {
+         tempptr = ptr;
+         c = check_escape(&ptr, errorptr, *brackets, options, FALSE);
+         if (c < 0) {
+           ptr = tempptr;
+           break;
+         }
+
+         /* If a character is > 127 in UTF-8 mode, we have to turn it into
+            two or more bytes in the UTF-8 encoding. */
+
+       }
+
+       /* Ordinary character or single-char escape */
+
+       *code++ = c;
+       length++;
+      }
+
+      /* This "while" is the end of the "do" above. */
+
+      while (length < MAXLIT && (cd->ctypes[c = *(++ptr)] & ctype_meta) == 0);
+
+      /* Update the first and last requirements. These are always bytes, even in
+         UTF-8 mode. However, there is a special case to be considered when there
+         are only one or two characters. Because this gets messy in UTF-8 mode, the
+         code is kept separate. When we get here "length" contains the number of
+         bytes. */
+
+
+      /* This is the code for non-UTF-8 operation, either without UTF-8 support,
+         or when UTF-8 is not enabled. */
+
+      {
+       /* firstbyte was not previously set; take it from this string */
+
+       if (firstbyte == REQ_UNSET) {
+         if (length == 1) {
+           zerofirstbyte = REQ_NONE;
+           firstbyte = previous[2] | req_caseopt;
+           zeroreqbyte = reqbyte;
+         } else {
+           zerofirstbyte = firstbyte = previous[2] | req_caseopt;
+           zeroreqbyte = (length > 2) ?
+             (code[-2] | req_caseopt | cd->req_varyopt) : reqbyte;
+           reqbyte = code[-1] | req_caseopt | cd->req_varyopt;
+         }
+       }
+
+       /* firstbyte was previously set */
+
+       else {
+         zerofirstbyte = firstbyte;
+         zeroreqbyte = (length == 1) ? reqbyte :
+           code[-2] | req_caseopt | cd->req_varyopt;
+         reqbyte = code[-1] | req_caseopt | cd->req_varyopt;
+       }
+      }
+
+      /* Set the length in the data vector, and advance to the next state. */
+
+      previous[1] = length;
+      if (length < MAXLIT)
+       ptr--;
+      break;
+    }
+  }                            /* end of big loop */
+
+/* Control never reaches here by falling through, only by a goto for all the
+error states. Pass back the position in the pattern so that it can be displayed
+to the user for diagnosing the error. */
+
+FAILED:
+  *ptrptr = ptr;
+  return FALSE;
+}
+
+
+
+
+/*************************************************
+*     Compile sequence of alternatives           *
+*************************************************/
+
+/* On entry, ptr is pointing past the bracket character, but on return
+it points to the closing bracket, or vertical bar, or end of string.
+The code variable is pointing at the byte into which the BRA operator has been
+stored. If the ims options are changed at the start (for a (?ims: group) or
+during any branch, we need to insert an OP_OPT item at the start of every
+following branch to ensure they get set correctly at run time, and also pass
+the new options into every subsequent branch compile.
+
+Argument:
+  options        option bits, including any changes for this subpattern
+  oldims         previous settings of ims option bits
+  brackets       -> int containing the number of extracting brackets used
+  codeptr        -> the address of the current code pointer
+  ptrptr         -> the address of the current pattern pointer
+  errorptr       -> pointer to error message
+  lookbehind     TRUE if this is a lookbehind assertion
+  skipbytes      skip this many bytes at start (for OP_COND, OP_BRANUMBER)
+  firstbyteptr   place to put the first required character, or a negative number
+  reqbyteptr     place to put the last required character, or a negative number
+  bcptr          pointer to the chain of currently open branches
+  cd             points to the data block with tables pointers etc.
+
+Returns:      TRUE on success
+*/
+
+static BOOL
+compile_regex(int options, int oldims, int *brackets, uschar ** codeptr,
+             const uschar ** ptrptr, const char **errorptr, BOOL lookbehind,
+             int skipbytes, int *firstbyteptr, int *reqbyteptr,
+             branch_chain * bcptr, compile_data * cd)
+{
+  const uschar *ptr = *ptrptr;
+  uschar *code = *codeptr;
+  uschar *last_branch = code;
+  uschar *start_bracket = code;
+  uschar *reverse_count = NULL;
+  int firstbyte, reqbyte;
+  int branchfirstbyte, branchreqbyte;
+  branch_chain bc;
+
+  bc.outer = bcptr;
+  bc.current = code;
+
+  firstbyte = reqbyte = REQ_UNSET;
+
+/* Offset is set zero to mark that this bracket is still open */
+
+  PUT(code, 1, 0);
+  code += 1 + LINK_SIZE + skipbytes;
+
+/* Loop for each alternative branch */
+
+  for (;;) {
+    /* Handle a change of ims options at the start of the branch */
+
+    if ((options & PCRE_IMS) != oldims) {
+      *code++ = OP_OPT;
+      *code++ = options & PCRE_IMS;
+    }
+
+    /* Set up dummy OP_REVERSE if lookbehind assertion */
+
+    if (lookbehind) {
+      *code++ = OP_REVERSE;
+      reverse_count = code;
+      PUTINC(code, 0, 0);
+    }
+
+    /* Now compile the branch */
+
+    if (!compile_branch(&options, brackets, &code, &ptr, errorptr,
+                       &branchfirstbyte, &branchreqbyte, &bc, cd)) {
+      *ptrptr = ptr;
+      return FALSE;
+    }
+
+    /* If this is the first branch, the firstbyte and reqbyte values for the
+       branch become the values for the regex. */
+
+    if (*last_branch != OP_ALT) {
+      firstbyte = branchfirstbyte;
+      reqbyte = branchreqbyte;
+    }
+
+    /* If this is not the first branch, the first char and reqbyte have to
+       match the values from all the previous branches, except that if the previous
+       value for reqbyte didn't have REQ_VARY set, it can still match, and we set
+       REQ_VARY for the regex. */
+
+    else {
+      /* If we previously had a firstbyte, but it doesn't match the new branch,
+         we have to abandon the firstbyte for the regex, but if there was previously
+         no reqbyte, it takes on the value of the old firstbyte. */
+
+      if (firstbyte >= 0 && firstbyte != branchfirstbyte) {
+       if (reqbyte < 0)
+         reqbyte = firstbyte;
+       firstbyte = REQ_NONE;
+      }
+
+      /* If we (now or from before) have no firstbyte, a firstbyte from the
+         branch becomes a reqbyte if there isn't a branch reqbyte. */
+
+      if (firstbyte < 0 && branchfirstbyte >= 0 && branchreqbyte < 0)
+       branchreqbyte = branchfirstbyte;
+
+      /* Now ensure that the reqbytes match */
+
+      if ((reqbyte & ~REQ_VARY) != (branchreqbyte & ~REQ_VARY))
+       reqbyte = REQ_NONE;
+      else
+       reqbyte |= branchreqbyte;       /* To "or" REQ_VARY */
+    }
+
+    /* If lookbehind, check that this branch matches a fixed-length string,
+       and put the length into the OP_REVERSE item. Temporarily mark the end of
+       the branch with OP_END. */
+
+    if (lookbehind) {
+      int length;
+      *code = OP_END;
+      length = find_fixedlength(last_branch, options);
+      DPRINTF(("fixed length = %d\n", length));
+      if (length < 0) {
+       *errorptr = (length == -2) ? ERR36 : ERR25;
+       *ptrptr = ptr;
+       return FALSE;
+      }
+      PUT(reverse_count, 0, length);
+    }
+
+    /* Reached end of expression, either ')' or end of pattern. Go back through
+       the alternative branches and reverse the chain of offsets, with the field in
+       the BRA item now becoming an offset to the first alternative. If there are
+       no alternatives, it points to the end of the group. The length in the
+       terminating ket is always the length of the whole bracketed item. If any of
+       the ims options were changed inside the group, compile a resetting op-code
+       following, except at the very end of the pattern. Return leaving the pointer
+       at the terminating char. */
+
+    if (*ptr != '|') {
+      int length = code - last_branch;
+      do {
+       int prev_length = GET(last_branch, 1);
+       PUT(last_branch, 1, length);
+       length = prev_length;
+       last_branch -= length;
+      }
+      while (length > 0);
+
+      /* Fill in the ket */
+
+      *code = OP_KET;
+      PUT(code, 1, code - start_bracket);
+      code += 1 + LINK_SIZE;
+
+      /* Resetting option if needed */
+
+      if ((options & PCRE_IMS) != oldims && *ptr == ')') {
+       *code++ = OP_OPT;
+       *code++ = oldims;
+      }
+
+      /* Set values to pass back */
+
+      *codeptr = code;
+      *ptrptr = ptr;
+      *firstbyteptr = firstbyte;
+      *reqbyteptr = reqbyte;
+      return TRUE;
+    }
+
+    /* Another branch follows; insert an "or" node. Its length field points back
+       to the previous branch while the bracket remains open. At the end the chain
+       is reversed. It's done like this so that the start of the bracket has a
+       zero offset until it is closed, making it possible to detect recursion. */
+
+    *code = OP_ALT;
+    PUT(code, 1, code - last_branch);
+    bc.current = last_branch = code;
+    code += 1 + LINK_SIZE;
+    ptr++;
+  }
+/* Control never reaches here */
+}
+
+
+
+
+/*************************************************
+*          Check for anchored expression         *
+*************************************************/
+
+/* Try to find out if this is an anchored regular expression. Consider each
+alternative branch. If they all start with OP_SOD or OP_CIRC, or with a bracket
+all of whose alternatives start with OP_SOD or OP_CIRC (recurse ad lib), then
+it's anchored. However, if this is a multiline pattern, then only OP_SOD
+counts, since OP_CIRC can match in the middle.
+
+We can also consider a regex to be anchored if OP_SOM starts all its branches.
+This is the code for \G, which means "match at start of match position, taking
+into account the match offset".
+
+A branch is also implicitly anchored if it starts with .* and DOTALL is set,
+because that will try the rest of the pattern at all possible matching points,
+so there is no point trying again.... er ....
+
+.... except when the .* appears inside capturing parentheses, and there is a
+subsequent back reference to those parentheses. We haven't enough information
+to catch that case precisely.
+
+At first, the best we could do was to detect when .* was in capturing brackets
+and the highest back reference was greater than or equal to that level.
+However, by keeping a bitmap of the first 31 back references, we can catch some
+of the more common cases more precisely.
+
+Arguments:
+  code           points to start of expression (the bracket)
+  options        points to the options setting
+  bracket_map    a bitmap of which brackets we are inside while testing; this
+                  handles up to substring 31; after that we just have to take
+                  the less precise approach
+  backref_map    the back reference bitmap
+
+Returns:     TRUE or FALSE
+*/
+
+static BOOL
+is_anchored(register const uschar * code, int *options,
+           unsigned int bracket_map, unsigned int backref_map)
+{
+  do {
+    const uschar *scode =
+      first_significant_code(code + 1 + LINK_SIZE, options, PCRE_MULTILINE);
+    register int op = *scode;
+
+    /* Capturing brackets */
+
+    if (op > OP_BRA) {
+      int new_map;
+      op -= OP_BRA;
+      if (op > EXTRACT_BASIC_MAX)
+       op = GET2(scode, 2 + LINK_SIZE);
+      new_map = bracket_map | ((op < 32) ? (1 << op) : 1);
+      if (!is_anchored(scode, options, new_map, backref_map))
+       return FALSE;
+    }
+
+    /* Other brackets */
+
+    else if (op == OP_BRA || op == OP_ASSERT || op == OP_ONCE || op == OP_COND) {
+      if (!is_anchored(scode, options, bracket_map, backref_map))
+       return FALSE;
+    }
+
+    /* .* is not anchored unless DOTALL is set and it isn't in brackets that
+       are or may be referenced. */
+
+    else if ((op == OP_TYPESTAR || op == OP_TYPEMINSTAR) &&
+            (*options & PCRE_DOTALL) != 0) {
+      if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0)
+       return FALSE;
+    }
+
+    /* Check for explicit anchoring */
+
+    else if (op != OP_SOD && op != OP_SOM &&
+            ((*options & PCRE_MULTILINE) != 0 || op != OP_CIRC))
+      return FALSE;
+    code += GET(code, 1);
+  }
+  while (*code == OP_ALT);     /* Loop for each alternative */
+  return TRUE;
+}
+
+
+
+/*************************************************
+*         Check for starting with ^ or .*        *
+*************************************************/
+
+/* This is called to find out if every branch starts with ^ or .* so that
+"first char" processing can be done to speed things up in multiline
+matching and for non-DOTALL patterns that start with .* (which must start at
+the beginning or after \n). As in the case of is_anchored() (see above), we
+have to take account of back references to capturing brackets that contain .*
+because in that case we can't make the assumption.
+
+Arguments:
+  code           points to start of expression (the bracket)
+  bracket_map    a bitmap of which brackets we are inside while testing; this
+                  handles up to substring 31; after that we just have to take
+                  the less precise approach
+  backref_map    the back reference bitmap
+
+Returns:         TRUE or FALSE
+*/
+
+static BOOL
+is_startline(const uschar * code, unsigned int bracket_map,
+            unsigned int backref_map)
+{
+  do {
+    const uschar *scode = first_significant_code(code + 1 + LINK_SIZE, NULL, 0);
+    register int op = *scode;
+
+    /* Capturing brackets */
+
+    if (op > OP_BRA) {
+      int new_map;
+      op -= OP_BRA;
+      if (op > EXTRACT_BASIC_MAX)
+       op = GET2(scode, 2 + LINK_SIZE);
+      new_map = bracket_map | ((op < 32) ? (1 << op) : 1);
+      if (!is_startline(scode, new_map, backref_map))
+       return FALSE;
+    }
+
+    /* Other brackets */
+
+    else if (op == OP_BRA || op == OP_ASSERT || op == OP_ONCE || op == OP_COND) {
+      if (!is_startline(scode, bracket_map, backref_map))
+       return FALSE;
+    }
+
+    /* .* is not anchored unless DOTALL is set and it isn't in brackets that
+       may be referenced. */
+
+    else if (op == OP_TYPESTAR || op == OP_TYPEMINSTAR) {
+      if (scode[1] != OP_ANY || (bracket_map & backref_map) != 0)
+       return FALSE;
+    }
+
+    /* Check for explicit circumflex */
+
+    else if (op != OP_CIRC)
+      return FALSE;
+    code += GET(code, 1);
+  }
+  while (*code == OP_ALT);     /* Loop for each alternative */
+  return TRUE;
+}
+
+
+
+/*************************************************
+*       Check for asserted fixed first char      *
+*************************************************/
+
+/* During compilation, the "first char" settings from forward assertions are
+discarded, because they can cause conflicts with actual literals that follow.
+However, if we end up without a first char setting for an unanchored pattern,
+it is worth scanning the regex to see if there is an initial asserted first
+char. If all branches start with the same asserted char, or with a bracket all
+of whose alternatives start with the same asserted char (recurse ad lib), then
+we return that char, otherwise -1.
+
+Arguments:
+  code       points to start of expression (the bracket)
+  options    pointer to the options (used to check casing changes)
+  inassert   TRUE if in an assertion
+
+Returns:     -1 or the fixed first char
+*/
+
+static int
+find_firstassertedchar(const uschar * code, int *options, BOOL inassert)
+{
+  register int c = -1;
+  do {
+    int d;
+    const uschar *scode =
+      first_significant_code(code + 1 + LINK_SIZE, options, PCRE_CASELESS);
+    register int op = *scode;
+
+    if (op >= OP_BRA)
+      op = OP_BRA;
+
+    switch (op) {
+    default:
+      return -1;
+
+    case OP_BRA:
+    case OP_ASSERT:
+    case OP_ONCE:
+    case OP_COND:
+      if ((d = find_firstassertedchar(scode, options, op == OP_ASSERT)) < 0)
+       return -1;
+      if (c < 0)
+       c = d;
+      else if (c != d)
+       return -1;
+      break;
+
+    case OP_EXACT:             /* Fall through */
+      scode++;
+
+    case OP_CHARS:             /* Fall through */
+      scode++;
+
+    case OP_PLUS:
+    case OP_MINPLUS:
+      if (!inassert)
+       return -1;
+      if (c < 0) {
+       c = scode[1];
+       if ((*options & PCRE_CASELESS) != 0)
+         c |= REQ_CASELESS;
+      } else if (c != scode[1])
+       return -1;
+      break;
+    }
+
+    code += GET(code, 1);
+  }
+  while (*code == OP_ALT);
+  return c;
+}
+
+
+
+
+
+
+
+/*************************************************
+*        Compile a Regular Expression            *
+*************************************************/
+
+/* This function takes a string and returns a pointer to a block of store
+holding a compiled version of the expression.
+
+Arguments:
+  pattern      the regular expression
+  options      various option bits
+  errorptr     pointer to pointer to error text
+  erroroffset  ptr offset in pattern where error was detected
+  tables       pointer to character tables or NULL
+
+Returns:       pointer to compiled data block, or NULL on error,
+               with errorptr and erroroffset set
+*/
+
+EXPORT pcre *
+pcre_compile(const char *pattern, int options, const char **errorptr,
+            int *erroroffset, const unsigned char *tables)
+{
+  real_pcre *re;
+  int length = 1 + LINK_SIZE;  /* For initial BRA plus length */
+  int runlength;
+  int c, firstbyte, reqbyte;
+  int bracount = 0;
+  int branch_extra = 0;
+  int branch_newextra;
+  int item_count = -1;
+  int name_count = 0;
+  int max_name_size = 0;
+  BOOL inescq = FALSE;
+  unsigned int brastackptr = 0;
+  size_t size;
+  uschar *code;
+  const uschar *codestart;
+  const uschar *ptr;
+  compile_data compile_block;
+  int brastack[BRASTACK_SIZE];
+  uschar bralenstack[BRASTACK_SIZE];
+
+/* We can't pass back an error message if errorptr is NULL; I guess the best we
+can do is just return NULL. */
+
+  if (errorptr == NULL)
+    return NULL;
+  *errorptr = NULL;
+
+/* However, we can give a message for this error */
+
+  if (erroroffset == NULL) {
+    *errorptr = ERR16;
+    return NULL;
+  }
+  *erroroffset = 0;
+
+/* Can't support UTF8 unless PCRE has been compiled to include the code. */
+
+  if ((options & PCRE_UTF8) != 0) {
+    *errorptr = ERR32;
+    return NULL;
+  }
+
+  if ((options & ~PUBLIC_OPTIONS) != 0) {
+    *errorptr = ERR17;
+    return NULL;
+  }
+
+/* Set up pointers to the individual character tables */
+
+  if (tables == NULL)
+    tables = pcre_default_tables;
+  compile_block.lcc = tables + lcc_offset;
+  compile_block.fcc = tables + fcc_offset;
+  compile_block.cbits = tables + cbits_offset;
+  compile_block.ctypes = tables + ctypes_offset;
+
+/* Maximum back reference and backref bitmap. This is updated for numeric
+references during the first pass, but for named references during the actual
+compile pass. The bitmap records up to 31 back references to help in deciding
+whether (.*) can be treated as anchored or not. */
+
+  compile_block.top_backref = 0;
+  compile_block.backref_map = 0;
+
+/* Reflect pattern for debugging output */
+
+  DPRINTF(("------------------------------------------------------------------\n"));
+  DPRINTF(("%s\n", pattern));
+
+/* The first thing to do is to make a pass over the pattern to compute the
+amount of store required to hold the compiled code. This does not have to be
+perfect as long as errors are overestimates. At the same time we can detect any
+flag settings right at the start, and extract them. Make an attempt to correct
+for any counted white space if an "extended" flag setting appears late in the
+pattern. We can't be so clever for #-comments. */
+
+  ptr = (const uschar *) (pattern - 1);
+  while ((c = *(++ptr)) != 0) {
+    int min, max;
+    int class_optcount;
+    int bracket_length;
+    int duplength;
+
+    /* If we are inside a \Q...\E sequence, all chars are literal */
+
+    if (inescq)
+      goto NORMAL_CHAR;
+
+    /* Otherwise, first check for ignored whitespace and comments */
+
+    if ((options & PCRE_EXTENDED) != 0) {
+      if ((compile_block.ctypes[c] & ctype_space) != 0)
+       continue;
+      if (c == '#') {
+       /* The space before the ; is to avoid a warning on a silly compiler
+          on the Macintosh. */
+       while ((c = *(++ptr)) != 0 && c != NEWLINE) ;
+       if (c == 0)
+         break;
+       continue;
+      }
+    }
+
+    item_count++;              /* Is zero for the first non-comment item */
+
+    switch (c) {
+      /* A backslashed item may be an escaped "normal" character or a
+         character type. For a "normal" character, put the pointers and
+         character back so that tests for whitespace etc. in the input
+         are done correctly. */
+
+    case '\\':
+      {
+       const uschar *save_ptr = ptr;
+       c = check_escape(&ptr, errorptr, bracount, options, FALSE);
+       if (*errorptr != NULL)
+         goto PCRE_ERROR_RETURN;
+       if (c >= 0) {
+         ptr = save_ptr;
+         c = '\\';
+         goto NORMAL_CHAR;
+       }
+      }
+
+      /* If \Q, enter "literal" mode */
+
+      if (-c == ESC_Q) {
+       inescq = TRUE;
+       continue;
+      }
+
+      /* Other escapes need one byte, and are of length one for repeats */
+
+      length++;
+
+      /* A back reference needs an additional 2 bytes, plus either one or 5
+         bytes for a repeat. We also need to keep the value of the highest
+         back reference. */
+
+      if (c <= -ESC_REF) {
+       int refnum = -c - ESC_REF;
+       compile_block.backref_map |= (refnum < 32) ? (1 << refnum) : 1;
+       if (refnum > compile_block.top_backref)
+         compile_block.top_backref = refnum;
+       length += 2;            /* For single back reference */
+       if (ptr[1] == '{' && is_counted_repeat(ptr + 2)) {
+         ptr = read_repeat_counts(ptr + 2, &min, &max, errorptr);
+         if (*errorptr != NULL)
+           goto PCRE_ERROR_RETURN;
+         if ((min == 0 && (max == 1 || max == -1)) || (min == 1 && max == -1))
+           length++;
+         else
+           length += 5;
+         if (ptr[1] == '?')
+           ptr++;
+       }
+      }
+      continue;
+
+    case '^':                  /* Single-byte metacharacters */
+    case '.':
+    case '$':
+      length++;
+      continue;
+
+    case '*':                  /* These repeats won't be after brackets; */
+    case '+':                  /* those are handled separately */
+    case '?':
+      length++;
+      goto POSESSIVE;          /* A few lines below */
+
+      /* This covers the cases of braced repeats after a single char, metachar,
+         class, or back reference. */
+
+    case '{':
+      if (!is_counted_repeat(ptr + 1))
+       goto NORMAL_CHAR;
+      ptr = read_repeat_counts(ptr + 1, &min, &max, errorptr);
+      if (*errorptr != NULL)
+       goto PCRE_ERROR_RETURN;
+
+      /* These special cases just insert one extra opcode */
+
+      if ((min == 0 && (max == 1 || max == -1)) || (min == 1 && max == -1))
+       length++;
+
+      /* These cases might insert additional copies of a preceding character. */
+
+      else {
+
+       /* Not UTF-8 mode: all characters are one byte */
+       {
+         if (min != 1) {
+           length--;           /* Uncount the original char or metachar */
+           if (min > 0)
+             length += 4;
+         }
+
+         length += (max > 0) ? 4 : 2;
+       }
+      }
+
+      if (ptr[1] == '?')
+       ptr++;                  /* Needs no extra length */
+
+    POSESSIVE:                 /* Test for possessive quantifier */
+      if (ptr[1] == '+') {
+       ptr++;
+       length += 2 + 2 * LINK_SIZE;    /* Allow for atomic brackets */
+      }
+      continue;
+
+      /* An alternation contains an offset to the next branch or ket. If any ims
+         options changed in the previous branch(es), and/or if we are in a
+         lookbehind assertion, extra space will be needed at the start of the
+         branch. This is handled by branch_extra. */
+
+    case '|':
+      length += 1 + LINK_SIZE + branch_extra;
+      continue;
+
+      /* A character class uses 33 characters provided that all the character
+         values are less than 256. Otherwise, it uses a bit map for low valued
+         characters, and individual items for others. Don't worry about character
+         types that aren't allowed in classes - they'll get picked up during the
+         compile. A character class that contains only one single-byte character
+         uses 2 or 3 bytes, depending on whether it is negated or not. Notice this
+         where we can. (In UTF-8 mode we can do this only for chars < 128.) */
+
+    case '[':
+      class_optcount = 0;
+
+
+      if (*(++ptr) == '^')
+       ptr++;
+
+      /* Written as a "do" so that an initial ']' is taken as data */
+
+      if (*ptr != 0)
+       do {
+         /* Inside \Q...\E everything is literal except \E */
+
+         if (inescq) {
+           if (*ptr != '\\' || ptr[1] != 'E')
+             goto NON_SPECIAL_CHARACTER;
+           inescq = FALSE;
+           ptr += 1;
+           continue;
+         }
+
+         /* Outside \Q...\E, check for escapes */
+
+         if (*ptr == '\\') {
+           int ch = check_escape(&ptr, errorptr, bracount, options, TRUE);
+           if (*errorptr != NULL)
+             goto PCRE_ERROR_RETURN;
+
+           /* \b is backspace inside a class */
+
+           if (-ch == ESC_b)
+             ch = '\b';
+
+           /* \Q enters quoting mode */
+
+           if (-ch == ESC_Q) {
+             inescq = TRUE;
+             continue;
+           }
+
+           /* Handle escapes that turn into characters */
+
+           if (ch >= 0) {
+             class_optcount++; /* for possible optimization */
+           } else
+             class_optcount = 10;      /* \d, \s etc; make sure > 1 */
+         }
+
+         /* Check the syntax for POSIX stuff. The bits we actually handle are
+            checked during the real compile phase. */
+
+         else if (*ptr == '[' && check_posix_syntax(ptr, &ptr, &compile_block)) {
+           ptr++;
+           class_optcount = 10;        /* Make sure > 1 */
+         }
+
+         /* Anything else just increments the possible optimization count. If
+            there are wide characters, we are going to have to use an XCLASS. */
+
+         else {
+         NON_SPECIAL_CHARACTER:
+           class_optcount++;
+
+         }
+       }
+       while (*(++ptr) != 0 && (inescq || *ptr != ']'));       /* Concludes "do" above */
+
+      if (*ptr == 0) {         /* Missing terminating ']' */
+       *errorptr = ERR6;
+       goto PCRE_ERROR_RETURN;
+      }
+
+      /* We can optimize when there was only one optimizable character. Repeats
+         for positive and negated single one-byte chars are handled by the general
+         code. Here, we handle repeats for the class opcodes. */
+
+      if (class_optcount == 1)
+       length += 3;
+      else {
+       length += 33;
+
+       /* A repeat needs either 1 or 5 bytes. If it is a possessive quantifier,
+          we also need extra for wrapping the whole thing in a sub-pattern. */
+
+       if (*ptr != 0 && ptr[1] == '{' && is_counted_repeat(ptr + 2)) {
+         ptr = read_repeat_counts(ptr + 2, &min, &max, errorptr);
+         if (*errorptr != NULL)
+           goto PCRE_ERROR_RETURN;
+         if ((min == 0 && (max == 1 || max == -1)) || (min == 1 && max == -1))
+           length++;
+         else
+           length += 5;
+         if (ptr[1] == '+') {
+           ptr++;
+           length += 2 + 2 * LINK_SIZE;
+         } else if (ptr[1] == '?')
+           ptr++;
+       }
+      }
+      continue;
+
+      /* Brackets may be genuine groups or special things */
+
+    case '(':
+      branch_newextra = 0;
+      bracket_length = 1 + LINK_SIZE;
+
+      /* Handle special forms of bracket, which all start (? */
+
+      if (ptr[1] == '?') {
+       int set, unset;
+       int *optset;
+
+       switch (c = ptr[2]) {
+         /* Skip over comments entirely */
+       case '#':
+         ptr += 3;
+         while (*ptr != 0 && *ptr != ')')
+           ptr++;
+         if (*ptr == 0) {
+           *errorptr = ERR18;
+           goto PCRE_ERROR_RETURN;
+         }
+         continue;
+
+         /* Non-referencing groups and lookaheads just move the pointer on, and
+            then behave like a non-special bracket, except that they don't increment
+            the count of extracting brackets. Ditto for the "once only" bracket,
+            which is in Perl from version 5.005. */
+
+       case ':':
+       case '=':
+       case '!':
+       case '>':
+         ptr += 2;
+         break;
+
+         /* (?R) specifies a recursive call to the regex, which is an extension
+            to provide the facility which can be obtained by (?p{perl-code}) in
+            Perl 5.6. In Perl 5.8 this has become (??{perl-code}).
+
+            From PCRE 4.00, items such as (?3) specify subroutine-like "calls" to
+            the appropriate numbered brackets. This includes both recursive and
+            non-recursive calls. (?R) is now synonymous with (?0). */
+
+       case 'R':
+         ptr++;
+
+       case '0':
+       case '1':
+       case '2':
+       case '3':
+       case '4':
+       case '5':
+       case '6':
+       case '7':
+       case '8':
+       case '9':
+         ptr += 2;
+         if (c != 'R')
+           while ((digitab[*(++ptr)] & ctype_digit) != 0) ;
+         if (*ptr != ')') {
+           *errorptr = ERR29;
+           goto PCRE_ERROR_RETURN;
+         }
+         length += 1 + LINK_SIZE;
+
+         /* If this item is quantified, it will get wrapped inside brackets so
+            as to use the code for quantified brackets. We jump down and use the
+            code that handles this for real brackets. */
+
+         if (ptr[1] == '+' || ptr[1] == '*' || ptr[1] == '?' || ptr[1] == '{') {
+           length += 2 + 2 * LINK_SIZE;        /* to make bracketed */
+           duplength = 5 + 3 * LINK_SIZE;
+           goto HANDLE_QUANTIFIED_BRACKETS;
+         }
+         continue;
+
+         /* (?C) is an extension which provides "callout" - to provide a bit of
+            the functionality of the Perl (?{...}) feature. An optional number may
+            follow (default is zero). */
+
+       case 'C':
+         ptr += 2;
+         while ((digitab[*(++ptr)] & ctype_digit) != 0) ;
+         if (*ptr != ')') {
+           *errorptr = ERR39;
+           goto PCRE_ERROR_RETURN;
+         }
+         length += 2;
+         continue;
+
+         /* Named subpatterns are an extension copied from Python */
+
+       case 'P':
+         ptr += 3;
+         if (*ptr == '<') {
+           const uschar *p;    /* Don't amalgamate; some compilers */
+           p = ++ptr;          /* grumble at autoincrement in declaration */
+           while ((compile_block.ctypes[*ptr] & ctype_word) != 0)
+             ptr++;
+           if (*ptr != '>') {
+             *errorptr = ERR42;
+             goto PCRE_ERROR_RETURN;
+           }
+           name_count++;
+           if (ptr - p > max_name_size)
+             max_name_size = (ptr - p);
+           break;
+         }
+
+         if (*ptr == '=' || *ptr == '>') {
+           while ((compile_block.ctypes[*(++ptr)] & ctype_word) != 0) ;
+           if (*ptr != ')') {
+             *errorptr = ERR42;
+             goto PCRE_ERROR_RETURN;
+           }
+           break;
+         }
+
+         /* Unknown character after (?P */
+
+         *errorptr = ERR41;
+         goto PCRE_ERROR_RETURN;
+
+         /* Lookbehinds are in Perl from version 5.005 */
+
+       case '<':
+         ptr += 3;
+         if (*ptr == '=' || *ptr == '!') {
+           branch_newextra = 1 + LINK_SIZE;
+           length += 1 + LINK_SIZE;    /* For the first branch */
+           break;
+         }
+         *errorptr = ERR24;
+         goto PCRE_ERROR_RETURN;
+
+         /* Conditionals are in Perl from version 5.005. The bracket must either
+            be followed by a number (for bracket reference) or by an assertion
+            group, or (a PCRE extension) by 'R' for a recursion test. */
+
+       case '(':
+         if (ptr[3] == 'R' && ptr[4] == ')') {
+           ptr += 4;
+           length += 3;
+         } else if ((digitab[ptr[3]] & ctype_digit) != 0) {
+           ptr += 4;
+           length += 3;
+           while ((digitab[*ptr] & ctype_digit) != 0)
+             ptr++;
+           if (*ptr != ')') {
+             *errorptr = ERR26;
+             goto PCRE_ERROR_RETURN;
+           }
+         } else {              /* An assertion must follow */
+
+           ptr++;              /* Can treat like ':' as far as spacing is concerned */
+           if (ptr[2] != '?' ||
+               (ptr[3] != '=' && ptr[3] != '!' && ptr[3] != '<')) {
+             ptr += 2;         /* To get right offset in message */
+             *errorptr = ERR28;
+             goto PCRE_ERROR_RETURN;
+           }
+         }
+         break;
+
+         /* Else loop checking valid options until ) is met. Anything else is an
+            error. If we are without any brackets, i.e. at top level, the settings
+            act as if specified in the options, so massage the options immediately.
+            This is for backward compatibility with Perl 5.004. */
+
+       default:
+         set = unset = 0;
+         optset = &set;
+         ptr += 2;
+
+         for (;; ptr++) {
+           c = *ptr;
+           switch (c) {
+           case 'i':
+             *optset |= PCRE_CASELESS;
+             continue;
+
+           case 'm':
+             *optset |= PCRE_MULTILINE;
+             continue;
+
+           case 's':
+             *optset |= PCRE_DOTALL;
+             continue;
+
+           case 'x':
+             *optset |= PCRE_EXTENDED;
+             continue;
+
+           case 'X':
+             *optset |= PCRE_EXTRA;
+             continue;
+
+           case 'U':
+             *optset |= PCRE_UNGREEDY;
+             continue;
+
+           case '-':
+             optset = &unset;
+             continue;
+
+             /* A termination by ')' indicates an options-setting-only item; if
+                this is at the very start of the pattern (indicated by item_count
+                being zero), we use it to set the global options. This is helpful
+                when analyzing the pattern for first characters, etc. Otherwise
+                nothing is done here and it is handled during the compiling
+                process.
+
+                [Historical note: Up to Perl 5.8, options settings at top level
+                were always global settings, wherever they appeared in the pattern.
+                That is, they were equivalent to an external setting. From 5.8
+                onwards, they apply only to what follows (which is what you might
+                expect).] */
+
+           case ')':
+             if (item_count == 0) {
+               options = (options | set) & (~unset);
+               set = unset = 0;        /* To save length */
+               item_count--;   /* To allow for several */
+             }
+
+             /* Fall through */
+
+             /* A termination by ':' indicates the start of a nested group with
+                the given options set. This is again handled at compile time, but
+                we must allow for compiled space if any of the ims options are
+                set. We also have to allow for resetting space at the end of
+                the group, which is why 4 is added to the length and not just 2.
+                If there are several changes of options within the same group, this
+                will lead to an over-estimate on the length, but this shouldn't
+                matter very much. We also have to allow for resetting options at
+                the start of any alternations, which we do by setting
+                branch_newextra to 2. Finally, we record whether the case-dependent
+                flag ever changes within the regex. This is used by the "required
+                character" code. */
+
+           case ':':
+             if (((set | unset) & PCRE_IMS) != 0) {
+               length += 4;
+               branch_newextra = 2;
+               if (((set | unset) & PCRE_CASELESS) != 0)
+                 options |= PCRE_ICHANGED;
+             }
+             goto END_OPTIONS;
+
+             /* Unrecognized option character */
+
+           default:
+             *errorptr = ERR12;
+             goto PCRE_ERROR_RETURN;
+           }
+         }
+
+         /* If we hit a closing bracket, that's it - this is a freestanding
+            option-setting. We need to ensure that branch_extra is updated if
+            necessary. The only values branch_newextra can have here are 0 or 2.
+            If the value is 2, then branch_extra must either be 2 or 5, depending
+            on whether this is a lookbehind group or not. */
+
+       END_OPTIONS:
+         if (c == ')') {
+           if (branch_newextra == 2 &&
+               (branch_extra == 0 || branch_extra == 1 + LINK_SIZE))
+             branch_extra += branch_newextra;
+           continue;
+         }
+
+         /* If options were terminated by ':' control comes here. Fall through
+            to handle the group below. */
+       }
+      }
+
+      /* Extracting brackets must be counted so we can process escapes in a
+         Perlish way. If the number exceeds EXTRACT_BASIC_MAX we are going to
+         need an additional 3 bytes of store per extracting bracket. However, if
+         PCRE_NO_AUTO)CAPTURE is set, unadorned brackets become non-capturing, so we
+         must leave the count alone (it will aways be zero). */
+
+      else if ((options & PCRE_NO_AUTO_CAPTURE) == 0) {
+       bracount++;
+       if (bracount > EXTRACT_BASIC_MAX)
+         bracket_length += 3;
+      }
+
+      /* Save length for computing whole length at end if there's a repeat that
+         requires duplication of the group. Also save the current value of
+         branch_extra, and start the new group with the new value. If non-zero, this
+         will either be 2 for a (?imsx: group, or 3 for a lookbehind assertion. */
+
+      if (brastackptr >= sizeof(brastack) / sizeof(int)) {
+       *errorptr = ERR19;
+       goto PCRE_ERROR_RETURN;
+      }
+
+      bralenstack[brastackptr] = branch_extra;
+      branch_extra = branch_newextra;
+
+      brastack[brastackptr++] = length;
+      length += bracket_length;
+      continue;
+
+      /* Handle ket. Look for subsequent max/min; for certain sets of values we
+         have to replicate this bracket up to that many times. If brastackptr is
+         0 this is an unmatched bracket which will generate an error, but take care
+         not to try to access brastack[-1] when computing the length and restoring
+         the branch_extra value. */
+
+    case ')':
+      length += 1 + LINK_SIZE;
+      if (brastackptr > 0) {
+       duplength = length - brastack[--brastackptr];
+       branch_extra = bralenstack[brastackptr];
+      } else
+       duplength = 0;
+
+      /* The following code is also used when a recursion such as (?3) is
+         followed by a quantifier, because in that case, it has to be wrapped inside
+         brackets so that the quantifier works. The value of duplength must be
+         set before arrival. */
+
+    HANDLE_QUANTIFIED_BRACKETS:
+
+      /* Leave ptr at the final char; for read_repeat_counts this happens
+         automatically; for the others we need an increment. */
+
+      if ((c = ptr[1]) == '{' && is_counted_repeat(ptr + 2)) {
+       ptr = read_repeat_counts(ptr + 2, &min, &max, errorptr);
+       if (*errorptr != NULL)
+         goto PCRE_ERROR_RETURN;
+      } else if (c == '*') {
+       min = 0;
+       max = -1;
+       ptr++;
+      } else if (c == '+') {
+       min = 1;
+       max = -1;
+       ptr++;
+      } else if (c == '?') {
+       min = 0;
+       max = 1;
+       ptr++;
+      } else {
+       min = 1;
+       max = 1;
+      }
+
+      /* If the minimum is zero, we have to allow for an OP_BRAZERO before the
+         group, and if the maximum is greater than zero, we have to replicate
+         maxval-1 times; each replication acquires an OP_BRAZERO plus a nesting
+         bracket set. */
+
+      if (min == 0) {
+       length++;
+       if (max > 0)
+         length += (max - 1) * (duplength + 3 + 2 * LINK_SIZE);
+      }
+
+      /* When the minimum is greater than zero, we have to replicate up to
+         minval-1 times, with no additions required in the copies. Then, if there
+         is a limited maximum we have to replicate up to maxval-1 times allowing
+         for a BRAZERO item before each optional copy and nesting brackets for all
+         but one of the optional copies. */
+
+      else {
+       length += (min - 1) * duplength;
+       if (max > min)          /* Need this test as max=-1 means no limit */
+         length += (max - min) * (duplength + 3 + 2 * LINK_SIZE)
+           - (2 + 2 * LINK_SIZE);
+      }
+
+      /* Allow space for once brackets for "possessive quantifier" */
+
+      if (ptr[1] == '+') {
+       ptr++;
+       length += 2 + 2 * LINK_SIZE;
+      }
+      continue;
+
+      /* Non-special character. For a run of such characters the length required
+         is the number of characters + 2, except that the maximum run length is
+         MAXLIT. We won't get a skipped space or a non-data escape or the start of a
+         # comment as the first character, so the length can't be zero. */
+
+    NORMAL_CHAR:
+    default:
+      length += 2;
+      runlength = 0;
+      do {
+
+       /* If in a \Q...\E sequence, check for end; otherwise it's a literal */
+       if (inescq) {
+         if (c == '\\' && ptr[1] == 'E') {
+           inescq = FALSE;
+           ptr++;
+         } else
+           runlength++;
+         continue;
+       }
+
+       /* Skip whitespace and comments for /x */
+
+       if ((options & PCRE_EXTENDED) != 0) {
+         if ((compile_block.ctypes[c] & ctype_space) != 0)
+           continue;
+         if (c == '#') {
+           /* The space before the ; is to avoid a warning on a silly compiler
+              on the Macintosh. */
+           while ((c = *(++ptr)) != 0 && c != NEWLINE) ;
+           continue;
+         }
+       }
+
+       /* Backslash may introduce a data char or a metacharacter; stop the
+          string before the latter. */
+
+       if (c == '\\') {
+         const uschar *saveptr = ptr;
+         c = check_escape(&ptr, errorptr, bracount, options, FALSE);
+         if (*errorptr != NULL)
+           goto PCRE_ERROR_RETURN;
+         if (c < 0) {
+           ptr = saveptr;
+           break;
+         }
+
+         /* In UTF-8 mode, add on the number of additional bytes needed to
+            encode this character, and save the total length in case this is a
+            final char that is repeated. */
+
+       }
+
+       /* Ordinary character or single-char escape */
+
+       runlength++;
+      }
+
+      /* This "while" is the end of the "do" above. */
+
+      while (runlength < MAXLIT &&
+            (compile_block.ctypes[c = *(++ptr)] & ctype_meta) == 0);
+
+      /* If we hit a meta-character, back off to point to it */
+
+      if (runlength < MAXLIT)
+       ptr--;
+
+      /* If the last char in the string is a UTF-8 multibyte character, we must
+         set lastcharlength correctly. If it was specified as an escape, this will
+         already have been done above. However, we also have to support in-line
+         UTF-8 characters, so check backwards from where we are. */
+
+
+      length += runlength;
+      continue;
+    }
+  }
+
+  length += 2 + LINK_SIZE;     /* For final KET and END */
+
+  if (length > MAX_PATTERN_SIZE) {
+    *errorptr = ERR20;
+    return NULL;
+  }
+
+/* Compute the size of data block needed and get it, either from malloc or
+externally provided function. */
+
+  size = length + sizeof(real_pcre) + name_count * (max_name_size + 3);
+  re = (real_pcre *) malloc(size);
+
+  if (re == NULL) {
+    *errorptr = ERR21;
+    return NULL;
+  }
+
+/* Put in the magic number, and save the size, options, and table pointer */
+
+  re->magic_number = MAGIC_NUMBER;
+  re->size = size;
+  re->options = options;
+  re->tables = tables;
+  re->name_entry_size = max_name_size + 3;
+  re->name_count = name_count;
+
+/* The starting points of the name/number translation table and of the code are
+passed around in the compile data block. */
+
+  compile_block.names_found = 0;
+  compile_block.name_entry_size = max_name_size + 3;
+  compile_block.name_table = (uschar *) re + sizeof(real_pcre);
+  codestart = compile_block.name_table + re->name_entry_size * re->name_count;
+  compile_block.start_code = codestart;
+  compile_block.req_varyopt = 0;
+
+/* Set up a starting, non-extracting bracket, then compile the expression. On
+error, *errorptr will be set non-NULL, so we don't need to look at the result
+of the function here. */
+
+  ptr = (const uschar *) pattern;
+  code = (uschar *) codestart;
+  *code = OP_BRA;
+  bracount = 0;
+  (void) compile_regex(options, options & PCRE_IMS, &bracount, &code, &ptr,
+                      errorptr, FALSE, 0, &firstbyte, &reqbyte, NULL,
+                      &compile_block);
+  re->top_bracket = bracount;
+  re->top_backref = compile_block.top_backref;
+
+/* If not reached end of pattern on success, there's an excess bracket. */
+
+  if (*errorptr == NULL && *ptr != 0)
+    *errorptr = ERR22;
+
+/* Fill in the terminating state and check for disastrous overflow, but
+if debugging, leave the test till after things are printed out. */
+
+  *code++ = OP_END;
+
+  if (code - codestart > length)
+    *errorptr = ERR23;
+
+/* Give an error if there's back reference to a non-existent capturing
+subpattern. */
+
+  if (re->top_backref > re->top_bracket)
+    *errorptr = ERR15;
+
+/* Failed to compile, or error while post-processing */
+
+  if (*errorptr != NULL) {
+    free(re);
+  PCRE_ERROR_RETURN:
+    *erroroffset = ptr - (const uschar *) pattern;
+    return NULL;
+  }
+
+/* If the anchored option was not passed, set the flag if we can determine that
+the pattern is anchored by virtue of ^ characters or \A or anything else (such
+as starting with .* when DOTALL is set).
+
+Otherwise, if we know what the first character has to be, save it, because that
+speeds up unanchored matches no end. If not, see if we can set the
+PCRE_STARTLINE flag. This is helpful for multiline matches when all branches
+start with ^. and also when all branches start with .* for non-DOTALL matches.
+*/
+
+  if ((options & PCRE_ANCHORED) == 0) {
+    int temp_options = options;
+    if (is_anchored(codestart, &temp_options, 0, compile_block.backref_map))
+      re->options |= PCRE_ANCHORED;
+    else {
+      if (firstbyte < 0)
+       firstbyte = find_firstassertedchar(codestart, &temp_options, FALSE);
+      if (firstbyte >= 0) {    /* Remove caseless flag for non-caseable chars */
+       int ch = firstbyte & 255;
+       re->first_byte = ((firstbyte & REQ_CASELESS) != 0 &&
+                         compile_block.fcc[ch] == ch) ? ch : firstbyte;
+       re->options |= PCRE_FIRSTSET;
+      } else if (is_startline(codestart, 0, compile_block.backref_map))
+       re->options |= PCRE_STARTLINE;
+    }
+  }
+
+/* For an anchored pattern, we use the "required byte" only if it follows a
+variable length item in the regex. Remove the caseless flag for non-caseable
+chars. */
+
+  if (reqbyte >= 0 &&
+      ((re->options & PCRE_ANCHORED) == 0 || (reqbyte & REQ_VARY) != 0)) {
+    int ch = reqbyte & 255;
+    re->req_byte = ((reqbyte & REQ_CASELESS) != 0 &&
+                   compile_block.fcc[ch] ==
+                   ch) ? (reqbyte & ~REQ_CASELESS) : reqbyte;
+    re->options |= PCRE_REQCHSET;
+  }
+
+/* Print out the compiled data for debugging */
+
+
+  return (pcre *) re;
+}
+
+
+
+/*************************************************
+*          Match a back-reference                *
+*************************************************/
+
+/* If a back reference hasn't been set, the length that is passed is greater
+than the number of characters left in the string, so the match fails.
+
+Arguments:
+  offset      index into the offset vector
+  eptr        points into the subject
+  length      length to be matched
+  md          points to match data block
+  ims         the ims flags
+
+Returns:      TRUE if matched
+*/
+
+static BOOL
+match_ref(int offset, register const uschar * eptr, int length, match_data * md,
+         unsigned long int ims)
+{
+  const uschar *p = md->start_subject + md->offset_vector[offset];
+
+
+/* Always fail if not enough characters left */
+
+  if (length > md->end_subject - eptr)
+    return FALSE;
+
+/* Separate the caselesss case for speed */
+
+  if ((ims & PCRE_CASELESS) != 0) {
+    while (length-- > 0)
+      if (md->lcc[*p++] != md->lcc[*eptr++])
+       return FALSE;
+  } else {
+    while (length-- > 0)
+      if (*p++ != *eptr++)
+       return FALSE;
+  }
+
+  return TRUE;
+}
+
+
+
+
+/***************************************************************************
+****************************************************************************
+                   RECURSION IN THE match() FUNCTION
+
+The match() function is highly recursive. Some regular expressions can cause
+it to recurse thousands of times. I was writing for Unix, so I just let it
+call itself recursively. This uses the stack for saving everything that has
+to be saved for a recursive call. On Unix, the stack can be large, and this
+works fine.
+
+It turns out that on non-Unix systems there are problems with programs that
+use a lot of stack. (This despite the fact that every last chip has oodles
+of memory these days, and techniques for extending the stack have been known
+for decades.) So....
+
+There is a fudge, triggered by defining NO_RECURSE, which avoids recursive
+calls by keeping local variables that need to be preserved in blocks of memory
+obtained from malloc instead instead of on the stack. Macros are used to
+achieve this so that the actual code doesn't look very different to what it
+always used to.
+****************************************************************************
+***************************************************************************/
+
+
+/* These versions of the macros use the stack, as normal */
+
+#ifndef NO_RECURSE
+#define REGISTER register
+#define RMATCH(rx,ra,rb,rc,rd,re,rf,rg) rx = match(ra,rb,rc,rd,re,rf,rg)
+#define RRETURN(ra) return ra
+#else
+
+
+/* These versions of the macros manage a private stack on the heap. Note
+that the rd argument of RMATCH isn't actually used. It's the md argument of
+match(), which never actually changes. */
+
+#define REGISTER
+
+#define RMATCH(rx,ra,rb,rc,rd,re,rf,rg)\
+  {\
+  heapframe *newframe = (pcre_stack_malloc)(sizeof(heapframe));\
+  if (setjmp(frame->Xwhere) == 0)\
+    {\
+    newframe->Xeptr = ra;\
+    newframe->Xecode = rb;\
+    newframe->Xoffset_top = rc;\
+    newframe->Xims = re;\
+    newframe->Xeptrb = rf;\
+    newframe->Xflags = rg;\
+    newframe->Xprevframe = frame;\
+    frame = newframe;\
+    DPRINTF(("restarting from line %d\n", __LINE__));\
+    goto HEAP_RECURSE;\
+    }\
+  else\
+    {\
+    DPRINTF(("longjumped back to line %d\n", __LINE__));\
+    frame = md->thisframe;\
+    rx = frame->Xresult;\
+    }\
+  }
+
+#define RRETURN(ra)\
+  {\
+  heapframe *newframe = frame;\
+  frame = newframe->Xprevframe;\
+  (pcre_stack_free)(newframe);\
+  if (frame != NULL)\
+    {\
+    frame->Xresult = ra;\
+    md->thisframe = frame;\
+    longjmp(frame->Xwhere, 1);\
+    }\
+  return ra;\
+  }
+
+
+/* Structure for remembering the local variables in a private frame */
+
+typedef struct heapframe {
+  struct heapframe *Xprevframe;
+
+  /* Function arguments that may change */
+
+  const uschar *Xeptr;
+  const uschar *Xecode;
+  int Xoffset_top;
+  long int Xims;
+  eptrblock *Xeptrb;
+  int Xflags;
+
+  /* Function local variables */
+
+  const uschar *Xcallpat;
+  const uschar *Xcharptr;
+  const uschar *Xdata;
+  const uschar *Xlastptr;
+  const uschar *Xnext;
+  const uschar *Xpp;
+  const uschar *Xprev;
+  const uschar *Xsaved_eptr;
+
+  recursion_info Xnew_recursive;
+
+  BOOL Xcur_is_word;
+  BOOL Xcondition;
+  BOOL Xminimize;
+  BOOL Xprev_is_word;
+
+  unsigned long int Xoriginal_ims;
+
+  int Xctype;
+  int Xfc;
+  int Xfi;
+  int Xlength;
+  int Xmax;
+  int Xmin;
+  int Xnumber;
+  int Xoffset;
+  int Xop;
+  int Xsave_capture_last;
+  int Xsave_offset1, Xsave_offset2, Xsave_offset3;
+  int Xstacksave[REC_STACK_SAVE_MAX];
+
+  eptrblock Xnewptrb;
+
+  /* Place to pass back result, and where to jump back to */
+
+  int Xresult;
+  jmp_buf Xwhere;
+
+} heapframe;
+
+#endif
+
+
+/***************************************************************************
+***************************************************************************/
+
+
+
+/*************************************************
+*         Match from current position            *
+*************************************************/
+
+/* On entry ecode points to the first opcode, and eptr to the first character
+in the subject string, while eptrb holds the value of eptr at the start of the
+last bracketed group - used for breaking infinite loops matching zero-length
+strings. This function is called recursively in many circumstances. Whenever it
+returns a negative (error) response, the outer incarnation must also return the
+same response.
+
+Performance note: It might be tempting to extract commonly used fields from the
+md structure (e.g. utf8, end_subject) into individual variables to improve
+performance. Tests using gcc on a SPARC disproved this; in the first case, it
+made performance worse.
+
+Arguments:
+   eptr        pointer in subject
+   ecode       position in code
+   offset_top  current top pointer
+   md          pointer to "static" info for the match
+   ims         current /i, /m, and /s options
+   eptrb       pointer to chain of blocks containing eptr at start of
+                 brackets - for testing for empty matches
+   flags       can contain
+                 match_condassert - this is an assertion condition
+                 match_isgroup - this is the start of a bracketed group
+
+Returns:       MATCH_MATCH if matched            )  these values are >= 0
+               MATCH_NOMATCH if failed to match  )
+               a negative PCRE_ERROR_xxx value if aborted by an error condition
+                 (e.g. stopped by recursion limit)
+*/
+
+static int
+match(REGISTER const uschar * eptr, REGISTER const uschar * ecode,
+      int offset_top, match_data * md, unsigned long int ims, eptrblock * eptrb,
+      int flags)
+{
+/* These variables do not need to be preserved over recursion in this function,
+so they can be ordinary variables in all cases. Mark them with "register"
+because they are used a lot in loops. */
+
+  register int rrc;            /* Returns from recursive calls */
+  register int i;              /* Used for loops not involving calls to RMATCH() */
+  register int c;              /* Character values not kept over RMATCH() calls */
+
+/* When recursion is not being used, all "local" variables that have to be
+preserved over calls to RMATCH() are part of a "frame" which is obtained from
+heap storage. Set up the top-level frame here; others are obtained from the
+heap whenever RMATCH() does a "recursion". See the macro definitions above. */
+
+#ifdef NO_RECURSE
+  heapframe *frame = (pcre_stack_malloc) (sizeof(heapframe));
+  frame->Xprevframe = NULL;    /* Marks the top level */
+
+/* Copy in the original argument variables */
+
+  frame->Xeptr = eptr;
+  frame->Xecode = ecode;
+  frame->Xoffset_top = offset_top;
+  frame->Xims = ims;
+  frame->Xeptrb = eptrb;
+  frame->Xflags = flags;
+
+/* This is where control jumps back to to effect "recursion" */
+
+HEAP_RECURSE:
+
+/* Macros make the argument variables come from the current frame */
+
+#define eptr               frame->Xeptr
+#define ecode              frame->Xecode
+#define offset_top         frame->Xoffset_top
+#define ims                frame->Xims
+#define eptrb              frame->Xeptrb
+#define flags              frame->Xflags
+
+/* Ditto for the local variables */
+
+#define callpat            frame->Xcallpat
+#define charptr            frame->Xcharptr
+#define data               frame->Xdata
+#define lastptr            frame->Xlastptr
+#define next               frame->Xnext
+#define pp                 frame->Xpp
+#define prev               frame->Xprev
+#define saved_eptr         frame->Xsaved_eptr
+
+#define new_recursive      frame->Xnew_recursive
+
+#define cur_is_word        frame->Xcur_is_word
+#define condition          frame->Xcondition
+#define minimize           frame->Xminimize
+#define prev_is_word       frame->Xprev_is_word
+
+#define original_ims       frame->Xoriginal_ims
+
+#define ctype              frame->Xctype
+#define fc                 frame->Xfc
+#define fi                 frame->Xfi
+#define length             frame->Xlength
+#define max                frame->Xmax
+#define min                frame->Xmin
+#define number             frame->Xnumber
+#define offset             frame->Xoffset
+#define op                 frame->Xop
+#define save_capture_last  frame->Xsave_capture_last
+#define save_offset1       frame->Xsave_offset1
+#define save_offset2       frame->Xsave_offset2
+#define save_offset3       frame->Xsave_offset3
+#define stacksave          frame->Xstacksave
+
+#define newptrb            frame->Xnewptrb
+
+/* When recursion is being used, local variables are allocated on the stack and
+get preserved during recursion in the normal way. In this environment, fi and
+i, and fc and c, can be the same variables. */
+
+#else
+#define fi i
+#define fc c
+
+  const uschar *callpat;       /* Many of these variables are used ony */
+  const uschar *charptr;       /* small blocks of the code. My normal  */
+  const uschar *data;          /* style of coding would have declared  */
+  const uschar *lastptr;       /* them within each of those blocks.    */
+  const uschar *next;          /* However, in order to accommodate the */
+  const uschar *pp;            /* version of this code that uses an    */
+  const uschar *prev;          /* external "stack" implemented on the  */
+  const uschar *saved_eptr;    /* heap, it is easier to declare them   */
+  /* all here, so the declarations can    */
+  recursion_info new_recursive;        /* be cut out in a block. The only      */
+  /* declarations within blocks below are */
+  BOOL cur_is_word;            /* for variables that do not have to    */
+  BOOL condition;              /* be preserved over a recursive call   */
+  BOOL minimize;               /* to RMATCH().                         */
+  BOOL prev_is_word;
+
+  unsigned long int original_ims;
+
+  int ctype;
+  int length;
+  int max;
+  int min;
+  int number;
+  int offset;
+  int op;
+  int save_capture_last;
+  int save_offset1, save_offset2, save_offset3;
+  int stacksave[REC_STACK_SAVE_MAX];
+
+  eptrblock newptrb;
+#endif
+
+
+/* OK, now we can get on with the real code of the function. Recursion is
+specified by the macros RMATCH and RRETURN. When NO_RECURSE is *not* defined,
+these just turn into a recursive call to match() and a "return", respectively.
+However, RMATCH isn't like a function call because it's quite a complicated
+macro. It has to be used in one particular way. This shouldn't, however, impact
+performance when true recursion is being used. */
+
+  if (md->match_call_count++ >= md->match_limit)
+    RRETURN(PCRE_ERROR_MATCHLIMIT);
+
+  original_ims = ims;          /* Save for resetting on ')' */
+
+/* At the start of a bracketed group, add the current subject pointer to the
+stack of such pointers, to be re-instated at the end of the group when we hit
+the closing ket. When match() is called in other circumstances, we don't add to
+this stack. */
+
+  if ((flags & match_isgroup) != 0) {
+    newptrb.epb_prev = eptrb;
+    newptrb.epb_saved_eptr = eptr;
+    eptrb = &newptrb;
+  }
+
+/* Now start processing the operations. */
+
+  for (;;) {
+    op = *ecode;
+    minimize = FALSE;
+
+    /* Opening capturing bracket. If there is space in the offset vector, save
+       the current subject position in the working slot at the top of the vector. We
+       mustn't change the current values of the data slot, because they may be set
+       from a previous iteration of this group, and be referred to by a reference
+       inside the group.
+
+       If the bracket fails to match, we need to restore this value and also the
+       values of the final offsets, in case they were set by a previous iteration of
+       the same bracket.
+
+       If there isn't enough space in the offset vector, treat this as if it were a
+       non-capturing bracket. Don't worry about setting the flag for the error case
+       here; that is handled in the code for KET. */
+
+    if (op > OP_BRA) {
+      number = op - OP_BRA;
+
+      /* For extended extraction brackets (large number), we have to fish out the
+         number from a dummy opcode at the start. */
+
+      if (number > EXTRACT_BASIC_MAX)
+       number = GET2(ecode, 2 + LINK_SIZE);
+      offset = number << 1;
+
+      if (offset < md->offset_max) {
+       save_offset1 = md->offset_vector[offset];
+       save_offset2 = md->offset_vector[offset + 1];
+       save_offset3 = md->offset_vector[md->offset_end - number];
+       save_capture_last = md->capture_last;
+
+       DPRINTF(("saving %d %d %d\n", save_offset1, save_offset2,
+                save_offset3));
+       md->offset_vector[md->offset_end - number] = eptr - md->start_subject;
+
+       do {
+         RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+                match_isgroup);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+         md->capture_last = save_capture_last;
+         ecode += GET(ecode, 1);
+       }
+       while (*ecode == OP_ALT);
+
+       DPRINTF(("bracket %d failed\n", number));
+
+       md->offset_vector[offset] = save_offset1;
+       md->offset_vector[offset + 1] = save_offset2;
+       md->offset_vector[md->offset_end - number] = save_offset3;
+
+       RRETURN(MATCH_NOMATCH);
+      }
+
+      /* Insufficient room for saving captured contents */
+
+      else
+       op = OP_BRA;
+    }
+
+    /* Other types of node can be handled by a switch */
+
+    switch (op) {
+    case OP_BRA:               /* Non-capturing bracket: optimized */
+      DPRINTF(("start bracket 0\n"));
+      do {
+       RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+              match_isgroup);
+       if (rrc != MATCH_NOMATCH)
+         RRETURN(rrc);
+       ecode += GET(ecode, 1);
+      }
+      while (*ecode == OP_ALT);
+      DPRINTF(("bracket 0 failed\n"));
+      RRETURN(MATCH_NOMATCH);
+
+      /* Conditional group: compilation checked that there are no more than
+         two branches. If the condition is false, skipping the first branch takes us
+         past the end if there is only one branch, but that's OK because that is
+         exactly what going to the ket would do. */
+
+    case OP_COND:
+      if (ecode[LINK_SIZE + 1] == OP_CREF) {   /* Condition extract or recurse test */
+       offset = GET2(ecode, LINK_SIZE + 2) << 1;       /* Doubled ref number */
+       condition = (offset == CREF_RECURSE * 2) ?
+         (md->recursive != NULL) :
+         (offset < offset_top && md->offset_vector[offset] >= 0);
+       RMATCH(rrc, eptr, ecode + (condition ?
+                                  (LINK_SIZE + 4) : (LINK_SIZE + 1 +
+                                                     GET(ecode, 1))),
+              offset_top, md, ims, eptrb, match_isgroup);
+       RRETURN(rrc);
+      }
+
+      /* The condition is an assertion. Call match() to evaluate it - setting
+         the final argument TRUE causes it to stop at the end of an assertion. */
+
+      else {
+       RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL,
+              match_condassert | match_isgroup);
+       if (rrc == MATCH_MATCH) {
+         ecode += 1 + LINK_SIZE + GET(ecode, LINK_SIZE + 2);
+         while (*ecode == OP_ALT)
+           ecode += GET(ecode, 1);
+       } else if (rrc != MATCH_NOMATCH) {
+         RRETURN(rrc);         /* Need braces because of following else */
+       } else
+         ecode += GET(ecode, 1);
+       RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+              match_isgroup);
+       RRETURN(rrc);
+      }
+      /* Control never reaches here */
+
+      /* Skip over conditional reference or large extraction number data if
+         encountered. */
+
+    case OP_CREF:
+    case OP_BRANUMBER:
+      ecode += 3;
+      break;
+
+      /* End of the pattern. If we are in a recursion, we should restore the
+         offsets appropriately and continue from after the call. */
+
+    case OP_END:
+      if (md->recursive != NULL && md->recursive->group_num == 0) {
+       recursion_info *rec = md->recursive;
+       DPRINTF(("Hit the end in a (?0) recursion\n"));
+       md->recursive = rec->prevrec;
+       memmove(md->offset_vector, rec->offset_save,
+               rec->saved_max * sizeof(int));
+       md->start_match = rec->save_start;
+       ims = original_ims;
+       ecode = rec->after_call;
+       break;
+      }
+
+      /* Otherwise, if PCRE_NOTEMPTY is set, fail if we have matched an empty
+         string - backtracking will then try other alternatives, if any. */
+
+      if (md->notempty && eptr == md->start_match)
+       RRETURN(MATCH_NOMATCH);
+      md->end_match_ptr = eptr;        /* Record where we ended */
+      md->end_offset_top = offset_top; /* and how many extracts were taken */
+      RRETURN(MATCH_MATCH);
+
+      /* Change option settings */
+
+    case OP_OPT:
+      ims = ecode[1];
+      ecode += 2;
+      DPRINTF(("ims set to %02lx\n", ims));
+      break;
+
+      /* Assertion brackets. Check the alternative branches in turn - the
+         matching won't pass the KET for an assertion. If any one branch matches,
+         the assertion is true. Lookbehind assertions have an OP_REVERSE item at the
+         start of each branch to move the current point backwards, so the code at
+         this level is identical to the lookahead case. */
+
+    case OP_ASSERT:
+    case OP_ASSERTBACK:
+      do {
+       RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL,
+              match_isgroup);
+       if (rrc == MATCH_MATCH)
+         break;
+       if (rrc != MATCH_NOMATCH)
+         RRETURN(rrc);
+       ecode += GET(ecode, 1);
+      }
+      while (*ecode == OP_ALT);
+      if (*ecode == OP_KET)
+       RRETURN(MATCH_NOMATCH);
+
+      /* If checking an assertion for a condition, return MATCH_MATCH. */
+
+      if ((flags & match_condassert) != 0)
+       RRETURN(MATCH_MATCH);
+
+      /* Continue from after the assertion, updating the offsets high water
+         mark, since extracts may have been taken during the assertion. */
+
+      do
+       ecode += GET(ecode, 1);
+      while (*ecode == OP_ALT);
+      ecode += 1 + LINK_SIZE;
+      offset_top = md->end_offset_top;
+      continue;
+
+      /* Negative assertion: all branches must fail to match */
+
+    case OP_ASSERT_NOT:
+    case OP_ASSERTBACK_NOT:
+      do {
+       RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, NULL,
+              match_isgroup);
+       if (rrc == MATCH_MATCH)
+         RRETURN(MATCH_NOMATCH);
+       if (rrc != MATCH_NOMATCH)
+         RRETURN(rrc);
+       ecode += GET(ecode, 1);
+      }
+      while (*ecode == OP_ALT);
+
+      if ((flags & match_condassert) != 0)
+       RRETURN(MATCH_MATCH);
+
+      ecode += 1 + LINK_SIZE;
+      continue;
+
+      /* Move the subject pointer back. This occurs only at the start of
+         each branch of a lookbehind assertion. If we are too close to the start to
+         move back, this match function fails. When working with UTF-8 we move
+         back a number of characters, not bytes. */
+
+    case OP_REVERSE:
+
+      /* No UTF-8 support, or not in UTF-8 mode: count is byte count */
+
+      {
+       eptr -= GET(ecode, 1);
+       if (eptr < md->start_subject)
+         RRETURN(MATCH_NOMATCH);
+      }
+
+      /* Skip to next op code */
+
+      ecode += 1 + LINK_SIZE;
+      break;
+
+      /* The callout item calls an external function, if one is provided, passing
+         details of the match so far. This is mainly for debugging, though the
+         function is able to force a failure. */
+
+    case OP_CALLOUT:
+      if (pcre_callout != NULL) {
+       pcre_callout_block cb;
+       cb.version = 0;         /* Version 0 of the callout block */
+       cb.callout_number = ecode[1];
+       cb.offset_vector = md->offset_vector;
+       cb.subject = (const char *) md->start_subject;
+       cb.subject_length = md->end_subject - md->start_subject;
+       cb.start_match = md->start_match - md->start_subject;
+       cb.current_position = eptr - md->start_subject;
+       cb.capture_top = offset_top / 2;
+       cb.capture_last = md->capture_last;
+       cb.callout_data = md->callout_data;
+       if ((rrc = (*pcre_callout) (&cb)) > 0)
+         RRETURN(MATCH_NOMATCH);
+       if (rrc < 0)
+         RRETURN(rrc);
+      }
+      ecode += 2;
+      break;
+
+      /* Recursion either matches the current regex, or some subexpression. The
+         offset data is the offset to the starting bracket from the start of the
+         whole pattern. (This is so that it works from duplicated subpatterns.)
+
+         If there are any capturing brackets started but not finished, we have to
+         save their starting points and reinstate them after the recursion. However,
+         we don't know how many such there are (offset_top records the completed
+         total) so we just have to save all the potential data. There may be up to
+         65535 such values, which is too large to put on the stack, but using malloc
+         for small numbers seems expensive. As a compromise, the stack is used when
+         there are no more than REC_STACK_SAVE_MAX values to store; otherwise malloc
+         is used. A problem is what to do if the malloc fails ... there is no way of
+         returning to the top level with an error. Save the top REC_STACK_SAVE_MAX
+         values on the stack, and accept that the rest may be wrong.
+
+         There are also other values that have to be saved. We use a chained
+         sequence of blocks that actually live on the stack. Thanks to Robin Houston
+         for the original version of this logic. */
+
+    case OP_RECURSE:
+      {
+       callpat = md->start_code + GET(ecode, 1);
+       new_recursive.group_num = *callpat - OP_BRA;
+
+       /* For extended extraction brackets (large number), we have to fish out
+          the number from a dummy opcode at the start. */
+
+       if (new_recursive.group_num > EXTRACT_BASIC_MAX)
+         new_recursive.group_num = GET2(callpat, 2 + LINK_SIZE);
+
+       /* Add to "recursing stack" */
+
+       new_recursive.prevrec = md->recursive;
+       md->recursive = &new_recursive;
+
+       /* Find where to continue from afterwards */
+
+       ecode += 1 + LINK_SIZE;
+       new_recursive.after_call = ecode;
+
+       /* Now save the offset data. */
+
+       new_recursive.saved_max = md->offset_end;
+       if (new_recursive.saved_max <= REC_STACK_SAVE_MAX)
+         new_recursive.offset_save = stacksave;
+       else {
+         new_recursive.offset_save =
+           (int *) malloc(new_recursive.saved_max * sizeof(int));
+         if (new_recursive.offset_save == NULL)
+           RRETURN(PCRE_ERROR_NOMEMORY);
+       }
+
+       memcpy(new_recursive.offset_save, md->offset_vector,
+              new_recursive.saved_max * sizeof(int));
+       new_recursive.save_start = md->start_match;
+       md->start_match = eptr;
+
+       /* OK, now we can do the recursion. For each top-level alternative we
+          restore the offset and recursion data. */
+
+       DPRINTF(("Recursing into group %d\n", new_recursive.group_num));
+       do {
+         RMATCH(rrc, eptr, callpat + 1 + LINK_SIZE, offset_top, md, ims,
+                eptrb, match_isgroup);
+         if (rrc == MATCH_MATCH) {
+           md->recursive = new_recursive.prevrec;
+           if (new_recursive.offset_save != stacksave)
+             free(new_recursive.offset_save);
+           RRETURN(MATCH_MATCH);
+         } else if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+
+         md->recursive = &new_recursive;
+         memcpy(md->offset_vector, new_recursive.offset_save,
+                new_recursive.saved_max * sizeof(int));
+         callpat += GET(callpat, 1);
+       }
+       while (*callpat == OP_ALT);
+
+       DPRINTF(("Recursion didn't match\n"));
+       md->recursive = new_recursive.prevrec;
+       if (new_recursive.offset_save != stacksave)
+         free(new_recursive.offset_save);
+       RRETURN(MATCH_NOMATCH);
+      }
+      /* Control never reaches here */
+
+      /* "Once" brackets are like assertion brackets except that after a match,
+         the point in the subject string is not moved back. Thus there can never be
+         a move back into the brackets. Friedl calls these "atomic" subpatterns.
+         Check the alternative branches in turn - the matching won't pass the KET
+         for this kind of subpattern. If any one branch matches, we carry on as at
+         the end of a normal bracket, leaving the subject pointer. */
+
+    case OP_ONCE:
+      {
+       prev = ecode;
+       saved_eptr = eptr;
+
+       do {
+         RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims,
+                eptrb, match_isgroup);
+         if (rrc == MATCH_MATCH)
+           break;
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+         ecode += GET(ecode, 1);
+       }
+       while (*ecode == OP_ALT);
+
+       /* If hit the end of the group (which could be repeated), fail */
+
+       if (*ecode != OP_ONCE && *ecode != OP_ALT)
+         RRETURN(MATCH_NOMATCH);
+
+       /* Continue as from after the assertion, updating the offsets high water
+          mark, since extracts may have been taken. */
+
+       do
+         ecode += GET(ecode, 1);
+       while (*ecode == OP_ALT);
+
+       offset_top = md->end_offset_top;
+       eptr = md->end_match_ptr;
+
+       /* For a non-repeating ket, just continue at this level. This also
+          happens for a repeating ket if no characters were matched in the group.
+          This is the forcible breaking of infinite loops as implemented in Perl
+          5.005. If there is an options reset, it will get obeyed in the normal
+          course of events. */
+
+       if (*ecode == OP_KET || eptr == saved_eptr) {
+         ecode += 1 + LINK_SIZE;
+         break;
+       }
+
+       /* The repeating kets try the rest of the pattern or restart from the
+          preceding bracket, in the appropriate order. We need to reset any options
+          that changed within the bracket before re-running it, so check the next
+          opcode. */
+
+       if (ecode[1 + LINK_SIZE] == OP_OPT) {
+         ims = (ims & ~PCRE_IMS) | ecode[4];
+         DPRINTF(("ims set to %02lx at group repeat\n", ims));
+       }
+
+       if (*ecode == OP_KETRMIN) {
+         RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+                0);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+         RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+       } else {                /* OP_KETRMAX */
+
+         RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+         RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+                0);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+       }
+      }
+      RRETURN(MATCH_NOMATCH);
+
+      /* An alternation is the end of a branch; scan along to find the end of the
+         bracketed group and go to there. */
+
+    case OP_ALT:
+      do
+       ecode += GET(ecode, 1);
+      while (*ecode == OP_ALT);
+      break;
+
+      /* BRAZERO and BRAMINZERO occur just before a bracket group, indicating
+         that it may occur zero times. It may repeat infinitely, or not at all -
+         i.e. it could be ()* or ()? in the pattern. Brackets with fixed upper
+         repeat limits are compiled as a number of copies, with the optional ones
+         preceded by BRAZERO or BRAMINZERO. */
+
+    case OP_BRAZERO:
+      {
+       next = ecode + 1;
+       RMATCH(rrc, eptr, next, offset_top, md, ims, eptrb, match_isgroup);
+       if (rrc != MATCH_NOMATCH)
+         RRETURN(rrc);
+       do
+         next += GET(next, 1);
+       while (*next == OP_ALT);
+       ecode = next + 1 + LINK_SIZE;
+      }
+      break;
+
+    case OP_BRAMINZERO:
+      {
+       next = ecode + 1;
+       do
+         next += GET(next, 1);
+       while (*next == OP_ALT);
+       RMATCH(rrc, eptr, next + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+              match_isgroup);
+       if (rrc != MATCH_NOMATCH)
+         RRETURN(rrc);
+       ecode++;
+      }
+      break;
+
+      /* End of a group, repeated or non-repeating. If we are at the end of
+         an assertion "group", stop matching and return MATCH_MATCH, but record the
+         current high water mark for use by positive assertions. Do this also
+         for the "once" (not-backup up) groups. */
+
+    case OP_KET:
+    case OP_KETRMIN:
+    case OP_KETRMAX:
+      {
+       prev = ecode - GET(ecode, 1);
+       saved_eptr = eptrb->epb_saved_eptr;
+
+       /* Back up the stack of bracket start pointers. */
+
+       eptrb = eptrb->epb_prev;
+
+       if (*prev == OP_ASSERT || *prev == OP_ASSERT_NOT ||
+           *prev == OP_ASSERTBACK || *prev == OP_ASSERTBACK_NOT ||
+           *prev == OP_ONCE) {
+         md->end_match_ptr = eptr;     /* For ONCE */
+         md->end_offset_top = offset_top;
+         RRETURN(MATCH_MATCH);
+       }
+
+       /* In all other cases except a conditional group we have to check the
+          group number back at the start and if necessary complete handling an
+          extraction by setting the offsets and bumping the high water mark. */
+
+       if (*prev != OP_COND) {
+         number = *prev - OP_BRA;
+
+         /* For extended extraction brackets (large number), we have to fish out
+            the number from a dummy opcode at the start. */
+
+         if (number > EXTRACT_BASIC_MAX)
+           number = GET2(prev, 2 + LINK_SIZE);
+         offset = number << 1;
+
+         /* Test for a numbered group. This includes groups called as a result
+            of recursion. Note that whole-pattern recursion is coded as a recurse
+            into group 0, so it won't be picked up here. Instead, we catch it when
+            the OP_END is reached. */
+
+         if (number > 0) {
+           md->capture_last = number;
+           if (offset >= md->offset_max)
+             md->offset_overflow = TRUE;
+           else {
+             md->offset_vector[offset] =
+               md->offset_vector[md->offset_end - number];
+             md->offset_vector[offset + 1] = eptr - md->start_subject;
+             if (offset_top <= offset)
+               offset_top = offset + 2;
+           }
+
+           /* Handle a recursively called group. Restore the offsets
+              appropriately and continue from after the call. */
+
+           if (md->recursive != NULL && md->recursive->group_num == number) {
+             recursion_info *rec = md->recursive;
+             DPRINTF(("Recursion (%d) succeeded - continuing\n", number));
+             md->recursive = rec->prevrec;
+             md->start_match = rec->save_start;
+             memcpy(md->offset_vector, rec->offset_save,
+                    rec->saved_max * sizeof(int));
+             ecode = rec->after_call;
+             ims = original_ims;
+             break;
+           }
+         }
+       }
+
+       /* Reset the value of the ims flags, in case they got changed during
+          the group. */
+
+       ims = original_ims;
+       DPRINTF(("ims reset to %02lx\n", ims));
+
+       /* For a non-repeating ket, just continue at this level. This also
+          happens for a repeating ket if no characters were matched in the group.
+          This is the forcible breaking of infinite loops as implemented in Perl
+          5.005. If there is an options reset, it will get obeyed in the normal
+          course of events. */
+
+       if (*ecode == OP_KET || eptr == saved_eptr) {
+         ecode += 1 + LINK_SIZE;
+         break;
+       }
+
+       /* The repeating kets try the rest of the pattern or restart from the
+          preceding bracket, in the appropriate order. */
+
+       if (*ecode == OP_KETRMIN) {
+         RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+                0);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+         RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+       } else {                /* OP_KETRMAX */
+
+         RMATCH(rrc, eptr, prev, offset_top, md, ims, eptrb, match_isgroup);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+         RMATCH(rrc, eptr, ecode + 1 + LINK_SIZE, offset_top, md, ims, eptrb,
+                0);
+         if (rrc != MATCH_NOMATCH)
+           RRETURN(rrc);
+       }
+      }
+
+      RRETURN(MATCH_NOMATCH);
+
+      /* Start of subject unless notbol, or after internal newline if multiline */
+
+    case OP_CIRC:
+      if (md->notbol && eptr == md->start_subject)
+       RRETURN(MATCH_NOMATCH);
+      if ((ims & PCRE_MULTILINE) != 0) {
+       if (eptr != md->start_subject && eptr[-1] != NEWLINE)
+         RRETURN(MATCH_NOMATCH);
+       ecode++;
+       break;
+      }
+      /* ... else fall through */
+
+      /* Start of subject assertion */
+
+    case OP_SOD:
+      if (eptr != md->start_subject)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+      /* Start of match assertion */
+
+    case OP_SOM:
+      if (eptr != md->start_subject + md->start_offset)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+      /* Assert before internal newline if multiline, or before a terminating
+         newline unless endonly is set, else end of subject unless noteol is set. */
+
+    case OP_DOLL:
+      if ((ims & PCRE_MULTILINE) != 0) {
+       if (eptr < md->end_subject) {
+         if (*eptr != NEWLINE)
+           RRETURN(MATCH_NOMATCH);
+       } else {
+         if (md->noteol)
+           RRETURN(MATCH_NOMATCH);
+       }
+       ecode++;
+       break;
+      } else {
+       if (md->noteol)
+         RRETURN(MATCH_NOMATCH);
+       if (!md->endonly) {
+         if (eptr < md->end_subject - 1 ||
+             (eptr == md->end_subject - 1 && *eptr != NEWLINE))
+           RRETURN(MATCH_NOMATCH);
+         ecode++;
+         break;
+       }
+      }
+      /* ... else fall through */
+
+      /* End of subject assertion (\z) */
+
+    case OP_EOD:
+      if (eptr < md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+      /* End of subject or ending \n assertion (\Z) */
+
+    case OP_EODN:
+      if (eptr < md->end_subject - 1 ||
+         (eptr == md->end_subject - 1 && *eptr != NEWLINE))
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+      /* Word boundary assertions */
+
+    case OP_NOT_WORD_BOUNDARY:
+    case OP_WORD_BOUNDARY:
+      {
+
+       /* Find out if the previous and current characters are "word" characters.
+          It takes a bit more work in UTF-8 mode. Characters > 255 are assumed to
+          be "non-word" characters. */
+
+
+       /* More streamlined when not in UTF-8 mode */
+
+       {
+         prev_is_word = (eptr != md->start_subject) &&
+           ((md->ctypes[eptr[-1]] & ctype_word) != 0);
+         cur_is_word = (eptr < md->end_subject) &&
+           ((md->ctypes[*eptr] & ctype_word) != 0);
+       }
+
+       /* Now see if the situation is what we want */
+
+       if ((*ecode++ == OP_WORD_BOUNDARY) ?
+           cur_is_word == prev_is_word : cur_is_word != prev_is_word)
+         RRETURN(MATCH_NOMATCH);
+      }
+      break;
+
+      /* Match a single character type; inline for speed */
+
+    case OP_ANY:
+      if ((ims & PCRE_DOTALL) == 0 && eptr < md->end_subject
+         && *eptr == NEWLINE)
+       RRETURN(MATCH_NOMATCH);
+      if (eptr++ >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+      /* Match a single byte, even in UTF-8 mode. This opcode really does match
+         any byte, even newline, independent of the setting of PCRE_DOTALL. */
+
+    case OP_ANYBYTE:
+      if (eptr++ >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+    case OP_NOT_DIGIT:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      GETCHARINCTEST(c, eptr);
+      if ((md->ctypes[c] & ctype_digit) != 0)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+    case OP_DIGIT:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      GETCHARINCTEST(c, eptr);
+      if ((md->ctypes[c] & ctype_digit) == 0)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+    case OP_NOT_WHITESPACE:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      GETCHARINCTEST(c, eptr);
+      if ((md->ctypes[c] & ctype_space) != 0)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+    case OP_WHITESPACE:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      GETCHARINCTEST(c, eptr);
+      if ((md->ctypes[c] & ctype_space) == 0)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+    case OP_NOT_WORDCHAR:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      GETCHARINCTEST(c, eptr);
+      if ((md->ctypes[c] & ctype_word) != 0)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+    case OP_WORDCHAR:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      GETCHARINCTEST(c, eptr);
+      if ((md->ctypes[c] & ctype_word) == 0)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      break;
+
+      /* Match a back reference, possibly repeatedly. Look past the end of the
+         item to see if there is repeat information following. The code is similar
+         to that for character classes, but repeated for efficiency. Then obey
+         similar code to character type repeats - written out again for speed.
+         However, if the referenced string is the empty string, always treat
+         it as matched, any number of times (otherwise there could be infinite
+         loops). */
+
+    case OP_REF:
+      {
+       offset = GET2(ecode, 1) << 1;   /* Doubled ref number */
+       ecode += 3;             /* Advance past item */
+
+       /* If the reference is unset, set the length to be longer than the amount
+          of subject left; this ensures that every attempt at a match fails. We
+          can't just fail here, because of the possibility of quantifiers with zero
+          minima. */
+
+       length = (offset >= offset_top || md->offset_vector[offset] < 0) ?
+         md->end_subject - eptr + 1 :
+         md->offset_vector[offset + 1] - md->offset_vector[offset];
+
+       /* Set up for repetition, or handle the non-repeated case */
+
+       switch (*ecode) {
+       case OP_CRSTAR:
+       case OP_CRMINSTAR:
+       case OP_CRPLUS:
+       case OP_CRMINPLUS:
+       case OP_CRQUERY:
+       case OP_CRMINQUERY:
+         c = *ecode++ - OP_CRSTAR;
+         minimize = (c & 1) != 0;
+         min = rep_min[c];     /* Pick up values from tables; */
+         max = rep_max[c];     /* zero for max => infinity */
+         if (max == 0)
+           max = INT_MAX;
+         break;
+
+       case OP_CRRANGE:
+       case OP_CRMINRANGE:
+         minimize = (*ecode == OP_CRMINRANGE);
+         min = GET2(ecode, 1);
+         max = GET2(ecode, 3);
+         if (max == 0)
+           max = INT_MAX;
+         ecode += 5;
+         break;
+
+       default:                /* No repeat follows */
+         if (!match_ref(offset, eptr, length, md, ims))
+           RRETURN(MATCH_NOMATCH);
+         eptr += length;
+         continue;             /* With the main loop */
+       }
+
+       /* If the length of the reference is zero, just continue with the
+          main loop. */
+
+       if (length == 0)
+         continue;
+
+       /* First, ensure the minimum number of matches are present. We get back
+          the length of the reference string explicitly rather than passing the
+          address of eptr, so that eptr can be a register variable. */
+
+       for (i = 1; i <= min; i++) {
+         if (!match_ref(offset, eptr, length, md, ims))
+           RRETURN(MATCH_NOMATCH);
+         eptr += length;
+       }
+
+       /* If min = max, continue at the same level without recursion.
+          They are not both allowed to be zero. */
+
+       if (min == max)
+         continue;
+
+       /* If minimizing, keep trying and advancing the pointer */
+
+       if (minimize) {
+         for (fi = min;; fi++) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+           if (fi >= max || !match_ref(offset, eptr, length, md, ims))
+             RRETURN(MATCH_NOMATCH);
+           eptr += length;
+         }
+         /* Control never gets here */
+       }
+
+       /* If maximizing, find the longest string and work backwards */
+
+       else {
+         pp = eptr;
+         for (i = min; i < max; i++) {
+           if (!match_ref(offset, eptr, length, md, ims))
+             break;
+           eptr += length;
+         }
+         while (eptr >= pp) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+           eptr -= length;
+         }
+         RRETURN(MATCH_NOMATCH);
+       }
+      }
+      /* Control never gets here */
+
+
+
+      /* Match a bit-mapped character class, possibly repeatedly. This op code is
+         used when all the characters in the class have values in the range 0-255.
+         The only difference between OP_CLASS and OP_NCLASS occurs when a data
+         character outside the range is encountered.
+
+         First, look past the end of the item to see if there is repeat information
+         following. Then obey similar code to character type repeats - written out
+         again for speed. */
+
+    case OP_NCLASS:
+    case OP_CLASS:
+      {
+       data = ecode + 1;       /* Save for matching */
+       ecode += 33;            /* Advance past the item */
+
+       switch (*ecode) {
+       case OP_CRSTAR:
+       case OP_CRMINSTAR:
+       case OP_CRPLUS:
+       case OP_CRMINPLUS:
+       case OP_CRQUERY:
+       case OP_CRMINQUERY:
+         c = *ecode++ - OP_CRSTAR;
+         minimize = (c & 1) != 0;
+         min = rep_min[c];     /* Pick up values from tables; */
+         max = rep_max[c];     /* zero for max => infinity */
+         if (max == 0)
+           max = INT_MAX;
+         break;
+
+       case OP_CRRANGE:
+       case OP_CRMINRANGE:
+         minimize = (*ecode == OP_CRMINRANGE);
+         min = GET2(ecode, 1);
+         max = GET2(ecode, 3);
+         if (max == 0)
+           max = INT_MAX;
+         ecode += 5;
+         break;
+
+       default:                /* No repeat follows */
+         min = max = 1;
+         break;
+       }
+
+       /* First, ensure the minimum number of matches are present. */
+
+       /* Not UTF-8 mode */
+       {
+         for (i = 1; i <= min; i++) {
+           if (eptr >= md->end_subject)
+             RRETURN(MATCH_NOMATCH);
+           c = *eptr++;
+           if ((data[c / 8] & (1 << (c & 7))) == 0)
+             RRETURN(MATCH_NOMATCH);
+         }
+       }
+
+       /* If max == min we can continue with the main loop without the
+          need to recurse. */
+
+       if (min == max)
+         continue;
+
+       /* If minimizing, keep testing the rest of the expression and advancing
+          the pointer while it matches the class. */
+
+       if (minimize) {
+         /* Not UTF-8 mode */
+         {
+           for (fi = min;; fi++) {
+             RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+             if (rrc != MATCH_NOMATCH)
+               RRETURN(rrc);
+             if (fi >= max || eptr >= md->end_subject)
+               RRETURN(MATCH_NOMATCH);
+             c = *eptr++;
+             if ((data[c / 8] & (1 << (c & 7))) == 0)
+               RRETURN(MATCH_NOMATCH);
+           }
+         }
+         /* Control never gets here */
+       }
+
+       /* If maximizing, find the longest possible run, then work backwards. */
+
+       else {
+         pp = eptr;
+
+         /* Not UTF-8 mode */
+         {
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject)
+               break;
+             c = *eptr;
+             if ((data[c / 8] & (1 << (c & 7))) == 0)
+               break;
+             eptr++;
+           }
+           while (eptr >= pp) {
+             RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+             eptr--;
+             if (rrc != MATCH_NOMATCH)
+               RRETURN(rrc);
+           }
+         }
+
+         RRETURN(MATCH_NOMATCH);
+       }
+      }
+      /* Control never gets here */
+
+
+      /* Match an extended character class. This opcode is encountered only
+         in UTF-8 mode, because that's the only time it is compiled. */
+
+
+      /* Match a run of characters */
+
+    case OP_CHARS:
+      {
+       register int slen = ecode[1];
+       ecode += 2;
+
+
+       if (slen > md->end_subject - eptr)
+         RRETURN(MATCH_NOMATCH);
+       if ((ims & PCRE_CASELESS) != 0) {
+         while (slen-- > 0)
+           if (md->lcc[*ecode++] != md->lcc[*eptr++])
+             RRETURN(MATCH_NOMATCH);
+       } else {
+         while (slen-- > 0)
+           if (*ecode++ != *eptr++)
+             RRETURN(MATCH_NOMATCH);
+       }
+      }
+      break;
+
+      /* Match a single character repeatedly; different opcodes share code. */
+
+    case OP_EXACT:
+      min = max = GET2(ecode, 1);
+      ecode += 3;
+      goto REPEATCHAR;
+
+    case OP_UPTO:
+    case OP_MINUPTO:
+      min = 0;
+      max = GET2(ecode, 1);
+      minimize = *ecode == OP_MINUPTO;
+      ecode += 3;
+      goto REPEATCHAR;
+
+    case OP_STAR:
+    case OP_MINSTAR:
+    case OP_PLUS:
+    case OP_MINPLUS:
+    case OP_QUERY:
+    case OP_MINQUERY:
+      c = *ecode++ - OP_STAR;
+      minimize = (c & 1) != 0;
+      min = rep_min[c];                /* Pick up values from tables; */
+      max = rep_max[c];                /* zero for max => infinity */
+      if (max == 0)
+       max = INT_MAX;
+
+      /* Common code for all repeated single-character matches. We can give
+         up quickly if there are fewer than the minimum number of characters left in
+         the subject. */
+
+    REPEATCHAR:
+
+      /* When not in UTF-8 mode, load a single-byte character. */
+      {
+       if (min > md->end_subject - eptr)
+         RRETURN(MATCH_NOMATCH);
+       fc = *ecode++;
+      }
+
+      /* The value of fc at this point is always less than 256, though we may or
+         may not be in UTF-8 mode. The code is duplicated for the caseless and
+         caseful cases, for speed, since matching characters is likely to be quite
+         common. First, ensure the minimum number of matches are present. If min =
+         max, continue at the same level without recursing. Otherwise, if
+         minimizing, keep trying the rest of the expression and advancing one
+         matching character if failing, up to the maximum. Alternatively, if
+         maximizing, find the maximum number of characters and work backwards. */
+
+      DPRINTF(("matching %c{%d,%d} against subject %.*s\n", fc, min, max,
+              max, eptr));
+
+      if ((ims & PCRE_CASELESS) != 0) {
+       fc = md->lcc[fc];
+       for (i = 1; i <= min; i++)
+         if (fc != md->lcc[*eptr++])
+           RRETURN(MATCH_NOMATCH);
+       if (min == max)
+         continue;
+       if (minimize) {
+         for (fi = min;; fi++) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+           if (fi >= max || eptr >= md->end_subject || fc != md->lcc[*eptr++])
+             RRETURN(MATCH_NOMATCH);
+         }
+         /* Control never gets here */
+       } else {
+         pp = eptr;
+         for (i = min; i < max; i++) {
+           if (eptr >= md->end_subject || fc != md->lcc[*eptr])
+             break;
+           eptr++;
+         }
+         while (eptr >= pp) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           eptr--;
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+         }
+         RRETURN(MATCH_NOMATCH);
+       }
+       /* Control never gets here */
+      }
+
+      /* Caseful comparisons (includes all multi-byte characters) */
+
+      else {
+       for (i = 1; i <= min; i++)
+         if (fc != *eptr++)
+           RRETURN(MATCH_NOMATCH);
+       if (min == max)
+         continue;
+       if (minimize) {
+         for (fi = min;; fi++) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+           if (fi >= max || eptr >= md->end_subject || fc != *eptr++)
+             RRETURN(MATCH_NOMATCH);
+         }
+         /* Control never gets here */
+       } else {
+         pp = eptr;
+         for (i = min; i < max; i++) {
+           if (eptr >= md->end_subject || fc != *eptr)
+             break;
+           eptr++;
+         }
+         while (eptr >= pp) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           eptr--;
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+         }
+         RRETURN(MATCH_NOMATCH);
+       }
+      }
+      /* Control never gets here */
+
+      /* Match a negated single one-byte character. The character we are
+         checking can be multibyte. */
+
+    case OP_NOT:
+      if (eptr >= md->end_subject)
+       RRETURN(MATCH_NOMATCH);
+      ecode++;
+      GETCHARINCTEST(c, eptr);
+      if ((ims & PCRE_CASELESS) != 0) {
+       c = md->lcc[c];
+       if (md->lcc[*ecode++] == c)
+         RRETURN(MATCH_NOMATCH);
+      } else {
+       if (*ecode++ == c)
+         RRETURN(MATCH_NOMATCH);
+      }
+      break;
+
+      /* Match a negated single one-byte character repeatedly. This is almost a
+         repeat of the code for a repeated single character, but I haven't found a
+         nice way of commoning these up that doesn't require a test of the
+         positive/negative option for each character match. Maybe that wouldn't add
+         very much to the time taken, but character matching *is* what this is all
+         about... */
+
+    case OP_NOTEXACT:
+      min = max = GET2(ecode, 1);
+      ecode += 3;
+      goto REPEATNOTCHAR;
+
+    case OP_NOTUPTO:
+    case OP_NOTMINUPTO:
+      min = 0;
+      max = GET2(ecode, 1);
+      minimize = *ecode == OP_NOTMINUPTO;
+      ecode += 3;
+      goto REPEATNOTCHAR;
+
+    case OP_NOTSTAR:
+    case OP_NOTMINSTAR:
+    case OP_NOTPLUS:
+    case OP_NOTMINPLUS:
+    case OP_NOTQUERY:
+    case OP_NOTMINQUERY:
+      c = *ecode++ - OP_NOTSTAR;
+      minimize = (c & 1) != 0;
+      min = rep_min[c];                /* Pick up values from tables; */
+      max = rep_max[c];                /* zero for max => infinity */
+      if (max == 0)
+       max = INT_MAX;
+
+      /* Common code for all repeated single-character (less than 255) matches.
+         We can give up quickly if there are fewer than the minimum number of
+         characters left in the subject. */
+
+    REPEATNOTCHAR:
+      if (min > md->end_subject - eptr)
+       RRETURN(MATCH_NOMATCH);
+      fc = *ecode++;
+
+      /* The code is duplicated for the caseless and caseful cases, for speed,
+         since matching characters is likely to be quite common. First, ensure the
+         minimum number of matches are present. If min = max, continue at the same
+         level without recursing. Otherwise, if minimizing, keep trying the rest of
+         the expression and advancing one matching character if failing, up to the
+         maximum. Alternatively, if maximizing, find the maximum number of
+         characters and work backwards. */
+
+      DPRINTF(("negative matching %c{%d,%d} against subject %.*s\n", fc, min,
+              max, max, eptr));
+
+      if ((ims & PCRE_CASELESS) != 0) {
+       fc = md->lcc[fc];
+
+
+       /* Not UTF-8 mode */
+       {
+         for (i = 1; i <= min; i++)
+           if (fc == md->lcc[*eptr++])
+             RRETURN(MATCH_NOMATCH);
+       }
+
+       if (min == max)
+         continue;
+
+       if (minimize) {
+         /* Not UTF-8 mode */
+         {
+           for (fi = min;; fi++) {
+             RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+             if (rrc != MATCH_NOMATCH)
+               RRETURN(rrc);
+             if (fi >= max || eptr >= md->end_subject
+                 || fc == md->lcc[*eptr++])
+               RRETURN(MATCH_NOMATCH);
+           }
+         }
+         /* Control never gets here */
+       }
+
+       /* Maximize case */
+
+       else {
+         pp = eptr;
+
+         /* Not UTF-8 mode */
+         {
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject || fc == md->lcc[*eptr])
+               break;
+             eptr++;
+           }
+           while (eptr >= pp) {
+             RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+             if (rrc != MATCH_NOMATCH)
+               RRETURN(rrc);
+             eptr--;
+           }
+         }
+
+         RRETURN(MATCH_NOMATCH);
+       }
+       /* Control never gets here */
+      }
+
+      /* Caseful comparisons */
+
+      else {
+       /* Not UTF-8 mode */
+       {
+         for (i = 1; i <= min; i++)
+           if (fc == *eptr++)
+             RRETURN(MATCH_NOMATCH);
+       }
+
+       if (min == max)
+         continue;
+
+       if (minimize) {
+         /* Not UTF-8 mode */
+         {
+           for (fi = min;; fi++) {
+             RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+             if (rrc != MATCH_NOMATCH)
+               RRETURN(rrc);
+             if (fi >= max || eptr >= md->end_subject || fc == *eptr++)
+               RRETURN(MATCH_NOMATCH);
+           }
+         }
+         /* Control never gets here */
+       }
+
+       /* Maximize case */
+
+       else {
+         pp = eptr;
+
+         /* Not UTF-8 mode */
+         {
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject || fc == *eptr)
+               break;
+             eptr++;
+           }
+           while (eptr >= pp) {
+             RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+             if (rrc != MATCH_NOMATCH)
+               RRETURN(rrc);
+             eptr--;
+           }
+         }
+
+         RRETURN(MATCH_NOMATCH);
+       }
+      }
+      /* Control never gets here */
+
+      /* Match a single character type repeatedly; several different opcodes
+         share code. This is very similar to the code for single characters, but we
+         repeat it in the interests of efficiency. */
+
+    case OP_TYPEEXACT:
+      min = max = GET2(ecode, 1);
+      minimize = TRUE;
+      ecode += 3;
+      goto REPEATTYPE;
+
+    case OP_TYPEUPTO:
+    case OP_TYPEMINUPTO:
+      min = 0;
+      max = GET2(ecode, 1);
+      minimize = *ecode == OP_TYPEMINUPTO;
+      ecode += 3;
+      goto REPEATTYPE;
+
+    case OP_TYPESTAR:
+    case OP_TYPEMINSTAR:
+    case OP_TYPEPLUS:
+    case OP_TYPEMINPLUS:
+    case OP_TYPEQUERY:
+    case OP_TYPEMINQUERY:
+      c = *ecode++ - OP_TYPESTAR;
+      minimize = (c & 1) != 0;
+      min = rep_min[c];                /* Pick up values from tables; */
+      max = rep_max[c];                /* zero for max => infinity */
+      if (max == 0)
+       max = INT_MAX;
+
+      /* Common code for all repeated single character type matches. Note that
+         in UTF-8 mode, '.' matches a character of any length, but for the other
+         character types, the valid characters are all one-byte long. */
+
+    REPEATTYPE:
+      ctype = *ecode++;                /* Code for the character type */
+
+      /* First, ensure the minimum number of matches are present. Use inline
+         code for maximizing the speed, and do the type test once at the start
+         (i.e. keep it out of the loop). Also we can test that there are at least
+         the minimum number of bytes before we start. This isn't as effective in
+         UTF-8 mode, but it does no harm. Separate the UTF-8 code completely as that
+         is tidier. */
+
+      if (min > md->end_subject - eptr)
+       RRETURN(MATCH_NOMATCH);
+      if (min > 0) {
+
+       /* Code for the non-UTF-8 case for minimum matching */
+
+       switch (ctype) {
+       case OP_ANY:
+         if ((ims & PCRE_DOTALL) == 0) {
+           for (i = 1; i <= min; i++)
+             if (*eptr++ == NEWLINE)
+               RRETURN(MATCH_NOMATCH);
+         } else
+           eptr += min;
+         break;
+
+       case OP_ANYBYTE:
+         eptr += min;
+         break;
+
+       case OP_NOT_DIGIT:
+         for (i = 1; i <= min; i++)
+           if ((md->ctypes[*eptr++] & ctype_digit) != 0)
+             RRETURN(MATCH_NOMATCH);
+         break;
+
+       case OP_DIGIT:
+         for (i = 1; i <= min; i++)
+           if ((md->ctypes[*eptr++] & ctype_digit) == 0)
+             RRETURN(MATCH_NOMATCH);
+         break;
+
+       case OP_NOT_WHITESPACE:
+         for (i = 1; i <= min; i++)
+           if ((md->ctypes[*eptr++] & ctype_space) != 0)
+             RRETURN(MATCH_NOMATCH);
+         break;
+
+       case OP_WHITESPACE:
+         for (i = 1; i <= min; i++)
+           if ((md->ctypes[*eptr++] & ctype_space) == 0)
+             RRETURN(MATCH_NOMATCH);
+         break;
+
+       case OP_NOT_WORDCHAR:
+         for (i = 1; i <= min; i++)
+           if ((md->ctypes[*eptr++] & ctype_word) != 0)
+             RRETURN(MATCH_NOMATCH);
+         break;
+
+       case OP_WORDCHAR:
+         for (i = 1; i <= min; i++)
+           if ((md->ctypes[*eptr++] & ctype_word) == 0)
+             RRETURN(MATCH_NOMATCH);
+         break;
+       }
+      }
+
+      /* If min = max, continue at the same level without recursing */
+
+      if (min == max)
+       continue;
+
+      /* If minimizing, we have to test the rest of the pattern before each
+         subsequent match. Again, separate the UTF-8 case for speed. */
+
+      if (minimize) {
+       /* Not UTF-8 mode */
+       {
+         for (fi = min;; fi++) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+           if (fi >= max || eptr >= md->end_subject)
+             RRETURN(MATCH_NOMATCH);
+           c = *eptr++;
+           switch (ctype) {
+           case OP_ANY:
+             if ((ims & PCRE_DOTALL) == 0 && c == NEWLINE)
+               RRETURN(MATCH_NOMATCH);
+             break;
+
+           case OP_ANYBYTE:
+             break;
+
+           case OP_NOT_DIGIT:
+             if ((md->ctypes[c] & ctype_digit) != 0)
+               RRETURN(MATCH_NOMATCH);
+             break;
+
+           case OP_DIGIT:
+             if ((md->ctypes[c] & ctype_digit) == 0)
+               RRETURN(MATCH_NOMATCH);
+             break;
+
+           case OP_NOT_WHITESPACE:
+             if ((md->ctypes[c] & ctype_space) != 0)
+               RRETURN(MATCH_NOMATCH);
+             break;
+
+           case OP_WHITESPACE:
+             if ((md->ctypes[c] & ctype_space) == 0)
+               RRETURN(MATCH_NOMATCH);
+             break;
+
+           case OP_NOT_WORDCHAR:
+             if ((md->ctypes[c] & ctype_word) != 0)
+               RRETURN(MATCH_NOMATCH);
+             break;
+
+           case OP_WORDCHAR:
+             if ((md->ctypes[c] & ctype_word) == 0)
+               RRETURN(MATCH_NOMATCH);
+             break;
+           }
+         }
+       }
+       /* Control never gets here */
+      }
+
+      /* If maximizing it is worth using inline code for speed, doing the type
+         test once at the start (i.e. keep it out of the loop). Again, keep the
+         UTF-8 stuff separate. */
+
+      else {
+       pp = eptr;
+
+
+       /* Not UTF-8 mode */
+       {
+         switch (ctype) {
+         case OP_ANY:
+           if ((ims & PCRE_DOTALL) == 0) {
+             for (i = min; i < max; i++) {
+               if (eptr >= md->end_subject || *eptr == NEWLINE)
+                 break;
+               eptr++;
+             }
+             break;
+           }
+           /* For DOTALL case, fall through and treat as \C */
+
+         case OP_ANYBYTE:
+           c = max - min;
+           if (c > md->end_subject - eptr)
+             c = md->end_subject - eptr;
+           eptr += c;
+           break;
+
+         case OP_NOT_DIGIT:
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject
+                 || (md->ctypes[*eptr] & ctype_digit) != 0)
+               break;
+             eptr++;
+           }
+           break;
+
+         case OP_DIGIT:
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject
+                 || (md->ctypes[*eptr] & ctype_digit) == 0)
+               break;
+             eptr++;
+           }
+           break;
+
+         case OP_NOT_WHITESPACE:
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject
+                 || (md->ctypes[*eptr] & ctype_space) != 0)
+               break;
+             eptr++;
+           }
+           break;
+
+         case OP_WHITESPACE:
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject
+                 || (md->ctypes[*eptr] & ctype_space) == 0)
+               break;
+             eptr++;
+           }
+           break;
+
+         case OP_NOT_WORDCHAR:
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject
+                 || (md->ctypes[*eptr] & ctype_word) != 0)
+               break;
+             eptr++;
+           }
+           break;
+
+         case OP_WORDCHAR:
+           for (i = min; i < max; i++) {
+             if (eptr >= md->end_subject
+                 || (md->ctypes[*eptr] & ctype_word) == 0)
+               break;
+             eptr++;
+           }
+           break;
+         }
+
+         /* eptr is now past the end of the maximum run */
+
+         while (eptr >= pp) {
+           RMATCH(rrc, eptr, ecode, offset_top, md, ims, eptrb, 0);
+           eptr--;
+           if (rrc != MATCH_NOMATCH)
+             RRETURN(rrc);
+         }
+       }
+
+       /* Get here if we can't make it match with any permitted repetitions */
+
+       RRETURN(MATCH_NOMATCH);
+      }
+      /* Control never gets here */
+
+      /* There's been some horrible disaster. Since all codes > OP_BRA are
+         for capturing brackets, and there shouldn't be any gaps between 0 and
+         OP_BRA, arrival here can only mean there is something seriously wrong
+         in the code above or the OP_xxx definitions. */
+
+    default:
+      DPRINTF(("Unknown opcode %d\n", *ecode));
+      RRETURN(PCRE_ERROR_UNKNOWN_NODE);
+    }
+
+    /* Do not stick any code in here without much thought; it is assumed
+       that "continue" in the code above comes out to here to repeat the main
+       loop. */
+
+  }                            /* End of main loop */
+/* Control never reaches here */
+}
+
+
+/***************************************************************************
+****************************************************************************
+                   RECURSION IN THE match() FUNCTION
+
+Undefine all the macros that were defined above to handle this. */
+
+#ifdef NO_RECURSE
+#undef eptr
+#undef ecode
+#undef offset_top
+#undef ims
+#undef eptrb
+#undef flags
+
+#undef callpat
+#undef charptr
+#undef data
+#undef lastptr
+#undef next
+#undef pp
+#undef prev
+#undef saved_eptr
+
+#undef new_recursive
+
+#undef cur_is_word
+#undef condition
+#undef minimize
+#undef prev_is_word
+
+#undef original_ims
+
+#undef ctype
+#undef length
+#undef max
+#undef min
+#undef number
+#undef offset
+#undef op
+#undef save_capture_last
+#undef save_offset1
+#undef save_offset2
+#undef save_offset3
+#undef stacksave
+
+#undef newptrb
+
+#endif
+
+/* These two are defined as macros in both cases */
+
+#undef fc
+#undef fi
+
+/***************************************************************************
+***************************************************************************/
+
+
+
+/*************************************************
+*         Execute a Regular Expression           *
+*************************************************/
+
+/* This function applies a compiled re to a subject string and picks out
+portions of the string if it matches. Two elements in the vector are set for
+each substring: the offsets to the start and end of the substring.
+
+Arguments:
+  external_re     points to the compiled expression
+  extra_data      points to extra data or is NULL
+  subject         points to the subject string
+  length          length of subject string (may contain binary zeros)
+  start_offset    where to start in the subject string
+  options         option bits
+  offsets         points to a vector of ints to be filled in with offsets
+  offsetcount     the number of elements in the vector
+
+Returns:          > 0 => success; value is the number of elements filled in
+                  = 0 => success, but offsets is not big enough
+                   -1 => failed to match
+                 < -1 => some kind of unexpected problem
+*/
+
+EXPORT int
+pcre_exec(const pcre * external_re, const pcre_extra * extra_data,
+         const char *subject, int length, int start_offset, int options,
+         int *offsets, int offsetcount)
+{
+  int rc, resetcount, ocount;
+  int first_byte = -1;
+  int req_byte = -1;
+  int req_byte2 = -1;
+  unsigned long int ims = 0;
+  BOOL using_temporary_offsets = FALSE;
+  BOOL anchored;
+  BOOL startline;
+  BOOL first_byte_caseless = FALSE;
+  BOOL req_byte_caseless = FALSE;
+  match_data match_block;
+  const uschar *start_bits = NULL;
+  const uschar *start_match = (const uschar *) subject + start_offset;
+  const uschar *end_subject;
+  const uschar *req_byte_ptr = start_match - 1;
+  const pcre_study_data *study;
+  const real_pcre *re = (const real_pcre *) external_re;
+
+/* Plausibility checks */
+
+  if ((options & ~PUBLIC_EXEC_OPTIONS) != 0)
+    return PCRE_ERROR_BADOPTION;
+  if (re == NULL || subject == NULL || (offsets == NULL && offsetcount > 0))
+    return PCRE_ERROR_NULL;
+
+/* Fish out the optional data from the extra_data structure, first setting
+the default values. */
+
+  study = NULL;
+  match_block.match_limit = MATCH_LIMIT;
+  match_block.callout_data = NULL;
+
+  if (extra_data != NULL) {
+    register unsigned int flags = extra_data->flags;
+    if ((flags & PCRE_EXTRA_STUDY_DATA) != 0)
+      study = (const pcre_study_data *) extra_data->study_data;
+    if ((flags & PCRE_EXTRA_MATCH_LIMIT) != 0)
+      match_block.match_limit = extra_data->match_limit;
+    if ((flags & PCRE_EXTRA_CALLOUT_DATA) != 0)
+      match_block.callout_data = extra_data->callout_data;
+  }
+
+/* Now we have re supposedly pointing to the regex */
+
+  if (re->magic_number != MAGIC_NUMBER)
+    return PCRE_ERROR_BADMAGIC;
+
+  anchored = ((re->options | options) & PCRE_ANCHORED) != 0;
+  startline = (re->options & PCRE_STARTLINE) != 0;
+
+  match_block.start_code =
+    (const uschar *) re + sizeof(real_pcre) +
+    re->name_count * re->name_entry_size;
+  match_block.start_subject = (const uschar *) subject;
+  match_block.start_offset = start_offset;
+  match_block.end_subject = match_block.start_subject + length;
+  end_subject = match_block.end_subject;
+
+  match_block.endonly = (re->options & PCRE_DOLLAR_ENDONLY) != 0;
+  match_block.utf8 = (re->options & PCRE_UTF8) != 0;
+
+  match_block.notbol = (options & PCRE_NOTBOL) != 0;
+  match_block.noteol = (options & PCRE_NOTEOL) != 0;
+  match_block.notempty = (options & PCRE_NOTEMPTY) != 0;
+
+  match_block.recursive = NULL;        /* No recursion at top level */
+
+  match_block.lcc = re->tables + lcc_offset;
+  match_block.ctypes = re->tables + ctypes_offset;
+
+
+/* The ims options can vary during the matching as a result of the presence
+of (?ims) items in the pattern. They are kept in a local variable so that
+restoring at the exit of a group is easy. */
+
+  ims = re->options & (PCRE_CASELESS | PCRE_MULTILINE | PCRE_DOTALL);
+
+/* If the expression has got more back references than the offsets supplied can
+hold, we get a temporary bit of working store to use during the matching.
+Otherwise, we can use the vector supplied, rounding down its size to a multiple
+of 3. */
+
+  ocount = offsetcount - (offsetcount % 3);
+
+  if (re->top_backref > 0 && re->top_backref >= ocount / 3) {
+    ocount = re->top_backref * 3 + 3;
+    match_block.offset_vector = (int *) malloc(ocount * sizeof(int));
+    if (match_block.offset_vector == NULL)
+      return PCRE_ERROR_NOMEMORY;
+    using_temporary_offsets = TRUE;
+    DPRINTF(("Got memory to hold back references\n"));
+  } else
+    match_block.offset_vector = offsets;
+
+  match_block.offset_end = ocount;
+  match_block.offset_max = (2 * ocount) / 3;
+  match_block.offset_overflow = FALSE;
+  match_block.capture_last = -1;
+
+/* Compute the minimum number of offsets that we need to reset each time. Doing
+this makes a huge difference to execution time when there aren't many brackets
+in the pattern. */
+
+  resetcount = 2 + re->top_bracket * 2;
+  if (resetcount > offsetcount)
+    resetcount = ocount;
+
+/* Reset the working variable associated with each extraction. These should
+never be used unless previously set, but they get saved and restored, and so we
+initialize them to avoid reading uninitialized locations. */
+
+  if (match_block.offset_vector != NULL) {
+    register int *iptr = match_block.offset_vector + ocount;
+    register int *iend = iptr - resetcount / 2 + 1;
+    while (--iptr >= iend)
+      *iptr = -1;
+  }
+
+/* Set up the first character to match, if available. The first_byte value is
+never set for an anchored regular expression, but the anchoring may be forced
+at run time, so we have to test for anchoring. The first char may be unset for
+an unanchored pattern, of course. If there's no first char and the pattern was
+studied, there may be a bitmap of possible first characters. */
+
+  if (!anchored) {
+    if ((re->options & PCRE_FIRSTSET) != 0) {
+      first_byte = re->first_byte & 255;
+      if ((first_byte_caseless =
+          ((re->first_byte & REQ_CASELESS) != 0)) == TRUE)
+       first_byte = match_block.lcc[first_byte];
+    } else
+      if (!startline && study != NULL &&
+         (study->options & PCRE_STUDY_MAPPED) != 0)
+      start_bits = study->start_bits;
+  }
+
+/* For anchored or unanchored matches, there may be a "last known required
+character" set. */
+
+  if ((re->options & PCRE_REQCHSET) != 0) {
+    req_byte = re->req_byte & 255;
+    req_byte_caseless = (re->req_byte & REQ_CASELESS) != 0;
+    req_byte2 = (re->tables + fcc_offset)[req_byte];   /* case flipped */
+  }
+
+/* Loop for handling unanchored repeated matching attempts; for anchored regexs
+the loop runs just once. */
+
+  do {
+    register int *iptr = match_block.offset_vector;
+    register int *iend = iptr + resetcount;
+
+    /* Reset the maximum number of extractions we might see. */
+
+    while (iptr < iend)
+      *iptr++ = -1;
+
+    /* Advance to a unique first char if possible */
+
+    if (first_byte >= 0) {
+      if (first_byte_caseless)
+       while (start_match < end_subject &&
+              match_block.lcc[*start_match] != first_byte)
+         start_match++;
+      else
+       while (start_match < end_subject && *start_match != first_byte)
+         start_match++;
+    }
+
+    /* Or to just after \n for a multiline match if possible */
+
+    else if (startline) {
+      if (start_match > match_block.start_subject + start_offset) {
+       while (start_match < end_subject && start_match[-1] != NEWLINE)
+         start_match++;
+      }
+    }
+
+    /* Or to a non-unique first char after study */
+
+    else if (start_bits != NULL) {
+      while (start_match < end_subject) {
+       register int c = *start_match;
+       if ((start_bits[c / 8] & (1 << (c & 7))) == 0)
+         start_match++;
+       else
+         break;
+      }
+    }
+
+    /* If req_byte is set, we know that that character must appear in the subject
+       for the match to succeed. If the first character is set, req_byte must be
+       later in the subject; otherwise the test starts at the match point. This
+       optimization can save a huge amount of backtracking in patterns with nested
+       unlimited repeats that aren't going to match. Writing separate code for
+       cased/caseless versions makes it go faster, as does using an autoincrement
+       and backing off on a match.
+
+       HOWEVER: when the subject string is very, very long, searching to its end can
+       take a long time, and give bad performance on quite ordinary patterns. This
+       showed up when somebody was matching /^C/ on a 32-megabyte string... so we
+       don't do this when the string is sufficiently long. */
+
+    if (req_byte >= 0 && end_subject - start_match < REQ_BYTE_MAX) {
+      register const uschar *p = start_match + ((first_byte >= 0) ? 1 : 0);
+
+      /* We don't need to repeat the search if we haven't yet reached the
+         place we found it at last time. */
+
+      if (p > req_byte_ptr) {
+       if (req_byte_caseless) {
+         while (p < end_subject) {
+           register int pp = *p++;
+           if (pp == req_byte || pp == req_byte2) {
+             p--;
+             break;
+           }
+         }
+       } else {
+         while (p < end_subject) {
+           if (*p++ == req_byte) {
+             p--;
+             break;
+           }
+         }
+       }
+
+       /* If we can't find the required character, break the matching loop */
+
+       if (p >= end_subject)
+         break;
+
+       /* If we have found the required character, save the point where we
+          found it, so that we don't search again next time round the loop if
+          the start hasn't passed this character yet. */
+
+       req_byte_ptr = p;
+      }
+    }
+
+    /* When a match occurs, substrings will be set for all internal extractions;
+       we just need to set up the whole thing as substring 0 before returning. If
+       there were too many extractions, set the return code to zero. In the case
+       where we had to get some local store to hold offsets for backreferences, copy
+       those back references that we can. In this case there need not be overflow
+       if certain parts of the pattern were not used. */
+
+    match_block.start_match = start_match;
+    match_block.match_call_count = 0;
+
+    rc = match(start_match, match_block.start_code, 2, &match_block, ims, NULL,
+              match_isgroup);
+
+    if (rc == MATCH_NOMATCH) {
+      start_match++;
+      continue;
+    }
+
+    if (rc != MATCH_MATCH) {
+      DPRINTF((">>>> error: returning %d\n", rc));
+      return rc;
+    }
+
+    /* We have a match! Copy the offset information from temporary store if
+       necessary */
+
+    if (using_temporary_offsets) {
+      if (offsetcount >= 4) {
+       memcpy(offsets + 2, match_block.offset_vector + 2,
+              (offsetcount - 2) * sizeof(int));
+       DPRINTF(("Copied offsets from temporary memory\n"));
+      }
+      if (match_block.end_offset_top > offsetcount)
+       match_block.offset_overflow = TRUE;
+
+      DPRINTF(("Freeing temporary memory\n"));
+      free(match_block.offset_vector);
+    }
+
+    rc = match_block.offset_overflow ? 0 : match_block.end_offset_top / 2;
+
+    if (offsetcount < 2)
+      rc = 0;
+    else {
+      offsets[0] = start_match - match_block.start_subject;
+      offsets[1] = match_block.end_match_ptr - match_block.start_subject;
+    }
+
+    DPRINTF((">>>> returning %d\n", rc));
+    return rc;
+  }
+
+/* This "while" is the end of the "do" above */
+
+  while (!anchored && start_match <= end_subject);
+
+  if (using_temporary_offsets) {
+    DPRINTF(("Freeing temporary memory\n"));
+    free(match_block.offset_vector);
+  }
+
+  DPRINTF((">>>> returning PCRE_ERROR_NOMATCH\n"));
+
+  return PCRE_ERROR_NOMATCH;
+}
+
+/* End of pcre.c */
diff --git a/src/player.c b/src/player.c
new file mode 100644 (file)
index 0000000..e904b5c
--- /dev/null
@@ -0,0 +1,579 @@
+/**
+ * \file player.c
+ *
+ * \brief Player creation and connection for PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <stdio.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <fcntl.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "access.h"
+#include "parse.h"
+#include "mymalloc.h"
+#include "log.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "parse.h"
+
+#ifdef HAS_CRYPT
+#ifdef I_CRYPT
+#include <crypt.h>
+#else
+extern char *crypt(const char *, const char *);
+#endif
+#endif
+
+#include "extmail.h"
+#include "confmagic.h"
+
+dbref email_register_player
+  (const char *name, const char *email, const char *host, const char *ip);
+static dbref make_player
+  (const char *name, const char *password, const char *host, const char *ip);
+static dbref create_guest(const char *host, const char *ip);
+void do_password
+  (dbref player, dbref cause, const char *old, const char *newobj);
+
+static const char pword_attr[] = "XYXXY";
+
+extern struct db_stat_info current_state;
+
+/** Check a player's password against a given string.
+ * \param player dbref of player.
+ * \param password plaintext password string to check.
+ * \retval 1 password matches (or player has no password).
+ * \retval 0 password fails to match.
+ */
+int
+password_check(dbref player, const char *password)
+{
+  ATTR *a;
+  char *saved;
+  char *passwd;
+
+  /* read the password and compare it */
+  if (!(a = atr_get_noparent(player, pword_attr))) {
+    /* If it's a division type object.. return 0, they can't enter this way */
+    return Typeof(player) == TYPE_DIVISION ? 0 : 1; /* No password attribute */
+  }
+
+  saved = strdup(atr_value(a));
+
+  if (!saved)
+    return 0;
+
+  passwd = mush_crypt(password);
+
+  if (strcmp(passwd, saved) != 0) {
+    /* Nope. Try non-SHS. */
+#ifdef HAS_CRYPT
+    /* shs encryption didn't match. Try crypt(3) */
+    if (strcmp(crypt(password, "XX"), saved) != 0)
+      /* Nope */
+#endif                         /* HAS_CRYPT */
+      /* crypt() didn't work. Try plaintext, being sure to not 
+       * allow unencrypted entry of encrypted password */
+      if ((strcmp(saved, password) != 0)
+         || (strlen(password) < 4)
+         || ((password[0] == 'X') && (password[1] == 'X'))) {
+       /* Nothing worked. You lose. */
+       free(saved);
+       return 0;
+      }
+    /* Something worked. Change password to SHS-encrypted */
+    (void) atr_add(player, pword_attr, passwd, GOD, NOTHING);
+  }
+  free(saved);
+  return 1;
+}
+
+/** Check to see if someone can connect to a player.
+ * \param name name of player to connect to.
+ * \param password password of player to connect to.
+ * \param host host from which connection is being attempted.
+ * \param ip ip address from which connection is being attempted.
+ * \param errbuf buffer to return connection errors.
+ * \return dbref of connected player object or NOTHING for failure
+ * (with reason for failure returned in errbuf).
+ */
+dbref
+connect_player(const char *name, const char *password, const char *host,
+              const char *ip, char *errbuf)
+{
+  dbref player;
+  char isgst = 0;
+
+  /* Default error */
+  strcpy(errbuf,
+        T("Either that player does not exist, or has a different password."));
+
+  if (!name || !*name)
+    return NOTHING;
+
+  /* validate name */
+  /* first check if it's a guest */
+  if(!strcasecmp(name, GUEST_KEYWORD))
+    isgst = 1;
+  else if ((player = lookup_player(name)) == NOTHING)
+    return NOTHING;
+
+  /* See if player is allowed to connect like this */
+  if (!isgst && ( Going(player) || Going_Twice(player) )) {
+    do_log(LT_CONN, 0, 0,
+          T("Connection to GOING player %s not allowed from %s (%s)"), name,
+          host, ip);
+    return NOTHING;
+  }
+  if (!Site_Can_Connect(host, player) || !Site_Can_Connect(ip, player)) {
+    if (!Deny_Silent_Site(host, player) && !Deny_Silent_Site(ip, player)) {
+      do_log(LT_CONN, 0, 0,
+            T("Connection to %s not allowed from %s (%s)"), name,
+            host, ip);
+      strcpy(errbuf, T("Player connections not allowed."));
+    }
+    return NOTHING;
+  }
+  /* validate password */
+  if (!isgst)
+    if (!password_check(player, password)) {
+      /* Increment count of login failures */
+      ModTime(player)++;
+      check_lastfailed(player, host);
+      return NOTHING;
+    }
+  /* If it's a Guest player, and already connected, search the
+   * db for another Guest player to connect them to. */
+  if(isgst) {
+  player = create_guest(host, ip);
+    if (!GoodObject(player)) {
+      do_log(LT_CONN, 0, 0, T("Can't connect to a guest (too many connected)"));
+      strcpy(errbuf, T("Too many guests are connected now."));
+      return NOTHING;
+    }
+  }
+  if (Suspect_Site(host, player) || Suspect_Site(ip, player)) {
+    do_log(LT_CONN, 0, 0,
+          T("Connection from Suspect site. Setting %s(#%d) suspect."),
+          Name(player), player);
+    set_flag_internal(player, "SUSPECT");
+  }
+  return player;
+}
+
+
+/** Attempt to create a new guest.
+ */
+
+dbref create_guest(const char *host, const char *ip) {
+       int i , mg;
+       char *guest_name;
+       dbref gst_id;
+
+       mg = MAX_GUESTS == 0 ? 100 : MAX_GUESTS;
+       guest_name = (char *) mush_malloc(BUFFER_LEN, "gst_buf");
+       gst_id = NOTHING;
+
+       for( i = 0 ; i  < mg ; i++)  {
+               /* reset vars */
+               memset(guest_name, '\0', BUFFER_LEN);
+               gst_id = NOTHING;  
+               strncpy(guest_name, T(GUEST_PREFIX), BUFFER_LEN-1);
+               strcat(guest_name, GUEST_NUMBER(i+1));
+               if(ok_player_name(guest_name, NOTHING))
+                 break;        
+               else if((gst_id = lookup_player(guest_name)) != NOTHING && !Connected(gst_id))
+                       break;
+               else continue;
+       }
+       if(gst_id == NOTHING) {
+         if(i >= mg)  {
+               mush_free((Malloc_t) guest_name, "gst_buf");
+               return NOTHING;
+         } else if(DBTOP_MAX && (db_top >= DBTOP_MAX + 1) && (first_free == NOTHING)) {
+               mush_free((Malloc_t) guest_name, "gst_buf");
+               do_log(LT_CONN, 0, 0, T("Failed creation (no db space) from %s"), host);
+               return NOTHING;
+         }
+         gst_id = make_player(guest_name, "", host, ip);
+       } else  { /* Reset Guest */
+               object_flag_type flags;
+               flags = string_to_bits("FLAG", options.player_flags);
+               copy_flag_bitmask("FLAG", Flags(gst_id), flags);
+       }
+       mush_free((Malloc_t) guest_name, "gst_buf");
+       atr_add(gst_id, "DESCRIBE", GUEST_DESCRIBE, gst_id, NOTHING);
+
+       SLEVEL(gst_id) = LEVEL_GUEST;
+       Home(gst_id) = options.guest_start;
+       if(GoodObject(options.guest_start) && IsRoom(options.guest_start)) {
+         Contents(Location(gst_id)) = remove_first(Contents(Location(gst_id)), gst_id);
+         Location(gst_id) = GUEST_START;
+         PUSH(gst_id, Contents(options.guest_start));
+       }
+       return gst_id;
+}
+/** Attempt to create a new player object.
+ * \param name name of player to create.
+ * \param password initial password of created player.
+ * \param host host from which creation is attempted.
+ * \param ip ip address from which creation is attempted.
+ * \return dbref of created player, NOTHING if bad name, AMBIGUOUS if bad
+ *  password.
+ */
+dbref
+create_player(const char *name, const char *password, const char *host,
+             const char *ip)
+{
+  dbref player;
+  if (!ok_player_name(name, NOTHING)) {
+    do_log(LT_CONN, 0, 0, T("Failed creation (bad name) from %s"), host);
+    return NOTHING;
+  }
+  if (!ok_password(password)) {
+    do_log(LT_CONN, 0, 0, T("Failed creation (bad password) from %s"), host);
+    return AMBIGUOUS;
+  }
+  if (DBTOP_MAX && (db_top >= DBTOP_MAX + 1) && (first_free == NOTHING)) {
+    /* Oops, out of db space! */
+    do_log(LT_CONN, 0, 0, T("Failed creation (no db space) from %s"), host);
+    return NOTHING;
+  }
+  /* else he doesn't already exist, create him */
+  player = make_player(name, password, host, ip);
+  SLEVEL(player) = LEVEL_UNREGISTERED;
+  powergroup_db_set(NOTHING, player, PLAYER_DEF_POWERGROUP, 1);
+  return player;
+}
+
+/* The HAS_SENDMAIL ifdef is kept here as a hint to metaconfig */
+#ifdef MAILER
+#undef HAS_SENDMAIL
+#define HAS_SENDMAIL 1
+#undef SENDMAIL
+#define SENDMAIL MAILER
+#endif
+
+#ifdef HAS_SENDMAIL
+
+/** Size of the elems array */
+#define NELEMS (sizeof(elems)-1)
+
+/** Attempt to register a new player at the connect screen.
+ * If registration is allowed, a new player object is created with
+ * a random password which is emailed to the registering player.
+ * \param name name of player to register.
+ * \param email email address to send registration details.
+ * \param host host from which registration is being attempted.
+ * \param ip ip address from which registration is being attempted.
+ * \return dbref of created player or NOTHING if creation failed.
+ */
+dbref
+email_register_player(const char *name, const char *email, const char *host,
+                     const char *ip)
+{
+  char *p;
+  char passwd[BUFFER_LEN];
+  static char elems[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+  int i, len;
+  dbref player;
+  FILE *fp;
+
+  if (!ok_player_name(name, NOTHING)) {
+    do_log(LT_CONN, 0, 0, T("Failed registration (bad name) from %s"), host);
+    return NOTHING;
+  }
+  /* Make sure that the email address is valid. A valid address must
+   * contain either an @ or a !
+   * Also, to prevent someone from using the MUSH to mailbomb another site,
+   * let's make sure that the site to which the user wants the email
+   * sent is also allowed to use the register command.
+   * If there's an @, we check whatever's after the last @
+   * (since @foo.bar:user@host is a valid email)
+   * If not, we check whatever comes before the first !
+   */
+  if ((p = strrchr(email, '@'))) {
+    p++;
+    if (!Site_Can_Register(p)) {
+      if (!Deny_Silent_Site(p, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0,
+              T("Failed registration (bad site in email: %s) from %s"),
+              email, host);
+      }
+      return NOTHING;
+    }
+  } else if ((p = strchr(email, '!'))) {
+    *p = '\0';
+    if (!Site_Can_Register(email)) {
+      *p = '!';
+      if (!Deny_Silent_Site(email, AMBIGUOUS)) {
+       do_log(LT_CONN, 0, 0,
+              T("Failed registration (bad site in email: %s) from %s"),
+              email, host);
+      }
+      return NOTHING;
+    } else
+      *p = '!';
+  } else {
+    if (!Deny_Silent_Site(host, AMBIGUOUS)) {
+      do_log(LT_CONN, 0, 0, T("Failed registration (bad email: %s) from %s"),
+            email, host);
+    }
+    return NOTHING;
+  }
+
+  if (DBTOP_MAX && (db_top >= DBTOP_MAX + 1) && (first_free == NOTHING)) {
+    /* Oops, out of db space! */
+    do_log(LT_CONN, 0, 0, T("Failed registration (no db space) from %s"), host);
+    return NOTHING;
+  }
+
+  /* Come up with a random password of length 7-12 chars */
+  len = get_random_long(7, 12);
+  for (i = 0; i < len; i++)
+    passwd[i] = elems[get_random_long(0, NELEMS - 1)];
+  passwd[len] = '\0';
+
+  /* If we've made it here, we can send the email and create the
+   * character. Email first, since that's more likely to go bad.
+   * Some security precautions we'll take:
+   *  1) We'll use sendmail -t, so we don't pass user-given values to a shell.
+   */
+
+  release_fd();
+  if ((fp =
+#ifdef __LCC__
+       (FILE *)
+#endif
+       popen(tprintf("%s -t", SENDMAIL), "w")) == NULL) {
+    do_log(LT_CONN, 0, 0,
+          T("Failed registration of %s by %s: unable to open sendmail"),
+          name, email);
+    reserve_fd();
+    return NOTHING;
+  }
+  fprintf(fp, T("Subject: [%s] Registration of %s\n"), MUDNAME, name);
+  fprintf(fp, "To: %s\n", email);
+  fprintf(fp, "Precedence: junk\n");
+  fprintf(fp, "\n");
+  fprintf(fp, T("This is an automated message.\n"));
+  fprintf(fp, "\n");
+  fprintf(fp, T("Your requested player, %s, has been created.\n"), name);
+  fprintf(fp, T("The password is %s\n"), passwd);
+  fprintf(fp, "\n");
+  fprintf(fp, T("To access this character, connect to %s and type:\n"),
+         MUDNAME);
+  fprintf(fp, "\tconnect \"%s\" %s\n", name, passwd);
+  fprintf(fp, "\n");
+  pclose(fp);
+  reserve_fd();
+  /* Ok, all's well, make a player */
+  player = make_player(name, passwd, host, ip);
+  (void) atr_add(player, "REGISTERED_EMAIL", email, GOD, NOTHING);
+  SLEVEL(player) = LEVEL_UNREGISTERED;
+  powergroup_db_set(NOTHING, player, PLAYER_DEF_POWERGROUP, 1);
+  return player;
+}
+#else
+dbref
+email_register_player(const char *name, const char *email, const char *host,
+                     const char *ip)
+{
+  do_log(LT_CONN, 0, 0, T("Failed registration (no sendmail) from %s"), host);
+  return NOTHING;
+}
+#endif
+
+static dbref
+make_player(const char *name, const char *password, const char *host,
+           const char *ip)
+{
+
+  dbref player;
+  char temp[SBUF_LEN];
+  object_flag_type flags;
+
+  player = new_object();
+
+  /* initialize everything */
+  set_name(player, name);
+  Location(player) = PLAYER_START;
+  Home(player) = PLAYER_START;
+  Owner(player) = player;
+  Parent(player) = NOTHING;
+  Type(player) = TYPE_PLAYER;
+  flags = string_to_bits("FLAG", options.player_flags);
+  copy_flag_bitmask("FLAG", Flags(player), flags);
+  destroy_flag_bitmask(flags);
+  if (Suspect_Site(host, player) || Suspect_Site(ip, player))
+    set_flag_internal(player, "SUSPECT");
+  set_initial_warnings(player);
+  /* Modtime tracks login failures */
+  ModTime(player) = (time_t) 0;
+  (void) atr_add(player, "XYXXY", mush_crypt(password), GOD, NOTHING);
+  giveto(player, START_BONUS); /* starting bonus */
+  (void) atr_add(player, "LAST", show_time(mudtime, 0), GOD, NOTHING);
+  (void) atr_add(player, "LASTSITE", host, GOD, NOTHING);
+  (void) atr_add(player, "LASTIP", ip, GOD, NOTHING);
+  (void) atr_add(player, "LASTFAILED", " ", GOD, NOTHING);
+  sprintf(temp, "%d", START_QUOTA);
+  (void) atr_add(player, "RQUOTA", temp, GOD, NOTHING);
+  (void) atr_add(player, "ICLOC", EMPTY_ATTRS ? "" : " ", GOD,
+                AF_MDARK | AF_PRIVATE | AF_NOCOPY);
+#ifdef USE_MAILER
+  (void) atr_add(player, "MAILCURF", "0", GOD,
+                AF_LOCKED | AF_NOPROG);
+  add_folder_name(player, 0, "inbox");
+#endif
+  /* link him to PLAYER_START */
+  PUSH(player, Contents(PLAYER_START));
+
+  add_player(player,NULL);
+  add_lock(GOD, player, Basic_Lock, parse_boolexp(player, "=me", Basic_Lock),
+          -1);
+  add_lock(GOD, player, Enter_Lock, parse_boolexp(player, "=me", Basic_Lock),
+          -1);
+  add_lock(GOD, player, Use_Lock, parse_boolexp(player, "=me", Basic_Lock), -1);
+
+  current_state.players++;
+
+  local_data_create(player);
+
+  return player;
+}
+
+
+/** Change a player's password.
+ * \verbatim
+ * This function implements @password.
+ * \endverbatim
+ * \param player the executor.
+ * \param cause the enactor.
+ * \param old player's current password.
+ * \param newobj player's desired new password.
+ */
+void
+do_password(dbref player, dbref cause, const char *old, const char *newobj)
+{
+  if (!global_eval_context.process_command_port) {
+    char old_eval[BUFFER_LEN];
+    char new_eval[BUFFER_LEN];
+    char const *sp;
+    char *bp;
+
+    sp = old;
+    bp = old_eval;
+    process_expression(old_eval, &bp, &sp, player, player, cause,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    old = old_eval;
+
+    sp = newobj;
+    bp = new_eval;
+    process_expression(new_eval, &bp, &sp, player, player, cause,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    newobj = new_eval;
+  }
+
+  if (!password_check(player, old)) {
+    notify(player, T("Suffering from memory loss? See an admin!"));
+  } else if (!ok_password(newobj)) {
+    notify(player, T("Bad new password."));
+  } else {
+    (void) atr_add(player, "XYXXY", mush_crypt(newobj), GOD, NOTHING);
+    notify(player, T("You have changed your password."));
+  }
+}
+
+/** Processing related to players' last connections.
+ * Here we check to see if a player gets a paycheck, tell them their
+ * last connection site, and update all their LAST* attributes.
+ * \param player dbref of player.
+ * \param host hostname of player's current connection.
+ * \param ip ip address of player's current connection.
+ */
+void
+check_last(dbref player, const char *host, const char *ip)
+{
+  char *s;
+  ATTR *a;
+  ATTR *h;
+  char last_time[MAX_COMMAND_LEN / 8];
+  char last_place[MAX_COMMAND_LEN];
+
+  /* compare to last connect see if player gets salary */
+  s = show_time(mudtime, 0);
+  a = atr_get_noparent(player, "LAST");
+  if (a && (strncmp(atr_value(a), s, 10) != 0))
+    giveto(player, Paycheck(player));
+  /* tell the player where he last connected from */
+  h = atr_get_noparent(player, "LASTSITE");
+  if (!Guest(player)) {
+    h = atr_get_noparent(player, "LASTSITE");
+    if (h && a) {
+      strcpy(last_place, atr_value(h));
+      strcpy(last_time, atr_value(a));
+      notify_format(player, T("Last connect was from %s on %s."),
+                  last_place, last_time);
+    }
+    /* How about last failed connection */
+    h = atr_get_noparent(player, "LASTFAILED");
+    if (h && a) {
+      strcpy(last_place, atr_value(h));
+      if (strlen(last_place) > 2)
+      notify_format(player, T("Last FAILED connect was from %s."),
+                    last_place);
+    }
+  }
+  /* if there is no Lastsite, then the player is newly created.
+   * the extra variables are a kludge to work around some weird
+   * behavior involving uncompress.
+   */
+
+  /* set the new attributes */
+  (void) atr_add(player, "LAST", s, GOD, NOTHING);
+  if(!has_flag_by_name(player, "WEIRDSITE", TYPE_PLAYER)) {
+   (void) atr_add(player, "LASTSITE", host, GOD, NOTHING);
+   (void) atr_add(player, "LASTIP", ip, GOD, NOTHING);
+  }
+  (void) atr_add(player, "LASTFAILED", " ", GOD, NOTHING);
+}
+
+
+/** Update the LASTFAILED attribute on a failed connection.
+ * \param player dbref of player.
+ * \param host host from which connection attempt failed.
+ */
+void
+check_lastfailed(dbref player, const char *host)
+{
+  char last_place[BUFFER_LEN], *bp;
+
+  bp = last_place;
+  safe_format(last_place, &bp, T("%s on %s"), host, show_time(mudtime, 0));
+  *bp = '\0';
+  (void) atr_add(player, "LASTFAILED", last_place, GOD, NOTHING);
+}
diff --git a/src/plyrlist.c b/src/plyrlist.c
new file mode 100644 (file)
index 0000000..c03d3ba
--- /dev/null
@@ -0,0 +1,110 @@
+/**
+ * \file plyrlist.c
+ *
+ * \brief Player list management for PennMUSH.
+ *
+ *
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "copyrite.h"
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "htab.h"
+#include "confmagic.h"
+
+
+/** Hash table of player names */
+HASHTAB htab_player_list;
+
+static int hft_initialized = 0;
+static void init_hft(void);
+
+static void
+init_hft(void)
+{
+  hashinit(&htab_player_list, 256, sizeof(dbref));
+  hft_initialized = 1;
+}
+
+/** Clear the player list htab. */
+void
+clear_players(void)
+{
+  if (hft_initialized)
+    hashflush(&htab_player_list, 256);
+  else
+    init_hft();
+}
+
+
+/** Add a player to the player list htab.
+ * \param player dbref of player to add.
+ * \param alias name to use as hash table key for player, if given.
+ */
+void
+add_player(dbref player, const char *alias)
+{
+  if (!hft_initialized)
+    init_hft();
+  if (alias)
+    hashadd(strupper(alias), (void *) player, &htab_player_list);
+  else
+    hashadd(strupper(Name(player)), (void *) player, &htab_player_list);
+}
+
+/** Look up a player in the player list htab (or by dbref).
+ * \param name name of player to find.
+ * \return dbref of player, or NOTHING.
+ */
+dbref
+lookup_player(const char *name)
+{
+  int p;
+  void *hval;
+
+  if (!name || !*name)
+    return NOTHING;
+  if (*name == NUMBER_TOKEN) {
+    name++;
+    if (!is_strict_number(name))
+      return NOTHING;
+    p = atoi(name);
+    return ((GoodObject(p) && IsPlayer(p)) ? p : NOTHING);
+  }
+  if (*name == LOOKUP_TOKEN)
+    name++;
+  hval = hashfind(strupper(name), &htab_player_list);
+  if (!hval)
+    return NOTHING;
+  return (dbref) hval;
+  /* By the way, there's a flaw in this code. If #0 was a player, we'd
+   * hash its name with a dbref of (void *)0, aka NULL, so we'd never
+   * be able to retrieve that player. However, we assume that #0 will
+   * always be the base room, and never a player, so that's ok.
+   * Nathan Baum offered a fix (hash the value + 1 and subtract 1 on
+   * lookup) but we (foolishly?) cling to our assumption and don't bother.
+   */
+}
+
+/** Remove a player from the player list htab.
+ * \param player dbref of player to remove.
+ * \param alias key to remove if given.
+ */
+void
+delete_player(dbref player, const char *alias)
+{
+  if (alias)
+    hashdelete(strupper(alias), &htab_player_list);
+  else
+    hashdelete(strupper(Name(player)), &htab_player_list);
+}
diff --git a/src/portmsg.c b/src/portmsg.c
new file mode 100644 (file)
index 0000000..238f9e5
--- /dev/null
@@ -0,0 +1,287 @@
+/*
+ *    portmsg - generate a message on a port, then close connection
+ *
+ *      Usage:  portmsg file port
+ *
+ *              When a telnet client connects to the specified port, the
+ *              text from the file will be echoed to the user.  After a
+ *              short delay the connection will close.
+ *
+ * Derived from ftpd by Klaas @ {RUD, LPSwat}, original ftpd copyright
+ * message follows:
+ *
+ * Copyright (c) 1985, 1988, 1990 Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *      This product includes software developed by the University of
+ *      California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * This version extensively modified by Javelin (Alan Schwartz)
+ * to conform to PennMUSH autoconfiguration standards.
+ */
+
+/* If you have multiple IP addresses and want to bind to only one,
+ * uncomment this:
+ */
+/*#define SINGLE_IP_ADDR "your.address.goes.here" */
+
+#include "config.h"
+#include <stdio.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <limits.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <errno.h>
+#ifdef I_SYS_ERRNO
+#include <sys/errno.h>
+#endif
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/param.h>
+#include <signal.h>
+#ifdef I_SYS_WAIT
+#include <sys/wait.h>
+#endif
+#include <fcntl.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+/* What htons expects */
+typedef unsigned short Port_t;
+
+#ifdef HAS_WAITPID
+#define WAIT_TYPE int
+#else
+#ifdef UNION_WAIT
+#define WAIT_TYPE union wait
+#else
+#define WAIT_TYPE int
+#endif
+#endif
+
+static Signal_t wait_on_child(int sig);
+static Signal_t lostconn(int sig);
+static int how_many_fds(void);
+
+static Signal_t
+wait_on_child(int sig __attribute__ ((__unused__)))
+{
+  WAIT_TYPE status;
+
+#ifdef HAS_WAITPID
+  while (waitpid(-1, &status, WNOHANG) > 0) ;
+#else
+  while (wait3(&status, WNOHANG, 0) > 0) ;
+#endif
+
+#ifndef SIGNALS_KEPT
+  signal(SIGCLD, (Sigfunc) wait_on_child);
+#endif
+#ifndef VOIDSIG
+  return 0;
+#endif
+}
+
+Signal_t
+lostconn(int sig __attribute__ ((__unused__)))
+{
+  exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+  int msgfd, fd, n;
+  struct stat statBuf;
+  Port_t port;
+  char *msg;
+  int sockfd, newsockfd;
+  int addrlen;
+  int opt;
+  struct sockaddr_in tcp_srv_addr;
+  struct sockaddr_in their_addr;
+  int num_fds;
+
+  if (argc != 3) {
+    fprintf(stderr, "Usage: portmsg file port\n");
+    exit(1);
+  }
+  port = atoi(argv[2]);
+  if (port == 0) {
+    fprintf(stderr, "error: bad port number [%s]\n", argv[2]);
+    exit(1);
+  }
+  if ((msgfd = open(argv[1], O_RDONLY)) < 0) {
+    fprintf(stderr, "error: cannot open message file [%s]\n", argv[1]);
+    exit(1);
+  }
+  /* read the message */
+  fstat(msgfd, &statBuf);
+  if (statBuf.st_size <= 0) {
+    fprintf(stderr, "error: message file [%s] is empty\n", argv[1]);
+    exit(1);
+  }
+  msg = (char *) malloc(statBuf.st_size);
+  if (read(msgfd, msg, statBuf.st_size) != statBuf.st_size) {
+    fprintf(stderr, "error: cannot read message file [%s]\n", argv[1]);
+    exit(1);
+  }
+  num_fds = how_many_fds();
+
+  /* become a daemon */
+  switch (fork()) {
+  case -1:
+    fprintf(stderr, "error: can't fork\n");
+    exit(1);
+  case 0:
+    break;
+  default:
+    exit(0);
+  }
+#ifdef HAS_SETPGRP
+#ifdef USE_BSD_SETPGRP
+  if (setpgrp(0, getpid()) == -1) {
+#else
+  if (setpgrp() == -1) {
+#endif
+    fprintf(stderr, "error: can't change process group\n");
+    exit(1);
+  }
+#endif
+
+#ifdef USE_TIOCNOTTY
+  if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
+    ioctl(fd, TIOCNOTTY, NULL);
+    close(fd);
+  }
+#endif
+
+  signal(SIGCLD, (void *) wait_on_child);
+  memset((char *) &tcp_srv_addr, 0, sizeof(tcp_srv_addr));
+  tcp_srv_addr.sin_family = AF_INET;
+#ifdef SINGLE_IP_ADDR
+  tcp_srv_addr.sin_addr.s_addr = inet_addr(MUSH_IP_ADDR);
+#else
+  tcp_srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
+#endif
+  tcp_srv_addr.sin_port = htons(port);
+
+  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+    fprintf(stderr, "can't create stream socket\n");
+    exit(-1);
+  }
+  opt = 1;
+  if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
+                (char *) &opt, sizeof(opt)) < 0) {
+    perror("setsockopt");
+    exit(1);
+  }
+  if (bind(sockfd, (struct sockaddr *) &tcp_srv_addr, sizeof(tcp_srv_addr)) < 0) {
+    fprintf(stderr, "can't bind local address\n");
+    exit(-1);
+  }
+  listen(sockfd, 5);
+
+main_again:
+  addrlen = sizeof(their_addr);
+  newsockfd = accept(sockfd, (struct sockaddr *) &their_addr, &addrlen);
+  if (newsockfd < 0) {
+    if (errno == EINTR)
+      goto main_again;
+    fprintf(stderr, "accept error\n");
+    exit(-1);
+  }
+  switch (fork()) {
+  case -1:
+    fprintf(stderr, "server can't fork\n");
+    exit(-1);
+  case 0:
+    dup2(newsockfd, 0);
+    dup2(newsockfd, 1);
+    for (n = 3; n < num_fds; n++)
+      close(n);
+    break;
+  default:
+    close(newsockfd);
+    goto main_again;
+  }
+
+  /* daemon child arrives here */
+  signal(SIGPIPE, lostconn);
+  signal(SIGCLD, SIG_IGN);
+
+  fprintf(stdout, msg);
+  fflush(stdout);
+  sleep(5);
+  exit(0);
+}
+
+static int
+how_many_fds(void)
+{
+  /* Determine how many open file descriptors we're allowed
+   * In order, we'll try:
+   * 0. OPEN_MAX constant - POSIX.1 limits.h
+   * 1. sysconf(_SC_OPEN_MAX) - POSIX.1
+   * 2. getdtablesize - BSD
+   * 3. NOFILE - in some sys/param.h
+   * 4. _NFILE - in some stdio.h
+   */
+#ifdef OPEN_MAX
+  static int open_max = OPEN_MAX;
+#else
+  static int open_max = 0;
+#endif
+
+  if (open_max)
+    return open_max;
+
+#ifdef HAS_SYSCONF
+  errno = 0;
+  if ((open_max = sysconf(_SC_OPEN_MAX)) < 0) {
+    if (errno == 0)            /* Value was indeterminate */
+      open_max = 0;
+  }
+  if (open_max)
+    return open_max;
+#endif
+  /* Caching getdtablesize is dangerous, since it's affected by
+   * getrlimit, so we don't.
+   */
+  open_max = 0;
+  return getdtablesize();
+}
diff --git a/src/predicat.c b/src/predicat.c
new file mode 100644 (file)
index 0000000..b86f7e0
--- /dev/null
@@ -0,0 +1,1344 @@
+/**
+ * \file predicat.c
+ *
+ * \brief Predicates for testing various conditions in PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <string.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <stdlib.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "lock.h"
+#include "flags.h"
+#include "match.h"
+#include "ansi.h"
+#include "parse.h"
+#include "dbdefs.h"
+#include "privtab.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+int forbidden_name(const char *name);
+void do_switch(dbref player, char *expression, char **argv,
+              dbref cause, int first, int notifyme, int regexp);
+void do_verb(dbref player, dbref cause, char *arg1, char **argv);
+static int grep_util_helper(dbref player, dbref thing, dbref parent,
+                           char const *pattern, ATTR *atr, void *args);
+static int grep_helper(dbref player, dbref thing, dbref parent,
+                      char const *pattern, ATTR *atr, void *args);
+void do_grep(dbref player, char *obj, char *lookfor, int flag, int insensitive);
+static int pay_quota(dbref, int);
+extern PRIV attr_privs[];
+
+/** A generic function to generate a formatted string. The
+ * return value is a statically allocated buffer.
+ *
+ * \param fmt format string.
+ * \return formatted string.
+ */
+char *WIN32_CDECL
+tprintf(const char *fmt, ...)
+{
+#ifdef HAS_VSNPRINTF
+  static char buff[BUFFER_LEN];
+#else
+  static char buff[BUFFER_LEN * 3];    /* safety margin */
+#endif
+  va_list args;
+
+  va_start(args, fmt);
+
+#ifdef HAS_VSNPRINTF
+  vsnprintf(buff, sizeof buff, fmt, args);
+#else
+  vsprintf(buff, fmt, args);
+#endif
+
+  buff[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+  return (buff);
+}
+
+/** lock evaluation -- determines if player passes lock on thing, for
+ * the purposes of picking up an object or moving through an exit.
+ * \param player to check against lock.
+ * \param thing thing to check the basic lock on.
+ * \retval 1 player passes lock.
+ * \retval 0 player fails lock.
+ */
+int
+could_doit(dbref player, dbref thing)
+{
+  if (!IsRoom(thing) && Location(thing) == NOTHING)
+    return 0;
+  return (eval_lock(player, thing, Basic_Lock));
+}
+
+/** Execute an action on an object, and handle objects with limited charges.
+ * \param player the enactor.
+ * \param thing object being used.
+ * \param awhat action attribute on object to be triggered.
+ * \retval 0 no action taken.
+ * \retval 1 action taken.
+ */
+int
+charge_action(dbref player, dbref thing, const char *awhat)
+{
+  ATTR *b;
+  char tbuf2[BUFFER_LEN];
+  int num;
+
+  if (!awhat || !*awhat)
+    return 0;
+
+  /* check if object has # of charges */
+  b = atr_get_noparent(thing, "CHARGES");
+
+  if (!b) {
+    /* no charges set, just execute the action */
+    return queue_attribute(thing, awhat, player);
+  } else {
+    strcpy(tbuf2, atr_value(b));
+    num = atoi(tbuf2);
+    if (num) {
+      /* charges left, decrement and execute */
+      int res;
+      if ((res = queue_attribute(thing, awhat, player)))
+       (void) atr_add(thing, "CHARGES", tprintf("%d", num - 1),
+                      Owner(b->creator), NOTHING);
+      return res;
+    } else {
+      /* no charges left, try to execute runout */
+      return queue_attribute(thing, "RUNOUT", player);
+    }
+  }
+}
+
+
+/** A wrapper for real_did_it that clears the environment first.
+ * \param player the enactor.
+ * \param thing object being triggered.
+ * \param what message attribute for enactor.
+ * \param def default message to enactor.
+ * \param owhat message attribute for others.
+ * \param odef default message to others.
+ * \param awhat action attribute to trigger.
+ * \param loc location in which action is taking place.
+ * \retval 0 no attributes were evaluated (only defaults used).
+ * \retval 1 some attributes were evaluated.
+ */
+int
+did_it(dbref player, dbref thing, const char *what, const char *def,
+       const char *owhat, const char *odef, const char *awhat, dbref loc)
+{
+  /* Bunch o' nulls */
+  static char *myenv[10] = { NULL };
+  return real_did_it(player, thing, what, def, owhat, odef, awhat, loc, myenv,
+                    NA_INTER_HEAR);
+}
+
+/** A wrapper for real_did_it that can set %0 and %1 to dbrefs.
+ * \param player the enactor.
+ * \param thing object being triggered.
+ * \param what message attribute for enactor.
+ * \param def default message to enactor.
+ * \param owhat message attribute for others.
+ * \param odef default message to others.
+ * \param awhat action attribute to trigger.
+ * \param loc location in which action is taking place.
+ * \param env0 dbref to pass as %0, or NOTHING.
+ * \param env1 dbref to pass as %1, or NOTHING.
+ * \param flags interaction flags to pass to real_did_it.
+ * \retval 0 no attributes were present, only defaults were used if given.
+ * \retval 1 some attributes were evaluated and used.
+ */
+int
+did_it_with(dbref player, dbref thing, const char *what, const char *def,
+           const char *owhat, const char *odef, const char *awhat,
+           dbref loc, dbref env0, dbref env1, int flags)
+{
+  char *myenv[10] = { NULL };
+  char e0[SBUF_LEN], e1[SBUF_LEN], *ep;
+
+  if (env0 != NOTHING) {
+    ep = e0;
+    safe_dbref(env0, e0, &ep);
+    *ep = '\0';
+    myenv[0] = e0;
+  }
+
+  if (env1 != NOTHING) {
+    ep = e1;
+    safe_dbref(env1, e1, &ep);
+    *ep = '\0';
+    myenv[1] = e1;
+  }
+
+  return real_did_it(player, thing, what, def, owhat, odef, awhat, loc, myenv,
+                    flags);
+}
+
+
+/** A wrapper for did_it that can pass interaction flags.
+ * \param player the enactor.
+ * \param thing object being triggered.
+ * \param what message attribute for enactor.
+ * \param def default message to enactor.
+ * \param owhat message attribute for others.
+ * \param odef default message to others.
+ * \param awhat action attribute to trigger.
+ * \param loc location in which action is taking place.
+ * \param flags interaction flags to pass to real_did_it.
+ * \retval 0 no attributes were present, only defaults were used if given.
+ * \retval 1 some attributes were evaluated and used.
+ */
+int
+did_it_interact(dbref player, dbref thing, const char *what, const char *def,
+               const char *owhat, const char *odef, const char *awhat,
+               dbref loc, int flags)
+{
+  /* Bunch o' nulls */
+  static char *myenv[10] = { NULL };
+  return real_did_it(player, thing, what, def, owhat, odef, awhat, loc, myenv,
+                    flags);
+}
+
+/** Take an action on an object and trigger attributes.
+ * \verbatim
+ * executes the @attr, @oattr, @aattr for a command - gives a message
+ * to the enactor and others in the room with the enactor, and executes
+ * an action. We load global_eval_context.wenv with the values in myenv.
+ * \endverbatim
+ *
+ * \param player the enactor.
+ * \param thing object being triggered.
+ * \param what message attribute for enactor.
+ * \param def default message to enactor.
+ * \param owhat message attribute for others.
+ * \param odef default message to others.
+ * \param awhat action attribute to trigger.
+ * \param loc location in which action is taking place.
+ * \param myenv copy of the environment.
+ * \param flags flags controlling type of interaction involved.
+ * \retval 0 no attributes were present, only defaults were used if given.
+ * \retval 1 some attributes were evaluated and used.
+ */
+int
+real_did_it(dbref player, dbref thing, const char *what, const char *def,
+           const char *owhat, const char *odef, const char *awhat, dbref loc,
+           char *myenv[10], int flags)
+{
+
+  ATTR *d;
+  char buff[BUFFER_LEN], *bp, *sp, *asave;
+  char const *ap;
+  int j;
+  char *preserves[10];
+  char *preserveq[NUMQ];
+  dbref preserve_orator = orator;
+  int need_pres = 0;
+  int attribs_used = 0;
+
+  loc = (loc == NOTHING) ? Location(player) : loc;
+  orator = player;
+
+  /* only give messages if the location is good */
+  if (GoodObject(loc)) {
+
+    /* message to player */
+    if (what && *what) {
+      d = atr_get(thing, what);
+      if (d) {
+       attribs_used = 1;
+       if (!need_pres) {
+         need_pres = 1;
+         save_global_regs("did_it_save", preserveq);
+         save_global_env("did_it_save", preserves);
+       }
+       restore_global_env("did_it", myenv);
+       asave = safe_atr_value(d);
+       ap = asave;
+       bp = buff;
+       process_expression(buff, &bp, &ap, thing, player, player,
+                          PE_DEFAULT, PT_DEFAULT, NULL);
+       *bp = '\0';
+       notify_by(thing, player, buff);
+       free((Malloc_t) asave);
+      } else if (def && *def)
+       notify_by(thing, player, def);
+    }
+    /* message to neighbors */
+    if (!DarkLegal(player)) {
+      if (owhat && *owhat) {
+       d = atr_get(thing, owhat);
+       if (d) {
+         attribs_used = 1;
+         if (!need_pres) {
+           need_pres = 1;
+           save_global_regs("did_it_save", preserveq);
+           save_global_env("did_it_save", preserves);
+         }
+         restore_global_env("did_it", myenv);
+         asave = safe_atr_value(d);
+         ap = asave;
+         bp = buff;
+         safe_str(Name(player), buff, &bp);
+         safe_chr(' ', buff, &bp);
+         sp = bp;
+         process_expression(buff, &bp, &ap, thing, player, player,
+                            PE_DEFAULT, PT_DEFAULT, NULL);
+         *bp = '\0';
+         if (bp != sp)
+           notify_except2(Contents(loc), player, thing, buff, flags);
+         free((Malloc_t) asave);
+       } else {
+         if (odef && *odef) {
+           notify_except2(Contents(loc), player, thing,
+                          tprintf("%s %s", Name(player), odef), flags);
+         }
+       }
+      }
+    }
+  }
+  if (need_pres) {
+    restore_global_regs("did_it_save", preserveq);
+    restore_global_env("did_it_save", preserves);
+  }
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = myenv[j];
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = NULL;
+  attribs_used = charge_action(player, thing, awhat) || attribs_used;
+  orator = preserve_orator;
+  return attribs_used;
+}
+
+/** Return the first object near another object that is visible to a player.
+ *
+ * BEWARE:
+ *
+ * first_visible() does not behave as intended. It _should_ return the first
+ * object in `thing' that is !DARK. However, because of the controls() check
+ * the function will return a DARK object if the player owns it.
+ *
+ * The behavior is left as is because so many functions in fundb.c rely on
+ * the incorrect behavior to return expected values. The lv*() functions
+ * also make rewriting this fairly pointless.
+ *
+ * \param player the looker.
+ * \param thing an object in the location to be inspected.
+ * \return dbref of first visible object or NOTHING.
+ */
+dbref
+first_visible(dbref player, dbref thing)
+{
+  int lck = 0;
+  int ldark;
+  dbref loc;
+
+  if (!GoodObject(thing) || IsRoom(thing))
+    return NOTHING;
+  loc = IsExit(thing) ? Source(thing) : Location(thing);
+  if (!GoodObject(loc))
+    return NOTHING;
+  ldark = IsPlayer(loc) ? Opaque(loc) : Dark(loc);
+
+  while (GoodObject(thing)) {
+    if(can_interact(thing, player, INTERACT_SEE)) {
+     if (DarkLegal(thing) || (ldark && !Light(thing))) {
+       if (!lck) {
+       if (CanSee(player, loc) || (loc == player) || controls(player, loc))
+         return thing;
+       lck = 1;
+      }
+      if (controls(player, thing))     /* this is what causes DARK objects to show */
+       return thing;
+     } else {
+       return thing;
+     }
+    }
+    thing = Next(thing);
+  }
+  return thing;
+}
+
+
+
+/** Can a player see something?
+ * \param player the looker.
+ * \param thing object to be seen.
+ * \param can_see_loc 1 if player can see the location, 0 if location is dark.
+ * \retval 1 player can see thing.
+ * \retval 0 player can not see thing.
+ */
+int
+can_see(dbref player, dbref thing, int can_see_loc)
+{
+  if (!can_interact(thing, player, INTERACT_SEE))
+    return 0;
+
+  /*
+   * 1) your own body isn't listed in a 'look' 2) exits aren't listed in a
+   * 'look' 3) unconnected (sleeping) players aren't listed in a 'look'
+   */
+  if (player == thing || IsExit(thing) ||
+      (IsPlayer(thing) && !Connected(thing)))
+    return 0;
+
+  /* if thing is in a room set LIGHT, it can be seen */
+  else if (IS(Location(thing), TYPE_ROOM, "LIGHT"))
+    return 1;
+
+  /* if the room is non-dark, you can see objects which are light or non-dark */
+  else if (can_see_loc)
+    return (Light(thing) || !DarkLegal(thing));
+
+  /* otherwise room is dark and you can only see lit things */
+  else
+    return (Light(thing));
+}
+
+/** Can a player control a thing?
+ * The control rules are, in order:
+ *   Only God controls God.
+ *   Mistrusted objects control only themselves.
+ *   Objects with the Privilege power control anything that they have the
+ *     Modify power over as long as their level is high enough.
+ *   Objects with the Privilege power cannot be controlled by anyone who
+ *     lacks the Privilege power and sufficient level to control them.
+ *   Objects with the same owner control each other, if they haven't been
+ *     eliminated by this point.
+ *   Objects with the Modify power over other objects can indeed control them.
+ *   If they haven't been successfully targeted by this point, players cannot
+ *     be controlled at all.
+ *   If ZMOs allow control, and you pass the ZMO, you control.
+ *   If the owner is a Zone Master, and you pass the ZM, you control.
+ *   If you pass the control lock, you control.
+ *   Otherwise, no dice.
+ * \param who object attempting to control.
+ * \param what object to be controlled.
+ * \retval 1 who controls what.
+ * \retval 0 who doesn't control what.
+ */
+int
+controls(dbref who, dbref what)
+{
+  boolexp c;
+
+  if (!GoodObject(what))
+    return 0;
+
+  if (God(what) && !God(who))
+    return 0;
+
+  if (Mistrust(who) && (who != what))
+    return 0;
+
+  if (Prived(who) && LEVEL(who) >= LEVEL(what) && CanModify(who, what))
+    return 1;
+
+  if (Inherit_Powers(who) && div_powover(Owner(who), Owner(who), "Privilege")
+       && LEVEL(who) >= LEVEL(what) && CanModify(Owner(who), what))
+    return 1;
+
+  if (div_powover(what,what,"Privilege"))
+    return 0;
+
+  if (Inherit_Powers(what) && div_powover(Owner(what),Owner(what),"Privilege"))
+    return 0;
+
+  if (Owns(who,what) && LEVEL(who) >= LEVEL(what))
+    return 1;
+
+  if (CanModify(who, what))
+    return 1;
+
+  if (IsPlayer(what)) /* Can't control players via zone lock */
+    return 0;
+
+  if (!ZONE_CONTROL_ZMP && (Zone(what) != NOTHING) &&
+      eval_lock(who, Zone(what), Zone_Lock))
+    return 1;
+
+  if (ZMaster(Owner(what)) && !IsPlayer(what) &&
+      eval_lock(who, Owner(what), Zone_Lock))
+    return 1;
+
+  c = getlock_noparent(what, Control_Lock);
+  if (c != TRUE_BOOLEXP) {
+    if (eval_boolexp(who, c, what, NULL))
+      return 1;
+  }
+  return 0;
+}
+
+/** Can someone pay for something (in cash and quota)?
+ * Does who have enough pennies to pay for something, and if something
+ * is being built, does who have enough quota? Admins
+ * aren't subject to either. This function not only checks that they
+ * can afford it, but actually charges them.
+ * \param who player attempting to pay.
+ * \param pennies cost in pennies.
+ * \retval 1 who can pay.
+ * \retval 0 who can't pay.
+ */
+int
+can_pay_fees(dbref who, int pennies)
+{
+  /* check database size -- EVERYONE is subject to this! */
+  if (DBTOP_MAX && (db_top >= DBTOP_MAX + 1) && (first_free == NOTHING)) {
+    notify(who, T("Sorry, there is no more room in the database."));
+    return 0;
+  }
+  /* Can they afford it? */
+  if (!NoPay(who) && (Pennies(Owner(who)) < pennies)) {
+    notify_format(who, T("Sorry, you don't have enough %s."), MONIES);
+    return 0;
+  }
+  /* check building quota */
+  if (!pay_quota(who, QUOTA_COST)) {
+    notify(who, T("Sorry, your building quota has run out."));
+    return 0;
+  }
+
+  /* charge */
+  payfor(who, pennies);
+
+  return 1;
+}
+
+/** Transfer pennies to a player.
+ * \param who recipient.
+ * \param pennies amount of pennies to give.
+ */
+void
+giveto(dbref who, int pennies)
+{
+  /* some people don't need pennies */
+  if (NoPay(who))
+    return;
+
+  who = Owner(who);
+  if ((Pennies(who) + pennies) > Max_Pennies(who))
+    s_Pennies(who, Max_Pennies(who));
+  else
+    s_Pennies(who, Pennies(who) + pennies);
+}
+
+/** Debit a player's pennies, if they can afford it.
+ * \param who player to debit.
+ * \param cost number of pennies to debit.
+ * \retval 1 player successfully debited.
+ * \retval 0 player can't afford the cost.
+ */
+int
+payfor(dbref who, int cost)
+{
+  /* subtract cost from who's pennies */
+  int tmp;
+  dbref owner;
+  if ((cost == 0) || NoPay(who))
+    return 1;
+  owner = Owner(who);
+  if ((tmp = Pennies(owner)) >= cost) {
+    if (Track_Money(owner)) {
+      notify_format(owner, T("GAME: %s(%s) spend %d %s."),
+                   Name(who), unparse_dbref(who), cost,
+                   (cost == 1) ? MONEY : MONIES);
+    }
+    s_Pennies(owner, tmp - cost);
+    return 1;
+  } else {
+    if (Track_Money(owner)) {
+      notify_format(owner, T("GAME: %s(%s) tried to spend %d %s."),
+                   Name(who), unparse_dbref(who), cost,
+                   (cost == 1) ? MONEY : MONIES);
+    }
+    return 0;
+  }
+}
+
+/** Debit a player's pennies, if they can afford it.
+ * \param who player to debit.
+ * \param cost number of pennies to debit.
+ * \retval 1 player successfully debited.
+ * \retval 0 player can't afford the cost.
+ */
+int
+quiet_payfor(dbref who, int cost)
+{
+  /* subtract cost from who's pennies */
+  int tmp;
+  if (NoPay(who))
+    return 1;
+  who = Owner(who);
+  if ((tmp = Pennies(who)) >= cost) {
+    s_Pennies(who, tmp - cost);
+    return 1;
+  } else
+    return 0;
+}
+
+/** Retrieve the amount of quote remaining to a player.
+ * Figure out a player's quota. Add the RQUOTA attribute if he doesn't
+ * have one already. This function returns the REMAINING quota, not
+ * the TOTAL limit.
+ * \param who player to check.
+ * \return player's remaining quota.
+ */
+int
+get_current_quota(dbref who)
+{
+  ATTR *a;
+  int i;
+  int limit;
+  int owned = 0;
+
+  if(!GoodObject(who))
+    return 0;
+
+  /* if he's got an RQUOTA attribute, his remaining quota is that */
+  a = atr_get_noparent(IsDivision(who) ? who : Owner(who), "RQUOTA");
+  if (a)
+    return parse_integer(atr_value(a));
+
+  /* else, count up his objects. If he has less than the START_QUOTA,
+   * then his remaining quota is that minus his number of current objects.
+   * Otherwise, it's his current number of objects. Add the attribute
+   * if he doesn't have it.
+   */
+
+  for (i = 0; i < db_top; i++)
+    if (!IsGarbage(i) && ((!IsDivision(who) && Owner(i) == Owner(who)) 
+         || (IsDivision(who) && !IsPlayer(i) && div_inscope(who,i))))
+      owned++;
+  owned--;                     /* don't count the player or division itself */
+
+  if (!IsDivision(who) && (owned <= START_QUOTA))
+    limit = START_QUOTA - owned;
+  else
+    limit = owned;
+
+  (void) atr_add(IsDivision(who) ? who : Owner(who), "RQUOTA", tprintf("%d", limit), GOD, NOTHING);
+
+  return limit;
+}
+
+
+/** Add or subtract from a player's quota.
+ * \param who object whose owner has the quota changed.
+ * \param payment amount to add to quota (may be negative).
+ */
+void
+change_quota(dbref who, int payment)
+{
+  /* some people don't need a quota */
+  if (NoQuota(who))
+    return;
+
+  (void) atr_add(Owner(who), "RQUOTA",
+                tprintf("%d", get_current_quota(who) + payment), GOD, NOTHING);
+
+  /* Check If Division Quota has to be adjusted now */
+
+  if(!NoQuota(Division(who))) 
+  (void) atr_add(Division(who), "RQUOTA",
+                tprintf("%d", get_current_quota(Division(who)) + payment), GOD, NOTHING);
+
+    
+}
+
+/** Debit a player's quota, if they can afford it.
+ * \param who player whose quota is to be debitted.
+ * \param cost amount of quota to be charged.
+ * \retval 1 quota successfully debitted.
+ * \retval 0 not enough quota to debit.
+ */
+static int
+pay_quota(dbref who, int cost)
+{
+  int curr;
+
+  /* Check to make sure they can pass the division first */
+  curr = get_current_quota(Division(who));
+
+  if(USE_QUOTA && !NoQuota(Division(who)) && (curr - cost < 0))
+    return 0;
+
+  /* some people don't need a quota */
+  if (NoQuota(who))
+    return 1;
+
+  /* figure out how much we have, and if it's big enough */
+  curr = get_current_quota(who);
+
+  if (USE_QUOTA && (curr - cost < 0))          /* not enough */
+    return 0;
+
+  change_quota(who, -cost);
+
+  return 1;
+}
+
+/** Is a name in the forbidden names file?
+ * \param name name to check.
+ * \retval 1 name is forbidden.
+ * \retval 0 name is not forbidden.
+ */
+int
+forbidden_name(const char *name)
+{
+  char buf[BUFFER_LEN], *newlin, *ptr;
+  FILE *fp;
+  char *upname;
+
+  upname = strupper(name);
+  fp = fopen(NAMES_FILE, FOPEN_READ);
+  if (!fp)
+    return 0;
+  while (fgets(buf, sizeof buf, fp)) {
+    upcasestr(buf);
+    /* step on the newline */
+    if ((newlin = strchr(buf, '\r')))
+      *newlin = '\0';
+    else if ((newlin = strchr(buf, '\n')))
+      *newlin = '\0';
+    ptr = buf;
+    if (name && ptr && quick_wild(ptr, name)) {
+      fclose(fp);
+      return 1;
+    }
+  }
+  fclose(fp);
+  return 0;
+}
+
+/** Is a name valid for an object?
+ * This involves several checks.
+ *   Names may not have leading or trailing spaces.
+ *   Names must be only printable characters.
+ *   Names may not exceed the length limit.
+ *   Names may not start with certain tokens, or be "me", "home", "here"
+ * \param n name to check.
+ * \retval 1 name is valid.
+ * \retval 0 name is not valid.
+ */
+int
+ok_name(const char *n)
+{
+  const unsigned char *p, *name = (const unsigned char *) n;
+
+  if (!name || !*name)
+    return 0;
+
+  /* No leading spaces */
+  if (isspace((unsigned char) *name))
+    return 0;
+
+  /* only printable characters */
+  for (p = name; p && *p; p++) {
+    if (!isprint((unsigned char) *p))
+      return 0;
+    if (ONLY_ASCII_NAMES && *p > 127)
+      return 0;
+    if (strchr("[]%\\=&|", *p))
+      return 0;
+  }
+
+  /* No trailing spaces */
+  p--;
+  if (isspace((unsigned char) *p))
+    return 0;
+
+  /* Not too long */
+  if (u_strlen(name) >= OBJECT_NAME_LIMIT)
+    return 0;
+
+  /* No magic cookies */
+  return (name
+         && *name
+         && *name != LOOKUP_TOKEN
+         && *name != NUMBER_TOKEN
+         && *name != NOT_TOKEN && strcasecmp((char *) name, "me")
+         && strcasecmp((char *) name, "home")
+         && strcasecmp((char *) name, "here"));
+}
+
+/** Is a name a valid player name?
+ * Player names must be valid object names, but also not forbidden (unless
+ * the player is a wizard). They are
+ * subject to a different length limit, and subject to more stringent
+ * restrictions on valid characters. Finally, it can't be the same as
+ * an existing player name or alias.
+ * \param name name to check.
+ * \param player player for permission checks.
+ * \retval 1 name is valid for players.
+ * \retval 0 name is not valid for players.
+ */
+int
+ok_player_name(const char *name, dbref player)
+{
+  const unsigned char *scan, *good;
+
+  if (!ok_name(name)
+      || (forbidden_name(name) && !(GoodObject(player) && Director(player)))
+      || strlen(name) >= (size_t) PLAYER_NAME_LIMIT)
+    return 0;
+
+  good = (unsigned char *) (PLAYER_NAME_SPACES ? " `$_-.,'" : "`$_-.,'");
+
+  /* Make sure that the name contains legal characters only */
+  for (scan = (unsigned char *) name; scan && *scan; scan++) {
+    if (isalnum((unsigned char) *scan))
+      continue;
+    if (!strchr((char *) good, *scan))
+      return 0;
+  }
+
+  return (lookup_player(name) == NOTHING);
+}
+
+/** Is a password acceptable?
+ * Acceptable passwords must be non-null and must contain only
+ * printable characters and no whitespace.
+ * \param password password to check.
+ * \retval 1 password is acceptable.
+ * \retval 0 password is not acceptable.
+ */
+int
+ok_password(const char *password)
+{
+  const unsigned char *scan;
+  if (*password == '\0')
+    return 0;
+
+  for (scan = (const unsigned char *) password; *scan; scan++) {
+    if (!(isprint(*scan) && !isspace(*scan))) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+/** Is a name ok for a command or function?
+ * It must begin with an uppercase alpha, and contain only
+ * uppercase alpha, numbers, or underscore thereafter.
+ * \param name name to check.
+ * \retval 1 name is acceptable.
+ * \retval 0 name is not acceptable.
+ */
+int
+ok_command_name(const char *name)
+{
+  const unsigned char *p;
+  if (!isupper((unsigned char) *name))
+    return 0;
+  for (p = (unsigned char *) name; p && *p; p++) {
+    if (!(isupper(*p) || isdigit(*p) || (*p == '_')))
+      return 0;
+  }
+
+  /* Not too long */
+  if (strlen(name) >= COMMAND_NAME_LIMIT)
+    return 0;
+
+  return 1;
+}
+
+/** Does params contain only acceptable HTML tag attributes?
+ * Right now, this means: filter out SEND and XCH_CMD if
+ * the player isn't a Wizard. Params may contain a space-separated
+ * list of tag=value pairs. It's probably possible to fool this
+ * checking. Needs more work, or removing HTML support. 
+ * \param player player using the attribute, or NOTHING for internal.
+ * \param params the attributes to use.
+ * \retval 1 params is acceptable.
+ * \retval 0 params is not accpetable.
+ */
+int
+ok_tag_attribute(dbref player, char *params)
+{
+  unsigned char *p, *q;
+
+  if (!GoodObject(player) || Can_Pueblo_Send(player))
+    return 1;
+  p = (unsigned char *) params;
+  while (*p) {
+    while (*p && isspace(*p))
+      p++;
+    q = p;
+    while (*q && *q != '=')
+      q++;
+    if (*q) {
+      size_t n = q - p;
+      /* Invalid params for non-directors.  Turn to a hashtable if we ever
+       * get more?
+       */
+      *q++ = '\0';
+      if (strncasecmp((char *) p, "SEND", n) == 0
+         || strncasecmp((char *) p, "XCH_CMD", n) == 0)
+       return 0;
+      while (*q && isspace(*q))
+       q++;
+      while (*q && !isspace(*q))
+       q++;
+      p = q;
+    } else
+       return 0;                       /* Malformed param without an = */
+  }
+  return 1;
+}
+
+
+/** The switch command.
+ * \verbatim
+ * For lack of better place the @switch code is here.
+ * @switch expression=args
+ * \endverbatim
+ * \param player the enactor.
+ * \param expression the expression to test against cases.
+ * \param argv array of cases and actions.
+ * \param cause the object that caused this code to run.
+ * \param first if 1, run only first matching case; if 0, run all matching cases.
+ * \param notifyme if 1, perform a notify after executing matched cases.
+ * \param regexp if 1, do regular expression matching; if 0, wildcard globbing.
+ */
+void
+do_switch(dbref player, char *expression, char **argv, dbref cause,
+         int first, int notifyme, int regexp)
+{
+  int any = 0, a;
+  char buff[BUFFER_LEN], *bp;
+  char const *ap;
+  char *tbuf1;
+
+  if (!argv[1])
+    return;
+
+  /* set up environment for any spawned commands */
+  for (a = 0; a < 10; a++)
+    global_eval_context.wnxt[a] = global_eval_context.wenv[a];
+  for (a = 0; a < NUMQ; a++)
+    global_eval_context.rnxt[a] = global_eval_context.renv[a];
+
+  /* now try a wild card match of buff with stuff in coms */
+  for (a = 1;
+       !(first && any) && (a < (MAX_ARG - 1)) && argv[a] && argv[a + 1];
+       a += 2) {
+    /* eval expression */
+    ap = argv[a];
+    bp = buff;
+    process_expression(buff, &bp, &ap, player, cause, cause,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+
+    /* check for a match */
+    if (regexp ? quick_regexp_match(buff, expression, 0)
+       : local_wild_match(buff, expression)) {
+      any = 1;
+      tbuf1 = replace_string("#$", expression, argv[a + 1]);
+      parse_que(player, tbuf1, cause);
+      mush_free(tbuf1, "replace_string.buff");
+    }
+  }
+
+  /* do default if nothing has been matched */
+  if ((a < MAX_ARG) && !any && argv[a]) {
+    tbuf1 = replace_string("#$", expression, argv[a]);
+    parse_que(player, tbuf1, cause);
+    mush_free(tbuf1, "replace_string.buff");
+  }
+
+  /* Pop on @notify me, if requested */
+  if (notifyme)
+    parse_que(player, "@notify me", cause);
+}
+
+/** Parse possessive matches for the possessor.
+ * This function parses strings of the form "Sam's bag" and attempts
+ * to match "Sam". It returns NOTHING if
+ * there's no possessive 's in the string. It destructively modifies
+ * the string (terminating after the possessor name) and modifies the pointer
+ * to the string to point at the name of the contained object.
+ * \param player the enactor/looker.
+ * \param str a pointer to a string to check for possessive matches.
+ * \return matching dbref or NOTHING or AMBIGUOUS.
+ */
+dbref
+parse_match_possessor(dbref player, const char **str)
+{
+  const char *box;             /* name of container */
+  char *obj;                   /* name of object */
+
+  box = *str;
+
+  /* check to see if we have an 's sequence */
+  if ((obj = strchr(box, '\'')) == NULL)
+    return NOTHING;
+  *obj++ = '\0';               /* terminate */
+  if ((*obj == '\0') || ((*obj != 's') && (*obj != 'S')))
+    return NOTHING;
+  /* skip over the 's' and whitespace */
+  do {
+    obj++;
+  } while (isspace((unsigned char) *obj));
+  *str = obj;
+
+  /* we already have a terminating null, so we're okay to just do matches */
+  return match_result(player, box, NOTYPE, MAT_NEIGHBOR | MAT_POSSESSION);
+}
+
+
+/** Autoreply messages for pages (HAVEN, IDLE, AWAY).
+ * \param player the paging player.
+ * \param target the paged player.
+ * \param type type of message to return.
+ * \param message name of attribute containing the message.
+ * \param def default message to return.
+ */
+void
+page_return(dbref player, dbref target, const char *type,
+           const char *message, const char *def)
+{
+  ATTR *d;
+  char buff[BUFFER_LEN], *bp, *asave;
+  char const *ap;
+  struct tm *ptr;
+
+  if (message && *message) {
+    d = atr_get(target, message);
+    if (d) {
+      asave = safe_atr_value(d);
+      ap = asave;
+      bp = buff;
+      process_expression(buff, &bp, &ap, target, player, player,
+                        PE_DEFAULT, PT_DEFAULT, NULL);
+      *bp = '\0';
+      free((Malloc_t) asave);
+      if (*buff) {
+       ptr = (struct tm *) localtime(&mudtime);
+       notify_format(player, T("%s message from %s: %s"), type,
+                     Name(target), buff);
+       if (!Haven(target))
+         notify_format(target,
+                       T("[%d:%02d] %s message sent to %s."), ptr->tm_hour,
+                       ptr->tm_min, type, Name(player));
+      }
+    } else if (def && *def)
+      notify(player, def);
+  }
+}
+
+/** Returns the apparent location of object. 
+ * This is the location for players and things, source for exits, and 
+ * NOTHING for rooms.
+ * \param thing object to get location of.
+ * \return apparent location of object (NOTHING for rooms).
+ */
+dbref
+where_is(dbref thing)
+{
+  if (!GoodObject(thing))
+    return NOTHING;
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    return NOTHING;
+  case TYPE_EXIT:
+    return Home(thing);
+  default:
+    return Location(thing);
+  }
+}
+
+/** Are two objects near each other?
+ * Returns 1 if obj1 is "nearby" object2. "Nearby" is a commutative
+ * relation defined as:  
+ *   obj1 is in the same room as obj2, obj1 is being carried by   
+ *   obj2, or obj1 is carrying obj2. 
+ * Returns 0 if object isn't nearby or the input is invalid.
+ * \param obj1 first object.
+ * \param obj2 second object.
+ * \retval 1 the objects are near each other.
+ * \retval 0 the objects are not near each other.
+ */
+int
+nearby(dbref obj1, dbref obj2)
+{
+  dbref loc1, loc2;
+
+  if (!GoodObject(obj1) || !GoodObject(obj2))
+    return 0;
+  if (IsRoom(obj1) && IsRoom(obj2))
+    return 0;
+  loc1 = where_is(obj1);
+  if (loc1 == obj2)
+    return 1;
+  loc2 = where_is(obj2);
+  if ((loc2 == obj1) || (loc2 == loc1))
+    return 1;
+  return 0;
+}
+
+/** User-defined verbs.
+ * \verbatim
+ * This implements the @verb command.
+ * \endverbatim
+ * \param player the enactor.
+ * \param cause the object causing this command to run.
+ * \param arg1 the object to read verb attributes from.
+ * \param argv the array of remaining arguments to the verb command.
+ */
+void
+do_verb(dbref player, dbref cause, char *arg1, char **argv)
+{
+  dbref victim;
+  dbref actor;
+  int i;
+  char *wsave[10];
+
+  /* find the object that we want to read the attributes off
+   * (the object that was the victim of the command)
+   */
+
+  /* our victim object can be anything */
+  victim = match_result(player, arg1, NOTYPE, MAT_EVERYTHING);
+
+  if (victim == NOTHING) {
+    notify(player, T("What was the victim of the verb?"));
+    return;
+  }
+  /* find the object that executes the action */
+
+  if (!argv || !argv[1] || !*argv[1]) {
+    notify(player, T("What do you want to do with the verb?"));
+    return;
+  }
+  actor = match_result(player, argv[1], NOTYPE, MAT_EVERYTHING);
+
+  if (actor == NOTHING) {
+    notify(player, T("What do you want to do the verb?"));
+    return;
+  }
+  /* Control check is fascist. 
+   * First check: we don't want <actor> to do something involuntarily.
+   *   Both victim and actor have to be controlled by the thing which did 
+   *   the @verb (for speed we do a WIZARD check first), or: cause controls
+   *   actor plus the second check is passed.
+   * Second check: we need read access to the attributes.
+   *   Either the player controls victim or the player
+   *   must be priviledged, or the victim has to be VISUAL.
+   */
+
+  if (!((controls(player, victim) && controls(player, actor)) ||
+       ((controls(cause, actor) && Can_Examine(player, victim))))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* We're okay.  Send out messages. */
+
+  for (i = 0; i < 10; i++) {
+    wsave[i] = global_eval_context.wenv[i];
+    global_eval_context.wenv[i] = argv[i + 7];
+  }
+
+  real_did_it(actor, victim,
+             upcasestr(argv[2]), argv[3], upcasestr(argv[4]), argv[5],
+             NULL, Location(actor), global_eval_context.wenv, NA_INTER_HEAR);
+
+  for (i = 0; i < 10; i++)
+    global_eval_context.wenv[i] = wsave[i];
+
+  /* Now we copy our args into the stack, and do the command. */
+
+  for (i = 0; i < 10; i++)
+    global_eval_context.wnxt[i] = argv[i + 7];
+
+  charge_action(actor, victim, upcasestr(argv[6]));
+}
+
+/** Structure for passing arguments to grep_util_helper().
+ */
+struct guh_args {
+  char *buff;          /**< Buffer for output */
+  char *bp;            /**< Pointer to buff's current position */
+  char *lookfor;       /**< String to grep for */
+  int len;             /**< Length of lookfor */
+  int insensitive;     /**< If 1, case-insensitive match; if 0, sensitive */
+};
+
+static int
+grep_util_helper(dbref player __attribute__ ((__unused__)),
+                dbref thing __attribute__ ((__unused__)),
+                dbref parent __attribute__ ((__unused__)),
+                char const *pattern
+                __attribute__ ((__unused__)), ATTR *atr, void *args)
+{
+  struct guh_args *guh = args;
+  int found;
+  char *s;
+
+  s = (char *) atr_value(atr); /* warning: static */
+  found = 0;
+  while (*s && !found) {
+    if ((!guh->insensitive && !strncmp(guh->lookfor, s, guh->len)) ||
+       (guh->insensitive && !strncasecmp(guh->lookfor, s, guh->len)))
+      found = 1;
+    else
+      s++;
+  }
+  if (found) {
+    if (guh->bp != guh->buff)
+      safe_chr(' ', guh->buff, &guh->bp);
+    safe_str(AL_NAME(atr), guh->buff, &guh->bp);
+  }
+  return found;
+}
+
+/** Utility function for grep funtions/commands.
+ * This function returns a list of attributes on an object that
+ * match a name pattern and contain another string.
+ * \param player the enactor.
+ * \param thing object to check attributes on.
+ * \param pattern wildcard pattern for attributes to check.
+ * \param lookfor string to find within each attribute.
+ * \param len length of lookfor.
+ * \param insensitive if 1, case-insensitive matching; if 0, case-sensitive.
+ * \return string containing list of attribute names with matching data.
+ */
+char *
+grep_util(dbref player, dbref thing, char *pattern, char *lookfor,
+         int len, int insensitive)
+{
+  struct guh_args guh;
+
+  guh.buff = (char *) mush_malloc(BUFFER_LEN + 1, "grep_util.buff");
+  guh.bp = guh.buff;
+  guh.lookfor = lookfor;
+  guh.len = len;
+  guh.insensitive = insensitive;
+  (void) atr_iter_get(player, thing, pattern, 0, grep_util_helper, &guh);
+  *guh.bp = '\0';
+  return guh.buff;
+}
+
+/** Structure for grep_helper() arguments. */
+struct gh_args {
+  char *lookfor;       /**< Pattern to look for. */
+  int len;             /**< Length of lookfor. */
+  int insensitive;     /**< if 1, case-insensitive matching; if 0, sensitive */
+};
+
+static int
+grep_helper(dbref player, dbref thing __attribute__ ((__unused__)),
+            dbref parent __attribute__ ((__unused__)),
+           char const *pattern
+           __attribute__ ((__unused__)), ATTR *atr, void *args)
+{
+  struct gh_args *gh = args;
+  int found;
+  char *s;
+  char tbuf1[BUFFER_LEN];
+  char buf[BUFFER_LEN];
+  char *tbp;
+
+  found = 0;
+  tbp = tbuf1;
+
+  s = (char *) atr_value(atr); /* warning: static */
+  while (s && *s) {
+    if ((gh->insensitive && !strncasecmp(gh->lookfor, s, gh->len)) ||
+       (!gh->insensitive && !strncmp(gh->lookfor, s, gh->len))) {
+      found = 1;
+      strncpy(buf, s, gh->len);
+      buf[gh->len] = '\0';
+      s += gh->len;
+      safe_format(tbuf1, &tbp, "%s%s%s", ANSI_HILITE, buf, ANSI_NORMAL);
+    } else {
+      safe_chr(*s, tbuf1, &tbp);
+      s++;
+    }
+  }
+  *tbp = '\0';
+
+  /* if we got it, display it */
+  if (found)
+    notify_format(player, "%s%s [#%d%s]%s %s",
+                 ANSI_HILITE, AL_NAME(atr),
+                 Owner(AL_CREATOR(atr)),
+                 privs_to_letters(attr_privs, AL_FLAGS(atr)),
+                 ANSI_NORMAL, tbuf1);
+  return found;
+}
+
+/** The grep command
+ * \verbatim
+ * This implements @grep.
+ * \endverbatim
+ * \param player the enactor.
+ * \param obj string containing obj/attr pattern to grep through.
+ * \param lookfor unparsed string to search for.
+ * \param flag if 0, return attribute names; if 1, return attrib text.
+ * \param insensitive if 1, case-insensitive match; if 0, sensitive.
+ */
+void
+do_grep(dbref player, char *obj, char *lookfor, int flag, int insensitive)
+{
+  struct gh_args gh;
+  dbref thing;
+  char *pattern;
+  int len;
+  char *tp;
+
+  if ((len = strlen(lookfor)) < 1) {
+    notify(player, T("What pattern do you want to grep for?"));
+    return;
+  }
+  /* find the attribute pattern */
+  pattern = strchr(obj, '/');
+  if (!pattern)
+    pattern = (char *) "*";    /* set it to global match */
+  else
+    *pattern++ = '\0';
+
+  /* now we've got the object. match for it. */
+  if ((thing = noisy_match_result(player, obj, NOTYPE, MAT_EVERYTHING)) ==
+      NOTHING)
+    return;
+  if (!Can_Examine(player, thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  if (flag) {
+    gh.lookfor = lookfor;
+    gh.len = len;
+    gh.insensitive = insensitive;
+    if (!atr_iter_get(player, thing, pattern, 0, grep_helper, &gh))
+      notify(player, T("No matching attributes."));
+  } else {
+    tp = grep_util(player, thing, pattern, lookfor, len, insensitive);
+    notify_format(player, T("Matches of '%s' on %s(#%d): %s"), lookfor,
+                 Name(thing), thing, tp);
+    mush_free((Malloc_t) tp, "grep_util.buff");
+  }
+}
diff --git a/src/privtab.c b/src/privtab.c
new file mode 100644 (file)
index 0000000..c399a5e
--- /dev/null
@@ -0,0 +1,158 @@
+/**
+ * \file privtab.c
+ *
+ * \brief Privilege tables for PennMUSH.
+ *
+ * A privilege table is a respresentation of different privilege
+ * flags with associated names, characters, and bitmasks.
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include "conf.h"
+#include "privtab.h"
+#include "externs.h"
+#include "confmagic.h"
+
+
+/** Convert a string to a set of privilege bits, masked by an original set.
+ * Given a privs table, a string, and an original set of privileges,
+ * return a modified set of privileges by applying the privs in the
+ * string to the original set of privileges.
+ * \param table pointer to a privtab.
+ * \param str a space-separated string of privilege names to apply.
+ * \param origprivs the original privileges.
+ * \return a privilege bitmask.
+ */
+int
+string_to_privs(PRIV *table, const char *str, long int origprivs)
+{
+  PRIV *c;
+  long int yes = 0;
+  long int no = 0;
+  char *p, *r;
+  char tbuf1[BUFFER_LEN];
+  int not;
+  int words = 0;
+
+  if (!str || !*str)
+    return origprivs;
+  strcpy(tbuf1, str);
+  r = trim_space_sep(tbuf1, ' ');
+  while ((p = split_token(&r, ' '))) {
+    words++;
+    not = 0;
+    if (*p == '!') {
+      not = 1;
+      if (!*++p)
+       continue;
+    }
+    for (c = table; c->name; c++) {
+      if (string_prefix(c->name, p)) {
+       if (not)
+         no |= c->bits_to_set;
+       else
+         yes |= c->bits_to_set;
+       break;
+      }
+    }
+  }
+  /* If we made no changes, and were given one word, 
+   * we probably were given letters instead */
+  if (!no && !yes && (words == 1))
+    return letter_to_privs(table, str, origprivs);
+  return ((origprivs | yes) & ~no);
+}
+
+/** Convert a letter string to a set of privilege bits, masked by an original set.
+ * Given a privs table, a letter string, and an original set of privileges,
+ * return a modified set of privileges by applying the privs in the
+ * string to the original set of privileges.
+ * \param table pointer to a privtab.
+ * \param str a string of privilege letters to apply.
+ * \param origprivs the original privileges.
+ * \return a privilege bitmask.
+ */
+int
+letter_to_privs(PRIV *table, const char *str, long int origprivs)
+{
+  PRIV *c;
+  long int yes = 0, no = 0;
+  const char *p;
+  int not;
+
+  if (!str || !*str)
+    return origprivs;
+
+  for (p = str; *p; p++) {
+    not = 0;
+    if (*p == '!') {
+      not = 1;
+      if (!*++p)
+       break;
+    }
+    for (c = table; c->name; c++) {
+      if (c->letter == *p) {
+       if (not)
+         no |= c->bits_to_set;
+       else
+         yes |= c->bits_to_set;
+       break;
+      }
+    }
+  }
+  return ((origprivs | yes) & ~no);
+}
+
+/** Given a table and a bitmask, return a privs string (static allocation).
+ * \param table pointer to a privtab.
+ * \param privs bitmask of privileges.
+ * \return statically allocated space-separated string of priv names.
+ */
+const char *
+privs_to_string(PRIV *table, int privs)
+{
+  PRIV *c;
+  static char buf[BUFFER_LEN];
+  char *bp;
+
+  bp = buf;
+  for (c = table; c->name; c++) {
+    if (privs & c->bits_to_show) {
+      if (bp != buf)
+       safe_chr(' ', buf, &bp);
+      safe_str(c->name, buf, &bp);
+      privs &= ~c->bits_to_set;
+    }
+  }
+  *bp = '\0';
+  return buf;
+}
+
+
+/** Given a table and a bitmask, return a privs letter string (static allocation).
+ * \param table pointer to a privtab.
+ * \param privs bitmask of privileges.
+ * \return statically allocated string of priv letters.
+ */
+const char *
+privs_to_letters(PRIV *table, int privs)
+{
+  PRIV *c;
+  static char buf[BUFFER_LEN];
+  char *bp;
+
+  bp = buf;
+  for (c = table; c->name; c++) {
+    if ((privs & c->bits_to_show) && c->letter) {
+      safe_chr(c->letter, buf, &bp);
+      privs &= ~c->bits_to_set;
+    }
+  }
+  *bp = '\0';
+  return buf;
+}
diff --git a/src/prog.c b/src/prog.c
new file mode 100644 (file)
index 0000000..d284a56
--- /dev/null
@@ -0,0 +1,689 @@
+/* prog.c - MUSH Program and Prompting Related Code - File created 3tpritnf/11/02
+ *          Part of the CobraMUSH Server Distribution
+ *
+ */
+#include "copyrite.h"
+#include "config.h"
+#ifdef I_STRING
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include "conf.h"
+#include "externs.h"
+#include "parse.h"
+#include "htab.h"
+#include "division.h"
+#include "boolexp.h"
+#include "command.h"
+#include "cmds.h"
+#include "confmagic.h"
+#include "attrib.h"
+#include "flags.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "ansi.h"
+#include "log.h"
+#include "extmail.h"
+
+/* External declarations. */
+
+extern int process_output(DESC * d);
+extern int queue_newwrite(DESC * d, const unsigned char *b, int n);
+extern int queue_eol(DESC * d);
+
+int password_handler(DESC *, char *);
+
+/* Telnet codes */
+#define IAC            255     /**< interpret as command */
+#define GOAHEAD                249     /**< go ahead */
+
+#define UNDEFINED_PROMPT "\x1B[1m>\x1b[0m"
+#define PI_LOCK                      0x1
+#define PI_PROMPT            0x2
+#define PI_INTERNAL_PROMPT    0x40
+
+extern DESC *descriptor_list;
+
+extern int do_command(DESC *, char *);
+void prog_place(dbref player, dbref object, ATTR * patr, int lock);
+
+/* SWITCHES- LOCK(requires power) QUIT */
+COMMAND(cmd_prog)
+{
+  dbref target;
+  dbref thing;
+  char tbuf1[BUFFER_LEN], *tbuf2;
+  char *s, *t, *tbp;
+  ATTR *patr;
+  DESC *d;
+  int i, pflags = 0;
+
+  if (!arg_left) {
+    notify(player, "Invalid arguments.");
+    return;
+  }
+  target = match_result(player, arg_left, TYPE_PLAYER, MAT_EVERYTHING);
+  if ((target == NOTHING) || !Connected(target)) {
+    notify(player, "Can't find that player.");
+    return;
+  }
+
+  if (!CanProg(player, target)) {
+    notify(player, "You can't place that player into a program.");
+    return;
+  }
+
+  if (SW_ISSET(sw, SWITCH_LOCK)) {
+    if (!CanProgLock(player, target)) {
+      notify(player, "You can't lock that player into a program.");
+      return;
+    }
+    pflags |= PI_LOCK;
+  }
+
+  DESC_ITER_CONN(d)
+      if (d->player == target) {
+    if ((InProg(d->player) && ((d->pinfo.lock & PI_LOCK) &&
+                               !CanProgLock(player, d->player)))
+        || (d->pinfo.lock & PI_INTERNAL_PROMPT)) {
+      notify(player, "That player is currently locked in their program.");
+      return;
+    } else
+      d->pinfo.lock = 0;
+  }
+
+  if (SW_ISSET(sw, SWITCH_QUIT)) {
+    DESC_ITER_CONN(d) {
+      if (d->player == target) {
+        if (!d->connected) {
+          notify(player, "Player not connected.");
+          return;
+        }
+        d->pinfo.object = -1;
+        d->pinfo.atr = NULL;
+        d->pinfo.lock = 0;
+        d->pinfo.function = NULL;
+        d->input_handler = do_command;
+
+        queue_newwrite(d, (unsigned char *) "\r\n", 2);
+      }
+    }
+    twiddle_flag_internal("FLAG", target, "INPROGRAM", 1);
+    atr_clr(target, "XY_PROGENV", GOD);
+    if (Nospoof(target))
+      notify_format(target, "%s took you out of your program.",
+                    object_header(target, player));
+    return;
+  }
+
+  s = strchr(arg_right, '/');
+
+  if (!s) {
+    thing = player;
+    t = strchr(arg_right, ':');
+    s = arg_right;
+  } else {
+    *s++ = '\0';
+    thing = match_thing(player, arg_right);
+    t = strchr(s, ':');
+  }
+
+  if (t != NULL)
+    *t++ = '\0';
+
+  if (thing == NOTHING) {
+    notify(player, "Can't find that object");
+    return;
+  } else if (!controls(player, thing)) {
+    notify(player, "Permission denied.");
+    return;
+  } /* get atr here into patr */
+  else if ((patr = atr_get(thing, upcasestr(s))) == NULL) {
+    notify(player, "No such attribute.");
+    notify(player, s);
+    return;
+  } else if (!Can_Write_Attr(player, thing, patr)) {
+    notify(player, "Permission denied.");
+    return;
+  }
+  /* NOW.. Put them in the prog */
+  notify_format(player, "You place %s into a program.",
+                object_header(player, target));
+  if (Nospoof(target))
+    notify_format(target, "You have been placed into a program by %s.",
+                  object_header(target, player));
+  if (t) {
+    pflags |= PI_PROMPT;
+    do_rawlog(LT_ERR, "Bad Program Prompt.  Allowed (will be defunct as of CobraMUSH 1.0).  Suggest use @prompt: %d - %s",
+                 player, global_eval_context.ccom);
+    notify_format(Owner(player), 
+         "WARNING: %s is using bad prompt syntax with @program.  Refer to help @prompt."
+         , object_header(Owner(player), player));
+
+    char buf[BUFFER_LEN], *bp;
+    tooref = ooref, ooref = NOTHING;
+    atr_add(target, "XY_PROGPROMPT", t, GOD, NOTHING);
+    ooref = tooref;
+    memset(buf, '\0', BUFFER_LEN);
+    bp = buf;
+  }
+  /* Save Registers to XY_PROGENV */
+  memset(tbuf1, '\0', BUFFER_LEN);
+  tbp = tbuf1;
+  for(i = 0 ; i < NUMQ ; i++) {
+      if(global_eval_context.renv[i][0]) {
+       tbuf2 = str_escaped_chr(global_eval_context.renv[i], '|');
+       safe_str(tbuf2, tbuf1, &tbp);
+       mush_free((Malloc_t) tbuf2, "str_escaped_chr.buff");
+      }
+      safe_chr('|', tbuf1, &tbp);
+  }
+
+  tbp--;
+  *tbp = '\0';
+
+  /* Now Save to XY_PROGENV */
+  (void) atr_add(target, "XY_PROGENV", tbuf1, GOD, NOTHING);
+
+
+  /* Place them into the actual program */
+
+  prog_place(target, thing, patr, pflags);
+}
+
+/* arg0 - player, 
+ * arg 1 - prog thing or attribute
+ * arg 2 - attribute, 
+ * arg 3 - program prompt silently ignored and logged bad usage (until Cobra 1.0) 
+ *       - **CORRECT USAGE** ProgLock 1 or 0
+ * arg 4 - lock(1/0) logged bad usage (until Cobra 1.0) */
+FUNCTION(fun_prog)
+{
+  ATTR *patr;
+  DESC *d;
+  char tbuf1[BUFFER_LEN], *tbuf2;
+  char *tbp;
+  dbref target, thing;
+  char pflags = 0x0;
+  int i;
+
+  target = match_result(executor, args[0], TYPE_PLAYER, MAT_EVERYTHING);
+  if (!GoodObject(target) || !Connected(target)) {
+    safe_str("#-1 UNFINDABLE", buff, bp);
+    return;
+  }
+
+  if (!CanProg(executor, target)) {
+    safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  }
+
+  if (args[3] && is_integer(args[3]) && atoi(args[3])
+      && !CanProgLock(executor, target)) {
+    safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  }
+
+  DESC_ITER_CONN(d) {
+    if (d->player == target) {
+      if ((InProg(d->player) && ((d->pinfo.lock & PI_LOCK) &&
+                                 !CanProgLock(executor, d->player)))
+          || (d->pinfo.lock & PI_INTERNAL_PROMPT)) {
+        safe_str("#-1 PERMISSION DENIED", buff, bp);
+        return;
+      } else
+        d->pinfo.lock = 0;
+    }
+  }
+  thing = match_thing(executor, args[1]);
+  if (thing == NOTHING) {
+    safe_str("#-1 THING UNFINDABLE", buff, bp);
+    return;
+  } else if (!controls(executor, thing)) {
+    safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  } /* get atr here into patr */
+  else if ((patr = atr_get(thing, upcasestr(args[2]))) == NULL) {
+    safe_str("#-1 NO SUCH ATTRIB", buff, bp);
+    return;
+  } else if (!Can_Write_Attr(executor, thing, patr)) {
+    safe_str("#-1 PERMISSION DENIED", buff, bp);
+    return;
+  }
+  if (args[3] && is_integer(args[3]) && atoi(args[3]) == 1)
+    pflags = PI_LOCK;
+  else {
+    do_rawlog(LT_ERR,
+              "Bad Program Prompt, Silently Ignored. HINT: Use Prompt() : %d - %s",
+              executor, global_eval_context.ccom);
+  }
+
+  if (args[4] && atoi(args[4]) == 1) {
+    pflags = PI_LOCK;
+    do_rawlog(LT_ERR, "Bad Proglock Usage format used on %d - %s",
+              executor, global_eval_context.ccom);
+  }
+  /* Save Registers to XY_PROGENV */
+  memset(tbuf1, '\0', BUFFER_LEN);
+  tbp = tbuf1;
+  for(i = 0 ; i < NUMQ ; i++) {
+      if(global_eval_context.renv[i][0]) {
+       tbuf2 = str_escaped_chr(global_eval_context.renv[i], '|');
+       safe_str(tbuf2, tbuf1, &tbp);
+       mush_free((Malloc_t) tbuf2, "str_escaped_chr.buff");
+      }
+      safe_chr('|', tbuf1, &tbp);
+  }
+
+  tbp--;
+  *tbp = '\0';
+  /* Now Save to XY_PROGENV */
+  (void) atr_add(target, "XY_PROGENV", tbuf1, GOD, NOTHING);
+
+
+
+  prog_place(target, thing, patr, pflags);
+  safe_chr('1', buff, bp);
+}
+
+/* quitprog(player) */
+FUNCTION(fun_quitprog)
+{
+  DESC *d;
+  dbref target;
+
+  target = match_result(executor, args[0], TYPE_PLAYER, MAT_EVERYTHING);
+
+  if (target == NOTHING || !Connected(target)) {
+    safe_str(e_notvis, buff, bp);
+    return;
+  }
+  if (!CanProg(executor, target)) {
+    safe_str(e_perm, buff, bp);
+    return;
+  }
+
+  DESC_ITER_CONN(d)
+    if (d->player == target) {
+      if (d->pinfo.lock && !CanProgLock(executor, target)) {
+        safe_str(e_perm, buff, bp);
+        return;
+      }
+      d->pinfo.object = -1;
+      d->pinfo.atr = NULL;
+      d->pinfo.lock = 0;
+      d->input_handler = do_command;
+      queue_newwrite(d, (unsigned char *) tprintf("%c%c", IAC, GOAHEAD), 2);
+    }
+  atr_clr(target, "XY_PROGINFO", GOD);
+  atr_clr(target, "XY_PROGPROMPT", GOD);
+  atr_clr(target, "XY_PROGENV", GOD);
+  twiddle_flag_internal("FLAG", target, "INPROGRAM", 1);
+  safe_chr('1', buff, bp);
+
+  if (Nospoof(target))
+    notify_format(target, "%s took you out of your program.",
+                  object_header(target, executor));
+
+}
+
+COMMAND(cmd_prompt)
+{
+  dbref who;
+  DESC *d;
+  char *prompt;
+  char *tmp;
+
+  switch (who =
+          match_result(player, arg_left, NOTYPE,
+                       MAT_OBJECTS | MAT_HERE | MAT_CONTAINER)) {
+  case NOTHING:
+    notify(player, T("I don't see that here."));
+    break;
+  case AMBIGUOUS:
+    notify(player, T("I don't know who you mean!"));
+    break;
+  default:
+    if (!CanProg(player, who)) {
+      notify_format(player,
+                    T("I'm sorry, but %s wishes to be left alone now."),
+                    Name(who));
+      return;
+    }
+    if (Typeof(who) != TYPE_PLAYER) {
+      notify_format(player, T("I'm sorry, but %s cannot receive prompts."),
+                    Name(who));
+      return;
+    }
+    if (!InProg(who)) {
+      notify_format(player, T("I'm sorry, but %s is not in a @program."),
+                    Name(who));
+      return;
+    }
+    if (arg_right && *arg_right) {
+      prompt = arg_right;
+      /* Save the prompt as well */
+      tooref = ooref, ooref = NOTHING;
+      atr_add(who, "XY_PROGPROMPT", arg_right, GOD, NOTHING);
+      ooref = tooref;
+    } else
+      prompt = (char *) UNDEFINED_PROMPT;
+
+    DESC_ITER_CONN(d) {
+      if (d->player == who) {
+        if (PromptConnection(d)) {
+          if (ShowAnsiColor(d->player))
+            tmp = tprintf("%s %c%c", prompt, IAC, GOAHEAD);
+          else
+            tmp =
+                tprintf("%s %c%c", remove_markup(prompt, NULL), IAC, GOAHEAD);
+        } else {
+          if (ShowAnsiColor(d->player))
+            tmp = tprintf("%s \r\n", prompt);
+          else
+            tmp = tprintf("%s \r\n", remove_markup(prompt, NULL));
+        }
+        queue_newwrite(d, (unsigned char *) tmp, strlen(tmp));
+      }
+    }
+  }
+
+}
+
+FUNCTION(fun_prompt)
+{
+  dbref who;
+  DESC *d;
+  char *prompt;
+  char *tmp;
+
+  switch (who =
+          match_result(executor, args[0], NOTYPE,
+                       MAT_OBJECTS | MAT_HERE | MAT_CONTAINER)) {
+  case NOTHING:
+    safe_str(T(e_match), buff, bp);
+    break;
+  case AMBIGUOUS:
+    safe_str(T("#-2 AMBIGUOUS MATCH"), buff, bp);
+    break;
+  default:
+    if (!CanProg(executor, who)) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (Typeof(who) != TYPE_PLAYER) {
+      safe_str(T(e_perm), buff, bp);
+      return;
+    }
+    if (!InProg(who)) {
+      safe_str(T("#-1 NOT IN @PROGRAM"), buff, bp);
+      return;
+    }
+    if (args[1])
+      prompt = args[1];
+    else
+      prompt = (char *) UNDEFINED_PROMPT;
+
+    DESC_ITER_CONN(d) {
+      if (d->player == who) {
+        if (PromptConnection(d)) {
+          if (ShowAnsiColor(who))
+            tmp = tprintf("%s %c%c", prompt, IAC, GOAHEAD);
+          else
+            tmp =
+                tprintf("%s %c%c", remove_markup(prompt, NULL), IAC, GOAHEAD);
+        } else {
+          if (ShowAnsiColor(who))
+            tmp = tprintf("%s \r\n", prompt);
+          else
+            tmp = tprintf("%s \r\n", remove_markup(prompt, NULL));
+        }
+        queue_newwrite(d, (unsigned char *) tmp, strlen(tmp));
+      }
+    }
+  }
+}
+
+/* eventually write core_commands() for holding QUIT LOGOUT WHO & other
+ * core commands for when locked
+ */
+int
+prog_handler(DESC * d, char *input)
+{
+  ATTR *a;
+  char buf[BUFFER_LEN];
+  char *tbuf, *bp;
+  char *p_buf[BUFFER_LEN / 2];
+  int rcnt, i;
+
+  if (!strcmp(input, "IDLE")) {
+    (d->cmds)++;
+    return 1;
+  }
+  /* handle escaped commandz */
+  if (d->pinfo.lock) {
+    if (!strcmp(input, "|QUIT")) {
+      return 0;
+    } else if (!strcmp(input, "|LOGOUT")) {
+      return -1;
+    }
+  } else if (*input == '|' && *(input + 1) != '\0') {
+    return do_command(d, input + 1);
+  }
+  (d->cmds)++;
+  d->last_time = mudtime;
+  /* First Load Any Q Registers if we have 'em */
+  a = atr_get(d->player, "XY_PROGENV");
+  if(a) {
+    rcnt = elist2arr(p_buf, BUFFER_LEN / 2, safe_atr_value(a), '|');
+
+    for(i = 0 ; i <  NUMQ && i < rcnt ; i++)
+      if(p_buf[i] && strlen(p_buf[i]) > 0)
+       strcpy(global_eval_context.renv[i], p_buf[i]);
+  }
+  strcpy(buf, atr_value(d->pinfo.atr));
+  global_eval_context.wnxt[0] = input;
+  parse_que(d->pinfo.object, buf, d->player);
+  /* Save Registers to XY_PROGENV */
+  memset(buf, '\0', BUFFER_LEN);
+  bp = buf;
+  for(i = 0 ; i < NUMQ ; i++) {
+      if(global_eval_context.renv[i][0]) {
+       tbuf = str_escaped_chr(global_eval_context.renv[i], '|');
+       safe_str(tbuf, buf, &bp);
+       mush_free((Malloc_t) tbuf, "str_escaped_chr.buff");
+      }
+      safe_chr('|', buf, &bp);
+  }
+  bp--;
+  *bp = '\0';
+  /* Now Save to XY_PROGENV */
+  (void) atr_add(d->player, "XY_PROGENV", buf, GOD, NOTHING);
+
+  return 1;
+}
+
+/* Check password of entering pinfo.object */
+int
+password_handler(DESC * d, char *input)
+{
+  int r;
+
+  if (!password_check(d->pinfo.object, input)) {
+    d->input_handler = do_command;
+    queue_newwrite(d, (unsigned char *) "Invalid password.\r\n", 19);
+    do_log(LT_WIZ, d->player, d->pinfo.object, "** @SU FAIL **");
+    d->pinfo.object = NOTHING;
+    return 1;
+  }
+
+  r = d->pinfo.function(d, input);
+  d->pinfo.function = NULL;
+  d->pinfo.lock = 0;
+  return r;
+}
+
+
+int
+pw_div_connect(DESC * d, char *input __attribute__ ((__unused__)))
+{
+  /* Division @su should be backlogged through an internal attribute. */
+  add_to_div_exit_path(d->player, Division(d->player));
+  /* Trigger SDOUT first */
+  did_it(d->player, Division(d->player), "SDOUT", NULL, NULL,  NULL, "ASDOUT", Location(d->player));
+  Division(d->player) = d->pinfo.object;
+  /* Now Trigger Sdin */
+  did_it(d->player, Division(d->player), "SDIN", 
+               tprintf("You have switched into Division: %s", object_header(d->player, Division(d->player)))
+              , NULL,  NULL, "ASDIN", Location(d->player));
+       
+  d->pinfo.object = NOTHING;
+  d->input_handler = do_command;
+  return 1;
+}
+
+int
+pw_player_connect(DESC * d, char *input __attribute__ ((__unused__)))
+{
+  DESC *match;
+  int num = 0, is_hidden;
+
+  do_log(LT_WIZ, d->player, d->pinfo.object, "** @SU SUCCESS **");
+  add_to_exit_path(d, d->player);
+  announce_disconnect(d->player);
+  d->player = d->pinfo.object;
+  d->mailp = (MAIL *) find_exact_starting_point(d->pinfo.object);
+  /* We're good @su him */
+  is_hidden = Can_Hide(d->pinfo.object) && Dark(d->pinfo.object);
+  DESC_ITER_CONN(match)
+      if (match->player == d->player) {
+    num++;
+    if (is_hidden)
+      match->hide = 1;
+  }
+  if (ModTime(d->pinfo.object))
+    notify_format(d->pinfo.object,
+                  T("%ld failed connections since last login."),
+                  ModTime(d->pinfo.object));
+  announce_connect(d->pinfo.object, 0, num);
+  check_last(d->pinfo.object, d->addr, d->ip);  /* set last, lastsite, give paycheck */
+  queue_eol(d);
+#ifdef USE_MAILER
+  if (command_check_byname(d->pinfo.object, "@MAIL"))
+    check_mail(d->pinfo.object, 0, 0);
+#endif
+  set_player_folder(d->pinfo.object, 0);
+  do_look_around(d->pinfo.object);
+  if (Haven(d->pinfo.object))
+    notify(d->player,
+           T("Your HAVEN flag is set. You cannot receive pages."));
+  d->pinfo.object = NOTHING;
+  d->input_handler = do_command;
+  return 1;
+}
+
+/* load program for connecting player */
+void
+prog_load_desc(DESC * d)
+{
+  ATTR *a;
+  char buf[BUFFER_LEN];
+  char buf2[BUFFER_LEN];
+  char *p, *t;
+  char wprmpt[2];
+  dbref obj;
+
+  t = buf;
+
+  a = atr_get(d->player, "XY_PROGINFO");
+  if (!a)
+    return;
+  /* OBJECT ATTRIBUTE PROGLOCK */
+  strcpy(buf, atr_value(a));
+
+  p = split_token(&t, ' ');
+  /* OBJECT */
+  obj = (dbref) parse_number(p);
+  if (!GoodObject(obj)) {       /* reset their prog status */
+    twiddle_flag_internal("FLAG", obj, "INPROGRAM", 1);
+    atr_clr(d->player, "XY_PROGINFO", GOD);
+    atr_clr(d->player, "XY_PROGPROMPT", GOD);
+    return;
+  }
+  p = split_token(&t, ' ');
+  /* ATTRIBUTE */
+  strcpy(buf2, p);
+  a = atr_get(obj, buf2);
+  if (!a)
+    return;
+  d->pinfo.object = obj;
+  d->pinfo.atr = a;
+  p = split_token(&t, ' ');
+  d->pinfo.lock = atoi(p);
+  d->input_handler = prog_handler;
+  memset(wprmpt, '\0', 2);
+  if ((a = atr_get(d->player, "XY_PROGPROMPT"))) {
+    /* program prompt */
+    char rbuf[BUFFER_LEN], *tbp, *rbp;
+    strcpy(buf, atr_value(a));
+    tbp = buf;
+    rbp = rbuf;
+    memset(rbuf, '\0', BUFFER_LEN);
+    process_expression(rbuf, &rbp, (const char **) &tbp, d->pinfo.object,
+                       d->pinfo.object, d->pinfo.object, PE_DEFAULT,
+                       PT_DEFAULT, (PE_Info *) NULL);
+
+    *rbp = '\0';
+    if (PromptConnection(d)) {
+      if (ShowAnsiColor(d->player))
+        snprintf(buf, BUFFER_LEN - 1, "%s %c%c", rbuf, IAC, GOAHEAD);
+      else
+        snprintf(buf, BUFFER_LEN - 1, "%s %c%c",
+                 remove_markup(rbuf, NULL), IAC, GOAHEAD);
+    } else {
+      if (ShowAnsiColor(d->player))
+        snprintf(buf, BUFFER_LEN - 1, "%s\r\n", rbuf);
+      else
+        snprintf(buf, BUFFER_LEN - 1, "%s\r\n", remove_markup(rbuf, NULL));
+    }
+  } else {
+    if (PromptConnection(d)) {
+      if (ShowAnsi(d->player))
+        snprintf(buf, BUFFER_LEN - 1, "%s>%s %c%c", ANSI_HILITE,
+                 ANSI_NORMAL, IAC, GOAHEAD);
+      else
+        snprintf(buf, BUFFER_LEN - 1, "> %c%c", IAC, GOAHEAD);
+    } else {
+      if (ShowAnsi(d->player))
+        snprintf(buf, BUFFER_LEN - 1, "%s>%s\r\n", ANSI_HILITE,
+                 ANSI_NORMAL);
+      else
+        snprintf(buf, BUFFER_LEN - 1, ">\r\n");
+    }
+  }
+  queue_newwrite(d, (unsigned char *) buf, strlen(buf));
+  process_output(d);
+}
+
+void
+prog_place(dbref player, dbref object, ATTR * patr, int lock)
+{
+  DESC *d;
+  char str[BUFFER_LEN];
+
+  twiddle_flag_internal("FLAG", player, "INPROGRAM", 0);
+  tooref = ooref, ooref = NOTHING;
+  sprintf(str, "%d %s %d", object, patr->name, (lock & PI_LOCK) ? 1 : 0);
+  if (!(lock & PI_PROMPT)) {
+    atr_clr(player, "XY_PROGPROMPT", GOD);
+  }
+  (void) atr_add(player, "XY_PROGINFO", str, GOD, NOTHING);
+  ooref = tooref;
+  DESC_ITER_CONN(d)
+      if (d->player == player) {
+    d->pinfo.lock = lock;
+    prog_load_desc(d);
+  }
+}
diff --git a/src/ptab.c b/src/ptab.c
new file mode 100644 (file)
index 0000000..d6fbfff
--- /dev/null
@@ -0,0 +1,364 @@
+/**
+ * \file ptab.c
+ *
+ * \brief Prefix tables for PennMUSH.
+ *
+ *
+ */
+#include "config.h"
+#include "copyrite.h"
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include "conf.h"
+#include "externs.h"
+#include "ptab.h"
+#include "confmagic.h"
+
+static int ptab_find_exact_nun(PTAB *tab, const char *key);
+static int WIN32_CDECL ptab_cmp(const void *, const void *);
+
+/** A ptab entry. */
+typedef struct ptab_entry {
+  void *data;                  /**< pointer to data */
+  char key[BUFFER_LEN];                /**< the index key */
+} ptab_entry;
+
+/** The memory usage of a ptab entry, not including data. */
+#define PTAB_SIZE (sizeof(struct ptab_entry) - BUFFER_LEN)
+
+/** Initialize a ptab.
+ * \param tab pointer to a ptab.
+ */
+void
+ptab_init(PTAB *tab)
+{
+  if (!tab)
+    return;
+  tab->state = tab->maxlen = tab->len = tab->current = 0;
+  tab->tab = NULL;
+}
+
+/** Free all entries in a ptab.
+ * \param tab pointer to a ptab.
+ */
+void
+ptab_free(PTAB *tab)
+{
+  if (!tab)
+    return;
+  if (tab->tab) {
+    int n;
+    for (n = 0; n < tab->len; n++)
+      mush_free(tab->tab[n], "ptab.entry");
+    mush_free(tab->tab, "ptab");
+  }
+  tab->tab = NULL;
+  tab->state = tab->maxlen = tab->len = tab->current = 0;
+}
+
+/** Search a ptab for an entry that prefix-matches a given key.
+ * We search through unique prefixes of keys in the table to try
+ * to match the key we're looking for.
+ * \param tab pointer to a ptab.
+ * \param key key to search for.
+ * \return void pointer to ptab data indexed by key, or NULL if none.
+ */
+void *
+ptab_find(PTAB *tab, const char *key)
+{
+  int nun;
+
+  if (!tab || !key || !*key || tab->state)
+    return NULL;
+
+  if (tab->len < 10) {         /* Just do a linear search for small tables */
+    for (nun = 0; nun < tab->len; nun++) {
+      if (string_prefix(tab->tab[nun]->key, key)) {
+       if (nun + 1 < tab->len && string_prefix(tab->tab[nun + 1]->key, key))
+         return NULL;
+       else
+         return tab->tab[nun]->data;
+      }
+    }
+  } else {                     /* Binary search of the index */
+    int left = 0;
+    int cmp;
+    int right = tab->len - 1;
+
+    while (1) {
+      nun = (left + right) / 2;
+
+      if (left > right)
+       break;
+
+      cmp = strcasecmp(key, tab->tab[nun]->key);
+
+      if (cmp == 0) {
+       return tab->tab[nun]->data;
+      } else if (cmp < 0) {
+       int mem;
+       /* We need to catch the first unique prefix */
+       if (string_prefix(tab->tab[nun]->key, key)) {
+         for (mem = nun - 1; mem >= 0; mem--) {
+           if (string_prefix(tab->tab[mem]->key, key)) {
+             if (strcasecmp(tab->tab[mem]->key, key) == 0)
+               return tab->tab[mem]->data;
+           } else
+             break;
+         }
+         /* Non-unique prefix */
+         if (mem != nun - 1)
+           return NULL;
+         for (mem = nun + 1; mem < tab->len; mem++) {
+           if (string_prefix(tab->tab[mem]->key, key)) {
+             if (strcasecmp(tab->tab[mem]->key, key) == 0)
+               return tab->tab[mem]->data;
+           } else
+             break;
+         }
+         if (mem != nun + 1)
+           return NULL;
+         return tab->tab[nun]->data;
+       }
+       if (left == right)
+         break;
+       right = nun - 1;
+      } else {                 /* cmp > 0 */
+       if (left == right)
+         break;
+       left = nun + 1;
+      }
+    }
+  }
+  return NULL;
+}
+
+/** Search a ptab for an entry that exactly matches a given key.
+ * We search through full names of keys in the table to try
+ * to match the key we're looking for.
+ * \param tab pointer to a ptab.
+ * \param key key to search for.
+ * \return void pointer to ptab data indexed by key, or NULL if none.
+ */
+void *
+ptab_find_exact(PTAB *tab, const char *key)
+{
+  int nun;
+  nun = ptab_find_exact_nun(tab, key);
+  if (nun < 0)
+    return NULL;
+  else
+    return tab->tab[nun]->data;
+}
+
+static int
+ptab_find_exact_nun(PTAB *tab, const char *key)
+{
+  int nun;
+
+  if (!tab || !key || tab->state)
+    return -1;
+
+  if (tab->len < 10) {         /* Just do a linear search for small tables */
+    int cmp;
+    for (nun = 0; nun < tab->len; nun++) {
+      cmp = strcasecmp(tab->tab[nun]->key, key);
+      if (cmp == 0)
+       return nun;
+      else if (cmp > 0)
+       return -1;
+    }
+  } else {                     /* Binary search of the index */
+    int left = 0;
+    int cmp;
+    int right = tab->len - 1;
+
+    while (1) {
+      nun = (left + right) / 2;
+
+      if (left > right)
+       break;
+
+      cmp = strcasecmp(key, tab->tab[nun]->key);
+
+      if (cmp == 0)
+       return nun;
+      if (left == right)
+       break;
+      if (cmp < 0)
+       right = nun - 1;
+      else                     /* cmp > 0 */
+       left = nun + 1;
+    }
+  }
+  return -1;
+}
+
+static void
+delete_entry(PTAB *tab, int n)
+{
+  mush_free(tab->tab[n], "ptab.entry");
+
+  /* If we're deleting the last item in the list, just decrement the length.
+   *  Otherwise, we have to fill in the hole
+   */
+  if (n < tab->len - 1) {
+    int i;
+    for (i = n + 1; i < tab->len; i++)
+      tab->tab[i - 1] = tab->tab[i];
+  }
+  tab->len--;
+}
+
+/** Delete a ptab entry indexed by key.
+ * \param tab pointer to a ptab.
+ * \param key key to search for.
+ */
+void
+ptab_delete(PTAB *tab, const char *key)
+{
+  int nun;
+  nun = ptab_find_exact_nun(tab, key);
+  if (nun >= 0)
+    delete_entry(tab, nun);
+  return;
+}
+
+/** Put a ptab into insertion state.
+ * \param tab pointer to a ptab.
+ */
+void
+ptab_start_inserts(PTAB *tab)
+{
+  if (!tab)
+    return;
+  tab->state = 1;
+}
+
+static int WIN32_CDECL
+ptab_cmp(const void *a, const void *b)
+{
+  const struct ptab_entry *const *ra = a;
+  const struct ptab_entry *const *rb = b;
+
+  return strcasecmp((*ra)->key, (*rb)->key);
+}
+
+/** Complete the ptab insertion process by re-sorting the entries.
+ * \param tab pointer to a ptab.
+ */
+void
+ptab_end_inserts(PTAB *tab)
+{
+  struct ptab_entry **tmp;
+  if (!tab)
+    return;
+  tab->state = 0;
+  qsort(tab->tab, tab->len, sizeof(struct ptab_entry *), ptab_cmp);
+
+  tmp = realloc(tab->tab, (tab->len + 10) * sizeof(struct ptab_entry *));
+  if (!tmp)
+    return;
+  tab->tab = tmp;
+  tab->maxlen = tab->len + 10;
+}
+
+/** Insert an entry into a ptab.
+ * \param tab pointer to a ptab.
+ * \param key key to insert entry under.
+ * \param data pointer to entry data.
+ */
+void
+ptab_insert(PTAB *tab, const char *key, void *data)
+{
+  int lamed;
+
+  if (!tab || tab->state != 1)
+    return;
+
+  if (tab->len == tab->maxlen) {
+    struct ptab_entry **tmp;
+    if (tab->maxlen == 0)
+      tab->maxlen = 200;
+    else
+      tab->maxlen *= 2;
+    tmp = realloc(tab->tab, tab->maxlen * sizeof(struct ptab_entry **));
+    if (tab->tab == NULL)
+      add_check("ptab");
+    if (!tmp)
+      return;
+    tab->tab = tmp;
+  }
+
+  lamed = strlen(key) + 1;
+
+  tab->tab[tab->len] = mush_malloc(PTAB_SIZE + lamed, "ptab.entry");
+
+  tab->tab[tab->len]->data = data;
+  memcpy(tab->tab[tab->len]->key, key, lamed);
+  tab->len++;
+
+}
+
+/** Return the data (and optionally the key) of the first entry in a ptab.
+ * This function resets the 'current' index in the ptab to the start
+ * of the table.
+ * \param tab pointer to a ptab.
+ * \param key memory location to store first key unless NULL is passed in.
+ * \return void pointer to data from first entry, or NULL if none.
+ */
+void *
+ptab_firstentry_new(PTAB *tab, char *key)
+{
+  if (!tab || tab->len == 0)
+    return NULL;
+  tab->current = 1;
+  if (key)
+    strcpy(key, tab->tab[0]->key);
+  return tab->tab[0]->data;
+}
+
+/** Return the data (and optionally the key) of the next entry in a ptab.
+ * This function increments the 'current' index in the ptab.
+ * \param tab pointer to a ptab.
+ * \param key memory location to store next key unless NULL is passed in.
+ * \return void pointer to data from next entry, or NULL if none.
+ */
+void *
+ptab_nextentry_new(PTAB *tab, char *key)
+{
+  if (!tab || tab->current >= tab->len)
+    return NULL;
+  if (key)
+    strcpy(key, tab->tab[tab->current]->key);
+  return tab->tab[tab->current++]->data;
+}
+
+/** Header for report of ptab stats.
+ * \param player player to notify with table header.
+ */
+void
+ptab_stats_header(dbref player)
+{
+  notify_format(player, "Table      Entries AvgComparisons %39s", "~Memory");
+}
+
+/** Data for one line of report of ptab stats.
+ * \param player player to notify with table.
+ * \param tab pointer to ptab to summarize stats for.
+ * \param pname name of ptab, for row header.
+ */
+void
+ptab_stats(dbref player, PTAB *tab, const char *pname)
+{
+  int mem, nun;
+
+  mem = sizeof(struct ptab_entry *) * tab->maxlen;
+
+  for (nun = 0; nun < tab->len; nun++)
+    mem += PTAB_SIZE + strlen(tab->tab[nun]->key) + 1;
+
+  notify_format(player, "%-10s %7d %14.3f %39d", pname, tab->len, log(tab->len),
+               mem);
+}
diff --git a/src/rob.c b/src/rob.c
new file mode 100644 (file)
index 0000000..3e0442a
--- /dev/null
+++ b/src/rob.c
@@ -0,0 +1,465 @@
+/**
+ * \file rob.c
+ *
+ * \brief Kill and give.
+ *
+ * This file is called rob.c for historical reasons, and one day it'll
+ * probably get folded into some other file.
+ *
+ *
+ */
+
+#include "config.h"
+#include "copyrite.h"
+#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "match.h"
+#include "parse.h"
+#include "flags.h"
+#include "log.h"
+#include "lock.h"
+#include "dbdefs.h"
+#include "game.h"
+#include "confmagic.h"
+#include "case.h"
+
+static void do_give_to(dbref player, char *arg, int silent);
+
+/** The give command.
+ * \param player the enactor/giver.
+ * \param recipient name of object to receive.
+ * \param amnt name of object to be transferred, or amount of pennies.
+ * \param silent if 1, hush the usual messages.
+ */
+void
+do_give(dbref player, char *recipient, char *amnt, int silent)
+{
+  dbref who;
+  int amount;
+  char tbuf1[BUFFER_LEN];
+  char *bp;
+
+  /* If we have a recipient, but no amnt, try parsing for
+   * 'give <amnt> to <recipient>' instead of 'give <recipient>=<amount>'
+   */
+  if (recipient && *recipient && (!amnt || !*amnt)) {
+    do_give_to(player, recipient, silent);
+    return;
+  }
+
+  /* check recipient */
+  switch (who =
+         match_result(player, recipient, TYPE_PLAYER,
+                      MAT_NEAR_THINGS | MAT_ENGLISH)) {
+  case NOTHING:
+    notify(player, T("Give to whom?"));
+    return;
+  case AMBIGUOUS:
+    notify(player, T("I don't know who you mean!"));
+    return;
+  }
+
+  /* Can't give to garbage... */
+  if (IsGarbage(who)) {
+    notify(player, T("Give to whom?"));
+    return;
+  }
+
+  if (!is_strict_integer(amnt)) {
+    dbref thing;
+    switch (thing =
+           match_result(player, amnt, TYPE_THING,
+                         MAT_POSSESSION | MAT_ENGLISH)) {
+    case NOTHING:
+       notify(player, T("You don't have that!"));
+      return;
+    case AMBIGUOUS:
+      notify(player, T("I don't know which you mean!"));
+      return;
+    default:
+      /* if you can give yourself, that's like "enter". since we
+       * do no lock check with give, we shouldn't be able to
+       * do this.
+       */
+      if (thing == player) {
+       notify(player, T("You can't give yourself away!"));
+       return;
+      }
+      /* Don't give things to themselves. */
+      if (thing == who) {
+       notify(player, T("You can't give an object to itself!"));
+       return;
+      }
+      if (!eval_lock(player, thing, Give_Lock)) {
+       notify(player, T("You can't give that away."));
+       return;
+      }
+      if (Mobile(thing) && (EnterOk(who) || controls(player, who))) {
+       moveto(thing, who);
+
+       /* Notify the giver with their GIVE message */
+       bp = tbuf1;
+       safe_format(tbuf1, &bp, T("You gave %s to %s."), Name(thing),
+                   Name(who));
+       *bp = '\0';
+       did_it_with(player, player, "GIVE", tbuf1, "OGIVE", NULL,
+                   "AGIVE", NOTHING, thing, who, NA_INTER_SEE);
+
+       /* Notify the object that it's been given */
+       notify_format(thing, T("%s gave you to %s."), Name(player), Name(who));
+
+       /* Recipient gets success message on thing and receive on self */
+       did_it(who, thing, "SUCCESS", NULL, "OSUCCESS", NULL, "ASUCCESS",
+              NOTHING);
+       bp = tbuf1;
+       safe_format(tbuf1, &bp, T("%s gave you %s."), Name(player),
+                   Name(thing));
+       *bp = '\0';
+       did_it_with(who, who, "RECEIVE", tbuf1, "ORECEIVE", NULL,
+                   "ARECEIVE", NOTHING, thing, player, NA_INTER_SEE);
+      } else
+       notify(player, T("Permission denied."));
+    }
+    return;
+  }
+  amount = parse_integer(amnt);
+  /* do amount consistency check */
+  if (Pennies(who) + amount > Max_Pennies(who))
+    amount = Max_Pennies(who) - Pennies(who);
+  if (amount < 0 && !(MoneyAdmin(player) && controls(player, who))) {
+    notify(player, T("What is this, a holdup?"));
+    return;
+  } else if (amount == 0) {
+    notify_format(player,
+                 T("You must specify a positive number of %s."), MONIES);
+    return;
+  }
+  if (MoneyAdmin(player) && (amount < 0) && (Pennies(who) + amount < 0))
+     amount = -Pennies(who);
+  /* try to do the give */
+  if (!MoneyAdmin(player) && !payfor(player, amount)) {
+    notify_format(player, T("You don't have that many %s to give!"), MONIES);
+  } else {
+    char *pay_env[10] = { NULL };
+    char paid[SBUF_LEN], *pb;
+
+    pay_env[0] = pb = paid;
+
+    /* objects work differently */
+    if (IsThing(who)) {
+      /* give pennies to an object */
+      int cost = 0;
+      ATTR *a;
+      char *preserveq[NUMQ];
+      char *preserves[10];
+      char fbuff[BUFFER_LEN];
+      char *fbp, *asave;
+      char const *ap;
+
+      a = atr_get(who, "COST");
+      if (!a) {
+       /* No cost attribute */
+       notify_format(player, T("%s refuses your money."), Name(who));
+       giveto(player, amount);
+       return;
+      }
+      save_global_regs("give_save", preserveq);
+      save_global_env("give_save", preserves);
+      asave = safe_atr_value(a);
+      ap = asave;
+      fbp = fbuff;
+      safe_integer_sbuf(amount, paid, &pb);
+      *pb = '\0';
+      global_eval_context.wenv[0] = paid;
+      process_expression(fbuff, &fbp, &ap, who, player, player,
+                        PE_DEFAULT, PT_DEFAULT, NULL);
+      *fbp = '\0';
+      free((Malloc_t) asave);
+      restore_global_regs("give_save", preserveq);
+      restore_global_env("give_save", preserves);
+      if (amount < (cost = atoi(fbuff))) {
+       notify(player, T("Feeling poor today?"));
+       giveto(player, amount);
+       return;
+      }
+      if (cost < 0)
+       return;
+      if ((amount - cost) > 0) {
+       notify_format(player, T("You get %d in change."), amount - cost);
+      } else {
+       notify_format(player, T("You paid %d %s."), amount,
+                     ((amount == 1) ? MONEY : MONIES));
+      }
+      giveto(player, amount - cost);
+      giveto(who, cost);
+      pb = paid;
+      safe_integer_sbuf(cost, paid, &pb);
+      *pb = '\0';
+      real_did_it(player, who, "PAYMENT", NULL, "OPAYMENT", NULL, "APAYMENT",
+                 NOTHING, pay_env, NA_INTER_SEE);
+      return;
+    } else {
+      /* give pennies to a player */
+      if (amount > 0) {
+       notify_format(player,
+                     T("You give %d %s to %s."), amount,
+                     ((amount == 1) ? MONEY : MONIES), Name(who));
+      } else {
+       notify_format(player, T("You took %d %s from %s!"), abs(amount),
+                     ((abs(amount) == 1) ? MONEY : MONIES), Name(who));
+      }
+      if (IsPlayer(who) && !silent) {
+       if (amount > 0) {
+         notify_format(who, T("%s gives you %d %s."), Name(player),
+                       amount, ((amount == 1) ? MONEY : MONIES));
+       } else {
+         notify_format(who, T("%s took %d %s from you!"), Name(player),
+                       abs(amount), ((abs(amount) == 1) ? MONEY : MONIES));
+       }
+      }
+      giveto(who, amount);
+      safe_integer_sbuf(amount, paid, &pb);
+      *pb = '\0';
+      real_did_it(player, who, "PAYMENT", NULL, "OPAYMENT", NULL, "APAYMENT",
+                 NOTHING, pay_env, NA_INTER_SEE);
+    }
+  }
+}
+
+/** the buy command
+ * \param player the enactor/buyer
+ * \param item the item to buy
+ * \param from who to buy it from
+ * \param cost the cost
+ */
+void
+do_buy(dbref player, char *item, char *from, int price)
+{
+  dbref vendor;
+  char *prices;
+  char *plus;
+  char *cost;
+  char finditem[BUFFER_LEN];
+  char buycost[BUFFER_LEN];
+  int boughtit;
+  int len;
+  char buff[BUFFER_LEN], *bp;
+  char obuff[BUFFER_LEN];
+  char *r[BUFFER_LEN / 2];
+  char *c[BUFFER_LEN / 2];
+  char *buy_env[10] = { NULL };
+  int affordable;
+  int costcount, ci;
+  int count, i;
+  int low, high;               /* lower bound, upper bound of cost */
+  ATTR *a;
+
+  if (!GoodObject(Location(player)))
+    return;
+
+  vendor = Contents(Location(player));
+  if (vendor == player)
+    vendor = Next(player);
+
+  if (from != NULL && *from) {
+    switch (vendor =
+           match_result(player, from, TYPE_PLAYER,
+                        MAT_NEAR_THINGS | MAT_ENGLISH)) {
+    case NOTHING:
+      notify(player, T("Buy from whom?"));
+      return;
+    case AMBIGUOUS:
+      notify(player, T("I don't know who you mean!"));
+      return;
+    }
+    if (vendor == player) {
+      notify(player, T("You can't buy from yourself!"));
+      return;
+    }
+  } else if (vendor == NOTHING) {
+    notify(player, T("There's nobody here to buy things from."));
+    return;
+  } else {
+    from = NULL;
+  }
+
+  if (!item || !*item || !(item = trim_space_sep(item, ' '))) {
+    notify(player, T("Buy what?"));
+    return;
+  }
+
+  bp = finditem;
+  safe_str(item, finditem, &bp);
+  safe_chr(':', finditem, &bp);
+  *bp = '\0';
+  for (bp = strchr(finditem, ' '); bp; bp = strchr(bp, ' '))
+    *bp = '_';
+
+  len = strlen(finditem);
+
+  /* Scan pricelists */
+  boughtit = -1;
+  affordable = 1;
+  do {
+    a = atr_get(vendor, "PRICELIST");
+    if (!a)
+      continue;
+    /* atr_value uses a static buffer, so we'll take advantage of that */
+    prices = atr_value(a);
+    upcasestr(prices);
+    count = list2arr(r, BUFFER_LEN / 2, atr_value(a), ' ');
+    if (!count)
+      continue;
+    for (i = 0; i < count; i++) {
+      if (!strncasecmp(finditem, r[i], len)) {
+       /* Check cost */
+       cost = r[i] + len;
+       if (!*cost)
+         continue;
+       costcount = list2arr(c, BUFFER_LEN / 2, cost, ',');
+       for (ci = 0; ci < costcount; ci++) {
+         cost = c[ci];
+         /* Formats:
+          * 10,2000+,10-100
+          */
+         if ((plus = strchr(cost, '-'))) {
+           *(plus++) = '\0';
+           if (!is_strict_integer(cost))
+             continue;
+           if (!is_strict_integer(plus))
+             continue;
+           low = parse_integer(cost);
+           high = parse_integer(plus);
+           if (price < 0) {
+             boughtit = low;
+           } else if (price >= low && price <= high) {
+             boughtit = price;
+           }
+         } else if ((plus = strchr(cost, '+'))) {
+           *(plus++) = '\0';
+           if (!is_strict_integer(cost))
+             continue;
+           low = parse_integer(cost);
+           if (price < 0) {
+             boughtit = low;
+           } else if (price > low) {
+             boughtit = price;
+           }
+         } else if (is_strict_integer(cost)) {
+           low = parse_integer(cost);
+           if (price < 0) {
+             boughtit = low;
+           } else if (low == price) {
+             boughtit = price;
+           }
+         } else {
+           continue;
+         }
+         if (boughtit >= 0) {
+           if (!payfor(player, boughtit)) {
+             affordable = 0;
+             boughtit = 0;
+             continue;
+           }
+           bp = strchr(finditem, ':');
+           if (bp)
+             *bp = '\0';
+           for (bp = finditem; *bp; bp++)
+             *bp = DOWNCASE(*bp);
+           bp = buff;
+           safe_format(buff, &bp, "You buy a %s from %s.",
+                       finditem, Name(vendor));
+           *bp = '\0';
+           bp = obuff;
+           safe_format(obuff, &bp, "buys a %s from %s.",
+                       finditem, Name(vendor));
+           buy_env[0] = finditem;
+           buy_env[1] = buycost;
+           bp = buycost;
+           safe_integer(boughtit, buycost, &bp);
+           *bp = '\0';
+           real_did_it(player, vendor, "BUY", buff, "OBUY", obuff, "ABUY",
+                       NOTHING, buy_env, NA_INTER_SEE);
+           return;
+         }
+       }
+      }
+    }
+  } while (!from && ((vendor = Next(vendor)) != NOTHING));
+
+  if (price >= 0) {
+    if (!from) {
+      notify(player, T("I can't find that item with that price here."));
+    } else {
+      notify_format(player, T("%s isn't selling that item for that price"),
+                   Name(vendor));
+    }
+  } else if (affordable) {
+    if (!from) {
+      notify(player, T("I can't find that item here."));
+    } else {
+      notify_format(player, T("%s isn't selling that item."), Name(vendor));
+    }
+  } else {
+    notify(player, T("You can't afford that."));
+  }
+}
+
+
+void s_Pennies(dbref thing, int amnt) {
+       if(amnt > HUGE_INT)
+               Pennies(thing) = (int) HUGE_INT;
+       else if(amnt < 0)
+               Pennies(thing) = 0;
+       else Pennies(thing) = amnt;
+}
+
+
+/** The other syntax of the give command.
+ * \param player the enactor/giver.
+ * \param arg "something to someone".
+ * \param silent if 1, hush the usual messages.
+ */
+static void
+do_give_to(dbref player, char *arg, int silent)
+{
+  char *s;
+
+  /* Parse out the object and recipient */
+  upcasestr(arg);
+  s = (char *) string_match(arg, "TO ");
+  if (!s) {
+    notify(player, T("Did you want to give something *to* someone?"));
+    return;
+  }
+  while ((s > arg) && isspace(*(s - 1))) {
+    s--;
+  }
+  if (s == arg) {
+    notify(player, T("Give what?"));
+    return;
+  }
+  *s++ = '\0';
+  s = (char *) string_match(s, "TO ");
+  s += 3;
+  while (*s && isspace(*s))
+    s++;
+  if (!*s) {
+    notify(player, T("Give to whom?"));
+    return;
+  }
+  /* At this point, 'arg' is the object, and 's' is the recipient.
+   * But be double-safe to be sure we don't loop.
+   */
+  if (!*arg || !*s) {
+    notify(player, T("I don't know what you mean."));
+    return;
+  }
+  do_give(player, s, arg, silent);
+  return;
+}
diff --git a/src/rplog.c b/src/rplog.c
new file mode 100644 (file)
index 0000000..a43b041
--- /dev/null
@@ -0,0 +1,283 @@
+/* rplog.c -> @rplog by Nveid.
+ * 01/29/05 - RLB - Created File and began work on RPLog Code
+ */
+#include "copyrite.h"
+#include "config.h"
+#ifdef I_STRING
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include "conf.h"
+#include "externs.h"
+#include "parse.h"
+#include "htab.h"
+#include "division.h"
+#include "command.h"
+#include "cmds.h"
+#include "confmagic.h"
+#include "attrib.h"
+#include "flags.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "ansi.h"
+#include "log.h"
+
+#define RPBUF_SIZE 1
+#define RPBUF_MAXSIZE 20
+#define RPRESET_TIME 86400 /* How long it'll be till statuses get reset */
+
+#ifdef RPMODE_SYS
+static void rplog_status(dbref room, char status);
+void init_rplogs();
+void rplog_room(dbref room, dbref player, char *str);
+static void rplog_recall(dbref player, dbref room, char quiet, int lines);
+static int expire_bufferq(BUFFERQ *,time_t);
+void rplog_shutdown();
+
+
+void init_rplogs() {
+  dbref current;
+
+  for(current = 0 ; current < db_top ; current++)
+    if(has_flag_by_name(current, "ICFUNCS", TYPE_ROOM)) {
+       db[current].rplog.bufferq = allocate_bufferq(RPBUF_SIZE); 
+       db[current].rplog.status = 0;
+    } else { 
+      db[current].rplog.bufferq = NULL;
+    }
+}
+
+void rplog_init_room(dbref room) {
+  db[room].rplog.bufferq = allocate_bufferq(RPBUF_SIZE);
+  db[room].rplog.status = 0;
+}
+
+void  rplog_shutdown_room(dbref room) {
+  if(db[room].rplog.bufferq)
+   free_bufferq(db[room].rplog.bufferq); 
+}
+
+void rplog_room(dbref room, dbref player, char *str) {
+  int lines; 
+
+  /* FIXME: i'm doing this all wrong num_buffered has nothing to do with
+   * bufferq_lines.. perhaps create a dynamic add_to_bufferq allocating
+   * fucker thingie. */
+
+  if(db[room].rplog.bufferq && has_flag_by_name(room, "ICFUNCS", TYPE_ROOM)) {
+    if(db[room].rplog.status == 1) { /* Check Size.. We might need to make it bigger */
+      if(db[room].rplog.bufferq->num_buffered == (lines = 
+           bufferq_lines(db[room].rplog.bufferq)) && lines < RPBUF_MAXSIZE )
+       reallocate_bufferq(db[room].rplog.bufferq,lines+1); 
+    }
+    add_to_bufferq(db[room].rplog.bufferq, 0, player, str); 
+  }
+}
+
+/* 1- Preserve logs past scroll out
+ * 0- Reset Room
+ */
+static void rplog_status(dbref room, char status) {
+  if(!db[room].rplog.bufferq)
+    return;
+  db[room].rplog.status = status;
+  if(status == 0)  /* expire old message and reset status */
+    expire_bufferq(db[room].rplog.bufferq, (mudtime-(60*20)));
+}
+
+
+void rplog_reset() {
+  int i;
+  char *bq;
+  time_t timeof;
+  if((mudtime % 3600)==0) { /* reset all things in a combat status 
+                            * that have been ina  combat status over 24 hours */
+    for(i = 0; i < db_top; i++)
+      if(IsGarbage(i))
+       continue;
+      else if(db[i].rplog.bufferq != NULL) {
+       if(db[i].rplog.status == 1) {
+         bq = db[i].rplog.bufferq->buffer;
+         bq += sizeof(int) + sizeof(dbref) + sizeof(int);
+         /* K.. we're pointed here.. make sure we're not greater than the buffer end though... */
+         if(bq >= db[i].rplog.bufferq->buffer_end) /* OOo... we are, nothings ever been done here.. So lets continue */
+           continue;
+         memcpy(&timeof, bq, sizeof(time_t)); 
+         if(timeof < (mudtime-RPRESET_TIME)) {
+           /* make sure its small.. */
+           reallocate_bufferq(db[i].rplog.bufferq,  RPBUF_SIZE);
+           expire_bufferq(db[i].rplog.bufferq, (time_t) (mudtime - 1200));
+         }
+       } else { /* Otherwise just do message expirations */
+         expire_bufferq(db[i].rplog.bufferq, (time_t) (mudtime - 1200));
+       }
+      }
+  }
+}
+
+COMMAND(cmd_rplog) {
+  dbref room;
+  char quiet = 0;
+  int lines;
+
+
+  if(arg_left && *arg_left) {
+    room = noisy_match_result(player, arg_left, TYPE_ROOM, MAT_EVERYTHING);
+    if(room == NOTHING)
+      return;
+    if(!IsRoom(room)) {
+      notify(player, "Must be a room.");
+      return;
+    }
+  } else room = absolute_room(player);
+
+  if(!GoodObject(room)) {
+    notify(player, "Can't find your absolute location.");
+    return;
+  }
+
+  if(!has_flag_by_name(room, "ICFUNCS", TYPE_ROOM)) {
+    notify(player, "You must use this command from an IC Room.");
+    return;
+  }
+
+  if(SW_ISSET(sw, SWITCH_COMBAT)) {
+    rplog_status(room, 1);
+    notify(player, "Combat Logging Triggered.");
+    return;
+  }
+
+  if(SW_ISSET(sw, SWITCH_RESET)) {
+    rplog_status(room, 0);
+    notify(player, "Room Resetted.");
+    return;
+  }
+
+  if(SW_ISSET(sw, SWITCH_QUIET))
+    quiet = 1;
+  if(arg_left)
+    lines = parse_integer(arg_left);
+  else 
+    lines = 0;
+  if(db[room].rplog.status)
+    rplog_recall(player, room, quiet, lines);
+  else
+    notify(player, "The room must be in a combat status to view the buffer.");
+}
+
+FUNCTION(fun_crplog) {
+  dbref room;
+  if(!args[0])
+    room = absolute_room(executor);
+  else
+    room = parse_dbref(args[0]);
+  if(!GoodObject(room)) {
+    safe_str("#-1 INVALID OBJECT", buff, bp);
+    return;
+  }
+
+  if(!has_flag_by_name(room, "ICFUNCS", TYPE_ROOM)) {
+    safe_str("#-1 NOT AN IC ROOM", buff, bp);
+    return;
+  }
+
+  safe_chr(( db[room].rplog.status ? '1' : '0' ), buff, bp);
+}
+
+/* View Buffered Room & what not */
+static void rplog_recall(dbref player, dbref room, char quiet, int lines) {
+  int num_lines;
+  time_t timestamp;
+  char *stamp, *buf, *p = NULL;
+  int skip;
+  dbref speaker;
+  int type;
+
+  if(!GoodObject(player) || !GoodObject(room)) {
+    return;
+  }
+
+  if(!IsRoom(room)) {
+    notify(player, "You have to be a room to use this.");
+    return;
+  }
+
+  if(isempty_bufferq(db[room].rplog.bufferq)) {
+      notify(player, "Nothing to see");
+      return;
+  }
+
+  num_lines = lines > 0 ? lines : db[room].rplog.bufferq->num_buffered;
+
+  skip = BufferQNum(db[room].rplog.bufferq) - num_lines;
+
+  do {
+    buf = iter_bufferq(db[room].rplog.bufferq, &p, &speaker, &type, &timestamp);
+    if (skip <= 0) {
+      if (buf) {
+        if (Nospoof(player) && GoodObject(speaker)) {
+          char *nsmsg =
+            ns_esnotify(speaker, na_one, &player, Paranoid(player) ? 1 : 0);
+          if (quiet)
+            notify_format(player, "%s %s", nsmsg, buf);
+          else {
+            stamp = show_time(timestamp, 0);
+            notify_format(player, "[%s] %s %s", stamp, nsmsg, buf);
+          }
+          mush_free(nsmsg, "string");
+        } else {
+          if (quiet)
+            notify(player, buf);
+          else {
+            stamp = show_time(timestamp, 0);
+            notify_format(player, "[%s] %s", stamp, buf);
+          }
+       }
+      }
+    }
+    skip--;
+  } while(buf);
+
+}
+
+void rplog_shutdown(void) {
+  dbref cur_obj;
+
+  for(cur_obj = 0 ; cur_obj < db_top ; cur_obj++)
+    if(has_flag_by_name(cur_obj, "ICFUNCS", TYPE_ROOM) 
+       && (db[cur_obj].rplog.bufferq)) {
+      free_bufferq(db[cur_obj].rplog.bufferq);
+      db[cur_obj].rplog.bufferq = NULL;
+      db[cur_obj].rplog.status = 0;
+    }
+}
+
+/* This is going to be a cool trick... go through this bufferq & expire old messages */
+static int expire_bufferq(BUFFERQ *bq, time_t time) {
+  /* First.. Loop though all the messages to find the time of the message we're expiring */
+  char *p = bq->buffer, *w;
+  time_t time_at;
+  int size, jump;
+  int skipped = 0;
+
+  while((p < bq->buffer_end)) {
+    memcpy(&size, p, sizeof(size));
+    jump = size + BUFFERQLINEOVERHEAD + 1;
+    w = p;
+    w += sizeof(size) + sizeof(dbref) + sizeof(int);
+    memcpy(&time_at, w, sizeof(time_t));
+    if((time_at < time))
+      p += jump;
+    else
+      break;
+    skipped++;
+  }
+  if(p < bq->buffer_end)
+    memmove(bq->buffer, p , bq->buffer_end - p);
+  bq->buffer_end -= (p - bq->buffer);
+  bq->num_buffered -= skipped;
+  return skipped;
+}
+#endif /* RPMODE_SYS */
diff --git a/src/services.c b/src/services.c
new file mode 100644 (file)
index 0000000..9da01a3
--- /dev/null
@@ -0,0 +1,834 @@
+/* Win32 services routines */
+
+/* Author: Nick Gammon */
+
+#ifdef WIN32
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <windows.h>           /* for service and thread routines */
+
+#include <stdlib.h>
+#include <process.h>
+#include <direct.h>
+
+#include "conf.h"
+#include "mushdb.h"
+
+#include "match.h"
+#include "externs.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+#ifdef WIN32SERVICES
+
+#define THIS_SERVICE "PennMUSH"
+#define THIS_SERVICE_DISPLAY "PennMUSH for Win32"
+
+int WIN32_CDECL main(int argc, char **argv);
+void mainthread(int argc, char **argv);
+
+
+SERVICE_STATUS ssStatus;       /*  current status of the service */
+
+SERVICE_STATUS_HANDLE sshStatusHandle;
+DWORD dwGlobalErr;
+DWORD TID = 0;
+HANDLE threadHandle = NULL;
+
+SC_HANDLE service = NULL;
+SC_HANDLE SCmanager = NULL;
+
+/*   declare the service threads: */
+VOID service_main(DWORD dwArgc, LPTSTR * lpszArgv);
+VOID WINAPI service_ctrl(DWORD dwCtrlCode);
+BOOL ReportStatusToSCMgr(DWORD dwCurrentState,
+                        DWORD dwWin32ExitCode,
+                        DWORD dwCheckPoint, DWORD dwWaitHint);
+VOID worker_thread(VOID * notused);
+VOID StopService(LPTSTR lpszMsg);
+
+
+static int CmdInstallService(int argc, char *argv[]);
+static int CmdRemoveService(void);
+static int CmdStartService(void);
+static int CmdStopService(void);
+static int CmdStatusService(void);
+static void CmdDisplayFormat(void);
+static char *convert_error(DWORD error);
+static DWORD get_service_status(SERVICE_STATUS * svcstatus, int leave_open);
+int WIN32_CDECL service_error(DWORD error_code, char *themessage, ...);
+
+
+/* Need to include library: advapi32.lib for services routines */
+
+
+int WIN32_CDECL
+main(argc, argv)
+    int argc;
+    char **argv;
+{
+
+  SERVICE_TABLE_ENTRY dispatchTable[] = {
+    {THIS_SERVICE, (LPSERVICE_MAIN_FUNCTION) service_main},
+    {NULL, NULL}
+  };
+
+  SERVICE_STATUS svcstatus;
+  DWORD status;
+
+/*
+   Get the command line parameters and see what the user wants us to do.
+ */
+
+  if ((argc == 2) &&
+      ((*argv[1] == '-') || (*argv[1] == '/') || (*argv[1] == '\\'))) {
+    if (!_stricmp("install", argv[1] + 1))
+      CmdInstallService(argc, argv);
+    else if (!_stricmp("remove", argv[1] + 1))
+      CmdRemoveService();
+    else if (!_stricmp("start", argv[1] + 1))
+      CmdStartService();
+    else if (!_stricmp("stop", argv[1] + 1))
+      CmdStopService();
+    else if (!_stricmp("status", argv[1] + 1))
+      CmdStatusService();
+    else if (!_stricmp("run", argv[1] + 1)) {
+
+/*  do not start the MUSH if it is already a running service */
+
+      status = get_service_status(&svcstatus, TRUE);
+      if (status == 0 && svcstatus.dwCurrentState == SERVICE_RUNNING) {
+       fprintf(stderr, T("The MUSH is already running as a service.\n"));
+       return 1;
+      }
+      worker_thread(NULL);
+    } else
+      CmdDisplayFormat();
+  } else if (argc != 1)
+    CmdDisplayFormat();
+  else {
+
+    /*  do not start the MUSH if it is already a running service */
+
+    status = get_service_status(&svcstatus, TRUE);
+    if (status == 0 && svcstatus.dwCurrentState == SERVICE_RUNNING) {
+      fprintf(stderr, T("The MUSH is already running as a service.\n"));
+      return 1;
+    }
+    /*  Under Windows 95 they won't be able to use the service manager */
+
+    if (status == ERROR_CALL_NOT_IMPLEMENTED) {
+      worker_thread(NULL);
+      return 0;
+    }
+    /*
+       Register the dispatch table with the service controller.
+
+       If this fails then we are running interactively.
+
+     */
+
+    fprintf(stderr, T("Attempting to start PennMUSH as a service ...\n"));
+    if (!StartServiceCtrlDispatcher(dispatchTable)) {
+      fprintf(stderr,
+             T
+             ("Unable to start service, assuming running console-mode application.\n"));
+      fprintf(stderr,
+             T
+             ("You can save time on the next invocation by specifying: pennmush /run\n"));
+      worker_thread(NULL);
+    }
+  }                            /*  end of argc == 1 */
+
+  return 0;
+}                              /* end of main */
+
+
+/*   service_main() -- */
+/*       this function takes care of actually starting the service, */
+/*       informing the service controller at each step along the way. */
+/*       After launching the worker thread, it waits on the event */
+/*       that the worker thread will signal at its termination. */
+static VOID
+service_main(DWORD dwArgc, LPTSTR * lpszArgv)
+{
+  DWORD dwWait;
+
+  /*  register our service control handler: */
+  sshStatusHandle = RegisterServiceCtrlHandler(THIS_SERVICE, service_ctrl);
+
+  if (!sshStatusHandle)
+    goto cleanup;
+
+  /*  SERVICE_STATUS members that don't change in example */
+  ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+  ssStatus.dwServiceSpecificExitCode = 0;
+
+
+  /*  report the status to Service Control Manager. */
+  if (!ReportStatusToSCMgr(SERVICE_START_PENDING,      /*  service state */
+                          NO_ERROR,    /*  exit code */
+                          1,   /*  checkpoint */
+                          3000))       /*  wait hint */
+    goto cleanup;
+
+
+  /*  start the thread that performs the work of the service. */
+  threadHandle = (HANDLE) _beginthreadex(NULL, /*  security attributes */
+                                        0,     /*  stack size (0 means inherit parent's stack size) */
+                                        (LPTHREAD_START_ROUTINE) worker_thread, NULL,  /*  argument to thread */
+                                        0,     /*  thread creation flags */
+                                        &TID); /*  pointer to thread ID */
+
+  if (!threadHandle)
+    goto cleanup;
+
+  /*  report the status to the service control manager. */
+  if (!ReportStatusToSCMgr(SERVICE_RUNNING,    /*  service state */
+                          NO_ERROR,    /*  exit code */
+                          0,   /*  checkpoint */
+                          0))  /*  wait hint */
+    goto cleanup;
+
+  /*  wait indefinitely until threadHandle is signaled. */
+  /*  The thread handle is signalled when the thread terminates */
+
+  dwWait = WaitForSingleObject(threadHandle,   /*  event object */
+                              INFINITE);       /*  wait indefinitely */
+
+cleanup:
+
+  /*  try to report the stopped status to the service control manager. */
+  if (sshStatusHandle)
+    (VOID) ReportStatusToSCMgr(SERVICE_STOPPED, dwGlobalErr, 0, 0);
+
+  /*  When SERVICE MAIN FUNCTION returns in a single service */
+  /*  process, the StartServiceCtrlDispatcher function in */
+  /*  the main thread returns, terminating the process. */
+  return;
+}                              /*  end of service_main */
+
+
+
+/*   service_ctrl() -- */
+/*       this function is called by the Service Controller whenever */
+/*       someone calls ControlService in reference to our service. */
+static VOID WINAPI
+service_ctrl(DWORD dwCtrlCode)
+{
+  DWORD dwState = SERVICE_RUNNING;
+
+  /*  Handle the requested control code. */
+  switch (dwCtrlCode) {
+
+    /*  Pause the service if it is running. */
+  case SERVICE_CONTROL_PAUSE:
+
+    if (ssStatus.dwCurrentState == SERVICE_RUNNING) {
+      SuspendThread(threadHandle);
+      dwState = SERVICE_PAUSED;
+    }
+    break;
+
+    /*  Resume the paused service. */
+  case SERVICE_CONTROL_CONTINUE:
+
+    if (ssStatus.dwCurrentState == SERVICE_PAUSED) {
+      ResumeThread(threadHandle);
+      dwState = SERVICE_RUNNING;
+    }
+    break;
+
+    /*  Stop the service. */
+  case SERVICE_CONTROL_STOP:
+
+    dwState = SERVICE_STOP_PENDING;
+
+    /*  Report the status, specifying the checkpoint and waithint, */
+    /*   before setting the termination event. */
+    ReportStatusToSCMgr(SERVICE_STOP_PENDING,  /*  current state */
+                       NO_ERROR,       /*  exit code */
+                       1,      /*  checkpoint */
+                       10000); /*  waithint (10 secs) */
+
+    shutdown_flag = 1;
+
+    flag_broadcast(0, 0, T("GAME: Game shutdown by system operator"));
+
+    return;
+
+    /*  Update the service status. */
+  case SERVICE_CONTROL_INTERROGATE:
+    break;
+
+    /*  invalid control code */
+  default:
+    break;
+
+  }                            /*  end of switch */
+
+  /*  send a status response. */
+  ReportStatusToSCMgr(dwState, NO_ERROR, 0, 0);
+}                              /*  end of service_ctrl */
+
+
+/*  utility functions... */
+
+
+
+/*  ReportStatusToSCMgr() -- */
+/*       This function is called by the ServMainFunc() and */
+/*       ServCtrlHandler() functions to update the service's status */
+/*       to the service control manager. */
+static BOOL
+ReportStatusToSCMgr(DWORD dwCurrentState,
+                   DWORD dwWin32ExitCode, DWORD dwCheckPoint, DWORD dwWaitHint)
+{
+  BOOL fResult;
+
+  /*  Disable control requests until the service is started. */
+  if (dwCurrentState == SERVICE_START_PENDING)
+    ssStatus.dwControlsAccepted = 0;
+  else
+    ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP |
+      SERVICE_ACCEPT_PAUSE_CONTINUE;
+
+  /*  These SERVICE_STATUS members are set from parameters. */
+  ssStatus.dwCurrentState = dwCurrentState;
+  ssStatus.dwWin32ExitCode = dwWin32ExitCode;
+  ssStatus.dwCheckPoint = dwCheckPoint;
+
+  ssStatus.dwWaitHint = dwWaitHint;
+
+  /*  Report the status of the service to the service control manager. */
+  if (!(fResult = SetServiceStatus(sshStatusHandle,    /*  service reference handle */
+                                  &ssStatus))) {       /*  SERVICE_STATUS structure */
+
+    /*  If an error occurs, stop the service. */
+    StopService("SetServiceStatus");
+  }
+  return fResult;
+}                              /*  end of ReportStatusToSCMgr */
+
+
+
+/*  The StopService function can be used by any thread to report an */
+/*   error, or stop the service. */
+static VOID
+StopService(LPTSTR lpszMsg)
+{
+  CHAR chMsg[256];
+  HANDLE hEventSource;
+  LPTSTR lpszStrings[2];
+
+  dwGlobalErr = GetLastError();
+
+  /*  Use event logging to log the error. */
+  hEventSource = RegisterEventSource(NULL, THIS_SERVICE);
+
+  sprintf(chMsg, "%s error: %s", THIS_SERVICE, convert_error(dwGlobalErr));
+  lpszStrings[0] = chMsg;
+  lpszStrings[1] = lpszMsg;
+
+  if (hEventSource) {
+    ReportEvent(hEventSource,  /*  handle of event source */
+               EVENTLOG_ERROR_TYPE,    /*  event type */
+               0,              /*  event category */
+               0,              /*  event ID */
+               NULL,           /*  current user's SID */
+               2,              /*  strings in lpszStrings */
+               0,              /*  no bytes of raw data */
+               lpszStrings,    /*  array of error strings */
+               NULL);          /*  no raw data */
+
+    (VOID) DeregisterEventSource(hEventSource);
+  }
+  if (threadHandle)
+    TerminateThread(threadHandle, 1);
+}                              /*  end of StopService */
+
+
+/*  called at shutdown, ctrl-c etc. */
+
+BOOL WINAPI
+shut_down_handler(DWORD dwCtrlType)
+{
+
+
+  if (dwCtrlType != CTRL_LOGOFF_EVENT) {
+
+    if (threadHandle)
+      TerminateThread(threadHandle, 1);
+    threadHandle = NULL;
+
+    mush_panic(T("System shutdown by system operator"));
+
+    _exit(99);
+
+  }
+  return FALSE;
+}                              /*  end of  shut_down_handler */
+
+
+/*
+   This is the service "worker" thread (as opposed to the main thread which
+   has called StartServiceCtrlDispatcher and is waiting on the worker thread
+   to end, by waiting on the thread handle).
+
+   The other threads are the timer thread (set up in timer.C) and the service
+   control thread (service_ctrl), which is called by the service controller.
+
+   If not running as a service, then this is not a separate thread, but is
+   called directly from "main". (In this case, threadHandle will be zero).
+
+   All this routine does is change directory to the same directory as the
+   executable file, set up MUSH.CFG as the configuration file, and then
+   call the "real" MUSH "main" routine in BSD.C.
+
+   All this is designed to hide the service control stuff from the main MUSH
+   code so as to make implementing the next version much easier.
+
+ */
+
+static VOID
+worker_thread(VOID * notused)
+{
+  int argc = 3;
+  char fullfilename[MAX_PATH];
+  char directory[MAX_PATH];
+  char configname[] = "mush.cnf";
+  char errorlogname[] = "log\\game.log";
+  char *argv[3] = { fullfilename, configname, errorlogname };
+  char *p;
+
+  if (!GetModuleFileName(NULL, fullfilename, sizeof(fullfilename))) {
+    service_error(GetLastError(), T("Cannot locate full filename"));
+    Win32_Exit(1);
+  }
+/*  remove last part of file name to get working directory */
+
+  strcpy(directory, fullfilename);
+
+  p = strrchr(directory, '\\');
+  if (p)
+    *p = 0;
+
+/*  make sure we are running in the MUSH directory */
+
+  _chdir(directory);
+
+/*  if running as a service, redirect stderr to a log file. */
+
+  if (threadHandle)
+    freopen("log\\game.log", "w", stderr);
+
+/*  handle shutdowns and ctrl-c */
+
+  SetConsoleCtrlHandler(shut_down_handler, TRUE);
+
+/*  start up the main MUSH code */
+
+  mainthread(argc, argv);
+
+}                              /*  end of worker_thread */
+
+void WIN32_CDECL
+Win32_Exit(int exit_code)
+{
+
+/*  if running as a thread, end the thread, otherwise just exit */
+
+  fflush(stderr);
+  if (threadHandle)
+    _endthread();
+  else
+    _exit(exit_code);
+
+}                              /*  end of Win32_Exit */
+
+/*  this is called from db_write (every 256 objects) */
+/*  to keep the service manager happy (it needs a checkpoint every 3 seconds) */
+
+void
+shutdown_checkpoint(void)
+{
+  static DWORD checkpoint = 1;
+
+  if (threadHandle && shutdown_flag)
+    ReportStatusToSCMgr(SERVICE_STOP_PENDING,  /*  current state */
+                       NO_ERROR,       /*  exit code */
+                       ++checkpoint,   /*  checkpoint */
+                       3000);  /*  waithint  (3 seconds) */
+
+}                              /*  end of shutdown_checkpoint */
+
+
+/*  We need to close these handles so often I'll do it in a separate routine */
+
+static void
+close_service_handles(void)
+{
+  if (service)
+    CloseServiceHandle(service);
+  service = NULL;
+  if (SCmanager)
+    CloseServiceHandle(SCmanager);
+  SCmanager = NULL;
+}                              /*  end of close_service_handles */
+
+
+/*  We put out *so* many error messages, let's centralise the whole thing */
+
+int WIN32_CDECL
+service_error(DWORD error_code, char *themessage, ...)
+{
+  va_list arglist;
+
+  char buff[200];
+
+/* print the message as if it was a PRINTF type message */
+
+  va_start(arglist, themessage);
+  _vsnprintf(buff, sizeof(buff), themessage, arglist);
+  va_end(arglist);
+
+  fprintf(stderr, "%s\n", buff);
+
+  if (error_code)
+    fprintf(stderr, "  ** Error %ld\n  ** %s\n",
+           error_code, convert_error(error_code));
+
+  close_service_handles();
+
+  return TRUE;
+}                              /*  end of service_error */
+
+/*
+   Open a handle to the Service Control Manager.
+ */
+
+static int
+open_service_manager(void)
+{
+
+  SCmanager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+
+  if (!SCmanager)
+    return service_error(GetLastError(),
+                        T("Unable to talk to the Service Control Manager"));
+
+  return FALSE;
+
+}                              /*  end of openServiceManager */
+
+
+/*
+   Open a handle to the Service.
+ */
+static int
+get_service(void)
+{
+  service = OpenService(SCmanager, THIS_SERVICE, SERVICE_ALL_ACCESS);
+
+  if (!service)
+    return service_error(GetLastError(), T("Cannot access service definition"));
+
+  return FALSE;
+}                              /*  end of get_service */
+
+/*
+   Opens the service manager and gets the status, optionally leaving
+   the manager open.
+ */
+
+static DWORD
+get_service_status(SERVICE_STATUS * svcstatus, int leave_open)
+{
+
+/*
+   Open a handle to the Service Control Manager.
+ */
+
+  SCmanager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
+
+  if (!SCmanager) {
+    close_service_handles();
+    return GetLastError();
+  }
+/*
+   Open a handle to the Service.
+ */
+
+  service = OpenService(SCmanager, THIS_SERVICE, SERVICE_ALL_ACCESS);
+
+  if (!service) {
+    close_service_handles();
+    return GetLastError();
+  }
+/*
+   Check to see that the service is not running.
+ */
+
+  if (!QueryServiceStatus(service, svcstatus)) {
+    close_service_handles();
+    return GetLastError();
+  }
+/*  leave handles open if requested */
+
+  if (!leave_open)
+    close_service_handles();
+
+  return 0;
+
+}                              /*  end of get_service_status */
+
+/*
+   Install this service.
+ */
+
+static int
+CmdInstallService(int argc, char *argv[])
+{
+  char fullfilename[MAX_PATH];
+
+/*
+   Pick up our full path and file name.
+ */
+
+  if (!GetModuleFileName(NULL, fullfilename, sizeof(fullfilename)))
+    return service_error(GetLastError(), T("Cannot locate full filename"));
+
+/*
+   Open a handle to the Service Control Manager.
+ */
+
+  if (open_service_manager())
+    return TRUE;
+
+/*
+   Now create the service definition.
+ */
+
+  service = CreateService(SCmanager, THIS_SERVICE, THIS_SERVICE_DISPLAY, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, fullfilename, NULL,  /*  no load ordering group */
+                         NULL, /*  no tag identifier */
+                         NULL, /*  no dependencies */
+                         NULL, /*  LocalSystem account */
+                         NULL);        /*  no password */
+
+  if (!service)
+    return service_error(GetLastError(), T("Unable to create service"));
+
+  close_service_handles();
+
+  fprintf(stderr, T("Service successfully installed\n"));
+
+  return FALSE;
+}                              /*  end of CmdInstallService */
+
+/*
+   Remove this service.
+ */
+
+static int
+CmdRemoveService(void)
+{
+  SERVICE_STATUS svcstatus;
+  DWORD status;
+
+/*
+   Open the service manager and find its status
+ */
+
+  if (status = get_service_status(&svcstatus, TRUE))
+    return service_error(status, T("Unable to access service details"));
+
+/*
+   Check to see that the service is not running.
+ */
+
+  if (svcstatus.dwCurrentState != SERVICE_STOPPED)
+    return service_error(0,
+                        T
+                        ("You must stop the service before you can remove it."));
+
+/*
+   Everything is fine, so delete the service definition.
+ */
+
+  if (!DeleteService(service))
+    return service_error(GetLastError(), T("Cannot remove service"));
+
+  close_service_handles();
+
+  fprintf(stderr, T("Service successfully removed\n"));
+
+  return FALSE;
+}                              /*  end of CmdRemoveService */
+
+
+/*
+   Start this service.
+ */
+
+static int
+CmdStartService(void)
+{
+  SERVICE_STATUS svcstatus;
+  DWORD status;
+
+/*
+   Open the service manager and find its status
+ */
+
+  if (status = get_service_status(&svcstatus, TRUE))
+    return service_error(status, T("Unable to access service details"));
+
+  if (svcstatus.dwCurrentState != SERVICE_STOPPED)
+    return service_error(0, T("The service is not currently stopped."));
+
+/*
+   Everything is fine, so start the service
+ */
+
+  if (!StartService(service, 0, NULL))
+    return service_error(GetLastError(), T("Cannot start service"));
+
+  close_service_handles();
+
+  fprintf(stderr, T("Start request sent to service\n"));
+
+  return FALSE;
+}                              /*  end of CmdStartService */
+
+/*
+   Stop this service.
+ */
+
+static int
+CmdStopService(void)
+{
+  SERVICE_STATUS svcstatus;
+  DWORD status;
+
+/*
+   Open the service manager and find its status
+ */
+
+  if (status = get_service_status(&svcstatus, TRUE))
+    return service_error(status, T("Unable to access service details"));
+
+  if (svcstatus.dwCurrentState != SERVICE_RUNNING)
+    return service_error(0, T("The service is not currently running."));
+
+/*
+   Everything is fine, so stop the service
+ */
+
+  if (!ControlService(service, SERVICE_CONTROL_STOP, &svcstatus))
+    return service_error(GetLastError(), T("Cannot stop service"));
+
+  close_service_handles();
+
+  fprintf(stderr, T("Stop request sent to service\n"));
+
+  return FALSE;
+}                              /*  end of CmdStopService */
+
+
+/*
+   Show status of this service.
+ */
+
+static int
+CmdStatusService(void)
+{
+  SERVICE_STATUS svcstatus;
+  DWORD status;
+  char *p;
+
+/*
+   Open the service manager and find its status
+ */
+
+  if (status = get_service_status(&svcstatus, FALSE))
+    return service_error(status, T("Unable to access service details"));
+
+  switch (svcstatus.dwCurrentState) {
+  case SERVICE_STOPPED:
+    p = T("The service is not running.");
+    break;
+  case SERVICE_START_PENDING:
+    p = T("The service is starting.");
+    break;
+  case SERVICE_STOP_PENDING:
+    p = T("The service is stopping.");
+    break;
+  case SERVICE_RUNNING:
+    p = T("The service is running.");
+    break;
+  case SERVICE_CONTINUE_PENDING:
+    p = T("The service continue is pending.");
+    break;
+  case SERVICE_PAUSE_PENDING:
+    p = T("The service pause is pending.");
+    break;
+  case SERVICE_PAUSED:
+    p = T("The service is paused.");
+    break;
+  default:
+    p = T("Unrecognised status.");
+    break;
+  }                            /*  end of switch */
+
+  fprintf(stderr, "%s\n", p);
+
+  return FALSE;
+}                              /*  end of CmdStatusService */
+
+
+
+/*
+   Display the available commands.
+ */
+
+static void
+CmdDisplayFormat(void)
+{
+  fprintf(stderr, T("Usage is :-\n"));
+  fprintf(stderr, T(" %s           - runs as a service, or stand-alone\n"),
+         THIS_SERVICE);
+  fprintf(stderr, T(" %s /run      - runs stand-alone\n"), THIS_SERVICE);
+  fprintf(stderr, T(" %s /start    - starts this service\n"), THIS_SERVICE);
+  fprintf(stderr, T(" %s /stop     - stops this service\n"), THIS_SERVICE);
+  fprintf(stderr, T(" %s /install  - installs this service\n"), THIS_SERVICE);
+  fprintf(stderr, T(" %s /remove   - removes (un-installs) this service\n"),
+         THIS_SERVICE);
+  fprintf(stderr, T(" %s /status   - displays the status of this service\n"),
+         THIS_SERVICE);
+  fprintf(stderr, T(" %s /help     - displays this information\n"),
+         THIS_SERVICE);
+}                              /*  end of CmdDisplayFormat */
+
+static char *
+convert_error(DWORD error)
+{
+
+  char *formattedmsg;
+  static char buff[100];
+
+  if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
+                    FORMAT_MESSAGE_ALLOCATE_BUFFER |
+                    FORMAT_MESSAGE_IGNORE_INSERTS,
+                    NULL,
+                    error, LANG_NEUTRAL, (LPTSTR) & formattedmsg, 0, NULL)) {
+    sprintf(buff, "<Error code: %ld>", error);
+    return buff;
+  } else
+    return formattedmsg;
+}                              /*  end of convert_error */
+
+
+#endif                         /* WIN32SERVICES */
+#endif                         /* WIN32 */
diff --git a/src/set.c b/src/set.c
new file mode 100644 (file)
index 0000000..34228ac
--- /dev/null
+++ b/src/set.c
@@ -0,0 +1,1130 @@
+/**
+ * \file set.c
+ *
+ * \brief PennMUSH commands that set parameters.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#include <stdlib.h>
+
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "game.h"
+#include "match.h"
+#include "attrib.h"
+#include "ansi.h"
+#include "command.h"
+#include "mymalloc.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "lock.h"
+#include "log.h"
+#include "confmagic.h"
+
+static int chown_ok(dbref player, dbref thing, dbref newowner);
+void do_attrib_flags
+  (dbref player, const char *obj, const char *atrname, const char *flag);
+static int af_helper(dbref player, dbref thing, dbref parent, 
+                       char const *pattern, ATTR *atr, void *args);
+static int gedit_helper(dbref player, dbref thing, dbref parent, char const *pattern,
+                       ATTR *atr, void *args);
+static int wipe_helper(dbref player, dbref thing, dbref parent, char const *pattern,
+                      ATTR *atr, void *args);
+static void copy_attrib_flags(dbref player, dbref target, ATTR *atr, int flags);
+
+
+/** Rename something.
+ * \verbatim
+ * This implements @name.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name current name of object to rename.
+ * \param newname new name for object.
+ */
+void
+do_name(dbref player, const char *name, char *newname)
+{
+  dbref thing;
+  char *password;
+  char *myenv[10];
+  int i;
+
+  newname = trim_space_sep(newname, ' ');
+
+  if ((thing = match_controlled(player, name)) != NOTHING) {
+    /* check for bad name */
+    if ((*newname == '\0') || strchr(newname, '[')) {
+      notify(player, T("Give it what new name?"));
+      return;
+    }
+    /* check for renaming a player */
+    if (IsPlayer(thing)) {
+      if (PLAYER_NAME_SPACES) {
+       if (*newname == '\"') {
+         for (; *newname && ((*newname == '\"')
+                             || isspace((unsigned char) *newname));
+              newname++) ;
+         password = newname;
+         while (*password && (*password != '\"')) {
+           while (*password && (*password != '\"'))
+             password++;
+           if (*password == '\"') {
+             *password++ = '\0';
+             while (*password && isspace((unsigned char) *password))
+               password++;
+             break;
+           }
+         }
+       } else {
+         password = newname;
+         while (*password && !isspace((unsigned char) *password))
+           password++;
+         if (*password) {
+           *password++ = '\0';
+           while (*password && isspace((unsigned char) *password))
+             password++;
+         }
+       }
+      } else {
+
+       /* split off password */
+       for (password = newname + strlen(newname) - 1;
+            *password && !isspace((unsigned char) *password); password--) ;
+       for (; *password && isspace((unsigned char) *password); password--) ;
+       /* eat whitespace */
+       if (*password) {
+         *++password = '\0';   /* terminate name */
+         password++;
+         while (*password && isspace((unsigned char) *password))
+           password++;
+       }
+      }
+      if (strcasecmp(newname, Name(thing))
+         && !ok_player_name(newname, thing)) {
+       /* strcasecmp allows changing foo to Foo, etc. */
+       notify(player, T("You can't give a player that name."));
+       return;
+      }
+      /* everything ok, notify */
+      do_log(LT_CONN, 0, 0, T("Name change by %s(#%d) to %s"),
+            Name(thing), thing, newname);
+      /* everything ok, we can fall through to change the name */
+    } else {
+      if (!ok_name(newname)) {
+       notify(player, T("That is not a reasonable name."));
+       return;
+      }
+    }
+
+    /* everything ok, change the name */
+    myenv[0] = (char *) mush_malloc(BUFFER_LEN, "string");
+    myenv[1] = (char *) mush_malloc(BUFFER_LEN, "string");
+    strncpy(myenv[0], Name(thing), BUFFER_LEN - 1);
+    myenv[0][BUFFER_LEN - 1] = '\0';
+    strcpy(myenv[1], newname);
+    for (i = 2; i < 10; i++)
+      myenv[i] = NULL;
+
+    if (IsPlayer(thing))
+      delete_player(thing, NULL);
+    set_name(thing, newname);
+    if(!IsPlayer(thing)) {
+          char lmbuf[1024];
+          ModTime(thing) = mudtime;
+          snprintf(lmbuf, 1023, "@name[#%d]", player);
+          lmbuf[strlen(lmbuf)+1] = '\0';
+          set_lmod(thing, lmbuf);
+    }
+    if (IsPlayer(thing))
+      add_player(thing, NULL);
+
+    if (!AreQuiet(player, thing))
+      notify(player, T("Name set."));
+    real_did_it(player, thing, NULL, NULL, "ONAME", NULL, "ANAME", NOTHING,
+               myenv, NA_INTER_PRESENCE);
+    mush_free(myenv[0], "string");
+    mush_free(myenv[1], "string");
+  }
+}
+
+/** Change an object's owner.
+ * \verbatim
+ * This implements @chown.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of object to change owner of.
+ * \param newobj name of new owner for object.
+ * \param preserve if 1, preserve privileges and don't halt the object.
+ */
+void
+do_chown(dbref player, const char *name, const char *newobj, int preserve)
+{
+  dbref thing;
+  dbref newowner = NOTHING;
+  char *sp;
+  long match_flags = MAT_POSSESSION | MAT_HERE | MAT_EXIT | MAT_ABSOLUTE;
+
+
+  /* check for '@chown <object>/<atr>=<player>'  */
+  sp = strchr(name, '/');
+  if (sp) {
+    do_atrchown(player, name, newobj);
+    return;
+  }
+  if (OOREF(player,div_powover(player, player, "Chown"),div_powover(ooref, ooref, "Chown")))
+    match_flags |= MAT_PLAYER;
+
+  if ((thing = noisy_match_result(player, name, TYPE_THING, match_flags))
+      == NOTHING)
+    return;
+
+  if (!*newobj || !strcasecmp(newobj, "me")) {
+    newowner = player;
+  } else {
+    if ((newowner = lookup_player(newobj)) == NOTHING) {
+      notify(player, T("I couldn't find that player."));
+      return;
+    }
+  }
+
+  if (IsPlayer(thing) && !God(player)) {
+    notify(player, T("Players always own themselves."));
+    return;
+  }
+  /* Permissions checking */
+  if (!chown_ok(player, thing, newowner)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (IsThing(thing) && !Admin(player) &&
+      !(GoodObject(Location(thing)) && (Location(thing) == player))) {
+    notify(player, T("You must carry the object to @chown it."));
+    return;
+  }
+  if (preserve && !OOREF(player,div_powover(player, player, "Chown"),div_powover(ooref, ooref, "Chown"))) {
+    notify(player, T("You cannot @CHOWN/PRESERVE. Use normal @CHOWN."));
+    return;
+  }
+  /* chowns to the zone master don't count towards fees */
+  if (!ZMaster(newowner)) {
+    /* Debit the owner-to-be */
+    if (!can_pay_fees(newowner, Pennies(thing))) {
+      /* not enough money or quota */
+      if (newowner != player)
+       notify(player,
+              T
+              ("That player doesn't have enough money or quota to receive that object."));
+      return;
+    }
+    /* Credit the current owner */
+    giveto(Owner(thing), Pennies(thing));
+    change_quota(Owner(thing), QUOTA_COST);
+  }
+  chown_object(player, thing, newowner, preserve);
+  notify(player, T("Owner changed."));
+}
+
+static int
+chown_ok(dbref player, dbref thing, dbref newowner)
+{
+  /* Cant' touch garbage */
+  if (IsGarbage(thing))
+    return 0;
+
+  /* God can do it all */
+  if (God(player))
+    return 1;
+
+  /* In order for non-admin player to @chown thing to newowner,
+   * player must control newowner or newowner must be a Zone Master
+   * and player must pass its zone lock.
+   *
+   * In addition, one of the following must apply:
+   *   1.  player owns thing, or
+   *   2.  player controls Owner(thing), newowner is a zone master,
+   *       and Owner(thing) passes newowner's zone-lock, or
+   *   3.  thing is CHOWN_OK, and player holds thing if it's an object.
+   *
+   * The third condition is syntactic sugar to handle the situation
+   * where Joe owns Box, an ordinary object, and Tool, an inherit object, 
+   * and ZMP, a Zone Master Player, is zone-locked to =tool.
+   * In this case, if Joe doesn't pass ZMP's lock, we don't want
+   *   Joe to be able to @fo Tool=@chown Box=ZMP
+   */
+
+  /* Does player a) control object, b) have chown power */
+  if(OOREF(player,controls(player,thing),controls(ooref,thing)) && 
+      OOREF(player,div_powover(player, player, "Chown"),div_powover(ooref, ooref, "Chown")))
+    return 1;
+  /* Does player control newowner, or is newowner a Zone Master and player
+   * passes the lock?
+   */
+  if (!(OOREF(player,controls(player, newowner),controls(ooref, newowner)) ||
+       (ZMaster(newowner) && eval_lock(player, newowner, Zone_Lock))))
+    return 0;
+
+  /* Target player is legitimate. Does player control the object? */
+  if (Owns(player, thing))
+    return 1;
+
+  if (controls(player, Owner(thing)) &&
+      ZMaster(newowner) && eval_lock(Owner(thing), newowner, Zone_Lock))
+    return 1;
+
+  if (ChownOk(thing) && (!IsThing(thing) || (Location(thing) == player) || CanRemote(player, thing)))
+    return 1;
+
+  return 0;
+}
+
+
+/** Actually change the ownership of something, and fix bits.
+ * \param player the enactor.
+ * \param thing object to change ownership of.
+ * \param newowner new owner for thing.
+ * \param preserve if 1, preserve privileges and don't halt.
+ */
+void
+chown_object(dbref player, dbref thing, dbref newowner, int preserve)
+{
+  (void) undestroy(player, thing);
+  if (God(player)) {
+    Owner(thing) = newowner;
+  } else {
+    Owner(thing) = Owner(newowner);
+  }
+  if(!preserve) Zone(thing) = Zone(newowner);
+  clear_flag_internal(thing, "CHOWN_OK");
+  if (!preserve || !Director(player)) {
+    set_flag_internal(thing, "HALT");
+    if(DPBITS(thing)) 
+      mush_free(DPBITS(thing), "POWER_SPOT");  /* wipe out all powers */
+    DPBITS(thing) = NULL;
+    do_halt(thing, "", thing);
+  } else {
+    adjust_powers(thing, newowner);
+    if (DPBITS(thing))
+      notify(player,
+            T
+            ("Warning: @CHOWN/PRESERVE on a target with @power privileges."));
+  }
+}
+
+
+/** Change an object's zone.
+ * \verbatim
+ * This implements @chzone.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the object to change zone of.
+ * \param newobj name of new ZMO.
+ * \param noisy if 1, notify player about success and failure.
+ * \retval 0 failed to change zone.
+ * \retval 1 successfully changed zone.
+ */
+int
+do_chzone(dbref player, char const *name, char const *newobj, int noisy)
+{
+  dbref thing;
+  dbref zone;
+
+  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING)
+    return 0;
+
+  if (!newobj || !*newobj || !strcasecmp(newobj, "none"))
+    zone = NOTHING;
+  else {
+    if ((zone = noisy_match_result(player, newobj, NOTYPE, MAT_EVERYTHING))
+       == NOTHING)
+      return 0;
+  }
+
+  if (Zone(thing) == zone) {
+    if (noisy)
+      notify(player, T("That object is already in that zone."));
+    return 0;
+  }
+
+  /* we do use ownership instead of control as a criterion because
+   * we only want the owner to be able to rezone the object. Also,
+   * this allows players to @chzone themselves to an object they own.
+   */
+  if (!(God(player) || (Director(player) && controls(player, thing))
+       || Owns(player, thing))) {
+    if (noisy)
+      notify(player, T("You don't have the power to shift reality."));
+    return 0;
+  }
+  /* a player may change an object's zone to:
+   * 1.  NOTHING 
+   * 2.  an object he owns
+   * 3.  an object with a chzone-lock that the player passes.
+   * Note that an object with no chzone-lock isn't valid
+   */
+  if (!((Director(player) && controls(player, zone))
+       || (zone == NOTHING) || Owns(player, zone) ||
+       ((getlock(zone, Chzone_Lock) != TRUE_BOOLEXP) &&
+        eval_lock(player, zone, Chzone_Lock)))) {
+    if (noisy)
+      notify(player, T("You cannot move that object to that zone."));
+    return 0;
+  }
+  /* Don't chzone object to itself! */
+  if (zone == thing) {
+    if (noisy)
+      notify(player, T("You shouldn't zone objects to themselves!"));
+    return 0;
+  }
+  /* Don't allow circular zones */
+  if (GoodObject(zone)) {
+    dbref tmp;
+    int zone_depth = MAX_ZONES;
+
+    for (tmp = Zone(zone); GoodObject(tmp); tmp = Zone(tmp)) {
+      if (tmp == thing) {
+       notify(player, T("You can't make circular zones!"));
+       return 0;
+      }
+      if (tmp == Zone(tmp))    /* Ran into an object zoned to itself */
+       break;
+      zone_depth--;
+      if(!zone_depth) {
+             notify(player, T("Overly deep zone chain"));
+             return 0;
+      }
+    }
+  }
+
+  /* Don't allow use of a division object as a ZMO */
+  if (IsDivision(zone)) {
+    notify(player, T("You can't use a division object as a ZMO."));
+    return 0;
+  }
+
+  /* Don't allow chzone to objects without elocks! 
+   * If no lock is set, set a default lock (warn if zmo are used for control)
+   * This checks for many trivial elocks (canuse/1, where &canuse=1)
+   */
+  if (zone != NOTHING)
+         check_zone_lock(player, zone, noisy);
+  /* Warn admins when they zone their stuff */
+  if ((zone != NOTHING) && Admin(Owner(thing))) {
+    if (noisy)
+      notify(player, T("Warning: @chzoning admin-owned object!"));
+  }
+  /* everything is okay, do the change */
+  Zone(thing) = zone;
+  /* If we're not unzoning, and we're working with a non-player object,
+   * we'll remove inherit and powers, for security.
+   */
+  if ((zone != NOTHING) && !IsPlayer(thing)) {
+    /* if the object is a player, resetting these flags is rather
+     * inconvenient -- although this may pose a bit of a security
+     * risk. Be careful when @chzone'ing admin players.
+     */
+    if(DPBITS(thing))
+      mush_free(DPBITS(thing), "POWER_SPOT"); /* Wipe Powers */
+  } else {
+    if (noisy && (zone != NOTHING)) {
+      if (Admin(thing) && noisy)
+       notify(player, T("Warning: @chzoning an administrator."));
+    }
+  }
+  if (noisy)
+    notify(player, T("Zone changed."));
+  return 1;
+}
+
+/** Structure for af_helper() data. */
+struct af_args {
+  int f;               /**< flag bits */
+  int clear;           /**< True to remove the flag */
+  char *flag;          /**< flag name */
+};
+
+static int
+af_helper(dbref player, dbref thing, dbref parent __attribute__ ((__unused__)),
+         char const *pattern
+         __attribute__ ((__unused__)), ATTR *atr, void *args)
+{
+  struct af_args *af = args;
+
+  /* We must be able to write to that attribute normally,
+   * to prevent players from doing funky things to, say, LAST.
+   * There is one special case - the resetting of the SAFE flag.
+   */
+  if (!(Can_Write_Attr(player, thing, AL_ATTR(atr)) ||
+       (af->clear && (af->f & AF_SAFE) &&
+        Can_Write_Attr_Ignore_Safe(player, thing, AL_ATTR(atr))))) {
+    notify_format(player, T("You cannot change that flag on %s/%s"),
+                 Name(thing), AL_NAME(atr));
+    return 0;
+  }
+
+  if (af->clear) {
+    AL_FLAGS(atr) &= ~af->f;
+    if (!AreQuiet(player, thing))
+      notify_format(player, T("%s/%s - %s reset."), Name(thing), AL_NAME(atr),
+                   af->flag);
+  } else {
+    AL_FLAGS(atr) |= af->f;
+    if (!AreQuiet(player, thing))
+      notify_format(player, T("%s/%s - %s set."), Name(thing), AL_NAME(atr),
+                   af->flag);
+  }
+
+  return 1;
+}
+
+static void
+copy_attrib_flags(dbref player, dbref target, ATTR *atr, int flags)
+{
+  if (!atr)
+    return;
+  if (!Can_Write_Attr(player, target, AL_ATTR(atr))) {
+    notify_format(player,
+                 T("You cannot set attrib flags on %s/%s"), Name(target),
+                 AL_NAME(atr));
+    return;
+  }
+  AL_FLAGS(atr) = flags;
+}
+
+/** Set a flag on an attribute.
+ * \param player the enactor.
+ * \param obj the name of the object with the attribute.
+ * \param atrname the name of the attribute.
+ * \param flag the name of the flag to set or clear.
+ */
+void
+do_attrib_flags(dbref player, const char *obj, const char *atrname,
+               const char *flag)
+{
+  struct af_args af;
+  dbref thing;
+  const char *p;
+
+  if ((thing = match_controlled(player, obj)) == NOTHING)
+    return;
+
+  if (!flag || !*flag) {
+    notify(player, T("What flag do you want to set?"));
+    return;
+  }
+
+  af.clear = 0;
+
+  /* move past NOT token if there is one */
+  for (p = flag; *p && ((*p == NOT_TOKEN) || isspace((unsigned char) *p)); p++)
+    if (*p == NOT_TOKEN)
+      af.clear = !af.clear;
+
+  if ((af.f = string_to_atrflag(player, p)) < 0) {
+    notify(player, T("Unrecognized attribute flag."));
+    return;
+  }
+  af.flag = mush_strdup(atrflag_to_string(af.f), "af_flag list");
+  if (!atr_iter_get(player, thing, atrname, 0, af_helper, &af))
+    notify(player, T("No attribute found to change."));
+  mush_free((Malloc_t) af.flag, "af_flag list");
+}
+
+
+/** Set a flag, attribute flag, or attribute.
+ * \verbatim
+ * This implements @set.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the first (left) argument to the command.
+ * \param flag the second (right) argument to the command.
+ * \retval 1 successful set.
+ * \retval 0 failure to set.
+ */
+int
+do_set(dbref player, const char *name, char *flag)
+{
+  dbref thing;
+  int her, listener, negate;
+  char *p, *f;
+  char flagbuff[BUFFER_LEN];
+
+  /* check for attribute flag set first */
+  if ((p = strchr(name, '/')) != NULL) {
+    *p++ = '\0';
+    do_attrib_flags(player, name, p, flag);
+    return 1;
+  }
+  /* find thing */
+  if(!GoodObject((thing = match_thing(player, name))))
+      return 0;
+  if (God(thing) && !God(player)) {
+    notify(player, T("Only God can set himself!"));
+    return 0;
+  }
+  /* check for attribute set first */
+  if ((p = strchr(flag, ':')) != NULL) {
+    *p++ = '\0';
+    if (!command_check_byname(player, "ATTRIB_SET")) {
+      notify(player, T("You may not set attributes."));
+      return 0;
+    }
+    return do_set_atr(thing, flag, p, player, 1);
+  }
+  /* we haven't set an attribute, so we must be setting flags */
+  strcpy(flagbuff, flag);
+  p = trim_space_sep(flagbuff, ' ');
+  if (*p == '\0') {
+    notify(player, T("You must specify a flag to set."));
+    return 0;
+  }
+  do {
+    her = Hearer(thing);         /* Must be in loop, can change! */
+    listener = Listener(thing);  /* Must be in loop, can change! */
+    f = split_token(&p, ' ');
+    negate = 0;
+    if (*f == NOT_TOKEN && *(f + 1)) {
+      negate = 1;
+      f++;
+    }
+    set_flag(player, thing, f, negate, her, listener);
+  } while (p);
+  return 1;
+}
+
+/** Copy or move an attribute.
+ * \verbatim
+ * This implements @cpattr and @mvattr.
+ * the command is of the format:
+ * @cpattr oldobj/oldattr = newobj1/newattr1, newobj2/newattr2, etc.
+ * \endverbatim
+ * \param player the enactor.
+ * \param oldpair the obj/attribute pair to copy from.
+ * \param newpair array of obj/attribute pairs to copy to.
+ * \param move if 1, move rather than copy.
+ * \param noflagcopy if 1, don't copy associated flags.
+ */
+void
+do_cpattr(dbref player, char *oldpair, char **newpair, int move, int noflagcopy)
+{
+  dbref oldobj, newobj;
+  char tbuf1[BUFFER_LEN], tbuf2[BUFFER_LEN];
+  int i;
+  char *p, *q;
+  ATTR *a;
+  char *text;
+  int copies = 0;
+
+  /* must copy from something */
+  if (!oldpair || !*oldpair) {
+    notify(player, T("What do you want to copy from?"));
+    return;
+  }
+  /* find the old object */
+  strcpy(tbuf1, oldpair);
+  p = strchr(tbuf1, '/');
+  if (!p || !*p) {
+    notify(player, T("What object do you want to copy the attribute from?"));
+    return;
+  }
+  *p++ = '\0';
+  oldobj = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);
+  if (!GoodObject(oldobj))
+    return;
+
+  strcpy(tbuf2, p);
+  p = tbuf2;
+  /* find the old attribute */
+  a = atr_get_noparent(oldobj, strupper(p));
+  if (!a) {
+    notify(player, T("No such attribute to copy from."));
+    return;
+  }
+  /* check permissions to get it */
+  if (!Can_Read_Attr(player, oldobj, a)) {
+    notify(player, T("Permission to read attribute denied."));
+    return;
+  }
+  /* we can read it. Copy the value. */
+  text = safe_atr_value(a);
+
+  /* now we loop through our new object pairs and copy, calling @set. */
+  for (i = 1; i < MAX_ARG && (newpair[i] != NULL); i++) {
+    if (!*newpair[i]) {
+      notify(player, T("What do you want to copy to?"));
+    } else {
+      strcpy(tbuf1, newpair[i]);
+      q = strchr(tbuf1, '/');
+      if (!q || !*q) {
+       q = (char *) AL_NAME(a);
+      } else {
+       *q++ = '\0';
+      }
+      newobj = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);
+      if (GoodObject(newobj) &&
+         ((newobj != oldobj) || strcasecmp(AL_NAME(a), q)) &&
+         do_set_atr(newobj, q, text, player, 1))
+       copies++;
+      /* copy the attribute flags too */
+      if (!noflagcopy)
+       copy_attrib_flags(player, newobj,
+                         atr_get_noparent(newobj, strupper(q)), a->flags);
+
+    }
+  }
+
+  free((Malloc_t) text);       /* safe_uncompress malloc()s memory */
+  if (copies) {
+    notify_format(player, T("Attribute %s (%d copies)"),
+                 (move ? "moved" : "copied"), copies);
+    if (move)
+      do_set_atr(oldobj, AL_NAME(a), NULL, player, 1);
+  } else {
+    notify_format(player, T("Unable to %s attribute."),
+                 (move ? "move" : "copy"));
+  }
+  return;
+}
+
+/** Argument struct for gedit_helper */
+struct gedit_args {
+  enum edit_type target; /**< The type of edit */
+  int doit; /**< Do we actually replace the attribute, or just pretend? */
+  char *from; /**< What is going to be replaced? */
+  char *to; /**< What it gets replaced with. */
+};
+
+static int
+gedit_helper(dbref player, dbref thing,
+            dbref parent __attribute__ ((__unused__)),
+            char const *pattern
+            __attribute__ ((__unused__)), ATTR *a, void *args)
+{
+  int ansi_long_flag = 0;
+  const char *r;
+  char *s, *val;
+  char tbuf1[BUFFER_LEN], tbuf_ansi[BUFFER_LEN];
+  char *tbufp, *tbufap;
+  size_t rlen, vlen;
+  struct gedit_args *gargs;
+
+  gargs = args;
+
+  val = gargs->from;
+  vlen = strlen(val);
+  r = gargs->to ? gargs->to : "";
+  rlen = strlen(r);
+
+  tbufp = tbuf1;
+  tbufap = tbuf_ansi;
+
+  if (!a) {                    /* Shouldn't ever happen, but better safe than sorry */
+    notify(player, T("No such attribute, try set instead."));
+    return 0;
+  }
+  if (!Can_Write_Attr(player, thing, a)) {
+    notify(player, T("You need to control an attribute to edit it."));
+    return 0;
+  }
+  s = (char *) atr_value(a);   /* warning: pointer to static buffer */
+
+  if (vlen == 1 && *val == '$') {
+    /* append */
+    safe_str(s, tbuf1, &tbufp);
+    safe_str(r, tbuf1, &tbufp);
+
+    if (safe_format(tbuf_ansi, &tbufap, "%s%s%s%s", s, ANSI_HILITE, r,
+                   ANSI_NORMAL))
+      ansi_long_flag = 1;
+  } else if (vlen == 1 && *val == '^') {
+    /* prepend */
+    safe_str(r, tbuf1, &tbufp);
+    safe_str(s, tbuf1, &tbufp);
+
+    if (safe_format(tbuf_ansi, &tbufap, "%s%s%s%s", ANSI_HILITE, r, ANSI_NORMAL,
+                   s))
+      ansi_long_flag = 1;
+  } else if (!*val) {
+    /* insert replacement string between every character */
+    ansi_string *haystack;
+    size_t last = 0;
+    int too_long = 0;
+
+    haystack = parse_ansi_string(s);
+
+    /* Add one at the start */
+    if (!safe_strl(r, rlen, tbuf1, &tbufp)) {
+      if (gargs->target != EDIT_FIRST) {
+       for (last = 0; last < haystack->len; last++) {
+         /* Add the next character */
+         if (safe_ansi_string(haystack, last, 1, tbuf1, &tbufp)) {
+           too_long = 1;
+           break;
+         }
+         if (!ansi_long_flag) {
+           if (safe_ansi_string(haystack, last, 1, tbuf_ansi, &tbufap))
+             ansi_long_flag = 1;
+         }
+         /* Copy in r */
+         if (safe_strl(r, rlen, tbuf1, &tbufp)) {
+           too_long = 1;
+           break;
+         }
+         if (!ansi_long_flag) {
+           if (safe_format(tbuf_ansi, &tbufap, "%s%s%s", ANSI_HILITE, r,
+                           ANSI_NORMAL))
+             ansi_long_flag = 1;
+         }
+       }
+      }
+    }
+    free_ansi_string(haystack);
+  } else {
+    /* find and replace */
+    ansi_string *haystack;
+    size_t last = 0;
+    char *p;
+    int too_long = 0;
+
+    haystack = parse_ansi_string(s);
+
+    while (last < haystack->len
+          && (p = strstr(haystack->text + last, val)) != NULL) {
+      if (safe_ansi_string(haystack, last, p - (haystack->text + last),
+                          tbuf1, &tbufp)) {
+       too_long = 1;
+       break;
+      }
+      if (!ansi_long_flag) {
+       if (safe_ansi_string(haystack, last, p - (haystack->text + last),
+                            tbuf_ansi, &tbufap))
+         ansi_long_flag = 1;
+      }
+
+      /* Copy in r */
+      if (safe_strl(r, rlen, tbuf1, &tbufp)) {
+       too_long = 1;
+       break;
+      }
+      if (!ansi_long_flag) {
+       if (safe_format(tbuf_ansi, &tbufap, "%s%s%s", ANSI_HILITE, r,
+                       ANSI_NORMAL))
+         ansi_long_flag = 1;
+      }
+      last = p - haystack->text + vlen;
+      if (gargs->target == EDIT_FIRST)
+       break;
+    }
+    if (last < haystack->len && !too_long) {
+      safe_ansi_string(haystack, last, haystack->len, tbuf1, &tbufp);
+      if (!ansi_long_flag) {
+       if (safe_ansi_string(haystack, last, haystack->len, tbuf_ansi, &tbufap))
+         ansi_long_flag = 1;
+      }
+    }
+    free_ansi_string(haystack);
+  }
+
+  *tbufp = '\0';
+  *tbufap = '\0';
+
+  if (gargs->doit) {
+    if (do_set_atr(thing, AL_NAME(a), tbuf1, player, 0) &&
+       !AreQuiet(player, thing)) {
+      if (!ansi_long_flag && ShowAnsi(player))
+       notify_format(player, "%s - Set: %s", AL_NAME(a), tbuf_ansi);
+      else
+       notify_format(player, "%s - Set: %s", AL_NAME(a), tbuf1);
+    }
+  } else {
+    /* We don't do it - we just pemit it. */
+    if (!ansi_long_flag && ShowAnsi(player))
+      notify_format(player, "%s - Set: %s", AL_NAME(a), tbuf_ansi);
+    else
+      notify_format(player, "%s - Set: %s", AL_NAME(a), tbuf1);
+  }
+
+  return 1;
+}
+
+/** Edit an attribute.
+ * \verbatim
+ * This implements @edit obj/attribute = {search}, {replace}
+ * \endverbatim
+ * \param player the enactor.
+ * \param it the object/attribute pair.
+ * \param argv array containing the search and replace strings.
+ */
+void
+do_gedit(dbref player, char *it, char **argv, enum edit_type target, int doit)
+{
+  dbref thing;
+  char tbuf1[BUFFER_LEN];
+  char *q;
+  struct gedit_args args;
+
+  if (!(it && *it)) {
+    notify(player, T("I need to know what you want to edit."));
+    return;
+  }
+  strcpy(tbuf1, it);
+  q = strchr(tbuf1, '/');
+  if (!(q && *q)) {
+    notify(player, T("I need to know what you want to edit."));
+    return;
+  }
+  *q++ = '\0';
+  thing = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);
+
+  if ((thing == NOTHING) || !controls(player, thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+
+  if (!argv[1] || !*argv[1]) {
+    notify(player, T("Nothing to do."));
+    return;
+  }
+  args.from = argv[1];
+  args.to = argv[2];
+  args.target = target;
+  args.doit = doit;
+
+  if (!atr_iter_get(player, thing, q, 0, gedit_helper, &args))
+    notify(player, T("No matching attributes."));
+}
+/** Trigger an attribute.
+ * \verbatim
+ * This implements @trigger obj/attribute = list-of-arguments.
+ * \endverbatim
+ * \param player the enactor.
+ * \param object the object/attribute pair.
+ * \param argv array of arguments.
+ */
+void
+do_trigger(dbref player, char *object, char **argv)
+{
+  dbref thing;
+  int a;
+  char *s;
+  char tbuf1[BUFFER_LEN];
+
+  strcpy(tbuf1, object);
+  for (s = tbuf1; *s && (*s != '/'); s++) ;
+  if (!*s) {
+    notify(player, T("I need to know what attribute to trigger."));
+    return;
+  }
+  *s++ = '\0';
+
+  thing = noisy_match_result(player, tbuf1, NOTYPE, MAT_EVERYTHING);
+
+  if (thing == NOTHING)
+    return;
+
+  if (IsDivision(thing) && GoodObject(Parent(thing))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (!controls(player, thing) && !(Owns(player, thing) && LinkOk(thing))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if (God(thing) && !God(player)) {
+    notify(player, T("You can't trigger God!"));
+    return;
+  }
+  /* trigger modifies the stack */
+  for (a = 0; a < 10; a++) {
+    if(!argv[a+1])
+           break;
+    global_eval_context.wnxt[a] = argv[a + 1];
+  }
+  while(a < 10)
+         global_eval_context.wnxt[a++] = NULL;
+
+  if (charge_action(player, thing, upcasestr(s))) {
+    if (!AreQuiet(player, thing))
+      notify_format(player, "%s - Triggered.", Name(thing));
+  } else {
+    notify(player, T("No such attribute."));
+  }
+}
+
+
+/** The use command.
+ * It's here for lack of a better place.
+ * \param player the enactor.
+ * \param what name of the object to use.
+ */
+void
+do_use(dbref player, const char *what)
+{
+  dbref thing;
+
+  /* if we pass the use key, do it */
+
+  if ((thing =
+       noisy_match_result(player, what, TYPE_THING,
+                         MAT_NEAR_THINGS)) != NOTHING) {
+    if (!eval_lock(player, thing, Use_Lock)) {
+      fail_lock(player, thing, Use_Lock, T("Permission denied."), NOTHING);
+      return;
+    } else
+      did_it(player, thing, "USE", "Used.", "OUSE", NULL, "AUSE", NOTHING);
+  }
+}
+
+/** Parent an object to another.
+ * \verbatim
+ * This implements @parent.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the name of the child object.
+ * \param parent_name the name of the new parent object.
+ */
+void
+do_parent(dbref player, char *name, char *parent_name)
+{
+  dbref thing;
+  dbref parent;
+  dbref check;
+  int i;
+
+  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING)
+    return;
+
+  if (!parent_name || !*parent_name || !strcasecmp(parent_name, "none"))
+    parent = NOTHING;
+  else if ((parent = noisy_match_result(player, parent_name, NOTYPE,
+                                       MAT_EVERYTHING)) == NOTHING)
+    return;
+
+  /* do control check */
+  if (!controls(player, thing) && !(Owns(player, thing) && LinkOk(thing))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* a player may change an object's parent to NOTHING or to an 
+   * object he owns, or one that is LINK_OK when the player passes
+   * the parent lock
+   * mod: also when the player controls the parent, it passes the parent lock
+   */
+  if ((parent != NOTHING) && !controls(player, parent) &&
+      !(LinkOk(parent) && eval_lock(player, parent, Parent_Lock))) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* check to make sure no recursion can happen */
+  if (parent == thing) {
+    notify(player, T("A thing cannot be its own ancestor!"));
+    return;
+  }
+  /* check if this is a division */
+  if(IsDivision(thing)) {
+    if(Division(thing) != -1) {
+      notify(player, "You can't only @parent the master division.");
+      return;
+     } else {
+       /* this is a master division              */
+       /* check parent recursion.. for a division */
+       for(i = 0, check = parent; i < MAX_PARENTS && check != NOTHING
+          ; i++ , check = Parent(check))
+        if(IsDivision(check)) {
+          notify(player, T("A division is not allowed to be in your parent tree."));
+          return;
+        }
+     }
+  }
+
+  if (parent != NOTHING) {
+    for (i = 0, check = Parent(parent);
+        (i < MAX_PARENTS) && (check != NOTHING); i++, check = Parent(check)) {
+      if (check == thing) {
+       notify(player, T("You are not allowed to be your own ancestor!"));
+       return;
+      }
+    }
+    if (i >= MAX_PARENTS) {
+      notify(player, T("Too many ancestors."));
+      return;
+    }
+    if (Owner(parent) != Owner(thing)
+       && !has_flag_by_name(parent, "AUTH_PARENT", NOTYPE)) {
+      notify(player, T("Warning: Parent and child are owned by different players and parent is not set AUTH_PARENT."));
+    }
+  }
+  /* everything is okay, do the change */
+  Parent(thing) = parent;
+  if (!AreQuiet(player, thing))
+    notify(player, T("Parent changed."));
+}
+
+static int
+wipe_helper(dbref player, dbref thing, dbref parent __attribute__ ((__unused__)),
+    char const *pattern, ATTR *atr, void *args __attribute__ ((__unused__)))
+{
+  /* for added security, only God can modify privileged
+   * attributes using this command and wildcards.  Wiping a specific
+   * attr still works, though.
+   */
+  if (wildcard(pattern) && (AL_FLAGS(atr) & AF_PRIVILEGE) && !Director(player))
+         return 0;
+  return do_set_atr(thing, AL_NAME(atr), NULL, player, 0) == 1;
+}
+
+/** Clear an attribute.
+ * \verbatim
+ * This implements @wipe.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name the object/attribute-pattern to wipe.
+ */
+void
+do_wipe(dbref player, char *name)
+{
+  dbref thing;
+  char *pattern;
+
+  if ((pattern = strchr(name, '/')) != NULL)
+    *pattern++ = '\0';
+
+  if ((thing = noisy_match_result(player, name, NOTYPE, MAT_NEARBY)) == NOTHING)
+    return;
+
+  /* this is too destructive of a command to be used by someone who
+   * doesn't own the object. Thus, the check is on Owns not controls.
+   */
+  if (!controls(player, thing)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* protect SAFE objects unless doing a non-wildcard pattern */
+  if (Safe(thing) && !(pattern && *pattern && !wildcard(pattern))) {
+    notify(player, T("That object is protected."));
+    return;
+  }
+
+  we_are_wiping = 1;
+
+  if (!atr_iter_get(player, thing, pattern, 0, wipe_helper, NULL))
+    notify(player, T("No matching attributes."));
+  else
+    notify(player, T("Attributes wiped."));
+
+  we_are_wiping = 0;
+}
diff --git a/src/shs.c b/src/shs.c
new file mode 100644 (file)
index 0000000..bc4ed10
--- /dev/null
+++ b/src/shs.c
@@ -0,0 +1,424 @@
+/**
+ * \file shs.c
+ *
+ * \brief The secure hash algorithm.
+ *
+ * Written 2 September 1992. Peter C. Gutmann.
+ * This implementation placed in the public domain.
+ *
+ * Comments to pgutl@cs.aukuni.ac.nz  
+ *
+ * Keyed in from: "Applied Cryptography by Bruce Schneier"
+ *
+ * by Nick Gammon.
+ *
+ * Note: include files below are slightly different from the original source
+ * in order to confirm to the MUSH include files standards.
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#ifndef HAS_OPENSSL
+
+#include <string.h>
+#include "shs.h"
+#include "confmagic.h"
+
+#ifndef DOXYGEN_SHOULD_SKIP_THIS
+
+/* The SHS f() functions */
+
+#define f1(x, y, z) ( (x & y) | (~x & z) )     /* rounds 0-19 */
+#define f2(x, y, z) ( x ^ y ^ z )      /* rounds 20-39 */
+#define f3(x, y, z) ( (x & y) | (x & z) | (y & z) )    /* rounds 40-59 */
+#define f4(x, y, z) ( x ^ y ^ z )      /* rounds 60-79 */
+
+/* The SHS mysterious constants */
+
+#define K1 0x5A827999L
+#define K2 0x6ED9EBA1L
+#define K3 0x8F1BBCDCL
+#define K4 0xCA62C1D6L
+
+/* SHS initial values */
+
+#define h0init 0x67452301L
+#define h1init 0xEFCDAB89L
+#define h2init 0x98BADCFEL
+#define h3init 0x10325476L
+#define h4init 0xC3D2E1F0L
+
+/* 32-bit rotate - kludged with shifts */
+
+#define S(n, X) ( ( X << n) | ( X >> ( 32 - n) ) )
+
+/* The initial expanding functions */
+
+#define expand(count) W [count] = W [count - 3] ^ W [count - 8] ^  \
+                      W [count - 14] ^ W [count - 16]
+
+/* The four SHS sub-rounds */
+
+#define subRound1(count)   \
+  { \
+  temp = S (5, A) + f1 (B, C, D) + E + W [count] + K1;  \
+  E = D;  \
+  D = C;  \
+  C = S (30, B);  \
+  B = A;  \
+  A = temp;  \
+  }
+
+#define subRound2(count)   \
+  { \
+  temp = S (5, A) + f2 (B, C, D) + E + W [count] + K2;  \
+  E = D;  \
+  D = C;  \
+  C = S (30, B);  \
+  B = A;  \
+  A = temp;  \
+  }
+
+
+#define subRound3(count)   \
+  { \
+  temp = S (5, A) + f3 (B, C, D) + E + W [count] + K3;  \
+  E = D;  \
+  D = C;  \
+  C = S (30, B);  \
+  B = A;  \
+  A = temp;  \
+  }
+
+
+#define subRound4(count)   \
+  { \
+  temp = S (5, A) + f4 (B, C, D) + E + W [count] + K4;  \
+  E = D;  \
+  D = C;  \
+  C = S (30, B);  \
+  B = A;  \
+  A = temp;  \
+  }
+
+
+/* The two buffers of 5 32-bit words */
+
+LONG h0, h1, h2, h3, h4;
+LONG A, B, C, D, E;
+
+#endif                         /* DOXYGEN_SHOULD_SKIP_THIS */
+
+/* Initialise the SHS values */
+
+/** Initialize SHS values. 
+ * \param shsInfo pointer to shs data structure.
+ */
+void
+shsInit(SHS_INFO *shsInfo)
+{
+  /* Set the h-vars to their initial values */
+  shsInfo->digest[0] = h0init;
+  shsInfo->digest[1] = h1init;
+  shsInfo->digest[2] = h2init;
+  shsInfo->digest[3] = h3init;
+  shsInfo->digest[4] = h4init;
+
+  /* Initialise bit count */
+  shsInfo->countLo = shsInfo->countHi = 0;
+
+}
+
+
+static void shsTransform(SHS_INFO *shsInfo);
+
+/* Perform the SHS transformation. Note that this code, like MD5, seems to
+ * break some optimizing compilers - it may be necessary to split it into 
+ * sections, e.g. based on the four subrounds.
+ */
+static void
+shsTransform(SHS_INFO *shsInfo)
+{
+  LONG W[80], temp;
+  int i;
+
+  /* Step A. Copy the data buffer into the local work buffer */
+
+  for (i = 0; i < 16; i++)
+    W[i] = shsInfo->data[i];
+
+  /* Step B. Expand the 16 words into 64 temporary data words */
+
+  expand(16);
+  expand(17);
+  expand(18);
+  expand(19);
+  expand(20);
+  expand(21);
+  expand(22);
+  expand(23);
+  expand(24);
+  expand(25);
+  expand(26);
+  expand(27);
+  expand(28);
+  expand(29);
+  expand(30);
+  expand(31);
+  expand(32);
+  expand(33);
+  expand(34);
+  expand(35);
+  expand(36);
+  expand(37);
+  expand(38);
+  expand(39);
+  expand(40);
+  expand(41);
+  expand(42);
+  expand(43);
+  expand(44);
+  expand(45);
+  expand(46);
+  expand(47);
+  expand(48);
+  expand(49);
+  expand(50);
+  expand(51);
+  expand(52);
+  expand(53);
+  expand(54);
+  expand(55);
+  expand(56);
+  expand(57);
+  expand(58);
+  expand(59);
+  expand(60);
+  expand(61);
+  expand(62);
+  expand(63);
+  expand(64);
+  expand(65);
+  expand(66);
+  expand(67);
+  expand(68);
+  expand(69);
+  expand(70);
+  expand(71);
+  expand(72);
+  expand(73);
+  expand(74);
+  expand(75);
+  expand(76);
+  expand(77);
+  expand(78);
+  expand(79);
+
+  /* Step C. Set up first buffer */
+
+  A = shsInfo->digest[0];
+  B = shsInfo->digest[1];
+  C = shsInfo->digest[2];
+  D = shsInfo->digest[3];
+  E = shsInfo->digest[4];
+
+  /* Step D. Serious mangling, divided into four subrounds */
+
+  subRound1(0);
+  subRound1(1);
+  subRound1(2);
+  subRound1(3);
+  subRound1(4);
+  subRound1(5);
+  subRound1(6);
+  subRound1(7);
+  subRound1(8);
+  subRound1(9);
+  subRound1(10);
+  subRound1(11);
+  subRound1(12);
+  subRound1(13);
+  subRound1(14);
+  subRound1(15);
+  subRound1(16);
+  subRound1(17);
+  subRound1(18);
+  subRound1(19);
+  subRound2(20);
+  subRound2(21);
+  subRound2(22);
+  subRound2(23);
+  subRound2(24);
+  subRound2(25);
+  subRound2(26);
+  subRound2(27);
+  subRound2(28);
+  subRound2(29);
+  subRound2(30);
+  subRound2(31);
+  subRound2(32);
+  subRound2(33);
+  subRound2(34);
+  subRound2(35);
+  subRound2(36);
+  subRound2(37);
+  subRound2(38);
+  subRound2(39);
+  subRound3(40);
+  subRound3(41);
+  subRound3(42);
+  subRound3(43);
+  subRound3(44);
+  subRound3(45);
+  subRound3(46);
+  subRound3(47);
+  subRound3(48);
+  subRound3(49);
+  subRound3(50);
+  subRound3(51);
+  subRound3(52);
+  subRound3(53);
+  subRound3(54);
+  subRound3(55);
+  subRound3(56);
+  subRound3(57);
+  subRound3(58);
+  subRound3(59);
+  subRound4(60);
+  subRound4(61);
+  subRound4(62);
+  subRound4(63);
+  subRound4(64);
+  subRound4(65);
+  subRound4(66);
+  subRound4(67);
+  subRound4(68);
+  subRound4(69);
+  subRound4(70);
+  subRound4(71);
+  subRound4(72);
+  subRound4(73);
+  subRound4(74);
+  subRound4(75);
+  subRound4(76);
+  subRound4(77);
+  subRound4(78);
+  subRound4(79);
+
+  /* Step E. Build message digest */
+
+  shsInfo->digest[0] += A;
+  shsInfo->digest[1] += B;
+  shsInfo->digest[2] += C;
+  shsInfo->digest[3] += D;
+  shsInfo->digest[4] += E;
+
+}                              /* end of shsTransform */
+
+static void byteReverse(LONG * buffer, int byteCount);
+static void
+byteReverse(LONG * buffer, int byteCount)
+{
+  LONG value;
+  int count;
+
+  byteCount /= sizeof(LONG);
+
+  for (count = 0; count < byteCount; count++) {
+    value = (buffer[count] << 16) | (buffer[count] >> 16);
+    buffer[count] = ((value & 0xFF00FF00L) >> 8) | ((value & 0x00FF00FFL) << 8);
+  }
+}                              /* end of byteReverse */
+
+
+/** Update SHS for a block of data. This code assumes that the buffer
+ * size is a multiple of SHS_BLOCKSIZE bytes long, which makes the
+ * code a lot more efficient since it does away with the need to
+ * handle partial blocks between calls to shsUpdate() 
+ * \param shsInfo pointer to shs data.
+ * \param buffer block of data to update the hash with.
+ * \param count size of buffer in bytes.
+ */
+void
+shsUpdate(SHS_INFO *shsInfo, const BYTE * buffer, int count)
+{
+  /* Update bitcount */
+
+  if ((shsInfo->countLo + ((LONG) count << 3)) < shsInfo->countLo)
+    shsInfo->countHi++;                /* Carry from low to high bitcount */
+
+  shsInfo->countLo += ((LONG) count << 3);
+  shsInfo->countHi += ((LONG) count >> 29);
+
+  /* process data in SHS_BLOCKSIZE chunks */
+
+  while (count >= SHS_BLOCKSIZE) {
+    memcpy(shsInfo->data, buffer, SHS_BLOCKSIZE);
+    if (shsInfo->reverse_wanted)
+      byteReverse(shsInfo->data, SHS_BLOCKSIZE);
+    shsTransform(shsInfo);
+    buffer += SHS_BLOCKSIZE;
+    count -= SHS_BLOCKSIZE;
+  }                            /* end of looping processing each chunk */
+
+  /* Handle any remaining bytes of data. This should only happen once on the final lot of data */
+
+  memcpy(shsInfo->data, buffer, count);
+
+}
+
+/** Finalize the SHS hash.
+ * \param shsInfo pointer to shs data.
+ */
+void
+shsFinal(SHS_INFO *shsInfo)
+{
+  int count;
+  LONG lowBitcount = shsInfo->countLo, highBitcount = shsInfo->countHi;
+
+  /* compute number of bytes mod 64 */
+
+  count = (int) ((shsInfo->countLo >> 3) & 0x3F);
+
+  /* set the first char of padding to 0x80. This is safe since there is always at */
+  /* least one byte free */
+
+  ((BYTE *) shsInfo->data)[count++] = 0x80;
+
+  /* pad out to 56 mod 64 */
+
+  if (count > 56) {
+    /* two lots of padding: Pad the first block to 64 bytes */
+
+    memset((BYTE *) shsInfo->data + count, 0, 64 - count);
+
+    if (shsInfo->reverse_wanted)
+      byteReverse(shsInfo->data, SHS_BLOCKSIZE);
+
+    shsTransform(shsInfo);
+
+    /* now fill the next block with 56 bytes */
+
+    memset((BYTE *) shsInfo->data, 0, 56);
+  }
+  /* end of count being > 56 */
+  else
+    /* pad block to 56 bytes */
+    memset((BYTE *) shsInfo->data + count, 0, 56 - count);
+
+  if (shsInfo->reverse_wanted)
+    byteReverse(shsInfo->data, SHS_BLOCKSIZE);
+
+  /* append length in bits and transform */
+
+  shsInfo->data[14] = highBitcount;
+  shsInfo->data[15] = lowBitcount;
+
+  shsTransform(shsInfo);
+
+  if (shsInfo->reverse_wanted)
+    byteReverse(shsInfo->data, SHS_DIGESTSIZE);
+}
+
+#endif                         /* HAS_OPENSSL */
diff --git a/src/sig.c b/src/sig.c
new file mode 100644 (file)
index 0000000..e09d8ac
--- /dev/null
+++ b/src/sig.c
@@ -0,0 +1,132 @@
+/**
+ * \file sig.c
+ *
+ * \brief Signal handling routines for PennMUSH.
+ *
+ *
+ */
+
+#include "config.h"
+#include <signal.h>
+#include "conf.h"
+#include "externs.h"
+#include "confmagic.h"
+
+#ifndef HAS_SIGPROCMASK
+static Sigfunc saved_handlers[NSIG];
+#endif
+
+/** Our own version of signal().
+ * We're going to rewrite the signal() function in terms of
+ * sigaction, where available, to ensure consistent semantics.
+ * We want signal handlers to remain installed, and we want
+ * signals (except SIGALRM) to restart system calls which they
+ * interrupt. This is how bsd signals work, and what we'd like.
+ * This function is essentially example 10.12 from Stevens'
+ * _Advanced Programming in the Unix Environment_.
+ * \param signo signal number.
+ * \param func signal handler function to install.
+ * \return former signal handler for signo.
+ */
+Sigfunc
+install_sig_handler(int signo, Sigfunc func)
+{
+#ifdef HAS_SIGACTION
+  struct sigaction act, oact;
+  act.sa_handler = func;
+  sigemptyset(&act.sa_mask);
+  act.sa_flags = 0;
+#ifdef SA_RESTART
+  act.sa_flags |= SA_RESTART;
+#endif
+  if (sigaction(signo, &act, &oact) < 0)
+    return SIG_ERR;
+  return oact.sa_handler;
+#else                          /* No sigaction, drat. */
+  return signal(signo, func);
+#endif
+}
+
+/** Reinstall a signal handler.
+ * \param signo the signal number.
+ * \param func signal handler function to reload.
+ */
+void
+reload_sig_handler(int signo __attribute__ ((__unused__)),
+                  Sigfunc func __attribute__ ((__unused__)))
+{
+#if !(defined(HAS_SIGACTION) || defined(SIGNALS_KEPT))
+  signal(signo, func);
+#endif
+}
+
+/** Set a signal to be ignored.
+ * \param signo signal number to ignore.
+ */
+void
+ignore_signal(int signo)
+{
+#ifdef HAS_SIGACTION
+  struct sigaction act;
+  act.sa_handler = SIG_IGN;
+  sigemptyset(&act.sa_mask);
+  act.sa_flags = 0;
+  sigaction(signo, &act, NULL);
+#else                          /* No sigaction, drat. */
+  signal(signo, SIG_IGN);
+#endif
+}
+
+/** Set a signal to block.
+ * These don't really work right without sigprocmask(), but we try.
+ * \param signo signal number to block.
+ */
+void
+block_a_signal(int signo)
+{
+#ifdef HAS_SIGPROCMASK
+  sigset_t mask;
+  sigemptyset(&mask);
+  sigaddset(&mask, signo);
+  sigprocmask(SIG_BLOCK, &mask, NULL);
+#else
+  if (signo > 0 && signo < NSIG)
+    saved_handlers[signo] = signal(signo, SIG_IGN);
+#endif
+}
+
+/** Unblock a signal.
+ * These don't really work right without sigprocmask(), but we try.
+ * \param signo signal number to unblock.
+ */
+void
+unblock_a_signal(int signo)
+{
+#ifdef HAS_SIGPROCMASK
+  sigset_t mask;
+  if (signo >= 0 && signo < NSIG) {
+    sigemptyset(&mask);
+    sigaddset(&mask, signo);
+    sigprocmask(SIG_UNBLOCK, &mask, NULL);
+  }
+#else
+  if (signo >= 0 && signo < NSIG)
+    signal(signo, saved_handlers[signo]);
+#endif
+}
+
+/** Block all signals.
+ */
+void
+block_signals(void)
+{
+#ifdef HAS_SIGPROCMASK
+  sigset_t mask;
+  sigfillset(&mask);
+  sigprocmask(SIG_BLOCK, &mask, NULL);
+#else
+  int i;
+  for (i = 0; i < NSIG; i++)
+    signal(i, SIG_IGN);
+#endif
+}
diff --git a/src/speech.c b/src/speech.c
new file mode 100644 (file)
index 0000000..92f49ee
--- /dev/null
@@ -0,0 +1,1368 @@
+/**
+ * \file speech.c
+ *
+ * \brief Speech-related commands in PennMUSH.
+ *
+ *
+ */
+/* speech.c */
+
+#include "copyrite.h"
+#include "config.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "conf.h"
+#include "ansi.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "lock.h"
+#include "flags.h"
+#include "log.h"
+#include "match.h"
+#include "attrib.h"
+#include "parse.h"
+#include "game.h"
+#include "pcre.h"
+#include "confmagic.h"
+
+int okay_pemit(dbref player, dbref target);
+static dbref speech_loc(dbref thing);
+void propagate_sound(dbref thing, const char *msg);
+static void do_audible_stuff(dbref loc, dbref *excs, int numexcs,
+                            const char *msg);
+static void do_one_remit(dbref player, const char *target, const char *msg,
+                        int flags);
+static int dbref_comp(const void *a, const void *b);
+
+dbref na_zemit(dbref current, void *data);
+
+const char *
+spname(dbref thing)
+{
+  /* if FULL_INVIS is defined, dark admins and dark objects will be
+   * Someone and Something, respectively.
+   */
+
+  if (FULL_INVIS && DarkLegal(thing)) {
+    if (IsPlayer(thing))
+      return "Someone";
+    else
+      return "Something";
+  } else {
+    return accented_name(thing);
+  }
+}
+
+/** Can player pemit to target?
+ * You can pemit if you're pemit_all, if you're pemitting to yourself,
+ * if you're pemitting to a non-player, or if you pass target's
+ * pagelock and target isn't HAVEN.
+ * \param player dbref attempting to pemit.
+ * \param target target dbref to pemit to.
+ * \retval 1 player may pemit to target.
+ * \retval 0 player may not pemit to target.
+ */
+int
+okay_pemit(dbref player, dbref target)
+{
+  if (Can_Pemit(player, target))
+    return 1;
+  if (IsPlayer(target) && Haven(target))
+    return 0;
+  if (!eval_lock(player, target, Page_Lock)) {
+    fail_lock(player, target, Page_Lock, NULL, NOTHING);
+    return 0;
+  }
+  return 1;
+}
+
+static dbref
+speech_loc(dbref thing)
+{
+  /* This is the place where speech, poses, and @emits by thing should be
+   * heard. For things and players, it's the loc; For rooms, it's the room
+   * itself; for exits, it's the source. */
+  if (!GoodObject(thing))
+    return NOTHING;
+  switch (Typeof(thing)) {
+  case TYPE_ROOM:
+    return thing;
+  case TYPE_EXIT:
+    return Home(thing);
+  default:
+    return Location(thing);
+  }
+}
+
+/** The teach command.
+ * \param player the enactor.
+ * \param cause the object causing the command to run.
+ * \param tbuf1 the command being taught.
+ */
+void
+do_teach(dbref player, dbref cause, const char *tbuf1)
+{
+  dbref loc;
+  static int recurse = 0;
+  char *command;
+
+  loc = speech_loc(player);
+  if (!GoodObject(loc))
+    return;
+
+  if (!CanSpeak(player,loc)) {
+    notify(player, T("You may not speak here!"));
+    return;
+  }
+
+  if (recurse) {
+    /* Somebody tried to teach the teach command. Cute. Dumb. */
+    notify(player, T("You can't teach 'teach', sorry."));
+    recurse = 0;
+    return;
+  }
+
+  if (!tbuf1 || !*tbuf1) {
+    notify(player, T("What command do you want to teach?"));
+    return;
+  }
+
+  recurse = 1;                 /* Protect use from recursive teach */
+  notify_except(Contents(loc), NOTHING,
+               tprintf(T("%s types --> %s%s%s"), spname(player),
+                       ANSI_HILITE, tbuf1, ANSI_NORMAL), NA_INTER_HEAR);
+  command = mush_strdup(tbuf1, "string");      /* process_command is destructive */
+  process_command(player, command, cause, cause, 1);
+  mush_free(command, "string");
+  recurse = 0;                 /* Ok, we can be called again safely */
+}
+
+/** The say command.
+ * \param player the enactor.
+ * \param tbuf1 the message to say.
+ */
+void
+do_say(dbref player, const char *tbuf1)
+{
+  dbref loc;
+
+  loc = speech_loc(player);
+  if (!GoodObject(loc))
+    return;
+
+  if(!CanSpeak(player,loc)) {
+    notify(player, T("You may not speak here!"));
+    return;
+  }
+
+  if (*tbuf1 == SAY_TOKEN && CHAT_STRIP_QUOTE)
+    tbuf1++;
+
+  /* notify everybody */
+  notify_format(player, T("You say, \"%s\""), tbuf1);
+  notify_except(Contents(loc), player,
+               tprintf(T("%s says, \"%s\""), spname(player), tbuf1),
+               NA_INTER_HEAR);
+}
+
+/** A comparator for raw dbrefs.
+ * This exists because the i_comp function in funlist.c got hairy some time ago.
+ * \param a the first element to compare.
+ * \param b the second element to compare.
+ */
+static int
+dbref_comp(const void *a, const void *b)
+{
+  const dbref *x = (const dbref *) a;
+  const dbref *y = (const dbref *) b;
+  return (int) *x - (int) *y;
+}
+
+/** The oemit(/list) command.
+ * \verbatim
+ * This implements @oemit and @oemit/list.
+ * \endverbatim
+ * \param player the enactor.
+ * \param list the list of dbrefs to oemit from the emit.
+ * \param message the message to emit.
+ * \param flags PEMIT_* flags.
+ */
+void
+do_oemit_list(dbref player, char *list, const char *message, int flags)
+{
+  char *temp, *p, *s;
+  dbref who;
+  dbref pass[12], locs[10];
+  int i, oneloc = 0;
+  int na_flags = NA_INTER_HEAR;
+
+  /* If no message, further processing is pointless.
+   * If no list, they should have used @remit. */
+  if (!message || !*message || !list || !*list)
+    return;
+
+  orator = player;
+  pass[0] = 0;
+  /* Find out what room to do this in. If they supplied a db# before
+   * the '/', then oemit to anyone in the room who's not on list.
+   * Otherwise, oemit to every location which has at least one of the
+   * people in the list. This is intended for actions which involve
+   * players who are in different rooms, e.g.:
+   *
+   * X (in #0) fires an arrow at Y (in #2).
+   *
+   * X sees: You fire an arrow at Y. (pemit to X)
+   * Y sees: X fires an arrow at you! (pemit to Y)
+   * #0 sees: X fires an arrow at Y. (oemit/list to X Y)
+   * #2 sees: X fires an arrow at Y. (from the same oemit)
+   */
+  /* Find out what room to do this in. They should have supplied a db#
+   * before the '/'. */
+  if ((temp = strchr(list, '/'))) {
+    *temp++ = '\0';
+    pass[1] = noisy_match_result(player, list, NOTYPE, MAT_EVERYTHING);
+    if (!GoodObject(pass[1])) {
+      notify(player, T("I can't find that room."));
+      return;
+    }
+
+    if(!CanSpeak(player, pass[1])) {
+      notify(player, T("You may not speak there!"));
+      return;
+    }
+
+    oneloc = 1;                        /* we are only oemitting to one location */
+  } else {
+    temp = list;
+  }
+
+  s = trim_space_sep(temp, ' ');
+  while (s) {
+    p = split_token(&s, ' ');
+    /* If a room was given, we match relative to the room */
+    if (oneloc)
+      who = match_result(pass[1], p, NOTYPE, MAT_POSSESSION | MAT_ABSOLUTE);
+    else
+      who = noisy_match_result(player, p, NOTYPE, MAT_OBJECTS);
+    /* pass[0] tracks the number of valid players we've found.
+     * pass[1] is the given room (possibly nothing right now)
+     * pass[2..12] are dbrefs of players
+     * locs[0..10] are corresponding dbrefs of locations
+     */
+    if (GoodObject(who) && GoodObject(Location(who))
+       && (CanSpeak(player, Location(who)))
+      ) {
+      if (pass[0] < 10) {
+       locs[pass[0]] = Location(who);
+       pass[pass[0] + 2] = who;
+       pass[0]++;
+      } else {
+       notify(player, T("Too many people to oemit to."));
+       break;
+      }
+    }
+  }
+
+  /* Sort the list of rooms to oemit to so we don't oemit to the same           
+   * room twice */
+  qsort((void *) locs, pass[0], sizeof(locs[0]), dbref_comp);
+
+  if (flags & PEMIT_SPOOF)
+    na_flags |= NA_SPOOF;
+  for (i = 0; i < pass[0]; i++) {
+    if (i != 0 && locs[i] == locs[i - 1])
+      continue;
+    pass[1] = locs[i];
+    notify_anything_loc(orator, na_exceptN, pass, ns_esnotify, na_flags,
+                       message, locs[i]);
+    do_audible_stuff(pass[1], &pass[2], pass[0], message);
+  }
+}
+
+
+/** The whisper command.
+ * \param player the enactor.
+ * \param arg1 name of the object to whisper to.
+ * \param arg2 message to whisper.
+ * \param noisy if 1, others overhear that a whisper has occurred.
+ */
+void
+do_whisper(dbref player, const char *arg1, const char *arg2, int noisy)
+{
+  dbref who;
+  int key;
+  const char *gap;
+  char *tbuf, *tp;
+  char *p;
+  dbref good[100];
+  int gcount = 0;
+  const char *head;
+  int overheard;
+  char *current;
+  const char **start;
+
+  if (!arg1 || !*arg1) {
+    notify(player, T("Whisper to whom?"));
+    return;
+  }
+  if (!arg2 || !*arg2) {
+    notify(player, T("Whisper what?"));
+    return;
+  }
+  tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!tbuf)
+    mush_panic("Unable to allocate memory in do_whisper_list");
+
+  overheard = 0;
+  head = arg1;
+  start = &head;
+  /* Figure out what kind of message */
+  gap = " ";
+  switch (*arg2) {
+  case SEMI_POSE_TOKEN:
+    gap = "";
+  case POSE_TOKEN:
+    key = 1;
+    arg2++;
+    break;
+  default:
+    key = 2;
+    break;
+  }
+
+  *tp = '\0';
+  /* Make up a list of good and bad names */
+  while (head && *head) {
+    current = next_in_list(start);
+    who = match_result(player, current, TYPE_PLAYER, MAT_NEAR_THINGS |
+                      MAT_CONTAINER );
+    if (!GoodObject(who) || !can_interact(player, who, INTERACT_HEAR)) {
+      safe_chr(' ', tbuf, &tp);
+      safe_str_space(current, tbuf, &tp);
+      if (GoodObject(who))
+       notify_format(player, T("%s can't hear you."), Name(who));
+    } else {
+      /* A good whisper */
+      good[gcount++] = who;
+      if (gcount >= 100) {
+       notify(player, T("Too many people to whisper to."));
+       break;
+      }
+    }
+  }
+
+  *tp = '\0';
+  if (*tbuf)
+    notify_format(player, T("Unable to whisper to:%s"), tbuf);
+
+  if (!gcount) {
+    mush_free((Malloc_t) tbuf, "string");
+    return;
+  }
+
+  /* Drunk admins... */
+  if (Dark(player))
+    noisy = 0;
+
+  /* Set up list of good names */
+  tp = tbuf;
+  safe_str(" to ", tbuf, &tp);
+  for (who = 0; who < gcount; who++) {
+    if (noisy && (get_random_long(0, 100) < WHISPER_LOUDNESS))
+      overheard = 1;
+    safe_itemizer(who + 1, (who == gcount - 1), ",", T("and"), " ", tbuf, &tp);
+    safe_str(Name(good[who]), tbuf, &tp);
+  }
+  *tp = '\0';
+
+  if (key == 1) {
+    notify_format(player, (gcount > 1) ? T("%s sense: %s%s%s") :
+                 T("%s senses: %s%s%s"), tbuf + 4, Name(player), gap, arg2);
+    p = tprintf("You sense: %s%s%s", Name(player), gap, arg2);
+  } else {
+    notify_format(player, T("You whisper, \"%s\"%s."), arg2, tbuf);
+    p = tprintf("%s whispers%s: %s", Name(player),
+               gcount > 1 ? tbuf : "", arg2);
+  }
+
+  for (who = 0; who < gcount; who++) {
+    notify_must_puppet(good[who], p);
+    if (Location(good[who]) != Location(player))
+      overheard = 0;
+  }
+  if (overheard) {
+    dbref first = Contents(Location(player));
+    if (!GoodObject(first))
+      return;
+    p = tprintf("%s whispers%s.", Name(player), tbuf);
+    DOLIST(first, first) {
+      overheard = 1;
+      for (who = 0; who < gcount; who++) {
+       if ((first == player) || (first == good[who])) {
+         overheard = 0;
+         break;
+       }
+      }
+      if (overheard)
+       notify_noecho(first, p);
+    }
+  }
+  mush_free((Malloc_t) tbuf, "string");
+}
+
+/** Send a message to a list of dbrefs. To avoid repeated generation
+ * of the NOSPOOF string, we set it up the first time we encounter
+ * something Nospoof, and then check for it thereafter.
+ * The list is destructively modified.
+ * \param player the enactor.
+ * \param list the list of players to pemit to, destructively modified.
+ * \param message the message to pemit.
+ * \param flags PEMIT_* flags
+ */
+void
+do_pemit_list(dbref player, char *list, const char *message, int flags)
+{
+  char *bp, *p;
+  char *nsbuf, *nspbuf;
+  char *l;
+  dbref who;
+  int nospoof;
+
+  /* If no list or no message, further processing is pointless. */
+  if (!message || !*message || !list || !*list)
+    return;
+
+  nspbuf = nsbuf = NULL;
+  nospoof = (flags & PEMIT_SPOOF) ? 0 : 1;
+  list[BUFFER_LEN - 1] = '\0';
+  l = trim_space_sep(list, ' ');
+
+  while ((p = split_token(&l, ' '))) {
+    who = noisy_match_result(player, p, NOTYPE, MAT_ABSOLUTE);
+    if (GoodObject(who) && okay_pemit(player, who)) {
+      if (nospoof && Nospoof(who)) {
+       if (Paranoid(who)) {
+         if (!nspbuf) {
+           bp = nspbuf = mush_malloc(BUFFER_LEN, "string");
+           if (player == Owner(player))
+             safe_format(nspbuf, &bp, "[%s(#%d)->] %s", Name(player),
+                         player, message);
+           else
+             safe_format(nspbuf, &bp, "[%s(#%d)'s %s(#%d)->] %s",
+                         Name(Owner(player)), Owner(player),
+                         Name(player), player, message);
+           *bp = '\0';
+         }
+         notify(who, nspbuf);
+       } else {
+         if (!nsbuf) {
+           bp = nsbuf = mush_malloc(BUFFER_LEN, "string");
+           safe_format(nsbuf, &bp, "[%s->] %s", Name(player), message);
+           *bp = '\0';
+         }
+         notify(who, nsbuf);
+       }
+      } else {
+       notify_must_puppet(who, message);
+      }
+    }
+  }
+  if (nsbuf)
+    mush_free(nsbuf, "string");
+  if (nspbuf)
+    mush_free(nspbuf, "string");
+
+}
+
+/** Send a message to an object.
+ * \param player the enactor.
+ * \param arg1 the name of the object to pemit to.
+ * \param arg2 the message to pemit.
+ * \param flags PEMIT_* flags.
+ */
+void
+do_pemit(dbref player, const char *arg1, const char *arg2, int flags)
+{
+  dbref who;
+  int silent, nospoof;
+
+  silent = (flags & PEMIT_SILENT) ? 1 : 0;
+  nospoof = (flags & PEMIT_SPOOF) ? 0 : 1;
+
+  switch (who = match_result(player, arg1, NOTYPE,
+                            MAT_OBJECTS | MAT_HERE | MAT_CONTAINER)) {
+  case NOTHING:
+    notify(player, T("I don't see that here."));
+    break;
+  case AMBIGUOUS:
+    notify(player, T("I don't know who you mean!"));
+    break;
+  default:
+    if (!okay_pemit(player, who)) {
+      notify_format(player,
+                   T("I'm sorry, but %s wishes to be left alone now."),
+                   Name(who));
+      return;
+    }
+    if (!silent)
+      notify_format(player, T("You pemit \"%s\" to %s."), arg2, Name(who));
+    if (nospoof && Nospoof(who)) {
+      if (Paranoid(who)) {
+       if (player == Owner(player))
+         notify_format(who, "[%s(#%d)->%s] %s", Name(player), player,
+                       Name(who), arg2);
+       else
+         notify_format(who, "[%s(#%d)'s %s(#%d)->%s] %s",
+                       Name(Owner(player)), Owner(player),
+                       Name(player), player, Name(who), arg2);
+      } else
+       notify_format(who, "[%s->%s] %s", Name(player), Name(who), arg2);
+    } else {
+      notify_must_puppet(who, arg2);
+    }
+    break;
+  }
+}
+
+/** The pose and semipose command.
+ * \param player the enactor.
+ * \param tbuf1 the message to pose.
+ * \param space if 1, put a space between name and pose; if 0, don't (semipose)
+ */
+void
+do_pose(dbref player, const char *tbuf1, int space)
+{
+  dbref loc;
+
+  loc = speech_loc(player);
+  if (!GoodObject(loc))
+    return;
+
+  if(!CanSpeak(player,loc)) {
+    notify(player, T("You may not speak here!"));
+    return;
+  }
+
+  /* notify everybody */
+  if (!space)
+    notify_except(Contents(loc), NOTHING,
+                 tprintf("%s %s", spname(player), tbuf1), NA_INTER_HEAR);
+  else
+    notify_except(Contents(loc), NOTHING,
+                 tprintf("%s%s", spname(player), tbuf1), NA_INTER_HEAR);
+}
+
+/** The *wall commands.
+ * \param player the enactor.
+ * \param message message to broadcast.
+ * \param target type of broadcast (all)
+ * \param emit if 1, this is a wallemit.
+ */
+void
+do_wall(dbref player, const char *message, enum wall_type target, int emit)
+{
+  const char *gap = "", *prefix;
+  const char *mask;
+  int pose = 0;
+
+  if (!Can_Announce(player)) {
+    notify(player, T("Posing as an admin could be hazardous to your health."));
+    return;
+  }
+  /* put together the message and figure out what type it is */
+  if (!emit) {
+    gap = " ";
+    switch (*message) {
+    case SAY_TOKEN:
+      if (CHAT_STRIP_QUOTE)
+       message++;
+      break;
+    case SEMI_POSE_TOKEN:
+      gap = "";
+    case POSE_TOKEN:
+      pose = 1;
+      message++;
+      break;
+    }
+  }
+
+  if (!*message) {
+    notify(player, T("What did you want to say?"));
+    return;
+  }
+  /* to everyone */
+  mask = NULL;
+  prefix = WALL_PREFIX;
+
+  /* broadcast the message */
+  if (pose)
+    flag_broadcast(mask, 0, "%s %s%s%s", prefix, Name(player), gap, message);
+  else if (emit)
+    flag_broadcast(mask, 0, "%s [%s]: %s", prefix, Name(player), message);
+  else
+    flag_broadcast(mask, 0,
+                  "%s %s %s, \"%s\"", prefix, Name(player),
+                  target == WALL_ALL ? "shouts" : "says", message);
+}
+
+/** The page command.
+ * \param player the enactor.
+ * \param arg1 the list of players to page.
+ * \param arg2 the message to page.
+ * \param cause the object that caused the command to run.
+ * \param noeval if 1, page/noeval.
+ * \param multipage if 1, a page/list; if 0, a page/blind.
+ * \param override if 1, page/override.
+ */
+void
+do_page(dbref player, const char *arg1, const char *arg2, dbref cause,
+       int noeval, int multipage, int override)
+{
+  dbref target;
+  const char *message;
+  const char *gap;
+  int key;
+  char *tbuf, *tp;
+  char *tbuf2, *tp2;
+  dbref good[100], almost_good[100], *gptr;
+  int gcount = 0, ag_count = 0;
+  char *msgbuf, *mb;
+  const char *head;
+  const char *hp = NULL;
+  const char **start;
+  char *current;
+  int i;
+  int repage = 0;
+  int fails_lock;
+  int is_haven;
+  ATTR *a;
+
+  tp2 = tbuf2 = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!tbuf2)
+    mush_panic("Unable to allocate memory in do_page");
+
+  if (arg2 && *arg2 && *arg1) {
+    /* page to=msg. Always evaluate to, maybe evaluate msg */
+    process_expression(tbuf2, &tp2, &arg1, player, cause, cause,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *tp2 = '\0';
+    head = tbuf2;
+    message = arg2;
+  } else if (arg2 && *arg2) {
+    /* page =msg */
+    message = arg2;
+    repage = 1;
+  } else {
+    /* page msg */
+    message = arg1;
+    repage = 1;
+  }
+  if (repage) {
+    a = atr_get_noparent(player, "LASTPAGED");
+    if (!a || !*((hp = head = safe_atr_value(a)))) {
+      notify(player, T("You haven't paged anyone since connecting."));
+      mush_free((Malloc_t) tbuf2, "string");
+      return;
+    }
+    if (!message || !*message) {
+      notify_format(player, T("You last paged %s."), head);
+      mush_free((Malloc_t) tbuf2, "string");
+      if (hp)
+       free((Malloc_t) hp);
+      return;
+    }
+  }
+
+  tp = tbuf = (char *) mush_malloc(BUFFER_LEN, "string");
+  if (!tbuf)
+    mush_panic("Unable to allocate memory in do_page");
+
+  start = &head;
+  while (head && *head && ((gcount+ag_count) < 99)) {
+    current = next_in_list(start);
+    target = lookup_player(current);
+    if (!GoodObject(target))
+      target = short_page(current);
+    if (target == NOTHING) {
+      notify_format(player,
+                   T("I can't find who you're trying to page with: %s"),
+                   current);
+      safe_chr(' ', tbuf, &tp);
+      safe_str_space(current, tbuf, &tp);
+    } else if (target == AMBIGUOUS) {
+      notify_format(player,
+                   T("I'm not sure who you want to page with: %s"), current);
+      safe_chr(' ', tbuf, &tp);
+      safe_str_space(current, tbuf, &tp);
+    } else {
+      fails_lock = !(override || eval_lock(player, target, Page_Lock));
+      is_haven = !override && Haven(target);
+      if (!Connected(target) || (Dark(target) && (is_haven || fails_lock))) {
+       /* A player isn't connected if they aren't connected, or if
+        * they're DARK and HAVEN, or DARK and the pagelock fails. */
+       page_return(player, target, "Away", "AWAY",
+                   tprintf(T("%s is not connected."), Name(target)));
+       if (fails_lock)
+         fail_lock(player, target, Page_Lock, NULL, NOTHING);
+       safe_chr(' ', tbuf, &tp);
+       safe_str_space(current, tbuf, &tp);
+#ifdef RPMODE_SYS
+      } else if(RPMODE(player) && LEVEL(target) < 23) {
+             notify(player, "You can't do that in RPMODE.");
+             safe_chr(' ', tbuf, &tp);
+             safe_str_space(Name(target), tbuf, &tp);
+#endif
+      } else if (is_haven) {
+       page_return(player, target, "Haven", "HAVEN",
+                   tprintf(T("%s is not accepting any pages."), Name(target)));
+       safe_chr(' ', tbuf, &tp);
+       safe_str_space(Name(target), tbuf, &tp);
+      } else if (fails_lock) {
+       page_return(player, target, "Haven", "HAVEN",
+                   tprintf(T("%s is not accepting your pages."),
+                           Name(target)));
+       fail_lock(player, target, Page_Lock, NULL, NOTHING);
+       safe_chr(' ', tbuf, &tp);
+       safe_str_space(Name(target), tbuf, &tp);
+      } else if(RPMODE(target) && LEVEL(player) < 23 && LEVEL(target) < 23 ) {
+             page_return(player, target, 
+                  "RPMode", "RPMODE", 
+                     tprintf(T("%s is in RPMode and can not communicate OOCly at this moment."), Name(target)));
+                             safe_chr(' ', tbuf , &tp);
+                             safe_str_space(current, tbuf, &tp);
+                     } else if(hidden(target) && !CanSee(player,target)){
+             /* this is a page that appears bad, but is good */
+                 page_return(player, target, "Away", "AWAY",
+                          tprintf(T("%s is not connected."), Name(target)));
+                 safe_chr(' ', tbuf, &tp);
+                 safe_str_space(current, tbuf, &tp);
+
+             almost_good[ag_count] = target;
+             ag_count++;
+      } else {
+       /* This is a good page */
+       good[gcount] = target;
+       gcount++;
+      }
+    }
+  }
+
+  /* Reset tbuf2 to use later */
+  tp2 = tbuf2;
+
+  /* We now have an array of good[] dbrefs, a gcount of the good ones,
+   * and a tbuf with bad ones.
+   */
+
+  /* We don't know what the heck's going on here, but we're not paging
+   * anyone, this looks like a spam attack. */
+  if ((gcount+ag_count) ==  98) {
+    notify(player, T("You're trying to page too many people at once."));
+    mush_free((Malloc_t) tbuf, "string");
+    mush_free((Malloc_t) tbuf2, "string");
+    if (hp)
+      free((Malloc_t) hp);
+    return;
+  }
+
+  /* We used to stick 'Unable to page' on at the start, but this is
+   * faster for the 90% of the cases where there isn't a bad name
+   * That may sound high, but, remember, we (almost) never have a bad
+   * name if we're repaging, which is probably 75% of all pages */
+  *tp = '\0';
+  if (*tbuf)
+    notify_format(player, T("Unable to page:%s"), tbuf);
+
+  if (!gcount && !ag_count) {
+    /* Well, that was a total waste of time. */
+    mush_free((Malloc_t) tbuf, "string");
+    mush_free((Malloc_t) tbuf2, "string");
+    if (hp)
+      free((Malloc_t) hp);
+    return;
+  }
+
+  /* Can the player afford to pay for this thing? */
+  if (!payfor(player, PAGE_COST * (gcount + ag_count))) {
+    notify_format(player, T("You don't have enough %s."), MONIES);
+    mush_free((Malloc_t) tbuf, "string");
+    mush_free((Malloc_t) tbuf2, "string");
+    if (hp)
+      free((Malloc_t) hp);
+    return;
+  }
+
+  /* Okay, we have a real page, the player can pay for it, and it's
+   * actually going to someone. We're in this for keeps now. */
+
+  /* Evaluate the message if we need to. */
+  if (noeval)
+    msgbuf = NULL;
+  else {
+    mb = msgbuf = (char *) mush_malloc(BUFFER_LEN, "string");
+    if (!msgbuf)
+      mush_panic("Unable to allocate memory in do_page");
+
+    process_expression(msgbuf, &mb, &message, player, cause, cause,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *mb = '\0';
+    message = msgbuf;
+  }
+
+  if (Haven(player))
+    notify(player, T("You are set HAVEN and cannot receive pages."));
+
+  /* Figure out what kind of message */
+  global_eval_context.wenv[0] = (char *) message;
+  gap = " ";
+  switch (*message) {
+  case SEMI_POSE_TOKEN:
+    gap = "";
+  case POSE_TOKEN:
+    key = 1;
+    message++;
+    break;
+  default:
+    key = 3;
+    break;
+  }
+
+  /* Reset tbuf and tbuf2 to use later */
+  tp = tbuf;
+  tp2 = tbuf2;
+
+  /* tbuf2 is used to hold a fancy formatted list of names,
+   * with commas and the word 'and' , if needed. */
+  /* tbuf holds a space-separated list of names for repaging */
+
+  /* Set up a pretty formatted list. */
+  for (i = 0; i < gcount; i++) {
+    if (i)
+      safe_chr(' ', tbuf, &tp);
+    safe_str_space(Name(good[i]), tbuf, &tp);
+    safe_itemizer(i + 1, (i == gcount - 1), ",", T("and"), " ", tbuf2, &tp2);
+    safe_str(Name(good[i]), tbuf2, &tp2);
+  }
+  *tp = '\0';
+  *tp2 = '\0';
+  (void) atr_add(player, "LASTPAGED", tbuf, GOD, NOTHING);
+
+  /* Reset tbuf to use later */
+  tp = tbuf;
+
+  /* Figure out the one success message, and send it */
+  
+  if(gcount > 0) {
+    if (key == 1)
+       notify_format(player, T("Long distance to %s%s: %s%s%s"),
+                 ((gcount > 1) && (!multipage)) ? "(blind) " : "", tbuf2,
+                 Name(player), gap, message);
+    else
+       notify_format(player, T("You paged %s%s with '%s'"),
+                 ((gcount > 1) && (!multipage)) ? "(blind) " : "", tbuf2,
+                 message);
+  }
+
+  /* Figure out the 'name' of the player */
+  if (PAGE_ALIASES && (a = atr_get_noparent(player, "ALIAS")))
+    current = tprintf("%s (%s)", Name(player), atr_value(a));
+  else
+    current = (char *) Name(player);
+
+  /* Now, build the thing we want to send to the pagees,
+   * and put it in tbuf */
+
+  /* Build the header */
+  if (key == 1) {
+    safe_str(T("From afar"), tbuf, &tp);
+    if ((gcount > 1) && (multipage)) {
+      safe_str(T(" (to "), tbuf, &tp);
+      safe_str(tbuf2, tbuf, &tp);
+      safe_chr(')', tbuf, &tp);
+    }
+    safe_str(", ", tbuf, &tp);
+    safe_str(current, tbuf, &tp);
+    safe_str(gap, tbuf, &tp);
+  } else {
+    safe_str(current, tbuf, &tp);
+    safe_str(T(" pages"), tbuf, &tp);
+    if ((gcount > 1) && (multipage)) {
+      safe_chr(' ', tbuf, &tp);
+      safe_str(tbuf2, tbuf, &tp);
+    }
+    safe_str(": ", tbuf, &tp);
+  }
+  /* Tack on the message */
+  safe_str(message, tbuf, &tp);
+  *tp = '\0';
+
+  /* Tell each page recipient with tbuf */
+  for (i = 0; i < (gcount+ag_count); i++) {
+
+    if(i >= gcount || gcount == 0)
+           gptr = almost_good + (i-gcount);
+    else
+           gptr = good + i;
+    
+    if (!IsPlayer(player) && Nospoof((dbref)*gptr))
+      notify_format((dbref)*gptr, "[#%d] %s%s", player,(i >= gcount || gcount == 0) ? "Hidden Receive>": "" ,  tbuf);
+    else
+      notify_format((dbref)*gptr, "%s%s", (i >= gcount || gcount == 0) ? "Hidden Receive>": "" ,tbuf);
+
+    if((i < gcount) && gcount != 0)
+      page_return(player, (dbref)*gptr, "Idle", "IDLE", NULL);
+  }
+
+  mush_free((Malloc_t) tbuf, "string");
+  mush_free((Malloc_t) tbuf2, "string");
+  if (msgbuf)
+    mush_free((Malloc_t) msgbuf, "string");
+  if (hp)
+    free((Malloc_t) hp);
+}
+
+
+/** Does a message match a filter pattern on an object?
+ * \param thing object with the filter.
+ * \param msg message to match.
+ * \param flag if 0, filter; if 1, infilter.
+ * \retval 1 message matches filter.
+ * \retval 0 message does not match filter.
+ */
+int
+filter_found(dbref thing, const char *msg, int flag)
+{
+  char *filter;
+  ATTR *a;
+  char *p, *bp;
+  char *temp;                  /* need this so we don't leak memory     
+                                * by failing to free the storage
+                                * allocated by safe_uncompress
+                                */
+  int i;
+  int matched = 0;
+
+  if (!flag)
+    a = atr_get(thing, "FILTER");
+  else
+    a = atr_get(thing, "INFILTER");
+  if (!a)
+    return matched;
+
+  filter = safe_atr_value(a);
+  temp = filter;
+
+  for (i = 0; (i < MAX_ARG) && !matched; i++) {
+    p = bp = filter;
+    process_expression(p, &bp, (char const **) &filter, 0, 0, 0,
+                      PE_NOTHING, PT_COMMA, NULL);
+    if (*filter == ',')
+      *filter++ = '\0';
+    if (*p == '\0' && *filter == '\0') /* No more filters */
+      break;
+    if (*p == '\0')            /* Empty filter */
+      continue;
+    if (AF_Regexp(a))
+      matched = quick_regexp_match(p, msg, AF_Case(a));
+    else
+      matched = local_wild_match_case(p, msg, AF_Case(a));
+  }
+
+  free((Malloc_t) temp);
+  return matched;
+}
+
+/** Copy a message into a buffer, adding an object's PREFIX attribute.
+ * \param thing object with prefix attribute.
+ * \param msg message.
+ * \param tbuf1 destination buffer.
+ */
+void
+make_prefixstr(dbref thing, const char *msg, char *tbuf1)
+{
+  char *bp, *asave;
+  char const *ap;
+  char *wsave[10], *preserve[NUMQ];
+  ATTR *a;
+  int j;
+
+  a = atr_get(thing, "PREFIX");
+
+  bp = tbuf1;
+
+  if (!a) {
+    safe_str("From ", tbuf1, &bp);
+    safe_str(Name(IsExit(thing) ? Source(thing) : thing), tbuf1, &bp);
+    safe_str(", ", tbuf1, &bp);
+  } else {
+    for (j = 0; j < 10; j++) {
+      wsave[j] = global_eval_context.wenv[j];
+      global_eval_context.wenv[j] = NULL;
+    }
+    global_eval_context.wenv[0] = (char *) msg;
+    save_global_regs("prefix_save", preserve);
+    asave = safe_atr_value(a);
+    ap = asave;
+    process_expression(tbuf1, &bp, &ap, thing, orator, orator,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    free((Malloc_t) asave);
+    restore_global_regs("prefix_save", preserve);
+    for (j = 0; j < 10; j++)
+      global_eval_context.wenv[j] = wsave[j];
+    if (bp != tbuf1)
+      safe_chr(' ', tbuf1, &bp);
+  }
+  safe_str(msg, tbuf1, &bp);
+  *bp = '\0';
+  return;
+}
+
+/** pass a message on, for AUDIBLE, prepending a prefix, unless the
+ * message matches a filter pattern.
+ * \param thing object to check for filter and prefix.
+ * \param msg message to pass.
+ */
+void
+propagate_sound(dbref thing, const char *msg)
+{
+  char tbuf1[BUFFER_LEN];
+  dbref loc = Location(thing);
+  dbref pass[2];
+
+  if (!GoodObject(loc))
+    return;
+
+  /* check to see that filter doesn't suppress message */
+  if (filter_found(thing, msg, 0))
+    return;
+
+  /* figure out the prefix */
+  make_prefixstr(thing, msg, tbuf1);
+
+  /* Exits pass the message on to things in the next room.
+   * Objects pass the message on to the things outside.
+   * Don't tell yourself your own message.
+   */
+
+  if (IsExit(thing)) {
+    notify_anything(orator, na_next, &Contents(loc), NULL, NA_INTER_HEAR,
+                   tbuf1);
+  } else {
+    pass[0] = Contents(loc);
+    pass[1] = thing;
+    notify_anything(orator, na_nextbut, pass, NULL, NA_INTER_HEAR, tbuf1);
+  }
+}
+
+static void
+do_audible_stuff(dbref loc, dbref *excs, int numexcs, const char *msg)
+{
+  dbref e;
+  int exclude = 0;
+  int i;
+
+  if (!Audible(loc))
+    return;
+
+  if (IsRoom(loc)) {
+    DOLIST(e, Exits(loc)) {
+      if (Audible(e))
+       propagate_sound(e, msg);
+    }
+  } else {
+    for (i = 0; i < numexcs; i++)
+      if (*(excs + i) == loc)
+       exclude = 1;
+    if (!exclude)
+      propagate_sound(loc, msg);
+  }
+}
+
+/** notify_anthing() wrapper to notify everyone in a location except one
+ * object.
+ * \param first object in location to notify.
+ * \param exception dbref of object not to notify, or NOTHING.
+ * \param msg message to send.
+ * \param flags flags to pass to notify_anything().
+ */
+void
+notify_except(dbref first, dbref exception, const char *msg, int flags)
+{
+  dbref loc;
+  dbref pass[2];
+
+  if (!GoodObject(first))
+    return;
+  loc = Location(first);
+  if (!GoodObject(loc))
+    return;
+
+  if (exception == NOTHING)
+    exception = AMBIGUOUS;
+
+  pass[0] = loc;
+  pass[1] = exception;
+
+  notify_anything(orator, na_except, pass, ns_esnotify, flags, msg);
+
+  do_audible_stuff(loc, &pass[1], 1, msg);
+}
+
+/** notify_anthing() wrapper to notify everyone in a location except two
+ * objects.
+ * \param first object in location to notify.
+ * \param exc1 dbref of one object not to notify, or NOTHING.
+ * \param exc2 dbref of another object not to notify, or NOTHING.
+ * \param msg message to send.
+ * \param flags interaction flags to control type of interaction.
+ */
+void
+notify_except2(dbref first, dbref exc1, dbref exc2, const char *msg, int flags)
+{
+  dbref loc;
+  dbref pass[3];
+
+  if (!GoodObject(first))
+    return;
+  loc = Location(first);
+  if (!GoodObject(loc))
+    return;
+
+  if (exc1 == NOTHING)
+    exc1 = AMBIGUOUS;
+  if (exc2 == NOTHING)
+    exc2 = AMBIGUOUS;
+
+  pass[0] = loc;
+  pass[1] = exc1;
+  pass[2] = exc2;
+
+  notify_anything(orator, na_except2, pass, NULL, flags, msg);
+
+  do_audible_stuff(loc, &pass[1], 2, msg);
+}
+
+/** The think command.
+ * \param player the enactor.
+ * \param message the message to think.
+ */
+void
+do_think(dbref player, const char *message)
+{
+  notify(player, message);
+}
+
+
+/** The emit command.
+ * \verbatim
+ * This implements @emit.
+ * \endverbatim
+ * \param player the enactor.
+ * \param tbuf1 the message to emit.
+ * \param flags bitmask of notification flags.
+ */
+void
+do_emit(dbref player, const char *tbuf1, int flags)
+{
+  dbref loc;
+  int na_flags = NA_INTER_HEAR;
+
+  loc = speech_loc(player);
+  if (!GoodObject(loc))
+    return;
+
+  if(!CanSpeak(player,loc)) {
+    notify(player, T("You may not speak here!"));
+    return;
+  }
+
+  /* notify everybody */
+  if (flags & PEMIT_SPOOF)
+    na_flags |= NA_SPOOF;
+  notify_anything(player, na_loc, &loc, ns_esnotify, na_flags, tbuf1);
+
+  do_audible_stuff(loc, NULL, 0, tbuf1);
+}
+
+/** Remit a message to a single room.
+ * \param player the enactor.
+ * \param target string containing dbref of room to remit in.
+ * \param msg message to emit.
+ * \param flags PEMIT_* flags
+ */
+static void
+do_one_remit(dbref player, const char *target, const char *msg, int flags)
+{
+  dbref room;
+  int na_flags = NA_INTER_HEAR;
+  room = match_result(player, target, NOTYPE, MAT_EVERYTHING);
+  if (!GoodObject(room)) {
+    notify(player, T("I can't find that."));
+  } else {
+    if (IsExit(room)) {
+      notify(player, T("There can't be anything in that!"));
+    } else if (!okay_pemit(player, room)) {
+      notify_format(player,
+                   T("I'm sorry, but %s wishes to be left alone now."),
+                   Name(room));
+    } else if (!CanSpeak(player,room)) {
+      notify(player, T("You may not speak there!"));
+    } else {
+      if (!(flags & PEMIT_SILENT) && (Location(player) != room)) {
+       const char *rmno;
+       rmno = unparse_object(player, room);
+       notify_format(player, T("You remit, \"%s\" in %s"), msg, rmno);
+      }
+      if (flags & PEMIT_SPOOF)
+       na_flags |= NA_SPOOF;
+      notify_anything_loc(player, na_loc, &room, ns_esnotify, na_flags,
+                         msg, room);
+      do_audible_stuff(room, NULL, 0, msg);
+    }
+  }
+}
+
+/** Remit a message
+ * \verbatim
+ * This implements @remit.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 string containing dbref(s) of rooms to remit it.
+ * \param arg2 message to emit.
+ * \param flags for remit.
+ */
+void
+do_remit(dbref player, char *arg1, const char *arg2, int flags)
+{
+  if (flags & PEMIT_LIST) {
+    /* @remit/list */
+    char *current;
+    arg1 = trim_space_sep(arg1, ' ');
+    while ((current = split_token(&arg1, ' ')) != NULL)
+      do_one_remit(player, current, arg2, flags);
+  } else {
+    do_one_remit(player, arg1, arg2, flags);
+  }
+}
+
+/** Emit a message to the absolute location of enactor.
+ * \param player the enactor.
+ * \param tbuf1 message to emit.
+ * \param flags bitmask of notification flags.
+ */
+void
+do_lemit(dbref player, const char *tbuf1, int flags)
+{
+  /* give a message to the "absolute" location of an object */
+  dbref room;
+  int rec = 0;
+  int na_flags = NA_INTER_HEAR;
+  int silent = (flags & PEMIT_SILENT) ? 1 : 0;
+
+  /* only players and things may use this command */
+  if (!Mobile(player))
+    return;
+
+  /* prevent infinite loop if player is inside himself */
+  if (((room = Location(player)) == player) || !GoodObject(room)) {
+    notify(player, T("Invalid container object."));
+    do_rawlog(LT_ERR, T("** BAD CONTAINER **  #%d is inside #%d."), player,
+             room);
+    return;
+  }
+  while (!IsRoom(room) && (rec < 15)) {
+    room = Location(room);
+    rec++;
+  }
+  if (rec > 15) {
+    notify(player, T("Too many containers."));
+    return;
+  } else if (!CanSpeak(player, room)) {
+    notify(player, T("You may not speak there!"));
+  } else {
+    if (!silent && (Location(player) != room))
+      notify_format(player, T("You lemit: \"%s\""), tbuf1);
+    if (flags & PEMIT_SPOOF)
+      na_flags |= NA_SPOOF;
+    notify_anything(player, na_loc, &room, ns_esnotify, na_flags, tbuf1);
+  }
+}
+
+/** notify_anything() function for zone emits.
+ * \param current unused.
+ * \param data array of notify data.
+ * \return last object in zone, or NOTHING.
+ */
+dbref
+na_zemit(dbref current __attribute__ ((__unused__)), void *data)
+{
+  dbref this;
+  dbref room;
+  dbref *dbrefs = data;
+  this = dbrefs[0];
+  do {
+    if (this == NOTHING) {
+      for (room = dbrefs[1]; room < db_top; room++) {
+       if (IsRoom(room) && (Zone(room) == dbrefs[2])
+           && CanSpeak(dbrefs[3], room))
+         break;
+      }
+      if (!(room < db_top))
+       return NOTHING;
+      this = room;
+      dbrefs[1] = room + 1;
+    } else if (IsRoom(this)) {
+      this = Contents(this);
+    } else {
+      this = Next(this);
+    }
+  } while ((this == NOTHING) || (this == dbrefs[4]));
+  dbrefs[0] = this;
+  return this;
+}
+
+/** The zemit command.
+ * \verbatim
+ * This implements @zemit and @nszemit.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 string containing dbref of ZMO.
+ * \param arg2 message to emit.
+ * \param flags bitmask of notificati flags.
+ */
+void
+do_zemit(dbref player, const char *arg1, const char *arg2, int flags)
+{
+  const char *where;
+  dbref zone;
+  dbref pass[5];
+  int na_flags = NA_INTER_HEAR;
+
+  zone = match_result(player, arg1, NOTYPE, MAT_ABSOLUTE);
+  if (!GoodObject(zone)) {
+    notify(player, T("Invalid zone."));
+    return;
+  }
+  if (!controls(player, zone)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* this command is computationally expensive */
+  if (!payfor(player, FIND_COST)) {
+    notify(player, T("Sorry, you don't have enough money to do that."));
+    return;
+  }
+  where = unparse_object(player, zone);
+  notify_format(player, T("You zemit, \"%s\" in zone %s"), arg2, where);
+
+  pass[0] = NOTHING;
+  pass[1] = 0;
+  pass[2] = zone;
+  pass[3] = player;
+  pass[4] = player;
+  if (flags & PEMIT_SPOOF)
+    na_flags |= NA_SPOOF;
+  notify_anything(player, na_zemit, pass, ns_esnotify, na_flags, arg2);
+}
diff --git a/src/sql.c b/src/sql.c
new file mode 100644 (file)
index 0000000..562fb89
--- /dev/null
+++ b/src/sql.c
@@ -0,0 +1,572 @@
+/**
+ * \file sql.c
+ *
+ * \brief Code to support PennMUSH connection to SQL databases.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <string.h>
+#include "conf.h"
+#ifdef HAS_MYSQL
+#include <mysql/mysql.h>
+#include <mysql/errmsg.h>
+#endif
+#include "externs.h"
+#include "access.h"
+#include "log.h"
+#include "parse.h"
+#include "boolexp.h"
+#include "command.h"
+#include "function.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "conf.h"
+#include "confmagic.h"
+
+#ifdef HAS_MYSQL
+static MYSQL *mysql_struct = NULL;
+#define MYSQL_RETRY_TIMES 3
+
+static int safe_sql_query(dbref player, char *q_string, char row_delim,
+                         char field_delim, char *buff, char **bp);
+void sql_timer();
+static int sql_init(void);
+#ifdef _SWMP_
+void sql_startup();
+void sqenv_clear(int);
+static int sqllist_check(dbref thing, dbref player, char type, char end, char *str, int just_match);
+int sql_env[2];
+#endif 
+static char sql_up = 0;
+
+void
+sql_shutdown(void)
+{
+  sql_up = 0;
+  if (!mysql_struct)
+    return;
+  mysql_close(mysql_struct);
+  mysql_struct = NULL;
+}
+
+void sql_startup() {
+       sql_init();
+
+       /* Delete All Entries in Auth_T Table to start with */
+       if(mysql_struct) mysql_query(mysql_struct, "DELETE FROM auth_t");
+       sql_up = 1;
+#ifdef _SWMP_
+       sql_env[0] = -1;
+       sql_env[1] = -1;
+#endif
+}
+
+static int
+sql_init(void)
+{
+  int retries = MYSQL_RETRY_TIMES;
+
+  /* If we are already connected, drop and retry the connection, in
+   * case for some reason the server went away.
+   */
+  if (mysql_struct)
+    sql_shutdown();
+
+  if (!strcasecmp(SQL_PLATFORM, "mysql")) {
+    while (retries && !mysql_struct) {
+      /* Try to connect to the database host. If we have specified
+       * localhost, use the Unix domain socket instead.
+       */
+      mysql_struct = mysql_init(NULL);
+
+      if (!mysql_real_connect
+         (mysql_struct, SQL_HOST, SQL_USER, SQL_PASS, SQL_DB, 3306, 0, 0)) {
+       do_rawlog(LT_ERR, "Failed mysql connection: %s\n",
+                 mysql_error(mysql_struct));
+       sql_shutdown();
+       sleep(1);
+      }
+      retries--;
+    }
+  }
+
+  if (mysql_struct)
+    return 1;
+  else
+    return 0;
+}
+
+#define print_sep(s,b,p)  if (s) safe_chr(s,b,p)
+
+static int
+safe_sql_query(dbref player, char *q_string, char row_delim, char field_delim,
+              char *buff, char **bp)
+{
+  MYSQL_RES *qres;
+  MYSQL_ROW row_p;
+  int num_rows, got_rows, got_fields;
+  int i, j;
+
+  /* If we have no connection, and we don't have auto-reconnect on
+   * (or we try to auto-reconnect and we fail), this is an error
+   * generating a #-1. Notify the player, too, and set the return code.
+   */
+
+  if (!mysql_struct) {
+    sql_init();
+    if (!mysql_struct) {
+      notify(player, "No SQL database connection.");
+      if (buff)
+       safe_str("#-1", buff, bp);
+      return -1;
+    }
+  }
+  if (!q_string || !*q_string)
+    return 0;
+
+  /* Send the query. */
+  got_rows = mysql_real_query(mysql_struct, q_string, strlen(q_string));
+  if (got_rows && (mysql_errno(mysql_struct) == CR_SERVER_GONE_ERROR)) {
+    /* We got this error because the server died unexpectedly
+     * and it shouldn't have. Try repeatedly to reconnect before
+     * giving up and failing. This induces a few seconds of lag,
+     * depending on number of retries
+     */
+    sql_init();
+    if (mysql_struct)
+      got_rows = mysql_real_query(mysql_struct, q_string, strlen(q_string));
+  }
+  if (got_rows) {
+    notify(player, mysql_error(mysql_struct));
+    if (buff)
+      safe_str("#-1", buff, bp);
+    return -1;
+  }
+
+  /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */
+  qres = mysql_store_result(mysql_struct);
+  if (!qres) {
+    if (!mysql_field_count(mysql_struct)) {
+      /* We didn't expect data back, so see if we modified anything */
+      num_rows = mysql_affected_rows(mysql_struct);
+      notify_format(player, "SQL: %d rows affected.", num_rows);
+      return 0;
+    } else {
+      /* Oops, we should have had data! */
+      notify_format(player, "SQL: Error: %s", mysql_error(mysql_struct));
+      return -1;
+    }
+  }
+
+  /* At this point, we know we've done something like SELECT and 
+   * we got results. But just in case...
+   */
+  got_rows = mysql_num_rows(qres);
+  if (!got_rows)
+    return 0;
+  got_fields = mysql_num_fields(qres);
+
+  /* Construct properly-delimited data. */
+  if (buff) {
+    for (i = 0; i < got_rows; i++) {
+      if (i > 0) {
+       print_sep(row_delim, buff, bp);
+      }
+      row_p = mysql_fetch_row(qres);
+      if (row_p) {
+       for (j = 0; j < got_fields; j++) {
+         if (j > 0) {
+           print_sep(field_delim, buff, bp);
+         }
+         if (row_p[j] && *row_p[j])
+           if (safe_str(row_p[j], buff, bp))
+             goto finished;    /* We filled the buffer, best stop */
+       }
+      }
+    }
+  } else {
+    for (i = 0; i < got_rows; i++) {
+      row_p = mysql_fetch_row(qres);
+      if (row_p) {
+       for (j = 0; j < got_fields; j++) {
+         if (row_p[j] && *row_p[j]) {
+           notify(player, tprintf("Row %d, Field %d: %s",
+                                  i + 1, j + 1, row_p[j]));
+         } else {
+           notify(player, tprintf("Row %d, Field %d: NULL", i + 1, j + 1));
+         }
+       }
+      } else {
+       notify(player, tprintf("Row %d: NULL", i + 1));
+      }
+    }
+  }
+
+finished:
+  mysql_free_result(qres);
+  return 0;
+}
+
+
+FUNCTION(fun_sql)
+{
+  char row_delim, field_delim;
+
+  if (!Sql_Ok(executor)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (nargs >= 2) {
+    /* we have a delimiter in args[2]. Got to parse it */
+    char insep[BUFFER_LEN];
+    char *isep = insep;
+    const char *arg2 = args[1];
+    process_expression(insep, &isep, &arg2, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *isep = '\0';
+    strcpy(args[1], insep);
+  }
+
+  if (nargs >= 3) {
+    /* we have a delimiter in args[3]. Got to parse it */
+    char insep[BUFFER_LEN];
+    char *isep = insep;
+    const char *arg3 = args[2];
+    process_expression(insep, &isep, &arg3, executor, caller, enactor,
+                      PE_DEFAULT, PT_DEFAULT, pe_info);
+    *isep = '\0';
+    strcpy(args[2], insep);
+  }
+
+  if (!delim_check(buff, bp, nargs, args, 2, &row_delim))
+    return;
+  if (nargs < 3)
+    field_delim = row_delim;
+  else if (!delim_check(buff, bp, nargs, args, 3, &field_delim))
+    return;
+
+  safe_sql_query(executor, args[0], row_delim, field_delim, buff, bp);
+}
+
+
+FUNCTION(fun_sql_escape)
+{
+  char bigbuff[BUFFER_LEN * 2 + 1];
+
+  if (!Sql_Ok(executor)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (!args[0] || !*args[0])
+    return;
+
+  if (!mysql_struct) {
+    sql_init();
+    if (!mysql_struct) {
+      notify(executor, "No SQL database connection.");
+      safe_str("#-1", buff, bp);
+      return;
+    }
+  }
+  if (mysql_real_escape_string(mysql_struct, bigbuff, args[0], strlen(args[0]))
+      < BUFFER_LEN)
+    safe_str(bigbuff, buff, bp);
+  else
+    safe_str("#-1 TOO LONG", buff, bp);
+}
+
+
+COMMAND (cmd_sql) {
+  safe_sql_query(player, arg_left, ' ', ' ', NULL, NULL);
+}
+#ifdef _SWMP_ /* SWM Protocol */
+/* Do secondly checks on Authentication Table & Query Tables */
+void sql_timer() {
+       /* query variables */
+       MYSQL_RES *qres, *qres2;
+       MYSQL_ROW row_p, row_p2;
+       int num_rows = 0 , got_rows = 0, got_fields = 0;
+       int got_fields2 = 0, got_rows2 = 0;
+       int i, j;
+       char *str;
+       char buf[BUFFER_LEN];
+       dbref player;
+
+       qres = NULL;
+       qres2 = NULL;
+
+       if(!sql_up || !GoodObject(SQLCMD_MasterRoom))
+               return;
+
+        if(!mysql_struct) {
+                if((mudtime % 60) == 0) { /* If its down, try every minute to get it back up. */
+                        sql_init();
+                       if(!mysql_struct)
+                               return;
+               } else return;
+        }
+
+       /* Before we do anything lets delete old shit */
+
+        mysql_query(mysql_struct, tprintf("DELETE FROM auth_t WHERE last < %d", mudtime-1800)); 
+
+       /* We're connected.. Check Authentication Table */
+       got_rows = mysql_query(mysql_struct, "SELECT * FROM auth_t WHERE authcode=0"); 
+
+       if(got_rows != 0) {
+         do_log(LT_ERR, 0, 0, "SQL-> (%s:%d) %s", __FILE__, __LINE__, mysql_error(mysql_struct));
+         goto query_table;
+       }
+
+       qres = mysql_store_result(mysql_struct);
+     if (qres == NULL) {
+        /* Oops, we should have had data! */
+        if(!mysql_field_count(mysql_struct))
+         goto query_table;
+     }
+
+     got_rows = mysql_num_rows(qres);
+     if(got_rows < 1) 
+            goto query_table;
+
+     got_fields = mysql_num_fields(qres);
+
+     if(got_fields < 6 || got_fields > 6) {
+       do_log(LT_ERR, 0, 0, "SQL 'auth_t' table %s fields.", got_fields < 6 ? "not enough" : "too many");
+       goto query_table;
+     }
+
+     for(i = 0, row_p = mysql_fetch_row(qres)  ; i < got_rows ; i++, row_p = mysql_fetch_row(qres)) {
+              if((player = lookup_player(row_p[4])) == NOTHING || 
+                  !Site_Can_Connect((const char *) row_p[1],player ) ||
+                  !password_check(player, (const char *)row_p[5])) {
+                            /* Mark this row as bad auth, can't find user */
+                j =  -1;
+              } else j = 1;
+
+              if( mysql_query(mysql_struct, 
+                  tprintf("UPDATE auth_t SET authcode=%d, pass=\"LOGGEDIN\", user=\"%d\" WHERE last = %d AND id = %d", 
+                    j, player, atoi(row_p[3]), atoi(row_p[0]))) != 0) 
+                do_log(LT_ERR, 0, 0, "SQL-> (%s/%d) %s ", __FILE__, __LINE__, mysql_error(mysql_struct));
+     }
+
+
+query_table:
+     if(qres != NULL) mysql_free_result(qres);
+
+     /* Check Query Table */
+     got_rows = mysql_query(mysql_struct, "SELECT * FROM query_t WHERE io=1");
+
+     if(got_rows != 0) {
+       do_log(LT_ERR, 0, 0, "SQL -> %d/%s : %s", __LINE__, __FILE__, mysql_error(mysql_struct));
+       goto st_finish;
+     }
+
+      qres = mysql_store_result(mysql_struct);
+
+      if(qres == NULL) {
+       do_log(LT_ERR, 0 , 0, "SQL: Error(%s:%d): %s", __FILE__, __LINE__, mysql_error(mysql_struct));
+       return;
+      }
+
+      got_rows = mysql_num_rows(qres);
+
+      if(got_rows < 1)
+       goto st_finish;
+      got_fields = mysql_num_fields(qres);
+      if(!got_fields)
+       goto st_finish;
+      if(got_fields < 4 || got_fields > 4) {
+       do_log(LT_ERR, 0, 0, "SQL 'query_t' table %s fields.", got_fields < 4 ? "not enough" : "too many");
+       goto st_finish;
+      }
+
+      for(i = 0, row_p = mysql_fetch_row(qres); i < got_rows && row_p != NULL ; i++, row_p = mysql_fetch_row(qres)) {
+       sql_env[0] =  atoi(row_p[0]);
+       sql_env[1] = atoi(row_p[1]);
+       /* Load AUTHID we're assosciated with */
+       got_rows2 = mysql_query(mysql_struct, 
+           tprintf("SELECT * FROM auth_t WHERE id = %d", atoi(row_p[1])));
+       if(got_rows2 != 0) {
+         /* Update Return Result As No Authorized user logged in */
+         mysql_query(mysql_struct, tprintf("UPDATE query_t SET io=0, query=\"%s\" WHERE id = %d", mysql_error(mysql_struct),atoi(row_p[0]) ));
+         continue;
+
+       }
+       qres2 = mysql_store_result(mysql_struct);
+
+       /* Not in auth_t table */
+       if(qres2 == NULL) {
+         mysql_query(mysql_struct, tprintf("UPDATE query_t SET io=0, query=\"%s\" WHERE id = %d", mysql_error(mysql_struct), atoi(row_p[0]) ));
+         continue;
+       }
+
+       got_rows2 = mysql_num_rows(qres2);
+
+        if(got_rows2 < 1) {
+          /* Update Return Result As No Authorized user logged in */
+         mysql_free_result(qres2);
+          mysql_query(mysql_struct, tprintf("UPDATE query_t SET io=0, query=\"not logged in\" WHERE id = %d", atoi(row_p[0]) ));
+          continue;
+        }
+       /* Fucked up auth_t Table*/
+       if(got_rows > 1) {
+         mysql_free_result(qres2);
+         mysql_query(mysql_struct, tprintf("UPDATE query_t SET io=0, query=\"ERROR: Identical User IDs logged in.\" WHERE id = %d", atoi(row_p[0]) ));
+         continue;
+       }
+       row_p2 = mysql_fetch_row(qres2);
+
+       player = atoi(row_p2[4]);
+       /* They Aren't Real */
+       if(!GoodObject(player) || !IsPlayer(player)) {
+         mysql_free_result(qres2);
+         mysql_query(mysql_struct, tprintf("UPDATE query_t SET io=0, query=\"ERROR: NonExistent Player ID logged into.\" WHERE id = %d", atoi(row_p[0]) ));
+         /* Delete Auth Table ID Entry , they shouldn't be there*/
+         mysql_query(mysql_struct, tprintf("DELETE FROM auth_t WHERE user=\"%d\"", player));
+         /* Delete Us Too */
+         continue;
+
+       }
+
+       memset(buf, '\0', BUFFER_LEN);
+       strncpy(buf, row_p[3], BUFFER_LEN-1);
+       j = sqllist_check(Contents(SQLCMD_MasterRoom), player, '$', ':', buf, 0);
+   //     j = list_check(Contents(SQLCMD_MasterRoom), player, '$', ':', buf, 0);
+       if(j < 1) {
+         mysql_free_result(qres2);
+         mysql_query(mysql_struct, tprintf("UPDATE query_t SET query=\"-1\", io=0 WHERE id = %d", atoi(row_p[0]) ));
+         continue;
+       }
+       mysql_free_result(qres2);
+
+       sql_env[0] =  -1;
+       sql_env[1] =  -1;
+      }
+      sql_env[0] =  -1;
+      sql_env[1] = -1;
+st_finish:
+     if(qres != NULL) mysql_free_result(qres);
+     if(qres2 != NULL) mysql_free_result(qres2);
+}
+
+int
+sqllist_check(dbref thing, dbref player, char type, char end, char *str,
+           int just_match)
+{
+  int match = 0;
+
+  while (thing != NOTHING) {
+    if (atr_comm_match(thing, player, type, end, str, just_match, NULL, NULL, NULL))
+      match = 1;
+    else 
+       match = 0;
+    
+    thing = Next(thing);
+  }
+  return (match);
+}
+
+
+/* SQ_Respond(<response>) - the way commands queued will respond to this type of query */
+FUNCTION(fun_sq_respond) {
+  MYSQL_RES *qres;
+  int got_rows;
+
+  
+  /* SQL Environment should be set to use this function, if not deny usage */
+  if(sql_env[0] == -1 || sql_env[1] == -1) {
+    safe_str("#-1 NOTHING TO RESPOND TO", buff, bp);
+    return;
+  }
+
+  if(!sql_up) {
+    safe_str("#-1 SQL Server Down", buff, bp);
+    return;
+  }
+
+  if(!mysql_struct) { 
+    sql_init();
+    if(!mysql_struct) {
+      safe_str("#-1 SQL Server Down", buff, bp);
+      return;
+    }
+  }
+
+  /* For some reason we're having some unfreed result here.. */
+  mysql_free_result(mysql_store_result(mysql_struct)); 
+   got_rows = mysql_query(mysql_struct, tprintf("SELECT * FROM query_t WHERE id = %d", sql_env[0])); 
+  if(got_rows != 0) {
+    safe_str(tprintf("#-1 %s", mysql_error(mysql_struct)), buff, bp);
+    return;
+  }
+  mysql_free_result(mysql_store_result(mysql_struct));
+
+  /* Ok.. We do Exist, now Update */
+  
+  got_rows = 
+    mysql_query(mysql_struct, tprintf("UPDATE query_t SET io = 0, query = \"%s\" WHERE id = %d", args[0], sql_env[0])); 
+
+  if(got_rows != 0) {
+    safe_format(buff, bp, "#-1 %s", mysql_error(mysql_struct));
+    return;
+  }
+ safe_chr('1', buff, bp);
+}
+
+void sqenv_clear(int id) {
+
+  sql_env[0] = -1;
+  sql_env[0] = -1;
+
+  if(!mysql_struct) {
+    sql_init();
+    if(!mysql_struct)
+      return;
+  }
+
+  mysql_query(mysql_struct,  tprintf("UPDATE query_t SET io = 0 WHERE id = %d", id));
+}
+
+#else
+FUNCTION(fun_sq_respond) {
+}
+
+void sql_timer() {
+}
+#endif
+
+#else
+
+/* Oops, no SQL */
+
+FUNCTION(fun_sql)
+{
+  safe_str(T(e_disabled), buff, bp);
+}
+
+FUNCTION(fun_sql_escape)
+{
+  safe_str(T(e_disabled), buff, bp);
+}
+
+/* Do secondly checks on Authentication Table & Query Tables */
+void sql_timer() {
+       return;
+}
+
+void
+sql_shutdown(void)
+{
+  return;
+}
+
+#endif
diff --git a/src/strdup.c b/src/strdup.c
new file mode 100644 (file)
index 0000000..b2ebc08
--- /dev/null
@@ -0,0 +1,37 @@
+/**
+ * \file strdup.c
+ *
+ * \brief strdup routine for systems without one.
+ *
+ *
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include "conf.h"
+#include "copyrite.h"
+#include "mymalloc.h"
+#include "confmagic.h"
+
+#ifndef HAS_STRDUP
+char *strdup(const char *s);
+
+/** strdup for systems without one.
+ * \param s string to duplicate.
+ * \return newly-allocated copy of s
+ */
+char *
+strdup(const char *s)
+{
+  int len;
+  char *dup = NULL;
+
+  len = strlen(s) + 1;
+  if ((dup = (char *) malloc(len)) != NULL)
+    memcpy(dup, s, len);
+  return (dup);
+}
+#endif
diff --git a/src/strtree.c b/src/strtree.c
new file mode 100644 (file)
index 0000000..8469aab
--- /dev/null
@@ -0,0 +1,629 @@
+/**
+ * \file strtree.c
+ *
+ * \brief String tables for PennMUSH.
+ *
+ * This is a string table implemented as a red-black tree.
+ *
+ * There are a couple of peculiarities about this implementation:
+ *
+ * (1) Parent pointers are not stored.  Instead, insertion and
+ *     deletion remember the search path used to get to the
+ *     current point in the tree, and use that path to determine
+ *     parents.
+ *
+ * (2) A usage count is kept on items in the tree; when items
+ *     have 127 concurrent uses, they become permanent, and
+ *     may never be fully deleted.
+ *
+ * (3) The red/black coloring is stored as the low order bit
+ *     in the same byte as the usage count (which takes up
+ *     the other 7 bits of that byte).
+ *
+ * (4) The data string is stored directly in the tree node,
+ *     instead of hung in a pointer off the node.  This means
+ *     that the nodes are of variable size.  What fun.
+ *
+ * (5) The strings are stored in the table _unaligned_.  If
+ *     you try to use this for anything other than strings,
+ *     expect alignment problems.
+ *
+ * This string table is _NOT_ reentrant.  If you try to use this
+ * in a multithreaded environment, you will probably get burned.
+ */
+
+#include "copyrite.h"
+
+#include "config.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+
+#include "strtree.h"
+#include "confmagic.h"
+
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 255  /**< Largest unsigned character */
+#endif
+
+/* Various constants.  Their import is either bleedingly obvious
+ * or explained below. */
+#define ST_MAX_DEPTH 64                /**< Max depth of the tree */
+#define ST_RED 1               /**< This node is red */
+#define ST_BLACK 0             /**< This node is black */
+#define ST_COLOR 1             /**< Bit mask for colors */
+#define ST_USE_STEP 2
+#define ST_USE_LIMIT (UCHAR_MAX - ST_USE_STEP + 1)
+
+/* Here we have a global for the path info, just so we don't
+ * eat tons of stack space.  (This code isn't reentrant no
+ * matter where we put this, so might as well save stack.)
+ * The fixed size of this array puts a limit on the maximum
+ * size of the string table... but with ST_MAX_DEPTH == 64,
+ * the tree can hold between 4 billion and 8 quintillion
+ * strings.  I don't think capacity is a problem.
+ */
+static StrNode *path[ST_MAX_DEPTH];
+
+unsigned long st_mem = 0;      /**< Memory used by string trees */
+
+static void st_left_rotate(int tree_depth, StrNode **root);
+static void st_right_rotate(int tree_depth, StrNode **root);
+static void st_print_tree(StrNode *node, int tree_depth, int lead);
+static void st_traverse_stats
+  (StrNode *node, int *maxdepth, int *mindepth, int *avgdepth, int *leaves,
+   unsigned long *perms, unsigned long *nperms);
+
+void st_stats_header(dbref player);
+void st_stats(dbref player, StrTree *root, const char *name);
+static void delete_node(StrNode *node);
+
+/** Initialize a string tree.
+ * \param root pointer to root of string tree.
+ */
+void
+st_init(StrTree *root)
+{
+  assert(root);
+  root->root = NULL;
+  root->count = 0;
+  root->mem = 0;
+}
+
+static void
+delete_node(StrNode *node)
+{
+  if (node->left)
+    delete_node(node->left);
+  if (node->right)
+    delete_node(node->right);
+  mush_free(node, "StrNode");
+}
+
+
+/** Clear a string tree.
+ * \param root pointer to root of string tree.
+ */
+void
+st_flush(StrTree *root)
+{
+  if (!root->root)
+    return;
+  delete_node(root->root);
+  root->root = NULL;
+  root->count = 0;
+  root->mem = 0;
+}
+
+/** Header for string tree stats.
+ * \param player player to notify with header.
+ */
+void
+st_stats_header(dbref player)
+{
+  notify(player,
+        "Tree       Entries  Leaves MinDep  Max  Avg   PermEnt     AvgTmpC ~Memory");
+}
+
+/** Statistics about the tree.
+ * \param player player to notify with header.
+ * \param root pointer to root of string tree.
+ * \param name name of string tree, for row header.
+ */
+void
+st_stats(dbref player, StrTree *root, const char *name)
+{
+  unsigned long bytes;
+  int maxdepth = 0, mindepth = 0, avgdepth = 0, leaves = 0;
+  unsigned long perms = 0, nperms = 0;
+
+  bytes = (sizeof(StrNode) - BUFFER_LEN) * root->count + root->mem;
+  st_traverse_stats(root->root, &maxdepth, &mindepth, &avgdepth, &leaves,
+                   &perms, &nperms);
+  notify_format(player, "%-10s %7d %7d %6d %4d %4d %9lu %11.3f %7lu",
+               name, root->count, leaves, mindepth, maxdepth,
+               avgdepth, perms,
+               ((double) nperms / (double) (root->count - perms)), bytes);
+}
+
+/* Tree rotations.  These preserve left-to-right ordering,
+ * while modifying depth.
+ */
+static void
+st_left_rotate(int tree_depth, StrNode **root)
+{
+  StrNode *x;
+  StrNode *y;
+
+  x = path[tree_depth];
+  assert(x);
+  y = x->right;
+  assert(y);
+  x->right = y->left;
+  y->left = x;
+  if (*root == x)
+    *root = y;
+  else if (path[tree_depth - 1]->left == x)
+    path[tree_depth - 1]->left = y;
+  else
+    path[tree_depth - 1]->right = y;
+}
+
+static void
+st_right_rotate(int tree_depth, StrNode **root)
+{
+  StrNode *y;
+  StrNode *x;
+
+  y = path[tree_depth];
+  assert(y);
+  x = y->left;
+  assert(x);
+  y->left = x->right;
+  x->right = y;
+  if (*root == y)
+    *root = x;
+  else if (path[tree_depth - 1]->right == y)
+    path[tree_depth - 1]->right = x;
+  else
+    path[tree_depth - 1]->left = x;
+}
+
+/** String tree insert.  If the string is already in the tree, bump its usage
+ * count and return the tree's version.  Otherwise, allocate a new tree
+ * node, copy the string into the node, insert it into the tree, and
+ * return the new node's string.
+ * \param s string to insert in tree.
+ * \param root pointer to root of string tree.
+ * \return string inserted or NULL.
+ */
+char const *
+st_insert(char const *s, StrTree *root)
+{
+  int tree_depth;
+  StrNode *n;
+  int cmp = 0;
+  size_t keylen;
+
+  assert(s);
+
+  /* Hunt for the string in the tree. */
+  tree_depth = 0;
+  n = root->root;
+  while (n && (cmp = strcmp(s, n->string))) {
+    path[tree_depth] = n;
+    tree_depth++;
+    assert(tree_depth < ST_MAX_DEPTH);
+    if (cmp < 0)
+      n = n->left;
+    else
+      n = n->right;
+  }
+
+  if (n) {
+    /* Found the string, so bump the usage and return. */
+    if (n->info < ST_USE_LIMIT)
+      n->info += ST_USE_STEP;
+    return n->string;
+  }
+
+  /* Need a new node.  Allocate and initialize it. */
+  keylen = strlen(s) + 1;
+  n = mush_malloc(sizeof(StrNode) - BUFFER_LEN + keylen, "StrNode");
+  if (!n)
+    return NULL;
+  memcpy(n->string, s, keylen);
+  n->left = NULL;
+  n->right = NULL;
+  if (tree_depth == 0) {
+    /* This is the first insertion!  Just stick it at the root
+     * and get out of here. */
+    root->root = n;
+    n->info = ST_BLACK + ST_USE_STEP;
+    return n->string;
+  }
+  n->info = ST_RED + ST_USE_STEP;
+
+  /* Foo.  Have to do a complex insert.  Well, start by putting
+   * the new node at the tip of an appropriate branch. */
+  path[tree_depth] = n;
+  tree_depth--;
+  if (cmp < 0)
+    path[tree_depth]->left = n;
+  else
+    path[tree_depth]->right = n;
+
+  /* I rely on ST_RED != 0 and ST_BLACK == 0 in my bitwise ops. */
+  assert(ST_RED);
+
+  /* Sigh.  Fix the tree to maintain the red-black properties. */
+  while (tree_depth > 0 && (path[tree_depth]->info & ST_COLOR) == ST_RED) {
+    /* We have a double-red.  Blitch.  Now we have some mirrored
+     * cases to look for, so stuff is duplicated left/right. */
+    if (path[tree_depth] == path[tree_depth - 1]->left) {
+      StrNode *y;
+      y = path[tree_depth - 1]->right;
+      if (y && (y->info & ST_COLOR) == ST_RED) {
+       /* Hmph.  Uncle is red.  Push the mess up the tree. */
+       path[tree_depth]->info &= ~ST_RED;
+       y->info &= ~ST_RED;
+       tree_depth--;
+       path[tree_depth]->info |= ST_RED;
+       tree_depth--;
+      } else {
+       /* Okay, uncle is black.  We can fix everything, now. */
+       if (path[tree_depth + 1] == path[tree_depth]->right) {
+         st_left_rotate(tree_depth, &root->root);
+         path[tree_depth + 1]->info &= ~ST_RED;
+       } else {
+         path[tree_depth]->info &= ~ST_RED;
+       }
+       path[tree_depth - 1]->info |= ST_RED;
+       st_right_rotate(tree_depth - 1, &root->root);
+       break;
+      }
+    } else {
+      StrNode *y;
+      y = path[tree_depth - 1]->left;
+      if (y && (y->info & ST_COLOR) == ST_RED) {
+       /* Hmph.  Uncle is red.  Push the mess up the tree. */
+       path[tree_depth]->info &= ~ST_RED;
+       y->info &= ~ST_RED;
+       tree_depth--;
+       path[tree_depth]->info |= ST_RED;
+       tree_depth--;
+      } else {
+       /* Okay, uncle is black.  We can fix everything, now. */
+       if (path[tree_depth + 1] == path[tree_depth]->left) {
+         st_right_rotate(tree_depth, &root->root);
+         path[tree_depth + 1]->info &= ~ST_RED;
+       } else {
+         path[tree_depth]->info &= ~ST_RED;
+       }
+       path[tree_depth - 1]->info |= ST_RED;
+       st_left_rotate(tree_depth - 1, &root->root);
+       break;
+      }
+    }
+  }
+
+  /* The tree is now red-black true again.  Make the root black
+   * just for convenience. */
+  root->root->info &= ~ST_RED;
+  root->count++;
+  root->mem += strlen(s) + 1;
+  return n->string;
+}
+
+/** Tree find.  Basically the first part of insert. 
+ * \param s string to find.
+ * \param root pointer to root of string tree.
+ * \return string if found, or NULL.
+ */
+char const *
+st_find(char const *s, StrTree *root)
+{
+  int tree_depth;
+  StrNode *n;
+  int cmp;
+
+  assert(s);
+
+  /* Hunt for the string in the tree. */
+  tree_depth = 0;
+  n = root->root;
+  while (n && (cmp = strcmp(s, n->string))) {
+    path[tree_depth] = n;
+    tree_depth++;
+    assert(tree_depth < ST_MAX_DEPTH);
+    if (cmp < 0)
+      n = n->left;
+    else
+      n = n->right;
+  }
+
+  if (n)
+    return n->string;
+  return NULL;
+}
+
+/** Tree delete.  Decrement the usage count of the string, unless the
+ * count is pegged.  If count reaches zero, delete.
+ * \param s string to find and delete.
+ * \param root pointer to root of string tree.
+ */
+void
+st_delete(char const *s, StrTree *root)
+{
+  int tree_depth;
+  StrNode *y;
+  StrNode *x;
+  int cmp;
+
+  assert(s);
+
+  /* Hunt for the string in the tree. */
+  tree_depth = 0;
+  y = root->root;
+  while (y && (cmp = strcmp(s, y->string))) {
+    path[tree_depth] = y;
+    tree_depth++;
+    assert(tree_depth < ST_MAX_DEPTH);
+    if (cmp < 0)
+      y = y->left;
+    else
+      y = y->right;
+  }
+
+  /* If it wasn't in the tree, we're done. */
+  if (!y)
+    return;
+
+  /* If this node is permanent, then we're done. */
+  if (y->info >= ST_USE_LIMIT)
+    return;
+
+  /* If this node has been used more than once, then decrement and exit. */
+  if (y->info >= ST_USE_STEP * 2) {
+    y->info -= ST_USE_STEP;
+    return;
+  }
+  if (y->left && y->right) {
+    /* It has two children.  We need to swap with successor. */
+    int z_depth;
+    char color;
+    /* Record where we are. */
+    z_depth = tree_depth;
+    /* Find the successor. */
+    path[tree_depth] = y;
+    tree_depth++;
+    y = y->right;
+    while (y->left) {
+      path[tree_depth] = y;
+      tree_depth++;
+      y = y->left;
+    }
+    /* Fix the parent's pointer... */
+    if (z_depth == 0)
+      root->root = y;
+    else if (path[z_depth - 1]->left == path[z_depth])
+      path[z_depth - 1]->left = y;
+    else
+      path[z_depth - 1]->right = y;
+    /* Swap out the path pieces. */
+    path[tree_depth] = path[z_depth];
+    path[z_depth] = y;
+    y = path[tree_depth];
+    /* Swap out the child pointers */
+    path[z_depth]->left = y->left;
+    y->left = NULL;
+    y->right = path[z_depth]->right;
+    path[z_depth]->right = path[z_depth + 1];
+    /* Fix the child pointer of the parent of the replacement */
+    if (tree_depth > z_depth + 1)
+      path[tree_depth - 1]->left = y;
+    else
+      path[tree_depth - 1]->right = y;
+    /* Swap out the color */
+    color = y->info & ST_COLOR;
+    y->info = (y->info & ~ST_COLOR) | (path[z_depth]->info & ST_COLOR);
+    path[z_depth]->info = (path[z_depth]->info & ~ST_COLOR) | color;
+  }
+
+  /* We are now looking at a node with less than two children */
+  assert(!y->left || !y->right);
+  /* Move the child (if any) up */
+  if (y->left)
+    x = y->left;
+  else
+    x = y->right;
+  if (root->root == y)
+    root->root = x;
+  else if (path[tree_depth - 1]->left == y)
+    path[tree_depth - 1]->left = x;
+  else
+    path[tree_depth - 1]->right = x;
+  if ((y->info & ST_COLOR) == ST_BLACK) {
+    while (x != root->root && (!x || (x->info & ST_COLOR) == ST_BLACK)) {
+      if (x == path[tree_depth - 1]->left) {
+       StrNode *w = path[tree_depth - 1]->right;
+       assert(w);
+       if (w && (w->info & ST_COLOR) == ST_RED) {
+         w->info &= ~ST_RED;
+         path[tree_depth - 1]->info |= ST_RED;
+         st_left_rotate(tree_depth - 1, &root->root);
+         path[tree_depth] = path[tree_depth - 1];
+         path[tree_depth - 1] = w;
+         tree_depth++;
+         w = path[tree_depth - 1]->right;
+         assert(w);
+       }
+       assert((w->info & ST_COLOR) == ST_BLACK);
+       if ((!w->left || (w->left->info & ST_COLOR) == ST_BLACK) &&
+           (!w->right || (w->right->info & ST_COLOR) == ST_BLACK)) {
+         w->info |= ST_RED;
+         x = path[tree_depth - 1];
+         tree_depth--;
+       } else {
+         if (!w->right || (w->right->info & ST_COLOR) == ST_BLACK) {
+           assert(w->left);
+           w->left->info &= ~ST_RED;
+           path[tree_depth] = w;
+           st_right_rotate(tree_depth, &root->root);
+           w = path[tree_depth - 1]->right;
+           assert(w);
+         }
+         w->info =
+           (w->info & ~ST_COLOR) | (path[tree_depth - 1]->info & ST_COLOR);
+         path[tree_depth - 1]->info &= ~ST_RED;
+         assert(w->right);
+         w->right->info &= ~ST_RED;
+         st_left_rotate(tree_depth - 1, &root->root);
+         x = root->root;
+       }
+      } else {
+       StrNode *w = path[tree_depth - 1]->left;
+       assert(w);
+       if (w && (w->info & ST_COLOR) == ST_RED) {
+         w->info &= ~ST_RED;
+         path[tree_depth - 1]->info |= ST_RED;
+         st_right_rotate(tree_depth - 1, &root->root);
+         path[tree_depth] = path[tree_depth - 1];
+         path[tree_depth - 1] = w;
+         tree_depth++;
+         w = path[tree_depth - 1]->left;
+         assert(w);
+       }
+       assert((w->info & ST_COLOR) == ST_BLACK);
+       if ((!w->right || (w->right->info & ST_COLOR) == ST_BLACK) &&
+           (!w->left || (w->left->info & ST_COLOR) == ST_BLACK)) {
+         w->info |= ST_RED;
+         x = path[tree_depth - 1];
+         tree_depth--;
+       } else {
+         if (!w->left || (w->left->info & ST_COLOR) == ST_BLACK) {
+           assert(w->right);
+           w->right->info &= ~ST_RED;
+           path[tree_depth] = w;
+           st_left_rotate(tree_depth, &root->root);
+           w = path[tree_depth - 1]->left;
+           assert(w);
+         }
+         w->info =
+           (w->info & ~ST_COLOR) | (path[tree_depth - 1]->info & ST_COLOR);
+         path[tree_depth - 1]->info &= ~ST_RED;
+         assert(w->left);
+         w->left->info &= ~ST_RED;
+         st_right_rotate(tree_depth - 1, &root->root);
+         x = root->root;
+       }
+      }
+    }
+    if (x)
+      x->info &= ~ST_RED;
+  }
+  root->mem -= strlen(s) + 1;
+  mush_free(y, "StrNode");
+  root->count--;
+}
+
+/* Print the tree, for debugging purposes. */
+static void
+st_print_tree(StrNode *node, int tree_depth, int lead)
+{
+  static char leader[ST_MAX_DEPTH * 2 + 1];
+  char tmp;
+  static StrNode *print_path[ST_MAX_DEPTH];
+  int looped;
+
+  if (tree_depth == 0)
+    memset(leader, ' ', sizeof leader);
+
+  print_path[tree_depth] = node;
+  looped = tree_depth;
+  while (looped--)
+    if (print_path[looped] == node)
+      break;
+  looped++;
+
+  if (node->left && !looped)
+    st_print_tree(node->left, tree_depth + 1, '.');
+  tmp = leader[tree_depth * 2];
+  leader[tree_depth * 2] = '\0';
+  printf("%s%c-+ %c %d %s%s\n", leader, lead,
+        (node->info & ST_COLOR) ? 'r' : 'b', node->info / ST_USE_STEP,
+        node->string, looped ? " -LOOPING" : "");
+  leader[tree_depth * 2] = ' ' + '|' - tmp;
+  leader[0] = ' ';
+  if (node->right && !looped)
+    st_print_tree(node->right, tree_depth + 1, '`');
+}
+
+/** Print a string tree (for debugging).
+ * \param root pointer to root of string tree.
+ */
+void
+st_print(StrTree *root)
+{
+  printf("---- print\n");
+  if (root->root)
+    st_print_tree(root->root, 0, '-');
+  printf("----\n");
+}
+
+static void st_depth_helper
+  (StrNode *node, int *maxdepth, int *mindepth, int *avgdepth, int *leaves,
+   unsigned long *perms, unsigned long *nperms, int count);
+static void
+st_depth_helper(StrNode *node, int *maxdepth, int *mindepth,
+               int *avgdepth, int *leaves, unsigned long *perms,
+               unsigned long *nperms, int count)
+{
+  if (!node)
+    return;
+
+  if (node->info >= ST_USE_LIMIT)
+    (*perms)++;
+  else
+    (*nperms) += node->info;
+
+  if (count > *maxdepth)
+    *maxdepth = count;
+
+  if (node->left) {
+    /* Inner node */
+    st_depth_helper(node->left, maxdepth, mindepth, avgdepth, leaves, perms,
+                   nperms, count + 1);
+  }
+  if (node->right) {
+    /* Inner node */
+    st_depth_helper(node->right, maxdepth, mindepth, avgdepth, leaves, perms,
+                   nperms, count + 1);
+  }
+  if (!node->left && !node->right) {   /* This is a leaf node */
+    (*leaves)++;
+    (*avgdepth) += count;
+    if (*mindepth > count)
+      *mindepth = count;
+  }
+}
+
+
+/* Find the depth and number of permanment nodes */
+static void
+st_traverse_stats(StrNode *node, int *maxdepth, int *mindepth, int *avgdepth,
+                 int *leaves, unsigned long *perms, unsigned long *nperms)
+{
+  *maxdepth = 0;
+  *mindepth = node ? (ST_MAX_DEPTH + 1) : 0;
+  *perms = 0;
+  *nperms = 0;
+  *avgdepth = 0;
+  *leaves = 0;
+  st_depth_helper(node, maxdepth, mindepth, avgdepth, leaves, perms, nperms, 1);
+  if (*avgdepth)
+    *avgdepth = *avgdepth / *leaves;
+}
diff --git a/src/strutil.c b/src/strutil.c
new file mode 100644 (file)
index 0000000..2d6e605
--- /dev/null
@@ -0,0 +1,1943 @@
+/**
+ * \file strutil.c
+ *
+ * \brief String utilities for PennMUSH.
+ *
+ *
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <limits.h>
+#include "copyrite.h"
+#include "conf.h"
+#include "case.h"
+#include "ansi.h"
+#include "pueblo.h"
+#include "parse.h"
+#include "externs.h"
+#include "mymalloc.h"
+#include "log.h"
+#include "confmagic.h"
+
+char *next_token(char *str, char sep);
+int format_long(long val, char *buff, char **bp, int maxlen, int base);
+static char *
+mush_strndup(const char *src, size_t len, const char *check)
+  __attribute_malloc__;
+
+/* Duplicate the first len characters of s */
+    static char *mush_strndup(const char *src, size_t len, const char *check)
+{
+  char *copy;
+  size_t rlen = strlen(src);
+
+  if (rlen < len)
+    len = rlen;
+
+  copy = mush_malloc(len + 1, check);
+  if (copy) {
+    memcpy(copy, src, len);
+    copy[len] = '\0';
+  }
+
+  return copy;
+}
+
+/** Our version of strdup, with memory leak checking.
+ * This should be used in preference to strdup, and in assocation
+ * with mush_free().
+ * \param s string to duplicate.
+ * \param check label for memory checking.
+ * \return newly allocated copy of s.
+ */
+char *
+mush_strdup(const char *s, const char *check __attribute__ ((__unused__)))
+{
+  char *x;
+
+#ifdef HAS_STRDUP
+  x = strdup(s);
+  if (x)
+    add_check(check);
+#else
+
+  size_t len = strlen(s) + 1;
+  x = mush_malloc(len, check);
+  if (x)
+    memcpy(x, s, len);
+#endif
+  return x;
+}
+
+/** Return the string chopped at lim characters.
+ * \param str string to chop.
+ * \param lim character at which to chop the string.
+ * \return statically allocated buffer with chopped string.
+ */
+char *
+chopstr(const char *str, size_t lim)
+{
+  static char tbuf1[BUFFER_LEN];
+  if (strlen(str) <= lim)
+    return (char *) str;
+  strncpy(tbuf1, str, lim);
+  tbuf1[lim] = '\0';
+  return tbuf1;
+}
+
+
+#ifndef HAS_STRCASECMP
+#ifndef WIN32
+/** strcasecmp for systems without it.
+ * \param s1 one string to compare.
+ * \param s2 another string to compare.
+ * \retval -1 s1 is less than s2.
+ * \retval 0 s1 equals s2
+ * \retval 1 s1 is greater than s2.
+ */
+int
+strcasecmp(const char *s1, const char *s2)
+{
+  while (*s1 && *s2 && DOWNCASE(*s1) == DOWNCASE(*s2))
+    s1++, s2++;
+
+  return (DOWNCASE(*s1) - DOWNCASE(*s2));
+}
+
+/** strncasecmp for systems without it.
+ * \param s1 one string to compare.
+ * \param s2 another string to compare.
+ * \param n number of characters to compare.
+ * \retval -1 s1 is less than s2.
+ * \retval 0 s1 equals s2
+ * \retval 1 s1 is greater than s2.
+ */
+int
+strncasecmp(const char *s1, const char *s2, size_t n)
+{
+  for (; 0 < n; ++s1, ++s2, --n)
+    if (DOWNCASE(*s1) != DOWNCASE(*s2))
+      return DOWNCASE(*s1) - DOWNCASE(*s2);
+    else if (*s1 == 0)
+      return 0;
+  return 0;
+
+}
+#endif                         /* !WIN32 */
+#endif                         /* !HAS_STRCASECMP */
+
+/** Does string begin with prefix? 
+ * This comparison is case-insensitive.
+ * \param string to check.
+ * \param prefix to check against.
+ * \retval 1 string begins with prefix.
+ * \retval 0 string does not begin with prefix.
+ */
+int
+string_prefix(const char *RESTRICT string, const char *RESTRICT prefix)
+{
+  if (!string || !prefix)
+    return 0;
+  while (*string && *prefix && DOWNCASE(*string) == DOWNCASE(*prefix))
+    string++, prefix++;
+  return *prefix == '\0';
+}
+
+/** Match a substring at the start of a word in a string, case-insensitively.
+ * \param src a string of words to match against.
+ * \param sub a prefix to match against the start of a word in string.
+ * \return pointer into src at the matched word, or NULL.
+ */
+const char *
+string_match(const char *src, const char *sub)
+{
+  if (!src || !sub)
+    return 0;
+
+  if (*sub != '\0') {
+    while (*src) {
+      if (string_prefix(src, sub))
+       return src;
+      /* else scan to beginning of next word */
+      while (*src && (isalpha((unsigned char) *src)
+                     || isdigit((unsigned char) *src)))
+       src++;
+      while (*src && !isalpha((unsigned char) *src)
+            && !isdigit((unsigned char) *src))
+       src++;
+    }
+  }
+  return NULL;
+}
+
+/** Return an initial-cased version of a string in a static buffer.
+ * \param s string to initial-case.
+ * \return pointer to a static buffer containing the initial-cased version.
+ */
+char *
+strinitial(const char *s)
+{
+  static char buf1[BUFFER_LEN];
+  char *p;
+
+  if (!s || !*s) {
+    buf1[0] = '\0';
+    return buf1;
+  }
+  strcpy(buf1, s);
+  for (p = buf1; *p; p++)
+    *p = DOWNCASE(*p);
+  buf1[0] = UPCASE(buf1[0]);
+  return buf1;
+}
+
+/** Return an uppercased version of a string in a static buffer.
+ * \param s string to uppercase.
+ * \return pointer to a static buffer containing the uppercased version.
+ */
+char *
+strupper(const char *s)
+{
+  static char buf1[BUFFER_LEN];
+  char *p;
+
+  if (!s || !*s) {
+    buf1[0] = '\0';
+    return buf1;
+  }
+  strcpy(buf1, s);
+  for (p = buf1; *p; p++)
+    *p = UPCASE(*p);
+  return buf1;
+}
+
+/** Return a lowercased version of a string in a static buffer.
+ * \param s string to lowercase.
+ * \return pointer to a static buffer containing the lowercased version.
+ */
+char *
+strlower(const char *s)
+{
+  static char buf1[BUFFER_LEN];
+  char *p;
+
+  if (!s || !*s) {
+    buf1[0] = '\0';
+    return buf1;
+  }
+  strcpy(buf1, s);
+  for (p = buf1; *p; p++)
+    *p = DOWNCASE(*p);
+  return buf1;
+}
+
+/** Modify a string in-place to uppercase.
+ * \param s string to uppercase.
+ * \return s, now modified to be all uppercase.
+ */
+char *
+upcasestr(char *s)
+{
+  char *p;
+  for (p = s; p && *p; p++)
+    *p = UPCASE(*p);
+  return s;
+}
+
+/** Safely add an accented string to a buffer.
+ * \param base base string to which accents are applied.
+ * \param tmplate accent template string.
+ * \param len length of base (and tmplate).
+ * \param buff pointer to buffer to store accented string.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 1 failed to store entire string.
+ * \retval 0 success.
+ */
+int
+safe_accent(const char *RESTRICT base, const char *RESTRICT tmplate, size_t len,
+           char *buff, char **bp)
+{
+  /* base and tmplate must be the same length */
+  size_t n;
+  unsigned char c;
+
+  for (n = 0; n < len; n++) {
+    switch (base[n]) {
+    case 'A':
+      switch (tmplate[n]) {
+      case '`':
+       c = 192;
+       break;
+      case '\'':
+       c = 193;
+       break;
+      case '^':
+       c = 194;
+       break;
+      case '~':
+       c = 195;
+       break;
+      case ':':
+       c = 196;
+       break;
+      case 'o':
+       c = 197;
+       break;
+      case 'e':
+      case 'E':
+       c = 198;
+       break;
+      default:
+       c = 'A';
+      }
+      break;
+    case 'a':
+      switch (tmplate[n]) {
+      case '`':
+       c = 224;
+       break;
+      case '\'':
+       c = 225;
+       break;
+      case '^':
+       c = 226;
+       break;
+      case '~':
+       c = 227;
+       break;
+      case ':':
+       c = 228;
+       break;
+      case 'o':
+       c = 229;
+       break;
+      case 'e':
+      case 'E':
+       c = 230;
+       break;
+      default:
+       c = 'a';
+      }
+      break;
+    case 'C':
+      if (tmplate[n] == ',')
+       c = 199;
+      else
+       c = 'C';
+      break;
+    case 'c':
+      if (tmplate[n] == ',')
+       c = 231;
+      else
+       c = 'c';
+      break;
+    case 'E':
+      switch (tmplate[n]) {
+      case '`':
+       c = 200;
+       break;
+      case '\'':
+       c = 201;
+       break;
+      case '^':
+       c = 202;
+       break;
+      case ':':
+       c = 203;
+       break;
+      default:
+       c = 'E';
+      }
+      break;
+    case 'e':
+      switch (tmplate[n]) {
+      case '`':
+       c = 232;
+       break;
+      case '\'':
+       c = 233;
+       break;
+      case '^':
+       c = 234;
+       break;
+      case ':':
+       c = 235;
+       break;
+      default:
+       c = 'e';
+      }
+      break;
+    case 'I':
+      switch (tmplate[n]) {
+      case '`':
+       c = 204;
+       break;
+      case '\'':
+       c = 205;
+       break;
+      case '^':
+       c = 206;
+       break;
+      case ':':
+       c = 207;
+       break;
+      default:
+       c = 'I';
+      }
+      break;
+    case 'i':
+      switch (tmplate[n]) {
+      case '`':
+       c = 236;
+       break;
+      case '\'':
+       c = 237;
+       break;
+      case '^':
+       c = 238;
+       break;
+      case ':':
+       c = 239;
+       break;
+      default:
+       c = 'i';
+      }
+      break;
+    case 'N':
+      if (tmplate[n] == '~')
+       c = 209;
+      else
+       c = 'N';
+      break;
+    case 'n':
+      if (tmplate[n] == '~')
+       c = 241;
+      else
+       c = 'n';
+      break;
+    case 'O':
+      switch (tmplate[n]) {
+      case '`':
+       c = 210;
+       break;
+      case '\'':
+       c = 211;
+       break;
+      case '^':
+       c = 212;
+       break;
+      case '~':
+       c = 213;
+       break;
+      case ':':
+       c = 214;
+       break;
+      default:
+       c = 'O';
+      }
+      break;
+    case 'o':
+      switch (tmplate[n]) {
+      case '&':
+       c = 240;
+       break;
+      case '`':
+       c = 242;
+       break;
+      case '\'':
+       c = 243;
+       break;
+      case '^':
+       c = 244;
+       break;
+      case '~':
+       c = 245;
+       break;
+      case ':':
+       c = 246;
+       break;
+      default:
+       c = 'o';
+      }
+      break;
+    case 'U':
+      switch (tmplate[n]) {
+      case '`':
+       c = 217;
+       break;
+      case '\'':
+       c = 218;
+       break;
+      case '^':
+       c = 219;
+       break;
+      case ':':
+       c = 220;
+       break;
+      default:
+       c = 'U';
+      }
+      break;
+    case 'u':
+      switch (tmplate[n]) {
+      case '`':
+       c = 249;
+       break;
+      case '\'':
+       c = 250;
+       break;
+      case '^':
+       c = 251;
+       break;
+      case ':':
+       c = 252;
+       break;
+      default:
+       c = 'u';
+      }
+      break;
+    case 'Y':
+      if (tmplate[n] == '\'')
+       c = 221;
+      else
+       c = 'Y';
+      break;
+    case 'y':
+      if (tmplate[n] == '\'')
+       c = 253;
+      else if (tmplate[n] == ':')
+       c = 255;
+      else
+       c = 'y';
+      break;
+    case '?':
+      if (tmplate[n] == 'u')
+       c = 191;
+      else
+       c = '?';
+      break;
+    case '!':
+      if (tmplate[n] == 'u')
+       c = 161;
+      else
+       c = '!';
+      break;
+    case '<':
+      if (tmplate[n] == '"')
+       c = 171;
+      else
+       c = '<';
+      break;
+    case '>':
+      if (tmplate[n] == '"')
+       c = 187;
+      else
+       c = '>';
+      break;
+    case 's':
+      if (tmplate[n] == 'B')
+       c = 223;
+      else
+       c = 's';
+      break;
+    case 'p':
+      if (tmplate[n] == '|')
+       c = 254;
+      else
+       c = 'p';
+      break;
+    case 'P':
+      if (tmplate[n] == '|')
+       c = 222;
+      else
+       c = 'P';
+      break;
+    case 'D':
+      if (tmplate[n] == '-')
+       c = 208;
+      else
+       c = 'D';
+      break;
+    default:
+      c = base[n];
+    }
+    if (isprint(c)) {
+      if (safe_chr((char) c, buff, bp))
+       return 1;
+    } else {
+      if (safe_chr(base[n], buff, bp))
+       return 1;
+    }
+  }
+  return 0;
+}
+
+
+/** Define the args used in APPEND_TO_BUF */
+#define APPEND_ARGS int len, blen, clen
+/** Append a string c to the end of buff, starting at *bp.
+ * This macro is used by the safe_XXX functions.
+ */
+#define APPEND_TO_BUF \
+  /* Trivial cases */  \
+  if (c[0] == '\0') \
+    return 0; \
+  /* The array is at least two characters long here */ \
+  if (c[1] == '\0') \
+    return safe_chr(c[0], buff, bp); \
+  len = strlen(c); \
+  blen = *bp - buff; \
+  if (blen > (BUFFER_LEN - 1)) \
+    return len; \
+  if ((len + blen) <= (BUFFER_LEN - 1)) \
+    clen = len; \
+  else \
+    clen = (BUFFER_LEN - 1) - blen; \
+  memcpy(*bp, c, clen); \
+  *bp += clen; \
+  return len - clen
+
+
+/** Safely store a formatted string into a buffer.
+ * This is a better way to do safe_str(tprintf(fmt,...),buff,bp)
+ * \param buff buffer to store formatted string.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \param fmt format string.
+ * \return number of characters left over, or 0 for success.
+ */
+int
+safe_format(char *buff, char **bp, const char *RESTRICT fmt, ...)
+{
+  APPEND_ARGS;
+#ifdef HAS_VSNPRINTF
+  char c[BUFFER_LEN];
+#else
+  char c[BUFFER_LEN * 3];
+#endif
+  va_list args;
+
+  va_start(args, fmt);
+
+#ifdef HAS_VSNPRINTF
+  vsnprintf(c, sizeof c, fmt, args);
+#else
+  vsprintf(c, fmt, args);
+#endif
+  c[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+
+  APPEND_TO_BUF;
+}
+
+/** Safely store an integer into a buffer.
+ * \param i integer to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \return 0 on success, non-zero on failure.
+ */
+int
+safe_integer(int i, char *buff, char **bp)
+{
+  return format_long(i, buff, bp, BUFFER_LEN, 10);
+}
+
+/** Safely store an unsigned integer into a buffer.
+ * \param i integer to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \return 0 on success, non-zero on failure.
+ */
+int
+safe_uinteger(unsigned int i, char *buff, char **bp)
+{
+  return safe_str(unparse_uinteger(i), buff, bp);
+}
+
+/** Safely store an unsigned integer into a short buffer.
+ * \param i integer to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \return 0 on success, non-zero on failure.
+ */
+int
+safe_integer_sbuf(int i, char *buff, char **bp)
+{
+  return format_long(i, buff, bp, SBUF_LEN, 10);
+}
+
+/** Safely store a dbref into a buffer.
+ * Don't store partial dbrefs.
+ * \param d dbref to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int
+safe_dbref(dbref d, char *buff, char **bp)
+{
+  char *saved = *bp;
+  if (safe_chr('#', buff, bp)) {
+    *bp = saved;
+    return 1;
+  }
+  if (format_long(d, buff, bp, BUFFER_LEN, 10)) {
+    *bp = saved;
+    return 1;
+  }
+  return 0;
+}
+
+
+/** Safely store a number into a buffer.
+ * \param n number to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int
+safe_number(NVAL n, char *buff, char **bp)
+{
+  const char *c;
+  APPEND_ARGS;
+  c = unparse_number(n);
+  APPEND_TO_BUF;
+}
+
+/** Safely store a string into a buffer.
+ * \param c string to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int
+safe_str(const char *c, char *buff, char **bp)
+{
+  APPEND_ARGS;
+  if (!c || !*c)
+    return 0;
+  APPEND_TO_BUF;
+}
+
+/** Safely store a string into a buffer, quoting it if it contains a space.
+ * \param c string to store.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int
+safe_str_space(const char *c, char *buff, char **bp)
+{
+  APPEND_ARGS;
+  char *saved = *bp;
+
+  if (!c || !*c)
+    return 0;
+
+  if (strchr(c, ' ')) {
+    if (safe_chr('"', buff, bp) || safe_str(c, buff, bp) ||
+       safe_chr('"', buff, bp)) {
+      *bp = saved;
+      return 1;
+    }
+    return 0;
+  } else {
+    APPEND_TO_BUF;
+  }
+}
+
+
+/** Safely store a string of known length into a buffer
+ * This is an optimization of safe_str for when we know the string's length.
+ * \param s string to store.
+ * \param len length of s.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int
+safe_strl(const char *s, int len, char *buff, char **bp)
+{
+  int blen, clen;
+
+  if (!s || !*s)
+    return 0;
+  if (len == 1)
+    return safe_chr(*s, buff, bp);
+
+  blen = *bp - buff;
+  if (blen > BUFFER_LEN - 2)
+    return len;
+  if ((len + blen) <= BUFFER_LEN - 2)
+    clen = len;
+  else
+    clen = BUFFER_LEN - 2 - blen;
+  memcpy(*bp, s, clen);
+  *bp += clen;
+  return len - clen;
+}
+
+/** Safely fill a string with a given character a given number of times.
+ * \param x character to fill with.
+ * \param n number of copies of character to fill in.
+ * \param buff buffer to store into.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \retval 0 success.
+ * \retval 1 failure (filled to end of buffer, but more was requested).
+ */
+int
+safe_fill(char x, size_t n, char *buff, char **bp)
+{
+  size_t blen;
+  int ret = 0;
+
+  if (n == 0)
+    return 0;
+  else if (n == 1)
+    return safe_chr(x, buff, bp);
+
+  if (n > BUFFER_LEN - 1)
+    n = BUFFER_LEN - 1;
+
+  blen = BUFFER_LEN - (*bp - buff) - 1;
+
+  if (blen < n) {
+    n = blen;
+    ret = 1;
+  }
+  memset(*bp, x, n);
+  *bp += n;
+
+  return ret;
+}
+
+#undef APPEND_ARGS
+#undef APPEND_TO_BUF
+
+/* skip_space and seek_char are essentially right out of the 2.0 code */
+
+/** Return a pointer to the next non-space character in a string, or NULL.
+ * We return NULL if given a null string or a string with only spaces.
+ * \param s string to search for non-spaces.
+ * \return pointer to next non-space character in s.
+ */
+char *
+skip_space(const char *s)
+{
+  char *c = (char *) s;
+  while (c && *c && isspace((unsigned char) *c))
+    c++;
+  return c;
+}
+
+/** Return a pointer to next char in s which matches c, or to the terminating
+ * null at the end of s.
+ * \param s string to search.
+ * \param c character to search for.
+ * \return pointer to next occurence of c or to the end of s.
+ */
+char *
+seek_char(const char *s, char c)
+{
+  char *p = (char *) s;
+  while (p && *p && (*p != c))
+    p++;
+  return p;
+}
+
+/** Unsigned char version of strlen.
+ * \param s string.
+ * \return length of s.
+ */
+int
+u_strlen(const unsigned char *s)
+{
+  return strlen((const char *) s);
+}
+
+/** Unsigned char version of strcpy. Equally dangerous.
+ * \param target destination for copy.
+ * \param source string to copy.
+ * \return pointer to copy.
+ */
+unsigned char *
+u_strcpy(unsigned char *target, const unsigned char *source)
+{
+  return (unsigned char *) strcpy((char *) target, (const char *) source);
+}
+
+/** Search for all copies of old in string, and replace each with newbit.
+ * The replaced string is returned, newly allocated.
+ * \param old string to find.
+ * \param newbit string to replace old with.
+ * \param string string to search for old in.
+ * \return allocated string with replacements performed.
+ */
+char *
+replace_string(const char *RESTRICT old, const char *RESTRICT newbit,
+              const char *RESTRICT string)
+{
+  char *result, *r;
+  size_t len, newlen;
+
+  r = result = mush_malloc(BUFFER_LEN, "replace_string.buff");
+  if (!result)
+    mush_panic(T("Couldn't allocate memory in replace_string!"));
+
+  len = strlen(old);
+  newlen = strlen(newbit);
+
+  while (*string) {
+    char *s = strstr(string, old);
+    if (s) {                   /* Match found! */
+      safe_strl(string, s - string, result, &r);
+      safe_strl(newbit, newlen, result, &r);
+      string = s + len;
+    } else {
+      safe_str(string, result, &r);
+      break;
+    }
+  }
+  *r = '\0';
+  return result;
+}
+
+char *
+str_escaped_chr(const char *RESTRICT string, char escape_chr)
+{
+  const char *p;
+  char *result, *r;
+
+  r = result = mush_malloc(BUFFER_LEN, "str_escaped_chr.buff");
+  if (!result)
+    mush_panic(T("Couldn't allocate memory in replace_string!"));
+
+  p = string;
+  while(*p) {
+    if(*p == '\\')
+      *r++ = '\\';
+    else if(*p == escape_chr)
+      *r++ = '\\';
+    *r++ = *p++;
+  }
+
+
+  *r = '\0';
+  return result;
+}
+
+
+/** Standard replacer tokens for text and position */
+const char *standard_tokens[2] = { "##", "#@" };
+
+/* Replace two tokens in a string at once. All-around better than calling
+ * replace_string() twice
+ */
+/** Search for all copies of two old strings, and replace each with a 
+ * corresponding newbit.
+ * The replaced string is returned, newly allocated.
+ * \param old array of two strings to find.
+ * \param newbits array of two strings to replace old with.
+ * \param string string to search for old.
+ * \return allocated string with replacements performed.
+ */
+char *
+replace_string2(const char *old[2], const char *newbits[2],
+               const char *RESTRICT string)
+{
+  char *result, *rp;
+  char firsts[3] = { '\0', '\0', '\0' };
+  size_t oldlens[2], newlens[2];
+
+  if (!string)
+    return NULL;
+
+  rp = result = mush_malloc(BUFFER_LEN, "replace_string.buff");
+  if (!result)
+    mush_panic(T("Couldn't allocate memory in replace_string2!"));
+
+  firsts[0] = old[0][0];
+  firsts[1] = old[1][0];
+
+  oldlens[0] = strlen(old[0]);
+  oldlens[1] = strlen(old[1]);
+  newlens[0] = strlen(newbits[0]);
+  newlens[1] = strlen(newbits[1]);
+
+  while (*string) {
+    size_t skip = strcspn(string, firsts);
+    if (skip) {
+      safe_strl(string, skip, result, &rp);
+      string += skip;
+    }
+    if(*string) {
+           if (strncmp(string, old[0], oldlens[0]) == 0) {     /* Copy the first */
+                   safe_strl(newbits[0], newlens[0], result, &rp);
+                   string += oldlens[0];
+           } else if (strncmp(string, old[1], oldlens[1]) == 0) {      /* The second */
+                   safe_strl(newbits[1], newlens[1], result, &rp); 
+                   string += oldlens[1]; 
+           } else {
+                   safe_chr(*string, result, &rp);
+                   string++;
+           }
+    }
+  }
+
+  *rp = '\0';
+  return result;
+
+}
+
+/** Given a string and a separator, trim leading and trailing spaces
+ * if the separator is a space. This destructively modifies the string.
+ * \param str string to trim.
+ * \param sep separator character.
+ * \return pointer to (trimmed) string.
+ */
+char *
+trim_space_sep(char *str, char sep)
+{
+  /* Trim leading and trailing spaces if we've got a space separator. */
+
+  char *p;
+
+  if (sep != ' ')
+    return str;
+  /* Skip leading spaces */
+  str += strspn(str, " ");
+  for (p = str; *p; p++) ;
+  /* And trailing */
+  for (p--; (*p == ' ') && (p > str); p--) ;
+  p++;
+  *p = '\0';
+  return str;
+}
+
+/** Find the start of the next token in a string.
+ * If the separator is a space, we magically skip multiple spaces.
+ * \param str the string.
+ * \param sep the token separator character.
+ * \return pointer to start of next token in string.
+ */
+char *
+next_token(char *str, char sep)
+{
+  /* move pointer to start of the next token */
+
+  while (*str && (*str != sep))
+    str++;
+  if (!*str)
+    return NULL;
+  str++;
+  if (sep == ' ') {
+    while (*str == sep)
+      str++;
+  }
+  return str;
+}
+
+/** Split out the next token from a string, destructively modifying it.
+ * As usually, if the separator is a space, we skip multiple spaces.
+ * The string's address is update to be past the token, and the token
+ * is returned. This code from TinyMUSH 2.0.
+ * \param sp pointer to string to split from.
+ * \param sep token separator.
+ * \return pointer to token, now null-terminated.
+ */
+char *
+split_token(char **sp, char sep)
+{
+  char *str, *save;
+
+  save = str = *sp;
+  if (!str) {
+    *sp = NULL;
+    return NULL;
+  }
+  while (*str && (*str != sep))
+    str++;
+  if (*str) {
+    *str++ = '\0';
+    if (sep == ' ') {
+      while (*str == sep)
+       str++;
+    }
+  } else {
+    str = NULL;
+  }
+  *sp = str;
+  return save;
+}
+
+/** Count the number of tokens in a string.
+ * \param str string to count.
+ * \param sep token separator.
+ * \return number of tokens in str.
+ */
+int
+do_wordcount(char *str, char sep)
+{
+  int n;
+
+  if (!*str)
+    return 0;
+  for (n = 0; str; str = next_token(str, sep), n++) ;
+  return n;
+}
+
+/** A version of strlen that ignores ansi and HTML sequences.
+ * \param p string to get length of.
+ * \return length of string p, not including ansi/html sequences.
+ */
+int
+ansi_strlen(const char *p)
+{
+  int i = 0;
+
+  if (!p)
+    return 0;
+
+  while (*p) {
+    if (*p == ESC_CHAR) {
+      while ((*p) && (*p != 'm'))
+       p++;
+    } else if (*p == TAG_START) {
+      while ((*p) && (*p != TAG_END))
+       p++;
+    } else {
+      i++;
+    }
+    p++;
+  }
+  return i;
+}
+
+/** Returns the apparent length of a string, up to numchars visible 
+ * characters. The apparent length skips over nonprinting ansi and
+ * tags.
+ * \param p string.
+ * \param numchars maximum size to report.
+ * \return apparent length of string.
+ */
+int
+ansi_strnlen(const char *p, size_t numchars)
+{
+  int i = 0;
+
+  if (!p)
+    return 0;
+  while (*p && numchars > 0) {
+    if (*p == ESC_CHAR) {
+      while ((*p) && (*p != 'm')) {
+       p++;
+       i++;
+      }
+    } else if (*p == TAG_START) {
+      while ((*p) && (*p != TAG_END)) {
+       p++;
+       i++;
+      }
+    } else
+      numchars--;
+    i++;
+    p++;
+  }
+  return i;
+}
+
+/** Given a string, a word, and a separator, remove first occurence
+ * of the word from the string. Destructive.
+ * \param list a string containing a separated list.
+ * \param word a word to remove from the list.
+ * \param sep the separator between list items.
+ * \return pointer to static buffer containing list without first occurence
+ * of word.
+ */
+char *
+remove_word(char *list, char *word, char sep)
+{
+  char *sp;
+  char *bp;
+  static char buff[BUFFER_LEN];
+
+  bp = buff;
+  sp = split_token(&list, sep);
+  if (!strcmp(sp, word)) {
+    sp = split_token(&list, sep);
+    safe_str(sp, buff, &bp);
+  } else {
+    safe_str(sp, buff, &bp);
+    while (list && strcmp(sp = split_token(&list, sep), word)) {
+      safe_chr(sep, buff, &bp);
+      safe_str(sp, buff, &bp);
+    }
+  }
+  while (list) {
+    sp = split_token(&list, sep);
+    safe_chr(sep, buff, &bp);
+    safe_str(sp, buff, &bp);
+  }
+  *bp = '\0';
+  return buff;
+}
+
+/** Return the next name in a list. A name may be a single word, or
+ * a quoted string. This is used by things like page/list. The list's
+ * pointer is advanced to the next name in the list.
+ * \param head pointer to pointer to string of names.
+ * \return pointer to static buffer containing next name.
+ */
+char *
+next_in_list(const char **head)
+{
+  int paren = 0;
+  static char buf[BUFFER_LEN];
+  char *p = buf;
+
+  while (**head == ' ')
+    (*head)++;
+
+  if (**head == '"') {
+    (*head)++;
+    paren = 1;
+  }
+
+  /* Copy it char by char until you hit a " or, if not in a
+   * paren, a space
+   */
+  while (**head && (paren || (**head != ' ')) && (**head != '"')) {
+    safe_chr(**head, buf, &p);
+    (*head)++;
+  }
+
+  if (paren && **head)
+    (*head)++;
+
+  safe_chr('\0', buf, &p);
+  return buf;
+
+}
+
+/** Strip all ansi and html markup from a string. As a side effect,
+ * stores the length of the stripped string in a provided address.
+ * NOTE! Length returned is length *including* the terminating NULL,
+ * because we usually memcpy the result.
+ * \param orig string to strip.
+ * \param s_len address to store length of stripped string, if provided.
+ * \return pointer to static buffer containing stripped string.
+ */
+char *
+remove_markup(const char *orig, size_t * s_len)
+{
+  static char buff[BUFFER_LEN];
+  char *bp = buff;
+  const char *q;
+  size_t len = 0;
+
+  if (!orig) {
+    if (s_len)
+      *s_len = 0;
+    return NULL;
+  }
+
+  for (q = orig; *q;) {
+    switch (*q) {
+    case ESC_CHAR:
+      /* Skip over ansi */
+      while (*q && *q++ != 'm') ;
+      break;
+    case TAG_START:
+      /* Skip over HTML */
+      while (*q && *q++ != TAG_END) ;
+      break;
+    default:
+      safe_chr(*q++, buff, &bp);
+      len++;
+    }
+  }
+  *bp = '\0';
+  if (s_len)
+    *s_len = len + 1;
+  return buff;
+}
+
+
+/** Safely append an int to a string. Returns a true value on failure.
+ * This will someday take extra arguments for use with our version 
+ * of snprintf. Please try not to use it.
+ * maxlen = total length of string.
+ * buf[maxlen - 1] = place where \0 will go.
+ * buf[maxlen - 2] = last visible character.
+ * \param val value to append.
+ * \param buff string to append to.
+ * \param bp pointer to pointer to insertion point in buff.
+ * \param maxlen total length of string. 
+ * \param base the base to render the number in.
+ */
+int
+format_long(long val, char *buff, char **bp, int maxlen, int base)
+{
+  char stack[128];             /* Even a negative 64 bit number will only be 21
+                                  digits or so max. This should be plenty of
+                                  buffer room. */
+  char *current;
+  int size = 0, neg = 0;
+  ldiv_t r;
+  const char *digits = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+  /* Sanity checks */
+  if (!bp || !buff || !*bp)
+    return 1;
+  if (*bp - buff >= maxlen - 1)
+    return 1;
+
+  if (base < 2)
+    base = 2;
+  else if (base > 36)
+    base = 36;
+
+  if (val < 0) {
+    neg = 1;
+    val = -val;
+    if (val < 0) {
+      /* -LONG_MIN == LONG_MIN on 2's complement systems. Take the
+         easy way out since this value is rarely encountered. */
+      switch (base) {
+      case 10:
+       return safe_format(buff, bp, "%ld", val);
+      case 16:
+       return safe_format(buff, bp, "%lx", val);
+      case 8:
+       return safe_format(buff, bp, "%lo", val);
+      default:
+       /* Weird base /and/ LONG_MIN. Fix someday. */
+       return 0;
+      }
+    }
+
+  }
+
+  current = stack + sizeof(stack);
+
+  /* Take the rightmost digit, and push it onto the stack, then
+   * integer divide by 10 to get to the next digit. */
+  r.quot = val;
+  do {
+    /* ldiv(x, y) does x/y and x%y at the same time (both of
+     * which we need).
+     */
+    r = ldiv(r.quot, base);
+    *(--current) = digits[r.rem];
+  } while (r.quot);
+
+  /* Add the negative sign if needed. */
+  if (neg)
+    *(--current) = '-';
+
+  /* The above puts the number on the stack.  Now we need to put
+   * it in the buffer.  If there's enough room, use Duff's Device
+   * for speed, otherwise do it one char at a time.
+   */
+
+  size = stack + sizeof(stack) - current;
+
+  /* if (size < (int) ((buff + maxlen - 1) - *bp)) { */
+  if (((int) (*bp - buff)) + size < maxlen - 2) {
+    switch (size % 8) {
+    case 0:
+      while (current < stack + sizeof(stack)) {
+       *((*bp)++) = *(current++);
+    case 7:
+       *((*bp)++) = *(current++);
+    case 6:
+       *((*bp)++) = *(current++);
+    case 5:
+       *((*bp)++) = *(current++);
+    case 4:
+       *((*bp)++) = *(current++);
+    case 3:
+       *((*bp)++) = *(current++);
+    case 2:
+       *((*bp)++) = *(current++);
+    case 1:
+       *((*bp)++) = *(current++);
+      }
+    }
+  } else {
+    while (current < stack + sizeof(stack)) {
+      if (*bp - buff >= maxlen - 1) {
+       return 1;
+      }
+      *((*bp)++) = *(current++);
+    }
+  }
+
+  return 0;
+}
+
+#if defined(HAS_STRXFRM) && !defined(WIN32)
+/** A locale-sensitive strncmp.
+ * \param s1 first string to compare.
+ * \param s2 second string to compare.
+ * \param t number of characters to compare.
+ * \retval -1 s1 collates before s2.
+ * \retval 0 s1 collates the same as s2.
+ * \retval 1 s1 collates after s2.
+ */
+int
+strncoll(const char *s1, const char *s2, size_t t)
+{
+  char *d1, *d2, *ns1, *ns2;
+  int result;
+  size_t s1_len, s2_len;
+
+  ns1 = mush_malloc(t + 1, "string");
+  ns2 = mush_malloc(t + 1, "string");
+  memcpy(ns1, s1, t);
+  ns1[t] = '\0';
+  memcpy(ns2, s2, t);
+  ns2[t] = '\0';
+  s1_len = strxfrm(NULL, ns1, 0) + 1;
+  s2_len = strxfrm(NULL, ns2, 0) + 1;
+
+  d1 = mush_malloc(s1_len + 1, "string");
+  d2 = mush_malloc(s2_len + 1, "string");
+  (void) strxfrm(d1, ns1, s1_len);
+  (void) strxfrm(d2, ns2, s2_len);
+  result = strcmp(d1, d2);
+  mush_free(d1, "string");
+  mush_free(d2, "string");
+  return result;
+}
+
+/** A locale-sensitive strcasecmp.
+ * \param s1 first string to compare.
+ * \param s2 second string to compare.
+ * \retval -1 s1 collates before s2.
+ * \retval 0 s1 collates the same as s2.
+ * \retval 1 s1 collates after s2.
+ */
+int
+strcasecoll(const char *s1, const char *s2)
+{
+  char *d1, *d2;
+  int result;
+  size_t s1_len, s2_len;
+
+  s1_len = strxfrm(NULL, s1, 0) + 1;
+  s2_len = strxfrm(NULL, s2, 0) + 1;
+
+  d1 = mush_malloc(s1_len, "string");
+  d2 = mush_malloc(s2_len, "string");
+  (void) strxfrm(d1, strupper(s1), s1_len);
+  (void) strxfrm(d2, strupper(s2), s2_len);
+  result = strcmp(d1, d2);
+  mush_free(d1, "string");
+  mush_free(d2, "string");
+  return result;
+}
+
+/** A locale-sensitive strncasecmp.
+ * \param s1 first string to compare.
+ * \param s2 second string to compare.
+ * \param t number of characters to compare.
+ * \retval -1 s1 collates before s2.
+ * \retval 0 s1 collates the same as s2.
+ * \retval 1 s1 collates after s2.
+ */
+int
+strncasecoll(const char *s1, const char *s2, size_t t)
+{
+  char *d1, *d2, *ns1, *ns2;
+  int result;
+  size_t s1_len, s2_len;
+
+  ns1 = mush_malloc(t + 1, "string");
+  ns2 = mush_malloc(t + 1, "string");
+  memcpy(ns1, s1, t);
+  ns1[t] = '\0';
+  memcpy(ns2, s2, t);
+  ns2[t] = '\0';
+  s1_len = strxfrm(NULL, ns1, 0) + 1;
+  s2_len = strxfrm(NULL, ns2, 0) + 1;
+
+  d1 = mush_malloc(s1_len, "string");
+  d2 = mush_malloc(s2_len, "string");
+  (void) strxfrm(d1, strupper(ns1), s1_len);
+  (void) strxfrm(d2, strupper(ns2), s2_len);
+  result = strcmp(d1, d2);
+  mush_free(d1, "string");
+  mush_free(d2, "string");
+  return result;
+}
+#endif                         /* HAS_STRXFRM && !WIN32 */
+
+/** Return a string pointer past any ansi/html markup at the start.
+ * \param p a string.
+ * \return pointer to string after any initial ansi/html markup.
+ */
+char *
+skip_leading_ansi(const char *p)
+{
+  if (!p)
+    return NULL;
+  while (*p == ESC_CHAR || *p == TAG_START) {
+    if (*p == ESC_CHAR) {
+      while (*p && *p != 'm')
+       p++;
+    } else {                   /* TAG_START */
+      while (*p && *p != TAG_END)
+       p++;
+    }
+    if (*p)
+      p++;
+  }
+  return (char *) p;
+
+}
+
+/** Convert a string into an ansi_string.
+ * This takes a string that may contain ansi/html markup codes and
+ * converts it to an ansi_string structure that separately stores
+ * the plain string and the markup codes for each character.
+ * \param src string to parse.
+ * \return pointer to an ansi_string structure representing the src string.
+ */
+ansi_string *
+parse_ansi_string(const char *src)
+{
+  ansi_string *data;
+  char *y, *current = NULL;
+  size_t p = 0;
+
+  if (!src)
+    return NULL;
+
+  data = mush_malloc(sizeof *data, "ansi_string");
+  if (!data)
+    return NULL;
+
+  data->len = ansi_strlen(src);
+
+  while (*src) {
+    y = skip_leading_ansi(src);
+    if (y != src) {
+      if (current)
+       mush_free(current, "markup_codes");
+      current = mush_strndup(src, y - src, "markup_codes");
+      src = y;
+    }
+    if (current)
+      data->codes[p] = mush_strdup(current, "markup_codes");
+    else
+      data->codes[p] = NULL;
+    data->text[p] = *src;
+    if (*src)
+      src++;
+    p++;
+  }
+  data->text[p] = '\0';
+
+  while (p <= data->len) {
+    data->codes[p] = NULL;
+    p++;
+  }
+
+  if (current)
+    mush_free(current, "markup_codes");
+
+  return data;
+}
+
+
+/** Fill up an ansi_string with codes so that when a code starts it
+ * applies to all the following characters until there's a new code.
+ * \param as pointer to an ansi_string to populate codes in.
+ */
+void
+populate_codes(ansi_string *as)
+{
+  size_t p;
+  char *current = NULL;
+
+  if (!as)
+    return;
+
+  for (p = 0; p < as->len; p++)
+    if (as->codes[p]) {
+      if (current)
+       mush_free(current, "markup_codes");
+      current = mush_strdup(as->codes[p], "markup_codes");
+    } else {
+      if (!current)
+       current = mush_strdup(ANSI_NORMAL, "markup_codes");
+      as->codes[p] = mush_strdup(current, "markup_codes");
+    }
+  if (current)
+    mush_free(current, "markup_codes");
+}
+
+/** Strip out codes from an ansi_string, leaving in only the codes where
+ * they change.
+ * \param as pointer to an ansi_string.
+ */
+void
+depopulate_codes(ansi_string *as)
+{
+  size_t p, m;
+  int normal = 1;
+
+  if (!as)
+    return;
+
+  for (p = 0; p <= as->len; p++) {
+    if (as->codes[p]) {
+      if (normal) {
+       if (strcmp(as->codes[p], ANSI_NORMAL) == 0) {
+         mush_free(as->codes[p], "markup_codes");
+         as->codes[p] = NULL;
+         continue;
+       } else {
+         normal = 0;
+       }
+      }
+
+      m = p;
+      for (p++; p < as->len; p++) {
+       if (as->codes[p] && strcmp(as->codes[p], as->codes[m]) == 0) {
+         mush_free(as->codes[p], "markup_codes");
+         as->codes[p] = NULL;
+       } else {
+         p--;
+         break;
+       }
+      }
+    }
+  }
+}
+
+static int is_ansi_code(const char *s);
+static int is_start_html_code(const char *s) __attribute__ ((__unused__));
+static int is_end_html_code(const char *s);
+/** Is s a string that signifies the end of ANSI codes? */
+#define is_end_ansi_code(s) (!strcmp((s),ANSI_NORMAL))
+
+
+static int
+is_ansi_code(const char *s)
+{
+  return s && *s == ESC_CHAR;
+}
+
+static int
+is_start_html_code(const char *s)
+{
+  return s && *s == TAG_START && *(s + 1) != '/';
+}
+
+static int
+is_end_html_code(const char *s)
+{
+  return s && *s == TAG_START && *(s + 1) == '/';
+}
+
+/** Free an ansi_string.
+ * \param as pointer to ansi_string to free.
+ */
+void
+free_ansi_string(ansi_string *as)
+{
+  int p;
+
+  if (!as)
+    return;
+  for (p = as->len; p >= 0; p--) {
+    if (as->codes[p])
+      mush_free(as->codes[p], "markup_codes");
+  }
+  mush_free(as, "ansi_string");
+}
+
+/** Safely append an ansi_string into a buffer as a real string.
+ * \param as pointer to ansi_string to append.
+ * \param start position in as to start copying from.
+ * \param len length in characters to copy from as.
+ * \param buff buffer to insert into.
+ * \param bp pointer to pointer to insertion point of buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int safe_ansi_string(ansi_string *as, size_t start, size_t len, char *buff, char **bp) {
+  int p, q;
+  int in_ansi = 0;
+  int in_html = 0;
+
+  if (!as)
+    return 1;
+
+  depopulate_codes(as);
+
+  if (start > as->len || len == 0 || as->len == 0)
+    return safe_str("", buff, bp);
+
+  /* Find the starting codes by working our way backward until we
+   * reach some opening codes, and then working our way back from there
+   * until we hit a non-opening code or non-code 
+   */
+  p = start;
+  while ((p >= 0) && (as->codes[p] == NULL))
+    p--;
+  /* p is now either <0 or pointing to a code */
+  if ((p >= 0) && !is_end_html_code(as->codes[p]) &&
+      !is_end_ansi_code(as->codes[p])) {
+    /* p is now pointing to a starting code */
+    q = p;
+    while ((q >= 0) && as->codes[q] && !is_end_html_code(as->codes[q]) &&
+          !is_end_ansi_code(as->codes[q])) {
+      if (is_ansi_code(as->codes[q]))
+       in_ansi = 1;
+      else if (is_start_html_code(as->codes[q]))
+       in_html++;
+      q--;
+    }
+    /* p is now pointing to the first starting code, and we know if we're
+     * in ansi, html, or both. We also know how many html tags have been
+     * opened.
+     */
+  }
+
+  /* Copy the text. The right thing to do now would be to have a stack
+   * of open html tags and clear in_html once all of the tags have
+   * been closed. We don't quite do that, alas.
+   */
+  for (p = (int) start; p < (int) (start + len) && p < (int) as->len; p++) {
+    if (as->codes[p]) {
+      if (safe_str(as->codes[p], buff, bp))
+      return 1;
+      if (is_end_ansi_code(as->codes[p]))
+      in_ansi = 0;
+      else if (is_ansi_code(as->codes[p]))
+      in_ansi = 1;
+      if (is_end_html_code(as->codes[p]))
+      in_html--;
+      else if (is_start_html_code(as->codes[p]))
+      in_html++;
+    }
+    if (safe_chr(as->text[p], buff, bp))
+      return 1;
+  }
+
+  /* Output (only) closing codes if needed. */
+  while (p <= (int) as->len) {
+    if (!in_ansi && !in_html)
+      break;
+    if (as->codes[p]) {
+      if (is_end_ansi_code(as->codes[p])) {
+      in_ansi = 0;
+      if (safe_str(as->codes[p], buff, bp))
+        return 1;
+      } else if (is_end_html_code(as->codes[p])) {
+      in_html--;
+      if (safe_str(as->codes[p], buff, bp))
+        return 1;
+      }
+    }
+    p++;
+  }
+  if (in_ansi)
+    safe_str(ANSI_NORMAL, buff, bp);
+  return 0;
+}
+
+/** Safely append an ansi_string into a buffer as a real string,
+ * with extra copying of starting tags (for wrap()/align()).
+ * \param as pointer to ansi_string to append.
+ * \param start position in as to start copying from.
+ * \param len length in characters to copy from as.
+ * \param buff buffer to insert into.
+ * \param bp pointer to pointer to insertion point of buff.
+ * \retval 0 success.
+ * \retval 1 failure.
+ */
+int safe_ansi_string2(ansi_string *as, size_t start, size_t len, char *buff, char **bp) {
+   int p, q;
+  int in_ansi = 0;
+  int in_html = 0;
+
+  if (!as)
+    return 1;
+
+  depopulate_codes(as);
+
+  if (start > as->len || len == 0 || as->len == 0)
+    return safe_str("", buff, bp);
+
+  /* Find the starting codes by working our way backward until we
+   * reach some opening codes, and then working our way back from there
+   * until we hit a non-opening code or non-code
+   */
+  p = start;
+  while ((p >= 0) && (as->codes[p] == NULL))
+    p--;
+  /* p is now either <0 or pointing to a code */
+  if ((p >= 0) && !is_end_html_code(as->codes[p]) &&
+      !is_end_ansi_code(as->codes[p])) {
+    /* p is now pointing to a starting code */
+    q = p;
+    while ((q >= 0) && as->codes[q] && !is_end_html_code(as->codes[q]) &&
+         !is_end_ansi_code(as->codes[q])) {
+      if (is_ansi_code(as->codes[q]))
+      in_ansi = 1;
+      else if (is_start_html_code(as->codes[q]))
+      in_html++;
+      q--;
+    }
+    /* p is now pointing to the first starting code, and we know if we're
+     * in ansi, html, or both. We also know how many html tags have been
+     * opened.
+     */
+
+    /* Except there's this one problem - Now we know it, we weren't
+     * doing anything with it.
+     */
+    for (q = q + 1; q <= p; q++) {
+      if (safe_str(as->codes[q], buff, bp))
+       return 1;
+    }
+  }
+
+  /* Copy the text. The right thing to do now would be to have a stack
+   * of open html tags and clear in_html once all of the tags have
+   * been closed. We don't quite do that, alas.
+   */
+  for (p = (int) start; p < (int) (start + len) && p < (int) as->len; p++) {
+    if (as->codes[p]) {
+      if (safe_str(as->codes[p], buff, bp))
+       return 1;
+      if (is_end_ansi_code(as->codes[p]))
+       in_ansi = 0;
+      else if (is_ansi_code(as->codes[p]))
+       in_ansi = 1;
+      if (is_end_html_code(as->codes[p]))
+       in_html--;
+      else if (is_start_html_code(as->codes[p]))
+       in_html++;
+    }
+    if (safe_chr(as->text[p], buff, bp))
+      return 1;
+  }
+
+  /* Output (only) closing codes if needed. */
+  while (p <= (int) as->len) {
+    if (!in_ansi && !in_html)
+      break;
+    if (as->codes[p]) {
+      if (is_end_ansi_code(as->codes[p])) {
+       in_ansi = 0;
+       if (safe_str(as->codes[p], buff, bp))
+         return 1;
+      } else if (is_end_html_code(as->codes[p])) {
+       in_html--;
+       if (safe_str(as->codes[p], buff, bp))
+         return 1;
+      }
+    }
+    p++;
+  }
+  if (in_ansi)
+    safe_str(ANSI_NORMAL, buff, bp);
+  return 0;
+}
+
+/** Safely append a list item to a buffer, possibly with punctuation
+ * and conjunctions.
+ * Given the current item number in a list, whether it's the last item
+ * in the list, the list's output separator, a conjunction,
+ * and a punctuation mark to use between items, store the appropriate
+ * inter-item stuff into the given buffer safely.
+ * \param cur_num current item number of the item to append.
+ * \param done 1 if this is the final item.
+ * \param delim string to insert after most items (comma).
+ * \param conjoin string to insert before last time ("and").
+ * \param space output delimiter.
+ * \param buff buffer to append to.
+ * \param bp pointer to pointer to insertion point in buff.
+ */
+void
+safe_itemizer(int cur_num, int done, const char *delim, const char *conjoin,
+             const char *space, char *buff, char **bp)
+{
+  /* We don't do anything if it's the first one */
+  if (cur_num == 1)
+    return;
+  /* Are we done? */
+  if (done) {
+    /* if so, we need a [<delim>]<space><conj> */
+    if (cur_num >= 3)
+      safe_str(delim, buff, bp);
+    safe_str(space, buff, bp);
+    safe_str(conjoin, buff, bp);
+  } else {
+    /* if not, we need just <delim> */
+    safe_str(delim, buff, bp);
+  }
+  /* And then we need another <space> */
+  safe_str(space, buff, bp);
+
+}
+
+/** Return a stringified time in a static buffer
+ * Just like ctime() except without the trailing newlines.
+ * \param t the time to format.
+ * \param utc true if the time should be displayed in UTC, 0 for local time zone.
+ * \return a pointer to a static buffer with the stringified time.
+ */
+char *
+show_time(time_t t, int utc)
+{
+  struct tm *when;
+
+  if (utc)
+    when = gmtime(&t);
+  else
+    when = localtime(&t);
+
+  return show_tm(when);
+}
+
+/** Return a stringified time in a static buffer
+ * Just like asctime() except without the trailing newlines.
+ * \param when the time to format.
+ * \return a pointer to a static buffer with the stringified time.
+ */
+char *
+show_tm(struct tm *when)
+{
+  static char buffer[BUFFER_LEN];
+  int p;
+
+  if (!when)
+    return NULL;
+
+  strcpy(buffer, asctime(when));
+
+  p = strlen(buffer) - 1;
+  if (buffer[p] == '\n')
+    buffer[p] = '\0';
+
+  if (buffer[8] == ' ')
+    buffer[8] = '0';
+
+  return buffer;
+}
+
diff --git a/src/switchinc.c b/src/switchinc.c
new file mode 100644 (file)
index 0000000..9f2c95b
--- /dev/null
@@ -0,0 +1,164 @@
+/* AUTOGENERATED FILE. DO NOT EDIT */
+SWITCH_VALUE switch_list[] = {
+  {"ACCESS", SWITCH_ACCESS},
+  {"ADD", SWITCH_ADD},
+  {"AFTER", SWITCH_AFTER},
+  {"ALIAS", SWITCH_ALIAS},
+  {"ALL", SWITCH_ALL},
+  {"ANY", SWITCH_ANY},
+  {"ATTRIBS", SWITCH_ATTRIBS},
+  {"AUTO", SWITCH_AUTO},
+  {"BAN", SWITCH_BAN},
+  {"BEFORE", SWITCH_BEFORE},
+  {"BLIND", SWITCH_BLIND},
+  {"BRIEF", SWITCH_BRIEF},
+  {"BUFFER", SWITCH_BUFFER},
+  {"CHECK", SWITCH_CHECK},
+  {"CHOWN", SWITCH_CHOWN},
+  {"CHUNKS", SWITCH_CHUNKS},
+  {"CLEAR", SWITCH_CLEAR},
+  {"CMD", SWITCH_CMD},
+  {"COMBAT", SWITCH_COMBAT},
+  {"COMMANDS", SWITCH_COMMANDS},
+  {"CONN", SWITCH_CONN},
+  {"CONNECT", SWITCH_CONNECT},
+  {"CONNECTED", SWITCH_CONNECTED},
+  {"CONTENTS", SWITCH_CONTENTS},
+  {"COSTS", SWITCH_COSTS},
+  {"COUNT", SWITCH_COUNT},
+  {"CREATE", SWITCH_CREATE},
+  {"DATABASE", SWITCH_DATABASE},
+  {"DB", SWITCH_DB},
+  {"DEBUG", SWITCH_DEBUG},
+  {"DECOMPILE", SWITCH_DECOMPILE},
+  {"DEFAULTS", SWITCH_DEFAULTS},
+  {"DELETE", SWITCH_DELETE},
+  {"DELIMIT", SWITCH_DELIMIT},
+  {"DESCRIBE", SWITCH_DESCRIBE},
+  {"DESTROY", SWITCH_DESTROY},
+  {"DISABLE", SWITCH_DISABLE},
+  {"DOWN", SWITCH_DOWN},
+  {"DSTATS", SWITCH_DSTATS},
+  {"EMIT", SWITCH_EMIT},
+  {"ENABLE", SWITCH_ENABLE},
+  {"EQSPLIT", SWITCH_EQSPLIT},
+  {"ERR", SWITCH_ERR},
+  {"EXITS", SWITCH_EXITS},
+  {"FILE", SWITCH_FILE},
+  {"FIRST", SWITCH_FIRST},
+  {"FLAGS", SWITCH_FLAGS},
+  {"FOLDERS", SWITCH_FOLDERS},
+  {"FORWARD", SWITCH_FORWARD},
+  {"FREESPACE", SWITCH_FREESPACE},
+  {"FSTATS", SWITCH_FSTATS},
+  {"FULL", SWITCH_FULL},
+  {"FUNCTIONS", SWITCH_FUNCTIONS},
+  {"FWD", SWITCH_FWD},
+  {"GAG", SWITCH_GAG},
+  {"GLOBALS", SWITCH_GLOBALS},
+  {"HEADER", SWITCH_HEADER},
+  {"HERE", SWITCH_HERE},
+  {"HIDE", SWITCH_HIDE},
+  {"IGNORE", SWITCH_IGNORE},
+  {"ILIST", SWITCH_ILIST},
+  {"INFO", SWITCH_INFO},
+  {"INIT", SWITCH_INIT},
+  {"INSIDE", SWITCH_INSIDE},
+  {"INVENTORY", SWITCH_INVENTORY},
+  {"IPRINT", SWITCH_IPRINT},
+  {"JOIN", SWITCH_JOIN},
+  {"LETTER", SWITCH_LETTER},
+  {"LIST", SWITCH_LIST},
+  {"LOCK", SWITCH_LOCK},
+  {"LOCKS", SWITCH_LOCKS},
+  {"LOGOUT", SWITCH_LOGOUT},
+  {"LOWERCASE", SWITCH_LOWERCASE},
+  {"LSARGS", SWITCH_LSARGS},
+  {"MAX", SWITCH_MAX},
+  {"ME", SWITCH_ME},
+  {"MEMBERS", SWITCH_MEMBERS},
+  {"MOD", SWITCH_MOD},
+  {"MORTAL", SWITCH_MORTAL},
+  {"MOTD", SWITCH_MOTD},
+  {"MUTE", SWITCH_MUTE},
+  {"NAME", SWITCH_NAME},
+  {"NO", SWITCH_NO},
+  {"NOEVAL", SWITCH_NOEVAL},
+  {"NOFLAGCOPY", SWITCH_NOFLAGCOPY},
+  {"NOISY", SWITCH_NOISY},
+  {"NOSIG", SWITCH_NOSIG},
+  {"NOSPACE", SWITCH_NOSPACE},
+  {"NOTIFY", SWITCH_NOTIFY},
+  {"NUKE", SWITCH_NUKE},
+  {"OBJECT", SWITCH_OBJECT},
+  {"OFF", SWITCH_OFF},
+  {"ON", SWITCH_ON},
+  {"OUTSIDE", SWITCH_OUTSIDE},
+  {"OVERRIDE", SWITCH_OVERRIDE},
+  {"PAGING", SWITCH_PAGING},
+  {"PANIC", SWITCH_PANIC},
+  {"PARANOID", SWITCH_PARANOID},
+  {"PLAYERS", SWITCH_PLAYERS},
+  {"PORT", SWITCH_PORT},
+  {"POWERS", SWITCH_POWERS},
+  {"PRESERVE", SWITCH_PRESERVE},
+  {"PRINT", SWITCH_PRINT},
+  {"PRIVS", SWITCH_PRIVS},
+  {"PURGE", SWITCH_PURGE},
+  {"QUICK", SWITCH_QUICK},
+  {"QUIET", SWITCH_QUIET},
+  {"QUIT", SWITCH_QUIT},
+  {"RAW", SWITCH_RAW},
+  {"READ", SWITCH_READ},
+  {"REBOOT", SWITCH_REBOOT},
+  {"RECALL", SWITCH_RECALL},
+  {"REGEXP", SWITCH_REGEXP},
+  {"REGIONS", SWITCH_REGIONS},
+  {"REGISTER", SWITCH_REGISTER},
+  {"REMOVE", SWITCH_REMOVE},
+  {"RENAME", SWITCH_RENAME},
+  {"RESET", SWITCH_RESET},
+  {"RESTORE", SWITCH_RESTORE},
+  {"RESTRICT", SWITCH_RESTRICT},
+  {"RETROACTIVE", SWITCH_RETROACTIVE},
+  {"ROOM", SWITCH_ROOM},
+  {"ROOMS", SWITCH_ROOMS},
+  {"RSARGS", SWITCH_RSARGS},
+  {"SEE", SWITCH_SEE},
+  {"SEEFLAG", SWITCH_SEEFLAG},
+  {"SELF", SWITCH_SELF},
+  {"SEND", SWITCH_SEND},
+  {"SET", SWITCH_SET},
+  {"SILENT", SWITCH_SILENT},
+  {"SKIPDEFAULTS", SWITCH_SKIPDEFAULTS},
+  {"SPEAK", SWITCH_SPEAK},
+  {"SPOOF", SWITCH_SPOOF},
+  {"STATS", SWITCH_STATS},
+  {"SUMMARY", SWITCH_SUMMARY},
+  {"TABLES", SWITCH_TABLES},
+  {"TAG", SWITCH_TAG},
+  {"TELEPORT", SWITCH_TELEPORT},
+  {"TF", SWITCH_TF},
+  {"THINGS", SWITCH_THINGS},
+  {"TITLE", SWITCH_TITLE},
+  {"TRACE", SWITCH_TRACE},
+  {"TYPE", SWITCH_TYPE},
+  {"UNCLEAR", SWITCH_UNCLEAR},
+  {"UNFOLDER", SWITCH_UNFOLDER},
+  {"UNGAG", SWITCH_UNGAG},
+  {"UNHIDE", SWITCH_UNHIDE},
+  {"UNMUTE", SWITCH_UNMUTE},
+  {"UNTAG", SWITCH_UNTAG},
+  {"UNTIL", SWITCH_UNTIL},
+  {"URGENT", SWITCH_URGENT},
+  {"USEFLAG", SWITCH_USEFLAG},
+  {"WHAT", SWITCH_WHAT},
+  {"WHO", SWITCH_WHO},
+  {"WIPE", SWITCH_WIPE},
+  {"WIZ", SWITCH_WIZ},
+  {"WIZARD", SWITCH_WIZARD},
+  {"WRITE", SWITCH_WRITE},
+  {"YES", SWITCH_YES},
+  {"ZONE", SWITCH_ZONE},
+  {NULL, 0}
+};
diff --git a/src/tables.c b/src/tables.c
new file mode 100644 (file)
index 0000000..a8c3cf6
--- /dev/null
@@ -0,0 +1,400 @@
+/* This file was generated by running ./gentables compiled from
+ * gentables.c. Edit that file, not this one, when making changes. */
+#include <stdlib.h>
+
+unsigned int roman_numeral_table[256] = {
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,100,500,  0,  0,  0,  0,  1,  0,  0, 50,1000,  0,  0,
+  0,  0,  0,  0,  0,  0,  5,  0, 10,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,100000,500000,  0,  0,  0,  0,1000,  0,  0,50000,1000000,  0,  0,
+  0,  0,  0,  0,  0,  0,5000,  0,10000,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+signed char qreg_indexes[256] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
+ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+char active_table[256] = {
+  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,
+  1,  0,  0,  0,  1,  1,  0,  0,  1,  1,  0,  0,  1,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+char atr_name_table[256] = {
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  1,  1,  1,  1,  0,  1,  1,  0,  0,  1,  1,  1,  1,  1,  1,
+  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  1,  1,  1,  1,  1,
+  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,
+  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  1,  0,  0,  0,  0,  1,
+  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+char valid_timefmt_codes[256] = {
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  1,  1,  0,  0,  0,  0,  0,  1,  1,  0,  0,  0,  1,  0,  0,
+  0,  0,  0,  1,  0,  1,  0,  1,  1,  1,  1,  0,  0,  0,  0,  0,
+  0,  1,  1,  1,  1,  0,  0,  0,  0,  0,  1,  0,  0,  1,  0,  0,
+  1,  0,  0,  0,  0,  0,  0,  1,  1,  1,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+char escaped_chars[256] = {
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+char escaped_chars_s[256] = {
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  1,  1,  0,  0,  1,  1,  0,  0,  1,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  1,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+typedef struct {
+const char *base;
+const char *entity;
+} accent_info;
+accent_info accent_table[256] = {
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{"\n", "<br>\n"},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{"\"", "&quot;"},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{"&", "&amp;"},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{"<", "&lt;"},
+{NULL, NULL},
+{">", "&gt;"},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{"!", "&iexcl;"},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{"(c)", "&copy;"},
+{NULL, NULL},
+{"<<", "&laquo;"},
+{NULL, NULL},
+{NULL, NULL},
+{"(r)", "&reg;"},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{NULL, NULL},
+{">>", "&raquo;"},
+{"1/4", "&frac14;"},
+{"1/2", "&frac12;"},
+{"3/4", "&frac34;"},
+{"?", "&iquest;"},
+{"A", "&Agrave;"},
+{"A", "&Aacute;"},
+{"A", "&Acirc;"},
+{"A", "&Atilde;"},
+{"A", "&Auml;"},
+{"A", "&Aring;"},
+{"AE", "&AElig;"},
+{"C", "&Ccedil;"},
+{"E", "&Egrave;"},
+{"E", "&Eacute;"},
+{"E", "&Ecirc;"},
+{"E", "&Euml;"},
+{"I", "&Igrave;"},
+{"I", "&Iacute;"},
+{"I", "&Icirc;"},
+{"I", "&Iuml;"},
+{"D", "&ETH;"},
+{"N", "&Ntilde;"},
+{"O", "&Ograve;"},
+{"O", "&Oacute;"},
+{"O", "&Ocirc;"},
+{"O", "&Otilde;"},
+{"O", "&Ouml;"},
+{NULL, NULL},
+{NULL, NULL},
+{"U", "&Ugrave;"},
+{"U", "&Uacute;"},
+{"U", "&Ucirc;"},
+{"U", "&Uuml;"},
+{"Y", "&Yacute;"},
+{"P", "&THORN;"},
+{"s", "&szlig;"},
+{"a", "&agrave;"},
+{"a", "&aacute;"},
+{"a", "&acirc;"},
+{"a", "&atilde;"},
+{"a", "&auml;"},
+{"a", "&aring;"},
+{"ae", "&aelig;"},
+{"c", "&ccedil;"},
+{"e", "&egrave;"},
+{"e", "&eacute;"},
+{"e", "&ecirc;"},
+{"e", "&euml;"},
+{"i", "&igrave;"},
+{"i", "&iacute;"},
+{"i", "&icirc;"},
+{"i", "&iuml;"},
+{"o", "&eth;"},
+{"n", "&ntilde;"},
+{"o", "&ograve;"},
+{"o", "&oacute;"},
+{"o", "&ocirc;"},
+{"o", "&otilde;"},
+{"o", "&ouml;"},
+{NULL, NULL},
+{NULL, NULL},
+{"u", "&ugrave;"},
+{"u", "&uacute;"},
+{"u", "&ucirc;"},
+{"u", "&uuml;"},
+{"y", "&yacute;"},
+{"p", "&thorn:"},
+{"y", "&yuml;"}
+};
+
diff --git a/src/timer.c b/src/timer.c
new file mode 100644 (file)
index 0000000..cbb866f
--- /dev/null
@@ -0,0 +1,382 @@
+/**
+ * \file timer.c
+ *
+ * \brief Timed events in PennMUSH.
+ *
+ *
+ */
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <string.h>
+#include <signal.h>
+#include <stdlib.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#ifdef WIN32
+#include <windows.h>
+#endif
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+
+#include "conf.h"
+#include "externs.h"
+#include "attrib.h"
+#include "dbdefs.h"
+#include "lock.h"
+#include "extmail.h"
+#include "match.h"
+#include "flags.h"
+#include "access.h"
+#include "log.h"
+#include "game.h"
+#include "help.h"
+#include "parse.h"
+#include "confmagic.h"
+
+
+int on_second = 0;   /**< Have we been called at the end of a second? */
+static sig_atomic_t hup_triggered = 0;
+static sig_atomic_t usr1_triggered = 0;
+
+extern void inactivity_check(void);
+extern void reopen_logs(void);
+#ifdef _SWMP_
+extern void sql_timer();
+#endif
+static void migrate_stuff(int amount);
+
+#ifndef WIN32
+void hup_handler(int);
+void usr1_handler(int);
+
+#endif
+void dispatch(void);
+
+#ifdef HAS_ITIMER
+void signal_cpu_limit(int signo);
+#endif
+
+#ifdef MUSHCRON
+extern int run_cron(void);
+#endif /* MUSHCRON */
+
+#ifndef WIN32
+
+/** Handler for HUP signal.
+ * Do the minimal work here - set a global variable and reload the handler.
+ * \param x unused.
+ */
+void
+hup_handler(int x __attribute__ ((__unused__)))
+{
+  hup_triggered = 1;
+  reload_sig_handler(SIGHUP, hup_handler);
+}
+
+/** Handler for USR1 signal.
+ * Do the minimal work here - set a global variable and reload the handler.
+ * \param x unused.
+ */
+void
+usr1_handler(int x __attribute__ ((__unused__)))
+{
+  usr1_triggered = 1;
+  reload_sig_handler(SIGUSR1, usr1_handler);
+}
+
+#endif                         /* WIN32 */
+
+/** Set up signal handlers.
+ */
+void
+init_timer(void)
+{
+#ifndef WIN32
+  install_sig_handler(SIGHUP, hup_handler);
+  install_sig_handler(SIGUSR1, usr1_handler);
+#endif
+#ifndef PROFILING
+#ifdef HAS_ITIMER
+  install_sig_handler(SIGPROF, signal_cpu_limit);
+#endif
+#endif
+}
+
+/** Migrate some number of chunks.
+ * The requested amount is only a guideline; the actual amount
+ * migrated will be more or less due to always migrating all the
+ * attributes, locks, and mail on any given object together.
+ * \param amount the suggested number of attributes to migrate.
+ */
+static void
+migrate_stuff(int amount)
+{
+  static int start_obj = 0;
+  static chunk_reference_t **refs = NULL;
+  static int refs_size = 0;
+  int end_obj;
+  int actual;
+  ATTR *aptr;
+  lock_list *lptr;
+  MAIL *mp;
+
+  if (db_top == 0)
+    return;
+
+  end_obj = start_obj;
+  actual = 0;
+  do {
+    for (aptr = List(end_obj); aptr; aptr = AL_NEXT(aptr))
+      if (aptr->data != NULL_CHUNK_REFERENCE)
+       actual++;
+    for (lptr = Locks(end_obj); lptr; lptr = L_NEXT(lptr))
+      if (L_KEY(lptr) != NULL_CHUNK_REFERENCE)
+       actual++;
+    if (IsPlayer(end_obj)) {
+      for (mp = find_exact_starting_point(end_obj); mp; mp = mp->next)
+       if (mp->msgid != NULL_CHUNK_REFERENCE)
+         actual++;
+    }
+    end_obj = (end_obj + 1) % db_top;
+  } while (actual < amount && end_obj != start_obj);
+
+  if (actual == 0)
+    return;
+
+  if (!refs || actual > refs_size) {
+    if (refs)
+      mush_free((Malloc_t) refs, "migration reference array");
+    refs =
+      (chunk_reference_t **) mush_malloc(actual * sizeof(chunk_reference_t *),
+                                        "migration reference array");
+    refs_size = actual;
+    if (!refs)
+      mush_panic("Could not allocate migration reference array");
+  }
+#ifdef DEBUG_MIGRATE
+  do_rawlog(LT_TRACE, "Migrate asked %d, actual objects #%d to #%d for %d",
+           amount, start_obj, (end_obj + db_top - 1) % db_top, actual);
+#endif
+
+  actual = 0;
+  do {
+    for (aptr = List(start_obj); aptr; aptr = AL_NEXT(aptr))
+      if (aptr->data != NULL_CHUNK_REFERENCE) {
+       refs[actual] = &(aptr->data);
+       actual++;
+      }
+    for (lptr = Locks(start_obj); lptr; lptr = L_NEXT(lptr))
+      if (L_KEY(lptr) != NULL_CHUNK_REFERENCE) {
+       refs[actual] = &(lptr->key);
+       actual++;
+      }
+    if (IsPlayer(start_obj)) {
+      for (mp = find_exact_starting_point(start_obj); mp; mp = mp->next)
+       if (mp->msgid != NULL_CHUNK_REFERENCE) {
+         refs[actual] = &(mp->msgid);
+         actual++;
+       }
+    }
+    start_obj = (start_obj + 1) % db_top;
+  } while (start_obj != end_obj);
+
+  chunk_migration(actual, refs);
+}
+
+/** Handle events that may need handling.
+ * This routine is polled from bsd.c. At any call, it can handle
+ * the HUP and USR1 signals. At calls that are 'on the second',
+ * it goes on to perform regular every-second processing and to
+ * check whether it's time to do other periodic processes like
+ * purge, dump, or inactivity checks.
+ */
+void
+dispatch(void)
+{
+  static int idle_counter = 0;
+
+  /* A HUP reloads configuration and reopens logs */
+  if (hup_triggered) {
+    do_rawlog(LT_ERR, T("SIGHUP received: reloading .txt and .cnf files"));
+    config_file_startup(NULL, 0);
+    config_file_startup(NULL, 1);
+    fcache_load(NOTHING);
+    help_reindex(NOTHING);
+    read_access_file();
+    reopen_logs();
+    hup_triggered = 0;
+  }
+  /* A USR1 does a shutdown/reboot */
+  if (usr1_triggered) {
+    do_reboot(NOTHING, 0);     /* We don't return from this */
+    usr1_triggered = 0;                /* But just in case */
+  }
+  if (!on_second)
+    return;
+  on_second = 0;
+
+  mudtime = time(NULL);
+
+  do_second();
+
+  migrate_stuff(CHUNK_MIGRATE_AMOUNT);
+
+  if (options.purge_counter <= mudtime) {
+    /* Free list reconstruction */
+    options.purge_counter = options.purge_interval + mudtime;
+    global_eval_context.cplr = NOTHING;
+    strcpy(global_eval_context.ccom, "purge");
+    purge();
+  }
+
+  if (options.dbck_counter <= mudtime) {
+    /* Database consistency check */
+    options.dbck_counter = options.dbck_interval + mudtime;
+    global_eval_context.cplr = NOTHING;
+    strcpy(global_eval_context.ccom, "dbck");
+    dbck();
+  }
+
+  if (idle_counter <= mudtime) {
+    /* Inactivity check */
+    idle_counter = 30 + mudtime;
+    inactivity_check();
+  }
+
+  /* Database dump routines */
+  if (options.dump_counter <= mudtime) {
+    log_mem_check();
+    options.dump_counter = options.dump_interval + mudtime;
+    global_eval_context.cplr = NOTHING;
+    strcpy(global_eval_context.ccom, "dump");
+    fork_and_dump(1);
+    flag_broadcast(0, "ON-VACATION", "%s",
+                  T
+                  ("Your ON-VACATION flag is set! If you're back, clear it."));
+  } else if (NO_FORK &&
+            (options.dump_counter - 60 == mudtime) &&
+            *options.dump_warning_1min) {
+    flag_broadcast(0, 0, "%s", options.dump_warning_1min);
+  } else if (NO_FORK &&
+            (options.dump_counter - 300 == mudtime) &&
+            *options.dump_warning_5min) {
+    flag_broadcast(0, 0, "%s", options.dump_warning_5min);
+  }
+  if (options.warn_interval && (options.warn_counter <= mudtime)) {
+    options.warn_counter = options.warn_interval + mudtime;
+    global_eval_context.cplr = NOTHING;
+    strcpy(global_eval_context.ccom, "warnings");
+    run_topology();
+  }
+#ifdef MUSHCRON
+  if((mudtime % 60) == 0)
+    run_cron(); /* Run the MUSH Cron Daemon */
+#endif /* MUSHCRON */
+#ifdef _SWMP_
+  sql_timer();
+#endif
+#ifdef RPMODE_SYS 
+  rplog_reset();
+#endif
+
+  local_timer();
+}
+
+sig_atomic_t cpu_time_limit_hit = 0;  /** Was the cpu time limit hit? */
+int cpu_limit_warning_sent = 0;         /** Have we issued a cpu limit warning? */
+
+#ifndef PROFILING
+#if defined(HAS_ITIMER)
+/** Handler for PROF signal.
+ * Do the minimal work here - set a global variable and reload the handler.
+ * \param signo unused.
+ */
+void
+signal_cpu_limit(int signo __attribute__ ((__unused__)))
+{
+  cpu_time_limit_hit = 1;
+  reload_sig_handler(SIGPROF, signal_cpu_limit);
+}
+#elif defined(WIN32)
+#if _MSC_VER <= 1100 && !defined(UINT_PTR)
+#define UINT_PTR UINT
+#endif
+UINT_PTR timer_id;
+VOID CALLBACK
+win32_timer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+  cpu_time_limit_hit = 1;
+}
+#endif
+#endif
+int timer_set = 0;     /**< Is a CPU timer set? */
+
+/** Start the cpu timer (before running a command).
+ */
+void
+start_cpu_timer(void)
+{
+#ifndef PROFILING
+  cpu_time_limit_hit = 0;
+  cpu_limit_warning_sent = 0;
+  timer_set = 1;
+#if defined(HAS_ITIMER)                /* UNIX way */
+  {
+    struct itimerval time_limit;
+    if (options.queue_entry_cpu_time > 0) {
+      ldiv_t t;
+      /* Convert from milliseconds */
+      t = ldiv(options.queue_entry_cpu_time, 1000);
+      time_limit.it_value.tv_sec = t.quot;
+      time_limit.it_value.tv_usec = t.rem * 1000;
+      time_limit.it_interval.tv_sec = 0;
+      time_limit.it_interval.tv_usec = 0;
+      if (setitimer(ITIMER_PROF, &time_limit, NULL)) {
+       perror("setitimer");
+       timer_set = 0;
+      }
+    } else
+      timer_set = 0;
+  }
+#elif defined(WIN32)           /* Windoze way */
+  if (options.queue_entry_cpu_time > 0)
+    timer_id = SetTimer(NULL, 0, (unsigned) options.queue_entry_cpu_time,
+                       (TIMERPROC) win32_timer);
+  else
+    timer_set = 0;
+#endif
+#endif
+}
+
+/** Reset the cpu timer (after running a command).
+ */
+void
+reset_cpu_timer(void)
+{
+#ifndef PROFILING
+  if (timer_set) {
+#if defined(HAS_ITIMER)
+    struct itimerval time_limit, time_left;
+    time_limit.it_value.tv_sec = 0;
+    time_limit.it_value.tv_usec = 0;
+    time_limit.it_interval.tv_sec = 0;
+    time_limit.it_interval.tv_usec = 0;
+    if (setitimer(ITIMER_PROF, &time_limit, &time_left))
+      perror("setitimer");
+#elif defined(WIN32)
+    KillTimer(NULL, timer_id);
+#endif
+  }
+  cpu_time_limit_hit = 0;
+  cpu_limit_warning_sent = 0;
+  timer_set = 0;
+#endif
+}
diff --git a/src/unparse.c b/src/unparse.c
new file mode 100644 (file)
index 0000000..1acc2f4
--- /dev/null
@@ -0,0 +1,334 @@
+/**
+ * \file unparse.c
+ *
+ * \brief Convert lots of things into strings for PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <string.h>
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "dbdefs.h"
+#include "flags.h"
+#include "lock.h"
+#include "attrib.h"
+#include "ansi.h"
+#include "pueblo.h"
+#include "parse.h"
+#include "confmagic.h"
+
+static int couldunparse;
+
+/** Format an object's name (and dbref and flags).
+ * This is a wrapper for real_unparse() that conditionally applies
+ * pueblo xch_cmd tags to make the object's name a hyperlink to examine
+ * it.
+ * \param player the looker.
+ * \param loc the object being looked at.
+ * \return static formatted object name string.
+ */
+const char *
+unparse_object(dbref player, dbref loc)
+{
+  static PUEBLOBUFF;
+  const char *result;
+  result = real_unparse(player, loc, 0, 0, 0);
+  if (couldunparse) {
+    PUSE;
+    tag_wrap("A", tprintf("XCH_CMD=\"examine #%d\"", loc), result);
+    PEND;
+    return pbuff;
+  } else {
+    return result;
+  }
+}
+
+/** Format an object's name, obeying MYOPIC/ownership rules.
+ * This is a wrapper for real_unparse() that conditionally applies
+ * pueblo xch_cmd tags to make the object's name a hyperlink to examine
+ * it.
+ * \param player the looker.
+ * \param loc the object being looked at.
+ * \return static formatted object name string.
+ */
+const char *
+unparse_object_myopic(dbref player, dbref loc)
+{
+  static PUEBLOBUFF;
+  const char *result;
+  result = real_unparse(player, loc, 1, 0, 1);
+  if (couldunparse) {
+    PUSE;
+    tag_wrap("A", tprintf("XCH_CMD=\"examine #%d\"", loc), result);
+    PEND;
+    return pbuff;
+  } else {
+    return result;
+  }
+}
+
+/** Format an object's name, obeying MYOPIC/ownership and NAMEFORMAT.
+ * \verbatim
+ * Like unparse_object, but tell real_unparse to use @NAMEFORMAT if present
+ * This should only be used if we're looking at our container, to prevent
+ * confusion in matching, since you can't match containers with their
+ * names anyway, but only with 'here'.
+ * \endverbatim
+ * \param player the looker.
+ * \param loc the object being looked at.
+ * \return static formatted object name string.
+ */
+const char *
+unparse_room(dbref player, dbref loc)
+{
+  static PUEBLOBUFF;
+  const char *result;
+  result = real_unparse(player, loc, 1, 1, 1);
+  if (couldunparse) {
+    PUSE;
+    tag_wrap("A", tprintf("XCH_CMD=\"examine #%d\"", loc), result);
+    PEND;
+    return pbuff;
+  } else {
+    return result;
+  }
+}
+
+/** Format an object's name in several ways.
+ * This function does the real work of converting a dbref to a
+ * name, possibly including the dbref and flags, possibly using
+ * a nameformat or nameaccent if present.
+ * \param player the looker.
+ * \param loc dbref of the object being looked at.
+ * \param obey_myopic if 0, always show Name(#xxxFLAGS); if 1, don't
+ * do so if player is MYOPIC or doesn't own loc.
+ * \param use_nameformat if 1, apply a NAMEFORMAT attribute if available.
+ * \param use_nameaccent if 1, apply a NAMEACCENT attribute if available.
+ * \return address of a static buffer containing the formatted name.
+ */
+/* As to the addition of the know system.  if both objects are 'fixed', they
+ * are considered 'IC' so we will use a know name for 'em
+ ***/
+const char *
+real_unparse(dbref player, dbref loc, int obey_myopic, int use_nameformat,
+            int use_nameaccent)
+{
+  static char buf[BUFFER_LEN], *bp;
+  static char tbuf1[BUFFER_LEN];
+  char *p;
+  int got_nameformat = 0;
+
+  couldunparse = 0;
+  if (!(GoodObject(loc) || (loc == NOTHING) || (loc == AMBIGUOUS) ||
+       (loc == HOME)))
+    return T("*NOTHING*");
+  switch (loc) {
+  case NOTHING:
+    return T("*NOTHING*");
+  case AMBIGUOUS:
+    return T("*VARIABLE*");
+  case HOME:
+    return T("*HOME*");
+  default:
+    if (use_nameformat && nameformat(player, loc, buf)) {
+      strcpy(tbuf1, buf);
+      got_nameformat = 1;
+    } else {
+      /* Not using @nameformat or couldn't get one */
+      if (use_nameaccent)
+       strcpy(tbuf1, accented_name(loc));
+      else
+       strcpy(tbuf1, Name(loc));
+    }
+    if (IsExit(loc) && obey_myopic) {
+      if ((p = strchr(tbuf1, ';')))
+       *p = '\0';
+    }
+    /* Don't let 'em get dbrefs when they're IC */
+    if ((Can_Examine(player, loc) || can_link_to(player, loc) ||
+        JumpOk(loc) || ChownOk(loc) || DestOk(loc)) &&
+       (!Myopic(player) || !obey_myopic) &&
+       !(use_nameformat && got_nameformat)
+       ) {
+      /* show everything */
+      if (SUPPORT_PUEBLO)
+       couldunparse = 1;
+      bp = buf;
+      if (ANSI_NAMES && ShowAnsi(player) && !got_nameformat)
+       safe_format(buf, &bp, "%s%s%s(#%d%s)", ANSI_HILITE, tbuf1,
+                   ANSI_NORMAL, loc, unparse_flags(loc, player));
+      else
+       safe_format(buf, &bp, "%s(#%d%s)", tbuf1, loc,
+                   unparse_flags(loc, player));
+      *bp = '\0';
+      return buf;
+    } else {
+      /* show only the name */
+      if (ANSI_NAMES && ShowAnsi(player) && !got_nameformat) {
+       bp = buf;
+       safe_format(buf, &bp, "%s%s%s", ANSI_HILITE, tbuf1, ANSI_NORMAL);
+       *bp = '\0';
+       return buf;
+      } else
+       return tbuf1;
+    }
+  }
+}
+
+/** Build the name of loc as seen by a player inside it, but only
+ * if it has a NAMEFORMAT.
+ * This function needs to avoid using a static buffer, so pass in 
+ * a pointer to an allocated BUFFER_LEN array.
+ * \param player the looker.
+ * \param loc dbref of location being looked at.
+ * \param tbuf1 address to store formatted name of loc.
+ * \retval 1 a NAMEFORMAT was found, and tbuf1 contains formatted name.
+ * \retval 0 no NAMEFORMAT on loc, tbuf1 is undefined.
+ */
+int
+nameformat(dbref player, dbref loc, char *tbuf1)
+{
+  ATTR *a;
+  char *wsave[10], *rsave[NUMQ];
+  char *arg, *bp;
+  char const *sp, *save;
+
+  int j;
+  a = atr_get(loc, "NAMEFORMAT");
+  if (a) {
+    arg = (char *) mush_malloc(BUFFER_LEN, "string");
+    if (!arg)
+      mush_panic("Unable to allocate memory in nameformat");
+    save_global_regs("nameformat", rsave);
+    for (j = 0; j < 10; j++) {
+      wsave[j] = global_eval_context.wenv[j];
+      global_eval_context.wenv[j] = NULL;
+    }
+    for (j = 0; j < NUMQ; j++)
+      global_eval_context.renv[j][0] = '\0';
+    strcpy(arg, unparse_dbref(loc));
+    global_eval_context.wenv[0] = arg;
+    sp = save = safe_atr_value(a);
+    bp = tbuf1;
+    process_expression(tbuf1, &bp, &sp, loc, player, player,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    free((Malloc_t) save);
+    for (j = 0; j < 10; j++) {
+      global_eval_context.wenv[j] = wsave[j];
+    }
+    restore_global_regs("nameformat", rsave);
+    mush_free((Malloc_t) arg, "string");
+    return 1;
+  } else {
+    /* No @nameformat attribute */
+    return 0;
+  }
+}
+
+/** Give a string representation of a dbref.
+ * \param num value to stringify
+ * \return address of static buffer containing stringified value.
+ */
+char *
+unparse_dbref(dbref num)
+{
+  /* Not BUFFER_LEN, but no dbref will come near this long */
+  static char str[SBUF_LEN];
+  char *strp;
+
+  strp = str;
+  safe_dbref(num, str, &strp);
+  *strp = '\0';
+  return str;
+}
+
+/** Give a string representation of an integer.
+ * \param num value to stringify
+ * \return address of static buffer containing stringified value.
+ */
+char *
+unparse_integer(int num)
+{
+  static char str[SBUF_LEN];
+  char *strp;
+
+  strp = str;
+  safe_integer_sbuf(num, str, &strp);
+  *strp = '\0';
+  return str;
+}
+
+/** Give a string representation of an unsigned integer.
+ * \param num value to stringify
+ * \return address of static buffer containing stringified value.
+ */
+char *
+unparse_uinteger(unsigned int num)
+{
+  static char str[16];
+
+  sprintf(str, "%u", num);
+  return str;
+}
+
+
+/** Give a string representation of a number.
+ * \param num value to stringify
+ * \return address of static buffer containing stringified value.
+ */
+char *
+unparse_number(NVAL num)
+{
+  static char str[100];                /* Should be large enough for even the HUGE floats */
+  char *p;
+  sprintf(str, "%.*f", FLOAT_PRECISION, num);
+
+  if ((p = strchr(str, '.'))) {
+    p += strlen(p);
+    while (p[-1] == '0')
+      p--;
+    if (p[-1] == '.')
+      p--;
+    *p = '\0';
+  }
+  return str;
+}
+
+/** Return the name of an object, applying NAMEACCENT if set.
+ * \param thing dbref of object.
+ * \return address of static buffer containing object name, with accents
+ * if object has a valid NAMEACCENT attribute.
+ */
+const char *
+accented_name(dbref thing)
+{
+  ATTR *na;
+  static char fbuf[BUFFER_LEN];
+
+  na = atr_get(thing, "NAMEACCENT");
+  if (!na)
+    return Name(thing);
+  else {
+    char tbuf[BUFFER_LEN];
+    char *bp = fbuf;
+    size_t len;
+
+    strcpy(tbuf, atr_value(na));
+
+    len = strlen(Name(thing));
+
+    if (len != strlen(tbuf))
+      return Name(thing);
+
+    safe_accent(Name(thing), tbuf, len, fbuf, &bp);
+    *bp = '\0';
+
+    return fbuf;
+  }
+}
diff --git a/src/utils.c b/src/utils.c
new file mode 100644 (file)
index 0000000..4078d73
--- /dev/null
@@ -0,0 +1,784 @@
+/**
+ * \file utils.c
+ *
+ * \brief Utility functions for PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#include <stdio.h>
+#include <limits.h>
+#ifdef sgi
+#include <math.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <time.h>
+#ifdef I_SYS_TYPES
+#include <sys/types.h>
+#endif
+#ifdef I_SYS_STAT
+#include <sys/stat.h>
+#endif
+#include <fcntl.h>
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#ifdef WIN32
+#include <wtypes.h>
+#include <winbase.h>           /* For GetCurrentProcessId() */
+#endif
+#include "conf.h"
+
+#include "match.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "mymalloc.h"
+#include "log.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "attrib.h"
+#include "lock.h"
+#include "confmagic.h"
+
+dbref find_entrance(dbref door);
+void initialize_mt(void);
+static unsigned long genrand_int32(void);
+static long genrand_int31(void);
+static void init_genrand(unsigned long);
+static void init_by_array(unsigned long *, int);
+extern int local_can_interact_first(dbref from, dbref to, int type);
+extern int local_can_interact_last(dbref from, dbref to, int type);
+
+/** A malloc wrapper that tracks type of allocation.
+ * This should be used in preference to malloc() when possible,
+ * to enable memory leak tracing with MEM_CHECK.
+ * \param size bytes to allocate.
+ * \param check string to label allocation with.
+ * \return allocated block of memory or NULL.
+ */
+Malloc_t
+mush_malloc(size_t size, const char *check)
+{
+  Malloc_t ptr;
+  add_check(check);
+  ptr = malloc(size);
+  if (ptr == NULL)
+    do_log(LT_ERR, 0, 0, "mush_malloc failed to malloc %d bytes for %s",
+          size, check);
+  return ptr;
+}
+
+/** A free wrapper that tracks type of allocation.
+ * If mush_malloc() gets the memory, mush_free() should free it
+ * to enable memory leak tracing with MEM_CHECK.
+ * \param ptr pointer to block of member to free.
+ * \param check string to label allocation with.
+ */
+void
+mush_free(Malloc_t RESTRICT ptr, const char *RESTRICT check
+         __attribute__ ((__unused__)))
+{
+  del_check(check);
+  free(ptr);
+  return;
+}
+
+
+/** Parse object/attribute strings into components.
+ * This function takes a string which is of the format obj/attr or attr,
+ * and returns the dbref of the object, and a pointer to the attribute.
+ * If no object is specified, then the dbref returned is the player's.
+ * str is destructively modified. This function is probably underused.
+ * \param player the default object.
+ * \param str the string to parse.
+ * \param thing pointer to dbref of object parsed out of string.
+ * \param attrib pointer to pointer to attribute structure retrieved.
+ */
+void
+parse_attrib(dbref player, char *str, dbref *thing, ATTR **attrib)
+{
+  char *name;
+
+  /* find the object */
+
+  if ((name = strchr(str, '/')) != NULL) {
+    *name++ = '\0';
+    *thing = noisy_match_result(player, str, NOTYPE, MAT_EVERYTHING);
+  } else {
+    name = str;
+    *thing = player;
+  }
+
+  /* find the attribute */
+  *attrib = (ATTR *) atr_get(*thing, upcasestr(name));
+}
+
+/** Parse an attribute or anonymous attribute into dbref and pointer.
+ * This function takes a string which is of the format #lambda/code, 
+ * <obj>/<attr> or <attr>,  and returns the dbref of the object, 
+ * and a pointer to the attribute.
+ * \param player the executor, for permissions checks.
+ * \param str string to parse.
+ * \param thing pointer to address to return dbref parsed, or NOTHING
+ * if none could be parsed.
+ * \param attrib pointer to address to return ATTR * of attrib parsed,
+ * or NULL if none could be parsed.
+ */
+void
+parse_anon_attrib(dbref player, char *str, dbref *thing, ATTR **attrib)
+{
+
+  if (string_prefix(str, "#lambda/")) {
+    unsigned char *t;
+    str += 8;
+    if (!*str) {
+      *attrib = NULL;
+      *thing = NOTHING;
+    } else {
+      *attrib = mush_malloc(sizeof(ATTR), "anon_attr");
+      AL_CREATOR(*attrib) = player;
+      AL_NAME(*attrib) = strdup("#lambda");
+      t = compress(str);
+      (*attrib)->data = chunk_create(t, (u_int_16) u_strlen(t), 0);
+      AL_RLock(*attrib) = AL_WLock(*attrib) = TRUE_BOOLEXP;
+      AL_FLAGS(*attrib) = AF_ANON;
+      AL_NEXT(*attrib) = NULL;
+      *thing = player;
+      return;
+    }
+  }
+  parse_attrib(player, str, thing, attrib);
+}
+
+/** Free the memory allocated for an anonymous attribute.
+ * \param attrib pointer to attribute.
+ */
+void
+free_anon_attrib(ATTR *attrib)
+{
+  if (attrib && (AL_FLAGS(attrib) & AF_ANON)) {
+    free((char *) AL_NAME(attrib));
+    chunk_delete(attrib->data);
+    mush_free(attrib, "anon_attr");
+  }
+}
+
+/** Given an exit, find the room that is its source through brute force.
+ * This is used in pathological cases where the exit's own source
+ * element is invalid.
+ * \param door dbref of exit to find source of.
+ * \return dbref of exit's source room, or NOTHING.
+ */
+dbref
+find_entrance(dbref door)
+{
+  dbref room;
+  dbref thing;
+  for (room = 0; room < db_top; room++)
+    if (IsRoom(room)) {
+      thing = Exits(room);
+      while (thing != NOTHING) {
+       if (thing == door)
+         return room;
+       thing = Next(thing);
+      }
+    }
+  return NOTHING;
+}
+
+/** Remove the first occurence of what in chain headed by first.
+ * This works for contents and exit chains.
+ * \param first dbref of first object in chain.
+ * \param what dbref of object to remove from chain.
+ * \return new head of chain.
+ */
+dbref
+remove_first(dbref first, dbref what)
+{
+  dbref prev;
+  /* special case if it's the first one */
+  if (first == what) {
+    return Next(first);
+  } else {
+    /* have to find it */
+    DOLIST(prev, first) {
+      if (Next(prev) == what) {
+       Next(prev) = Next(what);
+       return first;
+      }
+    }
+    return first;
+  }
+}
+
+/** Is an object on a chain?
+ * \param thing object to look for.
+ * \param list head of chain to search.
+ * \retval 1 found thing on list.
+ * \retval 0 did not find thing on list.
+ */
+int
+member(dbref thing, dbref list)
+{
+  DOLIST(list, list) {
+    if (list == thing)
+      return 1;
+  }
+
+  return 0;
+}
+
+
+/** Is an object inside another, at any level of depth?
+ * That is, we check if disallow is inside of from, i.e., if 
+ * loc(disallow) = from, or loc(loc(disallow)) = from, etc., with a 
+ * depth limit of 50.
+ * Despite the name of this function, it's not recursive any more.
+ * \param disallow interior object to check.
+ * \param from check if disallow is inside of this object.
+ * \param count depths of nesting checked so far.
+ * \retval 1 disallow is inside of from.
+ * \retval 0 disallow is not inside of from.
+ */
+int
+recursive_member(dbref disallow, dbref from, int count)
+{
+  do {
+    /* The end of the location chain. This is a room. */
+    if (!GoodObject(disallow) || IsRoom(disallow))
+      return 0;
+
+    if (from == disallow)
+      return 1;
+
+    disallow = Location(disallow);
+    count++;
+  } while (count <= 50);
+
+  return 1;
+}
+
+/** Is an object or its location unfindable?
+ * \param thing object to check.
+ * \retval 1 object or location is unfindable.
+ * \retval 0 neither object nor location is unfindable.
+ */
+int
+unfindable(dbref thing)
+{
+  int count = 0;
+  do {
+    if (!GoodObject(thing))
+      return 0;
+    if (Unfind(thing))
+      return 1;
+    if (IsRoom(thing))
+      return 0;
+    thing = Location(thing);
+    count++;
+  } while (count <= 50);
+  return 0;
+}
+
+
+/** Reverse the order of a dbref chain.
+ * \param list dbref at the head of the chain.
+ * \return dbref at the head of the reversed chain.
+ */
+dbref
+reverse(dbref list)
+{
+  dbref newlist;
+  dbref rest;
+  newlist = NOTHING;
+  while (list != NOTHING) {
+    rest = Next(list);
+    PUSH(list, newlist);
+    list = rest;
+  }
+  return newlist;
+}
+
+
+#define N 624 /**< PRNG constant */
+
+/* We use the Mersenne Twister PRNG. It's quite good as PRNGS go,
+ * much better than the typical ones provided in system libc's.
+ *
+ * The following two functions are based on the reference implementation,
+ * with changes in the seeding function to use /dev/urandom as a seed
+ * if possible.
+ *
+ * The Mersenne Twister homepage is:
+ *  http://www.math.keio.ac.jp/~matumoto/emt.html
+ *
+ * You can get the reference code there.
+ */
+
+
+/** Wrapper to choose a seed and initialize the Mersenne Twister PRNG. */
+void
+initialize_mt(void)
+{
+#ifdef HAS_DEV_URANDOM
+  int fd;
+  unsigned long buf[N];
+
+  fd = open("/dev/urandom", O_RDONLY);
+  if (fd >= 0) {
+    int r = read(fd, buf, sizeof buf);
+    close(fd);
+    if (r <= 0) {
+      do_rawlog(LT_ERR,
+               "Couldn't read from /dev/urandom! Resorting to normal seeding method.");
+    } else {
+      do_rawlog(LT_ERR, "Seeded RNG from /dev/urandom");
+      init_by_array(buf, r / sizeof(unsigned long));
+      return;
+    }
+  } else
+    do_rawlog(LT_ERR,
+             "Couldn't open /dev/urandom to seed random number generator. Resorting to normal seeding method.");
+
+#endif
+  /* Default seeder. Pick a seed that's fairly random */
+#ifdef WIN32
+  init_genrand(GetCurrentProcessId() | (time(NULL) << 16));
+#else
+  init_genrand(getpid() | (time(NULL) << 16));
+#endif
+}
+
+
+/* A C-program for MT19937, with initialization improved 2002/1/26.*/
+/* Coded by Takuji Nishimura and Makoto Matsumoto.                 */
+
+/* Before using, initialize the state by using init_genrand(seed)  */
+/* or init_by_array(init_key, key_length).                         */
+
+/* This library is free software.                                  */
+/* This library is distributed in the hope that it will be useful, */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of  */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.            */
+
+/* Copyright (C) 1997, 2002 Makoto Matsumoto and Takuji Nishimura. */
+/* Any feedback is very welcome.                                   */
+/* http://www.math.keio.ac.jp/matumoto/emt.html                    */
+/* email: matumoto@math.keio.ac.jp                                 */
+
+/* Period parameters */
+#define M 397  /**< PRNG constant */
+#define MATRIX_A 0x9908b0dfUL  /**< PRNG constant vector a */
+#define UPPER_MASK 0x80000000UL        /**< PRNG most significant w-r bits */
+#define LOWER_MASK 0x7fffffffUL        /**< PRNG least significant r bits */
+
+static unsigned long mt[N];    /* the array for the state vector  */
+static int mti = N + 1;                /* mti==N+1 means mt[N] is not initialized */
+
+/** initializes mt[N] with a seed.
+ * \param a seed value.
+ */
+static void
+init_genrand(unsigned long s)
+{
+  mt[0] = s & 0xffffffffUL;
+  for (mti = 1; mti < N; mti++) {
+    mt[mti] = (1812433253UL * (mt[mti - 1] ^ (mt[mti - 1] >> 30)) + mti);
+    /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+    /* In the previous versions, MSBs of the seed affect   */
+    /* only MSBs of the array mt[].                        */
+    /* 2002/01/09 modified by Makoto Matsumoto             */
+    mt[mti] &= 0xffffffffUL;
+    /* for >32 bit machines */
+  }
+}
+
+/** initialize by an array with array-length
+ * \param init_key the array for initializing keys 
+ * \param key_length the array's length 
+ */
+static void
+init_by_array(unsigned long init_key[], int key_length)
+{
+  int i, j, k;
+  init_genrand(19650218UL);
+  i = 1;
+  j = 0;
+  k = (N > key_length ? N : key_length);
+  for (; k; k--) {
+    mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1664525UL))
+      + init_key[j] + j;       /* non linear */
+    mt[i] &= 0xffffffffUL;     /* for WORDSIZE > 32 machines */
+    i++;
+    j++;
+    if (i >= N) {
+      mt[0] = mt[N - 1];
+      i = 1;
+    }
+    if (j >= key_length)
+      j = 0;
+  }
+  for (k = N - 1; k; k--) {
+    mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1566083941UL))
+      - i;                     /* non linear */
+    mt[i] &= 0xffffffffUL;     /* for WORDSIZE > 32 machines */
+    i++;
+    if (i >= N) {
+      mt[0] = mt[N - 1];
+      i = 1;
+    }
+  }
+
+  mt[0] = 0x80000000UL;                /* MSB is 1; assuring non-zero initial array */
+}
+
+/* generates a random number on [0,0xffffffff]-interval */
+static unsigned long
+genrand_int32(void)
+{
+  unsigned long y;
+  static unsigned long mag01[2] = { 0x0UL, MATRIX_A };
+  /* mag01[x] = x * MATRIX_A  for x=0,1 */
+
+  if (mti >= N) {              /* generate N words at one time */
+    int kk;
+
+    if (mti == N + 1)          /* if init_genrand() has not been called, */
+      init_genrand(5489UL);    /* a default initial seed is used */
+
+    for (kk = 0; kk < N - M; kk++) {
+      y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
+      mt[kk] = mt[kk + M] ^ (y >> 1) ^ mag01[y & 0x1UL];
+    }
+    for (; kk < N - 1; kk++) {
+      y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
+      mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
+    }
+    y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
+    mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ mag01[y & 0x1UL];
+
+    mti = 0;
+  }
+
+  y = mt[mti++];
+
+  /* Tempering */
+  y ^= (y >> 11);
+  y ^= (y << 7) & 0x9d2c5680UL;
+  y ^= (y << 15) & 0xefc60000UL;
+  y ^= (y >> 18);
+
+  return y;
+}
+
+/* generates a random number on [0,0x7fffffff]-interval */
+static long
+genrand_int31(void)
+{
+  return (long) (genrand_int32() >> 1);
+}
+
+/** Get a uniform random long between low and high values, inclusive.
+ * Based on MUX's RandomINT32()
+ * \param low lower bound for random number.
+ * \param high upper bound for random number.
+ * \return random number between low and high, or 0 or -1 for error.
+ */
+long
+get_random_long(long low, long high)
+{
+  unsigned long x, n, n_limit;
+
+  /* Validate parameters */
+  if (high < low) {
+    return 0;
+  } else if (high == low) {
+    return low;
+  }
+
+  x = high - low;
+  if (LONG_MAX < x) {
+    return -1;
+  }
+  x++;
+
+  /* We can now look for an random number on the interval [0,x-1].
+     //
+
+     // In order to be perfectly conservative about not introducing any
+     // further sources of statistical bias, we're going to call getrand()
+     // until we get a number less than the greatest representable
+     // multiple of x. We'll then return n mod x.
+     //
+     // N.B. This loop happens in randomized constant time, and pretty
+     // damn fast randomized constant time too, since
+     //
+     //      P(UINT32_MAX_VALUE - n < UINT32_MAX_VALUE % x) < 0.5, for any x.
+     //
+     // So even for the least desireable x, the average number of times
+     // we will call getrand() is less than 2.
+   */
+
+  n_limit = ULONG_MAX - (ULONG_MAX % x);
+
+  do {
+    n = genrand_int31();
+  } while (n >= n_limit);
+
+  return low + (n % x);
+}
+
+/** Return an object's name, but for exits, return just the first
+ * component. We expect a valid object.
+ * \param it dbref of object.
+ * \return object's short name.
+ */
+char *
+shortname(dbref it)
+{
+  static char n[BUFFER_LEN];   /* STATIC */
+  char *s;
+
+  strncpy(n, Name(it), BUFFER_LEN - 1);
+  n[BUFFER_LEN - 1] = '\0';
+  if (IsExit(it)) {
+    if ((s = strchr(n, ';')))
+      *s = '\0';
+  }
+  return n;
+}
+
+/** Return the absolute room (outermost container) of an object.
+ * Return  NOTHING if it's in an invalid object or in an invalid
+ * location or AMBIGUOUS if there are too many containers.
+ * \param it dbref of object.
+ * \return absolute room of object, NOTHING, or AMBIGUOUS.
+ */
+dbref
+absolute_room(dbref it)
+{
+  int rec = 0;
+  dbref room;
+  if (!GoodObject(it))
+    return NOTHING;
+  room = Location(it);
+  if (!GoodObject(room))
+    return NOTHING;
+  while (!IsRoom(room)) {
+    room = Location(room);
+    rec++;
+    if (rec > 20)
+      return AMBIGUOUS;
+  }
+  return room;
+}
+
+
+/** Can one object interact with/perceive another in a given way?
+ * This funtion checks to see if 'to' can perceive something from
+ * 'from'. The types of interactions currently supported include:
+ * INTERACT_SEE (will light rays from 'from' reach 'to'?), INTERACT_HEAR
+ * (will sound from 'from' reach 'to'?), INTERACT_MATCH (can 'to'
+ * match the name of 'from'?), and INTERACT_PRESENCE (will the arrival/
+ * departure/connection/disconnection/growing ears/losing ears of 
+ * 'from' be noticed by 'to'?).
+ * \param from object of interaction.
+ * \param to subject of interaction, attempting to interact with from.
+ * \param type type of interaction.
+ * \retval 1 to can interact with from in this way.
+ * \retval 0 to can not interact with from in this way.
+ */
+int
+can_interact(dbref from, dbref to, int type)
+{
+  int lci;
+
+  /* This shouldn't even be checked for rooms and garbage, but we're
+   * paranoid. Trying to stop interaction with yourself will not work 99%
+   * of the time, so we don't allow it anyway. */
+  if (IsGarbage(from) || IsGarbage(to))
+    return 0;
+
+  if ((from == to) || IsRoom(from) || IsRoom(to))
+    return 1;
+
+  /* This function can override standard checks! */
+  lci = local_can_interact_first(from, to, type);
+  if (lci != NOTHING)
+    return lci;
+
+  /* Standard checks */
+
+  /* If it's an audible message, it must pass your Interact_Lock
+   * (or be from a privileged speaker)
+   */
+  if ((type == INTERACT_HEAR) && !Pass_Interact_Lock(from, to))
+    return 0;
+
+  /* You can interact with the object you are in or any objects
+   * you're holding.
+   * You can interact with objects you control, but not
+   * specifically the other way around
+   */
+  if ((from == Location(to)) || (to == Location(from)) || controls(to, from))
+    return 1;
+
+  lci = local_can_interact_last(from, to, type);
+  if (lci != NOTHING)
+    return lci;
+
+  return 1;
+}
+
+#ifdef KNOW_SYS
+/* know system core code */
+/* Design in Simple:  
+ * In an IC zone, or in a 'fixed' status.  People will only see others in there @XY_KNOW attribute
+ * If they are not in that attribute, their @RACE will show in place following a - & the occurance
+ * of that race in the room.
+ */
+
+/* checks if p1 knows p2.
+ * If no, returns 0
+ * If yes, returns 1
+ */
+char check_know(dbref p1, dbref p2) {
+       ATTR *a;
+       dbref num;
+       char *b, *s, *e;
+       char di = 0;  /* variable set when in dbref isolating */
+
+       /* Quick Check first */
+       if(p1 == p2)
+               return 1;
+       /* load @XY_KNOW attribute into the register, & check for p2 in it */
+
+       a = atr_get(p1, "XY_KNOW");
+       if(!a) /* they know no one */
+               return 0;
+       b = s = e = safe_atr_value(a);
+       if(!s)
+               return 0;
+
+       /* isolate each dbref in the attribute & check if p2 matches anywhere */
+       while(*s && *e) 
+               if(di) {
+                       if(*e && e[1] != '\0' && !isspace(*e)) {
+                               e++;
+                               continue;
+                       }
+                       if(isspace(*e))
+                         *e = '\0';
+                       num = parse_dbref(s);
+                       if(num == p2) { /* FOUND 'EM! */ 
+                               mush_free(b, "ATRFREE");
+                               return 1;
+                       }
+                       if(e[1] != '\0') {
+                               di = 0;
+                               s = e++;  
+                               s++;
+                       } else break;
+               } else if(*s == '#') 
+                       di = 1;
+               else if(isspace(*s))
+                       s++, e++;  
+       mush_free(b,"ATRFREE");
+       return 0;
+}
+
+
+#define ROCC_LIMIT 25
+/* search which occ 'player' is in the room for their race & attach it to @RACE */
+/* *sigh* This function does alot of shit for something that is called alot.
+ * Best not to use the know system on a slow machine or a machine with alot
+ * of overhead
+ */
+const char *know_name_qk(dbref player) {
+       static char final_buffer[ROCC_LIMIT];
+       ATTR *a, *ao;
+       char *race, *ro, *p;
+       dbref spot;
+       /* to navigate backwards through content list */
+       struct object_ptr {
+               dbref cur;
+               struct object_ptr *back;
+       } *optr_add,*optr_nav;
+       int occ = 0;
+
+        /* ONLY PLAYERS! OR WE CRASH! */
+
+        if(Typeof(player) != TYPE_PLAYER)
+          return shortname(player);
+
+       /* initialize variables */
+       memset(final_buffer, '\0', ROCC_LIMIT);
+       p = final_buffer;
+       spot = Contents(Location(player));
+
+
+       /* Isolate player Race. */
+       a = atr_get(player, "RACE");
+       if(!a) 
+               race = strdup(DEF_RACE_NAME);
+        else race = safe_atr_value(a);
+
+       safe_str(race, final_buffer, &p);
+
+       /* Now we have the race, loop through location contents to see which 'occurance' player
+        * is of that race
+        */
+       /* first lets make our backwrds list */
+       optr_nav = optr_add = NULL;
+       DOLIST(spot,spot) {
+               if(!IsPlayer(spot))
+                       continue;
+               optr_nav = optr_add;
+               optr_add = mush_malloc(sizeof(struct object_ptr *) , "OPTR_ADD");
+               if(!optr_add)
+                 mush_panic("Can't Allocate OPTR_ADD");
+               if(optr_nav == NULL) {
+                       optr_add->back = NULL;
+               } else optr_add->back = optr_nav;
+               optr_add->cur = spot;
+       }
+
+       /* now loop through our backwards list */
+       for(optr_nav = optr_add; optr_nav ; optr_nav = optr_nav->back) {
+               /* grab race */
+               ao = atr_get(optr_nav->cur, "RACE");
+               if(!ao)
+                       ro = strdup(DEF_RACE_NAME); /* It's your average no race dude */
+               else ro = safe_atr_value(ao);
+               if(!strcasecmp(race, ro)) { 
+                       occ++;
+                       if(optr_nav->cur == player) break;
+               }
+
+               free(ro); 
+       }
+       /* attach occ to final_buffer */
+       safe_chr('-', final_buffer, &p);
+       safe_number(occ, final_buffer, &p);
+
+         
+       /* free up & return */
+       for(optr_nav = optr_add;;optr_nav = optr_add) {
+               if(!optr_nav)
+                 break;
+               optr_add = optr_add->back;
+               mush_free(optr_nav, "OPTR_ADD");
+       }
+
+       free(race);
+       free(ro);
+       *p = '\0';
+       return final_buffer;
+       
+}
+#endif
diff --git a/src/version.c b/src/version.c
new file mode 100644 (file)
index 0000000..3d0d909
--- /dev/null
@@ -0,0 +1,51 @@
+/* This file defines the @version command. It's all by itself because
+ * we want to rebuild this file at every compilation, so that the
+ * BUILDDATE is correct
+ */
+#include "config.h"
+#include "copyrite.h"
+
+#ifdef I_STRING
+#include <string.h>
+#else
+#include <strings.h>
+#endif
+#include <time.h>
+#include "conf.h"
+
+#include "externs.h"
+#include "version.h"
+#include "patches.h"
+#ifndef WIN32
+#include "buildinf.h"
+#endif
+#include "confmagic.h"
+
+extern time_t start_time;      /* from bsd.c */
+void do_version _((dbref player));
+
+void
+do_version(player)
+    dbref player;
+{
+  char buff[BUFFER_LEN];
+
+  notify_format(player, T("You are connected to %s"), MUDNAME);
+
+  strcpy(buff, ctime(&start_time));
+  buff[strlen(buff) - 1] = '\0';       /* eat the newline */
+  notify_format(player, T("Last restarted: %s"), buff);
+
+  notify_format(player, "CobraMUSH v%s [%s]", VERSION, VBRANCH);
+#ifdef PATCHES
+  notify_format(player, "Patches: %s", PATCHES);
+#endif
+#ifdef WIN32
+  notify_format(player, T("Build date: %s"), __DATE__);
+#else
+  notify_format(player, T("Build date: %s"), BUILDDATE);
+  notify_format(player, T("Compiler: %s"), COMPILER);
+  notify_format(player, T("Compilation flags: %s"), CCFLAGS);
+  notify_format(player, T("Malloc package: %d"), MALLOC_PACKAGE);
+#endif
+}
diff --git a/src/warnings.c b/src/warnings.c
new file mode 100644 (file)
index 0000000..0d32105
--- /dev/null
@@ -0,0 +1,525 @@
+/**
+ * \file warnings.c
+ *
+ * \brief Check topology and messages on PennMUSH objects and give warnings
+ *
+ *
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "copyrite.h"
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "lock.h"
+#include "flags.h"
+#include "dbdefs.h"
+#include "match.h"
+#include "attrib.h"
+#include "confmagic.h"
+
+
+/* We might check for both locked and unlocked warnings if we can't
+ * figure out a lock.
+ */
+#define W_UNLOCKED      0x1    /**< Check for unlocked-object warnings */
+#define W_LOCKED        0x2    /**< Check for locked-object warnings */
+
+#define W_EXIT_ONEWAY   0x1    /**< Find one-way exits */
+#define W_EXIT_MULTIPLE 0x2    /**< Find multiple exits to same place */
+#define W_EXIT_MSGS     0x4    /**< Find exits without messages */
+#define W_EXIT_DESC     0x8    /**< Find exits without descs */
+#define W_EXIT_UNLINKED 0x10   /**< Find unlinked exits */
+/* Space for more exit stuff */
+#define W_THING_MSGS    0x100  /**< Find things without messages */
+#define W_THING_DESC    0x200  /**< Find things without descs */
+/* Space for more thing stuff */
+#define W_ROOM_DESC     0x1000 /**< Find rooms without descs */
+/* Space for more room stuff */
+#define W_PLAYER_DESC   0x10000        /**< Find players without descs */
+
+#define W_LOCK_PROBS    0x100000       /**< Find bad locks */
+
+/* Groups of warnings */
+#define W_NONE          0      /**< No warnings */
+/** Serious warnings only */
+#define W_SERIOUS       (W_EXIT_UNLINKED|W_THING_DESC|W_ROOM_DESC|W_PLAYER_DESC|W_LOCK_PROBS)
+/** Standard warnings: serious warnings plus others */
+#define W_NORMAL        (W_SERIOUS|W_EXIT_ONEWAY|W_EXIT_MULTIPLE|W_EXIT_MSGS)
+/** Extra warnings: standard warnings plus others */
+#define W_EXTRA         (W_NORMAL|W_THING_MSGS)
+/** All warnings */
+#define W_ALL           (W_EXTRA|W_EXIT_DESC)
+
+
+/** A structure representing a topology warning check. */
+typedef struct a_tcheck {
+  const char *name;    /**< Name of warning. */
+  warn_type flag;      /**< Bitmask of warning. */
+} tcheck;
+
+
+extern int warning_lock_type(const boolexp l);
+void complain(dbref player, dbref i, const char *name, const char *desc, ...)
+  __attribute__ ((__format__(__printf__, 4, 5)));
+extern void check_lock(dbref player, dbref i, const char *name, boolexp be);
+static void ct_generic(dbref player, dbref i, warn_type flags);
+static void ct_room(dbref player, dbref i, warn_type flags);
+static void ct_exit(dbref player, dbref i, warn_type flags);
+static void ct_player(dbref player, dbref i, warn_type flags);
+static void ct_thing(dbref player, dbref i, warn_type flags);
+
+
+static tcheck checklist[] = {
+  {"none", W_NONE},            /* MUST BE FIRST! */
+  {"exit-unlinked", W_EXIT_UNLINKED},
+  {"thing-desc", W_THING_DESC},
+  {"room-desc", W_ROOM_DESC},
+  {"my-desc", W_PLAYER_DESC},
+  {"exit-oneway", W_EXIT_ONEWAY},
+  {"exit-multiple", W_EXIT_MULTIPLE},
+  {"exit-msgs", W_EXIT_MSGS},
+  {"thing-msgs", W_THING_MSGS},
+  {"exit-desc", W_EXIT_DESC},
+  {"lock-checks", W_LOCK_PROBS},
+
+  /* These should stay at the end */
+  {"serious", W_SERIOUS},
+  {"normal", W_NORMAL},
+  {"extra", W_EXTRA},
+  {"all", W_ALL},
+  {NULL, 0}
+};
+
+/** Issue a warning about an object.
+ * \param player player to receive the warning notification.
+ * \param i object the warning is about.
+ * \param name name of the warnings.
+ * \param desc a formatting string for the warning message.
+ */
+void
+complain(dbref player, dbref i, const char *name, const char *desc, ...)
+{
+#ifdef HAS_VSNPRINTF
+  char buff[BUFFER_LEN];
+#else
+  char buff[BUFFER_LEN * 3];   /* safety margin */
+#endif
+  va_list args;
+
+  va_start(args, desc);
+
+#ifdef HAS_VSNPRINTF
+  vsnprintf(buff, sizeof buff, desc, args);
+#else
+  vsprintf(buff, desc, args);
+#endif
+
+  buff[BUFFER_LEN - 1] = '\0';
+  va_end(args);
+
+  notify_format(player, T("Warning '%s' for %s:"),
+               name, unparse_object(player, i));
+  notify(player, buff);
+}
+
+static void
+ct_generic(dbref player, dbref i, warn_type flags)
+{
+  if ((flags & W_LOCK_PROBS)) {
+    lock_list *ll;
+    for (ll = Locks(i); ll; ll = L_NEXT(ll)) {
+      check_lock(player, i, L_TYPE(ll), L_KEY(ll));
+    }
+  }
+}
+
+static void
+ct_room(dbref player, dbref i, warn_type flags)
+{
+  if ((flags & W_ROOM_DESC) && !atr_get(i, "DESCRIBE"))
+    complain(player, i, "room-desc", T("room has no description"));
+}
+
+static void
+ct_exit(dbref player, dbref i, warn_type flags)
+{
+  dbref j, src, dst;
+  int count = 0;
+  int lt;
+
+  /* i must be an exit, must be in a valid room, and must lead to a
+   * different room
+   * Remember, for exit i, Exits(i) = source room
+   * and Location(i) = destination room
+   */
+
+  dst = Destination(i);
+  if ((flags & W_EXIT_UNLINKED) && (dst == NOTHING))
+    complain(player, i, "exit-unlinked",
+            T("exit is unlinked; anyone can steal it"));
+
+  if ((flags & W_EXIT_UNLINKED) && dst == AMBIGUOUS) {
+    ATTR *a;
+    const char *var = "DESTINATION";
+    a = atr_get(i, "DESTINATION");
+    if (!a)
+      a = atr_get(i, "EXITTO");
+    if (a)
+      var = "EXITTO";
+    if (!a)
+      complain(player, i, "exit-unlinked",
+              T("Variable exit has no %s attribute"), var);
+    else {
+      const char *x = atr_value(a);
+      if (!x || !*x)
+       complain(player, i, "exit-unlinked",
+                T("Variable exit has empty %s attribute"), var);
+    }
+  }
+
+  if (!Dark(i)) {
+    if (flags & W_EXIT_MSGS) {
+      lt = warning_lock_type(getlock(i, Basic_Lock));
+      if ((lt & W_UNLOCKED) &&
+         (!atr_get(i, "OSUCCESS") || !atr_get(i, "ODROP") ||
+          !atr_get(i, "SUCCESS")))
+       complain(player, i, "exit-msgs",
+                T("possibly unlocked exit missing succ/osucc/odrop"));
+      if ((lt & W_LOCKED) && !atr_get(i, "FAILURE"))
+       complain(player, i, "exit-msgs",
+                T("possibly locked exit missing fail"));
+    }
+    if (flags & W_EXIT_DESC) {
+      if (!atr_get(i, "DESCRIBE"))
+       complain(player, i, "exit-desc", T("exit is missing description"));
+    }
+  }
+  src = Source(i);
+  if (!GoodObject(src) || !IsRoom(src))
+    return;
+  if (src == dst)
+    return;
+  /* Don't complain about exits linked to HOME or variable exits. */
+  if (!GoodObject(dst))
+    return;
+
+  for (j = Exits(dst); GoodObject(j); j = Next(j))
+    if (Location(j) == src) {
+      if (!(flags & W_EXIT_MULTIPLE))
+       return;
+      else
+       count++;
+    }
+  if ((count == 0) && (flags & W_EXIT_ONEWAY))
+    complain(player, i, "exit-oneway", T("exit has no return exit"));
+  else if ((count > 1) && (flags & W_EXIT_MULTIPLE))
+    complain(player, i, "exit-multiple",
+            T("exit has multiple (%d) return exits"), count);
+}
+
+
+
+static void
+ct_player(dbref player, dbref i, warn_type flags)
+{
+  if ((flags & W_PLAYER_DESC) && !atr_get(i, "DESCRIBE"))
+    complain(player, i, "my-desc", T("player is missing description"));
+}
+
+
+
+static void
+ct_thing(dbref player, dbref i, warn_type flags)
+{
+  int lt;
+
+  /* Ignore carried objects */
+  if (Location(i) == player)
+    return;
+  if ((flags & W_THING_DESC) && !atr_get(i, "DESCRIBE"))
+    complain(player, i, "thing-desc", T("thing is missing description"));
+
+  if (flags & W_THING_MSGS) {
+    lt = warning_lock_type(getlock(i, Basic_Lock));
+    if ((lt & W_UNLOCKED) &&
+       (!atr_get(i, "OSUCCESS") || !atr_get(i, "ODROP") ||
+        !atr_get(i, "SUCCESS") || !atr_get(i, "DROP")))
+      complain(player, i, "thing-msgs",
+              T("possibly unlocked thing missing succ/osucc/drop/odrop"));
+    if ((lt & W_LOCKED) && !atr_get(i, "FAILURE"))
+      complain(player, i, "thing-msgs",
+              T("possibly locked thing missing fail"));
+  }
+}
+
+/** Set up the default warnings on an object.
+ * \param player object to set warnings on.
+ */
+void
+set_initial_warnings(dbref player)
+{
+  Warnings(player) = W_NORMAL;
+  return;
+}
+
+/** Set warnings on an object.
+ * \verbatim
+ * This implements @warnings obj=warning list
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of object to set warnings on.
+ * \param warns list of warnings to set, space-separated.
+ */
+void
+do_warnings(dbref player, const char *name, const char *warns)
+{
+  dbref thing;
+  warn_type w;
+
+  switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
+  case NOTHING:
+    notify(player, T("I don't see that object."));
+    return;
+  case AMBIGUOUS:
+    notify(player, T("I don't know which one you mean."));
+    return;
+  default:
+    if (!controls(player, thing)) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    if (IsGarbage(thing)) {
+      notify(player, T("Why would you want to be warned about garbage?"));
+      return;
+    }
+    break;
+  }
+
+  w = parse_warnings(player, warns);
+  if (w >= 0) {
+    Warnings(thing) = w;
+    if (Warnings(thing))
+      notify_format(player, T("@warnings set to: %s"),
+                   unparse_warnings(Warnings(thing)));
+    else
+      notify(player, T("@warnings cleared."));
+  } else {
+    notify(player, T("@warnings not changed."));
+  }
+}
+
+/** Given a list of warnings, return the bitmask that represents it.
+ * \param player dbref to report errors to, or NOTHING.
+ * \param warnings the string of warning names
+ * \return a warning bitmask
+ */
+warn_type
+parse_warnings(dbref player, const char *warnings)
+{
+  int found = 0;
+  warn_type flags, negate_flags;
+  char tbuf1[BUFFER_LEN];
+  char *w, *s;
+  tcheck *c;
+
+  flags = W_NONE;
+  negate_flags = W_NONE;
+  if (warnings && *warnings) {
+    strcpy(tbuf1, warnings);
+    /* Loop through whatever's listed and add on those warnings */
+    s = trim_space_sep(tbuf1, ' ');
+    w = split_token(&s, ' ');
+    while (w && *w) {
+      found = 0;
+      if (*w == '!') {
+       /* Found a negated warning */
+       w++;
+       for (c = checklist; c->name; c++) {
+         if (!strcasecmp(w, c->name)) {
+           negate_flags |= c->flag;
+           found++;
+         }
+       }
+      } else {
+       for (c = checklist; c->name; c++) {
+         if (!strcasecmp(w, c->name)) {
+           flags |= c->flag;
+           found++;
+         }
+       }
+      }
+      /* At this point, we haven't matched any warnings. */
+      if (!found && player != NOTHING) {
+       notify_format(player, T("Unknown warning: %s"), w);
+      }
+      w = split_token(&s, ' ');
+    }
+    /* If we haven't matched anything, don't change the player's stuff */
+    if (!found)
+      return -1;
+    return flags & ~negate_flags;
+  } else
+    return 0;
+}
+
+/** Given a warning bitmask, return a string of warnings on it.
+ * \param warns the warnings.
+ * \return pointer to statically allocated string listing warnings.
+ */
+const char *
+unparse_warnings(warn_type warns)
+{
+  static char tbuf1[BUFFER_LEN];
+  int listsize, indexx;
+
+  tbuf1[0] = '\0';
+
+  /* Get the # of elements in checklist */
+  listsize = sizeof(checklist) / sizeof(tcheck);
+
+  /* Point c at last non-null in checklist, and go backwards */
+  for (indexx = listsize - 2; warns && (indexx >= 0); indexx--) {
+    warn_type the_flag = checklist[indexx].flag;
+    if (!(the_flag & ~warns)) {
+      /* Which is to say:
+       * if the bits set on the_flag is a subset of the bits set on warns
+       */
+      strcat(tbuf1, checklist[indexx].name);
+      strcat(tbuf1, " ");
+      /* If we've got a flag which subsumes smaller ones, don't
+       * list the smaller ones
+       */
+      warns &= ~the_flag;
+    }
+  }
+  return tbuf1;
+}
+
+static void
+check_topology_on(dbref player, dbref i)
+{
+  warn_type flags;
+
+  /* Skip it if it's NOWARN or the player checking is the owner and
+   * is NOWARN.  Also skip GOING objects.
+   */
+  if (Going(i) || NoWarn(i))
+    return;
+
+  /* If the owner is checking, use the flags on the object, and fall back
+   * on the owner's flags as default. If it's not the owner checking
+   * (therefore, an admin), ignore the object flags, use the admin's flags
+   */
+  if (Owner(player) == Owner(i)) {
+    if (!(flags = Warnings(i)))
+      flags = Warnings(player);
+  } else
+    flags = Warnings(player);
+
+  ct_generic(player, i, flags);
+
+  switch (Typeof(i)) {
+  case TYPE_ROOM:
+    ct_room(player, i, flags);
+    break;
+  case TYPE_THING:
+    ct_thing(player, i, flags);
+    break;
+  case TYPE_EXIT:
+    ct_exit(player, i, flags);
+    break;
+  case TYPE_PLAYER:
+    ct_player(player, i, flags);
+    break;
+  }
+  return;
+}
+
+
+/** Loop through all objects and check their topology.  */
+void
+run_topology(void)
+{
+  int ndone;
+  for (ndone = 0; ndone < db_top; ndone++) {
+    if (!IsGarbage(ndone) && Connected(Owner(ndone)) && !NoWarn(Owner(ndone))) {
+      check_topology_on(Owner(ndone), ndone);
+    }
+  }
+}
+
+/** Admin command to check all objects.
+ * \verbatim
+ * This implements @wcheck/all.
+ * \endverbatim
+ * \param player the enactor.
+ */
+void
+do_wcheck_all(dbref player)
+{
+  if (!Site(player)) {
+    notify(player, T("You'd better check your site power first."));
+    return;
+  }
+  notify(player, T("Running database topology warning checks"));
+  run_topology();
+  notify(player, T("Warning checks complete."));
+}
+
+/** Check warnings on a specific player by themselves.
+ * \param player player checking warnings on their objects.
+ */
+void
+do_wcheck_me(dbref player)
+{
+  int ndone;
+  if (!Connected(player))
+    return;
+  for (ndone = 0; ndone < db_top; ndone++) {
+    if ((Owner(ndone) == player) && !IsGarbage(ndone))
+      check_topology_on(player, ndone);
+  }
+  notify(player, T("@wcheck complete."));
+  return;
+}
+
+
+/** Check warnings on a specific object.
+ * We check for ownership or hasprivs before allowing this.
+ * \param player the enactor.
+ * \param name name of object to check.
+ */
+void
+do_wcheck(dbref player, const char *name)
+{
+  dbref thing;
+
+  switch (thing = match_result(player, name, NOTYPE, MAT_EVERYTHING)) {
+  case NOTHING:
+    notify(player, T("I don't see that object."));
+    return;
+  case AMBIGUOUS:
+    notify(player, T("I don't know which one you mean."));
+    return;
+  default:
+    if (!(CanSee(player, thing) || (Owner(player) == Owner(thing)))) {
+      notify(player, T("Permission denied."));
+      return;
+    }
+    if (IsGarbage(thing)) {
+      notify(player, T("Why would you want to be warned about garbage?"));
+      return;
+    }
+    break;
+  }
+
+  check_topology_on(player, thing);
+  notify(player, T("@wcheck complete."));
+  return;
+}
diff --git a/src/wild.c b/src/wild.c
new file mode 100644 (file)
index 0000000..c561f92
--- /dev/null
@@ -0,0 +1,675 @@
+/**
+ * \file wild.c
+ *
+ * \brief Wildcard matching routings for PennMUSH
+ *
+ * Written by T. Alexander Popiel, 24 June 1993
+ * Last modified by Javelin, 2002-2003
+ *
+ * Thanks go to Andrew Molitor for debugging
+ * Thanks also go to Rich $alz for code to benchmark against
+ *
+ * Copyright (c) 1993,2000 by T. Alexander Popiel
+ * This code is available under the terms of the GPL,
+ * see http://www.gnu.org/copyleft/gpl.html for details.
+ *
+ * This code is included in PennMUSH under the PennMUSH
+ * license by special dispensation from the author,
+ * T. Alexander Popiel.  If you wish to include this
+ * code in other packages, but find the GPL too onerous,
+ * then please feel free to contact the author at
+ * popiel@wolfskeep.com to work out agreeable terms.
+ */
+
+#include "config.h"
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "copyrite.h"
+#include "conf.h"
+#include "case.h"
+#include "externs.h"
+#include "mymalloc.h"
+#include "parse.h"
+#include "pcre.h"
+#include "confmagic.h"
+
+/** Force a char to be lowercase */
+#define FIXCASE(a) (DOWNCASE(a))
+/** Check for equality of characters, maybe case-sensitive */
+#define EQUAL(cs,a,b) ((cs) ? (a == b) : (FIXCASE(a) == FIXCASE(b)))
+/** Check for inequality of characters, maybe case-sensitive */
+#define NOTEQUAL(cs,a,b) ((cs) ? (a != b) : (FIXCASE(a) != FIXCASE(b)))
+/** Maximum number of wildcarded arguments */
+#define NUMARGS (10)
+
+const unsigned char *tables = NULL;  /** Pointer to character tables */
+
+static char wspace[3 * BUFFER_LEN + NUMARGS];  /* argument return buffer */
+                                               /* big to match tprintf */
+
+static int wild1
+  (const char *RESTRICT tstr, const char *RESTRICT dstr, int arg,
+   char *RESTRICT wbuf, int cs);
+static int wild(const char *RESTRICT s, const char *RESTRICT d, int p, int cs);
+static int check_literals(const char *RESTRICT tstr, const char *RESTRICT dstr,
+                         int cs);
+static char *strip_backslashes(const char *str);
+
+/** Do a wildcard match, without remembering the wild data.
+ *
+ * This routine will cause crashes if fed NULLs instead of strings.
+ *
+ * \param tstr pattern to match against.
+ * \param dstr string to check.
+ * \retval 1 dstr matches the tstr pattern.
+ * \retval 0 dstr does not match the tstr pattern.
+ */
+int
+quick_wild(const char *RESTRICT tstr, const char *RESTRICT dstr)
+{
+  /* quick_wild_new does the real work, but before we call it, 
+   * we do some sanity checking. 
+   */
+  if (!check_literals(tstr, dstr, 0))
+    return 0;
+  return quick_wild_new(tstr, dstr, 0);
+}
+
+/** Do a wildcard match, possibly case-sensitive, without memory.
+ *
+ * This probably crashes if fed NULLs instead of strings, too.
+ *
+ * \param tstr pattern to match against.
+ * \param dstr string to check.
+ * \param cs if 1, case-sensitive; if 0, case-insensitive.
+ * \retval 1 dstr matches the tstr pattern.
+ * \retval 0 dstr does not match the tstr pattern.
+ */
+int
+quick_wild_new(const char *RESTRICT tstr, const char *RESTRICT dstr, int cs)
+{
+  while (*tstr != '*') {
+    switch (*tstr) {
+    case '?':
+      /* Single character match.  Return false if at
+       * end of data.
+       */
+      if (!*dstr)
+       return 0;
+      break;
+    case '\\':
+      /* Escape character.  Move up, and force literal
+       * match of next character.
+       */
+      tstr++;
+      /* FALL THROUGH */
+    default:
+      /* Literal character.  Check for a match.
+       * If matching end of data, return true.
+       */
+      if (NOTEQUAL(cs, *dstr, *tstr))
+       return 0;
+      if (!*dstr)
+       return 1;
+    }
+    tstr++;
+    dstr++;
+  }
+
+  /* Skip over '*'. */
+  tstr++;
+
+  /* Return true on trailing '*'. */
+  if (!*tstr)
+    return 1;
+
+  /* Skip over wildcards. */
+  while ((*tstr == '?') || (*tstr == '*')) {
+    if (*tstr == '?') {
+      if (!*dstr)
+       return 0;
+      dstr++;
+    }
+    tstr++;
+  }
+
+  /* Skip over a backslash in the pattern string if it is there. */
+  if (*tstr == '\\')
+    tstr++;
+
+  /* Return true on trailing '*'. */
+  if (!*tstr)
+    return 1;
+
+  /* Scan for possible matches. */
+  while (*dstr) {
+    if (EQUAL(cs, *dstr, *tstr) && quick_wild_new(tstr + 1, dstr + 1, cs))
+      return 1;
+    dstr++;
+  }
+  return 0;
+}
+
+/** Do an attribute name wildcard match.
+ *
+ * This probably crashes if fed NULLs instead of strings, too.
+ * The special thing about this one is that ` doesn't match normal
+ * wildcards; you have to use ** to match embedded `.  Also, patterns
+ * ending in ` are treated as patterns ending in `*, and empty patterns
+ * are treated as *.
+ *
+ * \param tstr pattern to match against.
+ * \param dstr string to check.
+ * \retval 1 dstr matches the tstr pattern.
+ * \retval 0 dstr does not match the tstr pattern.
+ */
+int
+atr_wild(const char *RESTRICT tstr, const char *RESTRICT dstr)
+{
+  int starcount;
+
+  if (!*tstr)
+    return !strchr(dstr, '`');
+
+  while (*tstr != '*') {
+    switch (*tstr) {
+    case '?':
+      /* Single character match.  Return false if at
+       * end of data.
+       */
+      if (!*dstr || *dstr == '`')
+       return 0;
+      break;
+    case '`':
+      /* Delimiter match.  Special handling if at end of pattern. */
+      if (*dstr != '`')
+       return 0;
+      if (!tstr[1])
+       return !strchr(dstr + 1, '`');
+      break;
+    case '\\':
+      /* Escape character.  Move up, and force literal
+       * match of next character.
+       */
+      tstr++;
+      /* FALL THROUGH */
+    default:
+      /* Literal character.  Check for a match.
+       * If matching end of data, return true.
+       */
+      if (NOTEQUAL(0, *dstr, *tstr))
+       return 0;
+      if (!*dstr)
+       return 1;
+    }
+    tstr++;
+    dstr++;
+  }
+
+  /* Skip over '*'. */
+  tstr++;
+  starcount = 1;
+
+  /* Skip over wildcards. */
+  while (starcount < 2 && ((*tstr == '?') || (*tstr == '*'))) {
+    if (*tstr == '?') {
+      if (!*dstr || *dstr == '`')
+       return 0;
+      dstr++;
+      starcount = 0;
+    } else
+      starcount++;
+    tstr++;
+  }
+
+  /* Skip over long strings of '*'. */
+  while (*tstr == '*')
+    tstr++;
+
+  /* Return true on trailing '**'. */
+  if (!*tstr)
+    return starcount == 2 || !strchr(dstr, '`');
+
+  if (*tstr == '?') {
+    /* Scan for possible matches. */
+    while (*dstr) {
+      if (*dstr != '`' && atr_wild(tstr + 1, dstr + 1))
+       return 1;
+      dstr++;
+    }
+  } else {
+    /* Skip over a backslash in the pattern string if it is there. */
+    if (*tstr == '\\')
+      tstr++;
+
+    /* Scan for possible matches. */
+    while (*dstr) {
+      if (EQUAL(0, *dstr, *tstr) && atr_wild(tstr + 1, dstr + 1))
+       return 1;
+      if (starcount < 2 && *dstr == '`')
+       return 0;
+      dstr++;
+    }
+  }
+  return 0;
+}
+
+/* ---------------------------------------------------------------------------
+ * wild1: INTERNAL: do a wildcard match, remembering the wild data.
+ *
+ * DO NOT CALL THIS FUNCTION DIRECTLY - DOING SO MAY RESULT IN
+ * SERVER CRASHES AND IMPROPER ARGUMENT RETURN.
+ *
+ * Side Effect: this routine modifies the 'global_eval_context.wnxt' global variable,
+ * and what it points to.
+ */
+static int
+wild1(const char *RESTRICT tstr, const char *RESTRICT dstr, int arg,
+      char *RESTRICT wbuf, int cs)
+{
+  const char *datapos;
+  char *wnext;
+  int argpos, numextra;
+
+  while (*tstr != '*') {
+    switch (*tstr) {
+    case '?':
+      /* Single character match.  Return false if at
+       * end of data.
+       */
+      if (!*dstr)
+       return 0;
+
+      global_eval_context.wnxt[arg++] = wbuf;
+      *wbuf++ = *dstr;
+      *wbuf++ = '\0';
+
+      /* Jump to the fast routine if we can. */
+
+      if (arg >= NUMARGS)
+       return quick_wild_new(tstr + 1, dstr + 1, cs);
+      break;
+    case '\\':
+      /* Escape character.  Move up, and force literal
+       * match of next character.
+       */
+      tstr++;
+      /* FALL THROUGH */
+    default:
+      /* Literal character.  Check for a match.
+       * If matching end of data, return true.
+       */
+      if (NOTEQUAL(cs, *dstr, *tstr))
+       return 0;
+      if (!*dstr)
+       return 1;
+    }
+    tstr++;
+    dstr++;
+  }
+
+  /* If at end of pattern, slurp the rest, and leave. */
+  if (!tstr[1]) {
+    global_eval_context.wnxt[arg] = wbuf;
+    strcpy(wbuf, dstr);
+    return 1;
+  }
+  /* Remember current position for filling in the '*' return. */
+  datapos = dstr;
+  argpos = arg;
+
+  /* Scan forward until we find a non-wildcard. */
+  do {
+    if (argpos < arg) {
+      /* Fill in arguments if someone put another '*'
+       * before a fixed string.
+       */
+      global_eval_context.wnxt[argpos++] = wbuf;
+      *wbuf++ = '\0';
+
+      /* Jump to the fast routine if we can. */
+      if (argpos >= NUMARGS)
+       return quick_wild_new(tstr, dstr, cs);
+
+      /* Fill in any intervening '?'s */
+      while (argpos < arg) {
+       global_eval_context.wnxt[argpos++] = wbuf;
+       *wbuf++ = *datapos++;
+       *wbuf++ = '\0';
+
+       /* Jump to the fast routine if we can. */
+       if (argpos >= NUMARGS)
+         return quick_wild_new(tstr, dstr, cs);
+      }
+    }
+    /* Skip over the '*' for now... */
+    tstr++;
+    arg++;
+
+    /* Skip over '?'s for now... */
+    numextra = 0;
+    while (*tstr == '?') {
+      if (!*dstr)
+       return 0;
+      tstr++;
+      dstr++;
+      arg++;
+      numextra++;
+    }
+  } while (*tstr == '*');
+
+  /* Skip over a backslash in the pattern string if it is there. */
+  if (*tstr == '\\')
+    tstr++;
+
+  /* Check for possible matches.  This loop terminates either at
+   * end of data (resulting in failure), or at a successful match.
+   */
+  if (!*tstr)
+    while (*dstr)
+      dstr++;
+  else {
+    wnext = wbuf;
+    wnext++;
+    while (1) {
+      if (EQUAL(cs, *dstr, *tstr) &&
+         ((arg < NUMARGS) ? wild1(tstr, dstr, arg, wnext, cs)
+          : quick_wild_new(tstr, dstr, cs)))
+       break;
+      if (!*dstr)
+       return 0;
+      dstr++;
+      wnext++;
+    }
+  }
+
+  /* Found a match!  Fill in all remaining arguments.
+   * First do the '*'...
+   */
+  global_eval_context.wnxt[argpos++] = wbuf;
+  strncpy(wbuf, datapos, (dstr - datapos) - numextra);
+  wbuf += (dstr - datapos) - numextra;
+  *wbuf++ = '\0';
+  datapos = dstr - numextra;
+
+  /* Fill in any trailing '?'s that are left. */
+  while (numextra) {
+    if (argpos >= NUMARGS)
+      return 1;
+    global_eval_context.wnxt[argpos++] = wbuf;
+    *wbuf++ = *datapos++;
+    *wbuf++ = '\0';
+    numextra--;
+  }
+
+  /* It's done! */
+  return 1;
+}
+
+/* ---------------------------------------------------------------------------
+ * wild: do a wildcard match, remembering the wild data.
+ *
+ * This routine will cause crashes if fed NULLs instead of strings.
+ *
+ * This function may crash if malloc() fails.
+ *
+ * Side Effect: this routine modifies the 'global_eval_context.wnxt' global variable.
+ */
+static int
+wild(const char *RESTRICT s, const char *RESTRICT d, int p, int cs)
+{
+  /* Do fast match. */
+  while ((*s != '*') && (*s != '?')) {
+    if (*s == '\\')
+      s++;
+    if (NOTEQUAL(cs, *d, *s))
+      return 0;
+    if (!*d)
+      return 1;
+    s++;
+    d++;
+  }
+
+  /* Do sanity check */
+  if (!check_literals(s, d, cs))
+    return 0;
+
+  /* Do the match. */
+  return wild1(s, d, p, wspace, cs);
+}
+
+/** Wildcard match, possibly case-sensitive, and remember the wild data.
+ *
+ * This routine will cause crashes if fed NULLs instead of strings.
+ *
+ * \param s pattern to match against.
+ * \param d string to check.
+ * \param cs if 1, case-sensitive; if 0, case-insensitive.
+ * \retval 1 d matches s.
+ * \retval 0 d doesn't match s.
+ */
+int
+wild_match_case(const char *RESTRICT s, const char *RESTRICT d, int cs)
+{
+  int j;
+  /* Clear %0-%9 and r(0) - r(9) */
+  for (j = 0; j < NUMARGS; j++)
+    global_eval_context.wnxt[j] = (char *) NULL;
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = (char *) NULL;
+  return wild(s, d, 0, cs);
+}
+
+/** Regexp match, possibly case-sensitive, and remember matched subexpressions.
+ *
+ * This routine will cause crashes if fed NULLs instead of strings.
+ *
+ * \param s regexp to match against.
+ * \param d string to check.
+ * \param cs if 1, case-sensitive; if 0, case-insensitive.
+ * \retval 1 d matches s.
+ * \retval 0 d doesn't match s.
+ */
+int
+regexp_match_case(const char *RESTRICT s, const char *RESTRICT d, int cs)
+{
+  int j;
+  pcre *re;
+  int i;
+  static char wtmp[NUMARGS][BUFFER_LEN];
+  const char *errptr;
+  int erroffset;
+  int offsets[99];
+  int subpatterns;
+
+  if ((re = pcre_compile(s, (cs ? 0 : PCRE_CASELESS), &errptr, &erroffset,
+                        tables)) == NULL) {
+    /*
+     * This is a matching error. We have an error message in
+     * errptr that we can ignore, since we're doing
+     * command-matching.
+     */
+    return 0;
+  }
+  add_check("pcre");
+  /* 
+   * Now we try to match the pattern. The relevant fields will
+   * automatically be filled in by this.
+   */
+  if ((subpatterns = pcre_exec(re, NULL, d, strlen(d), 0, 0, offsets, 99))
+      < 0) {
+    mush_free(re, "pcre");
+    return 0;
+  }
+  /* If we had too many subpatterns for the offsets vector, set the number
+   * to 1/3 of the size of the offsets vector
+   */
+  if (subpatterns == 0)
+    subpatterns = 33;
+
+  /*
+   * Now we fill in our args vector. Note that in regexp matching,
+   * 0 is the entire string matched, and the parenthesized strings
+   * go from 1 to 9. We DO PRESERVE THIS PARADIGM, for consistency
+   * with other languages.
+   */
+
+  /* Clear %0-%9 and r(0) - r(9) */
+  for (j = 0; j < NUMARGS; j++) {
+    wtmp[j][0] = '\0';
+    global_eval_context.wnxt[j] = (char *) NULL;
+  }
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = (char *) NULL;
+
+  for (i = 0; (i < 10) && (i < NUMARGS); i++) {
+    pcre_copy_substring(d, offsets, subpatterns, i, wtmp[i], BUFFER_LEN);
+    global_eval_context.wnxt[i] = wtmp[i];
+  }
+
+  mush_free(re, "pcre");
+  return 1;
+}
+
+
+/** Regexp match, possibly case-sensitive, and with no memory.
+ *
+ * This routine will cause crashes if fed NULLs instead of strings.
+ *
+ * \param s regexp to match against.
+ * \param d string to check.
+ * \param cs if 1, case-sensitive; if 0, case-insensitive.
+ * \retval 1 d matches s.
+ * \retval 0 d doesn't match s.
+ */
+int
+quick_regexp_match(const char *RESTRICT s, const char *RESTRICT d, int cs)
+{
+  pcre *re;
+  const char *errptr;
+  int erroffset;
+  int offsets[99];
+  int r;
+  int flags = 0;               /* There's a PCRE_NO_AUTO_CAPTURE flag to turn all raw
+                                  ()'s into (?:)'s, which would be nice to use,
+                                  except that people might use backreferences in
+                                  their patterns. Argh. */
+
+  if (!cs)
+    flags |= PCRE_CASELESS;
+
+  if ((re = pcre_compile(s, flags, &errptr, &erroffset, tables)) == NULL) {
+    /*
+     * This is a matching error. We have an error message in
+     * errptr that we can ignore, since we're doing
+     * command-matching.
+     */
+    return 0;
+  }
+  add_check("pcre");
+  /* 
+   * Now we try to match the pattern. The relevant fields will
+   * automatically be filled in by this.
+   */
+  r = pcre_exec(re, NULL, d, strlen(d), 0, 0, offsets, 99);
+
+  mush_free(re, "pcre");
+
+  return r >= 0;
+}
+
+
+/** Either an order comparison or a wildcard match with no memory.
+ *
+ * This routine will cause crashes if fed NULLs instead of strings.
+ *
+ * \param s pattern to match against.
+ * \param d string to check.
+ * \param cs if 1, case-sensitive; if 0, case-insensitive.
+ * \retval 1 d matches s.
+ * \retval 0 d doesn't match s.
+ */
+int
+local_wild_match_case(const char *RESTRICT s, const char *RESTRICT d, int cs)
+{
+  switch (*s) {
+  case '>':
+    s++;
+    if (is_number(s) && is_number(d))
+      return (parse_number(s) < parse_number(d));
+    else
+      return (strcoll(s, d) < 0);
+  case '<':
+    s++;
+    if (is_number(s) && is_number(d))
+      return (parse_number(s) > parse_number(d));
+    else
+      return (strcoll(s, d) > 0);
+  }
+
+  return quick_wild_new(s, d, cs);
+}
+
+/** Does a string contain a wildcard character (* or ?)?
+ * Not used by the wild matching routines, but suitable for outside use.
+ * \param s string to check.
+ * \retval 1 s contains a * or ?
+ * \retval 0 s does not contain a * or ?
+ */
+int
+wildcard(const char *s)
+{
+  if (strchr(s, '*') || strchr(s, '?'))
+    return 1;
+  return 0;
+}
+
+static int
+check_literals(const char *RESTRICT tstr, const char *RESTRICT dstr, int cs)
+{
+  /* Every literal string in tstr must appear, in order, in dstr,
+   * or no match can happen. That is, tstr is the pattern and dstr
+   * is the string-to-match
+   */
+  char tbuf1[BUFFER_LEN];
+  char dbuf1[BUFFER_LEN];
+  const char delims[] = "?*";
+  char *sp, *dp;
+  strncpy(dbuf1, dstr, BUFFER_LEN - 1);
+  dbuf1[BUFFER_LEN - 1] = '\0';
+  strcpy(tbuf1, strip_backslashes(tstr));
+  if (!cs) {
+    upcasestr(tbuf1);
+    upcasestr(dbuf1);
+  }
+  dp = dbuf1;
+  sp = strtok(tbuf1, delims);
+  while (sp) {
+    if (!dp)
+      return 0;
+    if (!(dp = strstr(dp, sp)))
+      return 0;
+    dp += strlen(sp);
+    sp = strtok(NULL, delims);
+  }
+  return 1;
+}
+
+
+static char *
+strip_backslashes(const char *str)
+{
+  /* Remove backslashes from a string, and return it in a static buffer */
+  static char buf[BUFFER_LEN];
+  int i = 0;
+
+  while (*str && (i < BUFFER_LEN - 1)) {
+    if (*str == '\\' && *(str + 1))
+      str++;
+    buf[i++] = *str++;
+  }
+  buf[i] = '\0';
+  return buf;
+}
diff --git a/src/wiz.c b/src/wiz.c
new file mode 100644 (file)
index 0000000..b4d2cfb
--- /dev/null
+++ b/src/wiz.c
@@ -0,0 +1,2090 @@
+/**
+ * \file wiz.c
+ *
+ * \brief Admin commands in PennMUSH.
+ *
+ *
+ */
+
+#include "copyrite.h"
+#include "config.h"
+
+#ifdef I_UNISTD
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <math.h>
+#ifdef I_SYS_TIME
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#include <stdlib.h>
+#include <ctype.h>
+#include <signal.h>
+#ifdef I_FCNTL
+#include <fcntl.h>
+#endif
+#ifdef WIN32
+#include <windows.h>
+#include "process.h"
+#endif
+#include "conf.h"
+#include "externs.h"
+#include "mushdb.h"
+#include "attrib.h"
+#include "match.h"
+#include "access.h"
+#include "parse.h"
+#include "mymalloc.h"
+#include "flags.h"
+#include "lock.h"
+#include "log.h"
+#include "game.h"
+#include "command.h"
+#include "dbdefs.h"
+#include "extmail.h"
+
+
+#include "confmagic.h"
+
+extern dbref find_entrance(dbref door);
+struct db_stat_info *get_stats(dbref owner);
+extern dbref find_player_by_desc(int port);
+extern int paranoid_dump;
+extern int paranoid_checkpt;
+
+
+#ifndef WIN32
+#ifdef I_SYS_FILE
+#include <sys/file.h>
+#endif
+#endif
+
+int tport_dest_ok(dbref player, dbref victim, dbref dest);
+int tport_control_ok(dbref player, dbref victim, dbref loc);
+static int mem_usage(dbref thing);
+static int raw_search(dbref player, const char *owner, const char *class,
+                     const char *restriction, const char *start,
+                     const char *stop, dbref **result, PE_Info * pe_info);
+
+#ifdef INFO_SLAVE
+void kill_info_slave(void);
+#endif
+
+extern char confname[BUFFER_LEN];
+extern char errlog[BUFFER_LEN];
+
+/** Create a player by admin fiat.
+ * \verbatim
+ * This implements @pcreate.
+ * \endverbatim
+ * \param creator the enactor.
+ * \param player_name name of player to create.
+ * \param player_password password for player.
+ * \return dbref of created player object, or NOTHING if failure.
+ */
+dbref
+do_pcreate(dbref creator, const char *player_name, const char *player_password)
+{
+  dbref player;
+
+  if (!Create_Player(creator)) {
+    notify(creator, T("You do not have the power over body and mind!"));
+    return NOTHING;
+  }
+  if (!can_pay_fees(creator, 0))
+    return NOTHING;
+  player = create_player(player_name, player_password, "None", "None");
+  if (player == NOTHING) {
+    notify_format(creator, T("Failure creating '%s' (bad name)"), player_name);
+    return NOTHING;
+  }
+  if (player == AMBIGUOUS) {
+    notify_format(creator, T("Failure creating '%s' (bad password)"),
+                 player_name);
+    return NOTHING;
+  }
+  notify_format(creator, T("New player '%s' (#%d) created with password '%s'"),
+               player_name, player, player_password);
+  do_log(LT_WIZ, creator, player, T("Player creation"));
+  return player;
+}
+
+/** Set or check a player's quota.
+ * \verbatim
+ * This implements @quota and @squota.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 name of player whose quota should be set or checked.
+ * \param arg2 amount to set or adjust quota, ignored if checking.
+ * \param seq_q if 1, set quota; if 0, check quota.
+ */
+void
+do_quota(dbref player, const char *arg1, const char *arg2, int set_q)
+{
+  dbref who, thing;
+  int owned, limit, dlimit = -1, downed = -1, adjust;
+
+  /* determine the victim */
+  if (!arg1 || !*arg1 || !strcmp(arg1, "me"))
+    who = player;
+  else {
+    who = lookup_player(arg1);
+    if (who == NOTHING && ((who = match_result(player, arg1, TYPE_DIVISION, MAT_EVERYTHING)) == NOTHING 
+         || Typeof(who) != TYPE_DIVISION)) {
+      notify(player, T("No such player or division."));
+      return;
+    }
+  }
+
+  /* check permissions */
+  if (set_q && !Change_Quota(player, who)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  if ((player != who) && !Do_Quotas(player, who) && !CanSee(player, who)) {
+    notify(player, T("You can't look at someone else's quota."));
+    return;
+  }
+  /* count up all owned objects */
+  owned = -1;                  /* a player is never included in his own
+                                * quota */
+  for (thing = 0; thing < db_top; thing++) {
+    if (Owner(thing) == who && !IsDivision(who)) {
+      if (!IsGarbage(thing))
+       ++owned;
+    } else if(IsDivision(who) && div_inscope(who,thing)) { /* incase we're doing division quotas */
+      if(!IsGarbage(thing) && !IsPlayer(thing)) /* we don't include garbage or player objects in div quotas */
+       ++owned;
+    }
+    /* And No matter what.. we have to calculate the same info from the division we're in.. */
+    if( !IsMasterDivision(Division(who)) &&
+       div_inscope(Division(who), thing)) 
+       downed++;
+  }
+
+   if(!IsMasterDivision(Division(who)) && !NoQuota(Division(who)))
+    dlimit = get_current_quota(who);
+
+
+  /* the quotas of priv'ed players are unlimited and cannot be set. */
+  if (!USE_QUOTA || NoQuota(who)) {
+    notify_format(player, T("Objects: %d   Limit: UNLIMITED"), owned);
+    return;
+  }
+  /* if we're not doing a change, determine the mortal's quota limit. 
+   * RQUOTA is the objects _left_, not the quota itself.
+   */
+
+  if (!set_q) {
+    limit = get_current_quota(who);
+    notify_format(player, T("Objects: %d   Limit: %d"), owned, (downed + dlimit) > (owned + limit) && dlimit != NOTHING
+       ? (downed + dlimit) : (owned + limit));
+    return;
+  }
+  /* set a new quota */
+  if (!arg2 || !*arg2) {
+    limit = get_current_quota(who);
+    notify_format(player, T("Objects: %d   Limit: %d"), owned, (downed + dlimit) > (owned + limit) && dlimit != NOTHING ? 
+       (downed + dlimit) : (owned + limit));
+    notify(player, T("What do you want to set the quota to?"));
+    return;
+  }
+  adjust = ((*arg2 == '+') || (*arg2 == '-'));
+  if (adjust)
+    limit = owned + get_current_quota(who) + atoi(arg2);
+  else
+    limit = atoi(arg2);
+  if (limit < owned)           /* always have enough quota for your objects */
+    limit = owned;
+  if(limit < dlimit && dlimit != NOTHING)
+    limit = dlimit;
+
+  (void) atr_add(!IsDivision(who) ? Owner(who) : who, "RQUOTA", tprintf("%d", limit - owned), GOD,
+                NOTHING);
+
+  notify_format(player, T("Objects: %d   Limit: %d"), owned, limit);
+}
+
+
+/** Check or set quota globally.
+ * \verbatim
+ * This implements @allquota.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 new quota limit, as a string.
+ * \param quiet if 1, don't display every player's quota.
+ */
+void
+do_allquota(dbref player, const char *arg1, int quiet)
+{
+  int oldlimit, limit, owned;
+  dbref who, thing;
+
+  if (!God(player)) {
+    notify(player, T("Who do you think you are, GOD?"));
+    return;
+  }
+  if (!arg1 || !*arg1) {
+    limit = -1;
+  } else if (!is_integer(arg1)) {
+    notify(player, T("You can only set quotas to a number."));
+    return;
+  } else {
+    limit = parse_integer(arg1);
+    if (limit < 0) {
+      notify(player, T("You can only set quotas to a positive number."));
+      return;
+    }
+  }
+
+  for (who = 0; who < db_top; who++) {
+    if (!IsPlayer(who))
+      continue;
+
+    /* count up all owned objects */
+    owned = -1;                        /* a player is never included in his own
+                                * quota */
+    for (thing = 0; thing < db_top; thing++) {
+      if (Owner(thing) == who)
+       if (!IsGarbage(thing))
+         ++owned;
+    }
+
+    if (NoQuota(who)) {
+      if (!quiet)
+       notify_format(player, "%s: Objects: %d   Limit: UNLIMITED",
+                     Name(who), owned);
+      continue;
+    }
+    if (!quiet) {
+      oldlimit = get_current_quota(who);
+      notify_format(player, "%s: Objects: %d   Limit: %d",
+                   Name(who), owned, oldlimit);
+    }
+    if (limit != -1) {
+      if (limit <= owned) {
+       (void) atr_add(who, "RQUOTA", "0", GOD, NOTHING);
+      } else {
+       (void) atr_add(who, "RQUOTA", tprintf("%d", limit - owned), GOD,
+                      NOTHING);
+      }
+    }
+  }
+  if (limit == -1)
+    notify(player, T("Quotas not changed."));
+  else
+    notify_format(player, T("All quotas changed to %d."), limit);
+}
+
+int
+tport_dest_ok(dbref player, dbref victim, dbref dest)
+{
+  /* can player legitimately send something to dest */
+
+  if (Tel_Where(player, dest))
+    return 1;
+
+  if (controls(player, dest))
+    return 1;
+
+  /* beyond this point, if you don't control it and it's not a room, no hope */
+  if (!IsRoom(dest))
+    return 0;
+
+  /* Check for a teleport lock. It fails if the player is not properly
+   * empowered, and the room is tport-locked against the victim, and the
+   * victim does not control the room.
+   */
+  if (!eval_lock(victim, dest, Tport_Lock))
+    return 0;
+
+  if (JumpOk(dest))
+    return 1;
+
+  return 0;
+}
+
+int
+tport_control_ok(dbref player, dbref victim, dbref loc)
+{
+  /* can player legitimately move victim from loc */
+
+  if (God(victim) && !God(player))
+    return 0;
+
+  if(RPMODE(victim) && !(Can_RPTEL(player)||Director(player))) /* require RPTEL power to tport things in RPMODE */
+         return 0;
+
+  if (Tel_Thing(player, victim))
+    return 1;
+
+  if (controls(player, victim))
+    return 1;
+
+  /* mortals can't @tel priv'ed players just on basis of location ownership */
+
+  if (controls(player, loc) && (!Admin(victim) || Owns(player, victim)))
+    return 1;
+
+  return 0;
+}
+
+/** Teleport something somewhere.
+ * \verbatim
+ * This implements @tel.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 the object to teleport (or location if no object given)
+ * \param arg2 the location to teleport to.
+ * \param silent if 1, don't trigger teleport messagse.
+ * \param inside if 1, always @tel to inventory, even of a player
+ */
+void
+do_teleport(dbref player, const char *arg1, const char *arg2, int silent,
+           int inside)
+{
+  dbref victim;
+  dbref destination;
+  dbref loc;
+  const char *to;
+  dbref absroom;               /* "absolute room", for NO_TEL check */
+
+  /* get victim, destination */
+  if (*arg2 == '\0') {
+    victim = player;
+    to = arg1;
+  } else {
+    if ((victim =
+        noisy_match_result(player, arg1, NOTYPE,
+                           MAT_OBJECTS | MAT_ENGLISH)) == NOTHING) {
+      return;
+    }
+    to = arg2;
+  }
+
+  if (IsRoom(victim)) {
+    notify(player, T("You can't teleport rooms."));
+    return;
+  }
+  if (IsGarbage(victim)) {
+    notify(player, T("Garbage belongs in the garbage dump."));
+    return;
+  }
+  /* get destination */
+
+  if (!strcasecmp(to, "home")) {
+    /* If the object is @tel'ing itself home, treat it the way we'd  
+     * treat a 'home' command 
+     */
+    if (player == victim) {
+      if (Fixed(victim) || RPMODE(victim))
+       notify(player, T("You can't do that IC!"));
+      else
+       safe_tel(victim, HOME, silent);
+      return;
+    } else
+      destination = Home(victim);
+  } else {
+    destination = match_result(player, to, TYPE_PLAYER, MAT_EVERYTHING);
+  }
+
+  switch (destination) {
+  case NOTHING:
+    notify(player, T("No match."));
+    break;
+  case AMBIGUOUS:
+    notify(player, T("I don't know which destination you mean!"));
+    break;
+  case HOME:
+    destination = Home(victim);
+    /* FALL THROUGH */
+  default:
+    /* check victim, destination types, teleport if ok */
+    if (!GoodObject(destination) || IsGarbage(destination)) {
+      notify(player, T("Bad destination."));
+      return;
+    }
+    if (recursive_member(destination, victim, 0)
+       || (victim == destination)) {
+      notify(player, T("Bad destination."));
+      return;
+    }
+    if (!Tel_Where(player, destination)
+       && IsPlayer(victim) && IsPlayer(destination)) {
+      notify(player, T("Bad destination."));
+      return;
+    }
+    if (IsExit(victim)) {
+      /* Teleporting an exit means moving its source */
+      if (!IsRoom(destination)) {
+       notify(player, T("Exits can only be teleported to other rooms."));
+       return;
+      }
+      if (Going(destination)) {
+       notify(player,
+              T("You can't move an exit to someplace that's crumbling."));
+       return;
+      }
+      if (!GoodObject(Home(victim)))
+       loc = find_entrance(victim);
+      else
+       loc = Home(victim);
+      /* Unlike normal teleport, you must control the destination 
+       * or have the open_anywhere power
+       */
+      if (!(tport_control_ok(player, victim, loc) &&
+           (controls(player, destination) || CanOpen(player, destination)))) {
+       notify(player, T("Permission denied."));
+       return;
+      }
+      /* Remove it from its old room */
+      Exits(loc) = remove_first(Exits(loc), victim);
+      /* Put it into its new room */
+      Source(victim) = destination;
+      PUSH(victim, Exits(destination));
+      if (!Quiet(player) && !(Quiet(victim) && (Owner(victim) == player)))
+       notify(player, T("Teleported."));
+      return;
+    }
+    loc = Location(victim);
+
+    /* if properly empowered and destination is player, tel to location */
+    if (IsPlayer(destination) && Can_Locate(player,destination) && Tel_Where(player, destination) && IsPlayer(victim)
+       && !inside) {
+      if (!silent && loc != Location(destination))
+       did_it(victim, victim, NULL, NULL, "OXTPORT", NULL, NULL, loc);
+      safe_tel(victim, Location(destination), silent);
+      if (!silent && loc != Location(destination))
+       did_it(victim, victim, "TPORT", NULL, "OTPORT", NULL, "ATPORT",
+              Location(destination));
+      return;
+    }
+    /* check needed for NOTHING. Especially important for unlinked exits */
+    if ((absroom = Location(victim)) == NOTHING) {
+      notify(victim, T("You're in the Void. This is not a good thing."));
+      /* At this point, they're in a bad location, so let's check
+       * if home is valid before sending them there. */
+      if (!GoodObject(Home(victim)))
+       Home(victim) = PLAYER_START;
+      do_move(victim, "home", 0);
+      return;
+    } else {
+      /* valid location, perform other checks */
+
+      /* if player is inside himself, send him home */
+      if (absroom == victim) {
+       notify(player, T("What are you doing inside of yourself?"));
+       if (Home(victim) == absroom)
+         Home(victim) = PLAYER_START;
+       do_move(victim, "home", 0);
+       return;
+      }
+      /* find the "absolute" room */
+      absroom = absolute_room(victim);
+
+      if (absroom == NOTHING) {
+       notify(victim, T("You're in the void - sending you home."));
+       if (Home(victim) == Location(victim))
+         Home(victim) = PLAYER_START;
+       do_move(victim, "home", 0);
+       return;
+      }
+      /* if there are a lot of containers, send him home */
+      if (absroom == AMBIGUOUS) {
+       notify(victim, T("You're in too many containers."));
+       if (Home(victim) == Location(victim))
+         Home(victim) = PLAYER_START;
+       do_move(victim, "home", 0);
+       return;
+      }
+      /* note that we check the NO_TEL status of the victim rather
+       * than the player that issued the command. This prevents someone
+       * in a NO_TEL room from having one of his objects @tel him out.
+       * The control check, however, is detemined by command-giving
+       * player. */
+
+      /* now check to see if the absolute room is set NO_TEL */
+      if (NoTel(absroom) && !controls(player, absroom)
+         && !Tel_Where(player, absroom)) {
+       notify(player, T("Teleports are not allowed in this room."));
+       return;
+      }
+
+      /* Check leave lock on room, if necessary */
+      if (!controls(player, absroom) && !Tel_Where(player, absroom) &&
+         !eval_lock(player, absroom, Leave_Lock)) {
+       fail_lock(player, absroom, Leave_Lock,
+                 T("Teleports are not allowed in this room."), NOTHING);
+       return;
+      }
+
+      /* Now check the Z_TEL status of the victim's room.
+       * Just like NO_TEL above, except that if the room (or its
+       * Zone Master Room, if any, is Z_TEL,
+       * the destination must also be a room in the same zone
+       */
+      if (GoodObject(Zone(absroom)) && (ZTel(absroom) || ZTel(Zone(absroom)))
+         && !controls(player, absroom) && !Tel_Where(player, absroom)
+         && (Zone(absroom) != Zone(destination))) {
+       notify(player,
+              T("You may not teleport out of the zone from this room."));
+       return;
+      }
+    }
+
+    if (!IsExit(destination)) {
+      if (tport_control_ok(player, victim, Location(victim)) &&
+         tport_dest_ok(player, victim, destination)
+         && (Tel_Thing(player, victim) ||
+             (Tel_Where(player, destination) && (player == victim)) ||
+             (destination == Owner(victim)) ||
+             (!Fixed(Owner(victim)) && !Fixed(player)))) {
+       if (!silent && loc != destination)
+         did_it(victim, victim, NULL, NULL, "OXTPORT", NULL, NULL, loc);
+       safe_tel(victim, destination, silent);
+       if (!silent && loc != destination)
+         did_it(victim, victim, "TPORT", NULL, "OTPORT", NULL, "ATPORT",
+                destination);
+       if ((victim != player) && !(Puppet(victim) &&
+                                   (Owner(victim) == Owner(player)))) {
+         if (!Quiet(player) && !(Quiet(victim) && (Owner(victim) == player)))
+           notify(player, T("Teleported."));
+       }
+       return;
+      }
+      /* we can't do it */
+      fail_lock(player, destination, Enter_Lock, T("Permission denied."),
+               Location(player));
+      return;
+    } else {
+      /* attempted teleport to an exit */
+      if (Tel_Thing(player, victim) || controls(player, victim)
+         || controls(player, Location(victim)))
+       do_move(victim, to, 0);
+      else
+       notify_format(victim,
+                     T("%s tries to impose his will on you and fails."),
+                     Name(player));
+    }
+  }
+}
+
+/** Force an object to run a command.
+ * \verbatim
+ * This implements @force.
+ * \endverbatim
+ * \param player the enactor.
+ * \param what name of the object to force.
+ * \param command command to force the object to run.
+ */
+void
+do_force(dbref player, const char *what, char *command)
+{
+  dbref victim;
+  char l = 0;
+  int j;
+
+  if ((victim = match_controlled(player, what)) == NOTHING) {
+    notify(player, "Sorry.");
+    return;
+  }
+  if (options.log_forces) {
+    {
+      /* Log forces when necessary */
+      if (Owner(victim) != Owner(player))
+       do_log(LT_WIZ, player, victim, "** FORCE: %s", command);
+    }
+  }
+  if (God(victim) && !God(player)) {
+    notify(player, T("You can't force God!"));
+    return;
+  }
+  /* force victim to do command */
+  for (j = 0; j < 10; j++)
+    global_eval_context.wnxt[j] = global_eval_context.wenv[j];
+  for (j = 0; j < NUMQ; j++)
+    global_eval_context.rnxt[j] = global_eval_context.renv[j];
+  if(ooref == NOTHING) {
+      ooref = player;
+      l = 1;
+  }
+  parse_que(victim, command, player);
+  if(l==1) ooref = NOTHING;
+}
+
+/** Parse a force token command, but don't force with it.
+ * \verbatim
+ * This function hacks up something of the form "#<dbref> <action>",
+ * finding the two args, and returns 1 if it's a sensible force,
+ * otherwise 0. We know only that the command starts with #.
+ * \endverbatim
+ * \param command the command to parse.
+ * \retval 1 sensible force command
+ * \retval 0 command failed (no action given, etc.)
+ */
+int
+parse_force(char *command)
+{
+  char *s;
+
+  s = command + 1;
+  while (*s && !isspace((unsigned char) *s)) {
+    if (!isdigit((unsigned char) *s))
+      return 0;                                /* #1a is no good */
+    s++;
+  }
+  if (!*s)
+    return 0;                  /* dbref with no action is no good */
+  *s = '=';                    /* Replace the first space with = so we have #3= <action> */
+  return 1;
+}
+
+
+extern struct db_stat_info current_state;
+
+/** Count up the number of objects of each type owned.
+ * \param owner player to count for (or ANY_OWNER for all).
+ * \return pointer to a static db_stat_info structure.
+ */
+struct db_stat_info *
+get_stats(dbref owner)
+{
+  dbref i;
+  static struct db_stat_info si;
+
+  if (owner == ANY_OWNER)
+    return &current_state;
+
+  si.total = si.rooms = si.exits = si.things = si.players = si.divisions = si.garbage = 0;
+  for (i = 0; i < db_top; i++) {
+    if (owner == ANY_OWNER || owner == Owner(i)) {
+      si.total++;
+      if (IsGarbage(i)) {
+       si.garbage++;
+      } else {
+       switch (Typeof(i)) {
+       case TYPE_ROOM:
+         si.rooms++;
+         break;
+       case TYPE_EXIT:
+         si.exits++;
+         break;
+       case TYPE_THING:
+         si.things++;
+         break;
+       case TYPE_PLAYER:
+         si.players++;
+         break;
+        case TYPE_DIVISION:
+          si.divisions++;
+          break;
+       default:
+         break;
+       }
+      }
+    }
+  }
+  return &si;
+}
+
+/** The stats command.
+ * \verbatim
+ * This implements @stats.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of player to check object stats for.
+ */
+void
+do_stats(dbref player, const char *name)
+{
+  struct db_stat_info *si;
+  dbref owner;
+
+  if (*name == '\0')
+    owner = ANY_OWNER;
+  else if (*name == '#') {
+    owner = atoi(&name[1]);
+    if (!GoodObject(owner))
+      owner = NOTHING;
+    else if (!IsPlayer(owner))
+      owner = NOTHING;
+  } else if (strcasecmp(name, "me") == 0)
+    owner = player;
+  else
+    owner = lookup_player(name);
+  if (owner == NOTHING) {
+    notify_format(player, T("%s: No such player."), name);
+    return;
+  }
+  if (!CanSearch(player, owner)) {
+    if (owner != ANY_OWNER && owner != player) {
+      notify(player, T("You need a search warrant to do that!"));
+      return;
+    }
+  }
+  si = get_stats(owner);
+  if (owner == ANY_OWNER) {
+    notify_format(player,
+                 T
+                 ("%d objects = %d rooms, %d exits, %d things, %d players, %d divisions, %d garbage."),
+                 si->total, si->rooms, si->exits, si->things, si->players,
+                 si->divisions, si->garbage);
+    if (first_free != NOTHING)
+      notify_format(player, T("The next object to be created will be #%d."),
+                   first_free);
+  } else {
+    notify_format(player,
+                 T("%d objects = %d rooms, %d exits, %d things, %d players, %d divisions."),
+                 si->total - si->garbage, si->rooms, si->exits, si->things,
+                 si->players, si->divisions);
+  }
+}
+
+/** Reset a player's password.
+ * \verbatim
+ * This implements @newpassword.
+ * \endverbatim
+ * \param player the executor.
+ * \param cause the enactor.
+ * \param name the name of the player whose password is to be reset.
+ * \param password the new password for the player.
+ */
+void
+do_newpassword(dbref player, dbref cause,
+              const char *name, const char *password)
+{
+  dbref victim;
+
+  if (!global_eval_context.process_command_port) {
+    char pass_eval[BUFFER_LEN];
+    char const *sp;
+    char *bp;
+
+    sp = password;
+    bp = pass_eval;
+    process_expression(pass_eval, &bp, &sp, player, player, cause,
+                      PE_DEFAULT, PT_DEFAULT, NULL);
+    *bp = '\0';
+    password = pass_eval;
+  }
+
+  if (((victim = lookup_player(name)) == NOTHING) && 
+      ((victim = match_result(player, name, TYPE_DIVISION, MAT_ABSOLUTE | MAT_NEIGHBOR | MAT_NEAR | MAT_ENGLISH)) == NOTHING || 
+     Typeof(victim) != TYPE_DIVISION )) {
+    notify(player, T("No such player or division."));
+  } else if(CanNewpass(player, victim)
+           || (Can_BCREATE(player) && (has_flag_by_name(victim, "BUILDER",
+                                                        TYPE_PLAYER))
+               && (LEVEL(player) >= LEVEL(victim)))) {
+    if(Typeof(victim) != TYPE_DIVISION && Typeof(victim) != TYPE_PLAYER) {
+       notify(player, "Wtf happened?");
+          return;
+    }
+    if (*password != '\0' && !ok_password(password)) {
+      /* Wiz can set null passwords, but not bad passwords */
+      notify(player, T("Bad password."));
+    } else {
+      /* it's ok, do it */
+      (void) atr_add(victim, "XYXXY", mush_crypt(password), GOD, NOTHING);
+      notify_format(player, T("Password for %s changed."), Name(victim));
+      notify_format(victim, T("Your password has been changed by %s."),
+                   Name(player));
+      do_log(LT_WIZ, player, victim, "*** NEWPASSWORD ***");
+    }
+  } else {
+    notify(player, T("Your delusions of grandeur have been duly noted."));
+    return;
+  }
+}
+
+/** Disconnect a player, forcibly.
+ * \verbatim
+ * This implements @boot.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of the player or descriptor to boot.
+ * \param flag the type of booting to do.
+ */
+void
+do_boot(dbref player, const char *name, enum boot_type flag)
+{
+  dbref victim;
+  DESC *d = NULL;
+
+  victim = NOTHING;
+  switch (flag) {
+  case BOOT_SELF:
+    /* self boot */
+    victim = player;
+    break;
+  case BOOT_DESC:
+    /* boot by descriptor */
+    victim = find_player_by_desc(atoi(name));
+    if (victim == player)
+      flag = BOOT_SELF;
+    else if (victim == NOTHING) {
+      d = port_desc(atoi(name));
+      if (!d) {
+       notify(player, "There is no one connected on that descriptor.");
+       return;
+      } else
+       victim = AMBIGUOUS;
+    }
+    break;
+  case BOOT_NAME:
+    /* boot by name */
+    if ((victim =
+        noisy_match_result(player, name, TYPE_PLAYER,
+                           MAT_LIMITED | MAT_ME)) == NOTHING) {
+      notify(player, T("No such connected player."));
+      return;
+    }
+    if (victim == player)
+      flag = BOOT_SELF;
+    break;
+  }
+
+  if ((victim != player) && !Can_Boot(player, victim)) {
+    notify(player, T("You can't boot that player!"));
+    return;
+  }
+  if (God(victim) && !God(player)) {
+    notify(player, T("You're good.  That's spelled with two 'o's."));
+    return;
+  }
+  /* look up descriptor */
+  switch (flag) {
+  case BOOT_NAME:
+    d = player_desc(victim);
+    break;
+  case BOOT_DESC:
+    d = port_desc(atoi(name));
+    break;
+  case BOOT_SELF:
+    d = inactive_desc(victim);
+    break;
+  }
+
+  /* check for more errors */
+  if (!d) {
+    if (flag == BOOT_SELF)
+      notify(player, T("None of your connections are idle."));
+    else
+      notify(player, T("That player isn't connected!"));
+  } else {
+    do_log(LT_WIZ, player, victim, "*** BOOT ***");
+    if (flag == BOOT_SELF)
+      notify(player, T("You boot an idle self."));
+    else if (victim == AMBIGUOUS)
+      notify_format(player, T("You booted unconnected port %s!"), name);
+    else {
+      notify_format(player, T("You booted %s off!"), Name(victim));
+      notify(victim, T("You are politely shown to the door."));
+    }
+    boot_desc(d);
+  }
+}
+
+/** Chown all of a player's objects.
+ * \verbatim
+ * This implements @chownall
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of player whose objects are to be chowned.
+ * \param target name of new owner for objects.
+ * \param preserve if 1, keep privileges and don't halt objects.
+ */
+void
+do_chownall(dbref player, const char *name, const char *target, int preserve)
+{
+  int i;
+  dbref victim;
+  dbref n_target;
+  int count = 0;
+
+  if ((victim = noisy_match_result(player, name, TYPE_PLAYER, MAT_LIMITED))
+      == NOTHING)
+    return;
+
+  if(!controls(player, victim)) {
+    notify(player, T("Try asking them first!"));
+    return;
+  }
+
+  if (!target || !*target) {
+    n_target = player;
+  } else {
+    if ((n_target =
+        noisy_match_result(player, target, TYPE_PLAYER,
+                           MAT_LIMITED)) == NOTHING)
+      return;
+  }
+
+  for (i = 0; i < db_top; i++) {
+    if ((Owner(i) == victim) && (!IsPlayer(i))) {
+      chown_object(player, i, n_target, preserve);
+      count++;
+    }
+  }
+
+  /* change quota (this command is admin only and we can assume that
+   * we intend for the recipient to get all the objects, so we
+   * don't do a quota check earlier.
+   */
+  change_quota(victim, count);
+  change_quota(n_target, -count);
+
+  notify_format(player, T("Ownership changed for %d objects."), count);
+}
+
+/** Change the zone of all of a player's objects.
+ * \verbatim
+ * This implements @chzoneall.
+ * \endverbatim
+ * \param player the enactor.
+ * \param name name of player whose objects should be rezoned.
+ * \param target string containing new zone master for objects.
+ */
+void
+do_chzoneall(dbref player, const char *name, const char *target)
+{
+  int i;
+  dbref victim;
+  dbref zone;
+  int count = 0;
+
+  if ((victim = noisy_match_result(player, name, TYPE_PLAYER, MAT_LIMITED))
+      == NOTHING)
+    return;
+
+  if (!(Director(player) || controls(player, victim))) {
+    notify(player, T("You do not have the power to change reality."));
+    return;
+  }
+
+  if (!target || !*target) {
+    notify(player, T("No zone specified."));
+    return;
+  }
+  if (!strcasecmp(target, "none"))
+    zone = NOTHING;
+  else {
+    switch (zone = match_result(player, target, NOTYPE, MAT_EVERYTHING)) {
+    case NOTHING:
+      notify(player, T("I can't seem to find that."));
+      return;
+    case AMBIGUOUS:
+      notify(player, T("I don't know which one you mean!"));
+      return;
+    }
+  }
+
+  /* Okay, now that we know we're not going to spew all sorts of errors,
+   * call the normal do_chzone for all the relevant objects.  This keeps
+   * consistency on things like flag resetting, etc... */
+  for (i = 0; i < db_top; i++) {
+    if (Owner(i) == victim && Zone(i) != zone) {
+      count += do_chzone(player, unparse_dbref(i), target, 0);
+    }
+  }
+  notify_format(player, T("Zone changed for %d objects."), count);
+}
+
+/*-----------------------------------------------------------------------
+ * Nasty management: @kick, examine/debug
+ */
+
+/** Execute a number of commands off the queue immediately.
+ * \verbatim
+ * This implements @kick, which is nasty.
+ * \endverbatim
+ * \param player the enactor.
+ * \param num string containing number of commands to run from the queue.
+ */
+void
+do_kick(dbref player, const char *num)
+{
+  int n;
+
+  if (!num || !*num) {
+    notify(player, T("How many commands do you want to execute?"));
+    return;
+  }
+  n = atoi(num);
+
+  if (n <= 0) {
+    notify(player, T("Number out of range."));
+    return;
+  }
+  n = do_top(n);
+
+  notify_format(player, T("%d commands executed."), n);
+}
+
+/** examine/debug.
+ * This implements examine/debug, which provides some raw values for
+ * object structure elements of an examined object.
+ * \param player the enactor.
+ * \param name name of object to examine.
+ */
+void
+do_debug_examine(dbref player, const char *name)
+{
+  MAIL *mp;
+  dbref thing;
+
+  if (!Admin(player)) {
+    notify(player, T("Permission denied."));
+    return;
+  }
+  /* find it */
+  thing = noisy_match_result(player, name, NOTYPE, MAT_EVERYTHING);
+  if (!GoodObject(thing))
+    return;
+
+  notify(player, object_header(player, thing));
+  notify_format(player, T("Flags value: %s"),
+               bits_to_string("FLAG", Flags(thing), GOD, NOTHING));
+
+  notify_format(player, "Next: %d", Next(thing));
+  notify_format(player, "Contents: %d", Contents(thing));
+
+  switch (Typeof(thing)) {
+  case TYPE_PLAYER:
+    mp = desc_mail(thing);
+    notify_format(player, T("First mail sender: %d"), mp ? mp->from : NOTHING);
+  case TYPE_THING:
+    notify_format(player, "Location: %d", Location(thing));
+    notify_format(player, "Home: %d", Home(thing));
+    break;
+  case TYPE_EXIT:
+    notify_format(player, "Destination: %d", Location(thing));
+    notify_format(player, "Source: %d", Source(thing));
+    break;
+  case TYPE_ROOM:
+    notify_format(player, "Drop-to: %d", Location(thing));
+    notify_format(player, "Exits: %d", Exits(thing));
+    break;
+  case TYPE_DIVISION:
+    notify_format(player, "Location: %d", Location(thing));
+    notify_format(player, "Home: %d", Home(thing));
+  case TYPE_GARBAGE:
+    break;
+  default:
+    notify(player, T("Bad object type."));
+  }
+}
+
+/*----------------------------------------------------------------------------
+ * Search functions
+ */
+
+/** User command to search the db for matching objects.
+ * \verbatim
+ * This implements @search.
+ * \endverbatim
+ * \param player the enactor.
+ * \param arg1 name of player whose objects are to be searched.
+ * \param arg3 additional search arguments.
+ */
+void
+do_search(dbref player, const char *arg1, char **arg3)
+{
+  char tbuf[BUFFER_LEN], *arg2 = tbuf, *tbp;
+  dbref *results = NULL;
+  int nresults;
+
+  /* parse first argument into two */
+  if (!arg1 || *arg1 == '\0')
+    arg1 = "me";
+
+  /* First argument is a player, so we could have a quoted name */
+  if (PLAYER_NAME_SPACES && *arg1 == '\"') {
+    for (; *arg1 && ((*arg1 == '\"') || isspace((unsigned char) *arg1));
+        arg1++) ;
+    strcpy(tbuf, arg1);
+    while (*arg2 && (*arg2 != '\"')) {
+      while (*arg2 && (*arg2 != '\"'))
+       arg2++;
+      if (*arg2 == '\"') {
+       *arg2++ = '\0';
+       while (*arg2 && isspace((unsigned char) *arg2))
+         arg2++;
+       break;
+      }
+    }
+  } else {
+    strcpy(tbuf, arg1);
+    while (*arg2 && !isspace((unsigned char) *arg2))
+      arg2++;
+    if (*arg2)
+      *arg2++ = '\0';
+    while (*arg2 && isspace((unsigned char) *arg2))
+      arg2++;
+  }
+
+  if (!*arg2) {
+    if (!arg3[1] || !*arg3[1])
+      arg2 = (char *) "";      /* arg1 */
+    else {
+      arg2 = (char *) arg1;    /* arg2=arg3 */
+      tbuf[0] = '\0';
+    }
+  }
+
+  nresults = raw_search(player, tbuf, arg2, arg3[1], arg3[2], arg3[3],
+                       &results, NULL);
+
+  if (nresults == 0) {
+    notify(player, T("Nothing found."));
+  } else if (nresults > 0) {
+    /* Split the results up by type and report. */
+    int n;
+    int nthings = 0, nexits = 0, nrooms = 0, nplayers = 0, ndivisions = 0;
+    dbref *things, *exits, *rooms, *players, *divisions;
+
+    things = (dbref *) mush_malloc(sizeof(dbref) * nresults, "dbref_list");
+    exits = (dbref *) mush_malloc(sizeof(dbref) * nresults, "dbref_list");
+    rooms = (dbref *) mush_malloc(sizeof(dbref) * nresults, "dbref_list");
+    players = (dbref *) mush_malloc(sizeof(dbref) * nresults, "dbref_list");
+    divisions = (dbref *) mush_malloc(sizeof(dbref) * nresults, "dbref_list");
+
+    for (n = 0; n < nresults; n++) {
+      switch (Typeof(results[n])) {
+      case TYPE_THING:
+       things[nthings++] = results[n];
+       break;
+      case TYPE_EXIT:
+       exits[nexits++] = results[n];
+       break;
+      case TYPE_ROOM:
+       rooms[nrooms++] = results[n];
+       break;
+      case TYPE_PLAYER:
+       players[nplayers++] = results[n];
+       break;
+      case TYPE_DIVISION:
+        divisions[ndivisions++] = results[n];
+        break;
+      default:
+       /* Unknown type. Ignore. */
+       do_rawlog(LT_ERR, T("Weird type for dbref #%d"), results[n]);
+      }
+    }
+
+    if (nrooms) {
+      notify(player, "\nROOMS:");
+      for (n = 0; n < nrooms; n++) {
+       tbp = tbuf;
+       safe_format(tbuf, &tbp, "%s [owner: ", object_header(player, rooms[n]));
+       safe_str(object_header(player, Owner(rooms[n])), tbuf, &tbp);
+       safe_chr(']', tbuf, &tbp);
+       *tbp = '\0';
+       notify(player, tbuf);
+      }
+    }
+
+    if (nexits) {
+      dbref from, to;
+
+      notify(player, "\nEXITS:");
+      for (n = 0; n < nexits; n++) {
+       tbp = tbuf;
+       if (Source(exits[n]) == NOTHING)
+         from = NOTHING;
+       else
+         from = Source(exits[n]);
+       to = Destination(exits[n]);
+       safe_format(tbuf, &tbp, "%s [from ", object_header(player, exits[n]));
+       safe_str((from == NOTHING) ? "NOWHERE" : object_header(player, from),
+                tbuf, &tbp);
+       safe_str(" to ", tbuf, &tbp);
+       safe_str((to == NOTHING) ? "NOWHERE" : object_header(player, to),
+                tbuf, &tbp);
+       safe_chr(']', tbuf, &tbp);
+       *tbp = '\0';
+       notify(player, tbuf);
+      }
+    }
+
+    if (nthings) {
+      notify(player, "\nOBJECTS:");
+      for (n = 0; n < nthings; n++) {
+       tbp = tbuf;
+       safe_format(tbuf, &tbp, "%s [owner: ",
+                   object_header(player, things[n]));
+       safe_str(object_header(player, Owner(things[n])), tbuf, &tbp);
+       safe_chr(']', tbuf, &tbp);
+       *tbp = '\0';
+       notify(player, tbuf);
+      }
+    }
+
+    if (nplayers) {
+      notify(player, "\nPLAYERS:");
+      for (n = 0; n < nplayers; n++) {
+       tbp = tbuf;
+       safe_str(object_header(player, players[n]), tbuf, &tbp);
+       if (Can_Examine(player, players[n]))
+         safe_format(tbuf, &tbp,
+                     T(" [location: %s]"),
+                     object_header(player, Location(players[n])));
+       *tbp = '\0';
+       notify(player, tbuf);
+      }
+    }
+
+    if (ndivisions) {
+      notify(player, "\nDIVISIONS:");
+      for (n = 0; n < ndivisions; n++) {
+       tbp = tbuf;
+       safe_format(tbuf, &tbp, "%s [owner: ",
+                   object_header(player, divisions[n]));
+       safe_str(object_header(player, Owner(divisions[n])), tbuf, &tbp);
+       safe_chr(']', tbuf, &tbp);
+       *tbp = '\0';
+       notify(player, tbuf);
+      }
+    }
+
+    notify(player, T("----------  Search Done  ----------"));
+    notify_format(player,
+                 T
+                 ("Totals: Rooms...%d  Exits...%d  Objects...%d  Players...%d  Divisions...%d"),
+                 nrooms, nexits, nthings, nplayers, ndivisions);
+    mush_free((Malloc_t) rooms, "dbref_list");
+    mush_free((Malloc_t) exits, "dbref_list");
+    mush_free((Malloc_t) things, "dbref_list");
+    mush_free((Malloc_t) players, "dbref_list");
+    mush_free((Malloc_t) divisions, "dbref_list");
+  }
+  if (results)
+    mush_free(results, "search_results");
+}
+
+FUNCTION(fun_lsearch)
+{
+  int nresults;
+  dbref *results = NULL;
+  int rev = !strcmp(called_as, "LSEARCHR");
+
+  if (!command_check_byname(executor, "@search")) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+
+  if (!strcmp(called_as, "CHILDREN"))
+    nresults = raw_search(executor, NULL, "PARENT", args[0], NULL,
+                         NULL, &results, pe_info);
+  else
+    nresults = raw_search(executor, args[0], args[1], args[2], args[3], args[4],
+                         &results, pe_info);
+
+  if (nresults < 0) {
+    safe_str("#-1", buff, bp);
+  } else if (nresults == 0) {
+    notify(executor, T("Nothing found."));
+    safe_str("#-1", buff, bp);
+  } else {
+    int first = 1, n;
+    if (!rev) {
+      for (n = 0; n < nresults; n++) {
+       if (first)
+         first = 0;
+       else if (safe_chr(' ', buff, bp))
+         break;
+       if (safe_dbref(results[n], buff, bp))
+         break;
+      }
+    } else {
+      for (n = nresults - 1; n >= 0; n--) {
+       if (first)
+         first = 0;
+       else if (safe_chr(' ', buff, bp))
+         break;
+       if (safe_dbref(results[n], buff, bp))
+         break;
+      }
+    }
+  }
+  if (results)
+    mush_free(results, "search_results");
+}
+
+
+/** Type of limitation to apply to a search of the db */
+enum search_class {
+  S_OWNER,     /**< Limit to a single owner */
+  S_TYPE,      /**< Limit to a single type */
+  S_PARENT,    /**< Limit to objects with a given parent */
+  S_ZONE,      /**< Limit to objects in a given zone */
+  S_FLAG,      /**< Limit to objects with given flag characters */
+  S_POWER,     /**< Limit to objects with given power */
+  S_EVAL,      /**< Limit to objects for which an expression evals true */
+  S_NAME,      /**< Limit to objects prefix-matching a given name */
+  S_LFLAG      /**< Limit to objects with given flag names */
+};
+
+/* Does the actual searching */
+static int
+raw_search(dbref player, const char *owner, const char *class,
+          const char *restriction, const char *start, const char *stop,
+          dbref **result, PE_Info * pe_info)
+{
+  size_t result_size;
+  size_t nresults = 0;
+  char *p;
+  enum search_class sclass = S_OWNER;
+  int n, i;
+  int restrict_type = NOTYPE;
+  POWER *restrict_powers;
+  dbref restrict_obj = NOTHING, restrict_owner = ANY_OWNER;
+  dbref low = 0, high = db_top - 1;
+
+  i = YESLT;
+
+  /* Range limits */
+  if (start && *start) {
+    size_t offset = 0;
+    if (start[0] == '#')
+      offset = 1;
+    low = parse_integer(start + offset);
+    if (!GoodObject(low))
+      low = 0;
+  }
+  if (stop && *stop) {
+    size_t offset = 0;
+    if (stop[0] == '#')
+      offset = 1;
+    high = parse_integer(stop + offset);
+    if (!GoodObject(high))
+      high = db_top - 1;
+  }
+
+  /* set limits on who we search */
+  if (!owner || !*owner || strcasecmp(owner, "all") == 0)
+    restrict_owner = ANY_OWNER;
+  else if (strcasecmp(owner, "me") == 0)
+    restrict_owner = player;
+  else
+    restrict_owner = lookup_player(owner);
+  if (restrict_owner == NOTHING) {
+    notify(player, T("Unknown owner."));
+    return -1;
+  }
+
+  /* Figure out the class */
+  if (!class || !*class || strcasecmp(class, "none") == 0) {
+    sclass = S_OWNER;
+  } else if (string_prefix("type", class)) {
+    sclass = S_TYPE;
+    if (string_prefix("things", restriction)
+       || string_prefix("objects", restriction)) {
+      restrict_type = TYPE_THING;
+    } else if (string_prefix("rooms", restriction)) {
+      restrict_type = TYPE_ROOM;
+    } else if (string_prefix("exits", restriction)) {
+      restrict_type = TYPE_EXIT;
+    } else if (string_prefix("rooms", restriction)) {
+      restrict_type = TYPE_ROOM;
+    } else if (string_prefix("players", restriction)) {
+      restrict_type = TYPE_PLAYER;
+    } else if (string_prefix("divisions", restriction)) {
+      restrict_type = TYPE_DIVISION;
+    } else {
+      notify(player, T("Unknown type."));
+      return -1;
+    }
+  } else if (string_prefix("things", class) || string_prefix("objects", class)) {
+    sclass = S_NAME;
+    restrict_type = TYPE_THING;
+  } else if (string_prefix("exits", class)) {
+    sclass = S_NAME;
+    restrict_type = TYPE_EXIT;
+  } else if (string_prefix("rooms", class)) {
+    sclass = S_NAME;
+    restrict_type = TYPE_ROOM;
+  } else if (string_prefix("players", class)) {
+    sclass = S_NAME;
+    restrict_type = TYPE_PLAYER;
+  } else if (string_prefix("divisions", class)) {
+    sclass = S_NAME;
+    restrict_type = TYPE_DIVISION;
+  } else if (string_prefix("name", class)) {
+    sclass = S_NAME;
+  } else if (string_prefix("parent", class)) {
+    sclass = S_PARENT;
+    if (!is_objid(restriction)) {
+      notify(player, T("Unknown parent."));
+      return -1;
+    }
+    restrict_obj = parse_objid(restriction);
+    if (!GoodObject(restrict_obj)) {
+      notify(player, T("Unknown parent."));
+      return -1;
+    }
+  } else if (string_prefix("zone", class)) {
+    sclass = S_ZONE;
+    if (!is_objid(restriction)) {
+      notify(player, T("Unknown zone."));
+      return -1;
+    }
+    restrict_obj = parse_objid(restriction);
+    if (!GoodObject(restrict_obj)) {
+      notify(player, T("Unknown zone."));
+      return -1;
+    }
+  } else if (string_prefix("eval", class)) {
+    sclass = S_EVAL;
+  } else if (string_prefix("ethings", class) ||
+            string_prefix("eobjects", class)) {
+    sclass = S_EVAL;
+    restrict_type = TYPE_THING;
+  } else if (string_prefix("eexits", class)) {
+    sclass = S_EVAL;
+    restrict_type = TYPE_EXIT;
+  } else if (string_prefix("erooms", class)) {
+    sclass = S_EVAL;
+    restrict_type = TYPE_ROOM;
+  } else if (string_prefix("eplayers", class)) {
+    sclass = S_EVAL;
+    restrict_type = TYPE_PLAYER;
+  } else if (string_prefix("powers", class)) {
+    sclass = S_POWER;
+    if((p = strchr(restriction, ':'))) {
+      *p++ = '\0';
+      while(*p == ' ')
+        p++;
+      i = yescode_i(p);
+    }
+
+    restrict_powers = find_power(restriction);
+    if (!restrict_powers) {
+      notify(player, T("No such power to search for."));
+      return -1;
+    }
+  } else if (string_prefix("flags", class)) {
+    /* Handle the checking later.  */
+    sclass = S_FLAG;
+    if (!restriction || !*restriction) {
+      notify(player, T("You must give a string of flag characters."));
+      return -1;
+    }
+  } else if (string_prefix("lflags", class)) {
+    /* Handle the checking later.  */
+    sclass = S_LFLAG;
+    if (!restriction || !*restriction) {
+      notify(player, T("You must give a list of flag names."));
+      return -1;
+    }
+  } else {
+    notify(player, T("Unknown search class."));
+    return -1;
+  }
+
+  if ((restrict_owner != ANY_OWNER && restrict_owner != player) &&
+      !(CanSearch(player, restrict_owner) || (sclass == S_TYPE && restrict_type == TYPE_PLAYER))) {
+    notify(player, T("You need a search warrant to do that."));
+    return -1;
+  }
+
+  /* make sure player has money to do the search */
+  if (!payfor(player, FIND_COST)) {
+    notify_format(player, T("Searches cost %d %s."), FIND_COST,
+                 ((FIND_COST == 1) ? MONEY : MONIES));
+    return -1;
+  }
+
+  result_size = (db_top / 4) + 1;
+  *result =
+    (dbref *) mush_malloc(sizeof(dbref) * result_size, "search_results");
+  if (!*result)
+    mush_panic(T("Couldn't allocate memory in search!"));
+
+  switch (sclass) {
+  case S_OWNER:                /* @search someone */
+  case S_TYPE:                 /* @search type=whatever */
+    for (n = low; n <= high; n++) {
+      if (CanSearch(player, Owner(n)) && (restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+         && (restrict_type == NOTYPE || Typeof(n) == restrict_type)) {
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults = (dbref *) realloc((Malloc_t) *result,
+                                        sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+       (*result)[nresults++] = (dbref) n;
+      }
+    }
+    break;
+  case S_ZONE:                 /* @search ZONE=#1234 */
+    for (n = low; n <= high; n++) {
+      if (CanSearch(player, Owner(n)) && (restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+         && Zone(n) == restrict_obj) {
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults =
+           (dbref *) realloc((Malloc_t) *result, sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+
+       (*result)[nresults++] = (dbref) n;
+      }
+    }
+    break;
+  case S_PARENT:               /* @search parent=#1234 */
+    for (n = low; n <= high; n++) {
+      if (CanSearch(player, Owner(n)) && (restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+         && Parent(n) == restrict_obj) {
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults =
+           (dbref *) realloc((Malloc_t) *result, sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+
+       (*result)[nresults++] = (dbref) n;
+      }
+    }
+    break;
+  case S_NAME:                 /* @search (?:name|exits|objects|rooms|players|things)=name */
+    for (n = low; n <= high; n++) {
+      if (CanSearch(player, Owner(n)) && (restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+         && (restrict_type == NOTYPE || Typeof(n) == restrict_type)
+         && string_match(Name(n), restriction)) {
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults =
+           (dbref *) realloc((Malloc_t) *result, sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+
+       (*result)[nresults++] = (dbref) n;
+      }
+    }
+    break;
+  case S_EVAL:                 /* @search (?:eval|ething|eroom|eplayer|eexit)=code */
+    {
+      char *ebuf1;
+      const char *ebuf2;
+      char tbuf1[BUFFER_LEN];
+      char *bp;
+
+      if (!restriction || !*restriction)
+       break;
+
+      for (n = low; n <= high; n++) {
+       if (!CanSearch(player, Owner(n)) || !((restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+             && (restrict_type == NOTYPE || Typeof(n) == restrict_type)))
+         continue;
+
+       ebuf1 = replace_string("##", unparse_dbref(n), restriction);
+       ebuf2 = ebuf1;
+       bp = tbuf1;
+       process_expression(tbuf1, &bp, &ebuf2, player, player, player,
+                          PE_DEFAULT, PT_DEFAULT, pe_info);
+       mush_free((Malloc_t) ebuf1, "replace_string.buff");
+       *bp = '\0';
+       if (!parse_boolean(tbuf1))
+         continue;
+
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults =
+           (dbref *) realloc((Malloc_t) *result, sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+       (*result)[nresults++] = (dbref) n;
+       if (pe_info && pe_info->fun_invocations >= FUNCTION_LIMIT)
+         break;
+      }
+    }
+    break;
+  case S_POWER:                /* @search power=see_all */
+    for (n = low; n <= high; n++) {
+      if (CanSearch(player, Owner(n))
+       && (restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+       && ((i == NO ? (God(n) ? YES : check_power_yescode(DPBITS(n), restrict_powers)) == NO :
+                       (God(n) ? YES : check_power_yescode(DPBITS(n), restrict_powers)) >= i))) {
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults =
+           (dbref *) realloc((Malloc_t) *result, sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+
+       (*result)[nresults++] = (dbref) n;
+      }
+    }
+    break;
+  case S_FLAG:
+  case S_LFLAG:
+    for (n = low; n <= high; n++) {
+      if (CanSearch(player, Owner(n)) && (restrict_owner == ANY_OWNER || Owner(n) == restrict_owner)
+         && (restrict_type == NOTYPE || Typeof(n) == restrict_type)
+         && ((sclass == S_FLAG) ?
+             flaglist_check("FLAG", player, n, restriction, 1)
+             : flaglist_check_long("FLAG", player, n, restriction, 1))) {
+       if (nresults >= result_size) {
+         dbref *newresults;
+         result_size *= 2;
+         newresults =
+           (dbref *) realloc((Malloc_t) *result, sizeof(dbref) * result_size);
+         if (!newresults)
+           mush_panic(T("Couldn't reallocate memory in search!"));
+         *result = newresults;
+       }
+
+       (*result)[nresults++] = (dbref) n;
+      }
+    }
+    break;
+  }
+
+  return (int) nresults;
+}
+
+#ifdef WIN32
+#pragma warning( disable : 4761)       /* Disable bogus conversion warning */
+#endif
+/* ARGSUSED */
+FUNCTION(fun_hidden)
+{
+  dbref it = match_thing(executor, args[0]);
+  if (CanSee(executor, it) && ((Admin(executor) ||
+                                 Location(executor) == Location(it) || Location(it) == executor))) {
+  if ((it == NOTHING) || (!IsPlayer(it))) {
+    notify(executor, T("Couldn't find that player."));
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  safe_boolean(hidden(it), buff, bp);
+  return;
+  } else { 
+         notify(executor, T("Permission denied.")); 
+         safe_str("#-1", buff, bp); 
+         return;
+  }
+}
+
+#ifdef WIN32
+#pragma warning( default : 4761)       /* Re-enable conversion warning */
+#endif
+
+/* ARGSUSED */
+FUNCTION(fun_quota)
+{
+  int owned;
+  /* Tell us player's quota */
+  dbref thing;
+  dbref who = lookup_player(args[0]);
+  if ((who == NOTHING) || !IsPlayer(who)) {
+    notify(executor, T("Couldn't find that player."));
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  if (!(Do_Quotas(executor, who) || CanSee(executor, who)
+       || controls(executor, who))) {
+    notify(executor, T("You can't see someone else's quota!"));
+    safe_str("#-1", buff, bp);
+    return;
+  }
+  if (NoQuota(who)) {
+    /* Unlimited, but return a big number to be sensible */
+    safe_str("99999", buff, bp);
+    return;
+  }
+  /* count up all owned objects */
+  owned = -1;                  /* a player is never included in his own
+                                * quota */
+  for (thing = 0; thing < db_top; thing++) {
+    if (Owner(thing) == who)
+      if (!IsGarbage(thing))
+       ++owned;
+  }
+
+  safe_integer(owned + get_current_quota(who), buff, bp);
+  return;
+}
+
+/** Modify access rules for a site.
+ * \verbatim
+ * This implements @sitelock.
+ * \endverbatim
+ * \param player the enactor.
+ * \param site name of site
+ * \param opts access rules to apply.
+ * \param who string containing dbref of player to whom rule applies.
+ * \param type sitelock operation to do.
+ */
+void
+do_sitelock(dbref player, const char *site, const char *opts, const char *who,
+           enum sitelock_type type)
+{
+
+  if (opts && *opts) {
+    int can, cant;
+    dbref whod = AMBIGUOUS;
+    /* Options form of the command. */
+    if (!site || !*site) {
+      notify(player, T("What site did you want to lock?"));
+      return;
+    }
+    can = cant = 0;
+    if (!parse_access_options(opts, NULL, &can, &cant, player)) {
+      notify(player, T("No valid options found."));
+      return;
+    }
+    if (who && *who) {         /* Specify a character */
+      whod = lookup_player(who);
+      if (!GoodObject(whod)) {
+       notify(player, T("Who do you want to lock?"));
+       return;
+      }
+    }
+
+    add_access_sitelock(player, site, whod, can, cant);
+    write_access_file();
+    if (whod != AMBIGUOUS) {
+      notify_format(player,
+                   T("Site %s access options for %s(%s) set to %s"),
+                   site, Name(whod), unparse_dbref(whod), opts);
+      do_log(LT_WIZ, player, NOTHING,
+            T("*** SITELOCK *** %s for %s(%s) --> %s"), site,
+            Name(whod), unparse_dbref(whod), opts);
+    } else {
+      notify_format(player, T("Site %s access options set to %s"), site, opts);
+      do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s --> %s", site, opts);
+    }
+    return;
+  } else {
+    /* Backward-compatible non-options form of the command,
+     * or @sitelock/name
+     */
+    switch (type) {
+    case SITELOCK_LIST:
+      /* List bad sites */
+      do_list_access(player);
+      return;
+    case SITELOCK_ADD:
+      add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_CREATE);
+      write_access_file();
+      notify_format(player, T("Site %s locked"), site);
+      do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site);
+      break;
+    case SITELOCK_BAN:
+      add_access_sitelock(player, site, AMBIGUOUS, 0, ACS_DEFAULT);
+      write_access_file();
+      notify_format(player, T("Site %s banned"), site);
+      do_log(LT_WIZ, player, NOTHING, "*** SITELOCK *** %s", site);
+      break;
+    case SITELOCK_CHECK:{
+       struct access *ap;
+       char tbuf[BUFFER_LEN], *bp;
+       int rulenum;
+       if (!site || !*site) {
+         do_list_access(player);
+         return;
+       }
+       ap = site_check_access(site, AMBIGUOUS, &rulenum);
+       bp = tbuf;
+       format_access(ap, rulenum, AMBIGUOUS, tbuf, &bp);
+       *bp = '\0';
+       notify(player, tbuf);
+       break;
+      }
+    case SITELOCK_REMOVE:{
+       int n;
+       n = remove_access_sitelock(site);
+       if (n > 0)
+         write_access_file();
+       notify_format(player, T("%d sitelocks removed."), n);
+       break;
+      }
+    }
+  }
+}
+
+/** Edit the list of restricted player names
+ * \verbatim
+ * This implements @sitelock/name
+ * \endverbatim
+ * \param player the player doing the command.
+ * \param name the name (Actually, wildcard pattern) to restrict.
+ */
+void
+do_sitelock_name(dbref player, const char *name)
+{
+  FILE *fp, *fptmp;
+  char buffer[BUFFER_LEN];
+  char *p;
+
+  if (!Site(player)) {
+    notify(player, T("Your delusions of grandeur have been noted."));
+    return;
+  }
+
+  release_fd();
+
+  if (!name || !*name) {
+    /* List bad names */
+    if ((fp = fopen(NAMES_FILE, FOPEN_READ)) == NULL) {
+      notify(player, T("Unable to open names file."));
+    } else {
+      notify(player, T("Any name matching these wildcard patterns is banned:"));
+      while (fgets(buffer, sizeof buffer, fp)) {
+       if ((p = strchr(buffer, '\r')) != NULL)
+         *p = '\0';
+       else if ((p = strchr(buffer, '\n')) != NULL)
+         *p = '\0';
+       notify(player, buffer);
+      }
+      fclose(fp);
+    }
+  } else if (name[0] == '!') { /* Delete a name */
+    if ((fp = fopen(NAMES_FILE, FOPEN_READ)) != NULL) {
+      if ((fptmp = fopen("tmp.tmp", FOPEN_WRITE)) == NULL) {
+       notify(player, T("Unable to delete name."));
+       fclose(fp);
+      } else {
+       while (fgets(buffer, sizeof buffer, fp)) {
+         if ((p = strchr(buffer, '\r')) != NULL)
+           *p = '\0';
+         else if ((p = strchr(buffer, '\n')) != NULL)
+           *p = '\0';
+         if (strcasecmp(buffer, name + 1) == 0)
+           /* Replace the name with #NAME, to allow things like
+              keeping track of unlocked feature names. */
+           fprintf(fptmp, "#%s\n", buffer);
+         else
+           fprintf(fptmp, "%s\n", buffer);
+       }
+       fclose(fp);
+       fclose(fptmp);
+#ifdef WIN32
+       /* Windows can't rename to an existing file. */
+       if (unlink(NAMES_FILE) != 0) {
+         notify(player, T("Unable to delete name."));
+       } else
+#endif
+       if (rename("tmp.tmp", NAMES_FILE) == 0) {
+         notify(player, T("Name removed."));
+         do_log(LT_WIZ, player, NOTHING, "*** UNLOCKED NAME *** %s", name + 1);
+       } else {
+         notify(player, T("Unable to delete name."));
+       }
+      }
+    } else
+      notify(player, T("Unable to delete name."));
+  } else {                     /* Add a name */
+    if ((fp = fopen(NAMES_FILE, FOPEN_READ)) != NULL) {
+      if ((fptmp = fopen("tmp.tmp", FOPEN_WRITE)) == NULL) {
+       notify(player, T("Unable to lock name."));
+      } else {
+       /* Read the names file, looking for #NAME and writing it
+          without the commenting #. Otherwise, add the new name
+          to the end of the file */
+       char commented[BUFFER_LEN + 1];
+       int found = 0;
+       commented[0] = '#';
+       strcpy(commented + 1, name);
+       while (fgets(buffer, sizeof buffer, fp) != NULL) {
+         if ((p = strchr(buffer, '\r')) != NULL)
+           *p = '\0';
+         else if ((p = strchr(buffer, '\n')) != NULL)
+           *p = '\0';
+         if (strcasecmp(commented, buffer) == 0) {
+           fprintf(fptmp, "%s\n", name);
+           found = 1;
+         } else
+           fprintf(fptmp, "%s\n", buffer);
+       }
+       if (!found)
+         fprintf(fptmp, "%s\n", name);
+       fclose(fp);
+       fclose(fptmp);
+
+#ifdef WIN32
+       /* Windows can't rename to an existing file. */
+       if (unlink(NAMES_FILE) != 0) {
+         notify(player, T("Unable to lock name."));
+       } else
+#endif
+       if (rename("tmp.tmp", NAMES_FILE) == 0) {
+         notify_format(player, T("Name %s locked."), name);
+         do_log(LT_WIZ, player, NOTHING, "*** NAMELOCK *** %s", name);
+       } else
+         notify(player, T("Unable to lock name."));
+      }
+    }
+  }
+  reserve_fd();
+}
+
+/*-----------------------------------------------------------------
+ * Functions which give memory information on objects or players.
+ * Source code originally by Kalkin, modified by Javelin
+ */
+
+static int
+mem_usage(dbref thing)
+{
+  int k;
+  ATTR *m;
+  lock_list *l;
+  k = sizeof(struct object);   /* overhead */
+  k += strlen(Name(thing)) + 1;        /* The name */
+  for (m = List(thing); m; m = AL_NEXT(m)) {
+    k += sizeof(ATTR);
+    if (AL_STR(m) && *AL_STR(m))
+      k += u_strlen(AL_STR(m)) + 1;
+    /* NOTE! In the above, we're getting the size of the
+     * compressed attrib, not the uncompressed one (as Kalkin did)
+     * because (1) it's more efficient, (2) it's more accurate
+     * since that's how the object is in memory. This relies on
+     * compressed attribs being terminated with \0's, which they
+     * are in compress.c. If that changes, this breaks.
+     */
+  }
+  for (l = Locks(thing); l; l = l->next) {
+    k += sizeof(lock_list);
+    k += sizeof_boolexp(l->key);
+  }
+  return k;
+}
+
+/* ARGSUSED */
+FUNCTION(fun_objmem)
+{
+  dbref thing;
+  if (!strcasecmp(args[0], "me"))
+    thing = executor;
+  else if (!strcasecmp(args[0], "here"))
+    thing = Location(executor);
+  else {
+    thing = noisy_match_result(executor, args[0], NOTYPE, MAT_OBJECTS);
+  }
+
+  if (!CanSearch(executor, thing)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  if (!GoodObject(thing)) {
+    safe_str(T(e_match), buff, bp);
+    return;
+  }
+  if (!Can_Examine(executor, thing)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  safe_integer(mem_usage(thing), buff, bp);
+}
+
+
+
+/* ARGSUSED */
+FUNCTION(fun_playermem)
+{
+  int tot = 0;
+  dbref thing;
+  dbref j;
+
+  if (!strcasecmp(args[0], "me") && IsPlayer(executor))
+    thing = executor;
+  else if (*args[0] && *args[0] == '*')
+    thing = lookup_player(args[0] + 1);
+  else if (*args[0] && *args[0] == '#')
+    thing = atoi(args[0] + 1);
+  else
+    thing = lookup_player(args[0]);
+  if (!GoodObject(thing) || !IsPlayer(thing)) {
+    safe_str(T(e_match), buff, bp);
+    return;
+  }
+  if (!CanSearch(executor, thing) || !Can_Examine(executor, thing)) {
+    safe_str(T(e_perm), buff, bp);
+    return;
+  }
+  for (j = 0; j < db_top; j++)
+    if (Owner(j) == thing)
+      tot += mem_usage(j);
+  safe_integer(tot, buff, bp);
+}
+
+
+/** Reboot the game without disconnecting players.
+ * \verbatim
+ * This implements @shutdown/reboot, which performs a dump, saves
+ * information about which player is associated with which socket,
+ * and then re-execs the mush process without closing the sockets.
+ * \endverbatim
+ * \param player the enactor.
+ * \param flag if 0, normal dump; if 1, paranoid dump.
+ */
+void
+do_reboot(dbref player, int flag)
+{
+  if (player == NOTHING) {
+    flag_broadcast(0, 0,
+                  T
+                  ("GAME: Reboot w/o disconnect from game account, please wait."));
+  } else {
+    flag_broadcast(0, 0,
+                  T
+                  ("GAME: Reboot w/o disconnect by %s, please wait."),
+                  Name(Owner(player)));
+  }
+  if (flag) {
+    paranoid_dump = 1;
+    paranoid_checkpt = db_top / 5;
+    if (paranoid_checkpt < 1)
+      paranoid_checkpt = 1;
+  }
+#ifdef HAS_OPENSSL
+  close_ssl_connections();
+#endif
+  sql_shutdown();
+  shutdown_queues();
+  fork_and_dump(0);
+#ifndef PROFILING
+#ifndef WIN32
+  /* Some broken libcs appear to retain the itimer across exec!
+   * So we make sure that if we get a SIGPROF in our next incarnation,
+   * we ignore it until our proper handler is set up.
+   */
+  ignore_signal(SIGPROF);
+#endif
+#endif
+  dump_reboot_db();
+#ifdef INFO_SLAVE
+  kill_info_slave();
+#endif
+  local_shutdown();
+  end_all_logs();
+#ifndef WIN32
+  execl("netmush", "netmush", confname, NULL);
+#else
+  execl("pennmush.exe", "pennmush.exe", "/run", NULL);
+#endif                         /* WIN32 */
+  exit(1);                     /* Shouldn't ever get here, but just in case... */
+}
diff --git a/utils/.gitify-empty b/utils/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/utils/clwrapper.sh b/utils/clwrapper.sh
new file mode 100644 (file)
index 0000000..3601caf
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+# A wrapper we use in the win32 build
+#
+
+outfile=""
+while test $# -gt 0; do
+       case "x$1" in
+       x) shift; args="$args ''";;
+       x-) shift; cat >_$$.c; args="$args _$$.c"; nukefiles=_$$.c;;
+       x-o)
+               shift
+               case "x$1" in
+               x*.o) args="$args /Fo$1";;
+               xnetmud) outfile="/out:PennMUSH.exe";;
+               x*) outfile="/out:$1.exe";;
+               esac
+               shift
+               ;;
+       x-c) shift; args="$args /c";;
+       x-E) shift; args="$args /E";;
+       x-D*) args="$args `echo $1 | sed 's/^-/\//'`"; shift;;
+       x-I*) args="$args `echo $1 | sed 's/^-/\//'`"; shift;;
+       x-L*) libsearch="$libsearch `echo $1 | sed 's/^-L//'`"; shift;;
+       x-l*)
+               f="`echo $1 | sed 's/^-l//'`"
+               for dir in . $libsearch; do
+                       test -f $dir/$f && args="$args $dir/$f"
+               done
+               shift;;
+       x*) args="$args $1"; shift;;
+       esac
+done
+
+case "x$outfile $args" in
+x/out:*/link*) args="$args $outfile";;
+x/out:*) args="$args /link $outfile";;
+x*) args="$args $outfile";;
+esac
+
+echo "Translated to: cl /nologo $args"
+cl /nologo $args
+retcode=$?
+
+case "x$nukefiles" in
+ x) nukefiles="";;
+ x*) rm $nukefiles;;
+esac
+
+exit $retcode
+
diff --git a/utils/customize.pl b/utils/customize.pl
new file mode 100644 (file)
index 0000000..628b105
--- /dev/null
@@ -0,0 +1,148 @@
+#!/usr/local/bin/perl
+#
+# If this script doesn't work for you, try changing the first line
+# to point to the location of Perl on your system. That shouldn't
+# be necessary if you're running it via 'make customize'
+#
+# customize.pl - Alan Schwartz <dunemush@pennmush.org>
+#
+# This script asks the user for a mush name and makes a copy of the
+# game/ directory called <mush>/. It also rewrites the restart script
+# to <mush>.restart and mush.cnf (which it calls <mush>.cnf) to
+# make it easier to keep track of multiple MUSHes.
+# 
+# $Id: customize.pl,v 1.1.1.1 2004-06-06 20:32:51 ari Exp $
+#
+
+$tar1="(cd game; tar cf - .) | (cd ";
+$tar2="; tar xfBp -)";
+
+@dontwrite = ("src","hdrs","hints","game");
+
+
+# Interact with the user
+print <<END;
+Welcome. This script creates a game directory for a MUSH and 
+customizes the files in order to make running multiple MUSHes
+easier. This is still experimental, but should not affect
+any of your files in standard MUSH directories (game, src, hdrs, etc.)
+
+When choosing the name for your directory, use a short version of
+the MUSH name. For example, if you MUSH was called Fallen Angels MUSH,
+you might choose 'fallen' or 'fa'. Use only upper- or lower-case
+letters and numbers in directory names.
+
+END
+
+print "Please enter the name for your directory: ";
+chop($targetdir = <STDIN>);
+
+# Verify that the target directory isn't an unwritable one, or
+# a tricky one.
+
+$targetdir =~ s/ +//g;
+die "Invalid directory: contains a bad character.\n"
+        if ($targetdir =~ /[^A-Za-z0-9_]/);
+foreach (@dontwrite) {
+  die "Invalid directory: already in use\n" if ($targetdir eq $_);
+}
+
+# Does the directory already exist?
+if (-d $targetdir) {
+  print "That directory already exists. Overwrite it? [n] ";
+  $yn = <STDIN>;
+  unless ($yn =~ /^[yY]/) {
+       exit 0;
+  }
+}
+
+# Ok, go ahead and create it. We probably should trap signals so
+# we can clean up, too.
+
+print "Making $targetdir...";
+mkdir($targetdir,0755) unless (-d $targetdir);
+die "Failed!\n" unless (-d $targetdir);
+print "done.\n";
+
+print "Using tar to copy from game/ to $targetdir/...";
+$tar = $tar1 . $targetdir . $tar2;
+if (system($tar)) {
+       die "Failed!\n";
+}
+print "done.\n";
+
+print "Replacing standard files in $targetdir/txt/hlp with\nlinks to files in game/txt/hlp...";
+chop($curdir = `pwd`);
+foreach $file (<$targetdir/txt/hlp/penn*.hlp>) {
+  unlink($file) || die "Failed!\n";
+  ($newfile) = $file =~ /(penn.*\.hlp)/;
+  symlink("$curdir/game/txt/hlp/$newfile","$targetdir/txt/hlp/$newfile") || die "Failed!\n";
+}
+print "done.\n";
+
+# Enter the directory, and, produce some files
+chdir($targetdir);
+chop($fullpath = `pwd`);
+
+print "Modifying mush.cnf (to $targetdir.cnf)...";
+unless (open(IN,"mush.cnf") && open(OUT,">$targetdir.cnf")) {
+       die "Failed!\n";
+}
+
+while (<IN>) {
+  if (/^mud_name/) {
+       print OUT "mud_name $targetdir\n";
+  } elsif (/^input_database/) {
+       print OUT "input_database data/indb.$targetdir\n";
+  } elsif (/^output_database/) {
+       print OUT "output_database data/outdb.$targetdir\n";
+  } elsif (/^mail_database/) {
+       print OUT "mail_database data/maildb.$targetdir\n";
+  } elsif (/^chat_database/) {
+       print OUT "chat_database data/chatdb.$targetdir\n";
+  } else {
+       print OUT $_;
+  }
+}
+close(IN); close(OUT);
+print "done\n";
+
+
+print "Modifying restart (to $targetdir.restart)...";
+unless (open(IN,"restart") && open(OUT,">$targetdir.restart")) {
+       die "Failed!\n";
+}
+while (<IN>) {
+  if (/^GAMEDIR/) {
+       print OUT "GAMEDIR=$fullpath\n";
+  } elsif (/^CONF_FILE/) {
+       print OUT "CONF_FILE=$targetdir.cnf\n";
+  } elsif (/^LOG/) {
+       print OUT "LOG=log/$targetdir.log\n";
+  } else {
+       print OUT $_;
+  }
+}
+close(IN); close(OUT);
+chmod(0744,"$targetdir.restart");
+print "done\n";
+
+print "Renaming data/minimal.db.Z to data/indb.$targetdir.Z...";
+rename("data/minimal.db.Z","data/indb.$targetdir.Z");
+print "done.\n";
+
+print "Removing old mush.cnf and restart script...";
+unlink("mush.cnf");
+unlink("restart");
+print "done\n";
+
+print "Fixing links...";
+symlink("../src/netmud","netmush");
+symlink("../src/info_slave","info_slave");
+print "done\n";
+
+chdir("..");
+
+print "Customization complete for $targetdir/\n";
+
+exit 0;
diff --git a/utils/fixdepend.pl b/utils/fixdepend.pl
new file mode 100644 (file)
index 0000000..83430d1
--- /dev/null
@@ -0,0 +1,4 @@
+#!/usr/local/bin/perl -pi~
+$_ = "" if m#/usr/include#;
+$_ = "" if m#/usr/lib#;
+
diff --git a/utils/gentables.c b/utils/gentables.c
new file mode 100644 (file)
index 0000000..3acebcc
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ * Requires a C99ish compiler. gcc 3 works. gcc 2.95 works. Earlier
+ * versions might.
+ *
+ * The arrays below use designated initializers to make it very explicit
+ * which elements are being set to what. The standard says that any elements
+ * without an initalizer in these starts out like it would if static - in
+ * other words, zero'ed out. That's usually what we wanted.
+ *
+ * However, since most people compiling Penn probably aren't going to be
+ * using a C99 compiler for some time to come, this program will translate
+ * from the DI form to the fully-initialized form that all C and C++ compilers
+ * understand.
+ *
+ * Example Usage:
+ * % cd pennmush
+ * % gcc -o gentables utils/gentables.c
+ * % ./gentables > src/tables.c
+ * % make
+ */
+
+#include <stdio.h>
+#include <limits.h>
+#include <stdlib.h>
+
+
+/* Offsets (+1) for q-register lookup. */
+char q_offsets[UCHAR_MAX + 1] = {
+  ['0'] = 1, ['1'] = 2, ['2'] = 3, ['3'] = 4, ['4'] = 5,
+  ['5'] = 6, ['6'] = 7, ['7'] = 8, ['8'] = 9, ['9'] = 10,
+  ['A'] = 11, ['a'] = 11,
+  ['B'] = 12, ['b'] = 12,
+  ['C'] = 13, ['c'] = 13,
+  ['D'] = 14, ['d'] = 14,
+  ['E'] = 15, ['e'] = 15,
+  ['F'] = 16, ['f'] = 16,
+  ['G'] = 17, ['g'] = 17,
+  ['H'] = 18, ['h'] = 18,
+  ['I'] = 19, ['i'] = 19,
+  ['J'] = 20, ['j'] = 20,
+  ['K'] = 21, ['k'] = 21,
+  ['L'] = 22, ['l'] = 22,
+  ['M'] = 23, ['m'] = 23,
+  ['N'] = 24, ['n'] = 24,
+  ['O'] = 25, ['o'] = 25,
+  ['P'] = 26, ['p'] = 26,
+  ['Q'] = 27, ['q'] = 27,
+  ['R'] = 28, ['r'] = 28,
+  ['S'] = 29, ['s'] = 29,
+  ['T'] = 30, ['t'] = 30,
+  ['U'] = 31, ['u'] = 31,
+  ['V'] = 32, ['v'] = 32,
+  ['W'] = 33, ['w'] = 33,
+  ['X'] = 34, ['x'] = 34,
+  ['Y'] = 35, ['y'] = 35,
+  ['Z'] = 36, ['z'] = 36
+};
+
+/* What characters the parser looks for. */
+char parse_interesting[UCHAR_MAX + 1] = {
+  ['\0'] = 1,
+  ['%'] = 1,
+  ['{'] = 1,
+  ['['] = 1,
+  ['('] = 1,
+  ['\\'] = 1,
+  [' '] = 1,
+  ['}'] = 1,
+  [']'] = 1,
+  [')'] = 1,
+  [','] = 1,
+  [';'] = 1,
+  ['='] = 1,
+  ['$'] = 1,
+  [0x1B] = 1
+};
+
+/* What characters are allowed in attribute names. */
+char attribute_names[UCHAR_MAX + 1] = {
+  ['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1, ['4'] = 1,
+  ['5'] = 1, ['6'] = 1, ['7'] = 1, ['8'] = 1, ['9'] = 1,
+  ['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1, ['E'] = 1,
+  ['F'] = 1, ['G'] = 1, ['H'] = 1, ['I'] = 1, ['J'] = 1,
+  ['K'] = 1, ['L'] = 1, ['M'] = 1, ['N'] = 1, ['O'] = 1,
+  ['P'] = 1, ['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1,
+  ['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1, ['Y'] = 1,
+  ['Z'] = 1, ['_'] = 1, ['#'] = 1, ['@'] = 1, ['$'] = 1,
+  ['!'] = 1, ['~'] = 1, ['|'] = 1, [';'] = 1, ['`'] = 1,
+  ['"'] = 1, ['\''] = 1,['&'] = 1, ['*'] = 1, ['-'] = 1,
+  ['+'] = 1, ['='] = 1, ['?'] = 1, ['/'] = 1, ['.'] = 1,
+  ['>'] = 1, ['<'] = 1, [','] = 1
+};
+
+/* C89 format codes for strftime() */
+char valid_timefmt_codes[UCHAR_MAX + 1] = {
+  ['a'] = 1, ['A'] = 1, ['b'] = 1, ['B'] = 1, ['c'] = 1,
+  ['d'] = 1, ['H'] = 1, ['I'] = 1, ['j'] = 1, ['m'] = 1,
+  ['M'] = 1, ['p'] = 1, ['S'] = 1, ['U'] = 1, ['w'] = 1,
+  ['W'] = 1, ['x'] = 1, ['X'] = 1, ['y'] = 1, ['Y'] = 1,
+  ['Z'] = 1, ['$'] = 1
+};
+
+/* Special characters for escape() and secure() */
+char escaped_chars[UCHAR_MAX + 1] = {['%'] =1, ['\\'] = 1, 
+       ['['] = 1, [']'] = 1, ['{'] = 1, ['}'] = 1, [';'] = 1 }; 
+
+
+char escaped_chars_s[UCHAR_MAX + 1] = {
+  ['('] = 1, [')'] = 1, ['['] = 1, [']'] = 1, ['{'] = 1,
+  ['}'] = 1, ['$'] = 1, ['^'] = 1, ['%'] = 1, [','] = 1,
+  [';'] = 1, ['\\'] = 1
+};
+
+  
+
+/* Roman Numeral Values */
+unsigned int roman_numeral_t[UCHAR_MAX + 1] = {
+      ['I'] = 1,
+      ['V'] = 5,
+      ['X'] = 10,
+      ['L'] = 50,
+      ['C'] = 100,
+      ['D'] = 500,
+      ['M'] = 1000,
+      ['i'] = 1000,
+      ['v'] = 5000,
+      ['x'] = 10000,
+      ['l'] = 50000,
+      ['c'] = 100000,
+      ['d'] = 500000,
+      ['m'] = 1000000
+};
+
+/** Accented characters 
+ *
+ * The table is for ISO 8859-1 character set.
+ *  It should be easy to modify it for other ISO 8859-X sets, or completely
+ *  different families.
+ */
+typedef struct {
+  const char *base;    /**< Base character */
+  const char *entity;  /**< HTML entity */
+} accent_info;
+accent_info entity_table[UCHAR_MAX + 1] = {
+  // Assorted characters 
+  ['<'] = {"<", "&lt;"},
+  ['>'] = {">", "&gt;"},
+  ['&'] = {"&", "&amp;"},
+  ['"'] = {"\\\"", "&quot;"},
+  ['\n'] = {"\\n", "<br>\\n"},
+  // << and >> quotes
+  [171] = {"<<", "&laquo;"},
+  [187] = {">>", "&raquo;"},
+  // Upside-down punctuation
+  [161] = {"!", "&iexcl;"},
+  [191] = {"?", "&iquest;"},
+  // szlig
+  [223] = {"s", "&szlig;"},
+  // thorn
+  [222] = {"P", "&THORN;"},
+  [254] = {"p", "&thorn:"},
+  // eth
+  [208] = {"D", "&ETH;"},
+  [240] = {"o", "&eth;"},
+  // Special symbols
+  [169] = {"(c)", "&copy;"},
+  [174] = {"(r)", "&reg;"},
+  [188] = {"1/4", "&frac14;"},
+  [189] = {"1/2", "&frac12;"},
+  [190] = {"3/4", "&frac34;"},
+
+    // AE ligatures
+  [198] = {"AE", "&AElig;"},
+  [230] = {"ae", "&aelig;"},
+
+  // Accented a's 
+  [192] = {"A", "&Agrave;"},
+  [193] = {"A", "&Aacute;"},
+  [194] = {"A", "&Acirc;"},
+  [195] = {"A", "&Atilde;"},
+  [196] = {"A", "&Auml;"},
+  [197] = {"A", "&Aring;"},
+  [224] = {"a", "&agrave;"},
+  [225] = {"a", "&aacute;"},
+  [226] = {"a", "&acirc;"},
+  [227] = {"a", "&atilde;"},
+  [228] = {"a", "&auml;"},
+  [229] = {"a", "&aring;"},
+
+  // Accented c's 
+  [199] = {"C", "&Ccedil;"},
+  [231] = {"c", "&ccedil;"},
+
+  // Accented e's 
+  [200] = {"E", "&Egrave;"},
+  [201] = {"E", "&Eacute;"},
+  [202] = {"E", "&Ecirc;"},
+  [203] = {"E", "&Euml;"},
+  [232] = {"e", "&egrave;"},
+  [233] = {"e", "&eacute;"},
+  [234] = {"e", "&ecirc;"},
+  [235] = {"e", "&euml;"},
+
+  // Accented i's 
+  [204] = {"I", "&Igrave;"},
+  [205] = {"I", "&Iacute;"},
+  [206] = {"I", "&Icirc;"},
+  [207] = {"I", "&Iuml;"},
+  [236] = {"i", "&igrave;"},
+  [237] = {"i", "&iacute;"},
+  [238] = {"i", "&icirc;"},
+  [239] = {"i", "&iuml;"},
+
+  // Accented n's 
+  [209] = {"N", "&Ntilde;"},
+  [241] = {"n", "&ntilde;"},
+
+  // Accented o's 
+  [210] = {"O", "&Ograve;"},
+  [211] = {"O", "&Oacute;"},
+  [212] = {"O", "&Ocirc;"},
+  [213] = {"O", "&Otilde;"},
+  [214] = {"O", "&Ouml;"},
+  [242] = {"o", "&ograve;"},
+  [243] = {"o", "&oacute;"},
+  [244] = {"o", "&ocirc;"},
+  [245] = {"o", "&otilde;"},
+  [246] = {"o", "&ouml;"},
+
+  // Accented u's 
+  [217] = {"U", "&Ugrave;"},
+  [218] = {"U", "&Uacute;"},
+  [219] = {"U", "&Ucirc;"},
+  [220] = {"U", "&Uuml;"},
+  [249] = {"u", "&ugrave;"},
+  [250] = {"u", "&uacute;"},
+  [251] = {"u", "&ucirc;"},
+  [252] = {"u", "&uuml;"},
+
+  // Accented y's 
+  [221] = {"Y", "&Yacute;"},
+  [253] = {"y", "&yacute;"},
+  [255] = {"y", "&yuml;"},
+};
+
+/* For tables of char's treated as small numeric values. */
+void print_table_bool(const char *type, const char *name,
+                      char table[], int delta) {
+  int n ;
+  printf("%s %s[%d] = {\n", type, name, UCHAR_MAX + 1);
+  for (n = 1; n < UCHAR_MAX + 2; n++) {
+    printf("%3d", table[n - 1] + delta);
+    if (n < UCHAR_MAX + 1)
+      putchar(',');
+    if (n % 16 == 0)
+      putchar('\n');
+  }
+  fputs("};\n\n", stdout);
+}
+
+void print_table_int(const char *type, const char *name, unsigned int table[], int delta) {
+  int n ;
+  printf("%s %s[%d] = {\n", type, name, UCHAR_MAX + 1);
+  for (n = 1; n < UCHAR_MAX + 2; n++) {
+    printf("%3d", table[n - 1] + delta);
+    if (n < UCHAR_MAX + 1)
+      putchar(',');
+    if (n % 16 == 0)
+      putchar('\n');
+  }
+  fputs("};\n\n", stdout);
+}
+
+void print_entity_table(const char *name,
+          const accent_info table[]) {
+  int n;
+  puts("typedef struct {");
+  puts("const char *base;");
+  puts("const char *entity;");
+  puts("} accent_info;");
+  printf("accent_info %s[%d] = {\n", name, UCHAR_MAX + 1);
+  for (n = 0; n < UCHAR_MAX + 1; n++) {
+    if (table[n].entity)
+      printf("{\"%s\", \"%s\"}", table[n].base, table[n].entity);
+    else
+      printf("{NULL, NULL}", n);
+    if (n < UCHAR_MAX)
+      putchar(',');
+    putchar('\n');
+  }
+  fputs("};\n\n", stdout);
+}
+
+
+int main(int argc, char *argv[]) {
+  printf("/* This file was generated by running %s compiled from\n"
+        " * %s. Edit that file, not this one, when making changes. */\n"
+        "#include <stdlib.h>\n\n",
+         argv[0], __FILE__);
+  print_table_int("unsigned int", "roman_numeral_table", roman_numeral_t, 0);
+  print_table_bool("signed char", "qreg_indexes", q_offsets, -1);
+  print_table_bool("char", "active_table", parse_interesting, 0);
+  print_table_bool("char", "atr_name_table", attribute_names, 0);
+  print_table_bool("char", "valid_timefmt_codes", valid_timefmt_codes, 0);
+  print_table_bool("char", "escaped_chars", escaped_chars, 0);
+  print_table_bool("char", "escaped_chars_s", escaped_chars_s, 0);
+  print_entity_table("accent_table", entity_table);
+  return EXIT_SUCCESS;
+}
diff --git a/utils/ln-dir.sh b/utils/ln-dir.sh
new file mode 100644 (file)
index 0000000..3efc3a2
--- /dev/null
@@ -0,0 +1,162 @@
+#!/bin/sh
+#  Program: ln-dir.sh
+#  Author: David Cheatham / david@mush.cx / Vadiv@M*U*S*H / Vadiv@ChaoticMUX
+#  Description: Creates symlink game dirs of pennmush
+#  Date: May 4, 2002
+#
+#  This script has no warranty. Don't do stupid things like run it on
+#  an already existing mush. It tried to check for that, but no
+#  promises. Under no circumstances I can think of will it overwrite
+#  your db, but it will overwrite your custom text files.
+#
+#  This script is public domain.
+#  If you haven't messed with it, and something goes wrong, email me.
+#
+#  DIRECTIONS:
+#
+#  TO USE: Find a directory, and run this script in it. It will
+#  create a game/ in that directory that has everything you need to
+#  run pennmush. Feel free to rename or move this directory. 
+#  (No, not while you're running it.)
+#
+#  If you have a database and/or help files, etc, copy those in, like you 
+#  would normally. See the FAQ for more info. Note that the included
+#  help files are symlinked, and it's best if you just leave those and
+#  create *other* files in that directory, so upgrading is seamless.
+#  nws/ and evt/ have normal text files in them, as those always change
+#  per-mush.
+#
+#
+#  TO SETUP: Compile pennmush, all the way. You should be able to type
+#  ./restart and have it launch. In fact, you should do that, it won't
+#  confuse anything and will save you from problems down the road, if you 
+#  know it works.  Then type 'make globalinstall'. All the files that you
+#  need are put in /usr/libexec/pennmush, but you can move them if you
+#  want to. (Do it before you create any user installs, though.)
+#  You can then delete the compile directory.
+#
+#  Note if you're compiling for other users you probably want all the flags
+#  and options and everything turned on. Otherwise one set of people will be
+#  whining about warnings and one set about ROY, etc...
+#
+#  That's technically all you need to do. *However*, if you want users to 
+#  not have to type the full path out, you can create a symlink to this
+#  script, and stick it in your path, with some nice name, and tell
+#  users about it. I recommend calling it penn-install and sticking it
+#  in /usr/bin. (I could do it for you, but I want the binaries to be
+#  freely relocatable, and thus don't want to hardcode a location.)
+#
+#
+#  TO UPGRADE: Just do the same thing as an install. All the user
+#  installs *should* continue to operate, or there should be a
+#  big warning in the Changelog. If there's something that needs doing,
+#  hopefully it will be clearly documented.
+#
+#  Then all the user installs just need @shutdown/reboot. Though a
+#  'make' in their txt/ directory before that can't hurt.
+#
+#
+#  Don't mess with the rest of this file.
+
+STARTPWD=`pwd`/pennmush
+DOWN=''
+# Good grief. Why can't we have a command to find the absolute place 
+# a symlink is pointing at?
+cd /`ls -l $0 |cut -d '>' -f 2- | sed 's/\/[^\/]*$//'|cut -d '/' -f 2-`
+MASTER=`pwd`
+mkdir $STARTPWD
+cd $STARTPWD
+
+mklns() {
+  while [ $1 ]; do
+  ln -s $MASTER/$DOWN/$1
+  shift
+  done
+}
+
+mkcps() {
+  while [ $1 ]; do 
+  cp $MASTER/$DOWN/$1 .
+  shift
+  done
+}
+
+mdcd() {
+  cd $STARTPWD
+  mkdir -p $1
+  cd $1
+  DOWN=$1
+}
+
+if test -f ./ln-dir.sh; then
+
+  echo "Are you running this from the master pennmush directory?"
+  echo "What exactly is going on here?"
+  echo "Open this file up and read the instructions."
+  echo "This creates a pennmush child directory in the currect"
+  echo "directory. You need to run it in a different directory."
+  exit
+
+fi
+
+if test -f ./utils/ln-dir.sh;  then
+
+  echo "If you want to use this script, you need to 'make globalinstall"
+  echo "as root first, and then run this script from where it's"
+  echo "installed."
+  exit
+
+fi
+
+if test -f ./game/restart; then
+
+  echo "Okay, I'm confused. You only should run this once, to create"
+  echo "the directories and symlinks. You shouldn't ever need to run it"
+  echo "again. And, as I can't figure out which files you've modified,"
+  echo "or what's wrong, that's a bad idea anyway."
+  echo "If you've run into problems, you should create a new directory."
+  echo "run me in that, and copy all the files you\'ve modified over."
+  exit
+
+fi
+
+mklns README mushcnf.dst access.README getdate.README getdate.template config.sh
+mkcps *.cnf restart
+
+# Weird binary linking stuff
+ln -s $MASTER/netmush
+ln -s $MASTER/info_slave
+
+# I'm a little baffled by needing to do this, but whatever.
+chmod u+x restart
+
+# The mostly empty directorys...
+mdcd save
+mklns README
+
+mdcd log
+mklns README
+
+mdcd data
+mklns minimal.db
+
+
+# Txt files stuff.
+# (Yes, I know compose.sh.SH generates compose.sh, but that never changes.)
+mdcd txt
+mklns compose.sh.SH compose.sh index-files.pl
+
+# Copy outright all the text files. Half of these need modifying and the
+# other half are autogenerated anyway.
+mkcps *.txt Makefile
+
+# Events and news are mush specific always, and they start out empty anyway
+
+mdcd txt/evt
+mkcps *
+mdcd txt/nws
+mkcps *
+
+mdcd txt/hlp
+mklns penncode.hlp pennfunc.hlp penntop.hlp pennchat.hlp pennconf.hlp \
+        pennmail.hlp pennvers.hlp penncmd.hlp pennflag.hlp pennpueb.hlp
diff --git a/utils/make_access_cnf.sh b/utils/make_access_cnf.sh
new file mode 100644 (file)
index 0000000..206ed61
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Take the file game/lockout.cnf and game/sites.cnf and combine them,
+# appending the result to game/access.cnf
+#
+# Usage:
+#   make_access_cnf.sh game-directory
+# (Commonly called by 'make access', which runs make_access_cnf.sh game)
+#
+dir=$1
+
+if [ -z "$dir" ]; then
+  echo "Usage: make_access_cnf.sh <game-directory>"
+  exit 0
+fi
+
+if [ ! -d $dir ]; then
+  echo "No such directory: $dir"
+  exit 0
+fi
+
+if [ -r $dir/lockout.cnf ]; then
+  echo "Processing lockout.cnf."
+  sed -e 's/$/ none/' < $dir/lockout.cnf >> $dir/access.cnf
+else
+  echo "No lockout.cnf found."
+fi
+
+
+if [ -r $dir/sites.cnf ]; then
+  echo "Processing sites.cnf."
+  sed -e 's/$/ !create/' < $dir/sites.cnf >> $dir/access.cnf
+else
+  echo "No sites.cnf found."
+fi
+
+echo "Done."
+
diff --git a/utils/mkcmds.sh.SH b/utils/mkcmds.sh.SH
new file mode 100644 (file)
index 0000000..1de7acd
--- /dev/null
@@ -0,0 +1,163 @@
+case $CONFIG in
+'')
+       if test -f config.sh; then TOP=.;
+       elif test -f ../config.sh; then TOP=..;
+       elif test -f ../../config.sh; then TOP=../..;
+       elif test -f ../../../config.sh; then TOP=../../..;
+       elif test -f ../../../../config.sh; then TOP=../../../..;
+       else
+               echo "Can't find config.sh."; exit 1
+       fi
+       . $TOP/config.sh
+       ;;
+esac
+: This forces SH files to create target in same directory as SH file.
+: This is so that make depend always knows where to find SH derivatives.
+case "$0" in
+*/*) cd `expr X$0 : 'X\(.*\)/'` ;;
+esac
+echo "Extracting mkcmds.sh (with variable substitutions)"
+: This section of the file will have variable substitutions done on it.
+: Move anything that needs config subs from !NO!SUBS! section to !GROK!THIS!.
+: Protect any dollar signs and backticks that you do not want interpreted
+: by putting a backslash in front.  You may delete these comments.
+$spitshell >mkcmds.sh <<!GROK!THIS!
+$startsh
+echo=$echo
+n=$n
+c=$c
+!GROK!THIS!
+
+: In the following dollars and backticks do not need the extra backslash.
+$spitshell >>mkcmds.sh <<'!NO!SUBS!'
+#
+# Make various tables and header files.
+# We run this from the utils directory.
+#
+if [ -f "../nocmds" ]; then
+  $echo "Not building"
+  exit
+fi
+
+case $1 in
+ patches)
+if [ -d ../patches ]; then
+  $echo "Rebuilding list of installed Patches"
+  for bu in ../patches/*; do
+    case $bu in
+      *~) ;; # Ignore these
+      *)
+          name=`grep "^# Patch name:" $bu | sed 's/[^:]*://'`
+          ver=`grep "^# Patch version:" $bu | sed 's/[^:]*://'`
+          name=`$echo $name`
+          ver=`$echo $ver`
+          if [ "X$name" != "X" ]; then
+            if [ "X$ver" != "X" ]; then
+              pat="${pat}${name}(${ver}) "
+            else
+              pat="${pat}${name} "
+            fi
+          fi
+        ;;
+    esac
+  done
+  pat=`$echo $pat`
+fi
+
+rm -f ../hdrs/temp.$$.h
+rm -f ../src/temp.$$.c
+
+$echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.$$.h
+$echo "#ifndef _PATCH_H" >> ../hdrs/temp.$$.h
+$echo "#define _PATCH_H" >> ../hdrs/temp.$$.h
+if [ "X$pat" = "X" ]; then
+  $echo "#undef PATCHES" >> ../hdrs/temp.$$.h
+else
+  $echo "#define PATCHES \"$pat\"" >> ../hdrs/temp.$$.h
+fi
+$echo "#endif /* _PATCH_H */" >> ../hdrs/temp.$$.h
+cmp -s ../hdrs/patches.h ../hdrs/temp.$$.h
+cmpstat=$?
+if [ $cmpstat -eq 0 ]; then
+    rm -f ../hdrs/temp.$$.h
+else
+    mv -f ../hdrs/temp.$$.h ../hdrs/patches.h
+fi
+
+;;
+switches)
+
+$echo "Rebuilding command switch file"
+snum=1
+$echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../src/temp.$$.c
+$echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.$$.h
+$echo "SWITCH_VALUE switch_list[] = {" >> ../src/temp.$$.c
+for s in `cat ../src/SWITCHES | sort`; do
+  $echo "#define SWITCH_$s $snum" >> ../hdrs/temp.$$.h
+  $echo $n "  {$c"                      >> ../src/temp.$$.c
+  $echo $n "\"$s\", SWITCH_$s$c"        >> ../src/temp.$$.c
+  $echo $n "}$c"                        >> ../src/temp.$$.c
+  $echo ","                     >> ../src/temp.$$.c
+  $echo $n "."
+  snum=`expr $snum + 1`
+done
+$echo "  {NULL, 0}" >> ../src/temp.$$.c
+$echo "};" >> ../src/temp.$$.c
+$echo ""
+
+# NUM_BYTES from command.h * 8.
+if [ $snum -gt 160 ]; then
+    $echo "You have too many switches defined!"
+fi
+
+mv -f ../hdrs/temp.$$.h ../hdrs/switches.h
+mv -f ../src/temp.$$.c ../src/switchinc.c
+
+;;
+commands)
+
+$echo "Rebuilding command prototype file"
+$echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.$$.h
+for c in `grep "^COMMAND *(" ../src/*.c | grep -v cmd_local_silly | cut -f2 -d\( | cut -f1 -d\) | sort | uniq`; do
+  $echo >>../hdrs/temp.$$.h "COMMAND_PROTO($c);"
+  $echo $n "."
+done
+$echo ""
+cmp -s ../hdrs/cmds.h ../hdrs/temp.$$.h
+cmpstat=$?
+if [ $cmpstat -eq 0 ]; then
+    rm -f ../hdrs/temp.$$.h
+else
+    mv -f ../hdrs/temp.$$.h ../hdrs/cmds.h
+fi
+
+if [ -d "../win32" ]; then
+  cp ../hdrs/cmds.h ../win32/cmds.h
+fi
+
+;;
+functions)
+
+$echo "Rebuilding function prototype file"
+$echo "/* AUTOGENERATED FILE. DO NOT EDIT */" > ../hdrs/temp.$$.h
+for c in `grep "^FUNCTION *(" ../src/*.c | grep -v local_fun_silly | cut -f2 -d\( | cut -f1 -d\) | sort | uniq`; do
+  $echo >>../hdrs/temp.$$.h "FUNCTION_PROTO($c);"
+  $echo $n "."
+done
+$echo ""
+cmp -s ../hdrs/funs.h ../hdrs/temp.$$.h
+cmpstat=$?
+if [ $cmpstat -eq 0 ]; then
+    rm -f ../hdrs/temp.$$.h
+else
+    mv -f ../hdrs/temp.$$.h ../hdrs/funs.h
+fi
+
+if [ -d "../win32" ]; then
+  cp ../hdrs/funs.h ../win32/funs.h
+fi
+;;
+esac
+!NO!SUBS!
+chmod 755 mkcmds.sh
+$eunicefix mkcmds.sh
diff --git a/utils/mkvershlp.pl b/utils/mkvershlp.pl
new file mode 100644 (file)
index 0000000..9ff7cc9
--- /dev/null
@@ -0,0 +1,92 @@
+#!/usr/local/bin/perl
+#
+# Generate game/txt/hlp/ files from the CHANGES file(s).
+# Should be run by Makefile from top-level directory
+#
+# Usage: mkvershlp game/txt/hlp CHANGES.176 CHANGES.OLD ...
+#
+# Each file CHANGES.<blah> generates file pennv<blah>.hlp in the
+# specified directory.
+#
+use strict;
+use Sort::Versions;
+use Text::Wrap;
+
+my $targetdir = shift;
+my @sources = @ARGV;
+my $verspat = '^Version (\S+) patchlevel (\S+)';
+my %patchlevels;
+
+@sources = sort byrevision @sources;
+
+my $really_started = 0;
+foreach my $file (@sources) {
+  warn "Can't open $file!\n", next unless open(IN,"<$file");
+  my $target = $file;
+  $target =~ s/.*\.(.*)/pennv$1.hlp/;
+  open(OUT,">$targetdir/$target") or die "Unable to open $targetdir/$target\n";
+  my $started = 0;
+  while (<IN>) {
+    if (/$verspat/o) {
+      print OUT "& $1p$2\n";
+      push @{$patchlevels{$1}}, $2;
+      unless ($started) {
+        # This is the first one 
+        unless ($really_started) {
+          print OUT <<'EOP';
+& changes
+This is a list of changes in this patchlevel which are probably of
+interest to players. More information about new commands and functions
+can probably be gotten via 'help <name of whatever>'. 'help credits'
+lists the [initials] of developers and porters that are used in the list 
+of changes.
+
+Information about changes in prior releases can be found under
+help topics named for each release (e.g. 'help 1.7.2p30').
+A list of the patchlevels associated with each release can
+be read in 'help patchlevels'.
+
+EOP
+          $really_started = 1;
+        }
+        $started = 1;
+      }
+      print OUT;
+    } elsif ($started) {
+      print OUT;
+    } 
+  }
+  close IN;
+}
+
+# Now spew the patchlevels list. Special case for 1.50
+$patchlevels{'1.5.0'} = $patchlevels{'1.50'};
+delete($patchlevels{'1.50'});
+my @versions = reverse sort versions keys %patchlevels;
+print OUT <<EOP;
+& patchlevels
+For information on a specific patchlevel of one of the versions listed,
+type 'help <version>p<patchlevel>'. For example, 'help 1.7.2p3'
+
+EOP
+foreach (@versions) {
+  my @pls = sort {$a <=> $b} @{$patchlevels{$_}};
+  my $line;
+  if ($_ eq "1.5.0") {
+    $line = "1.50: ". join(", ",@pls). "\n";
+  } else {
+    $line = "$_: ". join(", ",@pls). "\n";
+  }
+  print OUT wrap("","       ",$line);
+}
+
+close OUT;
+
+
+# A sort subroutine to order CHANGES.<blah> in reverse chronological
+# order
+sub byrevision {
+  return $b cmp $a if ($a =~ /\d/ and $b =~ /\d/);
+  return $a cmp $b;
+}
+
diff --git a/utils/penn-install b/utils/penn-install
new file mode 100644 (file)
index 0000000..69054cf
--- /dev/null
@@ -0,0 +1,277 @@
+#!/bin/sh
+#  Program: penn-install
+#  Version: 0.0.1
+#  Author: Ervin Hearn III (Noltar) <noltar@korongil.net>
+#  Date: Thu, 11 Sep 2003 18:04:43 -0400.
+#  Copyright:
+#
+#    This program is free software; you can redistribute it and/or modify it
+#    under the terms of the "Artistic License" which comes with Debian.
+#
+#    THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
+#    WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES
+#    OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+#
+#    On Debian GNU/Linux systems, the complete text of the Artistic License
+#    can be found in `/usr/share/common-licenses/Artistic'.
+#
+#  Description:
+#
+#    This program installs the pennmush server for the user in the directory
+#    from which it is ran. It provides all configuration, data, and script
+#    files necessary for the user to initiate and run an instance of the
+#    pennmush server process, netmush.
+#
+#  History:
+#   0.0.1  Initial Release.
+
+NAME=penn-install
+VERSION=0.0.1
+PKGDIR=`pwd`/pennmush
+DSTDIR=$PKGDIR/game
+SRCDIR=/usr/lib/pennmush/game
+DWNDIR=''
+
+
+################################################################
+#
+# 0. Check options
+
+case "$1" in
+  '')
+
+    ################################################################
+    #
+    # 1. Install pennmush
+    #
+
+    #
+    # 1.2 Helpers
+    #
+
+    mklns()
+    {
+      while [ $1 ]; do
+      ln -s $SRCDIR/$DWNDIR/$1
+      shift
+      done
+    }
+
+    mkcps()
+    {
+      while [ $1 ]; do 
+      cp $SRCDIR/$DWNDIR/$1 .
+      shift
+      done
+     }
+
+     mkcdir()
+     {
+       cd $DSTDIR
+       mkdir -p $1
+       cd $1
+       DWNDIR=$1
+     }
+
+     echo -n "Installing pennmush in $DSTDIR"
+
+     #
+     # 1.3 Create main pennmush directory
+     #
+
+     mkdir $PKGDIR
+     cd $PKGDIR
+
+     echo -n "."
+
+     #
+     # 1.4 Do sanity check for existing pennmush installations
+     #
+
+     if test -f ./$NAME; then
+
+       echo "It's not good idea to install pennmush in the current directory."
+       echo "You should read 'man $NAME' and run it in a different directory."
+       exit
+
+     fi
+
+     if test -f ./utils/$NAME; then
+
+       echo "If you want to use this script, you need to 'make debianinstall'"
+       echo "as root first, and then run this script. If you don't have the"
+       echo "necessary access, you should install pennmush normally from the"
+       echo "source or ask your system administrator about installing it"
+       echo "globally."
+       exit
+
+     fi
+
+     if test -f ./game/restart; then
+
+       echo "You should only run this once to install pennmush locally."
+       echo "All upgrades and modifications to the server itself are done"
+       echo "globally. Check with your system administrator if you are having"
+       echo "problems. If something is wrong with your configuration files,"
+       echo "create a new directory and run '$NAME' in that and copy"
+       echo "you're modified files over."
+       exit
+
+     fi
+
+     echo -n "."
+
+     # 1.5 Setup game directory
+
+     #
+     # 1.5.1 Create base game directory
+     #
+
+     mkdir $DSTDIR
+     cd $DSTDIR
+     mklns access.README getdate.README getdate.template config.sh mushcnf.dst README
+     mkcps *.cnf restart
+
+     echo -n "."
+
+     #
+     # 1.5.2 Link binaries
+     #
+
+     ln -s $SRCDIR/info_slave
+     ln -s $SRCDIR/netmush
+
+     echo -n "."
+
+     #
+     # 1.5.3 Make sure restart is executable by the user
+     #
+
+     chmod u+x restart
+
+     echo -n "."
+
+     #
+     # 1.6 Create storage directories
+     #
+
+     mkcdir save
+     mklns README
+
+     mkcdir log
+     mklns README
+
+     mkcdir data
+     mklns README
+
+     echo -n "."
+
+     #
+     # 1.7 Setup text files
+     #
+
+     #
+     # 1.7.1 Text generation files
+     #
+
+     mkcdir txt
+     mklns compose.sh.SH compose.sh index-files.pl
+
+     echo -n "."
+
+     #
+     # 1.7.2 Copy text files. Most will either be autogenerated or modified by the user
+     #
+
+     mkcps *.txt Makefile
+
+     echo -n "."
+
+     #
+     # 1.7.3 Events is mush specific and starts out essentially empty
+     #
+
+     mkcdir txt/evt
+     mkcps index.evt pennmush.evt
+
+     echo -n "."
+
+     #
+     # 1.7.4 News is mush specific and starts out essentially empty
+     #
+
+     mkcdir txt/nws
+     mkcps index.nws pennmush.nws
+
+     echo -n "."
+
+     #
+     # 1.7.5 Help contains a large part of the standard pennmush documentation
+
+     mkcdir txt/hlp
+     mklns index.hlp penncmd.hlp pennconf.hlp pennfunc.hlp pennpueb.hlp pennv174.hlp \
+       pennv176.hlp pennvOLD.hlp pennchat.hlp penncode.hlp pennflag.hlp \
+       pennmail.hlp penntop.hlp pennv175.hlp pennv177.hlp
+
+     echo "Done"
+     echo "** Be sure to change the 'port' entry in pennmush/game/mush.cnf"
+     echo "** to the one either assigned by your system admin or currently"
+     echo "** available on the system. Also log into the mush and change"
+     echo "** the password for One (#1) to something safe."
+    ;;
+  -v|--version)
+
+    ################################################################
+    #
+    # 1. Display version information
+    #
+    
+    echo "$NAME $VERSION  Copyright (C) 2003 Ervin Hearn III"
+    echo "This program is free software; you can redistribute it and/or modify it"
+    echo "under the terms of the \"Artistic License\" which comes with Debian."
+    echo " "
+    echo "THIS PACKAGE IS PROVIDED \"AS IS\" AND WITHOUT ANY EXPRESS OR IMPLIED"
+    echo "WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES"
+    echo "OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE."
+    echo " "
+    echo "On Debian GNU/Linux systems, the complete text of the Artistic License"
+    echo "can be found in '/usr/share/common-licenses/Artistic'."
+    echo " "
+    echo "Report bugs to <noltar@korongil.net>."
+    exit 1
+    ;;
+  *)
+
+    ################################################################
+    #
+    # 1. Display help information
+    #
+    
+    echo "$NAME $VERSION  Copyright (C) 2003 Ervin Hearn III"
+    echo "This program is free software; you can redistribute it and/or modify it"
+    echo "under the terms of the \"Artistic License\" which comes with Debian."
+    echo " "
+    echo "THIS PACKAGE IS PROVIDED \"AS IS\" AND WITHOUT ANY EXPRESS OR IMPLIED"
+    echo "WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES"
+    echo "OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE."
+    echo " "
+    echo "On Debian GNU/Linux systems, the complete text of the Artistic License"
+    echo "can be found in '/usr/share/common-licenses/Artistic'."
+    echo " "
+    echo "Usage: $NAME [OPTION]"
+    echo " "
+    echo "Installs pennmush in the current directory for the user."
+    echo " "
+    echo "This program follows the usual GNU command line syntax, with long"
+    echo "options starting with two dashes ('-')."
+    echo " "
+    echo "Options:"
+    echo "  -h, --help        display this help and exit"
+    echo "  -v, --version     output version information and exit"
+    echo " "
+    echo "Report bugs to <noltar@korongil.net>."
+    exit 1
+    ;;
+esac
+
+exit 0
diff --git a/utils/update-cnf.pl b/utils/update-cnf.pl
new file mode 100644 (file)
index 0000000..f621e05
--- /dev/null
@@ -0,0 +1,188 @@
+#!/usr/local/bin/perl
+#
+# You make have to change the path to perl above, unless you call this
+# script with 'make update'
+#
+# update-cnf.pl - integrate previous mush.cnf settings with new 
+#                  mush.cnf.dist. Results appear in mush.cnf
+#
+# Usage: update-cnf.pl old-file new-file
+#  e.g.: update-cnf.pl game/mush.cnf game/mush.cnf.dist
+#
+# 'make update' calls this program as in the example above.
+#
+# Here's how it works.
+# First, we make a backup of your old-file to old-file.bak
+# Then we read all the directives in the old-file, and their
+#  associated comments. Associated comments are those which
+#  appear on lines preceding the directive.
+#  We store the names of all the directives, 
+#  their comments, and how they're defined.
+# Then we do the same for the new-file. If we find a directive
+#  that wasn't in the old-file, we show the user the comment
+#  and ask them how they want it set. Every time we write out
+#  a directive, we delete it from the list of directives from old-file
+# Finally, if there's anything left from old-file that's not in
+#  new-file, we ask if the user would like to retain each one.
+#  Presumably users want to retain their custom directives, but don't
+#  want to retain obsoleted directives. Retained directives appear at
+#  the end of the file.
+
+die "Usage: update-cnf.pl old-file new-file\n" unless $#ARGV == 1;
+
+$old = $ARGV[0];
+$bak = $old . ".bak";
+$new = $ARGV[1];
+
+
+# Part 1 - back up the old file (inefficient but reliable method)
+if (-r $old) {
+    print "*** Backing up $old to $bak...\n";
+    die "update-cnf.pl: Unable to open $old\n" unless open(OLD,"$old"); 
+    die "update-cnf.pl: Unable to open $bak\n" unless open(BAK,">$bak");
+    print BAK <OLD>;
+    close(BAK);
+    close(OLD);
+} else {
+    # Heck, let's just copy the new file to the old one and quit!
+    print "*** Creating $old from $new...\n";
+    die "update-cnf.pl: Unable to open $old\n" unless open(OLD,">$old"); 
+    die "update-cnf.pl: Unable to open $new\n" unless open(NEW,"$new"); 
+    print OLD <NEW>;
+    close(OLD);
+    close(NEW);
+    exit 0;
+}
+  
+
+# Part 2 - read the settings from the old file and store them
+if (-r $old) {
+    print "*** Reading your settings from $old...\n";
+    die "update-cnf.pl: Unable to open $old\n" unless open(OLD,"$old"); 
+    while (<OLD>) {
+       # We can have comments, which start with #,
+        # or directives, which start with anything else.
+       if (/^#/) {
+         # A comment
+         push(@comment,$_);
+       } elsif (/^(\S+)\s+(.+)$/) {
+         # A directive
+         $key = $1; $val = $2;
+         chop;
+         if (defined($directive{$key})) {
+             # This is a repeatable directive! 
+             $num{$key}++;
+             $directive{"$key/$num{$key}"} = $val;
+             $comment{"$key/$num{$key}"} = join("",@comment);
+         } else {
+             $directive{$key} = $val; 
+             $comment{$key} = join("",@comment);
+         }
+         undef @comment;
+        } elsif (/^(\S+)/) {
+          # A directive that's defined as blank
+          $key = $1; $val = "";
+          $directive{$key} = $val;
+         $comment{$key} = join("",@comment);
+          undef @comment;
+       } elsif (/^$/) {
+         # A blank line. Ignore comments so far
+         undef @comment;
+       }
+    }
+    close(OLD);
+}
+
+# Part 3 - read in the new file, modifying its definition lines to
+#          match the old file. If we come across a definition that
+#          isn't in the old file, ask the user about it. 
+print "*** Updating $old from $new...\n";
+die "update-cnf.pl: Unable to open $old\n" unless open(OLD,">$old"); 
+die "update-cnf.pl: Unable to open $new\n" unless open(NEW,"$new"); 
+while (<NEW>) {
+    # We can have comments, which start with #,
+    # or directives, which start with anything else.
+    if (/^#/) {
+       # A comment
+       push(@comment,$_);
+    } elsif (/^(\S+)/) {
+       # Not a comment
+       $key = $1;
+       chop;
+       if ($num{$key}) {
+           if ($num{$key} > 0) {
+               # A repeatable directive!
+               # Spew them all here. It's probably the best we can do.
+               print OLD @comment;
+               print OLD "$key\t$directive{$key}\n\n";
+               delete $comment{$key};
+               for ($i = 1; $i <= $num{$key}; $i++) {
+                   print OLD $comment{"$key/$i"};
+                   print OLD "$key\t", $directive{"$key/$i"},"\n\n";
+                   delete $comment{"$key/$i"};
+                   delete $directive{"$key/$i"};
+               } 
+               $num{$key} = -1; # Don't spew again
+           }
+       } else {
+           print OLD @comment;
+           if (!defined($directive{$key}) || $key eq "include") {
+               # It's new, just add it
+               print OLD $_,"\n";
+               push(@newoptions,$key);
+              delete $comment{$key} if $key eq "include";
+           } else {
+               # It's old. Put it in, with a value if one's set.
+               print OLD $key;
+               print OLD "\t$directive{$key}" if (defined($directive{$key}));
+               print OLD "\n";
+               # Remove its comment.
+               delete $comment{$key};
+           }
+       }
+       undef @comment;
+    } elsif (/^$/) {
+       print OLD @comment;
+       undef @comment;
+       print OLD;
+    } else {
+       print OLD;
+    }
+}
+close(NEW);
+
+# Part 4 - if there are any definitions left from the old file,
+#          offer to delete them (or not)
+print "\n*** Checking for leftover defines from $old...\n";
+foreach $d (sort { $directive{$a} cmp $directive{$b} } keys %comment) {
+    $newd = $d;
+    $newd =~ s!/.*!!;
+    print "\nI found:\n";
+    print $comment{$d};
+    print "$newd\t$directive{$d}\n";
+    print "\n";
+    print "If this is a directive that you hacked in, you probably should retain it.\n";
+    print "If not, it's probably an obsolete directive from an earlier release,\n";
+    print "and you need not retain it.\n";
+    print "Do you want to retain this in your $old file? [y] ";
+    $yn = <STDIN>;
+    if ($yn !~ /^[Nn]/) {
+       print "Retaining directive. It will appear at the end of $old.\n";
+        @retained = (@retained, $newd);
+       print OLD $comment{$d};
+       print OLD "$newd\t$directive{$d}\n";
+       print OLD "\n";
+    } else {
+       print "Deleting definition.\n";
+       @deleted = (@deleted, $d);
+    }
+}
+close(OLD);
+
+print "\nSummary of changes:\n";
+print "New options from $new: ",join(" ",@newoptions),"\n";
+print "Old options retained: ",join(" ",@retained),"\n";
+print "Old options deleted: ",join(" ",@deleted),"\n";
+print "If this is wrong, you can recover $old from $bak.\n";
+print "Done!\n";
+exit 0;
diff --git a/utils/update.pl b/utils/update.pl
new file mode 100644 (file)
index 0000000..1daccf0
--- /dev/null
@@ -0,0 +1,323 @@
+#!/usr/local/bin/perl
+#
+# You make have to change the path to perl above, unless you call this
+# script with 'make update'
+#
+# update.pl - integrate previous options.h settings with new options.h.dist
+#             results appear in options.h
+#
+# Usage: update.pl old-file new-file
+#  e.g.: update.pl options.h options.h.dist
+#
+# 'make update' calls this.
+#
+# Here's how it works.
+# 1. We make a backup of your old-file to old-file.bak
+# 2. We read all the #def's in the old-file, and their
+#  associated comments. Associated comments means comments
+#  on the same line, after the define, or comments on lines
+#  preceding the define. We store the names of all the defines,
+#  their comments, and whether they're defined or not.
+# 3. We check to see if there's are enviroment variables named DEFINE
+#  or UNDEFINE. If so we parse them. DEFINE may contain NAMEs or
+#  NAME=value pairs. UNDEFINE should contain only NAMEs.
+#  We consider these as if they were present in old-file. 
+#  These override old-file.
+# 4. We read in the new-file. If we find a define
+#  that wasn't in the old-file, we show the user the comment
+#  and ask them how they want it set. Every time we write out
+#  a define, we delete it from the list of defines from old-file
+# 5. Finally, if there's anything left from old-file that's not in
+#  new-file, we ask if the user would like to retain each one.
+#  Presumably users want to retain their custom defines, but don't
+#  want to retain obsoleted defines. Retained defines appear at
+#  the end of the file.
+
+die "Usage: update.pl old-file new-file\n" unless $#ARGV == 1;
+
+$old = $ARGV[0];
+$bak = $old . ".bak";
+$new = $ARGV[1];
+
+
+# Part 1 - back up the old file (inefficient but reliable method)
+if (-r $old) {
+    print "*** Backing up $old to $bak...\n";
+    die "update.pl: Unable to open $old\n" unless open(OLD,"$old"); 
+    die "update.pl: Unable to open $bak\n" unless open(BAK,">$bak");
+    print BAK <OLD>;
+    close(BAK);
+    close(OLD);
+}
+
+# Part 2 - read the settings from the old file and store them
+if (-r $old) {
+   print "*** Reading your settings from $old...\n";
+    die "update.pl: Unable to open $old\n" unless open(OLD,"$old"); 
+    while (<OLD>) {
+        # There are a few possibilities for what we could have:
+        # an #ifdef, #ifndef, #else, #endif, #define, #undef,
+        # commented #define, comment text, etc. We only care
+        # about the settings of define/undefs
+        s#/\*\s*\*/##;
+        s#[ \t]+([\r\n]*)$#$1#;
+        if ( /^#define\s+([A-Z0-9_-]+).*\\$/ ) {
+       # A define with a continuation, we need the next line
+       chop($next = <OLD>);
+       $defs{$1} = $next;
+       $comment{$1} = $comment;
+        } elsif ( m!^#define\s+([A-Z0-9_-]+)\s+(.+)\s+(/\*.*\*/)!
+            ) {
+       # A define with a value and a comment
+       $name = $1;
+       $comment{$name} = $3;
+       $defs{$name} = $2;
+       undef $comment;
+        } elsif ( m!^#define\s+([A-Z0-9_-]+)\s+(.+)!
+            ) {
+       # A define with a value
+       $defs{$1} = $2;
+       $comment{$1} = $comment;
+        } elsif ( /^#undef\s+([A-Z0-9_-]+)/ 
+            ) {
+       # an undef
+       $defs{$1} = 'undef';
+       $comment{$1} = $comment;
+        } elsif ( m!^/\*\s*#define\s+([A-Z0-9_-]+)\s+(.+)\s+(/\*.*\*/)!
+            ) {
+       # A commented define with a value and a comment
+       $name = $1;
+       $comment{$name} = $3;
+        $cvaldef{$name}++;
+       $defs{$name} = $2;
+       undef $comment;
+        } elsif ( /^(\/\*)*\s*#define\s+([A-Z0-9-][A-Z0-9_-]+)/
+            ) {
+       # a define or commented define
+       $defs{$2} = ($1 eq "/*") ? 'undef' : 'define';
+       $comment{$2} = $comment;
+        } else {
+       if (m#^\s*/\*#) {
+           # Start of a comment
+           $incomment = 1;
+           undef $comment;
+       }
+       if ($incomment) {
+           $comment = $comment . $_;
+           if (m#\*/\s+$#) {
+               # End of a comment
+               $incomment = 0;
+           }
+       }
+        }
+    }
+    close(OLD);
+}
+undef $comment; $incomment = 0;
+
+
+# Part 3 - Check to see if we have environment variable SETTINGS and
+#          use those settings as if they were in the old file.
+if ($settings = $ENV{'DEFINE'}) {
+  print "\n*** Found a DEFINE environment variable - applying settings...\n";
+  @pairs = split ' ', $settings;
+  foreach (@pairs) {
+    if (($d,$v) = /(.+)=(.+)/) {
+      $defs{$d} = $v;
+    } else {
+      $defs{$_} = 'define';
+    }
+  }
+}
+if ($settings = $ENV{'UNDEFINE'}) {
+  print "\n*** Found an UNDEFINE environment variable - applying settings...\n";
+  @pairs = split ' ', $settings;
+  foreach (@pairs) {
+      $defs{$_} = 'undef';
+  }
+}
+
+# Part 4 - read in the new file, modifying its definition lines to
+#          match the old file. If we come across a definition that
+#          isn't in the old file, ask the user about it. 
+print "*** Updating $old from $new...\n";
+die "update.pl: Unable to open $old\n" unless open(OLD,">$old"); 
+die "update.pl: Unable to open $new\n" unless open(NEW,"$new"); 
+$_ = <NEW>;
+while ($next = <NEW>) {
+    # Just like before, but we need to keep track of
+    # comments in the file so that we can describe options
+    s#[ \t]+([\r\n]*)$#$1#;
+    if ( /^#define\s+([A-Z0-9_-]+).*\\$/
+       ) {
+       # A define with a continuation, we need the next line
+       print OLD "#define $1 \\\n";
+       &ask_value($1,$next) if (!defined($defs{$1}));
+       print OLD $defs{$1};
+       delete $defs{$1};
+       $next = <NEW>;
+    } elsif ( /^#define\s+([A-Z0-9-][A-Z0-9_-]+)\s+\/\*\s*\*\//) {
+       # a define followed by /* */
+       print OLD defined($defs{$1}) ? &def($1) 
+                                    : &def(&ask_simple($1,'define'));
+    } elsif ( m!^(/\*\s*)?#define\s+([A-Z0-9_-]+)\s+(.+)\s+(/\*.*\*/)!) {
+       # A define with a value and a comment
+        $maybeundef = $1;
+       $maybecomment = $4; $name = $2;
+       $olddef = $def = $3;
+       $comment = $maybecomment if ($maybecomment =~ /\w/);
+       $def = "undef" if $maybeundef =~ /./;
+       print OLD defined($defs{$name}) 
+           ? &def($name,$comment) : &def(&ask_value($name,$def),$maybecomment,$def eq "undef" ? $olddef : $def);
+    } elsif ( m!^#define\s+([A-Z0-9_-]+)\s+(.+)!) {
+       # A define with a value
+       print OLD defined($defs{$1}) ? &def($1) : &def(&ask_value($1,$2));
+    } elsif ( /^#undef\s+([A-Z0-9_-]+)/ 
+            ) {
+       print OLD defined($defs{$1}) ? &def($1) 
+                                    : &def(&ask_simple($1,'undef'));
+    } elsif ( /^(\/\*)*\s*#define\s+([A-Z0-9-][A-Z0-9_-]+)/
+            ) {
+       # a define or commented define
+       print OLD defined($defs{$2}) ?
+           &def($2)
+               : &def(&ask_simple($2,($1 eq "/*" ? 'undef': 'define')));
+    } else {
+       if (m#^\s*/\*#) {
+           # Start of a comment
+           $incomment = 1;
+           undef $comment;
+       }
+       if ($incomment) {
+           $comment = $comment . $_;
+           if (m#\*/\s+$#) {
+               # End of a comment
+               $incomment = 0;
+           }
+       }
+       print OLD;
+    }
+    $_ = $next;
+}
+# At the end of that loop, $_ contains the last line of the
+# file, which should be the #endif.
+$final = $_;
+close(NEW);
+
+# Part 5 - if there are any definitions left from the old file,
+#          offer to delete them (or not)
+print "\n*** Checking for leftover defines from $old...\n";
+foreach $d (keys %defs) {
+    print "\nI found: $d\n";
+    if ($defs{$d} eq 'undef') {
+       print "Currently undefined\n";
+    } elsif ($defs{$d} eq 'define') {
+       print "Currently defined\n";
+    } else {
+       print "Definition: $defs{$d}\n";
+    }
+    print $comment{$d};
+    print "\n";
+    print "If this is a define that you hacked in, you probably should retain it.\n";
+    print "If not, it's probably an obsolete define from an earlier patchlevel,\n";
+    print "and you need not retain it.\n";
+    print "Do you want to retain this in your $old file? [y] ";
+    $yn = <STDIN>;
+    if ($yn !~ /^[Nn]/) {
+       print "Retaining definition. It will appear at the end of $old.\n";
+        @retained = (@retained, $d);
+       print OLD $comment{$d};
+       print OLD &def($d);
+       print OLD "\n";
+    } else {
+       print "Deleting definition.\n";
+       @deleted = (@deleted, $d);
+    }
+}
+
+print OLD $final;
+
+close(OLD);
+
+print "\nSummary of changes:\n";
+print "New options from $new: ",join(" ",@newoptions),"\n";
+print "Old options retained: ",join(" ",@retained),"\n";
+print "Old options deleted: ",join(" ",@deleted),"\n";
+print "If this is wrong, you can recover $old from $bak.\n";
+print "Done!\n";
+exit 0;
+
+
+#
+# &def - Given a define name, return the appopriate C code
+# to define/undefine it. And delete it.
+# May also be given a comment as a second arg.
+#
+sub def {
+    # We should use my instead of local, but some folks have perl 4
+    local($d,$c,$oldval) = @_;
+    local($df) = $defs{$d};
+    delete $defs{$d};
+    $d =~ s/^\s+//;
+    $d =~ s/\s+$//;
+    $c =~ s/^\s+//;
+    $c =~ s/\s+$//;
+    $df =~ s/^\s+//;
+    $df =~ s/\s+$//;
+    if ($df eq 'undef') {
+      if (defined($oldval) and $oldval) {
+        $oldval =~ s/^\s+//;
+        $oldval =~ s/\s+$//;
+        return "/* #define $d\t$oldval /* */\n";
+      } else {
+        return "/* #define $d /* */\n";
+      }
+    }
+    return "#define $d /* */\n" if ($df eq 'define');
+    return "/* #define $d\t$df\t$c\n" if ($cvaldef{$d} and $c);
+    return "#define $d\t$df\t$c\n" if ($c);
+    return "#define $d\t$df\n";
+}
+
+#
+# &ask_simple - Given a define name and default setting,
+# show the comment in $comment,
+# and ask the user if they want to define it or not
+# Set $defs{$d} and return the name given
+#
+sub ask_simple {
+    local($d,$s) = @_;
+    local($yn);
+    print "\nNew option: $d\n";
+    print $comment;
+    $s = ($s eq 'define') ? 'y' : 'n';
+    while (1) {
+       print "Define this option? [$s] ";
+       $yn = <STDIN>;
+       $yn = $s if $yn =~ /^$/;
+       last if $yn =~ /^[YyNn]/;
+    }
+    $defs{$d} = ($yn =~ /^[Yy]/) ? 'define' : 'undef';
+    @newoptions = (@newoptions,$d);
+    return $d;
+}
+    
+
+#
+# &ask_value - Just like ask_simple, but instead of a yes/no,
+# we're going to get a value
+#
+sub ask_value {
+    local($d,$s) = @_;
+    local($val);
+    print "\nNew option: $d\n";
+    print "$comment\n" unless ($comment =~ /^\s*\/\*\s*\*\/\s*$/);
+    print "Default value: $s\n";
+    print "Value for this option? (undef to undefine) [$s] ";
+    $val = <STDIN>;
+    $val = $s if $val =~ /^$/;
+    $defs{$d} = $val;
+    @newoptions = (@newoptions,$d);
+    return $d;
+}
+
diff --git a/win32/.gitify-empty b/win32/.gitify-empty
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/win32/README.mingw b/win32/README.mingw
new file mode 100644 (file)
index 0000000..fb2fdf5
--- /dev/null
@@ -0,0 +1,66 @@
+How to compile PennMUSH 1.7.x under Windows (MinGW32 and MSys)
+--------------------------------------------------------------
+by Ervin Hearn III <noltar@korongil.net>
+
+Last update: Saturday, 28 February 2004
+
+ 1. Download and install the MinGW GNU Toolsets for Windows
+    from http://www.mingw.org/
+
+ 2. Download and install ActiveState Perl for Windows from
+    http://www.activestate.com/ActivePerl/
+
+ 3. Download and install the MSys package for Windows from
+    http://www.mingw.org/ This should be installed after
+    you have installed MinGW and ActivePerl
+
+ 4. Make certain that your MinGW and Perl locations are
+    properly moutned in your MSys /etc/fstab
+
+    An example fstab content is:
+
+    #Win32_Path                Mount_Point
+    c:/Progra~1/mingw  /mingw
+    c:/Progra~1/perl   /perl
+
+ 5. `cd' to the desired install location with the .tar.gz file
+    and unpack the downloaded <version> with:
+
+    tar xzf pennmush-<version>.tar.gz
+
+ 6. cd pennmush then type:
+
+    ./Configure -d
+
+    *** Note: This should configure the mush appropriately
+    *** if it fails to, you can attempt ./Configure
+    *** alone and use the mingw32 hints option when asked
+
+ 7. Now type:
+
+    cp options.h.dist options.h
+
+    and edit options.h as desired, keeping the following
+    suggestions in mind:
+
+    a. Do *NOT* define NT_TCP
+    b. Use MALLOC_PACKE 0
+    c. Do *NOT* define INFO_SLAVE 
+
+    Hopefully the last be changed in the future.
+
+ 8. Also, cp game/mushcnf.dist game/mush.cnf and edit.
+
+ 9. Do a `make install' This will build the necessary files and
+    install the necessary symbolic links. You can ignore the
+    warnings regarding the redefinition of BYTE and LONG which
+    are given for funcrypt.c mycrypt.c and/or shs.c.
+
+10. Read game/README and follow those instructions. For MinGW
+    you will also need to edit game/restart and explicitly
+    set the GAMEDIR variable to your installation path, an
+    example is:
+    
+    /home/noltar/pennmush/game
+
+
diff --git a/win32/README.txt b/win32/README.txt
new file mode 100644 (file)
index 0000000..09238bf
--- /dev/null
@@ -0,0 +1,45 @@
+How to compile PennMUSH 1.7.x under Windows (MSVC++/MS VS.NET)\r
+----------------------------------------------\r
+by Nick Gammon <nick@gammon.com.au> and Javelin and Luuk de Waard\r
+\r
+Last update: Monday, 1 November 2002\r
+\r
+1. From the top-level pennmush directory,\r
+   Copy the following files     to:\r
+\r
+  win32/config.h      config.h\r
+  win32/confmagic.h   confmagic.h\r
+  win32/options.h     options.h\r
+  win32/cmds.h        hdrs/cmds.h\r
+  win32/funs.h        hdrs/funs.h\r
+  win32/patches.h     hdrs/patches.h\r
+  src/local.dst       src/local.c\r
+  src/funlocal.dst    src/funlocal.c\r
+  src/cmdlocal.dst    src/cmdlocal.c\r
+  src/flaglocal.dst   src/flaglocal.c\r
+  game/mushcnf.dst    game/mush.cnf\r
+\r
+  Project files for MSVC++:\r
+  win32/pennmush.dsw  pennmush.dsw\r
+  win32/pennmush.dsp  pennmush.dsp\r
+\r
+  Project files for MS VS.NET:\r
+  win32/pennmush.vcproj  pennmush.vcproj\r
+  win32/pennmush.sln  pennmush.sln\r
+\r
+   (If you've already got src/*local.c files that you've modified,\r
+    you'll just have to make sure that there are no new functions\r
+    in src/*local.dst that're missing in your src/*local.c files)\r
+\r
+2. If you're running under Windows NT, you may wish to edit options.h\r
+and uncomment the #define NT_TCP option. If you can build\r
+with this, you'll get greatly enhanced network i/o performance. This\r
+does not work on Windows 95/98. This option is also incompatible\r
+with @shutdown/reboot.\r
+\r
+3. Use supplied project files in the top-level pennmush directory.\r
+\r
+4. Compile\r
+\r
+5. From the top-level pennmush directory, the binary is: game/pennmush.exe\r
+\r
diff --git a/win32/cmds.h b/win32/cmds.h
new file mode 100644 (file)
index 0000000..217b3b7
--- /dev/null
@@ -0,0 +1,152 @@
+/* AUTOGENERATED FILE. DO NOT EDIT */
+COMMAND_PROTO(cmd_allhalt);
+COMMAND_PROTO(cmd_allquota);
+COMMAND_PROTO(cmd_assert);
+COMMAND_PROTO(cmd_atrchown);
+COMMAND_PROTO(cmd_atrlock);
+COMMAND_PROTO(cmd_attribute);
+COMMAND_PROTO(cmd_boot);
+COMMAND_PROTO(cmd_break);
+COMMAND_PROTO(cmd_brief);
+COMMAND_PROTO(cmd_buy);
+COMMAND_PROTO(cmd_cemit);
+COMMAND_PROTO(cmd_channel);
+COMMAND_PROTO(cmd_chat);
+COMMAND_PROTO(cmd_chown);
+COMMAND_PROTO(cmd_chownall);
+COMMAND_PROTO(cmd_chzone);
+COMMAND_PROTO(cmd_chzoneall);
+COMMAND_PROTO(cmd_clock);
+COMMAND_PROTO(cmd_clone);
+COMMAND_PROTO(cmd_cobj);
+COMMAND_PROTO(cmd_command);
+COMMAND_PROTO(cmd_config);
+COMMAND_PROTO(cmd_cpattr);
+COMMAND_PROTO(cmd_create);
+COMMAND_PROTO(cmd_cron);
+COMMAND_PROTO(cmd_dbck);
+COMMAND_PROTO(cmd_decompile);
+COMMAND_PROTO(cmd_desert);
+COMMAND_PROTO(cmd_destroy);
+COMMAND_PROTO(cmd_dig);
+COMMAND_PROTO(cmd_disable);
+COMMAND_PROTO(cmd_dismiss);
+COMMAND_PROTO(cmd_division);
+COMMAND_PROTO(cmd_doing);
+COMMAND_PROTO(cmd_dolist);
+COMMAND_PROTO(cmd_drop);
+COMMAND_PROTO(cmd_dump);
+COMMAND_PROTO(cmd_edit);
+COMMAND_PROTO(cmd_elock);
+COMMAND_PROTO(cmd_emit);
+COMMAND_PROTO(cmd_empower);
+COMMAND_PROTO(cmd_empty);
+COMMAND_PROTO(cmd_enable);
+COMMAND_PROTO(cmd_enter);
+COMMAND_PROTO(cmd_entrances);
+COMMAND_PROTO(cmd_eunlock);
+COMMAND_PROTO(cmd_examine);
+COMMAND_PROTO(cmd_find);
+COMMAND_PROTO(cmd_firstexit);
+COMMAND_PROTO(cmd_flag);
+COMMAND_PROTO(cmd_follow);
+COMMAND_PROTO(cmd_force);
+COMMAND_PROTO(cmd_function);
+COMMAND_PROTO(cmd_get);
+COMMAND_PROTO(cmd_give);
+COMMAND_PROTO(cmd_goto);
+COMMAND_PROTO(cmd_grep);
+COMMAND_PROTO(cmd_halt);
+COMMAND_PROTO(cmd_helpcmd);
+COMMAND_PROTO(cmd_hide);
+COMMAND_PROTO(cmd_hook);
+COMMAND_PROTO(cmd_huh_command);
+COMMAND_PROTO(cmd_inventory);
+COMMAND_PROTO(cmd_kick);
+COMMAND_PROTO(cmd_leave);
+COMMAND_PROTO(cmd_lemit);
+COMMAND_PROTO(cmd_level);
+COMMAND_PROTO(cmd_link);
+COMMAND_PROTO(cmd_list);
+COMMAND_PROTO(cmd_listmotd);
+COMMAND_PROTO(cmd_lock);
+COMMAND_PROTO(cmd_log);
+COMMAND_PROTO(cmd_logwipe);
+COMMAND_PROTO(cmd_look);
+COMMAND_PROTO(cmd_lset);
+COMMAND_PROTO(cmd_mail);
+COMMAND_PROTO(cmd_malias);
+COMMAND_PROTO(cmd_map);
+COMMAND_PROTO(cmd_motd);
+COMMAND_PROTO(cmd_mvattr);
+COMMAND_PROTO(cmd_name);
+COMMAND_PROTO(cmd_newpassword);
+COMMAND_PROTO(cmd_notify_drain);
+COMMAND_PROTO(cmd_nuke);
+COMMAND_PROTO(cmd_null);
+COMMAND_PROTO(cmd_oemit);
+COMMAND_PROTO(cmd_open);
+COMMAND_PROTO(cmd_page);
+COMMAND_PROTO(cmd_parent);
+COMMAND_PROTO(cmd_password);
+COMMAND_PROTO(cmd_pcreate);
+COMMAND_PROTO(cmd_pemit);
+COMMAND_PROTO(cmd_poll);
+COMMAND_PROTO(cmd_poor);
+COMMAND_PROTO(cmd_pose);
+COMMAND_PROTO(cmd_power);
+COMMAND_PROTO(cmd_powergroup);
+COMMAND_PROTO(cmd_prog);
+COMMAND_PROTO(cmd_prompt);
+COMMAND_PROTO(cmd_ps);
+COMMAND_PROTO(cmd_purge);
+COMMAND_PROTO(cmd_quota);
+COMMAND_PROTO(cmd_readcache);
+COMMAND_PROTO(cmd_rejectmotd);
+COMMAND_PROTO(cmd_remit);
+COMMAND_PROTO(cmd_restart);
+COMMAND_PROTO(cmd_rplog);
+COMMAND_PROTO(cmd_say);
+COMMAND_PROTO(cmd_scan);
+COMMAND_PROTO(cmd_score);
+COMMAND_PROTO(cmd_search);
+COMMAND_PROTO(cmd_select);
+COMMAND_PROTO(cmd_semipose);
+COMMAND_PROTO(cmd_set);
+COMMAND_PROTO(cmd_shutdown);
+COMMAND_PROTO(cmd_sitelock);
+COMMAND_PROTO(cmd_snoop);
+COMMAND_PROTO(cmd_sql);
+COMMAND_PROTO(cmd_squota);
+COMMAND_PROTO(cmd_stats);
+COMMAND_PROTO(cmd_su);
+COMMAND_PROTO(cmd_sweep);
+COMMAND_PROTO(cmd_switch);
+COMMAND_PROTO(cmd_take);
+COMMAND_PROTO(cmd_teach);
+COMMAND_PROTO(cmd_teleport);
+COMMAND_PROTO(cmd_think);
+COMMAND_PROTO(cmd_trigger);
+COMMAND_PROTO(cmd_ulock);
+COMMAND_PROTO(cmd_undestroy);
+COMMAND_PROTO(cmd_unfollow);
+COMMAND_PROTO(cmd_unimplemented);
+COMMAND_PROTO(cmd_unlink);
+COMMAND_PROTO(cmd_unlock);
+COMMAND_PROTO(cmd_uptime);
+COMMAND_PROTO(cmd_use);
+COMMAND_PROTO(cmd_uunlock);
+COMMAND_PROTO(cmd_verb);
+COMMAND_PROTO(cmd_version);
+COMMAND_PROTO(cmd_wait);
+COMMAND_PROTO(cmd_wall);
+COMMAND_PROTO(cmd_warnings);
+COMMAND_PROTO(cmd_warn_on_missing);
+COMMAND_PROTO(cmd_wcheck);
+COMMAND_PROTO(cmd_whereis);
+COMMAND_PROTO(cmd_whisper);
+COMMAND_PROTO(cmd_wipe);
+COMMAND_PROTO(cmd_with);
+COMMAND_PROTO(cmd_zclone);
+COMMAND_PROTO(cmd_zemit);
+COMMAND_PROTO(command_atrset);
diff --git a/win32/cobramush.sln b/win32/cobramush.sln
new file mode 100644 (file)
index 0000000..ee31cb6
--- /dev/null
@@ -0,0 +1,21 @@
+Microsoft Visual Studio Solution File, Format Version 7.00\r
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cobramush", "cobramush.vcproj", "{7E857E31-C622-4096-92AC-2A6A615C6882}"\r
+EndProject\r
+Global\r
+       GlobalSection(SolutionConfiguration) = preSolution\r
+               ConfigName.0 = Debug\r
+               ConfigName.1 = Release\r
+       EndGlobalSection\r
+       GlobalSection(ProjectDependencies) = postSolution\r
+       EndGlobalSection\r
+       GlobalSection(ProjectConfiguration) = postSolution\r
+               {7E857E31-C622-4096-92AC-2A6A615C6882}.Debug.ActiveCfg = Release|Win32\r
+               {7E857E31-C622-4096-92AC-2A6A615C6882}.Debug.Build.0 = Release|Win32\r
+               {7E857E31-C622-4096-92AC-2A6A615C6882}.Release.ActiveCfg = Release|Win32\r
+               {7E857E31-C622-4096-92AC-2A6A615C6882}.Release.Build.0 = Release|Win32\r
+       EndGlobalSection\r
+       GlobalSection(ExtensibilityGlobals) = postSolution\r
+       EndGlobalSection\r
+       GlobalSection(ExtensibilityAddIns) = postSolution\r
+       EndGlobalSection\r
+EndGlobal\r
diff --git a/win32/config.h b/win32/config.h
new file mode 100644 (file)
index 0000000..88e8141
--- /dev/null
@@ -0,0 +1,810 @@
+/*
+ * This file was produced by running the config_h.SH script, which
+ * gets its values from config.sh, which is generally produced by
+ * running Configure.
+ *
+ * Feel free to modify any of this as the need arises.  Note, however,
+ * that running config_h.SH again will wipe out any changes you've made.
+ * For a more permanent change edit config.sh and rerun config_h.SH.
+ *
+ * $Id: config.h,v 1.2 2005-08-07 22:55:22 cobramush Exp $
+ */
+
+/*
+ * Package name      : pennmush
+ * Source directory  : .
+ * Configuration time: Fri Nov 16 19:02:38 EST 2001
+ * Configured by     : korongil
+ * Target system     : Win32
+ */
+
+#ifndef _config_h_
+#define _config_h_
+
+/* getdtablesize:
+ *     This catches use of the getdtablesize() subroutine, and remaps it
+ *     to either ulimit(4,0) or NOFILE, if getdtablesize() isn't available.
+ */
+/*#define getdtablesize()      / **/
+
+/* HAS_BCOPY:
+ *     This symbol is defined if the bcopy() routine is available to
+ *     copy blocks of memory.
+ */
+/* #define HAS_BCOPY   /**/
+
+/* HAS_BZERO:
+ *     This symbol is defined if the bzero() routine is available to
+ *     set a memory block to 0.
+ */
+/* #define HAS_BZERO   /**/
+
+/* HASCONST:
+ *     This symbol, if defined, indicates that this C compiler knows about
+ *     the const type. There is no need to actually test for that symbol
+ *     within your programs. The mere use of the "const" keyword will
+ *     trigger the necessary tests.
+ */
+#define HASCONST       /**/
+#ifndef HASCONST
+#define const
+#endif
+
+/* HAS_GETPRIORITY:
+ *     This symbol, if defined, indicates that the getpriority routine is
+ *     available to get a process's priority.
+ */
+/* #define HAS_GETPRIORITY             /**/
+
+/* INTERNET:
+ *     This symbol, if defined, indicates that there is a mailer available
+ *     which supports internet-style addresses (user@site.domain).
+ */
+/* #define     INTERNET        /**/
+
+/* HAS_MEMSET:
+ *     This symbol, if defined, indicates that the memset routine is available
+ *     to set blocks of memory.
+ */
+#define HAS_MEMSET     /**/
+
+/* HAS_RENAME:
+ *     This symbol, if defined, indicates that the rename routine is available
+ *     to rename files.  Otherwise you should do the unlink(), link(), unlink()
+ *     trick.
+ */
+#define HAS_RENAME     /**/
+
+/* HAS_GETRUSAGE:
+ *     This symbol, if defined, indicates that the getrusage() routine is
+ *     available to get process statistics with a sub-second accuracy.
+ *     Inclusion of <sys/resource.h> and <sys/time.h> may be necessary.
+ */
+/* #define HAS_GETRUSAGE               /**/
+
+/* HAS_SELECT:
+ *     This symbol, if defined, indicates that the select routine is
+ *     available to select active file descriptors. If the timeout field
+ *     is used, <sys/time.h> may need to be included.
+ */
+#define HAS_SELECT     /**/
+
+/* HAS_SETLOCALE:
+ *     This symbol, if defined, indicates that the setlocale routine is
+ *     available to handle locale-specific ctype implementations.
+ */
+#define HAS_SETLOCALE  /**/
+
+/* HAS_SETPGID:
+ *     This symbol, if defined, indicates that the setpgid(pid, gpid)
+ *     routine is available to set process group ID.
+ */
+/* #define HAS_SETPGID /**/
+
+/* HAS_SETPGRP:
+ *     This symbol, if defined, indicates that the setpgrp routine is
+ *     available to set the current process group.
+ */
+/* USE_BSD_SETPGRP:
+ *     This symbol, if defined, indicates that setpgrp needs two
+ *     arguments whereas USG one needs none.  See also HAS_SETPGID
+ *     for a POSIX interface.
+ */
+/*#define HAS_SETPGRP          /**/
+/*#define USE_BSD_SETPGRP      / **/
+
+/* HAS_SETPRIORITY:
+ *     This symbol, if defined, indicates that the setpriority routine is
+ *     available to set a process's priority.
+ */
+/*#define HAS_SETPRIORITY              /**/
+
+/* HAS_SIGACTION:
+ *     This symbol, if defined, indicates that Vr4's sigaction() routine
+ *     is available.
+ */
+/* #define HAS_SIGACTION       /**/
+
+/* HAS_SOCKET:
+ *     This symbol, if defined, indicates that the BSD socket interface is
+ *     supported.
+ */
+/* HAS_SOCKETPAIR:
+ *     This symbol, if defined, indicates that the BSD socketpair() call is
+ *     supported.
+ */
+/*#define HAS_SOCKET           /**/
+/*#define HAS_SOCKETPAIR       /**/
+
+/* HAS_STRCASECMP:
+ *     This symbol, if defined, indicates that the strcasecmp() routine is
+ *     available for case-insensitive string compares.
+ */
+/* #define HAS_STRCASECMP      /**/
+
+/* HAS_STRDUP:
+ *     This symbol, if defined, indicates that the strdup routine is
+ *     available to duplicate strings in memory. Otherwise, roll up
+ *     your own...
+ */
+#define HAS_STRDUP             /**/
+
+/* HAS_SYSCONF:
+ *     This symbol, if defined, indicates that sysconf() is available
+ *     to determine system related limits and options.
+ */
+/* #define HAS_SYSCONF /**/
+
+/* VOIDSIG:
+ *     This symbol is defined if this system declares "void (*signal(...))()" in
+ *     signal.h.  The old way was to declare it as "int (*signal(...))()".  It
+ *     is up to the package author to declare things correctly based on the
+ *     symbol.
+ */
+/* Signal_t:
+ *     This symbol's value is either "void" or "int", corresponding to the
+ *     appropriate return type of a signal handler.  Thus, you can declare
+ *     a signal handler using "Signal_t (*handler)()", and define the
+ *     handler using "Signal_t handler(sig)".
+ */
+#define VOIDSIG        /**/
+#define Signal_t void  /* Signal handler's return type */
+
+/* HASVOLATILE:
+ *     This symbol, if defined, indicates that this C compiler knows about
+ *     the volatile declaration.
+ */
+#define        HASVOLATILE     /**/
+#ifndef HASVOLATILE
+#define volatile
+#endif
+
+/* HAS_WAITPID:
+ *     This symbol, if defined, indicates that the waitpid routine is
+ *     available to wait for child process.
+ */
+/* #define HAS_WAITPID /**/
+
+/* I_ARPA_INET:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <arpa/inet.h> to get inet_addr and friends declarations.
+ */
+/*#define      I_ARPA_INET             /**/
+
+/* I_FCNTL:
+ *     This manifest constant tells the C program to include <fcntl.h>.
+ */
+#define I_FCNTL        /**/
+
+/* I_LIMITS:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <limits.h> to get definition of symbols like WORD_BIT or
+ *     LONG_MAX, i.e. machine dependant limitations.
+ */
+#define I_LIMITS               /**/
+
+/* I_LOCALE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <locale.h>.
+ */
+#define        I_LOCALE                /**/
+
+/* I_MALLOC:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <malloc.h>.
+ */
+#define I_MALLOC               /**/
+
+/* I_NETINET_IN:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <netinet/in.h>. Otherwise, you may try <sys/in.h>.
+ */
+/* I_SYS_IN:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/in.h> instead of <netinet/in.h>.
+ */
+/*#define I_NETINET_IN /**/
+/*#define I_SYS_IN             / **/
+
+/* I_STDDEF:
+ *     This symbol, if defined, indicates that <stddef.h> exists and should
+ *     be included.
+ */
+#define I_STDDEF       /**/
+
+/* I_STDLIB:
+ *     This symbol, if defined, indicates that <stdlib.h> exists and should
+ *     be included.
+ */
+#define I_STDLIB               /**/
+
+/* I_STRING:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <string.h> (USG systems) instead of <strings.h> (BSD systems).
+ */
+#define I_STRING               /**/
+
+/* I_SYS_FILE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/file.h> to get definition of R_OK and friends.
+ */
+/*#define I_SYS_FILE           /**/
+
+/* I_SYS_MMAN:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/mman.h>.
+ */
+/*#define      I_SYS_MMAN              /**/
+
+/* I_SYS_PARAM:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/param.h>.
+ */
+/*#define I_SYS_PARAM          /**/
+
+/* I_SYS_RESOURCE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/resource.h>.
+ */
+/* #define I_SYS_RESOURCE              /**/
+
+/* I_SYS_SELECT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/select.h> in order to get definition of struct timeval.
+ */
+/* #define I_SYS_SELECT        /**/
+
+/* I_SYS_SOCKET:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/socket.h> before performing socket calls.
+ */
+/*#define I_SYS_SOCKET         /**/
+
+/* I_SYS_STAT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/stat.h>.
+ */
+#define        I_SYS_STAT              /**/
+
+/* I_SYS_TYPES:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/types.h>.
+ */
+#define        I_SYS_TYPES             /**/
+
+/* I_SYS_WAIT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/wait.h>.
+ */
+/* #define I_SYS_WAIT  /**/
+
+/* I_TIME:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <time.h>.
+ */
+/* I_SYS_TIME:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/time.h>.
+ */
+#define I_TIME         / **/
+/* #define I_SYS_TIME          /**/
+
+/* I_UNISTD:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <unistd.h>.
+ */
+/* #define I_UNISTD            /**/
+
+/* I_VALUES:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <values.h> to get definition of symbols like MINFLOAT or
+ *     MAXLONG, i.e. machine dependant limitations.  Probably, you
+ *     should use <limits.h> instead, if it is available.
+ */
+/*#define I_VALUES             /**/
+
+/* Free_t:
+ *     This variable contains the return type of free().  It is usually
+ * void, but occasionally int.
+ */
+/* Malloc_t:
+ *     This symbol is the type of pointer returned by malloc and realloc.
+ */
+#define Malloc_t void *                        /**/
+#define Free_t void                    /**/
+
+/* Pid_t:
+ *     This symbol holds the type used to declare process ids in the kernel.
+ *     It can be int, uint, pid_t, etc... It may be necessary to include
+ *     <sys/types.h> to get any typedef'ed information.
+ */
+#define Pid_t unsigned long            /* PID type */
+
+/* CAN_PROTOTYPE:
+ *     If defined, this macro indicates that the C compiler can handle
+ *     function prototypes.
+ */
+/* _:
+ *     This macro is used to declare function parameters for folks who want
+ *     to make declarations with prototypes using a different style than
+ *     the above macros.  Use double parentheses.  For example:
+ *
+ *             int main _((int argc, char *argv[]));
+ */
+#define        CAN_PROTOTYPE   /**/
+#ifdef CAN_PROTOTYPE
+#define        _(args) args
+#else
+#define        _(args) ()
+#endif
+
+/* Size_t:
+ *     This symbol holds the type used to declare length parameters
+ *     for string functions.  It is usually size_t, but may be
+ *     unsigned long, int, etc.  It may be necessary to include
+ *     <sys/types.h> to get any typedef'ed information.
+ */
+#define Size_t size_t   /* length paramater for string functions */
+
+/* VOIDFLAGS:
+ *     This symbol indicates how much support of the void type is given by this
+ *     compiler.  What various bits mean:
+ *
+ *         1 = supports declaration of void
+ *         2 = supports arrays of pointers to functions returning void
+ *         4 = supports comparisons between pointers to void functions and
+ *                 addresses of void functions
+ *         8 = suports declaration of generic void pointers
+ *
+ *     The package designer should define VOIDUSED to indicate the requirements
+ *     of the package.  This can be done either by #defining VOIDUSED before
+ *     including config.h, or by defining defvoidused in Myinit.U.  If the
+ *     latter approach is taken, only those flags will be tested.  If the
+ *     level of void support necessary is not present, defines void to int.
+ */
+#ifndef VOIDUSED
+#define VOIDUSED 15
+#endif
+#define VOIDFLAGS 15
+#if (VOIDFLAGS & VOIDUSED) != VOIDUSED
+#define void int               /* is void to be avoided? */
+#define M_VOID                 /* Xenix strikes again */
+#endif
+
+/* WIN32_CDECL:
+ *     Defined as __cdecl if the compiler can handle that keyword to specify
+ *     C-style argument passing conventions. This allows MS VC++
+ *     on Win32 to use the __fastcall convention for everything else
+ *     and get a performance boost. Any compiler with a brain (read:
+ *     not MS VC) handles this optimization automatically without such a
+ *     kludge. On these systems, this is defined as nothing.
+ */
+#define WIN32_CDECL __cdecl
+
+/* CAN_TAKE_ARGS_IN_FP:
+ *     Defined if the compiler prefers that function pointer parameters
+ *     in prototypes include the function's arguments, rather than
+ *     nothing (that is, int (*fun)(int) rather than int(*fun)().
+ */
+#define CAN_TAKE_ARGS_IN_FP /**/
+
+/* HAS_ASSERT:
+ *     If defined, this system has the assert() macro.
+ */
+#define HAS_ASSERT     /**/
+
+/* HASATTRIBUTE:
+ *     This symbol indicates the C compiler can check for function attributes,
+ *     such as printf formats. This is normally only supported by GNU cc.
+ */
+/* #define HASATTRIBUTE        /**/
+#ifndef HASATTRIBUTE
+#define __attribute__(_arg_)
+#endif
+
+/* HAS_BINDTEXTDOMAIN:
+ *     Defined if bindtextdomain is available().
+ */
+/*#define HAS_BINDTEXTDOMAIN /**/
+
+/* HAS_CRYPT:
+ *     This symbol, if defined, indicates that the crypt routine is available
+ *     to encrypt passwords and the like.
+ */
+/* I_CRYPT:
+ *     This symbol, if defined, indicates that <crypt.h> can be included.
+ */
+/* #define HAS_CRYPT           /**/
+
+/* #define I_CRYPT             /**/
+
+/* FORCE_IPV4:
+ *     If defined, this system will not use IPv6. Necessary for Openbsd.
+ */
+/*#define FORCE_IPV4   / **/
+
+/* HAS_FPSETROUND:
+ *     This symbol, if defined, indicates that the crypt routine is available
+ *     to encrypt passwords and the like.
+ */
+/* I_FLOATINGPOINT:
+ *     This symbol, if defined, indicates that <crypt.h> can be included.
+ */
+/*#define HAS_FPSETROUND               / **/
+
+/*#define HAS_FPSETMASK                / **/
+
+/*#define I_FLOATINGPOINT              / **/
+
+/* HAS_GAI_STRERROR:
+ *     This symbol, if defined, indicates that getaddrinfo()'s error cores
+ * can be converted to strings for printing.
+ */
+/* #define HAS_GAI_STRERROR            /**/
+
+/* HAS_GETADDRINFO:
+ *     This symbol, if defined, indicates that the getaddrinfo() routine is
+ *     available to lookup internet addresses in some data base or other.
+ */
+/* #define HAS_GETADDRINFO             /**/
+
+/* HAS_GETDATE:
+ *     This symbol, if defined, indicates that the getdate() routine is
+ *     available to convert date strings into struct tm's.
+ */
+/*#define HAS_GETDATE          /**/
+
+/* HAS_GETHOSTBYNAME2:
+ *     This symbol, if defined, indicates that the gethostbyname2()
+ * function is available to resolve hostnames.
+ */
+/*#define HAS_GETHOSTBYNAME2           /**/
+
+/* HAS_GETNAMEINFO:
+ *     This symbol, if defined, indicates that the getnameinfo() routine is
+ *     available to lookup host names in some data base or other.
+ */
+/* #define HAS_GETNAMEINFO             /**/
+
+/* HAS_GETPAGESIZE:
+ *     This symbol, if defined, indicates that the getpagesize system call
+ *     is available to get system page size, which is the granularity of
+ *     many memory management calls.
+ */
+/* PAGESIZE_VALUE:
+ *     This symbol holds the size in bytes of a system page (obtained via
+ *     the getpagesize() system call at configuration time or asked to the
+ *     user if the system call is not available).
+ */
+/*#define HAS_GETPAGESIZE     /**/
+#define PAGESIZE_VALUE 4096 /* System page size, in bytes */
+
+/* HAS_GETTEXT:
+ *     Defined if gettext is available().
+ */
+/*#define HAS_GETTEXT /**/
+
+/* HAS_HUGE_VAL:
+ *     If defined, this system has the HUGE_VAL constant. We like this,
+ *     and don't bother defining the other floats below if we find it.
+ */
+/* HAS_HUGE:
+ *     If defined, this system has the HUGE constant. We like this, and
+ *     don't bother defining the other floats below if we find it.
+ */
+/* HAS_INT_MAX:
+ *     If defined, this system has the INT_MAX constant.
+ */
+/* HAS_MAXINT:
+ *     If defined, this system has the MAXINT constant.
+ */
+/* HAS_MAXDOUBLE:
+ *     If defined, this system has the MAXDOUBLE constant.
+ */
+#define HAS_HUGE_VAL   /**/
+
+/*#define HAS_HUGE     / **/
+
+#define HAS_INT_MAX    /**/
+
+/*#define HAS_MAXINT   / **/
+
+/*#define HAS_MAXDOUBLE        / **/
+
+/* HAS_IEEE_MATH:
+ *     Defined if the machine supports IEEE math - that is, can safely
+ *     return NaN or Inf rather than crash on bad math.
+ */
+/* #define HAS_IEEE_MATH /**/
+
+/* HAS_INET_PTON:
+ *     This symbol, if defined, indicates that the inet_pton() and
+ *     inet_ntop() routines are available to convert IP addresses..
+ */
+/*#define HAS_INET_PTON                /**/
+
+/* HAS_IPV6:
+ *     If defined, this system has the sockaddr_in6 struct and AF_INET6.
+ * We can't rely on just AF_INET6 being defined.
+ */
+/*#define HAS_IPV6     /**/
+
+/* SIGNALS_KEPT:
+ *     This symbol is defined if signal handlers needn't be reinstated after
+ *     receipt of a signal.
+ */
+#define SIGNALS_KEPT   /**/
+
+/* HAS_MEMCPY:
+ *     This symbol, if defined, indicates that the memcpy routine is available
+ *     to copy blocks of memory. If not, it will be mapped to bcopy
+ *     in confmagic.h
+ */
+/* HAS_MEMMOVE:
+ *     This symbol, if defined, indicates that the memmove routine is available
+ *     to copy blocks of memory. If not, it will be mapped to bcopy
+ */
+#define HAS_MEMCPY     /**/
+
+#define HAS_MEMMOVE    /**/
+
+/* CAN_NEWSTYLE:
+ *     Defined if new-style function definitions are allowable.
+ *     If they are, we can avoid some warnings that you get if
+ *     you declare char arguments in a prototype and use old-style
+ *     function definitions, which implicitly promote them to ints.
+ */
+#define CAN_NEWSTYLE /**/
+
+/* HAS_RANDOM:
+ *     Have we got random(), our first choice for number generation?
+ */
+/* HAS_LRAND48:
+ *     Have we got lrand48(), our second choice?
+ */
+/* HAS_RAND:
+ *     Have we got rand(), our last choice?
+ */
+/* #define HAS_RANDOM  /**/
+/* #define HAS_LRAND48 /**/
+#define HAS_RAND       /**/
+
+/* HAS_GETRLIMIT:
+ *     This symbol, if defined, indicates that the getrlimit() routine is
+ *     available to get resource limits. Probably means setrlimit too.
+ *     Inclusion of <sys/resource.h> and <sys/time.h> may be necessary.
+ */
+/* #define HAS_GETRLIMIT               /**/
+
+/* SENDMAIL:
+ *     This symbol contains the full pathname to sendmail.
+ */
+/* HAS_SENDMAIL:
+ *     If defined, we have sendmail.
+ */
+/* #define HAS_SENDMAIL        /**/
+#define SENDMAIL       "/usr/sbin/sendmail"
+
+/* HAS_SIGCHLD:
+ *     If defined, this system has the SIGCHLD constant.
+ */
+/* HAS_SIGCLD:
+ *     If defined, this system has the SIGCLD constant (SysVish SIGCHLD).
+ */
+/*#define HAS_SIGCHLD  /**/
+
+/*#define HAS_SIGCLD   /**/
+
+/* CAN_PROTOTYPE_SIGNAL:
+ *     This symbol is defined if we can safely prototype our rewritten
+ *     signal() function as:
+ *     Signal_t(*Sigfunc) _((int));
+ *     extern Sigfunc signal _((int signo, Sigfunc func));
+ */
+/*#define CAN_PROTOTYPE_SIGNAL /**/
+
+/* HAS_SIGPROCMASK:
+ *     This symbol, if defined, indicates that POSIX's sigprocmask() routine
+ *     is available.
+ */
+/*#define HAS_SIGPROCMASK      /**/
+
+/* HAS_SNPRINTF:
+ *     This symbol, if defined, indicates that the snprintf routine is
+ *     available. If not, we use sprintf, which is less safe.
+ */
+#define HAS_SNPRINTF   /**/
+
+/* HAS_SOCKLEN_T:
+ *     If defined, this system has the socklen_t type.
+ */
+/*#define HAS_SOCKLEN_T        /**/
+
+/* HAS_STRCHR:
+ *     This symbol is defined to indicate that the strchr()/strrchr()
+ *     functions are available for string searching. If not, try the
+ *     index()/rindex() pair.
+ */
+/* HAS_INDEX:
+ *     This symbol is defined to indicate that the index()/rindex()
+ *     functions are available for string searching.
+ */
+#define HAS_STRCHR     /**/
+/* #define HAS_INDEX   /**/
+
+/* HAS_STRCOLL:
+ *     This symbol, if defined, indicates that the strcoll routine is
+ *     available to compare strings using collating information.
+ */
+#define HAS_STRCOLL    /**/
+
+/* HAS_STRXFRM:
+ *     This symbol, if defined, indicates that the strxfrm routine is
+ *     available to transform strings using collating information.
+ */
+#define HAS_STRXFRM    /**/
+
+/* HAS_TCL:
+ *  This symbol, if defined, means we have the tcl library
+ */
+/*#define HAS_TCL              / **/
+
+/* I_TCL:
+ *  This symbol, if defined, means we have the <tcl.h> include file
+ */
+/*#define I_TCL                / **/
+
+/* HAS_TEXTDOMAIN:
+ *     Defined if textdomain is available().
+ */
+/*#define HAS_TEXTDOMAIN /**/
+
+/* HAS_TIMELOCAL:
+ *     This symbol, if defined, indicates that the timelocal() routine is
+ *     available.
+ */
+/* #define HAS_TIMELOCAL               /**/
+
+/* HAS_SAFE_TOUPPER:
+ *     Defined if toupper() can operate safely on any ascii character.
+ *     Some systems only allow toupper() on lower-case ascii chars.
+ */
+#define HAS_SAFE_TOUPPER /**/
+
+/* UPTIME_PATH:
+ *     This symbol gives the full path to the uptime(1) program if
+ *     it exists on the system. If not, this symbol is undefined.
+ */
+/* HAS_UPTIME:
+ *     This symbol is defined if uptime(1) is available.
+ */
+/* #define HAS_UPTIME  /**/
+#define UPTIME_PATH "/usr/bin/uptime"
+
+/* UNION_WAIT:
+ *     This symbol if defined indicates to the C program that the argument
+ *     for the wait() system call should be declared as 'union wait status'
+ *     instead of 'int status'. You probably need to include <sys/wait.h>
+ *     in the former case (see I_SYSWAIT).
+ */
+#define UNION_WAIT             / **/
+
+/* HAS_VSNPRINTF:
+ *     This symbol, if defined, indicates that the vsnprintf routine is
+ *     available to printf with a pointer to an argument list.  If not, you
+ *     may need to write your own, probably in terms of _doprnt().
+ */
+/* #define HAS_VSNPRINTF       /**/
+
+/* I_ARPA_NAMESER:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <arpa/nameser.h> to get nameser_addr and friends declarations.
+ */
+/*#define      I_ARPA_NAMESER          /**/
+
+/* I_ERRNO:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <errno.h>.
+ */
+/* I_SYS_ERRNO:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <sys/errno.h>.
+ */
+#define I_ERRNO                /**/
+
+/*#define I_SYS_ERRNO          /**/
+
+/* I_LIBINTL:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <libintl.h>.
+ */
+/*#define I_LIBINTL            /**/
+
+/* I_MEMORY:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <memory.h>.
+ */
+#define I_MEMORY               / **/
+
+/* I_NETDB:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <errno.h>.
+ */
+/*#define I_NETDB              /**/
+
+/* I_SETJMP:
+ *     This symbol, if defined, indicates to the C program that it can
+ *     include <setjmp.h> and have things work right.
+ */
+#define I_SETJMP               /**/
+
+/* USE_TIOCNOTTY:
+ *     This symbol, if defined indicate to the C program that the ioctl()
+ *     call with TIOCNOTTY should be used to void tty association.
+ *     Otherwise (on USG probably), it is enough to close the standard file
+ *     decriptors and do a setpgrp().
+ */
+/*#define USE_TIOCNOTTY        /**/
+
+/* I_SYS_PAGE:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/page.h>.
+ */
+/*#define I_SYS_PAGE           / **/
+
+/* I_SYS_VLIMIT:
+ *     This symbol, if defined, indicates to the C program that it should
+ *     include <sys/vlimit.h>.
+ */
+/* #define I_SYS_VLIMIT                /**/
+
+/* I_STDARG:
+ *     This symbol, if defined, indicates that <stdarg.h> exists and should
+ *     be included.
+ */
+#define I_STDARG               /**/
+
+/* HAS_MYSQL:
+ *     Defined if mysql client libraries are available.
+ */
+/* #define HAS_MYSQL   /**/
+
+/* HAS_OPENSSL:
+ *     Defined if openssl 0.9.6+ is available.
+ */
+/* #define HAS_OPENSSL /**/
+
+
+/* CAN_KEEPALIVE:
+ *      This symbol if defined indicates to the C program that the SO_KEEPALIVE
+ *      option of setsockopt() will work as advertised in the manual.
+ */
+/* CAN_KEEPIDLE:
+ *      This symbol if defined indicates to the C program that the TCP_KEEPIDLE
+ *      option of setsockopt() will work as advertised in the manual.
+ */
+/* #define CAN_KEEPALIVE           /**/
+
+/* #define CAN_KEEPIDLE            /**/
+
+#endif
diff --git a/win32/confmagic.h b/win32/confmagic.h
new file mode 100644 (file)
index 0000000..28859e9
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * This file was produced by running metaconfig and is intended to be included
+ * after config.h and after all the other needed includes have been dealt with.
+ *
+ * This file may be empty, and should not be edited. Rerun metaconfig instead.
+ * If you wish to get rid of this magic, remove this file and rerun metaconfig
+ * without the -M option.
+ *
+ */
+
+#ifndef _confmagic_h_
+#define _confmagic_h_
+
+#ifndef HAS_BCOPY
+#ifndef bcopy
+#define bcopy(s,d,l) memcpy((d),(s),(l))
+#endif
+#endif
+
+#ifndef HAS_BZERO
+#ifndef bzero
+#define bzero(s,l) memset((s),0,(l))
+#endif
+#endif
+
+/* If your system doesn't have the crypt(3) DES encryption code,
+ * (which isn't exportable from the U.S.), then don't encrypt
+ */
+#ifndef HAS_CRYPT
+#define crypt(s,t) (s)
+#endif
+
+#ifdef HAS_HUGE_VAL
+#define HUGE_DOUBLE    HUGE_VAL
+#else
+#ifdef HAS_HUGE
+#define HUGE_DOUBLE    HUGE
+#else
+#ifdef HAS_MAXDOUBLE
+#define HUGE_DOUBLE    MAXDOUBLE
+#else
+#define HUGE_DOUBLE    2000000000
+#endif
+#endif
+#endif
+#ifdef HAS_INT_MAX
+#define HUGE_INT       INT_MAX
+#else
+#ifdef HAS_MAXINT
+#define HUGE_INT       MAXINT
+#else
+#define HUGE_INT       2000000000
+#endif
+#endif
+
+#ifndef HAS_MEMCPY
+#ifndef memcpy
+#define memcpy(d,s,l) bcopy((s),(d),(l))
+#endif
+#endif
+
+#ifndef HAS_MEMMOVE
+#ifndef memmove
+#define memmove(d,s,l) bcopy((s),(d),(l))
+#endif
+#endif
+
+#ifndef HAS_RANDOM
+#ifndef random
+#ifdef HAS_LRAND48
+#define random lrand48
+#define srandom srand48
+#else
+#ifdef HAS_RAND
+#define random rand
+#define srandom srand
+#endif
+#endif
+#endif
+#endif
+
+#ifndef HAS_SIGCHLD
+#define SIGCHLD        SIGCLD
+#endif
+
+#ifndef HAS_SIGCLD
+#define SIGCLD SIGCHLD
+#endif
+
+#ifndef HAS_INDEX
+#ifndef index
+#define index strchr
+#endif
+#endif
+
+#ifndef HAS_STRCHR
+#ifndef strchr
+#define strchr index
+#endif
+#endif
+
+#ifndef HAS_INDEX
+#ifndef rindex
+#define rindex strrchr
+#endif
+#endif
+
+#ifndef HAS_STRCHR
+#ifndef strrchr
+#define strrchr rindex
+#endif
+#endif
+
+#ifndef HAS_STRCOLL
+#undef strcoll
+#define strcoll strcmp
+#endif
+
+#if !defined(WIN32) && !defined(HAS_STRXFRM)
+#define strncoll strncmp
+#define strncasecoll strncasecmp
+#define strcasecoll strcasecmp
+#endif
+\r
+#if defined(_MSC_VER)\r
+#define snprintf _snprintf\r
+#endif
+
+#endif
diff --git a/win32/funs.h b/win32/funs.h
new file mode 100644 (file)
index 0000000..1caf416
--- /dev/null
@@ -0,0 +1,369 @@
+/* AUTOGENERATED FILE. DO NOT EDIT */
+FUNCTION_PROTO(fun_abs);
+FUNCTION_PROTO(fun_accent);
+FUNCTION_PROTO(fun_accname);
+FUNCTION_PROTO(fun_acos);
+FUNCTION_PROTO(fun_add);
+FUNCTION_PROTO(fun_after);
+FUNCTION_PROTO(fun_align);
+FUNCTION_PROTO(fun_allof);
+FUNCTION_PROTO(fun_alphamax);
+FUNCTION_PROTO(fun_alphamin);
+FUNCTION_PROTO(fun_and);
+FUNCTION_PROTO(fun_andflags);
+FUNCTION_PROTO(fun_andlflags);
+FUNCTION_PROTO(fun_ansi);
+FUNCTION_PROTO(fun_aposs);
+FUNCTION_PROTO(fun_arabictoroman);
+FUNCTION_PROTO(fun_art);
+FUNCTION_PROTO(fun_asin);
+FUNCTION_PROTO(fun_atan);
+FUNCTION_PROTO(fun_atan2);
+FUNCTION_PROTO(fun_atat);
+FUNCTION_PROTO(fun_atrlock);
+FUNCTION_PROTO(fun_band);
+FUNCTION_PROTO(fun_baseconv);
+FUNCTION_PROTO(fun_beep);
+FUNCTION_PROTO(fun_before);
+FUNCTION_PROTO(fun_bnand);
+FUNCTION_PROTO(fun_bnot);
+FUNCTION_PROTO(fun_bor);
+FUNCTION_PROTO(fun_bound);
+FUNCTION_PROTO(fun_brackets);
+FUNCTION_PROTO(fun_break);
+FUNCTION_PROTO(fun_bxor);
+FUNCTION_PROTO(fun_cand);
+FUNCTION_PROTO(fun_capstr);
+FUNCTION_PROTO(fun_cat);
+FUNCTION_PROTO(fun_ceil);
+FUNCTION_PROTO(fun_cemit);
+FUNCTION_PROTO(fun_center);
+FUNCTION_PROTO(fun_cflags);
+FUNCTION_PROTO(fun_channels);
+FUNCTION_PROTO(fun_checkknow);
+FUNCTION_PROTO(fun_checkpass);
+FUNCTION_PROTO(fun_chr);
+FUNCTION_PROTO(fun_clock);
+FUNCTION_PROTO(fun_clone);
+FUNCTION_PROTO(fun_cmds);
+FUNCTION_PROTO(fun_cobj);
+FUNCTION_PROTO(fun_comp);
+FUNCTION_PROTO(fun_con);
+FUNCTION_PROTO(fun_config);
+FUNCTION_PROTO(fun_conn);
+FUNCTION_PROTO(fun_controls);
+FUNCTION_PROTO(fun_convsecs);
+FUNCTION_PROTO(fun_convtime);
+FUNCTION_PROTO(fun_cor);
+FUNCTION_PROTO(fun_cos);
+FUNCTION_PROTO(fun_cowner);
+FUNCTION_PROTO(fun_create);
+FUNCTION_PROTO(fun_crplog);
+FUNCTION_PROTO(fun_ctime);
+FUNCTION_PROTO(fun_ctitle);
+FUNCTION_PROTO(fun_ctu);
+FUNCTION_PROTO(fun_cwho);
+FUNCTION_PROTO(fun_dbwalker);
+FUNCTION_PROTO(fun_dec);
+FUNCTION_PROTO(fun_decrypt);
+FUNCTION_PROTO(fun_default);
+FUNCTION_PROTO(fun_delete);
+FUNCTION_PROTO(fun_die);
+FUNCTION_PROTO(fun_dig);
+FUNCTION_PROTO(fun_digest);
+FUNCTION_PROTO(fun_dist2d);
+FUNCTION_PROTO(fun_dist3d);
+FUNCTION_PROTO(fun_div);
+FUNCTION_PROTO(fun_division);
+FUNCTION_PROTO(fun_divscope);
+FUNCTION_PROTO(fun_doing);
+FUNCTION_PROTO(fun_e);
+FUNCTION_PROTO(fun_edefault);
+FUNCTION_PROTO(fun_edit);
+FUNCTION_PROTO(fun_element);
+FUNCTION_PROTO(fun_elements);
+FUNCTION_PROTO(fun_elock);
+FUNCTION_PROTO(fun_emit);
+FUNCTION_PROTO(fun_empire);
+FUNCTION_PROTO(fun_empower);
+FUNCTION_PROTO(fun_encrypt);
+FUNCTION_PROTO(fun_endtag);
+FUNCTION_PROTO(fun_entrances);
+FUNCTION_PROTO(fun_eq);
+FUNCTION_PROTO(fun_escape);
+FUNCTION_PROTO(fun_etimefmt);
+FUNCTION_PROTO(fun_eval);
+FUNCTION_PROTO(fun_exit);
+FUNCTION_PROTO(fun_exp);
+FUNCTION_PROTO(fun_extract);
+FUNCTION_PROTO(fun_fdiv);
+FUNCTION_PROTO(fun_filter);
+FUNCTION_PROTO(fun_findable);
+FUNCTION_PROTO(fun_first);
+FUNCTION_PROTO(fun_firstof);
+FUNCTION_PROTO(fun_flags);
+FUNCTION_PROTO(fun_flip);
+FUNCTION_PROTO(fun_floor);
+FUNCTION_PROTO(fun_floordiv);
+FUNCTION_PROTO(fun_fmod);
+FUNCTION_PROTO(fun_fold);
+FUNCTION_PROTO(fun_folderstats);
+FUNCTION_PROTO(fun_followers);
+FUNCTION_PROTO(fun_following);
+FUNCTION_PROTO(fun_foreach);
+FUNCTION_PROTO(fun_fraction);
+FUNCTION_PROTO(fun_fullname);
+FUNCTION_PROTO(fun_functions);
+FUNCTION_PROTO(fun_get);
+FUNCTION_PROTO(fun_get_eval);
+FUNCTION_PROTO(fun_grab);
+FUNCTION_PROTO(fun_graball);
+FUNCTION_PROTO(fun_grep);
+FUNCTION_PROTO(fun_gt);
+FUNCTION_PROTO(fun_gte);
+FUNCTION_PROTO(fun_hasattr);
+FUNCTION_PROTO(fun_hasdivpower);
+FUNCTION_PROTO(fun_hasflag);
+FUNCTION_PROTO(fun_haspowergroup);
+FUNCTION_PROTO(fun_hastype);
+FUNCTION_PROTO(fun_height);
+FUNCTION_PROTO(fun_hidden);
+FUNCTION_PROTO(fun_home);
+FUNCTION_PROTO(fun_hostname);
+FUNCTION_PROTO(fun_html);
+FUNCTION_PROTO(fun_idle_average);
+FUNCTION_PROTO(fun_idlesecs);
+FUNCTION_PROTO(fun_idle_times);
+FUNCTION_PROTO(fun_idle_total);
+FUNCTION_PROTO(fun_if);
+FUNCTION_PROTO(fun_ilev);
+FUNCTION_PROTO(fun_iname);
+FUNCTION_PROTO(fun_inc);
+FUNCTION_PROTO(fun_index);
+FUNCTION_PROTO(fun_indiv);
+FUNCTION_PROTO(fun_indivall);
+FUNCTION_PROTO(fun_insert);
+FUNCTION_PROTO(fun_inum);
+FUNCTION_PROTO(fun_ipaddr);
+FUNCTION_PROTO(fun_isdaylight);
+FUNCTION_PROTO(fun_isdbref);
+FUNCTION_PROTO(fun_isint);
+FUNCTION_PROTO(fun_isnum);
+FUNCTION_PROTO(fun_isword);
+FUNCTION_PROTO(fun_itemize);
+FUNCTION_PROTO(fun_items);
+FUNCTION_PROTO(fun_iter);
+FUNCTION_PROTO(fun_itext);
+FUNCTION_PROTO(fun_last);
+FUNCTION_PROTO(fun_lattr);
+FUNCTION_PROTO(fun_lcstr);
+FUNCTION_PROTO(fun_ldelete);
+FUNCTION_PROTO(fun_left);
+FUNCTION_PROTO(fun_lemit);
+FUNCTION_PROTO(fun_level);
+FUNCTION_PROTO(fun_lflags);
+FUNCTION_PROTO(fun_link);
+FUNCTION_PROTO(fun_list);
+FUNCTION_PROTO(fun_lit);
+FUNCTION_PROTO(fun_ljust);
+FUNCTION_PROTO(fun_lmath);
+FUNCTION_PROTO(fun_ln);
+FUNCTION_PROTO(fun_lnum);
+FUNCTION_PROTO(fun_loc);
+FUNCTION_PROTO(fun_localize);
+FUNCTION_PROTO(fun_locate);
+FUNCTION_PROTO(fun_lock);
+FUNCTION_PROTO(fun_loctree);
+FUNCTION_PROTO(fun_log);
+FUNCTION_PROTO(fun_lparent);
+FUNCTION_PROTO(fun_lports);
+FUNCTION_PROTO(fun_lpos);
+FUNCTION_PROTO(fun_lsearch);
+FUNCTION_PROTO(fun_lstats);
+FUNCTION_PROTO(fun_lt);
+FUNCTION_PROTO(fun_lte);
+FUNCTION_PROTO(fun_lwho);
+FUNCTION_PROTO(fun_mail);
+FUNCTION_PROTO(fun_mailfrom);
+FUNCTION_PROTO(fun_mailstats);
+FUNCTION_PROTO(fun_mailstatus);
+FUNCTION_PROTO(fun_mailsubject);
+FUNCTION_PROTO(fun_mailtime);
+FUNCTION_PROTO(fun_map);
+FUNCTION_PROTO(fun_match);
+FUNCTION_PROTO(fun_matchall);
+FUNCTION_PROTO(fun_max);
+FUNCTION_PROTO(fun_mean);
+FUNCTION_PROTO(fun_median);
+FUNCTION_PROTO(fun_member);
+FUNCTION_PROTO(fun_merge);
+FUNCTION_PROTO(fun_mid);
+FUNCTION_PROTO(fun_min);
+FUNCTION_PROTO(fun_mix);
+FUNCTION_PROTO(fun_modulo);
+FUNCTION_PROTO(fun_money);
+FUNCTION_PROTO(fun_mtime);
+FUNCTION_PROTO(fun_mudname);
+FUNCTION_PROTO(fun_mul);
+FUNCTION_PROTO(fun_munge);
+FUNCTION_PROTO(fun_name);
+FUNCTION_PROTO(fun_nand);
+FUNCTION_PROTO(fun_nattr);
+FUNCTION_PROTO(fun_nearby);
+FUNCTION_PROTO(fun_neq);
+FUNCTION_PROTO(fun_next);
+FUNCTION_PROTO(fun_nor);
+FUNCTION_PROTO(fun_not);
+FUNCTION_PROTO(fun_null);
+FUNCTION_PROTO(fun_num);
+FUNCTION_PROTO(fun_obj);
+FUNCTION_PROTO(fun_objeval);
+FUNCTION_PROTO(fun_objid);
+FUNCTION_PROTO(fun_objmem);
+FUNCTION_PROTO(fun_oemit);
+FUNCTION_PROTO(fun_ooref);
+FUNCTION_PROTO(fun_open);
+FUNCTION_PROTO(fun_or);
+FUNCTION_PROTO(fun_ord);
+FUNCTION_PROTO(fun_orflags);
+FUNCTION_PROTO(fun_orlflags);
+FUNCTION_PROTO(fun_owner);
+FUNCTION_PROTO(fun_parent);
+FUNCTION_PROTO(fun_pcreate);
+FUNCTION_PROTO(fun_pemit);
+FUNCTION_PROTO(fun_pghaspower);
+FUNCTION_PROTO(fun_pgpowers);
+FUNCTION_PROTO(fun_pi);
+FUNCTION_PROTO(fun_playermem);
+FUNCTION_PROTO(fun_pmatch);
+FUNCTION_PROTO(fun_poll);
+FUNCTION_PROTO(fun_ports);
+FUNCTION_PROTO(fun_pos);
+FUNCTION_PROTO(fun_poss);
+FUNCTION_PROTO(fun_power);
+FUNCTION_PROTO(fun_powergroups);
+FUNCTION_PROTO(fun_powers);
+FUNCTION_PROTO(fun_powover);
+FUNCTION_PROTO(fun_primary_division);
+FUNCTION_PROTO(fun_prog);
+FUNCTION_PROTO(fun_prompt);
+FUNCTION_PROTO(fun_pueblo);
+FUNCTION_PROTO(fun_quitprog);
+FUNCTION_PROTO(fun_quota);
+FUNCTION_PROTO(fun_r);
+FUNCTION_PROTO(fun_rand);
+FUNCTION_PROTO(fun_randword);
+FUNCTION_PROTO(fun_recv);
+FUNCTION_PROTO(fun_regmatch);
+FUNCTION_PROTO(fun_regrab);
+FUNCTION_PROTO(fun_regrep);
+FUNCTION_PROTO(fun_regreplace);
+FUNCTION_PROTO(fun_remainder);
+FUNCTION_PROTO(fun_remit);
+FUNCTION_PROTO(fun_remove);
+FUNCTION_PROTO(fun_repeat);
+FUNCTION_PROTO(fun_replace);
+FUNCTION_PROTO(fun_rest);
+FUNCTION_PROTO(fun_restarts);
+FUNCTION_PROTO(fun_restarttime);
+FUNCTION_PROTO(fun_reswitch);
+FUNCTION_PROTO(fun_revwords);
+FUNCTION_PROTO(fun_right);
+FUNCTION_PROTO(fun_rjust);
+FUNCTION_PROTO(fun_rloc);
+FUNCTION_PROTO(fun_rnum);
+FUNCTION_PROTO(fun_romantoarabic);
+FUNCTION_PROTO(fun_room);
+FUNCTION_PROTO(fun_root);
+FUNCTION_PROTO(fun_round);
+FUNCTION_PROTO(fun_s);
+FUNCTION_PROTO(fun_scan);
+FUNCTION_PROTO(fun_scramble);
+FUNCTION_PROTO(fun_secs);
+FUNCTION_PROTO(fun_secure);
+FUNCTION_PROTO(fun_sent);
+FUNCTION_PROTO(fun_set);
+FUNCTION_PROTO(fun_setdiff);
+FUNCTION_PROTO(fun_setinter);
+FUNCTION_PROTO(fun_setq);
+FUNCTION_PROTO(fun_setunion);
+FUNCTION_PROTO(fun_sha0);
+FUNCTION_PROTO(fun_shl);
+FUNCTION_PROTO(fun_shr);
+FUNCTION_PROTO(fun_shuffle);
+FUNCTION_PROTO(fun_sign);
+FUNCTION_PROTO(fun_signal);
+FUNCTION_PROTO(fun_sin);
+FUNCTION_PROTO(fun_sort);
+FUNCTION_PROTO(fun_sortby);
+FUNCTION_PROTO(fun_soundex);
+FUNCTION_PROTO(fun_soundlike);
+FUNCTION_PROTO(fun_space);
+FUNCTION_PROTO(fun_spellnum);
+FUNCTION_PROTO(fun_splice);
+FUNCTION_PROTO(fun_sql);
+FUNCTION_PROTO(fun_sql_escape);
+FUNCTION_PROTO(fun_sq_respond);
+FUNCTION_PROTO(fun_sqrt);
+FUNCTION_PROTO(fun_squish);
+FUNCTION_PROTO(fun_ssl);
+FUNCTION_PROTO(fun_starttime);
+FUNCTION_PROTO(fun_stddev);
+FUNCTION_PROTO(fun_step);
+FUNCTION_PROTO(fun_strcat);
+FUNCTION_PROTO(fun_strinsert);
+FUNCTION_PROTO(fun_stripaccents);
+FUNCTION_PROTO(fun_stripansi);
+FUNCTION_PROTO(fun_strlen);
+FUNCTION_PROTO(fun_strmatch);
+FUNCTION_PROTO(fun_strreplace);
+FUNCTION_PROTO(fun_sub);
+FUNCTION_PROTO(fun_subj);
+FUNCTION_PROTO(fun_switch);
+FUNCTION_PROTO(fun_t);
+FUNCTION_PROTO(fun_table);
+FUNCTION_PROTO(fun_tag);
+FUNCTION_PROTO(fun_tagwrap);
+FUNCTION_PROTO(fun_tan);
+FUNCTION_PROTO(fun_tel);
+FUNCTION_PROTO(fun_terminfo);
+FUNCTION_PROTO(fun_textfile);
+FUNCTION_PROTO(fun_time);
+FUNCTION_PROTO(fun_timefmt);
+FUNCTION_PROTO(fun_timestring);
+FUNCTION_PROTO(fun_tr);
+FUNCTION_PROTO(fun_trigger);
+FUNCTION_PROTO(fun_trim);
+FUNCTION_PROTO(fun_trunc);
+FUNCTION_PROTO(fun_type);
+FUNCTION_PROTO(fun_ucstr);
+FUNCTION_PROTO(fun_ufun);
+FUNCTION_PROTO(fun_uldefault);
+FUNCTION_PROTO(fun_ulocal);
+FUNCTION_PROTO(fun_updiv);
+FUNCTION_PROTO(fun_v);
+FUNCTION_PROTO(fun_vadd);
+FUNCTION_PROTO(fun_valid);
+FUNCTION_PROTO(fun_vcross);
+FUNCTION_PROTO(fun_vdot);
+FUNCTION_PROTO(fun_version);
+FUNCTION_PROTO(fun_visible);
+FUNCTION_PROTO(fun_vmag);
+FUNCTION_PROTO(fun_vmax);
+FUNCTION_PROTO(fun_vmin);
+FUNCTION_PROTO(fun_vmul);
+FUNCTION_PROTO(fun_vsub);
+FUNCTION_PROTO(fun_vunit);
+FUNCTION_PROTO(fun_wait);
+FUNCTION_PROTO(fun_where);
+FUNCTION_PROTO(fun_width);
+FUNCTION_PROTO(fun_wipe);
+FUNCTION_PROTO(fun_wordpos);
+FUNCTION_PROTO(fun_words);
+FUNCTION_PROTO(fun_wrap);
+FUNCTION_PROTO(fun_xget);
+FUNCTION_PROTO(fun_xor);
+FUNCTION_PROTO(fun_zemit);
+FUNCTION_PROTO(fun_zfun);
+FUNCTION_PROTO(fun_zone);
+FUNCTION_PROTO(fun_zwho);
diff --git a/win32/options.h b/win32/options.h
new file mode 100644 (file)
index 0000000..644cd59
--- /dev/null
@@ -0,0 +1,163 @@
+/* options.h */
+
+#ifndef __OPTIONS_H
+#define __OPTIONS_H
+
+/* *********** READ THIS BEFORE YOU MODIFY ANYTHING IN THIS FILE *********** */
+/* WARNING:  All options in this file have the ability to significantly change
+ * the look and feel and sometimes even internal behavior of the program.
+ * The ones shipped as the default have been extensively tested.  Others have
+ * been tested to a (usually) lesser degree, and therefore might still have
+ * latent bugs.  If you change any of them from the default, PLEASE check
+ * to make sure that you know the full effects of what you are changing. And
+ * if you encounter any errors or compile time problems with any options
+ * other than the default settings, PLEASE inform
+ * pennmush-bugs@pennmush.org
+ * immediately, so that they can be fixed.  The same goes for any other bug
+ * you might find in using this software.  All efforts will be made to fix
+ * errors encountered, but unless given a FULL description of the error,
+ * (IE telling me that logging in doesn't work is insufficient.  telling
+ * me that logging in with WCREAT undefined still gives you the registration
+ * message is a lot better.  MOST effective would be a full dbx trace, or a
+ * patch for the bug.)  Enjoy using the program.
+ */
+/***************************************************************************/
+
+/*---------------- Internals with many options ------------------------*/
+
+/* Malloc package options */
+/* malloc() is the routine that allocates memory while the MUSH is
+ * running. Because mallocs vary a lot from operating system to operating
+ * system, you can choose to use one of the mallocs we provide instead of
+ * your operating system's malloc.
+ * Set the value of MALLOC_PACKAGE  to one of these values:
+ *  0 -- Use my system's malloc. Required for Win32 systems.
+ *       Recommended for FreeBSD, Linux, Mac OS X/Darwin, and other OS's
+ *       where you think the malloc routines are efficient and debugged.
+ *       Otherwise, use only as a last resort.
+ *  1 -- Use the CSRI malloc package in normal mode.
+ *       Recommended for most operating systems where system malloc is
+ *       suspect. Known to work well on SunOS 4.1.x.
+ *  2 -- Use the CSRI malloc package in debug mode.
+ *       Only use this if you're tracking down memory leaks. Don't use
+ *       for a production MUSH - it's slow.
+ *  5 -- Use the GNU malloc (gmalloc) package.
+ *       Doesn't work on Alpha processors or FreeBSD systems, and
+ *       reportedly flaky on Linux. Requires an ANSI compiler.
+ *       Otherwise, similar to CSRI malloc.
+ *  3, 4, 6 -- Same as 0, kept for compatibility.
+ */
+#define MALLOC_PACKAGE 0
+
+/* What type of attribute compression should the MUSH use?
+ * Your options are:
+ * 1 - the default Huffman compression which has been in use for a
+ *     long time. In theory, this should be the best compression,
+ *     possibly at the cost of some speed. It is also 8-bit clean,
+ *     and thus suitable for locales that use extended character sets.
+ *     Sometimes has trouble on some linux systems for some reason.
+ * 2 - Same as 1, for backwards compability.
+ * 3 - Nick Gammon's word-based compression algorithm.
+ *     In theory, this should be considerably faster than Huffman
+ *     when decompressing, and considerably slower when compressing.
+ *     (But you decompress a lot more often). Compression ratio
+ *     is worse than Huffman for small dbs (<1.5Mb of text), but
+ *     better for larger dbs. Win32 systems must use this.
+ * 4 - Raevnos's almost 8-bit clean version of the word-based algorithm.
+ *     Prefer 3 unless you need extended characters. This algorithm
+ *     can encode all characters except 0x06.
+ * 0 - No compression at all. Very fast, but your db in memory
+ *     will be big - at least as large as your on-disk db.
+ *     Possibly suitable for the building stages of a small MUSH.
+ *     This should be 8-bit clean, too.
+ * You can change this at any time, with no worries. It only affects
+ * the in-memory compression of attribute/mail text, not the disk
+ * db compression. Recommend to keep it at 1. When in doubt, try them
+ * all, and check @uptime's memory usage stats for the most efficient
+ * choice among those that are stable for you. When using word-based
+ * compression, you can also #define COMP_STATS to get some detailed
+ * information in @stats/tables.
+ */
+#define COMPRESSION_TYPE       3
+
+
+/*------------------------- Other internals ----------------------*/
+
+/* If defined, use the info_slave to get information from identd,
+ * instead of having the MUSH do it directly.  This may help reduce lag
+ * from new logins.  This does _not_ work under Win32.
+ */
+/* #define INFO_SLAVE /* */
+
+/* Windows NT users may uncomment this define to get the native network i/o
+ * thread model instead of the bsd socket layer, for vastly better
+ * performance. Doesn't work on Win 95/98. By Nick Gammon
+ */
+#define NT_TCP /* */
+
+/*------------------------- MUSH Features ----------------------*/
+
+/* Comment this out if you don't wish to use the built-in mail system.
+ * The @mail command provides a flexible hardcoded mail system, which
+ * uses its own database to store messages.
+ */
+#define USE_MAILER /* */
+
+/* Defining ALLOW_NOSUBJECT marks mail sent with no subject as having
+ * subject '(no subject)'. The default is for the subject of the mail to
+ * be the first 30 characters of the message when not specfied
+ */
+#define ALLOW_NOSUBJECT /* */
+
+/*
+ * Should we have mail aliases (@mailing lists?). This modifies
+ * the maildb format, but you can reverse it.
+ */
+#define MAIL_ALIASES /* */
+
+/*  The chat channels system allows players to talk cross-MUSH to each
+ *  other, without needing to be in the same room. Whether or not you
+ *  want this depends on what type of MUSH you want.
+ */
+#define CHAT_SYSTEM /* */
+
+/* Many MUSHes want to change the +channels to =channels. That's
+ * annoying. So we've got this CHAT_TOKEN_ALIAS, which allows + as well
+ * as = (or whatever) channels. If you want this, define it to be
+ * the character you want to use in addition to +, enclosed in
+ * single quotes, as in '=' or '.' or whatever. Don't define it to '+'!
+ */
+/* #define CHAT_TOKEN_ALIAS /* */
+
+/* Quotas limit players to a fixed number of objects.
+ * Wizards can check and set quotas on players.
+ * See also restricted_building in game/mush.cnf for another way
+ * to slow database growth.
+ */
+#define QUOTA /* */
+
+
+/*------------------------------ DB ----------------------------------*/
+
+/*------------------------- Cosmetic Features --------------------*/
+
+/* If you're using the email registration feature, but want to
+ * use a mailer other than sendmail, put the full path to the mailer
+ * program here. The mailer must accept the -t command-line
+ * argument ("get the recipient address from the message header To:").
+ * If it doesn't, you could probably write a wrapper for it.
+ * Example: #define MAILER "/full/path/to/other/mailer"
+#define MAILER /* */
+
+
+/*--------------------- CobraMUSH Options ------------------------*/
+
+/* These are options specific to CobraMUSH.
+ */
+
+/* #define KNOW_SYS /* */
+#define COLOREDWHO /* */
+#define RPMODE_SYS /* */
+#define SNOOPER /* */
+
+#endif
diff --git a/win32/patches.h b/win32/patches.h
new file mode 100644 (file)
index 0000000..dfe9c2d
--- /dev/null
@@ -0,0 +1,4 @@
+#ifndef _PATCH_H
+#define _PATCH_H
+#undef PATCHES
+#endif _PATCH_H
diff --git a/win32/pennmush.dsp b/win32/pennmush.dsp
new file mode 100644 (file)
index 0000000..d101631
--- /dev/null
@@ -0,0 +1,588 @@
+# Microsoft Developer Studio Project File - Name="pennmush" - Package Owner=<4>\r
+# Microsoft Developer Studio Generated Build File, Format Version 6.00\r
+# ** DO NOT EDIT **\r
+\r
+# TARGTYPE "Win32 (x86) Console Application" 0x0103\r
+\r
+CFG=pennmush - Win32 SSL Debug\r
+!MESSAGE This is not a valid makefile. To build this project using NMAKE,\r
+!MESSAGE use the Export Makefile command and run\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "pennmush.mak".\r
+!MESSAGE \r
+!MESSAGE You can specify a configuration when running NMAKE\r
+!MESSAGE by defining the macro CFG on the command line. For example:\r
+!MESSAGE \r
+!MESSAGE NMAKE /f "pennmush.mak" CFG="pennmush - Win32 SSL Debug"\r
+!MESSAGE \r
+!MESSAGE Possible choices for configuration are:\r
+!MESSAGE \r
+!MESSAGE "pennmush - Win32 Release" (based on "Win32 (x86) Console Application")\r
+!MESSAGE "pennmush - Win32 Debug" (based on "Win32 (x86) Console Application")\r
+!MESSAGE "pennmush - Win32 SSL Release" (based on "Win32 (x86) Console Application")\r
+!MESSAGE "pennmush - Win32 SSL Debug" (based on "Win32 (x86) Console Application")\r
+!MESSAGE \r
+\r
+# Begin Project\r
+# PROP AllowPerConfigDependencies 0\r
+# PROP Scc_ProjName ""\r
+# PROP Scc_LocalPath ""\r
+CPP=cl.exe\r
+RSC=rc.exe\r
+\r
+!IF  "$(CFG)" == "pennmush - Win32 Release"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 0\r
+# PROP BASE Output_Dir "Release"\r
+# PROP BASE Intermediate_Dir "Release"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 0\r
+# PROP Output_Dir "./game"\r
+# PROP Intermediate_Dir "./src"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /GX /O2 /Ob0 /I "./hdrs" /I "./" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /I /Win32" " /c\r
+# ADD BASE RSC /l 0xc09 /d "NDEBUG"\r
+# ADD RSC /l 0xc09 /d "NDEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386\r
+# ADD LINK32 user32.lib winmm.lib wsock32.lib /nologo /subsystem:console /machine:I386\r
+# Begin Custom Build - Copying Necessary Files\r
+ProjDir=.\r
+InputPath=.\game\pennmush.exe\r
+SOURCE="$(InputPath)"\r
+\r
+BuildCmds= \\r
+       IF NOT EXIST $(ProjDir)\src\cmdlocal.c copy $(ProjDir)\src\cmdlocal.dst $(ProjDir)\src\cmdlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\flaglocal.c copy $(ProjDir)\src\flaglocal.dst $(ProjDir)\src\flaglocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\funlocal.c copy $(ProjDir)\src\funlocal.dst $(ProjDir)\src\funlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\local.c copy $(ProjDir)\src\local.dst $(ProjDir)\src\local.c \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf echo Don't forget to customize mush.cnf! \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf copy $(ProjDir)\game\mushcnf.dst $(ProjDir)\game\mush.cnf \\r
+       copy $(ProjDir)\win32\cmds.h $(ProjDir)\hdrs\cmds.h \\r
+       copy $(ProjDir)\win32\funs.h $(ProjDir)\hdrs\funs.h \\r
+       echo If any *local.c files failed to build, run build again. \\r
+       \r
+\r
+"$(ProjDir)\src\cmdlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\flaglocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\funlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\local.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\game\mush.cnf" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\cmds.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\funs.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+# End Custom Build\r
+\r
+!ELSEIF  "$(CFG)" == "pennmush - Win32 Debug"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 1\r
+# PROP BASE Output_Dir "Debug"\r
+# PROP BASE Intermediate_Dir "Debug"\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 1\r
+# PROP Output_Dir "./game"\r
+# PROP Intermediate_Dir "./src"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# ADD CPP /nologo /MD /W3 /GX /ZI /I "./win32" /I "./hdrs" /I "./" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# SUBTRACT CPP /Ox /Ot /Os\r
+# ADD BASE RSC /l 0xc09 /d "_DEBUG"\r
+# ADD RSC /l 0xc09 /d "_DEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept\r
+# ADD LINK32 kernel32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib user32.lib winmm.lib wsock32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept\r
+# Begin Custom Build - Copying Necessary Files\r
+ProjDir=.\r
+InputPath=.\game\pennmush.exe\r
+SOURCE="$(InputPath)"\r
+\r
+BuildCmds= \\r
+       IF NOT EXIST $(ProjDir)\src\cmdlocal.c copy $(ProjDir)\src\cmdlocal.dst $(ProjDir)\src\cmdlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\flaglocal.c copy $(ProjDir)\src\flaglocal.dst $(ProjDir)\src\flaglocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\funlocal.c copy $(ProjDir)\src\funlocal.dst $(ProjDir)\src\funlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\local.c copy $(ProjDir)\src\local.dst $(ProjDir)\src\local.c \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf echo Don't forget to customize mush.cnf! \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf copy $(ProjDir)\game\mushcnf.dst $(ProjDir)\game\mush.cnf \\r
+       copy $(ProjDir)\win32\cmds.h $(ProjDir)\hdrs\cmds.h \\r
+       copy $(ProjDir)\win32\funs.h $(ProjDir)\hdrs\funs.h \\r
+       echo If any *local.c files failed to build, run build again. \\r
+       \r
+\r
+"$(ProjDir)\src\cmdlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\flaglocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\funlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\local.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\game\mush.cnf" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\cmds.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\funs.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+# End Custom Build\r
+\r
+!ELSEIF  "$(CFG)" == "pennmush - Win32 SSL Release"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 0\r
+# PROP BASE Output_Dir "pennmush___Win32_SSL_Release"\r
+# PROP BASE Intermediate_Dir "pennmush___Win32_SSL_Release"\r
+# PROP BASE Ignore_Export_Lib 0\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 0\r
+# PROP Output_Dir "./game"\r
+# PROP Intermediate_Dir "./src"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /MD /W3 /GX /O2 /Ob0 /I "./hdrs" /I "./" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /I /Win32" " /c\r
+# ADD CPP /nologo /MD /W3 /GX /O2 /Ob0 /I "./hdrs" /I "./" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /I /Win32" " /c\r
+# ADD BASE RSC /l 0xc09 /d "NDEBUG"\r
+# ADD RSC /l 0xc09 /d "NDEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 user32.lib winmm.lib wsock32.lib /nologo /subsystem:console /machine:I386\r
+# ADD LINK32 user32.lib winmm.lib wsock32.lib ssleay32.lib libeay32.lib /nologo /subsystem:console /pdb:"pennmush.pdb" /machine:I386\r
+# SUBTRACT LINK32 /pdb:none\r
+# Begin Custom Build - Copying Necessary Files\r
+ProjDir=.\r
+InputPath=.\game\pennmush.exe\r
+SOURCE="$(InputPath)"\r
+\r
+BuildCmds= \\r
+       IF NOT EXIST $(ProjDir)\src\cmdlocal.c copy $(ProjDir)\src\cmdlocal.dst $(ProjDir)\src\cmdlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\flaglocal.c copy $(ProjDir)\src\flaglocal.dst $(ProjDir)\src\flaglocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\funlocal.c copy $(ProjDir)\src\funlocal.dst $(ProjDir)\src\funlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\local.c copy $(ProjDir)\src\local.dst $(ProjDir)\src\local.c \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf echo Don't forget to customize mush.cnf! \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf copy $(ProjDir)\game\mushcnf.dst $(ProjDir)\game\mush.cnf \\r
+       copy $(ProjDir)\win32\cmds.h $(ProjDir)\hdrs\cmds.h \\r
+       copy $(ProjDir)\win32\funs.h $(ProjDir)\hdrs\funs.h \\r
+       echo If any *local.c files failed to build, run build again. \\r
+       \r
+\r
+"$(ProjDir)\src\cmdlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\flaglocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\funlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\local.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\game\mush.cnf" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\cmds.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\funs.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+# End Custom Build\r
+\r
+!ELSEIF  "$(CFG)" == "pennmush - Win32 SSL Debug"\r
+\r
+# PROP BASE Use_MFC 0\r
+# PROP BASE Use_Debug_Libraries 1\r
+# PROP BASE Output_Dir "pennmush___Win32_SSL_Debug"\r
+# PROP BASE Intermediate_Dir "pennmush___Win32_SSL_Debug"\r
+# PROP BASE Ignore_Export_Lib 0\r
+# PROP BASE Target_Dir ""\r
+# PROP Use_MFC 0\r
+# PROP Use_Debug_Libraries 1\r
+# PROP Output_Dir "./game"\r
+# PROP Intermediate_Dir "./src"\r
+# PROP Ignore_Export_Lib 0\r
+# PROP Target_Dir ""\r
+# ADD BASE CPP /nologo /MD /W3 /GX /ZI /I "./win32" /I "./hdrs" /I "./" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# SUBTRACT BASE CPP /Ox /Ot /Os\r
+# ADD CPP /nologo /MD /W3 /GX /ZI /I "./win32" /I "./hdrs" /I "./" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c\r
+# SUBTRACT CPP /Ox /Ot /Os\r
+# ADD BASE RSC /l 0xc09 /d "_DEBUG"\r
+# ADD RSC /l 0xc09 /d "_DEBUG"\r
+BSC32=bscmake.exe\r
+# ADD BASE BSC32 /nologo\r
+# ADD BSC32 /nologo\r
+LINK32=link.exe\r
+# ADD BASE LINK32 kernel32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib user32.lib winmm.lib wsock32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept\r
+# ADD LINK32 kernel32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib user32.lib winmm.lib wsock32.lib ssleay32.lib libeay32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept\r
+# Begin Custom Build - Copying Necessary Files\r
+ProjDir=.\r
+InputPath=.\game\pennmush.exe\r
+SOURCE="$(InputPath)"\r
+\r
+BuildCmds= \\r
+       IF NOT EXIST $(ProjDir)\src\cmdlocal.c copy $(ProjDir)\src\cmdlocal.dst $(ProjDir)\src\cmdlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\flaglocal.c copy $(ProjDir)\src\flaglocal.dst $(ProjDir)\src\flaglocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\funlocal.c copy $(ProjDir)\src\funlocal.dst $(ProjDir)\src\funlocal.c \\r
+       IF NOT EXIST $(ProjDir)\src\local.c copy $(ProjDir)\src\local.dst $(ProjDir)\src\local.c \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf echo Don't forget to customize mush.cnf! \\r
+       IF NOT EXIST $(ProjDir)\game\mush.cnf copy $(ProjDir)\game\mushcnf.dst $(ProjDir)\game\mush.cnf \\r
+       copy $(ProjDir)\win32\cmds.h $(ProjDir)\hdrs\cmds.h \\r
+       copy $(ProjDir)\win32\funs.h $(ProjDir)\hdrs\funs.h \\r
+       echo If any *local.c files failed to build, run build again. \\r
+       \r
+\r
+"$(ProjDir)\src\cmdlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\flaglocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\funlocal.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\src\local.c" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\game\mush.cnf" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\cmds.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+\r
+"$(ProjDir)\hdrs\funs.h" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)"\r
+   $(BuildCmds)\r
+# End Custom Build\r
+\r
+!ENDIF \r
+\r
+# Begin Target\r
+\r
+# Name "pennmush - Win32 Release"\r
+# Name "pennmush - Win32 Debug"\r
+# Name "pennmush - Win32 SSL Release"\r
+# Name "pennmush - Win32 SSL Debug"\r
+# Begin Source File\r
+\r
+SOURCE=.\src\access.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\atr_tab.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\attrib.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\boolexp.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\bsd.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\bufferq.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\chunk.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\cmdlocal.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\cmds.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\command.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\compress.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\conf.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\cque.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\create.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\db.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\destroy.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\extchat.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\extmail.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\filecopy.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\flaglocal.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\flags.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funcrypt.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\function.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\fundb.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funlist.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funlocal.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funmath.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funmisc.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funstr.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funtime.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\funufun.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\game.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\help.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\htab.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\ident.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\local.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\lock.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\log.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\look.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\malias.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\match.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\memcheck.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\move.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\mycrypt.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\mymalloc.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\mysocket.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\myssl.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\notify.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\parse.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\pcre.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\player.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\plyrlist.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\predicat.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\privtab.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\ptab.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\rob.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\services.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\set.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\shs.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\sig.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\speech.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\sql.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\strdup.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\strtree.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\strutil.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\tables.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\timer.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\unparse.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\utils.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\version.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\warnings.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\wild.c\r
+# End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\src\wiz.c\r
+# End Source File\r
+# End Target\r
+# End Project\r
diff --git a/win32/pennmush.dsw b/win32/pennmush.dsw
new file mode 100644 (file)
index 0000000..fd13356
--- /dev/null
@@ -0,0 +1,29 @@
+Microsoft Developer Studio Workspace File, Format Version 6.00\r
+# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!\r
+\r
+###############################################################################\r
+\r
+Project: "pennmush"=.\pennmush.dsp - Package Owner=<4>\r
+\r
+Package=<5>\r
+{{{\r
+}}}\r
+\r
+Package=<4>\r
+{{{\r
+}}}\r
+\r
+###############################################################################\r
+\r
+Global:\r
+\r
+Package=<5>\r
+{{{\r
+}}}\r
+\r
+Package=<3>\r
+{{{\r
+}}}\r
+\r
+###############################################################################\r
+\r
diff --git a/win32/switches.h b/win32/switches.h
new file mode 100644 (file)
index 0000000..228cc8e
--- /dev/null
@@ -0,0 +1,161 @@
+/* AUTOGENERATED FILE. DO NOT EDIT */
+#define SWITCH_ACCESS 1
+#define SWITCH_ADD 2
+#define SWITCH_AFTER 3
+#define SWITCH_ALIAS 4
+#define SWITCH_ALL 5
+#define SWITCH_ANY 6
+#define SWITCH_ATTRIBS 7
+#define SWITCH_AUTO 8
+#define SWITCH_BAN 9
+#define SWITCH_BEFORE 10
+#define SWITCH_BLIND 11
+#define SWITCH_BRIEF 12
+#define SWITCH_BUFFER 13
+#define SWITCH_CHECK 14
+#define SWITCH_CHOWN 15
+#define SWITCH_CHUNKS 16
+#define SWITCH_CLEAR 17
+#define SWITCH_CMD 18
+#define SWITCH_COMBAT 19
+#define SWITCH_COMMANDS 20
+#define SWITCH_CONN 21
+#define SWITCH_CONNECT 22
+#define SWITCH_CONNECTED 23
+#define SWITCH_CONTENTS 24
+#define SWITCH_COSTS 25
+#define SWITCH_COUNT 26
+#define SWITCH_CREATE 27
+#define SWITCH_DATABASE 28
+#define SWITCH_DB 29
+#define SWITCH_DEBUG 30
+#define SWITCH_DECOMPILE 31
+#define SWITCH_DEFAULTS 32
+#define SWITCH_DELETE 33
+#define SWITCH_DELIMIT 34
+#define SWITCH_DESCRIBE 35
+#define SWITCH_DESTROY 36
+#define SWITCH_DISABLE 37
+#define SWITCH_DOWN 38
+#define SWITCH_DSTATS 39
+#define SWITCH_EMIT 40
+#define SWITCH_ENABLE 41
+#define SWITCH_EQSPLIT 42
+#define SWITCH_ERR 43
+#define SWITCH_EXITS 44
+#define SWITCH_FILE 45
+#define SWITCH_FIRST 46
+#define SWITCH_FLAGS 47
+#define SWITCH_FOLDERS 48
+#define SWITCH_FORWARD 49
+#define SWITCH_FREESPACE 50
+#define SWITCH_FSTATS 51
+#define SWITCH_FULL 52
+#define SWITCH_FUNCTIONS 53
+#define SWITCH_FWD 54
+#define SWITCH_GAG 55
+#define SWITCH_GLOBALS 56
+#define SWITCH_HEADER 57
+#define SWITCH_HERE 58
+#define SWITCH_HIDE 59
+#define SWITCH_IGNORE 60
+#define SWITCH_ILIST 61
+#define SWITCH_INFO 62
+#define SWITCH_INIT 63
+#define SWITCH_INSIDE 64
+#define SWITCH_INVENTORY 65
+#define SWITCH_IPRINT 66
+#define SWITCH_JOIN 67
+#define SWITCH_LETTER 68
+#define SWITCH_LIST 69
+#define SWITCH_LOCK 70
+#define SWITCH_LOCKS 71
+#define SWITCH_LOGOUT 72
+#define SWITCH_LOWERCASE 73
+#define SWITCH_LSARGS 74
+#define SWITCH_MAX 75
+#define SWITCH_ME 76
+#define SWITCH_MEMBERS 77
+#define SWITCH_MOD 78
+#define SWITCH_MORTAL 79
+#define SWITCH_MOTD 80
+#define SWITCH_MUTE 81
+#define SWITCH_NAME 82
+#define SWITCH_NO 83
+#define SWITCH_NOEVAL 84
+#define SWITCH_NOFLAGCOPY 85
+#define SWITCH_NOISY 86
+#define SWITCH_NOSIG 87
+#define SWITCH_NOSPACE 88
+#define SWITCH_NOTIFY 89
+#define SWITCH_NUKE 90
+#define SWITCH_OBJECT 91
+#define SWITCH_OFF 92
+#define SWITCH_ON 93
+#define SWITCH_OUTSIDE 94
+#define SWITCH_OVERRIDE 95
+#define SWITCH_PAGING 96
+#define SWITCH_PANIC 97
+#define SWITCH_PARANOID 98
+#define SWITCH_PLAYERS 99
+#define SWITCH_PORT 100
+#define SWITCH_POWERS 101
+#define SWITCH_PRESERVE 102
+#define SWITCH_PRINT 103
+#define SWITCH_PRIVS 104
+#define SWITCH_PURGE 105
+#define SWITCH_QUICK 106
+#define SWITCH_QUIET 107
+#define SWITCH_QUIT 108
+#define SWITCH_RAW 109
+#define SWITCH_READ 110
+#define SWITCH_REBOOT 111
+#define SWITCH_RECALL 112
+#define SWITCH_REGEXP 113
+#define SWITCH_REGIONS 114
+#define SWITCH_REGISTER 115
+#define SWITCH_REMOVE 116
+#define SWITCH_RENAME 117
+#define SWITCH_RESET 118
+#define SWITCH_RESTORE 119
+#define SWITCH_RESTRICT 120
+#define SWITCH_RETROACTIVE 121
+#define SWITCH_ROOM 122
+#define SWITCH_ROOMS 123
+#define SWITCH_RSARGS 124
+#define SWITCH_SEE 125
+#define SWITCH_SEEFLAG 126
+#define SWITCH_SELF 127
+#define SWITCH_SEND 128
+#define SWITCH_SET 129
+#define SWITCH_SILENT 130
+#define SWITCH_SKIPDEFAULTS 131
+#define SWITCH_SPEAK 132
+#define SWITCH_SPOOF 133
+#define SWITCH_STATS 134
+#define SWITCH_SUMMARY 135
+#define SWITCH_TABLES 136
+#define SWITCH_TAG 137
+#define SWITCH_TELEPORT 138
+#define SWITCH_TF 139
+#define SWITCH_THINGS 140
+#define SWITCH_TITLE 141
+#define SWITCH_TRACE 142
+#define SWITCH_TYPE 143
+#define SWITCH_UNCLEAR 144
+#define SWITCH_UNFOLDER 145
+#define SWITCH_UNGAG 146
+#define SWITCH_UNHIDE 147
+#define SWITCH_UNMUTE 148
+#define SWITCH_UNTAG 149
+#define SWITCH_UNTIL 150
+#define SWITCH_URGENT 151
+#define SWITCH_USEFLAG 152
+#define SWITCH_WHAT 153
+#define SWITCH_WHO 154
+#define SWITCH_WIPE 155
+#define SWITCH_WIZ 156
+#define SWITCH_WIZARD 157
+#define SWITCH_WRITE 158
+#define SWITCH_YES 159
+#define SWITCH_ZONE 160