tinymush-3.1p1/game/backups/
tinymush-3.1p1/game/bin/
tinymush-3.1p1/game/data/
tinymush-3.1p1/game/modules/
tinymush-3.1p1/game/modules/old/
tinymush-3.1p1/src/modules/comsys/
tinymush-3.1p1/src/modules/hello/
tinymush-3.1p1/src/modules/mail/
tinymush-3.1p1/src/tools/
/* comsys.c - module implementing DarkZone-style channel system */
/* $Id: comsys.c,v 1.80 2003/02/24 18:05:23 rmg Exp $ */

#include "../../api.h"

extern BOOLEXP *FDECL(getboolexp1, (FILE *));
extern void FDECL(putboolexp, (FILE *, BOOLEXP *));

/* --------------------------------------------------------------------------
 * Constants.
 */

#define NO_CHAN_MSG "That is not a valid channel name."

#define CHAN_FLAG_PUBLIC	0x00000010
#define CHAN_FLAG_LOUD		0x00000020
#define CHAN_FLAG_P_JOIN	0x00000040
#define CHAN_FLAG_P_TRANS	0x00000080
#define CHAN_FLAG_P_RECV	0x00000100
#define CHAN_FLAG_O_JOIN	0x00000200
#define CHAN_FLAG_O_TRANS	0x00000400
#define CHAN_FLAG_O_RECV	0x00000800
#define CHAN_FLAG_SPOOF		0x00001000

#define CBOOT_QUIET	1	/* No boot message, just has left */
#define CEMIT_NOHEADER  1	/* Channel emit without header */
#define CHANNEL_SET	1	/* Set channel flag */
#define CHANNEL_CHARGE	2	/* Set channel charge */
#define CHANNEL_DESC	4	/* Set channel desc */
#define CHANNEL_LOCK	8	/* Set channel lock */
#define CHANNEL_OWNER	16	/* Set channel owner */
#define CHANNEL_JOIN	32	/* Channel lock: join */
#define CHANNEL_TRANS	64	/* Channel lock: transmit */
#define CHANNEL_RECV	128	/* Channel lock: receive */
#define CHANNEL_HEADER	256	/* Set channel header */
#define CLIST_FULL	1	/* Full listing of channels */
#define CLIST_HEADER	2	/* Header listing of channels */
#define CWHO_ALL	1	/* Show disconnected players on channel */

#define MAX_CHAN_NAME_LEN	20
#define MAX_CHAN_ALIAS_LEN	10
#define MAX_CHAN_DESC_LEN	256
#define MAX_CHAN_HEAD_LEN	64

/* --------------------------------------------------------------------------
 * Configuration and hash tables.
 */

struct mod_comsys_confstorage {
	char	*public_channel;	/* Name of public channel */
	char	*guests_channel;	/* Name of guests channel */
	char	*public_calias;		/* Alias of public channel */
	char	*guests_calias;		/* Alias of guests channel */
} mod_comsys_config;

CONF mod_comsys_conftable[] = {
{(char *)"guests_calias",		cf_string,	CA_STATIC,	CA_PUBLIC,	(int *)&mod_comsys_config.guests_calias,	SBUF_SIZE},
{(char *)"guests_channel",		cf_string,	CA_STATIC,	CA_PUBLIC,	(int *)&mod_comsys_config.guests_channel,	SBUF_SIZE},
{(char *)"public_calias",		cf_string,	CA_STATIC,	CA_PUBLIC,	(int *)&mod_comsys_config.public_calias,	SBUF_SIZE},
{(char *)"public_channel",		cf_string,	CA_STATIC,	CA_PUBLIC,	(int *)&mod_comsys_config.public_channel,	SBUF_SIZE},
{ NULL,					NULL,		0,		0,		NULL,				0}};

HASHTAB mod_comsys_comsys_htab;
HASHTAB mod_comsys_calias_htab;
NHSHTAB mod_comsys_comlist_htab;

MODHASHES mod_comsys_hashtable[] = {
{ "Channels",		&mod_comsys_comsys_htab,	15,	8},
{ "Channel aliases",	&mod_comsys_calias_htab,	500,	16},
{ NULL,			NULL,				0,	0}};

MODNHASHES mod_comsys_nhashtable[] = {
{ "Channel lists",	&mod_comsys_comlist_htab,	100,	16},
{ NULL,			NULL,				0,	0}};

/* --------------------------------------------------------------------------
 * Structure definitions.
 */

typedef struct com_player CHANWHO;
struct com_player {
    dbref player;
    int is_listening;
    CHANWHO *next;
};

typedef struct com_channel CHANNEL;
struct com_channel {
    char *name;
    dbref owner;
    unsigned int flags;
    char *header;		/* channel header prefixing messages */
    int num_who;		/* number of people on the channel */
    CHANWHO *who;		/* linked list of players on channel */
    int num_connected;		/* number of connected players on channel */
    CHANWHO **connect_who;	/* array of connected player who structs */
    int charge;			/* cost to use channel */
    int charge_collected;	/* amount paid thus far */
    int num_sent;		/* number of messages sent */
    char *descrip;		/* description */
    BOOLEXP *join_lock;		/* who can join */
    BOOLEXP *trans_lock;	/* who can transmit */
    BOOLEXP *recv_lock;		/* who can receive */
};

typedef struct com_alias COMALIAS;
struct com_alias {
    dbref player;
    char *alias;
    char *title;
    CHANNEL *channel;
};

typedef struct com_list COMLIST;
struct com_list {
    COMALIAS *alias_ptr;
    COMLIST *next;
};

/* --------------------------------------------------------------------------
 * Macros.
 */

#define check_owned_channel(p,c) \
if (!Comm_All((p)) && ((p) != (c)->owner)) { \
    notify((p), NOPERM_MESSAGE); \
    return; \
}

#define find_channel(d,n,p) \
(p) = ((CHANNEL *) hashfind((n), &mod_comsys_comsys_htab)); \
if (!(p)) { \
    notify((d), NO_CHAN_MSG); \
    return; \
}

#define find_calias(d,a,p) \
(p)=((COMALIAS *) hashfind(tprintf("%d.%s",(d),(a)), &mod_comsys_calias_htab)); \
if (!(p)) { \
    notify((d), "No such channel alias."); \
    return; \
}

#define lookup_channel(s)  ((CHANNEL *) hashfind((s), &mod_comsys_comsys_htab))

#define lookup_calias(d,s)  \
((COMALIAS *) hashfind(tprintf("%d.%s",(d),(s)), &mod_comsys_calias_htab))

#define lookup_clist(d) \
((COMLIST *) nhashfind((int) (d), &mod_comsys_comlist_htab))

#define ok_joinchannel(d,c) \
ok_chanperms((d),(c),CHAN_FLAG_P_JOIN,CHAN_FLAG_O_JOIN,(c)->join_lock)

#define ok_recvchannel(d,c) \
ok_chanperms((d),(c),CHAN_FLAG_P_RECV,CHAN_FLAG_O_RECV,(c)->recv_lock)

#define ok_sendchannel(d,c) \
ok_chanperms((d),(c),CHAN_FLAG_P_TRANS,CHAN_FLAG_O_TRANS,(c)->trans_lock)

#define clear_chan_alias(n,a) \
XFREE((a)->alias, "clear_chan_alias.astring"); \
if ((a)->title) \
    XFREE((a)->title, "clear_chan_alias.title"); \
XFREE((a), "clear_chan_alias.alias"); \
hashdelete((n), &mod_comsys_calias_htab)

/* --------------------------------------------------------------------------
 * Basic channel utilities.
 */

INLINE static int is_onchannel(player, chp)
    dbref player;
    CHANNEL *chp;
{
    CHANWHO *wp;

    for (wp = chp->who; wp != NULL; wp = wp->next) {
	if (wp->player == player) 
	    return 1;
    }

    return 0;
}

INLINE static int is_listenchannel(player, chp)
    dbref player;
    CHANNEL *chp;
{
    int i;

    for (i = 0; i < chp->num_connected; i++) {
	if (chp->connect_who[i]->player == player)
	    return (chp->connect_who[i]->is_listening);
    }

    return 0;
}


INLINE static int is_listening_disconn(player, chp)
    dbref player;
    CHANNEL *chp;
{
    CHANWHO *wp;

    for (wp = chp->who; wp != NULL; wp = wp->next) {
	if (wp->player == player)
	    return (wp->is_listening);
    }

    return 0;
}


static int ok_channel_string(str, maxlen, ok_spaces, ok_ansi)
    char *str;
    int maxlen;
    int ok_spaces, ok_ansi;
{
    char *p;

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

    if ((int)strlen(str) > maxlen - 1)
	return 0;

    for (p = str; *p; p++) {
	if ((!ok_spaces && isspace(*p)) ||
	    (!ok_ansi && (*p == ESC_CHAR))) {
	    return 0;
	}
    }

    return 1;
}

INLINE static char *munge_comtitle(title)
    char *title;
{
    static char tbuf[MBUF_SIZE];
    char *tp;

    tp = tbuf;

    if (strchr(title, ESC_CHAR)) {
	safe_copy_str(title, tbuf, &tp, MBUF_SIZE - 5);
	safe_mb_str(ANSI_NORMAL, tbuf, &tp);
    } else {
	safe_mb_str(title, tbuf, &tp);
    }

    return tbuf;
}

