/* ChanServ routines

   Cygnus IRC Services - Copyright (c) 2001-2002 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/services.h"

ChanInfo *cs_chanlist[HASHSIZE];

int32 verifycnt = 0;
int32 verify_size = 0;
struct verify_ *verify = NULL;

static void do_help (User *u);
static void do_register (User *u);
static void do_identify (User *u);
static void do_drop (User *u);
static void do_info (User *u);
static void do_auth (User *u);
static void do_set (User *u);
static void do_count (User *u);
static void do_vop (User *u);
static void do_hop (User *u);
static void do_aop (User *u);
static void do_sop (User *u);
static void do_xop (User *u, int level);
static void do_send (User *u);
static void do_op (User *u);
static void do_deop (User *u);
static void do_halfop (User *u);
static void do_dehalfop (User *u);
static void do_voice (User *u);
static void do_devoice (User *u);
static void do_bans (User *u);
static void do_unban (User *u);
static void do_cs_kick (User *u);
static void do_clear (User *u);
static void do_invite (User *u);
static void do_akick (User *u);
static void do_status (User *u);
static void do_freeze (User *u);
static void do_unfreeze (User *u);
static void do_getpass (User *u);
static void do_setpass (User *u);
static void do_sendpass (User *u);

/* Main ChanServ routine. */
void chanserv (User *u, char *buf)  
{
    char *cmd, *s;

    cmd = strtok (buf, " ");

    if (!cmd)
	return;
    
    if (!stricmp (cmd, "\1PING"))
    {
	if (!(s = strtok (NULL, "")))
	    s = "0";
	strip (s);
	ctcpreply (s_ChanServ, u->nick, "PING %s", s);
	return;
    }
    else if (!stricmp (cmd, "\1VERSION\1"))
    {
	ctcpreply (s_ChanServ, u->nick, "VERSION Cygnus IRC Services v%s",
	    version);
	return;
    }

    /* A CTCP we don't recognize.. Ignore it. */
    else if (*cmd == '\1')
	return;

    else
    {
	Hash *command, hash_table[] =
	{
	    {"HELP",		H_NONE,		do_help},
	    {"REGISTER",	H_NONE,		do_register},
	    {"IDENTIFY",	H_NONE,		do_identify},
	    {"ID",		H_NONE,		do_identify},
	    {"INFO",		H_NONE,		do_info},
	    {"DROP",		H_NONE,		do_drop},
	    {"SET",		H_NONE,		do_set},
	    {"COUNT",		H_NONE,		do_count},
	    {"VOP",		H_NONE,		do_vop},
	    {"HOP",		H_NONE,		do_hop},
	    {"AOP",		H_NONE,		do_aop},
	    {"SOP",		H_NONE,		do_sop},
	    {"SEND",		H_NONE,		do_send},
	    {"OP",		H_NONE,		do_op},
	    {"DEOP",		H_NONE,		do_deop},
	    {"HALFOP",		H_NONE,		do_halfop},
	    {"DEHALFOP",	H_NONE,		do_dehalfop},
	    {"VOICE",		H_NONE,		do_voice},
	    {"DEVOICE",		H_NONE,		do_devoice},
	    {"BANS",		H_NONE,		do_bans},
	    {"UNBAN",		H_NONE,		do_unban},
	    {"KICK",		H_NONE,		do_cs_kick},
	    {"CLEAR",		H_NONE,		do_clear},
	    {"INVITE",		H_NONE,		do_invite},
	    {"AKICK",		H_NONE,		do_akick},
	    {"STATUS",		H_NONE,		do_status},
	    {"FREEZE",		H_CSOP,		do_freeze},
	    {"UNFREEZE",	H_CSOP,		do_unfreeze},
	    {"GETPASS",		H_CSOP,		do_getpass},
	    {"SETPASS",		H_CSOP,		do_setpass},
	    {"SENDPASS",	H_CSOP,		do_sendpass},
	    {"AUTH",		H_CSOP,		do_auth},
	    {NULL}
	};
            
	if ((command = get_hash (s_ChanServ, u, strupper (cmd),
				 hash_table)))
	{
	    if ((csregistertype != 4 && command->process == do_auth) ||
		((command->process == do_hop || command->process == do_halfop ||
		     command->process == do_dehalfop) && !(ircdtype == UNREAL3 ||
		     ircdtype == UNREAL3_2)))
		notice (s_ChanServ, u->nick, RPL_UNKNOWN_COMMAND, strupper (cmd),
		    haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
		    haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		    haveserv_on == TRUE ? "" : securitysetting == 1 ?
		    me.name : "");
	    else
		(*command->process) (u);
	}
    }
}

/* Return a help message. */
static void do_help (User *u) 
{
    char *cmd = strtok (NULL, "");
            
    if (!cmd)
	chanserv_help_index (u);
    else
    {
	Hash *command, hash_table[] =
	{
	    {"REGISTER",	H_NONE,		chanserv_help_register},
	    {"DROP",		H_NONE,		chanserv_help_drop},
	    {"INFO",		H_NONE,		chanserv_help_info},
	    {"IDENTIFY",	H_NONE,		chanserv_help_identify},
	    {"SET",		H_NONE,		chanserv_help_set},
	    {"SET MLOCK",	H_NONE,		chanserv_help_set_mlock},
	    {"SET FOUNDER",	H_NONE,		chanserv_help_set_founder},
	    {"SET SUCCESSOR",	H_NONE,		chanserv_help_set_successor},
	    {"SET PASS",	H_NONE,		chanserv_help_set_pass},
	    {"SET URL",		H_NONE,		chanserv_help_set_url},
	    {"SET GREET",	H_NONE,		chanserv_help_set_greet},
	    {"SET TOPIC",	H_NONE,		chanserv_help_set_topic},
	    {"SET TOPICLOCK",	H_NONE,		chanserv_help_set_topiclock},
	    {"SET MEMOLEVEL",	H_NONE,		chanserv_help_set_memolevel},
	    {"SET VERBOSE",	H_NONE,		chanserv_help_set_verbose},
	    {"SET LIMITED",	H_NONE,		chanserv_help_set_limited},
	    {"SET VOPALL",	H_NONE,		chanserv_help_set_vopall},
	    {"SET SECURE",	H_NONE,		chanserv_help_set_secure},
	    {"SET RESTRICTED",	H_NONE,		chanserv_help_set_restricted},
	    {"COUNT",		H_NONE,		chanserv_help_count},
	    {"STATUS",		H_NONE,		chanserv_help_status},
	    {"BANS",		H_NONE,		chanserv_help_bans},
	    {"UNBAN",		H_NONE,		chanserv_help_unban},
	    {"KICK",		H_NONE,		chanserv_help_kick},
	    {"CLEAR",		H_NONE,		chanserv_help_clear},
	    {"INVITE",		H_NONE,		chanserv_help_invite},
	    {"SEND",		H_NONE,		chanserv_help_send},
	    {"VOP",		H_NONE,		chanserv_help_vop},
	    {"HOP",		H_NONE,		chanserv_help_hop},
	    {"AOP",		H_NONE,		chanserv_help_aop},
	    {"SOP",		H_NONE,		chanserv_help_sop},
	    {"OP",		H_NONE,		chanserv_help_op},
	    {"DEOP",		H_NONE,		chanserv_help_deop},
	    {"HALFOP",		H_NONE,		chanserv_help_halfop},
	    {"DEHALFOP",	H_NONE,		chanserv_help_dehalfop},
	    {"VOICE",		H_NONE,		chanserv_help_voice},
	    {"DEVOICE",		H_NONE,		chanserv_help_devoice},
	    {"AKICK",		H_NONE,		chanserv_help_akick},
	    {"FREEZE",		H_CSOP,		chanserv_help_freeze},
	    {"UNFREEZE",	H_CSOP,		chanserv_help_unfreeze},
	    {"GETPASS",		H_CSOP,		chanserv_help_getpass},
	    {"SETPASS",		H_CSOP,		chanserv_help_setpass},
	    {"SENDPASS",	H_CSOP,		chanserv_help_sendpass},
	    {"AUTH",		H_CSOP,		chanserv_help_auth},
	    {NULL}
	};

	if ((command = get_help_hash (s_ChanServ, u, strupper (cmd),
				      hash_table)))
	    (*command->process) (u);
    }
}

/* Register a channel. */
static void do_register (User *u)
{
    ChanInfo *ci, **list;
    ChanAccess *ca;
    NickInfo *ni;
    Channel *c;
    time_t now = time (NULL);
    char *param1 = strtok (NULL, " ");
    char *param2 = strtok (NULL, " ");
    char *param3 = strtok (NULL, " ");
    int i, reason = 0;

    /* Make sure this nick is fully registered */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "REGISTER");
	return;
    }

    if (!check_ns_auth (s_ChanServ, u->nick, ni))
	return;

    /* Check here if the're completing registration from E-Mail AUTH */
    if (param1 && param2 && (csregistertype == 1 || csregistertype == 3))
    {
	ci = cs_findchan (param1);
	c = findchan (param1);

	/* Is this channel waiting for completion? */
	if (ci && (ci->flags & CF_WAITAUTH))
	{
	    if (ci->key && ci->key == atol (param2))
	    {
		notice (s_ChanServ, u->nick, CS_REGISTERED, ci->name, ni->nick);
		notice (s_ChanServ, u->nick, RPL_PASSWORD_SET, ci->pass);

		if (strlen (csregister_url))
		{
		    notice (s_ChanServ, u->nick, CS_REGISTER_INFO);
		    notice (s_ChanServ, u->nick, csregister_url);
		}

		/* Set the default topic if there is one, and set the channel +r */
		if (c && !strlen (c->topic) && strlen (def_topic))
		    set_topic (ci, def_topic, s_ChanServ);

		if (c)
		    check_modes (ci->name);

		/* Turn the key off */
		ci->key = 0;

		/* No longer waiting */
		ci->flags &= ~CF_WAITAUTH;

		return;
	    }
	}
    }

    /* Check that we have all needed parameters and that they're valid. */
    if ((!csregistertype || csregistertype == 4) && (!param1 || !param2))
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "REGISTER Channel Password");
	errmoreinfo (s_ChanServ, u->nick, "REGISTER");
        return;
    }

    /* REGISTER #Channel */
    if (csregistertype == 1 && !param1)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "REGISTER Channel");
	errmoreinfo (s_ChanServ, u->nick, "REGISTER");
	return;
    }

    /* REGISTER Channel Pass Pass */
    if (csregistertype == 2 || csregistertype == 3)
    {
	if (!param1 || !param2 || !param3)
	{
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "REGISTER Channel Password Password");
	    errmoreinfo (s_ChanServ, u->nick, "REGISTER");
	    return;
	}

	if (strcmp (param2, param3))
	{
	    notice (s_ChanServ, u->nick, RPL_PASS_DONT_MATCH);
	    return;
	}
    }

    if ((ci = cs_findchan (param1)))
    {
	if (ci->flags & CF_FROZEN)
	    notice (s_ChanServ, u->nick, CS_CANNOT_REGISTER, param1);
	else
	    notice (s_ChanServ, u->nick, CS_ALREADY_REGISTERED, param1);

	return;
    }

    /* We only register #Channels.. */
    if (!*param1 == '#')
    {
	notice (s_ChanServ, u->nick, CS_NOT_VALID, param1);
	return;
    }

    if (!is_opped (u->nick, param1))
    {
	notice (s_ChanServ, u->nick, CS_BE_OPPED, param1);
	return;
    }

    /* Password sanity checks */
    if (csregistertype != 1)
    {
	if (strlen (param2) < 5)
	    reason = 1;
	if (strlen (param2) > PASSWDLEN)
	    reason = 2;
	if (!stricmp (u->nick, param2))
	    reason = 3;
	if (!stricmp ("password", param2))
	    reason = 4;

	if (reason)
	{
	    notice (s_ChanServ, u->nick, RPL_BETTER_PASS);

	    if (reason == 1)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_SHORT);
	    if (reason == 2)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_LONG);
	    if (reason == 3)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_NICK);
	    if (reason == 4)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_WEAK);

	    errmoreinfo (s_ChanServ, u->nick, "REGISTER");
	    return;
	}
    }

    /* No register floods! */
    if ((chan_delay && (now - u->lastcreg < chan_delay)) && !is_oper (u))
    {
	notice (s_ChanServ, u->nick, CS_WAIT_REG,
	    duration (u->lastcreg - now + chan_delay, 2));

	return;
    }

    /* Make sure they have an email if the registertype calls for it. */
    if (csregistertype == 1 || csregistertype == 3)
	if (!ni->email)
	{
	    notice (s_ChanServ, u->nick, CS_NEED_EMAIL, s_NickServ);
	    return;
	}

    /* Do they already have enough channels? */
    if (ni->chancnt >= chanlimit && !is_sra (u))
    {
	notice (s_ChanServ, u->nick, CS_CHAN_LIMIT, ni->chancnt, chanlimit);
	notice (s_ChanServ, u->nick, CS_DROP_SOME);
	notice (s_ChanServ, u->nick, CS_LIST_OWNED, haveserv_on == TRUE ? "" : "MSG ",
	    s_NickServ, haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	    haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	return;
    }

    /* Register the Channel. */
    ci = scalloc (sizeof (ChanInfo), 1);
    strscpy (ci->name, param1, CHANNELLEN);
    strscpy (ci->founder, ni->nick, NICKLEN);

    if (csregistertype != 1)
	strscpy (ci->pass, param2, PASSWDLEN);

    /* We'll make a pw */
    if (csregistertype == 1 || csregistertype == 3)
	ci->key = makekey ();

    /* They don't have a password. We'll use the key as their pw. */
    if (csregistertype == 1)
	strscpy (ci->pass, itoa (ci->key), PASSWDLEN);

    ci->registered = ci->lastused = now;

    if (defchanflags)
	ci->flags = defchanflags;

    /* Set the default modelock */
    ci->mlock_on |= defmlock_on;

    if (defmlock_off)
	ci->mlock_off |= defmlock_off;

    ci->memolevel = SOP;

    u->lastcreg = now;

    /* Add this channel to the founder's channel list */
    ni->chancnt++;
    ni->chans = srealloc (ni->chans, sizeof (char *) *ni->chancnt);
    ni->chans[ni->chancnt - 1] = sstrdup (ci->name);

    list = &cs_chanlist[CSHASH(ci->name)];
    ci->next = *list;
    if (*list)
        (*list)->prev = ci;
    *list = ci;

    /* Add them as founder */
    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	if (ca->level)
	    break;

    if (i == ci->accesscnt)
    {
	ci->accesscnt++;
	ci->access = srealloc (ci->access, sizeof (ChanAccess) * ci->accesscnt);
	ca = &ci->access[ci->accesscnt - 1];
    }
 
    ca->mask = sstrdup (ni->nick);
    ca->level = FOUNDER;

    /* Make note of the occurance. */
    log ("CS:REGISTER: %s %s!%s@%s", param1, u->nick, u->user, u->host);
    snoop (s_ChanServ, "CS:REGISTER: %s %s!%s@%s", param1, u->nick, u->user,
	u->host);

    /* If this was an E-Mail auth register, we'll tell them to check their
       email, email the instructions, and bail. We don't need to go beyond
       this point.
     */
    if (csregistertype == 1 || csregistertype == 3)
    {
	sendemail (ci->name, itoa (ci->key), 3);

	notice (s_ChanServ, u->nick, CS_REGISTERED_TEMP, ci->name);
	notice (s_ChanServ, u->nick, RPL_EMAIL_INSTRUCTIONS);
	notice (s_ChanServ, u->nick, CS_TEMP_EXPIRE, duration (tempexpire, 2));

	/* Set a flag so we know we're waiting for them */
	ci->flags |= CF_WAITAUTH;

	return;
    }

    /* List the channel as awaiting verification, then tell them they
       have to wait for it.
     */
    if (csregistertype == 4 && !is_csop (u))
    {
	Verify *vf;

	ci->flags |= CF_WAITING;

	if (verifycnt >= verify_size)
	{
	    if (verify_size < 8)
		verify_size = 8;
	    else if (verify_size >= 16384)
		verify_size = 32767;
	    else
		verify_size *= 2;

	    verify = srealloc (verify, sizeof (*verify) * verify_size);
	}

	vf = &verify[verifycnt];
	vf->name = sstrdup (ci->name);
	vf->since = now;

	verifycnt++;

	verify_notice (vf);

	notice (s_ChanServ, u->nick, CS_REGISTERED_TEMP, ci->name);
	notice (s_ChanServ, u->nick, CS_WAIT_AUTH);
	notice (s_ChanServ, u->nick, CS_WAIT_AWHILE);

	return;
    }

    /* Tell them it worked */
    notice (s_ChanServ, u->nick, CS_REGISTERED, ci->name, ni->nick);
    notice (s_ChanServ, u->nick, RPL_PASSWORD_SET, ci->pass);

    if (strlen (csregister_url))
    {
	notice (s_ChanServ, u->nick, CS_REGISTER_INFO);
	notice (s_ChanServ, u->nick, csregister_url);
    }

    /* Set the default topic if there is one, and set the channel +r */
    if ((c = findchan (param1)) && !strlen (c->topic) && strlen (def_topic))
	set_topic (ci, def_topic, s_ChanServ);

    check_modes (ci->name);
}

