/*
 * ==================================================================
 * Filename:             adwords.c
 * Description:          Anti-spam filtering
 * Written by:		 AngryWolf <angrywolf@flashmail.com>
 * Documentation:        adwords.txt (comes with the package)
 * ==================================================================
 * Note that this is a modified version of UnrealIRCd's built-in
 * badword system which was written by the UnrealIRCd Team. This
 * means that I copied & modified some codes from src/badwords.c and
 * src/conf.c (they come with the Unreal3.2 package).
 * ==================================================================
 */

#include "config.h"
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#include <io.h>
#endif
#include <fcntl.h>
#include "h.h"
#ifdef STRIPBADWORDS
#include "badwords.h"
#endif
#ifdef _WIN32
#include "version.h"
#endif
#include "badwords.h"

extern void			sendto_one(aClient *to, char *pattern, ...);
extern void			sendto_serv_butone_token(aClient *one, char *prefix, char *command, char *token, char *pattern, ...);

// =================================================================
// Macros
// =================================================================

#define MSG_ADWORDS		"ADWORDS"
#define TOK_ADWORDS		"WO"
#define DEF_REGFLAGS		(REG_ICASE | REG_EXTENDED)
#define BACKREF_TOKEN		'\\'
#define IsAlpha(x)		((((x) >= 65) && ((x) <= 90)) || (((x) >= 97 ) && ((x) <= 122)))

#define ircfree(x)		if (x) MyFree(x); x = NULL
#define ircstrdup(x,y)		if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y)
#define DelHook(x)		if (x) HookDel(x); x = NULL
#define DelOverride(cmd, ovr)	if (ovr && CommandExists(cmd)) CmdoverrideDel(ovr); ovr = NULL
#define DelCommand(x)		if (x) CommandDel(x); x = NULL

#define DelAdwordChannel(a)	DelListItem(a, AdwordsChannel); FreeAdword(a);
#define DelAdwordMessage(a)	DelListItem(a, AdwordsMessage); FreeAdword(a);
#define DelAdwordQuit(a)	DelListItem(a, AdwordsQuit); FreeAdword(a);
#ifdef HOOKTYPE_PRE_LOCAL_PART
 #define DelAdwordPart(a)	DelListItem(a, AdwordsPart); FreeAdword(a);
#endif

/* Default texts */
#define DEF_KILL_REASON		"Yasaklanmis Kelimeyi Kullanan User Killenir.! (Advertising)"
#define DEF_BAN_REASON		"Yasaklanmis Kelimeyi Kullanan User Banlanir.! (Advertising)"
#define DEF_BLOCK_MSG		"Yasaklanmis Kelimeyi Kullanan User Bloacklanir.! (Advertising)"
#define DEF_WARNING_MSG		"Yasaklanmis Kelimeyi Kullanan User Sizin Belirlemis Oldugunuz Kelimeye Dönüsüm Yaptirilir."

/* Ban types */
#ifndef BAN_ACT_KILL
 #define NO_PLACE_HOST_BAN
 /* For backward compatibility */
 #define BAN_ACT_KILL		1
 #define BAN_ACT_KLINE		4
 #define BAN_ACT_ZLINE		5
 #define BAN_ACT_GLINE		6
 #define BAN_ACT_GZLINE		7
#else /* Unreal3.2-RC1 */
  #undef NO_PLACE_HOST_BAN
#endif

/* Message types */
#define MT_ALL			0
#define MT_PRIVMSG		1
#define MT_NOTICE		2
#define MT_CTCP			3
#define MT_NONCTCP		4

/* Command types */
#define CT_PRIVMSG		0
#define CT_NOTICE		1
#define CT_PART			2
#define CT_QUIT			3

/* User types */
#define UT_ALL			0
#define UT_REG			1
#define UT_NONREG		2

/* Word types */
#define WT_REGEX		0x1
#define WT_FAST			0x2
#define WT_FAST_L		0x4
#define WT_FAST_R		0x8

/* Notification methods */
#define NM_PRIVATE		0x00
#define NM_CHANNEL		0x01

// =================================================================
// Module header
// =================================================================

ModuleHeader MOD_HEADER(adwords)
  = {
	"reklam",
	"$Id: reklam.c,v 6.9 2005/03/28 15:31:52 angrywolf Exp Düzenleyen CLaS IRCD'e Uyarlayan By CLaS $",
	"Reklam Yasaklama Ve Filtreleme Modules",
	"3.2-b8-1",
	NULL 
    };

// =================================================================
// Type definitions
// =================================================================

typedef struct _adword Adword;
typedef struct _myconf MyConf;
typedef struct _chlist ChanList;

struct _adword
{
	Adword		*prev, *next;
	char		*word, *replace, *channel;
	u_short		type;
	u_int		notify:1, warn: 1, block:1, kill:1, ban:1;
	u_int		stoponmatch:1;
	u_int		msgtype:3, usertype:2;
	regex_t 	*expr;
};

struct _myconf
{
	u_int		enable_adwords : 1;
	u_int		enable_notifications : 1;
	u_int		enable_bans : 1;
	u_int		enable_kills : 1;
	u_int		enable_blockings : 1;
	u_int		enable_warnings : 1;
	u_int		n_method : 1;
	u_int		require_mode_g : 1;
	int		ban_type: 4;
	long		ban_period;
	char		*channels;
	char		*kill_reason;
	char		*ban_reason;
	char		*block_message;
	char		*warning_message;
};

struct _chlist
{
	ChanList	*prev, *next;
	char		*channel;
};

// =================================================================
// Function definitions & variable declarations
// =================================================================

extern ConfigEntry	*config_find_entry(ConfigEntry *ce, char *name);

static Command		*AddCommand(Module *module, char *msg, char *token, iFP func);
static int		m_adwords(aClient *cptr, aClient *sptr, int parc, char *parv[]);
static int		cb_config_test(ConfigFile *, ConfigEntry *, int, int *);
static int		cb_config_run(ConfigFile *, ConfigEntry *, int);
static int		cb_config_rehash();
static char		*cb_privmsg(aClient *, aClient *, aClient *, char *, int);
static char		*cb_chanmsg(aClient *, aClient *, aChannel *, char *, int);
static char		*cb_quit(aClient *sptr, char *comment);
#ifdef HOOKTYPE_PRE_LOCAL_PART
static char		*cb_partmsg(aClient *sptr, aChannel *chptr, char *comment);
#endif
#ifndef HOOKTYPE_PRE_LOCAL_QUIT
static Cmdoverride	*AddOverride(char *msg, iFP cb);
static int		override_quit(Cmdoverride *, aClient *, aClient *, int, char *[]);
#endif
static void		adwords_event_kill(char *name);
static void		stats_wordlist(aClient *sptr, Adword *wordlist, char wtype);
static char		*find_CmdType(u_int type);
static char		*find_BanType(int type);
#ifdef NO_PLACE_HOST_BAN
static char		*find_BanFlag(int type);
#endif
static char		find_MsgType(u_int type);
static char		find_WordFlag(u_int type);

static Hook		*HookConfTest, *HookConfRun, *HookConfRehash;
static Hook		*HookPrivMsg = NULL, *HookChanMsg = NULL;
#ifdef HOOKTYPE_PRE_LOCAL_PART
static Hook		*HookPrePart = NULL;
#endif
#ifdef HOOKTYPE_PRE_LOCAL_QUIT
static Hook		*HookPreQuit = NULL;
#endif

Adword			*AdwordsMessage = NULL, *AdwordsChannel = NULL, *AdwordsQuit = NULL;
#ifdef HOOKTYPE_PRE_LOCAL_PART
Adword			*AdwordsPart = NULL;
#endif

ConfigItem_except       *conf_exceptchan;
MyConf			myconf;
static char		nickbuf[NICKLEN+1];
Command			*CmdAdwords;
#ifndef HOOKTYPE_PRE_LOCAL_QUIT
Cmdoverride		*OvrQuit;
#endif

