pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
pennmush/po/
pennmush/win32/msvc.net/
pennmush/win32/msvc6/
/**
 * \file extchat.c
 *
 * \brief The PennMUSH chat system
 *
 *
 */
#include "copyrite.h"
#include "config.h"
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#ifdef I_SYS_TYPES
#include <sys/types.h>
#endif
#include <stdarg.h>
#include "conf.h"
#include "externs.h"
#include "attrib.h"
#include "mushdb.h"
#include "match.h"
#include "flags.h"
#include "extchat.h"
#include "ansi.h"
#include "privtab.h"
#include "mymalloc.h"
#include "pueblo.h"
#include "parse.h"
#include "lock.h"
#include "log.h"
#include "game.h"
#include "dbdefs.h"
#include "function.h"
#include "command.h"
#include "dbio.h"
#include "confmagic.h"


static CHAN *new_channel(void);
static CHANLIST *new_chanlist(void);
static CHANUSER *new_user(dbref who);
static void free_channel(CHAN *c);
static void free_chanlist(CHANLIST *cl);
static void free_user(CHANUSER *u);
static int load_chatdb_oldstyle(FILE * fp);
static int load_channel(FILE * fp, CHAN *ch);
static int load_chanusers(FILE * fp, CHAN *ch);
static int load_labeled_channel(FILE * fp, CHAN *ch);
static int load_labeled_chanusers(FILE * fp, CHAN *ch);
static void insert_channel(CHAN **ch);
static void remove_channel(CHAN *ch);
static void insert_obj_chan(dbref who, CHAN **ch);
static void remove_obj_chan(dbref who, CHAN *ch);
void remove_all_obj_chan(dbref thing);
static void chan_chown(CHAN *c, dbref victim);
void chan_chownall(dbref old, dbref new);
static int insert_user(CHANUSER *user, CHAN *ch);
static int remove_user(CHANUSER *u, CHAN *ch);
static int save_channel(FILE * fp, CHAN *ch);
static int save_chanuser(FILE * fp, CHANUSER *user);
static void channel_wipe(dbref player, CHAN *chan);
static int yesno(const char *str);
static int canstilladd(dbref player);
static enum cmatch_type find_channel_partial_on(const char *name, CHAN **chan,
						dbref player);
static enum cmatch_type find_channel_partial_off(const char *name, CHAN **chan,
						 dbref player);
static char *list_cflags(CHAN *c);
static char *list_cuflags(CHANUSER *u);
static void channel_join_self(dbref player, const char *name);
static void channel_leave_self(dbref player, const char *name);
static void do_channel_who(dbref player, CHAN *chan);
void chat_player_announce(dbref player, char *msg, int ungag);
static int ok_channel_name(const char *n);
static void format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim,
				     int flags, const char *msg,
				     const char *extra);
static void list_partial_matches(dbref player, const char *name,
				 enum chan_match_type type);

const char *chan_speak_lock = "ChanSpeakLock";	/**< Name of speak lock */
const char *chan_join_lock = "ChanJoinLock";	/**< Name of join lock */
const char *chan_mod_lock = "ChanModLock";	/**< Name of modify lock */
const char *chan_see_lock = "ChanSeeLock";	/**< Name of see lock */
const char *chan_hide_lock = "ChanHideLock";	/**< Name of hide lock */

#define YES 1	  /**< An affirmative. */
#define NO 0	  /**< A negative. */
#define ERR -1	  /**< An error. Clever, eh? */

/** Wrapper for insert_user() that generates a new CHANUSER and inserts it */
#define insert_user_by_dbref(who,chan) \
        insert_user(new_user(who),chan)
/** Wrapper for remove_user() that searches for the CHANUSER to remove */
#define remove_user_by_dbref(who,chan) \
        remove_user(onchannel(who,chan),chan)

int num_channels;  /**< Number of channels defined */

CHAN *channels;	   /**< Pointer to channel list */

static PRIV priv_table[] = {
  {"Disabled", 'D', CHANNEL_DISABLED, CHANNEL_DISABLED},
  {"Admin", 'A', CHANNEL_ADMIN | CHANNEL_PLAYER, CHANNEL_ADMIN},
  {"Wizard", 'W', CHANNEL_WIZARD | CHANNEL_PLAYER, CHANNEL_WIZARD},
  {"Player", 'P', CHANNEL_PLAYER, CHANNEL_PLAYER},
  {"Object", 'O', CHANNEL_OBJECT, CHANNEL_OBJECT},
  {"Quiet", 'Q', CHANNEL_QUIET, CHANNEL_QUIET},
  {"Open", 'o', CHANNEL_OPEN, CHANNEL_OPEN},
  {"Hide_Ok", 'H', CHANNEL_CANHIDE, CHANNEL_CANHIDE},
  {"NoTitles", 'T', CHANNEL_NOTITLES, CHANNEL_NOTITLES},
  {"NoNames", 'N', CHANNEL_NONAMES, CHANNEL_NONAMES},
  {"NoCemit", 'C', CHANNEL_NOCEMIT, CHANNEL_NOCEMIT},
  {NULL, '\0', 0, 0}
};


/** Get a player's CHANUSER entry if they're on a channel.
 * This function checks to see if a given player is on a given channel.
 * If so, it returns a pointer to their CHANUSER structure. If not,
 * returns NULL.
 * \param who player to test channel membership of.
 * \param ch pointer to channel to test membership on.
 * \return player's CHANUSER entry on the channel, or NULL.
 */
CHANUSER *
onchannel(dbref who, CHAN *ch)
{
  static CHANUSER *u;
  for (u = ChanUsers(ch); u; u = u->next) {
    if (CUdbref(u) == who) {
      return u;
    }
  }
  return NULL;
}

/** A macro to test if a channel exists and, if not, to notify. */
#define test_channel(player,name,chan) \
   do { \
    chan = NULL; \
    switch (find_channel(name,&chan,player)) { \
    case CMATCH_NONE: \
      notify(player, T ("CHAT: I don't recognize that channel.")); \
      return; \
    case CMATCH_AMBIG: \
      notify(player, T("CHAT: I don't know which channel you mean.")); \
      list_partial_matches(player, name, PMATCH_ALL); \
      return; \
    default: \
      break; \
     } \
    } while (0)

/*----------------------------------------------------------
 * Loading and saving the chatdb
 * The chatdb's format is pretty straightforward
 * Return 1 on success, 0 on failure
 */

/** Initialize the chat database .*/
void
init_chatdb(void)
{
  num_channels = 0;
  channels = NULL;
}

/** Load the chat database from a file.
 * \param fp pointer to file to read from.
 * \retval 1 success
 * \retval 0 failure
 */
static int
load_chatdb_oldstyle(FILE * fp)
{
  int i;
  CHAN *ch;
  char buff[20];

  /* How many channels? */
  num_channels = getref(fp);
  if (num_channels > MAX_CHANNELS)
    return 0;

  /* Load all channels */
  for (i = 0; i < num_channels; i++) {
    if (feof(fp))
      break;
    ch = new_channel();
    if (!ch)
      return 0;
    if (!load_channel(fp, ch)) {
      do_rawlog(LT_ERR, T("Unable to load channel %d."), i);
      free_channel(ch);
      return 0;
    }
    insert_channel(&ch);
  }
  num_channels = i;

  /* Check for **END OF DUMP*** */
  fgets(buff, sizeof buff, fp);
  if (!buff)
    do_rawlog(LT_ERR, T("CHAT: No end-of-dump marker in the chat database."));
  else if (strcmp(buff, EOD) != 0)
    do_rawlog(LT_ERR, T("CHAT: Trailing garbage in the chat database."));

  return 1;
}

extern char db_timestamp[];

/** Load the chat database from a file.
 * \param fp pointer to file to read from.
 * \retval 1 success
 * \retval 0 failure
 */
int
load_chatdb(FILE * fp)
{
  int i, flags;
  CHAN *ch;
  char buff[20];
  char *chat_timestamp;

  i = fgetc(fp);
  if (i == EOF) {
    do_rawlog(LT_ERR, T("CHAT: Invalid database format!"));
    longjmp(db_err, 1);
  } else if (i != '+') {
    ungetc(i, fp);
    return load_chatdb_oldstyle(fp);
  }

  i = fgetc(fp);

  if (i != 'V') {
    do_rawlog(LT_ERR, T("CHAT: Invalid database format!"));
    longjmp(db_err, 1);
  }

  flags = getref(fp);

  db_read_this_labeled_string(fp, "savedtime", &chat_timestamp);

  if (strcmp(chat_timestamp, db_timestamp))
    do_rawlog(LT_ERR,
	      T
	      ("CHAT: warning: chatdb and game db were saved at different times!"));

  /* How many channels? */
  db_read_this_labeled_number(fp, "channels", &num_channels);
  if (num_channels > MAX_CHANNELS)
    return 0;

  /* Load all channels */
  for (i = 0; i < num_channels; i++) {
    ch = new_channel();
    if (!ch)
      return 0;
    if (!load_labeled_channel(fp, ch)) {
      do_rawlog(LT_ERR, T("Unable to load channel %d."), i);
      free_channel(ch);
      return 0;
    }
    insert_channel(&ch);
  }
  num_channels = i;

  /* Check for **END OF DUMP*** */
  fgets(buff, sizeof buff, fp);
  if (!buff)
    do_rawlog(LT_ERR, T("CHAT: No end-of-dump marker in the chat database."));
  else if (strcmp(buff, EOD) != 0)
    do_rawlog(LT_ERR, T("CHAT: Trailing garbage in the chat database."));

  return 1;
}


/* Malloc memory for a new channel, and initialize it */
static CHAN *
new_channel(void)
{
  CHAN *ch;

  ch = (CHAN *) mush_malloc(sizeof(CHAN), "CHAN");
  if (!ch)
    return NULL;
  ch->name[0] = '\0';
  ch->title[0] = '\0';
  ChanType(ch) = CHANNEL_DEFAULT_FLAGS;
  ChanCreator(ch) = NOTHING;
  ChanCost(ch) = CHANNEL_COST;
  ChanNext(ch) = NULL;
  ChanNumMsgs(ch) = 0;
  /* By default channels are public but mod-lock'd to the creator */
  ChanJoinLock(ch) = TRUE_BOOLEXP;
  ChanSpeakLock(ch) = TRUE_BOOLEXP;
  ChanSeeLock(ch) = TRUE_BOOLEXP;
  ChanHideLock(ch) = TRUE_BOOLEXP;
  ChanModLock(ch) = TRUE_BOOLEXP;
  ChanNumUsers(ch) = 0;
  ChanMaxUsers(ch) = 0;
  ChanUsers(ch) = NULL;
  ChanBufferQ(ch) = NULL;
  return ch;
}



/* Malloc memory for a new user, and initialize it */
static CHANUSER *
new_user(dbref who)
{
  CHANUSER *u;
  u = (CHANUSER *) mush_malloc(sizeof(CHANUSER), "CHANUSER");
  if (!u)
    mush_panic("Couldn't allocate memory in new_user in extchat.c");
  CUdbref(u) = who;
  CUtype(u) = CU_DEFAULT_FLAGS;
  u->title[0] = '\0';
  CUnext(u) = NULL;
  return u;
}

/* Free memory from a channel */
static void
free_channel(CHAN *c)
{
  CHANUSER *u, *unext;
  if (!c)
    return;
  free_boolexp(ChanJoinLock(c));
  free_boolexp(ChanSpeakLock(c));
  free_boolexp(ChanHideLock(c));
  free_boolexp(ChanSeeLock(c));
  free_boolexp(ChanModLock(c));
  u = ChanUsers(c);
  while (u) {
    unext = u->next;
    free_user(u);
    u = unext;
  }
  return;
}

/* Free memory from a channel user */
static void
free_user(CHANUSER *u)
{
  if (u)
    mush_free(u, "CHANUSER");
}

/* Load in a single channel into position i. Return 1 if
 * successful, 0 otherwise.
 */
static int
load_channel(FILE * fp, CHAN *ch)
{
  strcpy(ChanName(ch), getstring_noalloc(fp));
  if (feof(fp))
    return 0;
  strcpy(ChanTitle(ch), getstring_noalloc(fp));
  ChanType(ch) = getref(fp);
  ChanCreator(ch) = getref(fp);
  ChanCost(ch) = getref(fp);
  ChanNumMsgs(ch) = 0;
  ChanJoinLock(ch) = getboolexp(fp, chan_join_lock);
  ChanSpeakLock(ch) = getboolexp(fp, chan_speak_lock);
  ChanModLock(ch) = getboolexp(fp, chan_mod_lock);
  ChanSeeLock(ch) = getboolexp(fp, chan_see_lock);
  ChanHideLock(ch) = getboolexp(fp, chan_hide_lock);
  ChanNumUsers(ch) = getref(fp);
  ChanMaxUsers(ch) = ChanNumUsers(ch);
  ChanUsers(ch) = NULL;
  if (ChanNumUsers(ch) > 0)
    ChanNumUsers(ch) = load_chanusers(fp, ch);
  return 1;
}

/* Load in a single channel into position i. Return 1 if
 * successful, 0 otherwise.
 */
