/* ChanServ functions.
 *
 * (C) 2003 Anope Team
 * Contact us at info@anope.org
 *
 * Please read COPYING and README for furhter details.
 *
 * Based on the original code of Epona by Lara.
 * Based on the original code of Services by Andy Church. 
 * 
 * $Id: chanserv.c 5 2004-03-29 01:29:50Z dane $ 
 *
 */

/*************************************************************************/

#include "services.h"
#include "pseudo.h"

/*************************************************************************/
/* *INDENT-OFF* */

ChannelInfo *chanlists[256];

static int def_levels[][2] = {
    { CA_AUTOOP,                     5 },
    { CA_AUTOVOICE,                  3 },
    { CA_AUTODEOP,                  -1 },
    { CA_NOJOIN,                    -2 },
    { CA_INVITE,                     5 },
    { CA_AKICK,                     10 },
    { CA_SET,     		ACCESS_INVALID },
    { CA_CLEAR,   		ACCESS_INVALID },
    { CA_UNBAN,                      5 },
    { CA_OPDEOP,                     5 },
    { CA_ACCESS_LIST,                1 },
    { CA_ACCESS_CHANGE,             10 },
    { CA_MEMO,                      10 },
    { CA_ASSIGN,  		ACCESS_INVALID },
    { CA_BADWORDS,                  10 },
    { CA_NOKICK,                     1 },
    { CA_FANTASIA,			         3 },
    { CA_SAY,				         5 },
    { CA_GREET,                      5 },
    { CA_VOICEME,			         3 },
    { CA_VOICE,				         5 },
    { CA_GETKEY,                     5 },
    { CA_AUTOHALFOP,                 4 },
    { CA_AUTOPROTECT,               10 },
    { CA_OPDEOPME,                   5 },
    { CA_HALFOPME,                   4 },
    { CA_HALFOP,                     5 },
    { CA_PROTECTME,                 10 },
    { CA_PROTECT,  		ACCESS_INVALID },
    { CA_KICKME,               		 5 },
    { CA_KICK,                       5 },
    { CA_SIGNKICK, 		ACCESS_INVALID },
    { CA_BANME,                      5 },
    { CA_BAN,                        5 },
    { CA_TOPIC,         ACCESS_INVALID },
    { CA_INFO,          ACCESS_INVALID },
    { -1 }
};

typedef struct {
    int what;
    char *name;
    int desc;
} LevelInfo;
static LevelInfo levelinfo[] = {
    { CA_AUTODEOP,      "AUTODEOP",   	CHAN_LEVEL_AUTODEOP },
#ifdef HAS_HALFOP
	{ CA_AUTOHALFOP,    "AUTOHALFOP",   CHAN_LEVEL_AUTOHALFOP },
#endif
    { CA_AUTOOP,        "AUTOOP",     	CHAN_LEVEL_AUTOOP },
#ifdef IRC_UNREAL
	{ CA_AUTOPROTECT,   "AUTOPROTECT",  CHAN_LEVEL_AUTOPROTECT },
#endif
#ifdef IRC_VIAGRA
        { CA_AUTOPROTECT,   "AUTOPROTECT",  CHAN_LEVEL_AUTOPROTECT },
#endif
#ifdef IRC_ULTIMATE3
	{ CA_AUTOPROTECT,   "AUTOADMIN",  CHAN_LEVEL_AUTOPROTECT },
#endif
    { CA_AUTOVOICE,     "AUTOVOICE",  	CHAN_LEVEL_AUTOVOICE },
    { CA_NOJOIN,        "NOJOIN",     	CHAN_LEVEL_NOJOIN },
    { CA_SIGNKICK,      "SIGNKICK",     CHAN_LEVEL_SIGNKICK },

    { CA_ACCESS_LIST,   "ACC-LIST",   	CHAN_LEVEL_ACCESS_LIST },
    { CA_ACCESS_CHANGE, "ACC-CHANGE", 	CHAN_LEVEL_ACCESS_CHANGE },
    { CA_AKICK,         "AKICK",      	CHAN_LEVEL_AKICK },
    { CA_SET,           "SET",        	CHAN_LEVEL_SET },

    { CA_BAN,           "BAN",          CHAN_LEVEL_BAN },
	{ CA_BANME,         "BANME",        CHAN_LEVEL_BANME },
    { CA_CLEAR,         "CLEAR",      	CHAN_LEVEL_CLEAR },
    { CA_GETKEY,        "GETKEY",     	CHAN_LEVEL_GETKEY },
#ifdef HAS_HALFOP
	{ CA_HALFOP,        "HALFOP",       CHAN_LEVEL_HALFOP },
    { CA_HALFOPME,      "HALFOPME",     CHAN_LEVEL_HALFOPME },
#endif
    { CA_INFO,          "INFO",         CHAN_LEVEL_INFO },
	{ CA_KICK,          "KICK",         CHAN_LEVEL_KICK },
	{ CA_KICKME,        "KICKME",       CHAN_LEVEL_KICKME },
    { CA_INVITE,        "INVITE",     	CHAN_LEVEL_INVITE },
    { CA_OPDEOP,        "OPDEOP",     	CHAN_LEVEL_OPDEOP },
    { CA_OPDEOPME,      "OPDEOPME",     CHAN_LEVEL_OPDEOPME },
#ifdef IRC_UNREAL
	{ CA_PROTECT,       "PROTECT",      CHAN_LEVEL_PROTECT },
    { CA_PROTECTME,     "PROTECTME",    CHAN_LEVEL_PROTECTME },
#endif
#ifdef IRC_VIAGRA
        { CA_PROTECT,       "PROTECT",      CHAN_LEVEL_PROTECT },
    { CA_PROTECTME,     "PROTECTME",    CHAN_LEVEL_PROTECTME },
#endif
#ifdef IRC_ULTIMATE3
  { CA_PROTECT,       "ADMIN",      CHAN_LEVEL_PROTECT },
    { CA_PROTECTME,     "ADMINME",    CHAN_LEVEL_PROTECTME },
#endif
    { CA_TOPIC,         "TOPIC",        CHAN_LEVEL_TOPIC },
    { CA_UNBAN,         "UNBAN",      	CHAN_LEVEL_UNBAN },
    { CA_VOICE,         "VOICE",      	CHAN_LEVEL_VOICE },
    { CA_VOICEME,       "VOICEME",      CHAN_LEVEL_VOICEME },

    { CA_MEMO,          "MEMO",       	CHAN_LEVEL_MEMO },

    { CA_ASSIGN,        "ASSIGN",     	CHAN_LEVEL_ASSIGN },
    { CA_BADWORDS,      "BADWORDS",   	CHAN_LEVEL_BADWORDS },
    { CA_FANTASIA,      "FANTASIA",   	CHAN_LEVEL_FANTASIA },
    { CA_GREET,			"GREET",	  	CHAN_LEVEL_GREET },
    { CA_NOKICK,        "NOKICK",     	CHAN_LEVEL_NOKICK },
    { CA_SAY,			"SAY",		  	CHAN_LEVEL_SAY },

    { -1 }
};
static int levelinfo_maxwidth = 0;

CSModeUtil csmodeutils[] = {
	{ "DEOP",   	"!deop",		"-o",   CI_OPNOTICE,	CA_OPDEOP, CA_OPDEOPME },
	{ "OP",			"!op",			"+o",	CI_OPNOTICE,	CA_OPDEOP, CA_OPDEOPME },
	{ "DEVOICE",	"!devoice", 	"-v",   0          ,	CA_VOICE,  CA_VOICEME  },
	{ "VOICE",		"!voice",   	"+v",   0          ,	CA_VOICE,  CA_VOICEME  },
#ifdef HAS_HALFOP
    { "DEHALFOP",	"!dehalfop",	"-h",	0          ,	CA_HALFOP, CA_HALFOPME },
	{ "HALFOP",		"!halfop",		"+h",	0          ,	CA_HALFOP, CA_HALFOPME },
#endif
#ifdef IRC_UNREAL
	{ "DEPROTECT",	"!deprotect",	"-a",	0          ,	CA_PROTECT, CA_PROTECTME },
	{ "PROTECT",	"!protect",		"+a",	0          ,	CA_PROTECT, CA_PROTECTME },
#endif
#ifdef IRC_VIAGRA
	{ "DEPROTECT",	"!deprotect",	"-a",	0          ,	CA_PROTECT, CA_PROTECTME },
	{ "PROTECT",	"!protect",		"+a",	0          ,	CA_PROTECT, CA_PROTECTME },
#endif
#ifdef IRC_ULTIMATE3
  	{ "DEPROTECT",	"!deadmin",	"-a",	0          ,	CA_PROTECT, CA_PROTECTME },
	{ "PROTECT",	"!admin",		"+a",	0          ,	CA_PROTECT, CA_PROTECTME },
#endif

	{ NULL }
};

int xop_msgs[4][14] = {
	{	CHAN_AOP_SYNTAX,
		CHAN_AOP_DISABLED,
		CHAN_AOP_NICKS_ONLY,
		CHAN_AOP_ADDED,
		CHAN_AOP_MOVED,
		CHAN_AOP_NO_SUCH_ENTRY,
		CHAN_AOP_NOT_FOUND,
		CHAN_AOP_NO_MATCH,
		CHAN_AOP_DELETED,
		CHAN_AOP_DELETED_ONE,
		CHAN_AOP_DELETED_SEVERAL,
		CHAN_AOP_LIST_EMPTY,
		CHAN_AOP_LIST_HEADER,
		CHAN_AOP_CLEAR
	},
	{	CHAN_SOP_SYNTAX,
		CHAN_SOP_DISABLED,
		CHAN_SOP_NICKS_ONLY,
		CHAN_SOP_ADDED,
		CHAN_SOP_MOVED,
		CHAN_SOP_NO_SUCH_ENTRY,
		CHAN_SOP_NOT_FOUND,
		CHAN_SOP_NO_MATCH,
		CHAN_SOP_DELETED,
		CHAN_SOP_DELETED_ONE,
		CHAN_SOP_DELETED_SEVERAL,
		CHAN_SOP_LIST_EMPTY,
		CHAN_SOP_LIST_HEADER,
		CHAN_SOP_CLEAR
	},
	{	CHAN_VOP_SYNTAX,
		CHAN_VOP_DISABLED,
		CHAN_VOP_NICKS_ONLY,
		CHAN_VOP_ADDED,
		CHAN_VOP_MOVED,
		CHAN_VOP_NO_SUCH_ENTRY,
		CHAN_VOP_NOT_FOUND,
		CHAN_VOP_NO_MATCH,
		CHAN_VOP_DELETED,
		CHAN_VOP_DELETED_ONE,
		CHAN_VOP_DELETED_SEVERAL,
		CHAN_VOP_LIST_EMPTY,
		CHAN_VOP_LIST_HEADER,
		CHAN_VOP_CLEAR
	},
	{	CHAN_HOP_SYNTAX,
		CHAN_HOP_DISABLED,
		CHAN_HOP_NICKS_ONLY,
		CHAN_HOP_ADDED,
		CHAN_HOP_MOVED,
		CHAN_HOP_NO_SUCH_ENTRY,
		CHAN_HOP_NOT_FOUND,
		CHAN_HOP_NO_MATCH,
		CHAN_HOP_DELETED,
		CHAN_HOP_DELETED_ONE,
		CHAN_HOP_DELETED_SEVERAL,
		CHAN_HOP_LIST_EMPTY,
		CHAN_HOP_LIST_HEADER,
		CHAN_HOP_CLEAR
	}
};

/* *INDENT-ON* */
/*************************************************************************/

static void alpha_insert_chan(ChannelInfo * ci);
static ChannelInfo *makechan(const char *chan);
static int delchan(ChannelInfo * ci);
static void reset_levels(ChannelInfo * ci);
static int is_real_founder(User * user, ChannelInfo * ci);
static int is_identified(User * user, ChannelInfo * ci);
static void make_unidentified(User * u, ChannelInfo * ci);