/* Identify for founder access on a channel. */
static void do_identify (User *u)
{
    ChanInfo *ci;
    char *chan = strtok (NULL, " ");
    char *pass = strtok (NULL, " ");

    if (!chan || !pass)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "IDENTIFY Channel Password");
	errmoreinfo (s_ChanServ, u->nick, "IDENTIFY");
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    /* Check the passwords. */
    if (!strcmp (pass, ci->pass))
    {
	int i, id = 0;
	char **chans;

	/* Only add this chan to their idchans if its not already there */
	if (u->idchancnt)
	    for (chans = u->idchans, i = 0; i < u->idchancnt; chans++, i++)
		if (!stricmp (*chans, ci->name))
		    id = 1;

	if (!id)
	{
	    u->idchancnt++;
	    u->idchans = srealloc (u->idchans, sizeof (char *) *u->idchancnt);
	    u->idchans[u->idchancnt - 1] = sstrdup (ci->name);
	}

	notice (s_ChanServ, u->nick, RPL_PASS_ACCEPTED, ci->name);

	ci->lastused = time (NULL);

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    send_cmd (s_ChanServ, "%s %s +q %s", me.token ? "G" : "MODE", ci->name, u->nick);
    }
    else
    {
	passfail (s_ChanServ, ci->name, "CS:IDENTIFY:BP:", u);
	return;
    }
}

/* Drop a registered channel */
static void do_drop (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    char *chan = strtok (NULL, " ");
    char *pass = strtok (NULL, " ");
    int csop = is_csop (u);

    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "DROP");
	return;
    }

    if (!chan || !pass)
    {
	if (!csop)
	{
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "DROP Channel Password");
	    errmoreinfo (s_ChanServ, u->nick, "DROP");
	    return;
	}
	else
	{
	    if (!chan)
	    {
		notice (s_ChanServ, u->nick, RPL_SYNTAX, "DROP Channel [Password]");
		errmoreinfo (s_ChanServ, u->nick, "DROP");
		return;
	    }
	}
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN && !csop)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!csop)
	if (!check_cs_auth (s_ChanServ, u->nick, ci))
	    return;

    /* Check the passwords. */
    if (csop || !strcmp (pass, ci->pass))
    {
	log ("CS:DROP: %s %s!%s@%s", ci->name, u->nick, u->user, u->host);
	snoop (s_ChanServ, "CS:DROP: %s %s!%s@%s", ci->name, u->nick, u->user, u->host);
	notice (s_ChanServ, u->nick, CS_DROPPED, ci->name);

	delchan (ci, 0);
    }
    else
    {
	passfail (s_ChanServ, ci->name, "CS:DROP:BP:", u);
	return;
    }
}

/* Show information on a registered channel */
static void do_info (User *u)
{
    ChanInfo *ci;
    NickInfo *ni, *tni, *hni, *sni = get_nick (u);
    char *chan = strtok (NULL, " ");
    char buf[BUFSIZE];

    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "INFO Channel");
	errmoreinfo (s_ChanServ, u->nick, "INFO");
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    notice (s_ChanServ, u->nick, CS_INFO_START, ci->name);

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_INFO_FROZEN);

	/* Hide the setter from non ircops to avoid evil retribution */
	if (is_oper (u))
	    notice (s_ChanServ, u->nick, CS_INFO_FROZEN_BY, ci->topicsetter);

	notice (s_ChanServ, u->nick, CS_INFO_FROZEN_WHY, ci->topic);
	notice (s_ChanServ, u->nick, CS_INFO_FROZEN_TIME, sni ? zone_time
	    (sni, ci->topictime, 4) : get_time (ci->topictime, 4, 0, "GMT"));
    }

    if (ci->flags & (CF_WAITING | CF_WAITAUTH))
	notice (s_ChanServ, u->nick, CS_INFO_WAITING);

    ni = findnick (ci->founder);

    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    if (!(hni->flags & NF_PRIVATE) || is_csop (u))
	notice (s_ChanServ, u->nick, CS_INFO_FOUNDER, ci->founder,
	    hni ? " (" : "", hni ? hni->usermask : "", hni ? ")" : "");
    else
	notice (s_ChanServ, u->nick, CS_INFO_FOUNDER, ci->founder,
	    " (", "\2Private\2", ")");

    if (ci->successor)
    {
	tni = findnick (ci->successor);

	if (!(tni->flags & NF_PRIVATE) || is_csop (u))
	    notice (s_ChanServ, u->nick, CS_INFO_SUCCESSOR, ci->successor,
		tni ? " (" : "", tni ? tni->usermask : "", tni ? ")" : "");
	else
	    notice (s_ChanServ, u->nick, CS_INFO_SUCCESSOR, ci->successor,
		" (", "\2Private\2", ")");
    }

    notice (s_ChanServ, u->nick, CS_INFO_REGISTERED, sni ? zone_time
	(sni, ci->registered, 4) : get_time (ci->registered, 4, 0, "GMT"),
	time_ago (ci->registered));

    if (!findchan (ci->name))
	notice (s_ChanServ, u->nick, CS_INFO_LAST_USED, sni ? zone_time
	    (sni, ci->lastused, 4) : get_time (ci->lastused, 4, 0, "GMT"),
	    time_ago (ci->lastused));

    if (ci->mlock_on || ci->mlock_off)
    {
	char params[BUFSIZE];

	*buf = 0;
	*params = 0;

	if (ci->mlock_on)
	{
	    strcat (buf, "+");
	    strcat (buf, flags_to_string (ci->mlock_on));

	    /* Add these in manually */
	    if (ci->mlock_limit)
	    {
		strcat (buf, "l");
		strcat (params, " ");
		strcat (params, itoa (ci->mlock_limit));
	    }

	    if (ci->mlock_key)
		strcat (buf, "k");

	    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    {
		if (ci->mlock_flood)
		{
		    strcat (buf, "f");
		    strcat (params, " ");
		    strcat (params, ci->mlock_flood);
		}

		if (ci->mlock_link)
		{
		    strcat (buf, "L");
		    strcat (params, " ");
		    strcat (params, ci->mlock_link);
		}
	    }
	}

	if (ci->mlock_off)
	{
	    strcat (buf, "-");
	    strcat (buf, flags_to_string (ci->mlock_off));
	}

	if (*buf)
	    notice (s_ChanServ, u->nick, CS_INFO_MLOCK, buf, params ? params : "");
    }

    if (ci->url)
	notice (s_ChanServ, u->nick, CS_INFO_URL, ci->url);

    if (ci->topiclock)
	notice (s_ChanServ, u->nick, CS_INFO_TOPICLOCK,
	    ci->topiclock == VOP ? "VOP and above" :
	    ((ircdtype == UNREAL3 || ircdtype == UNREAL3_2) && ci->topiclock == HOP)
	    ? "HOP and above" :
	    ci->topiclock == AOP ? "AOP and above" :
	    ci->topiclock == SOP ? "SOP and above" :
	    ci->topiclock == FOUNDER ? "Founder" : "Unknown!");

    notice (s_ChanServ, u->nick, CS_INFO_MEMOLEVEL,
	ci->memolevel == VOP ? "VOP and above" :
	((ircdtype == UNREAL3 || ircdtype == UNREAL3_2) && ci->memolevel == HOP)
            ? "HOP and above" :
	ci->memolevel == AOP ? "AOP and above" :
	ci->memolevel == SOP ? "SOP and above" :
	ci->memolevel == FOUNDER ? "Founder" : "Unknown!");

    *buf = 0;

    if (ci->flags & CF_SECURE)
	strcat (buf, FLAG_SECURE);

    if (ci->flags & CF_RESTRICTED)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_RESTRICTED);
    }

    if (ci->flags & CF_VERBOSE)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_VERBOSE);
    }

    if (ci->flags & CF_LIMITED)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_LIMITED);
    }

    if (ci->flags & CF_VOPALL)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_VOPALL);
    }

    if (ci->flags & CF_HELD)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_HELD);
    }

    if (ci->flags & CF_MARKED)
    {
	if (*buf)
	    strcat (buf, ", ");

	strcat (buf, FLAG_MARKED);
    }

    if (*buf)
	notice (s_ChanServ, u->nick, CS_INFO_FLAGS, buf);
    else
	notice (s_ChanServ, u->nick, CS_INFO_FLAGS, "None");

    notice (s_ChanServ, u->nick, CS_INFO_END);
}

/* Handle an AUTH command */
static void do_auth (User *u)
{
    User *utmp;
    ChanInfo *ci;
    Channel *c;
    char *param = strtok (NULL, " ");
    char *chan = strtok (NULL, " ");
    char *reason = strtok (NULL, "");
    char mbuf[BUFSIZE];
    int i, j = 0;

    if (!param)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "AUTH VERIFY|DENY|LIST [Channel] [Reason]");
	errmoreinfo (s_ChanServ, u->nick, "AUTH");
	return;
    }

    if (!stricmp (param, "VERIFY") || !stricmp (param, "DENY"))
    {
	if (!chan)
	{
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "AUTH VERIFY|DENY Channel [Reason]");
	    errmoreinfo (s_ChanServ, u->nick, "AUTH");
	    return;
	}

	for (i = 0; i < verifycnt; i++)
	    if (i + 1 == atoi (chan) || !stricmp (chan, verify[i].name))
		break;

	if (i == verifycnt)
	{
	    notice (s_ChanServ, u->nick, CS_VERIFY_NOT_FOUND, chan);
	    return;
	}

	if (!(ci = cs_findchan (verify[i].name)))
	{
	    notice (s_ChanServ, u->nick, CS_VERIFY_NOT_FOUND, verify[i].name);
	    return;
	}

	c = findchan (verify[i].name);
	utmp = finduser (ci->founder);

	/* If they're online, we'll tell them now. If not, we'll memo them. */
	if (utmp)
	{
	    if (!stricmp (param, "VERIFY"))
		notice (s_ChanServ, utmp->nick, CS_VERIFIED, ci->name);
	    else
		notice (s_ChanServ, utmp->nick, CS_DENIED, ci->name, reason ? " Reason: " : "",
		    reason ? reason : "");
	}
	else
	{
	    NickInfo *ni = findnick (ci->founder);

	    if (!stricmp (param, "VERIFY"))
	    {
		snprintf (mbuf, sizeof (mbuf), CS_VERIFIED, ci->name);
		send_memo (ni, s_ChanServ, mbuf);
	    }
	    else
	    {
		snprintf (mbuf, sizeof (mbuf), CS_DENIED, ci->name, reason ? " Reason: " : "", reason ? reason : "");
		send_memo (ni, s_ChanServ, mbuf);
	    }
	}

	/* Now tell the CSOps */
	if (!stricmp (param, "VERIFY"))
	{
	    noticecsops (s_ChanServ, CS_CSOP_VERIFIED, ci->name, u->nick, time_ago (verify[i].since));
	    log ("CS:AUTH:VERIFY: %s %s (Waited for %s)", u->nick, ci->name, time_ago (verify[i].since));
	    snoop (s_ChanServ, "CS:AUTH:VERIFY: %s %s (Waited for %s)", u->nick, ci->name, time_ago (verify[i].since));
	}
	else
	{
	    noticecsops (s_ChanServ, CS_CSOP_DENIED, ci->name, u->nick, time_ago (verify[i].since));
	    log ("CS:AUTH:DENY: %s %s (Waited for %s)", u->nick, ci->name, time_ago (verify[i].since));
	    snoop (s_ChanServ, "CS:AUTH:DENY: %s %s (Waited for %s)", u->nick, ci->name, time_ago (verify[i].since));
	}

	/* Delete it from the verify list */
	free (verify[i].name);

	verifycnt--;

	if (i < verifycnt)
	    memmove (verify+ i, verify + i + 1, sizeof (*verify)* (verifycnt - i));

	/* If it was denied, drop the channel */
	if (!stricmp (param, "DENY"))
	    delchan (ci, 0);

	/* Turn the waiting flag off if it was verified */
	if (!stricmp (param, "VERIFY"))
	{
	    ci->flags &= ~CF_WAITING;

	    /* Set the default topic if there isn't one */
	    if (c && !strlen (c->topic) && strlen (def_topic))
		set_topic (ci, def_topic, s_ChanServ);

	    if (c)
		check_modes (ci->name);
	}

	return;
    }

    if (!stricmp (param, "LIST"))
    {
	if (!verifycnt)
	{
	    notice (s_ChanServ, u->nick, RPL_LIST_EMPTY, "The", "", "verify");
	    return;
	}

	notice (s_ChanServ, u->nick, CS_VERIFY_LIST_START);
	notice (s_ChanServ, u->nick, CS_VERIFY_LIST);

	for (i = 0; i < verifycnt; i++)
	{
	    ci = cs_findchan (verify[i].name);

	    if (ci)
	    {
		NickInfo *ni = findnick (ci->founder);

		j++;

		notice (s_ChanServ, u->nick, "%-3d %-24s %-24s %-7d %s", i + 1, verify[i].name, ci->founder,
		    ni->chancnt, time_ago (verify[i].since));
	    }

	}

	notice (s_ChanServ, u->nick, CS_VERIFY_LIST_END, j);

	return;
    }

    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, param,
	haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
	haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
}

