/*
 * Extended ban that allows user to bypass message restrictions
 * (C) Copyright 2017-.. Bram Matthys (Syzop) and the UnrealIRCd team
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 1, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "unrealircd.h"

ModuleHeader MOD_HEADER(msgbypass)
= {
	"chanmodes/extbans/msgbypass",
	"4.0",
	"ExtBan ~m - bypass +m/+n/+c/+S/+T (msgbypass)",
	"3.2-b8-1",
	NULL 
};

/* Forward declarations */
int extban_msgbypass_is_banned(aClient *sptr, aChannel *chptr, char *banin, int type);
int msgbypass_can_bypass(aClient *sptr, aChannel *chptr, BypassChannelMessageRestrictionType bypass_type);
int msgbypass_extban_is_ok(aClient* sptr, aChannel* chptr, char* para, int checkt, int what, int what2);
char *msgbypass_extban_conv_param(char *para);

/** Called upon module init */
MOD_INIT(msgbypass)
{
	ExtbanInfo req;
	
	req.flag = 'm';
	req.is_ok = msgbypass_extban_is_ok;
	req.conv_param = msgbypass_extban_conv_param;
	req.is_banned = extban_msgbypass_is_banned;
	req.options = EXTBOPT_ACTMODIFIER;
	if (!ExtbanAdd(modinfo->handle, req))
	{
		config_error("could not register extended ban type ~m");
		return MOD_FAILED;
	}

	MARK_AS_OFFICIAL_MODULE(modinfo);
	
	return MOD_SUCCESS;
}

/** Called upon module load */
MOD_LOAD(msgbypass)
{
	HookAdd(modinfo->handle, HOOKTYPE_CAN_BYPASS_CHANNEL_MESSAGE_RESTRICTION, 0, msgbypass_can_bypass);
	return MOD_SUCCESS;
}

/** Called upon unload */
MOD_UNLOAD(msgbypass)
{
	return MOD_SUCCESS;
}

/** Is the user banned? No, never by us anyway. */
int extban_msgbypass_is_banned(aClient *sptr, aChannel *chptr, char *banin, int type)
{
	return 0; /* not banned by us */
}

/** Can the user bypass restrictions? */
int msgbypass_can_bypass(aClient *sptr, aChannel *chptr, BypassChannelMessageRestrictionType bypass_type)
{
	Ban *ban;
	char *p;
	
	for (ban = chptr->exlist; ban; ban=ban->next)
	{
		if ((ban->banstr[0] == '~') && (ban->banstr[1] == 'm') && (ban->banstr[2] == ':'))
		{
			char *type = ban->banstr + 3;
			char *matchby;
			
			if (((bypass_type == BYPASS_CHANMSG_EXTERNAL) && !strncmp(type, "external:", 9)) ||
			    ((bypass_type == BYPASS_CHANMSG_MODERATED) && !strncmp(type, "moderated:", 10)) ||
			    ((bypass_type == BYPASS_CHANMSG_COLOR) && !strncmp(type, "color:", 6)) || 
			    ((bypass_type == BYPASS_CHANMSG_CENSOR) && !strncmp(type, "censor:", 7)) ||
			    ((bypass_type == BYPASS_CHANMSG_NOTICE) && !strncmp(type, "notice:", 7)))
			{
				matchby = strchr(type, ':');
				if (!matchby)
					continue;
				matchby++;
				
				if (ban_check_mask(sptr, chptr, matchby, BANCHK_MSG, 0))
					return HOOK_ALLOW; /* Yes, user may bypass */
			}
		}
	}

	return HOOK_CONTINUE; /* No, may NOT bypass. */
}

/** Does this bypass type exist? (eg: 'external') */
int msgbypass_extban_type_ok(char *type)
{
	if (!strcmp(type, "external") ||
	    !strcmp(type, "moderated") ||
	    !strcmp(type, "censor") ||
	    !strcmp(type, "color") ||
	    !strcmp(type, "notice"))
	{
		return 1; /* Yes, OK type */
	}
	return 0; /* NOMATCH */
}