static int do_help(User * u);
static int do_register(User * u);
static int do_identify(User * u);
static int do_logout(User * u);
static int do_drop(User * u);
static int do_set(User * u);
static int do_set_founder(User * u, ChannelInfo * ci, char *param);
static int do_set_successor(User * u, ChannelInfo * ci, char *param);
static int do_set_password(User * u, ChannelInfo * ci, char *param);
static int do_set_desc(User * u, ChannelInfo * ci, char *param);
static int do_set_url(User * u, ChannelInfo * ci, char *param);
static int do_set_email(User * u, ChannelInfo * ci, char *param);
static int do_set_entrymsg(User * u, ChannelInfo * ci, char *param);
static int do_set_bantype(User * u, ChannelInfo * ci, char *param);
static int do_set_mlock(User * u, ChannelInfo * ci, char *param);
static int do_set_keeptopic(User * u, ChannelInfo * ci, char *param);
static int do_set_topiclock(User * u, ChannelInfo * ci, char *param);
static int do_set_private(User * u, ChannelInfo * ci, char *param);
static int do_set_secureops(User * u, ChannelInfo * ci, char *param);
static int do_set_securefounder(User * u, ChannelInfo * ci, char *param);
static int do_set_restricted(User * u, ChannelInfo * ci, char *param);
static int do_set_secure(User * u, ChannelInfo * ci, char *param);
static int do_set_signkick(User * u, ChannelInfo * ci, char *param);
static int do_set_opnotice(User * u, ChannelInfo * ci, char *param);
static int do_set_xop(User * u, ChannelInfo * ci, char *param);
static int do_set_peace(User * u, ChannelInfo * ci, char *param);
static int do_set_noexpire(User * u, ChannelInfo * ci, char *param);
static int do_xop(User * u, char *xname, int xlev, int *xmsgs);
static int do_aop(User * u);
#ifdef HAS_HALFOP
static int do_hop(User * u);
#endif
static int do_sop(User * u);
static int do_vop(User * u);
static int do_access(User * u);
static int do_akick(User * u);
static int do_info(User * u);
static int do_list(User * u);
static int do_invite(User * u);
static int do_levels(User * u);
static int do_util(User * u, CSModeUtil * util);
static int do_op(User * u);
static int do_deop(User * u);
static int do_voice(User * u);
static int do_devoice(User * u);
#ifdef HAS_HALFOP
static int do_halfop(User * u);
static int do_dehalfop(User * u);
#endif
#ifdef IRC_UNREAL
static int do_protect(User * u);
static int do_deprotect(User * u);
static int do_owner(User * u);
static int do_deowner(User * u);
#endif
#ifdef IRC_VIAGRA
static int do_protect(User * u);
static int do_deprotect(User * u);
static int do_owner(User * u);
static int do_deowner(User * u);
#endif
#ifdef IRC_ULTIMATE3
static int do_protect(User * u);
static int do_deprotect(User * u);
#endif
static int do_cs_kick(User * u);
static int do_ban(User * u);
static int do_cs_topic(User * u);
static int do_unban(User * u);
static int do_clear(User * u);
static int do_getkey(User * u);
static int do_getpass(User * u);
static int do_sendpass(User * u);
static int do_forbid(User * u);
static int do_suspend(User * u);
static int do_unsuspend(User * u);
static int do_status(User * u);
void moduleAddChanServCmds(void);
/*************************************************************************/
/* *INDENT-OFF* */
void moduleAddChanServCmds(void) {
    Command *c;
    c = createCommand("HELP",     do_help,     NULL,  -1,                       -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("REGISTER", do_register, NULL,  CHAN_HELP_REGISTER,       -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("IDENTIFY", do_identify, NULL,  CHAN_HELP_IDENTIFY,       -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("LOGOUT",   do_logout,   NULL,  -1,CHAN_HELP_LOGOUT, CHAN_SERVADMIN_HELP_LOGOUT,CHAN_SERVADMIN_HELP_LOGOUT, CHAN_SERVADMIN_HELP_LOGOUT); addCoreCommand(CHANSERV,c);
    c = createCommand("DROP",     do_drop,     NULL,  -1,CHAN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP,CHAN_SERVADMIN_HELP_DROP, CHAN_SERVADMIN_HELP_DROP); addCoreCommand(CHANSERV,c);
    c = createCommand("SET",      do_set,      NULL,  CHAN_HELP_SET,-1, CHAN_SERVADMIN_HELP_SET,CHAN_SERVADMIN_HELP_SET, CHAN_SERVADMIN_HELP_SET); addCoreCommand(CHANSERV,c);
    c = createCommand("SET FOUNDER",    NULL,  NULL,  CHAN_HELP_SET_FOUNDER,    -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET SUCCESSOR",  NULL,  NULL,  CHAN_HELP_SET_SUCCESSOR,  -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET PASSWORD",   NULL,  NULL,  CHAN_HELP_SET_PASSWORD,   -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET DESC",       NULL,  NULL,  CHAN_HELP_SET_DESC,       -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET URL",        NULL,  NULL,  CHAN_HELP_SET_URL,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET EMAIL",      NULL,  NULL,  CHAN_HELP_SET_EMAIL,      -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET ENTRYMSG",   NULL,  NULL,  CHAN_HELP_SET_ENTRYMSG,   -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET BANTYPE",    NULL,  NULL,  CHAN_HELP_SET_BANTYPE,    -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET PRIVATE",    NULL,  NULL,  CHAN_HELP_SET_PRIVATE,    -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET KEEPTOPIC",  NULL,  NULL,  CHAN_HELP_SET_KEEPTOPIC,  -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET TOPICLOCK",  NULL,  NULL,  CHAN_HELP_SET_TOPICLOCK,  -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET MLOCK",      NULL,  NULL,  CHAN_HELP_SET_MLOCK,      -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET RESTRICTED", NULL,  NULL,  CHAN_HELP_SET_RESTRICTED, -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET SECURE",     NULL,  NULL,  CHAN_HELP_SET_SECURE,     -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET SECUREOPS",  NULL,  NULL,  CHAN_HELP_SET_SECUREOPS,  -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET SECUREFOUNDER",	NULL,  NULL,  CHAN_HELP_SET_SECUREFOUNDER,  -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET SIGNKICK",   NULL,  NULL,  CHAN_HELP_SET_SIGNKICK,   -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET OPNOTICE",   NULL,  NULL,  CHAN_HELP_SET_OPNOTICE,   -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET XOP",        NULL,  NULL,  CHAN_HELP_SET_XOP,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET PEACE",      NULL,  NULL,  CHAN_HELP_SET_PEACE,      -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SET NOEXPIRE",   NULL,  NULL,  -1, -1,CHAN_SERVADMIN_HELP_SET_NOEXPIRE,CHAN_SERVADMIN_HELP_SET_NOEXPIRE,CHAN_SERVADMIN_HELP_SET_NOEXPIRE); addCoreCommand(CHANSERV,c);
    c = createCommand("AOP",      do_aop,      NULL,  CHAN_HELP_AOP,            -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#ifdef HAS_HALFOP
    c = createCommand("HOP",      do_hop,      NULL,  CHAN_HELP_HOP,            -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#endif
    c = createCommand("SOP",      do_sop,      NULL,  CHAN_HELP_SOP,            -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("VOP",      do_vop,      NULL,  CHAN_HELP_VOP,            -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("ACCESS",   do_access,   NULL,  CHAN_HELP_ACCESS,         -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("ACCESS LEVELS",  NULL,  NULL,  CHAN_HELP_ACCESS_LEVELS,  -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("AKICK",    do_akick,    NULL,  CHAN_HELP_AKICK,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("LEVELS",   do_levels,   NULL,  CHAN_HELP_LEVELS,         -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("INFO",     do_info,     NULL,  CHAN_HELP_INFO,-1, CHAN_SERVADMIN_HELP_INFO, CHAN_SERVADMIN_HELP_INFO,CHAN_SERVADMIN_HELP_INFO); addCoreCommand(CHANSERV,c);
    c = createCommand("LIST",     do_list,     NULL,  -1,CHAN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST,CHAN_SERVADMIN_HELP_LIST, CHAN_SERVADMIN_HELP_LIST); addCoreCommand(CHANSERV,c);
    c = createCommand("OP",       do_op,       NULL,  CHAN_HELP_OP,             -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEOP",     do_deop,     NULL,  CHAN_HELP_DEOP,           -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("VOICE",    do_voice,    NULL,  CHAN_HELP_VOICE,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEVOICE",  do_devoice,  NULL,  CHAN_HELP_DEVOICE,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#ifdef HAS_HALFOP
    c = createCommand("HALFOP",   do_halfop,   NULL,  CHAN_HELP_HALFOP,         -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEHALFOP", do_dehalfop, NULL,  CHAN_HELP_DEHALFOP,       -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#endif
#ifdef IRC_UNREAL
    c = createCommand("PROTECT",  do_protect,  NULL,  CHAN_HELP_PROTECT,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEPROTECT",do_deprotect,NULL,  CHAN_HELP_DEPROTECT,      -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("OWNER",    do_owner,    NULL,  CHAN_HELP_OWNER,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEOWNER",  do_deowner,  NULL,  CHAN_HELP_DEOWNER,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#endif
#ifdef IRC_VIAGRA
    c = createCommand("PROTECT",  do_protect,  NULL,  CHAN_HELP_PROTECT,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEPROTECT",do_deprotect,NULL,  CHAN_HELP_DEPROTECT,      -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("OWNER",    do_owner,    NULL,  CHAN_HELP_OWNER,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEOWNER",  do_deowner,  NULL,  CHAN_HELP_DEOWNER,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#endif
#ifdef IRC_ULTIMATE3
    c = createCommand("ADMIN",  do_protect,  NULL,  CHAN_HELP_PROTECT,        -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("DEADMIN",do_deprotect,NULL,  CHAN_HELP_DEPROTECT,      -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
#endif
    c = createCommand("KICK",     do_cs_kick,  NULL,  CHAN_HELP_KICK,           -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("BAN",      do_ban,      NULL,  CHAN_HELP_BAN,            -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("TOPIC",    do_cs_topic, NULL,  CHAN_HELP_TOPIC,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("INVITE",   do_invite,   NULL,  CHAN_HELP_INVITE,         -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("UNBAN",    do_unban,    NULL,  CHAN_HELP_UNBAN,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("CLEAR",    do_clear,    NULL,  CHAN_HELP_CLEAR,          -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("GETKEY",   do_getkey,   NULL,  CHAN_HELP_GETKEY,         -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("SENDPASS", do_sendpass, NULL,  CHAN_HELP_SENDPASS,       -1,-1,-1,-1); addCoreCommand(CHANSERV,c);
    c = createCommand("GETPASS",  do_getpass,  is_services_admin,  -1,-1, CHAN_SERVADMIN_HELP_GETPASS,CHAN_SERVADMIN_HELP_GETPASS, CHAN_SERVADMIN_HELP_GETPASS); addCoreCommand(CHANSERV,c);
    c = createCommand("FORBID",   do_forbid,   is_services_admin,  -1,-1, CHAN_SERVADMIN_HELP_FORBID,CHAN_SERVADMIN_HELP_FORBID, CHAN_SERVADMIN_HELP_FORBID); addCoreCommand(CHANSERV,c);
    c = createCommand("SUSPEND",   do_suspend,   is_services_admin,  -1,-1, CHAN_SERVADMIN_HELP_SUSPEND,CHAN_SERVADMIN_HELP_SUSPEND, CHAN_SERVADMIN_HELP_SUSPEND); addCoreCommand(CHANSERV,c);
    c = createCommand("UNSUSPEND",   do_unsuspend,   is_services_admin,  -1,-1, CHAN_SERVADMIN_HELP_UNSUSPEND,CHAN_SERVADMIN_HELP_UNSUSPEND, CHAN_SERVADMIN_HELP_UNSUSPEND); addCoreCommand(CHANSERV,c);
    c = createCommand("STATUS",   do_status,   is_services_admin,  -1,-1, CHAN_SERVADMIN_HELP_STATUS,CHAN_SERVADMIN_HELP_STATUS, CHAN_SERVADMIN_HELP_STATUS); addCoreCommand(CHANSERV,c);
}

/* *INDENT-ON* */
/*************************************************************************/
/*************************************************************************/

/* Returns modes for mlock in a nice way. */

static char *get_mlock_modes(ChannelInfo * ci, int complete)
{
    static char res[BUFSIZE];

    char *end = res;

    if (ci->mlock_on || ci->mlock_off) {
        int n = 0;
        CBModeInfo *cbmi = cbmodeinfos;

        if (ci->mlock_on) {
            *end++ = '+';
            n++;

            do {
                if (ci->mlock_on & cbmi->flag)
                    *end++ = cbmi->mode;
            } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);

            cbmi = cbmodeinfos;
        }

        if (ci->mlock_off) {
            *end++ = '-';
            n++;

            do {
                if (ci->mlock_off & cbmi->flag)
                    *end++ = cbmi->mode;
            } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);

            cbmi = cbmodeinfos;
        }

        if (ci->mlock_on && complete) {
            do {
                if (cbmi->csgetvalue && (ci->mlock_on & cbmi->flag)) {
                    char *value = cbmi->csgetvalue(ci);

                    if (value) {
                        *end++ = ' ';
                        while (*value)
                            *end++ = *value++;
                    }
                }
            } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1);
        }
    }

    *end = 0;

    return res;
}

/* Display total number of registered channels and info about each; or, if
 * a specific channel is given, display information about that channel
 * (like /msg ChanServ INFO <channel>).  If count_only != 0, then only
 * display the number of registered channels (the channel parameter is
 * ignored).
 */

void listchans(int count_only, const char *chan)
{
    int count = 0;
    ChannelInfo *ci;
    int i;

    if (count_only) {

        for (i = 0; i < 256; i++) {
            for (ci = chanlists[i]; ci; ci = ci->next)
                count++;
        }
        printf("%d channels registered.\n", count);

    } else if (chan) {

        struct tm *tm;
        char buf[BUFSIZE];

        if (!(ci = cs_findchan(chan))) {
            printf("Channel %s not registered.\n", chan);
            return;
        }
        if (ci->flags & CI_VERBOTEN) {
            printf("Channel %s is FORBIDden.\n", ci->name);
        } else {
            printf("Information about channel %s:\n", ci->name);
            printf("        Founder: %s\n", ci->founder->display);
            printf("    Description: %s\n", ci->desc);
            tm = localtime(&ci->time_registered);
            strftime(buf, sizeof(buf),
                     getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
            printf("     Registered: %s\n", buf);
            tm = localtime(&ci->last_used);
            strftime(buf, sizeof(buf),
                     getstring(NULL, STRFTIME_DATE_TIME_FORMAT), tm);
            printf("      Last used: %s\n", buf);
            if (ci->last_topic) {
                printf("     Last topic: %s\n", ci->last_topic);
                printf("   Topic set by: %s\n", ci->last_topic_setter);
            }
            if (ci->url)
                printf("            URL: %s\n", ci->url);
            if (ci->email)
                printf(" E-mail address: %s\n", ci->email);
            printf("        Options: ");
            if (!ci->flags) {
                printf("None\n");
            } else {
                int need_comma = 0;
                static const char commastr[] = ", ";
                if (ci->flags & CI_PRIVATE) {
                    printf("Private");
                    need_comma = 1;
                }
                if (ci->flags & CI_KEEPTOPIC) {
                    printf("%sTopic Retention",
                           need_comma ? commastr : "");
                    need_comma = 1;
                }
                if (ci->flags & CI_TOPICLOCK) {
                    printf("%sTopic Lock", need_comma ? commastr : "");
                    need_comma = 1;
                }
                if (ci->flags & CI_SECUREOPS) {
                    printf("%sSecure Ops", need_comma ? commastr : "");
                    need_comma = 1;
                }
                if (ci->flags & CI_RESTRICTED) {
                    printf("%sRestricted Access",
                           need_comma ? commastr : "");
                    need_comma = 1;
                }
                if (ci->flags & CI_SECURE) {
                    printf("%sSecure", need_comma ? commastr : "");
                    need_comma = 1;
                }
                if (ci->flags & CI_NO_EXPIRE) {
                    printf("%sNo Expire", need_comma ? commastr : "");
                    need_comma = 1;
                }
                printf("\n");
            }
            if (ci->mlock_on || ci->mlock_off)
                printf("      Mode lock: %s\n", get_mlock_modes(ci, 1));
        }

    } else {

        for (i = 0; i < 256; i++) {
            for (ci = chanlists[i]; ci; ci = ci->next) {
                printf("  %s %-20s  %s\n",
                       ci->flags & CI_NO_EXPIRE ? "!" : " ", ci->name,
                       ci->
                       flags & CI_VERBOTEN ? "Disallowed (FORBID)" : ci->
                       desc);
                count++;
            }
        }
        printf("%d channels registered.\n", count);

    }
}

/*************************************************************************/

/* Return information on memory use.  Assumes pointers are valid. */

void get_chanserv_stats(long *nrec, long *memuse)
{
    long count = 0, mem = 0;
    int i, j;
    ChannelInfo *ci;

    for (i = 0; i < 256; i++) {
        for (ci = chanlists[i]; ci; ci = ci->next) {
            count++;
            mem += sizeof(*ci);
            if (ci->desc)
                mem += strlen(ci->desc) + 1;
            if (ci->url)
                mem += strlen(ci->url) + 1;
            if (ci->email)
                mem += strlen(ci->email) + 1;
            mem += ci->accesscount * sizeof(ChanAccess);
            mem += ci->akickcount * sizeof(AutoKick);
            for (j = 0; j < ci->akickcount; j++) {
                if (!(ci->akick[j].flags & AK_ISNICK)
                    && ci->akick[j].u.mask)
                    mem += strlen(ci->akick[j].u.mask) + 1;
                if (ci->akick[j].reason)
                    mem += strlen(ci->akick[j].reason) + 1;
                if (ci->akick[j].creator)
                    mem += strlen(ci->akick[j].creator) + 1;
            }
            if (ci->mlock_key)
                mem += strlen(ci->mlock_key) + 1;
#ifdef HAS_FMODE
            if (ci->mlock_flood)
                mem += strlen(ci->mlock_flood) + 1;
#endif
#ifdef HAS_LMODE
            if (ci->mlock_redirect)
                mem += strlen(ci->mlock_redirect) + 1;
#endif
            if (ci->last_topic)
                mem += strlen(ci->last_topic) + 1;
            if (ci->entry_message)
                mem += strlen(ci->entry_message) + 1;
            if (ci->forbidby)
                mem += strlen(ci->forbidby) + 1;
            if (ci->forbidreason)
                mem += strlen(ci->forbidreason) + 1;
            if (ci->levels)
                mem += sizeof(*ci->levels) * CA_SIZE;
            mem += ci->memos.memocount * sizeof(Memo);
            for (j = 0; j < ci->memos.memocount; j++) {
                if (ci->memos.memos[j].text)
                    mem += strlen(ci->memos.memos[j].text) + 1;
            }
            if (ci->ttb)
                mem += sizeof(*ci->ttb) * TTB_SIZE;
            mem += ci->bwcount * sizeof(BadWord);
            for (j = 0; j < ci->bwcount; j++)
                if (ci->badwords[j].word)
                    mem += strlen(ci->badwords[j].word) + 1;
        }
    }
    *nrec = count;
    *memuse = mem;
}

/*************************************************************************/
/*************************************************************************/

/* ChanServ initialization. */

void cs_init(void)
{
    Command *cmd;
    moduleAddChanServCmds();
    cmd = findCommand(CHANSERV, "REGISTER");
    if (cmd)
        cmd->help_param1 = s_NickServ;
    cmd = findCommand(CHANSERV, "SET SECURE");
    if (cmd)
        cmd->help_param1 = s_NickServ;
    cmd = findCommand(CHANSERV, "SET SUCCESSOR");
    if (cmd)
        cmd->help_param1 = (char *) (long) CSMaxReg;
}

/*************************************************************************/

/* Main ChanServ routine. */

void chanserv(User * u, char *buf)
{
    char *cmd, *s;

    cmd = strtok(buf, " ");

    if (!cmd) {
        return;
    } else if (stricmp(cmd, "\1PING") == 0) {
        if (!(s = strtok(NULL, "")))
            s = "\1";
        notice(s_ChanServ, u->nick, "\1PING %s", s);
    } else if (skeleton) {
        notice_lang(s_ChanServ, u, SERVICE_OFFLINE, s_ChanServ);
    } else {
        mod_run_cmd(s_ChanServ, u, CHANSERV, cmd);
    }
}

/*************************************************************************/

/* Load/save data files. */


#define SAFE(x) do {					\
    if ((x) < 0) {					\
	if (!forceload)					\
	    fatal("Read error on %s", ChanDBName);	\
	failed = 1;					\
	break;						\
    }							\
} while (0)

void load_cs_dbase(void)
{
    dbFILE *f;
    int ver, i, j, c;
    ChannelInfo *ci, **last, *prev;
    int failed = 0;

    if (!(f = open_db(s_ChanServ, ChanDBName, "r", CHAN_VERSION)))
        return;

    ver = get_file_version(f);

    for (i = 0; i < 256 && !failed; i++) {
        int16 tmp16;
        int32 tmp32;
        int n_levels;
        char *s;
        NickAlias *na;

        last = &chanlists[i];
        prev = NULL;
        while ((c = getc_db(f)) != 0) {
            if (c != 1)
                fatal("Invalid format in %s", ChanDBName);
            ci = scalloc(sizeof(ChannelInfo), 1);
            *last = ci;
            last = &ci->next;
            ci->prev = prev;
            prev = ci;
            SAFE(read_buffer(ci->name, f));
            SAFE(read_string(&s, f));
            if (s) {
                if (ver >= 13)
                    ci->founder = findcore(s);
                else {
                    na = findnick(s);
                    if (na)
                        ci->founder = na->nc;
                    else
                        ci->founder = NULL;
                }
                free(s);
            } else
                ci->founder = NULL;
            if (ver >= 7) {
                SAFE(read_string(&s, f));
                if (s) {
                    if (ver >= 13)
                        ci->successor = findcore(s);
                    else {
                        na = findnick(s);
                        if (na)
                            ci->successor = na->nc;
                        else
                            ci->successor = NULL;
                    }
                    free(s);
                } else
                    ci->successor = NULL;
            } else {
                ci->successor = NULL;
            }
            SAFE(read_buffer(ci->founderpass, f));
            SAFE(read_string(&ci->desc, f));
            if (!ci->desc)
                ci->desc = sstrdup("");
            SAFE(read_string(&ci->url, f));
            SAFE(read_string(&ci->email, f));
            SAFE(read_int32(&tmp32, f));
            ci->time_registered = tmp32;
            SAFE(read_int32(&tmp32, f));
            ci->last_used = tmp32;
            SAFE(read_string(&ci->last_topic, f));
            SAFE(read_buffer(ci->last_topic_setter, f));
            SAFE(read_int32(&tmp32, f));
            ci->last_topic_time = tmp32;
            SAFE(read_int32(&ci->flags, f));
#ifdef USE_ENCRYPTION
            if (!(ci->flags & (CI_ENCRYPTEDPW | CI_VERBOTEN))) {
                if (debug)
                    alog("debug: %s: encrypting password for %s on load",
                         s_ChanServ, ci->name);
                if (encrypt_in_place(ci->founderpass, PASSMAX) < 0)
                    fatal("%s: load database: Can't encrypt %s password!",
                          s_ChanServ, ci->name);
                ci->flags |= CI_ENCRYPTEDPW;
            }
#else
            if (ci->flags & CI_ENCRYPTEDPW) {
                /* Bail: it makes no sense to continue with encrypted
                 * passwords, since we won't be able to verify them */
                fatal("%s: load database: password for %s encrypted "
                      "but encryption disabled, aborting",
                      s_ChanServ, ci->name);
            }
#endif
            /* Leaveops cleanup */
            if (ver <= 13 && (ci->flags & 0x00000020))
                ci->flags &= ~0x00000020;
            /* Temporary flags cleanup */
            ci->flags &= ~CI_INHABIT;

            if (ver >= 9) {
                SAFE(read_string(&ci->forbidby, f));
                SAFE(read_string(&ci->forbidreason, f));
            } else {
                ci->forbidreason = NULL;
                ci->forbidby = NULL;
            }
            if (ver >= 9)
                SAFE(read_int16(&tmp16, f));
            else
                tmp16 = CSDefBantype;
            ci->bantype = tmp16;
            SAFE(read_int16(&tmp16, f));
            n_levels = tmp16;
            ci->levels = scalloc(2 * CA_SIZE, 1);
            reset_levels(ci);
            for (j = 0; j < n_levels; j++) {
                if (j < CA_SIZE)
                    SAFE(read_int16(&ci->levels[j], f));
                else
                    SAFE(read_int16(&tmp16, f));
            }
            /* To avoid levels list silly hacks */
            if (ver < 10)
                ci->levels[CA_OPDEOPME] = ci->levels[CA_OPDEOP];
            if (ver < 11) {
                ci->levels[CA_KICKME] = ci->levels[CA_OPDEOP];
                ci->levels[CA_KICK] = ci->levels[CA_OPDEOP];
            }
            if (ver < 15) {

                /* Old Ultimate levels import */
                /* We now conveniently use PROTECT internals for Ultimate's ADMIN support - ShadowMaster */
                /* Doh, must of course be done before we change the values were trying to import - ShadowMaster */
                ci->levels[CA_AUTOPROTECT] = ci->levels[32];
                ci->levels[CA_PROTECTME] = ci->levels[33];
                ci->levels[CA_PROTECT] = ci->levels[34];

                ci->levels[CA_BANME] = ci->levels[CA_OPDEOP];
                ci->levels[CA_BAN] = ci->levels[CA_OPDEOP];
                ci->levels[CA_TOPIC] = ACCESS_INVALID;


            }

            SAFE(read_int16(&ci->accesscount, f));
            if (ci->accesscount) {
                ci->access = scalloc(ci->accesscount, sizeof(ChanAccess));
                for (j = 0; j < ci->accesscount; j++) {
                    SAFE(read_int16(&ci->access[j].in_use, f));
                    if (ci->access[j].in_use) {
                        SAFE(read_int16(&ci->access[j].level, f));
                        SAFE(read_string(&s, f));
                        if (s) {
                            if (ver >= 13)
                                ci->access[j].nc = findcore(s);
                            else {
                                na = findnick(s);
                                if (na)
                                    ci->access[j].nc = na->nc;
                                else
                                    ci->access[j].nc = NULL;
                            }
                            free(s);
                        }
                        if (ci->access[j].nc == NULL)
                            ci->access[j].in_use = 0;
                        if (ver >= 11) {
                            SAFE(read_int32(&tmp32, f));
                            ci->access[j].last_seen = tmp32;
                        } else {
                            ci->access[j].last_seen = 0;        /* Means we have never seen the user */
                        }
                    }
                }
            } else {
                ci->access = NULL;
            }

            SAFE(read_int16(&ci->akickcount, f));
            if (ci->akickcount) {
                ci->akick = scalloc(ci->akickcount, sizeof(AutoKick));
                for (j = 0; j < ci->akickcount; j++) {
                    if (ver >= 15) {
                        SAFE(read_int16(&ci->akick[j].flags, f));
                    } else {
                        SAFE(read_int16(&tmp16, f));
                        if (tmp16)
                            ci->akick[j].flags |= AK_USED;
                    }
                    if (ci->akick[j].flags & AK_USED) {
                        if (ver < 15) {
                            SAFE(read_int16(&tmp16, f));
                            if (tmp16)
                                ci->akick[j].flags |= AK_ISNICK;
                        }
                        SAFE(read_string(&s, f));
                        if (ci->akick[j].flags & AK_ISNICK) {
                            if (ver >= 13) {
                                ci->akick[j].u.nc = findcore(s);
                            } else {
                                na = findnick(s);
                                if (na)
                                    ci->akick[j].u.nc = na->nc;
                                else
                                    ci->akick[j].u.nc = NULL;
                            }
                            if (!ci->akick[j].u.nc)
                                ci->akick[j].flags &= ~AK_USED;
                            free(s);
                        } else {
                            ci->akick[j].u.mask = s;
                        }
                        SAFE(read_string(&s, f));
                        if (ci->akick[j].flags & AK_USED)
                            ci->akick[j].reason = s;
                        else if (s)
                            free(s);
                        if (ver >= 9) {
                            SAFE(read_string(&s, f));
                            if (ci->akick[j].flags & AK_USED) {
                                ci->akick[j].creator = s;
                            } else if (s) {
                                free(s);
                            }
                            SAFE(read_int32(&tmp32, f));
                            if (ci->akick[j].flags & AK_USED)
                                ci->akick[j].addtime = tmp32;
                        } else {
                            ci->akick[j].creator = NULL;
                            ci->akick[j].addtime = 0;
                        }
                    }

                    /* Bugfix */
                    if ((ver == 15) && ci->akick[j].flags > 8) {
                        ci->akick[j].flags = 0;
                        ci->akick[j].u.nc = NULL;
                        ci->akick[j].u.nc = NULL;
                        ci->akick[j].addtime = 0;
                        ci->akick[j].creator = NULL;
                        ci->akick[j].reason = NULL;
                    }
                }
            } else {
                ci->akick = NULL;
            }

            if (ver >= 10) {
                SAFE(read_int32(&ci->mlock_on, f));
                SAFE(read_int32(&ci->mlock_off, f));
            } else {
                SAFE(read_int16(&tmp16, f));
                ci->mlock_on = tmp16;
                SAFE(read_int16(&tmp16, f));
                ci->mlock_off = tmp16;
            }
            SAFE(read_int32(&ci->mlock_limit, f));
            SAFE(read_string(&ci->mlock_key, f));
            if (ver >= 10) {
#ifdef HAS_FMODE
                SAFE(read_string(&ci->mlock_flood, f));
#else
                SAFE(read_string(&s, f));
                if (s)
                    free(s);
#endif
#ifdef HAS_LMODE
                SAFE(read_string(&ci->mlock_redirect, f));
#else
                SAFE(read_string(&s, f));
                if (s)
                    free(s);
#endif
            }

            SAFE(read_int16(&ci->memos.memocount, f));
            SAFE(read_int16(&ci->memos.memomax, f));
            if (ci->memos.memocount) {
                Memo *memos;
                memos = scalloc(sizeof(Memo) * ci->memos.memocount, 1);
                ci->memos.memos = memos;
                for (j = 0; j < ci->memos.memocount; j++, memos++) {
                    SAFE(read_int32(&memos->number, f));
                    SAFE(read_int16(&memos->flags, f));
                    SAFE(read_int32(&tmp32, f));
                    memos->time = tmp32;
                    SAFE(read_buffer(memos->sender, f));
                    SAFE(read_string(&memos->text, f));
                }
            }

            SAFE(read_string(&ci->entry_message, f));

            ci->c = NULL;

            /* Some cleanup */
            if (ver <= 11) {
                /* Cleanup: Founder must be != than successor */
                if (!(ci->flags & CI_VERBOTEN)
                    && ci->successor == ci->founder) {
                    alog("Warning: founder and successor of %s are equal. Cleaning up.", ci->name);
                    ci->successor = NULL;
                }
            }

            /* BotServ options */

            if (ver >= 8) {
                int n_ttb;

                SAFE(read_string(&s, f));
                if (s) {
                    ci->bi = findbot(s);
                    free(s);
                } else
                    ci->bi = NULL;

                SAFE(read_int32(&tmp32, f));
                ci->botflags = tmp32;
                SAFE(read_int16(&tmp16, f));
                n_ttb = tmp16;
                ci->ttb = scalloc(2 * TTB_SIZE, 1);
                for (j = 0; j < n_ttb; j++) {
                    if (j < TTB_SIZE)
                        SAFE(read_int16(&ci->ttb[j], f));
                    else
                        SAFE(read_int16(&tmp16, f));
                }
                for (j = n_ttb; j < TTB_SIZE; j++)
                    ci->ttb[j] = 0;
                SAFE(read_int16(&tmp16, f));
                ci->capsmin = tmp16;
                SAFE(read_int16(&tmp16, f));
                ci->capspercent = tmp16;
                SAFE(read_int16(&tmp16, f));
                ci->floodlines = tmp16;
                SAFE(read_int16(&tmp16, f));
                ci->floodsecs = tmp16;
                SAFE(read_int16(&tmp16, f));
                ci->repeattimes = tmp16;

                SAFE(read_int16(&ci->bwcount, f));
                if (ci->bwcount) {
                    ci->badwords = scalloc(ci->bwcount, sizeof(BadWord));
                    for (j = 0; j < ci->bwcount; j++) {
                        SAFE(read_int16(&ci->badwords[j].in_use, f));
                        if (ci->badwords[j].in_use) {
                            SAFE(read_string(&ci->badwords[j].word, f));
                            SAFE(read_int16(&ci->badwords[j].type, f));
                        }
                    }
                } else {
                    ci->badwords = NULL;
                }
            } else {
                ci->bi = NULL;
                ci->botflags = 0;
                ci->ttb = scalloc(2 * TTB_SIZE, 1);
                for (j = 0; j < TTB_SIZE; j++)
                    ci->ttb[j] = 0;
                ci->bwcount = 0;
                ci->badwords = NULL;
            }

        }                       /* while (getc_db(f) != 0) */

        *last = NULL;

    }                           /* for (i) */

    close_db(f);

    /* Check for non-forbidden channels with no founder.
       Makes also other essential tasks. */
    for (i = 0; i < 256; i++) {
        ChannelInfo *next;
        for (ci = chanlists[i]; ci; ci = next) {
            next = ci->next;
            if (!(ci->flags & CI_VERBOTEN) && !ci->founder) {
                alog("%s: database load: Deleting founderless channel %s",
                     s_ChanServ, ci->name);
                delchan(ci);
                continue;
            }
            if (ver < 13) {
                ChanAccess *access, *access2;
                AutoKick *akick, *akick2;
                int k;

                if (ci->flags & CI_VERBOTEN)
                    continue;
                /* Need to regenerate the channel count for the founder */
                ci->founder->channelcount++;
                /* Check for eventual double entries in access/akick lists. */
                for (j = 0, access = ci->access; j < ci->accesscount;
                     j++, access++) {
                    if (!access->in_use)
                        continue;
                    for (k = 0, access2 = ci->access; k < j;
                         k++, access2++) {
                        if (access2->in_use && access2->nc == access->nc) {
                            alog("%s: deleting %s channel access entry of %s because it is already in the list (this is OK).", s_ChanServ, access->nc->display, ci->name);
                            memset(access, 0, sizeof(ChanAccess));
                            break;
                        }
                    }
                }
                for (j = 0, akick = ci->akick; j < ci->akickcount;
                     j++, akick++) {
                    if (!(akick->flags & AK_USED)
                        || !(akick->flags & AK_ISNICK))
                        continue;
                    for (k = 0, akick2 = ci->akick; k < j; k++, akick2++) {
                        if ((akick2->flags & AK_USED)
                            && (akick2->flags & AK_ISNICK)
                            && akick2->u.nc == akick->u.nc) {
                            alog("%s: deleting %s channel akick entry of %s because it is already in the list (this is OK).", s_ChanServ, akick->u.nc->display, ci->name);
                            if (akick->reason)
                                free(akick->reason);
                            if (akick->creator)
                                free(akick->creator);
                            memset(akick, 0, sizeof(AutoKick));
                            break;
                        }
                    }
                }
            }
        }
    }
}

#undef SAFE

/*************************************************************************/

#define SAFE(x) do {						\
    if ((x) < 0) {						\
	restore_db(f);						\
	log_perror("Write error on %s", ChanDBName);		\
	if (time(NULL) - lastwarn > WarningTimeout) {		\
	    wallops(NULL, "Write error on %s: %s", ChanDBName,	\
			strerror(errno));			\
	    lastwarn = time(NULL);				\
	}							\
	return;							\
    }								\
} while (0)

void save_cs_dbase(void)
{
    dbFILE *f;
    int i, j;
    ChannelInfo *ci;
    Memo *memos;
    static time_t lastwarn = 0;

    if (!(f = open_db(s_ChanServ, ChanDBName, "w", CHAN_VERSION)))
        return;

    for (i = 0; i < 256; i++) {
        int16 tmp16;

        for (ci = chanlists[i]; ci; ci = ci->next) {
            SAFE(write_int8(1, f));
            SAFE(write_buffer(ci->name, f));
            if (ci->founder)
                SAFE(write_string(ci->founder->display, f));
            else
                SAFE(write_string(NULL, f));
            if (ci->successor)
                SAFE(write_string(ci->successor->display, f));
            else
                SAFE(write_string(NULL, f));
            SAFE(write_buffer(ci->founderpass, f));
            SAFE(write_string(ci->desc, f));
            SAFE(write_string(ci->url, f));
            SAFE(write_string(ci->email, f));
            SAFE(write_int32(ci->time_registered, f));
            SAFE(write_int32(ci->last_used, f));
            SAFE(write_string(ci->last_topic, f));
            SAFE(write_buffer(ci->last_topic_setter, f));
            SAFE(write_int32(ci->last_topic_time, f));
            SAFE(write_int32(ci->flags, f));
            SAFE(write_string(ci->forbidby, f));
            SAFE(write_string(ci->forbidreason, f));
            SAFE(write_int16(ci->bantype, f));

            tmp16 = CA_SIZE;
            SAFE(write_int16(tmp16, f));
            for (j = 0; j < CA_SIZE; j++)
                SAFE(write_int16(ci->levels[j], f));

            SAFE(write_int16(ci->accesscount, f));
            for (j = 0; j < ci->accesscount; j++) {
                SAFE(write_int16(ci->access[j].in_use, f));
                if (ci->access[j].in_use) {
                    SAFE(write_int16(ci->access[j].level, f));
                    SAFE(write_string(ci->access[j].nc->display, f));
                    SAFE(write_int32(ci->access[j].last_seen, f));
                }
            }

            SAFE(write_int16(ci->akickcount, f));
            for (j = 0; j < ci->akickcount; j++) {
                SAFE(write_int16(ci->akick[j].flags, f));
                if (ci->akick[j].flags & AK_USED) {
                    if (ci->akick[j].flags & AK_ISNICK)
                        SAFE(write_string(ci->akick[j].u.nc->display, f));
                    else
                        SAFE(write_string(ci->akick[j].u.mask, f));
                    SAFE(write_string(ci->akick[j].reason, f));
                    SAFE(write_string(ci->akick[j].creator, f));
                    SAFE(write_int32(ci->akick[j].addtime, f));
                }
            }

            SAFE(write_int32(ci->mlock_on, f));
            SAFE(write_int32(ci->mlock_off, f));
            SAFE(write_int32(ci->mlock_limit, f));
            SAFE(write_string(ci->mlock_key, f));
#ifdef HAS_FMODE
            SAFE(write_string(ci->mlock_flood, f));
#else
            SAFE(write_string(NULL, f));
#endif
#ifdef HAS_LMODE
            SAFE(write_string(ci->mlock_redirect, f));
#else
            SAFE(write_string(NULL, f));
#endif

            SAFE(write_int16(ci->memos.memocount, f));
            SAFE(write_int16(ci->memos.memomax, f));
            memos = ci->memos.memos;
            for (j = 0; j < ci->memos.memocount; j++, memos++) {
                SAFE(write_int32(memos->number, f));
                SAFE(write_int16(memos->flags, f));
                SAFE(write_int32(memos->time, f));
                SAFE(write_buffer(memos->sender, f));
                SAFE(write_string(memos->text, f));
            }

            SAFE(write_string(ci->entry_message, f));

            if (ci->bi)
                SAFE(write_string(ci->bi->nick, f));
            else
                SAFE(write_string(NULL, f));

            SAFE(write_int32(ci->botflags, f));

            tmp16 = TTB_SIZE;
            SAFE(write_int16(tmp16, f));
            for (j = 0; j < TTB_SIZE; j++)
                SAFE(write_int16(ci->ttb[j], f));

            SAFE(write_int16(ci->capsmin, f));
            SAFE(write_int16(ci->capspercent, f));
            SAFE(write_int16(ci->floodlines, f));
            SAFE(write_int16(ci->floodsecs, f));
            SAFE(write_int16(ci->repeattimes, f));

            SAFE(write_int16(ci->bwcount, f));
            for (j = 0; j < ci->bwcount; j++) {
                SAFE(write_int16(ci->badwords[j].in_use, f));
                if (ci->badwords[j].in_use) {
                    SAFE(write_string(ci->badwords[j].word, f));
                    SAFE(write_int16(ci->badwords[j].type, f));
                }
            }
        }                       /* for (chanlists[i]) */

        SAFE(write_int8(0, f));

    }                           /* for (i) */

    close_db(f);

}

#undef SAFE

/*************************************************************************/

void save_cs_rdb_dbase(void)
{
#ifdef USE_RDB
    int i;
    ChannelInfo *ci;

    if (!rdb_open())
        return;

    rdb_tag_table("anope_cs_info");
    rdb_scrub_table("anope_ms_info", "serv='CHAN'");
    rdb_clear_table("anope_cs_access");
    rdb_clear_table("anope_cs_levels");
    rdb_clear_table("anope_cs_akicks");
    rdb_clear_table("anope_cs_badwords");

    for (i = 0; i < 256; i++) {
        for (ci = chanlists[i]; ci; ci = ci->next) {
            rdb_save_cs_info(ci);
        }                       /* for (chanlists[i]) */
    }                           /* for (i) */

    rdb_scrub_table("anope_cs_info", "active='0'");
    rdb_close();
#endif
}

/*************************************************************************/

/* Check the current modes on a channel; if they conflict with a mode lock,
 * fix them. */

void check_modes(Channel * c)
{
    char modebuf[64], argbuf[BUFSIZE], *end = modebuf, *end2 = argbuf;
    uint32 modes;
    ChannelInfo *ci;
    CBModeInfo *cbmi;
    CBMode *cbm;

    if (c->bouncy_modes)
        return;

    /* Check for mode bouncing */
    if (c->server_modecount >= 3 && c->chanserv_modecount >= 3) {
        wallops(NULL, "Warning: unable to set modes on channel %s.  "
                "Are your servers' U:lines configured correctly?",
                c->name);
        alog("%s: Bouncy modes on channel %s", s_ChanServ, c->name);
        c->bouncy_modes = 1;
        return;
    }

    if (c->chanserv_modetime != time(NULL)) {
        c->chanserv_modecount = 0;
        c->chanserv_modetime = time(NULL);
    }
    c->chanserv_modecount++;

    if (!(ci = c->ci)) {
#ifndef IRC_HYBRID
        if (c->mode & CMODE_r) {
            c->mode &= ~CMODE_r;
            send_cmd(whosends(ci), "MODE %s -r", c->name);
        }
#endif
        return;
    }

    modes = ~c->mode & ci->mlock_on;

    *end++ = '+';
    cbmi = cbmodeinfos;

    do {
        if (modes & cbmi->flag) {
            *end++ = cbmi->mode;
            c->mode |= cbmi->flag;

            /* Add the eventual parameter and modify the Channel structure */
            if (cbmi->getvalue && cbmi->csgetvalue) {
                char *value = cbmi->csgetvalue(ci);

                cbm = &cbmodes[(int) cbmi->mode];
                cbm->setvalue(c, value);

                if (value) {
                    *end2++ = ' ';
                    while (*value)
                        *end2++ = *value++;
                }
            }
        } else if (cbmi->getvalue && cbmi->csgetvalue
                   && (ci->mlock_on & cbmi->flag)
                   && (c->mode & cbmi->flag)) {
            char *value = cbmi->getvalue(c);
            char *csvalue = cbmi->csgetvalue(ci);

            /* Lock and actual values don't match, so fix the mode */
            if (value && csvalue && strcmp(value, csvalue)) {
                *end++ = cbmi->mode;

                cbm = &cbmodes[(int) cbmi->mode];
                cbm->setvalue(c, csvalue);

                *end2++ = ' ';
                while (*csvalue)
                    *end2++ = *csvalue++;
            }
        }
    } while ((++cbmi)->flag != 0);

    if (*(end - 1) == '+')
        end--;

    modes = c->mode & ci->mlock_off;

    if (modes) {
        *end++ = '-';
        cbmi = cbmodeinfos;

        do {
            if (modes & cbmi->flag) {
                *end++ = cbmi->mode;
                c->mode &= ~cbmi->flag;

                /* Add the eventual parameter and clean up the Channel structure */
                if (cbmi->getvalue) {
                    cbm = &cbmodes[(int) cbmi->mode];

                    if (!(cbm->flags & CBM_MINUS_NO_ARG)) {
                        char *value = cbmi->getvalue(c);

                        if (value) {
                            *end2++ = ' ';
                            while (*value)
                                *end2++ = *value++;
                        }
                    }

                    cbm->setvalue(c, NULL);
                }
            }
        } while ((++cbmi)->flag != 0);
    }

    if (end == modebuf)
        return;

    *end = 0;
    *end2 = 0;

    send_cmd(whosends(ci), "MODE %s %s%s", c->name, modebuf,
             (end2 == argbuf ? "" : argbuf));
}

/*************************************************************************/

#ifdef IRC_ULTIMATE3


int check_valid_admin(User * user, Channel * chan, int servermode)
{
    if (!chan->ci)
        return 1;

    /* They will be kicked; no need to deop, no need to update our internal struct too */
    if (chan->ci->flags & CI_VERBOTEN)
        return 0;

    if (servermode && !check_access(user, chan->ci, CA_AUTOPROTECT)) {
        notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
        send_cmd(whosends(chan->ci), "MODE %s -a %s", chan->name,
                 user->nick);
        return 0;
    }

    if (check_access(user, chan->ci, CA_AUTODEOP)) {
        send_cmd(whosends(chan->ci), "MODE %s -a %s", chan->name,
                 user->nick);
        return 0;
    }

    return 1;
}
#endif

/*************************************************************************/

/* Check whether a user is allowed to be opped on a channel; if they
 * aren't, deop them.  If serverop is 1, the +o was done by a server.
 * Return 1 if the user is allowed to be opped, 0 otherwise. */

int check_valid_op(User * user, Channel * chan, int servermode)
{
    if (!chan->ci)
        return 1;

    /* They will be kicked; no need to deop, no need to update our internal struct too */
    if (chan->ci->flags & CI_VERBOTEN)
        return 0;

    if (servermode && !check_access(user, chan->ci, CA_AUTOOP)) {
        notice_lang(s_ChanServ, user, CHAN_IS_REGISTERED, s_ChanServ);
#ifdef HAS_HALFOP
# if defined(IRC_UNREAL)
        if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
            send_cmd(whosends(chan->ci), "MODE %s -aoq %s %s %s",
                     chan->name, user->nick, user->nick, user->nick);
        } else {
            send_cmd(whosends(chan->ci), "MODE %s -ahoq %s %s %s %s",
                     chan->name, user->nick, user->nick, user->nick,
                     user->nick);
        }
# elif defined(IRC_ULTIMATE3)
        if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
            send_cmd(whosends(chan->ci), "MODE %s -ao %s %s",
                     chan->name, user->nick, user->nick);
        } else {
            send_cmd(whosends(chan->ci), "MODE %s -aoh %s %s %s",
                     chan->name, user->nick, user->nick, user->nick);
        }
# else
        if (check_access(user, chan->ci, CA_AUTOHALFOP)) {
            send_cmd(whosends(chan->ci), "MODE %s -o %s", chan->name,
                     user->nick);
        } else {
            send_cmd(whosends(chan->ci), "MODE %s -ho %s %s", chan->name,
                     user->nick, user->nick);
        }
# endif
#else
        send_cmd(whosends(chan->ci), "MODE %s -o %s", chan->name,
                 user->nick);
#endif
        return 0;
    }

    if (check_access(user, chan->ci, CA_AUTODEOP)) {
#ifdef HAS_HALFOP
# ifdef IRC_UNREAL
        send_cmd(whosends(chan->ci), "MODE %s -ahoq %s %s %s %s",
                 chan->name, user->nick, user->nick, user->nick,
                 user->nick);
# else
        send_cmd(whosends(chan->ci), "MODE %s -ho %s %s", chan->name,
                 user->nick, user->nick);
# endif
#else
        send_cmd(whosends(chan->ci), "MODE %s -o %s", chan->name,
                 user->nick);
#endif
        return 0;
    }

    return 1;
}

/*************************************************************************/

/* Check whether a user should be opped on a channel, and if so, do it.
 * Return 1 if the user was opped, 0 otherwise.  (Updates the channel's
 * last used time if the user was opped.) */

int check_should_op(User * user, const char *chan)
{
    ChannelInfo *ci = cs_findchan(chan);

    if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
        return 0;

    if ((ci->flags & CI_SECURE) && !nick_identified(user))
        return 0;

    if (check_access(user, ci, CA_AUTOOP)) {
        send_cmd(whosends(ci), "MODE %s +o %s", chan, user->nick);
        return 1;
    }

    return 0;
}

/*************************************************************************/

/* Check whether a user should be voiced on a channel, and if so, do it.
 * Return 1 if the user was voiced, 0 otherwise. */

int check_should_voice(User * user, const char *chan)
{
    ChannelInfo *ci = cs_findchan(chan);

    if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
        return 0;

    if ((ci->flags & CI_SECURE) && !nick_identified(user))
        return 0;

    if (check_access(user, ci, CA_AUTOVOICE)) {
        send_cmd(whosends(ci), "MODE %s +v %s", chan, user->nick);
        return 1;
    }

    return 0;
}

/*************************************************************************/

#ifdef HAS_HALFOP

int check_should_halfop(User * user, const char *chan)
{
    ChannelInfo *ci = cs_findchan(chan);

    if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
        return 0;

    if (check_access(user, ci, CA_AUTOHALFOP)) {
        send_cmd(whosends(ci), "MODE %s +h %s", chan, user->nick);
        return 1;
    }

    return 0;
}

#endif

/*************************************************************************/

#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)

int check_should_owner(User * user, const char *chan)
{
    ChannelInfo *ci = cs_findchan(chan);

    if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
        return 0;

    if (((ci->flags & CI_SECUREFOUNDER) && is_real_founder(user, ci))
        || (!(ci->flags & CI_SECUREFOUNDER) && is_founder(user, ci))) {
        send_cmd(whosends(ci), "MODE %s +oq %s %s", chan, user->nick,
                 user->nick);
        return 1;
    }

    return 0;
}

#endif

/*************************************************************************/

#if defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_ULTIMATE3)

int check_should_protect(User * user, const char *chan)
{
    ChannelInfo *ci = cs_findchan(chan);

    if (!ci || (ci->flags & CI_VERBOTEN) || *chan == '+')
        return 0;

    if (check_access(user, ci, CA_AUTOPROTECT)) {
        send_cmd(whosends(ci), "MODE %s +oa %s %s", chan, user->nick,
                 user->nick);
        return 1;
    }

    return 0;
}

#endif

/*************************************************************************/

/* Tiny helper routine to get ChanServ out of a channel after it went in. */

static void timeout_leave(Timeout * to)
{
    char *chan = to->data;
    ChannelInfo *ci = cs_findchan(chan);

    if (ci)                     /* Check cos the channel may be dropped in the meantime */
        ci->flags &= ~CI_INHABIT;

    send_cmd(s_ChanServ, "PART %s", chan);
    free(to->data);
}


/* Check whether a user is permitted to be on a channel.  If so, return 0;
 * else, kickban the user with an appropriate message (could be either
 * AKICK or restricted access) and return 1.  Note that this is called
 * _before_ the user is added to internal channel lists (so do_kick() is
 * not called).
 */

int check_kick(User * user, char *chan)
{
    ChannelInfo *ci = cs_findchan(chan);
    Channel *c;
    AutoKick *akick;
    int i;
    NickCore *nc;
    char *av[3];
    char mask[BUFSIZE];
    const char *reason;
    Timeout *t;

    if (!ci)
        return 0;

    if (is_oper(user) || is_services_admin(user))
        return 0;

    if (ci->flags & CI_VERBOTEN) {
        get_idealban(ci, user, mask, sizeof(mask));
        reason =
            ci->forbidreason ? ci->forbidreason : getstring(user->na,
                                                            CHAN_MAY_NOT_BE_USED);
        goto kick;
    }

    if (ci->flags & CI_SUSPENDED) {
        get_idealban(ci, user, mask, sizeof(mask));
        reason =
            ci->forbidreason ? ci->forbidreason : getstring(user->na,
                                                            CHAN_MAY_NOT_BE_USED);
        goto kick;
    }

    if (nick_recognized(user))
        nc = user->na->nc;
    else
        nc = NULL;

#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID)
    /*
     * Before we go through akick lists, see if they're excepted FIRST
     * We cannot kick excempted users that are akicked or not on the channel access list
     * as that will start services <-> server wars which ends up as a DoS against services.
     *
     * UltimateIRCd 3.x at least informs channel staff when a joining user is matching an exempt.
     */
    if (is_excepted(ci, user) == 1) {
        return 0;
    }
#endif

    for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
        if (!(akick->flags & AK_USED))
            continue;
        if ((akick->flags & AK_ISNICK && akick->u.nc == nc)
            || (!(akick->flags & AK_ISNICK)
                && match_usermask(akick->u.mask, user))) {
            if (debug >= 2)
                alog("debug: %s matched akick %s", user->nick,
                     (akick->flags & AK_ISNICK) ? akick->u.nc->
                     display : akick->u.mask);
            if (akick->flags & AK_ISNICK)
                get_idealban(ci, user, mask, sizeof(mask));
            else
                strcpy(mask, akick->u.mask);
            reason = akick->reason ? akick->reason : CSAutokickReason;
            goto kick;
        }
    }

    if (check_access(user, ci, CA_NOJOIN)) {
        get_idealban(ci, user, mask, sizeof(mask));
        reason = getstring(user->na, CHAN_NOT_ALLOWED_TO_JOIN);
        goto kick;
    }

    return 0;

  kick:
    if (debug)
        alog("debug: channel: AutoKicking %s!%s@%s from %s", user->nick,
             user->username, GetHost(user), chan);

    /* Remember that the user has not been added to our channel user list
     * yet, so we check whether the channel does not exist OR has no user
     * on it (before SJOIN would have created the channel structure, while
     * JOIN would not). */
    /* Don't check for CI_INHABIT before for the Channel record cos else
     * c may be NULL even if it exists */
    if ((!(c = findchan(chan)) || c->usercount == 0)
        && !(ci->flags & CI_INHABIT)) {
#if defined(IRC_BAHAMUT)
        send_cmd(s_ChanServ, "SJOIN %lu %s",
                 (c ? c->creation_time : time(NULL)), chan);
#elif defined(IRC_HYBRID)
        send_cmd(NULL, "SJOIN %ld %s + :@%s",
                 time(NULL), chan, s_ChanServ);
#else
        send_cmd(s_ChanServ, "JOIN %s", chan);
#endif
        t = add_timeout(CSInhabit, timeout_leave, 0);
        t->data = sstrdup(chan);
        ci->flags |= CI_INHABIT;
    }

    if (c) {
        av[0] = chan;
        av[1] = sstrdup("+b");
        av[2] = mask;
        do_cmode(whosends(ci), 3, av);
        free(av[1]);
    }

    send_cmd(whosends(ci), "MODE %s +b %s %lu", chan, mask, time(NULL));
    send_cmd(whosends(ci), "KICK %s %s :%s", chan, user->nick, reason);

    return 1;
}

/*************************************************************************/

/* Record the current channel topic in the ChannelInfo structure. */

void record_topic(const char *chan)
{
    Channel *c;
    ChannelInfo *ci;

    if (readonly)
        return;
    c = findchan(chan);
    if (!c || !(ci = c->ci))
        return;
    if (ci->last_topic)
        free(ci->last_topic);
    if (c->topic)
        ci->last_topic = sstrdup(c->topic);
    else
        ci->last_topic = NULL;
    strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
    ci->last_topic_time = c->topic_time;
}

/*************************************************************************/

/* Restore the topic in a channel when it's created, if we should. */

void restore_topic(const char *chan)
{
    Channel *c = findchan(chan);
    ChannelInfo *ci;

    if (!c || !(ci = c->ci) || !(ci->flags & CI_KEEPTOPIC))
        return;
    if (c->topic)
        free(c->topic);
    if (ci->last_topic) {
        c->topic = sstrdup(ci->last_topic);
        strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX);
        c->topic_time = ci->last_topic_time;
    } else {
        c->topic = NULL;
        strscpy(c->topic_setter, s_ChanServ, NICKMAX);
    }
#ifdef IRC_HYBRID
    if (whosends(ci) == s_ChanServ) {
        send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), chan, s_ChanServ);
        send_cmd(NULL, "MODE %s +o %s", chan, s_ChanServ);
    }
    send_cmd(whosends(ci), "TOPIC %s :%s", chan, c->topic ? c->topic : "");
    if (whosends(ci) == s_ChanServ) {
        send_cmd(s_ChanServ, "PART %s", chan);
    }
#else
    send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, c->topic_setter,
             c->topic_time, c->topic ? c->topic : "");
#endif
}

/*************************************************************************/

/* See if the topic is locked on the given channel, and return 1 (and fix
 * the topic) if so. */

int check_topiclock(Channel * c, time_t topic_time)
{
    ChannelInfo *ci;

    if (!(ci = c->ci) || !(ci->flags & CI_TOPICLOCK))
        return 0;

    if (c->topic)
        free(c->topic);
    if (ci->last_topic)
        c->topic = sstrdup(ci->last_topic);
    else
        c->topic = NULL;

    strscpy(c->topic_setter, ci->last_topic_setter, NICKMAX);
#ifdef IRC_UNREAL
    /* Because older timestamps are rejected */
    c->topic_time = topic_time + 1;
#else
    c->topic_time = ci->last_topic_time;
#endif

#ifdef IRC_HYBRID
    if (whosends(ci) == s_ChanServ) {
        send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), c->name,
                 s_ChanServ);
        send_cmd(NULL, "MODE %s +o %s", c->name, s_ChanServ);
    }
    send_cmd(whosends(ci), "TOPIC %s :%s", c->name,
             c->topic ? c->topic : "");
    if (whosends(ci) == s_ChanServ) {
        send_cmd(s_ChanServ, "PART %s", c->name);
    }
#else
    send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, c->topic_setter,
             c->topic_time, c->topic ? c->topic : "");
#endif
    return 1;
}

/*************************************************************************/

/* Remove all channels which have expired. */

void expire_chans()
{
    ChannelInfo *ci, *next;
    int i;
    time_t now = time(NULL);

    if (!CSExpire)
        return;

    for (i = 0; i < 256; i++) {
        for (ci = chanlists[i]; ci; ci = next) {
            next = ci->next;
            if (!ci->c && now - ci->last_used >= CSExpire
                && !(ci->flags & (CI_VERBOTEN | CI_NO_EXPIRE))) {
                alog("Expiring channel %s (founder: %s)", ci->name,
                     (ci->founder ? ci->founder->display : "(none)"));
                delchan(ci);
            }
        }
    }
}

/*************************************************************************/

/* Remove a (deleted or expired) nickname from all channel lists. */

void cs_remove_nick(const NickCore * nc)
{
    int i, j;
    ChannelInfo *ci, *next;
    ChanAccess *ca;
    AutoKick *akick;

    for (i = 0; i < 256; i++) {
        for (ci = chanlists[i]; ci; ci = next) {
            next = ci->next;
            if (ci->founder == nc) {
                if (ci->successor) {
                    NickCore *nc2 = ci->successor;
                    if (!nick_is_services_admin(nc2) && nc2->channelmax > 0
                        && nc2->channelcount >= nc2->channelmax) {
                        alog("%s: Successor (%s) of %s owns too many channels, " "deleting channel", s_ChanServ, nc2->display, ci->name);
                        delchan(ci);
                        continue;
                    } else {
                        alog("%s: Transferring foundership of %s from deleted " "nick %s to successor %s", s_ChanServ, ci->name, nc->display, nc2->display);
                        ci->founder = nc2;
                        ci->successor = NULL;
                        nc2->channelcount++;
#ifdef USE_RDB
                        if (rdb_open()) {
                            rdb_cs_set_founder(ci->name, nc2->display);
                            rdb_close();
                        }
#endif
                    }
                } else {
                    alog("%s: Deleting channel %s owned by deleted nick %s", s_ChanServ, ci->name, nc->display);
#ifndef IRC_HYBRID
                    /* Maybe move this to delchan() ? */
                    if ((ci->c) && (ci->c->mode & CMODE_r)) {
                        ci->c->mode &= ~CMODE_r;
                        send_cmd(whosends(ci), "MODE %s -r", ci->name);
                    }
#endif

                    delchan(ci);
                    continue;
                }
            }

            if (ci->successor == nc)
                ci->successor = NULL;

            for (ca = ci->access, j = ci->accesscount; j > 0; ca++, j--) {
                if (ca->in_use && ca->nc == nc) {
                    ca->in_use = 0;
                    ca->nc = NULL;
                }
            }

            for (akick = ci->akick, j = ci->akickcount; j > 0;
                 akick++, j--) {
                if ((akick->flags & AK_USED) && (akick->flags & AK_ISNICK)
                    && akick->u.nc == nc) {
                    if (akick->creator) {
                        free(akick->creator);
                        akick->creator = NULL;
                    }
                    if (akick->reason) {
                        free(akick->reason);
                        akick->reason = NULL;
                    }
                    akick->flags = 0;
                    akick->u.nc = NULL;
                }
            }
        }
    }
#ifdef USE_RDB
    if (rdb_open()) {
        rdb_cs_deluser(nc->display);
        rdb_close();
    }
#endif
}

/*************************************************************************/

/* Removes any reference to a bot */

void cs_remove_bot(const BotInfo * bi)
{
    int i;
    ChannelInfo *ci;

    for (i = 0; i < 256; i++)
        for (ci = chanlists[i]; ci; ci = ci->next)
            if (ci->bi == bi)
                ci->bi = NULL;
}

/*************************************************************************/

/* Return the ChannelInfo structure for the given channel, or NULL if the
 * channel isn't registered. */

ChannelInfo *cs_findchan(const char *chan)
{
    ChannelInfo *ci;

    for (ci = chanlists[tolower(chan[1])]; ci; ci = ci->next) {
        if (stricmp(ci->name, chan) == 0)
            return ci;
    }
    return NULL;
}

/*************************************************************************/

/* Return 1 if the user's access level on the given channel falls into the
 * given category, 0 otherwise.  Note that this may seem slightly confusing
 * in some cases: for example, check_access(..., CA_NOJOIN) returns true if
 * the user does _not_ have access to the channel (i.e. matches the NOJOIN
 * criterion). */

int check_access(User * user, ChannelInfo * ci, int what)
{
    int level = get_access(user, ci);
    int limit = ci->levels[what];

    /* Resetting the last used time */
    if (level > 0)
        ci->last_used = time(NULL);

    if (level == ACCESS_FOUNDER)
        return (what == CA_AUTODEOP || what == CA_NOJOIN) ? 0 : 1;
    /* Hacks to make flags work */
    if (what == CA_AUTODEOP && (ci->flags & CI_SECUREOPS) && level == 0)
        return 1;
    if (limit == ACCESS_INVALID)
        return 0;
    if (what == CA_AUTODEOP || what == CA_NOJOIN)
        return level <= ci->levels[what];
    else
        return level >= ci->levels[what];
}

/*************************************************************************/
/*********************** ChanServ private routines ***********************/
/*************************************************************************/

/* Insert a channel alphabetically into the database. */

static void alpha_insert_chan(ChannelInfo * ci)
{
    ChannelInfo *ptr, *prev;
    char *chan = ci->name;

    for (prev = NULL, ptr = chanlists[tolower(chan[1])];
         ptr != NULL && stricmp(ptr->name, chan) < 0;
         prev = ptr, ptr = ptr->next);
    ci->prev = prev;
    ci->next = ptr;
    if (!prev)
        chanlists[tolower(chan[1])] = ci;
    else
        prev->next = ci;
    if (ptr)
        ptr->prev = ci;
}

/*************************************************************************/

/* Add a channel to the database.  Returns a pointer to the new ChannelInfo
 * structure if the channel was successfully registered, NULL otherwise.
 * Assumes channel does not already exist. */

static ChannelInfo *makechan(const char *chan)
{
    int i;
    ChannelInfo *ci;

    ci = scalloc(sizeof(ChannelInfo), 1);
    strscpy(ci->name, chan, CHANMAX);
    ci->time_registered = time(NULL);
    reset_levels(ci);
    ci->ttb = scalloc(2 * TTB_SIZE, 1);
    for (i = 0; i < TTB_SIZE; i++)
        ci->ttb[i] = 0;
    alpha_insert_chan(ci);
    return ci;
}

/*************************************************************************/

/* Remove a channel from the ChanServ database.  Return 1 on success, 0
 * otherwise. */

static int delchan(ChannelInfo * ci)
{
    int i;
    NickCore *nc = ci->founder;

    if (ci->bi) {
        ci->bi->chancount--;
    }
    if (ci->c) {
        if (ci->bi && ci->c->usercount >= BSMinUsers) {
            send_cmd(ci->bi->nick, "PART %s", ci->c->name);
        }
        ci->c->ci = NULL;
    }
#ifdef USE_RDB
    if (rdb_open()) {
        rdb_cs_delchan(ci);
        rdb_close();
    }
#endif
    if (ci->next)
        ci->next->prev = ci->prev;
    if (ci->prev)
        ci->prev->next = ci->next;
    else
        chanlists[tolower(ci->name[1])] = ci->next;
    if (ci->desc)
        free(ci->desc);
    if (ci->mlock_key)
        free(ci->mlock_key);
#ifdef HAS_FMODE
    if (ci->mlock_flood)
        free(ci->mlock_flood);
#endif
#ifdef HAS_LMODE
    if (ci->mlock_redirect)
        free(ci->mlock_redirect);
#endif
    if (ci->last_topic)
        free(ci->last_topic);
    if (ci->forbidby)
        free(ci->forbidby);
    if (ci->forbidreason)
        free(ci->forbidreason);
    if (ci->access)
        free(ci->access);
    for (i = 0; i < ci->akickcount; i++) {
        if (!(ci->akick[i].flags & AK_ISNICK) && ci->akick[i].u.mask)
            free(ci->akick[i].u.mask);
        if (ci->akick[i].reason)
            free(ci->akick[i].reason);
        if (ci->akick[i].creator)
            free(ci->akick[i].creator);
    }
    if (ci->akick)
        free(ci->akick);
    if (ci->levels)
        free(ci->levels);
    if (ci->memos.memos) {
        for (i = 0; i < ci->memos.memocount; i++) {
            if (ci->memos.memos[i].text)
                free(ci->memos.memos[i].text);
        }
        free(ci->memos.memos);
    }
    if (ci->ttb)
        free(ci->ttb);
    for (i = 0; i < ci->bwcount; i++) {
        if (ci->badwords[i].word)
            free(ci->badwords[i].word);
    }
    if (ci->badwords)
        free(ci->badwords);
    free(ci);
    if (nc)
        nc->channelcount--;

    return 1;
}

/*************************************************************************/

/* Reset channel access level values to their default state. */

static void reset_levels(ChannelInfo * ci)
{
    int i;

    if (ci->levels)
        free(ci->levels);
    ci->levels = scalloc(CA_SIZE * sizeof(*ci->levels), 1);
    for (i = 0; def_levels[i][0] >= 0; i++)
        ci->levels[def_levels[i][0]] = def_levels[i][1];
}

/*************************************************************************/

/* Does the given user have founder access to the channel? */

int is_founder(User * user, ChannelInfo * ci)
{
    if (user->isSuperAdmin) {
        return 1;
    }

    if (user->na && user->na->nc == ci->founder) {
        if ((nick_identified(user)
             || (nick_recognized(user) && !(ci->flags & CI_SECURE))))
            return 1;
    }
    if (is_identified(user, ci))
        return 1;
    return 0;
}

/*************************************************************************/

static int is_real_founder(User * user, ChannelInfo * ci)
{
    if (user->isSuperAdmin) {
        return 1;
    }

    if (user->na && user->na->nc == ci->founder) {
        if ((nick_identified(user)
             || (nick_recognized(user) && !(ci->flags & CI_SECURE))))
            return 1;
    }
    return 0;
}

/*************************************************************************/

/* Has the given user password-identified as founder for the channel? */

static int is_identified(User * user, ChannelInfo * ci)
{
    struct u_chaninfolist *c;

    for (c = user->founder_chans; c; c = c->next) {
        if (c->chan == ci)
            return 1;
    }
    return 0;
}

/*************************************************************************/

/* Returns the ChanAccess entry for an user */

ChanAccess *get_access_entry(NickCore * nc, ChannelInfo * ci)
{
    ChanAccess *access;
    int i;

    for (access = ci->access, i = 0; i < ci->accesscount; access++, i++)
        if (access->in_use && access->nc == nc)
            return access;

    return NULL;
}

/*************************************************************************/

/* Return the access level the given user has on the channel.  If the
 * channel doesn't exist, the user isn't on the access list, or the channel
 * is CS_SECURE and the user hasn't IDENTIFY'd with NickServ, return 0. */

int get_access(User * user, ChannelInfo * ci)
{
    ChanAccess *access;

    if (!ci)
        return 0;

    if (is_founder(user, ci))
        return ACCESS_FOUNDER;

    if (!user->na)
        return 0;

    if (nick_identified(user)
        || (nick_recognized(user) && !(ci->flags & CI_SECURE)))
        if ((access = get_access_entry(user->na->nc, ci)))
            return access->level;

    return 0;
}

/*************************************************************************/

void update_cs_lastseen(User * user, ChannelInfo * ci)
{
    ChanAccess *access;

    if (!ci || !user->na)
        return;

    if (is_founder(user, ci) || nick_identified(user)
        || (nick_recognized(user) && !(ci->flags & CI_SECURE)))
        if ((access = get_access_entry(user->na->nc, ci)))
            access->last_seen = time(NULL);
}

/*************************************************************************/

/* Returns the best ban possible for an user depending of the bantype
   value. */

int get_idealban(ChannelInfo * ci, User * u, char *ret, int retlen)
{
    char *mask;

    if (!ci || !u || !ret || retlen == 0)
        return 0;

    switch (ci->bantype) {
    case 0:
        snprintf(ret, retlen, "*!%s@%s", GetIdent(u), GetHost(u));
        return 1;
    case 1:
        snprintf(ret, retlen, "*!%s%s@%s",
                 (strlen(GetIdent(u)) <
                  (*(GetIdent(u)) ==
                   '~' ? USERMAX + 1 : USERMAX) ? "*" : ""),
                 (*(GetIdent(u)) == '~' ? GetIdent(u) + 1 : GetIdent(u)),
                 GetHost(u));
        return 1;
    case 2:
        snprintf(ret, retlen, "*!*@%s", GetHost(u));
        return 1;
    case 3:
        mask = create_mask(u);
        snprintf(ret, retlen, "*!%s", mask);
        free(mask);
        return 1;

    default:
        return 0;
    }
}

/*************************************************************************/

static void make_unidentified(User * u, ChannelInfo * ci)
{
    struct u_chaninfolist *uci;

    if (!u || !ci)
        return;

    for (uci = u->founder_chans; uci; uci = uci->next) {
        if (uci->chan == ci) {
            if (uci->next)
                uci->next->prev = uci->prev;
            if (uci->prev)
                uci->prev->next = uci->next;
            else
                u->founder_chans = uci->next;
            free(uci);
            break;
        }
    }
}

/*************************************************************************/

#ifdef HAS_FMODE

char *cs_get_flood(ChannelInfo * ci)
{
    return ci->mlock_flood;
}

#endif

/*************************************************************************/

char *cs_get_key(ChannelInfo * ci)
{
    return ci->mlock_key;
}

/*************************************************************************/

char *cs_get_limit(ChannelInfo * ci)
{
    static char limit[16];

    if (ci->mlock_limit == 0)
        return NULL;

    snprintf(limit, sizeof(limit), "%lu", ci->mlock_limit);
    return limit;
}

/*************************************************************************/

#ifdef HAS_LMODE

char *cs_get_redirect(ChannelInfo * ci)
{
    return ci->mlock_redirect;
}

#endif

/*************************************************************************/

#ifdef HAS_FMODE

void cs_set_flood(ChannelInfo * ci, char *value)
{
    char *dp, *end;

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

    /* This looks ugly, but it works ;) */
    if (value && *value != ':'
        && (strtoul((*value == '*' ? value + 1 : value), &dp, 10) > 0)
        && (*dp == ':') && (*(++dp) != 0) && (strtoul(dp, &end, 10) > 0)
        && (*end == 0)) {
        ci->mlock_flood = sstrdup(value);
    } else {
        ci->mlock_on &= ~CMODE_f;
        ci->mlock_flood = NULL;
    }
}

#endif

/*************************************************************************/

void cs_set_key(ChannelInfo * ci, char *value)
{
    if (ci->mlock_key)
        free(ci->mlock_key);

    /* Don't allow keys with a coma */
    if (value && *value != ':' && !strchr(value, ',')) {
        ci->mlock_key = sstrdup(value);
    } else {
        ci->mlock_on &= ~CMODE_k;
        ci->mlock_key = NULL;
    }
}

/*************************************************************************/

void cs_set_limit(ChannelInfo * ci, char *value)
{
    ci->mlock_limit = value ? strtoul(value, NULL, 10) : 0;

    if (ci->mlock_limit <= 0)
        ci->mlock_on &= ~CMODE_l;
}

/*************************************************************************/

#ifdef HAS_LMODE

void cs_set_redirect(ChannelInfo * ci, char *value)
{
    if (ci->mlock_redirect)
        free(ci->mlock_redirect);

    /* Don't allow keys with a coma */
    if (value && *value == '#') {
        ci->mlock_redirect = sstrdup(value);
    } else {
        ci->mlock_on &= ~CMODE_L;
        ci->mlock_redirect = NULL;
    }
}

#endif

int get_access_level(ChannelInfo * ci, NickAlias * na)
{
    ChanAccess *access;
    int num;

    if (na->nc == ci->founder) {
        return ACCESS_FOUNDER;
    }

    for (num = 0; num < ci->accesscount; num++) {

        access = &ci->access[num];

        if (!access->in_use)
            return 0;

        if (access->nc == na->nc) {
            return access->level;
        }

    }

    return 0;

}

char *get_xop_level(int level)
{

    if (level < ACCESS_VOP) {
        return "Err";
#ifdef HAS_HALFOP
    } else if (level < ACCESS_HOP) {
        return "VOP";
    } else if (level < ACCESS_AOP) {
        return "HOP";
#else
    } else if (level < ACCESS_AOP) {
        return "VOP";
#endif
    } else if (level < ACCESS_SOP) {
        return "AOP";
    } else if (level < ACCESS_FOUNDER) {
        return "SOP";
    } else {
        return "Founder";
    }

}

/*************************************************************************/
/*********************** ChanServ command routines ***********************/
/*************************************************************************/

static int do_help(User * u)
{
    char *cmd = strtok(NULL, "");

    if (!cmd) {
        notice_help(s_ChanServ, u, CHAN_HELP);
#ifdef IRC_UNREAL
        notice_help(s_ChanServ, u, CHAN_HELP_UNREAL);
#endif
#ifdef IRC_VIAGRA
        notice_help(s_ChanServ, u, CHAN_HELP_UNREAL);
#endif
#ifdef IRC_ULTIMATE
        notice_help(s_ChanServ, u, CHAN_HELP_ULTIMATE);
#endif
#ifdef IRC_ULTIMATE3
        notice_help(s_ChanServ, u, CHAN_HELP_ULTIMATE3);
#endif
        if (CSExpire >= 86400)
            notice_help(s_ChanServ, u, CHAN_HELP_EXPIRES,
                        CSExpire / 86400);
        if (is_services_oper(u))
            notice_help(s_ChanServ, u, CHAN_SERVADMIN_HELP);
        moduleDisplayHelp(2, u);
    } else if (stricmp(cmd, "LEVELS DESC") == 0) {
        int i;
        notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC);
        if (!levelinfo_maxwidth) {
            for (i = 0; levelinfo[i].what >= 0; i++) {
                int len = strlen(levelinfo[i].name);
                if (len > levelinfo_maxwidth)
                    levelinfo_maxwidth = len;
            }
        }
        for (i = 0; levelinfo[i].what >= 0; i++) {
            notice_help(s_ChanServ, u, CHAN_HELP_LEVELS_DESC_FORMAT,
                        levelinfo_maxwidth, levelinfo[i].name,
                        getstring(u->na, levelinfo[i].desc));
        }
    } else {
        mod_help_cmd(s_ChanServ, u, CHANSERV, cmd);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_register(User * u)
{
    char *chan = strtok(NULL, " ");
    char *pass = strtok(NULL, " ");
    char *desc = strtok(NULL, "");
    NickCore *nc;
    Channel *c;
    ChannelInfo *ci;
    struct u_chaninfolist *uc;
    int is_servadmin = is_services_admin(u);
#ifdef USE_ENCRYPTION
    char founderpass[PASSMAX + 1];
#endif

    if (readonly) {
        notice_lang(s_ChanServ, u, CHAN_REGISTER_DISABLED);
        return MOD_CONT;
    }

    if (checkDefCon(DEFCON_NO_NEW_CHANNELS)) {
        notice_lang(s_ChanServ, u, OPER_DEFCON_DENIED);
        return MOD_CONT;
    }

    if (!desc) {
        syntax_error(s_ChanServ, u, "REGISTER", CHAN_REGISTER_SYNTAX);
    } else if (*chan == '&') {
        notice_lang(s_ChanServ, u, CHAN_REGISTER_NOT_LOCAL);
    } else if (!u->na || !(nc = u->na->nc)) {
        notice_lang(s_ChanServ, u, CHAN_MUST_REGISTER_NICK, s_NickServ);
    } else if (!nick_recognized(u)) {
        notice_lang(s_ChanServ, u, CHAN_MUST_IDENTIFY_NICK, s_NickServ,
                    s_NickServ);
    } else if ((ci = cs_findchan(chan)) != NULL) {
        if (ci->flags & CI_VERBOTEN) {
            alog("%s: Attempt to register FORBIDden channel %s by %s!%s@%s", s_ChanServ, ci->name, u->nick, u->username, GetHost(u));
            notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan);
        } else {
            notice_lang(s_ChanServ, u, CHAN_ALREADY_REGISTERED, chan);
        }
    } else if (!stricmp(chan, "#")) {
        notice_lang(s_ChanServ, u, CHAN_MAY_NOT_BE_REGISTERED, chan);
    } else if (!(c = findchan(chan))
               || !chan_has_user_status(c, u, CUS_OP)) {
        notice_lang(s_ChanServ, u, CHAN_MUST_BE_CHANOP);

    } else if (!is_servadmin && nc->channelmax > 0
               && nc->channelcount >= nc->channelmax) {
        notice_lang(s_ChanServ, u,
                    nc->channelcount >
                    nc->
                    channelmax ? CHAN_EXCEEDED_CHANNEL_LIMIT :
                    CHAN_REACHED_CHANNEL_LIMIT, nc->channelmax);

    } else if (!(ci = makechan(chan))) {
        alog("%s: makechan() failed for REGISTER %s", s_ChanServ, chan);
        notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);

#ifdef USE_ENCRYPTION
    } else if (strscpy(founderpass, pass, PASSMAX + 1),
               encrypt_in_place(founderpass, PASSMAX) < 0) {
        alog("%s: Couldn't encrypt password for %s (REGISTER)",
             s_ChanServ, chan);
        notice_lang(s_ChanServ, u, CHAN_REGISTRATION_FAILED);
        delchan(ci);
#endif

    } else {
        c->ci = ci;
        ci->c = c;
        ci->bantype = CSDefBantype;
        ci->flags = CSDefFlags;
#ifdef IRC_HYBRID
        ci->mlock_on = CMODE_n | CMODE_t;
#else
        ci->mlock_on = CMODE_n | CMODE_t | CMODE_r;
#endif
        ci->memos.memomax = MSMaxMemos;
        ci->last_used = ci->time_registered;
        ci->founder = nc;
#ifdef USE_ENCRYPTION
        if (strlen(pass) > PASSMAX)
            notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX);
        memset(pass, 0, strlen(pass));
        memcpy(ci->founderpass, founderpass, PASSMAX);
        ci->flags |= CI_ENCRYPTEDPW;
#else
        if (strlen(pass) > PASSMAX - 1) /* -1 for null byte */
            notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX - 1);
        strscpy(ci->founderpass, pass, PASSMAX);
#endif
        ci->desc = sstrdup(desc);
        if (c->topic) {
            ci->last_topic = sstrdup(c->topic);
            strscpy(ci->last_topic_setter, c->topic_setter, NICKMAX);
            ci->last_topic_time = c->topic_time;
        }
        ci->bi = NULL;
        ci->botflags = BSDefFlags;
        ci->founder->channelcount++;
        alog("%s: Channel '%s' registered by %s!%s@%s", s_ChanServ, chan,
             u->nick, u->username, GetHost(u));
        notice_lang(s_ChanServ, u, CHAN_REGISTERED, chan, u->nick);
#ifndef USE_ENCRYPTION
        notice_lang(s_ChanServ, u, CHAN_PASSWORD_IS, ci->founderpass);
#endif
        uc = scalloc(sizeof(*uc), 1);
        uc->next = u->founder_chans;
        uc->prev = NULL;
        if (u->founder_chans)
            u->founder_chans->prev = uc;
        u->founder_chans = uc;
        uc->chan = ci;
        /* Implement new mode lock */
        check_modes(c);
#ifdef IRC_ULTIMATE3
        send_cmd(s_ChanServ, "MODE %s +a %s", chan, u->nick);
#endif
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_identify(User * u)
{
    char *chan = strtok(NULL, " ");
    char *pass = strtok(NULL, " ");
    ChannelInfo *ci;
    struct u_chaninfolist *uc;

    if (!pass) {
        syntax_error(s_ChanServ, u, "IDENTIFY", CHAN_IDENTIFY_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!nick_identified(u)) {
        notice_lang(s_ChanServ, u, NICK_IDENTIFY_REQUIRED, s_NickServ);
    } else if (is_founder(u, ci)) {
        notice_lang(s_ChanServ, u, NICK_ALREADY_IDENTIFIED);
    } else {
        int res;

        if ((res = check_password(pass, ci->founderpass)) == 1) {
            if (!is_identified(u, ci)) {
                uc = scalloc(sizeof(*uc), 1);
                uc->next = u->founder_chans;
                if (u->founder_chans)
                    u->founder_chans->prev = uc;
                u->founder_chans = uc;
                uc->chan = ci;
                alog("%s: %s!%s@%s identified for %s", s_ChanServ, u->nick,
                     u->username, GetHost(u), ci->name);
            }

            notice_lang(s_ChanServ, u, CHAN_IDENTIFY_SUCCEEDED, chan);
        } else if (res < 0) {
            alog("%s: check_password failed for %s", s_ChanServ, ci->name);
            notice_lang(s_ChanServ, u, CHAN_IDENTIFY_FAILED);
        } else {
            alog("%s: Failed IDENTIFY for %s by %s!%s@%s",
                 s_ChanServ, ci->name, u->nick, u->username, GetHost(u));
            notice_lang(s_ChanServ, u, PASSWORD_INCORRECT);
            bad_password(u);
        }

    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_logout(User * u)
{
    char *chan = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");
    ChannelInfo *ci;
    User *u2 = NULL;
    int is_servadmin = is_services_admin(u);

    if (!chan || (!nick && !is_servadmin)) {
        syntax_error(s_ChanServ, u, "LOGOUT",
                     (!is_servadmin ? CHAN_LOGOUT_SYNTAX :
                      CHAN_LOGOUT_SERVADMIN_SYNTAX));
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (!is_servadmin & (ci->flags & CI_VERBOTEN)) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (nick && !(u2 = finduser(nick))) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, nick);
    } else if (!is_servadmin && u2 != u && !is_real_founder(u, ci)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else {
        if (u2) {
            make_unidentified(u2, ci);
            notice_lang(s_ChanServ, u, CHAN_LOGOUT_SUCCEEDED, nick, chan);
        } else {
            int i;
            for (i = 0; i < 1024; i++)
                for (u2 = userlist[i]; u2; u2 = u2->next)
                    make_unidentified(u2, ci);
            notice_lang(s_ChanServ, u, CHAN_LOGOUT_ALL_SUCCEEDED, chan);
        }
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_drop(User * u)
{
    char *chan = strtok(NULL, " ");
    ChannelInfo *ci;
    int is_servadmin = is_services_admin(u);

    if (readonly && !is_servadmin) {
        notice_lang(s_ChanServ, u, CHAN_DROP_DISABLED);
        return MOD_CONT;
    }

    if (!chan) {
        syntax_error(s_ChanServ, u, "DROP", CHAN_DROP_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (!is_servadmin && (ci->flags & CI_VERBOTEN)) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!is_servadmin && (ci->flags & CI_SUSPENDED)) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!is_servadmin
               && (ci->
                   flags & CI_SECUREFOUNDER ? !is_real_founder(u,
                                                               ci) :
                   !is_founder(u, ci))) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else {
        int level = get_access(u, ci);

        if (readonly)           /* in this case we know they're a Services admin */
            notice_lang(s_ChanServ, u, READ_ONLY_MODE);
#ifndef IRC_HYBRID
        if (ci->c) {
            ci->c->mode &= ~CMODE_r;
            send_cmd(whosends(ci), "MODE %s -r", ci->name);
        }
#endif
        alog("%s: Channel %s dropped by %s!%s@%s (founder: %s)",
             s_ChanServ, ci->name, u->nick, u->username, GetHost(u),
             (ci->founder ? ci->founder->display : "(none)"));

        delchan(ci);

        /* We must make sure that the Services admin has not normally the right to
         * drop the channel before issuing the wallops.
         */
        if (WallDrop && is_servadmin && level < ACCESS_FOUNDER)
            wallops(s_ChanServ, "\2%s\2 used DROP on channel \2%s\2",
                    u->nick, chan);

        notice_lang(s_ChanServ, u, CHAN_DROPPED, chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* Main SET routine.  Calls other routines as follows:
 *	do_set_command(User *command_sender, ChannelInfo *ci, char *param);
 * The parameter passed is the first space-delimited parameter after the
 * option name, except in the case of DESC, TOPIC, and ENTRYMSG, in which
 * it is the entire remainder of the line.  Additional parameters beyond
 * the first passed in the function call can be retrieved using
 * strtok(NULL, toks).
 */
static int do_set(User * u)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *param;
    ChannelInfo *ci;
    int is_servadmin = is_services_admin(u);

    if (readonly) {
        notice_lang(s_ChanServ, u, CHAN_SET_DISABLED);
        return MOD_CONT;
    }

    if (cmd) {
        if (stricmp(cmd, "DESC") == 0 || stricmp(cmd, "ENTRYMSG") == 0)
            param = strtok(NULL, "");
        else
            param = strtok(NULL, " ");
    } else {
        param = NULL;
    }

    if (!param && (!cmd || (stricmp(cmd, "SUCCESSOR") != 0 &&
                            stricmp(cmd, "URL") != 0 &&
                            stricmp(cmd, "EMAIL") != 0 &&
                            stricmp(cmd, "ENTRYMSG") != 0))) {
        syntax_error(s_ChanServ, u, "SET", CHAN_SET_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!is_servadmin && !check_access(u, ci, CA_SET)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (stricmp(cmd, "FOUNDER") == 0) {
        if (!is_servadmin
            && (ci->
                flags & CI_SECUREFOUNDER ? !is_real_founder(u,
                                                            ci) :
                !is_founder(u, ci))) {
            notice_lang(s_ChanServ, u, ACCESS_DENIED);
        } else {
            do_set_founder(u, ci, param);
        }
    } else if (stricmp(cmd, "SUCCESSOR") == 0) {
        if (!is_servadmin
            && (ci->
                flags & CI_SECUREFOUNDER ? !is_real_founder(u,
                                                            ci) :
                !is_founder(u, ci))) {
            notice_lang(s_ChanServ, u, ACCESS_DENIED);
        } else {
            do_set_successor(u, ci, param);
        }
    } else if (stricmp(cmd, "PASSWORD") == 0) {
        if (!is_servadmin
            && (ci->
                flags & CI_SECUREFOUNDER ? !is_real_founder(u,
                                                            ci) :
                !is_founder(u, ci))) {
            notice_lang(s_ChanServ, u, ACCESS_DENIED);
        } else {
            do_set_password(u, ci, param);
        }
    } else if (stricmp(cmd, "DESC") == 0) {
        do_set_desc(u, ci, param);
    } else if (stricmp(cmd, "URL") == 0) {
        do_set_url(u, ci, param);
    } else if (stricmp(cmd, "EMAIL") == 0) {
        do_set_email(u, ci, param);
    } else if (stricmp(cmd, "ENTRYMSG") == 0) {
        do_set_entrymsg(u, ci, param);
    } else if (stricmp(cmd, "TOPIC") == 0) {
        notice_lang(s_ChanServ, u, OBSOLETE_COMMAND, "TOPIC");
    } else if (stricmp(cmd, "BANTYPE") == 0) {
        do_set_bantype(u, ci, param);
    } else if (stricmp(cmd, "MLOCK") == 0) {
        do_set_mlock(u, ci, param);
    } else if (stricmp(cmd, "KEEPTOPIC") == 0) {
        do_set_keeptopic(u, ci, param);
    } else if (stricmp(cmd, "TOPICLOCK") == 0) {
        do_set_topiclock(u, ci, param);
    } else if (stricmp(cmd, "PRIVATE") == 0) {
        do_set_private(u, ci, param);
    } else if (stricmp(cmd, "SECUREOPS") == 0) {
        do_set_secureops(u, ci, param);
    } else if (stricmp(cmd, "SECUREFOUNDER") == 0) {
        if (!is_servadmin
            && (ci->
                flags & CI_SECUREFOUNDER ? !is_real_founder(u,
                                                            ci) :
                !is_founder(u, ci))) {
            notice_lang(s_ChanServ, u, ACCESS_DENIED);
        } else {
            do_set_securefounder(u, ci, param);
        }
    } else if (stricmp(cmd, "RESTRICTED") == 0) {
        do_set_restricted(u, ci, param);
    } else if (stricmp(cmd, "SECURE") == 0) {
        do_set_secure(u, ci, param);
    } else if (stricmp(cmd, "SIGNKICK") == 0) {
        do_set_signkick(u, ci, param);
    } else if (stricmp(cmd, "OPNOTICE") == 0) {
        do_set_opnotice(u, ci, param);
    } else if (stricmp(cmd, "XOP") == 0) {
        do_set_xop(u, ci, param);
    } else if (stricmp(cmd, "PEACE") == 0) {
        do_set_peace(u, ci, param);
    } else if (stricmp(cmd, "NOEXPIRE") == 0) {
        do_set_noexpire(u, ci, param);
    } else {
        notice_lang(s_ChanServ, u, CHAN_SET_UNKNOWN_OPTION, cmd);
        notice_lang(s_ChanServ, u, MORE_INFO, s_ChanServ, "SET");
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_founder(User * u, ChannelInfo * ci, char *param)
{
    NickAlias *na = findnick(param);
    NickCore *nc, *nc0 = ci->founder;

    if (!na) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
        return MOD_CONT;
    } else if (na->status & NS_VERBOTEN) {
        notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
        return MOD_CONT;
    }

    nc = na->nc;
    if (nc->channelmax > 0 && nc->channelcount >= nc->channelmax
        && !is_services_admin(u)) {
        notice_lang(s_ChanServ, u, CHAN_SET_FOUNDER_TOO_MANY_CHANS, param);
        return MOD_CONT;
    }

    alog("%s: Changing founder of %s from %s to %s by %s!%s@%s",
         s_ChanServ, ci->name, ci->founder->display, nc->display, u->nick,
         u->username, GetHost(u));

    /* Founder and successor must not be the same group */
    if (nc == ci->successor)
        ci->successor = NULL;

    nc0->channelcount--;
    ci->founder = nc;
    nc->channelcount++;

    notice_lang(s_ChanServ, u, CHAN_FOUNDER_CHANGED, ci->name, param);
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_successor(User * u, ChannelInfo * ci, char *param)
{
    NickAlias *na;
    NickCore *nc;

    if (param) {
        na = findnick(param);

        if (!na) {
            notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, param);
            return MOD_CONT;
        }
        if (na->status & NS_VERBOTEN) {
            notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, param);
            return MOD_CONT;
        }
        if (na->nc == ci->founder) {
            notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_IS_FOUNDER, param,
                        ci->name);
            return MOD_CONT;
        }
        nc = na->nc;

    } else {
        nc = NULL;
    }

    alog("%s: Changing successor of %s from %s to %s by %s!%s@%s",
         s_ChanServ, ci->name,
         (ci->successor ? ci->successor->display : "none"),
         (nc ? nc->display : "none"), u->nick, u->username, GetHost(u));

    ci->successor = nc;

    if (nc)
        notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_CHANGED, ci->name,
                    param);
    else
        notice_lang(s_ChanServ, u, CHAN_SUCCESSOR_UNSET, ci->name);
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_password(User * u, ChannelInfo * ci, char *param)
{
#ifdef USE_ENCRYPTION
    int len = strlen(param);

    if (len > PASSMAX) {
        len = PASSMAX;
        param[len] = 0;
        notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX);
    }

    if (encrypt(param, len, ci->founderpass, PASSMAX) < 0) {
        memset(param, 0, strlen(param));
        alog("%s: Failed to encrypt password for %s (set)", s_ChanServ,
             ci->name);
        notice_lang(s_ChanServ, u, CHAN_SET_PASSWORD_FAILED);
        return MOD_CONT;
    }

    memset(param, 0, strlen(param));
    notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED, ci->name);

#else                           /* !USE_ENCRYPTION */
    if (strlen(param) > PASSMAX - 1)    /* -1 for null byte */
        notice_lang(s_ChanServ, u, PASSWORD_TRUNCATED, PASSMAX - 1);
    strscpy(ci->founderpass, param, PASSMAX);
    notice_lang(s_ChanServ, u, CHAN_PASSWORD_CHANGED_TO, ci->name,
                ci->founderpass);
#endif                          /* USE_ENCRYPTION */

    if (get_access(u, ci) < ACCESS_FOUNDER) {
        alog("%s: %s!%s@%s set password as Services admin for %s",
             s_ChanServ, u->nick, u->username, GetHost(u), ci->name);
        if (WallSetpass)
            wallops(s_ChanServ,
                    "\2%s\2 set password as Services admin for channel \2%s\2",
                    u->nick, ci->name);
    } else {
        alog("%s: %s!%s@%s changed password of %s (founder: %s)",
             s_ChanServ, u->nick, u->username, GetHost(u),
             ci->name, ci->founder->display);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_desc(User * u, ChannelInfo * ci, char *param)
{
    if (ci->desc)
        free(ci->desc);
    ci->desc = sstrdup(param);
    notice_lang(s_ChanServ, u, CHAN_DESC_CHANGED, ci->name, param);
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_url(User * u, ChannelInfo * ci, char *param)
{
    if (ci->url)
        free(ci->url);
    if (param) {
        ci->url = sstrdup(param);
        notice_lang(s_ChanServ, u, CHAN_URL_CHANGED, ci->name, param);
    } else {
        ci->url = NULL;
        notice_lang(s_ChanServ, u, CHAN_URL_UNSET, ci->name);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_email(User * u, ChannelInfo * ci, char *param)
{
    if (ci->email)
        free(ci->email);
    if (param) {
        ci->email = sstrdup(param);
        notice_lang(s_ChanServ, u, CHAN_EMAIL_CHANGED, ci->name, param);
    } else {
        ci->email = NULL;
        notice_lang(s_ChanServ, u, CHAN_EMAIL_UNSET, ci->name);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_entrymsg(User * u, ChannelInfo * ci, char *param)
{
    if (ci->entry_message)
        free(ci->entry_message);
    if (param) {
        ci->entry_message = sstrdup(param);
        notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_CHANGED, ci->name,
                    param);
    } else {
        ci->entry_message = NULL;
        notice_lang(s_ChanServ, u, CHAN_ENTRY_MSG_UNSET, ci->name);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_mlock(User * u, ChannelInfo * ci, char *param)
{
    int add = -1;               /* 1 if adding, 0 if deleting, -1 if neither */
    unsigned char mode;
    CBMode *cbm;

    if (checkDefCon(DEFCON_NO_MLOCK_CHANGE)) {
        notice_lang(s_ChanServ, u, OPER_DEFCON_DENIED);
        return MOD_CONT;
    }

    /* Reinitialize everything */
#ifdef IRC_HYBRID
    ci->mlock_on = 0;
#else
    ci->mlock_on = CMODE_r;
#endif
    ci->mlock_off = ci->mlock_limit = 0;
    ci->mlock_key = NULL;
#ifdef HAS_FMODE
    ci->mlock_flood = NULL;
#endif
#ifdef HAS_LMODE
    ci->mlock_redirect = NULL;
#endif

    while ((mode = *param++)) {
        switch (mode) {
        case '+':
            add = 1;
            continue;
        case '-':
            add = 0;
            continue;
        default:
            if (add < 0)
                continue;
        }

        if ((int) mode < 128 && (cbm = &cbmodes[(int) mode])->flag != 0) {
            if ((cbm->flags & CBM_NO_MLOCK)
                || ((cbm->flags & CBM_NO_USER_MLOCK) && !is_oper(u))) {
                notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_IMPOSSIBLE_CHAR,
                            mode);
            } else if (add) {
                ci->mlock_on |= cbm->flag;
                ci->mlock_off &= ~cbm->flag;
                if (cbm->cssetvalue)
                    cbm->cssetvalue(ci, strtok(NULL, " "));
            } else {
                ci->mlock_off |= cbm->flag;
                if (ci->mlock_on & cbm->flag) {
                    ci->mlock_on &= ~cbm->flag;
                    if (cbm->cssetvalue)
                        cbm->cssetvalue(ci, NULL);
                }
            }
        } else {
            notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_UNKNOWN_CHAR, mode);
        }
    }                           /* while (*param) */

#ifdef HAS_LMODE
    /* We can't mlock +L if +l is not mlocked as well. */
    if ((ci->mlock_on & CMODE_L) && !(ci->mlock_on & CMODE_l)) {
        ci->mlock_on &= ~CMODE_L;
        free(ci->mlock_redirect);
        notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_L_REQUIRED);
    }
#endif

#if defined(IRC_ULTIMATE) || defined(IRC_UNREAL) || defined(IRC_ULTIMATE3)
    /* We can't mlock +K if +i is not mlocked as well. */
    if ((ci->mlock_on & CMODE_K) && !(ci->mlock_on & CMODE_i)) {
        ci->mlock_on &= ~CMODE_K;
        notice_lang(s_ChanServ, u, CHAN_SET_MLOCK_K_REQUIRED);
    }
#endif

    /* Since we always enforce mode r there is no way to have no
     * mode lock at all.
     */
#if defined(IRC_HYBRID)
    /* James: Hybrid doesn't HAVE mode r, so now you have to check :P */
    if (get_mlock_modes(ci, 0))
#endif
        notice_lang(s_ChanServ, u, CHAN_MLOCK_CHANGED, ci->name,
                    get_mlock_modes(ci, 0));


    /* Implement the new lock. */
    if (ci->c)
        check_modes(ci->c);
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_bantype(User * u, ChannelInfo * ci, char *param)
{
    char *endptr;

    int16 bantype = strtol(param, &endptr, 10);

    if (*endptr != 0 || bantype < 0 || bantype > 3) {
        notice_lang(s_ChanServ, u, CHAN_SET_BANTYPE_INVALID, param);
    } else {
        ci->bantype = bantype;
        notice_lang(s_ChanServ, u, CHAN_SET_BANTYPE_CHANGED, ci->name,
                    ci->bantype);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_keeptopic(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_KEEPTOPIC;
        notice_lang(s_ChanServ, u, CHAN_SET_KEEPTOPIC_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_KEEPTOPIC;
        notice_lang(s_ChanServ, u, CHAN_SET_KEEPTOPIC_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET KEEPTOPIC",
                     CHAN_SET_KEEPTOPIC_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_topiclock(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_TOPICLOCK;
        notice_lang(s_ChanServ, u, CHAN_SET_TOPICLOCK_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_TOPICLOCK;
        notice_lang(s_ChanServ, u, CHAN_SET_TOPICLOCK_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET TOPICLOCK",
                     CHAN_SET_TOPICLOCK_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_private(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_PRIVATE;
        notice_lang(s_ChanServ, u, CHAN_SET_PRIVATE_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_PRIVATE;
        notice_lang(s_ChanServ, u, CHAN_SET_PRIVATE_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET PRIVATE",
                     CHAN_SET_PRIVATE_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_secureops(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_SECUREOPS;
        notice_lang(s_ChanServ, u, CHAN_SET_SECUREOPS_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_SECUREOPS;
        notice_lang(s_ChanServ, u, CHAN_SET_SECUREOPS_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET SECUREOPS",
                     CHAN_SET_SECUREOPS_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_securefounder(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_SECUREFOUNDER;
        notice_lang(s_ChanServ, u, CHAN_SET_SECUREFOUNDER_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_SECUREFOUNDER;
        notice_lang(s_ChanServ, u, CHAN_SET_SECUREFOUNDER_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET SECUREFOUNDER",
                     CHAN_SET_SECUREFOUNDER_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_restricted(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_RESTRICTED;
        if (ci->levels[CA_NOJOIN] < 0)
            ci->levels[CA_NOJOIN] = 0;
        notice_lang(s_ChanServ, u, CHAN_SET_RESTRICTED_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_RESTRICTED;
        if (ci->levels[CA_NOJOIN] >= 0)
            ci->levels[CA_NOJOIN] = -2;
        notice_lang(s_ChanServ, u, CHAN_SET_RESTRICTED_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET RESTRICTED",
                     CHAN_SET_RESTRICTED_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_secure(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_SECURE;
        notice_lang(s_ChanServ, u, CHAN_SET_SECURE_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_SECURE;
        notice_lang(s_ChanServ, u, CHAN_SET_SECURE_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET SECURE", CHAN_SET_SECURE_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_signkick(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_SIGNKICK;
        ci->flags &= ~CI_SIGNKICK_LEVEL;
        notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_ON);
    } else if (stricmp(param, "LEVEL") == 0) {
        ci->flags |= CI_SIGNKICK_LEVEL;
        ci->flags &= ~CI_SIGNKICK;
        notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_LEVEL);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~(CI_SIGNKICK | CI_SIGNKICK_LEVEL);
        notice_lang(s_ChanServ, u, CHAN_SET_SIGNKICK_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET SIGNKICK",
                     CHAN_SET_SIGNKICK_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_opnotice(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_OPNOTICE;
        notice_lang(s_ChanServ, u, CHAN_SET_OPNOTICE_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_OPNOTICE;
        notice_lang(s_ChanServ, u, CHAN_SET_OPNOTICE_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET OPNOTICE",
                     CHAN_SET_OPNOTICE_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

#define CHECKLEV(lev) ((ci->levels[(lev)] != ACCESS_INVALID) && (access->level >= ci->levels[(lev)]))

static int do_set_xop(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        if (!(ci->flags & CI_XOP)) {
            int i;
            ChanAccess *access;

            for (access = ci->access, i = 0; i < ci->accesscount;
                 access++, i++) {
                if (!access->in_use)
                    continue;
                /* This will probably cause wrong levels to be set, but hey,
                 * it's better than losing it altogether.
                 */
                if (CHECKLEV(CA_AKICK) || CHECKLEV(CA_SET)) {
                    access->level = ACCESS_SOP;
                } else if (CHECKLEV(CA_AUTOOP) || CHECKLEV(CA_OPDEOP)
                           || CHECKLEV(CA_OPDEOPME)) {
                    access->level = ACCESS_AOP;
#ifdef HAS_HALFOP
                } else if (CHECKLEV(CA_AUTOHALFOP) || CHECKLEV(CA_HALFOP)
                           || CHECKLEV(CA_HALFOPME)) {
                    access->level = ACCESS_HOP;
#endif
                } else if (CHECKLEV(CA_AUTOVOICE) || CHECKLEV(CA_VOICE)
                           || CHECKLEV(CA_VOICEME)) {
                    access->level = ACCESS_VOP;
                } else {
                    access->in_use = 0;
                    access->nc = NULL;
                }
            }

            reset_levels(ci);
            ci->flags |= CI_XOP;
        }

        alog("%s: %s!%s@%s enabled XOP for %s", s_ChanServ, u->nick,
             u->username, GetHost(u), ci->name);
        notice_lang(s_ChanServ, u, CHAN_SET_XOP_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_XOP;

        alog("%s: %s!%s@%s disabled XOP for %s", s_ChanServ, u->nick,
             u->username, GetHost(u), ci->name);
        notice_lang(s_ChanServ, u, CHAN_SET_XOP_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET XOP", CHAN_SET_XOP_SYNTAX);
    }
    return MOD_CONT;
}

#undef CHECKLEV

/*************************************************************************/

static int do_set_peace(User * u, ChannelInfo * ci, char *param)
{
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_PEACE;
        notice_lang(s_ChanServ, u, CHAN_SET_PEACE_ON);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_PEACE;
        notice_lang(s_ChanServ, u, CHAN_SET_PEACE_OFF);
    } else {
        syntax_error(s_ChanServ, u, "SET PEACE", CHAN_SET_PEACE_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_set_noexpire(User * u, ChannelInfo * ci, char *param)
{
    if (!is_services_admin(u)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
        return MOD_CONT;
    }
    if (stricmp(param, "ON") == 0) {
        ci->flags |= CI_NO_EXPIRE;
        notice_lang(s_ChanServ, u, CHAN_SET_NOEXPIRE_ON, ci->name);
    } else if (stricmp(param, "OFF") == 0) {
        ci->flags &= ~CI_NO_EXPIRE;
        notice_lang(s_ChanServ, u, CHAN_SET_NOEXPIRE_OFF, ci->name);
    } else {
        syntax_error(s_ChanServ, u, "SET NOEXPIRE",
                     CHAN_SET_NOEXPIRE_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* `last' is set to the last index this routine was called with
 * `perm' is incremented whenever a permission-denied error occurs
 */

static int xop_del(User * u, ChanAccess * access, int *perm, int uacc,
                   int xlev)
{
    if (!access->in_use || access->level != xlev)
        return 0;
    if (!is_services_admin(u) && uacc <= access->level) {
        (*perm)++;
        return 0;
    }
    access->nc = NULL;
    access->in_use = 0;
    return 1;
}

static int xop_del_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *last = va_arg(args, int *);
    int *perm = va_arg(args, int *);
    int uacc = va_arg(args, int);
    int xlev = va_arg(args, int);

    if (num < 1 || num > ci->accesscount)
        return 0;
    *last = num;

    return xop_del(u, &ci->access[num - 1], perm, uacc, xlev);
}


static int xop_list(User * u, int index, ChannelInfo * ci,
                    int *sent_header, int xlev, int xmsg)
{
    ChanAccess *access = &ci->access[index];

    if (!access->in_use || access->level != xlev)
        return 0;

    if (!*sent_header) {
        notice_lang(s_ChanServ, u, xmsg, ci->name);
        *sent_header = 1;
    }

    notice_lang(s_ChanServ, u, CHAN_XOP_LIST_FORMAT, index + 1,
                access->nc->display);
    return 1;
}

static int xop_list_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    int xlev = va_arg(args, int);
    int xmsg = va_arg(args, int);

    if (num < 1 || num > ci->accesscount)
        return 0;

    return xop_list(u, num - 1, ci, sent_header, xlev, xmsg);
}


static int do_xop(User * u, char *xname, int xlev, int *xmsgs)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");

    ChannelInfo *ci;
    NickAlias *na;
    NickCore *nc;

    int i;
    int change = 0;
    short ulev;
    int is_list = (cmd && stricmp(cmd, "LIST") == 0);
    int is_servadmin = is_services_admin(u);
    ChanAccess *access;

    /* If CLEAR, we don't need any parameters.
     * If LIST, we don't *require* any parameters, but we can take any.
     * If DEL or ADD we require a nick. */
    if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 : !nick)) {
        syntax_error(s_ChanServ, u, xname, xmsgs[0]);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!(ci->flags & CI_XOP)) {
        notice_lang(s_ChanServ, u, CHAN_XOP_ACCESS, s_ChanServ);
    } else if (stricmp(cmd, "ADD") == 0) {
        if (readonly) {
            notice_lang(s_ChanServ, u, xmsgs[1]);
            return MOD_CONT;
        }

        ulev = get_access(u, ci);

        if (!is_servadmin && (xlev >= ulev || ulev < ACCESS_AOP)) {
            notice_lang(s_ChanServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        na = findnick(nick);
        if (!na) {
            notice_lang(s_ChanServ, u, xmsgs[2]);
            return MOD_CONT;
        } else if (na->status & NS_VERBOTEN) {
            notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, na->nick);
            return MOD_CONT;
        }

        nc = na->nc;
        for (access = ci->access, i = 0; i < ci->accesscount;
             access++, i++) {
            if (access->nc == nc) {
                /**
                 * Patch provided by PopCorn to prevert AOP's reducing SOP's levels
                 **/
                if ((access->level >= ulev) && (!u->isSuperAdmin)) {
                    notice_lang(s_ChanServ, u, PERMISSION_DENIED);
                    return MOD_CONT;
                }
                change++;
                break;
            }
        }

        if (!change) {
            for (i = 0; i < ci->accesscount; i++)
                if (!ci->access[i].in_use)
                    break;

            if (i == ci->accesscount) {
                if (i < CSAccessMax) {
                    ci->accesscount++;
                    ci->access =
                        srealloc(ci->access,
                                 sizeof(ChanAccess) * ci->accesscount);
                } else {
                    notice_lang(s_ChanServ, u, CHAN_XOP_REACHED_LIMIT,
                                CSAccessMax);
                    return MOD_CONT;
                }
            }

            access = &ci->access[i];
            access->nc = nc;
        }

        access->in_use = 1;
        access->level = xlev;
        access->last_seen = 0;

        alog("%s: %s!%s@%s (level %d) %s access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, change ? "changed" : "set", access->level, na->nick, nc->display, ci->name);

        if (!change) {
            notice_lang(s_ChanServ, u, xmsgs[3], access->nc->display,
                        ci->name);
        } else {
            notice_lang(s_ChanServ, u, xmsgs[4], access->nc->display,
                        ci->name);
        }

    } else if (stricmp(cmd, "DEL") == 0) {
        if (readonly) {
            notice_lang(s_ChanServ, u, xmsgs[1]);
            return MOD_CONT;
        }

        if (ci->accesscount == 0) {
            notice_lang(s_ChanServ, u, xmsgs[11], chan);
            return MOD_CONT;
        }

        ulev = get_access(u, ci);

        /* Special case: is it a number/list?  Only do search if it isn't. */
        if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
            int count, deleted, last = -1, perm = 0;
            deleted =
                process_numlist(nick, &count, xop_del_callback, u, ci,
                                &last, &perm, ulev, xlev);
            if (!deleted) {
                if (perm) {
                    notice_lang(s_ChanServ, u, PERMISSION_DENIED);
                } else if (count == 1) {
                    notice_lang(s_ChanServ, u, xmsgs[5], last, ci->name);
                } else {
                    notice_lang(s_ChanServ, u, xmsgs[7], ci->name);
                }
            } else if (deleted == 1) {
                notice_lang(s_ChanServ, u, xmsgs[9], ci->name);
            } else {
                notice_lang(s_ChanServ, u, xmsgs[10], deleted, ci->name);
            }
        } else {
            na = findnick(nick);
            if (!na) {
                notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
                return MOD_CONT;
            }
            nc = na->nc;

            for (i = 0; i < ci->accesscount; i++)
                if (ci->access[i].nc == nc && ci->access[i].level == xlev)
                    break;

            if (i == ci->accesscount) {
                notice_lang(s_ChanServ, u, xmsgs[6], nick, chan);
                return MOD_CONT;
            }

            access = &ci->access[i];
            if (!is_servadmin && ulev <= access->level) {
                notice_lang(s_ChanServ, u, PERMISSION_DENIED);
            } else {
                notice_lang(s_ChanServ, u, xmsgs[8], access->nc->display,
                            ci->name);
                access->nc = NULL;
                access->in_use = 0;
            }
        }
    } else if (stricmp(cmd, "LIST") == 0) {
        int sent_header = 0;

        ulev = get_access(u, ci);

        if (!is_servadmin && ulev < ACCESS_AOP) {
            notice_lang(s_ChanServ, u, ACCESS_DENIED);
            return MOD_CONT;
        }

        if (ci->accesscount == 0) {
            notice_lang(s_ChanServ, u, xmsgs[11], ci->name);
            return MOD_CONT;
        }

        if (nick && strspn(nick, "1234567890,-") == strlen(nick)) {
            process_numlist(nick, NULL, xop_list_callback, u, ci,
                            &sent_header, xlev, xmsgs[12]);
        } else {
            for (i = 0; i < ci->accesscount; i++) {
                if (nick && ci->access[i].nc
                    && !match_wild_nocase(nick, ci->access[i].nc->display))
                    continue;
                xop_list(u, i, ci, &sent_header, xlev, xmsgs[12]);
            }
        }
        if (!sent_header)
            notice_lang(s_ChanServ, u, xmsgs[7], chan);
    } else if (stricmp(cmd, "CLEAR") == 0) {
        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
            return MOD_CONT;
        }

        if (ci->accesscount == 0) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
            return MOD_CONT;
        }

        if (!is_servadmin && !is_founder(u, ci)) {
            notice_lang(s_ChanServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        for (i = 0; i < ci->accesscount; i++) {
            if (ci->access[i].in_use && ci->access[i].level == xlev) {
                ci->access[i].nc = NULL;
                ci->access[i].in_use = 0;
            }
        }

        notice_lang(s_ChanServ, u, xmsgs[13]);
    } else {
        syntax_error(s_ChanServ, u, xname, xmsgs[0]);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_aop(User * u)
{
    return do_xop(u, "AOP", ACCESS_AOP, xop_msgs[0]);
}

/*************************************************************************/

#ifdef HAS_HALFOP

static int do_hop(User * u)
{
    return do_xop(u, "HOP", ACCESS_HOP, xop_msgs[3]);
}

#endif

/*************************************************************************/

static int do_sop(User * u)
{
    return do_xop(u, "SOP", ACCESS_SOP, xop_msgs[1]);
}

/*************************************************************************/

static int do_vop(User * u)
{
    return do_xop(u, "VOP", ACCESS_VOP, xop_msgs[2]);
}

/*************************************************************************/

/* `last' is set to the last index this routine was called with
 * `perm' is incremented whenever a permission-denied error occurs
 */

static int access_del(User * u, ChanAccess * access, int *perm, int uacc)
{
    if (!access->in_use)
        return 0;
    if (!is_services_admin(u) && uacc <= access->level) {
        (*perm)++;
        return 0;
    }
    access->nc = NULL;
    access->in_use = 0;
    return 1;
}

static int access_del_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *last = va_arg(args, int *);
    int *perm = va_arg(args, int *);
    int uacc = va_arg(args, int);
    if (num < 1 || num > ci->accesscount)
        return 0;
    *last = num;
    return access_del(u, &ci->access[num - 1], perm, uacc);
}


static int access_list(User * u, int index, ChannelInfo * ci,
                       int *sent_header)
{
    ChanAccess *access = &ci->access[index];
    char *xop;

    if (!access->in_use)
        return 0;

    if (!*sent_header) {
        notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_HEADER, ci->name);
        *sent_header = 1;
    }

    if (ci->flags & CI_XOP) {
        xop = get_xop_level(access->level);
        notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_XOP_FORMAT, index + 1,
                    xop, access->nc->display);
    } else {
        notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_AXS_FORMAT, index + 1,
                    access->level, access->nc->display);
    }
    return 1;
}

static int access_list_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    if (num < 1 || num > ci->accesscount)
        return 0;
    return access_list(u, num - 1, ci, sent_header);
}


static int do_access(User * u)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");
    char *s = strtok(NULL, " ");

    ChannelInfo *ci;
    NickAlias *na;
    NickCore *nc;
    ChanAccess *access;

    int i;
    short level = 0, ulev;
    int is_list = (cmd && stricmp(cmd, "LIST") == 0);
    int is_servadmin = is_services_admin(u);

    /* If LIST, we don't *require* any parameters, but we can take any.
     * If DEL, we require a nick and no level.
     * Else (ADD), we require a level (which implies a nick). */
    if (!cmd || ((is_list || !stricmp(cmd, "CLEAR")) ? 0 :
                 (stricmp(cmd, "DEL") == 0) ? (!nick || s) : !s)) {
        syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
        /* We still allow LIST in xOP mode, but not others */
    } else if ((ci->flags & CI_XOP) && !is_list) {
        notice_lang(s_ChanServ, u, CHAN_ACCESS_XOP, s_ChanServ);
    } else if (((is_list && !check_access(u, ci, CA_ACCESS_LIST))
                || (!is_list && !check_access(u, ci, CA_ACCESS_CHANGE)))
               && !is_servadmin) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (stricmp(cmd, "ADD") == 0) {
        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
            return MOD_CONT;
        }

        level = atoi(s);
        ulev = get_access(u, ci);

        if (!is_servadmin && level >= ulev) {
            notice_lang(s_ChanServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (level == 0) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_NONZERO);
            return MOD_CONT;
        } else if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_RANGE,
                        ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
            return MOD_CONT;
        }

        na = findnick(nick);
        if (!na) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_NICKS_ONLY);
            return MOD_CONT;
        }
        if (na->status & NS_VERBOTEN) {
            notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, nick);
            return MOD_CONT;
        }

        nc = na->nc;
        for (access = ci->access, i = 0; i < ci->accesscount;
             access++, i++) {
            if (access->nc == nc) {
                /* Don't allow lowering from a level >= ulev */
                if (!is_servadmin && access->level >= ulev) {
                    notice_lang(s_ChanServ, u, PERMISSION_DENIED);
                    return MOD_CONT;
                }
                if (access->level == level) {
                    notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_UNCHANGED,
                                access->nc->display, chan, level);
                    return MOD_CONT;
                }
                access->level = level;
                alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, access->level, na->nick, nc->display, ci->name);
                notice_lang(s_ChanServ, u, CHAN_ACCESS_LEVEL_CHANGED,
                            access->nc->display, chan, level);
                return MOD_CONT;
            }
        }

        for (i = 0; i < ci->accesscount; i++) {
            if (!ci->access[i].in_use)
                break;
        }
        if (i == ci->accesscount) {
            if (i < CSAccessMax) {
                ci->accesscount++;
                ci->access =
                    srealloc(ci->access,
                             sizeof(ChanAccess) * ci->accesscount);
            } else {
                notice_lang(s_ChanServ, u, CHAN_ACCESS_REACHED_LIMIT,
                            CSAccessMax);
                return MOD_CONT;
            }
        }

        access = &ci->access[i];
        access->nc = nc;
        access->in_use = 1;
        access->level = level;
        access->last_seen = 0;

        alog("%s: %s!%s@%s (level %d) set access level %d to %s (group %s) on channel %s", s_ChanServ, u->nick, u->username, GetHost(u), ulev, access->level, na->nick, nc->display, ci->name);
        notice_lang(s_ChanServ, u, CHAN_ACCESS_ADDED, nc->display,
                    ci->name, access->level);
    } else if (stricmp(cmd, "DEL") == 0) {

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
            return MOD_CONT;
        }

        if (ci->accesscount == 0) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
            return MOD_CONT;
        }

        /* Special case: is it a number/list?  Only do search if it isn't. */
        if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
            int count, deleted, last = -1, perm = 0;
            deleted = process_numlist(nick, &count, access_del_callback, u,
                                      ci, &last, &perm, get_access(u, ci));
            if (!deleted) {
                if (perm) {
                    notice_lang(s_ChanServ, u, PERMISSION_DENIED);
                } else if (count == 1) {
                    notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_SUCH_ENTRY,
                                last, ci->name);
                } else {
                    notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH,
                                ci->name);
                }
            } else if (deleted == 1) {
                notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_ONE,
                            ci->name);
            } else {
                notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED_SEVERAL,
                            deleted, ci->name);
            }
        } else {
            na = findnick(nick);
            if (!na) {
                notice_lang(s_ChanServ, u, NICK_X_NOT_REGISTERED, nick);
                return MOD_CONT;
            }
            nc = na->nc;
            for (i = 0; i < ci->accesscount; i++) {
                if (ci->access[i].nc == nc)
                    break;
            }
            if (i == ci->accesscount) {
                notice_lang(s_ChanServ, u, CHAN_ACCESS_NOT_FOUND, nick,
                            chan);
                return MOD_CONT;
            }
            access = &ci->access[i];
            if (!is_servadmin && get_access(u, ci) <= access->level) {
                notice_lang(s_ChanServ, u, PERMISSION_DENIED);
            } else {
                notice_lang(s_ChanServ, u, CHAN_ACCESS_DELETED,
                            access->nc->display, ci->name);
                alog("%s: %s!%s@%s (level %d) deleted access of %s (group %s) on %s", s_ChanServ, u->nick, u->username, GetHost(u), get_access(u, ci), na->nick, access->nc->display, chan);
                access->nc = NULL;
                access->in_use = 0;
            }
        }
    } else if (stricmp(cmd, "LIST") == 0) {
        int sent_header = 0;

        if (ci->accesscount == 0) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_EMPTY, chan);
            return MOD_CONT;
        }
        if (nick && strspn(nick, "1234567890,-") == strlen(nick)) {
            process_numlist(nick, NULL, access_list_callback, u, ci,
                            &sent_header);
        } else {
            for (i = 0; i < ci->accesscount; i++) {
                if (nick && ci->access[i].nc
                    && !match_wild_nocase(nick, ci->access[i].nc->display))
                    continue;
                access_list(u, i, ci, &sent_header);
            }
        }
        if (!sent_header) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_NO_MATCH, chan);
        } else {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_LIST_FOOTER, ci->name);
        }
    } else if (stricmp(cmd, "CLEAR") == 0) {

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_ACCESS_DISABLED);
            return MOD_CONT;
        }

        if (!is_servadmin && !is_founder(u, ci)) {
            notice_lang(s_ChanServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        free(ci->access);
        ci->access = NULL;
        ci->accesscount = 0;

        notice_lang(s_ChanServ, u, CHAN_ACCESS_CLEAR);
        alog("%s: %s!%s@%s (level %d) cleared access list on %s",
             s_ChanServ, u->nick, u->username, GetHost(u), get_access(u,
                                                                      ci),
             chan);

    } else {
        syntax_error(s_ChanServ, u, "ACCESS", CHAN_ACCESS_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* Is the mask stuck? */

AutoKick *is_stuck(ChannelInfo * ci, char *mask)
{
    int i;
    AutoKick *akick;

    for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
        if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)
            || !(akick->flags & AK_STUCK))
            continue;
        /* Example: mask = *!*@*.org and akick->u.mask = *!*@*.epona.org */
        if (match_wild_nocase(mask, akick->u.mask))
            return akick;
#ifdef IRC_DREAMFORGE
        /* Example: mask = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */
        if (match_wild_nocase(akick->u.mask, mask))
            return akick;
#endif
    }

    return NULL;
}

/* Ban the stuck mask in a safe manner. */

void stick_mask(ChannelInfo * ci, AutoKick * akick)
{
    int i;
    char *av[2];

    for (i = 0; i < ci->c->bancount; i++) {
        /* If akick is already covered by a wider ban.
           Example: c->bans[i] = *!*@*.org and akick->u.mask = *!*@*.epona.org */
        if (match_wild_nocase(ci->c->bans[i], akick->u.mask))
            return;

#ifdef IRC_DREAMFORGE
        /* If akick is wider than a ban already in place.
           Example: c->bans[i] = *!*@irc.epona.org and akick->u.mask = *!*@*.epona.org */
        if (match_wild_nocase(akick->u.mask, ci->c->bans[i]))
            return;
#endif
    }

    /* Falling there means set the ban */

    av[0] = sstrdup("+b");
    av[1] = akick->u.mask;
    send_cmd(whosends(ci), "MODE %s +b %s", ci->c->name, akick->u.mask);
    chan_set_modes(s_ChanServ, ci->c, 2, av, 1);
    free(av[0]);
}

/* Ban the stuck mask in a safe manner. */

void stick_all(ChannelInfo * ci)
{
    int i;
    char *av[2];
    AutoKick *akick;

    for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
        if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK)
            || !(akick->flags & AK_STUCK))
            continue;

        av[0] = sstrdup("+b");
        av[1] = akick->u.mask;
        send_cmd(whosends(ci), "MODE %s +b %s", ci->c->name,
                 akick->u.mask);
        chan_set_modes(s_ChanServ, ci->c, 2, av, 1);
        free(av[0]);
    }
}

/* `last' is set to the last index this routine was called with */
static int akick_del(User * u, AutoKick * akick)
{
    if (!(akick->flags & AK_USED))
        return 0;
    if (akick->flags & AK_ISNICK) {
        akick->u.nc = NULL;
    } else {
        free(akick->u.mask);
        akick->u.mask = NULL;
    }
    if (akick->reason) {
        free(akick->reason);
        akick->reason = NULL;
    }
    if (akick->creator) {
        free(akick->creator);
        akick->creator = NULL;
    }
    akick->addtime = 0;
    akick->flags = 0;
    return 1;
}

static int akick_del_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *last = va_arg(args, int *);
    if (num < 1 || num > ci->akickcount)
        return 0;
    *last = num;
    return akick_del(u, &ci->akick[num - 1]);
}


static int akick_list(User * u, int index, ChannelInfo * ci,
                      int *sent_header)
{
    AutoKick *akick = &ci->akick[index];

    if (!(akick->flags & AK_USED))
        return 0;
    if (!*sent_header) {
        notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name);
        *sent_header = 1;
    }

    notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_FORMAT, index + 1,
                ((akick->flags & AK_ISNICK) ? akick->u.nc->
                 display : akick->u.mask),
                (akick->reason ? akick->
                 reason : getstring(u->na, NO_REASON)));
    return 1;
}

static int akick_list_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    if (num < 1 || num > ci->akickcount)
        return 0;
    return akick_list(u, num - 1, ci, sent_header);
}

static int akick_view(User * u, int index, ChannelInfo * ci,
                      int *sent_header)
{
    AutoKick *akick = &ci->akick[index];
    char timebuf[64];
    struct tm tm;

    if (!(akick->flags & AK_USED))
        return 0;
    if (!*sent_header) {
        notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_HEADER, ci->name);
        *sent_header = 1;
    }

    if (akick->addtime) {
        tm = *localtime(&akick->addtime);
        strftime_lang(timebuf, sizeof(timebuf), u,
                      STRFTIME_SHORT_DATE_FORMAT, &tm);
    } else {
        snprintf(timebuf, sizeof(timebuf), getstring(u->na, UNKNOWN));
    }

    notice_lang(s_ChanServ, u,
                ((akick->
                  flags & AK_STUCK) ? CHAN_AKICK_VIEW_FORMAT_STUCK :
                 CHAN_AKICK_VIEW_FORMAT), index + 1,
                ((akick->flags & AK_ISNICK) ? akick->u.nc->
                 display : akick->u.mask),
                akick->creator ? akick->creator : getstring(u->na,
                                                            UNKNOWN),
                timebuf,
                (akick->reason ? akick->
                 reason : getstring(u->na, NO_REASON)));
    return 1;
}

static int akick_view_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    if (num < 1 || num > ci->akickcount)
        return 0;
    return akick_view(u, num - 1, ci, sent_header);
}

static int do_akick(User * u)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *mask = strtok(NULL, " ");
    char *reason = strtok(NULL, "");
    ChannelInfo *ci;
    AutoKick *akick;
    int i;

    if (!cmd || (!mask && (!stricmp(cmd, "ADD") || !stricmp(cmd, "STICK")
                           || !stricmp(cmd, "UNSTICK")
                           || !stricmp(cmd, "DEL")))) {

        syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!check_access(u, ci, CA_AKICK) && !is_services_admin(u)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (stricmp(cmd, "ADD") == 0) {

        NickAlias *na = findnick(mask);
        NickCore *nc = NULL;
        char *nick, *user, *host;

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
            return MOD_CONT;
        }

        if (!na) {
            split_usermask(mask, &nick, &user, &host);
            mask =
                scalloc(strlen(nick) + strlen(user) + strlen(host) + 3, 1);
            sprintf(mask, "%s!%s@%s", nick, user, host);
            free(nick);
            free(user);
            free(host);
        } else {
            if (na->status & NS_VERBOTEN) {
                notice_lang(s_ChanServ, u, NICK_X_FORBIDDEN, mask);
                return MOD_CONT;
            }
            nc = na->nc;
        }

        /* Check excepts BEFORE we get this far */
#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID)
        if (is_excepted_mask(ci, mask) == 1) {
            notice_lang(s_ChanServ, u, CHAN_EXCEPTED, mask, chan);
            return MOD_CONT;
        }
#endif

        for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
            if (!(akick->flags & AK_USED))
                continue;
            if ((akick->flags & AK_ISNICK) ? akick->u.nc == nc
                : stricmp(akick->u.mask, mask) == 0) {
                notice_lang(s_ChanServ, u, CHAN_AKICK_ALREADY_EXISTS,
                            (akick->flags & AK_ISNICK) ? akick->u.nc->
                            display : akick->u.mask, chan);
                return MOD_CONT;
            }
        }

        for (i = 0; i < ci->akickcount; i++) {
            if (!(ci->akick[i].flags & AK_USED))
                break;
        }
        if (i == ci->akickcount) {
            if (ci->akickcount >= CSAutokickMax) {
                notice_lang(s_ChanServ, u, CHAN_AKICK_REACHED_LIMIT,
                            CSAutokickMax);
                return MOD_CONT;
            }
            ci->akickcount++;
            ci->akick =
                srealloc(ci->akick, sizeof(AutoKick) * ci->akickcount);
        }
        akick = &ci->akick[i];
        akick->flags = AK_USED;
        if (nc) {
            akick->flags |= AK_ISNICK;
            akick->u.nc = nc;
        } else {
            akick->u.mask = mask;
        }
        akick->creator = sstrdup(u->nick);
        akick->addtime = time(NULL);
        if (reason) {
            if (strlen(reason) > 200)
                reason[200] = '\0';
            akick->reason = sstrdup(reason);
        } else {
            akick->reason = NULL;
        }
        notice_lang(s_ChanServ, u, CHAN_AKICK_ADDED, mask, chan);

    } else if (stricmp(cmd, "STICK") == 0) {
        NickAlias *na;
        NickCore *nc;

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
            return MOD_CONT;
        }

        if (ci->akickcount == 0) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, ci->name);
            return MOD_CONT;
        }

        na = findnick(mask);
        nc = (na ? na->nc : NULL);

        for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
            if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK))
                continue;
            if (!stricmp(akick->u.mask, mask))
                break;
        }

        if (i == ci->akickcount) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask,
                        ci->name);
            return MOD_CONT;
        }

        akick->flags |= AK_STUCK;
        notice_lang(s_ChanServ, u, CHAN_AKICK_STUCK, akick->u.mask,
                    ci->name);

        if (ci->c)
            stick_mask(ci, akick);
    } else if (stricmp(cmd, "UNSTICK") == 0) {
        NickAlias *na;
        NickCore *nc;

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
            return MOD_CONT;
        }

        if (ci->akickcount == 0) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, ci->name);
            return MOD_CONT;
        }

        na = findnick(mask);
        nc = (na ? na->nc : NULL);

        for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
            if (!(akick->flags & AK_USED) || (akick->flags & AK_ISNICK))
                continue;
            if (!stricmp(akick->u.mask, mask))
                break;
        }

        if (i == ci->akickcount) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask,
                        ci->name);
            return MOD_CONT;
        }

        akick->flags &= ~AK_STUCK;
        notice_lang(s_ChanServ, u, CHAN_AKICK_UNSTUCK, akick->u.mask,
                    ci->name);

    } else if (stricmp(cmd, "DEL") == 0) {

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
            return MOD_CONT;
        }

        if (ci->akickcount == 0) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
            return MOD_CONT;
        }

        /* Special case: is it a number/list?  Only do search if it isn't. */
        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
            int count, deleted, last = -1;
            deleted = process_numlist(mask, &count, akick_del_callback, u,
                                      ci, &last);
            if (!deleted) {
                if (count == 1) {
                    notice_lang(s_ChanServ, u, CHAN_AKICK_NO_SUCH_ENTRY,
                                last, ci->name);
                } else {
                    notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH,
                                ci->name);
                }
            } else if (deleted == 1) {
                notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_ONE,
                            ci->name);
            } else {
                notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED_SEVERAL,
                            deleted, ci->name);
            }
        } else {
            NickAlias *na = findnick(mask);
            NickCore *nc = (na ? na->nc : NULL);

            for (akick = ci->akick, i = 0; i < ci->akickcount;
                 akick++, i++) {
                if (!(akick->flags & AK_USED))
                    continue;
                if (((akick->flags & AK_ISNICK) && akick->u.nc == nc)
                    || (!(akick->flags & AK_ISNICK)
                        && stricmp(akick->u.mask, mask) == 0))
                    break;
            }
            if (i == ci->akickcount) {
                notice_lang(s_ChanServ, u, CHAN_AKICK_NOT_FOUND, mask,
                            chan);
                return MOD_CONT;
            }
            notice_lang(s_ChanServ, u, CHAN_AKICK_DELETED, mask, chan);
            akick_del(u, akick);
        }

    } else if (stricmp(cmd, "LIST") == 0) {
        int sent_header = 0;

        if (ci->akickcount == 0) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
            return MOD_CONT;
        }
        if (mask && isdigit(*mask) &&
            strspn(mask, "1234567890,-") == strlen(mask)) {
            process_numlist(mask, NULL, akick_list_callback, u, ci,
                            &sent_header);
        } else {
            for (akick = ci->akick, i = 0; i < ci->akickcount;
                 akick++, i++) {
                if (!(akick->flags & AK_USED))
                    continue;
                if (mask) {
                    if (!(akick->flags & AK_ISNICK)
                        && !match_wild_nocase(mask, akick->u.mask))
                        continue;
                    if ((akick->flags & AK_ISNICK)
                        && !match_wild_nocase(mask, akick->u.nc->display))
                        continue;
                }
                akick_list(u, i, ci, &sent_header);
            }
        }
        if (!sent_header)
            notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan);

    } else if (stricmp(cmd, "VIEW") == 0) {
        int sent_header = 0;

        if (ci->akickcount == 0) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_LIST_EMPTY, chan);
            return MOD_CONT;
        }
        if (mask && isdigit(*mask) &&
            strspn(mask, "1234567890,-") == strlen(mask)) {
            process_numlist(mask, NULL, akick_view_callback, u, ci,
                            &sent_header);
        } else {
            for (akick = ci->akick, i = 0; i < ci->akickcount;
                 akick++, i++) {
                if (!(akick->flags & AK_USED))
                    continue;
                if (mask) {
                    if (!(akick->flags & AK_ISNICK)
                        && !match_wild_nocase(mask, akick->u.mask))
                        continue;
                    if ((akick->flags & AK_ISNICK)
                        && !match_wild_nocase(mask, akick->u.nc->display))
                        continue;
                }
                akick_view(u, i, ci, &sent_header);
            }
        }
        if (!sent_header)
            notice_lang(s_ChanServ, u, CHAN_AKICK_NO_MATCH, chan);

    } else if (stricmp(cmd, "ENFORCE") == 0) {
        Channel *c = findchan(ci->name);
        struct c_userlist *cu = NULL;
        struct c_userlist *next;
        char *argv[3];
        int count = 0;

        if (!c) {
            notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, ci->name);
            return MOD_CONT;
        }

        cu = c->users;

        while (cu) {
            next = cu->next;
            if (check_kick(cu->user, c->name)) {
                argv[0] = sstrdup(c->name);
                argv[1] = sstrdup(cu->user->nick);
                argv[2] = sstrdup(CSAutokickReason);

                do_kick(s_ChanServ, 3, argv);

                free(argv[2]);
                free(argv[1]);
                free(argv[0]);

                count++;
            }
            cu = next;
        }

        notice_lang(s_ChanServ, u, CHAN_AKICK_ENFORCE_DONE, chan, count);

    } else if (stricmp(cmd, "CLEAR") == 0) {

        if (readonly) {
            notice_lang(s_ChanServ, u, CHAN_AKICK_DISABLED);
            return MOD_CONT;
        }

        for (akick = ci->akick, i = 0; i < ci->akickcount; akick++, i++) {
            if (!(akick->flags & AK_USED))
                continue;
            akick_del(u, akick);
        }

        free(ci->akick);
        ci->akick = NULL;
        ci->akickcount = 0;

        notice_lang(s_ChanServ, u, CHAN_AKICK_CLEAR);

    } else {
        syntax_error(s_ChanServ, u, "AKICK", CHAN_AKICK_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_levels(User * u)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *what = strtok(NULL, " ");
    char *s = strtok(NULL, " ");
    char *error;

    ChannelInfo *ci;
    short level;
    int i;

    /* If SET, we want two extra parameters; if DIS[ABLE] or FOUNDER, we want only
     * one; else, we want none.
     */
    if (!cmd
        || ((stricmp(cmd, "SET") == 0) ? !s
            : ((strnicmp(cmd, "DIS", 3) == 0)) ? (!what || s) : !!what)) {
        syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (ci->flags & CI_XOP) {
        notice_lang(s_ChanServ, u, CHAN_LEVELS_XOP);
    } else if (!is_founder(u, ci) && !is_services_admin(u)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (stricmp(cmd, "SET") == 0) {
        level = strtol(s, &error, 10);

        if (*error != '\0') {
            syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
            return MOD_CONT;
        }

        if (level <= ACCESS_INVALID || level >= ACCESS_FOUNDER) {
            notice_lang(s_ChanServ, u, CHAN_LEVELS_RANGE,
                        ACCESS_INVALID + 1, ACCESS_FOUNDER - 1);
            return MOD_CONT;
        }

        for (i = 0; levelinfo[i].what >= 0; i++) {
            if (stricmp(levelinfo[i].name, what) == 0) {
                ci->levels[levelinfo[i].what] = level;

                alog("%s: %s!%s@%s set level %s on channel %s to %d",
                     s_ChanServ, u->nick, u->username, GetHost(u),
                     levelinfo[i].name, ci->name, level);
                notice_lang(s_ChanServ, u, CHAN_LEVELS_CHANGED,
                            levelinfo[i].name, chan, level);
                return MOD_CONT;
            }
        }

        notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);

    } else if (stricmp(cmd, "DIS") == 0 || stricmp(cmd, "DISABLE") == 0) {
        for (i = 0; levelinfo[i].what >= 0; i++) {
            if (stricmp(levelinfo[i].name, what) == 0) {
                ci->levels[levelinfo[i].what] = ACCESS_INVALID;

                alog("%s: %s!%s@%s disabled level %s on channel %s",
                     s_ChanServ, u->nick, u->username, GetHost(u),
                     levelinfo[i].name, ci->name);
                notice_lang(s_ChanServ, u, CHAN_LEVELS_DISABLED,
                            levelinfo[i].name, chan);
                return MOD_CONT;
            }
        }

        notice_lang(s_ChanServ, u, CHAN_LEVELS_UNKNOWN, what, s_ChanServ);
    } else if (stricmp(cmd, "LIST") == 0) {
        int i;

        notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_HEADER, chan);

        if (!levelinfo_maxwidth) {
            for (i = 0; levelinfo[i].what >= 0; i++) {
                int len = strlen(levelinfo[i].name);
                if (len > levelinfo_maxwidth)
                    levelinfo_maxwidth = len;
            }
        }

        for (i = 0; levelinfo[i].what >= 0; i++) {
            int j = ci->levels[levelinfo[i].what];

            if (j == ACCESS_INVALID) {
                j = levelinfo[i].what;

                if (j == CA_AUTOOP || j == CA_AUTODEOP || j == CA_AUTOVOICE
                    || j == CA_NOJOIN) {
                    notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
                                levelinfo_maxwidth, levelinfo[i].name);
                } else {
                    notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_DISABLED,
                                levelinfo_maxwidth, levelinfo[i].name);
                }
            } else if (j == ACCESS_FOUNDER) {
                notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_FOUNDER,
                            levelinfo_maxwidth, levelinfo[i].name);
            } else {
                notice_lang(s_ChanServ, u, CHAN_LEVELS_LIST_NORMAL,
                            levelinfo_maxwidth, levelinfo[i].name, j);
            }
        }

    } else if (stricmp(cmd, "RESET") == 0) {
        reset_levels(ci);

        alog("%s: %s!%s@%s reset levels definitions on channel %s",
             s_ChanServ, u->nick, u->username, GetHost(u), ci->name);
        notice_lang(s_ChanServ, u, CHAN_LEVELS_RESET, chan);
    } else {
        syntax_error(s_ChanServ, u, "LEVELS", CHAN_LEVELS_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* SADMINS and users, who have identified for a channel, can now cause it's
 * Enstry Message and Successor to be displayed by supplying the ALL parameter.
 * Syntax: INFO channel [ALL]
 * -TheShadow (29 Mar 1999)
 */

static int do_info(User * u)
{
    char *chan = strtok(NULL, " ");
    char *param = strtok(NULL, " ");
    ChannelInfo *ci;
    char buf[BUFSIZE], *end;
    struct tm *tm;
    int need_comma = 0;
    const char *commastr = getstring(u->na, COMMA_SPACE);
    int is_servadmin = is_services_admin(u);
    int show_all = 0;

    if (!chan) {
        syntax_error(s_ChanServ, u, "INFO", CHAN_INFO_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        if (is_oper(u) && ci->forbidby)
            notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN_OPER, chan,
                        ci->forbidby,
                        (ci->forbidreason ? ci->
                         forbidreason : getstring(u->na, NO_REASON)));
        else
            notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!ci->founder) {
        /* Paranoia... this shouldn't be able to happen */
        delchan(ci);
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else {

        /* Should we show all fields? Only for sadmins and identified users */
        if (param && stricmp(param, "ALL") == 0 &&
            (check_access(u, ci, CA_INFO) || is_servadmin))
            show_all = 1;

        notice_lang(s_ChanServ, u, CHAN_INFO_HEADER, chan);
        notice_lang(s_ChanServ, u, CHAN_INFO_NO_FOUNDER,
                    ci->founder->display);

        if (show_all && ci->successor)
            notice_lang(s_ChanServ, u, CHAN_INFO_NO_SUCCESSOR,
                        ci->successor->display);

        notice_lang(s_ChanServ, u, CHAN_INFO_DESCRIPTION, ci->desc);
        tm = localtime(&ci->time_registered);
        strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
        notice_lang(s_ChanServ, u, CHAN_INFO_TIME_REGGED, buf);
        tm = localtime(&ci->last_used);
        strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
        notice_lang(s_ChanServ, u, CHAN_INFO_LAST_USED, buf);
        if (ci->last_topic && (show_all || (!(ci->mlock_on & CMODE_s)
                                            && (!ci->c
                                                || !(ci->c->
                                                     mode & CMODE_s))))) {
            notice_lang(s_ChanServ, u, CHAN_INFO_LAST_TOPIC,
                        ci->last_topic);
            notice_lang(s_ChanServ, u, CHAN_INFO_TOPIC_SET_BY,
                        ci->last_topic_setter);
        }

        if (ci->entry_message && show_all)
            notice_lang(s_ChanServ, u, CHAN_INFO_ENTRYMSG,
                        ci->entry_message);
        if (ci->url)
            notice_lang(s_ChanServ, u, CHAN_INFO_URL, ci->url);
        if (ci->email)
            notice_lang(s_ChanServ, u, CHAN_INFO_EMAIL, ci->email);

        if (show_all) {
            notice_lang(s_ChanServ, u, CHAN_INFO_BANTYPE, ci->bantype);

            end = buf;
            *end = 0;
            if (ci->flags & CI_KEEPTOPIC) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s",
                                getstring(u->na, CHAN_INFO_OPT_KEEPTOPIC));
                need_comma = 1;
            }
            if (ci->flags & CI_OPNOTICE) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_OPNOTICE));
                need_comma = 1;
            }
            if (ci->flags & CI_PEACE) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_PEACE));
                need_comma = 1;
            }
            if (ci->flags & CI_PRIVATE) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_PRIVATE));
                need_comma = 1;
            }
            if (ci->flags & CI_RESTRICTED) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na,
                                          CHAN_INFO_OPT_RESTRICTED));
                need_comma = 1;
            }
            if (ci->flags & CI_SECURE) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_SECURE));
                need_comma = 1;
            }
            if (ci->flags & CI_SECUREOPS) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_SECUREOPS));
                need_comma = 1;
            }
            if (ci->flags & CI_SECUREFOUNDER) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na,
                                          CHAN_INFO_OPT_SECUREFOUNDER));
                need_comma = 1;
            }
            if ((ci->flags & CI_SIGNKICK)
                || (ci->flags & CI_SIGNKICK_LEVEL)) {
                end +=
                    snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                             need_comma ? commastr : "", getstring(u->na,
                                                                   CHAN_INFO_OPT_SIGNKICK));
                need_comma = 1;
            }
            if (ci->flags & CI_TOPICLOCK) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_TOPICLOCK));
                need_comma = 1;
            }
            if (ci->flags & CI_XOP) {
                end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                                need_comma ? commastr : "",
                                getstring(u->na, CHAN_INFO_OPT_XOP));
                need_comma = 1;
            }
            notice_lang(s_ChanServ, u, CHAN_INFO_OPTIONS,
                        *buf ? buf : getstring(u->na, CHAN_INFO_OPT_NONE));
            notice_lang(s_ChanServ, u, CHAN_INFO_MODE_LOCK,
                        get_mlock_modes(ci, 1));

        }

        if ((ci->flags & CI_NO_EXPIRE) && show_all)
            notice_lang(s_ChanServ, u, CHAN_INFO_NO_EXPIRE);
        if (ci->flags & CI_SUSPENDED) {
            notice_lang(s_ChanServ, u, CHAN_X_SUSPENDED, ci->forbidby,
                        (ci->forbidreason ? ci->
                         forbidreason : getstring(u->na, NO_REASON)));
        }

        if (!show_all && (check_access(u, ci, CA_INFO) || is_servadmin))
            notice_lang(s_ChanServ, u, NICK_INFO_FOR_MORE, s_ChanServ,
                        ci->name);

    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_list(User * u)
{
    char *pattern = strtok(NULL, " ");
    ChannelInfo *ci;
    int nchans, i;
    char buf[BUFSIZE];
    int is_servadmin = is_services_admin(u);
    int count = 0, from = 0, to = 0;
    char *tmp = NULL;
    char *s = NULL;
    char *keyword;
    int32 matchflags = 0;


    if (CSListOpersOnly && (!u || !is_oper(u))) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
        return MOD_CONT;
    }

    if (!pattern) {
        syntax_error(s_ChanServ, u, "LIST",
                     is_servadmin ? CHAN_LIST_SERVADMIN_SYNTAX :
                     CHAN_LIST_SYNTAX);
    } else {

        if (pattern) {
            if (pattern[0] == '#') {
                tmp = myStrGetOnlyToken((pattern + 1), '-', 0); /* Read FROM out */
                if (!tmp) {
                    return MOD_CONT;
                }
                for (s = tmp; *s; s++) {
                    if (!isdigit(*s)) {
                        return MOD_CONT;
                    }
                }
                from = atoi(tmp);
                tmp = myStrGetTokenRemainder(pattern, '-', 1);  /* Read TO out */
                if (!tmp) {
                    return MOD_CONT;
                }
                for (s = tmp; *s; s++) {
                    if (!isdigit(*s)) {
                        return MOD_CONT;
                    }
                }
                to = atoi(tmp);
                pattern = sstrdup("*");
            }
        }

        nchans = 0;

        while (is_servadmin && (keyword = strtok(NULL, " "))) {
            if (stricmp(keyword, "FORBIDDEN") == 0)
                matchflags |= CI_VERBOTEN;
            if (stricmp(keyword, "SUSPENDED") == 0)
                matchflags |= CI_SUSPENDED;
            if (stricmp(keyword, "NOEXPIRE") == 0)
                matchflags |= CI_NO_EXPIRE;
        }

        notice_lang(s_ChanServ, u, CHAN_LIST_HEADER, pattern);
        for (i = 0; i < 256; i++) {
            for (ci = chanlists[i]; ci; ci = ci->next) {
                if (!is_servadmin && ((ci->flags & CI_PRIVATE)
                                      || (ci->flags & CI_VERBOTEN)))
                    continue;
                if ((matchflags != 0) && !(ci->flags & matchflags))
                    continue;

                if (stricmp(pattern, ci->name) == 0
                    || match_wild_nocase(pattern, ci->name)) {
                    if ((((count + 1 >= from) && (count + 1 <= to))
                         || ((from == 0) && (to == 0)))
                        && (++nchans <= CSListMax)) {
                        char noexpire_char = ' ';
                        if (is_servadmin && (ci->flags & CI_NO_EXPIRE))
                            noexpire_char = '!';

                        if (ci->flags & CI_VERBOTEN) {
                            snprintf(buf, sizeof(buf),
                                     "%-20s  [Forbidden]", ci->name);
                        } else if (ci->flags & CI_SUSPENDED) {
                            snprintf(buf, sizeof(buf),
                                     "%-20s  [Suspended]", ci->name);
                        } else {
                            snprintf(buf, sizeof(buf), "%-20s  %s",
                                     ci->name, ci->desc ? ci->desc : "");
                        }

                        notice_user(s_ChanServ, u, "  %c%s",
                                    noexpire_char, buf);
                    }
                    count++;
                }
            }
        }
        notice_lang(s_ChanServ, u, CHAN_LIST_END,
                    nchans > CSListMax ? CSListMax : nchans, nchans);
    }
    return MOD_CONT;

}

