/*
 * ctcp.c:handles the client-to-client protocol(ctcp). 
 *
 * Written By Michael Sandrof 
 * Copyright(c) 1990, 1995 Michael Sandroff and others
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 *
 * Serious cleanup by jfn (August 1996)
 */


#include "irc.h"
static char cvsrevision[] = "$Id: ctcp.c,v 1.1.1.2 2003/05/27 07:00:22 root Exp $";
CVS_REVISION(ctcp_c)
#include "struct.h"

#include <pwd.h>


#ifdef HAVE_UNAME
# include <sys/utsname.h>
#endif

#include <stdarg.h>

#include "encrypt.h"
#include "ctcp.h"
#include "dcc.h"
#include "commands.h"
#include "flood.h"
#include "hook.h"
#include "if.h"
#include "ignore.h"
#include "ircaux.h"
#include "lastlog.h"
#include "list.h"
#include "names.h"
#include "output.h"
#include "parse.h"
#include "server.h"
#include "status.h"
#include "vars.h"
#include "window.h"
#include "cdcc.h"
#include "misc.h"
#include "userlist.h"
#include "hash2.h"

#ifdef WANT_TCL
#include "tcl_bx.h"
#endif

#define MAIN_SOURCE
#include "modval.h"

/*
 * ctcp_entry: the format for each ctcp function.   note that the function
 * described takes 4 parameters, a pointer to the ctcp entry, who the message
 * was from, who the message was to (nickname, channel, etc), and the rest of
 * the ctcp message.  it can return null, or it can return a malloced string
 * that will be inserted into the oringal message at the point of the ctcp.
 * if null is returned, nothing is added to the original message
 */

/* forward declarations for the built in CTCP functions */
static	char	*do_sed 	(CtcpEntry *, char *, char *, char *);
static	char	*do_version 	(CtcpEntry *, char *, char *, char *);
static	char	*do_clientinfo 	(CtcpEntry *, char *, char *, char *);
static	char	*do_ping 	(CtcpEntry *, char *, char *, char *);
static	char	*do_echo 	(CtcpEntry *, char *, char *, char *);
static	char	*do_userinfo 	(CtcpEntry *, char *, char *, char *);
static	char	*do_finger 	(CtcpEntry *, char *, char *, char *);
static	char	*do_time 	(CtcpEntry *, char *, char *, char *);
static	char	*do_atmosphere 	(CtcpEntry *, char *, char *, char *);
static	char	*do_dcc 	(CtcpEntry *, char *, char *, char *);
static	char	*do_utc 	(CtcpEntry *, char *, char *, char *);
static	char	*do_dcc_reply 	(CtcpEntry *, char *, char *, char *);
static	char	*do_ping_reply 	(CtcpEntry *, char *, char *, char *);
static	char	*do_bdcc 	(CtcpEntry *, char *, char *, char *);
static	char	*do_cinvite 	(CtcpEntry *, char *, char *, char *);
static	char	*do_whoami 	(CtcpEntry *, char *, char *, char *);
static	char	*do_ctcpops 	(CtcpEntry *, char *, char *, char *);
static	char	*do_ctcpunban 	(CtcpEntry *, char *, char *, char *);
static	char	*do_botlink 	(CtcpEntry *, char *, char *, char *);
static	char	*do_botlink_rep	(CtcpEntry *, char *, char *, char *);
static	char	*do_ctcp_uptime	(CtcpEntry *, char *, char *, char *);
static	char	*do_ctcpident	(CtcpEntry *, char *, char *, char *);

static CtcpEntry ctcp_cmd[] =
{
	{ "SED",	CTCP_SED, 	CTCP_INLINE | CTCP_NOLIMIT,
		"contains simple_encrypted_data",
		do_sed, 	do_sed },
	{ "UTC",	CTCP_UTC, 	CTCP_INLINE | CTCP_NOLIMIT,
		"substitutes the local timezone",
		do_utc, 	do_utc },
	{ "ACTION",	CTCP_ACTION, 	CTCP_SPECIAL | CTCP_NOLIMIT,
		"contains action descriptions for atmosphere",
		do_atmosphere, 	do_atmosphere },

	{ "DCC",	CTCP_DCC, 	CTCP_SPECIAL,
		"requests a direct_client_connection",
		do_dcc, 	do_dcc_reply },

	{ "CDCC",	CTCP_CDCC, 	CTCP_SPECIAL | CTCP_TELLUSER,
		"checks cdcc info for you",
		do_bdcc,	NULL },
	{ "BDCC",	CTCP_CDCC1, 	CTCP_SPECIAL | CTCP_TELLUSER,
		"checks cdcc info for you",
		do_bdcc,	NULL },
	{ "XDCC",	CTCP_CDCC2, 	CTCP_SPECIAL | CTCP_TELLUSER,
		"checks cdcc info for you",
		do_bdcc,	NULL },

	{ "VERSION",	CTCP_VERSION,	CTCP_REPLY | CTCP_TELLUSER,
		"shows client type, version and environment",
		do_version, 	NULL },

	{ "CLIENTINFO",	CTCP_CLIENTINFO,CTCP_REPLY | CTCP_TELLUSER,
		"gives information about available CTCP commands",
		do_clientinfo, 	NULL },
	{ "USERINFO",	CTCP_USERINFO, 	CTCP_REPLY | CTCP_TELLUSER,
		"returns user settable information",
		do_userinfo, 	NULL },
	{ "ERRMSG",	CTCP_ERRMSG, 	CTCP_REPLY | CTCP_TELLUSER,
		"returns error messages",
		do_echo, 	NULL },
	{ "FINGER",	CTCP_FINGER, 	CTCP_REPLY | CTCP_TELLUSER,
		"shows real name, login name and idle time of user", 
		do_finger, 	NULL },
	{ "TIME",	CTCP_TIME, 	CTCP_REPLY | CTCP_TELLUSER,
		"tells you the time on the user's host",
		do_time, 	NULL },
	{ "PING", 	CTCP_PING, 	CTCP_REPLY | CTCP_TELLUSER,
		"returns the arguments it receives",
		do_ping, 	do_ping_reply },
	{ "ECHO", 	CTCP_ECHO, 	CTCP_REPLY | CTCP_TELLUSER,
		"returns the arguments it receives",
		do_echo, 	NULL },

	{ "INVITE",	CTCP_INVITE, 	CTCP_SPECIAL,
		"invite to channel specified",
		 do_cinvite, 	NULL },
	{ "WHOAMI",	CTCP_WHOAMI,	CTCP_SPECIAL,
		"user list information",
		do_whoami,	NULL },
	{ "OP",		CTCP_OPS,	CTCP_SPECIAL,
		"ops the person if on userlist",
		do_ctcpops,	NULL },
	{ "OPS",	CTCP_OPS,	CTCP_SPECIAL,
		 "ops the person if on userlist",
		do_ctcpops,	NULL },
	{ "UNBAN",	CTCP_UNBAN,	CTCP_SPECIAL,
		"unbans the person from channel",
		do_ctcpunban,	NULL },
	{ "IDENT",	CTCP_IDENT,	CTCP_SPECIAL,
		"change userhost of userlist",
		do_ctcpident,	NULL },
	{ "XLINK",	CTCP_BOTLINK,	CTCP_SPECIAL,
		"x-filez rule",
		do_botlink,	do_botlink_rep },

	{ "UPTIME",	CTCP_UPTIME,	CTCP_SPECIAL,
		"my uptime",
		do_ctcp_uptime, NULL },

	{ NULL,		CTCP_CUSTOM,	CTCP_REPLY | CTCP_TELLUSER,
		NULL,
		NULL, NULL }
};

