/*
 * =================================================================
 * Filename:       cmdshun.c
 * Description:    Command shun
 * Author:         AngryWolf <angrywolf@flashmail.com>
 * Documentation:  cmdshun.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

typedef struct _cmdentry	CmdEntry;
typedef struct _cmdshun		CmdShun;
typedef struct _ovrentry	OvrEntry;

/*
 * CmdEntry:
 *     list of commands disabled for a user@host
 *
 *     cmd: info about the disabled command
 *     opt: additional options for PRIVSMG & NOTICE
 */
struct _cmdentry
{
	CmdEntry		*prev, *next;
	OvrEntry		*cmd;
	char			opt;
};

/*
 * OvrEntry:
 *      list of command overrides used by this module
 *
 *      ovr:       an override entry for the command
 *      name:      name of the command
 *      used:      how many times the command is used in CmdShuns
 *      temporary: has a true value if the command belongs to temporarily
 *                 created command lists
 */
struct _ovrentry
{
	OvrEntry		*prev, *next;
	Cmdoverride		*ovr;
	char			*name;
	unsigned short		used;
	unsigned char		temporary;
};

/*
 * CmdShun:
 *     data structure for a command shun
 *     primary keys: usermask, hostmask, cmdlist
 *
 *     commands:  a copy of the command list given the second parameter of
 *                /cmdshun
 *     cmdlist:   a parsed form of the command list
 *     numofcmds: how many commands are in the command list
 *     event:     an event to remove an expired cmdshun
 */
struct _cmdshun
{
	CmdShun			*prev, *next;
	char			*usermask, *hostmask;
	char			*setby, *reason, *commands;
	CmdEntry		*cmdlist;
	struct irc_netmask	*netmask;
	Event			*event;
	u_short			numofcmds;
	TS			set_at;
	TS			expire_at;
};

extern void			sendto_one(aClient *to, char *pattern, ...);
extern void			sendto_realops(char *pattern, ...);
extern void			sendto_serv_butone_token(aClient *one, char *prefix, char *command, char *token, char *pattern, ...);

#define ERR_DISABLED		":%s 517 %s %s :Command disabled (%s)" /* ircu */
#define MSG_CMDSHUN		"CMDSHUN"
#define TOK_CMDSHUN		NULL
#define MSG_CMDSHUNFWD		"CMDSHUNFWD"
#define TOK_CMDSHUNFWD		"SF"
#define OPT_FLAG		'#'

#define IsParam(x)      	(parc > (x) && !BadPtr(parv[(x)]))
#define IsNotParam(x)   	(parc <= (x) || BadPtr(parv[(x)]))
#define DelHook(x)		if (x) HookDel(x); x = NULL
#define DelCommand(x)		if (x) CommandDel(x); x = NULL
#define DelOverride(cmd, ovr)	if (ovr && CommandExists(cmd)) CmdoverrideDel(ovr); ovr = NULL

static Command			*AddCommand(Module *module, char *msg, char *token, iFP func, unsigned char parameters, int flags);
static Cmdoverride		*AddOverride(char *msg, iFP cb);
static int			m_cmdshun(aClient *, aClient *, int, char *[]);
static int			m_cmdshunfwd(aClient *, aClient *, int, char *[]);
static int			ovr_cmd(Cmdoverride *, aClient *, aClient *, int, char *[]);
static int			cb_rehash();
static int			cb_rehash_complete();
static int			cb_server_connect(aClient *);
static void			del_cmdshun(CmdShun *s);

static ModuleInfo		*ModCmdshun;
static Hook			*HookRehashDone, *HookRehash, *HookServConn;
static Command			*CmdCmdshun, *CmdCmdshunfwd;
static CmdShun			*CmdShunList;
static OvrEntry			*OverridesList;
static CmdEntry			*tmp_cmdlist;
static aClient			*SetBy;
static u_char			module_loaded = 0;
static char			buf[BUFSIZE];
static u_short			tmp_numofcmds;

#ifndef STATIC_LINKING
static ModuleInfo		*MyModInfo = NULL;
 #define MyMod			MyModInfo->handle
 #define SAVE_MODINFO		MyModInfo = modinfo;