#ifndef STATIC_LINKING
static ModuleInfo	*MyModInfo;
 #define MyMod		MyModInfo->handle
 #define SAVE_MODINFO	MyModInfo = modinfo;
#else
 #define MyMod		NULL
 #define SAVE_MODINFO
#endif

// =================================================================
// Functions related to the Adword struct
// =================================================================

static void FreeAdword(Adword *a)
{
	MyFree(a->word);
	ircfree(a->replace);
	ircfree(a->channel);
	if (a->expr)
	{
		regfree(a->expr);
		MyFree(a->expr);
	}

	MyFree(a);
}

static Adword *adword_duplicate(Adword *a, int regflags)
{
	Adword *x;

	x = (Adword *) MyMalloc(sizeof(Adword));
        memcpy(x, a, sizeof(Adword));
	x->word = strdup(a->word);
	if (a->replace)
	        x->replace = strdup(a->replace);
	if (a->channel)
	        x->channel = strdup(a->channel);
	if (a->expr)
	{
		x->expr = (regex_t *) MyMallocEx(sizeof(regex_t));
		regcomp(x->expr, x->word, regflags);
	}

        return x;
}

static void AddExceptChan(char *mask)
{
        ConfigItem_except *e;

        e = (ConfigItem_except *) MyMallocEx(sizeof(ConfigItem_except));
        e->mask = strdup(mask);

        AddListItem(e, conf_exceptchan);
}

// =================================================================
// Functions related to loading/unloading configuration
// =================================================================

static void InitConf()
{
	memset(&myconf, 0, sizeof myconf);
	memset(&nickbuf, 0, sizeof nickbuf);

	myconf.ban_type		= BAN_ACT_KLINE;
	myconf.ban_period	= 3600; /* 1h */
        conf_exceptchan         = NULL;
}

static void FreeConf()
{
	Adword			*a;
	ListStruct		*next;
        ConfigItem_except	*e;

	/* myconf */

	if (myconf.channels)
		MyFree(myconf.channels);
	if (myconf.kill_reason)
		MyFree(myconf.kill_reason);
	if (myconf.ban_reason)
		MyFree(myconf.ban_reason);
	if (myconf.block_message)
		MyFree(myconf.block_message);
	if (myconf.warning_message)
		MyFree(myconf.warning_message);

	/* adwords */

	for (a = AdwordsChannel; a; a = (Adword *) next)
	{
		next = (ListStruct *) a->next;
		DelAdwordChannel(a);
	}
	for (a = AdwordsMessage; a; a = (Adword *) next)
	{
		next = (ListStruct *) a->next;
		DelAdwordMessage(a);
	}
	for (a = AdwordsQuit; a; a = (Adword *) next)
	{
		next = (ListStruct *) a->next;
		DelAdwordQuit(a);
	}
#ifdef HOOKTYPE_PRE_LOCAL_PART
	for (a = AdwordsPart; a; a = (Adword *) next)
	{
		next = (ListStruct *) a->next;
		DelAdwordPart(a);
	}
#endif
	/* channel exceptions */

        for (e = conf_exceptchan; e; e = (ConfigItem_except *) next)
        {
                next = (ListStruct *) e->next;
                DelListItem(e, conf_exceptchan);
                MyFree(e->mask);
                MyFree(e);
        }
}

// =================================================================
// Module functions
// =================================================================

DLLFUNC int MOD_TEST(adwords)(ModuleInfo *modinfo)
{
	SAVE_MODINFO
	HookConfTest = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, cb_config_test);

	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(adwords)(ModuleInfo *modinfo)
{
	SAVE_MODINFO
	InitConf();

	CmdAdwords	= AddCommand(modinfo->handle, MSG_ADWORDS, TOK_ADWORDS, m_adwords);
	HookConfRun	= HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, cb_config_run);
	HookConfRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH, cb_config_rehash);

	if (!CmdAdwords)
		return MOD_FAILED;

	HookPrivMsg	= HookAddPCharEx(modinfo->handle, HOOKTYPE_USERMSG, cb_privmsg);
	HookChanMsg	= HookAddPCharEx(modinfo->handle, HOOKTYPE_CHANMSG, cb_chanmsg);
#ifdef HOOKTYPE_PRE_LOCAL_PART
	HookPrePart	= HookAddPCharEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_PART, cb_partmsg);
#endif
#ifdef HOOKTYPE_PRE_LOCAL_QUIT
	HookPreQuit	= HookAddPCharEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_QUIT, cb_quit);
