#include "os.h"

#include "config.h"
#include "db.h"
#include "externs.h"
#include "interface.h"
#include "match.h"
#include "attrib.h"
#include "oldattrib.h"

#ifdef MEM_CHECK
#include "mem_check.h"
#endif

/* attribute list */
ATTR attr[] = {
  {(char *) "AAHEAR", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ACLONE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ACONNECT", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ADEATH", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ADESCRIBE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ADISCONNECT", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ADROP", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AEFAIL", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AENTER", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AFAILURE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AHEAR", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ALEAVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AMHEAR", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AMOVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "APAYMENT", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ASUCCESS", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AUSE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "AWAY", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "CHARGES", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "CLASS", AF_NOPROG, NULL, 0},
  {(char *) "COST", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "DEATH", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "DESCRIBE", AF_NOPROG, NULL, 0},
  {(char *) "DOES", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "DROP", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "EALIAS", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "EFAIL", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ENTER", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "FAILURE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "HAVEN", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "IDESCRIBE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "IDLE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "LALIAS", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "LAST", AF_WIZARD | AF_LOCKED, NULL, 0},
  {(char *) "LASTSITE", AF_LOCKED | AF_ODARK, NULL, 0},
  {(char *) "LEAVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "LISTEN", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "MOVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ODEATH", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ODESCRIBE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "ODROP", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OEFAIL", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OENTER", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OFAILURE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OLEAVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OMOVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OPAYMENT", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OSUCCESS", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OUSE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OXENTER", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "OXLEAVE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "PAYMENT", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "QUEUE", AF_ODARK | AF_WIZARD, NULL, 0},
  {(char *) "RACE", AF_NOPROG | AF_WIZARD, NULL, 0},
  {(char *) "RQUOTA", AF_DARK | AF_WIZARD, NULL, 0},
  {(char *) "RUNOUT", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "SEX", AF_NOPROG, NULL, 0},
  {(char *) "STARTUP", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "SUCCESS", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "USE", AF_ODARK | AF_NOPROG, NULL, 0},
  {(char *) "VA", AF_ODARK, NULL, 0},
  {(char *) "VB", AF_ODARK, NULL, 0},
  {(char *) "VC", AF_ODARK, NULL, 0},
  {(char *) "VD", AF_ODARK, NULL, 0},
  {(char *) "VE", AF_ODARK, NULL, 0},
  {(char *) "VF", AF_ODARK, NULL, 0},
  {(char *) "VG", AF_ODARK, NULL, 0},
  {(char *) "VH", AF_ODARK, NULL, 0},
  {(char *) "VI", AF_ODARK, NULL, 0},
  {(char *) "VJ", AF_ODARK, NULL, 0},
  {(char *) "VK", AF_ODARK, NULL, 0},
  {(char *) "VL", AF_ODARK, NULL, 0},
  {(char *) "VM", AF_ODARK, NULL, 0},
  {(char *) "VN", AF_ODARK, NULL, 0},
  {(char *) "VO", AF_ODARK, NULL, 0},
  {(char *) "VP", AF_ODARK, NULL, 0},
  {(char *) "VQ", AF_ODARK, NULL, 0},
  {(char *) "VR", AF_ODARK, NULL, 0},
  {(char *) "VS", AF_ODARK, NULL, 0},
  {(char *) "VT", AF_ODARK, NULL, 0},
  {(char *) "VU", AF_ODARK, NULL, 0},
  {(char *) "VV", AF_ODARK, NULL, 0},
  {(char *) "VW", AF_ODARK, NULL, 0},
  {(char *) "VX", AF_ODARK, NULL, 0},
  {(char *) "VY", AF_ODARK, NULL, 0},
  {(char *) "VZ", AF_ODARK, NULL, 0},
  {(char *) "WA", AF_ODARK, NULL, 0},
  {(char *) "WB", AF_ODARK, NULL, 0},
  {(char *) "WC", AF_ODARK, NULL, 0},
  {(char *) "WD", AF_ODARK, NULL, 0},
  {(char *) "WE", AF_ODARK, NULL, 0},
  {(char *) "WF", AF_ODARK, NULL, 0},
  {(char *) "WG", AF_ODARK, NULL, 0},
  {(char *) "WH", AF_ODARK, NULL, 0},
  {(char *) "WI", AF_ODARK, NULL, 0},
  {(char *) "WJ", AF_ODARK, NULL, 0},
  {(char *) "WK", AF_ODARK, NULL, 0},
  {(char *) "WL", AF_ODARK, NULL, 0},
  {(char *) "WM", AF_ODARK, NULL, 0},
  {(char *) "WN", AF_ODARK, NULL, 0},
  {(char *) "WO", AF_ODARK, NULL, 0},
  {(char *) "WP", AF_ODARK, NULL, 0},
  {(char *) "WQ", AF_ODARK, NULL, 0},
  {(char *) "WR", AF_ODARK, NULL, 0},
  {(char *) "WS", AF_ODARK, NULL, 0},
  {(char *) "WT", AF_ODARK, NULL, 0},
  {(char *) "WU", AF_ODARK, NULL, 0},
  {(char *) "WV", AF_ODARK, NULL, 0},
  {(char *) "WW", AF_ODARK, NULL, 0},
  {(char *) "WX", AF_ODARK, NULL, 0},
  {(char *) "WY", AF_ODARK, NULL, 0},
  {(char *) "WZ", AF_ODARK, NULL, 0},
  {(char *) "XA", AF_ODARK, NULL, 0},
  {(char *) "XB", AF_ODARK, NULL, 0},
  {(char *) "XC", AF_ODARK, NULL, 0},
  {(char *) "XD", AF_ODARK, NULL, 0},
  {(char *) "XE", AF_ODARK, NULL, 0},
  {(char *) "XF", AF_ODARK, NULL, 0},
  {(char *) "XG", AF_ODARK, NULL, 0},
  {(char *) "XH", AF_ODARK, NULL, 0},
  {(char *) "XI", AF_ODARK, NULL, 0},
  {(char *) "XJ", AF_ODARK, NULL, 0},
  {(char *) "XK", AF_ODARK, NULL, 0},
  {(char *) "XL", AF_ODARK, NULL, 0},
  {(char *) "XM", AF_ODARK, NULL, 0},
  {(char *) "XN", AF_ODARK, NULL, 0},
  {(char *) "XO", AF_ODARK, NULL, 0},
  {(char *) "XP", AF_ODARK, NULL, 0},
  {(char *) "XQ", AF_ODARK, NULL, 0},
  {(char *) "XR", AF_ODARK, NULL, 0},
  {(char *) "XS", AF_ODARK, NULL, 0},
  {(char *) "XT", AF_ODARK, NULL, 0},
  {(char *) "XU", AF_ODARK, NULL, 0},
  {(char *) "XV", AF_ODARK, NULL, 0},
  {(char *) "XW", AF_ODARK, NULL, 0},
  {(char *) "XX", AF_ODARK, NULL, 0},
  {(char *) "XY", AF_ODARK, NULL, 0},
  {(char *) "XZ", AF_ODARK, NULL, 0},
  {(char *) "XYXXY", AF_DARK | AF_NOPROG | AF_LOCKED | AF_WIZARD,
    NULL, 0},
  {NULL, 0, NULL, 0}
};

static char *clean_atr_name (char *s)
{
  static char buf[BUFFER_LEN];
  char *q = buf;
  char *a;

  if (!*s || !s) {
    sprintf (buf, "NULL");
    return buf;
  }

  if (!string_compare ("KILL", s)) {
    sprintf (buf, "DEATH");
    return buf;
  }

  if (!string_compare ("KILL", s + 1) &&
    (*s == 'o' || *s == 'O' || *s == 'a' || *s == 'A')) {
    sprintf (buf, "%c%s", *s, s + 1);
    return buf;
  }

  for (a = s; *a; a++)
    if (isprint ((int)*a) && !isspace ((int)*a))
      *q++ = *a;
  *q = '\0';
  return buf;
}

ATTR *atr_str (char *s)
{
  ATTR *result;
  ATTR *a = attr;
  int done = 0;
  char *q;

  result = (ATTR *) malloc (sizeof (ATTR));
  q = clean_atr_name (s);
  result->name = (char *) malloc (strlen (q) + 1);
#ifdef MEM_CHECK
  add_check ("attribute");
  add_check ("attribute_name");
#endif
  strcpy (result->name, strupper (q));
  result->flags = AF_ODARK;

  while (a->name && !done) {
    if (!string_compare (result->name, a->name)) {
      result->flags = a->flags;
      done++;
    }
    a++;
  }
  return result;
}

struct boolatr *alloc_atr (char *name, char *s)
{
  struct boolatr *a;
  const char *p;

  a = (struct boolatr *) malloc (sizeof (struct boolatr));
  a->name = (char *) malloc (strlen (name) + 1);
  strcpy (a->name, name);
  p = compress (s);
  a->text = (char *) malloc (strlen (p) + 1);
  strcpy (a->text, p);
#ifdef MEM_CHECK
  add_check ("bool_atr");
  add_check ("bool_atr_name");
  add_check ("bool_atr_val");
#endif
  return a;
}

void atr_clr (dbref thing, char *atr)
{
  ALIST *ptr = db[thing].list;

  while (ptr) {
    if (!string_compare (atr, AL_NAME (ptr))) {
      AL_DISPOSE (ptr);
      return;
    }
    ptr = AL_NEXT (ptr);
  }
}

ALIST *AL_MAKE (char *type, ALIST * next, char *string, dbref owner,
  dbref flags)
{
  ALIST *ptr;
  const char *p;

  ptr = (ALIST *) malloc (sizeof (ALIST));
  AL_ATTR (ptr) = atr_str (type);
  AL_CREATOR (ptr) = owner;
  p = compress (string);
  AL_STR (ptr) = (char *) malloc (strlen (p) + 1);
#ifdef MEM_CHECK
  add_check ("ALIST");
  add_check ("attribute_value");
#endif
  strcpy (AL_STR (ptr), p);
  if (flags != NOTHING)
    AL_FLAGS (ptr) |= flags;
  AL_NEXT (ptr) = next;
  return ptr;
}

void atr_new_add (dbref thing, char *atr, char *s, dbref player, dbref flags)
{
  s = (char *) compress (s);
  db[thing].list = AL_MAKE (atr, db[thing].list, s, player, flags);
}

int atr_add (dbref thing, char *atr, char *s, dbref player, dbref flags)
{
  ALIST *ptr;
  dbref privs;

  if (thing == 0) {
    privs = GOD;                /* should only be hit by first couple objects */
    db[player].owner = GOD;
  } else
    privs = db[player].owner;

  if (!s)
    s = (char *) "";

  for (ptr = db[thing].list;
    (ptr && string_compare (atr, AL_NAME (ptr))); ptr = AL_NEXT (ptr));
  if (!*s) {
    if (ptr)
      if (!Wizard (privs) &&
        ((AL_FLAGS (ptr) & AF_WIZARD) ||
          ((privs != db[AL_CREATOR (ptr)].owner) &&
            (AL_FLAGS (ptr) & AF_LOCKED)))) {
        return -1;
      } else {
        AL_DISPOSE (ptr);
        return 1;
    } else
      return 0;
  }
  s = (char *) compress (s);
  if (!ptr) {
    db[thing].list = AL_MAKE (atr, db[thing].list, s, privs, flags);
    return 1;
  } else {
    if (!Wizard (privs) &&
      ((AL_FLAGS (ptr) & AF_WIZARD) ||
        ((privs != db[AL_CREATOR (ptr)].owner) &&
          (AL_FLAGS (ptr) & AF_LOCKED)))) {
      return -1;
    } else {
      const char *p;
      free ((char *) AL_STR (ptr));
#ifdef MEM_CHECK
      del_check ("attribute_value");
#endif
      p = compress (s);
      AL_STR (ptr) = (char *) malloc (strlen (p) + 1);
#ifdef MEM_CHECK
      add_check ("attribute_value");
#endif
      strcpy (AL_STR (ptr), p);
      AL_CREATOR (ptr) = db[privs].owner;
      if (flags != NOTHING)
        AL_FLAGS (ptr) = flags;
      if (AL_BAD (ptr))
        AL_FLAGS (ptr) &= ~AF_NUKED;
      return 1;
    }
  }
   /*NOTREACHED*/ return 0;
}

ATTR *atr_get (dbref thing, char *atr)
{
  ALIST *ptr;

  if (thing == NOTHING || !atr)
    return NULL;

  for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr)) {
    if (!AL_BAD (ptr) && !string_compare (AL_NAME (ptr), atr)) {
      return AL_ATTR (ptr);
    }
  }

  for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr)) {
    if (!AL_BAD (ptr) && string_prefix (AL_NAME (ptr), atr)) {
      return (AL_ATTR (ptr));
    }
  }
  return (ATTR *) NULL;
}

