Example #1
0
/** 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);
}
Example #2
0
/** 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;
}
Example #3
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;
}
Example #4
0
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);
}
Example #5
0
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);
  }
}
Example #6
0
/** 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;
}
Example #7
0
/** 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));
}
Example #8
0
/** 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;
}
Example #9
0
/** 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);
}
Example #10
0
/** 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));
}
Example #11
0
/** 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));
  }
}
Example #12
0
/** 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;
}
Example #13
0
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;
}