/* Set various channel options. */
static void do_set (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    ChanAccess *ca;
    char *chan = strtok (NULL, " ");
    char *option = strtok (NULL, " ");
    char *param;
    char **chans;
    int i, id = 0, stopic = 0, ulev = 0;

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "SET");
	return;
    }

    /* Special handling for this for some options.. */
    if (option && !stricmp (option, "MLOCK"))
	param = strtok (NULL, " ");
    else
	param = strtok (NULL, "");

    /* SET FOUNDER doesn't have a param */
    if (option && !stricmp (option, "FOUNDER"))
	param = "";

    /* Check for everything we need */
    if (!chan || !option || !param)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "SET Channel Option Parameters");
	errmoreinfo (s_ChanServ, u->nick, "SET");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    ulev = get_access (u, ci);

    /* Channel successors get limited access. */
    if (ci->successor && !stricmp (ni->nick, ci->successor))
	id = 2;

    /* Only someone with founder access can modify settings. Check if
       they've identified for this channel.
     */
    if (u->idchancnt)
	for (chans = u->idchans, i = 0; i < u->idchancnt; chans++, i++)
	    if (!stricmp (*chans, ci->name))
		id = 1;

    /* Also check for identification to the founder's nick, which we
       accept as well.
     */
    if (!stricmp (ni->nick, ci->founder))
	id = 1;

    /* Allow SET TOPIC from users with topiclock access */
    if (!stricmp (option, "TOPIC") && ci->topiclock <= ulev)
	stopic = 1;

    if (!id && !stopic)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "the channel founder");
	return;
    }

    /* Set the MLock? */
    if (!stricmp (option, "MLOCK"))
    {
	char *s, modebuf[32], *end, c;
	int add = -1;
	int32 newlock_on = 0, newlock_off = 0, newlock_limit = 0;
	char *newlock_key = NULL;
	char *newlock_flood = NULL, *newlock_link = NULL;

	while (*param)
	{
	    if (*param != '+' && *param != '-' && add < 0)
	    {
		param++;
		continue;
	    }

	    switch ((c = *param++))
	    {
		case '+':
		    add = 1;
		    break;

		case '-':
		    add = 0;
		    break;
		case 'k':
		    if (add)
		    {
			if (!(s = strtok (NULL, " ")))
			{
			    notice (s_ChanServ, u->nick, CS_MLOCK_NEED_PARAM, "key",
				"+k");
			    return;
			}

			if (newlock_key)
			    free (newlock_key);

			newlock_key = sstrdup (s);
			newlock_off &= ~CMODE_k;
		    }
		    else
		    {
			if (newlock_key)
			{
			    free (newlock_key);
			    newlock_key = NULL;
			}

			newlock_off |= CMODE_k;
		    }

		    break;
		case 'l':
		    if (add)
		    {
			if (!(s = strtok (NULL, " ")))
			{
			    notice (s_ChanServ, u->nick, CS_MLOCK_NEED_PARAM, "limit", "+l");
			    return;
			}

			if (atol (s) <= 0)
			{
			    notice (s_ChanServ, u->nick, CS_MLOCK_LIMIT_POSITIVE);
			    return;
			}

			newlock_limit = atol (s);
			newlock_off &= ~CMODE_l;
		    }
		    else
		    {
			newlock_limit = 0;
			newlock_off |= CMODE_l;
		    }

		    break;

		case 'f':
		    if (!(ircdtype == UNREAL3 || ircdtype == UNREAL3_2))
			break;

		    if (add)
		    {
			if (!(s = strtok (NULL, " ")))
			{
			    notice (s_ChanServ, u->nick, CS_MLOCK_NEED_PARAM, "flood trigger", "+f");
			    return;
			}

			if (newlock_flood)
			    free (newlock_flood);

			newlock_flood = sstrdup (s);
			newlock_off &= ~CMODE_f;
		    }
		    else
		    {
			if (newlock_flood)
			{
			    free (newlock_flood);
			    newlock_flood = NULL;
			}

			newlock_off |= CMODE_f;
		    }

		    break;

		case 'L':
		    if (!(ircdtype == UNREAL3 || ircdtype == UNREAL3_2))
			break;

		    /* Only the channel founder can set +L. */
		    if (u->idchancnt)
			for (chans = u->idchans, i = 0; i < u->idchancnt; chans++, i++)
			    if (!stricmp (*chans, ci->name))
				id = 1;

		    if (!id)
		    {
			notice (s_ChanServ, u->nick, CS_IDENTIFY, ci->name, "MLock +L");
			return;
		    }

		    if (add)
		    {
			if (!(s = strtok (NULL, " ")))
			{
			    notice (s_ChanServ, u->nick, CS_MLOCK_NEED_PARAM, "channel", "+L");
			    return;
			}

			if (newlock_link)
			    free (newlock_link);

			newlock_link = sstrdup (s);
			newlock_off &= ~CMODE_L;
		    }
		    else
		    {
			if (newlock_link)
			{
			    free (newlock_link);
			    newlock_link = NULL;
			}

			newlock_off |= CMODE_L;
		    }

		    break;

		default:
		{
		    int32 flag = mode_to_flag (c);

		    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
			if ((flag & (CMODE_A | CMODE_H)) && (!is_csop (u) ||
			    !(u->mode & (UMODE_A | UMODE_N | UMODE_T))))
			    continue;

		    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS || ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
			if ((flag & CMODE_O) && !is_oper (u))
			    continue;

		    if (flag)
		    {
			if (add)
			    newlock_on |= flag, newlock_off &= ~flag;
			else
			    newlock_off |= flag, newlock_on &= ~flag;
		    }
		}
	    }
	}

	/* Registered channels are always +r. Add it to mlock on and remove it
	   from mlock off.
	 */
	newlock_on |= CMODE_r, newlock_off &= ~CMODE_r;

	/* Save it to ChanInfo. */
	ci->mlock_on = newlock_on;
	ci->mlock_off = newlock_off;

	ci->mlock_limit = newlock_limit;

	if (ci->mlock_key)
	    free (ci->mlock_key);

	ci->mlock_key = newlock_key;

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    if (ci->mlock_flood)
		free (ci->mlock_flood);

	    ci->mlock_flood = newlock_flood;

	    if (ci->mlock_link)
		free (ci->mlock_link);

	    ci->mlock_link = newlock_link;
	}

	/* Tell the user about it. */
	end = modebuf;
	*end = 0;

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    if (ci->mlock_on || ci->mlock_key || ci->mlock_limit || ci->mlock_flood || ci->mlock_link)
		end += snprintf (end, sizeof (modebuf) - (end - modebuf), "+%s%s%s%s%s",
		    flags_to_string (ci->mlock_on),
		    ci->mlock_key ? "k" : "",
		    ci->mlock_limit ? "l" : "",
		    ci->mlock_flood ? "f" : "",
		    ci->mlock_link ? "L" : "");
	}
	else
	{
	    if (ci->mlock_on || ci->mlock_key || ci->mlock_limit)
		end += snprintf (end, sizeof (modebuf) - (end - modebuf), "+%s%s%s",
		    flags_to_string (ci->mlock_on),
		    ci->mlock_key ? "k" : "",
		    ci->mlock_limit ? "l" : "");
	}

	if (ci->mlock_off)
	    end += snprintf (end, sizeof (modebuf) - (end - modebuf), "-%s",
		flags_to_string (ci->mlock_off));

	if (*modebuf)
	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2MLOCK\2", modebuf);
	else
	    notice (s_ChanServ, u->nick, RPL_REMOVED, "Your channel's \2MLOCK\2");

	/* Finally, check the modes and make any nessecary changes. */
	check_modes (ci->name);

	return;
    }

    /* Set the Successor? */
    if (!stricmp (option, "SUCCESSOR"))
    {
	NickInfo *hni, *tni = findnick (param);
	char mbuf[BUFSIZE];

	/* Only the actual founder can change this. */
	if (id > 1)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "the channel founder");
	    return;
	}

	if (ci->successor && (!stricmp (param, "NONE") || !stricmp (param, "OFF")))
	{
	    /* Remove this channel from old successor's list, and
	       free the successor.
	     */
	    if (ci->successor)
	    {
		NickInfo *sni = findnick (ci->successor);

		/* Send a memo to the ex-successor */
		snprintf (mbuf, sizeof (mbuf), CS_SUCCESSOR_CHANGE, ci->founder, "removed", ci->name);
		send_memo (sni, s_ChanServ, mbuf);

		/* Take this channel out of their successor channel list. */
		for (i = 0; i < sni->successorcnt; i++)
		{
		    if (!stricmp (sni->successor[i], ci->name))
		    {
			free (sni->successor[i]);
			sni->successorcnt--;
			break;
		    }
		}

		if (!sni->successorcnt)
		    free (sni->successor);

		free (ci->successor);
		ci->successor = NULL;

		notice (s_ChanServ, u->nick, RPL_REMOVED, "Your channel's \2SUCCESSOR\2");
		return;
	    }
	}

	if (!tni)
	{
	    notice (s_ChanServ, u->nick, NS_NOT_REGISTERED, param);
	    return;
	}

	if (tni->host)
	    hni = findnick (tni->host);
	else
	    hni = tni;

	/* Nuh-uh. */
	if (!stricmp (hni->nick, ci->founder))
	{
	    notice (s_ChanServ, u->nick, CS_SUCCESSOR_SELF);
	    return;
	}

	if ((hni->chancnt + hni->successorcnt) >= chanlimit)
	{
	    notice (s_ChanServ, u->nick, CS_CHAN_LIMIT_OTHER, hni->nick);
	    return;
	}

	/* Check for the NoSuccessor flag */
	if (hni->flags & NF_NOSUCCESSOR)
	{
	    notice (s_ChanServ, u->nick, CS_NO_SUCCESSOR, hni->nick);
	    return;
	}

	/* If theres already a successor, we have to do a bit of cleanup. */
	if (ci->successor)
	{
	    NickInfo *sni = findnick (ci->successor);

	    if (!stricmp (sni->nick, hni->nick))
	    {
		notice (s_ChanServ, u->nick, CS_IS_ALREADY, hni->nick, " successor", ci->name);
		return;
	    }

	    /* Send a memo to the ex-successor */
	    snprintf (mbuf, sizeof (mbuf), CS_SUCCESSOR_CHANGE, ci->founder, "removed", ci->name);
	    send_memo (sni, s_ChanServ, mbuf);

	    /* Take this channel out of their successor channel list. */
	    for (i = 0; i < sni->successorcnt; i++)
	    {
		if (!stricmp (sni->successor[i], ci->name))
		{
		    free (sni->successor[i]);
		    sni->successorcnt--;
		    break;
		}
	    }

	    if (!sni->successorcnt)
		free (sni->successor);

	    free (ci->successor);
	}

	ci->successor = sstrdup (hni->nick);

	/* Send a memo to the new successor */
	snprintf (mbuf, sizeof (mbuf), CS_SUCCESSOR_CHANGE, ci->founder, "added", ci->name);
	send_memo (hni, s_ChanServ, mbuf);

	/* Add this channel to the users successor channel list */
	hni->successorcnt++;
	hni->successor = srealloc (hni->successor, sizeof (char *) *hni->successorcnt);
	hni->successor[hni->successorcnt - 1] = sstrdup (ci->name);

	notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2SUCCESSOR\2", hni->nick);

	return;
    }

    /* Change password? */
    if (!stricmp (option, "PASS") || !stricmp (option, "PASSWD") ||
	!stricmp (option, "PASSWORD"))
    {
	int reason = 0;
	char *pass1 = strtok (param, " ");
	char *pass2 = strtok (NULL, " ");

	/* Founder only. */
	if (id > 1)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "the channel founder");
	    return;
	}

	if (!pass2)
	{
	    notice (s_ChanServ, u->nick, RPL_SYNTAX,
		"SET Channel PASS Password Password");
	    errmoreinfo (s_ChanServ, u->nick, "SET PASS");
	    return;
	}

	/* Password sanity checks */
	if (strlen (pass1) < 5)
	    reason = 1;
	if (strlen (pass1) > PASSWDLEN)
	    reason = 2;
	if (!stricmp (u->nick, pass1))
	    reason = 3;
	if (!stricmp ("password", pass1))
	    reason = 4;
	if (strcmp (pass1, pass2))
	    reason = 5;
	if (!stricmp (ci->name, pass1))
	    reason = 6;
	if (!stricmp (ci->founder, pass1))
	    reason = 7;

	if (reason)
	{
	    notice (s_ChanServ, u->nick, RPL_BETTER_PASS);

	    if (reason == 1)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_SHORT);
	    if (reason == 2)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_LONG);
	    if (reason == 3)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_NICK);
	    if (reason == 4)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_WEAK);
	    if (reason == 5)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_DIFF);
	    if (reason == 6)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_CHAN);
	    if (reason == 7)
		notice (s_ChanServ, u->nick, RPL_PASS_REJECTED_FNDR);

	    errmoreinfo (s_ChanServ, u->nick, "SET PASS");
	    return;
	}

	/* Passed checks, set it */
	strscpy (ci->pass, pass1, PASSWDLEN);
	notice (s_ChanServ, u->nick, RPL_PASSWORD_SET, ci->pass);

	/* Log this. This might be an attempt to steal a nick.. */
	log ("CS:SET:PASS: %s %s!%s@%s", ci->name, u->nick, u->user, u->host);
	return;
    }

    /* Change Founder? */
    if (!stricmp (option, "FOUNDER"))
    {
	/* Founder only. */
	if (id > 1)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "the channel founder");
	    return;
	}

	/* Already founder? */
	if (!stricmp (ci->founder, ni->nick))
	{
	    notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "founder", ci->name);
	    return;
	}

	/* Go through the access list, find the current founder, and change it
	   to this person.
	 */
	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	    if (ca->level && !stricmp (ca->mask, ci->founder))
	    {
		free (ca->mask);
		ca->mask = sstrdup (ni->nick);
	    }

	/* The channel founder can only be set to the nickname of the
	   person doing SET FOUNDER. This is to stop people from registering
	   offensive channels and doing set founder to someone else.
	 */
	strscpy (ci->founder, ni->nick, NICKLEN);

	notice (s_ChanServ, u->nick, CS_YOURE_NOW, "founder", ci->name);

	/* If the person who just did SET FOUNDER is ALSO the SUCCESSOR, remove
	   them as successor quietly.. Since it's the same person we won't bother
	   with any notices or memos.
	 */
	if (ci->successor && !stricmp (ci->founder, ci->successor))
	{
	    NickInfo *sni = findnick (ci->successor);

	    /* Take this channel out of their successor channel list. */
	    for (i = 0; i < sni->successorcnt; i++)
	    {
		if (!stricmp (sni->successor[i], ci->name))
		{
		    free (sni->successor[i]);
		    sni->successorcnt--;
		    break;
		}
	    }

	    if (!sni->successorcnt)
		free (sni->successor);

	    free (ci->successor);
	    ci->successor = NULL;
	}

	/* Log this. */
	log ("CS:SET:FOUNDER: %s %s!%s@%s", ci->name, u->nick, u->user, u->host);
	return;
    }

    /* Change URL? */
    if (!stricmp (option, "URL"))
    {
	if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	{
	    if (ci->url)
		free (ci->url);
	    ci->url = NULL;
	    notice (s_ChanServ, u->nick, RPL_REMOVED, "Your channel's \2URL\2");
	    return;
	}
	else
	{
	    if (strstr (param, "://"))
	    {
		if (ci->url)
		    free (ci->url);
		ci->url = sstrdup (param);
		notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2URL\2", param);
	    }
	    else
		notice (s_ChanServ, u->nick, RPL_INVALID, "URL", "");

	    return;
	}
    }

    /* Change GREET? */
    if (!stricmp (option, "GREET"))
    {
	if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	{
	    if (ci->greet)
		free (ci->greet);
	    ci->greet = NULL;
	    notice (s_ChanServ, u->nick, RPL_REMOVED, "Your channel's \2GREET\2 message");
	    return;
	}
	else
	{
	    if (strlen (param) <= 50)
	    {
		if (ci->greet)
		    free (ci->greet);
		ci->greet = sstrdup (param);
		notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2GREET\2 message", param);
	    }
	    else
		notice (s_ChanServ, u->nick, RPL_INVALID, "GREET message", " It is too long.");

	    return;
	}
    }

    /* Change TOPIC? */
    if (!stricmp (option, "TOPIC"))
    {
	Channel *c = findchan (ci->name);

	if (!c)
	    return;

	if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	{
	    strscpy (ci->topic, "", TOPICLEN);
	    strscpy (ci->topicsetter, s_ChanServ, NICKLEN);
	    strscpy (c->topic, "", TOPICLEN);
	    strscpy (c->topicsetter, s_ChanServ, NICKLEN);
	    c->topictime = ci->topictime = time (NULL);
	    send_cmd (s_ChanServ, "%s %s %s %lu :", me.token ? ")" : "TOPIC", ci->name,
		s_ChanServ, time (NULL));

	    notice (s_ChanServ, u->nick, RPL_REMOVED, "Your channel's \2TOPIC\2");
	    return;
	}
	else
	{
	    /* This differs on some IRCds, but this is as low as it gets AFAIK. */
	    if (strlen (param) <= 307)
	    {
		strscpy (ci->topic, param, TOPICLEN);
		strscpy (ci->topicsetter, u->nick, NICKLEN);
		strscpy (c->topic, param, TOPICLEN);
		strscpy (c->topicsetter, u->nick, NICKLEN);
		c->topictime = ci->topictime = time (NULL);
		send_cmd (s_ChanServ, "%s %s %s %lu :%s", me.token ? ")" : "TOPIC", ci->name,
		    u->nick, time (NULL), param);

		notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2TOPIC\2", param);
	    }
	    else
		notice (s_ChanServ, u->nick, RPL_INVALID, "TOPIC", " It is too long.");

	    return;
	}
    }

    /* Change TOPICLOCK? */
    if (!stricmp (option, "TOPICLOCK"))
    {
	if (!stricmp (param, "NONE") || !stricmp (param, "OFF"))
	{
	    ci->topiclock = 0;

	    notice (s_ChanServ, u->nick, RPL_REMOVED, "Your channel's \2TOPICLOCK\2");
	    return;
	}

	if (!stricmp (param, "VOP"))
	{
	    ci->topiclock = VOP;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2TOPICLOCK\2", "VOP and above");
	    return;
	}

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    if (!stricmp (param, "HOP"))
	    {
		ci->topiclock = HOP;

		notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2TOPICLOCK\2", "HOP and above");
		return;
	    }
	}

	if (!stricmp (param, "AOP"))
	{
	    ci->topiclock = AOP;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2TOPICLOCK\2", "AOP and above");
	    return;
	}

	if (!stricmp (param, "SOP"))
	{
	    ci->topiclock = SOP;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2TOPICLOCK\2", "SOP and above");
	    return;
	}

	if (!stricmp (param, "FOUNDER"))
	{
	    ci->topiclock = FOUNDER;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2TOPICLOCK\2", "Founder");
	    return;
	}

	/* Dunno what they want.. */
	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SET Channel TOPICLOCK VOP|HOP|AOP|SOP|Founder");
	else
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SET Channel TOPICLOCK VOP|AOP|SOP|Founder");

	errmoreinfo (s_ChanServ, u->nick, "SET");
	return;
    }

    /* Change MEMOLEVEL? */
    if (!stricmp (option, "MEMOLEVEL"))
    {
	if (!stricmp (param, "VOP"))
	{
	    ci->memolevel = VOP;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2MEMOLEVEL\2", "VOP and above");
	    return;
	}

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    if (!stricmp (param, "HOP"))
	    {
		ci->memolevel = HOP;

		notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2MEMOLEVEL\2", "HOP and above");
		return;
	    }
	}

	if (!stricmp (param, "AOP"))
	{
	    ci->memolevel = AOP;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2MEMOLEVEL\2", "AOP and above");
	    return;
	}

	if (!stricmp (param, "SOP"))
	{
	    ci->memolevel = SOP;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2MEMOLEVEL\2", "SOP and above");
	    return;
	}

	if (!stricmp (param, "FOUNDER"))
	{
	    ci->memolevel = FOUNDER;

	    notice (s_ChanServ, u->nick, RPL_ADDED, "Your channel's \2MEMOLEVEL\2", "Founder");
	    return;
	}

	/* Dunno what they want.. */
	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SET Channel MEMOLEVEL VOP|HOP|AOP|SOP|Founder");
	else
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SET Channel MEMOLEVEL VOP|AOP|SOP|Founder");

	errmoreinfo (s_ChanServ, u->nick, "SET");
	return;
    }

    /* Toggle VERBOSE? */
    if (!stricmp (option, "VERBOSE"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ci->flags & CF_VERBOSE)
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Verbose", "on");
		return;
	    }

	    ci->flags |= CF_VERBOSE;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Verbose", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ci->flags & CF_VERBOSE))
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Verbose", "off");
		return;
	    }

	    ci->flags &= ~CF_VERBOSE;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Verbose", "off");
	    return;
	}

	else
	{
	    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
 		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Toggle LIMITED? */
    if (!stricmp (option, "LIMITED"))
    {
	Channel *c = findchan (ci->name);

	if (!stricmp (param, "ON"))
	{
	    if (ci->flags & CF_LIMITED)
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Limited", "on");
		return;
	    }

	    ci->flags |= CF_LIMITED;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Limited", "on");

	    /* Set the proper limit */
	    if (c)
		if (c->limit < (c->usercnt + 5))
		{
		    if (!(c->mode & CMODE_l))
			c->mode |= CMODE_l;

		    c->limit = c->usercnt + 5;
		    c->lastlimit = time (NULL);
		    send_cmode (s_ChanServ, ci->name, "+l", itoa (c->limit));
		}

	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ci->flags & CF_LIMITED))
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Limited", "off");
		return;
	    }

	    ci->flags &= ~CF_LIMITED;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Limited", "off");

	    if (c)
	    {
		c->limit = 0;
		send_cmode (s_ChanServ, ci->name, "-l");
	    }

	    return;
	}

	else
	{
	    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
 		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Toggle VOPALL? */
    if (!stricmp (option, "VOPALL"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ci->flags & CF_VOPALL)
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "VOPAll", "on");
		return;
	    }

	    ci->flags |= CF_VOPALL;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "VOPAll", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ci->flags & CF_VOPALL))
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "VOPAll", "off");
		return;
	    }

	    ci->flags &= ~CF_VOPALL;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "VOPAll", "off");
	    return;
	}

	else
	{
	    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
 		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Toggle SECURE? */
    if (!stricmp (option, "SECURE"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ci->flags & CF_SECURE)
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Secure", "on");
		return;
	    }

	    ci->flags |= CF_SECURE;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Secure", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ci->flags & CF_SECURE))
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Secure", "off");
		return;
	    }

	    ci->flags &= ~CF_SECURE;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Secure", "off");
	    return;
	}

	else
	{
	    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
 		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Toggle RESTRICTED? */
    if (!stricmp (option, "RESTRICTED"))
    {
	if (!stricmp (param, "ON"))
	{
	    if (ci->flags & CF_RESTRICTED)
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Restricted", "on");
		return;
	    }

	    ci->flags |= CF_RESTRICTED;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Restricted", "on");
	    return;
	}

	if (!stricmp (param, "OFF"))
	{
	    if (!(ci->flags & CF_RESTRICTED))
	    {
		notice (s_ChanServ, u->nick, CS_OPTION_ALREADY, ci->name, "Restricted", "off");
		return;
	    }

	    ci->flags &= ~CF_RESTRICTED;
	    notice (s_ChanServ, u->nick, CS_OPTION_NOW, ci->name, "Restricted", "off");
	    return;
	}

	else
	{
	    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, strupper (param),
		haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
 		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
	    return;
	}
    }

    /* Don't know what they want .. */
    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, strupper (option),
	haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
	haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
}

