/*
 * =================================================================
 * Filename:          jointhrottle.c
 * Description:       Channel mode +j: join flood protection.
 * Author:            AngryWolf <angrywolf@flashmail.com>
 * Documentation:     jointhrottle.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

#ifndef EXTCMODE
 #error "This module requires extended channel modes to be enabled."
 #error "See the EXTCMODE macro in include/config.h for more information."
 #error "Compilation aborted."
#endif

extern void			sendto_one(aClient *to, char *pattern, ...);

#define FLAG_JOINTHROTTLE	'j'
#define ERR_TOOMUCHJOINS	496
#define HasJoinLimit(x)		((x)->mode.extmode & MODE_JOINTHROTTLE)
#define ClearMem(x)		memset(&(x), 0, sizeof (x))
#define DelCmode(x)		if (x) CmodeDel(x); x = NULL
#define DelHook(x)		if (x) HookDel(x); x = NULL

/* Backward compatibility */
#ifndef EX_DENY
 #define EX_DENY		0
 #define EX_ALLOW		1
 #define EX_ALWAYS_DENY		0
#endif
#ifndef HOOK_CONTINUE
 #define HOOK_CONTINUE		0
 #define HOOK_ALLOW		-1
 #define HOOK_DENY		1
#endif

typedef struct
{
	EXTCM_PAR_HEADER

	int	joins;
	long	seconds;
} aModeJTEntry;

typedef struct _joinflood JoinFlood;

struct _joinflood
{
	JoinFlood	*prev, *next;
	aChannel	*chptr;
	aClient		*sptr;
	int		joins;
	TS		last;
};

static Cmode			*AddCmode(Module *module, CmodeInfo *req, Cmode_t *mode);
static int			ModeJT_is_ok(aClient *, aChannel *, char *, int, int);
static CmodeParam		*ModeJT_put_param(CmodeParam *, char *);
static char			*ModeJT_get_param(CmodeParam *);
static char			*ModeJT_conv_param(char *);
static void			ModeJT_free_param(CmodeParam *);
static CmodeParam		*ModeJT_dup_struct(CmodeParam *);
static int			ModeJT_sjoin_check(aChannel *, CmodeParam *, CmodeParam *);

static int			cb_channel_destroy(aChannel *);
static int			cb_join(aClient *, aChannel *, char *[]);
static int			cb_quit(aClient *, char *);

static void			InitConf();
static void			FreeConf();

Cmode_t				MODE_JOINTHROTTLE = 0L;
Cmode				*ModeJoinThrottle = NULL;
Hook				*HookJoin, *HookChannelDestroy;
Hook				*HookQuit, *HookRemoteQuit;
JoinFlood			*JFlist;

ModuleHeader MOD_HEADER(jointhrottle)
  = {
	"jointhrottle",
	"$Id: jointhrottle.c,v 2.4 2004/04/17 18:29:32 angrywolf Exp $",
	"channel mode +j (join throttle)",
	"3.2-b8-1",
	NULL 
    };

