All pastes #462803 Raw Edit

TheShining

public text v1 · immutable
#462803 ·published 2007-04-28 15:45 UTC
rendered paste body
/*

 * ==================================================================

 * Filename:             m_spy.c

 * Description:          Real-time spying

 * Written by:		 AngryWolf <angrywolf@flashmail.com>

 * The idea was born by: Toxyc <toxyc@freemail.hu>

 * ==================================================================

 */



#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



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

// Definitions & macros

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



#define MSG_SPY 		"OKUMA"

#define TOK_SPY 		"SPY"

#define MSG_SPYCHANNEL 		"SPYCHAN"

#define TOK_SPYCHANNEL		"SPC"

#define MSG_SPYSEND 		"SPYSEND"

#define TOK_SPYSEND 		"SPE"

#define MSG_SPYCHSEND 		"SPYCHSEND"

#define TOK_SPYCHSEND 		"SPT"

#define MSG_SPYFORWARD 		"SPYFORWARD"

#define TOK_SPYFORWARD 		"SPF"

#define MSG_SPYCHFORWARD 	"SPYCHFORWARD"

#define TOK_SPYCHFORWARD 	"SPG"

#define MSG_SPYSTATS 		"SPYSTATS"

#define TOK_SPYSTATS 		"SPS"



#define MyMod			SpyModInfo->handle

#define FlagSpy			'X'

#define LockFlag		'@'

#define LockFlagStr		"@"



#define SPY_NOTICE		":%s NOTICE %s :*** "

#define SPY_LINE		"=================================================="



#define ircfree(x)		if (x) free(x)

#define ircstrdup(x,y)		if (x) MyFree(x); if (!y) x = NULL; else x = strdup(y)

#define DelSnomask(x)		if (x) SnomaskDel(x); x = NULL

#define DelHook(x)		if (x) HookDel(x); x = NULL

#define DelCommand(x)		if (x) CommandDel(x); x = NULL





#define IsParam(x)		(parc > (x) && !BadPtr(parv[(x)]))

#define IsValidParam(x)		(parc > (x) && !BadPtr(parv[(x)]) && *parv[(x)] != '*')



#define AddLock(lock)		(lock ? LockFlagStr : "")

#define GetLock(str)		(*str == LockFlag ? 1 : 0)

#define SplitLock(lock, str)	del_prefix(str, &(lock), &(str))

#define MergeLock(lock, str)	AddLock(lock), str

#define MergeLockOpt(lock, str)	str ? AddLock(lock) : "", str ? str : "*"

#define MergeLockEmp(lock, str)	str ? AddLock(lock) : "", str ? str : ""



#define SpyForward5(cptr, cmd, who, nick1, nick2, channel, lock1, lock2) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYFORWARD, TOK_SPYFORWARD, \

		"%s %s %s%s %s%s %s", \

		cmd, who, MergeLock(lock1, nick1), MergeLockOpt(lock2, nick2), \

		channel ? channel : "*")



#define SpyForward4(cptr, cmd, who, nick1, nick2, lock1, lock2) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYFORWARD, TOK_SPYFORWARD, \

		"%s %s %s%s %s%s", \

		cmd, who, MergeLock(lock1, nick1), MergeLockOpt(lock2, nick2))



#define SpyForward3(cptr, cmd, who, nick1, lock1) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYFORWARD, TOK_SPYFORWARD, \

		"%s %s %s%s", \

		cmd, who, MergeLock(lock1, nick1))



#define SpyForward2(cptr, cmd, who) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYFORWARD, TOK_SPYFORWARD, \

		"%s %s", \

		cmd, who)



#define SpyForward1(cptr, cmd) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYFORWARD, TOK_SPYFORWARD, \

		"%s", \

		cmd)



#define SpyChForward4(cptr, cmd, who, channel, sendto, lock) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYCHFORWARD, TOK_SPYCHFORWARD, \

		"%s %s %s%s %s", \

		cmd, who, MergeLock(lock, channel), sendto ? sendto : "*")



#define SpyChForward3(cptr, cmd, who, channel, lock) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYCHFORWARD, TOK_SPYCHFORWARD, \

		"%s %s %s%s", \

		cmd, who, MergeLock(lock, channel))



#define SpyChForward2(cptr, cmd, who) \

		sendto_serv_butone_token(cptr, me.name, MSG_SPYCHFORWARD, TOK_SPYCHFORWARD, \

		"%s %s", \

		cmd, who)



typedef struct _spystruct	SpyStruct;

typedef struct _spychstruct	SpyChStruct;



struct _spystruct

{

	SpyStruct	*prev, *next;



	aClient		*who;		/* Who is spying */

	char		*nick1;		/* First nick to spy */

	char		*nick2;		/* Second nick to spy (may be NULL) */

	char		*chname;	/* Where to put messages (may be NULL) */

	u_int		lock1:1;		/* 1 if nick1 is locked, otherwise 0 */

	u_int		lock2:1;		/* 1 if nick2 is locked, otherwise 0 */

};



struct _spychstruct

{

	SpyChStruct	*prev, *next;



	aClient		*who;		/* Who is spying */

	char		*channel;	/* Channel to be spyed */

	char		*sendto;	/* Where to put messages (may be NULL) */

	u_int		lock;		/* True if nick1 is locked, otherwise 0 */

};



static SpyStruct	*FindSpyItem1(aClient *acptr, char *nick);

static SpyStruct	*FindSpyItem2(aClient *acptr, char *nick1, char *nick2);

static SpyChStruct	*FindSpyChItem(aClient *acptr, char *channel);

static void		AddSpyItem(aClient *acptr, char *nick1, char *nick2, char *chname, u_int lock1, u_int lock2);

static void		AddSpyChItem(aClient *acptr, char *channel, char *sendto, u_int lock);

static void		DelSpyItem(SpyStruct *s);

static void		DelSpyChItem(SpyChStruct *s);

static unsigned int	spy_paramcheck(aClient *cptr, int parc, char *parv[], char **nick1, char **nick2, char **channel, u_int *lock1, u_int *lock2);

static unsigned int	spych_paramcheck(aClient *cptr, int parc, char *parv[], char **channel, char **sendto, u_int *lock);

static void		spy_send_client(aClient *acptr, int notice, char *nick1, char *nick2, char *text);

static void		spy_send_channel(aClient *cptr, char *channel, int notice, char *nick1, char *nick2, char *text);

static void		spych_send_client(aClient *acptr, int notice, char *nick, char *channel, char *text);

static void		spych_send_channel(aClient *cptr, char *sendto, int notice, char *nick, char *channel, char *text);

static void		del_prefix(char *str, u_int *lock, char **newstr);



static Command		*AddCommand(char *msg, char *token, int (*func)());

DLLFUNC int		m_spy(aClient *cptr, aClient *sptr, int parc, char *parv[]);

DLLFUNC int		m_spychannel(aClient *cptr, aClient *sptr, int parc, char *parv[]);

DLLFUNC int		m_spysend(aClient *cptr, aClient *sptr, int parc, char *parv[]);

DLLFUNC int		m_spychsend(aClient *cptr, aClient *sptr, int parc, char *parv[]);