#ifdef WANT_DLL
CtcpEntryDll *dll_ctcp = NULL;
#endif


/* CDE do ops and unban logging */

static char	*ctcp_type[] =
{
	"PRIVMSG",
	"NOTICE"
};

/* This is set to one if we parsed an SED */
int     sed = 0;

/*
 * in_ctcp_flag is set to true when IRCII is handling a CTCP request.  This
 * is used by the ctcp() sending function to force NOTICEs to be used in any
 * CTCP REPLY 
 */
int	in_ctcp_flag = 0;

#define CTCP_HANDLER(x) \
	static char * x (CtcpEntry *ctcp, char *from, char *to, char *cmd)

/**************************** CTCP PARSERS ****************************/

/********** INLINE EXPANSION CTCPS ***************/


#ifdef WANT_USERLIST
void print_ctcp(char *from, char *uh, char *to, char *str, char *cmd)
{
	put_it("%s", convert_output_format(fget_string_var(is_channel(to)?FORMAT_CTCP_CLOAK_FUNC_FSET:FORMAT_CTCP_CLOAK_FUNC_USER_FSET),
		"%s %s %s %s %s %s",update_clock(GET_TIME), from, uh, to, str, *cmd? cmd : empty_string));
}

void log_denied(char *type, char *from, char *uh, char *to, char *cmd)
{
	logmsg(LOG_CTCP, from, 0, "%s %s %s [%s]", type, "! Access Denied from ", uh, cmd);
}
#endif

CTCP_HANDLER(do_ctcpident)
{
#if defined(WANT_USERLIST)
UserList *ThisNick = NULL;
char *nick, *passwd = NULL;
char *channel = "*";
int ok = 0;
	print_ctcp(from, FromUserHost, to, "IDENT ", cmd);
	if (is_channel(to))
		return NULL;
	nick = next_arg(cmd, &cmd);
	passwd = next_arg(cmd, &cmd);
	if (cmd && *cmd)
		channel = next_arg(cmd, &cmd);
	ThisNick = lookup_userlevelc(nick, "*", channel, passwd);
	if (nick && passwd && ThisNick && ThisNick->password)
	{
		if (!checkpass(passwd, ThisNick->password))
		{
			char *bang = NULL;
			char *u, *h;
			ok = 1;
			bang = LOCAL_COPY(FromUserHost);
			u = bang;
			if ((h = strchr(bang, '@')))
				*h++ = 0;
			if (!h || !*h)
				return NULL;
			malloc_sprintf(&ThisNick->host, "*%s@%s", clear_server_flags(u), cluster(h));
		}
	}
	if (!ok && !get_int_var(CLOAK_VAR))
		log_denied("IDENT", from, FromUserHost, to, cmd);
	else if (ok || (ThisNick && (ThisNick->flags & ADD_CTCP)))
	{
		if (get_int_var(SEND_CTCP_MSG_VAR))
			send_to_server("NOTICE %s :UserHost updated to %s", from, FromUserHost);
		logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s", "IDENT", from, FromUserHost);
	}
#endif
	return NULL;
}




/* parse a remote ctcp CDCC command */
CTCP_HANDLER(do_bdcc)
{
#ifdef WANT_CDCC
	int i;
	char *secure = NULL;
extern	pack *offerlist;
extern	remote_cmd remote[];
	char *rest, *arg, *temp = NULL;
		
	if ((check_ignore(from, FromUserHost, to, IGNORE_CDCC, NULL) == IGNORED))
		return NULL;
		
	if (!offerlist || !get_int_var(CDCC_VAR))
		return NULL;

	if ((secure = get_string_var(CDCC_SECURITY_VAR)))
	{
		UserList *tmp;
		char *pass = NULL;
/* workaround for var.c which had a int for a var */
		if (*secure == '0' && strlen(secure) == 1)
			goto got_good_pass1;
		if (cmd && *cmd)
			pass = strrchr(cmd, ' ');
		if (pass && *pass)
		{
			pass++;
			if (*pass && !my_stricmp(pass, secure))
			{
				*pass-- = 0;
				goto got_good_pass1;
			}
		}	
#if defined(WANT_USERLIST)
		if (!(tmp = lookup_userlevelc("*", FromUserHost, "*", NULL)) || !(tmp->flags & ADD_DCC))
			return NULL;
#else
		return NULL;
#endif
	}

got_good_pass1:
	temp = LOCAL_COPY(cmd);
	arg = next_arg(temp, &temp);
	if (!arg) 
		return NULL;
	rest = temp;
	for (i = 0; *remote[i].name; i++) 
	{
		if (!my_stricmp(arg, remote[i].name)) 
		{
			remote[i].function(from, rest);
			return NULL;
		}
	}
	send_to_server("NOTICE %s :try /ctcp %s cdcc help", from, get_server_nickname(from_server));
#endif	 
	return NULL;
}

CTCP_HANDLER(do_botlink)
{
#if !defined(BITCHX_LITE)
#ifdef WANT_USERLIST
char *nick = NULL, *password = NULL, *port = NULL;
	nick = next_arg(cmd, &cmd);
	password = next_arg(cmd, &cmd);
	port = next_arg(cmd, &cmd);
	print_ctcp(from, FromUserHost, to, "XLINK ", cmd);
	if (nick && password)
	{
		char *t = NULL;
		UserList *n = NULL;
		if (get_string_var(BOT_PASSWD_VAR) || (n = lookup_userlevelc(nick, FromUserHost, "*", password)))
		{
			char *pass;
			if (!n)
				pass = get_string_var(BOT_PASSWD_VAR);
			else
				pass = n->password;
			if (pass && !my_stricmp(pass, password))
			{
				if (port)
					malloc_sprintf(&t, "%s -p %s -e %s", nick, port, password);
				else
					malloc_sprintf(&t, "%s -e %s", nick, password);
				dcc_chat("BOT", t);
				new_free(&t);
				set_int_var(BOT_MODE_VAR, 1);
				logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s to %s [%s]", "XLINK", from, FromUserHost, to, cmd?cmd:empty_string);
			} else 
				log_denied("XLINK", from, FromUserHost, to, cmd);
		} else
			log_denied("XLINK", from, FromUserHost, to, cmd);
	}
	else
	{
		log_denied("XLINK", from, FromUserHost, to, cmd);
		return m_sprintf("Invalid Bot Link request %s %s %s", nick?nick:"null", password?password:"-", port?port:"*");
	}
#endif
#endif

	return NULL;
}

CTCP_HANDLER(do_botlink_rep)
{
#ifndef BITCHX_LITE
char *type, *description, *inetaddr, *port, *extra_flags;
	if (my_stricmp(to, get_server_nickname(from_server)))
		return NULL;

	if     (!(type = next_arg(cmd, &cmd)) ||
		!(description = next_arg(cmd, &cmd)) ||
		!(inetaddr = next_arg(cmd, &cmd)) ||
		!(port = next_arg(cmd, &cmd)))
			return NULL;

	extra_flags = next_arg(cmd, &cmd);
	set_int_var(BOT_MODE_VAR, 1);
	register_dcc_type(from, type, description, inetaddr, port, NULL, extra_flags, FromUserHost, NULL);
#endif
	return NULL;
}




