/**
 * -----------------------------------------------------------
 * Name:   cs_freeze
 * Author: Viper <Viper@absurd-irc.net>
 * Date:   16/10/2005 (Last update: 10/11/2006)
 * Version: 2.1 (Alfa 1)
 * -----------------------------------------------------------------------------
 *
 * !!! IMPORTANT !!!
 *     This module is still under development and should not be used unless for
 *     testing purposes.
 *
 * -----------------------------------------------------------------------------
 * Tested: Anope 1.7.17 + UnrealIRCd3.2.5
 * -----------------------------------------------------------------------------
 * This module provides the ability to 'freeze' a channel.
 *
 * When a channel is frozen:
 *
 *  - BotServ bots are unassigned from it
 *  - All users on the channel are set -(q)(a)ohv, all bans and exceptions
 *    etc. are removed.
 *  - All ChanServ and BotServ commands that can be used for the channel
 *    are disabled for it (for non-opers / Services Admins).
 *  - If a ChanServ OP or VOICE command [etc.] is used, modes are
 *    revoked immiedialy after they are set for a user on the channel.
 *  - 'Channel [chan] is FROZEN' will appear in /CS INFO [Chan]
 *  - ChanServ will not 'auto-op' etc. when people join the channel.
 *  - AKICKs etc. won't function on FROZEN channels.
 *
 *  When a channel is 'UNFROZEN', the above is reversed, except the
 *  setting of modes and replacing of bans / exceptions etc.
 *
 *
 * Based on the original cs_freeze by SGR <Alex_SGR@ntlworld.com>
 * so most credit goes to him.
 * -----------------------------------------------------------------------------
 * Changelog:
 *
 *   2.1  Code cleanup and partial rewrite (in progress)
 *        Fixed a few bugs, incl a few nasty ones.
 *
 *   2.0  First release by my
 *        Updated to work with 1.7.11
 *        Moved configuration to the config file
 *
 * -----------------------------------------------------------------------------
 * TODO:
 *
 *  - Update the code to use 1.7.x style coding
 *  - option to specify FC_DB_NAME in services.conf
 *  - replace the remaining strtok()'s
 *
 * -----------------------------------------------------------------------------
 **/

/**
 * Configuration directives that should be copy-pasted to services.conf

# AddChanInfoTag [OPTIONAL]
# Module: cs_freeze
#
# If AddChanInfoTag is defined, the line
# 'Channel <channel> is FROZEN'
# will appear at the end of a channels /ChanServ INFO output.
#
#AddChanInfoTag

# UpdateOnEdit [OPTIONAL]
# Module: cs_freeze
#
# If UpdateOnEdit is defined, the Frozen Channels database will be updated everytime
# a frozen channel is added or removed.
#
#UpdateOnEdit

# JoinFrozenChan1-4[OPTIONAL]
# Module: cs_freeze
#
# These notices (when defined) will be sent to a user when
# they join a channel that is FROZEN. Upto 4 notices can be defined.
#
JoinFrozenChan1 "This channel has been frozen. ChanServ will not function on this channel."
#JoinFrozenChan2 ""
#JoinFrozenChan3 ""

# ChanIsFrozen1-4[OPTIONAL]
# Module: cs_freeze
#
# These notices (when defined) will be sent to a user when
# they attempt to change the settings on a channel that is FROZEN.
# Upto 4 notices can be defined. If none are used the standard
# 'ACCESS DENIED' message is returned.
#
ChanIsFrozen1 "This channel is FROZEN. This means you"
ChanIsFrozen2 "cannot change any of its ChanServ settings. "
#ChanIsFrozen3 ""

 *
 **/

/*------------------------------Configuration Block----------------------------*/


// This module has no compile time configuration directives...


/*-------------------------End of Configuration Block--------------------------*/


/* ------------------------------------------------------------------------------- */


#include "module.h"
#include "../../include/datafiles.h"

#define AUTHOR "Viper"
#define VERSION "2.0 (Alfa 1)"

/* ------------------------------------------------------------------------------- */

/* constants */
#define FC_DB_NAME     "frozenchans.db"
#define FC_DB_VERSION  1
#define FC_DB_UPDATE   "5m"
#define FREEZETABLESIZE 512


/* Default settings */
int AddChanInfoTag = 0;
int UpdateOnEdit = 0;


/* Struct declaration */
typedef struct FreezeChan_ FreezeChan;

struct FreezeChan_
{
	FreezeChan *next;
	FreezeChan *prev;
	char   *chan;
	char   *by;
	char   *reason;
	time_t  time;
	char   *botnick;
};


/* variables */
char *JoinFrozenChan1 = NULL;
char *JoinFrozenChan2 = NULL;
char *JoinFrozenChan3 = NULL;
char *JoinFrozenChan4 = NULL;

char *ChanIsFrozen1 = NULL;
char *ChanIsFrozen2 = NULL;
char *ChanIsFrozen3 = NULL;
char *ChanIsFrozen4 = NULL;

FreezeChan *freezechanhash[FREEZETABLESIZE];
unsigned int freezenumber    = 0;
User *last_join_user  = NULL;


/* Language defines */
#define LNG_NUM_STRINGS 				20