#else
 #define MyMod			NULL
 #define SAVE_MODINFO
#endif

ModuleHeader MOD_HEADER(cmdshun)
  = {
	"cmdshun",
	"$Id: cmdshun.c,v 1.1 2004/08/16 13:31:25 angrywolf Exp $",
	"Command Shun",
	"3.2-b8-1",
	NULL 
    };

DLLFUNC int MOD_TEST(cmdshun)(ModuleInfo *modinfo)
{
	return MOD_SUCCESS;
}

DLLFUNC int MOD_INIT(cmdshun)(ModuleInfo *modinfo)
{
	SAVE_MODINFO

#ifndef STATIC_LINKING
        ModuleSetOptions(modinfo->handle, MOD_OPT_PERM);
#endif

	ModCmdshun	= modinfo;
	CmdShunList	= NULL;
	OverridesList	= NULL;
	SetBy		= NULL;
	tmp_cmdlist	= NULL;
	tmp_numofcmds	= 0;

	HookRehash	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH,
				cb_rehash);
	HookRehashDone	= HookAddEx(modinfo->handle, HOOKTYPE_REHASH_COMPLETE,
				cb_rehash_complete);
	HookServConn	= HookAddEx(modinfo->handle, HOOKTYPE_SERVER_CONNECT,
				cb_server_connect);

	CmdCmdshun	= AddCommand(modinfo->handle, MSG_CMDSHUN, TOK_CMDSHUN,
				m_cmdshun, 4, 0);
	CmdCmdshunfwd	= AddCommand(modinfo->handle, MSG_CMDSHUNFWD, TOK_CMDSHUNFWD,
				m_cmdshunfwd, MAXPARA, M_SERVER);

	if (!CmdCmdshun || !CmdCmdshunfwd)
		return MOD_FAILED;

	return MOD_SUCCESS;
}

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

DLLFUNC int MOD_UNLOAD(cmdshun)(int module_unload)
{
	CmdShun		*s;
	ListStruct	*next;

	DelHook(HookServConn);
	DelHook(HookRehashDone);
	DelHook(HookRehash);
	DelCommand(CmdCmdshunfwd);
	DelCommand(CmdCmdshun);
	cb_rehash();

	for (s = CmdShunList; s; s = (CmdShun *) next)
	{
		next = (ListStruct *) s->next;
		del_cmdshun(s);
	}

	return MOD_SUCCESS;
}

static int cb_rehash()
{
	OvrEntry *o;

	module_loaded = 0;

	/*
	 * What I do here is just deleting command overrides.
	 * (Only command shun removals may delete OvrEntry structures.)
	 */

	for (o = OverridesList; o; o = o->next)
		if (o->ovr)
		{
			DelOverride(o->name, o->ovr);
		}

	return 0;
}

static int cb_rehash_complete()
{
	OvrEntry *o;

	if (!module_loaded)
	{
		for (o = OverridesList; o; o = o->next)
			if (!o->ovr)
				o->ovr = AddOverride(o->name, ovr_cmd);

		module_loaded = 1;
	}

	return 0;
}

static Command *AddCommand(Module *module, char *msg, char *token, iFP func,
                           unsigned char parameters, int flags)
{
	Command *cmd;

	if (CommandExists(msg))
    	{
		config_error("Command %s already exists", msg);
		return NULL;
    	}
    	if (token && CommandExists(token))
	{
		config_error("Token %s already exists", token);
		return NULL;
    	}

	cmd = CommandAdd(module, msg, token, func, parameters, flags);

#ifndef STATIC_LINKING
	if (ModuleGetError(module) != MODERR_NOERROR || !cmd)
#else
	if (!cmd)
#endif
	{
#ifndef STATIC_LINKING
		config_error("Error adding command %s: %s", msg,
			ModuleGetErrorStr(module));
#else
		config_error("Error adding command %s", msg);
#endif
		return NULL;
	}

	return cmd;
}

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

	return ovr;
}

/*
 * cb_server_connect:
 *     Distribute our CmdShuns to other servers on the network
 *     when they are connecting to us or we to them.
 */