CTCP_HANDLER(do_cinvite)
{
#ifdef WANT_USERLIST
UserList *tmp;
char *channel;
char *password = NULL;
ChannelList *chan;
int serv = from_server;
	print_ctcp(from, FromUserHost, to, "Invite to ", cmd);
	channel = next_arg(cmd, &cmd);
	if (cmd && *cmd)
		password = next_arg(cmd, &cmd);

	if (is_channel(to) || !channel || (channel && *channel && !is_channel(channel)))
		return NULL;

	tmp = lookup_userlevelc("*", FromUserHost, channel, password);
	chan = prepare_command(&serv, channel, 3);
	if (chan && tmp && (tmp->flags & ADD_INVITE) && (check_channel_match(tmp->channels, channel)))
	{
		if (tmp->password && !password)
		{
			log_denied("INVITE", from, FromUserHost, to, cmd);
			return NULL;
		}
		if (im_on_channel(channel, from_server) && is_chanop(channel, get_server_nickname(from_server)))
		{
			if (chan->key)
				my_send_to_server(serv, "NOTICE %s :\002Ctcp-inviting you to %s. Key is [%s]\002", from, channel, chan->key);
			else
				my_send_to_server(serv, "NOTICE %s :\002Ctcp-inviting you to %s\002", from, channel);
			my_send_to_server(serv, "INVITE %s %s", from, channel);
			logmsg(LOG_CTCP, from, 0, "Successful INVITE from %s!%s to %s", from, FromUserHost, channel);
		}
		else
		{
			if (get_int_var(SEND_CTCP_MSG_VAR))
				my_send_to_server(serv, "NOTICE %s :\002%s\002: I'm not on %s, or I'm not opped", from, version, channel);
			log_denied("INVITE", from, FromUserHost, to, cmd);
		}
	} 
	else if (!get_int_var(CLOAK_VAR) || (tmp && (tmp->flags & ADD_CTCP)))
	{
		if (!chan)
		{
			if (get_int_var(SEND_CTCP_MSG_VAR))
				my_send_to_server(serv, "NOTICE %s :\002%s\002: I'm not on that channel", from, version);
			log_denied("INVITE", from, FromUserHost, to, cmd);
		}
		else
		{
			if (get_int_var(SEND_CTCP_MSG_VAR))
				my_send_to_server(serv, "NOTICE %s :\002%s\002: Access Denied", from, version);
			log_denied("INVITE", from, FromUserHost, to, cmd);
		}
	}
	else
		log_denied("INVITE", from, FromUserHost, to, cmd);
#endif
	return NULL;
}

CTCP_HANDLER(do_whoami)
{
#ifdef WANT_USERLIST
UserList *Nick = NULL;
	print_ctcp(from, FromUserHost, to, "WhoAmI", cmd);
	if (is_channel(to))
		return NULL;

	Nick = lookup_userlevelc("*", FromUserHost, "*", NULL);
	if (Nick && Nick->flags)
	{
		send_to_server("NOTICE %s :\002%s\002: Userlevel - %s ",from, version, convert_flags_to_str(Nick->flags));
		send_to_server("NOTICE %s :\002%s\002: Host Mask - %s Channels Allowed - %s   %s", 
			from, version, Nick->host, Nick->channels, Nick->password?"Password Required":empty_string);
		logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s to %s", "WHOAMI", from, FromUserHost, to);
	} 
	else if (!get_int_var(CLOAK_VAR) || (Nick && (Nick->flags & ADD_CTCP)))
	{
		send_to_server("NOTICE %s :\002%s\002: Access Denied", from, version);
		if (get_int_var(SEND_CTCP_MSG_VAR))
			log_denied("WHOAMI", from, FromUserHost, to, cmd);
	} else 	if (get_int_var(SEND_CTCP_MSG_VAR))
		log_denied("WHOAMI", from, FromUserHost, to, cmd);
#endif
	return NULL;
}

CTCP_HANDLER(do_ctcp_uptime)
{
#ifdef WANT_USERLIST
UserList *Nick = NULL;
	print_ctcp(from, FromUserHost, to, "Uptime", cmd);
	Nick = lookup_userlevelc("*", FromUserHost, "*", NULL);
	if (Nick)
	{
		send_to_server("NOTICE %s :\002%s\002: Current Uptime is %s", from, version, convert_time(now-start_time)); 
		logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s to %s", "UPTIME", from, FromUserHost, to);
	} else 	if (get_int_var(SEND_CTCP_MSG_VAR))
		log_denied("UPTIME", from, FromUserHost, to, cmd);
#endif
	return NULL;
}

CTCP_HANDLER(do_ctcpops)
{
#ifdef WANT_USERLIST
UserList *Nick = NULL;
char *channel;
char *password = NULL;
ChannelList *chan;
int serv = from_server;
	print_ctcp(from, FromUserHost, to, "Ops on", cmd);

	if (is_channel(to))
		return NULL;
	channel = next_arg(cmd, &cmd);
	if (!channel || !*channel)
		return NULL;
	if (cmd && *cmd)
		password = next_arg(cmd, &cmd);
	Nick = lookup_userlevelc("*", FromUserHost, channel, password);
	chan = prepare_command(&serv, channel, 3);
	if (chan && get_cset_int_var(chan->csets, USERLIST_CSET) && Nick)
	{
		if (Nick->flags & ADD_OPS)
		{
			if (Nick->password && !password)
			{
				log_denied("OPS", from, FromUserHost, to, cmd);
				return NULL;
			}
			if (im_on_channel(channel, from_server) && is_chanop(channel, get_server_nickname(from_server)))
			{
				my_send_to_server(serv, "MODE %s +o %s", channel, from); 
				logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s", "OPS", from, FromUserHost);
			}
			else
			{
				if (get_int_var(SEND_CTCP_MSG_VAR))
					my_send_to_server(serv, "NOTICE %s :\002%s\002: I'm not on %s, or I'm not opped", from, version, channel);
				log_denied("OPS", from, FromUserHost, to, cmd);
			}
		}
		else if (Nick->flags & ADD_VOICE)
		{
			if (im_on_channel(channel, from_server) && is_chanop(channel, get_server_nickname(from_server)))
			{
				my_send_to_server(serv, "MODE %s +v %s", channel, from); 
				logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s", "VOICE", from, FromUserHost);
			}
			else
			{
				if (get_int_var(SEND_CTCP_MSG_VAR))
					my_send_to_server(serv, "NOTICE %s :\002%s\002: I'm not on %s, or I'm not opped", from, version, channel);
				log_denied("OPS", from, FromUserHost, to, cmd);
			}
		}
		else
		{
			if ((!get_int_var(CLOAK_VAR) || (Nick && (Nick->flags & ADD_CTCP))) && get_int_var(SEND_CTCP_MSG_VAR))
				my_send_to_server(serv, "NOTICE %s :\002%s\002: I'm not on %s, or I'm not opped", from, version, channel?channel:empty_string);
			log_denied("OPS", from, FromUserHost, to, cmd);
		}
	}
	else if (!get_int_var(CLOAK_VAR) || (Nick && (Nick->flags & ADD_CTCP)))
	{
		if (get_int_var(SEND_CTCP_MSG_VAR))
			my_send_to_server(serv, "NOTICE %s :\002%s\002: I'm not on %s, or I'm not opped", from, version, channel?channel:empty_string);
		log_denied("OPS", from, FromUserHost, to, cmd);
	}
#endif
	return NULL;
}