static int
load_labeled_channel(FILE * fp, CHAN *ch)
{
  char *tmp;
  int i;
  dbref d;
  char *label, *value;

  db_read_this_labeled_string(fp, "name", &tmp);
  strcpy(ChanName(ch), tmp);
  db_read_this_labeled_string(fp, "description", &tmp);
  strcpy(ChanTitle(ch), tmp);
  db_read_this_labeled_number(fp, "flags", &i);
  ChanType(ch) = i;
  db_read_this_labeled_dbref(fp, "creator", &d);
  ChanCreator(ch) = d;
  db_read_this_labeled_number(fp, "cost", &i);
  ChanCost(ch) = i;
  ChanNumMsgs(ch) = 0;
  while (1) {
    db_read_labeled_string(fp, &label, &value);
    if (strcmp(label, "lock"))
      break;
    else if (strcmp(value, "join") == 0)
      ChanJoinLock(ch) = getboolexp(fp, chan_join_lock);
    else if (strcmp(value, "speak") == 0)
      ChanSpeakLock(ch) = getboolexp(fp, chan_speak_lock);
    else if (strcmp(value, "modify") == 0)
      ChanModLock(ch) = getboolexp(fp, chan_mod_lock);
    else if (strcmp(value, "see") == 0)
      ChanSeeLock(ch) = getboolexp(fp, chan_see_lock);
    else if (strcmp(value, "hide") == 0)
      ChanHideLock(ch) = getboolexp(fp, chan_hide_lock);
  }
  ChanNumUsers(ch) = parse_integer(value);
  ChanMaxUsers(ch) = ChanNumUsers(ch);
  ChanUsers(ch) = NULL;
  if (ChanNumUsers(ch) > 0)
    ChanNumUsers(ch) = load_labeled_chanusers(fp, ch);
  return 1;
}


/* Load the *channel's user list. Return number of users on success, or 0 */
static int
load_chanusers(FILE * fp, CHAN *ch)
{
  int i, num = 0;
  CHANUSER *user;
  dbref player;
  for (i = 0; i < ChanNumUsers(ch); i++) {
    player = getref(fp);
    /* Don't bother if the player isn't a valid dbref or the wrong type */
    if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
      user = new_user(player);
      CUtype(user) = getref(fp);
      strcpy(CUtitle(user), getstring_noalloc(fp));
      CUnext(user) = NULL;
      if (insert_user(user, ch))
	num++;
    } else {
      /* But be sure to read (and discard) the player's info */
      do_log(LT_ERR, 0, 0, T("Bad object #%d removed from channel %s"),
	     player, ChanName(ch));
      (void) getref(fp);
      (void) getstring_noalloc(fp);
    }
  }
  return num;
}

/* Load the *channel's user list. Return number of users on success, or 0 */
static int
load_labeled_chanusers(FILE * fp, CHAN *ch)
{
  int i, num = 0, n;
  char *tmp;
  CHANUSER *user;
  dbref player;
  for (i = 0; i < ChanNumUsers(ch); i++) {
    db_read_this_labeled_dbref(fp, "dbref", &player);
    /* Don't bother if the player isn't a valid dbref or the wrong type */
    if (GoodObject(player) && Chan_Ok_Type(ch, player)) {
      user = new_user(player);
      db_read_this_labeled_number(fp, "flags", &n);
      CUtype(user) = n;
      db_read_this_labeled_string(fp, "title", &tmp);
      strcpy(CUtitle(user), tmp);
      CUnext(user) = NULL;
      if (insert_user(user, ch))
	num++;
    } else {
      /* But be sure to read (and discard) the player's info */
      do_log(LT_ERR, 0, 0, T("Bad object #%d removed from channel %s"),
	     player, ChanName(ch));
      db_read_this_labeled_number(fp, "type", &n);
      db_read_this_labeled_string(fp, "title", &tmp);
    }
  }
  return num;
}


/* Insert the channel onto the list of channels, sorted by name */
static void
insert_channel(CHAN **ch)
{
  CHAN *p;
  char cleanname[CHAN_NAME_LEN];
  char cleanp[CHAN_NAME_LEN];

  if (!ch || !*ch)
    return;

  /* If there's no users on the list, or if the first user is already
   * alphabetically greater, user should be the first entry on the list */
  /* No channels? */
  if (!channels) {
    channels = *ch;
    channels->next = NULL;
    return;
  }
  p = channels;
  /* First channel? */
  strcpy(cleanp, remove_markup(ChanName(p), NULL));
  strcpy(cleanname, remove_markup(ChanName(*ch), NULL));
  if (strcasecoll(cleanp, cleanname) > 0) {
    channels = *ch;
    channels->next = p;
    return;
  }
  /* Otherwise, find which user this user should be inserted after */
  while (p->next) {
    strcpy(cleanp, remove_markup(ChanName(p->next), NULL));
    if (strcasecoll(cleanp, cleanname) > 0)
      break;
    p = p->next;
  }
  (*ch)->next = p->next;
  p->next = *ch;
  return;
}

/* Remove a channel from the list, but don't free it */
static void
remove_channel(CHAN *ch)
{
  CHAN *p;

  if (!ch)
    return;
  if (!channels)
    return;
  if (channels == ch) {
    /* First channel */
    channels = ch->next;
    return;
  }
  /* Otherwise, find the channel before this one */
  for (p = channels; p->next && (p->next != ch); p = p->next) ;

  if (p->next) {
    p->next = ch->next;
  }
  return;
}

/* Insert the channel onto the list of channels on a given object,
 * sorted by name
 */
static void
insert_obj_chan(dbref who, CHAN **ch)
{
  CHANLIST *p;
  CHANLIST *tmp;

  if (!ch || !*ch)
    return;

  tmp = new_chanlist();
  if (!tmp)
    return;
  tmp->chan = *ch;
  /* If there's no channels on the list, or if the first channel is already
   * alphabetically greater, chan should be the first entry on the list */
  /* No channels? */
  if (!Chanlist(who)) {
    tmp->next = NULL;
    s_Chanlist(who, tmp);
    return;
  }
  p = Chanlist(who);
  /* First channel? */
  if (strcasecoll(ChanName(p->chan), ChanName(*ch)) > 0) {
    tmp->next = p;
    s_Chanlist(who, tmp);
    return;
  } else if (!strcasecmp(ChanName(p->chan), ChanName(*ch))) {
    /* Don't add the same channel twice! */
    free_chanlist(tmp);
  } else {
    /* Otherwise, find which channel this channel should be inserted after */
    for (;
	 p->next
	 && (strcasecoll(ChanName(p->next->chan), ChanName(*ch)) < 0);
	 p = p->next) ;
    if (p->next && !strcasecmp(ChanName(p->next->chan), ChanName(*ch))) {
      /* Don't add the same channel twice! */
      free_chanlist(tmp);
    } else {
      tmp->next = p->next;
      p->next = tmp;
    }
  }
  return;
}

/* Remove a channel from the obj's chanlist, and free the chanlist ptr */
static void
remove_obj_chan(dbref who, CHAN *ch)
{
  CHANLIST *p, *q;

  if (!ch)
    return;
  p = Chanlist(who);
  if (!p)
    return;
  if (p->chan == ch) {
    /* First channel */
    s_Chanlist(who, p->next);
    free_chanlist(p);
    return;
  }
  /* Otherwise, find the channel before this one */
  for (; p->next && (p->next->chan != ch); p = p->next) ;

  if (p->next) {
    q = p->next;
    p->next = p->next->next;
    free_chanlist(q);
  }
  return;
}

/** Remove all channels from the obj's chanlist, freeing them.
 * \param thing object to have channels removed from.
 */
void
remove_all_obj_chan(dbref thing)
{
  CHANLIST *p, *nextp;
  for (p = Chanlist(thing); p; p = nextp) {
    nextp = p->next;
    remove_user_by_dbref(thing, p->chan);
  }
  return;
}


static CHANLIST *
new_chanlist(void)
{
  CHANLIST *c;
  c = (CHANLIST *) mush_malloc(sizeof(CHANLIST), "CHANLIST");
  if (!c)
    return NULL;
  c->chan = NULL;
  c->next = NULL;
  return c;
}

static void
free_chanlist(CHANLIST *cl)
{
  mush_free(cl, "CHANLIST");
}


/* Insert the user onto the channel's list, sorted by the user's name */
static int
insert_user(CHANUSER *user, CHAN *ch)
{
  CHANUSER *p;

  if (!user || !ch)
    return 0;

  /* If there's no users on the list, or if the first user is already
   * alphabetically greater, user should be the first entry on the list */
  p = ChanUsers(ch);
  if (!p || (strcasecoll(Name(CUdbref(p)), Name(CUdbref(user))) > 0)) {
    user->next = ChanUsers(ch);
    ChanUsers(ch) = user;
  } else {
    /* Otherwise, find which user this user should be inserted after */
    for (;
	 p->next
	 && (strcasecoll(Name(CUdbref(p->next)), Name(CUdbref(user))) <= 0);
	 p = p->next) ;
    if (CUdbref(p) == CUdbref(user)) {
      /* Don't add the same user twice! */
      mush_free((Malloc_t) user, "CHANUSER");
      return 0;
    } else {
      user->next = p->next;
      p->next = user;
    }
  }
  insert_obj_chan(CUdbref(user), &ch);
  return 1;
}

/* Remove a user from a channel list, and free it */
static int
remove_user(CHANUSER *u, CHAN *ch)
{
  CHANUSER *p;
  dbref who;

  if (!ch || !u)
    return 0;
  p = ChanUsers(ch);
  if (!p)
    return 0;
  who = CUdbref(u);
  if (p == u) {
    /* First user */
    ChanUsers(ch) = p->next;
    free_user(u);
  } else {
    /* Otherwise, find the user before this one */
    for (; p->next && (p->next != u); p = p->next) ;

    if (p->next) {
      p->next = u->next;
      free_user(u);
    } else
      return 0;
  }

  /* Now remove the channel from the user's chanlist */
  remove_obj_chan(who, ch);
  ChanNumUsers(ch)--;
  return 1;
}


/** Write the chat database to disk.
 * \param fp pointer to file to write to.
 * \retval 1 success
 * \retval 0 failure
 */
int
save_chatdb(FILE * fp)
{
  CHAN *ch;
  int default_flags = 0;

  /* How many channels? */
  OUTPUT(fprintf(fp, "+V%d\n", default_flags));
  db_write_labeled_string(fp, "savedtime", show_time(mudtime, 1));
  db_write_labeled_number(fp, "channels", num_channels);
  for (ch = channels; ch; ch = ch->next) {
    save_channel(fp, ch);
  }
  OUTPUT(fputs(EOD, fp));
  return 1;
}

/* Save a single channel. Return 1 if  successful, 0 otherwise.
 */
static int
save_channel(FILE * fp, CHAN *ch)
{
  CHANUSER *cu;

  db_write_labeled_string(fp, " name", ChanName(ch));
  db_write_labeled_string(fp, "  description", ChanTitle(ch));
  db_write_labeled_number(fp, "  flags", ChanType(ch));
  db_write_labeled_dbref(fp, "  creator", ChanCreator(ch));
  db_write_labeled_number(fp, "  cost", ChanCost(ch));
  db_write_labeled_string(fp, "  lock", "join");
  putboolexp(fp, ChanJoinLock(ch));
  db_write_labeled_string(fp, "  lock", "speak");
  putboolexp(fp, ChanSpeakLock(ch));
  db_write_labeled_string(fp, "  lock", "modify");
  putboolexp(fp, ChanModLock(ch));
  db_write_labeled_string(fp, "  lock", "see");
  putboolexp(fp, ChanSeeLock(ch));
  db_write_labeled_string(fp, "  lock", "hide");
  putboolexp(fp, ChanHideLock(ch));
  db_write_labeled_number(fp, "  users", ChanNumUsers(ch));
  for (cu = ChanUsers(ch); cu; cu = cu->next)
    save_chanuser(fp, cu);
  return 1;
}

/* Save the channel's user list. Return 1 on success, 0 on failure */
static int
save_chanuser(FILE * fp, CHANUSER *user)
{
  db_write_labeled_dbref(fp, "   dbref", CUdbref(user));
  db_write_labeled_number(fp, "    flags", CUtype(user));
  db_write_labeled_string(fp, "    title", CUtitle(user));
  return 1;
}

/*-------------------------------------------------------------*
 * Some utility functions:
 *  find_channel - given a name and a player, return a channel
 *  find_channel_partial - given a name and a player, return
 *    the first channel that matches name 
 *  find_channel_partial_on - given a name and a player, return
 *    the first channel that matches name that player is on.
 *  onchannel - is player on channel?
 */

/** Removes markup and <>'s in channel names.
 * \param name The name to normalize.
 * \retval a pointer to a static buffer with the normalized name. 
 */
static char *
normalize_channel_name(const char *name)
{
  static char cleanname[BUFFER_LEN];
  size_t len;

  cleanname[0] = '\0';

  if (!name || !*name)
    return cleanname;

  strcpy(cleanname, remove_markup(name, &len));
  len--;

  if (!*cleanname)
    return cleanname;

  if (cleanname[0] == '<' && cleanname[len - 1] == '>') {
    cleanname[len - 1] = '\0';
    return cleanname + 1;
  } else
    return cleanname;
}