DLLFUNC int		m_spyforward(aClient *cptr, aClient *sptr, int parc, char *parv[]);

DLLFUNC int		m_spychforward(aClient *cptr, aClient *sptr, int parc, char *parv[]);

DLLFUNC int		m_spystats(aClient *cptr, aClient *sptr, int parc, char *parv[]);



DLLFUNC char		*spy_on_privmsg(aClient *, aClient *, aClient *, char *, int);

DLLFUNC char		*spy_on_chanmsg(aClient *, aClient *, aChannel *, char *, int);

DLLFUNC int		spy_on_quit(aClient *, char *);

DLLFUNC int		spy_on_remote_quit(aClient *, char *);

DLLFUNC int		spy_on_server_connect(aClient *);

DLLFUNC int		spy_on_local_nickchange(aClient *, char *);

DLLFUNC int		spy_on_remote_nickchange(aClient *, aClient *, char *);

DLLFUNC int		spy_on_channel_destroy(aChannel *);



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

// Module header

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



ModuleHeader MOD_HEADER(m_spy)

  = {

	"spy",

	"$Id: m_spy.c,v 3.6 2003/12/01 18:31:45 angrywolf Exp $",

	"real-time spying",

	"3.2-b8-1",

	NULL 

    };



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

// Main declarations

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



static char *SpyHelp[] =

{

    SPY_NOTICE	"\2Usage:\2",

    SPY_NOTICE	"/spy add <nick1> [<nick2> [<#channel>]]",

    SPY_NOTICE	"/spy del <nick1> [<nick2>]",

    SPY_NOTICE	"/spy list [<nick1> [<nick2>]] [<#channel>]",

    SPY_NOTICE	"/spy clear",

    SPY_NOTICE	"\2Examples:\2",

    SPY_NOTICE	"/spy add AngryWolf Toxyc #secret",

    SPY_NOTICE	"/spy del AngryWolf",

    SPY_NOTICE	"/spy list #secret",

    NULL

};



static char *SpyChHelp[] =

{

    SPY_NOTICE	"\2Usage:\2",

    SPY_NOTICE	"/spychan add <#channel> [<#sendto>]]",

    SPY_NOTICE	"/spychan del <#channel>",

    SPY_NOTICE	"/spychan list [<#channel>]",

    SPY_NOTICE	"/spychan clear",

    SPY_NOTICE	"\2Examples:\2",

    SPY_NOTICE	"/spychan add #warez #spy",

    SPY_NOTICE	"/spychan del #warez",

    SPY_NOTICE	"/spychan list",

    NULL

};



ModuleInfo		*SpyModInfo;

Command			*CmdSpy, *CmdSpychannel, *CmdSpysend, *CmdSpychsend;

Command			*CmdSpyforward, *CmdSpychforward, *CmdSpystats;

Snomask			*SpySnomask;

long			SNO_SPY;



static Hook		*HookPrivMsg;

static Hook		*HookChanMsg;

static Hook		*HookQuit;

static Hook		*HookRemoteQuit;

static Hook		*HookServConn;

static Hook		*HookLocalNick;

static Hook		*HookRemoteNick;

static Hook		*HookChanDest;



static SpyStruct	*SpyList = NULL;

static SpyChStruct	*SpyChList = NULL;



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

// Module initalization, loading and unloading

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



static Command *AddCommand(char *msg, char *token, int (*func)())

{

	Command *cmd;



	if (CommandExists(msg))

    	{

		config_error("Command %s already exists", msg);

		return NULL;

    	}

    	if (CommandExists(token))

	{

		config_error("Token %s already exists", token);

		return NULL;

    	}



	cmd = CommandAdd(MyMod, msg, token, func, MAXPARA, 0);



#ifndef _WIN32

	if (ModuleGetError(MyMod) != MODERR_NOERROR || !cmd)

#else

	if (!cmd)

#endif

	{

#ifndef _WIN32

		config_error("Error adding command %s: %s", msg,

			ModuleGetErrorStr(MyMod));

#else

		config_error("Error adding command %s", msg);

#endif

		return NULL; /* just to be sure */

	}



	return cmd;

}



static Snomask *AddSnomask(char flag, int (*allowed)(aClient *sptr, int what), long *mode)

{

        Snomask *s;



        *mode = 0;

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



#ifndef _WIN32

        if ((ModuleGetError(MyMod) != MODERR_NOERROR) || !s)

#else

        if (!s)

#endif

        {

#ifndef _WIN32

                sendto_realops("[\2m_spy\2] Error adding snomask %c: %s",

                        flag, ModuleGetErrorStr(MyMod));

#else

                sendto_realops("[\2m_spy\2] Error adding snomask %c",

                        flag);

#endif

                return NULL;

        }



        return s;

}



DLLFUNC int MOD_INIT(m_spy)(ModuleInfo *modinfo)

{

	int ret = MOD_SUCCESS;



	SpyModInfo	= modinfo;



	HookPrivMsg	= HookAddPCharEx(MyMod, HOOKTYPE_USERMSG, spy_on_privmsg);

	HookChanMsg	= HookAddPCharEx(MyMod, HOOKTYPE_CHANMSG, spy_on_chanmsg);

	HookQuit	= HookAddEx(MyMod, HOOKTYPE_LOCAL_QUIT, spy_on_quit);

	HookRemoteQuit	= HookAddEx(MyMod, HOOKTYPE_REMOTE_QUIT, spy_on_remote_quit);

	HookServConn	= HookAddEx(MyMod, HOOKTYPE_SERVER_CONNECT, spy_on_server_connect);

	HookLocalNick	= HookAddEx(MyMod, HOOKTYPE_LOCAL_NICKCHANGE, spy_on_local_nickchange);

	HookRemoteNick	= HookAddEx(MyMod, HOOKTYPE_REMOTE_NICKCHANGE, spy_on_remote_nickchange);

	HookChanDest	= HookAddEx(MyMod, HOOKTYPE_CHANNEL_DESTROY, spy_on_channel_destroy);



	CmdSpy		= AddCommand(MSG_SPY, TOK_SPY, m_spy);

	CmdSpychannel	= AddCommand(MSG_SPYCHANNEL, TOK_SPYCHANNEL, m_spychannel);

	CmdSpysend	= AddCommand(MSG_SPYSEND, TOK_SPYSEND, m_spysend);

	CmdSpychsend	= AddCommand(MSG_SPYCHSEND, TOK_SPYCHSEND, m_spychsend);

	CmdSpyforward	= AddCommand(MSG_SPYFORWARD, TOK_SPYFORWARD, m_spyforward);

	CmdSpychforward	= AddCommand(MSG_SPYCHFORWARD, TOK_SPYCHFORWARD, m_spychforward);

	CmdSpystats	= AddCommand(MSG_SPYSTATS, TOK_SPYSTATS, m_spystats);



	SpySnomask	= AddSnomask(FlagSpy, umode_allow_opers, &SNO_SPY);



        if (!SpySnomask || !CmdSpy || !CmdSpychannel || !CmdSpysend || !CmdSpychsend)

		ret = MOD_FAILED;

	if (!CmdSpyforward || !CmdSpychforward || !CmdSpystats)

		ret = MOD_FAILED;



	return ret;

}