INLINE static int ok_chanperms(player, chp, pflag, oflag, c_lock)
    dbref player;
    CHANNEL *chp;
    int pflag, oflag;
    BOOLEXP *c_lock;
{
    if (Comm_All(player))
	return 1;

    switch (Typeof(player)) {
	case TYPE_PLAYER:
	    if (chp->flags & pflag)
		return 1;
	    break;
	case TYPE_THING:
	    if (chp->flags & oflag)
		return 1;
	    break;
	default:		/* only players and things on channels */
	    return 0;
    }

    /* If we don't have a flag, and we don't have a lock, we default to
     * permission denied.
     */
    if (!c_lock)
	return 0;

    /* Channel locks are evaluated with respect to the channel owner. */

    if (eval_boolexp(player, chp->owner, chp->owner, c_lock))
	return 1;

    return 0;
}


/* --------------------------------------------------------------------------
 * More complex utilities.
 */

static void update_comwho(chp)
    CHANNEL *chp;
{
    /* We have to call this every time a channel is joined or left,
     * explicitly, as well as when players connect and disconnect.
     * This is a candidate for optimization; we should really just update
     * the list in-place, but this will do.
     */

    CHANWHO *wp;
    int i, count;

    /* We're only interested in whether or not a player is connected,
     * not whether or not they're actually listening to the channel.
     */

    count = 0;

    for (wp = chp->who; wp != NULL; wp = wp->next) {
	if (!isPlayer(wp->player) || Connected(wp->player))
	    count++;
    }

    if (chp->connect_who)
	XFREE(chp->connect_who, "update_comwho");

    chp->num_connected = count;

    if (count > 0) {
	chp->connect_who = (CHANWHO **) XCALLOC(count, sizeof(CHANWHO *), "update_comwho");
	i = 0;
	for (wp = chp->who; wp != NULL; wp = wp->next) {
	    if (!isPlayer(wp->player) || Connected(wp->player)) {
		chp->connect_who[i] = wp;
		i++;
	    }
	}
    }
}

static void com_message(chp, msg, cause)
    CHANNEL *chp;
    char *msg;
    dbref cause;
{
    int i;
    CHANWHO *wp;
    char *mp, msg_ns[LBUF_SIZE];
#ifdef PUEBLO_SUPPORT
    char *mh, *mh_ns;

    mh = mh_ns = NULL;
#endif

    chp->num_sent++;
    mp = NULL;

    for (i = 0; i < chp->num_connected; i++) {
	wp = chp->connect_who[i];
	if (wp->is_listening && ok_recvchannel(wp->player, chp)) {
	    if (isPlayer(wp->player)) {
		if (Nospoof(wp->player) && (wp->player != cause) &&
		    (wp->player != mudstate.curr_enactor) &&
		    (wp->player != mudstate.curr_player)) {
		    if (!mp) {
			/* Construct Nospoof buffer. Can't use tprintf
			 * because we end up calling it later.
			 */
			mp = msg_ns;
			safe_chr('[', msg_ns, &mp);
			safe_name(cause, msg_ns, &mp);
			safe_chr('(', msg_ns, &mp);
			safe_chr('#', msg_ns, &mp);
			safe_ltos(msg_ns, &mp, cause);
			safe_chr(')', msg_ns, &mp);
			if (cause != Owner(cause)) {
			    safe_chr('{', msg_ns, &mp);
			    safe_name(Owner(cause), msg_ns, &mp);
			    safe_chr('}', msg_ns, &mp);
			}
			if (cause != mudstate.curr_enactor) {
			    safe_known_str((char *) "<-(#", 4, msg_ns, &mp);
			    safe_ltos(msg_ns, &mp, cause);
			    safe_chr(')', msg_ns, &mp);
			}
			safe_known_str((char *) "] ", 2, msg_ns, &mp);
			safe_str(msg, msg_ns, &mp);
		    }
#ifndef PUEBLO_SUPPORT
		    raw_notify(wp->player, msg_ns);
#else
		    if (Html(wp->player)) {
			if (!mh_ns) {
			    mh_ns = alloc_lbuf("com_message.html.nospoof");
 			    html_escape(msg_ns, mh_ns, 0);
			}
			raw_notify(wp->player, mh_ns);
		    } else {
			raw_notify(wp->player, msg_ns);
		    }
#endif
		} else {
#ifndef PUEBLO_SUPPORT
		    raw_notify(wp->player, msg);
#else
		    if (Html(wp->player)) {
			if (!mh) {
			    mh = alloc_lbuf("com_message.html");
 			    html_escape(msg, mh, 0);
			}
			raw_notify(wp->player, mh);
		    } else {
			raw_notify(wp->player, msg);
		    }
#endif
		}
	    } else {
		notify_with_cause(wp->player, cause, msg);
	    }
	}
    }
#ifdef PUEBLO_SUPPORT
    if (mh)
	free_lbuf(mh);
    if (mh_ns)
	free_lbuf(mh_ns);
#endif
}

static void remove_from_channel(player, chp, is_quiet)
    dbref player;
    CHANNEL *chp;
    int is_quiet;
{
    /* We assume that the player's channel aliases have already been
     * removed, and that other cleanup that is not directly related to
     * the channel structure itself has been accomplished. (We also
     * do no sanity-checking.)
     */

    CHANWHO *wp, *prev;

    /* Should never happen, but just in case... */

    if ((chp->num_who == 0) || !chp->who)
	return;

    /* If we only had one person, we can just nuke stuff. */

    if (chp->num_who == 1) {
	chp->num_who = 0;
	XFREE(chp->who, "remove_from_channel.who");
	return;
    }

    for (wp = chp->who, prev = NULL; wp != NULL; wp = wp->next) {
	if (wp->player == player) {
	    if (prev) {
		prev->next = wp->next;
	    } else {
		chp->who = wp->next;
	    }
	    XFREE(wp, "remove_from_channel.who");
	    break;
	} else {
	    prev = wp;
	}
    }

    chp->num_who--;

    update_comwho(chp);

    if (!is_quiet &&
	(!isPlayer(player) || (Connected(player) && !Hidden(player)))) {
	com_message(chp, tprintf("%s %s has left this channel.",
				 chp->header, Name(player)),
		    player);
    }
}


INLINE static void zorch_alias_from_list(cap)
    COMALIAS *cap;
{
    COMLIST *clist, *cl_ptr, *prev;

    clist = lookup_clist(cap->player);
    if (!clist)
	return;

    prev = NULL;
    for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = cl_ptr->next) {
	if (cl_ptr->alias_ptr == cap) {
	    if (prev)
		prev->next = cl_ptr->next;
	    else {
		clist = cl_ptr->next;
		if (clist)
		    nhashrepl((int) cap->player, (int *) clist,
			      &mod_comsys_comlist_htab);
		else
		    nhashdelete((int) cap->player, &mod_comsys_comlist_htab);
	    }
	    XFREE(cl_ptr, "zorch_alias.cl_ptr");
	    return;
	}
	prev = cl_ptr;
    }
}