/** Attempt to match a channel name for a player.
 * Given name and a chan pointer, set chan pointer to point to
 * channel if found (NULL otherwise), and return an indication
 * of how good the match was. If the player is not able to see
 * the channel, fail to match.
 * \param name name of channel to find.
 * \param chan pointer to address of channel structure to return.
 * \param player dbref to use for permission checks.
 * \retval CMATCH_EXACT exact match of channel name.
 * \retval CMATCH_PARTIAL partial match of channel name.
 * \retval CMATCH_AMBIG multiple match of channel name.
 * \retval CMATCH_NONE no match for channel name.
 */
enum cmatch_type
find_channel(const char *name, CHAN **chan, dbref player)
{
  CHAN *p;
  int count = 0;
  char *cleanname;
  char cleanp[CHAN_NAME_LEN];

  *chan = NULL;
  if (!name || !*name)
    return CMATCH_NONE;

  cleanname = normalize_channel_name(name);
  for (p = channels; p; p = p->next) {
    strcpy(cleanp, remove_markup(ChanName(p), NULL));
    if (!strcasecmp(cleanname, cleanp)) {
      *chan = p;
      if (Chan_Can_See(*chan, player) || onchannel(player, *chan))
	return CMATCH_EXACT;
      else
	return CMATCH_NONE;
    }
    if (string_prefix(cleanp, name)) {
      /* Keep the alphabetically first channel if we've got one */
      if (Chan_Can_See(p, player) || onchannel(player, p)) {
	if (!*chan)
	  *chan = p;
	count++;
      }
    }
  }
  switch (count) {
  case 0:
    return CMATCH_NONE;
  case 1:
    return CMATCH_PARTIAL;
  }
  return CMATCH_AMBIG;
}


/** Attempt to match a channel name for a player.
 * Given name and a chan pointer, set chan pointer to point to
 * channel if found (NULL otherwise), and return an indication
 * of how good the match was. If the player is not able to see
 * the channel, fail to match. If the match is ambiguous, return
 * the first channel matched.
 * \param name name of channel to find.
 * \param chan pointer to address of channel structure to return.
 * \param player dbref to use for permission checks.
 * \retval CMATCH_EXACT exact match of channel name.
 * \retval CMATCH_PARTIAL partial match of channel name.
 * \retval CMATCH_AMBIG multiple match of channel name.
 * \retval CMATCH_NONE no match for channel name.
 */
enum cmatch_type
find_channel_partial(const char *name, CHAN **chan, dbref player)
{
  CHAN *p;
  int count = 0;
  char *cleanname;
  char cleanp[CHAN_NAME_LEN];

  *chan = NULL;
  if (!name || !*name)
    return CMATCH_NONE;
  cleanname = normalize_channel_name(name);
  for (p = channels; p; p = p->next) {
    strcpy(cleanp, remove_markup(ChanName(p), NULL));
    if (!strcasecmp(cleanname, cleanp)) {
      *chan = p;
      return CMATCH_EXACT;
    }
    if (string_prefix(cleanp, cleanname)) {
      /* If we've already found an ambiguous match that the
       * player is on, keep using that one. Otherwise, this is
       * our best candidate so far.
       */
      if (!*chan || (!onchannel(player, *chan) && onchannel(player, p)))
	*chan = p;
      count++;
    }
  }
  switch (count) {
  case 0:
    return CMATCH_NONE;
  case 1:
    return CMATCH_PARTIAL;
  }
  return CMATCH_AMBIG;
}

static void
list_partial_matches(dbref player, const char *name, enum chan_match_type type)
{
  CHAN *p;
  char *cleanname;
  char cleanp[CHAN_NAME_LEN];
  char buff[BUFFER_LEN], *bp;
  bp = buff;

  if (!name || !*name)
    return;

  safe_str(T("CHAT: Partial matches are:"), buff, &bp);
  cleanname = normalize_channel_name(name);
  for (p = channels; p; p = p->next) {
    if (!Chan_Can_See(p, player))
      continue;
    if ((type == PMATCH_ALL) || ((type == PMATCH_ON)
				 ? (int) (void *) onchannel(player, p)
				 : !onchannel(player, p))) {
      strcpy(cleanp, remove_markup(ChanName(p), NULL));
      if (string_prefix(cleanp, cleanname)) {
	safe_chr(' ', buff, &bp);
	safe_str(ChanName(p), buff, &bp);
      }
    }
  }

  safe_chr('\0', buff, &bp);
  notify(player, buff);

}




/** Attempt to match a channel name for a player.
 * Given name and a chan pointer, set chan pointer to point to
 * channel if found and player is on the channel (NULL otherwise), 
 * and return an indication of how good the match was. If the player is 
 * not able to see the channel, fail to match. If the match is ambiguous, 
 * return the first channel matched.
 * \param name name of channel to find.
 * \param chan pointer to address of channel structure to return.
 * \param player dbref to use for permission checks.
 * \retval CMATCH_EXACT exact match of channel name.
 * \retval CMATCH_PARTIAL partial match of channel name.
 * \retval CMATCH_AMBIG multiple match of channel name.
 * \retval CMATCH_NONE no match for channel name.
 */
static enum cmatch_type
find_channel_partial_on(const char *name, CHAN **chan, dbref player)
{
  CHAN *p;
  int count = 0;
  char *cleanname;
  char cleanp[CHAN_NAME_LEN];

  *chan = NULL;
  if (!name || !*name)
    return CMATCH_NONE;
  cleanname = normalize_channel_name(name);
  for (p = channels; p; p = p->next) {
    if (onchannel(player, p)) {
      strcpy(cleanp, remove_markup(ChanName(p), NULL));
      if (!strcasecmp(cleanname, cleanp)) {
	*chan = p;
	return CMATCH_EXACT;
      }
      if (string_prefix(cleanp, cleanname) && onchannel(player, p)) {
	if (!*chan)
	  *chan = p;
	count++;
      }
    }
  }
  switch (count) {
  case 0:
    return CMATCH_NONE;
  case 1:
    return CMATCH_PARTIAL;
  }
  return CMATCH_AMBIG;
}

/** Attempt to match a channel name for a player.
 * Given name and a chan pointer, set chan pointer to point to
 * channel if found and player is NOT on the channel (NULL otherwise), 
 * and return an indication of how good the match was. If the player is 
 * not able to see the channel, fail to match. If the match is ambiguous, 
 * return the first channel matched.
 * \param name name of channel to find.
 * \param chan pointer to address of channel structure to return.
 * \param player dbref to use for permission checks.
 * \retval CMATCH_EXACT exact match of channel name.
 * \retval CMATCH_PARTIAL partial match of channel name.
 * \retval CMATCH_AMBIG multiple match of channel name.
 * \retval CMATCH_NONE no match for channel name.
 */
static enum cmatch_type
find_channel_partial_off(const char *name, CHAN **chan, dbref player)
{
  CHAN *p;
  int count = 0;
  char *cleanname;
  char cleanp[CHAN_NAME_LEN];

  *chan = NULL;
  if (!name || !*name)
    return CMATCH_NONE;
  cleanname = normalize_channel_name(name);
  for (p = channels; p; p = p->next) {
    if (!onchannel(player, p)) {
      strcpy(cleanp, remove_markup(ChanName(p), NULL));
      if (!strcasecmp(cleanname, cleanp)) {
	*chan = p;
	return CMATCH_EXACT;
      }
      if (string_prefix(cleanp, cleanname)) {
	if (!*chan)
	  *chan = p;
	count++;
      }
    }
  }
  switch (count) {
  case 0:
    return CMATCH_NONE;
  case 1:
    return CMATCH_PARTIAL;
  }
  return CMATCH_AMBIG;
}

/*--------------------------------------------------------------*
 * User commands:
 *  do_channel - @channel/on,off,who
 *  do_chan_admin - @channel/add,delete,name,priv,quiet
 *  do_chan_desc
 *  do_chan_title
 *  do_chan_lock
 *  do_chan_boot
 *  do_chan_wipe
 */

/** User interface to channels.
 * \verbatim
 * This is one of the top-level functions for @channel.
 * It handles the /on, /off and /who switches. It also
 * parses and handles the older @channel <channel>=<command>
 * format, for the on, off, who, and wipe commands.
 * \endverbatim
 * \param player the enactor.
 * \param name name of channel.
 * \param target name of player to add/remove (NULL for self)
 * \param com channel command switch.
 */
void
do_channel(dbref player, const char *name, const char *target, const char *com)
{
  CHAN *chan = NULL;
  CHANUSER *u;
  dbref victim;

  if (!name && !*name) {
    notify(player, T("You need to specify a channel."));
    return;
  }
  if (!com && !*com) {
    notify(player, T("What do you want to do with that channel?"));
    return;
  }

  /* Quickly catch two common cases and handle separately */
  if (!target || !*target) {
    if (!strcasecmp(com, "on") || !strcasecmp(com, "join")) {
      channel_join_self(player, name);
      return;
    } else if (!strcasecmp(com, "off") || !strcasecmp(com, "leave")) {
      channel_leave_self(player, name);
      return;
    }
  }

  test_channel(player, name, chan);
  if (!Chan_Can_See(chan, player)) {
    if (onchannel(player, chan))
      notify_format(player,
		    T("CHAT: You can't do that with channel <%s>."),
		    ChanName(chan));
    else
      notify(player, T("CHAT: I don't recognize that channel."));
    return;
  }
  if (!strcasecmp(com, "who")) {
    do_channel_who(player, chan);
    return;
  } else if (!strcasecmp(com, "wipe")) {
    channel_wipe(player, chan);
    return;
  }
  /* It's on or off now */
  /* Determine who is getting added or deleted. If we don't have
   * an argument, we return, because we should've caught those above,
   * and this shouldn't happen.
   */
  if (!target || !*target) {
    notify(player, T("I don't understand what you want to do."));
    return;
  } else if ((victim = lookup_player(target)) != NOTHING) ;
  else if (Channel_Object(chan))
    victim = match_result(player, target, TYPE_THING, MAT_OBJECTS);
  else
    victim = NOTHING;
  if (!GoodObject(victim)) {
    notify(player, T("Invalid target."));
    return;
  }
  if (!strcasecmp("on", com) || !strcasecmp("join", com)) {
    if (!Chan_Ok_Type(chan, victim)) {
      notify_format(player,
		    T("Sorry, wrong type of thing for channel <%s>."),
		    ChanName(chan));
      return;
    }
    if (Guest(player)) {
      notify(player, T("Guests are not allowed to join channels."));
      return;
    }
    if (!controls(player, victim)) {
      notify(player, T("Invalid target."));
      return;
    }
    /* Is victim already on the channel? */
    if (onchannel(victim, chan)) {
      notify_format(player,
		    T("%s is already on channel <%s>."), Name(victim),
		    ChanName(chan));
      return;
    }
    /* Does victim pass the joinlock? */
    if (!Chan_Can_Join(chan, victim)) {
      if (Wizard(player)) {
	/* Wizards can override join locks */
	notify(player,
	       T
	       ("CHAT: Warning: Target does not meet channel join permissions (joining anyway)"));
      } else {
	notify(player, T("Permission to join denied."));
	return;
      }
    }
    if (insert_user_by_dbref(victim, chan)) {
      notify_format(victim,
		    T("CHAT: %s joins you to channel <%s>."), Name(player),
		    ChanName(chan));
      notify_format(player,
		    T("CHAT: You join %s to channel <%s>."), Name(victim),
		    ChanName(chan));
      u = onchannel(victim, chan);
      if (!Channel_Quiet(chan) && !DarkLegal(victim)) {
	format_channel_broadcast(chan, u, victim, CB_CHECKQUIET | CB_PRESENCE,
				 T("<%s> %s has joined this channel."), NULL);
      }
      ChanNumUsers(chan)++;
    } else {
      notify_format(player,
		    T("%s is already on channel <%s>."), Name(victim),
		    ChanName(chan));
    }
    return;
  } else if (!strcasecmp("off", com) || !strcasecmp("leave", com)) {
    char title[CU_TITLE_LEN];
    /* You must control either the victim or the channel */
    if (!controls(player, victim) && !Chan_Can_Modify(chan, player)) {
      notify(player, T("Invalid target."));
      return;
    }
    if (Guest(player)) {
      notify(player, T("Guests may not leave channels."));
      return;
    }
    u = onchannel(victim, chan);
    strcpy(title, (u &&CUtitle(u)) ? CUtitle(u) : "");
    if (remove_user(u, chan)) {
      if (!Channel_Quiet(chan) && !DarkLegal(victim)) {
	format_channel_broadcast(chan, NULL, victim,
				 CB_CHECKQUIET | CB_PRESENCE,
				 T("<%s> %s has left this channel."), title);
      }
      notify_format(victim,
		    T("CHAT: %s removes you from channel <%s>."),
		    Name(player), ChanName(chan));
      notify_format(player,
		    T("CHAT: You remove %s from channel <%s>."),
		    Name(victim), ChanName(chan));
    } else {
      notify_format(player, T("%s is not on channel <%s>."), Name(victim),
		    ChanName(chan));
    }
    return;
  } else {
    notify(player, T("I don't understand what you want to do."));
    return;
  }
}