#endif

	return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(adwords)(int module_load)
{
#ifndef HOOKTYPE_PRE_LOCAL_QUIT
	if (!(OvrQuit = AddOverride("quit", override_quit)))
		return MOD_FAILED;
#endif

	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(adwords)(int module_unload)
{
	FreeConf();

#ifdef HOOKTYPE_PRE_LOCAL_PART
	DelHook(HookPrePart);
#endif
	DelHook(HookChanMsg);
	DelHook(HookPrivMsg);
	DelHook(HookConfRehash);
	DelHook(HookConfRun);
	DelHook(HookConfTest);

#ifndef HOOKTYPE_PRE_LOCAL_QUIT
	DelOverride("quit", OvrQuit);
#endif
	DelCommand(CmdAdwords);

	return MOD_SUCCESS;
}

// =================================================================
// Types
// =================================================================

static char *find_CmdType(u_int type)
{
	switch(type)
	{
		case CT_PRIVMSG:	return "PRIVMSG";
		case CT_NOTICE:		return "NOTICE";
		case CT_PART:		return "PART";
		case CT_QUIT:		return "QUIT";
	}

	return "???";
}

static char find_MsgType(u_int type)
{
	switch(type)
	{
		case MT_ALL:		return 'a';
		case MT_PRIVMSG:	return 'p';
		case MT_NOTICE:		return 'n';
		case MT_CTCP:		return 'C';
		case MT_NONCTCP:	return 'N';
	}

	return '?';
}

static char find_WordFlag(u_int type)
{
	if (type & WT_FAST)
		return 'F';
	else if (type & WT_REGEX)
		return 'R';

	return '?';
}

static char find_UserType(u_int type)
{
	switch(type)
	{
		case UT_ALL:		return 'a';
		case UT_REG:		return 'r';
		case UT_NONREG:		return 'n';
	}

	return '?';
}

static char *find_BanType(int type)
{
	switch(type)
	{
		case BAN_ACT_KLINE:		return "kline";
		case BAN_ACT_GLINE:		return "gline";
		case BAN_ACT_ZLINE:		return "zline";
		case BAN_ACT_GZLINE:		return "gzline";
	}

	return "<none>";
}

#ifdef NO_PLACE_HOST_BAN
static char *find_BanFlag(int type)
{
	switch(type)
	{
		case BAN_ACT_KLINE:		return "k";
		case BAN_ACT_GLINE:		return "G";
		case BAN_ACT_ZLINE:		return "z";
		case BAN_ACT_GZLINE:		return "Z";
	}

	return NULL;
}
#endif

static char *make_optlist(Adword *a)
{
	static char buf[11];
	char *p = buf;

	if (a->notify)		*(p++) = 'n';
	if (a->warn)		*(p++) = 'w';
	if (a->block)		*(p++) = 'b';
	if (a->kill)		*(p++) = 'k';
	if (a->ban)		*(p++) = 'B';
	if (a->stoponmatch)	*(p++) = 's';
	if (p == buf)		*(p++) = '*';

	*p = 0;

	return buf;
}

// =================================================================
// Misc stuff
// =================================================================

static Command *AddCommand(Module *module, char *msg, char *token, iFP func)
{
	Command *cmd;

	if (CommandExists(msg))
    	{
            	    config_error("Command %s already exists", msg);
            	    return NULL;
    	}
    	if (CommandExists(token))
	{
            	    config_error("Token %s already exists", token);
            	    return NULL;
    	}

	cmd = CommandAdd(module, msg, token, func, MAXPARA, 0);

#ifndef STATIC_LINKING
	if (ModuleGetError(module) != MODERR_NOERROR || !cmd)
#else
	if (!cmd)
#endif
	{
#ifndef STATIC_LINKING
		config_error("Error adding command %s: %s", msg,
			ModuleGetErrorStr(module));
#else
		config_error("Error adding command %s", msg);
#endif
		return NULL;
	}

	return cmd;
}

#ifndef HOOKTYPE_PRE_LOCAL_QUIT
static Cmdoverride *AddOverride(char *msg, iFP cb)
{
	Cmdoverride *ovr = CmdoverrideAdd(MyMod, msg, cb);

#ifndef STATIC_LINKING
        if (ModuleGetError(MyMod) != MODERR_NOERROR || !ovr)
#else
	if (!ovr)
#endif
	{
#ifndef STATIC_LINKING
		config_error("Error replacing command %s when loading module %s: %s",
			msg, MOD_HEADER(adwords).name, ModuleGetErrorStr(MyMod));
#else
		config_error("Error replacing command %s when loading module %s",
			msg, MOD_HEADER(adwords).name);
#endif
		return NULL;
	}

	return ovr;
}
#endif

static u_int check_type(char *word)
{
	/*
	 * This code originally comes from src/s_conf.c,
	 * with some modifications.
	 */

	u_int		type = WT_FAST, fasttype = 0;
	char		*p;

	for (p = word; *p; p++)
	{
		/*
		 * I use my own check for alphabetic characters.
		 * The old one accepted character codes between
		 * 65 and 123. That means ABCDEFGHIJKLMNOPQRSTU
		 * WXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{ were
		 * accepted. I don't want this.
		 */
		if (!IsAlpha(*p))
		{
			if ((word == p) && (*p == '*'))
			{
				/* Asterisk at the left */
				fasttype |= WT_FAST_L;
				continue;
			}
			if ((p[1] == 0) && (*p == '*'))
			{
				/* Asterisk at the right */
				fasttype |= WT_FAST_R;
				continue;
			}
			type = WT_REGEX;
			break;
		}
	}

	if (type != WT_REGEX)
		type |= fasttype;

	return type;
}

static u_int check_regex(ConfigEntry *ce_word)
{
	/*
	 * This code originally comes from src/s_conf.c,
	 * with some modifications.
	 */

	int		errorcode, errorbufsize;
	u_int		errors = 0;
	char		*errorbuf;
	regex_t		*expr;

	expr = (regex_t *) MyMallocEx(sizeof(regex_t));
	errorcode = regcomp(expr, ce_word->ce_vardata, DEF_REGFLAGS);
	if (errorcode > 0)
	{
		errorbufsize = regerror(errorcode, expr, NULL, 0)+1;
		errorbuf = MyMalloc(errorbufsize);
		regerror(errorcode, expr, errorbuf, errorbufsize);
		config_error("%s:%i: adwords::adword::%s contains an invalid regex: %s",
			ce_word->ce_fileptr->cf_filename,
			ce_word->ce_varlinenum,
			ce_word->ce_varname, errorbuf);
		errors++;
		MyFree(errorbuf);
	}
	regfree(expr);
	MyFree(expr);

	return errors;
}

static char *find_backref(char *string)
{
	char *p;

	for (p = string; *p; p++)
	{
		if (p[0] == BACKREF_TOKEN)
		{
			if (p != string && p[-1] == BACKREF_TOKEN)
				continue;
			if (p[1] >= '1' && p[1] <= '9')
				return p;
		}
	}

	return NULL;
}

static char *dump_fastword(u_int type, char *word)
{
	u_int		wlength;
	char		*wtmp, *p, *q;

	wlength = strlen(word) + 1;
	if (type & WT_FAST_L)
		wlength--;
	if (type & WT_FAST_R)
		wlength--;

	q = wtmp = (char *) MyMalloc(wlength);
	for (p = word; *p; p++)
		if (*p != '*')
			*(q++) = *p;
	*q = '\0';

	return wtmp;
}

// =================================================================
// Config file interfacing
// =================================================================

static int cb_config_rehash()
{
	FreeConf();
	InitConf();

	return 1;
}

static int cb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
	ConfigEntry	*ce_matchtype, *ce_word, *ce_replace, *cep, *cepp;
	int		errors = 0;
	u_int		matchtype;

	if (type != CONFIG_MAIN)
		return 0;

	if (!strcmp(ce->ce_varname, "adwords"))
	{
		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
		{
			if (!cep->ce_varname)
			{
				config_error("%s:%i: blank adwords item",
					cep->ce_fileptr->cf_filename,
					cep->ce_varlinenum);
				errors++;
			}
			else if (!strcmp(cep->ce_varname, "enable"))
			{
				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
				{
					if (!cepp->ce_varname)
					{
						config_error("%s:%i: blank adwords::enable item",
							cepp->ce_fileptr->cf_filename,
							cepp->ce_varlinenum);	
						errors++;
						continue;
					}
					if (!strcmp(cepp->ce_varname, "adwords"))
						 ;
					else if (!strcmp(cepp->ce_varname, "bans"))
						 ;
					else if (!strcmp(cepp->ce_varname, "kills"))
						 ;
					else if (!strcmp(cepp->ce_varname, "blockings"))
						 ;
					else if (!strcmp(cepp->ce_varname, "notifications"))
						 ;
					else if (!strcmp(cepp->ce_varname, "warnings"))
						 ;
					else
					{
						config_error("%s:%i: unknown directive adwords::enable::%s",
							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
							cepp->ce_varname);
						errors++;
					}
				}
			}
			else if (!cep->ce_vardata)
			{
				config_error("%s:%i: adwords::%s item without value",
					cep->ce_fileptr->cf_filename,
					cep->ce_varlinenum, cep->ce_varname);
				errors++;
			}
			else if (!strcmp(cep->ce_varname, "adword"))
			{
				matchtype = 0;

				if (!strcmp(cep->ce_vardata, "all"))
					;
				else if (!strcmp(cep->ce_vardata, "message"))
					;
				else if (!strcmp(cep->ce_vardata, "channel"))
					;
				else if (!strcmp(cep->ce_vardata, "quit"))
					;
#ifdef HOOKTYPE_PRE_LOCAL_PART
				else if (!strcmp(cep->ce_vardata, "part"))
					;
#endif
				else
				{
					config_error("%s:%i: unknown adwords::adword type %s",
						cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
						cep->ce_vardata);
					errors++;
					continue;
				}

				/* matchtype */
				if ((ce_matchtype = config_find_entry(cep->ce_entries, "matchtype")))
				{
					if (!ce_matchtype->ce_vardata)
					{
						config_error("%s:%i: adwords::adword::matchtype without contents",
							ce_matchtype->ce_fileptr->cf_filename,
							ce_matchtype->ce_varlinenum);
						errors++;
						continue;
					}

					if (!strcmp(ce_matchtype->ce_vardata, "regex"))
						matchtype = WT_REGEX;
					else if (!strcmp(ce_matchtype->ce_vardata, "fast"))
						matchtype = WT_FAST;
					else if (!strcmp(ce_matchtype->ce_vardata, "auto"))
						;
					else
					{
						config_error("%s:%i: unknown adwords::adword::matchtype %s",
							ce_matchtype->ce_fileptr->cf_filename,
							ce_matchtype->ce_varlinenum,
							ce_matchtype->ce_vardata);
						errors++;
						continue;
					}
				}

				/* word */
				if (!(ce_word = config_find_entry(cep->ce_entries, "word")))
				{
					config_error("%s:%i: adwords::adword without word item",
						cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
					errors++;
					continue;
				}
				else if (!ce_word->ce_vardata)
				{
					config_error("%s:%i: adwords::adword::word without contents",
						ce_word->ce_fileptr->cf_filename, ce_word->ce_varlinenum);
					errors++;
					continue;
				}
				else
				{
					u_int wordtype = check_type(ce_word->ce_vardata);

					if (!matchtype)
						;
					else if (matchtype & WT_FAST)
					{
						if (wordtype & WT_REGEX)
						{
							config_error("%s:%i: adwords::adword::word contains invalid data (the fast badword replace system can do: \"blah\", \"*blah\", \"blah*\" and \"*blah*\", in all other cases use regex)",
								ce_word->ce_fileptr->cf_filename,
								ce_word->ce_varlinenum);
							errors++;
						}
					}
					else
						errors += check_regex(ce_word);

					if (errors)
						continue;
				}

				/* replace */
				if ((ce_replace = config_find_entry(ce->ce_entries, "replace")))
				{
					if (!ce_replace->ce_vardata)
					{
						config_error("%s:%i: adwords::adword::replace without contents",
							ce_replace->ce_fileptr->cf_filename,
							ce_replace->ce_varlinenum);
						errors++;
					}
				}

				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
				{
					if (!cepp->ce_varname)
					{
						config_error("%s:%i: blank adwords::adword item",
							cepp->ce_fileptr->cf_filename,
							cepp->ce_varlinenum);	
						errors++;
						continue;
					}
					if (!strcmp(cepp->ce_varname, "notify"))
						 ;
					else if (!strcmp(cepp->ce_varname, "warn"))
						 ;
					else if (!strcmp(cepp->ce_varname, "block"))
						 ;
					else if (!strcmp(cepp->ce_varname, "kill"))
						 ;
					else if (!strcmp(cepp->ce_varname, "ban"))
						 ;
					else if (!strcmp(cepp->ce_varname, "stoponmatch"))
						 ;
					else if (!cepp->ce_vardata)
					{
						config_error("%s:%i: adwords::adword::%s item without value",
							cepp->ce_fileptr->cf_filename,
							cepp->ce_varlinenum, cepp->ce_varname);	
						errors++;
						continue;
					}
					else if (!strcmp(cepp->ce_varname, "matchtype"))
						;
					else if (!strcmp(cepp->ce_varname, "word"))
						;
					else if (!strcmp(cepp->ce_varname, "replace"))
						;
					else if (!strcmp(cepp->ce_varname, "channel"))
						;
					else if (!strcmp(cepp->ce_varname, "msgtype"))
					{
						if (!strcmp(cepp->ce_vardata, "all"))
							;
						else if (!strcmp(cepp->ce_vardata, "privmsg"))
							;
						else if (!strcmp(cepp->ce_vardata, "notice"))
							;
						else if (!strcmp(cepp->ce_vardata, "ctcp"))
							;
						else if (!strcmp(cepp->ce_vardata, "non-ctcp"))
							;
						else
						{
							config_error("%s:%i: unknown adwords::adword::msgtype %s",
								cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
								cepp->ce_vardata);
							errors++;
						}
					}
					else if (!strcmp(cepp->ce_varname, "usertype"))
					{
						if (!strcmp(cepp->ce_vardata, "all"))
							;
						else if (!strcmp(cepp->ce_vardata, "registered"))
							;
						else if (!strcmp(cepp->ce_vardata, "non-registered"))
							;
						else
						{
							config_error("%s:%i: unknown adwords::adword::usertype %s",
								cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
								cepp->ce_vardata);
							errors++;
						}
					}
					else
					{
						config_error("%s:%i: unknown directive adwords::adword::%s",
							cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum,
							cepp->ce_varname);
						errors++;
					}
				}
			}
			else if (!strcmp(cep->ce_varname, "notification-channels"))
				;
			else if (!strcmp(cep->ce_varname, "notification-method"))
			{
				if (!strcmp(cep->ce_vardata, "channel"))
					;
				else if (!strcmp(cep->ce_vardata, "private"))
					;
				else
				{
					config_error("%s:%i: unknown notification method %s",
						cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
						cep->ce_vardata);
					errors++;
				}
			}
			else if (!strcmp(cep->ce_varname, "require-mode-g"))
				;
			else if (!strcmp(cep->ce_varname, "kill-reason"))
				;
			else if (!strcmp(cep->ce_varname, "ban-reason"))
				;
			else if (!strcmp(cep->ce_varname, "block-message"))
				;
			else if (!strcmp(cep->ce_varname, "warning-message"))
				;
			else if (!strcmp(cep->ce_varname, "ban-type"))
			{
				if (!strcmp(cep->ce_vardata, "kline"))
					;
				else if (!strcmp(cep->ce_vardata, "gline"))
					;
				else if (!strcmp(cep->ce_vardata, "zline"))
					;
				else if (!strcmp(cep->ce_vardata, "gzline"))
					;
				else
				{
					config_error("%s:%i: unknown ban type %s",
						cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
						cep->ce_vardata);
					errors++;
				}
			}
			else if (!strcmp(cep->ce_varname, "ban-period"))
				;
			else if (!strcmp(cep->ce_varname, "except"))
			{
				if (!strcmp(cep->ce_vardata, "channel"))
				{
					if (!config_find_entry(cep->ce_entries, "mask"))
					{
						config_error("%s:%i: adwords::except channel without mask item",
							cep->ce_fileptr->cf_filename, cep->ce_varlinenum);
						errors++;
						continue;
					}
					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
					{
						if (!cepp->ce_vardata)
						{
							config_error("%s:%i: adwords::except channel item without contents",
								cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum);
							errors++;
							continue;
						}
						if (!strcmp(cepp->ce_varname, "mask"))
							;
						else
						{
							config_error("%s:%i: unknown adwords::except channel directive %s",
								cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname);
							errors++;
							continue;
						}
					}
				}
			}
			else 
			{
				config_error("%s:%i: unknown directive adwords::%s",
					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
					cep->ce_varname);
				errors++;
			}
		}
		*errs = errors;
		return errors ? -1 : 1;
	}
	else
		return 0;
}

static int cb_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
	ConfigEntry	*cep, *cepp;

	if (type != CONFIG_MAIN)
		return 0;

	if (!strcmp(ce->ce_varname, "adwords"))
	{
		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
		{
			if (!strcmp(cep->ce_varname, "enable"))
			{
				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
				{
					if (!strcmp(cepp->ce_varname, "adwords"))
						myconf.enable_adwords = config_checkval(cepp->ce_vardata, CFG_YESNO);
					else if (!strcmp(cepp->ce_varname, "bans"))
						myconf.enable_bans = config_checkval(cepp->ce_vardata, CFG_YESNO);
					else if (!strcmp(cepp->ce_varname, "kills"))
						myconf.enable_kills = config_checkval(cepp->ce_vardata, CFG_YESNO);
					else if (!strcmp(cepp->ce_varname, "blockings"))
						myconf.enable_blockings = config_checkval(cepp->ce_vardata, CFG_YESNO);
					else if (!strcmp(cepp->ce_varname, "notifications"))
						myconf.enable_notifications = config_checkval(cepp->ce_vardata, CFG_YESNO);
					else if (!strcmp(cepp->ce_varname, "warnings"))
						myconf.enable_warnings = config_checkval(cepp->ce_vardata, CFG_YESNO);
				}
			}
			else if (!strcmp(cep->ce_varname, "notification-channels"))
			{
				ircstrdup(myconf.channels, cep->ce_vardata);
			}
			else if (!strcmp(cep->ce_varname, "notification-method"))
			{
				if (!strcmp(cep->ce_vardata, "channel"))
					myconf.n_method = NM_CHANNEL;
				else if (!strcmp(cep->ce_vardata, "private"))
					myconf.n_method = NM_PRIVATE;
			}
			else if (!strcmp(cep->ce_varname, "kill-reason"))
			{
				if (strcmp(cep->ce_vardata, "no"))
				{
					ircstrdup(myconf.kill_reason, cep->ce_vardata);
				}
			}
			else if (!strcmp(cep->ce_varname, "ban-reason"))
			{
				if (strcmp(cep->ce_vardata, "no"))
				{
					ircstrdup(myconf.ban_reason, cep->ce_vardata);
				}
			}
			else if (!strcmp(cep->ce_varname, "block-message"))
			{
				if (strcmp(cep->ce_vardata, "no"))
				{
					ircstrdup(myconf.block_message, cep->ce_vardata);
				}
			}
			else if (!strcmp(cep->ce_varname, "warning-message"))
			{
				if (strcmp(cep->ce_vardata, "no"))
				{
					ircstrdup(myconf.warning_message, cep->ce_vardata);
				}
			}
			else if (!strcmp(cep->ce_varname, "require-mode-g"))
				myconf.require_mode_g = config_checkval(cep->ce_vardata, CFG_YESNO);
			else if (!strcmp(cep->ce_varname, "ban-period"))
				myconf.ban_period = config_checkval(cep->ce_vardata, CFG_TIME);
			else if (!strcmp(cep->ce_varname, "ban-type"))
			{
				if (!strcmp(cep->ce_vardata, "kline"))
					myconf.ban_type = BAN_ACT_KLINE;
				else if (!strcmp(cep->ce_vardata, "gline"))
					myconf.ban_type = BAN_ACT_GLINE;
				else if (!strcmp(cep->ce_vardata, "zline"))
					myconf.ban_type = BAN_ACT_ZLINE;
				else if (!strcmp(cep->ce_vardata, "gzline"))
					myconf.ban_type = BAN_ACT_GZLINE;
			}
			else if (!strcmp(cep->ce_varname, "except"))
			{
				if (!strcmp(cep->ce_vardata, "channel"))
				{
					for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
						if (!strcmp(cepp->ce_varname, "mask"))
							AddExceptChan(cepp->ce_vardata);
				}
			}
			else if (!strcmp(cep->ce_varname, "adword"))
			{
				ConfigEntry	*ce_matchtype, *ce_word, *ce_block;
				Adword		*a;
				int		regflags;

				regflags = DEF_REGFLAGS;
				a = (Adword *) MyMallocEx(sizeof(Adword));

				/* block */
				if ((ce_block = config_find_entry(cep->ce_entries, "block")))
				{
					a->block = 1;
					regflags |= REG_NOSUB;
				}

				/* matchtype */
				if ((ce_matchtype = config_find_entry(cep->ce_entries, "matchtype")))
				{
					if (!strcmp(ce_matchtype->ce_vardata, "regex"))
						a->type = WT_REGEX;
					else if (!strcmp(ce_matchtype->ce_vardata, "fast"))
						a->type = WT_FAST;
				}

				/* word */
				if ((ce_word = config_find_entry(cep->ce_entries, "word")))
				{
					if (!a->type)
						a->type = check_type(ce_word->ce_vardata);

					if (a->type & WT_REGEX)
					{
						ircstrdup(a->word, ce_word->ce_vardata);
						a->expr = (regex_t *) MyMallocEx(sizeof(regex_t));
						regcomp(a->expr, a->word, regflags);
					}
					else
					{
						/* We have to do this twice (because of "matchtype fast") */
						a->type = check_type(ce_word->ce_vardata);
						a->word = dump_fastword(a->type, ce_word->ce_vardata);
					}
				}

				for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
				{
					if (!strcmp(cepp->ce_varname, "word"))
						;
					else if (!strcmp(cepp->ce_varname, "block"))
						;
					else if (!strcmp(cepp->ce_varname, "replace"))
					{
						ircstrdup(a->replace, cepp->ce_vardata);
					}
					else if (!strcmp(cepp->ce_varname, "channel"))
					{
						ircstrdup(a->channel, cepp->ce_vardata);
					}
					else if (!strcmp(cepp->ce_varname, "notify"))
						a->notify = 1;
					else if (!strcmp(cepp->ce_varname, "warn"))
						a->warn = 1;
					else if (!strcmp(cepp->ce_varname, "kill"))
						a->kill = 1;
					else if (!strcmp(cepp->ce_varname, "ban"))
						a->ban = 1;
					else if (!strcmp(cepp->ce_varname, "stoponmatch"))
						a->stoponmatch = 1;
					else if (!strcmp(cepp->ce_varname, "msgtype"))
					{
						if (!strcmp(cepp->ce_vardata, "all"))
							a->msgtype = MT_ALL;
						else if (!strcmp(cepp->ce_vardata, "privmsg"))
							a->msgtype = MT_PRIVMSG;
						else if (!strcmp(cepp->ce_vardata, "notice"))
							a->msgtype = MT_NOTICE;
						else if (!strcmp(cepp->ce_vardata, "ctcp"))
							a->msgtype = MT_CTCP;
						else if (!strcmp(cepp->ce_vardata, "non-ctcp"))
							a->msgtype = MT_NONCTCP;
					}
					else if (!strcmp(cepp->ce_varname, "usertype"))
					{
						if (!strcmp(cepp->ce_vardata, "all"))
							a->usertype = UT_ALL;
						else if (!strcmp(cepp->ce_vardata, "registered"))
							a->usertype = UT_REG;
						else if (!strcmp(cepp->ce_vardata, "non-registered"))
							a->usertype = UT_NONREG;
					}
				}

				if (!strcmp(cep->ce_vardata, "message"))
				{
					AddListItem(a, AdwordsMessage);
				}
				else if (!strcmp(cep->ce_vardata, "channel"))
				{
					AddListItem(a, AdwordsChannel);
				}
				else if (!strcmp(cep->ce_vardata, "quit"))
				{
					AddListItem(a, AdwordsQuit);
				}
#ifdef HOOKTYPE_PRE_LOCAL_PART
				else if (!strcmp(cep->ce_vardata, "part"))
				{
					AddListItem(a, AdwordsPart);
				}
#endif
				else if (!strcmp(cep->ce_vardata, "all"))
				{
					AddListItem(a, AdwordsMessage);
					AddListItem(adword_duplicate(a, regflags), AdwordsChannel);
					AddListItem(adword_duplicate(a, regflags), AdwordsQuit);
#ifdef HOOKTYPE_PRE_LOCAL_PART
					AddListItem(adword_duplicate(a, regflags), AdwordsPart);
#endif
				}
			}
		}
	}

	return 0;
}