static void process_comsys(player, arg, cap)
    dbref player;
    char *arg;
    COMALIAS *cap;
{
    CHANWHO *wp;
    char *buff, *name_buf, tbuf[LBUF_SIZE], *tp;
    int i;

    if (!arg || !*arg) {
	notify(player, "No message.");
	return;
    }

    if (!strcmp(arg, (char *) "on")) {

	for (wp = cap->channel->who; wp != NULL; wp = wp->next) {
	    if (wp->player == player)
		break;
	}
	if (!wp) {
	    STARTLOG(LOG_ALWAYS, "BUG", "COM")
	      log_printf("Object #%d with alias %s is on channel %s but not on its player list.", player, cap->alias, cap->channel->name);
	    ENDLOG
	    notify(player, "An unusual channel error has been detected.");
	    return;
	}
	if (wp->is_listening) {
	    notify(player, tprintf("You are already on channel %s.",
				   cap->channel->name));
	    return;
	}
	wp->is_listening = 1;

	/* Only tell people that we've joined if we're an object, or
	 * we're a connected and non-hidden player.
	 */
	if (!isPlayer(player) || (Connected(player) && !Hidden(player))) {
	    com_message(cap->channel,
			tprintf("%s %s has joined this channel.",
				cap->channel->header, Name(player)),
			player);
	}
	return;
    
    } else if (!strcmp(arg, (char *) "off")) {

	for (wp = cap->channel->who; wp != NULL; wp = wp->next) {
	    if (wp->player == player)
		break;
	}
	if (!wp) {
	    STARTLOG(LOG_ALWAYS, "BUG", "COM")
	      log_printf("Object #%d with alias %s is on channel %s but not on its player list.", player, cap->alias, cap->channel->name);
	    ENDLOG
	    notify(player, "An unusual channel error has been detected.");
	    return;
	}
	if (wp->is_listening == 0) {
	    notify(player, tprintf("You are not on channel %s.",
				   cap->channel->name));
	    return;
	}
	wp->is_listening = 0;
	notify(player, tprintf("You leave channel %s.", cap->channel->name));

	/* Only tell people about it if we're an object, or we're a 
	 * connected and non-hidden player.
	 */
	if (!isPlayer(player) || (Connected(player) && !Hidden(player))) {
	    com_message(cap->channel,
			tprintf("%s %s has left this channel.",
				cap->channel->header, Name(player)),
			player);
	}
	return;

    } else if (!strcmp(arg, (char *) "who")) {

	/* Allow players who have an alias for a channel to see who is
	 * on it, even if they are not actively receiving.
	 */

	notify(player, "-- Players --");

	for (i = 0; i < cap->channel->num_connected; i++) {
	    wp = cap->channel->connect_who[i];
	    if (isPlayer(wp->player)) {
		if (wp->is_listening && Connected(wp->player) &&
		    (!Hidden(wp->player) || See_Hidden(player))) {
		    buff = unparse_object(player, wp->player, 0);
		    notify(player, buff);
		    free_lbuf(buff);
		}
	    }
	}

	notify(player, "-- Objects -- ");

	for (i = 0; i < cap->channel->num_connected; i++) {
	    wp = cap->channel->connect_who[i];
	    if (!isPlayer(wp->player)) {
		if (wp->is_listening) {
		    buff = unparse_object(player, wp->player, 0);
		    notify(player, buff);
		    free_lbuf(buff);
		}
	    }
	}

	notify(player, tprintf("-- %s --", cap->channel->name));

	return;

    } else {

	if (Gagged(player)) {
	    notify(player, NOPERM_MESSAGE);
	    return;
	}

	if (!is_listenchannel(player, cap->channel)) {
	    notify(player, tprintf("You must be on %s to do that.",
				   cap->channel->name));
	    return;
	}

	if (!ok_sendchannel(player, cap->channel)) {
	    notify(player, "You cannot transmit on that channel.");
	    return;
	}

	if (!payfor(player, Guest(player) ? 0 : cap->channel->charge)) {
	    notify(player, tprintf("You don't have enough %s.",
				   mudconf.many_coins));
	    return;
	}
	cap->channel->charge_collected += cap->channel->charge;
	giveto(cap->channel->owner, cap->channel->charge);

	if (cap->title) {
	    if (cap->channel->flags & CHAN_FLAG_SPOOF) {
		name_buf = cap->title;
	    } else {
		tp = tbuf;
		safe_str(cap->title, tbuf, &tp);
		safe_chr(' ', tbuf, &tp);
		safe_name(player, tbuf, &tp);
		*tp = '\0';
		name_buf = tbuf;
	    }
	} else {
	    name_buf = NULL;
	}
	if (*arg == ':') {
	    com_message(cap->channel,
			tprintf("%s %s %s",
				cap->channel->header,
				(name_buf) ? name_buf : Name(player),
				arg + 1),
			player);
	} else if (*arg == ';') {
	    com_message(cap->channel,
			tprintf("%s %s%s",
				cap->channel->header,
				(name_buf) ? name_buf : Name(player),
				arg + 1),
			player);
	} else {
	    com_message(cap->channel,
			tprintf("%s %s says, \"%s\"",
				cap->channel->header,
				(name_buf) ? name_buf : Name(player),
				arg),
			player);
	}

	return;
    }
}

/* --------------------------------------------------------------------------
 * Other externally-exposed utilities.
 */

void join_channel(player, chan_name, alias_str, title_str)
    dbref player;
    char *chan_name, *alias_str, *title_str;
{
    CHANNEL *chp;
    COMALIAS *cap;
    CHANWHO *wp;
    COMLIST *clist;
    int has_joined;

    if (!ok_channel_string(alias_str, MAX_CHAN_ALIAS_LEN, 0, 0)) {
	notify(player, "That is not a valid channel alias.");
	return;
    }
    
    if (lookup_calias(player, alias_str) != NULL) {
	notify(player, "You are already using that channel alias.");
	return;
    }

    find_channel(player, chan_name, chp);

    has_joined = is_onchannel(player, chp);
    if (!has_joined && !ok_joinchannel(player, chp)) {
	notify(player, "You cannot join that channel.");
	return;
    }

    /* Construct the alias. */

    cap = (COMALIAS *) XMALLOC(sizeof(COMALIAS), "join_channel.alias");
    cap->player = player;
    cap->alias = XSTRDUP(alias_str, "join_channel.alias_str");

    /* Note that even if the player is already on this channel,
     * we do not inherit the channel title from other aliases.
     */
    if (title_str && *title_str)
	cap->title = XSTRDUP(munge_comtitle(title_str),
			     "join_channel.title_str");
    else
	cap->title = NULL;
    cap->channel = chp;

    hashadd(tprintf("%d.%s", player, alias_str), (int *) cap,
	    &mod_comsys_calias_htab, 0);

    /* Add this to the list of all aliases for the player. */

    clist = (COMLIST *) XMALLOC(sizeof(COMLIST), "join_channel.clist");
    clist->alias_ptr = cap;
    clist->next = lookup_clist(player);
    if (clist->next == NULL)
	nhashadd((int) player, (int *) clist, &mod_comsys_comlist_htab);
    else
	nhashrepl((int) player, (int *) clist, &mod_comsys_comlist_htab);

    /* If we haven't joined the channel, go do that. */

    if (!has_joined) {

	wp = (CHANWHO *) XMALLOC(sizeof(CHANWHO), "join_channel.who");
	wp->player = player;
	wp->is_listening = 1;

	if ((chp->num_who == 0) || (chp->who == NULL)) {
	    wp->next = NULL;
	} else {
	    wp->next = chp->who;
	}
	chp->who = wp;
	chp->num_who++;

	update_comwho(chp);
	
	if (!isPlayer(player) || (Connected(player) && !Hidden(player))) {
	    com_message(chp, tprintf("%s %s has joined this channel.",
				     chp->header, Name(player)),
			player);
	}

	if (title_str) {
	    notify(player,
		  tprintf("Channel '%s' added with alias '%s' and title '%s'.",
			  chp->name, alias_str, cap->title));
	} else {
	    notify(player,
		   tprintf("Channel '%s' added with alias '%s'.",
			   chp->name, alias_str));
	}

    } else {

	if (title_str) {
	    notify(player,
  	      tprintf("Alias '%s' with title '%s' added for channel '%s'.",
		      alias_str, cap->title, chp->name));
	} else {
	    notify(player,
		   tprintf("Alias '%s' added for channel '%s'.",
			   alias_str, chp->name));
	}
    }
}

void channel_clr(player)
    dbref player;
{
    CHANNEL **ch_array;
    COMLIST *clist, *cl_ptr, *next;
    int i, found, pos;
    char tbuf[SBUF_SIZE];

    /* We do not check if the comsys is enabled, because we want to clean
     * up our mess regardless.
     */

    clist = lookup_clist(player);
    if (!clist)
	return;

    /* Figure out all the channels we're on, then free up aliases. */

    ch_array = (CHANNEL **) XCALLOC(mod_comsys_comsys_htab.entries,
				    sizeof(CHANNEL *), "channel_clr.array");
    pos = 0;
    for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = next) {

	/* This is unnecessarily redundant, but it's not as if
	 * a player is going to be on tons of channels.
	 */
	found = 0;
	for (i = 0;
	     (i < mod_comsys_comsys_htab.entries) && (ch_array[i] != NULL);
	     i++) {
	    if (ch_array[i] == cl_ptr->alias_ptr->channel) {
		found = 1;
		break;
	    }
	}
	if (!found) {
	    ch_array[pos] = cl_ptr->alias_ptr->channel;
	    pos++;
	}

	sprintf(tbuf, "%d.%s", player, cl_ptr->alias_ptr->alias),
	clear_chan_alias(tbuf, cl_ptr->alias_ptr);
	next = cl_ptr->next;
	XFREE(cl_ptr, "channel_clr.cl_ptr");
    }

    nhashdelete((int) player, &mod_comsys_comlist_htab);

    /* Remove from channels. */

    for (i = 0; i < pos; i++)
	remove_from_channel(player, ch_array[i], 0);
    XFREE(ch_array, "channel_clr.array");
}

void mod_comsys_announce_connect(player, reason, num)
    dbref player;
    const char *reason;
    int num;
{
    CHANNEL *chp;

    /* It's slightly easier to just go through the channels and see
     * which ones the player is on, for announcement purposes.
     */

    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	if (is_onchannel(player, chp)) {
	    update_comwho(chp);
	    if ((chp->flags & CHAN_FLAG_LOUD) && !Hidden(player) &&
		is_listenchannel(player, chp)) {
		com_message(chp, tprintf("%s %s has connected.",
					 chp->header, Name(player)),
			    player);
	    }
	}
    }
}

void mod_comsys_announce_disconnect(player, reason, num)
    dbref player;
    const char *reason;
    int num;
{
    CHANNEL *chp;

    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	if (is_onchannel(player, chp)) {
	    if ((chp->flags & CHAN_FLAG_LOUD) && !Hidden(player) &&
		is_listenchannel(player, chp)) {
		com_message(chp, tprintf("%s %s has disconnected.",
					 chp->header, Name(player)),
			    player);
	    }
	    update_comwho(chp);
	}
    }
}

void update_comwho_all()
{
    CHANNEL *chp;

    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	update_comwho(chp);
    }
}

void comsys_chown(from_player, to_player)
    dbref from_player, to_player;
{
    CHANNEL *chp;

    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	if (chp->owner == from_player)
	    chp->owner = to_player;
    }
}


