/*
 * =================================================================
 * Filename:		m_banlist.c
 * Description:         Command /banlist
 * Author:		AngryWolf <angrywolf@flashmail.com>
 * Documentation:       m_banlist.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 MODVAR /* Unreal3.2.1 */
 #define MODVAR
#endif

#ifdef TKLISTLEN /* Unreal3.2-RC1 */
extern MODVAR int	tkl_hash(char c);
extern MODVAR aTKline	*tklines[TKLISTLEN];
#else
extern aTKline		*tklines;
#endif
extern void		sendto_one(aClient *to, char *pattern, ...);

#define IsParam(x)	(parc > (x) && !BadPtr(parv[(x)]))
#define IsNotParam(x)	(parc <= (x) || BadPtr(parv[(x)]))
#define DelCommand(x)	if (x) CommandDel(x); x = NULL

static int		m_banlist(aClient *cptr, aClient *sptr, int parc, char *parv[]);

Command			*CmdBanlist;

ModuleHeader MOD_HEADER(m_banlist)
  = {
	"banlist",
	"$Id: m_banlist.c,v 3.1 2004/09/25 14:01:21 angrywolf Exp $",
	"command /banlist",
	"3.2-b8-1",
	NULL 
    };

DLLFUNC int MOD_INIT(m_banlist)(ModuleInfo *modinfo)
{
	if (CommandExists("BANLIST"))
    	{
		config_error("Command BANLIST already exists");
		return MOD_FAILED;
    	}

	CmdBanlist = CommandAdd(modinfo->handle, "BANLIST", NULL, m_banlist, MAXPARA, 0);

#ifndef STATIC_LINKING
	if (ModuleGetError(modinfo->handle) != MODERR_NOERROR || !CmdBanlist)
#else
	if (!CmdBanlist)
#endif
	{
#ifndef STATIC_LINKING
		config_error("Error adding command BANLIST: %s",
			ModuleGetErrorStr(modinfo->handle));
#else
		config_error("Error adding command BANLIST");
#endif
		return MOD_FAILED;
	}

	return MOD_SUCCESS;
}

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

DLLFUNC int MOD_UNLOAD(m_banlist)(int module_unload)
{
	DelCommand(CmdBanlist);
	return MOD_SUCCESS;
}

// =================================================================

typedef struct
{
	int		type;
	char		flag;
	char		*name;
} TKL_type;

typedef struct
{
	int		howmany;
	int		set;
	char		*param;
} BanArg;

typedef struct
{
	char		relation;
	long		expiretime;
} Options;

#define VALID_FLAGS		"ehmst"
#define VALID_RELATIONS		"><"

static TKL_type tkline_types[] =
{
	{ TKL_KILL,			'k',		"K:Line",		},
	{ TKL_ZAP,			'z',		"Z:Line",		},
	{ TKL_KILL	| TKL_GLOBAL,	'G',		"G:Line",		},
	{ TKL_ZAP	| TKL_GLOBAL,	'Z',		"Global Z:Line",	},
	{ TKL_SHUN	| TKL_GLOBAL,	's',		"Shun",			},
	{ 0,				'\0',		NULL			},
};

char *banlist_help[] =
{
	"\2Usage:\2 /banlist [+|-]ehnst <args>",
	"Flag e '<'|'>'<expiretime>: expires before/after <expiretime>",
	"Flag h <user@host>: <user@host> matches ban mask",
	"Flag m <mask>: ban mask matches <mask>",
	"Flag s <mask>: set by someone whose nick!user@host matches <mask>",
	"Flag t <types>: type of ban is one of <types>, valid types are: kzGZs",
	"\2Example:\2 /banlist +ehst >2h user@localhost AngryWolf!*@* G",
	NULL
};

BanArg		banargs['z'];
Options		options;

// =================================================================

static TKL_type *find_tkline_type(int type)
{
	TKL_type *t;

	for (t = tkline_types; t->type; t++)
		if (t->type == type)
			return t;

	return NULL;
}

static char *find_tkline_type_name(int type)
{
	TKL_type *t;

	if ((t = find_tkline_type(type)))
		return t->name;

	return "Unknown *:Line";
}

static char find_tkline_type_flag(int type)
{
	TKL_type *t;

	if ((t = find_tkline_type(type)))
		return t->flag;

	return '\0';
}