/* Count various lists on a channel */
static void do_count (User *u)
{
    ChanInfo *ci;
    ChanAccess *ca;
    AKick *ak;
    char *chan = strtok (NULL, " ");
    int i, ulev, vop = 0, hop = 0, aop = 0, sop = 0, akick = 0;

    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "COUNT Channel");
	errmoreinfo (s_ChanServ, u->nick, "COUNT");
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    ulev = get_access (u, ci);

    if (ulev < VOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "VOP");
	return;
    }

    /* Count VOPs */
    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level || !(ca->level == VOP))
	    continue;

	vop++;
    }

    /* Count HOPs */
    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	{
	    if (!ca->level || !(ca->level == HOP))
		continue;

	    hop++;
	}

    /* Count AOPs */
    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level || !(ca->level == AOP))
	    continue;

	aop++;
    }

    /* Count SOPs */
    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level || !(ca->level == SOP))
	    continue;

	sop++;
    }

    /* Count AKicks */
    for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
    {
	if (!ak->level)
	    continue;

	akick++;
    }

    notice (s_ChanServ, u->nick, CS_COUNT, vop, vop == 1 ? "" : "s",
	(ircdtype == UNREAL3 || ircdtype == UNREAL3_2) ? itoa (hop) : "",
	(ircdtype == UNREAL3 || ircdtype == UNREAL3_2) ? hop == 1 ? " HOP, " : " HOPs" : "",
	aop, aop == 1 ? "" : "s", sop, sop == 1 ? "" : "s",
	akick, akick == 1 ? "" : "s", ci->name);
}

/* Call do_xop for VOP */
static void do_vop (User *u)
{
    do_xop (u, VOP);
}

/* Call do_xop for HOP */
static void do_hop (User *u)
{ 
    do_xop (u, HOP);
}

/* Call do_xop for AOP */
static void do_aop (User *u)
{ 
    do_xop (u, AOP);
}

/* Call do_xop for SOP */
static void do_sop (User *u)
{ 
    do_xop (u, SOP);
}

/* Add, Remove, or List VOP-SOP entries. */
static void do_xop (User *u, int level)
{
    char *chan = strtok (NULL, " ");
    char *option = strtok (NULL, " ");
    char *mask = strtok (NULL, " ");
    char *xop, tmp[BUFSIZE];
    ChanInfo *ci;
    ChanAccess *ca;
    NickInfo *ni, *hni, *tni;
    int i, j = 0, ulev;

    /* Set the list we're talking about for future notices */
    xop = (level == VOP ? "VOP" : level == HOP ? "HOP" : level == AOP ? "AOP" :
	   level == SOP ? "SOP" : "XOP");

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, xop);
	return;
    }

    /* Check for everything we need */
    if (!chan || !option)
    {
	snprintf (tmp, sizeof (tmp), "%s Channel ADD|DEL|LIST [Mask|Nickname]", xop);

	notice (s_ChanServ, u->nick, RPL_SYNTAX, tmp);
	errmoreinfo (s_ChanServ, u->nick, xop);
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Give successors Founder-level access here. */
    if (ci->successor && !stricmp (ci->successor, ni->nick))
	ulev = FOUNDER;

    ulev = get_access (u, ci);

    /* ADD */
    if (!stricmp (option, "ADD"))
    {
	if (!mask)
	{
	    snprintf (tmp, sizeof (tmp), "%s Channel ADD Nick", xop);

	    notice (s_ChanServ, u->nick, RPL_SYNTAX, tmp);
	    errmoreinfo (s_ChanServ, u->nick, xop);
	    return;
	}

	/* Stop SOPs from adding SOPs. */
	if (level == SOP && ulev == SOP)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "Founder");
	    return;
	}

	if (ulev < SOP)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "SOP");
	    return;
	}

	tni = findnick (mask);

	if (!tni)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_REGISTERED, mask);
	    return;
	}

	/* Check for a host nick */
	if (tni->host)
	    hni = findnick (tni->host);
	else
	    hni = tni;

	if (!check_ns_auth (s_ChanServ, u->nick, hni))
	    return;

	/* Check for NoOp */
	if (hni->flags & NF_NOOP)
	{
	    notice (s_ChanServ, u->nick, CS_NO_OP, hni->nick);
	    return;
	}

	/* No duplicate access for the founder. */
	if (!stricmp (hni->nick, ci->founder))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_FOUNDER, ci->name, xop);
	    return;
	}

	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	{
	    if (!ca->level)
		continue;

	    if (!stricmp (ca->mask, hni->nick))
	    {
		/* Do they already have this level? */
		if (ca->level == level)
		{
		    notice (s_ChanServ, u->nick, CS_ACCESS_ALREADY, hni->nick, xop, ci->name);
		    return;
		}

		/* Check if they're trying to change their own access. In this case,
		   the check for greater or equal access below will work, but we want to
		   return a different message.
		 */
		if (!stricmp (ni->nick, ca->mask))
		{
		    notice (s_ChanServ, u->nick, CS_ACCESS_SELF);
		    return;
		}

		/* Do they have greater or equal access than this user? */
		if (ca->level >= ulev)
		{
		    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "Founder");
		    return;
		}

		notice (s_ChanServ, u->nick, CS_ACCESS_CHANGED, hni->nick, ci->name,
		    ca->level == VOP ? "VOP" : ca->level == HOP ? "HOP" :
		    ca->level == AOP ? "AOP" : ca->level == SOP ? "SOP" : "Unknown", xop);

		if (ci->flags & CF_VERBOSE)
		    opnotice (s_ChanServ, ci->name, CS_VERBOSE_CHANGED, u->nick, hni->nick,
			ca->level == VOP ? "VOP" : ca->level == HOP ? "HOP" :
			ca->level == AOP ? "AOP" : ca->level == SOP ? "SOP" :
			"Unknown", xop);

		ca->level = level;

		return;
	    }
	}

	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	    if (!ca->level)
		break;

	if (i == ci->accesscnt)
	{
	    ci->accesscnt++;
	    ci->access = srealloc (ci->access, sizeof (ChanAccess) * ci->accesscnt);
	    ca = &ci->access[ci->accesscnt - 1];
	}

	ca->mask = sstrdup (hni->nick);
	ca->level = level;

	notice (s_ChanServ, u->nick, CS_ACCESS_ADDED, hni->nick, ci->name, xop);

	if (ci->flags & CF_VERBOSE)
	    opnotice (s_ChanServ, ci->name, CS_VERBOSE_ADDED, u->nick, hni->nick, xop);

	return;
    }

    /* DEL */
    if (!stricmp (option, "DEL"))
    {
	int success = 0;

	if (!mask)
	{
	    snprintf (tmp, sizeof (tmp), "%s Channel DEL Nick", xop);

	    notice (s_ChanServ, u->nick, RPL_SYNTAX, tmp);
	    errmoreinfo (s_ChanServ, u->nick, xop);
	    return;
	}

	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	{
	    if (!ca->level || !(ca->level == level))
		continue;

	    j++;

	    if (!stricmp (mask, ca->mask))
	    {
		success = 1;
		break;
	    }

	    if (atoi (mask) == j)
	    {
		success = 1;
		break;
	    }
	}

	if (!success)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_NOT_FOUND, mask, ci->name, xop);
	    return;
	}
	else
	{
	    /* We found the entry they want. Now. Is the entry they want themselves?
	       If so, we'll let them remove it. If not, make sure they at least have
	       SOP access, or we stop here.
	     */
	    if (!stricmp (ca->mask, u->lastnick))
	    {
		notice (s_ChanServ, u->nick, CS_REMOVED_SELF, xop, ci->name);

		if (ci->flags & CF_VERBOSE)
		    opnotice (s_ChanServ, ci->name, CS_VERBOSE_REMOVED_SELF, u->nick, xop);

		if (ca->mask)
		    free (ca->mask);

		ca->level = 0;

		return;
	    }

	    /* They're not trying to remove themselves. See if they have access. */
	    if (ulev < SOP)
	    {
		notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "SOP");
		return;
	    }

	    /* See if they're an SOP trying to remove an SOP. */
	    if (ulev == SOP && ca->level == SOP)
	    {
		notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "Founder");
		return;
	    }

	    /* Ok, remove it. */
	    notice (s_ChanServ, u->nick, CS_ACCESS_REMOVED, ca->mask, ci->name, xop);

	    if (ci->flags & CF_VERBOSE)
		opnotice (s_ChanServ, ci->name, CS_VERBOSE_REMOVED, u->nick, ca->mask, xop);

	    if (ca->mask)
		free (ca->mask);

	    ca->level = 0;
	}

	return;
    }

    /* LIST */
    if (!stricmp (option, "LIST"))
    {
	if (ulev < VOP)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "VOP");
	    return;
	}

	if (!ci->accesscnt)
	{
	    notice (s_ChanServ, u->nick, RPL_LIST_EMPTY, "The ", ci->name, xop);
	    return;
	}

	notice (s_ChanServ, u->nick, CS_LIST_START, xop, ci->name);
	notice (s_ChanServ, u->nick, CS_ACCESS_LIST_HEADER);

	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	{
	    if (!ca->level || !(ca->level == level))
		continue;

	    j++;

	    tni = findnick (ca->mask);

	    if (!(tni->flags & NF_PRIVATE) || is_csop (u))
		notice (s_ChanServ, u->nick, CS_ACCESS_LIST, j, ca->mask,
		    tni->usermask ? "(" : "", tni->usermask ? tni->usermask : "",
		    tni->usermask ? ")" : "");
	    else
		notice (s_ChanServ, u->nick, CS_ACCESS_LIST, j, ca->mask,
		    "(", "\2Private\2", ")");
	}

	notice (s_ChanServ, u->nick, CS_LIST_END, j, j == 1 ? "" : "es");

	return;
    }

    notice (s_ChanServ, u->nick, RPL_UNKNOWN_OPTION, option,
        haveserv_on == TRUE ? "" : "MSG ", s_ChanServ,
        haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
        haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");
}

/* Send a channel memo. */
static void do_send (User *u)
{
    ChanInfo *ci;
    ChanAccess *ca;
    NickInfo *ni, *tni;
    char *chan = strtok (NULL, " ");
    char *param = strtok (NULL, " ");
    char *text = strtok (NULL, "");
    int i, ulev = 0, j = 0, level = 0;
    time_t now = time (NULL);

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "SEND");
	return;
    }

    /* Check for everything we need */
    if (!chan || !param || !text)
    {
	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SEND Channel ALL|VOP|HOP|AOP|SOP Text");
	else
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SEND Channel ALL|VOP|AOP|SOP Text");

	errmoreinfo (s_ChanServ, u->nick, "SEND");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* SOP or better. */
    if (ulev < ci->memolevel)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED,
	    ci->memolevel == VOP ? "VOP" :
	    ci->memolevel == HOP ? "HOP" :
	    ci->memolevel == AOP ? "AOP" :
	    ci->memolevel == SOP ? "SOP" : "Founder");
	return;
    }

    /* See who they want to send it to */
    level = !stricmp (param, "SOP") ? SOP :
	    !stricmp (param, "AOP") ? AOP :
	    !stricmp (param, "HOP") ? HOP :
	    !stricmp (param, "VOP") ? VOP : 0;

    if ((!level && stricmp (param, "ALL")) || (level == HOP &&
	!(ircdtype == UNREAL3 || ircdtype == UNREAL3_2)))
    {
	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SEND Channel ALL|VOP|HOP|AOP|SOP Text");
	else
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "SEND Channel ALL|VOP|AOP|SOP Text");

	errmoreinfo (s_ChanServ, u->nick, "SEND");
	return;
    }

    /* No memobombs. Since these memos bypass things like memosfromuser, Channel
       memos have memo_delay * 5 waiting period.
     */
    if ((memo_delay && (now - u->lastmemo < memo_delay * 5)) && !is_oper (u))
    {
	notice (s_ChanServ, u->nick, MS_WAIT_SEND,
	    duration (u->lastmemo - now + (memo_delay * 5), 2));
	return;
    }

    /* Do it! */
    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level)
	    continue;

	if (stricmp (param, "ALL") && !(ca->level == level))
	    continue;

	if ((tni = findnick (ca->mask)))
	{
	    char mbuf[BUFSIZE];

	    snprintf (mbuf, sizeof (mbuf), CS_CHANNEL_MEMO, ci->name, text);
	    send_memo (tni, ni->nick, mbuf);

	    j++;
	}
    }

    notice (s_ChanServ, u->nick, CS_MEMO_SENT, j, ci->name);

    u->lastmemo = now;
}

/* Op someone on the given channel */
static void do_op (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    User *uo = NULL;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0, uolev = 0;
    char *av[3];

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "OP");
	return;
    }

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "OP Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "OP");
	return;
    }

    if (!findchan (chan))
    {
	notice (s_ChanServ, u->nick, CS_NOT_IN_USE, chan);
	return;
    }

    if (!is_on_chan (u->nick, chan))
    {
	notice (s_ChanServ, u->nick, CS_MUST_BE_IN, chan, "OP");
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* Get the other users access */
    if (nick)
    {
	uo = finduser (nick);

	if (!uo || !is_on_chan (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, nick, ci->name);
	    return;
	}

	if (is_opped (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, nick,
		" opped", ci->name);
	    return;
	}

	uolev = get_access (uo, ci);
    }

    if (!nick && is_opped (u->nick, ci->name))
    {
	notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "opped", ci->name);
	return;
    }

    /* Make sure they have enough access to op people. (AOP) */
    if (ulev < AOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    /* If Secure is on, make sure the person they want to op has
       AOP or higher.
     */
    if (uo && (ci->flags & CF_SECURE) && uolev < 3)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED_OTHER, uo->nick,
	    "AOP");
	return;
    }

    /* Everything checks out, do the opping. */
    if (nick)
    {
	av[0] = ci->name;
	av[1] = "+o";
	av[2] = uo->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "+o", uo->nick);

	/* Only notice them if they didnt op themselves */
	if (stricmp (uo->nick, u->nick))
	{
	    notice (s_ChanServ, u->nick, CS_HAS_BEEN, uo->nick, "opped", "on ",
		ci->name);
	    notice (s_ChanServ, uo->nick, CS_YOU_WERE, "opped", "on",
		ci->name, u->nick);
	}
    }
    else
    {
	av[0] = ci->name;
	av[1] = "+o";
	av[2] = u->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "+o", u->nick);
    }
}