/* --------------------------------------------------------------------------
 * Comsys commands: channel administration.
 */

void do_ccreate(player, cause, key, name)
    dbref player, cause;
    int key;
    char *name;
{
    CHANNEL *chp;
    char buf[LBUF_SIZE];

    if (!Comm_All(player)) {
	notify(player, NOPERM_MESSAGE);
	return;
    }

    if (!ok_channel_string(name, MAX_CHAN_NAME_LEN, 1, 0)) {
	notify(player, NO_CHAN_MSG);
	return;
    }

    if (lookup_channel(name) != NULL) {
	notify(player, "That channel name is in use.");
	return;
    }

    chp = (CHANNEL *) XMALLOC(sizeof(CHANNEL), "ccreate.channel");
    if (!chp) {
	notify(player, "Out of memory.");
	return;
    }

    chp->name = XSTRDUP(name, "ccreate.name");
    chp->owner = Owner(player);
    chp->flags = CHAN_FLAG_P_JOIN | CHAN_FLAG_P_TRANS | CHAN_FLAG_P_RECV |
	CHAN_FLAG_O_JOIN | CHAN_FLAG_O_TRANS | CHAN_FLAG_O_RECV;
    chp->who = NULL;
    chp->num_who = 0;
    chp->connect_who = NULL;
    chp->num_connected = 0;
    chp->charge = 0;
    chp->charge_collected = 0;
    chp->num_sent = 0;
    chp->descrip = NULL;
    chp->join_lock = chp->trans_lock = chp->recv_lock = NULL;
    sprintf(buf, "[%s]", chp->name);
    chp->header = XSTRDUP(buf, "ccreate.header");

    hashadd(name, (int *) chp, &mod_comsys_comsys_htab, 0);

    notify(player, tprintf("Channel %s created.", name));
}


void do_cdestroy(player, cause, key, name)
    dbref player, cause;
    int key;
    char *name;
{
    CHANNEL *chp;
    COMALIAS **alias_array;
    COMALIAS *cap;
    char **name_array;
    HASHTAB *htab;
    HASHENT *hptr;
    int i, count;

    find_channel(player, name, chp);
    check_owned_channel(player, chp);

    /* We have the wonderful joy of cleaning out all the aliases
     * that are currently pointing to this channel. We begin by
     * warning everyone that it's going away, and then we obliterate
     * it. We have to delete the pointers one by one or we run into
     * hashtable chaining issues.
     */

    com_message(chp, tprintf("Channel %s has been destroyed by %s.",
			     chp->name, Name(player)),
		player);

    htab = &mod_comsys_calias_htab;
    alias_array = (COMALIAS **) XCALLOC(htab->entries, sizeof(COMALIAS *),
					"cdestroy.alias_array");
    name_array = (char **) XCALLOC(htab->entries, sizeof(char *),
				   "cdestroy.name_array");
    count = 0;
    for (i = 0; i < htab->hashsize; i++) {
	for (hptr = htab->entry[i]; hptr != NULL; hptr = hptr->next) {
	    cap = (COMALIAS *) hptr->data;
	    if (cap->channel == chp) {
		name_array[count] = hptr->target.s;
		alias_array[count] = cap;
		count++;
	    }
	}
    }

    /* Delete the aliases from the players' lists, then wipe them out. */

    if (count > 0) {
	for (i = 0; i < count; i++) {
	    zorch_alias_from_list(alias_array[i]);
	    clear_chan_alias(name_array[i], alias_array[i]);
	}
    }

    XFREE(name_array, "cdestroy.name_array");
    XFREE(alias_array, "cdestroy.alias_array");

    /* Zap the channel itself. */

    XFREE(chp->name, "cdestroy.cname");
    if (chp->who)
	XFREE(chp->who, "cdestroy.who");
    if (chp->connect_who)
	XFREE(chp->connect_who, "cdestroy.connect_who");
    if (chp->descrip)
	XFREE(chp->descrip, "cdestroy.descrip");
    XFREE(chp->header, "cdestroy.header");
    if (chp->join_lock)
	free_boolexp(chp->join_lock);
    if (chp->trans_lock)
	free_boolexp(chp->trans_lock);
    if (chp->recv_lock)
	free_boolexp(chp->recv_lock);
    XFREE(chp, "cdestroy.channel");
    hashdelete(name, &mod_comsys_comsys_htab);

    notify(player, tprintf("Channel %s destroyed.", name));
}

void do_channel(player, cause, key, chan_name, arg)
    dbref player, cause;
    int key;
    char *chan_name, *arg;
{
    CHANNEL *chp;
    BOOLEXP *boolp;
    dbref new_owner;
    int c_charge, negate, flag;

    find_channel(player, chan_name, chp);
    check_owned_channel(player, chp);

    if (!key || (key & CHANNEL_SET)) {

	if (*arg == '!') {
	    negate = 1;
	    arg++;
	} else {
	    negate = 0;
	}

	if (!strcasecmp(arg, (char *) "public")) {
	    flag = CHAN_FLAG_PUBLIC;
	} else if (!strcasecmp(arg, (char *) "loud")) {
	    flag = CHAN_FLAG_LOUD;
	} else if (!strcasecmp(arg, (char *) "spoof")) {
	    flag = CHAN_FLAG_SPOOF;
	} else if (!strcasecmp(arg, (char *) "p_join")) {
	    flag = CHAN_FLAG_P_JOIN;
	} else if (!strcasecmp(arg, (char *) "p_transmit")) {
	    flag = CHAN_FLAG_P_TRANS;
	} else if (!strcasecmp(arg, (char *) "p_receive")) {
	    flag = CHAN_FLAG_P_RECV;
	} else if (!strcasecmp(arg, (char *) "o_join")) {
	    flag = CHAN_FLAG_O_JOIN;
	} else if (!strcasecmp(arg, (char *) "o_transmit")) {
	    flag = CHAN_FLAG_O_TRANS;
	} else if (!strcasecmp(arg, (char *) "o_receive")) {
	    flag = CHAN_FLAG_O_RECV;
	} else {
	    notify(player, "That is not a valid channel flag name.");
	    return;
	}

	if (negate)
	    chp->flags &= ~flag;
	else
	    chp->flags |= flag;

	notify(player, "Set.");

    } else if (key & CHANNEL_LOCK) {

	if (arg && *arg) {
	    boolp = parse_boolexp(player, arg, 0);
	    if (boolp == TRUE_BOOLEXP) {
		notify(player, "I don't understand that key.");
		free_boolexp(boolp);
		return;
	    }
	    if (key & CHANNEL_JOIN) {
		if (chp->join_lock)
		    free_boolexp(chp->join_lock);
		chp->join_lock = boolp;
	    } else if (key & CHANNEL_RECV) {
		if (chp->recv_lock)
		    free_boolexp(chp->recv_lock);
		chp->recv_lock = boolp;
	    } else if (key & CHANNEL_TRANS) {
		if (chp->trans_lock)
		    free_boolexp(chp->trans_lock);
		chp->trans_lock = boolp;
	    } else {
		notify(player, "You must specify a valid lock type.");
		free_boolexp(boolp);
		return;
	    }
	    notify(player, "Channel locked.");
	} else {
	    if (key & CHANNEL_JOIN) {
		if (chp->join_lock)
		    free_boolexp(chp->join_lock);
		chp->join_lock = NULL;
	    } else if (key & CHANNEL_RECV) {
		if (chp->recv_lock)
		    free_boolexp(chp->recv_lock);
		chp->recv_lock = NULL;
	    } else if (key & CHANNEL_TRANS) {
		if (chp->trans_lock)
		    free_boolexp(chp->trans_lock);
		chp->trans_lock = NULL;
	    }
	    notify(player, "Channel unlocked.");
	}

    } else if (key & CHANNEL_OWNER) {

	new_owner = lookup_player(player, arg, 1);
	if (Good_obj(new_owner)) {
	    chp->owner = Owner(new_owner); /* no robots */
	    notify(player, "Owner set.");
	} else {
	    notify(player, "No such player.");
	}

    } else if (key & CHANNEL_CHARGE) {

	c_charge = atoi(arg);
	if ((c_charge < 0) || (c_charge > 32767)) {
	    notify(player, "That is not a reasonable cost.");
	    return;
	}
	chp->charge = c_charge;
	notify(player, "Set.");

    } else if (key & CHANNEL_DESC) {

	if (arg && *arg && !ok_channel_string(arg, MAX_CHAN_DESC_LEN, 1, 1)) {
	    notify(player, "That is not a reasonable channel description.");
	    return;
	}

	if (chp->descrip)
	    XFREE(chp->descrip, "do_channel.desc");
	if (arg && *arg)
	    chp->descrip = XSTRDUP(arg, "do_channel.desc");
	notify(player, "Set.");

    } else if (key & CHANNEL_HEADER) {

	if (arg && *arg && !ok_channel_string(arg, MAX_CHAN_HEAD_LEN, 1, 1)) {
	    notify(player, "That is not a reasonable channel header.");
	    return;
	}

	XFREE(chp->header, "do_channel.header");
	if (!arg)
	    chp->header = XSTRDUP("", "do_channel.header");
	else
	    chp->header = XSTRDUP(arg, "do_channel.header");
	notify(player, "Set.");

    } else {
	notify(player, "Invalid channel command.");
    }
}