static int cb_stats(aClient *sptr)
{
	sendto_one(sptr, ":%s %i %s :enable::adwords: %d",
		me.name, RPL_TEXT, sptr->name, myconf.enable_adwords);
	sendto_one(sptr, ":%s %i %s :enable::bans: %d",
		me.name, RPL_TEXT, sptr->name, myconf.enable_bans);
	sendto_one(sptr, ":%s %i %s :enable::kills: %d",
		me.name, RPL_TEXT, sptr->name, myconf.enable_kills);
	sendto_one(sptr, ":%s %i %s :enable::blockings: %d",
		me.name, RPL_TEXT, sptr->name, myconf.enable_blockings);
	sendto_one(sptr, ":%s %i %s :enable::notifications: %d",
		me.name, RPL_TEXT, sptr->name, myconf.enable_notifications);
	sendto_one(sptr, ":%s %i %s :enable::warnings: %d",
		me.name, RPL_TEXT, sptr->name, myconf.enable_warnings);
	sendto_one(sptr, ":%s %i %s :require-mode-g: %d",
		me.name, RPL_TEXT, sptr->name, myconf.require_mode_g);
	sendto_one(sptr, ":%s %i %s :notification-method: %s",
		me.name, RPL_TEXT, sptr->name, (myconf.n_method == NM_PRIVATE) ? "private" : (myconf.n_method == NM_CHANNEL) ? "channel" : "<unknown>");
	sendto_one(sptr, ":%s %i %s :notification-channels: %s",
		me.name, RPL_TEXT, sptr->name, myconf.channels ? myconf.channels : "<none>");
	sendto_one(sptr, ":%s %i %s :kill-reason: %s",
		me.name, RPL_TEXT, sptr->name, myconf.kill_reason ? myconf.kill_reason : DEF_KILL_REASON);
	sendto_one(sptr, ":%s %i %s :ban-reason: %s",
		me.name, RPL_TEXT, sptr->name, myconf.ban_reason ? myconf.ban_reason : DEF_BAN_REASON);
	sendto_one(sptr, ":%s %i %s :block-message: %s",
		me.name, RPL_TEXT, sptr->name, myconf.block_message ? myconf.block_message : DEF_BLOCK_MSG);
	sendto_one(sptr, ":%s %i %s :warning-message: %s",
		me.name, RPL_TEXT, sptr->name, myconf.warning_message ? myconf.warning_message : DEF_WARNING_MSG);
	sendto_one(sptr, ":%s %i %s :ban-type: %s",
		me.name, RPL_TEXT, sptr->name, find_BanType(myconf.ban_type));
	sendto_one(sptr, ":%s %i %s :ban-period: %s",
		me.name, RPL_TEXT, sptr->name, pretty_time_val(myconf.ban_period));

	sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, sptr->name, 'S');
	return 0;
}