static int cb_server_connect(aClient *cptr)
{
	CmdShun *s;

	for (s = CmdShunList; s; s = s->next)
	{
		sendto_one(cptr, ":%s %s ! %s %s %s %s %ld %ld :%s",
			me.name, IsToken(cptr) ? TOK_CMDSHUNFWD : MSG_CMDSHUNFWD,
			s->usermask, s->hostmask, s->commands, s->setby,
			(long) s->set_at, (long) s->expire_at,
			s->reason ? s->reason : "");
	}

	return 0;
}

/*
 * find_Cmd_quick:
 *     This is a quick way to search for commands names.
 */

static inline aCommand *find_Cmd_quick(char *cmd)
{
	aCommand *p;

	for (p = CommandHash[toupper(*cmd)]; p; p = p->next)
		if (!stricmp(p->cmd, cmd))
			break;

	return p;
}

/*
 * find_cmdentry:
 *     Look through a list of commands, and search for a command with
 *     the given options (c->opt).
 */

static CmdEntry *find_cmdentry(char *name, CmdEntry *list, char *param)
{
	CmdEntry *c;

	for (c = list; c; c = c->next)
		if (!stricmp(c->cmd->name, name))
		{
			if (c->opt)
			{
				if (!param)
					continue;
				if (!stricmp(name, "privmsg") ||
				    !stricmp(name, "notice"))
				{
					if (c->opt == 'p' && param[0] == '#')
						continue;
					if (c->opt == 'c' && param[0] != '#')
						continue;
				}
			}
			break;
		}

	return c;
}

static OvrEntry *find_ovrentry(char *name)
{
	OvrEntry *o;

	for (o = OverridesList; o; o = o->next)
		if (!stricmp(o->name, name))
			break;

	return o;
}

/*
 * del_cmdlist:
 *     Delete a list of command entries with their overrides.
 */

static void del_cmdlist(CmdEntry **list)
{
	CmdEntry	*c;
	OvrEntry	*o;
	ListStruct	*next;

	for (c = *list; c; c = (CmdEntry *) next)
	{
		next	= (ListStruct *) c->next;
		o	= c->cmd;

		DelListItem(c, *list);
		if (!--o->used)
		{
			DelListItem(o, OverridesList);
			DelOverride(o->name, o->ovr);
			if (!o->temporary)
			{
				MyFree(o->name);
				MyFree(o);
			}
		}
		MyFree(c);
	}
}

/*
 * del_cmdlist_noovr:
 *     Delete a list of command entries, but keep the overrides (this is
 *     used with make_cmdlist, see below).
 */

static void del_cmdlist_noovr(CmdEntry **list)
{
	CmdEntry	*c;
	OvrEntry	*o;
	ListStruct	*next;

	for (c = *list; c; c = (CmdEntry *) next)
	{
		next	= (ListStruct *) c->next;
		o	= c->cmd;

		DelListItem(c, *list);
		if (!o->used)
		{
			MyFree(o->name);
			MyFree(o);
		}
		else
			o->temporary = 0;
		MyFree(c);
	}
}

/*
 * make_cmdlist:
 *     Create an initial list of command entries without overrides.
 *     ('commands' may not be NULL.) Also return the number of
 *     commands in the list through 'num'.
 */

static CmdEntry *make_cmdlist(char *commands, u_short *num)
{
	CmdEntry	*entry, *cmdlist = NULL;
	OvrEntry	*o;
	char    	*tmp = strdup(commands);
	char		*c, *p = NULL;
	char		*opt;
	u_short		count = 0;

	for (c = strtoken(&p, tmp, ","); c; c = strtoken(&p, NULL, ","))
	{
		entry = (CmdEntry *) MyMallocEx(sizeof(CmdEntry));
		if ((opt = strchr(c, OPT_FLAG)))
		{
			opt[0] = 0;
			/*
			 * Syntax is: <command>#<flag>. Set entry->opt to
			 * OPT_FLAG when <flag> is missing or long than
			 * one character.
			 */
			entry->opt = (!opt[1] || opt[2])
				? OPT_FLAG : opt[1];
		}
		if (!(o = find_ovrentry(c)))
		{
			o = (OvrEntry *) MyMallocEx(sizeof(OvrEntry));
			o->name = strdup(c);
		}
		if (opt)
			opt[0] = OPT_FLAG;
		o->temporary = 1;
		entry->cmd = o;
		AddListItem(entry, cmdlist);
		count++;
	}

	MyFree(tmp);
	*num = count;

	return cmdlist;
}