#define MAX_LENGTH 128
char *msgbypass_extban_conv_param(char *para_in)
{
	static char retbuf[MAX_LENGTH+1];
	char para[MAX_LENGTH+1];
	char tmpmask[MAX_LENGTH+1];
	char *type; /**< Type, such as 'external' */
	char *matchby; /**< Matching method, such as 'n!u@h' */
	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */

	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
	
	/* ~m:type:n!u@h   for direct matching
	 * ~m:type:~x:.... when calling another bantype
	 */

	type = para;
	matchby = strchr(para, ':');
	if (!matchby || !matchby[1])
		return NULL;
	*matchby++ = '\0';

	if (!msgbypass_extban_type_ok(type))
		return NULL;

	/* This is quite silly, we have to create a fake extban here due to
	 * the current API of extban_conv_param_nuh and extban_conv_param_nuh_or_extban
	 * expecting the full banmask rather than the portion that actually matters.
	 */
	snprintf(tmpmask, sizeof(tmpmask), "~?:%s", matchby);
	newmask = extban_conv_param_nuh_or_extban(tmpmask);
	if (!newmask || (strlen(newmask) <= 3))
		return NULL;

	snprintf(retbuf, sizeof(retbuf), "~m:%s:%s", type, newmask+3);
	return retbuf;
}

int msgbypass_extban_syntax(aClient *sptr, int checkt, char *reason)
{
	if (MyClient(sptr) && (checkt == EXBCHK_PARAM))
	{
		sendnotice(sptr, "Error when setting ban exception: %s", reason);
		sendnotice(sptr, " Syntax: +e ~m:type:mask");
		sendnotice(sptr, "Example: +e ~m:moderated:~a:TrustedUser");
		sendnotice(sptr, "Valid types are: external, moderated, color, notice");
		sendnotice(sptr, "Valid masks are: nick!user@host or another extban type such as ~a, ~c, ~S, ..");
	}
	return 0; /* FAIL: ban rejected */
}

int msgbypass_extban_is_ok(aClient* sptr, aChannel* chptr, char* para_in, int checkt, int what, int what2)
{
	char para[MAX_LENGTH+1];
	char tmpmask[MAX_LENGTH+1];
	char *type; /**< Type, such as 'external' */
	char *matchby; /**< Matching method, such as 'n!u@h' */
	char *newmask; /**< Cleaned matching method, such as 'n!u@h' */

	/* Always permit deletion */
	if (what == MODE_DEL)
		return 1;
	
	if (what2 != EXBTYPE_EXCEPT)
	{
		if (checkt == EXBCHK_PARAM)
			sendnotice(sptr, "Ban type ~m only works with exceptions (+e) and not with bans or invex (+b/+I)");
		return 0; /* reject */
	}

	strlcpy(para, para_in+3, sizeof(para)); /* work on a copy (and truncate it) */
	
	/* ~m:type:n!u@h   for direct matching
	 * ~m:type:~x:.... when calling another bantype
	 */

	type = para;
	matchby = strchr(para, ':');
	if (!matchby || !matchby[1])
		return msgbypass_extban_syntax(sptr, checkt, "Invalid syntax");
	*matchby++ = '\0';

	if (!msgbypass_extban_type_ok(type))
		return msgbypass_extban_syntax(sptr, checkt, "Unknown type");

	/* This is quite silly, we have to create a fake extban here due to
	 * the current API of extban_conv_param_nuh and extban_conv_param_nuh_or_extban
	 * expecting the full banmask rather than the portion that actually matters.
	 */
	snprintf(tmpmask, sizeof(tmpmask), "~?:%s", matchby);
	if (extban_is_ok_nuh_extban(sptr, chptr, tmpmask, checkt, what, what2) == 0)
	{
		/* This could be anything ranging from:
		 * invalid n!u@h syntax, unknown (sub)extbantype,
		 * disabled extban type in conf, too much recursion, etc.
		 */
		return msgbypass_extban_syntax(sptr, checkt, "Invalid matcher");
	}

	return 1; /* OK */
}