#ifdef NO_PLACE_HOST_BAN
void ban_client(aClient *cptr, char *type, long period, char *reason)
{
	char hostip[128], mo[100], mo2[100];
	char *tkllayer[9] = {
		me.name,	/*0  server.name */
		"+",		/*1  +|- */
		type,		/*2  ban type */
		"*",		/*3  user */
		NULL,		/*4  host */
		NULL,
		NULL,		/*6  expire_at */
		NULL,		/*7  set_at */
		NULL		/*8  reason */
	};

	strlcpy(hostip, Inet_ia2p(&cptr->ip), sizeof(hostip));

	tkllayer[4] = hostip;
	tkllayer[5] = me.name;
	ircsprintf(mo, "%li", period + TStime());
	ircsprintf(mo2, "%li", TStime());
	tkllayer[6] = mo;
	tkllayer[7] = mo2;
	tkllayer[8] = reason;

	m_tkl(&me, &me, 9, tkllayer);
	return;
}
#endif

/*
 * These stuffs are from src/badwords.c,
 * I modified them for my own purposes
 */

#ifndef SPAMFILTER_BAN_TIME
static char *our_strcasestr(char *haystack, char *needle)
{
	int i;
	int nlength = strlen (needle);
	int hlength = strlen (haystack);

	if (nlength > hlength) return NULL;
	if (hlength <= 0) return NULL;
	if (nlength <= 0) return haystack;

	for (i = 0; i <= (hlength - nlength); i++) {
		if (strncasecmp (haystack + i, needle, nlength) == 0)
			return haystack + i;
	}

	return NULL;
}
#endif