/* DeOp someone on the given channel */
static void do_deop (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    User *uo = NULL;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0, uolev = 0;
    char *av[3];

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "DEOP");
	return;
    }

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "DEOP Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "DEOP");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* Get the other users access */
    if (nick)
    {
	uo = finduser (nick);

	if (!uo || !is_on_chan (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, nick, ci->name);
	    return;
	}

	if (!is_opped (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, nick,
		" deopped", ci->name);
	    return;
	}

	uolev = get_access (uo, ci);
    }

    if (!nick && !is_opped (u->nick, ci->name))
    {
	notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "deopped", ci->name);
	return;
    }

    /* Make sure they have enough access to deop people. (AOP) */
    if (ulev < AOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    /* Everything checks out, do the deopping. */
    if (nick)
    {
	av[0] = ci->name;
	av[1] = "-o";
	av[2] = uo->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "-o", uo->nick);

	/* Only notice them if they didnt deop themselves */
	if (stricmp (uo->nick, u->nick))
	{
	    notice (s_ChanServ, u->nick, CS_HAS_BEEN, uo->nick, "deopped", "on ",
		ci->name);
	    notice (s_ChanServ, uo->nick, CS_YOU_WERE, "deopped", "on",
		ci->name, u->nick);
	}
    }
    else
    {
	av[0] = ci->name;
	av[1] = "-o";
	av[2] = u->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "-o", u->nick);
    }
}

/* HalfOp someone on the given channel */
static void do_halfop (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    User *uo = NULL;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0, uolev = 0;
    char *av[3];

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "HALFOP");
	return;
    }

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "HALFOP Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "HALFOP");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* Get the other users access */
    if (nick)
    {
	uo = finduser (nick);

	if (!uo || !is_on_chan (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, nick, ci->name);
	    return;
	}

	if (is_halfopped (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, nick,
		" halfopped", ci->name);
	    return;
	}

	uolev = get_access (uo, ci);
    }

    if (!nick && is_halfopped (u->nick, ci->name))
    {
	notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "halfopped", ci->name);
	return;
    }

    /* Make sure they have enough access to halfop people. (AOP) */
    if (ulev < AOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    /* If Secure is on, make sure the person they want to halfop has
       HOP or higher.
     */
    if (uo && (ci->flags & CF_SECURE) && uolev < 2)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED_OTHER, uo->nick,
	    "HOP");
	return;
    }

    /* Everything checks out, do the halfopping. */
    if (nick)
    {
	av[0] = ci->name;
	av[1] = "+h";
	av[2] = uo->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "+h", uo->nick);

	/* Only notice them if they didnt halfop themselves */
	if (stricmp (uo->nick, u->nick))
	{
	    notice (s_ChanServ, u->nick, CS_HAS_BEEN, uo->nick, "halfopped", "on ",
		ci->name);
	    notice (s_ChanServ, uo->nick, CS_YOU_WERE, "halfopped", "on",
		ci->name, u->nick);
	}
    }
    else
    {
	av[0] = ci->name;
	av[1] = "+h";
	av[2] = u->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "+h", u->nick);
    }
}

/* DeHalfOp someone on the given channel */
static void do_dehalfop (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    User *uo = NULL;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0, uolev = 0;
    char *av[3];

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "DEHALFOP");
	return;
    }

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "DEHALFOP Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "DEHALFOP");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* Get the other users access */
    if (nick)
    {
	uo = finduser (nick);

	if (!uo || !is_on_chan (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, nick, ci->name);
	    return;
	}

	if (!is_halfopped (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, nick,
		" dehalfopped", ci->name);
	    return;
	}

	uolev = get_access (uo, ci);
    }

    if (!nick && !is_halfopped (u->nick, ci->name))
    {
	notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "dehalfopped", ci->name);
	return;
    }

    /* Make sure they have enough access to dehalfop people. (AOP)
       If they want to dehalfop themselves, we'll always let them,
       regardless of access.
     */
    if ((ulev < AOP) && (nick || (nick && stricmp (nick, u->nick))))
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    /* Everything checks out, do the dehalfopping. */
    if (nick)
    {
	av[0] = ci->name;
	av[1] = "-h";
	av[2] = uo->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "-h", uo->nick);

	/* Only notice them if they didnt dehalfop themselves */
	if (stricmp (uo->nick, u->nick))
	{
	    notice (s_ChanServ, u->nick, CS_HAS_BEEN, uo->nick, "dehalfopped", "on ",
		ci->name);
	    notice (s_ChanServ, uo->nick, CS_YOU_WERE, "dehalfopped", "on",
		ci->name, u->nick);
	}
    }
    else
    {
	av[0] = ci->name;
	av[1] = "-h";
	av[2] = u->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "-h", u->nick);
    }
}

/* Voice someone on the given channel */
static void do_voice (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    User *uo = NULL;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0, uolev = 0;
    char *av[3];

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "VOICE");
	return;
    }

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "VOICE Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "VOICE");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* Get the other users access */
    if (nick)
    {
	uo = finduser (nick);

	if (!uo || !is_on_chan (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, nick, ci->name);
	    return;
	}

	if (is_voiced (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, nick,
		" voiced", ci->name);
	    return;
	}

	uolev = get_access (uo, ci);
    }

    if (!nick && is_voiced (u->nick, ci->name))
    {
	notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "voiced", ci->name);
	return;
    }

    /* Make sure they have enough access to voice people. (AOP) */
    if (ulev < AOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    /* If Secure is on, make sure the person they want to voice has
       VOP or higher.
     */
    if (uo && (ci->flags & CF_SECURE) && uolev < 1)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED_OTHER, uo->nick,
	    "VOP");
	return;
    }

    /* Everything checks out, do the halfopping. */
    if (nick)
    {
	av[0] = ci->name;
	av[1] = "+v";
	av[2] = uo->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "+v", uo->nick);

	/* Only notice them if they didnt voice themselves */
	if (stricmp (uo->nick, u->nick))
	{
	    notice (s_ChanServ, u->nick, CS_HAS_BEEN, uo->nick, "voiced", "on ",
		ci->name);
	    notice (s_ChanServ, uo->nick, CS_YOU_WERE, "voiced", "on",
		ci->name, u->nick);
	}
    }
    else
    {
	av[0] = ci->name;
	av[1] = "+v";
	av[2] = u->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "+v", u->nick);
    }
}

/* DeVoice someone on the given channel */
static void do_devoice (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    User *uo = NULL;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0, uolev = 0;
    char *av[3];

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "DEVOICE");
	return;
    }

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "DEVOICE Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "DEVOICE");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* Get the other users access */
    if (nick)
    {
	uo = finduser (nick);

	if (!uo || !is_on_chan (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, nick, ci->name);
	    return;
	}

	if (!is_voiced (nick, ci->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, nick,
		" devoiced", ci->name);
	    return;
	}

	uolev = get_access (uo, ci);
    }

    if (!nick && !is_voiced (u->nick, ci->name))
    {
	notice (s_ChanServ, u->nick, CS_YOURE_ALREADY, "devoiced", ci->name);
	return;
    }

    /* Make sure they have enough access to devoice people. (AOP)
       If they want to devoice themselves, we'll always let them,
       regardless of access.
     */
    if ((ulev < AOP) && (nick || (nick && stricmp (nick, u->nick))))
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    /* Everything checks out, do the devoiceing. */
    if (nick)
    {
	av[0] = ci->name;
	av[1] = "-v";
	av[2] = uo->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "-v", uo->nick);

	/* Only notice them if they didnt devoice themselves */
	if (stricmp (uo->nick, u->nick))
	{
	    notice (s_ChanServ, u->nick, CS_HAS_BEEN, uo->nick, "devoiced", "on ",
		ci->name);
	    notice (s_ChanServ, uo->nick, CS_YOU_WERE, "devoiced", "on",
		ci->name, u->nick);
	}
    }
    else
    {
	av[0] = ci->name;
	av[1] = "-v";
	av[2] = u->nick;

	do_cmode (s_ChanServ, 3, av);

	send_cmode (s_ChanServ, ci->name, "-v", u->nick);
    }
}

/* List the bans on the channel */
static void do_bans (User *u)
{
    ChanInfo *ci;
    Channel *c;
    char *chan = strtok (NULL, " ");
    char *mask = strtok (NULL, " ");
    int i, j = 0, k = 0, ulev = 0;
    char **bans;

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "BANS Channel [Mask]");
	errmoreinfo (s_ChanServ, u->nick, "BANS");
	return;
    }

    if (!(c = findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_IN_USE, chan);
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;
     
    /* Get their access */
    ulev = get_access (u, ci);

    /* VOP or better. */
    if (ulev < VOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "VOP");
	return;
    }

    /* Are there any bans? */
    if (!c->bancnt)
    {
	notice (s_ChanServ, u->nick, CS_NO_BANS, c->name);
	return;
    }

    notice (s_ChanServ, u->nick, CS_LIST_START, "Bans", ci->name);
    notice (s_ChanServ, u->nick, CS_BANS_LIST_HEADER);

    /* List the bans */
    for (bans = c->bans, i = 0; i < c->bancnt; ++bans, ++i)
    {
	/* This goes up either way, so the number of the ban
	   isn't different if they give a mask. This lets people
	   do UNBAN #.
	 */
	j++;

	if (mask && !match_wild_nocase (mask, *bans))
	    continue;

	k++;

	notice (s_ChanServ, u->nick, CS_BANS_LIST, j, *bans);
    }

    notice (s_ChanServ, u->nick, CS_LIST_END, k, k == 1 ? "" : "s");
}

/* Unban the specified mask */
static void do_unban (User *u)
{
    ChanInfo *ci;
    Channel *c;
    User *utmp;
    char *chan = strtok (NULL, " ");
    char *mask = strtok (NULL, " ");
    int i, ulev = 0;
    char *av[3], **bans, usermask[128];

    /* Check for everything we need */
    if (!chan || !mask)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "UNBAN Channel Mask");
	errmoreinfo (s_ChanServ, u->nick, "UNBAN");
	return;
    }

    if (!(c = findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_IN_USE, chan);
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* VOP or better. */
    if (ulev < VOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "VOP");
	return;
    }

    /* Are there any bans? */
    if (!c->bancnt)
    {
	notice (s_ChanServ, u->nick, CS_NO_BANS, c->name);
	return;
    }

    /* UNBAN ME */
    if (!stricmp (mask, "ME"))
	snprintf (usermask, sizeof (usermask), "%s!%s@%s", u->nick, u->user, u->host);

    /* UNBAN User */
    if ((utmp = finduser (mask)))
	snprintf (usermask, sizeof (usermask), "%s!%s@%s", utmp->nick, utmp->user, utmp->host);

    if (!stricmp (mask, "ALL"))
    {
	av[0] = c->name;
	av[1] = "-b";

	/* Remove all bans here. */
	for (i = 0; i < c->bancnt; i++)
	{
	    if (c->bans[i])
	    {
		av[2] = c->bans[i];
		send_cmode (s_ChanServ, av[0], av[1], av[2]);
	    }
	}

	/* Now free the bans */
	for (i = 0; i < c->bancnt; i++)
	    if (c->bans[i])
	    {
		av[2] = c->bans[i];

		do_cmode (s_ChanServ, 3, av);

		free (c->bans[i]);
	    }

	free (c->bans);
	c->bancnt = 0;

	notice (s_ChanServ, u->nick, CS_ALL_UNBANNED, c->name);

	/* All done, now. */
	return;
    }

    /* Find the ban */
    if (strspn (mask, "1234567890") == strlen (mask) &&
	(i = atoi (mask)) > 0 && i <= c->bancnt)
    {
	--i;
	bans = &c->bans[i];
    }
    else
    {
	for (bans = c->bans, i = 0; i < c->bancnt; bans++, i++)
	{
	    if (usermask && match_wild_nocase (*bans, usermask))
		break;

	    if (!stricmp (*bans, mask))
		break;
	}

	if (i == c->bancnt)
	{
	    notice (s_ChanServ, u->nick, CS_NO_BANS, c->name);
	    return;
	}
    }

    notice (s_ChanServ, u->nick, CS_HAS_BEEN, *bans, "unbanned", c->name, "");

    av[0] = c->name;
    av[1] = "-b";
    av[2] = *bans;

    send_cmode (s_ChanServ, av[0], av[1], av[2]);
    do_cmode (s_ChanServ, 3, av);
}

/* Kick someone from the given channel. */
static void do_cs_kick (User *u)
{
    ChanInfo *ci;
    Channel *c;
    User *utmp;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    char *av[3], reason[512];
    int ulev = 0;

    /* Check for everything we need */
    if (!chan || !nick)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "KICK Channel Nickname");
	errmoreinfo (s_ChanServ, u->nick, "KICK");
	return;
    }

    if (!(c = findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_IN_USE, chan);
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* AOP or better. */
    if (ulev < AOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "AOP");
	return;
    }

    if ((utmp = finduser (nick)))
    {
	if (!(is_on_chan (utmp->nick, c->name)))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_ON, utmp->nick, ci->name);
	    return;
	}

	av[0] = c->name;
	av[1] = utmp->nick;

	snprintf (reason, sizeof (reason), CS_KICK, "KICK", u->nick);

	av[2] = reason;

	send_cmd (s_ChanServ, "%s %s %s :%s", me.token ? "H" : "KICK", av[0], av[1], av[2]);
	do_kick (s_ChanServ, 3, av);

	if (stricmp (nick, u->nick))
	    notice (s_ChanServ, utmp->nick, CS_YOU_WERE, "kicked", "from", ci->name,
		u->nick);

	notice (s_ChanServ, u->nick, CS_HAS_BEEN, utmp->nick, "kicked", "from ",
	    ci->name);
    }
    else
	notice (s_ChanServ, u->nick, RPL_NOT_ONLINE, nick);
}

/* Clear the selected item from the channel. */
static void do_clear (User *u)
{
    ChanInfo *ci;
    Channel *c;
    char *chan = strtok (NULL, " ");
    char *item = strtok (NULL, " ");
    char *av[3], tmpstr[512];
    struct c_userlist *cu, *next;
    int i, tmp = 0, ulev = 0;

    /* Check for everything we need */
    if (!chan || !item)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "CLEAR Channel BANS|MODES|USERS");
	errmoreinfo (s_ChanServ, u->nick, "CLEAR");
	return;
    }

    if (!(c = findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_IN_USE, chan);
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* SOP or better. */
    if (ulev < SOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "SOP");
	return;
    }

    /* Clear Bans? */
    if (!stricmp (item, "BANS"))
    {
	char **bans;

	/* Are there any bans? */
	if (!c->bancnt)
	{
	    notice (s_ChanServ, u->nick, CS_NO_BANS, ci->name);
	    return;
	}

	tmp = c->bancnt;
	bans = smalloc (sizeof (char *) *tmp);
	memcpy (bans, c->bans, sizeof (char *) *tmp);

	av[0] = c->name;
	av[1] = "-b";

	for (i = 0; i < tmp; i++)
	{
	    av[2] = bans[i];

	    send_cmode (s_ChanServ, av[0], av[1], av[2]);
	    do_cmode (s_ChanServ, 3, av);
	}

	free(bans);

	if (ci->flags & CF_VERBOSE)
	    opnotice (s_ChanServ, ci->name, CS_VERBOSE_CLEARED, u->nick, "bans");

	notice (s_ChanServ, u->nick, CS_CLEARED, "bans", c->name);
	return;
    }

    /* Clear Users? */
    if (!stricmp (item, "USERS"))
    {
	av[0] = c->name;

	snprintf (tmpstr, sizeof (tmpstr), CS_KICK, "CLEAR", u->nick);

	av[2] = tmpstr;

	if (ci->flags & CF_VERBOSE)
	    opnotice (s_ChanServ, ci->name, CS_VERBOSE_CLEARED, u->nick, "users");

	for (cu = c->users; cu; cu = next)
	{
	    next = cu->next;
	    av[1] = cu->user->nick;

	    send_cmd (s_ChanServ, "%s %s %s :%s", me.token ? "H" : "KICK", av[0], av[1], av[2]);
	    do_kick (s_ChanServ, 3, av);
	}

	notice (s_ChanServ, u->nick, CS_CLEARED, "users", c->name);
	return;
    }

    /* Clear Modes? */
    if (!stricmp (item, "MODES"))
    {
        tmp = c->mode;
        tmp &= ~ci->mlock_on;

	if (!tmp)
	{
	    notice (s_ChanServ, u->nick, CS_NO_MODES, ci->name);
	    return;
	}

	if (ci->flags & CF_VERBOSE)
	    opnotice (s_ChanServ, ci->name, CS_VERBOSE_CLEARED, u->nick, "modes");

	snprintf (tmpstr, sizeof (tmpstr), "-%s", flags_to_string (tmp));

	c->mode = tmp;
	send_cmode (s_ChanServ, c->name, tmpstr);

	notice (s_ChanServ, u->nick, CS_CLEARED, "modes", c->name);
	return;
    }

    /* Don't know what they want? */
    notice (s_ChanServ, u->nick, RPL_SYNTAX, "CLEAR Channel BANS|MODES|USERS");
    errmoreinfo (s_ChanServ, u->nick, "CLEAR");
}