static void
channel_join_self(dbref player, const char *name)
{
  CHAN *chan = NULL;
  CHANUSER *u;

  if (Guest(player)) {
    notify(player, T("Guests are not allowed to join channels."));
    return;
  }

  switch (find_channel_partial_off(name, &chan, player)) {
  case CMATCH_NONE:
    if (find_channel_partial_on(name, &chan, player))
      notify_format(player, T("CHAT: You are already on channel <%s>"),
		    ChanName(chan));
    else
      notify(player, T("CHAT: I don't recognize that channel."));
    return;
  case CMATCH_AMBIG:
    notify(player, T("CHAT: I don't know which channel you mean."));
    list_partial_matches(player, name, PMATCH_OFF);
    return;
  default:
    break;
  }
  if (!Chan_Can_See(chan, player)) {
    notify(player, T("CHAT: I don't recognize that channel."));
    return;
  }
  if (!Chan_Ok_Type(chan, player)) {
    notify_format(player,
		  T("Sorry, wrong type of thing for channel <%s>."),
		  ChanName(chan));
    return;
  }
  /* Does victim pass the joinlock? */
  if (!Chan_Can_Join(chan, player)) {
    if (Wizard(player)) {
      /* Wizards can override join locks */
      notify(player,
	     T
	     ("CHAT: Warning: You don't meet channel join permissions (joining anyway)"));
    } else {
      notify(player, T("Permission to join denied."));
      return;
    }
  }
  if (insert_user_by_dbref(player, chan)) {
    notify_format(player, T("CHAT: You join channel <%s>."), ChanName(chan));
    u = onchannel(player, chan);
    if (!Channel_Quiet(chan) && !DarkLegal(player))
      format_channel_broadcast(chan, u, player, CB_CHECKQUIET | CB_PRESENCE,
			       T("<%s> %s has joined this channel."), NULL);
    ChanNumUsers(chan)++;
  } else {
    /* Should never happen */
    notify_format(player,
		  T("%s is already on channel <%s>."), Name(player),
		  ChanName(chan));
  }
}

static void
channel_leave_self(dbref player, const char *name)
{
  CHAN *chan = NULL;
  CHANUSER *u;
  char title[CU_TITLE_LEN];

  if (Guest(player)) {
    notify(player, T("Guests are not allowed to leave channels."));
    return;
  }
  switch (find_channel_partial_on(name, &chan, player)) {
  case CMATCH_NONE:
    if (find_channel_partial_off(name, &chan, player)
	&& Chan_Can_See(chan, player))
      notify_format(player, T("CHAT: You are not on channel <%s>"),
		    ChanName(chan));
    else
      notify(player, T("CHAT: I don't recognize that channel."));
    return;
  case CMATCH_AMBIG:
    notify(player, T("CHAT: I don't know which channel you mean."));
    list_partial_matches(player, name, PMATCH_ON);
    return;
  default:
    break;
  }
  u = onchannel(player, chan);
  strcpy(title, (u &&CUtitle(u)) ? CUtitle(u) : "");
  if (remove_user(u, chan)) {
    if (!Channel_Quiet(chan) && !DarkLegal(player))
      format_channel_broadcast(chan, NULL, player, CB_CHECKQUIET | CB_PRESENCE,
			       T("<%s> %s has left this channel."), title);
    notify_format(player, T("CHAT: You leave channel <%s>."), ChanName(chan));
  } else {
    /* Should never happen */
    notify_format(player, T("%s is not on channel <%s>."), Name(player),
		  ChanName(chan));
  }
}

/** Parse a chat token command, but don't chat with it.
 * \verbatim
 * This function hacks up something of the form "+<channel> <message>",
 * finding the two args, and returns 1 if the channel exists,
 * otherwise 0.
 * \endverbatim
 * \param player the enactor.
 * \param command the command to parse.
 * \retval 1 channel exists
 * \retval 0 chat failed (no such channel, etc.)
 */
int
parse_chat(dbref player, char *command)
{
  char *arg1;
  char *arg2;
  char *s;
  char ch;
  CHAN *c;

  s = command;
  arg1 = s;
  while (*s && !isspace((unsigned char) *s))
    s++;

  if (!*s)
    return 0;

  ch = *s;
  arg2 = s;
  *arg2++ = '\0';
  while (*arg2 && isspace((unsigned char) *arg2))
    arg2++;

  /* arg1 is channel name, arg2 is text. */
  switch (find_channel_partial_on(arg1, &c, player)) {
  case CMATCH_AMBIG:
  case CMATCH_EXACT:
  case CMATCH_PARTIAL:
    *s = '=';
    return 1;
  default:
    *s = ch;
    return 0;
  }
}


/** Chat on a channel, given its name.
 * \verbatim
 * This function parses a channel name and then calls do_chat()
 * to do the actual work of chatting. If it was called through
 * @chat, it fails noisily, but if it was called through +channel,
 * it fails silently so that the command can be matched against $commands.
 * \endverbatim
 * \param player the enactor.
 * \param name name of channel to speak on.
 * \param msg message to send to channel.
 * \param source 0 if from +channel, 1 if from the chat command.
 * \retval 1 successful chat.
 * \retval 0 failed chat.
 */
int
do_chat_by_name(dbref player, const char *name, const char *msg, int source)
{
  CHAN *c;
  c = NULL;
  if (!msg || !*msg) {
    if (source)
      notify(player, T("Don't you have anything to say?"));
    return 0;
  }
  /* First try to find a channel that the player's on. If that fails,
   * look for one that the player's not on.
   */
  switch (find_channel_partial_on(name, &c, player)) {
  case CMATCH_AMBIG:
    if (!ChanUseFirstMatch(player)) {
      notify(player, T("CHAT: I don't know which channel you mean."));
      list_partial_matches(player, name, PMATCH_ON);
      notify(player,
	     T
	     ("CHAT: You may wish to set the CHAN_USEFIRSTMATCH flag on yourself."));
      return 1;
    }
  case CMATCH_EXACT:
  case CMATCH_PARTIAL:
    do_chat(player, c, msg);
    return 1;
  case CMATCH_NONE:
    if (find_channel(name, &c, player) == CMATCH_NONE) {
      if (source)
	notify(player, T("CHAT: No such channel."));
      return 0;
    }
  }
  return 0;
}

/** Send a message to a channel.
 * This function does the real work of putting together a message
 * to send to a chat channel (which it then does via channel_broadcast()).
 * \param player the enactor.
 * \param chan pointer to the channel to speak on.
 * \param arg1 message to send.
 */
void
do_chat(dbref player, CHAN *chan, const char *arg1)
{
  CHANUSER *u;
  const char *gap;
  const char *someone = "Someone";
  char *title;
  const char *name;
  int canhear;

  if (!Chan_Ok_Type(chan, player)) {
    notify_format(player,
		  T
		  ("Sorry, you're not the right type to be on channel <%s>."),
		  ChanName(chan));
    return;
  }
  if (!Chan_Can_Speak(chan, player)) {
    if (Chan_Can_See(chan, player))
      notify_format(player,
		    T("Sorry, you're not allowed to speak on channel <%s>."),
		    ChanName(chan));
    else
      notify(player, T("No such channel."));
    return;
  }
  u = onchannel(player, chan);
  canhear = u ? !Chanuser_Gag(u) : 0;
  /* If the channel isn't open, you must hear it in order to speak */
  if (!Channel_Open(chan)) {
    if (!u) {
      notify(player, T("You must be on that channel to speak on it."));
      return;
    } else if (!canhear) {
      notify(player, T("You must stop gagging that channel to speak on it."));
      return;
    }
  }

  if (!*arg1) {
    notify(player, T("What do you want to say to that channel?"));
    return;
  }

  if (!Channel_NoTitles(chan) && u &&CUtitle(u) && *CUtitle(u))
     title = CUtitle(u);
  else
    title = NULL;
  if (Channel_NoNames(chan))
    name = NULL;
  else
    name = accented_name(player);
  if (!title && !name)
    name = someone;

  /* figure out what kind of message we have */
  gap = " ";
  switch (*arg1) {
  case SEMI_POSE_TOKEN:
    gap = "";
    /* FALLTHRU */
  case POSE_TOKEN:
    arg1 = arg1 + 1;
    channel_broadcast(chan, player, 0, "<%s> %s%s%s%s%s%s", ChanName(chan),
		      title ? title : "", title ? ANSI_NORMAL : "",
		      (title && name) ? " " : "", name ? name : "", gap, arg1);
    if (!canhear)
      notify_format(player, T("To channel %s: %s%s%s%s%s%s"), ChanName(chan),
		    title ? title : "", title ? ANSI_NORMAL : "",
		    (title && name) ? " " : "", name ? name : "", gap, arg1);
    break;
  default:
    if (CHAT_STRIP_QUOTE && (*arg1 == SAY_TOKEN))
      arg1 = arg1 + 1;
    channel_broadcast(chan, player, 0, T("<%s> %s%s%s%s says, \"%s\""),
		      ChanName(chan), title ? title : "",
		      title ? ANSI_NORMAL : "", (title && name) ? " " : "",
		      name ? name : "", arg1);
    if (!canhear)
      notify_format(player,
		    T("To channel %s: %s%s%s%s says, \"%s\""),
		    ChanName(chan), title ? title : "",
		    title ? ANSI_NORMAL : "", (title && name) ? " " : "",
		    name ? name : "", arg1);
    break;
  }

  ChanNumMsgs(chan)++;
}

/** Emit on a channel.
 * \verbatim
 * This is the top-level function for @cemit.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the channel.
 * \param msg message to emit.
 * \param flags PEMIT_* flags.
 */
void
do_cemit(dbref player, const char *name, const char *msg, int flags)
{
  CHAN *chan = NULL;
  CHANUSER *u;
  int canhear;
  int override_checks = 0;

  if (!name || !*name) {
    notify(player, T("That is not a valid channel."));
    return;
  }
  switch (find_channel(name, &chan, player)) {
  case CMATCH_NONE:
    notify(player, T("I don't recognize that channel."));
    return;
  case CMATCH_AMBIG:
    notify(player, T("I don't know which channel you mean."));
    list_partial_matches(player, name, PMATCH_ALL);
    return;
  default:
    break;
  }
  if (!Chan_Can_See(chan, player)) {
    notify(player, T("CHAT: I don't recognize that channel."));
    return;
  }
  /* If the cemitter is both See_All and Pemit_All, always allow them
   * to @cemit, as they could iterate over connected players, examine
   * their channels, and pemit to them anyway.
   */
  if (See_All(player) && Pemit_All(player))
    override_checks = 1;
  if (!override_checks && !Chan_Ok_Type(chan, player)) {
    notify_format(player,
		  T
		  ("Sorry, you're not the right type to be on channel <%s>."),
		  ChanName(chan));
    return;
  }
  if (!override_checks && !Chan_Can_Cemit(chan, player)) {
    notify_format(player,
		  T("Sorry, you're not allowed to @cemit on channel <%s>."),
		  ChanName(chan));
    return;
  }
  u = onchannel(player, chan);
  canhear = u ? !Chanuser_Gag(u) : 0;
  /* If the channel isn't open, you must hear it in order to speak */
  if (!override_checks && !Channel_Open(chan)) {
    if (!u) {
      notify(player, T("You must be on that channel to speak on it."));
      return;
    } else if (!canhear) {
      notify(player, T("You must stop gagging that channel to speak on it."));
      return;
    }
  }

  if (!msg || !*msg) {
    notify(player, T("What do you want to emit?"));
    return;
  }
  if (!(flags & PEMIT_SILENT))
    channel_broadcast(chan, player, (flags & PEMIT_SPOOF) ? 0 : CB_NOSPOOF,
		      "<%s> %s", ChanName(chan), msg);
  else
    channel_broadcast(chan, player, (flags & PEMIT_SPOOF) ? 0 : CB_NOSPOOF,
		      "%s", msg);
  if (!canhear)
    notify_format(player, T("Cemit to channel %s: %s"), ChanName(chan), msg);
  ChanNumMsgs(chan)++;
  return;
}

/** Administrative channel commands.
 * \verbatim
 * This is one of top-level functions for @channel. This one handles
 * the /add, /delete, /rename, and /priv switches.
 * \endverbatim
 * \param player the enactor.
 * \param name the name of the channel.
 * \param perms the permissions to set on an added/priv'd channel, the newname for a renamed channel, or on/off for a quieted channel.
 * \param flag switch indicator: 0=add, 1=delete, 2=rename, 3=priv
 */