/*************************************************************************/

static int do_invite(User * u)
{
    char *chan = strtok(NULL, " ");
    Channel *c;
    ChannelInfo *ci;

    if (!chan) {
        syntax_error(s_ChanServ, u, "INVITE", CHAN_INVITE_SYNTAX);
    } else if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!u || !check_access(u, ci, CA_INVITE)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
    } else {
        send_cmd(whosends(ci), "INVITE %s %s", u->nick, chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* do_util: not a command, but does the job of other */

static int do_util(User * u, CSModeUtil * util)
{
    char *av[2];
    char *chan = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");

    Channel *c;
    ChannelInfo *ci;
    User *u2;

    int is_same;

    if (!chan) {
        struct u_chanlist *uc;

        av[0] = util->mode;
        av[1] = u->nick;

        /* Sets the mode to the user on every channels he is on. */

        for (uc = u->chans; uc; uc = uc->next) {
            if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
                && check_access(u, ci, util->levelself)) {
                send_cmd(whosends(ci), "MODE %s %s %s", uc->chan->name,
                         util->mode, u->nick);
                chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);

                if (util->notice && ci->flags & util->notice)
                    notice(whosends(ci), chan,
                           "%s command used for %s by %s", util->name,
                           u->nick, u->nick);
            }
        }

        return MOD_CONT;
    } else if (!nick) {
        nick = u->nick;
    }

    is_same = (nick == u->nick) ? 1 : (stricmp(nick, u->nick) == 0);

    if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
    } else if (is_same ? !(u2 = u) : !(u2 = finduser(nick))) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, nick);
    } else if (!is_on_chan(c, u2)) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u2->nick, c->name);
    } else if (is_same ? !check_access(u, ci, util->levelself) :
               !check_access(u, ci, util->level)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (*util->mode == '-' && !is_same && (ci->flags & CI_PEACE)
               && (get_access(u2, ci) >= get_access(u, ci))) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
#if defined (IRC_ULTIMATE) || defined (IRC_ULTIMATE3)
    } else if (*util->mode == '-' && is_protected(u2)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
#endif
    } else {
        send_cmd(whosends(ci), "MODE %s %s %s", c->name, util->mode,
                 u2->nick);

        av[0] = util->mode;
        av[1] = u2->nick;
        chan_set_modes(s_ChanServ, c, 2, av, 1);

        if (util->notice && ci->flags & util->notice)
            notice(whosends(ci), c->name, "%s command used for %s by %s",
                   util->name, u2->nick, u->nick);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_op(User * u)
{
    return do_util(u, &csmodeutils[MUT_OP]);
}

/*************************************************************************/

static int do_deop(User * u)
{
    return do_util(u, &csmodeutils[MUT_DEOP]);
}

/*************************************************************************/

static int do_voice(User * u)
{
    return do_util(u, &csmodeutils[MUT_VOICE]);
}

/*************************************************************************/

static int do_devoice(User * u)
{
    return do_util(u, &csmodeutils[MUT_DEVOICE]);
}

/*************************************************************************/

#ifdef HAS_HALFOP

static int do_halfop(User * u)
{
    return do_util(u, &csmodeutils[MUT_HALFOP]);
}

/*************************************************************************/

static int do_dehalfop(User * u)
{
    return do_util(u, &csmodeutils[MUT_DEHALFOP]);
}

#endif

/*************************************************************************/

#if defined(IRC_UNREAL) || defined(IRC_ULTIMATE3) || defined(IRC_VIAGRA)

static int do_protect(User * u)
{
    return do_util(u, &csmodeutils[MUT_PROTECT]);
}

/*************************************************************************/

static int do_deprotect(User * u)
{
    return do_util(u, &csmodeutils[MUT_DEPROTECT]);
}

/*************************************************************************/
#endif

#if defined(IRC_UNREAL) || defined(IRC_VIAGRA)

static int do_owner(User * u)
{
    char *av[2];
    char *chan = strtok(NULL, " ");

    Channel *c;
    ChannelInfo *ci;

    if (!chan) {
        struct u_chanlist *uc;

        av[0] = sstrdup("+q");
        av[1] = u->nick;

        /* Sets the mode to the user on every channels he is on. */

        for (uc = u->chans; uc; uc = uc->next) {
            if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
                && is_founder(u, ci)) {
                send_cmd(whosends(ci), "MODE %s %s %s", uc->chan->name,
                         av[0], u->nick);
                chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
            }
        }

        free(av[0]);
        return MOD_CONT;
    }

    if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
    } else if (!is_on_chan(c, u)) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u->nick, c->name);
    } else if (!is_founder(u, ci)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else {
        send_cmd(whosends(ci), "MODE %s +q %s", c->name, u->nick);

        av[0] = sstrdup("+q");
        av[1] = u->nick;
        chan_set_modes(s_ChanServ, c, 2, av, 1);
        free(av[0]);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_deowner(User * u)
{
    char *av[2];
    char *chan = strtok(NULL, " ");

    Channel *c;
    ChannelInfo *ci;

    if (!chan) {
        struct u_chanlist *uc;

        av[0] = sstrdup("-q");
        av[1] = u->nick;

        /* Sets the mode to the user on every channels he is on. */

        for (uc = u->chans; uc; uc = uc->next) {
            if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
                && is_founder(u, ci)) {
                send_cmd(whosends(ci), "MODE %s %s %s", uc->chan->name,
                         av[0], u->nick);
                chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
            }
        }

        free(av[0]);
        return MOD_CONT;
    }

    if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
    } else if (!is_on_chan(c, u)) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u->nick, c->name);
    } else if (!is_founder(u, ci)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else {
        send_cmd(whosends(ci), "MODE %s -q %s", c->name, u->nick);

        av[0] = sstrdup("-q");
        av[1] = u->nick;
        chan_set_modes(s_ChanServ, c, 2, av, 1);
        free(av[0]);
    }
    return MOD_CONT;
}