void free_attrib (ATTR * thisattr)
{
  if (thisattr) {
    if (thisattr->name)
      free ((char *) thisattr->name);
    if (thisattr->value)
      free ((char *) thisattr->value);
    free ((char *) thisattr);
  }
#ifdef MEM_CHECK
  del_check ("attribute_name");
  del_check ("attribute_value");
  del_check ("attribute");
#endif
}

void atr_free (dbref thing)
{
  ALIST *ptr, *next;

  for (ptr = db[thing].list; ptr; ptr = next) {
    next = AL_NEXT (ptr);
    free_attrib (AL_ATTR (ptr));
    free ((char *) ptr);
#ifdef MEM_CHECK
    del_check ("ALIST");
#endif
  }
  db[thing].list = (ALIST *) NULL;
}

/* reconstruct an attribute list */
void atr_collect (dbref thing)
{
  ALIST *ptr, *next;

  ptr = db[thing].list;
  db[thing].list = NULL;

  while (ptr) {
    if (!AL_BAD (ptr)) {
      db[thing].list = AL_MAKE (AL_NAME (ptr), db[thing].list,
        AL_STR (ptr), AL_CREATOR (ptr), AL_FLAGS (ptr));
    }
    next = AL_NEXT (ptr);
    free_attrib (AL_ATTR (ptr));
    free ((char *) ptr);
#ifdef MEM_CHECK
    del_check ("ALIST");
#endif
    ptr = next;
  }
}