CTCP_HANDLER(do_ctcpunban)
{
#ifdef WANT_USERLIST
UserList *Nick = NULL;
char *channel;
char *password = NULL;
char ban[BIG_BUFFER_SIZE];
ChannelList *chan;
int server;

	print_ctcp(from, FromUserHost, to, "UnBan on ", cmd);

	if (is_channel(to))
		return NULL;

	channel = next_arg(cmd, &cmd);
	if (!channel || !*channel)
		return NULL;
	if (cmd && *cmd)
		password = next_arg(cmd, &cmd);

	Nick = lookup_userlevelc("*", FromUserHost, channel, password);
	chan = prepare_command(&server, channel, 3);

	if (chan && get_cset_int_var(chan->csets, USERLIST_CSET) && Nick && (Nick->flags & ADD_UNBAN))
	{
		BanList *b = NULL;
		if (Nick->password && !password)
		{
			log_denied("UNBAN", from, FromUserHost, to, cmd);
			return NULL;
		}
		sprintf(ban, "%s!%s", from, FromUserHost);
		if (chan && chan->chop)
		{
			if ((b = ban_is_on_channel(ban, chan)))
			{
				my_send_to_server(server, "MODE %s -b %s", channel, b->ban); 
				logmsg(LOG_CTCP, from, 0, "Successful %s from %s!%s on %s", "UNBAN", from, FromUserHost, channel);
			}
			else
			{
				if (get_int_var(SEND_CTCP_MSG_VAR))
					my_send_to_server(server, "NOTICE %s :\002%s\002: you %s are not banned on %s", from, version, ban, channel);
				log_denied("OPS", from, FromUserHost, to, cmd);
			}		
		} 
		else
		{
			my_send_to_server(server, "NOTICE %s :\002%s\002: I'm not on %s, or I'm not opped", from, version, channel);
			log_denied("OPS", from, FromUserHost, to, cmd);
		}
	}
	else if (!get_int_var(CLOAK_VAR) || (Nick && (Nick->flags & ADD_CTCP)))
	{
		if (!chan)
			my_send_to_server(server, "NOTICE %s :\002%s\002: I'm not on that channel", from, version);
		else
			my_send_to_server(server, "NOTICE %s :\002%s\002: Access Denied", from, version);
		log_denied("OPS", from, FromUserHost, to, cmd);
	}
#endif
	return NULL;
}



/*
 * do_sed: Performs the Simple Encrypted Data trasfer for ctcp.  Returns in a
 * malloc string the decryped message (if a key is set for that user) or the
 * text "[ENCRYPTED MESSAGE]" 
 */
CTCP_HANDLER(do_sed)
{
	char	*key = NULL,
		*crypt_who;
	char	*ret = NULL, *ret2 = NULL;

	if (*from == '=')
		crypt_who = from;
	if (my_stricmp(to, get_server_nickname(from_server)))
		crypt_who = to;
	else
		crypt_who = from;

	if ((key = is_crypted(crypt_who)))
		ret = decrypt_msg(cmd, key);

	if (!key || !ret)
		malloc_strcpy(&ret2, "[ENCRYPTED MESSAGE]");
	else
	{
		/* 
		 * There might be a CTCP message in there,
		 * so we see if we can find it.
		 */
		ret2 = m_strdup(do_ctcp(from, to, ret));
		sed = 1;
	}

	new_free(&ret);
	return ret2;
}

CTCP_HANDLER(do_utc)
{

	if (get_int_var(CLOAK_VAR))
		return m_strdup(empty_string);
	if (!cmd || !*cmd)
		return m_strdup(empty_string);

	return m_strdup(my_ctime(my_atol(cmd)));
}


/*
 * do_atmosphere: does the CTCP ACTION command --- done by lynX
 * Changed this to make the default look less offensive to people
 * who don't like it and added a /on ACTION. This is more in keeping
 * with the design philosophy behind IRCII
 */
CTCP_HANDLER(do_atmosphere)
{
	const char *old_message_from;
	unsigned long old_message_level;
	int ac_reply;
	char *ptr1 = cmd;
	
	if (!cmd || !*cmd)
		return NULL;
	
	if ((ac_reply = check_auto_reply(cmd)))
		addtabkey(from,"msg", 1);

	save_display_target(&old_message_from, &old_message_level);
	logmsg(LOG_ACTION, from, 0, "%s %s", to, cmd);
	
	if (is_channel(to))
	{
		int r = 0;
		ChannelList *chan = NULL;
		chan = lookup_channel(to, from_server, CHAN_NOUNLINK);
		do_logchannel(LOG_CTCP, chan, "%s %s %s %s", from, FromUserHost, to, cmd);
#ifdef WANT_USERLIST
		r = (lookup_userlevelc("*", FromUserHost, to, NULL)) ? 1 : 0;
#endif
		set_display_target(to, LOG_ACTION);
		if (do_hook(ACTION_LIST, "%s %s %s", from, to, cmd))
		{
			char *s;
	s = fget_string_var((ac_reply && r)?FORMAT_ACTION_USER_AR_FSET : 
		ac_reply ? FORMAT_ACTION_OTHER_AR_FSET : 
		r ? FORMAT_ACTION_USER_FSET :FORMAT_ACTION_OTHER_FSET);
			if (is_current_channel(to, from_server, 0))
			{
	s = fget_string_var((r && ac_reply)?FORMAT_ACTION_USER_AR_FSET: 
		(ac_reply) ? FORMAT_ACTION_AR_FSET:
		r ? FORMAT_ACTION_USER_FSET : FORMAT_ACTION_CHANNEL_FSET);
				put_it("%s", convert_output_format(s, "%s %s %s %s %s",update_clock(GET_TIME), from, FromUserHost, to, ptr1));
			}
			else
				put_it("%s", convert_output_format(s, "%s %s %s %s %s", update_clock(GET_TIME), from, FromUserHost, to, ptr1));
		}
	}
	else
	{
		set_display_target(from, LOG_ACTION);
		if (do_hook(ACTION_LIST, "%s %s %s", from, to, cmd))
			put_it("%s", convert_output_format(fget_string_var(ac_reply?FORMAT_ACTION_AR_FSET:FORMAT_ACTION_FSET), "%s %s %s %s %s", update_clock(GET_TIME), from, FromUserHost, to, ptr1));
	}

	restore_display_target(old_message_from, old_message_level);
	return NULL;
}

/*
 * do_dcc: Records data on an incoming DCC offer. Makes sure it's a
 *	user->user CTCP, as channel DCCs don't make any sense whatsoever
 */
CTCP_HANDLER(do_dcc)
{
	char	*type;
	char	*description = NULL;
	char	*inetaddr = NULL;
	char	*port = NULL;
	char	*size = NULL;
	char	*extra_flags = NULL;
	if (my_stricmp(to, get_server_nickname(from_server)))
		return NULL;
	if (!(type = next_arg(cmd, &cmd)))
		return NULL;
#if 1
	if (!(description = new_next_arg(cmd, &cmd)) || !*description)
		return NULL;
	if     (!(inetaddr = next_arg(cmd, &cmd)) ||
		!(port = next_arg(cmd, &cmd)))
			return NULL;

	size = next_arg(cmd, &cmd);
	extra_flags = next_arg(cmd, &cmd);
#else
	size = last_arg(&cmd);
	port = last_arg(&cmd);
	inetaddr = last_arg(&cmd);
	if (!size || !port || !inetaddr || !description)
		return NULL;
#endif
	register_dcc_type(from, type, description, inetaddr, port, size, extra_flags, FromUserHost, NULL);
	return NULL;
}

/*************** REPLY-GENERATING CTCPS *****************/

/*
 * do_clientinfo: performs the CLIENTINFO CTCP.  If cmd is empty, returns the
 * list of all CTCPs currently recognized by IRCII.  If an arg is supplied,
 * it returns specific information on that CTCP.  If a matching CTCP is not
 * found, an ERRMSG ctcp is returned 
 */