/* Send an invite for someone to the given channel. */
static void do_invite (User *u)
{
    ChanInfo *ci;
    Channel *c;
    User *utmp;
    char *chan = strtok (NULL, " ");
    char *nick = strtok (NULL, " ");
    int ulev = 0;

    /* Check for everything we need */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "INVITE Channel [Nickname]");
	errmoreinfo (s_ChanServ, u->nick, "INVITE");
	return;
    }

    if (!(c = findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_IN_USE, chan);
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Get their access */
    ulev = get_access (u, ci);

    /* VOP or better. */
    if (ulev < VOP)
    {
	notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "VOP");
	return;
    }

    /* If they specified a nickname, invite that nickname. */
    if (nick && (utmp = finduser (nick)))
    {
	if (is_on_chan (utmp->nick, c->name))
	{
	    notice (s_ChanServ, u->nick, CS_IS_ALREADY, utmp->nick, "", ci->name); 
	    return;
	}

	send_cmd (s_ChanServ, "%s %s %s", me.token ? "*" : "INVITE", utmp->nick, ci->name);

	if (stricmp (nick, u->nick))
	    notice (s_ChanServ, utmp->nick, CS_YOU_WERE, "invited", "to", ci->name,
		u->nick);

	notice (s_ChanServ, u->nick, CS_HAS_BEEN, utmp->nick, "invited", "to ",
	    ci->name);
    }
    else
    {
	send_cmd (s_ChanServ, "%s %s %s", me.token ? "*" : "INVITE", u->nick, ci->name);

	notice (s_ChanServ, u->nick, CS_YOU_WERE, "invited", "to", ci->name,
	    u->nick);
    }
}

/* Add, Remove, or List AKick entries. */
static void do_akick (User *u)
{
    char *chan = strtok (NULL, " ");
    char *option = strtok (NULL, " ");
    char *mask = strtok (NULL, " ");
    char *reason = strtok (NULL, "");
    char *akmask;
    ChanInfo *ci;
    Channel *c;
    AKick *ak;
    NickInfo *ni;
    User *aku;
    int i, j = 0, ulev, nonwild = 0;

    /* Get their nickname */
    ni = get_nick (u);

    if (!ni)
    {
	notice (s_ChanServ, u->nick, RPL_NEED_IDENT, "SOP");
	return;
    }

    /* Check for everything we need */
    if (!chan || !option)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "AKICK Channel ADD|DEL|LIST [Mask] [Reason]");
	errmoreinfo (s_ChanServ, u->nick, "AKICK");
	return;
    }

    /* Find the channel and make sure it's usable */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_CHAN_FROZEN, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    ulev = get_access (u, ci);

    /* ADD */
    if (!stricmp (option, "ADD"))
    {
	if (!mask)
	{
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "AKICK Channel ADD Mask [Reason]");
	    errmoreinfo (s_ChanServ, u->nick, "AKICK");
	    return;
	}

	if (ulev < SOP)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "SOP");
	    return;
	}

	/* See if the channel has too many AKicks. */
	if (ci->akickcnt >= akickmax)
	{
	    notice (s_ChanServ, u->nick, CS_AKICK_FULL, ci->name, akickmax);
	    return;
	}

	/* Try to find an online user */
	aku = finduser (mask);

	if (aku)
	    akmask = create_mask (aku);
	else
	{
	    /* Not an online user either.. Check if it's already a hostmask */
	    if (!strchr (mask, '@') || !strchr (mask, '.'))
	    {
		notice (s_ChanServ, u->nick, RPL_INVALID, "hostmask",
		    " Hostmasks must be in the format of user@host.tld.");
		return;
	    }
	    else
		akmask = mask;

	    for (i = 0; i < strlen (mask); i++)
		if (!(mask[i] == '*' || mask[i] == '?' || mask[i] == '@' || mask[i] == '.'))
		    nonwild++;

	    if (nonwild < nonwildreq)
	    {
		notice (s_ChanServ, u->nick, RPL_TOOMANYWILD, nonwildreq);
		return;
	    }
	}

	/* Check if its already present. */
	for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
	{
	    if (!ak->level)
		continue;

	    if (!stricmp (ak->mask, akmask))
	    {
		notice (s_ChanServ, u->nick, CS_AKICK_ALREADY, akmask, ci->name);
		return;
	    }
	}

	for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
	    if (!ak->level)
		break;

	if (i == ci->akickcnt)
	{
	    ci->akickcnt++;
	    ci->akick = srealloc (ci->akick, sizeof (AKick) * ci->akickcnt);
	    ak = &ci->akick[ci->akickcnt - 1];
	}

	ak->mask = sstrdup (akmask);
	ak->level = 1;

	if (reason)
	    ak->reason = sstrdup (reason);
	else
	    ak->reason = NULL;

	ak->time = time (NULL);

	strscpy (ak->setter, u->nick, NICKLEN);

	notice (s_ChanServ, u->nick, CS_AKICK_ADDED, akmask, ci->name);

	if (ci->flags & CF_VERBOSE)
	    opnotice (s_ChanServ, ci->name, CS_VERBOSE_AKICKED, u->nick, akmask);

	/* Kick anyone in the channel who matches the AKick */
	if ((c = findchan (ci->name)))
	{
	    struct c_userlist *cu, *next;
	    char *av[3], *tmp;
	    int banned = 0;

	    av[0] = c->name;

	    if (reason)
		av[2] = (char *)reason;
	    else
		av[2] = (char *)def_akick_reason;

	    for (cu = c->users; cu; cu = next)
	    {
		next = cu->next;
		av[1] = cu->user->nick;

		tmp = create_mask (cu->user);

		if (match_wild_nocase (tmp, ak->mask))
		{
		    if (!banned)
		    {
			send_cmd (s_ChanServ, "%s %s +b %s", me.token ? "G" : "MODE", c->name, ak->mask);
			banned = 1;
		    }

		    send_cmd (s_ChanServ, "%s %s %s :%s", me.token ? "H" : "KICK", av[0], av[1], av[2]);
		    do_kick (s_ChanServ, 3, av);

		    av[1] = "+b";
		    av[2] = tmp;

		    do_cmode (s_ChanServ, 3, av);
		}
	    }
	}

	return;
    }

    /* DEL */
    if (!stricmp (option, "DEL"))
    {
	int success = 0;

	if (!mask)
	{
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "AKICK Channel DEL Nick|Mask|Num");
	    errmoreinfo (s_ChanServ, u->nick, "AKICK");
	    return;
	}

	if (ulev < SOP)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "SOP");
	    return;
	}

	for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
	{
	    if (!ak->level)
		continue;

	    j++;

	    if (!stricmp (mask, ak->mask))
	    {
		success = 1;
		break;
	    }

	    if (atoi (mask) == j)
	    {
		success = 1;
		break;
	    }
	}

	if (!success)
	{
	    notice (s_ChanServ, u->nick, CS_AKICK_NOT_FOUND, mask, ci->name);
	    return;
	}
	else
	{
	    notice (s_ChanServ, u->nick, CS_AKICK_REMOVED, ak->mask, ci->name);

	    if (ci->flags & CF_VERBOSE)
		opnotice (s_ChanServ, ci->name, CS_VERBOSE_UNAKICK, u->nick, ak->mask);

	    if (ak->mask)
		free (ak->mask);

	    if (ak->reason)
		free (ak->reason);

	    ak->level = 0;
	}

	return;
    }

    /* LIST */
    if (!stricmp (option, "LIST"))
    {
	int fulllist = 0;

	if (ulev < VOP)
	{
	    notice (s_ChanServ, u->nick, CS_ACCESS_DENIED, "VOP");
	    return;
	}

	if (mask && !stricmp (mask, "FULL"))
	    fulllist = 1;

	if (!ci->akickcnt)
	{
	    notice (s_ChanServ, u->nick, RPL_LIST_EMPTY, "The ", ci->name, "AKick");
	    return;
	}

	notice (s_ChanServ, u->nick, CS_AKICK_LIST_START, ci->name,
	    fulllist ? " - With reasons" : "",
	    (fulllist && reason) ? " - Matching " :
	    (!fulllist && mask) ? " - Matching " : "",
	    (fulllist && reason) ? reason : (!fulllist && mask) ?
            mask : "");

	notice (s_ChanServ, u->nick, CS_AKICK_LIST_HEADER);

	for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
	{
	    if (!ak->level)
		continue;

	    if (fulllist && reason)
		if (!match_wild_nocase (reason, ak->mask) &&
		    !match_wild_nocase (reason, ak->setter) &&
		    (!ak->reason || (ak->reason &&
		    !match_wild_nocase (reason, ak->reason))))
		    continue;

	    if (!fulllist && mask)
		if (!match_wild_nocase (mask, ak->mask) &&
		    !match_wild_nocase (mask, ak->setter) &&
		    (!ak->reason || (ak->reason &&
		    !match_wild_nocase (mask, ak->reason))))
		    continue;

	    /* To avoid flooding them. They can refine their search. */
	    if ((fulllist && j >= 25) || (!fulllist && j >= 50))
		break;

	    j++;

	    notice (s_ChanServ, u->nick, CS_AKICK_LIST, j, ak->setter, ak->mask);

	    if (fulllist)
	    {
		if (ak->reason)
		    notice (s_ChanServ, u->nick, CS_AKICK_LIST_REASON, ak->reason);

		notice (s_ChanServ, u->nick, CS_AKICK_LIST_SET, time_ago (ak->time));
	    }
	}

	notice (s_ChanServ, u->nick, CS_AKICK_LIST_END, j, j == 1 ? "" : "es");
    }
}

/* Show a users status on the given channel. If no channel is given, show
   them what channels they have identified for.
 */
static void do_status (User *u)
{
    ChanInfo *ci;
    ChanAccess *ca;
    NickInfo *ni, *hni;
    AKick *ak;
    char *chan = strtok (NULL, " "), *nick = strtok (NULL, " "), *xop;
    int i, ulev = 0, akicked = 0, csop = is_csop (u);

    if (!chan)
    {
	if (csop)
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "STATUS #Channel [Nickname]");
	else
	    notice (s_ChanServ, u->nick, RPL_SYNTAX, "STATUS #Channel");

	return;
    }

    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (nick && csop)
	ni = findnick (nick);
    else
	ni = findnick (u->nick);

    if (!ni)
    {
	if (nick && csop)
	    notice (s_NickServ, u->nick, NS_NOT_REGISTERED, nick);
	else
	    notice (s_NickServ, u->nick, RPL_NEED_IDENT, "STATUS");

	return;
    }

    /* Find the host for linked nicks. */
    if (ni->host)
	hni = findnick (ni->host);
    else
	hni = ni;

    /* Ok. get_access() only works with users who are online. But here,
       theres a large possibility that the user we're checking up on isn't
       online. Rather than recode get_access and every call to it, we'll just
       search the access list here.
     */
    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level)
	    continue;

	/* We may have the linked nick on the access list, in which
	   case it won't match the host nick. Check for that.
	 */
	if (islinked (ca->mask, hni))
	{
	    ulev = ca->level;
	    break;
	}

	/* It doesn't appear to be linked, does it match? */
	if (!stricmp (ca->mask, hni->nick))
	{
	    ulev = ca->level;
	    break;
	}
    }

    xop = (ulev == VOP ? "VOP" : ulev == HOP ? "HOP" : ulev == AOP ? "AOP" :
	   ulev == SOP ? "SOP" : ulev == FOUNDER ? "Founder" : "Normal User");

    /* Successors are people, too! */
    if (ci->successor && !stricmp (ci->successor, hni->nick))
	xop = "Successor";

    /* AKick supercedes any access they may have. */
    for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
    {
	if (!ak->level)
	    continue;

	if (match_wild_nocase (ak->mask, hni->usermask))
	    akicked = 1;
    }

    if (akicked)
	xop = "AutoKicked";

    if (nick && csop)
	notice (s_ChanServ, u->nick, CS_STATUS, hni->nick, "'s", ci->name, xop);
    else
	notice (s_ChanServ, u->nick, CS_STATUS, "Your", "", ci->name, xop);
}

/* Freeze a channel, making it unusable. */
static void do_freeze (User *u)
{
    ChanInfo *ci;
    Channel *c;
    char *chan = strtok (NULL, " ");
    char *reason = strtok (NULL, "");

    if (!chan || !reason)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "FREEZE Channel Reason");
	errmoreinfo (s_ChanServ, u->nick, "FREEZE");
	return;
    }

    if (!(ci = cs_findchan (chan)))
    {	
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_FROZEN)
    {
	notice (s_ChanServ, u->nick, CS_ALREADY_FROZEN, ci->name);
	return;
    }

    /* Freeze! */
    ci->flags |= CF_FROZEN;

    /* We'll abuse the topic fields for frozen channels. */
    strscpy (ci->topic, reason, TOPICLEN);
    strscpy (ci->topicsetter, u->nick, NICKLEN);
    ci->topictime = time (NULL);

    snoop (s_ChanServ, "CS:FREEZE: %s %s!%s@%s", ci->name, u->nick, u->user,
	u->host);
    log ("CS:FREEZE: %s %s!%s@%s", ci->name, u->nick, u->user, u->host);

    if ((c = findchan (chan)))
    {
	struct c_userlist *cu, *next;
	char *av[3];

	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	    send_cmd (s_ChanServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL), ci->name, s_ChanServ);
	else
	    send_cmd (s_ChanServ, "%s %s", me.token ? "C" : "JOIN", ci->name);

	add_cs_timeout (ci, TO_UNINHABIT, 60);

	/* Ban everyone. */
	send_cmode (s_ChanServ, ci->name, "+b", "*!*@*");
	send_cmode (NULL);

	/* Now empty the channel. */
	av[0] = c->name;
	av[2] = (char *)reason;

	for (cu = c->users; cu; cu = next)
	{
	    next = cu->next;
	    av[1] = cu->user->nick;

	    send_cmd (s_ChanServ, "%s %s %s :This channel is frozen: %s", me.token ? "H" : "KICK", av[0], av[1], av[2]);
	    do_kick (s_ChanServ, 3, av);
	}
    }

    notice (s_ChanServ, u->nick, CS_FROZEN, ci->name);
}

/* UnFreeze a channel */
static void do_unfreeze (User *u)
{
    ChanInfo *ci;
    char *chan = strtok (NULL, " ");

    if ((ci = cs_findchan (chan)))
    {
	if (!(ci->flags & CF_FROZEN))
	{
	    notice (s_ChanServ, u->nick, CS_NOT_FROZEN, ci->name);
	    return;
	}

	ci->flags &= ~CF_FROZEN;

	/* Set the topic info to nothing, to get rid of our freeze info */
	strscpy (ci->topic, "", TOPICLEN);
	strscpy (ci->topicsetter, s_ChanServ, NICKLEN);
	ci->topictime = time (NULL);

	snoop (s_ChanServ, "CS:UNFREEZE: %s %s!%s@%s", ci->name, u->nick, u->user,
 	    u->host);
	log ("CS:UNFREEZE: %s %s!%s@%s", ci->name, u->nick, u->user, u->host);

	/* Send a part just in case ChanServ is inhabiting the channel */
	send_cmd (s_ChanServ, "%s %s", me.token ? "D" : "PART", ci->name);

	notice (s_ChanServ, u->nick, CS_UNFROZEN, ci->name);
    }
    else
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
}

/* Delete a registered channel. Note: We ignore the channel successor here.
   Successors are only used when the founders nick expires.
 */
int delchan (ChanInfo *ci, int fromdelnick)
{
    Channel *c = findchan (ci->name);
    ChanAccess *ca;
    NickInfo *ni = findnick (ci->founder);
    AKick *ak;
    char **chans;
    int i, j = 0;

    /* If this channel was dropped while awaiting verification, delete it from the
       Verify list.
     */
    for (i = 0; i < verifycnt; i++)
	if (!stricmp (ci->name, verify[i].name))
	{
	    free (verify[i].name);

	    verifycnt--;

	    if (i < verifycnt)
		memmove (verify + i, verify + i + 1, sizeof (*verify) * (verifycnt - i));
	}

    if (ci->successor)
    {
	NickInfo *sni = findnick (ci->successor);
	char **successor;

	/* First, we have to take this channel out of the successors
	   successor list.
	 */
	if (sni)
	{
	    for (successor = sni->successor, i = 0; i < sni->successorcnt; successor++, i++)
		if (!stricmp (sni->successor[i], ci->name))
		    break;

	    if (*successor)
		free (*successor);

	    --sni->successorcnt;

	    if (i < sni->successorcnt)
		bcopy (successor + 1, successor, (sni->successorcnt - i)
		    * sizeof (char *));

	    if (sni->successorcnt)
		sni->successor = srealloc (sni->successor, sni->successorcnt
		    * sizeof (char *));
	    else
	    {
		if (sni->successor)
		    free (sni->successor);
		sni->successor = NULL;
	    }
	}

	free (ci->successor);
	ci->successor = NULL;
    }

    /* Remove this channel from the founders nickinfo if we're not being
       called from delnick.
     */
    if (!fromdelnick && ni)
    {
	for (chans = ni->chans, i = 0; i < ni->chancnt; chans++, i++)
	{
	    if (!stricmp (ci->name, *chans))
	    {
		free (*chans);

		--ni->chancnt;

		if (i < ni->chancnt)
		    bcopy (chans + 1, chans, (ni->chancnt - i) * sizeof (char *));

		if (ni->chancnt)
		    ni->chans = srealloc (ni->chans, ni->chancnt * sizeof (char *));
		else
		{
		    if (ni->chans)
			free (ni->chans);

		    ni->chans = NULL;
		}
	    }
	}
    }

    if (ci->greet)
	free (ci->greet);

    if (ci->mlock_key)
	free (ci->mlock_key);

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
    {
	if (ci->mlock_flood)
	    free (ci->mlock_flood);

	if (ci->mlock_link)
	    free (ci->mlock_link);
    }

    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level)
	    continue;

	if (ca->mask)
	    free (ca->mask);

	ca->level = 0;

	j++;
    }

    if (j != ci->accesscnt)
	log ("delchan(): Expected %d access list entries, got %d. Memory leak?",
	    ci->accesscnt, j);

    j = 0;

    for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
    {
	if (ak->mask)
	    free (ak->mask);

	if (ak->reason)
	    free (ak->reason);

	j++;
    }

    if (j != ci->akickcnt)
	log ("delchan(): Expected %d AKick entries, got %d. Memory leak?",
	    ci->akickcnt, j);

    if (ci->prev)
	ci->prev->next = ci->next;
    else
	cs_chanlist[CSHASH(ci->name)] = ci->next;

    if (ci->next)
	ci->next->prev = ci->prev;

    free (ci);

    /* No longer registered. */
    if (c && (c->mode & CMODE_r))
    {
	c->mode &= ~CMODE_r;
	send_cmode (s_ChanServ, c->name, "-r");
    }

    return 1;
}