void do_cboot(player, cause, key, name, objstr)
    dbref player, cause;
    int key;
    char *name, *objstr;
{
    CHANNEL *chp;
    dbref thing;
    COMLIST *chead, *clist, *cl_ptr, *next, *prev;
    char *t;
    char tbuf[SBUF_SIZE];

    find_channel(player, name, chp);
    check_owned_channel(player, chp);

    thing = match_thing(player, objstr);
    if (thing == NOTHING)
	return;

    if (!is_onchannel(thing, chp)) {
	notify(player, "Your target is not on that channel.");
	return;
    }

    /* Clear out all of the player's aliases for this channel. */

    chead = clist = lookup_clist(thing);
    if (clist) {
	prev = NULL;
	for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = next) {
	    next = cl_ptr->next;
	    if (cl_ptr->alias_ptr->channel == chp) {
		if (prev)
		    prev->next = cl_ptr->next;
		else
		    clist = cl_ptr->next;
		sprintf(tbuf, "%d.%s", thing, cl_ptr->alias_ptr->alias);
		clear_chan_alias(tbuf, cl_ptr->alias_ptr);
		XFREE(cl_ptr, "do_cboot.cl_ptr");
	    } else {
		prev = cl_ptr;
	    }
	}
	if (!clist)
	    nhashdelete((int) thing, &mod_comsys_comlist_htab);
	else if (chead != clist)
	    nhashrepl((int) thing, (int *) clist, &mod_comsys_comlist_htab);
    }

    notify(player, tprintf("You boot %s off channel %s.",
			   Name(thing), chp->name));
    notify(thing, tprintf("%s boots you off channel %s.",
			  Name(player), chp->name));

    if (key & CBOOT_QUIET) {
	remove_from_channel(thing, chp, 0);
    } else {
	remove_from_channel(thing, chp, 1);
	t = tbuf;
	safe_sb_str(Name(player), tbuf, &t);
	com_message(chp, tprintf("%s %s boots %s off the channel.",
				 chp->header, tbuf, Name(thing)),
		    player);
    }
}

void do_cemit(player, cause, key, chan_name, str)
    dbref player, cause;
    int key;
    char *chan_name, *str;
{
    CHANNEL *chp;

    find_channel(player, chan_name, chp);
    check_owned_channel(player, chp);

    if (key & CEMIT_NOHEADER)
	com_message(chp, str, player);
    else
	com_message(chp, tprintf("%s %s", chp->header, str),
		    player);
}

void do_cwho(player, cause, key, chan_name)
    dbref player, cause;
    int key;
    char *chan_name;
{
    CHANNEL *chp;
    CHANWHO *wp;
    int i;
    int p_count, o_count;

    find_channel(player, chan_name, chp);
    check_owned_channel(player, chp);

    p_count = o_count = 0;

    notify(player, "      Name                      Player?");

    if (key & CWHO_ALL) {
	for (wp = chp->who; wp != NULL; wp = wp->next) {
	    notify(player, tprintf("%s  %-25s %7s",
				   (wp->is_listening) ? "[on]" : "    ",
				   Name(wp->player),
				   isPlayer(wp->player) ? "Yes" : "No"));
	    if (isPlayer(wp->player))
		p_count++;
	    else
		o_count++;
	}
    } else {
	for (i = 0; i < chp->num_connected; i++) {
	    wp = chp->connect_who[i];
	    if (!Hidden(wp->player) || See_Hidden(player)) {
		notify(player, tprintf("%s  %-25s %7s",
				       (wp->is_listening) ? "[on]" : "    ",
				       Name(wp->player),
				       isPlayer(wp->player) ? "Yes" : "No"));
		if (isPlayer(wp->player))
		    p_count++;
		else
		    o_count++;
	    }
	}
    }

    notify(player, tprintf("Counted %d %s and %d %s on channel %s.",
			   p_count, (p_count == 1) ? "player" : "players",
			   o_count, (o_count == 1) ? "object" : "objects",
			   chp->name));
}


/* --------------------------------------------------------------------------
 * Comsys commands: player-usable.
 */

void do_addcom(player, cause, key, alias_str, args, nargs)
    dbref player, cause;
    int key;
    char *alias_str;
    char *args[];
    int nargs;
{
    char *chan_name, *title_str;

    if (nargs < 1) {
	notify(player, "You need to specify a channel.");
	return;
    }
    chan_name = args[0];
    if (nargs < 2) {
	title_str = NULL;
    } else {
	title_str = args[1];
    }

    join_channel(player, chan_name, alias_str, title_str);
}

void do_delcom(player, cause, key, alias_str)
    dbref player, cause;
    int key;
    char *alias_str;
{
    COMALIAS *cap;
    CHANNEL *chp;
    COMLIST *clist, *cl_ptr;
    int has_mult;

    find_calias(player, alias_str, cap);

    chp = cap->channel;		/* save this for later */

    zorch_alias_from_list(cap);
    clear_chan_alias(tprintf("%d.%s", player, alias_str), cap);

    /* Check if we have any aliases left pointing to that channel. */

    clist = lookup_clist(player);
    has_mult = 0;
    for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = cl_ptr->next) {
	if (cl_ptr->alias_ptr->channel == chp) {
	    has_mult = 1;
	    break;
	}
    }

    if (has_mult) {
	notify(player, tprintf("You remove the alias '%s' for channel %s.",
			       alias_str, chp->name));
    } else {
	notify(player, tprintf("You leave channel %s.", chp->name));
	remove_from_channel(player, chp, 0);
    }
}

void do_clearcom(player, cause, key)
    dbref player, cause;
    int key;
{
    notify(player, "You remove yourself from all channels.");
    channel_clr(player);
}


void do_comtitle(player, cause, key, alias_str, title)
    dbref player, cause;
    int key;
    char *alias_str, *title;
{
    COMALIAS *cap;

    find_calias(player, alias_str, cap);

    if (cap->title)
	XFREE(cap->title, "do_comtitle.title");

    if (!title || !*title) {
	notify(player, tprintf("Title cleared on channel %s.",
			       cap->channel->name));
	return;
    }

    cap->title = XSTRDUP(munge_comtitle(title), "do_comtitle.title");

    notify(player, tprintf("Title set to '%s' on channel %s.",
			   cap->title, cap->channel->name));
}

void do_clist(player, cause, key, chan_name)
    dbref player, cause;
    int key;
    char *chan_name;
{
    CHANNEL *chp;
    char *buff, tbuf[LBUF_SIZE], *tp;
    int count = 0;

    if (chan_name && *chan_name) {

	find_channel(player, chan_name, chp);
	check_owned_channel(player, chp);

	notify(player, chp->name);

	tp = tbuf;
	safe_str("Flags:", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_PUBLIC)
	    safe_str(" Public", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_LOUD)
	    safe_str(" Loud", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_SPOOF)
	    safe_str(" Spoof", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_P_JOIN)
	    safe_str(" P_Join", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_P_RECV)
	    safe_str(" P_Receive", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_P_TRANS)
	    safe_str(" P_Transmit", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_O_JOIN)
	    safe_str(" O_Join", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_O_RECV)
	    safe_str(" O_Receive", tbuf, &tp);
	if (chp->flags & CHAN_FLAG_O_TRANS)
	    safe_str(" O_Transmit", tbuf, &tp);
	*tp = '\0';
	notify(player, tbuf);

	if (chp->join_lock)
	    buff = unparse_boolexp(player, chp->join_lock);
	else
	    buff = (char *) "*UNLOCKED*";
	notify(player, tprintf("Join Lock: %s", buff));

	if (chp->trans_lock)
	    buff = unparse_boolexp(player, chp->trans_lock);
	else
	    buff = (char *) "*UNLOCKED*";
	notify(player, tprintf("Transmit Lock: %s", buff));

	if (chp->recv_lock)
	    buff = unparse_boolexp(player, chp->recv_lock);
	else
	    buff = (char *) "*UNLOCKED*";
	notify(player, tprintf("Receive Lock: %s", buff));

	if (chp->descrip)
	    notify(player, tprintf("Description: %s", chp->descrip));

	return;
    }

    if (key & CLIST_FULL) {
	notify(player, "Channel              Flags      Locks  Charge  Balance  Users  Messages  Owner");
    } else if (key & CLIST_HEADER) {
	notify(player, "Channel              Owner              Header");
    } else {
	notify(player, "Channel              Owner              Description");
    }

    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	if ((chp->flags & CHAN_FLAG_PUBLIC) ||
	    Comm_All(player) || (chp->owner == player)) {

	    if (key & CLIST_FULL) {
		notify(player,
         tprintf("%-20s %c%c%c%c%c%c%c%c%c  %c%c%c    %6d  %7d  %5d  %8d  #%d",
		  chp->name,
		  (chp->flags & CHAN_FLAG_PUBLIC) ? 'P' : '-',
		  (chp->flags & CHAN_FLAG_LOUD) ? 'L' : '-',
		  (chp->flags & CHAN_FLAG_SPOOF) ? 'S' : '-',
		  (chp->flags & CHAN_FLAG_P_JOIN) ? 'J' : '-',
		  (chp->flags & CHAN_FLAG_P_TRANS) ? 'X' : '-',
		  (chp->flags & CHAN_FLAG_P_RECV) ? 'R' : '-',
		  (chp->flags & CHAN_FLAG_O_JOIN) ? 'j' : '-',
		  (chp->flags & CHAN_FLAG_O_TRANS) ? 'x' : '-',
		  (chp->flags & CHAN_FLAG_O_RECV) ? 'r' : '-',
		  (chp->join_lock) ? 'J' : '-',
		  (chp->trans_lock) ? 'X' : '-',
		  (chp->recv_lock) ? 'R' : '-',
		  chp->charge,
		  chp->charge_collected,
		  chp->num_who,
		  chp->num_sent,
		  chp->owner));
	    } else {
		notify(player,
		       tprintf("%-20s %-18s %-38.38s",
			       chp->name, Name(chp->owner),
			       ((key & CLIST_HEADER) ? chp->header :
				(chp->descrip ? chp->descrip : " "))));
	    }
	    count++;
	}
    }

    if (Comm_All(player)) {
	notify(player, tprintf("There %s %d %s.",
			       (count == 1) ? "is" : "are",
			       count,
			       (count == 1) ? "channel" : "channels"));
    } else {
	notify(player, tprintf("There %s %d %s visible to you.",
			       (count == 1) ? "is" : "are",
			       count,
			       (count == 1) ? "channel" : "channels"));
    }
}