CTCP_HANDLER(do_clientinfo)
{
	int	i;
#ifdef WANT_DLL
	CtcpEntryDll *dll = NULL;
#endif	

	if (get_int_var(CLOAK_VAR))
		return NULL;
	if (cmd && *cmd)
	{
#ifdef WANT_DLL
		for (dll = dll_ctcp; dll; dll = dll->next)
		{
			if (!my_stricmp(cmd, dll->name))
			{
				send_ctcp(CTCP_NOTICE, from, CTCP_CLIENTINFO, 
					"%s %s", 
					dll->name, dll->desc?dll->desc:"none");
				return NULL;
			}
		}
#endif
		for (i = 0; i < NUMBER_OF_CTCPS; i++)
		{
			if (!my_stricmp(cmd, ctcp_cmd[i].name))
			{
				send_ctcp(CTCP_NOTICE, from, CTCP_CLIENTINFO, 
					"%s %s", 
					ctcp_cmd[i].name, ctcp_cmd[i].desc);
				return NULL;
			}
		}
		send_ctcp(CTCP_NOTICE, from, CTCP_ERRMSG,
				"%s: %s is not a valid function",
				ctcp_cmd[CTCP_CLIENTINFO].name, cmd);
	}
	else
	{
		char buffer[BIG_BUFFER_SIZE + 1];
		*buffer = '\0';

		for (i = 0; i < NUMBER_OF_CTCPS; i++)
		{
			strmcat(buffer, ctcp_cmd[i].name, BIG_BUFFER_SIZE);
			strmcat(buffer, space, BIG_BUFFER_SIZE);
		}
#ifdef WANT_DLL
		for (dll = dll_ctcp; dll; dll = dll->next)
		{
			strmcat(buffer, dll->name, BIG_BUFFER_SIZE);
			strmcat(buffer, space, BIG_BUFFER_SIZE);
		}
#endif
		send_ctcp(CTCP_NOTICE, from, CTCP_CLIENTINFO,
			"%s :Use CLIENTINFO <COMMAND> to get more specific information", 
			buffer);
	}
	return NULL;
}