static u_int init_check(int parc, char *parv[])
{
	char		*p;
	int		i = 1;
	u_int		set = 1;

	memset(&banargs, 0, sizeof banargs);
	memset(&options, 0, sizeof options);

	if (IsParam(1))
	{
		for (p = parv[1]; *p; p++)
			switch (*p)
			{
				case '+':
					set = 1;
					break;
				case '-':
					set = -1;
					break;
				default:
					if (!strchr(VALID_FLAGS, *p))
						return 0;
					i++;
					banargs[(int) *p].howmany++;
					if (IsNotParam(i))
						return 0;
					if (banargs[(int) *p].howmany > 1)
						return 0;
					banargs[(int) *p].set	= set;
					banargs[(int) *p].param	= parv[i];
			}

		if (banargs['e'].howmany)
		{
			options.relation	= *(banargs['e'].param);

			if (!strchr(VALID_RELATIONS, options.relation))
				return 0;

			options.expiretime	= config_checkval(banargs['e'].param+1, CFG_TIME);
		}

		/* Don't allow '!' in given ban masks */
		
		if (banargs['h'].set && strchr(banargs['h'].param, '!'))
			return 0;

		if (banargs['m'].set && strchr(banargs['m'].param, '!'))
			return 0;
	}

	return 1;	
}

static unsigned check_tkl(aTKline *tk)
{
	BanArg		*b;
	time_t		now = TStime();
	unsigned	show;

	if ((b = &banargs['e'])->set)
	{
		if (tk->expire_at == 0)
			show = 0;
		else if (options.relation == '<')
			show = now + options.expiretime < tk->expire_at;
		else
			show = now + options.expiretime > tk->expire_at;
		
		if ((show && b->set == -1) || (!show && b->set == 1))
			return 0;
	}

	if ((b = &banargs['h'])->set)
	{
		show = !match(make_user_host(tk->usermask, tk->hostmask), b->param);
		if ((show && b->set == -1) || (!show && b->set == 1))
			return 0;
	}

	if ((b = &banargs['m'])->set)
	{
		show = !match(b->param, make_user_host(tk->usermask, tk->hostmask));
		if ((show && b->set == -1) || (!show && b->set == 1))
			return 0;
	}

	if ((b = &banargs['s'])->set)
	{
		show = !match(b->param, tk->setby);
		if ((show && b->set == -1) || (!show && b->set == 1))
			return 0;
	}

	if ((b = &banargs['t'])->set)
	{
		show = strchr(b->param, find_tkline_type_flag(tk->type)) != NULL;
		if ((show && b->set == -1) || (!show && b->set == 1))
			return 0;
	}

	return 1;
}

static void DisplayHelp(aClient *sptr)
{
	unsigned i;
	
	for (i = 0; banlist_help[i]; i++)
		sendto_one(sptr, ":%s NOTICE %s :*** %s",
			me.name, sptr->name, banlist_help[i]);
}

static void DisplayTKL(aClient *sptr, aTKline *tk)
{
	char gmt[256], gmt2[256], *tkl_name;

	strncpyzt(gmt, asctime(gmtime((TS *)&tk->set_at)), sizeof(gmt));
	iCstrip(gmt);

	tkl_name = find_tkline_type_name(tk->type);

	if (tk->expire_at == 0)	/* Permanent */
	{
		sendto_one(sptr, ":%s NOTICE %s :*** Permanent %s for %s@%s, set by %s at %s GMT, reason: %s",
			me.name, sptr->name,
			tkl_name, tk->usermask, tk->hostmask,
			tk->setby, gmt, tk->reason);
	}
	else /* Timed */
	{
		strncpyzt(gmt2, asctime(gmtime((TS *)&tk->expire_at)), sizeof(gmt2));
		iCstrip(gmt2);

		sendto_one(sptr, ":%s NOTICE %s :*** Timed %s for %s@%s, set by %s at %s GMT, expires at %s GMT, reason: %s",
			me.name, sptr->name,
			tkl_name, tk->usermask, tk->hostmask,
			tk->setby, gmt, gmt2, tk->reason);
	}
}

static int m_banlist(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
#ifdef TKLISTLEN
	TKL_type	*t;
#endif
	aTKline		*tk;
	u_int		found = 0;

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

	if (!init_check(parc, parv))
	{
		DisplayHelp(sptr);
		return 0;
	}

#ifdef TKLISTLEN
	for (t = tkline_types; t->type; t++)
		for (tk = tklines[tkl_hash(t->flag)]; tk; tk = tk->next)
#else
		for (tk = tklines; tk; tk = tk->next)
#endif
			if (check_tkl(tk))
			{
				DisplayTKL(sptr, tk);
				found++;
			}

	if (!found)
		sendto_one(sptr, ":%s NOTICE %s :*** No entries found",
			me.name, sptr->name);
	else
		sendto_one(sptr, ":%s NOTICE %s :*** Found %d entr%s",
			me.name, sptr->name,
			found, found > 1 ? "ies" : "y");

	return 0;
}