DLLFUNC int MOD_LOAD(m_spy)(int module_load)

{

	return MOD_SUCCESS;

}



DLLFUNC int MOD_UNLOAD(m_spy)(int module_unload)

{

	SpyStruct	*s;

	SpyChStruct	*sc;

	ListStruct 	*next;



	DelSnomask(SpySnomask);



	DelCommand(CmdSpystats);

	DelCommand(CmdSpychforward);

	DelCommand(CmdSpyforward);

	DelCommand(CmdSpychsend);

	DelCommand(CmdSpysend);

	DelCommand(CmdSpychannel);

	DelCommand(CmdSpy);



	DelHook(HookChanDest);

	DelHook(HookRemoteNick);

	DelHook(HookLocalNick);

	DelHook(HookServConn);

	DelHook(HookRemoteQuit);

	DelHook(HookQuit);

	DelHook(HookChanMsg);

	DelHook(HookPrivMsg);



	for (s = SpyList; s; s = (SpyStruct *) next)

	{

		next = (ListStruct *) s->next;

		DelSpyItem(s);

	}

	for (sc = SpyChList; sc; sc = (SpyChStruct *) next)

	{

		next = (ListStruct *) sc->next;

		DelSpyChItem(sc);

	}



	return MOD_SUCCESS;

}



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

// Functions for nicknames, channel names and prefixes

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



static void del_prefix(char *str, u_int *lock, char **newstr)

{

	*lock = 0;



	if (*str == LockFlag)

	{

		*lock = 1;

		str++;

	}



	*newstr = str;

}



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

// Misc functions

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



static SpyStruct *FindSpyItem1(aClient *acptr, char *nick)

{

	SpyStruct *s;

	

	for (s = SpyList; s; s = s->next)

		if (s->who == acptr && !s->nick2 && !strcasecmp(s->nick1, nick))

			break;



	return s;

}



static SpyStruct *FindSpyItem2(aClient *acptr, char *nick1, char *nick2)

{

	SpyStruct *s;

	

	for (s = SpyList; s; s = s->next)

		if (s->who == acptr && !strcasecmp(s->nick1, nick1)

		  && (s->nick2 && !strcasecmp(s->nick2, nick2)))

			break;



	return s;

}



static SpyChStruct *FindSpyChItem(aClient *acptr, char *channel)

{

	SpyChStruct *sc;

	

	for (sc = SpyChList; sc; sc = sc->next)

		if (sc->who == acptr && !strcasecmp(sc->channel, channel))

			break;



	return sc;

}



static void AddSpyItem(aClient *acptr, char *nick1, char *nick2, char *chname, u_int lock1, u_int lock2)

{

	SpyStruct	*s;



	s		= (SpyStruct *) MyMalloc(sizeof(SpyStruct));

	s->who		= acptr;

	s->nick1	= strdup(nick1);

	s->nick2	= BadPtr(nick2) ? NULL : strdup(nick2);

	s->chname	= BadPtr(chname) ? NULL : strdup(chname);

	s->lock1	= lock1;

	s->lock2	= lock2;



	AddListItem(s, SpyList);

}



static void AddSpyChItem(aClient *acptr, char *channel, char *sendto, u_int lock)

{

	SpyChStruct	*sc;



	sc		= (SpyChStruct *) MyMalloc(sizeof(SpyChStruct));

	sc->who		= acptr;

	sc->channel	= strdup(channel);

	sc->sendto	= BadPtr(sendto) ? NULL : strdup(sendto);

	sc->lock	= lock;



	AddListItem(sc, SpyChList);

}



static void DelSpyItem(SpyStruct *s)

{

	DelListItem(s, SpyList);

	MyFree(s->nick1);

	ircfree(s->nick2);

	ircfree(s->chname);

	MyFree(s);

}



static void DelSpyChItem(SpyChStruct *sc)

{

	DelListItem(sc, SpyChList);

	MyFree(sc->channel);

	ircfree(sc->sendto);

	MyFree(sc);

}



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

// display_text: a general function to display two or more lines

//     of text to a user

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



static void display_text(aClient *cptr, char *text[])

{

	char **pp;



	for (pp = text; *pp; pp++)

    		sendto_one(cptr, *pp, me.name, cptr->name);

	/* let user take 7 seconds to read it! */

        if (MyClient(cptr))

                cptr->since += 7;

}



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

// spy_paramcheck: checks for validity of /spy parameters

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



static unsigned int spy_paramcheck(aClient *cptr, int parc, char *parv[],

	char **nick1, char **nick2, char **channel, u_int *lock1, u_int *lock2)

{

	*nick1		= NULL;

	*nick2		= NULL;

	*channel	= NULL;

	*lock1		= 0;

	*lock2		= 0;



	if (IsParam(2))

	{

		if (*parv[2] == '#')

			*channel = parv[2];

		else

			*nick1	= parv[2];

	}

	if (IsParam(3))

	{

		if (*channel)

			return 0;

		else if (*parv[3] == '#')

		{

			*nick2		= NULL;

			*channel	= parv[3];

		}

		else

			*nick2		= parv[3];

	}

	if (IsParam(4))

	{

		if (*channel)

			return 0;

		*channel = parv[4];

	}



	if (*nick1)

		SplitLock(*lock1, *nick1);

	if (*nick2)

		SplitLock(*lock2, *nick2);



	return 1;

}



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

// spych_paramcheck: checks for validity of /spychannel parameters

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



static unsigned int spych_paramcheck(aClient *cptr, int parc, char *parv[],

	char **channel, char **sendto, u_int *lock)

{

	*channel	= NULL;

	*sendto		= NULL;

	*lock		= 0;



	if (!IsParam(2))

		return 0;



	*channel = parv[2];



	if (IsParam(3))

		*sendto = parv[3];



	if (*channel)

		SplitLock(*lock, *channel);



	return 1;

}



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

// spy_send_client & spy_send_channel: handle sending users's private

//	messages to clients & channels

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



static void spy_send_client(aClient *acptr, int notice, char *nick1, char *nick2, char *text)

{

	if (MyClient(acptr))

    		sendto_one(acptr, ":IRC!IRC@%s PRIVMSG %s :[%s %c> %s] %s",

	            	me.name, acptr->name,

			nick1, (notice ? '-' : '>'), nick2, text);

	else

		sendto_one(acptr->from, "%s %s %c %s %s :%s",

			IsToken(acptr->from) ? TOK_SPYSEND : MSG_SPYSEND,

			acptr->name, (notice ? 'N' : 'P'),

			nick1, nick2, text);

}



static void spy_send_channel(aClient *cptr, char *channel, int notice, char *nick1, char *nick2, char *text)

{

	aChannel	*chptr;



	if ((chptr = find_channel(channel, NullChn)) == NullChn)

		return;



        sendto_channel_butserv(chptr, &me, ":IRC!IRC@%s PRIVMSG %s :[%s %c> %s] %s",

                me.name, chptr->chname,

		nick1, (notice ? '-' : '>'), nick2, text);



	sendto_serv_butone_token(cptr, me.name, MSG_SPYSEND, TOK_SPYSEND,

		"%s %c %s %s :%s",

		chptr->chname, (notice ? 'N' : 'P'),

		nick1, nick2, text);

}



