/*
 * =================================================================
 * Filename:            operpasswd.c
 * Description:         Failed OPER attempts notifier
 * Documentation:	operpasswd.txt (comes with the package)
 * Author:              AngryWolf <angrywolf@flashmail.com>
 * =================================================================
 */

#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 _fcount FCount;

struct _fcount
{
	FCount			*prev, *next;
	aClient			*cptr;
	u_int			count;
};

#ifndef MODVAR
extern int			SVSNOOP;
#endif
extern void			sendto_realops(char *pattern, ...);
extern void			sendto_serv_butone_token(aClient *one, char *prefix, char *command, char *token, char *pattern, ...);

#define FlagOperPass		'O'
#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
#define DelHook(x)		if (x) HookDel(x); x = NULL
#define DelOverride(cmd, ovr)	if (ovr && CommandExists(cmd)) CmdoverrideDel(ovr); ovr = NULL
#define DelSnomask(x)		if (x) SnomaskDel(x); x = NULL
#define IsSkoAdmin(sptr)	(IsAdmin(sptr) || IsNetAdmin(sptr) || IsSAdmin(sptr) || IsCoAdmin(sptr))

#define UMODE_DENY		0
#define UMODE_ALLOW		1

#define ENABLE_SNOMASK		0x0001
#define ENABLE_GLOBAL_NOTICES	0x0002
#define ENABLE_LOGGING		0x0004

static Cmdoverride		*AddOverride(char *msg, iFP cb);
static Snomask			*AddSnomask(char flag, iFP allowed, long *mode);
static int			ovr_oper(Cmdoverride *, aClient *, aClient *, int, char *[]);
static int			cb_test(ConfigFile *, ConfigEntry *, int, int *);
static int			cb_conf(ConfigFile *, ConfigEntry *, int);
static int			cb_quit(aClient *, char *);
static int			cb_rehash();
static void			del_failop_counts();
static int			umode_allow_admins(aClient *sptr, int what);
#ifdef HOOKTYPE_REHASH_COMPLETE
static int			cb_rehash_complete();
#endif

static Cmdoverride		*OvrOper = NULL;
static Hook			*HookConfTest, *HookConfRun, *HookConfRehash;
static Hook			*HookQuit;
static Snomask			*SnomaskOperPass = NULL;
static long			SNO_OPERPASS = 0;
static FCount			*FailedOperups = NULL;
static char			*textbuf = NULL;

#ifdef HOOKTYPE_REHASH_COMPLETE
Hook				*HookRehashDone;
static u_char			module_loaded = 0;
#else
u_int				event_added;
#endif

struct {
	unsigned char	options;
	u_int		max_failed_operups;
	char		*failop_kill_reason;
} Settings;

#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(operpasswd)
  = {
	"operpasswd",
	"$Id: operpasswd.c,v 3.1 2004/08/06 13:52:47 angrywolf Exp $",
	"Failed OPER attempts notifier",
	"3.2-b8-1",
	NULL
    };

static void InitConf()
{
#ifndef HOOKTYPE_REHASH_COMPLETE
	event_added = 0;
#endif
	memset(&Settings, 0, sizeof Settings);
}

static void FreeConf()
{
	ircfree(Settings.failop_kill_reason);
}

static int cb_rehash()
{
	module_loaded = 0;
#ifndef STATIC_LINKING
	DelOverride("oper", OvrOper);
#endif
	FreeConf();
	InitConf();

	return 1;
}