void do_comlist(player, cause, key)
    dbref player, cause;
    int key;
{
    COMLIST *clist, *cl_ptr;
    int count = 0;

    clist = lookup_clist(player);
    if (!clist) {
	notify(player, "You are not on any channels.");
	return;
    }

    notify(player, "Alias      Channel              Title");

    for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = cl_ptr->next) {

	/* We are guaranteed alias and channel lengths that are not truncated.
	 * We need to truncate title.
	 */
	notify(player,
	       tprintf("%-10s %-20s %-40.40s  %s",
		       cl_ptr->alias_ptr->alias,
		       cl_ptr->alias_ptr->channel->name,
		       (cl_ptr->alias_ptr->title) ? cl_ptr->alias_ptr->title :
		       (char *) "                                        ",
		       (is_listenchannel(player, cl_ptr->alias_ptr->channel)) ?
		       "[on]" : " "));
	count++;
    }

    notify(player, tprintf("You have %d channel %s.",
			   count,
			   (count == 1) ? "alias" : "aliases"));
}
			

void do_allcom(player, cause, key, cmd)
    dbref player, cause;
    int key;
    char *cmd;
{
    COMLIST *clist, *cl_ptr;

    clist = lookup_clist(player);
    if (!clist) {
	notify(player, "You are not on any channels.");
	return;
    }

    for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = cl_ptr->next)
	process_comsys(player, cmd, cl_ptr->alias_ptr);
}


int mod_comsys_process_command(player, cause, interactive, in_cmd,
			       args, nargs)
    dbref player, cause;
    int interactive;
    char *in_cmd, *args[];
    int nargs;
{
    /* Return 1 if we got something, 0 if we didn't. */

    char *arg;
    COMALIAS *cap;
    char cmd[LBUF_SIZE];	/* temp -- can't nibble our input */

    if (!in_cmd || !*in_cmd || Slave(player))
	return 0;

    strcpy(cmd, in_cmd);

    arg = cmd;
    while (*arg && !isspace(*arg))
	arg++;
    if (*arg)
	*arg++ = '\0';

    cap = lookup_calias(player, cmd);
    if (!cap)
	return 0;

    while (*arg && isspace(*arg))
	arg++;
    if (!*arg) {
	notify(player, "No message.");
	return 1;
    }

    process_comsys(player, arg, cap);
    return 1;
}

/* --------------------------------------------------------------------------
 * Command tables.
 */

NAMETAB cboot_sw[] = {
{(char *)"quiet",	1,	CA_PUBLIC,	CBOOT_QUIET},
{ NULL,			0,	0,		0}};

NAMETAB cemit_sw[] = {
{(char *)"noheader",	1,	CA_PUBLIC,	CEMIT_NOHEADER},
{ NULL,			0,	0,		0}};

NAMETAB channel_sw[] = {
{(char *)"charge",	1,	CA_PUBLIC,	CHANNEL_CHARGE},
{(char *)"desc",	1,	CA_PUBLIC,	CHANNEL_DESC},
{(char *)"header",	1,	CA_PUBLIC,	CHANNEL_HEADER},
{(char *)"lock",	1,	CA_PUBLIC,	CHANNEL_LOCK},
{(char *)"owner",	1,	CA_PUBLIC,	CHANNEL_OWNER},
{(char *)"set",		1,	CA_PUBLIC,	CHANNEL_SET},
{(char *)"join",	1,	CA_PUBLIC,	CHANNEL_JOIN | SW_MULTIPLE},
{(char *)"transmit",	1,	CA_PUBLIC,	CHANNEL_TRANS | SW_MULTIPLE},
{(char *)"receive",	1,	CA_PUBLIC,	CHANNEL_RECV | SW_MULTIPLE},
{ NULL,			0,	0,		0}};

NAMETAB clist_sw[] = {
{(char *)"full",        1,      CA_PUBLIC,      CLIST_FULL},
{(char *)"header",	1,	CA_PUBLIC,	CLIST_HEADER},
{ NULL,                 0,      0,              0}};

NAMETAB cwho_sw[] = {
{(char *)"all",         1,      CA_PUBLIC,      CWHO_ALL},
{ NULL,                 0,      0,              0}};

CMDENT mod_comsys_cmdtable[] = {
{(char *)"@cboot",              cboot_sw,       CA_NO_SLAVE|CA_NO_GUEST,
        0,               CS_TWO_ARG,          
	NULL,		NULL,	NULL,		{do_cboot}},
{(char *)"@ccreate",            NULL,           CA_NO_SLAVE|CA_NO_GUEST,
        0,               CS_ONE_ARG,          
	NULL,		NULL,	NULL,		{do_ccreate}},
{(char *)"@cdestroy",           NULL,           CA_NO_SLAVE|CA_NO_GUEST,
        0,               CS_ONE_ARG,          
	NULL,		NULL,	NULL,		{do_cdestroy}},
{(char *)"@cemit",		cemit_sw,	CA_NO_SLAVE|CA_NO_GUEST,
	0,		 CS_TWO_ARG,		
	NULL,		NULL,	NULL,		{do_cemit}},
{(char *)"@channel",		channel_sw,	CA_NO_SLAVE|CA_NO_GUEST,
	0,		 CS_TWO_ARG|CS_INTERP,		
	NULL,		NULL,	NULL,		{do_channel}},
{(char *)"@clist",              clist_sw,       CA_NO_SLAVE,
        0,              CS_ONE_ARG,           
	NULL,		NULL,	NULL,		{do_clist}},
{(char *)"@cwho",               cwho_sw,           CA_NO_SLAVE,
        0,              CS_ONE_ARG,           
	NULL,		NULL,	NULL,		{do_cwho}},
{(char *)"addcom",              NULL,           CA_NO_SLAVE,
        0,              CS_TWO_ARG|CS_ARGV,           
	NULL,		NULL,	NULL,		{do_addcom}},
{(char *)"allcom",              NULL,           CA_NO_SLAVE,
        0,              CS_ONE_ARG,           
	NULL,		NULL,	NULL,		{do_allcom}},
{(char *)"comlist",             NULL,           CA_NO_SLAVE,
        0,              CS_NO_ARGS,           
	NULL,		NULL,	NULL,		{do_comlist}},
{(char *)"comtitle",            NULL,           CA_NO_SLAVE,
        0,              CS_TWO_ARG,          
	NULL,		NULL,	NULL,		{do_comtitle}},
{(char *)"clearcom",            NULL,           CA_NO_SLAVE,
        0,              CS_NO_ARGS,           
	NULL,		NULL,	NULL,		{do_clearcom}},
{(char *)"delcom",              NULL,           CA_NO_SLAVE,
        0,              CS_ONE_ARG,           
	NULL,		NULL,	NULL,		{do_delcom}},
{(char *)NULL,			NULL,		0,
	0,		0,				
	NULL,		NULL,	NULL,		{NULL}}};

/* --------------------------------------------------------------------------
 * Initialization, and other fun with files.
 */

void mod_comsys_dump_database(fp)
FILE *fp;
{
    CHANNEL *chp;
    COMALIAS *cap;

    fprintf(fp, "+V4\n");

    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	putstring(fp, chp->name);
	putref(fp, chp->owner);
	putref(fp, chp->flags);
	putref(fp, chp->charge);
	putref(fp, chp->charge_collected);
	putref(fp, chp->num_sent);
	putstring(fp, chp->descrip);
	putstring(fp, chp->header);
	putboolexp(fp, chp->join_lock);
	fprintf(fp, "-\n");
	putboolexp(fp, chp->trans_lock);
	fprintf(fp, "-\n");
	putboolexp(fp, chp->recv_lock);
	fprintf(fp, "-\n");
	fprintf(fp, "<\n");
    }

    fprintf(fp, "+V1\n");

    for (cap = (COMALIAS *) hash_firstentry(&mod_comsys_calias_htab);
	 cap != NULL;
	 cap = (COMALIAS *) hash_nextentry(&mod_comsys_calias_htab)) {
	putref(fp, cap->player);
	putstring(fp, cap->channel->name);
	putstring(fp, cap->alias);
	putstring(fp, cap->title);
	putref(fp, is_listening_disconn(cap->player, cap->channel));
	fprintf(fp, "<\n");
    }

    fprintf(fp, "*** END OF DUMP ***\n");
}