inline int my_fast_badword_match(Adword *adword, char *line)
{
 	char *p;
	int bwlen = strlen(adword->word);
	if ((adword->type & WT_FAST_L) && (adword->type & WT_FAST_R))
		return (our_strcasestr(line, adword->word) ? 1 : 0);

	p = line;
	while((p = our_strcasestr(p, adword->word)))
	{
		if (!(adword->type & WT_FAST_L))
		{
			if ((p != line) && isalnum(*(p - 1)))
				goto next;
		}
		if (!(adword->type & WT_FAST_R))
		{
			if (isalnum(*(p + bwlen)))
				goto next;
		}
		return 1;
next:
		p += bwlen;
	}
	return 0;
}

/*
 * This code comes from src/badwords.c (fast_badword_replace
 * which was written by Syzop), I modified it a bit.
 * (Also made speed optimizations.)
 */

inline int my_fast_badword_replace(Adword *adword, char *line, char *buf, int max)
{
	char	*searchw = adword->word, *replacew = adword->replace;
	char	*pold = line, *pnew = buf;
	char	*poldx = line;
	char	*startw, *endw;
	char	*c_eol = buf + max - 1;
	int	replacen, searchn;
	int	cleaned = 0;

	replacen = strlen(replacew);
	searchn = strlen(searchw);

	while (1)
	{
		pold = our_strcasestr(pold, searchw);
		if (!pold)
			break;
		cleaned = 1;
		startw = pold;
 		if (pold > line)
		{
			while (isalnum(*startw) && (startw != line))
				startw--;
			if (!isalnum(*startw))
				startw++;
		}

		if (!(adword->type & WT_FAST_L) && (pold != startw))
		{
			pold++;
			continue;
		}

		for (endw = pold; ((*endw != '\0') && (isalnum(*endw))); endw++);

		if (!(adword->type & WT_FAST_R) && (pold+searchn != endw))
		{
			pold++;
			continue;
		}

		if (poldx != startw)
		{
			int tmp_n = startw - poldx;
			if (pnew + tmp_n >= c_eol)
			{
				memcpy(pnew, poldx, c_eol - pnew);
				*c_eol = '\0';
				return 1;
			}

			memcpy(pnew, poldx, tmp_n);
			pnew += tmp_n;
		}

		if (replacen)
		{
			if ((pnew + replacen) >= c_eol) {
				memcpy(pnew, replacew, c_eol - pnew);
				*c_eol = '\0';
				return 1;
			}
			memcpy(pnew, replacew, replacen);
			pnew += replacen;
		}
		poldx = pold = endw;
	}

	if (*poldx)
	{
		strncpy(pnew, poldx, c_eol - pnew);
		*c_eol = '\0';
	}
	else
		*pnew = '\0';

	return cleaned;
}

/*
 * adword_checkmsg
 * ===============
 *
 * The badword checking stuff was copied from src/badwords.c,
 * I modified it a lot.
 */

#define SendNotice_channel \
		sendto_channel_butone(&me, &me, chptr, \
			":Uyari PRIVMSG %s :[\2Reklam\2] (%s Kanali Uyarisidir.! : \2%s\2 Adli Kullanici %s %s): \2%s\2 Kanal/Ozel 'de : \2%s\2 Yasaklanmis Kelimesini Kullandi.!", \
                	me.name, chptr->chname, from->name, \
			cmd, to ? ":" : "", to ? to : "", str)

#define SendNotice_private \
		sendto_serv_butone_token(NULL, me.name, MSG_GLOBOPS, TOK_GLOBOPS, \
			"[\2Reklam\2] (%s Kanali Uyarisidir.! : \2%s\2 Adli Kullanici %s %s ): \2%s\2 Kanal/Ozel 'de  : \2%s\2 Yasaklanmis Kelimesini Kullandi.!", \
                	from->name, cmd, \
			to ? ":" : "", to ? to : "", str); \
		sendto_failops_whoare_opers( \
			"[\2Reklam\2] (%s Kanali Uyarisidir.! : \2%s\2 Adli Kullanici %s %s): \2%s\2 Kanal/Ozel 'de : \2%s\2 Yasaklanmis Kelimesini Kullandi.!", \
                	from->name, cmd, \
			to ? ":" : "", to ? to : "", str)