void
do_chan_admin(dbref player, char *name, const char *perms, int flag)
{
  CHAN *chan = NULL, *temp = NULL;
  long int type;
  int res;
  boolexp key;
  char old[CHAN_NAME_LEN];

  if (!name || !*name) {
    notify(player, T("You must specify a channel."));
    return;
  }
  if (Guest(player)) {
    notify(player, T("Guests may not modify channels."));
    return;
  }
  if ((flag > 1) && (!perms || !*perms)) {
    notify(player, T("What do you want to do with the channel?"));
    return;
  }
  /* Make sure we've got a unique channel name unless we're
   * adding a channel */
  if (flag)
    test_channel(player, name, chan);
  switch (flag) {
  case 0:
    /* add a channel */
    if (num_channels == MAX_CHANNELS) {
      notify(player, T("No more room for channels."));
      return;
    }
    if (strlen(name) > CHAN_NAME_LEN - 1) {
      notify(player, T("The channel needs a shorter name."));
      return;
    }
    if (!ok_channel_name(name)) {
      notify(player, T("Invalid name for a channel."));
      return;
    }
    if (!Hasprivs(player) && !canstilladd(player)) {
      notify(player, T("You already own too many channels."));
      return;
    }
    res = find_channel(name, &chan, GOD);
    if (res != CMATCH_NONE) {
      notify(player, T("CHAT: The channel needs a more unique name."));
      return;
    }
    /* get the permissions. Invalid specs default to the default */
    type = string_to_privs(priv_table, perms, 0);
    if (!Chan_Can(player, type)) {
      notify(player, T("You can't create channels of that type."));
      return;
    }
    if (type & CHANNEL_DISABLED)
      notify(player, T("Warning: channel will be created disabled."));
    /* Can the player afford it? There's a cost */
    if (!payfor(Owner(player), CHANNEL_COST)) {
      notify_format(player, T("You can't afford the %d %s."), CHANNEL_COST,
		    MONIES);
      return;
    }
    /* Ok, let's do it */
    chan = new_channel();
    if (!chan) {
      notify(player, T("CHAT: No more memory for channels!"));
      giveto(Owner(player), CHANNEL_COST);
      return;
    }
    key = parse_boolexp(player, tprintf("=#%d", player), chan_mod_lock);
    if (!key) {
      mush_free(chan, "CHAN");
      notify(player, T("CHAT: No more memory for channels!"));
      giveto(Owner(player), CHANNEL_COST);
      return;
    }
    ChanModLock(chan) = key;
    num_channels++;
    if (type)
      ChanType(chan) = type;
    ChanCreator(chan) = Owner(player);
    strcpy(ChanName(chan), name);
    insert_channel(&chan);
    notify_format(player, T("CHAT: Channel <%s> created."), ChanName(chan));
    break;
  case 1:
    /* remove a channel */
    /* Check permissions. Wizards and owners can remove */
    if (!Chan_Can_Nuke(chan, player)) {
      notify(player, T("Permission denied."));
      return;
    }
    /* remove everyone from the channel */
    channel_wipe(player, chan);
    /* refund the owner's money */
    giveto(ChanCreator(chan), ChanCost(chan));
    /* zap the channel */
    remove_channel(chan);
    free_channel(chan);
    num_channels--;
    notify(player, T("Channel removed."));
    break;
  case 2:
    /* rename a channel */
    /* Can the player do this? */
    if (!Chan_Can_Modify(chan, player)) {
      notify(player, T("Permission denied."));
      return;
    }
    /* make sure the channel name is unique */
    if (find_channel(perms, &temp, GOD)) {
      /* But allow renaming a channel to a differently-cased version of
       * itself 
       */
      if (temp != chan) {
	notify(player, T("The channel needs a more unique new name."));
	return;
      }
    }
    if (strlen(perms) > CHAN_NAME_LEN - 1) {
      notify(player, T("That name is too long."));
      return;
    }
    /* When we rename a channel, we actually remove it and re-insert it */
    strcpy(old, ChanName(chan));
    remove_channel(chan);
    strcpy(ChanName(chan), perms);
    insert_channel(&chan);
    channel_broadcast(chan, player, 0,
		      "<%s> %s has renamed channel %s to %s.",
		      ChanName(chan), Name(player), old, ChanName(chan));
    notify(player, T("Channel renamed."));
    break;
  case 3:
    /* change the permissions on a channel */
    if (!Chan_Can_Modify(chan, player)) {
      notify(player, T("Permission denied."));
      return;
    }
    /* get the permissions. Invalid specs default to no change */
    type = string_to_privs(priv_table, perms, ChanType(chan));
    if (!Chan_Can_Priv(player, type)) {
      notify(player, T("You can't make channels that type."));
      return;
    }
    if (type & CHANNEL_DISABLED)
      notify(player, T("Warning: channel will be disabled."));
    if (type == ChanType(chan)) {
      notify_format(player,
		    T
		    ("Invalid or same permissions on channel <%s>. No changes made."),
		    ChanName(chan));
    } else {
      ChanType(chan) = type;
      notify_format(player,
		    T("Permissions on channel <%s> changed."), ChanName(chan));
    }
    break;
  }
}

static int
ok_channel_name(const char *n)
{
  /* is name valid for a channel? */
  const char *p;
  char name[BUFFER_LEN];

  strcpy(name, remove_markup(n, NULL));

  if (!name || !*name)
    return 0;

  /* No leading spaces */
  if (isspace((unsigned char) *name))
    return 0;

  /* only printable characters */
  for (p = name; p && *p; p++) {
    if (!isprint((unsigned char) *p))
      return 0;
  }

  /* No trailing spaces */
  p--;
  if (isspace((unsigned char) *p))
    return 0;

  return 1;
}


/** Modify a user's settings on a channel.
 * \verbatim
 * This is one of top-level functions for @channel. This one
 * handles the /mute, /hide, and /gag switches, which control an
 * individual user's settings on a single channel.
 * \endverbatim
 * \param player the enactor.
 * \param name the name of the channel.
 * \param isyn a yes/no string.
 * \param flag switch indicator: 0=mute, 1=hide, 2=gag
 * \param silent if 1, no notification of actions.
 */
void
do_chan_user_flags(dbref player, char *name, const char *isyn, int flag,
		   int silent)
{
  CHAN *c = NULL;
  CHANUSER *u;
  CHANLIST *p;
  int setting = abs(yesno(isyn));
  p = NULL;

  if (!name || !*name) {
    p = Chanlist(player);
    if (!p) {
      notify(player, T("You are not on any channels."));
      return;
    }
    silent = 1;
    switch (flag) {
    case 0:
      notify(player, setting ? T("All channels have been muted.")
	     : T("All channels have been unmuted."));
      break;
    case 1:
      notify(player, setting ? T("You hide on all the channels you can.")
	     : T("You unhide on all channels."));
      break;
    case 2:
      notify(player, setting ? T("All channels have been gagged.")
	     : T("All channels have been ungagged."));
      break;
    }
  } else {
    test_channel(player, name, c);
  }

  /* channel loop */
  do {
    /* If we have a channel list at the start, 
     * that means they didn't gave us a channel name,
     * so we now figure out c. */
    if (p != NULL) {
      c = p->chan;
      p = p->next;
    }

    u = onchannel(player, c);
    if (!u) {
      /* This should only happen if they gave us a bad name */
      if (!silent)
	notify_format(player, T("You are not on channel <%s>."), ChanName(c));
      return;
    }

    switch (flag) {
    case 0:
      /* Mute */
      if (setting) {
	CUtype(u) |= CU_QUIET;
	if (!silent)
	  notify_format(player,
			T
			("You will no longer hear connection messages on channel <%s>."),
			ChanName(c));
      } else {
	CUtype(u) &= ~CU_QUIET;
	if (!silent)
	  notify_format(player,
			T
			("You will now hear connection messages on channel <%s>."),
			ChanName(c));
      }
      break;

    case 1:
      /* Hide */
      if (setting) {
	if (!Chan_Can_Hide(c, player) && !Wizard(player)) {
	  if (!silent)
	    notify_format(player,
			  T("You are not permitted to hide on channel <%s>."),
			  ChanName(c));
	} else {
	  CUtype(u) |= CU_HIDE;
	  if (!silent)
	    notify_format(player,
			  T
			  ("You no longer appear on channel <%s>'s who list."),
			  ChanName(c));
	}
      } else {
	CUtype(u) &= ~CU_HIDE;
	if (!silent)
	  notify_format(player,
			T("You now appear on channel <%s>'s who list."),
			ChanName(c));
      }
      break;
    case 2:
      /* Gag */
      if (setting) {
	CUtype(u) |= CU_GAG;
	if (!silent)
	  notify_format(player,
			T
			("You will no longer hear messages on channel <%s>."),
			ChanName(c));
      } else {
	CUtype(u) &= ~CU_GAG;
	if (!silent)
	  notify_format(player,
			T("You will now hear messages on channel <%s>."),
			ChanName(c));
      }
      break;
    }
  } while (p != NULL);

  return;
}

/** Set a user's title for the channel.
 * \verbatim
 * This is one of the top-level functions for @channel. It handles
 * the /title switch.
 * \param player the enactor.
 * \param name the name of the channel.
 * \param title the player's new channel title.
 */
void
do_chan_title(dbref player, const char *name, const char *title)
{
  CHAN *c = NULL;
  CHANUSER *u;
  const char *scan;

  if (!name || !*name) {
    notify(player, T("You must specify a channel."));
    return;
  }
  if (strlen(title) >= CU_TITLE_LEN) {
    notify(player, T("Title too long."));
    return;
  }
  /* Stomp newlines and other weird whitespace */
  for (scan = title; *scan; scan++) {
    if ((isspace((unsigned char) *scan) && (*scan != ' '))
	|| (*scan == BEEP_CHAR)) {
      notify(player, T("Invalid character in title."));
      return;
    }
  }
  test_channel(player, name, c);
  u = onchannel(player, c);
  if (!u) {
    notify_format(player, T("You are not on channel <%s>."), ChanName(c));
    return;
  }
  strcpy(CUtitle(u), title);
  if (!Quiet(player))
    notify_format(player, T("Title %s for %schannel <%s>."),
		  *title ? T("set") : T("cleared"),
		  Channel_NoTitles(c) ? "(NoTitles) " : "", ChanName(c));
  return;
}

/** List all the channels and their flags.
 * \verbatim
 * This is one of the top-level functions for @channel. It handles the
 * /list switch.
 * \endverbatim
 * \param player the enactor.
 * \param partname a partial channel name to match.
 */
void
do_channel_list(dbref player, const char *partname)
{
  CHAN *c;
  CHANUSER *u;
  char numusers[BUFFER_LEN];
  char cleanname[CHAN_NAME_LEN];
  const char thirtyblanks[31] = "                              ";
  char blanks[31];
  int numblanks;

  if (SUPPORT_PUEBLO)
    notify_noenter(player, tprintf("%cSAMP%c", TAG_START, TAG_END));
  notify_format(player, "%-30s %-5s %8s %-16s %-8s %-3s",
		"Name", "Users", "Msgs", T("Chan Type"), "Status", "Buf");
  for (c = channels; c; c = c->next) {
    strcpy(cleanname, remove_markup(ChanName(c), NULL));
    if (Chan_Can_See(c, player) && string_prefix(cleanname, partname)) {
      u = onchannel(player, c);
      if (SUPPORT_PUEBLO)
	sprintf(numusers,
		"%cA XCH_CMD=\"@channel/who %s\" XCH_HINT=\"See who's on this channel now\"%c%5ld%c/A%c",
		TAG_START, cleanname, TAG_END, ChanNumUsers(c),
		TAG_START, TAG_END);
      else
	sprintf(numusers, "%5ld", ChanNumUsers(c));
      numblanks = strlen(ChanName(c)) - strlen(cleanname);
      if (numblanks > 0 && numblanks < 31) {
	strcpy(blanks, thirtyblanks);
	blanks[numblanks] = '\0';
      } else {
	blanks[0] = '\0';
      }
      notify_format(player,
		    "%-30s%s %s %8ld [%c%c%c%c%c%c%c %c%c%c%c%c%c] [%-3s %c%c] %3d",
		    ChanName(c), blanks, numusers, ChanNumMsgs(c),
		    Channel_Disabled(c) ? 'D' : '-',
		    Channel_Player(c) ? 'P' : '-',
		    Channel_Object(c) ? 'O' : '-',
		    Channel_Admin(c) ? 'A' : (Channel_Wizard(c) ? 'W' : '-'),
		    Channel_Quiet(c) ? 'Q' : '-',
		    Channel_CanHide(c) ? 'H' : '-', Channel_Open(c) ? 'o' : '-',
		    /* Locks */
		    ChanJoinLock(c) != TRUE_BOOLEXP ? 'j' : '-',
		    ChanSpeakLock(c) != TRUE_BOOLEXP ? 's' : '-',
		    ChanModLock(c) != TRUE_BOOLEXP ? 'm' : '-',
		    ChanSeeLock(c) != TRUE_BOOLEXP ? 'v' : '-',
		    ChanHideLock(c) != TRUE_BOOLEXP ? 'h' : '-',
		    /* Does the player own it? */
		    ChanCreator(c) == player ? '*' : '-',
		    /* User status */
		    u ? (Chanuser_Gag(u) ? "Gag" : "On") : "Off",
		    (u &&Chanuser_Quiet(u)) ? 'Q' : ' ',
		    (u &&Chanuser_Hide(u)) ? 'H' : ' ',
		    bufferq_lines(ChanBufferQ(c)));
    }
  }
  if (SUPPORT_PUEBLO)
    notify_noenter(player, tprintf("%c/SAMP%c", TAG_START, TAG_END));
}