#define LNG_FREEZE_SYNTAX 				0
#define LNG_HELP_FREEZE 				1
#define LNG_UNFREEZE_SYNTAX				2
#define LNG_HELP_UNFREEZE 				3
#define LNG_HELP_ADMINS 				4
#define LNG_CHANNEL_FROZEN				5
#define LNG_FREEZE_INVALID_SYNTAX 		6
#define LNG_CHANNEL_UNFROZEN 			7
#define LNG_UNFREEZE_INVALID_CHANNEL 	8
#define LNG_FREEZE_INVALID_CHANNEL		9
#define LNG_FREEZE_ALLREADY_FROZEN 		10
#define LNG_CHANNEL_FROZEN_NOW 			11
#define LNG_ERROR_MEM 					12
#define LNG_NO_CHANS_FROZEN 			13
#define LNG_LIST_HEADER 				14
#define LNG_LIST_COUNT 					15
#define LNG_FROZEN_INFO_HEADER 			16
#define LNG_FROZEN_BY 					17
#define LNG_FROZEN_REASON 				18
#define LNG_FROZEN_AT 					19


/* Functions */
int                     save_freezechan_database(int argc, char **argv);
int                     load_freezechan_database(void);
static void             add_entry(FreezeChan *fc);
static int              del_entry(FreezeChan *fc);
static FreezeChan       *create_entry(char *chan, char *reason, NickAlias *na, char *botnick);
static FreezeChan       *find_freezechan_entry(char *chan);
int                     my_cs_privmsg_frozen_check(char *source, int ac, char **av);
int                     my_frozen_check(char *source, int argc, char **argv, char *from);
int                     my_cs_cs_frozen_check(char *source, int ac, char **av);
int                     my_cs_bs_frozen_check(char *source, int ac, char **av);
int                     my_cs_add_freeze(User *u, char *chan, char *reason);
int                     my_cs_del_freeze(User *u, char *chan);
int                     my_cs_list_freeze(User *u);
void                    send_frozen_join_notices(User *u, char *from, FreezeChan *fc);
void                    send_frozen_nochange_notices(User *u, char *from, FreezeChan *fc);
void                    my_cs_freeze_checkanddelifneedbe(char *chan);
int                     my_cs_forbid_freeze_check(User *u);
int                     my_cs_drop_freeze_check(User *u);
int                     my_cs_suspend_freeze_check(User *u);
int                     my_cs_unsetmodes_ifneedbe(User *u);
int                     my_cs_freeze_do_op(User *u);
int                     my_cs_needfreeze_check(User *u);
int                     my_cs_freeze_do_voice(User *u);
int                     my_cs_freeze_do_halfop(User *u);
int                     my_cs_freeze_do_protect(User *u);
int                     my_update_on_osupdate(User *u);
int                     my_cs_freeze(User *u);
int						my_cs_unfreeze(User *u);
int                     my_cs_freeze_identify(User *u);
int                     my_cs_freeze_appendinfo(User *u);
void                    my_cs_freeze_clearallmodes(Channel *c);
void                    help_chanserv(User *u);
int                     help_chanserv_freeze_full(User *u);
int                     help_chanserv_unfreeze_full(User *u);

int 					on_join(int argc, char **argv);
void 					delete_ignore(const char *nick);
int 					on_join_unsetmodes_ifneedbe(User *u, Channel *c);
int 					cs_freeze_list(User * u);

void 					add_languages(void);
void 					load_config(void);
int 					reloadConf(int argc, char **argv);


/* ------------------------------------------------------------------------------- */

/**
 * Create the command, and tell anope about it.
 * @param argc Argument count
 * @param argv Argument list
 * @return MOD_CONT to allow the module, MOD_STOP to stop it
 **/