static char *adword_checkmsg(aClient *cptr, aClient *from, char *to, char *str, u_int cmdtype, Adword *wordlist)
{
	static regmatch_t	pmatch[10];
	static char		cleanstr[4096], buf[4096];
	char			*ptr, *ptr2, *backref;
	int			matchlen = 0, stringlen, cleaned = 0;
	u_int			notify = 0, warn = 0, kill = 0;
	u_int			block = 0, ban = 0, ctcp, i;
	ChanList		*channels = NULL, *ch;
	Adword			*a;
	ListStruct		*next;

	if (!myconf.enable_adwords || !wordlist)
		return str;
	if (!MyConnect(from) || !IsPerson(from) || IsAnOper(from))
		return str;

	stringlen = strlcpy(cleanstr, StripControlCodes(str), sizeof cleanstr);

	/* Check for adwords */

	for (a = wordlist; a; a = a->next)
	{
		/* 1. Some conditions before matching */

		if (a->usertype == UT_REG && !IsRegNick(from))
			continue;
		if (a->usertype == UT_NONREG && IsRegNick(from))
			continue;
		if (cmdtype == CT_PRIVMSG || cmdtype == CT_NOTICE)
		{
			if (a->msgtype == MT_NOTICE && cmdtype != CT_NOTICE)
				continue;
			if (a->msgtype == MT_PRIVMSG && cmdtype == CT_NOTICE)
				continue;
			ctcp = (*str == '\001' && str[strlen(str)-1] == '\001');
			if (a->msgtype == MT_CTCP && !ctcp)
				continue;
			if (a->msgtype == MT_NONCTCP && ctcp)
				continue;
		}

		/* 2. Now try to match this word */

		if (a->type & WT_REGEX)
		{
			if (regexec(a->expr, cleanstr, 0, NULL, 0))
				continue;
		}
		else
		{
			if (!my_fast_badword_match(a, cleanstr))
				continue;
		}

		/* 3. Check certain things if matched */

		notify	|= a->notify;
		warn	|= a->warn;
		kill	|= a->kill;
		ban	|= a->ban;
		block	|= a->block;

		if (myconf.enable_notifications &&
		    (myconf.n_method == NM_CHANNEL) && notify && a->channel)
		{
			ch = (ChanList *) MyMallocEx(sizeof(ChanList));
			ch->channel = a->channel;
			AddListItem(ch, channels);
		}

		/* 4. Do the replaces if needed */

		if (a->replace && !block)
		{
			buf[0] = 0;

			if (a->type & WT_REGEX)
			{
				ptr = cleanstr;

				if (!find_backref(a->replace))
				{
					while (!regexec(a->expr, ptr, 1, pmatch, 0))
					{
						if (pmatch[0].rm_so == -1)
							break;
						cleaned = 1;
						matchlen += pmatch[0].rm_eo - pmatch[0].rm_so;
						strlncat(buf, ptr, sizeof buf, pmatch[0].rm_so);
						strlcat(buf, a->replace, sizeof buf); 
						ptr += pmatch[0].rm_eo;
					}
				}
				else
				{
					while (!regexec(a->expr, ptr, 10, pmatch, 0))
					{
						if (pmatch[0].rm_so == -1)
							break;

						cleaned = 1;
						matchlen += pmatch[0].rm_eo - pmatch[0].rm_so;
						strlncat(buf, ptr, sizeof buf, pmatch[0].rm_so);
						ptr2 = a->replace;

						for (backref = find_backref(ptr2); backref; backref = find_backref(ptr2))
						{
							i = backref[1] - '0';
							strlncat(buf, ptr2, sizeof buf, backref - ptr2);
							if (pmatch[i].rm_so != -1)
								strlncat(buf, ptr + pmatch[i].rm_so, sizeof buf,
									pmatch[i].rm_eo - pmatch[i].rm_so);
							ptr2 = backref + 2;
						}

						strlcat(buf, ptr2, sizeof buf);
						ptr += pmatch[0].rm_eo;
					}
				}

				strlcat(buf, ptr, sizeof buf);
				strcpy(cleanstr, buf);
				if (matchlen == stringlen)
					break;
			}
			else
			{
				cleaned |= my_fast_badword_replace(a, cleanstr, buf, 512);
				strcpy(cleanstr, buf);
			}
		}

		/* 5. Post-checkings */

		if (a->stoponmatch)
			break;
	}

	/* Do other actions */

	if (myconf.enable_notifications && notify)
	{
		char *cmd = find_CmdType(cmdtype);

		if (myconf.n_method == NM_CHANNEL)
		{
			char		*tmp, *name, *p = NULL;
			aChannel	*chptr;

			if (myconf.channels)
			{
				tmp = strdup(myconf.channels);

    				for (name = strtoken(&p, tmp, ","); name; name = strtoken(&p, NULL, ","))
            				if ((chptr = find_channel(name, NullChn)) != NullChn)
						SendNotice_channel;

				MyFree(tmp);
			}

			for (ch = channels; ch; ch = (ChanList *) next)
			{
				next = (ListStruct *) ch->next;

            			if ((chptr = find_channel(ch->channel, NullChn)) != NullChn)
					SendNotice_channel;

				DelListItem(ch, channels);
				MyFree(ch);
			}
		}
		else
		{
			SendNotice_private;
		}
	}

	if (myconf.enable_bans && ban)
	{
#ifndef NO_PLACE_HOST_BAN
		place_host_ban(from, myconf.ban_type,
				myconf.ban_reason ? myconf.ban_reason
					: DEF_BAN_REASON,
				myconf.ban_period);
#else
		char *bantype;

		if ((bantype = find_BanFlag(myconf.ban_type)))
			ban_client(from, bantype, myconf.ban_period,
				myconf.ban_reason ? myconf.ban_reason
					: DEF_BAN_REASON);
#endif
	}
	else if ((cmdtype != CT_QUIT) && myconf.enable_kills && kill)
	{
		/*
		 * Can't find a better solution for killing a user due to an adword;
		 * using the Event system seems to be working. The problem actually
		 * is we shouldn't kill a user more than one time (fe. when ppl use
		 * multiple targets in a PRIVMSG/NOTICE).
		 */
		strcpy(nickbuf, from->name);
		EventAdd("adwords_kill", 0, 1, &adwords_event_kill, nickbuf);
	}
	else if (myconf.enable_warnings && warn)
	{
		if (myconf.enable_blockings && block)
			sendto_one(from, ":%s NOTICE %s :%s",
				me.name, from->name, myconf.block_message ?
					myconf.block_message : DEF_BLOCK_MSG);
		else
			sendto_one(from, ":%s NOTICE %s :%s",
				me.name, from->name, myconf.warning_message ?
					myconf.warning_message : DEF_WARNING_MSG);
	}

	cleanstr[511] = '\0'; /* cutoff, just to be sure */

	return (myconf.enable_blockings && block) ?
		NULL : cleaned ? cleanstr : str;
}


/*
 * cb_privmsg
 * ==========
 *
 * from:	who is sending the message
 * to:		who will receive it
 * str:		message text
 * notice:	tells whether it's a notice or privmsg
 */

static char *cb_privmsg(aClient *cptr, aClient *from, aClient *to, char *str, int notice)
{
	if (!IsClient(from) || IsULine(from) || IsULine(to)
	    || (myconf.require_mode_g && !IsFilteringWords(to)))
		return str;

	return adword_checkmsg(cptr, from, to->name, str,
		notice ? CT_NOTICE : CT_PRIVMSG, AdwordsMessage);
}

/*
 * cb_chanmsg
 * ==========
 *
 * from:	who is sending the message
 * to:		the channel where it will be sent to
 * str:		message text
 * notice:	tells whether it's a notice or privmsg
 */

static char *cb_chanmsg(aClient *cptr, aClient *from, aChannel *to, char *str, int notice)
{
	ConfigItem_except	*e;

	if (!IsClient(from) || IsULine(from) || (myconf.require_mode_g
	    && !(to->mode.mode & UMODE_STRIPBADWORDS)))
		return str;

	for (e = conf_exceptchan; e; e = (ConfigItem_except *) e->next)
		if (!match(e->mask, to->chname))
			return str;

	return adword_checkmsg(cptr, from, to->chname, str,
		notice? CT_NOTICE : CT_PRIVMSG, AdwordsChannel);
}

/*
 * cb_quit
 * =======
 *
 * sptr:	who is sending the message
 * comment:	message text
 */

