/**
 **	This module has the intend to restrict the use of /oper to some extend.
 **
 **	Firstly it lets only secure connections oper up.
 **
 **	Secondly, there are 3 additional settings for this module (of which all
 **	are optional and counted cumulative, what means if you
 **	specified more than 1 of these, all of these MUST match to allow /oper 
 **	from that connection/nickname):
 **
 **	1.)
 **		 The "opernicks"-configuration directive:
 **		set {
 **			opernicks "nick1,nick2,nick3";
 **		};
 **		This confines a successful opering to the comma-delimited list of nicknames.
 **
 **	2.)
 **		 The "operneedreg"-configuration directive:
 **		set {
 **			operneedreg "yes";
 **		};
 **		If this is set to "yes" those who want to oper must have a registered nickname.
 **
 **	3.)
 **		 The "operexempt"-configuration directive:
 **		set {
 **			operexempt "IP.IP.IP.IP";
 **		};
 **		If this is set to an IP, clients from that IP may oper without using a secure
 **		connection AND without having a registered nick (e.g. useful for BOPM).
 **		The client must still be in the opernicks list (if defined).
 **
 **	I know this doesn't prevent cleartext passwords from being sent when an
 **	oper not using SSL tries to oper up, but it ensures that private messages
 **	between opers are always encrypted.
 **
 **	Changelog:
 **
 **	Version 0.1
 **		- Initial release
 **	Version 0.2
 **		- Description fixed
 **		- Fixed Typo in year (MOD_HEADER, was 2004)
 **		- Added support for an ip (localhost?) from which one can oper without
 **			using SSL (useful for BOPM or such)
 **	Version 0.3
 **		- Fixed a compiler warning about implicit function declaration (Thx to Bram)
 **	Version 0.4
 **		- Added (optional) functionality to let only a specified set of nicks oper
 **	Version 0.5
 **		- Added logging
 **		- Failed oper messages are being sent now
 **		- Made all options configurable in the set::-block
 **		- Added set::operneedreg which requires a user to be identified before /oper
 **		- Added set::operexempt which does the same as the #define NOSSL_ALLOWED
 **			before namely lets one specify an IP from which clients may oper without
 **			using a secure connection
 **		- /stats S now shows all configurable settings of the module
 **		- Changed module description to fit all options now available
 **		- Various code- and aesthetical-cleanups
 **/

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

ModuleInfo*  modinfo_m_soper  = NULL;
Cmdoverride* cmd_soper        = NULL;

static Hook* HookConfTest = NULL, *HookConfRun = NULL, *HookStats = NULL;
static int soper_config_test(ConfigFile*, ConfigEntry*, int, int*);
static int soper_config_run(ConfigFile*, ConfigEntry*, int);
static int soper_stats(aClient *sptr, char *stats);
DLLFUNC int  m_f_soper(Cmdoverride* ovr, aClient* cptr, aClient* sptr, int parc, char* parv[]);

char* opernicks = NULL, *operexempt = NULL, *operneedreg = NULL;

#define SOPER_NOERR 1
#define SOPER_ERR_NOSSL 2
#define SOPER_ERR_NOVALIDNICK 3
#define SOPER_ERR_NOTREGISTERED 4

ModuleHeader MOD_HEADER(m_soper)
  = {
	"m_soper",	/* Name of module */
	"$Id: m_soper, v 0.5 2007/01/24 cloud", /* Version */
	"m_soper - Restricts the use of /oper", /* Short description of module */
	"3.2-b8-1",
	NULL 
};

DLLFUNC int MOD_TEST(m_soper)(ModuleInfo *modinfo)
{
	HookConfTest = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGTEST, soper_config_test);
        return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(m_soper)(ModuleInfo *modinfo)
{
	modinfo_m_soper = modinfo;
	HookConfRun = HookAddEx(modinfo->handle, HOOKTYPE_CONFIGRUN, soper_config_run);
	HookStats = HookAddEx(modinfo->handle, HOOKTYPE_STATS, soper_stats);
	return MOD_SUCCESS;
}