static void spych_send_client(aClient *acptr, int notice, char *nick, char *channel, char *text)

{

	if (MyClient(acptr))

    		sendto_one(acptr, ":IRC!IRC@%s PRIVMSG %s :[%s] %c%s%c %s",

	            	me.name, acptr->name, channel,

			(notice ? '-' : '<'), nick, (notice ? '-' : '>'), text);

	else

		sendto_one(acptr->from, "%s %s %c %s %s :%s",

			IsToken(acptr->from) ? TOK_SPYCHSEND : MSG_SPYCHSEND,

			acptr->name, (notice ? 'N' : 'P'),

			nick, channel, text);

}



static void spych_send_channel(aClient *cptr, char *sendto, int notice, char *nick, char *channel, char *text)

{

	aChannel	*chptr;



	if ((chptr = find_channel(sendto, NullChn)) == NullChn)

		return;



        sendto_channel_butserv(chptr, &me, ":IRC!IRC@%s PRIVMSG %s :[%s] %c%s%c %s",

                me.name, chptr->chname, channel,

		(notice ? '-' : '<'), nick, (notice ? '-' : '>'), text);



	sendto_serv_butone_token(cptr, me.name, MSG_SPYCHSEND, TOK_SPYCHSEND,

		"%s %c %s %s :%s",

		chptr->chname, (notice ? 'N' : 'P'),

		nick, channel, text);

}



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

// Events

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



DLLFUNC int spy_on_quit(aClient *sptr, char *comment)

{

	SpyStruct	*s;

	SpyChStruct	*sc;

	ListStruct 	*next;



	// sendto_realops("(spy_on_quit) sptr->name=[%s]", sptr->name);



	for (s = SpyList; s; s = (SpyStruct *) next)

	{

		next = (ListStruct *) s->next;

		if (s->who == sptr)

			DelSpyItem(s);

		else if (!s->lock1 && !strcasecmp(s->nick1, sptr->name))

			DelSpyItem(s);

		else if (s->nick2 && !s->lock2 && !strcasecmp(s->nick2, sptr->name))

			DelSpyItem(s);

	}

	for (sc = SpyChList; sc; sc = (SpyChStruct *) next)

	{

		next = (ListStruct *) sc->next;

		if (sc->who == sptr)

			DelSpyChItem(sc);

	}



	return 0;

}



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



DLLFUNC int spy_on_remote_quit(aClient *sptr, char *comment)

{

	SpyStruct	*s;

	SpyChStruct	*sc;

	ListStruct 	*next;



	// sendto_realops("(spy_on_remote_quit) sptr->name=[%s]", sptr->name);



	for (s = SpyList; s; s = (SpyStruct *) next)

	{

		next = (ListStruct *) s->next;

		if (s->who == sptr)

			DelSpyItem(s);

		else if (!s->lock1 && !strcasecmp(s->nick1, sptr->name))

			DelSpyItem(s);

		else if (s->nick2 && !s->lock2 && !strcasecmp(s->nick2, sptr->name))

			DelSpyItem(s);

	}

	for (sc = SpyChList; sc; sc = (SpyChStruct *) next)

	{

		next = (ListStruct *) sc->next;

		if (sc->who == sptr)

			DelSpyChItem(sc);

	}



	return 0;

}



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



DLLFUNC int spy_on_local_nickchange(aClient *sptr, char *nick)

{

	SpyStruct	*s;



	for (s = SpyList; s; s = s->next)

	{

		if (!s->lock1 && !strcasecmp(s->nick1, sptr->name))

		{

			ircstrdup(s->nick1, nick);

		}

		if (s->nick2 && !s->lock2 && !strcasecmp(s->nick2, sptr->name))

		{

			ircstrdup(s->nick2, nick);

		}

	}



	return 0;

}



DLLFUNC int spy_on_remote_nickchange(aClient *cptr, aClient *sptr, char *nick)

{

	SpyStruct	*s;



	for (s = SpyList; s; s = s->next)

	{

		if (!s->lock1 && !strcasecmp(s->nick1, sptr->name))

		{

			ircstrdup(s->nick1, nick);

		}

		if (s->nick2 && !s->lock2 && !strcasecmp(s->nick2, sptr->name))

		{

			ircstrdup(s->nick2, nick);

		}

	}



	return 0;

}



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



DLLFUNC int spy_on_channel_destroy(aChannel *chptr)

{

	SpyChStruct	*sc;

	ListStruct	*next;



	for (sc = SpyChList; sc; sc = (SpyChStruct *) next)

	{

		next = (ListStruct *) sc->next;



		if (!sc->lock && !strcasecmp(sc->channel, chptr->chname))

			DelSpyChItem(sc);

	}



	return 0;

}



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



DLLFUNC int spy_on_server_connect(aClient *sptr)

{

	SpyStruct	*s;

	SpyChStruct	*sc;



	// sendto_realops("(spy_on_server_connect) sptr->name=[%s]", sptr->name);



	/* TODO: optimize this! */

	sendto_one(sptr, ":%s %s CONNECT",

		me.name, (IsToken(sptr) ? TOK_SPYFORWARD : MSG_SPYFORWARD));

	sendto_one(sptr, ":%s %s CONNECT",

		me.name, (IsToken(sptr) ? TOK_SPYCHFORWARD : MSG_SPYCHFORWARD));



	for (s = SpyList; s; s = s->next)

	{

		sendto_one(sptr, ":%s %s ADD2 %s %s%s %s%s %s",

			me.name,

			(IsToken(sptr) ? TOK_SPYFORWARD : MSG_SPYFORWARD),

			s->who->name,

			MergeLock(s->lock1, s->nick1),

			MergeLockOpt(s->lock2, s->nick2),

			s->chname ? s->chname : "*");

	}

	for (sc = SpyChList; sc; sc = sc->next)

	{

		sendto_one(sptr, ":%s %s ADD2 %s %s%s %s",

			me.name,

			(IsToken(sptr) ? TOK_SPYCHFORWARD : MSG_SPYCHFORWARD),

			sc->who->name,

			MergeLock(sc->lock, sc->channel),

			sc->sendto ? sc->sendto : "*");

	}



	return 0;

}



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



DLLFUNC char *spy_on_privmsg(aClient *cptr, aClient *sptr, aClient *acptr, char *text, int notice)

{

	SpyStruct	*s;

	aChannel	*chptr;

	char		*nick1, *nick2;



	if (!MyClient(sptr))

		return text;



	/* First do some sorting */



	if (smycmp(sptr->name, acptr->name) > 0)

	{

    		nick1 = acptr->name;

    		nick2 = sptr->name;

	}

	else

	{

		nick1 = sptr->name;

		nick2 = acptr->name;

	}



	for (s = SpyList; s; s = s->next)

	{

		if (

			/* spy every messages sent and received by one client */

			(

				!s->nick2 &&

				(!strcasecmp(s->nick1, sptr->name) || !strcasecmp(s->nick1, acptr->name))

			) ||

			/* spy messages between two specific users */

			(

				!strcasecmp(s->nick1, nick1) && !strcasecmp(s->nick2, nick2)

			)

		   )

		{

			if (s->chname)

					spy_send_channel(&me, s->chname, notice,

						sptr->name, acptr->name, text);

				else

					spy_send_client(s->who, notice,

						sptr->name, acptr->name, text);

		}

	}



	return text;

}