#endif

/*************************************************************************/

static int do_cs_kick(User * u)
{
    char *chan = strtok(NULL, " ");
    char *params = strtok(NULL, " ");
    char *reason = strtok(NULL, "");

    Channel *c;
    ChannelInfo *ci;
    User *u2;

    int is_same;

    if (!reason) {
        reason = "Requested";
    } else {
        if (strlen(reason) > 200)
            reason[200] = '\0';
    }

    if (!chan) {
        struct u_chanlist *uc, *next;

        /* Kicks the user on every channels he is on. */

        for (uc = u->chans; uc; uc = next) {
            next = uc->next;
            if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
                && check_access(u, ci, CA_KICKME)) {
                char *av[3];

                if ((ci->flags & CI_SIGNKICK)
                    || ((ci->flags & CI_SIGNKICK_LEVEL)
                        && !check_access(u, ci, CA_SIGNKICK)))
                    send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name,
                             u->nick, reason, u->nick);
                else
                    send_cmd(whosends(ci), "KICK %s %s :%s", ci->name,
                             u->nick, reason);
                av[0] = ci->name;
                av[1] = u->nick;
                av[2] = reason;
                do_kick(s_ChanServ, 3, av);
            }
        }

        return MOD_CONT;
    } else if (!params) {
        params = u->nick;
    }

    is_same = (params == u->nick) ? 1 : (stricmp(params, u->nick) == 0);

    if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (is_same ? !(u2 = u) : !(u2 = finduser(params))) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, params);
    } else if (!is_on_chan(c, u2)) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_ON_CHAN, u2->nick, c->name);
    } else if (!is_same ? !check_access(u, ci, CA_KICK) :
               !check_access(u, ci, CA_KICKME)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (!is_same && (ci->flags & CI_PEACE)
               && (get_access(u2, ci) >= get_access(u, ci))) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
#if defined (IRC_ULTIMATE) || defined (IRC_ULTIMATE3)
    } else if (is_protected(u2)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
#endif
    } else {
        char *av[3];

        if ((ci->flags & CI_SIGNKICK)
            || ((ci->flags & CI_SIGNKICK_LEVEL)
                && !check_access(u, ci, CA_SIGNKICK)))
            send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, params,
                     reason, u->nick);
        else
            send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, params,
                     reason);
        av[0] = ci->name;
        av[1] = params;
        av[2] = reason;
        do_kick(s_ChanServ, 3, av);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_ban(User * u)
{
    char *chan = strtok(NULL, " ");
    char *params = strtok(NULL, " ");
    char *reason = strtok(NULL, "");

    Channel *c;
    ChannelInfo *ci;
    User *u2;

    int is_same;

    if (!reason) {
        reason = "Requested";
    } else {
        if (strlen(reason) > 200)
            reason[200] = '\0';
    }

    if (!chan) {
        struct u_chanlist *uc, *next;

        /* Bans the user on every channels he is on. */

        for (uc = u->chans; uc; uc = next) {
            next = uc->next;
            if ((ci = uc->chan->ci) && !(ci->flags & CI_VERBOTEN)
                && check_access(u, ci, CA_BANME)) {
                char *av[3];
                char mask[BUFSIZE];

#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID)
                /*
                 * Dont ban/kick the user on channels where he is excepted
                 * to prevent services <-> server wars.
                 */
                if (is_excepted(ci, u))
                    notice_lang(s_ChanServ, u, CHAN_EXCEPTED,
                                u->nick, ci->name);
                continue;
#endif
                av[0] = sstrdup("+b");
                get_idealban(ci, u, mask, sizeof(mask));
                av[1] = mask;
                send_cmd(whosends(ci), "MODE %s +b %s", uc->chan->name,
                         av[1]);
                chan_set_modes(s_ChanServ, uc->chan, 2, av, 1);
                free(av[0]);

                if ((ci->flags & CI_SIGNKICK)
                    || ((ci->flags & CI_SIGNKICK_LEVEL)
                        && !check_access(u, ci, CA_SIGNKICK)))
                    send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name,
                             u->nick, reason, u->nick);
                else
                    send_cmd(whosends(ci), "KICK %s %s :%s", ci->name,
                             u->nick, reason);
                av[0] = ci->name;
                av[1] = u->nick;
                av[2] = reason;
                do_kick(s_ChanServ, 3, av);
            }
        }

        return MOD_CONT;
    } else if (!params) {
        params = u->nick;
    }

    is_same = (params == u->nick) ? 1 : (stricmp(params, u->nick) == 0);

    if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (is_same ? !(u2 = u) : !(u2 = finduser(params))) {
        notice_lang(s_ChanServ, u, NICK_X_NOT_IN_USE, params);
    } else if (!is_same ? !check_access(u, ci, CA_BAN) :
               !check_access(u, ci, CA_BANME)) {
        notice_lang(s_ChanServ, u, ACCESS_DENIED);
    } else if (!is_same && (ci->flags & CI_PEACE)
               && (get_access(u2, ci) >= get_access(u, ci))) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID)
        /*
         * Dont ban/kick the user on channels where he is excepted
         * to prevent services <-> server wars.
         */
    } else if (is_excepted(ci, u2)) {
        notice_lang(s_ChanServ, u, CHAN_EXCEPTED, u2->nick, ci->name);
#endif
    } else {
        char *av[3];
        char mask[BUFSIZE];

        av[0] = sstrdup("+b");
        get_idealban(ci, u2, mask, sizeof(mask));
        av[1] = mask;
        send_cmd(whosends(ci), "MODE %s +b %s", c->name, av[1]);
        chan_set_modes(s_ChanServ, c, 2, av, 1);
        free(av[0]);

        /* We still allow host banning while not allowing to kick */
        if (!is_on_chan(c, u2))
            return MOD_CONT;

        if ((ci->flags & CI_SIGNKICK)
            || ((ci->flags & CI_SIGNKICK_LEVEL)
                && !check_access(u, ci, CA_SIGNKICK)))
            send_cmd(whosends(ci), "KICK %s %s :%s (%s)", ci->name, params,
                     reason, u->nick);
        else
            send_cmd(whosends(ci), "KICK %s %s :%s", ci->name, params,
                     reason);
        av[0] = ci->name;
        av[1] = params;
        av[2] = reason;
        do_kick(s_ChanServ, 3, av);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_cs_topic(User * u)
{
    char *chan = strtok(NULL, " ");
    char *topic = strtok(NULL, "");

    Channel *c;
    ChannelInfo *ci;

    if (!chan) {
        syntax_error(s_ChanServ, u, "TOPIC", CHAN_TOPIC_SYNTAX);
    } else if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, c->name);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, ci->name);
    } else if (!is_services_admin(u) && !check_access(u, ci, CA_TOPIC)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
    } else {
        if (ci->last_topic)
            free(ci->last_topic);
        ci->last_topic = topic ? sstrdup(topic) : NULL;
        strscpy(ci->last_topic_setter, u->nick, NICKMAX);
        ci->last_topic_time = time(NULL);

        if (c->topic)
            free(c->topic);
        c->topic = topic ? sstrdup(topic) : NULL;
        strscpy(c->topic_setter, u->nick, NICKMAX);
#if defined(IRC_DREAMFORGE) && !defined(IRC_ULTIMATE) && !defined(IRC_UNREAL)
        c->topic_time = c->topic_time - 1;
#else
        c->topic_time = ci->last_topic_time;
#endif

        if (is_services_admin(u))
            alog("%s: %s!%s@%s changed topic of %s as services admin.",
                 s_ChanServ, u->nick, u->username, u->host, c->name);
#ifdef IRC_HYBRID
        if (whosends(ci) == s_ChanServ) {
            send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), c->name,
                     s_ChanServ);
            send_cmd(NULL, "MODE %s +o %s", c->name, s_ChanServ);
        }
        send_cmd(whosends(ci), "TOPIC %s :%s", c->name,
                 c->topic ? c->topic : "");
        if (whosends(ci) == s_ChanServ) {
            send_cmd(s_ChanServ, "PART %s", c->name);
        }