static void comsys_flag_convert(chp)
    CHANNEL *chp;
{
    /* Convert MUX-style comsys flags to the new style. */

    int old_flags, new_flags;

    old_flags = chp->flags;
    new_flags = 0;

    if (old_flags & 0x200)
	new_flags |= CHAN_FLAG_PUBLIC;
    if (old_flags & 0x100)
	new_flags |= CHAN_FLAG_LOUD;
    if (old_flags & 0x1)
	new_flags |= CHAN_FLAG_P_JOIN;
    if (old_flags & 0x2)
	new_flags |= CHAN_FLAG_P_TRANS;
    if (old_flags & 0x4)
	new_flags |= CHAN_FLAG_P_RECV;
    if (old_flags & (0x10 * 0x1))
	new_flags |= CHAN_FLAG_O_JOIN;
    if (old_flags & (0x10 * 0x2))
	new_flags |= CHAN_FLAG_O_TRANS;
    if (old_flags & (0x10 * 0x4))
	new_flags |= CHAN_FLAG_O_RECV;

    chp->flags = new_flags;
}

static void comsys_data_update(chp, obj)
    CHANNEL *chp;
    dbref obj;
{
    /* Copy data from a MUX channel object to a new-style channel. */

    char *key;
    dbref aowner;
    int aflags, alen;

    key = atr_get(obj, A_LOCK, &aowner, &aflags, &alen);
    chp->join_lock = parse_boolexp(obj, key, 1);
    free_lbuf(key);

    key = atr_get(obj, A_LUSE, &aowner, &aflags, &alen);
    chp->trans_lock = parse_boolexp(obj, key, 1);
    free_lbuf(key);

    key = atr_get(obj, A_LENTER, &aowner, &aflags, &alen);
    chp->recv_lock = parse_boolexp(obj, key, 1);
    free_lbuf(key);

    key = atr_pget(obj, A_DESC, &aowner, &aflags, &alen);
    if (*key)
	chp->descrip = XSTRDUP(key, "comsys_data_update.desc");
    else
	chp->descrip = NULL;
    free_lbuf(key);
}

static void read_comsys(fp, com_ver)
    FILE *fp;
    int com_ver;
{
    CHANNEL *chp;
    COMALIAS *cap;
    COMLIST *clist;
    CHANWHO *wp;
    char c, *s, buf[LBUF_SIZE];
    int done;

    done = 0;
    c = getc(fp);
    if (c == '+')		/* do we have any channels? */
	done = 1;
    ungetc(c, fp);

    /* Load up the channels */

    while (!done) {

	chp = (CHANNEL *) XMALLOC(sizeof(CHANNEL), "load_comsys.channel");
	chp->name = XSTRDUP(getstring_noalloc(fp, 1), "load_comsys.name");
	chp->owner = getref(fp);
	if (!Good_obj(chp->owner) || !isPlayer(chp->owner))
	    chp->owner = GOD;	/* sanitize */
	chp->flags = getref(fp);
	if (com_ver == 1)
	    comsys_flag_convert(chp);
	chp->charge = getref(fp);
	chp->charge_collected = getref(fp);
	chp->num_sent = getref(fp);
	chp->header = NULL;
	if (com_ver == 1) {
	    comsys_data_update(chp, getref(fp));
	} else {
	    s = (char *) getstring_noalloc(fp, 1);
	    if (s && *s)
		chp->descrip = XSTRDUP(s, "read_comsys.desc");
	    else
		chp->descrip = NULL;

	    if (com_ver > 3) {
		s = (char *) getstring_noalloc(fp, 1);
		if (s && *s)
		    chp->header = XSTRDUP(s, "read_comsys.header");
	    }

	    if (com_ver == 2) {
		/* Inherently broken behavior. Can't deal with eval locks,
		 * among other things.
		 */
		chp->join_lock = getboolexp1(fp);
		getc(fp);		/* eat newline */
		chp->trans_lock = getboolexp1(fp);
		getc(fp);		/* eat newline */
		chp->recv_lock = getboolexp1(fp);
		getc(fp);		/* eat newline */
	    } else {
		chp->join_lock = getboolexp1(fp);
		if (getc(fp) != '\n') {
		    /* Uh oh. Format error. Trudge on valiantly... probably
		     * won't work, but we can try.
		     */
		    fprintf(mainlog_fp,
	    "Missing newline while reading join lock for channel %s\n",
			    chp->name);
		}
		c = getc(fp);
		if (c == '\n') {
		    getc(fp);	/* eat the dash on the next line */
		    getc(fp);	/* eat the newline on the next line */
		} else if (c == '-') {
		    getc(fp);	/* eat the next newline */
		} else {
		    fprintf(mainlog_fp,
    "Expected termination sequence while reading join lock for channel %s\n",
			    chp->name);
		}
		chp->trans_lock = getboolexp1(fp);
		if (getc(fp) != '\n') {
		    fprintf(mainlog_fp,
	    "Missing newline while reading transmit lock for channel %s\n",
			    chp->name);
		}
		c = getc(fp);
		if (c == '\n') {
		    getc(fp);	/* eat the dash on the next line */
		    getc(fp);	/* eat the newline on the next line */
		} else if (c == '-') {
		    getc(fp);	/* eat the next newline */
		} else {
		    fprintf(mainlog_fp,
 "Expected termination sequence while reading transmit lock for channel %s\n",
			    chp->name);
		}
		chp->recv_lock = getboolexp1(fp);
		if (getc(fp) != '\n') {
		    fprintf(mainlog_fp,
	    "Missing newline while reading receive lock for channel %s\n",
			    chp->name);
		}
		c = getc(fp);
		if (c == '\n') {
		    getc(fp);	/* eat the dash on the next line */
		    getc(fp);	/* eat the newline on the next line */
		} else if (c == '-') {
		    getc(fp);	/* eat the next newline */
		} else {
		    fprintf(mainlog_fp,
  "Expected termination sequence while reading receive lock for channel %s\n",
			    chp->name);
		}
	    }
		    
	}

	if (!chp->header) {
	    sprintf(buf, "[%s]", chp->name);
	    chp->header = XSTRDUP(buf, "read_comsys.header");
	}

	chp->who = NULL;
	chp->num_who = 0;
	chp->connect_who = NULL;
	chp->num_connected = 0;
	hashadd(chp->name, (int *) chp, &mod_comsys_comsys_htab, 0);
	getstring_noalloc(fp, 0);	/* discard the < */
	c = getc(fp);
	if (c == '+')		/* look ahead for the end of the channels */
	    done = 1;
	ungetc(c, fp);
    }

    getstring_noalloc(fp, 0);	/* discard the version string */

    done = 0;
    c = getc(fp);
    if (c == '*')		/* do we have any aliases? */
	done = 1;
    ungetc(c, fp);

    /* Load up the aliases */

    while (!done) {
	cap = (COMALIAS *) XMALLOC(sizeof(COMALIAS), "load_comsys.alias");
	cap->player = getref(fp);
	cap->channel = lookup_channel((char *) getstring_noalloc(fp, 1));
	cap->alias = XSTRDUP(getstring_noalloc(fp, 1), "load_comsys.alias_str");
	s = (char *) getstring_noalloc(fp, 1);
	if (s && *s)
	    cap->title = XSTRDUP(s, "load_comsys.title");
	else
	    cap->title = NULL;
	hashadd(tprintf("%d.%s", cap->player, cap->alias), (int *) cap,
		&mod_comsys_calias_htab, 0);
	clist = (COMLIST *) XMALLOC(sizeof(COMLIST), "load_comsys.clist");
	clist->alias_ptr = cap;
	clist->next = lookup_clist(cap->player);
	if (clist->next == NULL)
	    nhashadd((int) cap->player, (int *) clist, &mod_comsys_comlist_htab);
	else
	    nhashrepl((int) cap->player, (int *) clist,
		      &mod_comsys_comlist_htab);
	if (!is_onchannel(cap->player, cap->channel)) {
	    wp = (CHANWHO *) XMALLOC(sizeof(CHANWHO), "load_comsys.who");
	    wp->player = cap->player;
	    wp->is_listening = getref(fp);
	    if ((cap->channel->num_who == 0) || (cap->channel->who == NULL)) {
		wp->next = NULL;
	    } else {
		wp->next = cap->channel->who;
	    }
	    cap->channel->who = wp;
	    cap->channel->num_who++;
	} else {
	    getref(fp);		/* toss the value */
	}
	getstring_noalloc(fp, 0);	/* discard the < */
	c = getc(fp);
	if (c == '*')		/* look ahead for the end of the aliases */
	    done = 1;
	ungetc(c, fp);
    }

    s = (char *) getstring_noalloc(fp, 0);
    if (strcmp(s, (char *) "*** END OF DUMP ***")) {
	STARTLOG(LOG_STARTUP, "INI", "COM")
	    log_printf("Aborted load on unexpected line: %s", s);
	ENDLOG
    }
}

