/** Active a lock's failure attributes. * \param player dbref failing to pass the lock. * \param thing object containing the lock. * \param ltype type of lock failed. * \param def default message if there is no appropriate failure attribute. * \param loc location in which action is taking place. * \retval 1 some attribute on the object was actually evaluated. * \retval 0 no attributes were evaluated (only defaults used). */ int fail_lock(dbref player, dbref thing, lock_type ltype, const char *def, dbref loc) { const LOCKMSGINFO *lm; char atr[BUFFER_LEN]; char oatr[BUFFER_LEN]; char aatr[BUFFER_LEN]; char realdef[BUFFER_LEN]; char *bp; if (def) strcpy(realdef, def); /* Because a lot of default msgs use tprintf */ else realdef[0] = '\0'; /* Find the lock's failure attribute, if it's there */ for (lm = lock_msgs; lm->type; lm++) { if (!strcmp(lm->type, ltype)) break; } if (lm->type) { strcpy(atr, lm->failbase); bp = oatr; safe_format(oatr, &bp, "O%s", lm->failbase); *bp = '\0'; strcpy(aatr, oatr); aatr[0] = 'A'; } else { /* Oops, it's not in the table. So we construct them on these lines: * <LOCKNAME>_LOCK`<type>FAILURE */ bp = atr; safe_format(atr, &bp, "%s_LOCK`FAILURE", ltype); *bp = '\0'; bp = oatr; safe_format(oatr, &bp, "%s_LOCK`OFAILURE", ltype); *bp = '\0'; bp = aatr; safe_format(aatr, &bp, "%s_LOCK`AFAILURE", ltype); *bp = '\0'; } /* Now do the work */ upcasestr(atr); upcasestr(oatr); upcasestr(aatr); return did_it(player, thing, atr, realdef, oatr, NULL, aatr, loc, AN_SYS); }
/** Is a name in the forbidden names file? * \param name name to check. * \retval 1 name is forbidden. * \retval 0 name is not forbidden. */ int forbidden_name(const char *name) { char buf[BUFFER_LEN], *newlin, *ptr; FILE *fp; fp = fopen(NAMES_FILE, FOPEN_READ); if (!fp) return 0; while (fgets(buf, sizeof buf, fp)) { upcasestr(buf); /* step on the newline */ if ((newlin = strchr(buf, '\r'))) *newlin = '\0'; else if ((newlin = strchr(buf, '\n'))) *newlin = '\0'; ptr = buf; if (name && ptr && quick_wild(ptr, name)) { fclose(fp); return 1; } } fclose(fp); return 0; }
/* Add a new, or restrict an existing, standard attribute from cnf file */ int cnf_attribute_access(char *attrname, char *opts) { ATTR *a; privbits flags = 0; upcasestr(attrname); if (!good_atr_name(attrname)) return 0; if (strcasecmp(opts, "none")) { flags = list_to_privs(attr_privs_set, opts, 0); if (!flags) return 0; } a = (ATTR *) ptab_find_exact(&ptab_attrib, attrname); if (a) { if (AF_Internal(a)) return 0; } else { a = (ATTR *) mush_malloc(sizeof(ATTR), "ATTR"); if (!a) return 0; AL_NAME(a) = strdup(attrname); a->data = NULL_CHUNK_REFERENCE; ptab_insert_one(&ptab_attrib, attrname, a); } AL_FLAGS(a) = flags; AL_CREATOR(a) = GOD; return 1; }
static ATTR * aname_find_exact(const char *name) { char atrname[BUFFER_LEN]; strcpy(atrname, name); upcasestr(atrname); return (ATTR *) ptab_find_exact(&ptab_attrib, atrname); }
static void genrecord(s_rec *sp, dbref player, ListTypeInfo * lti) { lti->make_record(sp, player, lti->attrname); if (lti->flags & IS_CASE_INSENS && sp->memo.str.s) { if (sp->memo.str.freestr == 0) { sp->memo.str.s = mush_strdup(sp->memo.str.s, "genrecord"); sp->memo.str.freestr = 1; } upcasestr(sp->memo.str.s); } }
/** Rename an attribute in the attribute table. * \verbatim * Top-level function for @attrib/rename. * \endverbatim * \param player the enactor. * \param old the name of the attribute to rename. * \param newname the new name (surprise!) */ void do_attribute_rename(dbref player, char *old, char *newname) { ATTR *ap; if (!old || !*old || !newname || !*newname) { notify(player, T("Which attributes do you mean?")); return; } upcasestr(old); upcasestr(newname); /* Is the new name valid? */ if (!good_atr_name(newname)) { notify(player, T("Invalid attribute name.")); return; } /* Is the new name already in use? */ ap = (ATTR *) ptab_find_exact(&ptab_attrib, newname); if (ap) { notify_format(player, T("The name %s is already used in the attribute table."), newname); return; } /* Is the old name a real attribute? */ ap = (ATTR *) ptab_find_exact(&ptab_attrib, old); if (!ap) { notify(player, T("That attribute isn't in the attribute table")); return; } /* Ok, take it out and put it back under the new name */ ptab_delete(&ptab_attrib, old); /* This causes a slight memory leak if you rename an attribute added via /access. But that doesn't happen often. Will fix someday. */ AL_NAME(ap) = strdup(newname); ptab_insert_one(&ptab_attrib, newname, ap); notify_format(player, T("Renamed %s to %s in attribute table."), old, newname); return; }
/** Parse object/attribute strings into components. * This function takes a string which is of the format obj/attr or attr, * and returns the dbref of the object, and a pointer to the attribute. * If no object is specified, then the dbref returned is the player's. * str is destructively modified. This function is probably underused. * \param player the default object. * \param str the string to parse. * \param thing pointer to dbref of object parsed out of string. * \param attrib pointer to pointer to attribute structure retrieved. */ void parse_attrib(dbref player, char *str, dbref *thing, ATTR **attrib) { char *name; /* find the object */ if ((name = strchr(str, '/')) != NULL) { *name++ = '\0'; *thing = noisy_match_result(player, str, NOTYPE, MAT_EVERYTHING); } else { name = str; *thing = player; } /* find the attribute */ *attrib = (ATTR *) atr_get(*thing, upcasestr(name)); }
/** Populate a ufun_attrib struct from an obj/attr pair. * \verbatim Given an attribute [<object>/]<name> pair (which may include #lambda), * fetch its value, owner (thing), and pe_flags, and store in the struct * pointed to by ufun * \endverbatim * \param attrstring The obj/name of attribute. * \param executor Dbref of the executing object. * \param ufun Pointer to an allocated ufun_attrib struct to fill in. * \param flags A bitwise or of desired UFUN_* flags. * \return 0 on failure, true on success. */ bool fetch_ufun_attrib(const char *attrstring, dbref executor, ufun_attrib * ufun, int flags) { char *thingname, *attrname; char astring[BUFFER_LEN]; ATTR *attrib; if (!ufun) return 0; ufun->contents[0] = '\0'; ufun->errmess = (char *) ""; ufun->thing = executor; ufun->pe_flags = PE_UDEFAULT; ufun->ufun_flags = flags; ufun->thing = executor; thingname = NULL; if (!attrstring) return 0; strncpy(astring, attrstring, BUFFER_LEN); /* Split obj/attr */ if ((flags & UFUN_OBJECT) && ((attrname = strchr(astring, '/')) != NULL)) { thingname = astring; *(attrname++) = '\0'; } else { attrname = astring; } if (thingname && (flags & UFUN_LAMBDA) && (strcasecmp(thingname, "#lambda") == 0 || strncasecmp(thingname, "#apply", 6) == 0)) { /* It's a lambda. */ ufun->ufun_flags &= ~UFUN_NAME; ufun->thing = executor; if (strcasecmp(thingname, "#lambda") == 0) mush_strncpy(ufun->contents, attrname, BUFFER_LEN); else { /* #apply */ char *ucb = ufun->contents; unsigned nargs = 1, n; thingname += 6; if (*thingname) nargs = parse_uinteger(thingname); /* Limit between 1 and 10 arguments (%0-%9) */ if (nargs == 0) nargs = 1; if (nargs > 10) nargs = 10; safe_str(attrname, ufun->contents, &ucb); safe_chr('(', ufun->contents, &ucb); for (n = 0; n < nargs; n++) { if (n > 0) safe_chr(',', ufun->contents, &ucb); safe_format(ufun->contents, &ucb, "%%%u", n); } safe_chr(')', ufun->contents, &ucb); *ucb = '\0'; } ufun->attrname[0] = '\0'; return 1; } if (thingname) { /* Attribute is on something else. */ ufun->thing = noisy_match_result(executor, thingname, NOTYPE, MAT_EVERYTHING); if (!GoodObject(ufun->thing)) { ufun->errmess = (char *) "#-1 INVALID OBJECT"; return 0; } } attrib = (ATTR *) atr_get(ufun->thing, upcasestr(attrname)); if (attrib && AF_Internal(attrib)) { /* Regardless of whether we're doing permission checks, we should * never be showing internal attributes here */ attrib = NULL; } /* An empty attrib is the same as no attrib. */ if (attrib == NULL) { if (flags & UFUN_REQUIRE_ATTR) { if (!(flags & UFUN_IGNORE_PERMS) && !Can_Examine(executor, ufun->thing)) ufun->errmess = e_atrperm; return 0; } else { mush_strncpy(ufun->attrname, attrname, ATTRIBUTE_NAME_LIMIT + 1); return 1; } } if (!(flags & UFUN_IGNORE_PERMS) && !Can_Read_Attr(executor, ufun->thing, attrib)) { ufun->errmess = e_atrperm; return 0; } if (!(flags & UFUN_IGNORE_PERMS) && !CanEvalAttr(executor, ufun->thing, attrib)) { ufun->errmess = e_perm; return 0; } /* DEBUG attributes */ if (AF_NoDebug(attrib)) ufun->pe_flags |= PE_NODEBUG; /* No_Debug overrides Debug */ else if (AF_Debug(attrib)) ufun->pe_flags |= PE_DEBUG; if (flags & UFUN_NAME) { if (attrib->flags & AF_NONAME) ufun->ufun_flags &= ~UFUN_NAME; else if (attrib->flags & AF_NOSPACE) ufun->ufun_flags |= UFUN_NAME_NOSPACE; } /* Populate the ufun object */ mush_strncpy(ufun->contents, atr_value(attrib), BUFFER_LEN); mush_strncpy(ufun->attrname, AL_NAME(attrib), ATTRIBUTE_NAME_LIMIT + 1); /* We're good */ return 1; }
/** User-defined verbs. * \verbatim * This implements the @verb command. * \endverbatim * \param executor the executor. * \param enactor the object causing this command to run. * \param arg1 the object to read verb attributes from. * \param argv the array of remaining arguments to the verb command. * \param queue_entry The queue entry \@verb is running in */ void do_verb(dbref executor, dbref enactor, char *arg1, char **argv, MQUE *queue_entry) { dbref victim; dbref actor; int i; PE_REGS *pe_regs = NULL; /* find the object that we want to read the attributes off * (the object that was the victim of the command) */ /* our victim object can be anything */ victim = match_result(executor, arg1, NOTYPE, MAT_EVERYTHING); if (!GoodObject(victim)) { notify(executor, T("What was the victim of the verb?")); return; } /* find the object that executes the action */ if (!argv || !argv[1] || !*argv[1]) { notify(executor, T("What do you want to do with the verb?")); return; } actor = match_result(executor, argv[1], NOTYPE, MAT_EVERYTHING); if (!GoodObject(actor)) { notify(executor, T("What do you want to do the verb?")); return; } /* Control check is fascist. * First check: we don't want <actor> to do something involuntarily. * Both victim and actor have to be controlled by the thing which did * the @verb (for speed we do a WIZARD check first), or: cause controls * actor plus the second check is passed. * Second check: we need read access to the attributes. * Either the player controls victim or the player * must be priviledged, or the victim has to be VISUAL. */ if (!(Wizard(executor) || (controls(executor, victim) && controls(executor, actor)) || ((controls(enactor, actor) && Can_Examine(executor, victim))))) { notify(executor, T("Permission denied.")); return; } /* We're okay. Send out messages. */ pe_regs = pe_regs_create(PE_REGS_ARG | PE_REGS_Q, "do_verb"); for (i = 0; i < MAX_STACK_ARGS; i++) { if (argv[i + 7]) { pe_regs_setenv_nocopy(pe_regs, i, argv[i + 7]); } } pe_regs_qcopy(pe_regs, queue_entry->pe_info->regvals); real_did_it(actor, victim, upcasestr(argv[2]), argv[3], upcasestr(argv[4]), argv[5], NULL, Location(actor), pe_regs, NA_INTER_HEAR, AN_SYS); /* Now we copy our args into the stack, and do the command. */ if (argv[6] && *argv[6]) queue_attribute_base(victim, upcasestr(argv[6]), actor, 0, pe_regs, (queue_entry->queue_type & QUEUE_EVENT)); pe_regs_free(pe_regs); }
/** Add new standard attributes, or change permissions on them. * \verbatim * Given the name and permission string for an attribute, add it to * the attribute table (or modify the permissions if it's already * there). Permissions may be changed retroactively, which modifies * permissions on any copies of that attribute set on objects in the * database. This is the top-level code for @attribute/access. * \endverbatim * \param player the enactor. * \param name the attribute name. * \param perms a string of attribute permissions, space-separated. * \param retroactive if true, apply the permissions retroactively. */ void do_attribute_access(dbref player, char *name, char *perms, int retroactive) { ATTR *ap, *ap2; privbits flags = 0; int i; int insert = 0; /* Parse name and perms */ if (!name || !*name) { notify(player, T("Which attribute do you mean?")); return; } if (strcasecmp(perms, "none")) { flags = list_to_privs(attr_privs_set, perms, 0); if (!flags) { notify(player, T("I don't understand those permissions.")); return; } } upcasestr(name); /* Is this attribute already in the table? */ ap = (ATTR *) ptab_find_exact(&ptab_attrib, name); if (ap) { if (AF_Internal(ap)) { /* Don't muck with internal attributes */ notify(player, T("That attribute's permissions can not be changed.")); return; } } else { /* Create fresh if the name is ok */ if (!good_atr_name(name)) { notify(player, T("Invalid attribute name.")); return; } insert = 1; ap = (ATTR *) mush_malloc(sizeof(ATTR), "ATTR"); if (!ap) { notify(player, T("Critical memory failure - Alert God!")); do_log(LT_ERR, 0, 0, "do_attribute_access: unable to malloc ATTR"); return; } AL_NAME(ap) = strdup(name); ap->data = NULL_CHUNK_REFERENCE; } AL_FLAGS(ap) = flags; AL_CREATOR(ap) = player; /* Only insert when it's not already in the table */ if (insert) { ptab_insert_one(&ptab_attrib, name, ap); } /* Ok, now we need to see if there are any attributes of this name * set on objects in the db. If so, and if we're retroactive, set * perms/creator */ if (retroactive) { for (i = 0; i < db_top; i++) { if ((ap2 = atr_get_noparent(i, name))) { if (AL_FLAGS(ap2) & AF_ROOT) AL_FLAGS(ap2) = flags | AF_ROOT; else AL_FLAGS(ap2) = flags; AL_CREATOR(ap2) = player; } } } notify_format(player, T("%s -- Attribute permissions now: %s"), name, privs_to_string(attr_privs_view, flags)); }
/** Limit an attribute's possible values, using either an enum or a * regexp /limit. * \verbatim * Given a name, restriction type and string for an attribute, * set its data value to said data and set a flag for limit or * enum. * * For an enum, the attr's data will be set to * <delim><pattern><delim>, so a simple strstr() can be used when * matching the pattern. * * An optional delimiter can be provided on the left hand side by using * @attr/enum <delim> <attrname>=<enum list> * \endverbatim * \param player the enactor. * \param name the attribute name. * \param type AF_RLIMIT for regexp, AF_ENUM for enum. * \param pattern The allowed pattern for the attribute. */ void do_attribute_limit(dbref player, char *name, int type, char *pattern) { ATTR *ap; char buff[BUFFER_LEN]; char *ptr, *bp; char delim = ' '; pcre *re; const char *errptr; int erroffset; int unset = 0; if (pattern && *pattern) { if (type == AF_RLIMIT) { /* Compile to regexp. */ re = pcre_compile(remove_markup(pattern, NULL), PCRE_CASELESS, &errptr, &erroffset, tables); if (!re) { notify(player, T("Invalid Regular Expression.")); return; } /* We only care if it's valid, we're not using it. */ free(re); /* Copy it to buff to be placed into ap->data. */ snprintf(buff, BUFFER_LEN, "%s", pattern); } else if (type == AF_ENUM) { ptr = name; /* Check for a delimiter: @attr/enum | attrname=foo */ if ((name = strchr(ptr, ' ')) != NULL) { *(name++) = '\0'; if (strlen(ptr) > 1) { notify(player, T("Delimiter must be one character.")); return; } delim = *ptr; } else { name = ptr; delim = ' '; } /* For speed purposes, we require the pattern to begin and end with * a delimiter. */ snprintf(buff, BUFFER_LEN, "%c%s%c", delim, pattern, delim); buff[BUFFER_LEN - 1] = '\0'; /* For sanity's sake, we'll enforce a properly delimited enum * with a quick and dirty squish(). * We already know we start with a delim, hence the +1 =). */ for (ptr = buff + 1, bp = buff + 1; *ptr; ptr++) { if (!(*ptr == delim && *(ptr - 1) == delim)) { *(bp++) = *ptr; } } *bp = '\0'; } else { /* Err, we got called with the wrong limit type? */ notify(player, T("Unknown limit type?")); return; } } else { unset = 1; } /* Parse name and perms */ if (!name || !*name) { notify(player, T("Which attribute do you mean?")); return; } upcasestr(name); if (*name == '@') name++; /* Is this attribute already in the table? */ ap = (ATTR *) ptab_find_exact(&ptab_attrib, name); if (!ap) { notify(player, T ("I don't know that attribute. Please use @attribute/access to create it, first.")); return; } if (AF_Internal(ap)) { /* Don't muck with internal attributes */ notify(player, T("That attribute's permissions cannot be changed.")); return; } /* All's good, set the data and the AF_RLIMIT or AF_ENUM flag. */ if (ap->data != NULL_CHUNK_REFERENCE) { chunk_delete(ap->data); } /* Clear any extant rlimit or enum flags */ ap->flags &= ~(AF_RLIMIT | AF_ENUM); if (unset) { if (ap->data != NULL_CHUNK_REFERENCE) { ap->data = NULL_CHUNK_REFERENCE; notify_format(player, T("%s -- Attribute limit or enum unset."), name); } else { notify_format(player, T("%s -- Attribute limit or enum already unset."), name); } } else { unsigned char *t = compress(buff); ap->data = chunk_create(t, u_strlen(t), 0); free(t); ap->flags |= type; notify_format(player, T("%s -- Attribute %s set to: %s"), name, type == AF_RLIMIT ? "limit" : "enum", display_attr_limit(ap)); } }
/** Check an attribute's value against /limit or /enum restrictions. * \param player Player attempting to set the attribute. Used for notify() * \param name the attribute name. * \param value The desired attribute value. * \retval The new value to set if valid, NULL if not. */ const char * check_attr_value(dbref player, const char *name, const char *value) { /* Check for attribute limits and enums. */ ATTR *ap; char *attrval; pcre *re; int subpatterns; const char *errptr; int erroffset; char *ptr, *ptr2; char delim; int len; static char buff[BUFFER_LEN]; char vbuff[BUFFER_LEN]; if (!name || !*name) return value; if (!value) return value; upcasestr((char *) name); ap = (ATTR *) ptab_find_exact(&ptab_attrib, name); if (!ap) return value; attrval = atr_value(ap); if (!attrval) { return value; } if (ap->flags & AF_RLIMIT) { re = pcre_compile(remove_markup(attrval, NULL), PCRE_CASELESS, &errptr, &erroffset, tables); if (!re) return value; subpatterns = pcre_exec(re, default_match_limit(), value, strlen(value), 0, 0, NULL, 0); free(re); if (subpatterns >= 0) { return value; } else { notify(player, T("Attribute value does not match the /limit regexp.")); return NULL; } } else if (ap->flags & AF_ENUM) { /* Delimiter is always the first character of the enum string. * and the value cannot have the delimiter in it. */ delim = *attrval; if (!*value || strchr(value, delim)) { notify_format(player, T("Value for %s needs to be one of: %s"), ap->name, display_attr_limit(ap)); return NULL; } /* We match the enum case-insensitively, BUT we use the case * that is defined in the enum, so we copy the attr value * to buff and use that. */ snprintf(buff, BUFFER_LEN, "%s", attrval); upcasestr(buff); len = strlen(value); snprintf(vbuff, BUFFER_LEN, "%c%s%c", delim, value, delim); upcasestr(vbuff); ptr = strstr(buff, vbuff); if (!ptr) { *(vbuff + len + 1) = '\0'; /* Remove the second delim */ ptr = strstr(buff, vbuff); } /* Do we have a match? */ if (ptr) { /* ptr is pointing at the delim before the value. */ ptr++; ptr2 = strchr(ptr, delim); if (!ptr2) return NULL; /* Shouldn't happen, but sanity check. */ /* Now we need to copy over the _original case_ version of the * enumerated string. Nasty pointer arithmetic. */ strncpy(buff, attrval + (ptr - buff), (int) (ptr2 - ptr)); buff[ptr2 - ptr] = '\0'; return buff; } else { notify_format(player, T("Value for %s needs to be one of: %s"), ap->name, display_attr_limit(ap)); return NULL; } } return value; }
void attr_read_all(PENNFILE *f) { ATTR *a; int c, found, count = 0; char alias[BUFFER_LEN]; /* Clear existing attributes */ ptab_free(&ptab_attrib); ptab_start_inserts(&ptab_attrib); db_read_this_labeled_int(f, "attrcount", &count); for (found = 0;;) { c = penn_fgetc(f); penn_ungetc(c, f); if (c != ' ') break; found++; if ((a = attr_read(f))) ptab_insert(&ptab_attrib, a->name, a); } ptab_end_inserts(&ptab_attrib); if (found != count) do_rawlog(LT_ERR, "WARNING: Actual number of attrs (%d) different than expected count (%d).", found, count); /* Assumes we'll always have at least one alias */ db_read_this_labeled_int(f, "attraliascount", &count); for (found = 0;;) { c = penn_fgetc(f); penn_ungetc(c, f); if (c != ' ') break; found++; if ((a = attr_alias_read(f, alias))) { upcasestr(alias); if (!good_atr_name(alias)) { do_rawlog(LT_ERR, "Bad attribute name on alias '%s' in db.", alias); } else if (aname_find_exact(strupper(alias))) { do_rawlog(LT_ERR, "Unable to alias attribute '%s' to '%s' in db: alias already in use.", AL_NAME(a), alias); } else if (!alias_attribute(AL_NAME(a), alias)) { do_rawlog(LT_ERR, "Unable to alias attribute '%s' to '%s' in db.", AL_NAME(a), alias); } } } if (found != count) do_rawlog(LT_ERR, "WARNING: Actual number of attr aliases (%d) different than expected count (%d).", found, count); return; }