DLLFUNC int MOD_LOAD(m_soper)(int module_load)
{
	cmd_soper = CmdoverrideAdd(modinfo_m_soper->handle, "oper", m_f_soper);
	if (NULL == cmd_soper) {
		return MOD_FAILED;
	}

	return MOD_SUCCESS;
}

DLLFUNC int MOD_UNLOAD(m_soper)(int module_unload)
{
	CmdoverrideDel(cmd_soper);
	if (HookConfTest) {
		HookDel(HookConfTest);
		HookConfTest = NULL;
	}
	if (HookConfRun) {
		HookDel(HookConfRun);
		HookConfRun = NULL;
	}
	if (HookStats) {
		HookDel(HookStats);
		HookStats = NULL;
	}
	if (opernicks) {
		free(opernicks);
		opernicks = NULL;
	}
	if (operneedreg) {
		free(operneedreg);
		operneedreg = NULL;
	}
	if (operexempt) {
		free(operexempt);
		operexempt = NULL;
	}
	return MOD_SUCCESS;
}

static int soper_stats(aClient *sptr, char *stats)
{
	if (*stats == 'S') {
		if (opernicks) {
		sendto_one(sptr, ":%s %i %s :opernicks: %s",
					me.name, RPL_TEXT, sptr->name, opernicks);
		}
		if (operexempt) {
		sendto_one(sptr, ":%s %i %s :operexempt: %s",
					me.name, RPL_TEXT, sptr->name, operexempt);

		}
		if (operneedreg) {
		sendto_one(sptr, ":%s %i %s :operneedreg: %s",
					me.name, RPL_TEXT, sptr->name, operneedreg);

		}
	}

        return 0;
}

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

	if (type != CONFIG_SET)
		return 0;

	if (!strcmp(ce->ce_varname, "opernicks"))
	{
		if (!ce->ce_vardata)
		{
			config_error("%s:%i: set::opernicks has no arguments.",
					ce->ce_fileptr->cf_filename,
					ce->ce_varlinenum);
			errors++;
		}

		*errs = errors;
		return errors ? -1 : 1;
	}
	else if (!strcmp(ce->ce_varname, "operexempt")) {
		if (!ce->ce_vardata)
		{
			config_error("%s:%i: set::operexempt has no arguments.",
					ce->ce_fileptr->cf_filename,
					ce->ce_varlinenum);
			errors++;
		}

		*errs = errors;
		return errors ? -1 : 1;
	}
	else if (!strcmp(ce->ce_varname, "operneedreg")) {
		if (!ce->ce_vardata)
		{
			config_error("%s:%i: set::operneedreg has no arguments.",
					ce->ce_fileptr->cf_filename,
					ce->ce_varlinenum);
			errors++;
		}

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

static int soper_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
	if (type != CONFIG_SET)
		return 0;

	if (!strcmp(ce->ce_varname, "opernicks")) {
		if (opernicks) {
			free(opernicks);
			opernicks = NULL;
		}

		if (ce->ce_vardata) {
			opernicks = strdup(ce->ce_vardata);
		}
		return 1;
	}
	else if (!strcmp(ce->ce_varname, "operexempt")) {
		if (operexempt) {
			free(operexempt);
			operexempt = NULL;
		}

		if (ce->ce_vardata) {
			operexempt = strdup(ce->ce_vardata);
		}
		return 1;
	}
	else if (!strcmp(ce->ce_varname, "operneedreg")) {
		if (operneedreg) {
			free(operneedreg);
			operneedreg = NULL;
		}

		if (ce->ce_vardata) {
			operneedreg = strcmp("yes", ce->ce_vardata) ? strdup("no") : strdup("yes");
		}
		return 1;
	}
	else
		return 0;
}