#else
        send_cmd(whosends(ci), "TOPIC %s %s %lu :%s", c->name, u->nick,
                 c->topic_time, topic ? topic : "");
#endif
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_unban(User * u)
{
    char *chan = strtok(NULL, " ");
    Channel *c;
    ChannelInfo *ci;

    if (!chan) {
        syntax_error(s_ChanServ, u, "UNBAN", CHAN_UNBAN_SYNTAX);
    } else if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!check_access(u, ci, CA_UNBAN)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
    } else {
#ifndef IRC_BAHAMUT
        int i;
        char *av[3];
        /* Save original ban info */
        int count = c->bancount;
        char **bans = scalloc(sizeof(char *) * count, 1);
        memcpy(bans, c->bans, sizeof(char *) * count);

        av[0] = chan;
        av[1] = sstrdup("-b");
        for (i = 0; i < count; i++) {
            if (match_usermask(bans[i], u)) {
                send_cmd(whosends(ci), "MODE %s -b %s", chan, bans[i]);
                av[2] = sstrdup(bans[i]);
                do_cmode(s_ChanServ, 3, av);
                free(av[2]);
            }
        }
        free(av[1]);
        free(bans);
#else
        send_cmd(ServerName, "SVSMODE %s -b %s", chan, u->nick);
#endif
        notice_lang(s_ChanServ, u, CHAN_UNBANNED, chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_clear(User * u)
{
    char *chan = strtok(NULL, " ");
    char *what = strtok(NULL, " ");
    Channel *c;
    ChannelInfo *ci;

    if (!what) {
        syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX);
    } else if (!(c = findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (!(ci = c->ci)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!u || !check_access(u, ci, CA_CLEAR)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
    } else if (stricmp(what, "bans") == 0) {
        char *av[3];
        int i;

        /* Save original ban info */
        int count = c->bancount;
        char **bans = scalloc(sizeof(char *) * count, 1);
        for (i = 0; i < count; i++)
            bans[i] = sstrdup(c->bans[i]);

        for (i = 0; i < count; i++) {
            av[0] = sstrdup(chan);
            av[1] = sstrdup("-b");
            av[2] = bans[i];
            send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]);
            do_cmode(s_ChanServ, 3, av);
            free(av[2]);
            free(av[1]);
            free(av[0]);
        }
        notice_lang(s_ChanServ, u, CHAN_CLEARED_BANS, chan);
        free(bans);
#if defined (IRC_ULTIMATE) || defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined(IRC_HYBRID)
    } else if (stricmp(what, "excepts") == 0) {
        char *av[3];
        int i;

        /* Save original except info */
        int count = c->exceptcount;
        char **excepts = scalloc(sizeof(char *) * count, 1);
        for (i = 0; i < count; i++)
            excepts[i] = sstrdup(c->excepts[i]);

        for (i = 0; i < count; i++) {
            av[0] = sstrdup(chan);
            av[1] = sstrdup("-e");
            av[2] = excepts[i];
            send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]);
            do_cmode(s_ChanServ, 3, av);
            free(av[2]);
            free(av[1]);
            free(av[0]);
        }
        notice_lang(s_ChanServ, u, CHAN_CLEARED_EXCEPTS, chan);
        free(excepts);
#endif
    } else if (stricmp(what, "modes") == 0) {
        char buf[BUFSIZE], *end = buf;
        char *argv[2];

        if (c->mode) {
            /* Clear modes */
            send_cmd(s_ChanServ, "MODE %s %s %s", c->name, MODESTOREMOVE,
                     c->key ? c->key : "");
            argv[0] = sstrdup(MODESTOREMOVE);
            argv[1] = c->key ? c->key : NULL;
            chan_set_modes(s_OperServ, c, c->key ? 2 : 1, argv, 0);
            free(argv[0]);
            check_modes(c);
        }

        /* TODO: decide if the above implementation is better than this one. */

        if (0) {
            CBModeInfo *cbmi = cbmodeinfos;
            CBMode *cbm;

            do {
                if (c->mode & cbmi->flag)
                    *end++ = cbmi->mode;
            } while ((++cbmi)->flag != 0);

            cbmi = cbmodeinfos;

            do {
                if (cbmi->getvalue && (c->mode & cbmi->flag)
                    && !(cbmi->flags & CBM_MINUS_NO_ARG)) {
                    char *value = cbmi->getvalue(c);

                    if (value) {
                        *end++ = ' ';
                        while (*value)
                            *end++ = *value++;

                        /* Free the value */
                        cbm = &cbmodes[(int) cbmi->mode];
                        cbm->setvalue(c, NULL);
                    }
                }
            } while ((++cbmi)->flag != 0);

            *end = 0;

            send_cmd(whosends(ci), "MODE %s -%s", c->name, buf);
            c->mode = 0;
            check_modes(c);
        }
        notice_lang(s_ChanServ, u, CHAN_CLEARED_MODES, chan);
    } else if (stricmp(what, "ops") == 0) {
        char *av[3];
        struct c_userlist *cu, *next;

        for (cu = c->users; cu; cu = next) {
            next = cu->next;
            if (!chan_has_user_status(c, cu->user, CUS_OP))
                continue;
            av[0] = sstrdup(chan);
            av[1] = sstrdup("-o");
            av[2] = sstrdup(cu->user->nick);
            send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]);
            do_cmode(s_ChanServ, 3, av);
            free(av[2]);
            free(av[1]);
            free(av[0]);
        }
        notice_lang(s_ChanServ, u, CHAN_CLEARED_OPS, chan);
    } else if (stricmp(what, "voices") == 0) {
        char *av[3];
        struct c_userlist *cu, *next;

        for (cu = c->users; cu; cu = next) {
            next = cu->next;
            if (!chan_has_user_status(c, cu->user, CUS_VOICE))
                continue;
            av[0] = sstrdup(chan);
            av[1] = sstrdup("-v");
            av[2] = sstrdup(cu->user->nick);
            send_cmd(whosends(ci), "MODE %s %s :%s", av[0], av[1], av[2]);
            do_cmode(s_ChanServ, 3, av);
            free(av[2]);
            free(av[1]);
            free(av[0]);
        }
        notice_lang(s_ChanServ, u, CHAN_CLEARED_VOICES, chan);
    } else if (stricmp(what, "users") == 0) {
        char *av[3];
        struct c_userlist *cu, *next;
        char buf[256];

        snprintf(buf, sizeof(buf), "CLEAR USERS command from %s", u->nick);

        for (cu = c->users; cu; cu = next) {
            next = cu->next;
            av[0] = sstrdup(chan);
            av[1] = sstrdup(cu->user->nick);
            av[2] = sstrdup(buf);
            send_cmd(whosends(ci), "KICK %s %s :%s", av[0], av[1], av[2]);
            do_kick(s_ChanServ, 3, av);
            free(av[2]);
            free(av[1]);
            free(av[0]);
        }
        notice_lang(s_ChanServ, u, CHAN_CLEARED_USERS, chan);
    } else {
        syntax_error(s_ChanServ, u, "CLEAR", CHAN_CLEAR_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_getkey(User * u)
{
    char *chan = strtok(NULL, " ");
    ChannelInfo *ci;

    if (chan && (ci = cs_findchan(chan)) && !(ci->flags & CI_VERBOTEN)
        && check_access(u, ci, CA_GETKEY)) {
        notice_user(s_ChanServ, u, "KEY %s %s", ci->name,
                    (ci->
                     c ? (ci->c->key ? ci->c->key : "NO KEY") : "NO KEY"));
    } else {
        notice_user(s_ChanServ, u, "KEY %s ERROR", chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_getpass(User * u)
{
#ifndef USE_ENCRYPTION
    char *chan = strtok(NULL, " ");
    ChannelInfo *ci;
#endif

    /* Assumes that permission checking has already been done. */
#ifdef USE_ENCRYPTION
    notice_lang(s_ChanServ, u, CHAN_GETPASS_UNAVAILABLE);
#else
    if (!chan) {
        syntax_error(s_ChanServ, u, "GETPASS", CHAN_GETPASS_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (CSRestrictGetPass && !is_services_root(u)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
    } else {
        alog("%s: %s!%s@%s used GETPASS on %s",
             s_ChanServ, u->nick, u->username, GetHost(u), ci->name);
        if (WallGetpass) {
            wallops(s_ChanServ, "\2%s\2 used GETPASS on channel \2%s\2",
                    u->nick, chan);
        }
        notice_lang(s_ChanServ, u, CHAN_GETPASS_PASSWORD_IS,
                    chan, ci->founderpass);
    }
#endif
    return MOD_CONT;
}

/*************************************************************************/

static int do_sendpass(User * u)
{
#ifndef USE_ENCRYPTION
    char *chan = strtok(NULL, " ");
    ChannelInfo *ci;
    NickCore *founder;
#endif

#ifdef USE_ENCRYPTION
    notice_lang(s_ChanServ, u, CHAN_SENDPASS_UNAVAILABLE);
#else
    if (!chan) {
        syntax_error(s_ChanServ, u, "SENDPASS", CHAN_SENDPASS_SYNTAX);
    } else if (RestrictMail && !is_oper(u)) {
        notice_lang(s_ChanServ, u, PERMISSION_DENIED);
    } else if (!(ci = cs_findchan(chan)) || !(founder = ci->founder)) {
        notice_lang(s_ChanServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_ChanServ, u, CHAN_X_FORBIDDEN, chan);
    } else {
        char buf[BUFSIZE];
        MailInfo *mail;

        snprintf(buf, sizeof(buf),
                 getstring2(founder, CHAN_SENDPASS_SUBJECT), ci->name);
        mail = MailBegin(u, founder, buf, s_ChanServ);
        if (!mail)
            return MOD_CONT;

        fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_HEAD));
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_1),
                ci->name);
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_2),
                ci->founderpass);
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_3));
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_4));
        fprintf(mail->pipe, "\n\n");
        fprintf(mail->pipe, getstring2(founder, CHAN_SENDPASS_LINE_5),
                NetworkName);
        fprintf(mail->pipe, "\n.\n");

        MailEnd(mail);

        alog("%s: %s!%s@%s used SENDPASS on %s", s_ChanServ, u->nick,
             u->username, GetHost(u), chan);
        notice_lang(s_ChanServ, u, CHAN_SENDPASS_OK, chan);
    }