#ifdef HOOKTYPE_REHASH_COMPLETE
static int cb_rehash_complete()
#else
static EVENT(LoadObjects)
#endif
{
	if (!module_loaded)
	{
#ifndef STATIC_LINKING
		OvrOper = AddOverride("oper", ovr_oper);
#endif

		if (!(Settings.options & ENABLE_SNOMASK))
			Settings.options &= ~ENABLE_GLOBAL_NOTICES;

		if (Settings.options)
		{
			if (!textbuf)
				textbuf = (char *) MyMalloc(BUFSIZE);
		}
		else
		{
			if (textbuf)
			{
				MyFree(textbuf);
				textbuf = NULL;
			}
		}

		if (Settings.options & ENABLE_SNOMASK)
		{
			if (!SnomaskOperPass)
				SnomaskOperPass = AddSnomask(FlagOperPass,
					umode_allow_admins, &SNO_OPERPASS);
		}
		else
		{
			DelSnomask(SnomaskOperPass);
		}

		if (!Settings.max_failed_operups)
			del_failop_counts();

		module_loaded = 1;
	}

#ifdef HOOKTYPE_REHASH_COMPLETE
	return 0;
#endif
}

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

	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(operpasswd)(ModuleInfo *modinfo)
{
	SAVE_MODINFO
#ifndef STATIC_LINKING
	ModuleSetOptions(modinfo->handle, MOD_OPT_PERM);
#endif
	SnomaskOperPass	= NULL;
	FailedOperups	= NULL;
	textbuf		= NULL;
	InitConf();

	HookConfRun	= HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, cb_conf);
	HookConfRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH, cb_rehash);
        HookQuit	= HookAddEx(modinfo->handle, HOOKTYPE_LOCAL_QUIT, cb_quit);
#ifdef HOOKTYPE_REHASH_COMPLETE
	HookRehashDone	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH_COMPLETE, cb_rehash_complete);
#endif

        return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(operpasswd)(int module_load)
{
	cb_rehash_complete();
	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(operpasswd)(int module_unload)
{
	FreeConf();
	del_failop_counts();

	DelOverride("oper", OvrOper);
	DelSnomask(SnomaskOperPass);
#ifdef HOOKTYPE_REHASH_COMPLETE
	DelHook(HookRehashDone);
#endif
	DelHook(HookQuit);
	DelHook(HookConfRehash);
	DelHook(HookConfRun);
	DelHook(HookConfTest);

	return MOD_SUCCESS;
}

static void del_failop_counts()
{
	FCount		*f;
	ListStruct	*next;

	for (f = FailedOperups; f; f = (FCount *) next)
	{
		next = (ListStruct *) f->next;
		DelListItem(f, FailedOperups);
		MyFree(f);
	}
}

static FCount *find_failop_count(aClient *cptr)
{
	FCount *f;

	for (f = FailedOperups; f; f = f->next)
		if (f->cptr == cptr)
			break;

	return f;
}

static int umode_allow_admins(aClient *sptr, int what)
{
	/* don't check access remotely */
	return (!MyClient(sptr) || IsSkoAdmin(sptr))
		? UMODE_ALLOW : UMODE_DENY;
}

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

	return ovr;
}

static Snomask *AddSnomask(char flag, iFP allowed, long *mode)
{
	Snomask *s;

	*mode = 0;
	s = SnomaskAdd(MyMod, flag, allowed, mode);

#ifndef STATIC_LINKING
	if ((ModuleGetError(MyMod) != MODERR_NOERROR) || !s)
#else
	if (!s)
#endif
	{
#ifndef STATIC_LINKING
		sendto_realops("Error adding snomask %c: %s",
			flag, ModuleGetErrorStr(MyMod));
#else
		sendto_realops("Error adding snomask %c",
			flag);
#endif
                return NULL;
	}

	return s;
}