DLLFUNC char *spy_on_chanmsg(aClient *cptr, aClient *sptr, aChannel *chptr, char *text, int notice)

{

	SpyChStruct	*sc;



	if (!MyClient(sptr))

		return text;



	for (sc = SpyChList; sc; sc = sc->next)

	{

		if (!strcasecmp(chptr->chname, sc->channel))

		{

			if (sc->sendto)

				spych_send_channel(&me, sc->sendto, notice,

					sptr->name, chptr->chname, text);

			else

				spy_send_client(sc->who, notice,

					sptr->name, chptr->chname, text);

		}

	}



	return text;

}



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

// m_spy: Adds, removes or lists nicknames in spy list

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



DLLFUNC int m_spy(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	SpyStruct	*s;

	char		*tmp = NULL, *cmd;

	char		*nick1, *nick2, *channel;

	u_int		lock1, lock2;



        if (!MyClient(cptr) || !IsOper(cptr) || !IsSAdmin(cptr))

	{

        	sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);

		return -1;

	}



	cmd = IsParam(1) ? parv[1] : NULL;



        if (!cmd)

	{

		display_text(cptr, SpyHelp);

		return -1;

	}



	/* ADD */

	if (!strcasecmp(cmd, "add"))

	{

		if (!spy_paramcheck(cptr, parc, parv, &nick1, &nick2, &channel, &lock1, &lock2))

		{

			display_text(cptr, SpyHelp);

			return -1;

		}

		if (!nick1 || IsParam(5))

		{

			display_text(cptr, SpyHelp);

			return -1;

		}

		if (nick2 && !strcasecmp(nick1, nick2))

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s %s: Nicks cannot be the same",

	            		me.name, sptr->name,

				nick1, nick2);

			return -1;

		}

		if (channel && *channel != '#')

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s: Invalid channel name",

	            		me.name, sptr->name, channel);

			return -1;

		}

		if (nick2 && (smycmp(nick1, nick2) > 0))

		{

			tmp	= nick1;

			nick1	= nick2;

			nick2	= tmp;

		}

		if ((nick2 && FindSpyItem2(sptr, nick1, nick2)) || (!nick2 && FindSpyItem1(sptr, nick1)))

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s %s: Already on your private spy list",

	            		me.name, sptr->name,

				nick1, nick2 ? nick2 : "<irc>");

			return -1;

		}

		AddSpyItem(sptr, nick1, nick2, channel, lock1, lock2);

    		sendto_one(sptr, ":%s NOTICE %s :*** %s %s: Added to your private spy list",

	            	me.name, sptr->name,

			nick1, nick2 ? nick2 : "<irc>");

		SpyForward5(&me, "ADD", sptr->name, nick1, nick2, channel, lock1, lock2);

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPY ADD %s%s" "%s" "%s%s" "%s" "%s",

			sptr->name, MergeLock(lock1, nick1),

			nick2 ? " " : "", MergeLockEmp(lock2, nick2),

			channel ? " " : "", channel ? channel : "");

	}



	/* DEL */

	else if (!strcasecmp(cmd, "del"))

	{

		if (!spy_paramcheck(cptr, parc, parv, &nick1, &nick2, &channel, &lock1, &lock2))

		{

			display_text(cptr, SpyHelp);

			return -1;

		}

		if (!nick1 || channel || IsParam(4))

		{

			display_text(cptr, SpyHelp);

			return -1;

		}

		if (nick2 && (smycmp(nick1, nick2) > 0))

		{

			tmp	= nick1;

			nick1	= nick2;

			nick2	= tmp;

		}

		if ((nick2 && !(s = FindSpyItem2(sptr, nick1, nick2))) || (!nick2 && !(s = FindSpyItem1(sptr, nick1))))

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s %s: Not yet on your private spy list",

	            		me.name, sptr->name,

				nick1, nick2 ? nick2 : "<irc>");

			return -1;

		}

		SpyForward4(&me, "DEL", s->who->name, s->nick1, s->nick2, 0, 0);

    		sendto_one(sptr, ":%s NOTICE %s :*** %s %s: Deleted from your private spy list",

	            	me.name, sptr->name,

			nick1, nick2 ? nick2 : "<irc>");

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPY DEL %s%s%s",

			sptr->name, s->nick1, s->nick2 ? " " : "", s->nick2 ? s->nick2 : "");

		DelSpyItem(s);

	}



	/* LIST */

	else if (!strcasecmp(cmd, "list"))

	{

		enum ListType { LT_n1n2c, LT_n1n2, LT_n1c, LT_n1, LT_c, LT_none };

		unsigned int found = 0, type = 0, show = 0, empty = 1;



		if (!spy_paramcheck(cptr, parc, parv, &nick1, &nick2, &channel, &lock1, &lock2))

		{

			display_text(cptr, SpyHelp);

			return -1;

		}

		if (channel)

		{

			if (nick2)

				type = LT_n1n2c;

			else if (nick1)

				type = LT_n1c;

			else

				type = LT_c;

		}

		else if (nick2)

			type = LT_n1n2;

		else if (nick1)

			type = LT_n1;

		else

			type = LT_none;



		if (nick1 && nick2 && (smycmp(nick1, nick2) > 0))

		{

			tmp	= nick1;

			nick1	= nick2;

			nick2	= tmp;

		}



		for (s = SpyList; s; s = s->next)

			if (s->who == sptr)

			{

				empty	= 0;

				show	= 0;



				switch (type)

				{

					case LT_n1n2c:

						if (

							s->nick2 &&

							s->chname &&

							!strcasecmp(s->nick1, nick1) &&

							!strcasecmp(s->nick2, nick2) &&

							!strcasecmp(s->chname, channel)

						   )

							show = 1;

						break;

					case LT_n1n2:

						if (

							s->nick2 &&

							!strcasecmp(s->nick1, nick1) &&

							!strcasecmp(s->nick2, nick2)

						   )

							show = 1;

						break;

					case LT_n1c:

						if (

							s->chname &&

							!strcasecmp(s->nick1, nick1) &&

							!strcasecmp(s->chname, channel)

						   )

							show = 1;

						break;

					case LT_n1:

						if (!strcasecmp(s->nick1, nick1))

							show = 1;

						break;

					case LT_c:

						if (s->chname && !strcasecmp(s->chname, channel))

							show = 1;

						break;

					default:

						show = 1;

				}



				if (show)

				{

					found++;

				

    					sendto_one(sptr, ":%s NOTICE %s :*** %s%s %s%s %s",

	            				me.name, sptr->name,

						MergeLock(s->lock1, s->nick1),

						MergeLockOpt(s->lock2, s->nick2),

						s->chname ? s->chname : "<irc>");

				}

			}



		if (!found)

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s",

	    			me.name, sptr->name,

				empty ? "Your spy list is empty" : "No matching entries");

		}

		else

		{

			sendto_one(sptr, ":%s NOTICE %s :*** " SPY_LINE,

				me.name, sptr->name);

			sendto_one(sptr, ":%s NOTICE %s :*** %d entr%s",

				me.name, sptr->name,

				found, found > 1 ? "ies" : "y");

		}

	}



	/* CLEAR */

	else if (!strcasecmp(cmd, "clear"))

	{

		ListStruct *next;



		for (s = SpyList; s; s = (SpyStruct *) next)

		{

			next = (ListStruct *) s->next;

			if (s->who == sptr)

				DelSpyItem(s);

		}

    		sendto_one(sptr, ":%s NOTICE %s :*** Your private spy list is now empty",

	            	me.name, sptr->name);

		SpyForward2(&me, "CLEAR", sptr->name);

	}



	/* <INVALID OPTION> */

	else

	{

    		sendto_one(sptr, ":%s NOTICE %s :*** Invalid option %s",

	            	me.name, sptr->name, cmd);

		return -1;

	}



	return 0;

}



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