int AnopeInit(int argc, char **argv) {
	EvtHook *hook;
	Message *msg;
	Command *c;

	alog("Loading module cs_freeze.so");

	hook = createEventHook(EVENT_JOIN_CHANNEL, on_join);
	moduleAddEventHook(hook);

    hook = createEventHook(EVENT_RELOAD, reloadConf);
    moduleAddEventHook(hook);

	msg = createMessage("PRIVMSG", my_cs_privmsg_frozen_check);
	moduleAddMessage(msg, MOD_HEAD);

	msg = createMessage("CS", my_cs_cs_frozen_check);
	moduleAddMessage(msg, MOD_HEAD);

	msg = createMessage("BS", my_cs_cs_frozen_check);
	moduleAddMessage(msg, MOD_HEAD);

	c = createCommand("FORBID", my_cs_needfreeze_check, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);
	c = createCommand("SUSPEND", my_cs_needfreeze_check, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);
	c = createCommand("DROP", my_cs_needfreeze_check, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);
	c = createCommand("UNSUSPEND", my_cs_needfreeze_check, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);

	c = createCommand("OP", my_cs_unsetmodes_ifneedbe, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);
	c = createCommand("VOICE", my_cs_unsetmodes_ifneedbe, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);
	c = createCommand("HALFOP", my_cs_unsetmodes_ifneedbe, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);
	c = createCommand("PROTECT", my_cs_unsetmodes_ifneedbe, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);

    c = createCommand("LIST", cs_freeze_list, is_services_oper, -1, -1, -1, -1, -1);
    moduleAddCommand(CHANSERV, c, MOD_HEAD);

	c = createCommand("UPDATE", my_update_on_osupdate, is_services_admin, -1, -1, -1, -1, -1);
	moduleAddCommand(OPERSERV, c, MOD_TAIL);
	c = createCommand("SHUTDOWN", my_update_on_osupdate, is_services_admin, -1, -1, -1, -1, -1);
	moduleAddCommand(OPERSERV, c, MOD_HEAD);
	c = createCommand("RESTART", my_update_on_osupdate, is_services_admin, -1, -1, -1, -1, -1);
	moduleAddCommand(OPERSERV, c, MOD_HEAD);

	c = createCommand("FREEZE", my_cs_freeze, is_services_admin, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_UNIQUE);
	moduleAddHelp(c,help_chanserv_freeze_full);

	c = createCommand("UNFREEZE", my_cs_unfreeze, is_services_admin, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_UNIQUE);
	moduleAddHelp(c,help_chanserv_unfreeze_full);

	c = createCommand("IDENTIFY", my_cs_freeze_identify, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(NICKSERV, c, MOD_TAIL);

	c = createCommand("INFO", my_cs_freeze_appendinfo, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(CHANSERV, c, MOD_TAIL);

	c = createCommand("ID", my_cs_freeze_identify, NULL, -1, -1, -1, -1, -1);
	moduleAddCommand(NICKSERV, c, MOD_TAIL);

	moduleAddCallback("FreezeChanSave",time(NULL)+dotime(FC_DB_UPDATE),save_freezechan_database,0,NULL);

	moduleSetChanHelp(help_chanserv);

	load_config();
	add_languages();

	moduleAddAuthor(AUTHOR);
	moduleAddVersion(VERSION);
	load_freezechan_database();
	alog("[cs_freeze] MODULE LOADED AND ACTIVE");
	return MOD_CONT;
}


/**
 * Unload the module
 **/
void AnopeFini(void)
{
	FreezeChan *fc;
	int         i = 0;
	save_freezechan_database(0, NULL);

	if (freezenumber) {
		for (fc = freezechanhash[i]; fc; fc = fc->next, i++) {
			freezenumber--;
			free(freezechanhash[i]);
			free(fc->chan);
			free(fc->by);
			free(fc->reason);
			free(fc->botnick);
			free(fc);
		}
		i = 0;
	}

	free(JoinFrozenChan1);
	free(JoinFrozenChan2);
	free(JoinFrozenChan3);
	free(JoinFrozenChan4);
	free(ChanIsFrozen1);
	free(ChanIsFrozen2);
	free(ChanIsFrozen3);
	free(ChanIsFrozen4);
	return;
}


/* ------------------------------------------------------------------------------- */

/******************************************************************/
/******* Command Hooks - FORBID, SUSPEND, FREEZE, DROP etc. *******/
/******************************************************************/

void help_chanserv(User *u) {
	if (is_services_admin(u)) {
		moduleNoticeLang(s_ChanServ, u, LNG_HELP_ADMINS);
	}
	return;
}


int help_chanserv_freeze_full(User *u) {
	if (is_services_admin(u)) {
		moduleNoticeLang(s_ChanServ, u, LNG_FREEZE_SYNTAX);
		notice(s_HostServ, u->nick, " ");
		moduleNoticeLang(s_ChanServ, u, LNG_HELP_FREEZE, s_ChanServ, s_BotServ, s_ChanServ);
	} else {
		notice_lang(s_ChanServ, u, NO_HELP_AVAILABLE, "FREEZE");
	}

	return MOD_CONT;
}


int help_chanserv_unfreeze_full(User *u) {
	if (is_services_admin(u)) {
		moduleNoticeLang(s_ChanServ, u, LNG_UNFREEZE_SYNTAX);
		notice(s_HostServ, u->nick, " ");
		moduleNoticeLang(s_ChanServ, u, LNG_HELP_UNFREEZE, s_ChanServ);
	} else {
		notice_lang(s_ChanServ, u, NO_HELP_AVAILABLE, "UNFREEZE");
	}
	return MOD_CONT;
}


int my_cs_freeze(User *u) {
	char *arg1 =  strtok(NULL, " ");  /* #Chan */
	char *arg2 =  strtok(NULL, "");   /* Reason */

	return my_cs_add_freeze(u, (arg1 ? arg1 : NULL), (arg2 ? arg2 : NULL));
}


int my_cs_unfreeze(User *u) {
	char *arg1 =  strtok(NULL, " ");  /* #Chan */

	return my_cs_del_freeze(u, (arg1 ? arg1 : NULL));
}


int my_cs_freeze_identify(User *u) {
	if (NSModeOnID && nick_recognized(u)) {
		return my_cs_unsetmodes_ifneedbe(u);
	}
	return MOD_CONT;
}


int my_cs_freeze_appendinfo(User *u) {
	char dilim = ' ';
	char *mainbuf = sstrdup(moduleGetLastBuffer());
	char *arg1 = myStrGetToken(mainbuf, dilim, 0);
	char *arg2 = myStrGetToken(mainbuf, dilim, 1);
	char *time;
	FreezeChan *fc;

	if (AddChanInfoTag){
		if (arg1) {
			if ((fc = find_freezechan_entry(arg1))) {
				if (arg2) {
					if ((stricmp(arg2,"all")==0) && (is_services_admin(u))) {
						moduleNoticeLang(s_ChanServ, u, LNG_FROZEN_INFO_HEADER, arg1);
						moduleNoticeLang(s_ChanServ, u, LNG_FROZEN_BY, fc->by);
						moduleNoticeLang(s_ChanServ, u, LNG_FROZEN_REASON, fc->reason);
						time = asctime(localtime(&fc->time));
						moduleNoticeLang(s_ChanServ, u, LNG_FROZEN_AT, time);
						free(time);
					} else {
						moduleNoticeLang(s_ChanServ, u, LNG_CHANNEL_FROZEN, arg1);
					}
				} else {
					moduleNoticeLang(s_ChanServ, u, LNG_CHANNEL_FROZEN, arg1);
				}
			}
		}
	}
	free(mainbuf);
	return MOD_CONT;
}


int my_cs_needfreeze_check(User *u) {
	char *mainbuf = moduleGetLastBuffer();
	char *arg1 = strtok(mainbuf, " ");
	if (arg1) {
		my_cs_freeze_checkanddelifneedbe(arg1);
		free(mainbuf);
	}
	return MOD_CONT;
}


int my_cs_unsetmodes_ifneedbe(User *u) {
    ChannelInfo *ci;
    FreezeChan *fc;
    boolean ok = true;

	struct u_chanlist *uc;
	char *argv[10];
	for (uc = u->chans; uc; uc = uc->next) {
	   if (!uc->chan || !uc->chan->name) {
			continue;
	   }
	   if ((ci = uc->chan->ci) && (fc = find_freezechan_entry(uc->chan->name))) {
			int   n = 0;
			if (chan_has_user_status(uc->chan, u, CUS_OP) && chan_has_user_status(uc->chan, u, CUS_VOICE)) {
				ok = false;
			} else {
				if (ircd->adminset != NULL) {
					if (chan_has_user_status(uc->chan, u, CUS_PROTECT))
						ok = false;
				}
				if (ircd->owner) {
					if (chan_has_user_status(uc->chan, u, CUS_OWNER))
						ok = false;
				}
				if (ircd->halfop) {
					if (chan_has_user_status(uc->chan, u, CUS_HALFOP))
						ok = false;
				}
			}

			if (ok) {
				continue; /* They have no modes */
			}

			if (ircd->owner) argv[0] = sstrdup(ircd->ownerunset);
			if (ircd->adminset != NULL) argv[1] = sstrdup(ircd->adminunset);
			argv[2] = sstrdup("-o");
			if (ircd->halfop) argv[3] = sstrdup("-h");
			argv[4] = sstrdup("-v");
			argv[5] = sstrdup(u->nick);
			argv[6] = sstrdup(u->nick);
			argv[7] = sstrdup(u->nick);
			argv[8] = sstrdup(u->nick);
			argv[9] = sstrdup(u->nick);
			anope_cmd_mode(s_ChanServ, uc->chan->name, "%s%s%s%s%s %s %s %s %s %s", argv[0], argv[1], argv[2], argv[3], argv[4],
						   argv[5], argv[6], argv[7], argv[8], argv[9]);
			chan_set_modes(s_ChanServ, uc->chan, 10, argv, 0);

			for (n = 0; n < 10; n++) {
				free(argv[n]);
			}
	   }
	}

    return MOD_CONT;
}


int my_update_on_osupdate(User *u) {
	save_freezechan_database(0, NULL);
	return MOD_CONT;
}


/* ------------------------------------------------------------------------------- */

/******************************************************************/
/** Add/Del/List/Info functions [called by main FREEZE handler] ***/
/******************************************************************/

int my_cs_del_freeze(User *u, char *chan) {
	FreezeChan *fc;

	if (!u) {
		return MOD_CONT;
	}
	if (!chan || (chan[0] != '#')) {
		moduleNoticeLang(s_ChanServ, u, LNG_FREEZE_INVALID_SYNTAX);
		return MOD_CONT;
	}
	if ((fc = find_freezechan_entry(chan))) {
		moduleNoticeLang(s_ChanServ, u, LNG_CHANNEL_UNFROZEN, chan);
		del_entry(fc);
		if (UpdateOnEdit)
			save_freezechan_database(0, NULL);
	}
	else {
		moduleNoticeLang(s_ChanServ, u, LNG_UNFREEZE_INVALID_CHANNEL, chan);
	}
	return MOD_CONT;
}


int my_cs_add_freeze(User *u, char *chan, char *reason) {
	NickAlias *na;
	BotInfo *bi;
	ChannelInfo *ci;
	Channel *c;
	FreezeChan *fc;

	if (!u) {
		return MOD_CONT;
	}
	if (!chan || !reason || (chan[0] != '#')) {
		moduleNoticeLang(s_ChanServ, u, LNG_FREEZE_INVALID_SYNTAX);
		return MOD_CONT;
	}
	if (!(na = findnick(u->nick))) {
		notice(s_ChanServ, u->nick, "ERROR");
		return MOD_CONT;
	}
	if (!(ci = cs_findchan(chan))) {
		moduleNoticeLang(s_ChanServ, u, LNG_FREEZE_INVALID_CHANNEL, chan);
		return MOD_CONT;
	}
	if ((fc = find_freezechan_entry(chan))) {
		moduleNoticeLang(s_ChanServ, u, LNG_FREEZE_ALLREADY_FROZEN, chan, s_ChanServ, chan);
		return MOD_CONT;
	}

	bi = ci->bi;
	fc = create_entry(chan, reason, na, (bi ? bi->nick : "*"));
	if (fc) {
		if ((c = ci->c)) {
			my_cs_freeze_clearallmodes(c);
		}
		if (bi) {
			if (ci->c && (ci->c->usercount >= BSMinUsers)) {
				send_cmd(ci->bi->nick, "PART %s :This channel has been FROZEN by %s.", ci->name, u->nick);
			}
			ci->bi->chancount--;
			ci->bi = NULL;
		}
		moduleNoticeLang(s_ChanServ, u, LNG_CHANNEL_FROZEN_NOW, chan);
		if (UpdateOnEdit)
			save_freezechan_database(0, NULL);
	}
	else {
		moduleNoticeLang(s_ChanServ, u, LNG_ERROR_MEM);
	}
	return MOD_CONT;
}


int my_cs_list_freeze(User *u) {
	int i = 0;
	FreezeChan *fc;
	int count = 0;

	if (!u) {
		return MOD_CONT;
	}
	if (!freezenumber) {
		moduleNoticeLang(s_ChanServ, u, LNG_NO_CHANS_FROZEN);
		return MOD_CONT;
	}
	moduleNoticeLang(s_ChanServ, u, LNG_LIST_HEADER);
	for (i = 0; i < FREEZETABLESIZE; i++) {
		for (fc = freezechanhash[i]; fc; fc = fc->next) {
			count++;
			notice(s_ChanServ, u->nick, "Entry: %-3d    %-15s  %s", count, fc->chan, fc->by);
			if (count > FREEZETABLESIZE) {
				break;
			}
		}
	}
	moduleNoticeLang(s_ChanServ, u, LNG_LIST_COUNT, count);
	return MOD_CONT;
}


int cs_freeze_list(User * u) {
	char *key;

	key = moduleGetLastBuffer();
	if (!key)
		return MOD_CONT;

	if ((stricmp(key, "+froz") != 0) && (stricmp(key, "+frozen") != 0))
		return MOD_CONT;

	my_cs_list_freeze(u);

	return MOD_STOP;
}


/* ------------------------------------------------------------------------------- */

/******************************************************************/
/******** JOIN, SJOIN, PRIVMSG, and CS and BS Functions ***********/
/******************************************************************/


int my_cs_privmsg_frozen_check(char *source, int ac, char **av) {
	char *s = NULL;
	char *t = NULL;
	User *u = NULL;
	FreezeChan *fc = NULL;

	if (ac != 2) {
		return MOD_CONT;
	}
	u = finduser(source);

	/* No source user OR message is to a channel OR no '#' in main text */
	if (!u || ((*av[0] == '#')) || (!(t = strchr(av[1], '#')))) {
		return MOD_CONT;
	}

    /* If a server is specified (nick@server format), make sure it matches us, and strip it off. */
	s = strchr(av[0], '@');
	if (s) {
		*s++ = 0;
		if (stricmp(s, ServerName) != 0) {
			return MOD_CONT;
		}
	}

	/* Message is to BotServ or ChanServ */
	if (((stricmp(av[0], s_ChanServ) == 0) || (s_ChanServAlias && (stricmp(av[0], s_ChanServAlias) == 0))) ||
	((stricmp(av[0], s_BotServ) == 0) || (s_BotServAlias && (stricmp(av[0], s_BotServAlias) == 0)))) {
		if (!is_oper(u) && !is_services_oper(u)) {
			int  i       = 0;
			char moo[66] = { '\0' }; /* dunno any chans over 64 characters */
			for (i = 0; i < FREEZETABLESIZE; i++) {
				for (fc = freezechanhash[i]; fc; fc = fc->next) {
					snprintf(moo, 66, " %s ", fc->chan);
					/* #x IS IN the string #xxx. However, ' #x ' is not in
					 * ' #xxx ' ... See, i'm not just a pretty face ... (and
					 * sexy body, witty mind, charasmatic personality etc.)
					 */
					if (stristr(av[1],moo)) {
						send_frozen_nochange_notices(u, av[0], fc);
						return MOD_STOP;
					}
				}
			}
		}
	}
	return MOD_CONT;
}


int my_cs_cs_frozen_check(char *source, int ac, char **av) {
	return my_frozen_check(source, ac, av, s_ChanServ);
}


int my_cs_bs_frozen_check(char *source, int ac, char **av) {
	return my_frozen_check(source, ac, av, s_BotServ);
}


int my_frozen_check(char *source, int argc, char **argv, char *from) {
	User *u;
	FreezeChan *fc;

	if (argc < 1) {
		return MOD_CONT;
	}
	u = finduser(source);

	/* No source user OR message is to a channel OR no '#' in main text */
	if (!u) {
		return MOD_CONT;
	}

	if (!is_oper(u) && !is_services_oper(u)) {
		int i = 0;
		char moo[66] = { '\0' };
		for (i = 0; i < FREEZETABLESIZE; i++) {
			for (fc = freezechanhash[i]; fc; fc = fc->next) {
				snprintf(moo, 66, " %s ", fc->chan);
				if (stristr(argv[0],moo)) {
					send_frozen_nochange_notices(u, from, fc);
					return MOD_STOP;
				}
			}
		}
	}
	return MOD_CONT;
}


int on_join_unsetmodes_ifneedbe(User *u, Channel * c) {
	/* Checks are being performed in the function calling us */
	int n = 0;
	char *temp[10];
	boolean ok = true;

	if (chan_has_user_status(c, u, CUS_OP) || chan_has_user_status(c, u, CUS_VOICE)) {
		ok = false;
	} else {
		if (ircd->adminset != NULL) {
			if (chan_has_user_status(c, u, CUS_PROTECT))
				ok = false;
		}
		if (ircd->owner) {
			if (chan_has_user_status(c, u, CUS_OWNER))
				ok = false;
		}
		if (ircd->halfop) {
			if (chan_has_user_status(c, u, CUS_HALFOP))
				ok = false;
		}
	}

	if (ok) {
		return MOD_CONT; /* They have no modes */
	}

	if (ircd->owner)
		temp[0] = sstrdup(ircd->ownerunset);
	if (ircd->adminset != NULL)
		temp[1] = sstrdup(ircd->adminunset);
	temp[2] = sstrdup("-o");
	if (ircd->halfop)
		temp[3] = sstrdup("-h");

	temp[4] = sstrdup("-v");
	temp[5] = sstrdup(u->nick);
	temp[6] = sstrdup(u->nick);
	temp[7] = sstrdup(u->nick);
	temp[8] = sstrdup(u->nick);
	temp[9] = sstrdup(u->nick);
	anope_cmd_mode(s_ChanServ, c->name, "%s%s%s%s%s %s %s %s %s %s", temp[0], temp[1], temp[2], temp[3], temp[4],
		temp[5], temp[6], temp[7], temp[8], temp[9]);
	chan_set_modes(s_ChanServ, c, 10, temp, 0);

	for (n = 0; n < 10; n++) {
		free(temp[n]);
	}

	return MOD_CONT;
}

int on_join(int argc, char **argv) {
	ChannelInfo *ci;
	User *u;
	FreezeChan *fc;

	if(argc!=3) {
		return MOD_CONT;
	}

	if (!(u = finduser(argv[1]))) {
		return MOD_CONT;
	}

	if ((ci = cs_findchan(argv[2]))) {
		if ((fc = find_freezechan_entry(argv[2]))) {
			if (!stricmp(argv[0], EVENT_START)) {
				last_join_user = u;
				add_ignore(u->nick, 120);
			} else if (!stricmp(argv[0], EVENT_STOP)) {
				if ((u == last_join_user) && (((get_ignore(u->nick) != 0)) || is_oper(u))) {
					delete_ignore(u->nick);
					send_frozen_join_notices(u, s_ChanServ, fc);
					on_join_unsetmodes_ifneedbe(u,ci->c);
				}
			}

		}
	}


	return MOD_CONT;
}


/* ------------------------------------------------------------------------------- */

/******************************************************************/
/***************** Notice about FREEZE functions ******************/
/******************************************************************/

void send_frozen_join_notices(User *u, char *from, FreezeChan *fc) {
	if (u) {
		if (JoinFrozenChan1)
			notice(from, u->nick, JoinFrozenChan1);
		if (JoinFrozenChan2)
			notice(from, u->nick, JoinFrozenChan2);
		if (JoinFrozenChan3)
			notice(from, u->nick, JoinFrozenChan3);
		if (JoinFrozenChan4)
			notice(from, u->nick, JoinFrozenChan4);
	}
}


void send_frozen_nochange_notices(User *u, char *from, FreezeChan *fc) {
	if (u) {
		if (ChanIsFrozen1)
			notice(from, u->nick, ChanIsFrozen1);
		if (ChanIsFrozen2)
			notice(from, u->nick, ChanIsFrozen2);
		if (ChanIsFrozen3)
			notice(from, u->nick, ChanIsFrozen3);
		if (ChanIsFrozen4)
			notice(from, u->nick, ChanIsFrozen4);
	}
}


/* ------------------------------------------------------------------------------- */

/******************************************************************/
/************************ Struct Functions ************************/
/******************************************************************/


static int del_entry(FreezeChan *fc) {
	BotInfo *bi;
	ChannelInfo *ci;
	char *botnick = fc->botnick;

	if (fc->next) {
		fc->next->prev = fc->prev;
	}

	if (fc->prev) {
		fc->prev->next = fc->next;
	} else {
		freezechanhash[tolower(*fc->chan)] = fc->next;
	}

	if ((stricmp(fc->botnick,"*") != 0) && (bi = findbot(botnick))) {
		ci = cs_findchan(fc->chan);
		ci->bi = bi;
		bi->chancount++;
		if (ci->c && ci->c->usercount >= BSMinUsers) {
			bot_join(ci);
		}
	}
	freezenumber--;

	if (fc->chan) {
		free(fc->chan);
	}
	if (fc->reason) {
		free(fc->reason);
	}
	if (fc->by) {
		free(fc->by);
	}
	if (fc->botnick) {
		free(fc->botnick);
	}
	free(fc);
	return 1;
}


FreezeChan *find_freezechan_entry(char *chan) {
	FreezeChan *fc;
	if (!chan || !*chan || !freezenumber || *chan != '#') {
		return NULL;
	}
	for (fc = freezechanhash[tolower(*chan)]; fc; fc = fc->next) {
		if (!stricmp(chan, fc->chan)) {
			return fc;
		}
	}
	return NULL;
}


static FreezeChan *create_entry(char *chan, char *reason, NickAlias *na, char *botnick) {
	FreezeChan *fc;
	fc = scalloc(sizeof(FreezeChan), 1);
	if (fc == NULL) {
		alog("[cs_freeze] WARNING: COULD NOT ADD CHANNEL FREEZE RECORD! [Out of memory?]");
		return NULL;
	}
	fc->chan    = sstrdup(chan);
	fc->reason  = sstrdup(reason);
	fc->by      = sstrdup(na->nc->display);
	fc->botnick = sstrdup(botnick);
	fc->time    = time(NULL);
	add_entry(fc);
	freezenumber++;
	return fc;
}

static void add_entry(FreezeChan *fc) {
	FreezeChan *next, *prev;

	for (prev = NULL, next = freezechanhash[tolower(*fc->chan)];
	next != NULL && stricmp(next->chan, fc->chan) < 0;
	prev = next, next = next->next);
	fc->prev = prev;
	fc->next = next;

	if (!prev) {
		freezechanhash[tolower(*fc->chan)] = fc;
	} else {
		prev->next = fc;
	}
	if (next) {
		next->prev = fc;
	}
	return;
}

void my_cs_freeze_checkanddelifneedbe(char *chan) {
	FreezeChan *fc;
	ChannelInfo *ci;

	if ((!(ci = cs_findchan(chan))) || ((ci->flags & CI_VERBOTEN) || (ci->flags & CI_SUSPENDED))) {
		if ((fc = find_freezechan_entry(chan))) {
			del_entry(fc);
		}
	}
	return;
}


/* ------------------------------------------------------------------------------- */

/******************************************************************/
/********************** Database Functions ************************/
/******************************************************************/


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


int load_freezechan_database(void) {
	dbFILE *f;
	FreezeChan *fc;
	int i = 0, c = 0, ver = 0, failed = 0;
	int32 tmp32;

	if (!(f = open_db(s_ChanServ, FC_DB_NAME, "r", FC_DB_VERSION))) {
		return -2;
	}
	ver = get_file_version(f);

	for (i = 0; i < 1024 && !failed; i++) {
		while ((c = getc_db(f)) == 1) {
			if (c != 1)  {
				alog("Invalid format in %s", FC_DB_NAME);
				return -1;
			}
			fc = scalloc(1, sizeof(FreezeChan));
			SAFE(read_string(&fc->chan, f));
			SAFE(read_string(&fc->reason, f));
			SAFE(read_string(&fc->by, f));
			SAFE(read_string(&fc->botnick, f));
			SAFE(read_int32(&tmp32, f));
			fc->time = tmp32;
			freezenumber++;
			add_entry(fc);
		}
	}
	close_db(f);
	return 0;
}
#undef SAFE


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

int save_freezechan_database(int argc, char **argv) {
	dbFILE *f;
	int i;
	FreezeChan *fc;
	static time_t lastwarn = 0;

	if (!(f = open_db(s_ChanServ, FC_DB_NAME, "w", FC_DB_VERSION))) {
		return -2;
	}

	for (i = 0; i < FREEZETABLESIZE; i++) {
		for (fc = freezechanhash[i]; fc; fc = fc->next) {
			SAFE(write_int8(1, f));
			SAFE(write_string(fc->chan, f));
			SAFE(write_string(fc->reason, f));
			SAFE(write_string(fc->by, f));
			SAFE(write_string(fc->botnick, f));
			SAFE(write_int32(fc->time, f));
			SAFE(write_int8(0, f));
		}
		SAFE(write_int8(0, f));
	}
	close_db(f);

	return 0;
}
#undef SAFE

void my_cs_freeze_clearallmodes(Channel *c) {
	int i = 0,  n = 0, count, exceptcount;
	char *argv[6];
	char **bans, **excepts;
	struct c_userlist *cu;
	struct c_userlist *next;

	for (cu = c->users; cu; cu = next) {
		next = cu->next;
		if (!chan_has_user_status(c, cu->user, CUS_OP) && !chan_has_user_status(c, cu->user, CUS_VOICE)
			&& !chan_has_user_status(c, cu->user, CUS_PROTECT)
			&& !chan_has_user_status(c, cu->user, CUS_OWNER)
			&& !chan_has_user_status(c, cu->user, CUS_HALFOP)) {
			continue;
		}

		argv[0] = sstrdup("-qahov");
		argv[1] = sstrdup(cu->user->nick);
		argv[2] = sstrdup(cu->user->nick);
		argv[3] = sstrdup(cu->user->nick);
		argv[4] = sstrdup(cu->user->nick);
		argv[5] = sstrdup(cu->user->nick);
		anope_cmd_mode(s_ChanServ, c->name, "%s %s %s %s %s %s", argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
		chan_set_modes(s_ChanServ, c, 6, argv, 0);
		for (n = 0; n < 6; n++) {
			free(argv[n]);
		}
	}

	/* Clear modes */
	argv[0] = sstrdup(ircd->modestoremove);
	argv[1] = c->key ? sstrdup(c->key) : NULL;
	anope_cmd_mode(s_ChanServ, c->name, "%s %s", argv[0], argv[1] ? argv[1] : "");
	if (argv[1]) {
		chan_set_modes(s_ChanServ, c, 2, argv, 0);
		free(argv[1]);
	}
	else {
		chan_set_modes(s_ChanServ, c, 1, argv, 0);
	}
	free(argv[0]);

    /* Clear bans */
	count = c->bancount;
	bans = scalloc(sizeof(char *) * count, 1);
	for (i = 0; i < count; i++) {
		bans[i] = sstrdup(c->bans[i]);
	}
	for (i = 0; i < count; i++) {
		argv[0] = sstrdup("-b");
		argv[1] = bans[i];
		anope_cmd_mode(s_ChanServ, c->name, "%s %s", argv[0], argv[1]);
		chan_set_modes(s_ChanServ, c, 2, argv, 0);
		free(argv[1]);
		free(argv[0]);
	}
	free(bans);
	if (!ircd->except) {
		return;
	}
	exceptcount = c->exceptcount;
	excepts = scalloc(sizeof(char *) * exceptcount, 1);
	for (i = 0; i < exceptcount; i++) {
		excepts[i] = sstrdup(c->excepts[i]);
	}
	for (i = 0; i < exceptcount; i++) {
		argv[0] = sstrdup("-e");
		argv[1] = excepts[i];
		anope_cmd_mode(s_ChanServ, c->name, "%s %s", argv[0], argv[1]);
		chan_set_modes(s_ChanServ, c, 2, argv, 0);
		free(argv[1]);
		free(argv[0]);
	}
	free(excepts);
}

/* ------------------------------------------------------------------------------- */

/******************************************************************/
/************************ Ignore Functions ************************/
/******************************************************************/

void delete_ignore(const char *nick) {
	IgnoreData *ign, *prev;
	IgnoreData **whichlist;

	if (!nick || !*nick) {
		return;
	}

    whichlist = &ignore[tolower(nick[0])];

	for (ign = *whichlist, prev = NULL; ign; prev = ign, ign = ign->next) {
		if (stricmp(ign->who, nick) == 0)
			break;
	}
	if (!ign)
		return;
	if (prev)
		prev->next = ign->next;
	else
		*whichlist = ign->next;
	free(ign);
	ign = NULL;
}

/* ------------------------------------------------------------------------------- */

/******************************************************************/
/******************** Configuration Functions *********************/
/******************************************************************/

void load_config(void) {
	int i;

	//reset all settings
	AddChanInfoTag = 0;
	UpdateOnEdit = 0;
	free(JoinFrozenChan1);
	free(JoinFrozenChan2);
	free(JoinFrozenChan3);
	free(JoinFrozenChan4);
	free(ChanIsFrozen1);
	free(ChanIsFrozen2);
	free(ChanIsFrozen3);
	free(ChanIsFrozen4);

	Directive confvalues[][1] = {
		{{"AddChanInfoTag", {{PARAM_SET, PARAM_RELOAD, &AddChanInfoTag}}}},
		{{"UpdateOnEdit", {{PARAM_SET, PARAM_RELOAD, &UpdateOnEdit}}}},
		{{"JoinFrozenChan1", {{PARAM_STRING, PARAM_RELOAD, &JoinFrozenChan1}}}},
		{{"JoinFrozenChan2", {{PARAM_STRING, PARAM_RELOAD, &JoinFrozenChan2}}}},
		{{"JoinFrozenChan3", {{PARAM_STRING, PARAM_RELOAD, &JoinFrozenChan3}}}},
		{{"JoinFrozenChan4", {{PARAM_STRING, PARAM_RELOAD, &JoinFrozenChan4}}}},
		{{"ChanIsFrozen1", {{PARAM_STRING, PARAM_RELOAD, &ChanIsFrozen1}}}},
		{{"ChanIsFrozen2", {{PARAM_STRING, PARAM_RELOAD, &ChanIsFrozen2}}}},
		{{"ChanIsFrozen3", {{PARAM_STRING, PARAM_RELOAD, &ChanIsFrozen3}}}},
		{{"ChanIsFrozen4", {{PARAM_STRING, PARAM_RELOAD, &ChanIsFrozen4}}}}
	};

	for (i = 0; i < 10; i++)
		moduleGetConfigDirective(confvalues[i]);
}

int reloadConf(int argc, char **argv) {
	int ret = 0;

	if (argc >= 1) {
		if (!stricmp(argv[0], EVENT_START)) {
			alog("[cs_freeze] Reloading configuration directives...");
			load_config();
		}
	}

	if (ret)
	alog("[cs_freeze] ERROR: An error has occured while reloading the configuration file");

	return MOD_CONT;
}


/******************************************************************/
/***************** Adding Multilanguage Support *******************/
/******************************************************************/

void add_languages(void) {
	char *langtable_en_us[] = {
		/* LNG_FREEZE_SYNTAX */
		"Syntax: \002FREEZE \037channel\037 \037reason\037\002",
		/* LNG_HELP_FREEZE */
		"The FREEZE command locks a registered channels %s settings.\n"
		"When a channel is FROZEN, none of the channels settings (for example\n"
		"AKICKS, Access Lists, SET options [e.g MLOCK, SECUREOPs etc.] and so\n"
		"on are able to be changed by non-services admins. Additionally, users\n"
		"who are on the given channels access list will not recieve the modes\n"
		"they would normally be given when joining said channel.\n"
		"When FROZEN, all modes on the channel are revoked and any %s bots\n"
		"assigned to the channel are un-assigned untill the the FREEZE is lifted.\n"
		" \n"
		"A reason is mandatory when adding a channel FREEZE.\n"
		"To get a list of all frozen channels: /msg %s list +froz(en).\n",
		/* LNG_UNFREEZE_SYNTAX */
		"Syntax: \002UNFREEZE \037channel\037\002",
		/* LNG_HELP_UNFREEZE */
		"This releases FROZEN channel. All data and settings\n"
		"are preserved from before the suspension.\n"
		"To get a list of all frozen channels: /msg %s list +froz(en).\n",
		/* LNG_HELP_ADMINS */
		"    FREEZE     Freeze a channel and its settings.\n"
		"    UNFREEZE     Releases a frozen channel",
		/* LNG_CHANNEL_FROZEN */
		"The channel %s is FROZEN",
		/* LNG_FREEZE_INVALID_SYNTAX */
		"A valid channel name and reason must be provided",
		/* LNG_CHANNEL_UNFROZEN */
		"Channel \002%s\002 is no longer frozen.",
		/* LNG_UNFREEZE_INVALID_CHANNEL */
		"Could not find a FROZEN channel named %s",
		/* LNG_FREEZE_INVALID_CHANNEL */
		"%s is not registered, thus cannot be FROZEN. Use FORBID.",
		/* LNG_FREEZE_ALLREADY_FROZEN */
		"%s is already FROZEN. See \002/%s INFO %s ALL\002 for more info.",
		/* LNG_CHANNEL_FROZEN_NOW */
		"Channel \002%s\002 is now frozen",
		/* LNG_ERROR_MEM */
		"An ERROR occured. Perhaps we're running out of memory?",
		/* LNG_NO_CHANS_FROZEN */
		"No channels are frozen.",
		/* LNG_LIST_HEADER */
		"       Num.    Channel.         Frozen By",
		/* LNG_LIST_COUNT */
		"Total Forzen Channels: %d",
		/* LNG_FROZEN_INFO_HEADER */
		"Channel %s Freeze Info:",
		/* LNG_FROZEN_BY */
		"   Frozen By: %s",
		/* LNG_FROZEN_REASON */
		"   Reason   : %s",
		/* LNG_FROZEN_AT */
		"   Frozen At: %s"
	};

	moduleInsertLanguage(LANG_EN_US, LNG_NUM_STRINGS, langtable_en_us);
}

/* EOF */