static void del_cmdshun(CmdShun *s)
{
	DelListItem(s, CmdShunList);
	MyFree(s->usermask);
	MyFree(s->hostmask);
	MyFree(s->commands);
	MyFree(s->setby);
	MyFree(s->reason);
	del_cmdlist(&s->cmdlist);
	if (s->netmask)
		MyFree(s->netmask);
	if (s->event)
		EventDel(s->event);
	MyFree(s);
}

static void del_tmp_data()
{
	del_cmdlist_noovr(&tmp_cmdlist);
	tmp_numofcmds = 0;
}

static void event_cmdshunexpire(CmdShun *s)
{
	char	gmt[64], *settime, *p;
	TS	now = TStime();

	if (s->expire_at > now)
	{
		/*
		 * I don't know how feasible this can be, but at least
		 * inform opers if such a thing happens...
		 */
		if (s->expire_at)
		{
			char gmt2[64];

			strlcpy(gmt2, asctime(gmtime(&s->expire_at)), sizeof gmt);
			iCstrip(gmt2);

			sendto_realops("event_cmdshunexpire(): expire for "
				"non-yet-expired CmdShun %s@%s with "
				"command%s %s (should expire on %s GMT)",
				s->usermask, s->hostmask,
				s->cmdlist->next ? "s" : "", s->commands,
				gmt);
		}
		else
		{
			sendto_realops("event_cmdshunexpire(): expire for "
				"permanent CmdShun %s@%s with command%s %s",
				s->usermask, s->hostmask,
				s->cmdlist->next ? "s" : "", s->commands,
				gmt);
		}

		return;
	}

	/* notification */
	strlcpy(gmt, asctime(gmtime(&s->set_at)), sizeof gmt);
	iCstrip(gmt);

	settime = pretty_time_val(TStime() - s->set_at);
	if (*(p = settime + strlen(settime) - 1) == ' ')
		*p = 0;

	snprintf(buf, sizeof buf, "Expiring CmdShun (%s@%s with command%s "
		"%s) made by %s (Reason: %s) set %s ago)",
		s->usermask, s->hostmask, s->cmdlist->next ? "s" : "",
		s->commands, s->setby, s->reason ? s->reason : "no reason",
		settime);

	sendto_snomask(SNO_TKL, "*** %s", buf);
	ircd_log(LOG_TKL, "%s", buf);

	del_cmdshun(s);
}

static int ovr_cmd(Cmdoverride *ovr, aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	if (MyConnect(sptr) && !IsULine(sptr) && IsPerson(sptr) && !IsAnOper(sptr))
	{
		CmdShun		*s;
		char		*param	= IsParam(1) ? parv[1] : NULL;
		char		*ip	= GetIP(sptr);
		char		*user	= sptr->user->username;
		char		*host	= sptr->sockhost;
		char		*cmd	= ovr->command->cmd;

		for (s = CmdShunList; s; s = s->next)
		{
			if (!find_cmdentry(cmd, s->cmdlist, param))
				continue;
			if (match(s->usermask, user))
				continue;
			if (s->netmask)
			{
				if (match_ip(sptr->ip, NULL, NULL, s->netmask))
					break;
				continue;
			}
			if (!match(s->hostmask, host))
				break;
			if (!match(s->hostmask, ip))
				break;
		}

		if (s)
		{
			sendto_one(sptr, ERR_DISABLED, me.name, sptr->name,
				cmd, s->reason ? s->reason : "no reason");
			return 0;
		}
	}

	return CallCmdoverride(ovr, cptr, sptr, parc, parv);
}

/*
** m_cmdshun
** ---------
**
**    Used by IRCOps to add/remove CmdShuns.
**
**    parv[1]: u@h mask or nick
**    parv[2]: commands
**    parv[3]: expire time (optional)
**    parv[4]: reason (optional)
**
*/

#define PFrom		0
#define PWhat		1
#define PUserMask	2
#define PHostMask	3
#define PCommands	4
#define PSetBy		5
#define PSetAt		6
#define PExpireAt	7
#define PReason		8