// m_spychannel: Adds, removes or lists channel names in channel

//     spy list

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



DLLFUNC int m_spychannel(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	SpyChStruct	*sc;

	char		*cmd, *channel, *sendto;

	u_int		lock;



        if (!MyClient(cptr) || !IsOper(cptr) || !IsSAdmin(cptr))

	{

        	sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);

		return -1;

	}



	cmd = IsParam(1) ? parv[1] : NULL;



        if (!cmd)

	{

		display_text(cptr, SpyChHelp);

		return -1;

	}



	/* ADD */

	if (!strcasecmp(cmd, "add"))

	{

		if (!spych_paramcheck(cptr, parc, parv, &channel, &sendto, &lock))

		{

			display_text(cptr, SpyChHelp);

			return -1;

		}

		if (*channel != '#')

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s: Invalid channel name",

	            		me.name, sptr->name, channel);

			return -1;

		}

		if (sendto && *sendto != '#')

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s: Invalid channel name",

	            		me.name, sptr->name, sendto);

			return -1;

		}

		if (FindSpyChItem(sptr, channel))

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s: Already on your channel spy list",

	            		me.name, sptr->name, channel);

			return -1;

		}

		if (sendto && !strcasecmp(channel, sendto))

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s %s: Source and target channels cannot be the same",

	            		me.name, sptr->name, channel, sendto);

			return -1;

		}

		AddSpyChItem(sptr, channel, sendto, lock);

    		sendto_one(sptr, ":%s NOTICE %s :*** %s: Added to your channel spy list",

	            	me.name, sptr->name, channel);

		SpyChForward4(&me, "ADD", sptr->name, channel, sendto, lock);

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPYCHANNEL ADD %s%s" "%s" "%s",

			sptr->name, MergeLock(lock, channel), sendto ? " " : "",

			sendto ? sendto : "");

	}



	/* DEL */

	else if (!strcasecmp(cmd, "del"))

	{

		if (!spych_paramcheck(cptr, parc, parv, &channel, &sendto, &lock))

		{

			display_text(cptr, SpyChHelp);

			return -1;

		}

		if (!(sc = FindSpyChItem(sptr, channel)))

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s: Not yet on your channel spy list",

	            		me.name, sptr->name, channel);

			return -1;

		}

		SpyChForward3(&me, "DEL", sc->who->name, sc->channel, 0);

    		sendto_one(sptr, ":%s NOTICE %s :*** %s: Deleted from your channel spy list",

	            	me.name, sptr->name, channel);

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPYCHANNEL DEL %s",

			sptr->name, sc->channel);

		DelSpyChItem(sc);

	}



	/* LIST */

	else if (!strcasecmp(cmd, "list"))

	{

		unsigned int found = 0, empty = 1;



		sendto	= NULL;

		channel	= IsParam(2) ? parv[2] : NULL;



		if (channel)

			SplitLock(lock, channel);



		for (sc = SpyChList; sc; sc = sc->next)

			if (sc->who == sptr)

			{

				empty = 0;



				if (!channel || !strcasecmp(sc->channel, channel))

				{

					found++;

				

    					sendto_one(sptr, ":%s NOTICE %s :*** %s%s %s",

	            				me.name, sptr->name,

						MergeLock(sc->lock, sc->channel),

						sc->sendto ? sc->sendto : "<irc>");

				}

			}



		if (!found)

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** %s",

	    			me.name, sptr->name,

				empty ? "Your channel spy list is empty" : "No matching entries");

		}

		else

		{

			sendto_one(sptr, ":%s NOTICE %s :*** " SPY_LINE,

				me.name, sptr->name);

			sendto_one(sptr, ":%s NOTICE %s :*** %d entr%s",

				me.name, sptr->name,

				found, found > 1 ? "ies" : "y");

		}

	}



	/* CLEAR */

	else if (!strcasecmp(cmd, "clear"))

	{

		ListStruct *next;



		for (sc = SpyChList; sc; sc = (SpyChStruct *) next)

		{

			next = (ListStruct *) sc->next;

			if (sc->who == sptr)

				DelSpyChItem(sc);

		}

    		sendto_one(sptr, ":%s NOTICE %s :*** Your channel spy list is now empty",

	            	me.name, sptr->name);

		SpyChForward2(&me, "CLEAR", sptr->name);

	}



	/* <INVALID OPTION> */

	else

	{

    		sendto_one(sptr, ":%s NOTICE %s :*** Invalid option %s",

	            	me.name, sptr->name, cmd);

		return -1;

	}



	return 0;

}



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

// m_spyforward: Keeps SpyList updated on all servers

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