static char *
list_cflags(CHAN *c)
{
  static char tbuf1[BUFFER_LEN];
  char *bp;

  bp = tbuf1;
  if (Channel_Disabled(c))
    safe_chr('D', tbuf1, &bp);
  if (Channel_Player(c))
    safe_chr('P', tbuf1, &bp);
  if (Channel_Object(c))
    safe_chr('O', tbuf1, &bp);
  if (Channel_Admin(c))
    safe_chr('A', tbuf1, &bp);
  if (Channel_Wizard(c))
    safe_chr('W', tbuf1, &bp);
  if (Channel_Quiet(c))
    safe_chr('Q', tbuf1, &bp);
  if (Channel_CanHide(c))
    safe_chr('H', tbuf1, &bp);
  if (Channel_Open(c))
    safe_chr('o', tbuf1, &bp);
  if (Channel_NoTitles(c))
    safe_chr('T', tbuf1, &bp);
  if (Channel_NoNames(c))
    safe_chr('N', tbuf1, &bp);
  if (Channel_NoCemit(c))
    safe_chr('C', tbuf1, &bp);
  *bp = '\0';
  return tbuf1;
}

static char *
list_cuflags(CHANUSER *u)
{
  static char tbuf1[BUFFER_LEN];
  char *bp;

  bp = tbuf1;
  if (Chanuser_Gag(u))
     safe_chr('G', tbuf1, &bp);
  if (Chanuser_Quiet(u))
     safe_chr('Q', tbuf1, &bp);
  if (Chanuser_Hide(u))
     safe_chr('H', tbuf1, &bp);
  *bp = '\0';
  return tbuf1;
}

/* ARGSUSED */
FUNCTION(fun_cflags)
{
  /* With one channel arg, returns list of set flags, as per 
   * do_channel_list. Sample output: PQ, Oo, etc.
   * With two args (channel,object) return channel-user flags
   * for that object on that channel (a subset of GQH).
   * You must pass channel's @clock/see, and, in second case,
   * must be able to examine the object.
   */
  CHAN *c;
  CHANUSER *u;
  dbref thing;

  if (!args[0] || !*args[0]) {
    safe_str(T("#-1 NO CHANNEL GIVEN"), buff, bp);
    return;
  }
  switch (find_channel(args[0], &c, executor)) {
  case CMATCH_NONE:
    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
    return;
  case CMATCH_AMBIG:
    safe_str(T("#-1 AMBIGUOUS CHANNEL NAME"), buff, bp);
    return;
  default:
    if (!Chan_Can_See(c, executor)) {
      safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
      return;
    }
    if (nargs == 1) {
      safe_str(list_cflags(c), buff, bp);
      return;
    }
    thing = match_thing(executor, args[1]);
    if (thing == NOTHING) {
      safe_str(T(e_match), buff, bp);
      return;
    }
    if (!Can_Examine(executor, thing)) {
      safe_str(T(e_perm), buff, bp);
      return;
    }
    u = onchannel(thing, c);
    if (!u) {
      safe_str(T("#-1 NOT ON CHANNEL"), buff, bp);
      return;
    }
    safe_str(list_cuflags(u), buff, bp);
    break;
  }
}


/* ARGSUSED */
FUNCTION(fun_ctitle)
{
  /* ctitle(<channel>,<object>) returns the object's chantitle on that chan.
   * You must pass the channel's see-lock, and
   * either you must either be able to examine <object>, or
   * <object> must not be hidden, and either
   *   a) You must be on <channel>, or
   *   b) You must pass the join-lock 
   */
  CHAN *c;
  CHANUSER *u;
  dbref thing;
  int ok;
  int can_ex;

  if (!args[0] || !*args[0]) {
    safe_str(T("#-1 NO CHANNEL GIVEN"), buff, bp);
    return;
  }
  switch (find_channel(args[0], &c, executor)) {
  case CMATCH_NONE:
    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
    return;
  case CMATCH_AMBIG:
    safe_str(T("#-1 AMBIGUOUS CHANNEL NAME"), buff, bp);
    return;
  default:
    thing = match_thing(executor, args[1]);
    if (thing == NOTHING) {
      safe_str(T(e_match), buff, bp);
      return;
    }
    if (!Chan_Can_See(c, executor)) {
      safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
      return;
    }
    can_ex = Can_Examine(executor, thing);
    ok = (onchannel(executor, c) || Chan_Can_Join(c, executor));
    u = onchannel(thing, c);
    if (!u) {
      if (can_ex || ok)
	safe_str(T("#-1 NOT ON CHANNEL"), buff, bp);
      else
	safe_str(T("#-1 PERMISSION DENIED"), buff, bp);
      return;
    }
    ok &= !Chanuser_Hide(u);
    if (!(can_ex || ok)) {
      safe_str(T("#-1 PERMISSION DENIED"), buff, bp);
      return;
    }
    if (CUtitle(u))
       safe_str(CUtitle(u), buff, bp);
    break;
  }
}

FUNCTION(fun_cowner)
{
  /* Return the dbref of the owner of a channel. */
  CHAN *c;

  if (!args[0] || !*args[0]) {
    safe_str(T("#-1 NO CHANNEL GIVEN"), buff, bp);
    return;
  }
  switch (find_channel(args[0], &c, executor)) {
  case CMATCH_NONE:
    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
    break;
  case CMATCH_AMBIG:
    safe_str(T("#-1 AMBIGUOUS CHANNEL NAME"), buff, bp);
    break;
  default:
    safe_dbref(ChanCreator(c), buff, bp);
  }

}

/* Remove all players from a channel, notifying them. This is the 
 * utility routine for handling it. The command @channel/wipe
 * calls do_chan_wipe, below
 */
static void
channel_wipe(dbref player, CHAN *chan)
{
  CHANUSER *u, *nextu;
  dbref victim;
  /* This is easy. Just call remove_user on each user in the list */
  if (!chan)
    return;
  for (u = ChanUsers(chan); u; u = nextu) {
    nextu = u->next;
    victim = CUdbref(u);
    if (remove_user(u, chan))
       notify_format(victim, T("CHAT: %s has removed all users from <%s>."),
		     Name(player), ChanName(chan));
  }
  ChanNumUsers(chan) = 0;
  return;
}

/** Remove all players from a channel.
 * \verbatim
 * This is the top-level function for @channel/wipe, which removes all
 * players from a channel.
 * \endverbatim
 * \param player the enactor.
 * \param name name of channel to wipe.
 */
void
do_chan_wipe(dbref player, const char *name)
{
  CHAN *c;
  /* Find the channel */
  test_channel(player, name, c);
  /* Check permissions */
  if (!Chan_Can_Modify(c, player)) {
    notify(player, T("CHAT: Wipe that silly grin off your face instead."));
    return;
  }
  /* Wipe it */
  channel_wipe(player, c);
  notify_format(player, T("CHAT: Channel <%s> wiped."), ChanName(c));
  return;
}

/** Change the owner of a channel.
 * \verbatim
 * This is the top-level function for @channel/chown, which changes
 * ownership of a channel.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the channel.
 * \param newowner name of the new owner for the channel.
 */
void
do_chan_chown(dbref player, const char *name, const char *newowner)
{
  CHAN *c;
  dbref victim;
  /* Only a Wizard can do this */
  if (!Wizard(player)) {
    notify(player, T("CHAT: Only a Wizard can do that."));
    return;
  }
  /* Find the channel */
  test_channel(player, name, c);
  /* Find the victim */
  if ((victim = lookup_player(newowner)) == NOTHING) {
    notify(player, T("CHAT: Invalid owner."));
    return;
  }
  /* We refund the original owner's money, but don't charge the
   * new owner. 
   */
  chan_chown(c, victim);
  notify_format(player,
		T("CHAT: Channel <%s> now owned by %s."), ChanName(c),
		Name(ChanCreator(c)));
  return;
}

/** Chown all of a player's channels. 
 * This function changes ownership of all of a player's channels. It's
 * usually used before destroying the player.
 * \param old dbref of old channel owner.
 * \param newowner dbref of new channel owner.
 */
void
chan_chownall(dbref old, dbref newowner)
{
  CHAN *c;

  /* Run the channel list. If a channel is owned by old, chown it
     silently to newowner */
  for (c = channels; c; c = c->next) {
    if (ChanCreator(c) == old)
      chan_chown(c, newowner);
  }
}

/* The actual chowning of a channel */
static void
chan_chown(CHAN *c, dbref victim)
{
  giveto(ChanCreator(c), ChanCost(c));
  ChanCreator(c) = victim;
  ChanCost(c) = 0;
  return;
}

/** Lock one of the channel's locks.
 * \verbatim
 * This is the top-level function for @clock.
 * \endverbatim
 * \param player the enactor.
 * \param name the name of the channel.
 * \param lockstr string representation of the lock value.
 * \param whichlock which lock is to be set.
 */
void
do_chan_lock(dbref player, const char *name, const char *lockstr, int whichlock)
{
  CHAN *c;
  boolexp key;
  const char *ltype;

  /* Make sure the channel exists */
  test_channel(player, name, c);
  /* Make sure the player has permission */
  if (!Chan_Can_Modify(c, player)) {
    notify_format(player, T("CHAT: Channel <%s> resists."), ChanName(c));
    return;
  }
  /* Ok, let's do it */
  switch (whichlock) {
  case CL_JOIN:
    ltype = chan_join_lock;
    break;
  case CL_MOD:
    ltype = chan_mod_lock;
    break;
  case CL_SEE:
    ltype = chan_see_lock;
    break;
  case CL_HIDE:
    ltype = chan_hide_lock;
    break;
  case CL_SPEAK:
    ltype = chan_speak_lock;
    break;
  default:
    ltype = "ChanUnknownLock";
  }

  if (!lockstr || !*lockstr) {
    /* Unlock it */
    key = TRUE_BOOLEXP;
  } else {
    key = parse_boolexp(player, lockstr, ltype);
    if (key == TRUE_BOOLEXP) {
      notify(player, T("CHAT: I don't understand that key."));
      return;
    }
  }
  switch (whichlock) {
  case CL_JOIN:
    free_boolexp(ChanJoinLock(c));
    ChanJoinLock(c) = key;
    notify_format(player, (key == TRUE_BOOLEXP) ?
		  T("CHAT: Joinlock on <%s> reset.") :
		  T("CHAT: Joinlock on <%s> set."), ChanName(c));
    break;
  case CL_SPEAK:
    free_boolexp(ChanSpeakLock(c));
    ChanSpeakLock(c) = key;
    notify_format(player, (key == TRUE_BOOLEXP) ?
		  T("CHAT: Speaklock on <%s> reset.") :
		  T("CHAT: Speaklock on <%s> set."), ChanName(c));
    break;
  case CL_SEE:
    free_boolexp(ChanSeeLock(c));
    ChanSeeLock(c) = key;
    notify_format(player, (key == TRUE_BOOLEXP) ?
		  T("CHAT: Seelock on <%s> reset.") :
		  T("CHAT: Seelock on <%s> set."), ChanName(c));
    break;
  case CL_HIDE:
    free_boolexp(ChanHideLock(c));
    ChanHideLock(c) = key;
    notify_format(player, (key == TRUE_BOOLEXP) ?
		  T("CHAT: Hidelock on <%s> reset.") :
		  T("CHAT: Hidelock on <%s> set."), ChanName(c));
    break;
  case CL_MOD:
    free_boolexp(ChanModLock(c));
    ChanModLock(c) = key;
    notify_format(player, (key == TRUE_BOOLEXP) ?
		  T("CHAT: Modlock on <%s> reset.") :
		  T("CHAT: Modlock on <%s> set."), ChanName(c));
    break;
  }
  return;
}


/** A channel list with names and descriptions only.
 * \verbatim
 * This is the top-level function for @channel/what.
 * \endverbatim
 * \param player the enactor.
 * \param partname a partial name of channels to match.
 */
void
do_chan_what(dbref player, const char *partname)
{
  CHAN *c;
  int found = 0;
  char *cleanname;
  char cleanp[CHAN_NAME_LEN];

  cleanname = normalize_channel_name(partname);
  for (c = channels; c; c = c->next) {
    strcpy(cleanp, remove_markup(ChanName(c), NULL));
    if (Chan_Can_See(c, player) && string_prefix(cleanp, cleanname)) {
      notify(player, ChanName(c));
      notify_format(player, T("Description: %s"), ChanTitle(c));
      notify_format(player, T("Owner: %s"), Name(ChanCreator(c)));
      notify_format(player, T("Flags: %s"),
		    privs_to_string(priv_table, ChanType(c)));
      if (ChanBufferQ(c))
	notify_format(player,
		      T("Recall buffer: %dk, with %d lines stored."),
		      BufferQSize(ChanBufferQ(c)),
		      bufferq_lines(ChanBufferQ(c)));
      found++;
    }
  }
  if (!found)
    notify(player, T("CHAT: I don't recognize that channel."));
}


/** A decompile of a channel.
 * \verbatim
 * This is the top-level function for @channel/decompile, which attempts
 * to produce all the MUSHcode necessary to recreate a channel and its
 * membership.
 * \param player the enactor.
 * \param name name of the channel.
 * \param brief if 1, don't include channel membership.
 */