void atr_cpy (dbref dest, dbref source)
{
  ALIST *ptr;
  ptr = db[source].list;

  db[dest].list = NULL;
  while (ptr) {
    if (!AL_BAD (ptr)) {
      db[dest].list = AL_MAKE (AL_NAME (ptr), db[dest].list,
        AL_STR (ptr), AL_CREATOR (ptr), AL_FLAGS (ptr));
    }
    ptr = AL_NEXT (ptr);
  }
}

char *convert_atr (dbref oldatr)
{
  static char result[MAX_COMMAND_LEN];
  int factor = 0;

  switch (oldatr) {
  case A_OSUCC:
    return "OSUCCESS";
  case A_OFAIL:
    return "OFAILURE";
  case A_FAIL:
    return "FAILURE";
  case A_SUCC:
    return "SUCCESS";
  case A_PASS:
    return "XYXXY";
  case A_DESC:
    return "DESCRIBE";
  case A_SEX:
    return "SEX";
  case A_ODROP:
    return "ODROP";
  case A_DROP:
    return "DROP";
  case A_OKILL:
    return "OKILL";
  case A_KILL:
    return "KILL";
  case A_ASUCC:
    return "ASUCCESS";
  case A_AFAIL:
    return "AFAILURE";
  case A_ADROP:
    return "ADROP";
  case A_AKILL:
    return "AKILL";
  case A_USE:
    return "DOES";
  case A_CHARGES:
    return "CHARGES";
  case A_RUNOUT:
    return "RUNOUT";
  case A_STARTUP:
    return "STARTUP";
  case A_ACLONE:
    return "ACLONE";
  case A_APAY:
    return "APAYMENT";
  case A_OPAY:
    return "OPAYMENT";
  case A_PAY:
    return "PAYMENT";
  case A_COST:
    return "COST";
  case A_RAND:
    return "RAND";
  case A_LISTEN:
    return "LISTEN";
  case A_AAHEAR:
    return "AAHEAR";
  case A_AMHEAR:
    return "AMHEAR";
  case A_AHEAR:
    return "AHEAR";
  case A_LAST:
    return "LAST";
  case A_QUEUE:
    return "QUEUE";
  case A_IDESC:
    return "IDESCRIBE";
  case A_ENTER:
    return "ENTER";
  case A_OXENTER:
    return "OXENTER";
  case A_AENTER:
    return "AENTER";
  case A_ADESC:
    return "ADESCRIBE";
  case A_ODESC:
    return "ODESCRIBE";
  case A_RQUOTA:
    return "RQUOTA";
  case A_ACONNECT:
    return "ACONNECT";
  case A_ADISCONNECT:
    return "ADISCONNECT";
  case A_LEAVE:
    return "LEAVE";
  case A_ALEAVE:
    return "ALEAVE";
  case A_OLEAVE:
    return "OLEAVE";
  case A_OENTER:
    return "OENTER";
  case A_OXLEAVE:
    return "OXLEAVE";
  default:
    if (oldatr >= 100 && oldatr < 126)
      factor = 0;
    else if (oldatr >= 126 && oldatr < 152)
      factor = 1;
    else if (oldatr >= 152 && oldatr < 178)
      factor = 2;
    else {
      fprintf (stderr,
        "ERROR: Invalid attribute number in convert_atr. aborting.\n");
      fflush (stderr);
      abort ();
    }
    sprintf ((char *) result, "%c%c",
      'V' + factor, oldatr - (100 + (factor * 26)) + 'A');
    return result;
  }
   /*NOTREACHED*/ return "";
}