DLLFUNC int m_spyforward(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	SpyStruct	*s;

	ListStruct 	*next;

	aClient		*acptr;

	char		*tmp, *cmd, *who, *nick1, *nick2, *channel;

	u_int		lock1, lock2;



        if (IsClient(cptr))

	{

        	sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);

		return -1;

	}



	cmd	= IsParam(1)		? parv[1] : NULL;

	who	= IsParam(2)		? parv[2] : NULL;

	nick1	= IsParam(3)		? parv[3] : NULL;

	nick2	= IsValidParam(4)	? parv[4] : NULL;

	channel	= IsValidParam(5)	? parv[5] : NULL;



	/* sendto_realops("(\2m_spyforward\2) cptr=%s, sptr=%s, cmd=%s who=%s nick1=%s nick2=%s channel=%s",

		cptr->name, sptr->name,

		cmd	? cmd		: "(null)",

		who	? who		: "(null)",

		nick1	? nick1		: "(null)",

		nick2	? nick2		: "(null)",

		channel	? channel	: "(null)"); */



        if (!cmd)

		return -1;

	if (nick1)

		SplitLock(lock1, nick1);

	if (nick2)

		SplitLock(lock2, nick2);



	/* ADD */

	if (!strcasecmp(cmd, "add"))

	{

		if (!nick1)

			return -1;

		if (nick2 && !strcasecmp(nick1, nick2))

			return -1;

		if (nick2 && (smycmp(nick1, nick2) > 0))

		{

			tmp	= nick1;

			nick1	= nick2;

			nick2	= tmp;

		}

		if (!(acptr = find_person(who, NULL)))

			return -1;

		if ((nick2 && FindSpyItem2(acptr, nick1, nick2)) || (!nick2 && FindSpyItem1(acptr, nick1)))

			return -1;

		AddSpyItem(acptr, nick1, nick2, channel, lock1, lock2);

		SpyForward5(cptr, "ADD", acptr->name, nick1, nick2, channel, lock1, lock2);

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPY ADD %s%s" "%s" "%s%s" "%s" "%s",

			acptr->name, MergeLock(lock1, nick1),

			nick2 ? " " : "", MergeLockEmp(lock2, nick2),

			channel ? " " : "", channel ? channel : "");

	}



	/* ADD2 -- Like ADD, but without forwarding */

	if (!strcasecmp(cmd, "add2"))

	{

		if (!nick1)

			return -1;

		if (nick2 && !strcasecmp(nick1, nick2))

			return -1;

		if (nick2 && (smycmp(nick1, nick2) > 0))

		{

			tmp	= nick1;

			nick1	= nick2;

			nick2	= tmp;

		}

		if (!(acptr = find_person(who, NULL)))

			return -1;

		if ((nick2 && FindSpyItem2(acptr, nick1, nick2)) || (!nick2 && FindSpyItem1(acptr, nick1)))

			return -1;

		AddSpyItem(acptr, nick1, nick2, channel, lock1, lock2);

	}



	/* DEL */

	else if (!strcasecmp(cmd, "del"))

	{

		if (!nick1)

			return -1;

		if (nick2 && (smycmp(nick1, nick2) > 0))

		{

			tmp	= nick1;

			nick1	= nick2;

			nick2	= tmp;

		}

		if (!(acptr = find_person(who, NULL)))

			return -1;

		if ((nick2 && !(s = FindSpyItem2(acptr, nick1, nick2))) || (!nick2 && !(s = FindSpyItem1(acptr, nick1))))

			return -1;

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPY DEL %s%s%s",

			acptr->name, s->nick1, s->nick2 ? " " : "", s->nick2 ? s->nick2 : "");

		SpyForward4(cptr, "DEL", acptr->name, s->nick1, s->nick2, 0, 0);

		DelSpyItem(s);

	}



	/* CLEAR */

	else if (!strcasecmp(cmd, "clear"))

	{

		if (!who)

			return -1;

		if (!(acptr = find_person(who, NULL)))

			return -1;

		for (s = SpyList; s; s = (SpyStruct *) next)

		{

			next = (ListStruct *) s->next;

			if (s->who == acptr)

				DelSpyItem(s);

		}

		SpyForward2(cptr, "CLEAR", acptr->name);

	}



	/* CONNECT */

	else if (!strcasecmp(cmd, "connect"))

	{

		for (s = SpyList; s; s = s->next)

			/* act as a client who is just adding a spy entry */

			SpyForward5(&me, "ADD", s->who->name, s->nick1, s->nick2,

				s->chname, s->lock1, s->lock2);

	}



	/* <INVALID OPTION> */

	else

	{

		return -1;

	}



	return 0;

}



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

// m_spychforward: Keeps SpyChList updated on all servers

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



DLLFUNC int m_spychforward(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	SpyChStruct	*sc;

	ListStruct 	*next;

	aClient		*acptr;

	char		*cmd, *who, *channel, *sendto;

	u_int		lock;



        if (IsClient(cptr))

	{

        	sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);

		return -1;

	}



	cmd	= IsParam(1)		? parv[1] : NULL;

	who	= IsParam(2)		? parv[2] : NULL;

	channel	= IsValidParam(3)	? parv[3] : NULL;

	sendto	= IsValidParam(4)	? parv[4] : NULL;



	/* sendto_realops("(\2m_spychforward\2) cptr=%s, sptr=%s, cmd=%s who=%s channel=%s sendto=%s",

		cptr->name, sptr->name,

		cmd	? cmd		: "(null)",

		who	? who		: "(null)",

		channel	? channel	: "(null)",

		sendto	? sendto	: "(null)"); */



        if (!cmd)

		return -1;

	if (channel)

		SplitLock(lock, channel);



	/* ADD */

	if (!strcasecmp(cmd, "add"))

	{

		if (!channel)

			return -1;

		if (!(acptr = find_person(who, NULL)))

			return -1;

		if (FindSpyChItem(acptr, channel))

			return -1;

		AddSpyChItem(acptr, channel, sendto, lock);

		SpyChForward4(cptr, "ADD", acptr->name, channel, sendto, lock);

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPYCHANNEL ADD %s%s" "%s" "%s",

			acptr->name, MergeLock(lock, channel),

			sendto ? " " : "", sendto ? sendto : "");

	}



	/* ADD2 -- Like ADD, but without forwarding */

	if (!strcasecmp(cmd, "add2"))

	{

		if (!channel)

			return -1;

		if (!(acptr = find_person(who, NULL)))

			return -1;

		if (FindSpyChItem(acptr, channel))

			return -1;

		AddSpyChItem(acptr, channel, sendto, lock);

	}



	/* DEL */

	else if (!strcasecmp(cmd, "del"))

	{

		if (!channel)

			return -1;

		if (!(acptr = find_person(who, NULL)))

			return -1;

		if (!(sc = FindSpyChItem(acptr, channel)))

			return -1;

		sendto_snomask(SNO_SPY, "*** [\2m_spy\2] %s used /SPYCHANNEL DEL %s",

			acptr->name, sc->channel);

		SpyChForward3(cptr, "DEL", acptr->name, sc->channel, 0);

		DelSpyChItem(sc);

	}



	/* CLEAR */

	else if (!strcasecmp(cmd, "clear"))

	{

		if (!who)

			return -1;

		if (!(acptr = find_person(who, NULL)))

			return -1;

		for (sc = SpyChList; sc; sc = (SpyChStruct *) next)

		{

			next = (ListStruct *) sc->next;

			if (sc->who == acptr)

				DelSpyChItem(sc);

		}

		SpyChForward2(cptr, "CLEAR", acptr->name);

	}



	/* CONNECT */

	else if (!strcasecmp(cmd, "connect"))

	{

		for (sc = SpyChList; sc; sc = sc->next)

			/* act as a client who is just adding a channel spy entry */

			SpyChForward4(&me, "ADD", sc->who->name, sc->channel,

				sc->sendto, sc->lock);

	}



	/* <INVALID OPTION> */

	else

	{

		return -1;

	}



	return 0;

}



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

// m_spysend: Sends users' private messages to a client on the network

//	Easier way?

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