static void sanitize_comsys()
{
    /* Because we can run into situations where the comsys db and
     * regular database are not in sync (ex: restore from backup),
     * we need to sanitize the comsys data structures at load time.
     * The comlists we have represent the dbrefs of objects on channels.
     * Thus we can just look at what objects are there, and work
     * accordingly.
     */

    int i, count;
    NHSHTAB *htab;
    NHSHENT *hptr;
    int *ptab;

    count = 0;
    htab = &mod_comsys_comlist_htab;
    ptab = (int *) XCALLOC(htab->entries, sizeof(int), "sanitize_comsys");

    for (i = 0; i < htab->hashsize; i++) {
	for (hptr = htab->entry[i]; hptr != NULL; hptr = hptr->next) {
	    if (!Good_obj(hptr->target.i)) {
		ptab[count] = hptr->target.i;
		count++;
	    }
	}
    }

    /* Have to do this separately, so we don't mess up the hashtable
     * linking while we're trying to traverse it.
     */

    for (i = 0; i < count; i++)
	channel_clr(ptab[i]);

    XFREE(ptab, "sanitize_comsys");
}

void mod_comsys_make_minimal()
{
    CHANNEL *chp;

    do_ccreate(GOD, GOD, 0, mod_comsys_config.public_channel);
    chp = lookup_channel(mod_comsys_config.public_channel);
    if (chp) {		/* should always be true, but be safe */
	chp->flags |= CHAN_FLAG_PUBLIC;
    }

    do_ccreate(GOD, GOD, 0, mod_comsys_config.guests_channel);
    chp = lookup_channel(mod_comsys_config.guests_channel);
    if (chp) {		/* should always be true, but be safe */
	chp->flags |= CHAN_FLAG_PUBLIC;
    }
}

void mod_comsys_load_database(fp)
FILE *fp;
{
    char buffer[2 * MBUF_SIZE + 8];	/* depends on max length of params */

    fgets(buffer, sizeof(buffer), fp);
    if (!strncmp(buffer, (char *) "+V", 2)) {
	read_comsys(fp, atoi(buffer + 2));
	sanitize_comsys();
    } else {
	STARTLOG(LOG_STARTUP, "INI", "COM")
	    log_printf("Unrecognized comsys format.");
	ENDLOG
	mod_comsys_make_minimal();
    }
}

/* --------------------------------------------------------------------------
 * User functions.
 */

#define Grab_Channel(p) \
    chp = lookup_channel(fargs[0]); \
    if (!chp) { \
	safe_str((char *) "#-1 CHANNEL NOT FOUND", buff, bufc); \
	return; \
    } \
    if ((!Comm_All(p) && ((p) != chp->owner))) { \
	safe_str((char *) "#-1 NO PERMISSION TO USE", buff, bufc); \
	return; \
    }

#define Comsys_User(p,t) \
    t = lookup_player(p, fargs[0], 1); \
    if (!Good_obj(t) || (!Controls(p,t) && !Comm_All(p))) { \
	safe_str((char *) "#-1 NO PERMISSION TO USE", buff, bufc); \
	return; \
    }

#define Grab_Alias(p,n) \
    cap = lookup_calias(p,n); \
    if (!cap) { \
        safe_str((char *) "#-1 NO SUCH ALIAS", buff, bufc); \
        return; \
    }

FUNCTION(fun_comlist)
{
    CHANNEL *chp;
    char *bb_p;
    Delim osep;

    VaChk_Only_Out(1);

    bb_p = *bufc;
    for (chp = (CHANNEL *) hash_firstentry(&mod_comsys_comsys_htab);
	 chp != NULL;
	 chp = (CHANNEL *) hash_nextentry(&mod_comsys_comsys_htab)) {
	if ((chp->flags & CHAN_FLAG_PUBLIC) ||
	    Comm_All(player) || (chp->owner == player)) {
	    if (*bufc != bb_p) {
		print_sep(&osep, buff, bufc);
	    }
	    safe_str(chp->name, buff, bufc);
	}
    }
}

FUNCTION(fun_cwho)
{
    CHANNEL *chp;
    char *bb_p;
    int i;

    Grab_Channel(player);
    bb_p = *bufc;
    for (i = 0; i < chp->num_connected; i++) {
	if (chp->connect_who[i]->is_listening &&
	    (!isPlayer(chp->connect_who[i]->player) ||
	     (Connected(chp->connect_who[i]->player) &&
	      (!Hidden(chp->connect_who[i]->player) || See_Hidden(player))))) {
	    if (*bufc != bb_p)
		safe_chr(' ', buff, bufc);
	    safe_dbref(buff, bufc, chp->connect_who[i]->player);
	}
    }
}

FUNCTION(fun_cwhoall)
{
    CHANNEL *chp;
    CHANWHO *wp;
    char *bb_p;

    Grab_Channel(player);
    bb_p = *bufc;
    for (wp = chp->who; wp != NULL; wp = wp->next) {
	if (*bufc != bb_p)
	    safe_chr(' ', buff, bufc);
	safe_dbref(buff, bufc, wp->player);
    }
}

FUNCTION(fun_comowner)
{
    CHANNEL *chp;

    Grab_Channel(player);
    safe_dbref(buff, bufc, chp->owner);
}

FUNCTION(fun_comdesc)
{
    CHANNEL *chp;

    Grab_Channel(player);
    if (chp->descrip)
	safe_str(chp->descrip, buff, bufc);
}

FUNCTION(fun_comheader)
{
    CHANNEL *chp;

    Grab_Channel(player);
    if (chp->header)
	safe_str(chp->header, buff, bufc);
}

FUNCTION(fun_comalias)
{
    dbref target;
    COMLIST *clist, *cl_ptr;
    char *bb_p;

    Comsys_User(player, target);

    clist = lookup_clist(target);
    if (!clist)
	return;
    bb_p = *bufc;
    for (cl_ptr = clist; cl_ptr != NULL; cl_ptr = cl_ptr->next) {
	if (*bufc != bb_p)
	    safe_chr(' ', buff, bufc);
	safe_str(cl_ptr->alias_ptr->alias, buff, bufc);
    }
}

FUNCTION(fun_cominfo)
{
    dbref target;
    COMALIAS *cap;

    Comsys_User(player, target);
    Grab_Alias(target, fargs[1]);
    safe_str(cap->channel->name, buff, bufc);
}

FUNCTION(fun_comtitle)
{
    dbref target;
    COMALIAS *cap;

    Comsys_User(player, target);
    Grab_Alias(target, fargs[1]);
    if (cap->title)
	safe_str(cap->title, buff, bufc);
}

FUNCTION(fun_cemit)
{
    CHANNEL *chp;

    Grab_Channel(player);
    com_message(chp, fargs[1], player);
}

FUN mod_comsys_functable[] = {
{"CEMIT",	fun_cemit,	2,  0,		CA_PUBLIC,	NULL},
{"COMALIAS",	fun_comalias,	1,  0,		CA_PUBLIC,	NULL},
{"COMDESC",	fun_comdesc,	1,  0,		CA_PUBLIC,	NULL},
{"COMHEADER",	fun_comheader,	1,  0,		CA_PUBLIC,	NULL},
{"COMINFO",	fun_cominfo,	2,  0,		CA_PUBLIC,	NULL},
{"COMLIST",	fun_comlist,	0,  FN_VARARGS, CA_PUBLIC,	NULL},
{"COMOWNER",	fun_comowner,	1,  0,		CA_PUBLIC,	NULL},
{"COMTITLE",	fun_comtitle,	2,  0,		CA_PUBLIC,	NULL},
{"CWHO",        fun_cwho,       1,  0,          CA_PUBLIC,	NULL},
{"CWHOALL",     fun_cwhoall,    1,  0,          CA_PUBLIC,	NULL},
{NULL,		NULL,		0,  0,		0,		NULL}};

/* --------------------------------------------------------------------------
 * Initialization.
 */

void mod_comsys_cleanup_startup()
{
    update_comwho_all();
}

void mod_comsys_create_player(creator, player, isrobot, isguest)
    dbref creator, player;
    int isrobot, isguest;
{
    if (isguest && (player != 1)) {
	if (*mod_comsys_config.guests_channel)
	    join_channel(player, mod_comsys_config.guests_channel,
			 mod_comsys_config.guests_calias, NULL);
    } else if (player != 1) { /* avoid problems with minimal db */
	if (*mod_comsys_config.public_channel)
	    join_channel(player, mod_comsys_config.public_channel,
			 mod_comsys_config.public_calias, NULL);
    }
}

void mod_comsys_destroy_obj(player, obj)
    dbref player, obj;
{
    channel_clr(obj);
}

void mod_comsys_destroy_player(player, victim)
    dbref player, victim;
{
    comsys_chown(victim, Owner(player));
}

void mod_comsys_init()
{
    mod_comsys_config.public_channel = XSTRDUP("Public", "cf_string");
    mod_comsys_config.guests_channel = XSTRDUP("Guests", "cf_string");
    mod_comsys_config.public_calias = XSTRDUP("pub", "cf_string");
    mod_comsys_config.guests_calias = XSTRDUP("g", "cf_string");

    register_hashtables(mod_comsys_hashtable, mod_comsys_nhashtable);
    register_commands(mod_comsys_cmdtable);
    register_functions(mod_comsys_functable);
}