DLLFUNC int m_f_soper(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	int mayOper = SOPER_NOERR;
	char* ptr = NULL, *name = NULL;
	size_t len = 0;

	if (!MyClient(sptr))
		return 0;

	if (parc < 3) {
		sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS),
		    me.name, parv[0], "OPER");
		return 0;
	}

	if (IsServer(sptr) || !(sptr->name) || !GetIP(sptr)) return CallCmdoverride(ovr, cptr, sptr, parc, parv);;

	/* Check for valid nick */
	if (opernicks && opernicks[0] != '\0') {
		len = strlen(sptr->name);
		ptr = opernicks;
		while (ptr = strstr(ptr, sptr->name)) {
			if (*(ptr + len) == ' ' || *(ptr + len) == ',' || *(ptr + len) == '\0') {
				if (ptr == opernicks || *(ptr - 1) == ',' || *(ptr - 1) == ' ') {
					break;
				}
			}
			ptr++;
		}
		if (!ptr) mayOper = SOPER_ERR_NOVALIDNICK;
	}

	/* Check for registered nick */
	if (operneedreg && operneedreg[0] != '0' && !strcmp(operneedreg, "yes") && !(sptr->umodes & UMODE_REGNICK)) {
		if (match(operexempt, GetIP(sptr))) {
			mayOper = (mayOper == SOPER_NOERR) ? SOPER_ERR_NOTREGISTERED : mayOper;
		}

	}

	/* Check for secure connection */
	if ((operexempt[0] != '\0') && !(sptr->umodes & UMODE_SECURE)) {
		/* If operexempt is not empty and matches the IP of the connecting client
		let it oper.
		*/
		if (match(operexempt, GetIP(sptr))) {
			mayOper = (mayOper == SOPER_NOERR) ? SOPER_ERR_NOSSL : mayOper;
		}
	}

	name = parv[1];
	switch (mayOper) {
		case SOPER_ERR_NOVALIDNICK:
			sendto_one(	sptr,
						":%s %s %s :*** Invalid nick: %s. Please change your nickname to your oper-nick and try again.", me.name, IsWebTV(sptr) ? "PRIVMSG" : "NOTICE",
						sptr->name, sptr->name);
			sendto_snomask_global(	SNO_OPER,
								"Failed OPER attempt by %s (%s@%s) [nick doesn't match]",
								parv[0], sptr->user->username, sptr->sockhost);
			ircd_log(	LOG_OPER,
					"OPER UNKNOWNNICK (%s) by (%s!%s@%s)",
					name,
					parv[0],
					sptr->user->username, sptr->sockhost);
			break;
		case SOPER_ERR_NOSSL:
			sendto_one(	sptr,
						":%s %s %s :*** The /oper command is restricted to secure connections.", me.name, IsWebTV(sptr) ? "PRIVMSG" : "NOTICE",
						sptr->name);
			sendto_snomask_global(	SNO_OPER,
									"Failed OPER attempt by %s (%s@%s) [no SSL]",
									parv[0], sptr->user->username, sptr->sockhost);
			ircd_log(	LOG_OPER,
					"OPER NOSSL (%s) by (%s!%s@%s)",
					name,
					parv[0],
					sptr->user->username, sptr->sockhost);
			break;
		case SOPER_ERR_NOTREGISTERED:
			sendto_one(	sptr,
						":%s %s %s :*** You need a registered nick to oper.", me.name, IsWebTV(sptr) ? "PRIVMSG" : "NOTICE",
						sptr->name);
			sendto_snomask_global(	SNO_OPER,
									"Failed OPER attempt by %s (%s@%s) [no registered nick]",
									parv[0], sptr->user->username, sptr->sockhost);
			ircd_log(	LOG_OPER,
					"OPER NOREGISTEREDNICK (%s) by (%s!%s@%s)",
					name,
					parv[0],
					sptr->user->username, sptr->sockhost);
			break;
		case SOPER_NOERR:
			return CallCmdoverride(ovr, cptr, sptr, parc, parv);
			break;
	}

	return 0;
}