DLLFUNC int MOD_TEST(jointhrottle)(ModuleInfo *modinfo)
{
	CmodeInfo ModeJT;

	ClearMem(ModeJT);
	ModeJT.flag		= FLAG_JOINTHROTTLE;
	ModeJT.paracount	= 1;
	ModeJT.is_ok		= ModeJT_is_ok;
	ModeJT.put_param	= ModeJT_put_param;
	ModeJT.get_param	= ModeJT_get_param;
	ModeJT.conv_param	= ModeJT_conv_param;
	ModeJT.free_param	= ModeJT_free_param;
	ModeJT.sjoin_check	= ModeJT_sjoin_check;
	ModeJT.dup_struct	= ModeJT_dup_struct;
	ModeJoinThrottle	= AddCmode(modinfo->handle, &ModeJT, &MODE_JOINTHROTTLE);

	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(jointhrottle)(ModuleInfo *modinfo)
{
#ifndef STATIC_LINKING
	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM);
#endif
	InitConf();

	HookJoin		= HookAddEx(modinfo->handle, HOOKTYPE_PRE_LOCAL_JOIN, cb_join);
	HookChannelDestroy	= HookAddEx(modinfo->handle, HOOKTYPE_CHANNEL_DESTROY, cb_channel_destroy);
        HookQuit		= HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_QUIT, cb_quit);
        HookRemoteQuit		= HookAddEx(modinfo->handle, HOOKTYPE_REMOTE_QUIT, cb_quit);

	return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(jointhrottle)(int module_load)
{
	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(jointhrottle)(int module_unload)
{
	FreeConf();
	DelCmode(ModeJoinThrottle);
	DelHook(HookJoin);
	DelHook(HookChannelDestroy);
	DelHook(HookQuit);
	DelHook(HookRemoteQuit);

	return MOD_FAILED;
}

static void InitConf()
{
	JFlist = NULL;
}

static void FreeConf()
{
	JoinFlood	*j;
	ListStruct	*next;

	for (j = JFlist; j; j = (JoinFlood *) next)
	{
		next = (ListStruct *) j->next;
		DelListItem(j, JFlist);
		MyFree(j);
	}
}

static Cmode *AddCmode(Module *module, CmodeInfo *req, Cmode_t *mode)
{
	Cmode *cmode;

	*mode = 0;
	cmode = CmodeAdd(module, *req, mode);

#ifndef STATIC_LINKING
        if (ModuleGetError(module) != MODERR_NOERROR || !cmode)
#else
        if (!cmode)
#endif
	{
#ifndef STATIC_LINKING
		config_error("Error adding channel mode +%c when loading module %s: %s",
			req->flag, MOD_HEADER(jointhrottle).name, ModuleGetErrorStr(module));
#else
		config_error("Error adding channel mode +%c when loading module %s",
			req->flag, MOD_HEADER(jointhrottle).name);
#endif
		return NULL;
	}

	return cmode;
}

static int cb_channel_destroy(aChannel *chptr)
{
	JoinFlood	*j;
	ListStruct	*next;

	for (j = JFlist; j; j = (JoinFlood *) next)
	{
		next = (ListStruct *) j->next;
		if (j->chptr == chptr)
		{
			DelListItem(j, JFlist);
			MyFree(j);
		}
	}

	return 0;
}

static int cb_quit(aClient *sptr, char *comment)
{
	JoinFlood	*j;
	ListStruct	*next;

	for (j = JFlist; j; j = (JoinFlood *) next)
	{
		next = (ListStruct *) j->next;
		if (j->sptr == sptr)
		{
			DelListItem(j, JFlist);
			MyFree(j);
		}
	}

	return 0;
}

static int cb_join(aClient *sptr, aChannel *chptr, char *parv[])
{
	JoinFlood	*j;
	aModeJTEntry	*p;
	TS		now = TStime();

	if (!HasJoinLimit(chptr))
		return HOOK_CONTINUE;

	for (j = JFlist; j; j = j->next)
		if ((j->chptr == chptr && j->sptr == sptr))
			break;


	if (!j)
	{
		j = (JoinFlood *) MyMallocEx(sizeof(JoinFlood));
		j->sptr = sptr;
		j->chptr = chptr;
		j->last = now;
		j->joins = 1;
		AddListItem(j, JFlist);
	}
	else
	{
		p = (aModeJTEntry *) extcmode_get_struct(chptr->mode.extmodeparam,
			FLAG_JOINTHROTTLE);

		if (now - j->last >= p->seconds)
		{
			j->last = now;
			j->joins = 1;
		}
		else
		{
			j->joins++;

			if (j->joins > p->joins && now - j->last < p->seconds)
			{
#ifndef NO_OPEROVERRIDE
				if (IsOper(sptr) && OPCanOverride(sptr))
				{
    					sendto_snomask(SNO_EYES,
            					"*** OperOverride -- %s (%s@%s) joined %s (overriding +%c).",
            					sptr->name, sptr->user->username, sptr->user->realhost,
						chptr->chname, FLAG_JOINTHROTTLE);
					return HOOK_CONTINUE;
				}
#endif
				sendto_one(sptr, ":%s %d %s %s :Too many join requests. " 
					"Wait %ld seconds before trying again.",
					me.name, ERR_TOOMUCHJOINS, parv[0], chptr->chname,
					j->last + p->seconds - now);
				return HOOK_DENY;
			}
		}
	}	

	return HOOK_CONTINUE;
}

static int ModeJT_is_ok(aClient *sptr, aChannel *chptr, char *param, int type, int what)
{
	char		*p;
	int		num;
	long		sec;
	u_int		ok;

	if (type == EXCHK_ACCESS || type == EXCHK_ACCESS_ERR)
	{
		if (!IsPerson(sptr) || IsULine(sptr))
			return EX_ALLOW;

		if (!is_chan_op(sptr, chptr))
		{
			if (type == EXCHK_ACCESS_ERR)
				sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED),
					me.name, sptr->name, chptr->chname);
			return EX_DENY;
		}

		return EX_ALLOW;
	}
	else if (type == EXCHK_PARAM)
	{
		ok	= 1;
		p	= strchr(param, ':');

		if (!p)
		{
			ok = 0;

			sendto_one(sptr, ":%s NOTICE %s :Channel mode +%c requires a parameter in form <joins>:<seconds>",
				me.name, sptr->name, FLAG_JOINTHROTTLE);
		}
		else
		{
			*p	= '\0';
			num	= atoi(param);
			sec	= config_checkval(p+1, CFG_TIME);

			if (num < 1 || sec < 1 || num > 10000 || sec > 60*60*24)
			{
				ok = 0;

				sendto_one(sptr, ":%s NOTICE %s :Parameter for channel mode +%c is out of range",
					me.name, sptr->name, FLAG_JOINTHROTTLE);
			}

			*p	= ':';
		}

		return ok;
	}

	return 0;
}