static char *cb_quit(aClient *sptr, char *comment)
{
	Membership *lp;

	if (IsULine(sptr))
		return comment;

	if (myconf.require_mode_g)
	{
		u_int strip = 0;

		for (lp = sptr->user->channel; lp; lp = lp->next)
    			if (lp->chptr->mode.mode & UMODE_STRIPBADWORDS)
			{
				strip = 1;
				break;
			}

		if (!strip)
			return comment;
	}

	return adword_checkmsg(sptr, sptr, NULL, comment,
		CT_QUIT, AdwordsQuit);
}

#ifndef HOOKTYPE_PRE_LOCAL_QUIT
/* ugly */
int override_quit(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	char *ocomment = (parc > 1 && parv[1]) ? parv[1] : parv[0];
	static char comment[TOPICLEN + 1];
	Membership *lp;

	if (!IsServer(cptr) && IsPerson(sptr))
	{
#ifdef STRIPBADWORDS
		int blocked = 0;
#endif
		char *s = comment;
		if (STATIC_QUIT)
		{
			return exit_client(cptr, sptr, sptr, STATIC_QUIT);
		}
		if (!prefix_quit || strcmp(prefix_quit, "no"))
			s = ircsprintf(comment, "%s ",
		    		BadPtr(prefix_quit) ? "Quit:" : prefix_quit);
#ifdef STRIPBADWORDS
		ocomment = (char *)stripbadwords_quit(ocomment, &blocked);
		if (blocked)
			ocomment = parv[0];
#endif
		ocomment = cb_quit(sptr, ocomment);
		if (!ocomment)
			ocomment = parv[0];
		if (!IsAnOper(sptr) && ANTI_SPAM_QUIT_MSG_TIME)
			if (sptr->firsttime+ANTI_SPAM_QUIT_MSG_TIME > TStime())
				ocomment = parv[0];

		/* Strip color codes if any channel is +S, use nick as reason if +c. */
		if (IsPerson(sptr) && (strchr(ocomment, '\003')))
		{
			unsigned char filtertype = 0; /* 1=filter, 2=block, highest wins. */
			for (lp = sptr->user->channel; lp; lp = lp->next)
			{
				if (lp->chptr->mode.mode & MODE_NOCOLOR)
				{
					filtertype = 2;
					break;
				}
				if (lp->chptr->mode.mode & MODE_STRIP)
				{
					if (!filtertype)
						filtertype = 1;
				}
			}
			if (filtertype == 1)
			{
				ocomment = StripColors(ocomment);
				if (*ocomment == '\0')
					ocomment = parv[0];
			} else
			if (filtertype == 2)
				ocomment = parv[0];
		} /* (strip color codes) */

		strncpy(s, ocomment, TOPICLEN - (s - comment));
		comment[TOPICLEN] = '\0';
		return exit_client(cptr, sptr, sptr, comment);
	}
	else
	{
		return exit_client(cptr, sptr, sptr, ocomment);
	}
}
#endif

#ifdef HOOKTYPE_PRE_LOCAL_PART
/*
 * cb_partmsg
 * ==========
 *
 * sptr:	who is sending the message
 * chptr:	the channel where it will be sent to
 * comment:	message text
 */

static char *cb_partmsg(aClient *sptr, aChannel *chptr, char *comment)
{
	ConfigItem_except       *e;

	if (!comment)
		return NULL;
	if (IsULine(sptr) || (myconf.require_mode_g && !(chptr->mode.mode & UMODE_STRIPBADWORDS)))
		return comment;

	for (e = conf_exceptchan; e; e = (ConfigItem_except *) e->next)
		if (!match(e->mask, chptr->chname))
			return comment;

	return adword_checkmsg(sptr, sptr, chptr->chname, comment, CT_PART, AdwordsPart);
}
#endif


/*
 * m_adwords
 * =========
 *
 * parv[0]:	sender prefix
 * parv[1]:	option
 * parv[2]:	value (optional)
 *
 */

static int m_adwords(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
        ConfigItem_except	*e = NULL;

	if (!MyConnect(sptr) || !IsPerson(sptr) || !IsAnOper(sptr))
	{
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
		return -1;
	}
	if (parc < 2 || BadPtr(parv[1]))
	{
		sendto_one(sptr, ":%s NOTICE %s :Usage:",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :    /adwords <option>",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :Options:",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :    message: shows you the adword message block list",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :    channel: shows you the adword channel block list",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :    quit: shows you the adword quit block list",
			me.name, sptr->name);
#ifdef HOOKTYPE_PRE_LOCAL_PART
		sendto_one(sptr, ":%s NOTICE %s :    part: shows you the adword part block list",
			me.name, sptr->name);
#endif
		sendto_one(sptr, ":%s NOTICE %s :    all: shows you all the adword block list",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :    exceptions: shows you the except channel mask list",
			me.name, sptr->name);
		sendto_one(sptr, ":%s NOTICE %s :    config: displays adwords configuration",
			me.name, sptr->name);
		return 0;
	}
	if (!strcasecmp(parv[1], "message"))
	{
		stats_wordlist(sptr, AdwordsMessage, 'M');
		sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, parv[0], 'a');
	}
	else if (!strcasecmp(parv[1], "channel"))
	{
		stats_wordlist(sptr, AdwordsChannel, 'C');
		sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, parv[0], 'a');
	}
	else if (!strcasecmp(parv[1], "quit"))
	{
		stats_wordlist(sptr, AdwordsQuit, 'Q');
		sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, parv[0], 'a');
	}
#ifdef HOOKTYPE_PRE_LOCAL_PART
	else if (!strcasecmp(parv[1], "part"))
	{
		stats_wordlist(sptr, AdwordsPart, 'P');
		sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, parv[0], 'a');
	}
#endif
	else if (!strcasecmp(parv[1], "all"))
	{
		stats_wordlist(sptr, AdwordsMessage, 'M');
		stats_wordlist(sptr, AdwordsChannel, 'C');
		stats_wordlist(sptr, AdwordsQuit, 'Q');
#ifdef HOOKTYPE_PRE_LOCAL_PART
		stats_wordlist(sptr, AdwordsPart, 'P');
#endif
		sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, parv[0], 'a');
	}
        else if (!strcasecmp(parv[1], "exceptions"))
        {
                for (e = conf_exceptchan; e; e = (ConfigItem_except *) e->next)
                        sendto_one(sptr, ":%s %i %s :m %s",
                                me.name, RPL_TEXT, sptr->name, e->mask);
                sendto_one(sptr, rpl_str(RPL_ENDOFSTATS), me.name, parv[0], 'm');
        }
	else if (!strcasecmp(parv[1], "config"))
		cb_stats(sptr);
	else
	{
		sendto_one(sptr, ":%s NOTICE %s :Unknown option %s."
			" Valid options are: message | channel | all | exceptions | config",
			me.name, sptr->name, parv[1]);
		return -1;
	}

	return 0;
}

static void stats_wordlist(aClient *sptr, Adword *wordlist, char wtype)
{
	Adword *a;

	for (a = wordlist; a; a = (Adword *) a->next)
		sendto_one(sptr, ":%s %i %s :a %c %c %c %c %s %s%s%s" "%s%s" "%s%s",
			me.name, RPL_TEXT, sptr->name,
			wtype,
			find_WordFlag(a->type),
			find_MsgType(a->msgtype),
			find_UserType(a->usertype),
			make_optlist(a),
			a->type & WT_FAST_L ? "*" : "",
			a->word,
			a->type & WT_FAST_R ? "*" : "",
			a->replace ? " " : "",
			a->replace ? a->replace : "",
			a->channel ? " " : "",
			a->channel ? a->channel : "");
}

static void adwords_event_kill(char *name)
{
	aClient *cptr;

	if ((cptr = find_person(name, NULL)))
		exit_client(cptr, cptr, &me,
			myconf.kill_reason ? myconf.kill_reason : DEF_KILL_REASON);
}