ATTR *atr_match (char *string)
{
  ATTR *a = attr;

  while (a->name) {
    if (!string_compare (string, a->name)) {
      return a;
    }
    a++;
  }
  return (ATTR *) NULL;
}

int atr_comm_match (dbref thing, dbref player, int type, int end, char *str)
{
  ALIST *ptr;
  int match = 0;
  char tbuf1[BUFFER_LEN];
  char *s;

  if (thing < 0 || thing >= db_top)
    return 0;

  for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr)) {
    if (!AL_BAD (ptr) && (*AL_STR (ptr) == type) &&
      !(AL_FLAGS (ptr) & AF_NOPROG)) {
      strcpy (tbuf1, uncompress (AL_STR (ptr)));
      for (s = tbuf1 + 1; *s && (*s != end); s++);
      if (!*s)
        continue;
      *s++ = '\0';
      if (wild_match (tbuf1 + 1, str)) {
        match = 1;
        if ((type == '$') &&
          (!eval_boolexp (player, db[thing].usekey, thing, 0, USELOCK)))
          notify (player, "Permission denied.");
        else
          parse_que (thing, s, player);
      }
    }
  }
  return match;
}

void do_atrlock (dbref player, const char *arg1, const char *arg2)
{
  dbref thing;
  char *p;
  ALIST *ptr;
  int status;

  if (!arg2 || !*arg2)
    status = 0;
  else {
    if (!string_compare (arg2, "on")) {
      status = 1;
    } else if (!string_compare (arg2, "off")) {
      status = 2;
    } else
      status = 0;
  }

  if (!arg1 || !*arg1) {
    notify (player, "You need to give an object/attribute pair.");
    return;
  }

  if (!(p = index (arg1, '/')) || !(*(p + 1))) {
    notify (player, "You need to give an object/attribute pair.");
    return;
  }
  *p++ = '\0';

  init_match (player, arg1, NOTYPE);
  match_everything ();
  if ((thing = noisy_match_result ()) == NOTHING)
    return;

  for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr))
    if (!AL_BAD (ptr) && !string_compare (AL_NAME (ptr), p))
      break;

  if (!ptr)
    for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr))
      if (!AL_BAD (ptr) && string_prefix (AL_NAME (ptr), p))
        break;

  if (ptr) {
    if (!status) {
      notify (player, tprintf ("That attribute is %slocked.",
          (AL_FLAGS (ptr) & AF_LOCKED) ? "" : "un"));
      return;
    } else if (!Wizard (player) &&
      (db[AL_CREATOR (ptr)].owner != db[player].owner)) {
      notify (player, "You need to own the attribute to change its lock.");
      return;
    } else {
      if (status == 1) {
        AL_FLAGS (ptr) |= AF_LOCKED;
        notify (player, "Attribute locked.");
        return;
      } else if (status == 2) {
        AL_FLAGS (ptr) &= ~AF_LOCKED;
        notify (player, "Attribute unlocked.");
        return;
      } else {
        notify (player, "Invalid status on atrlock.. Notify god.");
        return;
      }
    }
  } else
    notify (player, "No such attribute.");
  return;
}