/* Check all channels and expire any nessecary */
void expire_chans ()
{
    ChanInfo *ci, *next;
    int j = 0;

    if (logupdates_on == TRUE)
	log ("CS:SYNC: Checking expires...");

    for (ci = firstci (); ci; ci = next)
    {
	next = nextci ();

	/* Only non-held channels */
	if (!(ci->flags & CF_HELD))
	{
	    if ((time (NULL) >= ci->lastused + chan_expire) ||
		(ci->flags & CF_WAITAUTH && time (NULL) >=
		ci->registered + tempexpire))
	    {
		log ("CS:SYNC:EXPIRE: %s", ci->name);

		j++;

		delchan (ci, 0);
	    }
	}
    }

    /* Only log this if we have something to report, or if we should
       log it regardless.
     */
    if (logupdates_on == TRUE || j)
	log ("CS:SYNC:EXPIRE: %d channels expired.", j);
}

/* Retrieve the password for a channel */
static void do_getpass (User *u)
{
    ChanInfo *ci;
    char *chan = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "GETPASS Channel");
	errmoreinfo (s_ChanServ, u->nick, "GETPASS");
	return;
    }

    /* Is the channel registered? */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (ci->flags & CF_WAITAUTH)
    {
	/* If this channel is awaiting auth, get the key. */
	notice (s_ChanServ, u->nick, RPL_GETPASS, "key", ci->name, " (", ci->founder, ")",
	    itoa (ci->key));

	globops (s_ChanServ, RPL_PASS_GLOBAL, u->nick, "GETPASS", ci->name,
	    " (", ci->founder, ")");

	snoop (s_ChanServ, "CS:GETPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

	log ("CS:GETPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Check if it's marked */
    if (ci->flags & CF_MARKED && !is_sra (u))
    {
	notice (s_ChanServ, u->nick, CS_CHAN_MARKED, ci->name, "GET");
	return;
    }

    notice (s_ChanServ, u->nick, RPL_GETPASS, "password", ci->name, " (",
	ci->founder, ")", ci->pass);

    globops (s_ChanServ, RPL_PASS_GLOBAL, u->nick, "GETPASS", ci->name,
	" (", ci->founder, ")");

    snoop (s_ChanServ, "CS:GETPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

    log ("CS:GETPASS: %s %s (%s)", u->nick, ci->name, ci->founder);
}

/* Set the password for a channel */
static void do_setpass (User *u)
{
    ChanInfo *ci;
    char *chan = strtok (NULL, " ");
    char *pass1 = strtok (NULL, " ");
    char *pass2 = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!chan || !pass1 || !pass2)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX,
	    "SETPASS Channel NewPass NewPass");
	errmoreinfo (s_ChanServ, u->nick, "SETPASS");
	return;
    }

    /* Is the channel registered? */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
	return;

    /* Check if it's marked */
    if (ci->flags & CF_MARKED && !is_sra (u))
    {
	notice (s_ChanServ, u->nick, CS_CHAN_MARKED, ci->name, "SET");
	return;
    }

    if (strcmp (pass1, pass2))
    {
	notice (s_ChanServ, u->nick, RPL_PASS_DONT_MATCH);
	return;
    }

    strscpy (ci->pass, pass1, sizeof (ci->pass));

    notice (s_ChanServ, u->nick, RPL_SETPASS, ci->name, " (", ci->founder, ")",
	ci->pass);

    globops (s_ChanServ, RPL_PASS_GLOBAL, u->nick, "SETPASS", ci->name, " (",
	ci->founder, ")");

    snoop (s_ChanServ, "CS:SETPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

    log ("CS:SETPASS: %s %s (%s)", u->nick, ci->name, ci->founder);
}

/* Email the password for a channel */
static void do_sendpass (User *u)
{
    ChanInfo *ci;
    NickInfo *ni;
    char *chan = strtok (NULL, " ");

    /* Do we have all needed info? */
    if (!chan)
    {
	notice (s_ChanServ, u->nick, RPL_SYNTAX, "SENDPASS Channel");
	errmoreinfo (s_ChanServ, u->nick, "SENDPASS");
	return;
    }

    /* Is the channel registered? */
    if (!(ci = cs_findchan (chan)))
    {
	notice (s_ChanServ, u->nick, CS_NOT_REGISTERED, chan);
	return;
    }

    ni = findnick (ci->founder);

    if (!ni->email)
    {
	notice (s_ChanServ, u->nick, CS_CANT_SENDPASS, ci->name);
	return;
    }

    if (!check_cs_auth (s_ChanServ, u->nick, ci))
    {
	/* If this channel is awaiting auth, generate a new key and re issue it. */
	ci->key = makekey ();
	sendemail (ci->name, itoa (ci->key), 3);

	notice (s_ChanServ, u->nick, RPL_KEY_RESENT, ci->name);

	globops (s_ChanServ, RPL_PASS_GLOBAL, u->nick, "SENDPASS", ci->name,
	    " (", ci->founder, ")");

	snoop (s_ChanServ, "CS:SENDPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

	log ("CS:SENDPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

	return;
    }

    /* Check if it's marked */
    if (ci->flags & CF_MARKED && !is_sra (u))
    {
	notice (s_ChanServ, u->nick, CS_CHAN_MARKED, ci->name, "SEND");
	return;
    }

    notice (s_ChanServ, u->nick, RPL_SENDPASS, ci->name, " (", ci->founder,
	")", ni->email);

    globops (s_ChanServ, RPL_PASS_GLOBAL, u->nick, "SENDPASS", ci->name,
	" (", ci->founder, ")");

    snoop (s_ChanServ, "CS:SENDPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

    log ("CS:SENDPASS: %s %s (%s)", u->nick, ci->name, ci->founder);

    sendemail (ci->name, ni->email, 4);
}

/* Load the ChanServ DB from disk */
void load_cs_dbase ()
{
    FILE *f = fopen (CHANSERV_DB, "r");
    ChanInfo *ci = NULL;
    NickInfo *ni;
    ChanAccess *ca;
    Verify *vf;
    AKick *ak;
    char *item, *s, dBuf[2048], backup [2048];
    int dolog = 0, cin = 0, convert = 1, delete = 0, dbend = 0, dbver = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

#ifdef HAVE_GETTIMEOFDAY
    log_sameline (1, "CS:DB: Loading... ");
#else
    log ("CS:DB: Loading... ");
#endif

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (2, "No %s found! ", CHANSERV_DB);
#else
	log ("CS:DB: No %s found!", CHANSERV_DB);
#endif
	dolog = 1;

	/* Check for a db.save */
	strcpy (backup, CHANSERV_DB);
	strcat (backup, ".save");

	if ((f = fopen (backup, "r")))
	{
#ifdef HAVE_GETTIMEOFDAY
	    log_sameline (0, "Recovering %s.save.", CHANSERV_DB);
#else
	    log ("CS:DB: Recovering %s.save.", CHANSERV_DB);
#endif
	}
	else
	{
	    log_sameline (0, "");
	    return;
	}
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    while (fgets (dBuf, 2047, f))
    {
	item = strtok (dBuf, " ");

	/* Ignore certain things.. */
	if (*item == '#' || *item == '\n' || *item == '\t' || *item == ' ' ||
	    *item == '\0' || *item == '\r' || !*item)
	    continue;

	if (!strcmp (item, "CV"))
	{
	    if ((s = strtok (NULL, "\n"))) { dbver = atoi (s); }

	    if (dbver < cs.version)
		convert = 1;
	    else
		convert = 0;
	}

	/* ChanInfo */
	if (!strcmp (item, "CI"))
	{
	    ChanInfo **list;
	    int i;

	    /* Is there a previous CI marked for deletion? */
	    if (ci && delete)
	    {
		delchan (ci, 0);
		delete = 0;
	    }

	    ci = scalloc (sizeof (ChanInfo), 1);

	    if ((s = strtok (NULL, " "))) { strscpy (ci->name, s, CHANNELLEN); } else { continue; }
	    if ((s = strtok (NULL, " "))) { strscpy (ci->founder, s, NICKLEN); } else { continue; }
	    if ((s = strtok (NULL, " "))) { strscpy (ci->pass, s, PASSWDLEN); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ci->registered = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ci->lastused = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ci->flags = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ci->mlock_on = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ci->mlock_off = atol (s); } else { continue; }
	    if ((s = strtok (NULL, " "))) { ci->topiclock = atol (s); } else { continue; }

	    if (cs.version > 1 && !convert)
	    {
		if ((s = strtok (NULL, " "))) { ci->memolevel = atol (s); } else { continue; }
	    }

	    if (!ci->memolevel)
		ci->memolevel = SOP;

	    if ((s = strtok (NULL, "\n"))) { ci->key = atol (s); }

	    list = &cs_chanlist[CSHASH(ci->name)];
	    ci->next = *list;
	    if (*list)
		(*list)->prev = ci;
	    *list = ci;

	    cin++;

	    /* A bug in earlier versions caused the founder to not be given founder access
	       sometimes. To fix this, we'll add them as founder here and drop any founder
	       level access we find in the db. That way the only founder on the channel is
	       the right one.
	     */
	    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
		if (ca->level)
		    break;

	    if (i == ci->accesscnt)
	    {
		ci->accesscnt++;
		ci->access = srealloc (ci->access, sizeof (ChanAccess) * ci->accesscnt);
		ca = &ci->access[ci->accesscnt - 1];
	    }

	    ca->mask = sstrdup (ci->founder);
	    ca->level = FOUNDER;

	    /* Add this channel to the founders chans list. */
	    ni = findnick (ci->founder);

	    if (!ni)
	    {
		/* This is bad. Mark the channel for deletion. */
		delete = 1;
	    }
	    else
	    {
		++ni->chancnt;
		ni->chans = srealloc (ni->chans, sizeof (char *) *ni->chancnt);
		ni->chans[ni->chancnt - 1] = sstrdup (ci->name);
	    }

	    continue;
	}

	/* Access list */
	if (!strcmp (item, "CA"))
	{
	    char *mask = strtok (NULL, " ");
	    char *level = strtok (NULL, "\n");

	    if (!mask || !level)
		continue;


	    /* Check if this is an access list entry for the founder, for other
	       than founder access. If it is, skip it. We don't want founder's having
	       founder access and say, SOP.
	     */
	    if (!stricmp (mask, ci->founder) && !(atoi (level) == FOUNDER))
		continue;

	    /* Also skip any founder level access, they're added above. */
	    if (atoi (level) == FOUNDER)
		continue;

	    ci->accesscnt++;
	    ci->access = srealloc (ci->access, sizeof (ChanAccess) * ci->accesscnt);
	    ca = &ci->access[ci->accesscnt - 1];

	    ca->mask = sstrdup (mask);
	    ca->level = atoi (level);

	    continue;
	}

	/* AKick list */
	if (!strcmp (item, "AK"))
	{
	    char *mask, *setter, *level, *aktime, *reason;

	    mask = strtok (NULL, " ");
	    setter = strtok (NULL, " ");

	    if (dbver < 3 && convert)
		level = strtok (NULL, " ");

	    aktime = strtok (NULL, " ");
	    reason = strtok (NULL, "\n");

	    if (!mask || !setter || !aktime)
		continue;

	    /* Drop old nick akicks */
	    if (!strchr (mask, '@'))
		continue;

	    ci->akickcnt++;
	    ci->akick = srealloc (ci->akick, sizeof (AKick) * ci->akickcnt);
	    ak = &ci->akick[ci->akickcnt - 1];

	    ak->mask = sstrdup (mask);
	    strscpy (ak->setter, setter, NICKLEN);
	    ak->level = 1;
	    ak->time = atol (aktime);

	    if (reason)
		ak->reason = sstrdup (reason);
	    else
		ak->reason = NULL;

	    continue;
	}

	/* Successor */
	if (!strcmp (item, "SU"))
	{
	    if ((s = strtok (NULL, "\n"))) { ci->successor = sstrdup (s); } else { continue; }

	    /* Add this channel to the successors list */
	    if (!(ni = findnick (ci->successor)))
	    {
		char mbuf[BUFSIZE];

		/* The founders nick is already confirmed registered above */
		ni = findnick (ci->founder);

		/* The successors nick isn't registered? Let the founder know. */
		snprintf (mbuf, sizeof (mbuf), CS_SUCCESSOR_GONE_MEMO, ci->name, ci->successor);
		send_memo (ni, s_ChanServ, mbuf);

		/* Free the successor */
		free (ci->successor);
		ci->successor = NULL;
	    }
	    else
	    {
		++ni->successorcnt;
		ni->successor = srealloc (ni->successor, sizeof (char *) *ni->successorcnt);
		ni->successor[ni->successorcnt - 1] = sstrdup (ci->successor);
	    }

	    continue;
	}

	/* Greet */
	if (!strcmp (item, "GR"))
	{
	    if ((s = strtok (NULL, "\n"))) { ci->greet = sstrdup (s); }
	    continue;
	}

	/* URL */
	if (!strcmp (item, "UR"))
	{
	    if ((s = strtok (NULL, "\n"))) { ci->url = sstrdup (s); }
	    continue;
	}

	/* Channel topic */
	if (!strcmp (item, "CT"))
	{
	    char *setter, *ttime, *topic;

	    setter = strtok (NULL, " ");
	    ttime = strtok (NULL, " ");
	    topic = strtok (NULL, "\n");

	    if (!setter || !ttime || !topic)
		continue;

	    strscpy (ci->topicsetter, setter, NICKLEN);
	    ci->topictime = atol (ttime);
	    strscpy (ci->topic, topic, TOPICLEN);

	    continue;
	}

	/* MLock Key */
	if (!strcmp (item, "KY"))
	{
	    if ((s = strtok (NULL, "\n"))) { ci->mlock_key = sstrdup (s); }
	    continue;
	}

	/* MLock Limit */
	if (!strcmp (item, "LM"))
	{
	    if ((s = strtok (NULL, "\n"))) { ci->mlock_limit = atol (s); }
	    continue;
	}

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    /* MLock Flood */
	    if (!strcmp (item, "FL"))
	    {
		if ((s = strtok (NULL, "\n"))) { ci->mlock_flood = sstrdup (s); }
		continue;
	    }

	    /* MLock Link */
	    if (!strcmp (item, "LK"))
	    {
		if ((s = strtok (NULL, "\n"))) { ci->mlock_link = sstrdup (s); }
		continue;
	    }
	}

	/* Channel Verify */
	if (!strcmp (item, "VF"))
	{
	    char *name, *since;

	    name = strtok (NULL, " ");
	    since = strtok (NULL, "\n");

	    if (!name || !since)
		continue;

	    if (verifycnt >= verify_size)
	    {
		if (verify_size < 8)
		    verify_size = 8;
		else if (verify_size >= 16384)
		    verify_size = 32767;
		else
		    verify_size *= 2;

		verify = srealloc (verify, sizeof (*verify) * verify_size);
	    }

	    vf = &verify[verifycnt];
	    vf->name = sstrdup (name);
	    vf->since = atoi (since);

	    verifycnt++;
	}

	/* End of DB */
	if (!strcmp (item, "DE") || (cs.version < 3 && !strcmp (item, "CS")))
	{
	    int newline = 0;

	    if ((s = strtok (NULL, " ")))
	    {
		if (atoi (s) != cin)
		{
		    log_sameline (0, "");
		    log ("Warning: Expected %s channels, got %d.", s, cin);
		    newline = 1;
		}
	    }

	    if (newline)
		log_sameline (1, "CS:DB: Loading... ");

	    dbend = 1;
	}
    }

    fclose (f);

    if (!dbend && cs.version > 1 && !convert && !force)
    {
	/* We need a newline first. */
	log_sameline (0, "");
	log ("Incomplete chanserv.db detected. Only found %d channels.", cin);
        fatal (1, "Please restore from backup or run Cygnus with -force\n");
    }

#ifdef HAVE_GETTIMEOFDAY
    if (!dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms. (%d channels)", tv2ms (&tmp), cin);
    }
#endif
}

/* Write the ChanServ db to disk. */
void save_cs_dbase ()
{
    FILE *f;
    ChanInfo *ci;
    ChanAccess *ca;
    AKick *ak;
    int i, j, dolog = 0, cout = 0;
    char backup[2048];
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

    if (logupdates_on == TRUE)
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (1, "CS:DB: Saving... ");
#else
	log ("CS:DB: Saving... ");
#endif

    f = fopen (CHANSERV_DB, "w");

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "Can't write to %s! ", CHANSERV_DB);
#else
	log ("CS:DB: Can't write to %s!", CHANSERV_DB);
#endif
	dolog = 1;

	strcpy (backup, CHANSERV_DB);
	strcat (backup, ".save");

	if (rename (backup, CHANSERV_DB) < 0)
	    fatal (0, "CS:DB: Can't restore backup of %s!", CHANSERV_DB);

	return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    /* Write the version */
    fprintf (f, "CV %d\n", cs.version);

    /* Write each channel's info */
    for (ci = firstci (); ci; ci = nextci ())
    {
	/* ChanInfo */
	fprintf (f, "CI %s %s %s %lu %lu %d %d %d %d %d %lu\n", ci->name,
	    ci->founder, ci->pass, ci->registered, ci->lastused,
	    ci->flags, ci->mlock_on, ci->mlock_off, ci->topiclock,
	    ci->memolevel, ci->key);

	cout++;

	/* Access list */
	for (ca = ci->access, j = 0; j < ci->accesscnt; ca++, j++)
	{
	    if (!ca->level)
		continue;

	    fprintf (f, "CA %s %d\n", ca->mask, ca->level);
	}

	/* AKick list */
	for (ak = ci->akick, j = 0; j < ci->akickcnt; ak++, j++)
	{
	    if (!ak->level)
		continue;

	    fprintf (f, "AK %s %s %lu %s\n", ak->mask, ak->setter,
		ak->time, ak->reason ? ak->reason : "");
	}

	/* Successor */
	if (ci->successor)
	    fprintf (f, "SU %s\n", ci->successor);

	/* Greet */
	if (ci->greet)
	    fprintf (f, "GR %s\n", ci->greet);

	/* URL */
	if (ci->url)
	    fprintf (f, "UR %s\n", ci->url);

	/* Last topic info */
	if (strlen (ci->topic))
	    fprintf (f, "CT %s %lu %s\n", ci->topicsetter ? ci->topicsetter : s_ChanServ,
		ci->topictime, ci->topic);

	/* MLock Key */
	if (ci->mlock_key)
	    fprintf (f, "KY %s\n", ci->mlock_key);

	/* MLock Limit */
	if (ci->mlock_limit)
	    fprintf (f, "LM %d\n", ci->mlock_limit);

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    /* MLock Flood */
	    if (ci->mlock_flood)
		fprintf (f, "FL %s\n", ci->mlock_flood);

	    /* MLock Link */
	    if (ci->mlock_link)
		fprintf (f, "LK %s\n", ci->mlock_link);
	}
    }

    /* Write out channel verifies */
    for (i = 0; i < verifycnt; i++)
	fprintf (f, "VF %s %lu\n", verify[i].name, verify[i].since);

    /* Write some stats. This helps us determine if a DB has been truncated as well. */
    fprintf (f, "DE %d\n", cout);

    fclose (f);

#ifdef HAVE_GETTIMEOFDAY
    if (logupdates_on == TRUE && !dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms. (%d Channel%s)",
	    tv2ms (&tmp), cout, cout == 1 ? "" : "s");
    }
#endif
}


