/*! \file predicates.cpp * \brief Miscellaneous commands and functions. * * In theory, most of these functions could plausibly be called * "predicates", either because they determine some boolean property * of the input, or because they perform some action that makes them * verb-like. In practice, this is a miscellany. */ #include "copyright.h" #include "autoconf.h" #include "config.h" #include "externs.h" #include "mux_table.h" extern "C" { #include "color_ops.h" } static inline const UTF8 *utf8_advance_predicate(const UTF8 *p) { if (nullptr == p || '\0' == *p) { return p; } size_t n = utf8_FirstByte[static_cast(*p)]; if (n < 1 || n >= UTF8_CONTINUE) { return p + 1; } for (size_t i = 1; i < n; i++) { if ( '\0' == p[i] || UTF8_CONTINUE != utf8_FirstByte[static_cast(p[i])]) { return p + 1; } } return p + n; } #include "ganl_stub.h" /* --------------------------------------------------------------------------- * insert_first, remove_first: Insert or remove objects from lists. */ dbref insert_first(dbref head, dbref thing) { s_Next(thing, head); return thing; } dbref remove_first(dbref head, dbref thing) { if (head == thing) { return Next(thing); } dbref prev; DOLIST(prev, head) { if (Next(prev) == thing) { s_Next(prev, Next(thing)); return head; } } return head; } /* --------------------------------------------------------------------------- * reverse_list: Reverse the order of members in a list. */ dbref reverse_list(dbref list) { dbref newlist, rest; newlist = NOTHING; while (list != NOTHING) { rest = Next(list); s_Next(list, newlist); newlist = list; list = rest; } return newlist; } /* --------------------------------------------------------------------------- * member - indicate if thing is in list */ bool member(dbref thing, dbref list) { DOLIST(list, list) { if (list == thing) { return true; } } return false; } bool could_doit(dbref player, dbref thing, int locknum) { if (thing == HOME) { return true; } // If nonplayer tries to get key, then no. // if ( !isPlayer(player) && Key(thing)) { return false; } if (Pass_Locks(player)) { return true; } dbref aowner; int aflags; LBuf key = LBuf_Adopt(atr_get("could_doit.134", thing, locknum, &aowner, &aflags)); bool doit = eval_boolexp_atr(player, thing, thing, key); return doit; } bool can_see(dbref player, dbref thing, bool can_see_loc) { // Don't show if all the following apply: Sleeping players should not be // seen. The thing is a disconnected player. The player is not a // puppet. // if ( mudconf.dark_sleepers && isPlayer(thing) && !Connected(thing) && !Puppet(thing)) { return false; } // You don't see yourself or exits. // if ( player == thing || isExit(thing)) { return false; } // To be visible, light must come from either the location (can_see_loc) // or the object itself (Light(thing)). This light is then blocked // by the object itself being dark (it blocked its own light), by not // passing the visibility lock, or by being in a different reality. // // The exception to the above is mudconf.see_own_dark which allows a // myopic self-examination. // return ( ( ( can_see_loc || Light(thing)) && !Dark(thing) #ifdef REALITY_LVLS && IsReal(player, thing) #endif // REALITY_LVLS && could_doit(player, thing, A_LVISIBLE)) || ( mudconf.see_own_dark && MyopicExam(player, thing))); } // Read A_RQUOTA as the 32-bit quantity the rest of the quota system treats // it as. // // The quota domain is 32-bit by construction everywhere else: pay_quota and // mung_quotas hold it in int, do_quota parses into int, destroy_obj's refund // is int, and both writers format into a UTF8[I32BUF_SIZE]. But the stored // attribute is just text, and mux_atoi64 will parse whatever is in it -- so a // corrupted or hand-crafted A_RQUOTA delivers a value no caller can represent. // // Widening the callers instead was the alternative, and is wrong: it would // make one writer able to store a value every other reader still truncates, // which converts a visible error into silent drift. Clamping keeps the // domain honest and identical on LP64 and LLP64 (#1408). // static int quota_from_attr(const UTF8 *pQuota) { int64_t q = mux_atoi64(pQuota); if (q > INT32_MAX) { return INT32_MAX; } if (q < INT32_MIN) { return INT32_MIN; } return static_cast(q); } // Add two quota quantities without overflowing int. Both operands are // already in range; their sum need not be. // static int quota_add(int a, int b) { int64_t sum = static_cast(a) + static_cast(b); if (sum > INT32_MAX) { return INT32_MAX; } if (sum < INT32_MIN) { return INT32_MIN; } return static_cast(sum); } static bool pay_quota(dbref who, int cost) { // If no cost, succeed // if (cost <= 0) { return true; } // determine quota // dbref aowner; int aflags; LBuf quota_str = LBuf_Adopt(atr_get("pay_quota.200", Owner(who), A_RQUOTA, &aowner, &aflags)); int quota = quota_from_attr(quota_str); // enough to build? Wizards always have enough. // quota = quota_add(quota, -cost); if ( quota < 0 && !Free_Quota(who) && !Free_Quota(Owner(who))) { return false; } // Dock the quota. // UTF8 buf[I32BUF_SIZE]; mux_ltoa(quota, buf); atr_add_raw(Owner(who), A_RQUOTA, buf); return true; } bool canpayfees(dbref player, dbref who, int pennies, int quota) { if ( !Wizard(who) && !Wizard(Owner(who)) && !Free_Money(who) && !Free_Money(Owner(who)) && (Pennies(Owner(who)) < pennies)) { if (player == who) { notify(player, tprintf(M_("Sorry, you don’t have enough %s."), mudconf.many_coins)); } else { notify(player, tprintf(M_("Sorry, that player doesn’t have enough %s."), mudconf.many_coins)); } return false; } if (mudconf.quotas) { if (!pay_quota(who, quota)) { if (player == who) { notify(player, M_("Sorry, your building contract has run out.")); } else { notify(player, M_("Sorry, that player’s building contract has run out.")); } return false; } } payfor(who, pennies); return true; } bool payfor(dbref who, int cost) { if ( Wizard(who) || Wizard(Owner(who)) || Free_Money(who) || Free_Money(Owner(who))) { return true; } who = Owner(who); int tmp; if ((tmp = Pennies(who)) >= cost) { s_Pennies(who, tmp - cost); return true; } return false; } void add_quota(dbref who, int payment) { dbref aowner; int aflags; UTF8 buf[I32BUF_SIZE]; LBuf quota = LBuf_Adopt(atr_get("add_quota.288", who, A_RQUOTA, &aowner, &aflags)); // mux_ltoa takes long, so passing the int64_t from mux_atoi64 directly // narrowed it -- a no-op on LP64, but on LLP64 the high word was dropped // and a stored 8589934592 came back as 1 (#1408). // mux_ltoa(quota_add(quota_from_attr(quota), payment), buf); atr_add_raw(who, A_RQUOTA, buf); } void giveto(dbref who, int pennies) { if ( Wizard(who) || Wizard(Owner(who)) || Free_Money(who) || Free_Money(Owner(who))) { return; } who = Owner(who); s_Pennies(who, Pennies(who) + pennies); } // Every character in the name must be allowed by one of the character sets mentioned. // If no character sets are mentions, everything is allowed. // bool IsRestricted(const UTF8 *pName, int charset) { if (0 == charset) { return false; } while ('\0' != pName[0]) { bool bAllowed = false; if ( (ALLOW_CHARSET_ASCII & charset) && (0x80 & pName[0]) == 0) { bAllowed = true; } else if ( (ALLOW_CHARSET_8859_1 & charset) && mux_is8859_1(pName)) { bAllowed = true; } else if ( (ALLOW_CHARSET_8859_2 & charset) && mux_is8859_2(pName)) { bAllowed = true; } else if ( (ALLOW_CHARSET_HANGUL & charset) && mux_ishangul(pName)) { bAllowed = true; } else if ( (ALLOW_CHARSET_HIRAGANA & charset) && mux_ishiragana(pName)) { bAllowed = true; } else if ( (ALLOW_CHARSET_KANJI & charset) && mux_iskanji(pName)) { bAllowed = true; } else if ( (ALLOW_CHARSET_KATAKANA & charset) && mux_iskatakana(pName)) { bAllowed = true; } if (!bAllowed) { return true; } pName = utf8_advance_predicate(pName); } return false; } // The following function validates that the object names (which will be // used for things and rooms, but not for players or exits) and generates // a canonical form of that name (with optimized ANSI). // UTF8 *MakeCanonicalObjectName(const UTF8 *pName, size_t *pnName, bool *pbValid, int charset) { thread_local UTF8 Buf[MBUF_SIZE]; *pnName = 0; *pbValid = false; if (!pName) { return nullptr; } // Build up what the real name would be. If we pass all the // checks, this is what we will return as a result. // mux_field fldLen = StripTabsAndTruncate(pName, Buf, MBUF_SIZE-1, MBUF_SIZE-1); // Disallow pure ANSI names. There must be at least -something- // visible. // if (0 == fldLen.m_column) { return nullptr; } // Get the stripped version (Visible parts without color info). // size_t nStripped; const UTF8 *pStripped = strip_color(Buf, &nStripped); // Do not allow LOOKUP_TOKEN, NUMBER_TOKEN, NOT_TOKEN, or SPACE // as the first character, or SPACE as the last character // if ( reinterpret_cast(strchr(reinterpret_cast("*!#"), pStripped[0])) || mux_isspace(pStripped[0]) || mux_isspace(pStripped[nStripped-1])) { return nullptr; } // Only printable characters besides ARG_DELIMITER, AND_TOKEN, // and OR_TOKEN are allowed. // const UTF8 *p = pStripped; while ('\0' != *p) { if (!mux_isobjectname(p)) { return nullptr; } p = utf8_advance_predicate(p); } // Special names are specifically dis-allowed. // if ( (nStripped == 2 && memcmp("me", pStripped, 2) == 0) || (nStripped == 4 && ( memcmp("home", pStripped, 4) == 0 || memcmp("here", pStripped, 4) == 0))) { return nullptr; } if (IsRestricted(pStripped, charset)) { return nullptr; } // Reject names that would be silently truncated. // size_t nOrigStripped; strip_color(pName, &nOrigStripped); if (nOrigStripped > nStripped) { return nullptr; } *pnName = fldLen.m_byte; *pbValid = true; return Buf; } // The following function validates exit names. // UTF8 *MakeCanonicalExitName(const UTF8 *pName, size_t *pnName, bool *pbValid) { thread_local UTF8 Buf[MBUF_SIZE]; *pnName = 0; *pbValid = false; if (!pName) { return nullptr; } mux_strncpy(Buf, pName, MBUF_SIZE - 1); // Sanitize the input before processing. // string_token st(Buf, T(";")); // Break the exitname down into semi-colon-separated segments. The first // segment can contain color as it is used for showing the exit, but the // remaining segments are stripped of color. A valid exitname requires // at least one (display) segment. // UTF8 *ptr; UTF8 clean_buf[MBUF_SIZE]; UTF8 *bp_clean = clean_buf; bool bHaveDisplay = false; for (ptr = st.parse(); ptr; ptr = st.parse()) { UTF8 *pTrimmedSegment = nullptr; if (bHaveDisplay) { // No color allowed in segments after the first one. // UTF8 *pNoColor = strip_color(ptr); pTrimmedSegment = trim_spaces(pNoColor); } else { // Color allowed in first segment. // pTrimmedSegment = trim_spaces(ptr); } // Ignore segments which contained nothing but spaces. // if ('\0' != pTrimmedSegment[0]) { bool valid = false; size_t len = 0; UTF8 *pValidSegment = MakeCanonicalObjectName(pTrimmedSegment, &len, &valid, mudconf.exit_name_charset); if (valid) { if (bHaveDisplay) { safe_mb_chr_ascii(';', clean_buf, &bp_clean); safe_mb_str(pValidSegment, clean_buf, &bp_clean); } else { safe_mb_str(pValidSegment, clean_buf, &bp_clean); bHaveDisplay = true; } } } free_lbuf(pTrimmedSegment); pTrimmedSegment = nullptr; } *bp_clean = '\0'; *pbValid = bHaveDisplay; if (!bHaveDisplay) { *pnName = 0; return Buf; } mux_strncpy(Buf, clean_buf, MBUF_SIZE - 1); *pnName = mux_strlen(Buf); return Buf; } // The following function validates the player name. ANSI is not // allowed in player names. However, a player name must satisfy // the requirements of a regular name as well. // bool ValidatePlayerName(const UTF8 *pName) { if (!pName) { return false; } size_t nName = strlen(reinterpret_cast(pName)); // Verify that name is not empty, but not too long, either. // if ( nName <= 0 || PLAYER_NAME_LIMIT <= nName) { return false; } // Do not allow LOOKUP_TOKEN, NUMBER_TOKEN, NOT_TOKEN, or SPACE // as the first character, or SPACE as the last character // if ( reinterpret_cast(strchr(reinterpret_cast("*!#"), pName[0])) || mux_isspace(pName[0]) || mux_isspace(pName[nName-1])) { return false; } // Only printable characters besides ARG_DELIMITER, AND_TOKEN, // and OR_TOKEN are allowed. // if ( mudstate.bStandAlone || mudconf.name_spaces) { const UTF8 *p = pName; while ('\0' != *p) { if ( !mux_isplayername(p) && ' ' != *p) { return false; } p = utf8_advance_predicate(p); } } else { const UTF8 *p = pName; while ('\0' != *p) { if (!mux_isplayername(p)) { return false; } p = utf8_advance_predicate(p); } } // Special names are specifically dis-allowed. // if ( (nName == 2 && memcmp("me", pName, 2) == 0) || (nName == 4 && ( memcmp("home", pName, 4) == 0 || memcmp("here", pName, 4) == 0))) { return false; } if (IsRestricted(pName, mudconf.player_name_charset)) { return false; } return true; } bool ok_password(const UTF8 *password, const UTF8 **pmsg) { *pmsg = nullptr; if (*password == '\0') { *pmsg = T("Null passwords are not allowed."); return false; } int num_upper = 0; int num_special = 0; int num_lower = 0; const UTF8 *scan = password; for ( ; *scan; scan = utf8_advance_predicate(scan)) { if ( !mux_isprint(scan) || mux_isspace(*scan)) { *pmsg = T("Illegal character in password."); return false; } if (mux_isupper_ascii(*scan)) { num_upper++; } else if (mux_islower_ascii(*scan)) { num_lower++; } else if ( *scan != '\'' && *scan != '-') { num_special++; } } if ( !mudstate.bStandAlone && mudconf.safer_passwords) { if (num_upper < 1) { *pmsg = T("The password must contain at least one capital letter."); return false; } if (num_lower < 1) { *pmsg = T("The password must contain at least one lowercase letter."); return false; } if (num_special < 1) { *pmsg = T("The password must contain at least one number or a symbol other than the apostrophe or dash."); return false; } } return true; } /* --------------------------------------------------------------------------- * handle_ears: Generate the 'grows ears' and 'loses ears' messages. */ void handle_ears(dbref thing, bool could_hear, bool can_hear) { if (could_hear != can_hear) { LBuf buf = LBuf_Src("handle_ears"); UTF8 *bp = buf.get(); // Moniker returns PUA-encoded name. // const UTF8 *name = Moniker(thing); if (isExit(thing)) { // Truncate at first semicolon. // const UTF8 *semi = (const UTF8 *)strchr((const char *)name, ';'); if (semi) { safe_copy_buf(name, semi - name, buf, &bp); } else { safe_str(name, buf, &bp); } } else { safe_str(name, buf, &bp); } const PRONOUN_SET *pg = get_pronoun_set(thing); if (can_hear) { safe_tprintf_str(buf, &bp, T(" grow%s ears and can now hear."), pg->plural ? "" : "s"); } else { safe_tprintf_str(buf, &bp, T(" lose%s %s ears and become%s deaf."), pg->plural ? "" : "s", pg->possessive, pg->plural ? "" : "s"); } *bp = '\0'; notify_check(thing, thing, buf, MSG_ME | MSG_NBR | MSG_LOC | MSG_INV); } } // For lack of better place the @switch code is here. // void do_switch ( dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *expr, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs ) { if ( !expr || nargs <= 0) { return; } bool bMatchOne; switch (key & SWITCH_MASK) { case SWITCH_DEFAULT: if (mudconf.switch_df_all) { bMatchOne = false; } else { bMatchOne = true; } break; case SWITCH_ANY: bMatchOne = false; break; case SWITCH_ONE: default: bMatchOne = true; break; } // Now try a wild card match of buff with stuff in coms. // bool bAny = false; int a; LBuf buff = LBuf_Src("do_switch"); UTF8 *bp = buff.get(); CLinearTimeAbsolute lta; for ( a = 0; ( !bMatchOne || !bAny) && a < nargs - 1 && args[a] && args[a + 1]; a += 2) { bp = buff; mux_exec(args[a], LBUF_SIZE-1, buff, &bp, executor, caller, enactor, eval|EV_FCHECK|EV_EVAL|EV_TOP, cargs, ncargs); *bp = '\0'; if (wild_match(buff, expr)) { const UTF8 *save_switch = mudstate.switch_token; mudstate.switch_token = expr; if (key & SWITCH_NOW) { process_command(executor, caller, enactor, eval, false, args[a+1], cargs, ncargs); } else { { // Preserve enclosing iter context (## from @dolist) // so it survives into the queued @switch body. // Respect safer_iter — same guard as @dolist uses. // const UTF8 *iter_tok = nullptr; int iter_num = 0; if (!mudconf.safer_iter) { int iLoop = mudstate.in_loop - 1; if ( 0 <= iLoop && iLoop < MAX_ITEXT && mudstate.itext[iLoop]) { iter_tok = mudstate.itext[iLoop]; iter_num = mudstate.inum[iLoop]; } } wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0, args[a+1], ncargs, cargs, mudstate.global_regs, nullptr, // named_sargs iter_tok, iter_num, // iter context expr); // switch_token } } mudstate.switch_token = save_switch; bAny = true; } } if ( a < nargs && !bAny && args[a]) { const UTF8 *save_switch = mudstate.switch_token; mudstate.switch_token = expr; if (key & SWITCH_NOW) { process_command(executor, caller, enactor, eval, false, args[a], cargs, ncargs); } else { { const UTF8 *iter_tok = nullptr; int iter_num = 0; if (!mudconf.safer_iter) { int iLoop = mudstate.in_loop - 1; if ( 0 <= iLoop && iLoop < MAX_ITEXT && mudstate.itext[iLoop]) { iter_tok = mudstate.itext[iLoop]; iter_num = mudstate.inum[iLoop]; } } wait_que(executor, caller, enactor, eval, false, lta, NOTHING, 0, args[a], ncargs, cargs, mudstate.global_regs, nullptr, // named_sargs iter_tok, iter_num, // iter context expr); // switch_token } } mudstate.switch_token = save_switch; } if (key & SWITCH_NOTIFY) { LBuf tbuf = LBuf_Src("switch.notify_cmd"); mux_strncpy(tbuf, T("@notify/quiet me"), LBUF_SIZE-1); wait_que(executor, caller, enactor, eval, false, lta, NOTHING, A_SEMAPHORE, tbuf, ncargs, cargs, mudstate.global_regs); } } // Also for lack of better place the @ifelse code is here. // Idea for @ifelse from ChaoticMUX. // void do_if ( dbref player, dbref caller, dbref enactor, int eval, int key, UTF8 *expr, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(key); if ( !expr || nargs <= 0) { return; } CLinearTimeAbsolute lta; LBuf buff = LBuf_Src("do_if"); UTF8 *bp = buff.get(); mux_exec(expr, LBUF_SIZE-1, buff, &bp, player, caller, enactor, eval|EV_FCHECK|EV_EVAL|EV_TOP, cargs, ncargs); *bp = '\0'; int a = !xlate(buff); if (a < nargs) { wait_que(player, caller, enactor, eval, false, lta, NOTHING, 0, args[a], ncargs, cargs, mudstate.global_regs); } } void do_addcommand ( dbref player, dbref caller, dbref enactor, int eval, int key, int nargs, UTF8 *name, UTF8 *command, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); bool bNoEval = (key & ADDCMD_NOEVAL) != 0; // Validate command name. // thread_local UTF8 pName[LBUF_SIZE]; if (1 <= nargs) { // Strip color, then strip \r\n\t and space. // { size_t nRaw = strlen(reinterpret_cast(name)); unsigned char plain[LBUF_SIZE]; size_t nPlain = co_strip_color(plain, reinterpret_cast(name), nRaw); size_t j = 0; for (size_t i = 0; i < nPlain; i++) { if (plain[i] != '\r' && plain[i] != '\n' && plain[i] != '\t' && plain[i] != ' ') { pName[j++] = plain[i]; } } pName[j] = '\0'; } { size_t nPlain = strlen(reinterpret_cast(pName)); LBuf tmp = LBuf_Src("ok_name"); nPlain = co_tolower(tmp, pName, nPlain); memcpy(pName, tmp, nPlain + 1); } } if ( 0 == nargs || '\0' == pName[0] || ( pName[0] == '_' && pName[1] == '_')) { notify(player, M_("That is not a valid command name.")); return; } // Validate object/attribute. // dbref thing; ATTR *pattr; if ( !parse_attrib(player, command, &thing, &pattr) || !pattr) { notify(player, M_("No such attribute.")); return; } if (!See_attr(player, thing, pattr)) { notify(player, NOPERM_MESSAGE); return; } size_t nName = strlen(reinterpret_cast(pName)); auto it_old = mudstate.command_htab.find(std::vector(pName, pName + nName)); CMDENT *old = (it_old != mudstate.command_htab.end()) ? static_cast(it_old->second) : nullptr; CMDENT *cmd; if ( old && (old->callseq & CS_ADDED)) { // Don't allow the same (thing,atr) in the list. // for (auto &entry : *old->addent) { if ( entry.thing == thing && entry.atr == pattr->number) { notify(player, tprintf(M_("%s already added."), pName)); return; } } // Otherwise, add another (thing,atr) to the list. // old->addent->push_back(ADDENT{}); ADDENT &add = old->addent->back(); add.thing = thing; add.atr = pattr->number; add.name = reinterpret_cast(pName); if (bNoEval) { old->callseq |= CS_NOINTERP; } } else { if (old) { // Delete the old built-in (which will later be added back as // __name). // mudstate.command_htab.erase(std::vector(pName, pName + nName)); } cmd = nullptr; try { cmd = new CMDENT; } catch (...) { ; // Nothing. } if (nullptr == cmd) { notify(player, OUT_OF_MEMORY); return; } cmd->cmdname = StringClone(pName); cmd->switches = nullptr; cmd->perms = 0; cmd->extra = 0; if ( old && (old->callseq & CS_LEADIN)) { cmd->callseq = CS_ADDED|CS_ONE_ARG|CS_LEADIN; } else { cmd->callseq = CS_ADDED|CS_ONE_ARG; } if (bNoEval) { cmd->callseq |= CS_NOINTERP; } cmd->flags = CEF_ALLOC; cmd->addent = new std::vector(); cmd->addent->push_back(ADDENT{}); ADDENT &add = cmd->addent->back(); add.thing = thing; add.atr = pattr->number; add.name = reinterpret_cast(pName); mudstate.command_htab.emplace(std::vector(pName, pName + nName), cmd); if ( old && strcmp(reinterpret_cast(pName), reinterpret_cast(old->cmdname)) == 0) { // We are @addcommand'ing over a built-in command by its // unaliased name, therefore, we want to re-target all the // aliases. // UTF8 *p = tprintf(T("__%s"), pName); size_t nP = strlen(reinterpret_cast(p)); mudstate.command_htab.erase(std::vector(p, p + nP)); for (auto &[k, v] : mudstate.command_htab) { if (v == old) v = cmd; } mudstate.command_htab.emplace(std::vector(p, p + nP), old); } } // We reset the one letter commands here so you can overload them. // cache_prefix_cmds(); notify(player, tprintf(M_("Command %s added."), pName)); } void do_listcommands(dbref player, dbref caller, dbref enactor, int eval, int key, UTF8 *name, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); CMDENT *old; bool didit = false; // Let's make this case insensitive... // size_t nCased; UTF8 *pCased = mux_strlwr(name, nCased); if (*pCased) { { auto it = mudstate.command_htab.find(std::vector(pCased, pCased + nCased)); old = (it != mudstate.command_htab.end()) ? static_cast(it->second) : nullptr; } if ( old && (old->callseq & CS_ADDED)) { // If it's already found in the hash table, and it's being added // using the same object and attribute... // for (auto &entry : *old->addent) { ATTR *ap = reinterpret_cast(atr_num(entry.atr)); const UTF8 *pName = T("(WARNING: Bad Attribute Number)"); if (ap) { pName = ap->name; } notify(player, tprintf(T("%s: #%d/%s"), reinterpret_cast(entry.name.c_str()), entry.thing, pName)); } } else { notify(player, tprintf(M_("%s not found in command table."), pCased)); } return; } else { for (auto &[key, val] : mudstate.command_htab) { old = static_cast(val); if (old->callseq & CS_ADDED) { const UTF8 *pKeyName = key.data(); int nKeyName = static_cast(key.size()); for (auto &entry : *old->addent) { if ( static_cast(nKeyName) != entry.name.size() || memcmp(pKeyName, entry.name.c_str(), nKeyName) != 0) { continue; } ATTR *ap = reinterpret_cast(atr_num(entry.atr)); const UTF8 *pName = T("(WARNING: Bad Attribute Number)"); if (ap) { pName = ap->name; } notify(player, tprintf(T("%s: #%d/%s"), reinterpret_cast(entry.name.c_str()), entry.thing, pName)); didit = true; } } } } if (!didit) { notify(player, M_("No added commands found in command table.")); } } void do_delcommand ( dbref player, dbref caller, dbref enactor, int eval, int key, int nargs, UTF8 *name, UTF8 *command, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if (!*name) { notify(player, M_("Sorry.")); return; } dbref thing = NOTHING; int atr = NOTHING; ATTR *pattr; if (*command) { if ( !parse_attrib(player, command, &thing, &pattr) || !pattr) { notify(player, M_("No such attribute.")); return; } if (!See_attr(player, thing, pattr)) { notify(player, NOPERM_MESSAGE); return; } atr = pattr->number; } // Let's make this case insensitive... // size_t nCased; UTF8 *pCased = mux_strlwr(name, nCased); CMDENT *old, *cmd; { auto it = mudstate.command_htab.find(std::vector(pCased, pCased + nCased)); old = (it != mudstate.command_htab.end()) ? static_cast(it->second) : nullptr; } if ( old && (old->callseq & CS_ADDED)) { UTF8 *p__Name = tprintf(T("__%s"), pCased); size_t n__Name = strlen(reinterpret_cast(p__Name)); if (command[0] == '\0') { // Delete all @addcommand'ed associations with the given name. // delete old->addent; old->addent = nullptr; mudstate.command_htab.erase(std::vector(pCased, pCased + nCased)); { auto it_cmd = mudstate.command_htab.find(std::vector(p__Name, p__Name + n__Name)); cmd = (it_cmd != mudstate.command_htab.end()) ? static_cast(it_cmd->second) : nullptr; } if (cmd) { size_t nCmdName = strlen(reinterpret_cast(cmd->cmdname)); mudstate.command_htab.emplace(std::vector(cmd->cmdname, cmd->cmdname + nCmdName), cmd); if (strcmp(reinterpret_cast(pCased), reinterpret_cast(cmd->cmdname)) != 0) { mudstate.command_htab.emplace(std::vector(pCased, pCased + nCased), cmd); } mudstate.command_htab.erase(std::vector(p__Name, p__Name + n__Name)); mudstate.command_htab.emplace(std::vector(p__Name, p__Name + n__Name), cmd); for (auto &[k, v] : mudstate.command_htab) { if (v == old) v = cmd; } } else { // No backup command to restore. Remove all hash // entries that still reference 'old'. // mudstate.command_htab.erase(std::vector(p__Name, p__Name + n__Name)); for (auto it2 = mudstate.command_htab.begin(); it2 != mudstate.command_htab.end(); ) { if (it2->second == old) { it2 = mudstate.command_htab.erase(it2); } else { ++it2; } } } MEMFREE(old->cmdname); old->cmdname = nullptr; MEMFREE(old); old = nullptr; cache_prefix_cmds(); notify(player, M_("Done.")); } else { // Remove only the (name,thing,atr) association. // for (auto it = old->addent->begin(); it != old->addent->end(); ++it) { if ( it->thing == thing && it->atr == atr) { old->addent->erase(it); if (old->addent->empty()) { delete old->addent; old->addent = nullptr; mudstate.command_htab.erase(std::vector(pCased, pCased + nCased)); { auto it_cmd2 = mudstate.command_htab.find(std::vector(p__Name, p__Name + n__Name)); cmd = (it_cmd2 != mudstate.command_htab.end()) ? static_cast(it_cmd2->second) : nullptr; } if (cmd) { size_t nCmdName = strlen(reinterpret_cast(cmd->cmdname)); mudstate.command_htab.emplace(std::vector(cmd->cmdname, cmd->cmdname + nCmdName), cmd); if (strcmp(reinterpret_cast(pCased), reinterpret_cast(cmd->cmdname)) != 0) { mudstate.command_htab.emplace(std::vector(pCased, pCased + nCased), cmd); } mudstate.command_htab.erase(std::vector(p__Name, p__Name + n__Name)); mudstate.command_htab.emplace(std::vector(p__Name, p__Name + n__Name), cmd); for (auto &[k, v] : mudstate.command_htab) { if (v == old) v = cmd; } } MEMFREE(old->cmdname); old->cmdname = nullptr; MEMFREE(old); old = nullptr; } cache_prefix_cmds(); notify(player, M_("Done.")); return; } } notify(player, M_("Command not found in command table.")); } } else { notify(player, M_("Command not found in command table.")); } } void do_quitprog(dbref player, dbref caller, dbref enactor, int eval, int key, UTF8 *name, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); dbref doer; if (*name) { doer = match_thing(player, name); } else { doer = player; } if ( !( Prog(player) || Prog(Owner(player))) && player != doer) { notify(player, NOPERM_MESSAGE); return; } if ( !Good_obj(doer) || !isPlayer(doer)) { notify(player, M_("That is not a player.")); return; } if (!Connected(doer)) { notify(player, M_("That player is not connected.")); return; } if (!player_has_program(doer)) { notify(player, M_("Player is not in an @program.")); return; } program_data* program = detach_player_program(doer); if (program) { for (auto& wait_reg : program->wait_regs) { if (wait_reg) { RegRelease(wait_reg); wait_reg = nullptr; } } NamedRegsClear(program->named_wait_regs); MEMFREE(program); program = nullptr; } atr_clr(doer, A_PROGCMD); notify(player, M_("@program cleared.")); notify(doer, M_("Your @program has been terminated.")); } void do_prog ( dbref player, dbref caller, dbref enactor, int eval, int key, int nargs, UTF8 *name, UTF8 *command, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); if ( !name || !*name) { notify(player, M_("No players specified.")); return; } const dbref doer = match_thing(player, name); if ( !( Prog(player) || Prog(Owner(player))) && player != doer) { notify(player, NOPERM_MESSAGE); return; } if ( !Good_obj(doer) || !isPlayer(doer)) { notify(player, M_("That is not a player.")); return; } if (!Connected(doer)) { notify(player, M_("That player is not connected.")); return; } // Check to see if the enactor already has an @prog input pending. // if (player_has_program(doer)) { notify(player, M_("Input already pending.")); return; } UTF8 *msg = command; const UTF8 *attrib = parse_to(&msg, ':', 1); if (msg && *msg) { notify(doer, msg); } dbref thing; ATTR *ap; if (!parse_attrib(player, attrib, &thing, &ap)) { notify(player, NOMATCH_MESSAGE); return; } if (ap) { dbref aowner; int aflags; int lev; dbref parent; UTF8 *pBuffer = nullptr; bool bFound = false; ITER_PARENTS(thing, parent, lev) { pBuffer = atr_get("do_prog.1405", parent, ap->number, &aowner, &aflags); if (pBuffer[0]) { bFound = true; break; } free_lbuf(pBuffer); } if (bFound) { if ( ( God(player) || !God(thing)) && See_attr(player, thing, ap)) { atr_add_raw(doer, A_PROGCMD, pBuffer); } else { notify(player, NOPERM_MESSAGE); free_lbuf(pBuffer); return; } free_lbuf(pBuffer); } else { notify(player, M_("Attribute not present on object.")); return; } } else { notify(player, M_("No such attribute.")); return; } const auto program = static_cast(MEMALLOC(sizeof(program_data))); if (nullptr == program) { notify(player, OUT_OF_MEMORY); return; } program->wait_enactor = player; for (int i = 0; i < MAX_GLOBAL_REGS; i++) { program->wait_regs[i] = mudstate.global_regs[i]; if (mudstate.global_regs[i]) { RegAddRef(mudstate.global_regs[i]); } } program->named_wait_regs = NamedRegsCopy(mudstate.named_regs); // Now, start waiting. // set_player_program(doer, program); send_prog_prompt(doer); } /* --------------------------------------------------------------------------- * do_restart: Restarts the game. */ void do_restart(dbref executor, dbref caller, dbref enactor, int eval, int key) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); if (!Can_SiteAdmin(executor)) { notify(executor, NOPERM_MESSAGE); return; } bool bDenied = false; #if defined(HAVE_WORKING_FORK) if (mudstate.dumping) { notify(executor, M_("Dumping. Please try again later.")); bDenied = true; } #endif // HAVE_WORKING_FORK if (!mudstate.bCanRestart) { notify(executor, M_("Server just started. Please try again in a few seconds.")); bDenied = true; } if (bDenied) { STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT"); log_text(T("Restart requested but not executed by ")); log_name(executor); ENDLOG; return; } #ifdef UNIX_SSL raw_broadcast(0, M_("GAME: Restart by %s, please wait. (All SSL connections will be dropped.)"), Moniker(Owner(executor))); #else raw_broadcast(0, M_("GAME: Restart by %s, please wait."), Moniker(Owner(executor))); #endif STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT"); log_text(T("Restart by ")); log_name(executor); ENDLOG; // Write restart.db BEFORE the teardown, and decline the restart if it // cannot be written (#2043). // // This used to happen after prepare_for_restart(), final_modules() and // CLOSE, i.e. after the point of no return -- so a failed write was // discovered when there was nothing left to return to, and the successor // exec'd into a game it could not serve: prepare_for_restart() detaches // the listener fds but leaves them open for the successor to adopt, so // without restart.db the successor collides with its own inheritance and // dies on "bind() failed: Address already in use". // // DumpRestartDb() records the listeners and closes the TLS/WebSocket // sessions itself, because the dump needs both done first. Those are the // only destructive steps that precede the decision, which is why the // decline below is not free and says so. // #if defined(HAVE_WORKING_FORK) if (!dump_restart_db()) { raw_broadcast(0, M_("GAME: Restart could not be prepared and has been cancelled. The game is still running.")); STARTLOG(LOG_ALWAYS, "WIZ", "RSTRT"); log_text(T("Restart CANCELLED: restart.db could not be written. Requested by ")); log_name(executor); ENDLOG; return; } #endif // HAVE_WORKING_FORK g_GanlAdapter.prepare_for_restart(); local_presync_database(); ServerEventsSinkNode *p = g_pServerEventsSinkListHead; while (nullptr != p) { p->pSink->presync_database(); p = p->pNext; } #if defined(STUB_SLAVE) final_stubslave(); #endif // STUB_SLAVE final_modules(); pcache_sync(); dump_database_internal(DUMP_I_RESTART); SYNC; CLOSE; #if defined(WINDOWS_PROCESSES) exit(12345678); #elif defined(UNIX_PROCESSES) // restart.db was written above, before the teardown, and the restart was // declined if it could not be (#2043). Dumping here as well would // re-serialise descriptors the teardown has since closed. Log.StopLogging(); // #2199: build argv, omitting any option whose value is empty. // // -c, -p and -e are all CLI_REQUIRED, so passing a bare flag makes the // successor log "Option 'x' requires an argument, but none was found." // That is what a restarted server used to do for BOTH -p and -e, every // time, because mudconf.pid_file and mudconf.log_dir were read here and // written nowhere. They are populated now, but a site that supplies // only one of the two would still collect a warning for the other on // every restart -- so skip what we do not have rather than pass "". // const char *argvRestart[9]; int argcRestart = 0; argvRestart[argcRestart++] = "netmux"; argvRestart[argcRestart++] = "-c"; argvRestart[argcRestart++] = reinterpret_cast(mudconf.config_file); if ( nullptr != mudconf.pid_file && '\0' != mudconf.pid_file[0]) { argvRestart[argcRestart++] = "-p"; argvRestart[argcRestart++] = reinterpret_cast(mudconf.pid_file); } if ( nullptr != mudconf.log_dir && '\0' != mudconf.log_dir[0]) { argvRestart[argcRestart++] = "-e"; argvRestart[argcRestart++] = reinterpret_cast(mudconf.log_dir); } argvRestart[argcRestart] = nullptr; execv("bin/netmux", const_cast(argvRestart)); mux_assert(false); #endif // UNIX_PROCESSES } /* --------------------------------------------------------------------------- * do_comment: Implement the @@ (comment) command. Very cpu-intensive :-) */ void do_comment(dbref executor, dbref caller, dbref enactor, int eval, int key) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); } void do_eval(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *arg1, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(executor); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(key); UNUSED_PARAMETER(arg1); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); } static dbref promote_dflt(dbref old, dbref new0) { if ( old == NOPERM || new0 == NOPERM) { return NOPERM; } if ( old == AMBIGUOUS || new0 == AMBIGUOUS) { return AMBIGUOUS; } return NOTHING; } dbref match_possessed(dbref player, dbref thing, const UTF8 *target, dbref dflt, bool check_enter) { // First, check normally. // if (Good_obj(dflt)) { return dflt; } // Didn't find it directly. Recursively do a contents check. // dbref result, result1; UTF8 *d1; const UTF8 *place, *s1, *temp; const UTF8 *start = target; while (*target) { // Fail if no ' characters. // place = target; target = reinterpret_cast(strchr(reinterpret_cast(place), '\'')); if ( target == nullptr || !*target) { return dflt; } // If string started with a ', skip past it // if (place == target) { target++; continue; } // If next character is not an s or a space, skip past // temp = target++; if (!*target) { return dflt; } if ( *target != 's' && *target != 'S' && *target != ' ') { continue; } // If character was not a space make sure the following character is // a space. // if (*target != ' ') { target++; if (!*target) { return dflt; } if (*target != ' ') { continue; } } // Copy the container name to a new buffer so we can terminate it. // { LBuf buff = LBuf_Src("is_posess"); d1 = buff.get(); for (s1 = start; *s1 && (s1 < temp); *d1++ = (*s1++)) { ; // Nothing. } *d1 = '\0'; // Look for the container here and in our inventory. Skip past if we // can't find it. // init_match(thing, buff, NOTYPE); if (player == thing) { match_neighbor(); match_possession(); } else { match_possession(); } result1 = match_result(); } if (!Good_obj(result1)) { dflt = promote_dflt(dflt, result1); continue; } // If we don't control it and it is either dark or opaque, skip past. // bool control = Controls(player, result1); if ( ( Dark(result1) || Opaque(result1)) && !control) { dflt = promote_dflt(dflt, NOTHING); continue; } // Validate object has the ENTER bit set, if requested. // if ( check_enter && !Enter_ok(result1) && !control) { dflt = promote_dflt(dflt, NOPERM); continue; } // Look for the object in the container. // init_match(result1, target, NOTYPE); match_possession(); result = match_result(); result = match_possessed(player, result1, target, result, check_enter); if (Good_obj(result)) { return result; } dflt = promote_dflt(dflt, result); } return dflt; } /* --------------------------------------------------------------------------- * parse_range: break up ,, syntax */ void parse_range(UTF8 **name, dbref *low_bound, dbref *high_bound) { UTF8 *buff1 = *name; if (buff1 && *buff1) { *name = parse_to(&buff1, ',', EV_STRIP_TS); } if (buff1 && *buff1) { UTF8 *buff2 = parse_to(&buff1, ',', EV_STRIP_TS); if (buff1 && *buff1) { while (mux_isspace(*buff1)) { buff1++; } if (*buff1 == NUMBER_TOKEN) { buff1++; } *high_bound = mux_atoi64(buff1); if (*high_bound >= mudstate.db_top) { *high_bound = mudstate.db_top - 1; } } else { *high_bound = mudstate.db_top - 1; } while (mux_isspace(*buff2)) { buff2++; } if (*buff2 == NUMBER_TOKEN) { buff2++; } *low_bound = mux_atoi64(buff2); if (*low_bound < 0) { *low_bound = 0; } } else { *low_bound = 0; *high_bound = mudstate.db_top - 1; } } bool parse_thing_slash(dbref player, const UTF8 *thing, const UTF8 **after, dbref *it) { // Get name up to '/'. // size_t i = 0; while ( thing[i] != '\0' && thing[i] != '/') { i++; } // If no '/' in string, return failure. // if (thing[i] == '\0') { *after = nullptr; *it = NOTHING; return false; } *after = thing + i + 1; // Look for the object. // init_match(player, thing, i, NOTYPE); match_everything(MAT_EXIT_PARENTS); *it = match_result(); // Return status of search. // return Good_obj(*it); } bool get_obj_and_lock(dbref player, const UTF8 *what, dbref *it, ATTR **attr, UTF8 *errmsg, UTF8 **bufc) { // Get name up to '/'. // size_t i = 0; while ( what[i] != '\0' && what[i] != '/') { i++; } *it = match_thing_quiet(player, what, i); if (!Good_obj(*it)) { safe_match_result(*it, errmsg, bufc); return false; } int anum; if (what[i] == '/') { // / syntax, use the named lock. // if (!search_nametab(player, lock_sw, what + i + 1, &anum)) { safe_str(S_("#-1 LOCK NOT FOUND"), errmsg, bufc); return false; } } else { // Not /, do a normal get of the default lock. // anum = A_LOCK; } // Get the attribute definition, fail if not found. // *attr = atr_num(anum); if (nullptr == *attr) { safe_str(S_("#-1 LOCK NOT FOUND"), errmsg, bufc); return false; } return true; } // --------------------------------------------------------------------------- // bCanReadAttr, bCanSetAttr: Verify permission to affect attributes. // --------------------------------------------------------------------------- bool bCanReadAttr(dbref executor, dbref target, ATTR *tattr, bool bCheckParent) { if (!tattr) { return false; } dbref aowner; int aflags; if ( !mudstate.bStandAlone && bCheckParent) { atr_pget_info(target, tattr->number, &aowner, &aflags); } else { atr_get_info(target, tattr->number, &aowner, &aflags); } int test_flags = tattr->flags; if (mudstate.attrperm_list) { ATTRPERM *perm_walk = mudstate.attrperm_list; while (nullptr != perm_walk) { if (quick_wild(perm_walk->wildcard, tattr->name)) { test_flags |= perm_walk->flags; } perm_walk = perm_walk->next; } } int mAllow = AF_VISUAL; if ( (test_flags & mAllow) || (aflags & mAllow)) { if ( mudstate.bStandAlone || tattr->number != A_DESC || mudconf.read_rem_desc || nearby(executor, target)) { return true; } } int mDeny = 0; if (WizRoy(executor)) { if (God(executor)) { mDeny = AF_INTERNAL; } else { mDeny = AF_INTERNAL|AF_DARK; } } else if ( Owner(executor) == aowner || Examinable(executor, target)) { mDeny = AF_INTERNAL|AF_DARK|AF_MDARK; } if (mDeny) { if ( (test_flags & mDeny) || (aflags & mDeny)) { return false; } else { return true; } } return false; } bool bCanSetAttr(dbref executor, dbref target, ATTR *tattr) { if (!tattr) { return false; } if (NoModify(target) && !WizRoy(executor)) { return false; } int mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST; if (!God(executor)) { if (God(target)) { return false; } if (Wizard(executor)) { mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_LOCK|AF_GOD; } else if (Controls(executor, target)) { mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_LOCK|AF_WIZARD|AF_GOD; } else { return false; } } dbref aowner; int aflags; bool info = atr_get_info(target, tattr->number, &aowner, &aflags); int test_flags = tattr->flags; if (mudstate.attrperm_list) { ATTRPERM *perm_walk = mudstate.attrperm_list; while (nullptr != perm_walk) { if (quick_wild(perm_walk->wildcard,tattr->name)) { test_flags |= perm_walk->flags; } perm_walk = perm_walk->next; } } if ( (test_flags & mDeny) || (info && (aflags & mDeny))) { return false; } else { return true; } } bool bCanLockAttr(dbref executor, dbref target, ATTR *tattr) { if (!tattr) { return false; } if (NoModify(target) && !WizRoy(executor)) { return false; } int mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST; if (!God(executor)) { if (God(target)) { return false; } if (Wizard(executor)) { mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_GOD; } else { mDeny = AF_INTERNAL|AF_IS_LOCK|AF_CONST|AF_WIZARD|AF_GOD; } } dbref aowner; int aflags; bool info = atr_get_info(target, tattr->number, &aowner, &aflags); int test_flags = tattr->flags; if (mudstate.attrperm_list) { ATTRPERM *perm_walk = mudstate.attrperm_list; while (nullptr != perm_walk) { if (quick_wild(perm_walk->wildcard,tattr->name)) { test_flags |= perm_walk->flags; } perm_walk = perm_walk->next; } } if ( (test_flags & mDeny) || !info || (aflags & mDeny)) { return false; } else if ( Wizard(executor) || Owner(executor) == aowner) { return true; } else { return false; } } /* --------------------------------------------------------------------------- * where_is: Returns place where obj is linked into a list. * ie. location for players/things, source for exits, NOTHING for rooms. */ dbref where_is(dbref what) { if (!Good_obj(what)) { return NOTHING; } dbref loc; switch (Typeof(what)) { case TYPE_PLAYER: case TYPE_THING: loc = Location(what); break; case TYPE_EXIT: loc = Exits(what); break; default: loc = NOTHING; break; } return loc; } /* --------------------------------------------------------------------------- * where_room: Return room containing player, or NOTHING if no room or * recursion exceeded. If player is a room, returns itself. */ dbref where_room(dbref what) { for (int count = mudconf.ntfy_nest_lim; count > 0; count--) { if (!Good_obj(what)) { break; } if (isRoom(what)) { return what; } if (!Has_location(what)) { break; } what = Location(what); } return NOTHING; } bool locatable(dbref player, dbref it, dbref enactor) { // No sense in trying to locate a bad object. // if (!Good_obj(it)) { return false; } dbref loc_it = where_is(it); // Succeed if we can examine the target, if we are the target, if we can // examine the location, if the player is a wizard, or if the target // caused the lookup. // if ( Examinable(player, it) || Find_Unfindable(player) || loc_it == player || ( loc_it != NOTHING && ( Examinable(player, loc_it) || loc_it == where_is(player)) && ( !Hidden(it) || See_Hidden(player))) || Wizard(player) || it == enactor) { return true; } dbref room_it = where_room(it); bool findable_room; if (Good_obj(room_it)) { findable_room = Findable(room_it); } else { findable_room = true; } // Succeed if we control the containing room or if the target is findable // and the containing room is not unfindable. // if ( ( room_it != NOTHING && Examinable(player, room_it)) && ( !Hidden(it) || See_Hidden(player)) || Find_Unfindable(player) || ( Findable(it) && findable_room) && ( !Hidden(it) || See_Hidden(player))) { return true; } // We can't do it. // return false; } /* --------------------------------------------------------------------------- * nearby: Check if thing is nearby player (in inventory, in same room, or * IS the room. */ bool nearby(dbref player, dbref thing) { if ( !Good_obj(player) || !Good_obj(thing)) { return false; } if ( Can_Hide(thing) && Hidden(thing) && !See_Hidden(player)) { return false; } dbref thing_loc = where_is(thing); if (thing_loc == player) { return true; } dbref player_loc = where_is(player); if ( thing_loc == player_loc || thing == player_loc) { return true; } return false; } /* * --------------------------------------------------------------------------- * * exit_visible, exit_displayable: Is exit visible? */ bool exit_visible(dbref exit, dbref player, int key) { #ifdef WOD_REALMS if (!mudstate.bStandAlone) { int iRealmDirective = DoThingToThingVisibility(player, exit, ACTION_IS_STATIONARY); if (REALM_DO_HIDDEN_FROM_YOU == iRealmDirective) { return false; } } #endif // WOD_REALMS #ifdef REALITY_LVLS if (!mudstate.bStandAlone) { if (!IsReal(player, exit)) { return false; } } #endif // REALITY_LVLS // Exam exit's location // if ( (key & VE_LOC_XAM) || Examinable(player, exit) || Light(exit)) { return true; } // Dark location or base // if ( (key & (VE_LOC_DARK | VE_BASE_DARK)) || Dark(exit)) { return false; } // VisibleLock // if (!could_doit(player, exit, A_LVISIBLE)) { return false; } // Default // return true; } // Exit visible to look // bool exit_displayable(dbref exit, dbref player, int key) { // Dark exit // if (Dark(exit)) { return false; } #ifdef WOD_REALMS if (!mudstate.bStandAlone) { int iRealmDirective = DoThingToThingVisibility(player, exit, ACTION_IS_STATIONARY); if (REALM_DO_HIDDEN_FROM_YOU == iRealmDirective) { return false; } } #endif // WOD_REALMS #ifdef REALITY_LVLS if (!mudstate.bStandAlone) { if (!IsReal(player, exit)) { return false; } } #endif // REALITY_LVLS // Light exit // if (Light(exit)) { return true; } // Dark location or base. // if (key & (VE_LOC_DARK | VE_BASE_DARK)) { return false; } // VisibleLock // if (!could_doit(player, exit, A_LVISIBLE)) { return false; } // Default // return true; } /* --------------------------------------------------------------------------- * did_it: Have player do something to/with thing */ void did_it(dbref player, dbref thing, int what, const UTF8 *def, int owhat, const UTF8 *odef, int awhat, int ctrl_flags, const UTF8 *args[], int nargs) { if (alarm_clock.alarmed) { return; } UTF8 *d, *buff, *act, *charges, *bp; dbref loc, aowner; int64_t num; int aflags; // If we need to call exec() from within this function, we first save // the state of the global registers, in order to avoid munging them // inappropriately. Do note that the restoration to their original // values occurs BEFORE the execution of the @a-attribute. Therefore, // any changing of setq() values done in the @-attribute and @o-attribute // will NOT be passed on. This prevents odd behaviors that result from // odd @verbs and so forth (the idea is to preserve the caller's control // of the global register values). // bool need_pres = false; reg_ref **preserve = nullptr; // message to player. // if (what > 0) { d = atr_pget(thing, what, &aowner, &aflags); if (*d) { if ((aflags & AF_NOEVAL) || NoEval(thing)) { // Output raw text with no substitutions. // notify(player, d); } else { need_pres = true; preserve = PushRegisters(MAX_GLOBAL_REGS); save_global_regs(preserve); buff = bp = alloc_lbuf("did_it.1"); mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, player, player, AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_FCHECK|EV_TOP), args, nargs); *bp = '\0'; if ( (aflags & AF_HTML) && Html(player)) { safe_str(T("\r\n"), buff, &bp); *bp = '\0'; notify_html(player, buff); } else { notify(player, buff); } free_lbuf(buff); } } else if (def) { notify(player, def); } free_lbuf(d); } else if (what < 0 && def) { notify(player, def); } // message to neighbors. // if ( 0 < owhat && Has_location(player) && Good_obj(loc = Location(player))) { d = atr_pget(thing, owhat, &aowner, &aflags); if (*d) { if ((aflags & AF_NOEVAL) || NoEval(thing)) { // Output raw text with no substitutions. // #ifdef REALITY_LVLS if (aflags & AF_NONAME) { notify_except2_rlevel(loc, player, player, thing, d); } else { notify_except2_rlevel(loc, player, player, thing, tprintf(T("%s %s"), Moniker(player), d)); } #else if (aflags & AF_NONAME) { notify_except2(loc, player, player, thing, d); } else { notify_except2(loc, player, player, thing, tprintf(T("%s %s"), Moniker(player), d)); } #endif // REALITY_LVLS } else { if (!need_pres) { need_pres = true; preserve = PushRegisters(MAX_GLOBAL_REGS); save_global_regs(preserve); } buff = bp = alloc_lbuf("did_it.2"); mux_exec(d, LBUF_SIZE-1, buff, &bp, thing, player, player, AttrTrace(aflags, EV_EVAL|EV_FIGNORE|EV_FCHECK|EV_TOP), args, nargs); *bp = '\0'; if (*buff) { #ifdef REALITY_LVLS if (aflags & AF_NONAME) { notify_except2_rlevel(loc, player, player, thing, buff); } else { notify_except2_rlevel(loc, player, player, thing, tprintf(T("%s %s"), Moniker(player), buff)); } #else if (aflags & AF_NONAME) { notify_except2(loc, player, player, thing, buff); } else { notify_except2(loc, player, player, thing, tprintf(T("%s %s"), Moniker(player), buff)); } #endif // REALITY_LVLS } free_lbuf(buff); } // else (not NOEVAL) } else if (odef) { #ifdef REALITY_LVLS if (ctrl_flags & VERB_NONAME) { notify_except2_rlevel(loc, player, player, thing, odef); } else { notify_except2_rlevel(loc, player, player, thing, tprintf(T("%s %s"), Moniker(player), odef)); } #else if (ctrl_flags & VERB_NONAME) { notify_except2(loc, player, player, thing, odef); } else { notify_except2(loc, player, player, thing, tprintf(T("%s %s"), Moniker(player), odef)); } #endif // REALITY_LVLS } free_lbuf(d); } else if ( owhat < 0 && odef && Has_location(player) && Good_obj(loc = Location(player))) { #ifdef REALITY_LVLS if (ctrl_flags & VERB_NONAME) { notify_except2_rlevel(loc, player, player, thing, odef); } else { notify_except2_rlevel(loc, player, player, thing, tprintf(T("%s %s"), Name(player), odef)); } #else if (ctrl_flags & VERB_NONAME) { notify_except2(loc, player, player, thing, odef); } else { notify_except2(loc, player, player, thing, tprintf(T("%s %s"), Name(player), odef)); } #endif // REALITY_LVLS } // If we preserved the state of the global registers, restore them. // if (need_pres) { restore_global_regs(preserve); PopRegisters(preserve, MAX_GLOBAL_REGS); } // Do the action attribute. // #ifdef REALITY_LVLS if ( 0 < awhat && IsReal(thing, player)) #else if (0 < awhat) #endif // REALITY_LVLS { if (*(act = atr_pget(thing, awhat, &aowner, &aflags))) { dbref aowner2; int aflags2; charges = atr_pget(thing, A_CHARGES, &aowner2, &aflags2); if (*charges) { // int64_t, not int (#1402). // num = mux_atoi64(charges); if (num > 0) { buff = alloc_sbuf("did_it.charges"); mux_i64toa(num - 1, buff); atr_add_raw(thing, A_CHARGES, buff); free_sbuf(buff); } else if (*(buff = atr_pget(thing, A_RUNOUT, &aowner2, &aflags2))) { free_lbuf(act); act = buff; } else { free_lbuf(act); free_lbuf(buff); free_lbuf(charges); return; } } free_lbuf(charges); if (!((aflags & AF_NOEVAL) || NoEval(thing))) { CLinearTimeAbsolute lta; wait_que(thing, player, player, AttrTrace(aflags, 0), false, lta, NOTHING, 0, act, nargs, args, mudstate.global_regs); } } free_lbuf(act); } } /* --------------------------------------------------------------------------- * do_verb: Command interface to did_it. */ void do_verb(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *victim_str, UTF8 *args[], int nargs, const UTF8 *cargs[], int ncargs) { UNUSED_PARAMETER(eval); UNUSED_PARAMETER(caller); UNUSED_PARAMETER(key); UNUSED_PARAMETER(cargs); UNUSED_PARAMETER(ncargs); // Look for the victim. // if ( !victim_str || !*victim_str) { notify(executor, M_("Nothing to do.")); return; } // Get the victim. // init_match(executor, victim_str, NOTYPE); match_everything(MAT_EXIT_PARENTS); dbref victim = noisy_match_result(); if (!Good_obj(victim)) { return; } // Get the actor. Default is my cause. // dbref actor; if ( nargs >= 1 && args[0] && *args[0]) { init_match(executor, args[0], NOTYPE); match_everything(MAT_EXIT_PARENTS); actor = noisy_match_result(); if (!Good_obj(actor)) { return; } } else { actor = enactor; } // Check permissions. There are two possibilities: // // 1. Executor controls both victim and actor. In this case, // victim runs his action list. // // 2. Executor controls actor. In this case victim does not run // his action list and any attributes that executor cannot read // from victim are defaulted. // if (!Controls(executor, actor)) { notify_quiet(executor, M_("Permission denied,")); return; } ATTR *ap; int what = -1; int owhat = -1; int awhat = -1; const UTF8 *whatd = nullptr; const UTF8 *owhatd = nullptr; int nxargs = 0; dbref aowner = NOTHING; int aflags = NOTHING; UTF8 *xargs[10]; switch (nargs) // Yes, this IS supposed to fall through. { case 7: // Get arguments. // parse_arglist(victim, actor, actor, args[6], EV_STRIP_LS | EV_STRIP_TS, xargs, 10, nullptr, 0, &nxargs); case 6: // Get action attribute. // ap = atr_str(args[5]); if (ap) { awhat = ap->number; } case 5: // Get others message default. // if (args[4] && *args[4]) { owhatd = args[4]; } case 4: // Get others message attribute. // ap = atr_str(args[3]); if (ap && (ap->number > 0)) { owhat = ap->number; } case 3: // Get enactor message default. // if (args[2] && *args[2]) { whatd = args[2]; } case 2: // Get enactor message attribute. // ap = atr_str(args[1]); if (ap && (ap->number > 0)) { what = ap->number; } } // If executor doesn't control both, enforce visibility restrictions. // if (!Controls(executor, victim)) { ap = nullptr; if (what != -1) { atr_get_info(victim, what, &aowner, &aflags); ap = atr_num(what); } if ( !ap || !bCanReadAttr(executor, victim, ap, false) || ( ap->number == A_DESC && !mudconf.read_rem_desc && !Examinable(executor, victim) && !nearby(executor, victim))) { what = -1; } ap = nullptr; if (owhat != -1) { atr_get_info(victim, owhat, &aowner, &aflags); ap = atr_num(owhat); } if ( !ap || !bCanReadAttr(executor, victim, ap, false) || ( ap->number == A_DESC && !mudconf.read_rem_desc && !Examinable(executor, victim) && !nearby(executor, victim))) { owhat = -1; } awhat = 0; } // Go do it. // did_it(actor, victim, what, whatd, owhat, owhatd, awhat, key & VERB_NONAME, (const UTF8 **)xargs, nxargs); // Free user args. // for (int i = 0; i < nxargs; i++) { free_lbuf(xargs[i]); } } // AssertionFailed and OutOfMemory moved to libmux.cpp. static void ListReferences(dbref executor, UTF8 *reference_name) { dbref target = NOTHING; bool global_only = false; if ( nullptr == reference_name || '\0' == reference_name[0]) { global_only = true; } else { global_only = false; target = lookup_player(executor, reference_name, 1); if (!Good_obj(target)) { raw_notify(executor, M_("No such player.")); return; } if (!Controls(executor, target)) { raw_notify(executor, NOPERM_MESSAGE); return; } } // Listing: // - if global_only is true, list all references that begin with _ // - Otherwise, list all references whose owner is target // reference_entry *htab_entry; bool match_found = false; for (auto &[ref_key, ref_val] : mudstate.reference_htab) { htab_entry = static_cast(ref_val); if ( ( global_only && '_' == htab_entry->name[0]) || ( !global_only && target == htab_entry->owner)) { if (!Good_obj(htab_entry->target)) { continue; } // @list ref schema (#1667 Phase 4 C5): name 12 / target 20 / owner 20. // static const size_t kRefNameCols = 12; static const size_t kRefTargetCols = 20; static const size_t kRefOwnerCols = 20; if (!match_found) { match_found = true; UTF8 header[LBUF_SIZE]; size_t pos = 0; pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Reference"), kRefNameCols); pos = mux_table_append_bytes(header, sizeof(header), pos, " "); pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Target"), kRefTargetCols); pos = mux_table_append_bytes(header, sizeof(header), pos, " "); pos = mux_table_append_ljust(header, sizeof(header), pos, M_("Owner"), kRefOwnerCols); raw_notify(executor, header); raw_notify(executor, M_("-------------------------------------------------------")); } UTF8 *object_buf = unparse_object(executor, htab_entry->target, false); UTF8 line[LBUF_SIZE]; size_t pos = 0; pos = mux_table_append_ljust(line, sizeof(line), pos, htab_entry->name, kRefNameCols); pos = mux_table_append_bytes(line, sizeof(line), pos, " "); pos = mux_table_append_ljust(line, sizeof(line), pos, object_buf, kRefTargetCols); pos = mux_table_append_bytes(line, sizeof(line), pos, " "); pos = mux_table_append_ljust(line, sizeof(line), pos, Moniker(htab_entry->owner), kRefOwnerCols); raw_notify(executor, line); free_lbuf(object_buf); } } if (!match_found) { raw_notify(executor, M_("GAME: No references found.")); } else { raw_notify(executor, M_("---------------- End of Reference List ----------------")); } } void do_reference ( dbref executor, dbref caller, dbref enactor, int eval, int key, int nargs, UTF8 *reference_name, UTF8 *object_name, const UTF8 *cargs[], int ncargs ) { UNUSED_PARAMETER(caller); UNUSED_PARAMETER(enactor); UNUSED_PARAMETER(eval); UNUSED_PARAMETER(nargs); UNUSED_PARAMETER(ncargs); UNUSED_PARAMETER(cargs); if (key & REFERENCE_LIST) { ListReferences(executor, reference_name); return; } // References can only be set on objects the executor can examine. // dbref target = NOTHING; if ( nullptr != object_name && '\0' != object_name[0]) { target = match_thing_quiet(executor, object_name); if (!Good_obj(target)) { notify(executor, NOMATCH_MESSAGE); return; } else if (!Examinable(executor, target)) { notify(executor, NOPERM_MESSAGE); return; } } if ('_' == reference_name[0]) { if (!Wizard(executor)) { notify(executor, NOPERM_MESSAGE); return; } } LBuf tbuf = LBuf_Src("do_name"); // mux_snprintf returns length written (0..count-1), not "would have". // size_t tbuf_len; if ('_' == reference_name[0]) { tbuf_len = mux_snprintf(tbuf.get(), LBUF_SIZE, T("%s"), reference_name); } else { tbuf_len = mux_snprintf(tbuf.get(), LBUF_SIZE, T("%s.%lld"), reference_name, static_cast(executor)); } auto it_ref = mudstate.reference_htab.find(std::vector(tbuf.get(), tbuf.get() + tbuf_len)); struct reference_entry *result = (it_ref != mudstate.reference_htab.end()) ? static_cast(it_ref->second) : nullptr; enum { Delete, Add, Update, NotFound, Redundant, OutOfMemory } eOperation; if (nullptr != result) { if (NOTHING == target) { eOperation = Delete; } else if (result->target == target) { eOperation = Redundant; } else // if (result->target != target) { eOperation = Update; } } else { if (NOTHING == target) { eOperation = NotFound; } else { eOperation = Add; if ( !Wizard(executor) && ThrottleReferences(executor)) { raw_notify(executor, M_("References requested too quickly.")); return; } } } if ( Delete == eOperation || Update == eOperation) { // Release the existing reference. // MEMFREE(result->name); result->name = nullptr; MEMFREE(result); result = nullptr; mudstate.reference_htab.erase(std::vector(tbuf.get(), tbuf.get() + tbuf_len)); } if ( Update == eOperation || Add == eOperation) { try { result = static_cast(MEMALLOC(sizeof(reference_entry))); } catch(...) { ; // Nothing; } if (nullptr != result) { result->target = target; result->owner = executor; result->name = StringCloneLen(tbuf, tbuf_len); mudstate.reference_htab.emplace(std::vector(tbuf.get(), tbuf.get() + tbuf_len), result); } else { eOperation = OutOfMemory; } } if (Delete == eOperation) { raw_notify(executor, M_("Reference cleared.")); } else if (Update == eOperation) { raw_notify(executor, M_("Reference updated.")); } else if (Redundant == eOperation) { raw_notify(executor, M_("That reference already exists.")); } else if (NotFound == eOperation) { raw_notify(executor, M_("No such reference to clear.")); } else if (Add == eOperation) { raw_notify(executor, M_("Reference added.")); } else if (OutOfMemory == eOperation) { raw_notify(executor, OUT_OF_MEMORY); } }