void
do_chan_decompile(dbref player, const char *name, int brief)
{
  CHAN *c;
  CHANUSER *u;
  int found;
  char cleanname[BUFFER_LEN];
  char cleanp[CHAN_NAME_LEN];

  found = 0;
  strcpy(cleanname, remove_markup(name, NULL));
  for (c = channels; c; c = c->next) {
    strcpy(cleanp, remove_markup(ChanName(c), NULL));
    if (string_prefix(cleanp, cleanname)) {
      found++;
      if (!(See_All(player) || Chan_Can_Modify(c, player)
	    || (ChanCreator(c) == player))) {
	if (Chan_Can_See(c, player))
	  notify_format(player, T("CHAT: No permission to decompile <%s>"),
			ChanName(c));
	continue;
      }
      notify_format(player, "@channel/add %s = %s", ChanName(c),
		    privs_to_string(priv_table, ChanType(c)));
      notify_format(player, "@channel/chown %s = %s", ChanName(c),
		    Name(ChanCreator(c)));
      if (ChanModLock(c) != TRUE_BOOLEXP)
	notify_format(player, "@clock/mod %s = %s", ChanName(c),
		      unparse_boolexp(player, ChanModLock(c), UB_MEREF));
      if (ChanHideLock(c) != TRUE_BOOLEXP)
	notify_format(player, "@clock/hide %s = %s", ChanName(c),
		      unparse_boolexp(player, ChanHideLock(c), UB_MEREF));
      if (ChanJoinLock(c) != TRUE_BOOLEXP)
	notify_format(player, "@clock/join %s = %s", ChanName(c),
		      unparse_boolexp(player, ChanJoinLock(c), UB_MEREF));
      if (ChanSpeakLock(c) != TRUE_BOOLEXP)
	notify_format(player, "@clock/speak %s = %s", ChanName(c),
		      unparse_boolexp(player, ChanSpeakLock(c), UB_MEREF));
      if (ChanSeeLock(c) != TRUE_BOOLEXP)
	notify_format(player, "@clock/see %s = %s", ChanName(c),
		      unparse_boolexp(player, ChanSeeLock(c), UB_MEREF));
      if (ChanTitle(c))
	notify_format(player, "@channel/desc %s = %s", ChanName(c),
		      ChanTitle(c));
      if (ChanBufferQ(c))
	notify_format(player, "@channel/buffer %s = %d", ChanName(c),
		      bufferq_lines(ChanBufferQ(c)));
      if (!brief) {
	for (u = ChanUsers(c); u; u = u->next) {
	  if (!Chanuser_Hide(u) || Priv_Who(player))

	     notify_format(player, "@channel/on %s = %s", ChanName(c),
			   Name(CUdbref(u)));
	}
      }
    }
  }
  if (!found)
    notify(player, T("CHAT: No channel matches that string."));
}

static void
do_channel_who(dbref player, CHAN *chan)
{
  char tbuf1[BUFFER_LEN];
  char *bp;
  CHANUSER *u;
  dbref who;
  int i = 0;
  bp = tbuf1;
  for (u = ChanUsers(chan); u; u = u->next) {
    who = CUdbref(u);
    if ((IsThing(who) || Connected(who)) &&
	(!Chanuser_Hide(u) || Priv_Who(player))) {
      i++;
      safe_itemizer(i, !(u->next), ",", T("and"), " ", tbuf1, &bp);
      safe_str(Name(who), tbuf1, &bp);
      if (IsThing(who))
	safe_format(tbuf1, &bp, "(#%d)", who);
      if (Chanuser_Hide(u) && Chanuser_Gag(u))
	 safe_str(" (hidden,gagging)", tbuf1, &bp);
      else if (Chanuser_Hide(u))
	 safe_str(" (hidden)", tbuf1, &bp);
      else if (Chanuser_Gag(u))
	 safe_str(" (gagging)", tbuf1, &bp);
    }
  }
  *bp = '\0';
  if (!*tbuf1)
    notify(player, T("There are no connected players on that channel."));
  else {
    notify_format(player, T("Members of channel <%s> are:"), ChanName(chan));
    notify(player, tbuf1);
  }
}

/* ARGSUSED */
FUNCTION(fun_cwho)
{
  int first = 1;
  CHAN *chan = NULL;
  CHANUSER *u;
  dbref who;

  switch (find_channel(args[0], &chan, executor)) {
  case CMATCH_NONE:
    notify(executor, T("No such channel."));
    return;
  case CMATCH_AMBIG:
    notify(executor, T("I can't tell which channel you mean."));
    return;
  default:
    break;
  }

  /* Feh. We need to do some sort of privilege checking, so that
   * if mortals can't do '@channel/who wizard', they can't do
   * 'think cwho(wizard)' either. The first approach that comes to
   * mind is the following:
   * if (!ChannelPermit(privs,chan)) ...
   * Unfortunately, we also want objects to be able to check cwho()
   * on channels.
   * So, we check the owner, instead, because most uses of cwho()
   * are either in the Master Room owned by a wizard, or on people's
   * quicktypers.
   */

  if (!Chan_Can_See(chan, Owner(executor))
      && !Chan_Can_See(chan, executor)) {
    safe_str(T("#-1 NO PERMISSIONS FOR CHANNEL"), buff, bp);
    return;
  }
  for (u = ChanUsers(chan); u; u = u->next) {
    who = CUdbref(u);
    if ((IsThing(who) || Connected(who)) &&
	(!Chanuser_Hide(u) || Priv_Who(executor))) {
      if (first)
	first = 0;
      else
	safe_chr(' ', buff, bp);
      safe_dbref(who, buff, bp);
    }
  }
}


/** Modify a channel's description.
 * \verbatim
 * This is the top-level function for @channel/desc, which sets a channel's
 * description.
 * \endverbatim
 * \param player the enactor.
 * \param name name of the channel.
 * \param title description of the channel.
 */
void
do_chan_desc(dbref player, const char *name, const char *title)
{
  CHAN *c;
  /* Check new title length */
  if (title && strlen(title) > CHAN_TITLE_LEN - 1) {
    notify(player, T("CHAT: New description too long."));
    return;
  }
  /* Make sure the channel exists */
  test_channel(player, name, c);
  /* Make sure the player has permission */
  if (!Chan_Can_Modify(c, player)) {
    notify(player, "CHAT: Yeah, right.");
    return;
  }
  /* Ok, let's do it */
  if (!title || !*title) {
    ChanTitle(c)[0] = '\0';
    notify_format(player, T("CHAT: Channel <%s> description cleared."),
		  ChanName(c));
  } else {
    strcpy(ChanTitle(c), title);
    notify_format(player, T("CHAT: Channel <%s> description set."),
		  ChanName(c));
  }
}



static int
yesno(const char *str)
{
  if (!str || !*str)
    return ERR;
  switch (str[0]) {
  case 'y':
  case 'Y':
    return YES;
  case 'n':
  case 'N':
    return NO;
  case 'o':
  case 'O':
    switch (str[1]) {
    case 'n':
    case 'N':
      return YES;
    case 'f':
    case 'F':
      return NO;
    default:
      return ERR;
    }
  default:
    return ERR;
  }
}

/* Can this player still add channels, or have they created their
 * limit already?
 */
static int
canstilladd(dbref player)
{
  CHAN *c;
  int num = 0;
  for (c = channels; c; c = c->next) {
    if (ChanCreator(c) == player)
      num++;
  }
  return (num < MAX_PLAYER_CHANS);
}



/** Tell players on a channel when someone connects or disconnects.
 * \param player player that is connecting or disconnecting.
 * \param msg message to announce.
 * \param ungag if 1, remove any channel gags the player has.
 */
void
chat_player_announce(dbref player, char *msg, int ungag)
{
  CHAN *c;
  CHANUSER *u;
  char buff[BUFFER_LEN], *bp;

  for (c = channels; c; c = c->next) {
    u = onchannel(player, c);
    if (u) {
      if (!Channel_Quiet(c) && (Channel_Admin(c) || Channel_Wizard(c)
				|| (!Chanuser_Hide(u) && !Dark(player)))) {
	bp = buff;

	safe_format(buff, &bp, "<%s> %s", "%s", msg);
	*bp = '\0';
	format_channel_broadcast(c, u, player, CB_CHECKQUIET | CB_PRESENCE,
				 buff, NULL);
      }
      if (ungag)
	CUtype(u) &= ~CU_GAG;
    }
  }
}

/** Return a list of channels that the player is on.
 * \param player player whose channels are to be shown.
 * \return string listing player's channels, prefixed with Channels:
 */
const char *
channel_description(dbref player)
{
  static char buf[BUFFER_LEN];
  CHANLIST *c;

  *buf = '\0';

  if (Chanlist(player)) {
    strcpy(buf, T("Channels:"));
    for (c = Chanlist(player); c; c = c->next)
      sprintf(buf, "%s %s", buf, ChanName(c->chan));
  } else if (IsPlayer(player))
    strcpy(buf, T("Channels: *NONE*"));
  return buf;
}


FUNCTION(fun_channels)
{
  dbref it;
  char sep = ' ';
  CHAN *c;
  CHANLIST *cl;
  CHANUSER *u;
  int can_ex;

  /* There are these possibilities:
   *  no args - just a list of all channels
   *   2 args - object, delimiter
   *   1 arg - either object or delimiter. If it's longer than 1 char,
   *           we treat it as an object.
   * You can see an object's channels if you can examine it.
   * Otherwise you can see only channels that you share with
   * it where it's not hidden.
   */
  if (nargs >= 1) {
    /* Given an argument, return list of channels it's on */
    it = match_result(executor, args[0], NOTYPE, MAT_EVERYTHING);
    if (GoodObject(it)) {
      int first = 1;
      if (!delim_check(buff, bp, nargs, args, 2, &sep))
	return;
      can_ex = Can_Examine(executor, it);
      for (cl = Chanlist(it); cl; cl = cl->next) {
	if (can_ex || ((u = onchannel(it, cl->chan)) &&!Chanuser_Hide(u)
		       && onchannel(executor, cl->chan))) {
	  if (!first)
	    safe_chr(sep, buff, bp);
	  safe_str(ChanName(cl->chan), buff, bp);
	  first = 0;
	}
      }
      return;
    } else {
      /* args[0] didn't match. Maybe it's a delimiter? */
      if (arglens[0] > 1) {
	if (it == NOTHING)
	  notify(executor, T("I can't see that here."));
	else if (it == AMBIGUOUS)
	  notify(executor, T("I don't know which thing you mean."));
	return;
      } else if (!delim_check(buff, bp, nargs, args, 1, &sep))
	return;
    }
  }
  /* No arguments (except maybe delimiter) - return list of all channels */
  for (c = channels; c; c = c->next) {
    if (Chan_Can_See(c, executor)) {
      if (c != channels)
	safe_chr(sep, buff, bp);
      safe_str(ChanName(c), buff, bp);
    }
  }
  return;
}

FUNCTION(fun_clock)
{
  CHAN *c = NULL;
  char *p = NULL;
  boolexp lock_ptr = TRUE_BOOLEXP;
  int which_lock = 0;

  if ((p = strchr(args[0], '/'))) {
    *p++ = '\0';
  } else {
    p = (char *) "JOIN";
  }

  switch (find_channel(args[0], &c, executor)) {
  case CMATCH_NONE:
    safe_str(T("#-1 NO SUCH CHANNEL"), buff, bp);
    return;
  case CMATCH_AMBIG:
    safe_str("#-2 AMBIGUOUS CHANNEL MATCH", buff, bp);
    return;
  default:
    break;
  }

  if (!strcasecmp(p, "JOIN")) {
    which_lock = CL_JOIN;
    lock_ptr = ChanJoinLock(c);
  } else if (!strcasecmp(p, "SPEAK")) {
    which_lock = CL_SPEAK;
    lock_ptr = ChanSpeakLock(c);
  } else if (!strcasecmp(p, "MOD")) {
    which_lock = CL_MOD;
    lock_ptr = ChanModLock(c);
  } else if (!strcasecmp(p, "SEE")) {
    which_lock = CL_SEE;
    lock_ptr = ChanSeeLock(c);
  } else if (!strcasecmp(p, "HIDE")) {
    which_lock = CL_HIDE;
    lock_ptr = ChanHideLock(c);
  } else {
    safe_str(T("#-1 NO SUCH LOCK TYPE"), buff, bp);
    return;
  }

  if (nargs == 2) {
    if (FUNCTION_SIDE_EFFECTS) {
      if (!command_check_byname(executor, "@clock") || fun->flags & FN_NOSIDEFX) {
	safe_str(T(e_perm), buff, bp);
	return;
      }
      do_chan_lock(executor, args[0], args[1], which_lock);
      return;
    } else {
      safe_str(T(e_disabled), buff, bp);
    }
  }

  if (Chan_Can_Decomp(c, executor)) {
    safe_str(unparse_boolexp(executor, lock_ptr, UB_MEREF), buff, bp);
    return;
  } else {
    safe_str(T(e_perm), buff, bp);
    return;
  }
}

/* ARGSUSED */
FUNCTION(fun_cemit)
{
  int ns = string_prefix(called_as, "NS");
  int flags = PEMIT_SILENT;
  flags |= (ns ? PEMIT_SPOOF : 0);
  if (!command_check_byname(executor, ns ? "@nscemit" : "@cemit") ||
      fun->flags & FN_NOSIDEFX) {
    safe_str(T(e_perm), buff, bp);
    return;
  }
  if (nargs == 3 && parse_boolean(args[2]))
    flags &= ~PEMIT_SILENT;
  orator = executor;
  do_cemit(executor, args[0], args[1], flags);
}

COMMAND (cmd_cemit) {
  int spflags = !strcmp(cmd->name, "@NSCEMIT") ? PEMIT_SPOOF : 0;
  SPOOF(player, cause, sw);
  if (!SW_ISSET(sw, SWITCH_NOISY))
    spflags |= PEMIT_SILENT;
  do_cemit(player, arg_left, arg_right, spflags);
}