/* Check if the given channel is registered. If it is,
   restore the topic, modes, etc.
 */
void restore_channel (Channel *c)
{
    ChanInfo *ci = cs_findchan (c->name);

    if (!ci || (ci && (ci->flags & CF_WAITAUTH)))
	return;

    /* Restore the topic if it's present. */
    if (strlen (ci->topic) && !bursting)
    {
	strscpy (c->topic, ci->topic, TOPICLEN);
	strscpy (c->topicsetter, ci->topicsetter, NICKLEN);
	c->topictime = ci->topictime;

	send_cmd (s_ChanServ, "%s %s %s %lu :%s", me.token ? ")" : "TOPIC", ci->name,
	    ci->topicsetter, ci->topictime, ci->topic);
    }

    /* Restore the channel modes. */
    if (!bursting)
	check_modes (ci->name);
}

/* Check the current modes on the given channel. Make any mode changes
   nessecary to keep with the modelock.
 */
void check_modes (const char *chan)
{
    Channel *c = findchan (chan);
    ChanInfo *ci = cs_findchan (chan);
    char newmodes[40], *newkey = NULL;
    char *end = newmodes;
    int32 newlimit = 0;
    int modes, set_limit = 0, set_key = 0;
    char *newlink = NULL, *newflood = NULL;
    int set_link = 0, set_flood = 0;

    if (!c)
	return;

    if (!ci)
    {
	if (c->mode & CMODE_r)
	{
	    c->mode &= ~CMODE_r;
	    send_cmode (s_ChanServ, c->name, "-r");
	}

	return;
    }

    if (debuglevel > 1)
	debug ("check_modes(): %s", ci->name);

    modes = ~c->mode & (ci->mlock_on | CMODE_r);

    end += snprintf (end, sizeof (newmodes) - (end - newmodes) - 2,
	"+%s", flags_to_string (modes));
    c->mode |= modes;

    /* Ignore MLock Limit if LIMITED is on */
    if (ci->flags & CF_LIMITED)
    {
	if ((c->limit < c->usercnt + 5 || c->limit > c->usercnt + 5) &&
	    time (NULL) - c->lastlimit > 60)
	{
	    *end++ = 'l';
	    newlimit = c->usercnt + 5;
	    c->limit = newlimit;
	    c->lastlimit = time (NULL);
	    set_limit = 1;
	}
    }
    else if (ci->mlock_limit && ci->mlock_limit != c->limit)
    {
	*end++ = 'l';
	newlimit = ci->mlock_limit;
	c->limit = newlimit;
	set_limit = 1;
    }

    if (ci->mlock_key)
    {
	if (c->key && strcmp (c->key, ci->mlock_key))
	{
	    send_cmode (s_ChanServ, c->name, "-k", c->key);
	    free (c->key);
	    c->key = NULL;
	}

	if (!c->key)
	{
	    *end++ = 'k';
	    newkey = ci->mlock_key;
	    c->key = sstrdup (newkey);
	    set_key = 1;
	}
    }

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
    {
	if (ci->mlock_flood)
	{
	    if (c->flood && strcmp (c->flood, ci->mlock_flood))
	    {
		send_cmode (s_ChanServ, c->name, "-f", c->flood);
		free (c->flood);
		c->flood = NULL;
	    }

	    if (!c->flood)
	    {
		*end++ = 'f';
		newflood = ci->mlock_flood;
		c->flood = sstrdup (newflood);
		set_flood = 1;
	    }
	}

	if (ci->mlock_link)
	{
	    if (c->link && strcmp (c->link, ci->mlock_link))
	    {
		send_cmode (s_ChanServ, c->name, "-L", c->link);
		free (c->link);
		c->link = NULL;
	    }

	    if (!c->link)
	    {
		*end++ = 'L';
		newlink = ci->mlock_link;
		c->link = sstrdup (newlink);
		set_link = 1;
	    }
	}
    }

    if (end[-1] == '+')
	end--;

    modes = c->mode & ci->mlock_off;
    modes &= ~(CMODE_k | CMODE_l);

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	modes &= ~(CMODE_L | CMODE_f);

    end += snprintf (end, sizeof (newmodes) - (end - newmodes) - 1,
	"-%s", flags_to_string (modes));
    c->mode &= ~modes;

    if (!(ci->flags & CF_LIMITED) && c->limit && (ci->mlock_off & CMODE_l))
    {
	*end++ = 'l';
	c->limit = 0;
    }

    if (c->key && (ci->mlock_off & CMODE_k))
    {
	*end++ = 'k';
	newkey = sstrdup (c->key);
	free (c->key);
	c->key = NULL;
	set_key = 1;
    }

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
    {
	if (c->flood && (ci->mlock_off & CMODE_f))
	{
	    *end++ = 'f';
	    newflood = sstrdup (c->flood);
	    free (c->flood);
	    c->flood = NULL;
	    set_flood = 1;
	}

	if (c->link && (ci->mlock_off & CMODE_L))
	{
	    *end++ = 'L';
	    newlink = sstrdup (c->link);
	    free (c->link);
	    c->link = NULL;
	    set_link = 1;
	}
    }

    if (end[-1] == '-')
	end--;

    if (end == newmodes)
	return;

    *end = 0;

    send_cmode (s_ChanServ, c->name, newmodes,
		set_limit ? itoa (newlimit) : set_key ? newkey :
		set_flood ? newflood : set_link ? newlink :
		"");

    if (newkey && !c->key)
	free (newkey);

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
    {
	if (newflood && !c->flood)
	    free (newflood);

	if (newlink && !c->link)
	    free (newlink);
    }
}

/* Change the topic of a registered channel. Store the new one in ChanInfo. */
void set_topic (ChanInfo *ci, const char *topic, const char *setter)
{
    Channel *c = findchan (ci->name);
    time_t now = time (NULL);

    if (!c)
	return;

    /* Update the ChanInfo */
    strscpy (ci->topic, topic, TOPICLEN);
    strscpy (ci->topicsetter, setter, NICKLEN);
    ci->topictime = now;

    /* Update the channel's topic info */
    strscpy (c->topic, topic, TOPICLEN);
    strscpy (c->topicsetter, setter, NICKLEN);
    c->topictime = now;

    /* Now change the topic. */
    send_cmd (s_ChanServ, "%s %s %s %lu :%s", me.token ? ")" : "TOPIC", ci->name, setter, now, topic);
}

/* Return the user's access on the given channel. */
int get_access (User *u, ChanInfo *ci)
{
    NickInfo *ni;
    ChanAccess *ca;
    char **chans;
    int i;

    if (ci->flags & CF_FROZEN || !u)
	return 0;

    ni = get_nick (u);

    if (!ni)
	return 0;

    /* Check for founder access. They may have identified to the channel,
       while not being on it's access list.
     */
    if (u->idchancnt)
	for (chans = u->idchans, i = 0; i < u->idchancnt; chans++, i++)
	    if (!stricmp (*chans, ci->name))
		return FOUNDER;

    /* If they're Successor for this channel, return SOP access. */
    if (ci->successor && !stricmp (ci->successor, ni->nick))
	return SOP;

    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
    {
	if (!ca->level)
	    continue;

	/* Is this access entry registered? Drop it otherwise. */
	if (!findnick (ca->mask))
	{
	    if (ca->mask)
		free (ca->mask);

	    ca->level = 0;

	    continue;
	}

	/* We may have the linked nick on the access list, in which
	   case it won't match the host nick. Check for that.
	 */
	if (islinked (ca->mask, ni))
	    return ca->level;

	/* It doesn't appear to be linked, does it match? */
	if (!stricmp (ca->mask, ni->nick))
	    return ca->level;
    }

    return 0;
}

/* Check if the user is AKicked on the channel. Return 1 if true, 0 otherwise. */
int is_akicked (User *u, ChanInfo *ci)
{
    AKick *ak;
    char mask[128];
    int i, akicked = 0;

    /* Get their mask */
    snprintf (mask, sizeof (mask), "%s@%s", u->user, u->host);

    /* Check for existing AKicks against this user. */
    for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
    {
	if (!ak->level)
	    continue;

	if (match_wild_nocase (mask, ak->mask))
	    akicked = 1;
    }

    if (akicked)
	return 1;

    return 0;
}

/* Do various ChanServ specific on join things. */
int cs_join (User *u, const char *c, int fromburst)
{
    ChanInfo *ci = cs_findchan (c);
    Channel *ch;
    AKick *ak;
    char *av[3];
    char mask[128];
    int i, akicked = 0;
    char **chans;

    if (!u || !ci || (ci && (ci->flags & CF_WAITAUTH)))
	return 0;

    /* Get their entire address. We'll match this against any AKicks. */
    snprintf (mask, sizeof (mask), "*!%s@%s", u->user, u->host);

    /* Check if the channel is froze. Kick and ban if true. */
    if (ci->flags & CF_FROZEN && !is_oper (u))
    {
	/* Join it to keep the ban from being removed */
	if (!(ch = findchan (c)))
	{
	    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		send_cmd (s_ChanServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL), ci->name, s_ChanServ);
	    else
		send_cmd (s_ChanServ, "%s %s", me.token ? "C" : "JOIN", ci->name);

	    add_cs_timeout (ci, TO_UNINHABIT, 60);
	}

	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS || ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    /* If we were bursting, they've already been added to memory. We need to
	       remove them. c should be the channel, from the above if..
	     */
	    if (fromburst && ch)
	    {
		av[0] = ci->name;
		av[1] = u->nick;
		av[2] = "";

		do_kick (s_ChanServ, 3, av);

		/* They're out of the channel. NOW check if the channel is empty, and
		   inhabit if needed.
		 */
		if (!findchan (c))
		{
		    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
			send_cmd (s_ChanServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL), ci->name, s_ChanServ);
		    else
			send_cmd (s_ChanServ, "%s %s", me.token ? "C" : "JOIN", ci->name);

		    add_cs_timeout (ci, TO_UNINHABIT, 60);
		}
	    }
	}

	/* The user isn't yet in memory for this channel, so we can just ban and kick.
	   Since it's a frozen channel, we'll ban everyone.
	 */
	send_cmd (s_ChanServ, "%s %s +b *!*@*.*", me.token ? "G" : "MODE", ci->name);
	send_cmd (s_ChanServ, "%s %s %s :This channel is frozen: %s", me.token ? "H" : "KICK", ci->name,
	    u->nick, ci->topic);

	return 1;
    }

    /* Check for existing AKicks against this user. */
    for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
    {
	if (!ak->level)
	    continue;

	/* Mask AKick */
	if (match_wild_nocase (ak->mask, mask))
	    akicked = 1;

	if (akicked)
	{
	    if (is_on_chan (u->nick, ci->name))
	    {
		av[0] = ci->name;
		av[1] = u->nick;
		av[2] = "";

		do_kick (s_ChanServ, 3, av);

		av[1] = "+b";
		av[2] = mask;

		do_cmode (s_ChanServ, 3, av);
	    }

	    send_cmd (s_ChanServ, "%s %s +b %s", me.token ? "G" : "MODE", ci->name, ak->mask);
	    send_cmd (s_ChanServ, "%s %s %s :%s", me.token ? "H" : "KICK", ci->name, u->nick,
		ak->reason ? ak->reason : def_akick_reason);

	    /* They're out of the channel. NOW check if the channel is empty, and
	       inhabit if needed.
	     */
	    if (!findchan (c))
	    {
		if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		    send_cmd (s_ChanServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL), ci->name, s_ChanServ);
		else
		    send_cmd (s_ChanServ, "%s %s", me.token ? "C" : "JOIN", ci->name);

		add_cs_timeout (ci, TO_UNINHABIT, 60);
	    }

	    return 1;
	}
    }

    /* Check if the channel is restricted */
    if ((ci->flags & CF_RESTRICTED) && !is_oper (u))
    {
	if (!get_access (u, ci))
	{
	    /* Is anyone else in it? We need to inhabit it otherwise. */
	    if (!findchan (c))
	    {
		if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		    send_cmd (s_ChanServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL), ci->name, s_ChanServ);
		else
		    send_cmd (s_ChanServ, "%s %s", me.token ? "C" : "JOIN", ci->name);

		add_cs_timeout (ci, TO_UNINHABIT, 60);
	    }

	    av[0] = ci->name;
	    av[1] = "+b";
	    av[2] = create_mask (u);

	    do_cmode (s_ChanServ, 3, av);
    
	    send_cmd (s_ChanServ, "%s %s +b %s", me.token ? "G" : "MODE", ci->name, create_mask (u));
	    send_cmd (s_ChanServ, "%s %s %s :%s", me.token ? "H" : "KICK", ci->name, u->nick,
		CS_RESTRICTED_CHAN);

	    return 1;
	}
    }

    if (!fromburst)
    {
	/* Check for a channel greet message. */
	if (ci->greet)
	    notice (s_ChanServ, u->nick, CS_GREET, ci->name, ci->greet);

	/* Check for a channel URL. */
	if (ci->url)
	    send_cmd (me.name, "328 %s %s :%s", u->nick, ci->name, ci->url);
    }

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	if (u->idchancnt)
	    for (chans = u->idchans, i = 0; i < u->idchancnt; chans++, i++)
		if (!stricmp (*chans, ci->name))
		    send_cmd (s_ChanServ, "%s %s +q %s", me.token ? "G" : "MODE",  ci->name, u->nick);

    return 0;
}