static int m_cmdshun(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	char		*params[9];
	char		*p, *mask, *usermask, *hostmask;
	char		set_at[BUFSIZE], expire_at[BUFSIZE];
	u_char		add;

	if (!MyConnect(sptr) || !IsPerson(sptr))
		return 0;

	if (!IsAnOper(sptr) && !IsULine(sptr))
	{
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, sptr->name);
		return 0;
	}

	if (IsNotParam(1))
	{
		CmdShun		*s;
		char		gmt[64], *expiretime = NULL, *p;

		for (s = CmdShunList; s; s = s->next)
		{
			strlcpy(gmt, asctime(gmtime(&s->set_at)), sizeof gmt);
			iCstrip(gmt);

			if (s->expire_at)
			{
				expiretime = pretty_time_val(s->expire_at - TStime());
				if (*(p = expiretime + strlen(expiretime) - 1) == ' ')
					*p = 0;
			}

			sendto_one(sptr, ":%s %i %s :CmdShun %s@%s %s (set at: %s, "
				"%s%s, reason: %s)",
				me.name, RPL_TEXT, sptr->name,
				s->usermask, s->hostmask, s->commands, gmt,
				s->expire_at ? "expires in: " : "never expires",
				s->expire_at ? expiretime : "",
				s->reason ? s->reason : "no reason");
		}

		sendnotice(sptr, "End of /CMDSHUN list");
		return 0;
	}

	if (!IsULine(sptr) && !OPCanTKL(sptr))
	{
		sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, sptr->name);
		return 0;
	}

	/* add or remove? */
	mask = parv[1];
	add = 1;

	switch(*mask)
	{
		case '-':
			add = 0;
		case '+':
			mask++;
	}

	/* no mask & command(s)? */
	if (!*mask || IsNotParam(2))
	{
		sendnotice(sptr, "*** \2Usage:\2 /cmdshun [+]<user@host>|<nick> <command-list> [<expiretime>] :<reason>]");
		sendnotice(sptr, "***        /cmdshun -<user@host>|<nick> <command-list>");
		sendnotice(sptr, "***        <command-list>: a comma-separated list of one or more command names");
		sendnotice(sptr, "***        <expiretime>: a time value in the usual format");
		sendnotice(sptr, "*** \2Examples:\2 /cmdshun *user@*.host1 whois,whowas 2h5s This is your punishment");
		sendnotice(sptr, "***           /cmdshun -*user@*.host1 whois,whowas");
		return 0;
	}

	/* check if it's a hostmask and legal */
	if ((p = strchr(mask, '@')))
	{
		if (add && strchr(mask, '!'))
		{
			sendnotice(sptr, "[error] Cannot have ! in masks.");
			return 0;
		}

		/*
		 * I know buggy OS'es shouldn't be supported, but since I'm developing
		 * modules on a Linux with glibc-2.3.2, I don't want to see that
		 * strtok() bug again with /cmdshun +@ ... (see m_tkl_line if you don't
		 * know what I'm talking about). Also: I don't think my solution is
		 * slower.
		 *
		 * http://www.mail-archive.com/bug-glibc@gnu.org/msg01210.html
		 * http://lists.gnu.org/archive/html/bug-glibc/2001-02/msg00117.html
		 */

		*p		= 0;
		usermask	= mask;
		hostmask	= p + 1;

		if (!*hostmask)
		{
			if (!*usermask)
			{
				sendnotice(sptr, "[error] Syntax error in mask.");
				return 0;
			}
			
			/* convert "user@" to "*@user" */
			hostmask = usermask;
			usermask = "*";
		}
		else if (!*usermask)
			/* convert "@host" to "*@host" */
			usermask = "*";
	}
	else
	{
		aClient *acptr;

		/* It's seemingly a nick .. let's see if we can find the user */
		if (!(acptr = find_person(mask, NULL)))
		{
			sendto_one(sptr, rpl_str(ERR_NOSUCHNICK), me.name,
				sptr->name, mask);
			return 0;
		}

		usermask = "*";
		hostmask = acptr->user->realhost;
	}	

	/* do other checkings & build up params list */
	if (add)
	{
		struct tm	*t;
		OvrEntry	*o;
		CmdEntry	*c, *c2;
		TS		secs, now;
		int		i = 0;

		for (p = hostmask; *p; p++)
			if (*p != '*' && *p != '.' && *p != '?')
				i++;
		if (i < 4)
		{
			sendnotice(sptr, "*** [error] Too broad mask");
			return 0;
		}

		/* Prepare temporary cmdlist for m_cmdshunfwd */
		tmp_cmdlist = make_cmdlist(parv[2], &tmp_numofcmds);

		/* Check if valid command names & options are given */
		for (c = tmp_cmdlist; c; c = c->next)
		{
			o = c->cmd;

			if (!find_Cmd_quick(o->name))
			{
				/*
				 * It may happen that a command exists on
				 * a server while it doesn't on other servers
				 * of the network. I think the best is to
				 * accept non-existing commands only remotely.
				 * The goal is to not have desynchs between
				 * servers.
				 */

				sendnotice(sptr, "[error] No such command: "
					"%s", o->name);
				del_tmp_data();
				return 0;
			}
			if (!stricmp(o->name, "privmsg") ||
			    !stricmp(o->name, "notice"))
			{
				if (!c->opt)
					continue;
				if (c->opt == OPT_FLAG)
				{
					sendnotice(sptr, "[error] Syntax "
						"error after command '%s'",
						o->name);
					del_tmp_data();
					return 0;
				}
				if (c->opt != 'p' && c->opt != 'c')
				{
					sendnotice(sptr, "[error] Option '%c' "
						"is invalid for command '%s'",
						c->opt, o->name);
					del_tmp_data();
					return 0;
				}
			}
			else if (c->opt)
			{
				sendnotice(sptr, "[error] No options "
						"available for command %s",
						o->name);
				del_tmp_data();
				return 0;
			}
		}

		/* Check for duplicated commands */
		for (c = tmp_cmdlist; c; c = c->next)
			for (c2 = c->next; c2; c2 = c2->next)
				if (!stricmp(c->cmd->name, c2->cmd->name))
				{
					sendnotice(sptr, "[error] Command '%s' "
						"is already on the given list",
						c->cmd->name);
					del_tmp_data();
					return 0;
				}

		secs = 0;
		if (IsParam(3))
		{
			secs = atime(parv[3]);
			if (secs < 0)
			{
				sendnotice(sptr, "*** [error] The time you "
					"specified is out of range!");
				return 0;
			}
		}
		else if (DEFAULT_BANTIME)
			secs = DEFAULT_BANTIME;

		now = TStime();
		if (secs)
			secs += now;

		ircsprintf(set_at, "%ld", now);
		ircsprintf(expire_at, "%ld", secs);

		i = atol(expire_at);
		t = gmtime((TS *)&i);
		if (!t)
		{
			sendnotice(sptr, "*** [error] The time you specified "
				"is out of range");
			return 0;
		}

		params[PWhat]		= "+";
		params[PSetAt]		= set_at;
		params[PExpireAt]	= expire_at;
		params[PReason]		= IsParam(4) ? parv[4] : "";
	}
	else
	{
		params[PWhat]		= "-";
	}

	params[PFrom]		= me.name;
	params[PUserMask]	= usermask;
	params[PHostMask]	= hostmask;
	params[PCommands]	= parv[2];
	params[PSetBy]		= make_nick_user_host(sptr->name,
					sptr->user->username, GetHost(sptr));

	SetBy = sptr;
	m_cmdshunfwd(&me, &me, 9, params);
	SetBy = NULL;

	return 0;
}