#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 value", \
				(ce)->ce_fileptr->cf_filename, \
				(ce)->ce_varlinenum, \
				(parent)->ce_varname, (ce)->ce_varname); \
			errors++; \
			continue; \
		}

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

	if (type != CONFIG_MAIN)
		return 0;

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

			if (!strcmp(cep->ce_varname, "enable-snomask"))
				;
			else if (!strcmp(cep->ce_varname, "enable-global-notices"))
				;
			else if (!strcmp(cep->ce_varname, "enable-logging"))
				;
			else if (!strcmp(cep->ce_varname, "max-failed-operups"))
				;
			else if (!strcmp(cep->ce_varname, "failop-kill-reason"))
				;
			else
			{
				config_error("%s:%i: unknown directive operpasswd::%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_conf(ConfigFile *cf, ConfigEntry *ce, int type)
{
	ConfigEntry	*cep;

	if (type != CONFIG_MAIN)
		return 0;

	if (!strcmp(ce->ce_varname, "operpasswd"))
	{
		for (cep = ce->ce_entries; cep; cep = cep->ce_next)
		{
			if (!strcmp(cep->ce_varname, "enable-snomask"))
			{
				if (config_checkval(cep->ce_vardata, CFG_YESNO))
					Settings.options |= ENABLE_SNOMASK;
			}
			else if (!strcmp(cep->ce_varname, "enable-global-notices"))
			{
				if (config_checkval(cep->ce_vardata, CFG_YESNO))
					Settings.options |= ENABLE_GLOBAL_NOTICES;
			}
			else if (!strcmp(cep->ce_varname, "enable-logging"))
			{
				if (config_checkval(cep->ce_vardata, CFG_YESNO))
					Settings.options |= ENABLE_LOGGING;
			}
			else if (!strcmp(cep->ce_varname, "max-failed-operups"))
				Settings.max_failed_operups = atoi(cep->ce_vardata);
			else if (!strcmp(cep->ce_varname, "failop-kill-reason"))
			{
				ircstrdup(Settings.failop_kill_reason, cep->ce_vardata);
			}
		}

#ifndef HOOKTYPE_REHASH_COMPLETE
		if (!event_added)
		{
			/* Not the best solution, but it works. */
			EventAdd("operpasswd_loadobjects", 0, 1,
				LoadObjects, NULL);
			event_added = 1;
		}
#endif

		return 1;
	}

	return 0;
}

static int cb_quit(aClient *sptr, char *comment)
{
	FCount *f;

	if ((f = find_failop_count(sptr)))
	{
		DelListItem(f, FailedOperups);
		MyFree(f);
	}

	return 0;
}

static int ovr_oper(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	FCount		*f;
	int		ret;

	/* No need to check if '!MyConnect(sptr)'. */
	if (!IsPerson(sptr) || IsAnOper(sptr) || SVSNOOP || parc < 3)
		return CallCmdoverride(ovr, cptr, sptr, parc, parv);

	ret = CallCmdoverride(ovr, cptr, sptr, parc, parv);

	if (!IsAnOper(sptr)) /* Oper-up failed */
	{
		if (Settings.options)
		{
			snprintf(textbuf, BUFSIZE, "[FAILOP] From: %s, "
				"login: %s, password: %s",
				sptr->name, parv[1], parv[2]);

			if (Settings.options & ENABLE_SNOMASK)
			{
				sendto_snomask(SNO_OPERPASS, "*** Notice -- %s",
					textbuf);
				if (Settings.options & ENABLE_GLOBAL_NOTICES)
					sendto_serv_butone_token(NULL, me.name,
					MSG_SENDSNO, TOK_SENDSNO,
					"O :*** Notice -- %s", textbuf);
			}
			if (Settings.options & ENABLE_LOGGING)
				ircd_log(LOG_OPER, "%s", textbuf);
		}

		if (Settings.max_failed_operups)
		{
		        if (!(f = find_failop_count(sptr)))
			{
				f = (FCount *) MyMallocEx(sizeof(FCount));
				f->cptr = sptr;
				f->count = 1;
				AddListItem(f, FailedOperups);
			}
			else
			{
				f->count++;

				if (f->count > Settings.max_failed_operups)
					return exit_client(cptr, sptr, &me,
						Settings.failop_kill_reason
						    ? Settings.failop_kill_reason
						    : "Too many failed oper-ups");
			}
		}
	}

	return ret;
}