DLLFUNC int m_spysend(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	aClient		*acptr;

	char		*from, *to, *cmd, *nick1, *nick2, *text;

	unsigned	notice;



	if (!IsServer(sptr) || !IsParam(5))

		return -1;



	to	= IsParam(1)		? parv[1] : NULL;

	cmd	= IsParam(2)		? parv[2] : NULL;

	nick1	= IsParam(3)		? parv[3] : NULL;

	nick2	= IsParam(4)		? parv[4] : NULL;

	text	= IsParam(5)		? parv[5] : NULL;



	if (!text)

		return -1;



	notice	= *cmd == 'N' ? 1 : 0;



	if (*to == '#')

		spy_send_channel(cptr, to, notice,

			nick1, nick2, text);

	else

	{

		if (!(acptr = find_person(to, NULL)))

			return -1;

		spy_send_client(acptr, notice,

			nick1, nick2, text);

	}



	return 0;

}



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

// m_spychsend: Sends users' channel messages to a client on the network

//	Easier way?

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



DLLFUNC int m_spychsend(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	aClient		*acptr;

	char		*to, *cmd, *nick, *channel, *text;

	unsigned	notice;



	if (!IsServer(sptr) || !IsParam(5))

		return -1;



	to	= IsParam(1)		? parv[1] : NULL;

	cmd	= IsParam(2)		? parv[2] : NULL;

	nick	= IsParam(3)		? parv[3] : NULL;

	channel	= IsParam(4)		? parv[4] : NULL;

	text	= IsParam(5)		? parv[5] : NULL;



	if (!text)

		return -1;



	notice	= *cmd == 'N' ? 1 : 0;



	if (*to == '#')

		spy_send_channel(cptr, to, notice,

			nick, channel, text);

	else

	{

		if (!(acptr = find_person(to, NULL)))

			return -1;

		spy_send_client(acptr, notice,

			nick, channel, text);

	}



	return 0;

}



/* 

 * m_spystats

 *

 * Displays SpyList statistics based on a given nickname. If you leave out

 * <nickname>, all SpyList entries will be displayed.

 *

 * :prefix SPYSTATS [<nickname>]

 * parv[0] - (sender)

 * parv[1] - channel|message

 * parv[2] - <nickname> (optional)

 *

 */



DLLFUNC int m_spystats(aClient *cptr, aClient *sptr, int parc, char *parv[])

{

	SpyStruct	*s;

	SpyChStruct	*sc;

	aClient		*acptr = NULL;

	unsigned long	item = 0, count = 0, total = 0;

	unsigned long	subcount = 0, subtotal = 0;

	char		*cmd;



        if (!MyClient(cptr) || !IsOper(cptr) || !IsSAdmin(cptr))

	{

        	sendto_one(sptr, err_str(ERR_NOPRIVILEGES), me.name, parv[0]);

		return -1;

	}

	if (!IsParam(1))

	{

    		sendto_one(sptr, ":%s NOTICE %s :*** Usage: /spystats private|channel [<nickname>]",

	    		me.name, sptr->name);

		return 0;

	}



	cmd = parv[1];



	/* PRIVATE */

	if (!strcasecmp(cmd, "private"))

	{

		if (!SpyList)

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** Private spy list is empty",

	    			me.name, sptr->name);

			return 0;

		}

		if (IsParam(2) && !(acptr = find_person(parv[2], NULL)))

		{

	    		sendto_one(sptr, err_str(ERR_NOSUCHNICK),

				me.name, sptr->name, parv[2]);

	    		return -1;

		}



		for (s = SpyList; s; s = s->next)

		{

			item = sizeof(*s) + 1; /* 1 byte is the size of (lock1 + lock2) */

			item += sizeof(char) * (strlen(s->nick1) + 1);

			if (s->nick2)

				item += sizeof(char) * (strlen(s->nick2) + 1);

			if (s->chname)

				item += sizeof(char) * (strlen(s->chname) + 1);

			if (acptr && s->who == acptr)

			{

				subtotal += item;

				subcount++;

			}

			total += item;

			count++;



			if (!acptr || s->who == acptr)

    				sendto_one(sptr, ":%s NOTICE %s :*** %s (%s%s): %s%s %s (mem: %ld)",

	    				me.name, sptr->name,

					s->who->name,

					MergeLock(s->lock1, s->nick1),

					MergeLockOpt(s->lock2, s->nick2),

					s->chname ? s->chname : "<irc>",

					item);

		}



		if (acptr)

		{

			sendto_one(sptr, ":%s NOTICE %s :*** " SPY_LINE,

				me.name, sptr->name);

    			sendto_one(sptr, ":%s NOTICE %s :*** Sub total (%s): %ld entr%s (mem: %ld)",

	    			me.name, sptr->name,

				acptr->name,

				subcount, count > 1 ? "ies" : "y",

				subtotal);

		}



		sendto_one(sptr, ":%s NOTICE %s :*** " SPY_LINE,

			me.name, sptr->name);



		sendto_one(sptr, ":%s NOTICE %s :*** Total: %d entr%s (mem: %ld)",

			me.name, sptr->name,

			count, count > 1 ? "ies" : "y",

			total);

	}



	/* CHANNEL */

	else if (!strcasecmp(cmd, "channel"))

	{

		if (!SpyChList)

		{

    			sendto_one(sptr, ":%s NOTICE %s :*** Channel spy list is empty",

	    			me.name, sptr->name);

			return 0;

		}

		if (IsParam(2) && !(acptr = find_person(parv[2], NULL)))

		{

	    		sendto_one(sptr, err_str(ERR_NOSUCHNICK),

				me.name, sptr->name, parv[2]);

	    		return -1;

		}



		for (sc = SpyChList; sc; sc = sc->next)

		{

			item = sizeof(*sc) + sizeof(sc->lock);

			item += sizeof(char) * (strlen(sc->channel) + 1);

			if (sc->sendto)

				item += sizeof(char) * (strlen(sc->sendto) + 1);

			if (acptr && sc->who == acptr)

			{

				subtotal += item;

				subcount++;

			}

			total += item;

			count++;



			if (!acptr || sc->who == acptr)

    				sendto_one(sptr, ":%s NOTICE %s :*** %s (%s%s): %s (mem: %ld)",

	    				me.name, sptr->name,

					sc->who->name,

					MergeLock(sc->lock, sc->channel),

					sc->sendto ? sc->sendto : "<irc>",

					item);

		}



		if (acptr)

		{

			sendto_one(sptr, ":%s NOTICE %s :*** " SPY_LINE,

				me.name, sptr->name);

    			sendto_one(sptr, ":%s NOTICE %s :*** Sub total (%s): %ld entr%s (mem: %ld)",

	    			me.name, sptr->name,

				acptr->name,

				subcount, count > 1 ? "ies" : "y",

				subtotal);

		}



		sendto_one(sptr, ":%s NOTICE %s :*** " SPY_LINE,

			me.name, sptr->name);



		sendto_one(sptr, ":%s NOTICE %s :*** Total: %d entr%s (mem: %ld)",

			me.name, sptr->name,

			count, count > 1 ? "ies" : "y",

			total);

	}



	/* <INVALID OPTION> */

	else

	{

    		sendto_one(sptr, ":%s NOTICE %s :*** Invalid option %s",

	            	me.name, sptr->name, cmd);

		return -1;

	}

	

	return 0;

}