COMMAND (cmd_channel) {
  if (switches)
    do_channel(player, arg_left, arg_right, switches);
  else if (SW_ISSET(sw, SWITCH_LIST))
    do_channel_list(player, arg_left);
  else if (SW_ISSET(sw, SWITCH_ADD))
    do_chan_admin(player, arg_left, arg_right, 0);
  else if (SW_ISSET(sw, SWITCH_DELETE))
    do_chan_admin(player, arg_left, arg_right, 1);
  else if (SW_ISSET(sw, SWITCH_NAME))
    do_chan_admin(player, arg_left, arg_right, 2);
  else if (SW_ISSET(sw, SWITCH_RENAME))
    do_chan_admin(player, arg_left, arg_right, 2);
  else if (SW_ISSET(sw, SWITCH_PRIVS))
    do_chan_admin(player, arg_left, arg_right, 3);
  else if (SW_ISSET(sw, SWITCH_RECALL))
    do_chan_recall(player, arg_left, arg_right, SW_ISSET(sw, SWITCH_QUIET));
  else if (SW_ISSET(sw, SWITCH_DECOMPILE))
    do_chan_decompile(player, arg_left, SW_ISSET(sw, SWITCH_BRIEF));
  else if (SW_ISSET(sw, SWITCH_DESCRIBE))
    do_chan_desc(player, arg_left, arg_right);
  else if (SW_ISSET(sw, SWITCH_TITLE))
    do_chan_title(player, arg_left, arg_right);
  else if (SW_ISSET(sw, SWITCH_CHOWN))
    do_chan_chown(player, arg_left, arg_right);
  else if (SW_ISSET(sw, SWITCH_WIPE))
    do_chan_wipe(player, arg_left);
  else if (SW_ISSET(sw, SWITCH_MUTE))
    do_chan_user_flags(player, arg_left, arg_right, 0, 0);
  else if (SW_ISSET(sw, SWITCH_UNMUTE))
    do_chan_user_flags(player, arg_left, "n", 0, 0);
  else if (SW_ISSET(sw, SWITCH_HIDE))
    do_chan_user_flags(player, arg_left, arg_right, 1, 0);
  else if (SW_ISSET(sw, SWITCH_UNHIDE))
    do_chan_user_flags(player, arg_left, "n", 1, 0);
  else if (SW_ISSET(sw, SWITCH_GAG))
    do_chan_user_flags(player, arg_left, arg_right, 2, 0);
  else if (SW_ISSET(sw, SWITCH_UNGAG))
    do_chan_user_flags(player, arg_left, "n", 2, 0);
  else if (SW_ISSET(sw, SWITCH_WHAT))
    do_chan_what(player, arg_left);
  else if (SW_ISSET(sw, SWITCH_BUFFER))
    do_chan_buffer(player, arg_left, arg_right);
  else
    do_channel(player, arg_left, NULL, arg_right);
}

COMMAND (cmd_chat) {
  do_chat_by_name(player, arg_left, arg_right, 1);
}

COMMAND (cmd_clock) {
  if (SW_ISSET(sw, SWITCH_JOIN))
    do_chan_lock(player, arg_left, arg_right, CL_JOIN);
  else if (SW_ISSET(sw, SWITCH_SPEAK))
    do_chan_lock(player, arg_left, arg_right, CL_SPEAK);
  else if (SW_ISSET(sw, SWITCH_MOD))
    do_chan_lock(player, arg_left, arg_right, CL_MOD);
  else if (SW_ISSET(sw, SWITCH_SEE))
    do_chan_lock(player, arg_left, arg_right, CL_SEE);
  else if (SW_ISSET(sw, SWITCH_HIDE))
    do_chan_lock(player, arg_left, arg_right, CL_HIDE);
  else
    notify(player, T("You must specify a type of lock"));
}

/** Find the next player on a channel to notify.
 * This function is a helper for notify_anything that is used to
 * notify all players on a channel.
 * \param current next dbref to notify (not used).
 * \param data pointer to structure containing channel and chanuser data.
 * \return next dbref to notify.
 */
dbref
na_channel(dbref current, void *data)
{
  struct na_cpass *nac = data;
  CHANUSER *u, *nu;
  int cont;

  nu = nac->u;
  do {
    u = nu;
    if (!u)
      return NOTHING;
    current = CUdbref(u);
    nu = u->next;
    cont = (!GoodObject(current) ||
	    (nac->checkquiet && Chanuser_Quiet(u)) ||
	    Chanuser_Gag(u) || (IsPlayer(current) && !Connected(current)));
  } while (cont);
  nac->u = nu;
  return current;
}

/** Broadcast a message to a channel.
 * \param channel pointer to channel to broadcast to.
 * \param player message speaker.
 * \param flags broadcast flag mask (see CB_* constants in extchat.h)
 * \param fmt message format string.
 */
void WIN32_CDECL
channel_broadcast(CHAN *channel, dbref player, int flags, const char *fmt, ...)
/* flags: 0x1 = checkquiet, 0x2 = nospoof */
{
  va_list args;
#ifdef HAS_VSNPRINTF
  char tbuf1[BUFFER_LEN];
#else
  char tbuf1[BUFFER_LEN * 2];	/* Safety margin as per tprintf */
#endif
  struct na_cpass nac;

  /* Make sure we can write to the channel before doing so */
  if (Channel_Disabled(channel))
    return;

  va_start(args, fmt);

#ifdef HAS_VSNPRINTF
  (void) vsnprintf(tbuf1, sizeof tbuf1, fmt, args);
#else
  (void) vsprintf(tbuf1, fmt, args);
#endif
  va_end(args);
  tbuf1[BUFFER_LEN - 1] = '\0';

  nac.u = ChanUsers(channel);
  nac.checkquiet = (flags & CB_CHECKQUIET) ? 1 : 0;
  notify_anything(player, na_channel, &nac, ns_esnotify,
		  ((flags & CB_PRESENCE) ? NA_INTER_PRESENCE : NA_INTER_HEAR) |
		  ((flags & CB_NOSPOOF) ? 0 : NA_SPOOF), tbuf1);
  if (ChanBufferQ(channel))
    add_to_bufferq(ChanBufferQ(channel), 0,
		   (flags & CB_NOSPOOF) ? player : NOTHING, tbuf1);
}


/** Recall past lines from the channel's buffer.
 * We try to recall no more lines that are requested by the player,
 * but sometimes we may have fewer to recall.
 * \verbatim
 * This is the top-level function for @chan/recall.
 * \endverbatim
 * \param player the enactor.
 * \param name the name of the channel.
 * \param lines a string given the number of lines to recall (default all).
 * \param quiet if true, don't show timestamps.
 */
void
do_chan_recall(dbref player, const char *name, const char *lines, int quiet)
{
  CHAN *chan;
  CHANUSER *u;
  int num_lines = 10;		/* Default if none is given */
  char *p = NULL, *buf;
  time_t timestamp;
  char *stamp;
  int skip;
  dbref speaker;
  int type;

  if (!name || !*name) {
    notify(player, T("You need to specify a channel."));
    return;
  }
  if (lines && *lines) {
    if (is_integer(lines)) {
      num_lines = parse_integer(lines);
      if (num_lines == 0)
	num_lines = INT_MAX;
    } else {
      notify(player, T("How many lines did you want to recall?"));
      return;
    }
  }
  if (num_lines < 1) {
    notify(player, T("How many lines did you want to recall?"));
    return;
  }

  test_channel(player, name, chan);
  if (!Chan_Can_See(chan, player)) {
    if (onchannel(player, chan))
      notify_format(player,
		    T("CHAT: You can't do that with channel <%s>."),
		    ChanName(chan));
    else
      notify(player, T("CHAT: I don't recognize that channel."));
    return;
  }
  u = onchannel(player, chan);
  if (!u &&!Chan_Can_Access(chan, player)) {
    notify(player, T("CHAT: You must join a channel to recall from it."));
    return;
  }
  if (!ChanBufferQ(chan)) {
    notify(player, T("CHAT: That channel doesn't have a recall buffer."));
    return;
  }
  if (isempty_bufferq(ChanBufferQ(chan))) {
    notify(player, T("CHAT: Nothing to recall."));
    return;
  }

  skip = BufferQNum(ChanBufferQ(chan)) - num_lines;
  notify_format(player, T("CHAT: Recall from channel <%s>"), ChanName(chan));

  do {
    buf = iter_bufferq(ChanBufferQ(chan), &p, &speaker, &type, &timestamp);
    if (skip <= 0) {
      if (buf) {
	if (Nospoof(player) && GoodObject(speaker)) {
	  char *nsmsg =
	    ns_esnotify(speaker, na_one, &player, Paranoid(player) ? 1 : 0);
	  if (quiet)
	    notify_format(player, "%s %s", nsmsg, buf);
	  else {
	    stamp = show_time(timestamp, 0);
	    notify_format(player, "[%s] %s %s", stamp, nsmsg, buf);
	  }
	  mush_free(nsmsg, "string");
	} else {
	  if (quiet)
	    notify(player, buf);
	  else {
	    stamp = show_time(timestamp, 0);
	    notify_format(player, "[%s] %s", stamp, buf);
	  }
	}
      }
    }
    skip--;
  } while (buf);


  notify(player, T("CHAT: End recall"));
  if (!lines || !*lines)
    notify_format(player,
		  T
		  ("CHAT: To recall the entire buffer, use @chan/recall %s=0"),
		  ChanName(chan));
}

/** Set the size of a channel's buffer in maximum lines.
 * \verbatim
 * This is the top-level function for @chan/buffer.
 * \endverbatim
 * \param player the enactor.
 * \param name the name of the channel.
 * \param lines a string given the number of lines to buffer.
 */
void
do_chan_buffer(dbref player, const char *name, const char *lines)
{
  CHAN *chan;
  int size;

  if (!name || !*name) {
    notify(player, T("You need to specify a channel."));
    return;
  }
  if (!lines || !*lines || !is_integer(lines)) {
    notify(player, T("You need to specify the number of lines to buffer."));
    return;
  }
  size = parse_integer(lines);
  if (size < 0 || size > 10) {
    notify(player, T("Invalid buffer size."));
    return;
  }
  test_channel(player, name, chan);
  if (!Chan_Can_Modify(chan, player)) {
    notify(player, T("Permission denied."));
    return;
  }
  if (!size) {
    /* Remove a channel's buffer */
    if (ChanBufferQ(chan)) {
      free_bufferq(ChanBufferQ(chan));
      ChanBufferQ(chan) = NULL;
      notify_format(player,
		    T("CHAT: Channel buffering disabled for channel <%s>."),
		    ChanName(chan));
    } else {
      notify_format(player,
		    T
		    ("CHAT: Channel buffering already disabled for channel <%s>."),
		    ChanName(chan));
    }
  } else {
    if (ChanBufferQ(chan)) {
      /* Resize a buffer */
      ChanBufferQ(chan) = reallocate_bufferq(ChanBufferQ(chan), size);
      notify_format(player, T("CHAT: Resizing buffer of channel <%s>"),
		    ChanName(chan));
    } else {
      /* Start a new buffer */
      ChanBufferQ(chan) = allocate_bufferq(size);
      notify_format(player,
		    T("CHAT: Buffering enabled on channel <%s>."),
		    ChanName(chan));
    }
  }
}

static void
format_channel_broadcast(CHAN *chan, CHANUSER *u, dbref victim, int flags,
			 const char *msg, const char *extra)
{
  const char *title = NULL;
  if (extra && *extra)
    title = extra;
  else if (u &&CUtitle(u))
     title = CUtitle(u);

  if (Channel_NoNames(chan)) {
    if (Channel_NoTitles(chan) || !title)
      channel_broadcast(chan, victim, flags, msg, ChanName(chan), "Someone");
    else
      channel_broadcast(chan, victim, flags, msg, ChanName(chan), title);
  } else
    channel_broadcast(chan, victim, flags, msg, ChanName(chan), Name(victim));
}


/** Evaluate a channel lock with %0 set to the channel name.
 * \param c the channel to test.
 * \param p the object trying to pass the lock.
 * \param type the type of channel lock to test.
 * \return true or false
 */
int
eval_chan_lock(CHAN *c, dbref p, enum clock_type type)
{
  char *oldenv[10];
  boolexp b = TRUE_BOOLEXP;
  int retval, n;

  if (!c || !GoodObject(p))
    return 0;

  switch (type) {
  case CLOCK_SEE:
    b = ChanSeeLock(c);
    break;
  case CLOCK_JOIN:
    b = ChanJoinLock(c);
    break;
  case CLOCK_SPEAK:
    b = ChanSpeakLock(c);
    break;
  case CLOCK_HIDE:
    b = ChanHideLock(c);
    break;
  case CLOCK_MOD:
    b = ChanModLock(c);
  }

  save_global_env("eval_chan_lock", oldenv);
  global_eval_context.wenv[0] = ChanName(c);
  for (n = 1; n < 10; n++)
    global_eval_context.wenv[n] = NULL;
  retval = eval_boolexp(p, b, p);
  restore_global_env("eval_chan_lock", oldenv);

  return retval;

}