/* do_version: does the CTCP VERSION command */
CTCP_HANDLER(do_version)
{
	char	*tmp;
	char	*version_reply = NULL;
extern	char	tcl_versionstr[];
	/*
	 * The old way seemed lame to me... let's show system name and
	 * release information as well.  This will surely help out
	 * experts/gurus answer newbie questions.  -- Jake [WinterHawk] Khuon
	 *
	 * For the paranoid, UNAME_HACK hides the gory details of your OS.
	 */


#if defined(HAVE_UNAME) && !defined(UNAME_HACK)
	struct utsname un;
	char	*the_unix,
		*the_version;

	if (get_int_var(AUTOKICK_ON_VERSION_VAR))
	{
		char *channel = get_current_channel_by_refnum(0);
		if (channel)
		{
			ChannelList *chan;
			if ((chan = lookup_channel(channel, from_server, CHAN_NOUNLINK)))
			{
				NickList *nick;
				if ((nick = find_nicklist_in_channellist(from, chan, 0)) && !isme(from))
					send_to_server("KICK %s %s :%s", channel, from, "/VER is lame");
			}
		}
		return NULL;
	}
	if (get_int_var(CLOAK_VAR))
		return NULL;
	if (uname(&un) < 0)
	{
		the_version = empty_string;
		the_unix = "unknown";
	}
	else
	{
		the_version = un.release;
		the_unix = un.sysname;
	}
	malloc_strcpy(&version_reply, stripansicodes(convert_output_format(fget_string_var(FORMAT_VERSION_FSET), "%s %s %s %s %s", irc_version, internal_version, the_unix, the_version, tcl_versionstr)));
	send_ctcp(CTCP_NOTICE, from, CTCP_VERSION, "%s :%s", version_reply, 
#else
	if (get_int_var(AUTOKICK_ON_VERSION_VAR))
	{
		char *channel = get_current_channel_by_refnum(0);
		if (channel)
		{
			ChannelList *chan;
			if ((chan = lookup_channel(channel, from_server, CHAN_NOUNLINK)))
			{
				NickList *nick;
				if ((nick = find_nicklist_in_channellist(from, chan, 0)))
					send_to_server("KICK %s %s :%s", channel, from, "/VER is lame");
			}
		}
		return NULL;
	}
	if (get_int_var(CLOAK_VAR))
		return NULL;
	malloc_strcpy(&version_reply, stripansicodes(convert_output_format(fget_string_var(FORMAT_VERSION_FSET), "%s %s %s %s %s", irc_version, internal_version, "*IX", "ÿ", tcl_versionstr)));
	send_ctcp(CTCP_NOTICE, from, CTCP_VERSION, "%s :%s", version_reply, 
#endif
		(tmp = get_string_var(CLIENTINFO_VAR)) ?  tmp : IRCII_COMMENT);
	new_free(&version_reply);
	return NULL;
}

/* do_time: does the CTCP TIME command --- done by Veggen */
CTCP_HANDLER(do_time)
{


	if (get_int_var(CLOAK_VAR))
		return NULL;
	send_ctcp(CTCP_NOTICE, from, CTCP_TIME, 
			"%s", my_ctime(now));
	return NULL;
}

/* do_userinfo: does the CTCP USERINFO command */
CTCP_HANDLER(do_userinfo)
{


	if (get_int_var(CLOAK_VAR))
		return NULL;
	send_ctcp(CTCP_NOTICE, from, CTCP_USERINFO,
			"%s", get_string_var(USERINFO_VAR));
	return NULL;
}

/*
 * do_echo: does the CTCP ERRMSG and CTCP ECHO commands. Does
 * not send an error for ERRMSG and if the CTCP was sent to a channel.
 */
CTCP_HANDLER(do_echo)
{


	if (get_int_var(CLOAK_VAR))
		return NULL;
	if (!is_channel(to))
	{
		if (strlen(cmd) > 60)
		{
			yell("WARNING ctcp echo request longer than 60 chars. truncating");
			cmd[60] = 0;
		}
		send_ctcp(CTCP_NOTICE, from, ctcp->id, "%s", cmd);
	}
	return NULL;
}

CTCP_HANDLER(do_ping)
{


	if (get_int_var(CLOAK_VAR) == 2)
		return NULL;
	send_ctcp(CTCP_NOTICE, from, CTCP_PING, "%s", cmd ? cmd : empty_string);
	return NULL;
}


/* 
 * Does the CTCP FINGER reply 
 */
CTCP_HANDLER(do_finger)
{
	struct	passwd	*pwd;
	time_t	diff;
	char	*tmp;
	char	*ctcpuser,
		*ctcpfinger;
const	char	*my_host;


	if (get_int_var(CLOAK_VAR))
		return NULL;
	if ((my_host = get_server_userhost(from_server)) && strchr(my_host, '@'))
		my_host = strchr(my_host, '@') + 1;
	else
		my_host = hostname;
		
	diff = now - idle_time;

	if (!(pwd = getpwuid(getuid())))
		return NULL;

#ifndef GECOS_DELIMITER
#define GECOS_DELIMITER ','
#endif

	if ((tmp = strchr(pwd->pw_gecos, GECOS_DELIMITER)) != NULL)
		*tmp = '\0';

/* 
 * Three optionsn for handling CTCP replies
 *  + Fascist Bastard Way -- normal, non-hackable fashion
 *  + Winterhawk way (default) -- allows hacking through IRCUSER and
 *	IRCFINGER environment variables
 *  + hop way -- returns a blank always
 */
	/* 
	 * It would be pretty pointless to allow for customisable
	 * usernames if they can track via IRCNAME from the
	 * /etc/passwd file...  We therefore need to either disable
	 * CTCP_FINGER or also make it customisable.  Let's do the
	 * latter because it invokes less suspicion in the long run
	 *				-- Jake [WinterHawk] Khuon
	 */
	if ((ctcpuser = getenv("IRCUSER"))) 
		strmcpy(pwd->pw_name, ctcpuser, NAME_LEN);
	if ((ctcpfinger = getenv("IRCFINGER"))) 
		strmcpy(pwd->pw_gecos, ctcpfinger, NAME_LEN);

	send_ctcp(CTCP_NOTICE, from, CTCP_FINGER, 
		"%s (%s@%s) Idle %ld second%s", 
		pwd->pw_gecos, pwd->pw_name, my_host, diff, plural(diff));
	return NULL;
}


/* 
 * If we recieve a CTCP DCC REJECT in a notice, then we want to remove
 * the offending DCC request
 */
CTCP_HANDLER(do_dcc_reply)
{
	char *subcmd = NULL;
	char *type = NULL;

	if (is_channel(to))
		return NULL;

	if (cmd && *cmd)
		subcmd = next_arg(cmd, &cmd);
	if (cmd && *cmd)
		type = next_arg(cmd, &cmd);

	if (subcmd && type && cmd && !strcmp(subcmd, "REJECT"))
		dcc_reject (from, type, cmd);

	return NULL;
}


/*
 * Handles CTCP PING replies.
 */
CTCP_HANDLER(do_ping_reply)
{
	char *sec, *usec = NULL;
	struct timeval t;
	time_t tsec = 0, tusec = 0, orig;

	if (!cmd || !*cmd)
		return NULL;		/* This is a fake -- cant happen. */

	orig = my_atol(cmd);
	
	get_time(&t);
	if (orig < start_time || orig > t.tv_sec)
		return NULL;
	
       /* We've already checked 'cmd' here, so its safe. */
        sec = cmd;
	tsec = t.tv_sec - my_atol(sec);
        
	if ((usec = strchr(sec, ' ')))
	{
		*usec++ = 0;
		tusec = t.tv_usec - my_atol(usec);
	}
                        
	/*
	 * 'cmd', a variable passed in from do_notice_ctcp()
	 * points to a buffer which is MUCH larger than the
	 * string 'cmd' points at.  So this is safe, even
	 * if it looks "unsafe".
	 */
	sprintf(cmd, "%5.3f seconds", (float)(tsec + (tusec / 1000000.0)));
	return NULL;
}


/************************************************************************/
/*
 * do_ctcp: a re-entrant form of a CTCP parser.  The old one was lame,
 * so i took a hatchet to it so it didnt suck.
 */
extern 	char *do_ctcp (char *from, char *to, char *str)
{
	int 	flag;

	char 	local_ctcp_buffer [BIG_BUFFER_SIZE + 1],
		the_ctcp          [IRCD_BUFFER_SIZE + 1],
		last              [IRCD_BUFFER_SIZE + 1];
	char	*ctcp_command,
		*ctcp_argument;
	int	i;
	char	*ptr = NULL;
	int	allow_ctcp_reply = 1;

#ifdef WANT_DLL
	CtcpEntryDll  *dll = NULL;
#endif
	int delim_char = charcount(str, CTCP_DELIM_CHAR);

	if (delim_char < 2)
		return str;             /* No CTCPs. */

	if (delim_char > 8)
		allow_ctcp_reply = 0;   /* Historical limit of 4 CTCPs */


	flag = check_ignore(from, FromUserHost, to, IGNORE_CTCPS, NULL);

	in_ctcp_flag++;
	strmcpy(local_ctcp_buffer, str, IRCD_BUFFER_SIZE-2);

	for (;;strmcat(local_ctcp_buffer, last, IRCD_BUFFER_SIZE-2))
	{
		split_CTCP(local_ctcp_buffer, the_ctcp, last);

		if (!*the_ctcp)
			break;		/* all done! */
		/*
		 * Apply some integrety rules:
		 * -- If we've already replied to a CTCP, ignore it.
		 * -- If user is ignoring sender, ignore it.
		 * -- If we're being flooded, ignore it.
		 * -- If CTCP was a global msg, ignore it.
		 */

		/*
		 * Yes, this intentionally ignores "unlimited" CTCPs like
		 * UTC and SED.  Ultimately, we have to make sure that
		 * CTCP expansions dont overrun any buffers that might
		 * contain this string down the road.  So by allowing up to
		 * 4 CTCPs, we know we cant overflow -- but if we have more
		 * than 40, it might overflow, and its probably a spam, so
		 * no need to shed tears over ignoring them.  Also makes
		 * the sanity checking much simpler.
		 */
		if (!allow_ctcp_reply)
			continue;

		/*
		 * Check to see if the user is ignoring person.
		 */

		if (flag == IGNORED)
		{
			allow_ctcp_reply = 0;
			continue;
		}
					
		ctcp_command = the_ctcp;
		ctcp_argument = strchr(the_ctcp, ' ');
		if (ctcp_argument)
			*ctcp_argument++ = 0;
		else
			ctcp_argument = empty_string;

		/* Set up the window level/logging */
		if (im_on_channel(to, from_server))
			set_display_target(to, LOG_CTCP);
		else
			set_display_target(from, LOG_CTCP);

		/* Global messages -- just drop the CTCP */
		if (*to == '$' || (*to == '#' && !lookup_channel(to, from_server, 0)))
		{
			allow_ctcp_reply = 0;
			continue;
		}
#ifdef WANT_DLL
		/* Find the correct CTCP and run it. */
		for (dll = dll_ctcp; dll; dll = dll->next)
			if (!strcmp(dll->name, ctcp_command))
				break;  			
#endif
		for (i = 0; i < NUMBER_OF_CTCPS; i++)
			if (!strcmp(ctcp_command, ctcp_cmd[i].name))
				break;

		/* Set up the window level/logging */
		if (im_on_channel(to, from_server))
			set_display_target(to, LOG_CTCP);
		else
			set_display_target(NULL, LOG_CTCP);

#ifdef WANT_DLL
		if (!dll && ctcp_cmd[i].id == CTCP_ACTION)
			check_flooding(from, CTCP_ACTION_FLOOD, ctcp_argument, is_channel(to)?to:NULL);
		else if (!dll && ctcp_cmd[i].id == CTCP_DCC)
			check_flooding(from, CTCP_FLOOD, ctcp_argument, is_channel(to)?to:NULL);
#else
		if (ctcp_cmd[i].id == CTCP_ACTION)
			check_flooding(from, CTCP_ACTION_FLOOD, ctcp_argument, is_channel(to)?to:NULL);
		else if (ctcp_cmd[i].id == CTCP_DCC)
			check_flooding(from, CTCP_FLOOD, ctcp_argument, is_channel(to)?to:NULL);
#endif
		else
		{
			check_flooding(from, CTCP_FLOOD, ctcp_argument, is_channel(to)?to:NULL);
			if (get_int_var(NO_CTCP_FLOOD_VAR) && (now - get_server_last_ctcp_time(from_server) < get_int_var(CTCP_DELAY_VAR)))
			{
				if (get_int_var(FLOOD_WARNING_VAR))
					put_it("%s", convert_output_format(fget_string_var(FORMAT_FLOOD_FSET), "%s %s %s %s %s", update_clock(GET_TIME),ctcp_command,from, FromUserHost, to));
				set_server_last_ctcp_time(from_server, time(NULL));
				allow_ctcp_reply = 0;
				continue;
			}
		}
		/* Did the CTCP search work? */
#ifdef WANT_DLL
		if (i == NUMBER_OF_CTCPS && !dll)
#else
		if (i == NUMBER_OF_CTCPS)
#endif
		{
			/*
			 * Offer it to the user.
			 * Maybe they know what to do with it.
			 */
#ifdef WANT_TCL
			if (check_tcl_ctcp(from, FromUserHost, from, to, ctcp_command, ctcp_argument))
				continue;
#endif

			if (do_hook(CTCP_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument))
			{
				if (allow_ctcp_reply && get_int_var(CTCP_VERBOSE_VAR))
				{
#ifdef WANT_USERLIST
					if (lookup_userlevelc("*", FromUserHost, "*", NULL))
						put_it("%s", convert_output_format(fget_string_var(get_int_var(CLOAK_VAR)? FORMAT_CTCP_CLOAK_UNKNOWN_USER_FSET:FORMAT_CTCP_UNKNOWN_USER_FSET),
							"%s %s %s %s %s %s",update_clock(GET_TIME), from, FromUserHost, to, ctcp_command, *ctcp_argument? ctcp_argument : empty_string));
					else
#endif
						put_it("%s", convert_output_format(fget_string_var(get_int_var(CLOAK_VAR)? FORMAT_CTCP_CLOAK_UNKNOWN_FSET:FORMAT_CTCP_UNKNOWN_FSET),
							"%s %s %s %s %s %s",update_clock(GET_TIME), from, FromUserHost, to, ctcp_command, *ctcp_argument? ctcp_argument : empty_string));
				}
			}
			allow_ctcp_reply = 0;
			continue;
		}

#ifdef WANT_TCL
		check_tcl_ctcp(from, FromUserHost, from, to, ctcp_command, ctcp_argument);
#endif

#ifdef WANT_DLL
		if (dll)
			ptr = (dll->func) (dll, from, to, ctcp_argument);
		else
#endif
			ptr = ctcp_cmd[i].func(ctcp_cmd + i, from, to, ctcp_argument);

#ifdef WANT_DLL
		if (!(ctcp_cmd[i].flag & CTCP_NOLIMIT) || (dll && !(dll->flag & CTCP_NOLIMIT)))
#else
		if (!(ctcp_cmd[i].flag & CTCP_NOLIMIT))
#endif
		{
			set_server_last_ctcp_time(from_server, time(NULL));
			allow_ctcp_reply = 0;
		}

		/*
		 * We've only gotten to this point if its a valid CTCP
		 * query and we decided to parse it.
		 */

		/* If its an inline CTCP paste it back in */
#ifdef WANT_DLL
		if ((ctcp_cmd[i].flag & CTCP_INLINE) || (dll && (dll->flag & CTCP_INLINE)))
			strmcat(local_ctcp_buffer, ptr, BIG_BUFFER_SIZE);
#else
		if ((ctcp_cmd[i].flag & CTCP_INLINE))
			strmcat(local_ctcp_buffer, ptr, BIG_BUFFER_SIZE);
#endif
		/* If its interesting, tell the user. */
#ifdef WANT_DLL
		if ((ctcp_cmd[i].flag & CTCP_TELLUSER) || (dll && (dll->flag & CTCP_TELLUSER)))
#else
		if ((ctcp_cmd[i].flag & CTCP_TELLUSER))
#endif
		{
			if (do_hook(CTCP_LIST, "%s %s %s %s", from, to, ctcp_command, ctcp_argument))
			{
				if (get_int_var(CTCP_VERBOSE_VAR))
#ifdef WANT_USERLIST
					put_it("%s", convert_output_format(fget_string_var((!lookup_userlevelc("*",FromUserHost, "*", NULL))? get_int_var(CLOAK_VAR)?FORMAT_CTCP_CLOAK_FSET:FORMAT_CTCP_FSET:get_int_var(CLOAK_VAR)?FORMAT_CTCP_CLOAK_USER_FSET:FORMAT_CTCP_USER_FSET),
						"%s %s %s %s %s %s", update_clock(GET_TIME), from, FromUserHost, to, ctcp_command, *ctcp_argument? ctcp_argument : empty_string));
#else
					put_it("%s", convert_output_format(fget_string_var((get_int_var(CLOAK_VAR)?FORMAT_CTCP_CLOAK_FSET:FORMAT_CTCP_FSET)),
						"%s %s %s %s %s %s", update_clock(GET_TIME), from, FromUserHost, to, ctcp_command, *ctcp_argument? ctcp_argument : empty_string));
#endif
			}
		}

		new_free(&ptr);
	}

	/* Reset the window level/logging */
	reset_display_target();

	in_ctcp_flag--;
	if (*local_ctcp_buffer)
		return strcpy(str, local_ctcp_buffer);
	else
		return empty_string;
}



/*
 * do_notice_ctcp: a re-entrant form of a CTCP reply parser.
 */
extern 	char *do_notice_ctcp (char *from, char *to, char *str)
{
	int 	flag;
	char 	local_ctcp_buffer [BIG_BUFFER_SIZE + 1],
		the_ctcp          [IRCD_BUFFER_SIZE + 1],
		last              [IRCD_BUFFER_SIZE + 1];
	char	*ctcp_command,
		*ctcp_argument;
	int	i;
	char	*ptr;
	char	*tbuf = NULL;
	int	allow_ctcp_reply = 1;

#ifdef WANT_DLL
	CtcpEntryDll  *dll = NULL;
#endif

	int delim_char = charcount(str, CTCP_DELIM_CHAR);

	if (delim_char < 2)
		return str;		/* No CTCPs. */
	if (delim_char > 8)
		allow_ctcp_reply = 0;	/* Ignore all the CTCPs. */

	flag = check_ignore(from, FromUserHost, to, IGNORE_CTCPS, NULL);
	if (!in_ctcp_flag)
		in_ctcp_flag = -1;

	tbuf = stripansi(str);
	strmcpy(local_ctcp_buffer, tbuf, IRCD_BUFFER_SIZE-2);
	new_free(&tbuf);
		
	for (;;strmcat(local_ctcp_buffer, last, IRCD_BUFFER_SIZE-2))
	{
		split_CTCP(local_ctcp_buffer, the_ctcp, last);
		if (!*the_ctcp)
			break;		/* all done! */

		if (!allow_ctcp_reply)
			continue;
			
		if (flag == IGNORED)
		{
			allow_ctcp_reply = 0;
			continue;
		}

		/* Global messages -- just drop the CTCP */
		if (*to == '$' || (*to == '#' && !lookup_channel(to, from_server, 0)))
		{
			allow_ctcp_reply = 0;
			continue;
		}

		ctcp_command = the_ctcp;
		ctcp_argument = strchr(the_ctcp, ' ');
		if (ctcp_argument)
			*ctcp_argument++ = 0;
		else
			ctcp_argument = empty_string;
		
#ifdef WANT_DLL
		/* Find the correct CTCP and run it. */
		for (dll = dll_ctcp; dll; dll = dll->next)
			if (!strcmp(dll->name, ctcp_command))
				break;  			
#endif
		/* Find the correct CTCP and run it. */
		for (i = 0; i < NUMBER_OF_CTCPS; i++)
			if (!strcmp(ctcp_command, ctcp_cmd[i].name))
				break;

		/* 
		 * If we've already parsed one, and there is a limit
		 * on this CTCP, then just punt it.
		 */
#ifdef WANT_DLL
		if (i < NUMBER_OF_CTCPS && !dll && ctcp_cmd[i].repl)
#else
		if (i < NUMBER_OF_CTCPS && ctcp_cmd[i].repl)
#endif
		{
			if ((ptr = ctcp_cmd[i].repl(ctcp_cmd + i, from, to, ctcp_argument)))
			{
				strmcat(local_ctcp_buffer, ptr, BIG_BUFFER_SIZE);
				new_free(&ptr);
				continue;
			}
		}
#ifdef WANT_DLL
		if (dll && dll->repl)
		{
			if ((ptr = dll->repl(dll, from, to, ctcp_argument)))
			{
				strmcat(local_ctcp_buffer, ptr, BIG_BUFFER_SIZE);
				new_free(&ptr);
				continue;
			}
		}
#endif
#ifdef WANT_TCL
		check_tcl_ctcr(from, FromUserHost, from, to, ctcp_command, ctcp_argument?ctcp_argument:empty_string);
#endif
		/* Set up the window level/logging */
		set_display_target(from, LOG_CTCP);
		/* Toss it at the user.  */
		if (do_hook(CTCP_REPLY_LIST, "%s %s %s", from, ctcp_command, ctcp_argument))
		{
			put_it("%s", convert_output_format(fget_string_var(FORMAT_CTCP_REPLY_FSET),"%s %s %s %s %s", update_clock(GET_TIME), from, FromUserHost, ctcp_command, ctcp_argument));
			add_last_type(&last_ctcp[0], 1, from, FromUserHost, ctcp_command, ctcp_argument);
		}

		allow_ctcp_reply = 0;
	}

	if (in_ctcp_flag == -1)
		in_ctcp_flag = 0;
	/* Reset the window level/logging */
	reset_display_target();

	return strcpy(str, local_ctcp_buffer);
}



/* in_ctcp: simply returns the value of the ctcp flag */
extern int	in_ctcp (void) { return (in_ctcp_flag); }



/*
 * This is no longer directly sends information to its target.
 * As part of a larger attempt to consolidate all data transmission
 * into send_text, this function was modified so as to use send_text().
 * This function can send both direct CTCP requests, as well as the
 * appropriate CTCP replies.  By its use of send_text(), it can send
 * CTCPs to DCC CHAT and irc nickname peers, and handles encryption
 * transparantly.  This greatly reduces the logic, complexity, and
 * possibility for error in this function.
 */
extern	void	send_ctcp (int type, char *to, int datatag, char *format, ...)
{
	char putbuf [BIG_BUFFER_SIZE + 1],
	     *putbuf2;
	int len;
	
	if ((len = IRCD_BUFFER_SIZE - (12 + strlen(to))) < 0)
		return;

	if (len < strlen(ctcp_cmd[datatag].name) + 3)
		return;
	
	putbuf2 = alloca(len);

	if (format)
	{
		va_list args;
		va_start(args, format);
		vsnprintf(putbuf, BIG_BUFFER_SIZE, format, args);
		va_end(args);
		
		do_hook(SEND_CTCP_LIST, "%s %s %s %s", 
				ctcp_type[type], to, 
				ctcp_cmd[datatag].name, putbuf);
		snprintf(putbuf2, len, "%c%s %s%c", 
				CTCP_DELIM_CHAR, 
				ctcp_cmd[datatag].name, putbuf, 
				CTCP_DELIM_CHAR);
	}
	else
	{
		do_hook(SEND_CTCP_LIST, "%s %s %s", 
				ctcp_type[type], to, 
				ctcp_cmd[datatag].name);
		snprintf(putbuf2, len, "%c%s%c", 
				CTCP_DELIM_CHAR, 
				ctcp_cmd[datatag].name, 
				CTCP_DELIM_CHAR);
	}

	putbuf2[len - 2] = CTCP_DELIM_CHAR;
	putbuf2[len - 1] = 0;
	send_text(to, putbuf2, ctcp_type[type], 0, 0);
}


/*
 * quote_it: This quotes the given string making it sendable via irc.  A
 * pointer to the length of the data is required and the data need not be
 * null terminated (it can contain nulls).  Returned is a malloced, null
 * terminated string.   
 */
extern 	char	*ctcp_quote_it (char *str, int len)
{
	char	buffer[BIG_BUFFER_SIZE + 1];
	char	*ptr;
	int	i;

	ptr = buffer;
	for (i = 0; i < len; i++)
	{
		switch (str[i])
		{
			case CTCP_DELIM_CHAR:	*ptr++ = CTCP_QUOTE_CHAR;
						*ptr++ = 'a';
						break;
			case '\n':		*ptr++ = CTCP_QUOTE_CHAR;
						*ptr++ = 'n';
						break;
			case '\r':		*ptr++ = CTCP_QUOTE_CHAR;
						*ptr++ = 'r';
						break;
			case CTCP_QUOTE_CHAR:	*ptr++ = CTCP_QUOTE_CHAR;
						*ptr++ = CTCP_QUOTE_CHAR;
						break;
			case '\0':		*ptr++ = CTCP_QUOTE_CHAR;
						*ptr++ = '0';
						break;
			default:		*ptr++ = str[i];
						break;
		}
	}
	*ptr = '\0';
	return m_strdup(buffer);
}

/*
 * ctcp_unquote_it: This takes a null terminated string that had previously
 * been quoted using ctcp_quote_it and unquotes it.  Returned is a malloced
 * space pointing to the unquoted string.  NOTE: a trailing null is added for
 * convenied, but the returned data may contain nulls!.  The len is modified
 * to contain the size of the data returned. 
 */
extern	char	*ctcp_unquote_it (char *str, int *len)
{
	char	*buffer;
	char	*ptr;
	char	c;
	int	i,
		new_size = 0;

	buffer = (char *) new_malloc((sizeof(char) * *len) + 1);
	ptr = buffer;
	i = 0;
	while (i < *len)
	{
		if ((c = str[i++]) == CTCP_QUOTE_CHAR)
		{
			switch (c = str[i++])
			{
				case CTCP_QUOTE_CHAR:
					*ptr++ = CTCP_QUOTE_CHAR;
					break;
				case 'a':
					*ptr++ = CTCP_DELIM_CHAR;
					break;
				case 'n':
					*ptr++ = '\n';
					break;
				case 'r':
					*ptr++ = '\r';
					break;
				case '0':
					*ptr++ = '\0';
					break;
				default:
					*ptr++ = c;
					break;
			}
		}
		else
			*ptr++ = c;
		new_size++;
	}
	*ptr = '\0';
	*len = new_size;
	return (buffer);
}

int get_ctcp_val (char *str)
{
	int i;

	for (i = 0; i < NUMBER_OF_CTCPS; i++)
		if (!strcmp(str, ctcp_cmd[i].name))
			return i;

	/*
	 * This is *dangerous*, but it works.  The only place that
	 * calls this function is edit.c:ctcp(), and it immediately
	 * calls send_ctcp().  So the pointer that is being passed
	 * to us is globally allocated at a level higher then ctcp().
	 * so it wont be bogus until some time after ctcp() returns,
	 * but at that point, we dont care any more.
	 */
	ctcp_cmd[CTCP_CUSTOM].name = str;
	return CTCP_CUSTOM;
}



/*
 * XXXX -- some may call this a hack, but if youve got a better
 * way to handle this job, id love to use it.
 */
void BX_split_CTCP(char *raw_message, char *ctcp_dest, char *after_ctcp)
{
	char *ctcp_start, *ctcp_end;

	*ctcp_dest = *after_ctcp = 0;
	ctcp_start = strchr(raw_message, CTCP_DELIM_CHAR);
	if (!ctcp_start)
		return;		/* No CTCPs present. */

	*ctcp_start++ = 0;
	ctcp_end = strchr(ctcp_start, CTCP_DELIM_CHAR);
	if (!ctcp_end)
	{
		*--ctcp_start = CTCP_DELIM_CHAR;
		return;		/* Thats _not_ a CTCP. */
	}

	*ctcp_end++ = 0;
	strmcpy(ctcp_dest, ctcp_start, IRCD_BUFFER_SIZE-2);
	strmcpy(after_ctcp, ctcp_end, IRCD_BUFFER_SIZE-2);

	return;		/* All done! */
}