#endif
    return MOD_CONT;
}

/*************************************************************************/

static int do_forbid(User * u)
{
    ChannelInfo *ci;
    char *chan = strtok(NULL, " ");
    char *reason = strtok(NULL, "");

    Channel *c;

    /* Assumes that permission checking has already been done. */
    if (!chan || (ForceForbidReason && !reason)) {
        syntax_error(s_ChanServ, u, "FORBID",
                     (ForceForbidReason ? CHAN_FORBID_SYNTAX_REASON :
                      CHAN_FORBID_SYNTAX));
        return MOD_CONT;
    }
    if (readonly)
        notice_lang(s_ChanServ, u, READ_ONLY_MODE);
    if ((ci = cs_findchan(chan)) != NULL)
        delchan(ci);
    ci = makechan(chan);
    if (ci) {
        ci->flags |= CI_VERBOTEN;
        ci->forbidby = sstrdup(u->nick);
        if (reason)
            ci->forbidreason = sstrdup(reason);

        if ((c = findchan(ci->name))) {
            struct c_userlist *cu, *next;
            char *av[3];

            for (cu = c->users; cu; cu = next) {
                next = cu->next;

                if (is_oper(cu->user))
                    continue;

                av[0] = c->name;
                av[1] = cu->user->nick;
                av[2] = reason ? reason : "CHAN_FORBID_REASON";
                send_cmd(s_ChanServ, "KICK %s %s :%s", av[0], av[1],
                         av[2]);
                do_kick(s_ChanServ, 3, av);
            }
        }

        if (WallForbid)
            wallops(s_ChanServ, "\2%s\2 used FORBID on channel \2%s\2",
                    u->nick, ci->name);

        alog("%s: %s set FORBID for channel %s", s_ChanServ, u->nick,
             ci->name);
        notice_lang(s_ChanServ, u, CHAN_FORBID_SUCCEEDED, chan);
    } else {
        alog("%s: Valid FORBID for %s by %s failed", s_ChanServ, ci->name,
             u->nick);
        notice_lang(s_ChanServ, u, CHAN_FORBID_FAILED, chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_suspend(User * u)
{
    ChannelInfo *ci;
    char *chan = strtok(NULL, " ");
    char *reason = strtok(NULL, "");

    Channel *c;

    /* Assumes that permission checking has already been done. */
    if (!chan || (ForceForbidReason && !reason)) {
        syntax_error(s_ChanServ, u, "SUSPEND",
                     (ForceForbidReason ? CHAN_SUSPEND_SYNTAX_REASON :
                      CHAN_SUSPEND_SYNTAX));
        return MOD_CONT;
    }
    if (readonly)
        notice_lang(s_ChanServ, u, READ_ONLY_MODE);
    if ((ci = cs_findchan(chan)) == NULL)
        ci = makechan(chan);
    if (ci) {
        ci->flags |= CI_SUSPENDED;
        ci->forbidby = sstrdup(u->nick);
        if (reason)
            ci->forbidreason = sstrdup(reason);

        if ((c = findchan(ci->name))) {
            struct c_userlist *cu, *next;
            char *av[3];

            for (cu = c->users; cu; cu = next) {
                next = cu->next;

                if (is_oper(cu->user))
                    continue;

                av[0] = c->name;
                av[1] = cu->user->nick;
                av[2] = reason ? reason : "CHAN_SUSPEND_REASON";
                send_cmd(s_ChanServ, "KICK %s %s :%s", av[0], av[1],
                         av[2]);
                do_kick(s_ChanServ, 3, av);
            }
        }

        if (WallForbid)
            wallops(s_ChanServ, "\2%s\2 used SUSPEND on channel \2%s\2",
                    u->nick, ci->name);

        alog("%s: %s set SUSPEND for channel %s", s_ChanServ, u->nick,
             ci->name);
        notice_lang(s_ChanServ, u, CHAN_SUSPEND_SUCCEEDED, chan);
    } else {
        alog("%s: Valid SUSPEND for %s by %s failed", s_ChanServ, ci->name,
             u->nick);
        notice_lang(s_ChanServ, u, CHAN_SUSPEND_FAILED, chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_unsuspend(User * u)
{
    ChannelInfo *ci;
    char *chan = strtok(NULL, " ");

    /* Assumes that permission checking has already been done. */
    if (!chan) {
        syntax_error(s_ChanServ, u, "UNSUSPEND", CHAN_UNSUSPEND_SYNTAX);
        return MOD_CONT;
    }
    if (chan[0] != '#') {
        notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_ERROR);
        return MOD_CONT;
    }
    if (readonly)
        notice_lang(s_ChanServ, u, READ_ONLY_MODE);

    ci = cs_findchan(chan);

    if (ci) {
        ci->flags &= ~CI_SUSPENDED;
        ci->forbidreason = NULL;
        ci->forbidby = NULL;

        if (WallForbid)
            wallops(s_ChanServ, "\2%s\2 used UNSUSPEND on channel \2%s\2",
                    u->nick, ci->name);

        alog("%s: %s set UNSUSPEND for channel %s", s_ChanServ, u->nick,
             ci->name);
        notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_SUCCEEDED, chan);
    } else {
        alog("%s: Valid UNSUSPEND for %s by %s failed", s_ChanServ,
             chan, u->nick);
        notice_lang(s_ChanServ, u, CHAN_UNSUSPEND_FAILED, chan);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_status(User * u)
{
    ChannelInfo *ci;
    User *u2;
    char *nick, *chan;

    chan = strtok(NULL, " ");
    nick = strtok(NULL, " ");
    if (!nick || strtok(NULL, " ")) {
        notice_user(s_ChanServ, u, "STATUS ERROR Syntax error");
        return MOD_CONT;
    }
    if (!(ci = cs_findchan(chan))) {
        char *temp = chan;
        chan = nick;
        nick = temp;
        ci = cs_findchan(chan);
    }
    if (!ci) {
        notice_user(s_ChanServ, u,
                    "STATUS ERROR Channel %s not registered", chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_user(s_ChanServ, u, "STATUS ERROR Channel %s forbidden",
                    chan);
        return MOD_CONT;
    } else if ((u2 = finduser(nick)) != NULL) {
        notice_user(s_ChanServ, u, "STATUS %s %s %d", chan, nick,
                    get_access(u2, ci));
    } else {                    /* !u2 */
        notice_user(s_ChanServ, u, "STATUS ERROR Nick %s not online",
                    nick);
    }
    return MOD_CONT;
}

/*************************************************************************/