/*
** m_cmdshunfwd
** ------------
**
**    Used by servers to distribute CmdShuns.
**
**    parv[0]: server name
**    parv[1]: + (add) / - (remove) / ! (sync)
**    parv[2]: usermask
**    parv[3]: hostmask
**    parv[4]: commands
**    parv[5]: setby (or removed by)
**    parv[6]: set_at
**    parv[7]: expire_at
**    parv[8]: reason (optional)
**
**    Note: NO PARAM CHECKING! You shouldn't really know anything
**          about the existence of this command. Otherwise
**          don't complain about server crashes!
*/

static int m_cmdshunfwd(aClient *cptr, aClient *sptr, int parc, char *parv[])
{
	char		*usermask = parv[PUserMask], *hostmask = parv[PHostMask];
	char		*commands = parv[PCommands];
	char		gmt[64], gmt2[64], what = *parv[PWhat];
	CmdEntry	*c, *c2;
	CmdShun		*s;

	if (!tmp_cmdlist)
		tmp_cmdlist = make_cmdlist(commands, &tmp_numofcmds);

	switch(what)
	{
		case '!':
		case '+':
		{
			struct irc_netmask	tmp;
			OvrEntry		*o;
			TS			setat = atol(parv[PSetAt]);
			TS			expireat = atol(parv[PExpireAt]);

			/* Preliminary checks on commands list */
			for (c = tmp_cmdlist; c; c = c->next)
				if (c->cmd->used >= 5000) /* just to be sure */
				{
					if (SetBy)
						sendnotice(SetBy, "[error] Too much "
							"cmdshuns for command %s",
							c->cmd->name);
					del_tmp_data();
					return 0;
				}

			/* Check if the cmdshun is added */
			for (s = CmdShunList; s; s = s->next)
			{
				if (stricmp(s->usermask, usermask) ||
				    stricmp(s->hostmask, hostmask))
					continue;

				for (c = s->cmdlist; c; c = c->next)
					for (c2 = tmp_cmdlist; c2; c2 = c2->next)
						if (!stricmp(c->cmd->name,
						    c2->cmd->name))
							goto loopend;
			}

			loopend: if (s) /* found */
			{
				/*
				 * I haven't got definite plans about allowing
				 * updates of CmdShuns. Currently the sync fight
				 * idea seems to work, and I'll leave it here
				 * until I work out the new method to handle
				 * duplicate entries.
				 */

				if (what != '!')
				{
					if (SetBy)
						sendnotice(SetBy, "[error] A cmdshun "
							"with mask %s@%s and one of "
							"the specified command(s) is "
							"already set",
							usermask, hostmask);

					del_tmp_data();
					return 0;
				}
				else /* sync problem, begin fight */
				{
					enum	Results { r_WeWon, r_TheyWon };	
					u_char	result = r_WeWon;

					if (s->numofcmds > tmp_numofcmds)
						/* we won */;
					else if (s->numofcmds < tmp_numofcmds)
						result = r_TheyWon;
					else if (s->set_at < setat)
						/* we won */;
					else if (s->set_at > setat)
						result = r_TheyWon;
					else if (s->expire_at > expireat)
						/* we won */;
					else if (s->expire_at < expireat)
						result = r_TheyWon;
					else if (stricmp(s->reason, parv[PReason]) < 0)
						/* we won */;
					else if (stricmp(s->reason, parv[PReason]) > 0)
						result = r_TheyWon;
					else if (stricmp(s->commands, commands) < 0)
						/* we won */;
					else if (stricmp(s->commands, commands) > 0)
						result = r_TheyWon;
					/* default:
						same cmdshuns -> we won */

					/* sendto_realops("cmdshun sync-time fight: result=[%s], ours=[%s@%s %s %ld %ld] theirs=[%s@%s %s %ld %ld]",
						result == r_TheyWon ? "r_TheyWon" : "r_WeWon",
						s->usermask, s->hostmask, s->commands, (long) s->set_at, (long) s->expire_at,
						usermask, hostmask, commands, setat, expireat); */

					if (result == r_TheyWon)
					{
						/* replace the cmdshun with the new one */
						del_cmdshun(s);
					}
					else
					{
						/* don't accept the new cmdshun */
						del_tmp_data();
						return 0;
					}
				}
			}

			/* Create a cmdshun entry */
			s = (CmdShun *) MyMallocEx(sizeof(CmdShun));
			s->usermask = strdup(usermask);
			s->hostmask = strdup(hostmask);
			s->commands = strdup(commands);
			s->setby = strdup(parv[PSetBy]);
			s->reason = *parv[PReason] ? strdup(parv[PReason]) : NULL;
			for (c = tmp_cmdlist; c; c = c->next)
			{
				o = c->cmd;
				o->temporary = 0;
				if (!o->used++)
				{
					o->ovr = AddOverride(o->name, ovr_cmd);
					AddListItem(o, OverridesList);
				}
			}
			s->cmdlist = tmp_cmdlist;
			s->numofcmds = tmp_numofcmds;
			s->set_at = setat;
			s->expire_at = expireat;
			if ((tmp.type = parse_netmask(hostmask, &tmp)) != HM_HOST)
			{
				s->netmask = MyMallocEx(sizeof(struct irc_netmask));
				bcopy(&tmp, s->netmask, sizeof(struct irc_netmask));
			}
			AddListItem(s, CmdShunList);
			tmp_cmdlist = NULL;

			/* Notification */
			if (what == '+')
			{
				strlcpy(gmt, asctime(gmtime(&s->set_at)), sizeof gmt);
				iCstrip(gmt);

				if(s->expire_at)
				{
					s->event = EventAddEx(MyMod, "cmdshunexpire",
						s->expire_at - TStime(), 1,
						event_cmdshunexpire, s);

					strlcpy(gmt2, asctime(gmtime(&s->expire_at)),
						sizeof gmt2);
					iCstrip(gmt2);

					snprintf(buf, sizeof buf, "Timed CmdShun added "
						"for %s@%s with command%s %s on %s GMT "
						"(from %s to expire at %s GMT: %s)",
						usermask, hostmask,
						s->cmdlist->next ? "s" : "",
						commands, gmt, parv[PSetBy], gmt2,
						s->reason ? s->reason : "no reason");
				}
				else
				{
					snprintf(buf, sizeof buf, "Permanent CmdShun "
						"added for %s@%s with command%s %s on "
						"%s GMT (from %s: %s)",
						usermask, hostmask,
						s->cmdlist->next ? "s" : "",
						commands, gmt, parv[PSetBy],
						s->reason ? s->reason : "no reason");
				}

				sendto_snomask(SNO_TKL, "*** %s", buf);
				ircd_log(LOG_TKL, "%s", buf);

			}

			sendto_serv_butone_token(NULL, parv[PFrom], MSG_CMDSHUNFWD,
				TOK_CMDSHUNFWD, "%c %s %s %s %s %s %s :%s",
				what, usermask, hostmask, commands, parv[PSetBy],
				parv[PSetAt], parv[PExpireAt], parv[PReason]);

			break;
		}

		case '-':
		{
			u_char required, found;

			/* Check if the cmdshun is added */
			for (s = CmdShunList; s; s = s->next)
			{
				if (stricmp(s->usermask, usermask) ||
				    stricmp(s->hostmask, hostmask))
					continue;

				required = found = 0;
				for (c = s->cmdlist; c; c = c->next)
				{
					required++;
					for (c2 = tmp_cmdlist; c2; c2 = c2->next)
						if (!stricmp(c->cmd->name,
						    c2->cmd->name))
							found++;
				}

				if (required == found)
					break;
			}

			if (!s)
			{
				if (SetBy)
					sendnotice(SetBy, "[error] No cmdshun "
						"is set with mask %s@%s and "
						"command%s %s",
						usermask, hostmask,
						strchr(commands, ',') ? "s" : "",
						commands);

				del_tmp_data();
				return 0;
			}

			/* Notification */
			strlcpy(gmt, asctime(gmtime(&s->set_at)), sizeof gmt);
			iCstrip(gmt);

			snprintf(buf, sizeof buf, "%s removed CmdShun %s@%s set "
				"with command%s %s (at %s - reason: %s)",
				parv[PSetBy], usermask, hostmask,
				s->cmdlist->next ? "s" : "", commands,
				gmt, s->reason ? s->reason : "no reason");

			sendto_snomask(SNO_TKL, "*** %s", buf);
			ircd_log(LOG_TKL, "%s", buf);

			/* Delete cmdshun entry & temporary stuff */
			del_cmdshun(s);
			del_tmp_data();

			sendto_serv_butone_token(NULL, parv[PFrom],
				MSG_CMDSHUNFWD, TOK_CMDSHUNFWD,
				"- %s %s %s %s",
				usermask, hostmask, commands, parv[PSetBy]);

			break;
		}
	}

	return 0;
}
