/*
 * =================================================================
 * Filename:             cmdflood.c
 * Description:          Flood protection for commands.
 * Written by:           AngryWolf <angrywolf@flashmail.com>
 * Requested by:         aaadicted
 * Documentation:        cmdflood.txt (comes with the 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

typedef struct _floodsetting FloodSetting;
typedef struct _floodinfo FloodInfo;

struct _floodsetting
{
	FloodSetting	*prev, *next;
	char		*cmd;
	Cmdoverride	*ovr;
	int		count;
	int		period;
};

struct _floodinfo
{
	FloodInfo	*prev, *next;
	aClient		*cptr;
	char		*cmd;
	int		count;
	TS		last;
};

extern void			sendto_one(aClient *to, char *pattern, ...);
extern void			sendto_realops(char *pattern, ...);
extern int			config_parse_flood(char *orig, int *times, int *period);

#define MSG_CMDFLOOD		"CMDFLOOD"
#define TOK_CMDFLOOD		"CF"
#define ERR_TOOMANYUSES		":%s 495 %s :Too many uses of command %s. Wait %d seconds before trying again."
#define DelOverride(cmd, ovr)	if (ovr && CommandExists(cmd)) CmdoverrideDel(ovr); ovr = NULL
#define DelHook(x)      	if (x) HookDel(x); x = NULL
#define DelCommand(x)		if (x) CommandDel(x); x = NULL
#define ircstrdup(x,y)		if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y)
#define ircfree(x)		if (x) MyFree(x); x = NULL

static Command			*AddCommand(Module *module, char *msg, char *token, iFP func);
static Cmdoverride		*AddOverride(char *msg, iFP cb);
static int			m_cmdflood(aClient *cptr, aClient *sptr, int parc, char *parv[]);
static int			override_cmd(Cmdoverride *, aClient *, aClient *, int, char *[]);
static int			cb_config_test(ConfigFile *, ConfigEntry *, int, int *);
static int			cb_config_run(ConfigFile *, ConfigEntry *, int);
static int			cb_config_rehash();
static int			cb_quit(aClient *, char *);
static FloodSetting		*find_FloodSetting(char *cmd);
static FloodInfo		*find_FloodInfo(aClient *cptr, char *cmd);
static void			FreeFloodSettings();
static void			FreeFloodInfoList();
static void			InitConf();
static void			FreeConf();
#ifdef HOOKTYPE_REHASH_COMPLETE
static int			cb_rehash_complete();
#else
static				EVENT(LoadOverrides);
#endif

static Command			*CmdCmdflood;
static Hook			*HookConfTest, *HookConfRun, *HookConfRehash;
static Hook			*HookQuit;
#ifdef STATIC_LINKING
 #ifdef HOOKTYPE_REHASH_COMPLETE
static Hook			*HookRehashDone;
 #else
static u_int			event_added;
 #endif
#endif
static FloodSetting		*FloodSettings;
static FloodInfo		*FloodInfoList;

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

ModuleHeader MOD_HEADER(cmdflood)
  = {
	"cmdflood",
	"$Id: cmdflood.c,v 1.8 2004/05/17 19:58:06 angrywolf Exp $",
	"Flood protection for commands",
	"3.2-b8-1",
	NULL 
    };

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

	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(cmdflood)(ModuleInfo *modinfo)
{
	SAVE_MODINFO
	FloodInfoList = NULL;

#ifndef STATIC_LINKING
	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM);
#endif
	InitConf();

	HookConfRun	= HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, cb_config_run);
	HookConfRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH, cb_config_rehash);
#if defined(HOOKTYPE_REHASH_COMPLETE) && defined(STATIC_LINKING)
	HookRehashDone	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, cb_rehash_complete);
#endif
        HookQuit	= HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_QUIT, cb_quit);
	CmdCmdflood	= AddCommand(modinfo->handle, MSG_CMDFLOOD, TOK_CMDFLOOD, m_cmdflood);

	if (!CmdCmdflood)
		return MOD_FAILED;

        return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(cmdflood)(int module_load)
{
#ifdef HOOKTYPE_REHASH_COMPLETE
	cb_rehash_complete();
#else
	LoadOverrides(NULL);
#endif

	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(cmdflood)(int module_unload)
{
	FreeConf();
	FreeFloodInfoList();

#if defined(HOOKTYPE_REHASH_COMPLETE) && defined(STATIC_LINKING)
	DelHook(HookRehashDone);
#endif
	DelHook(HookQuit);
	DelHook(HookConfRehash);
	DelHook(HookConfRun);
	DelHook(HookConfTest);
	DelCommand(CmdCmdflood);

	return MOD_SUCCESS;
}

static void InitConf()
{
	FloodSettings	= NULL;
#if defined(STATIC_LINKING) && !defined(HOOKTYPE_REHASH_COMPLETE)
	event_added	= 0;
#endif
}

static void FreeConf()
{
	FreeFloodSettings();
}

#ifdef HOOKTYPE_REHASH_COMPLETE
static int cb_rehash_complete()
#else
static EVENT(LoadOverrides)
#endif
{
	FloodSetting *f;

	for (f = FloodSettings; f; f = f->next)
		f->ovr = AddOverride(f->cmd, override_cmd);

#ifdef HOOKTYPE_REHASH_COMPLETE
	return 0;
#endif
}

static FloodSetting *find_FloodSetting(char *cmd)
{
	FloodSetting *f;

	for (f = FloodSettings; f; f = f->next)
		if (!stricmp(f->cmd, cmd))
			break;

	return f;
}

static FloodInfo *find_FloodInfo(aClient *cptr, char *cmd)
{
	FloodInfo *f;

	for (f = FloodInfoList; f; f = f->next)
		if (f->cptr == cptr && !stricmp(f->cmd, cmd))
			break;

	return f;
}

static void FreeFloodSettings()
{
	FloodSetting	*f;
	ListStruct	*next;

	for (f = FloodSettings; f; f = (FloodSetting *) next)
	{
		next = (ListStruct *) f->next;
		DelListItem(f, FloodSettings);
		DelOverride(f->cmd, f->ovr);
		MyFree(f->cmd);
		MyFree(f);
	}
}

static void FreeFloodInfoList()
{
	FloodInfo	*f;
	ListStruct	*next;

	for (f = FloodInfoList; f; f = (FloodInfo *) next)
	{
		next = (ListStruct *) f->next;
		DelListItem(f, FloodInfoList);
		MyFree(f->cmd);
		MyFree(f);
	}
}

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

	return 1;
}

#define CHECK_EMPTY(ce, parent) \
		if (!(ce)->ce_varname) \
		{ \
			config_error("%s:%i: blank %s item", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, (parent)->ce_varname); \
			errors++; \
			continue; \
		} \
		if (!(ce)->ce_vardata) \
		{ \
			config_error("%s:%i: %s::%s without contents", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, \
				(parent)->ce_varname, (ce)->ce_varname); \
			errors++; \
			continue; \
		}

static int cb_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
	ConfigEntry	*cep;
	int		errors = 0;
	int		count, period;

	if (type != CONFIG_MAIN)
		return 0;

	if (!strcmp(ce->ce_varname, "cmdflood"))
	{
		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
		{
			CHECK_EMPTY(cep, ce)

			if (!config_parse_flood(cep->ce_vardata, &count, &period) ||
			    (count < 1) || (count > 255) || (period < 5))
			{
				config_error("%s:%i: Bad value for cmdflood::%s. Syntax is '<count>:<period>' (ie. 5:60), "
					"count should be in range 1-255, period greater than 4",
					cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
					cep->ce_varname);
				errors++;
			}
		}

		*errs = errors;
		return errors ? -1 : 1;
	}

	return 0;
}

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

	if (type != CONFIG_MAIN)
		return 0;

	if (!strcmp(ce->ce_varname, "cmdflood"))
	{
		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
		{
			if (find_FloodSetting(cep->ce_varname))
				continue;

			f = (FloodSetting *) MyMallocEx(sizeof(FloodSetting));
			f->cmd = strdup(cep->ce_varname);
			config_parse_flood(cep->ce_vardata, &f->count, &f->period);
			AddListItem(f, FloodSettings);

#if defined(STATIC_LINKING) && !defined(HOOKTYPE_REHASH_COMPLETE)
			if (!event_added)
			{
				/* Not the best solution, but it works. */
				EventAdd("cmdflood_loadoverrides", 0, 1,
					LoadOverrides, NULL);
				event_added = 1;
			}