void do_atrchown (dbref player, const char *arg1, const char *arg2)
{
  dbref thing, new_owner;
  char *p;
  ALIST *ptr;

  if (!arg1 || !*arg1) {
    notify (player, "You need to give an object/attribute pair.");
    return;
  }

  if (!(p = index (arg1, '/')) || !(*(p + 1))) {
    notify (player, "You need to give an object/attribute pair.");
    return;
  }
  *p++ = '\0';

  init_match (player, arg1, NOTYPE);
  match_everything ();
  if ((thing = noisy_match_result ()) == NOTHING)
    return;

  if ((!arg2 && !*arg2) || !string_compare (arg2, "me"))
    new_owner = player;
  else
    new_owner = lookup_player (arg2);

  if (new_owner == NOTHING) {
    notify (player, "I can't find that player");
    return;
  }

  for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr))
    if (!AL_BAD (ptr) && !string_compare (AL_NAME (ptr), p))
      break;

  if (!ptr)
    for (ptr = db[thing].list; ptr; ptr = AL_NEXT (ptr))
      if (!AL_BAD (ptr) && string_prefix (AL_NAME (ptr), p))
        break;

  if (ptr) {
    if ((controls (player, thing) && !(AL_FLAGS (ptr) & AF_LOCKED)) ||
      (db[player].owner == db[AL_CREATOR (ptr)].owner)) {
      if (new_owner != db[thing].owner && !Wizard (player)) {
        notify (player,
          "You can only chown an attribute to the current owner of the object.");
        return;
      }
      AL_CREATOR (ptr) = db[new_owner].owner;
      notify (player, "Attribute owner changed.");
      return;
    } else {
      notify (player, "You don't have the permission to chown that.");
      return;
    }
  } else
    notify (player, "No such attribute.");
}

ATTR *atr_complete_match (dbref player, char *atr, dbref privs)
{
  ATTR *a;
  char *s = atr;
  dbref thing;

  if (*s == '_')
    s++;

  if ((a = atr_get (player, s)) != NULL) {
    if (!controls (privs, player) &&
      db[privs].owner != db[a->creator].owner && (a->flags & AF_ODARK)) {
      DOLIST (thing, db[player].contents) {
        if ((a = atr_get (thing, s)) != NULL) {
          if (!controls (privs, player) &&
            db[privs].owner != db[a->creator].owner &&
            (a->flags & AF_ODARK)) {
            return NULL;
          } else {
            return a;
          }
        }
      }
    } else {
      return a;
    }
  }
  return NULL;
}