/** * \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); 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); /** The list of standard commands. Additional commands can be added * at runtime with add_command(). */ COMLIST commands[] = { {"@COMMAND", "ADD ALIAS DELETE EQSPLIT LSARGS RSARGS NOEVAL ON OFF QUIET ENABLE DISABLE RESTRICT", cmd_command, CMD_T_PLAYER | CMD_T_EQSPLIT, 0, 0}, {"@@", NULL, cmd_null, CMD_T_ANY | CMD_T_NOPARSE, 0, 0}, {"@ALLHALT", NULL, cmd_allhalt, CMD_T_ANY, "WIZARD", "HALT"}, {"@ALLQUOTA", "QUIET", cmd_allquota, CMD_T_ANY, "WIZARD", "QUOTA"}, {"@ASSERT", NULL, cmd_assert, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0}, {"@ATRLOCK", NULL, cmd_atrlock, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@ATRCHOWN", NULL, cmd_atrchown, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@ATTRIBUTE", "ACCESS DELETE RENAME RETROACTIVE", cmd_attribute, CMD_T_ANY | CMD_T_EQSPLIT, "WIZARD", 0}, {"@BOOT", "PORT ME SILENT", cmd_boot, CMD_T_ANY, 0, 0}, {"@BREAK", NULL, cmd_break, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0}, {"@CEMIT", "NOEVAL NOISY SPOOF", cmd_cemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@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", cmd_channel, CMD_T_ANY | CMD_T_SWITCHES | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@CHAT", NULL, cmd_chat, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@CHOWNALL", "PRESERVE", cmd_chownall, CMD_T_ANY | CMD_T_EQSPLIT, "WIZARD", 0}, {"@CHOWN", "PRESERVE", cmd_chown, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@CHZONEALL", NULL, cmd_chzoneall, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@CHZONE", NULL, cmd_chzone, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@CONFIG", "SET LOWERCASE LIST GLOBALS DEFAULTS COSTS FLAGS FUNCTIONS COMMANDS ATTRIBS", cmd_config, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@CPATTR", "CONVERT NOFLAGCOPY", cmd_cpattr, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, 0, 0}, {"@CREATE", NULL, cmd_create, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@CLONE", "PRESERVE", cmd_clone, CMD_T_ANY | CMD_T_NOGAGGED | CMD_T_EQSPLIT, 0, 0}, {"@CLOCK", "JOIN SPEAK MOD SEE HIDE", cmd_clock, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@DBCK", NULL, cmd_dbck, CMD_T_ANY, "WIZARD", 0}, {"@DECOMPILE", "DB PREFIX TF FLAGS ATTRIBS SKIPDEFAULTS", cmd_decompile, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@DESTROY", "OVERRIDE", cmd_destroy, CMD_T_ANY, 0, 0}, {"@DIG", "TELEPORT", cmd_dig, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0}, {"@DISABLE", NULL, cmd_disable, CMD_T_ANY, "WIZARD", 0}, {"@DOING", "HEADER", cmd_doing, CMD_T_ANY | CMD_T_NOPARSE | CMD_T_NOGAGGED, 0, 0}, {"@DOLIST", "NOTIFY DELIMIT", cmd_dolist, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0}, {"@DRAIN", "ALL ANY", cmd_notify_drain, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@DUMP", "PARANOID DEBUG", cmd_dump, CMD_T_ANY, "WIZARD", 0}, {"@EDIT", "FIRST", cmd_edit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE | CMD_T_NOGAGGED, 0, 0}, {"@ELOCK", NULL, cmd_elock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@EMIT", "ROOM NOEVAL SILENT SPOOF", cmd_emit, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@ENABLE", NULL, cmd_enable, CMD_T_ANY | CMD_T_NOGAGGED, "WIZARD", 0}, {"@ENTRANCES", "EXITS THINGS PLAYERS ROOMS", cmd_entrances, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0}, {"@EUNLOCK", NULL, cmd_eunlock, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@FIND", NULL, cmd_find, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0}, {"@FIRSTEXIT", NULL, cmd_firstexit, CMD_T_ANY, 0, 0}, {"@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, 0, 0}, {"@FORCE", "NOEVAL", cmd_force, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@FUNCTION", "DELETE ENABLE DISABLE RESTORE RESTRICT", cmd_function, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0}, {"@GREP", "LIST PRINT ILIST IPRINT", cmd_grep, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE | CMD_T_NOGAGGED, 0, 0}, {"@HALT", "ALL", cmd_halt, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@HIDE", "NO OFF YES ON", cmd_hide, CMD_T_ANY, 0, 0}, {"@HOOK", "AFTER BEFORE IGNORE OVERRIDE", cmd_hook, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, "WIZARD", 0}, {"@KICK", NULL, cmd_kick, CMD_T_ANY, "WIZARD", 0}, {"@LEMIT", "NOEVAL SILENT SPOOF", cmd_lemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@LINK", "PRESERVE", cmd_link, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@LISTMOTD", NULL, cmd_listmotd, CMD_T_ANY, 0, 0}, {"@LIST", "LOWERCASE MOTD FLAGS FUNCTIONS POWERS COMMANDS ATTRIBS", cmd_list, CMD_T_ANY, 0, 0}, {"@LOCK", NULL, cmd_lock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_SWITCHES | CMD_T_NOGAGGED, 0, 0}, {"@LOG", "CHECK CMD CONN ERR TRACE WIZ", cmd_log, CMD_T_ANY | CMD_T_NOGAGGED, "WIZARD", 0}, {"@LOGWIPE", "CHECK CMD CONN ERR TRACE WIZ", cmd_logwipe, CMD_T_ANY | CMD_T_NOGAGGED | CMD_T_GOD, 0, 0}, {"@LSET", NULL, cmd_lset, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@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, 0, 0}, {"@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, 0, 0}, {"@MAP", "DELIMIT", cmd_map, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0}, {"@MOTD", "CONNECT LIST WIZARD DOWN FULL", cmd_motd, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@MVATTR", "CONVERT NOFLAGCOPY", cmd_mvattr, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, 0, 0}, {"@NAME", NULL, cmd_name, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED | CMD_T_NOGUEST, 0, 0}, {"@NEWPASSWORD", NULL, cmd_newpassword, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, "WIZARD", 0}, {"@NOTIFY", "ALL ANY", cmd_notify_drain, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@NSCEMIT", "NOEVAL NOISY SPOOF", cmd_cemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"}, {"@NSEMIT", "ROOM NOEVAL SILENT SPOOF", cmd_emit, CMD_T_ANY | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"}, {"@NSLEMIT", "NOEVAL SILENT SPOOF", cmd_lemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"}, {"@NSOEMIT", "NOEVAL SPOOF", cmd_oemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"}, {"@NSPEMIT", "LIST SILENT NOISY NOEVAL", cmd_pemit, CMD_T_ANY | CMD_T_EQSPLIT, "WIZARD", "CAN_NSPEMIT"}, {"@NSREMIT", "LIST NOEVAL NOISY SILENT SPOOF", cmd_remit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"}, {"@NSZEMIT", NULL, cmd_zemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, "WIZARD", "CAN_NSPEMIT"}, {"@NUKE", NULL, cmd_nuke, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@OEMIT", "NOEVAL SPOOF", cmd_oemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@OPEN", NULL, cmd_open, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0}, {"@PARENT", NULL, cmd_parent, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@PASSWORD", NULL, cmd_password, CMD_T_PLAYER | CMD_T_EQSPLIT | CMD_T_NOPARSE | CMD_T_RS_NOPARSE | CMD_T_NOGUEST, 0, 0}, {"@PCREATE", NULL, cmd_pcreate, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@PEMIT", "LIST CONTENTS SILENT NOISY NOEVAL SPOOF", cmd_pemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@POLL", NULL, cmd_poll, CMD_T_ANY, 0, 0}, {"@POOR", NULL, cmd_poor, CMD_T_ANY, 0, 0}, {"@POWER", "ADD TYPE LETTER LIST RESTRICT DELETE ALIAS DISABLE ENABLE", cmd_power, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, "WIZARD", 0}, {"@PS", "ALL SUMMARY COUNT QUICK", cmd_ps, CMD_T_ANY, 0, 0}, {"@PURGE", NULL, cmd_purge, CMD_T_ANY, 0, 0}, {"@QUOTA", "ALL SET", cmd_quota, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@READCACHE", NULL, cmd_readcache, CMD_T_ANY, "WIZARD", 0}, {"@RECYCLE", "OVERRIDE", cmd_destroy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@REMIT", "LIST NOEVAL NOISY SILENT SPOOF", cmd_remit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@REJECTMOTD", NULL, cmd_rejectmotd, CMD_T_ANY, "WIZARD", 0}, {"@RESTART", "ALL", cmd_restart, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@RWALL", "NOEVAL EMIT", cmd_rwall, CMD_T_ANY, "WIZARD ROYALTY", 0}, {"@SCAN", "ROOM SELF ZONE GLOBALS", cmd_scan, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@SEARCH", NULL, cmd_search, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE, 0, 0}, {"@SELECT", "NOTIFY REGEXP", cmd_select, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE, 0, 0}, {"@SET", NULL, cmd_set, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@SHUTDOWN", "PANIC REBOOT PARANOID", cmd_shutdown, CMD_T_ANY, "WIZARD", 0}, #ifdef HAS_MYSQL {"@SQL", NULL, cmd_sql, CMD_T_ANY, "WIZARD", "SQL_OK"}, #else {"@SQL", NULL, cmd_unimplemented, CMD_T_ANY, "WIZARD", "SQL_OK"}, #endif {"@SITELOCK", "BAN CHECK REGISTER REMOVE NAME", cmd_sitelock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, "WIZARD", 0}, {"@STATS", "CHUNKS FREESPACE PAGING REGIONS TABLES", cmd_stats, CMD_T_ANY, 0, 0}, {"@SWEEP", "CONNECTED HERE INVENTORY EXITS", cmd_sweep, CMD_T_ANY, 0, 0}, {"@SWITCH", "NOTIFY FIRST ALL REGEXP", cmd_switch, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_RS_NOPARSE | CMD_T_NOGAGGED, 0, 0}, {"@SQUOTA", NULL, cmd_squota, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@TELEPORT", "SILENT INSIDE", cmd_teleport, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@TRIGGER", NULL, cmd_trigger, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS | CMD_T_NOGAGGED, 0, 0}, {"@ULOCK", NULL, cmd_ulock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"@UNDESTROY", NULL, cmd_undestroy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@UNLINK", NULL, cmd_unlink, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@UNLOCK", NULL, cmd_unlock, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_SWITCHES | CMD_T_NOGAGGED, 0, 0}, {"@UNRECYCLE", NULL, cmd_undestroy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@UPTIME", "MORTAL", cmd_uptime, CMD_T_ANY, 0, 0}, {"@UUNLOCK", NULL, cmd_uunlock, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@VERB", NULL, cmd_verb, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_ARGS, 0, 0}, {"@VERSION", NULL, cmd_version, CMD_T_ANY, 0, 0}, {"@WAIT", "UNTIL", cmd_wait, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_RS_NOPARSE, 0, 0}, {"@WALL", "NOEVAL EMIT", cmd_wall, CMD_T_ANY, "WIZARD ROYALTY", "ANNOUNCE"}, {"@WARNINGS", NULL, cmd_warnings, CMD_T_ANY | CMD_T_EQSPLIT, 0, 0}, {"@WCHECK", "ALL ME", cmd_wcheck, CMD_T_ANY, 0, 0}, {"@WHEREIS", NULL, cmd_whereis, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"@WIPE", NULL, cmd_wipe, CMD_T_ANY, 0, 0}, {"@WIZWALL", "NOEVAL EMIT", cmd_wizwall, CMD_T_ANY, "WIZARD", 0}, {"@WIZMOTD", NULL, cmd_wizmotd, CMD_T_ANY, "WIZARD", 0}, {"@ZEMIT", NULL, cmd_zemit, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"BUY", NULL, cmd_buy, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"BRIEF", NULL, cmd_brief, CMD_T_ANY, 0, 0}, {"DESERT", NULL, cmd_desert, CMD_T_PLAYER | CMD_T_THING, 0, 0}, {"DISMISS", NULL, cmd_dismiss, CMD_T_PLAYER | CMD_T_THING, 0, 0}, {"DROP", NULL, cmd_drop, CMD_T_PLAYER | CMD_T_THING, 0, 0}, {"EXAMINE", "ALL BRIEF DEBUG MORTAL PARENT", cmd_examine, CMD_T_ANY, 0, 0}, {"EMPTY", NULL, cmd_empty, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0}, {"ENTER", NULL, cmd_enter, CMD_T_ANY, 0, 0}, {"FOLLOW", NULL, cmd_follow, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0}, {"GET", NULL, cmd_get, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0}, {"GIVE", "SILENT", cmd_give, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"GOTO", NULL, cmd_goto, CMD_T_PLAYER | CMD_T_THING, 0, 0}, {"INVENTORY", NULL, cmd_inventory, CMD_T_ANY, 0, 0}, {"KILL", NULL, cmd_kill, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"LOOK", "OUTSIDE", cmd_look, CMD_T_ANY, 0, 0}, {"LEAVE", NULL, cmd_leave, CMD_T_PLAYER | CMD_T_THING, 0, 0}, {"PAGE", "BLIND NOEVAL LIST PORT OVERRIDE", cmd_page, CMD_T_ANY | CMD_T_RS_NOPARSE | CMD_T_NOPARSE | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"POSE", "NOEVAL NOSPACE", cmd_pose, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"SCORE", NULL, cmd_score, CMD_T_ANY, 0, 0}, {"SAY", "NOEVAL", cmd_say, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"SEMIPOSE", "NOEVAL", cmd_semipose, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"SLAY", NULL, cmd_slay, CMD_T_ANY, 0, 0}, {"TAKE", NULL, cmd_take, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0}, {"TEACH", NULL, cmd_teach, CMD_T_ANY | CMD_T_NOPARSE, 0, 0}, {"THINK", "NOEVAL", cmd_think, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"UNFOLLOW", NULL, cmd_unfollow, CMD_T_PLAYER | CMD_T_THING | CMD_T_NOGAGGED, 0, 0}, {"USE", NULL, cmd_use, CMD_T_ANY | CMD_T_NOGAGGED, 0, 0}, {"WHISPER", "LIST NOISY SILENT NOEVAL", cmd_whisper, CMD_T_ANY | CMD_T_EQSPLIT | CMD_T_NOGAGGED, 0, 0}, {"WITH", "NOEVAL ROOM", cmd_with, CMD_T_PLAYER | CMD_T_THING | CMD_T_EQSPLIT, 0, 0}, /* 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, 0, 0}, /* 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, 0, 0}, /* A way to let people override the Huh? message */ {"HUH_COMMAND", NULL, cmd_huh_command, CMD_T_ANY | CMD_T_NOPARSE | CMD_T_INTERNAL, 0, 0}, {NULL, NULL, NULL, 0, 0, 0} }; /* 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}, {"nofixed", CMD_T_NOFIXED}, {"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 i = 0; 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) { i++; if (SW_ISSET(cmd->sw, i) && (strncmp(sw_val->name, sw, len) == 0)) return i; 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, object_flag_type flagmask, object_flag_type powers, switch_mask *sw, command_func func) { 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->flagmask = flagmask; cmd->powers = powers; 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 flagstr space-separated list of flags sufficient to use command. * \param powerstr space-separated list of powers sufficient to use command. * \param switchstr space-separated list of switches for the command. * \param func function to call when command is executed. * \return pointer to a newly allocated COMMAND_INFO entry or NULL. */ COMMAND_INFO * command_add(const char *name, int type, const char *flagstr, const char *powerstr, const char *switchstr, command_func func) { object_flag_type flagmask = NULL, powers = NULL; switch_mask *sw = switchmask(switchstr); if (flagstr) flagmask = string_to_bits("FLAG", flagstr); if (powerstr) powers = string_to_bits("POWER", powerstr); ptab_start_inserts(&ptab_command); ptab_insert(&ptab_command, name, make_command(name, type, flagmask, powers, sw, func)); 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 NULL 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, object_flag_type flagmask, object_flag_type powers, switch_mask *sw, command_func func) { COMMAND_INFO *cmd; cmd = command_find(name); if (!cmd) return NULL; if (type != -1) cmd->type = type; if (flagmask) cmd->flagmask = flagmask; if (powers) cmd->powers = powers; 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, string_to_bits("FLAG", cmd->flagstr), string_to_bits("POWER", cmd->powers), switchmask(cmd->switches), cmd->func)); } 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 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, cause, 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, 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); 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; 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; } 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)) { /* This is a "#obj foo" force style * We set noevtoken to keep its noeval way, and * set the cmd to allow @hook. */ replacer = "@FORCE"; noevtoken = 1; } } 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, cause, 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, cause, cause, noevtoken ? PE_NOTHING : ((PE_DEFAULT & ~PE_FUNCTION_CHECK) | PE_COMMAND_BRACES), PT_DEFAULT, NULL); } *c2 = '\0'; command_parse_free_args; return commandraw; } /* Check the permissions */ if (!command_check(player, cmd)) { command_parse_free_args; return NULL; } /* 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); /* 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, cause, &p, ls, lsa, cmd, 0, 0); command_argparse(player, 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, 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, cause, &p, ls, lsa, cmd, 0, 0); } command_argparse(player, cause, &p, rs, rsa, cmd, 1, noeval); } else { command_argparse(player, 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, cause, cause, noevtoken ? PE_NOTHING : ((PE_DEFAULT & ~PE_EVALUATE) | 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); 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) { 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); 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 royalty or wizards * wizard can only be used by wizards * 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; FLAG *mask; FLAG *powers; 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; } /* Gah. I love backwards compatiblity. */ if (!strcasecmp(restriction, "admin")) { FLAG *roy = match_flag("ROYALTY"); FLAG *wiz = match_flag("WIZARD"); if (clear && command->flagmask) { clear_flag_bitmask(command->flagmask, roy->bitpos); clear_flag_bitmask(command->flagmask, wiz->bitpos); } else { if (!command->flagmask) command->flagmask = new_flag_bitmask("FLAG"); set_flag_bitmask(command->flagmask, roy->bitpos); set_flag_bitmask(command->flagmask, wiz->bitpos); } } else if ((c = ptab_find(&ptab_command_perms, restriction))) { if (clear) command->type &= ~c->type; else command->type |= c->type; } else if ((mask = match_flag(restriction))) { if (clear && command->flagmask) clear_flag_bitmask(command->flagmask, mask->bitpos); else { if (!command->flagmask) command->flagmask = new_flag_bitmask("FLAG"); set_flag_bitmask(command->flagmask, mask->bitpos); } } else if ((powers = match_power(restriction))) { if (clear && command->powers) clear_flag_bitmask(command->powers, powers->bitpos); else { if (!command->powers) command->powers = new_flag_bitmask("POWER"); set_flag_bitmask(command->powers, powers->bitpos); } } restriction = tp; } 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) { 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, NULL, 0, (flags & CMD_T_NOPARSE ? NULL : "NOEVAL"), cmd_unimplemented); 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 (Wizard(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 (Wizard(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_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"); } *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_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(player, show_command_flags(command->flagmask, command->powers)); 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 (Wizard(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); } do_gensort((dbref) 0, (char **) ptrs, nptrs, ALPHANUM_LIST); bp = buff; safe_str(ptrs[0], buff, &bp); for (i = 1; i < nptrs; i++) { if (gencomp ((dbref) 0, (char *) ptrs[i], (char *) ptrs[i - 1], ALPHANUM_LIST) > 0) { 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) { int ok; char *mess = NULL; int check_flags, check_powers; /* 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; } if ((cmd->type & CMD_T_NOGUEST) && Guest(player)) { mess = T("Guests cannot do that."); goto send_error; } if ((cmd->type & CMD_T_GOD) && (!God(player))) { mess = T("Only God can do that."); goto send_error; } 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; 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; check_flags = cmd->flagmask && !null_flagmask("FLAG", cmd->flagmask); check_powers = cmd->powers && !null_flagmask("POWER", cmd->powers); if (check_flags) ok = !!(has_any_flags_by_mask(player, cmd->flagmask)); if (!ok && check_powers) ok = !!(has_any_powers_by_mask(player, cmd->powers)); if (!ok) { 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); } 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); } }