static CmodeParam *ModeJT_put_param(CmodeParam *param, char *text)
{
	aModeJTEntry	*m;
	char		*p;

	m = (aModeJTEntry *) param;

	if (!m)
	{
		m = (aModeJTEntry *) MyMallocEx(sizeof(aModeJTEntry));
		m->flag = FLAG_JOINTHROTTLE;
	}

	p		= strchr(text, ':');
	*p		= '\0';
	m->joins	= atoi(text);
	m->seconds	= config_checkval(p+1, CFG_TIME);
	*p		= ':';

	return (CmodeParam *) m;
}

static char *ModeJT_get_param(CmodeParam *param)
{
	aModeJTEntry	*m;
	static char	text[50];

	m = (aModeJTEntry *) param;
	
	if (!m)
		return NULL;

	ClearMem(text);
	sprintf(text, "%d:%ld", m->joins, m->seconds);

	return text;
}

static char *ModeJT_conv_param(char *in)
{
	int		joins;
	long		seconds;
	static char	text[50];
	char		*p;

	p	= strchr(in, ':');
	*p	= '\0';
	joins	= atoi(in);
	seconds	= config_checkval(p+1, CFG_TIME);
	*p	= ':';

	ClearMem(text);
	sprintf(text, "%d:%ld", joins, seconds);

	return text;
}

static void ModeJT_free_param(CmodeParam *param)
{
	aModeJTEntry *m = (aModeJTEntry *) param;

	MyFree(m);
}

static CmodeParam *ModeJT_dup_struct(CmodeParam *in)
{
	aModeJTEntry	*m;

	m = (aModeJTEntry *) MyMalloc(sizeof(aModeJTEntry));
	memcpy(m, in, sizeof(aModeJTEntry));
	m->prev = m->next = NULL;

	return (CmodeParam *) m;
}

static int ModeJT_sjoin_check(aChannel *chptr, CmodeParam *ours, CmodeParam *theirs)
{
	aModeJTEntry	*o, *t;

	o = (aModeJTEntry *) ours;
	t = (aModeJTEntry *) theirs;

	// sendto_realops("ModeJT_sjoin_check(): (OURS) o->joins=%d, o->seconds=%ld", o->joins, o->seconds);
	// sendto_realops("ModeJT_sjoin_check(): (THEIRS) t->joins=%d, t->seconds=%ld", t->joins, t->seconds);

	if (o->joins == t->joins && o->seconds == t->seconds)
		return EXSJ_SAME;
	if (o->seconds < t->seconds)
		return EXSJ_THEYWON;
	else if (o->joins < t->joins)
		return EXSJ_THEYWON;
	else
		return EXSJ_WEWON;
}