#endif
		}

		return 1;		
	}

	return 0;
}

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(cmdflood).name, ModuleGetErrorStr(MyMod));
#else
		config_error("Error replacing command %s when loading module %s",
			msg, MOD_HEADER(cmdflood).name);
#endif
		return NULL;
	}

	return ovr;
}

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;
}

static int cb_quit(aClient *sptr, char *comment)
{
	FloodInfo	*f;
	ListStruct	*next;

	for (f = FloodInfoList; f; f = (FloodInfo *) next)
	{
		next = (ListStruct *) f->next;
		if (f->cptr == sptr)
		{
			DelListItem(f, FloodInfoList);
			MyFree(f->cmd);
			MyFree(f);
		}
	}

	return 0;
}

static int override_cmd(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	FloodInfo	*fi;
	FloodSetting	*fs;
	TS		now		= TStime();
	char		*cmd		= ovr->command->cmd;

	if (IsPerson(sptr) && !IsAnOper(sptr))
	{
		if (!(fi = find_FloodInfo(sptr, cmd)))
		{
			fi		= (FloodInfo *) MyMallocEx(sizeof(FloodInfo));
			fi->cptr	= sptr;
			fi->cmd		= strdup(cmd);
			fi->last	= now;
			fi->count	= 1;

			AddListItem(fi, FloodInfoList);
		}
		else
		{
			fs = find_FloodSetting(cmd);

			if (!fs)
			{
				sendto_realops("*** [BUG] Couldn't find the flood configuration of command %s."
				    " Please report this bug at http://angrywolf.clanintern-irc.de/.",
				    cmd);
				return CallCmdoverride(ovr, cptr, sptr, parc, parv);
			}

			if (now - fi->last >= fs->period)
			{
				fi->last = now;
				fi->count = 1;
			}
			else
			{
				fi->count++;

				if (fi->count > fs->count && now - fi->last < fs->period)
				{
					sendto_one(sptr, ERR_TOOMANYUSES,
						me.name, sptr->name, cmd,
						fi->last + fs->period - now);
					return 0;
				}
			}
		}
	}

	return CallCmdoverride(ovr, cptr, sptr, parc, parv);
}

static int m_cmdflood(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	FloodSetting *f;

	if (!IsPerson(sptr))
		return -1;

	if (!MyConnect(sptr) || !IsAnOper(sptr))
	{
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);
		return -1;
	}

	for (f = FloodSettings; f; f = f->next)
		sendto_one(sptr, ":%s %i %s :x %s: %d per %s",
			me.name, RPL_TEXT, sptr->name,
			f->cmd, f->count,
			pretty_time_val(f->period));

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