/*
 * dcc.c: Things dealing client to client connections. 
 *
 * Written By Troy Rollo <troy@cbme.unsw.oz.au> 
 *
 * Copyright(c) 1991 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#if 0
static	char	rcsid[] = "@(#)$Id: dcc.c,v 1.37 1994/07/26 15:33:23 mrg Exp $";
#endif

#include "irc.h"

#ifdef ESIX
# include <lan/net_types.h>
#endif /* ESIX */

#if defined(ISC30) && defined(_POSIX_SOURCE)
# undef _POSIX_SOURCE
#include <sys/stat.h>
# define _POSIX_SOURCE
#else
# include <sys/stat.h>
#endif /* ICS30 || _POSIX_SOURCE */

#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#include <stdio.h>

#include "crypt.h"
#include "ctcp.h"
#include "dcc.h"
#include "hook.h"
#include "ircaux.h"
#include "lastlog.h"
#include "newio.h"
#include "output.h"
#include "parse.h"
#include "server.h"
#include "status.h"
#include "vars.h"
#include "whois.h"
#include "window.h"
#include "term.h"
#include "notice.h"


typedef	struct	DCC_struct
{
		unsigned	flags;
		int		read;
		int		write;
		int		file;
		unsigned long	filesize;
		char *		description;
		char *		user;
		char *		othername;
		char *		encrypt;
	struct	DCC_struct *	next;
	struct	in_addr		remote;
		u_short		remport;
		u_short		local_port;
		long		bytes_read;
		long		bytes_sent;
		int		window_sent;
		int		window_max;
	struct	timeval		lasttime;
	struct	timeval		starttime;
		char		*buffer;
		char *		cksum;
		long		packets_total;
		long		packets_transfer;
		long		packets_ack;
		long		packets_outstanding;
}	DCC_list;

static	off_t		filesize = 0;
static	DCC_list *	ClientList = NULL;
static	char		DCC_current_transfer_buffer[256];
static	int		DCC_done = 1;
static 	char 		DCC_reject_type[12];
static 	char 		DCC_reject_description[40];

static	void 		dcc_add_deadclient _((DCC_list *));
static	void		dcc_chat _((char *));
static	void 		dcc_close _((char *));
static	void		dcc_erase _((DCC_list *));
static	void		dcc_filesend _((char *));
static	void		dcc_getfile _((char *));
static	int		dcc_open _((DCC_list *));
static	void 		dcc_really_erase _((void));
static	void		dcc_rename _((char *));
static	DCC_list *	dcc_searchlist _((char *, char *, int, int, char *, int));
static	void		dcc_send_raw _((char *));
static	void 		output_reject_ctcp _((char *, char *));
static	void		process_incoming_chat _((DCC_list *));
static	void		process_incoming_listen _((DCC_list *));
static	void		process_incoming_raw _((DCC_list *));
static	void		process_outgoing_file _((DCC_list *));
static	void		process_incoming_file _((DCC_list *));
static	void		file_checksum _((unsigned char foo[], char *));
static	void		DCC_close_filesend _((DCC_list *, char *));
static	void		update_transfer_buffer _((char *format, ...));

#ifdef MIRC_BROKEN_DCC_RESUME
static	void		dcc_getfile_resume _((char *));
static 	void 		dcc_getfile_resume_demanded _((char *user, char *filename, char *port, char *offset));
static	void		dcc_getfile_resume_start _((char *nick, char *filename, char *port, char *offset));
#endif


/*
 * These are the possible <type> arguments to /DCC
 */
typedef void (*dcc_function) _((char *));
struct
{
	char	*	name;
	dcc_function 	function;
}	dcc_commands[] =
{
	{ "CHAT",	dcc_chat 		},	/* DCC_CHAT */
	{ "SEND",	dcc_filesend 		},	/* DCC_FILEOFFER */
	{ "GET",	dcc_getfile 		},	/* DCC_FILEREAD */
	{ "RAW",	dcc_send_raw 		},	/* DCC_RAW */

	{ "CLOSE",	dcc_close 		},
	{ "LIST",	dcc_list 		},
	{ "RENAME",	dcc_rename 		},
#ifdef MIRC_BROKEN_DCC_RESUME
	{ "RESUME",	dcc_getfile_resume 	},
#endif
	{ NULL,		(dcc_function) NULL 	}
};

/*
 * These are the possible types of DCCs that can be open.
 */
char	*dcc_types[] =
{
	"<none>",
	"CHAT",		/* DCC_CHAT */
	"SEND",		/* DCC_FILEOFFER */
	"GET",		/* DCC_FILEREAD */
	"RAW",		/* DCC_RAW */
	"RAW_LISTEN",
	NULL
};

struct	deadlist
{
	DCC_list *it;
	struct deadlist *next;
}	*deadlist = NULL;


/*
 * When a DCC is considered to be "invalid", the DCC_DELETE bit is set.
 * The structure is not immediately removed, however -- it sticks around
 * until the next looping synchornization point where dcc_check() collects
 * the "deleted" DCC structures into the 'deadlist'.  After all of the
 * DCCs have been parsed, it then goes through and cleans the list.  This
 * ensures that no DCCs are removed from under the feet of some unsuspecting
 * function -- they are only deleted synchronously.
 *
 * The downside is that it requires an M*N algorithm to remove the DCCs.
 */
#ifdef __STDC__
static	void dcc_add_deadclient(DCC_list *client)
#else
static	void dcc_add_deadclient(client)
	DCC_list *client;
#endif
{
	struct deadlist *new;

	new = (struct deadlist *) new_malloc(sizeof(struct deadlist));
	new->next = deadlist;
	new->it = client;
	deadlist = new;
}

static void dcc_really_erase _((void))
{
	struct deadlist *dies;

	while ((dies = deadlist))
	{
		deadlist = deadlist->next;
		dcc_erase(dies->it);
	}
}

#ifdef __STDC__
static 	void		dcc_erase(DCC_list *Element)
#else
static 	void 		dcc_erase(Element)
	DCC_list	*Element;
#endif
{
	DCC_list	**Client;

	for (Client = &ClientList; *Client; Client = &(**Client).next)
	{
		if (*Client != Element)
			continue;

		*Client = Element->next;
		if (Element->write != -1)
			close(Element->write);
		if (Element->read != -1)
		{
			FD_CLR(Element->read, &readables);
			close(Element->read);
		}
		if (Element->file != -1)
			close(Element->file);

		new_free(&Element->description);
		new_free(&Element->user);
		new_free(&Element->buffer);
		new_free((char **)&Element);
		return;
	}
}

/*
 * close_all_dcc:  We call this when we create a new process so that
 * we don't leave any fd's lying around, that won't close when we
 * want them to..
 */
extern void close_all_dcc _((void))
{
	DCC_list *Client;

	while ((Client = ClientList))
		dcc_erase(Client);
}




/*
 * These functions handle important DCC jobs.
 */


/*
 * dcc_searchlist searches through the dcc_list and finds the client
 * with the the flag described in type set.
 */
#ifdef __STDC__
static	DCC_list *dcc_searchlist(char *name, char *user, int type, int flag, char *othername, int active)
#else
static	DCC_list *dcc_searchlist(name, user, type, flag, othername, active)
	char	*name,
		*user;
	int	type,
		flag;
	char	*othername;
	int	active;
#endif
{
	DCC_list **Client, *NewClient;
	char *last = NULL;

if (x_debug)
	yell("entering dcc_sl.  name (%s) user (%s) type (%d) flag (%d) other (%s) active (%d)", name, user, type, flag, othername, active);

	for (Client = (&ClientList); *Client ; Client = (&(**Client).next))
	{
if (x_debug)
	yell("checking against  name (%s) user (%s) type (%d) flag (%d) other (%s) active (%x) flag (%x)", 
	(**Client).description, (**Client).user, (**Client).flags & DCC_TYPES,
	(**Client).flags, (**Client).othername, (**Client).flags & DCC_ACTIVE);


		/*
		 * Ok. first of all, it has to be the right type.
		 */
		if (((**Client).flags & DCC_TYPES) != type)
			continue;

		/*
		 * Its OK if the user matches the client's user
		 */
		if (my_stricmp(user, (**Client).user))
			continue;


		/*
		 * Its OK as long as one of the following is true:
		 *  'name' or 'description' is NULL
		 *  'name' is the same as 'description'
		 *  'name' is the same as last portion of 'description'
		 *  'othername' is NULL
		 *  'othername' is the same as client's 'othername'.
		 */
		if (name && (**Client).description)
		{
			if (my_stricmp(name, (**Client).description))
			{
				last = rindex((**Client).description, '/');
				if (!last || my_stricmp(name, last))
				{
					if (othername && (**Client).othername)
					{
						if (my_stricmp(othername, (**Client).othername))
							continue;
					}
					else
						continue;
				}
			}
		}

		/*
		 * If the user wants something that is INACTIVE,
		 * its OK if its not active.
		 */
		if (active == 0)
			if (((**Client).flags & DCC_ACTIVE) != 0)
				continue;

		/*
		 * If the user wants something that is ACTIVE,
		 * its OK if its active.
		 */
		if (active == 1)
			if (((**Client).flags & DCC_ACTIVE) == 0)
				continue;

		if (x_debug)
			yell("We have a winner!");

		/* Looks like we have a winner! */
		return *Client;
	}

	if (!flag)
		return NULL;
	*Client = NewClient = (DCC_list *) new_malloc(sizeof(DCC_list));
	NewClient->flags = type;
	NewClient->read = NewClient->write = NewClient->file = -1;
	NewClient->filesize = filesize;
	NewClient->packets_total = filesize ? (filesize / DCC_BLOCK_SIZE + 1) : 0;
	NewClient->packets_transfer = 0;
	NewClient->packets_outstanding = 0;
	NewClient->packets_ack = 0;
	NewClient->next = (DCC_list *) 0;
	NewClient->user = NewClient->description = NewClient->othername = NULL;
	NewClient->bytes_read = NewClient->bytes_sent = 0L;
	NewClient->starttime.tv_sec = 0;
	NewClient->starttime.tv_usec = 0;
	NewClient->buffer = 0;
	NewClient->window_max = 0;
	NewClient->window_sent = 0;
	NewClient->local_port = 0;
	NewClient->cksum = NULL;
	NewClient->encrypt = NULL;
	malloc_strcpy(&NewClient->description, name);
	malloc_strcpy(&NewClient->user, user);
	malloc_strcpy(&NewClient->othername, othername);
	get_time(&NewClient->lasttime);
	return NewClient;
}

/*
 * Added by Chaos: Is used in edit.c for checking redirect.
 */
#ifdef __STDC__
extern int	dcc_active (char *user)
#else
extern int	dcc_active (user)
	char	*user;
#endif
{
	return (dcc_searchlist("chat", user, DCC_CHAT, 0, (char *) 0, 1)) ? 1 : 0;
}



/*
 * Whenever a DCC changes state from WAITING->ACTIVE, it calls this function
 * to initiate the internet connection for the transaction.
 */
#ifdef __STDC__
static	int		dcc_open (DCC_list *Client)
#else
static	int 		dcc_open(Client)
	DCC_list	*Client;
#endif
{
	char    *	user,
		*	Type;
struct	sockaddr_in	remaddr;
struct	in_addr 	myip;
	int		rl = sizeof(remaddr);
	int		old_server, jvs_blah;
#ifdef MIRC_BROKEN_DCC_RESUME
	char		buf[10];
#endif

	user = Client->user;
	old_server = from_server;
	if (from_server == -1)
		from_server = get_window_server(0);
	myip.s_addr = server_list[from_server].local_addr.s_addr;
	Type = dcc_types[Client->flags & DCC_TYPES];

	if (Client->flags & DCC_OFFER)
	{
		message_from((char *) 0, LOG_DCC);

		Client->remport = ntohs(Client->remport);
		if ((Client->write = connect_by_number(inet_ntoa(Client->remote), &Client->remport, SERVICE_CLIENT, PROTOCOL_TCP)) < 0)
		{
			say("Unable to create connection: %s", errno ? sys_errlist[errno] : "Unknown Host");
			message_from((char *) 0, LOG_CURRENT);
			Client->flags |= DCC_DELETE;
			from_server = old_server;
			return 0;
		}

		Client->remport = htons(Client->remport);
		Client->read = Client->write;
		FD_SET(Client->read, &readables);
		Client->flags &= ~DCC_OFFER;
		Client->flags |= DCC_ACTIVE;
		getpeername(Client->read, (struct sockaddr *) &remaddr, &rl);
		if ((Client->flags & DCC_TYPES) != DCC_RAW)
		{
                        /* It would be nice if SEND also showed the filename
                           and size (since it's possible to have multiple
                           SEND requests queued), so we check for a file size
                           first. */
                        if ( Client->filesize )
                                jvs_blah = do_hook(DCC_CONNECT_LIST,
                                           "%s %s %s %d %s %d", user, Type,
                                           inet_ntoa(Client->remote),
                                           ntohs(Client->remport),
                                           Client->description,
                                           Client->filesize);
                        else
                                jvs_blah = do_hook(DCC_CONNECT_LIST,
                                           "%s %s %s %d", user, Type,
                                           inet_ntoa(Client->remote),
                                           ntohs(Client->remport));
                        if ( jvs_blah )
			say("DCC %s connection with %s[%s %d] established",
				Type, user, inet_ntoa(remaddr.sin_addr),
				ntohs(remaddr.sin_port));
		}
		message_from((char *) 0, LOG_CURRENT);
		get_time(&Client->starttime);
		from_server = old_server;
		return 1;
	}
	else
	{
		unsigned short portnum = Client->local_port;

		Client->flags |= DCC_WAIT;
		message_from((char *) 0, LOG_DCC);
		if ((Client->read = connect_by_number(NULL, &portnum, SERVICE_SERVER, PROTOCOL_TCP)) < 0)
		{
			say("Unable to create connection: %s", errno ? sys_errlist[errno] : "Unknown Host");
			message_from((char *) 0, LOG_CURRENT);
			Client->flags |= DCC_DELETE;
			from_server = old_server;
			return 0;
		}
		if (Client->flags & DCC_TWOCLIENTS)
		{
			/* patch to NOT send pathname accross */
			char	*nopath;

			if ((Client->flags & DCC_FILEOFFER) && (nopath = rindex(Client->description, '/')))
				nopath++;
			else
				nopath = Client->description;

			if (Client->filesize)
			{
#ifdef USE_DCC_CHECKSUM
				unsigned char foo[4];
				file_checksum(foo, Client->description);
				Client->cksum = m_sprintf("%x%x%x%x", foo[0], foo[1], foo[2], foo[3]);
				send_ctcp(CTCP_PRIVMSG, user, CTCP_DCC,
					 "%s %s %lu %u %d %s", 
					 Type, nopath,
					 (u_long) ntohl(myip.s_addr),
					 (u_short) portnum,
					 Client->filesize, Client->cksum);
#else
				send_ctcp(CTCP_PRIVMSG, user, CTCP_DCC,
					 "%s %s %lu %u %d", 
					 Type, nopath,
					 (u_long) ntohl(myip.s_addr),
					 (u_short) portnum,
					 Client->filesize);
#endif
			}
			else
				send_ctcp(CTCP_PRIVMSG, user, CTCP_DCC,
					 "%s %s %lu %u", 
					 Type, nopath,
					 (u_long) ntohl(myip.s_addr),
					 (u_short) portnum);

			message_from((char *) 0, LOG_DCC);
			say("Sent DCC %s request to %s", Type, user);
			message_from((char *) 0, LOG_CURRENT);
		}

#ifdef MIRC_BROKEN_DCC_RESUME
		sprintf(buf, "%d", (int) portnum);
		malloc_strcpy(&Client->othername, buf);
#endif

		FD_SET(Client->read, &readables);
		Client->starttime.tv_sec = 0;
		Client->starttime.tv_usec = 0;
		Client->local_port = portnum;
		from_server = old_server;
		return 2;
	}
}


/* flag == 1 means show it.  flag == 0 used by redirect (and /ctcp) */
#ifdef __STDC__
extern void	dcc_message_transmit (char *user, char *text, int type, int flag, char *cmd)
#else
extern void 	dcc_message_transmit(user, text, type, flag, cmd)
	char	*user;
	char	*text;
	int	type,
		flag;
	char	*cmd;
#endif
{
	DCC_list	*Client;
	char		tmp[DCC_BLOCK_SIZE+1];
	int		lastlog_level;
	char		thing = '\0';
	int		list = 0;
	char		*host = (char *) 0;

	tmp[0] = 0;

	switch(type)
	{
		case DCC_CHAT:
		{
			host = "chat";
			thing = '=';
			list = SEND_DCC_CHAT_LIST;
			break;
		}
		case DCC_RAW:
		{
			host = next_arg(text, &text);
			if (!host)
			{
				say("No host specified for DCC RAW");
				return;
			}
			break;
		}
	}
	if (!(Client = dcc_searchlist(host, user, type, 0, (char *) 0, 1)) || !(Client->flags&DCC_ACTIVE))
	{
		say("No active DCC %s:%s connection for %s",
			dcc_types[type], host?host:"<any>", user);
		return;
	}
	lastlog_level = set_lastlog_msg_level(LOG_DCC);
	message_from(Client->user, LOG_DCC);

	/*
	 * Check for CTCPs... whee.
	 */
	if (*text == CTCP_DELIM_CHAR)
	{
		if (!strcmp(cmd, "PRIVMSG"))
			strmcpy(tmp, "CTCP_MESSAGE ", DCC_BLOCK_SIZE);
		else
			strmcpy(tmp, "CTCP_REPLY ", DCC_BLOCK_SIZE);
	}

	strmcat(tmp, text, DCC_BLOCK_SIZE);
	strmcat(tmp, "\n", DCC_BLOCK_SIZE); 
	if (x_debug) yell("-> [%s]", tmp);
	write(Client->write, tmp, strlen(tmp));
	Client->bytes_sent += strlen(tmp);

	if (flag && type != DCC_RAW)
	{
		if (do_hook(list, "%s %s", Client->user, text))
			put_it("=> %c%s%c %s", thing, Client->user, thing, text);
	}
	set_lastlog_msg_level(lastlog_level);
	message_from((char *) 0, LOG_CURRENT);
	return;
}

#ifdef __STDC__
extern void	dcc_chat_transmit (char *user, char *text, char *type)
#else
extern void 	dcc_chat_transmit(user,	text, type)
	char	*user;
	char	*text;
	char	*type;
#endif
{
	dcc_message_transmit(user, text, DCC_CHAT, 0, type);
}




/*
 *
 * All these functions are user-generated -- that is, they are all called
 * when the user does /DCC <command> or one of the DCC-oriented built in
 * functions.
 *
 */


/*
 * The /DCC command.  Delegates the work to other functions.
 */
#ifdef __STDC__
extern void	process_dcc(char *args)
#else
extern void 	process_dcc(args)
	char	*args;
#endif
{
	char	*command;
	int	i;
	int	lastlog_level;

	if (!(command = next_arg(args, &args)))
		return;
	for (i = 0; dcc_commands[i].name != NULL; i++)
	{
		if (!my_stricmp(dcc_commands[i].name, command))
		{
			message_from((char *) 0, LOG_DCC);
			lastlog_level = set_lastlog_msg_level(LOG_DCC);
			dcc_commands[i].function(args);
			message_from((char *) 0, LOG_CURRENT);
			(void) set_lastlog_msg_level(lastlog_level);
			return;
		}
	}
	say("Unknown DCC command: %s", command);
}


/*
 * Usage: /DCC CHAT <nick> [-e passkey]
 */
#ifdef __STDC__
static void	dcc_chat (char *args)
#else
static void 	dcc_chat(args)
	char	*args;
#endif
{
	char	*user;
	char	*passwd = NULL;
	DCC_list	*Client;

	if ((user = next_arg(args, &args)) == NULL)
	{
		say("You must supply a nickname for DCC CHAT");
		return;
	}
	if (args && *args == '-' && *(args+1) == 'e')
	{
		next_arg(args, &args);
		passwd = next_arg(args, &args);
	}

	Client = dcc_searchlist("chat", user, DCC_CHAT, 1, NULL, -1);
	if ((Client->flags & DCC_ACTIVE) || (Client->flags & DCC_WAIT))
	{
		say("A previous DCC CHAT to %s exists", user);
		return;
	}
	Client->flags |= DCC_TWOCLIENTS;
	if (passwd)
		Client->encrypt = m_strdup(passwd);
	dcc_open(Client);
}

/*
 * Usage: /DCC CLOSE <type> <nick>
 */
#ifdef __STDC__
static	void 	dcc_close (char *args)
#else
static void 	dcc_close(args)
	char	*args;
#endif
{
	DCC_list	*Client;
	unsigned	flags;
	char		*Type;
	char		*user;
	char		*description;
	int		CType;

	if (!(Type = next_arg(args, &args)) || !(user = next_arg(args, &args)))
	{
		say("you must specify a type and nick for DCC CLOSE");
		return;
	}
	description = next_arg(args, &args);
	for (CType = 0; dcc_types[CType] != NULL; CType++)
		if (!my_stricmp(Type, dcc_types[CType]))
			break;

	if (!dcc_types[CType])
	{
		say("Unknown DCC type: %s", Type);
		return;
	}
	if ((Client = dcc_searchlist(NULL, user, CType, 0, description, -1)))
	{
		flags = Client->flags;

		if (flags & DCC_DELETE)
			return;
		if ((flags & DCC_WAIT) || (flags & DCC_ACTIVE))
		{
			FD_CLR(Client->read, &readables);
			close(Client->read);
			if (Client->file != -1)
				close(Client->file);
			Client->file = Client->read = -1;
		}
                if (do_hook(DCC_LOST_LIST,"%s %s %s", user, Type,
                        description ? description : "<any>"))
		say("DCC %s:%s to %s closed", Type, description ? description : "<any>", user);

		strcpy(DCC_reject_description, description ? description : "<any>");
		if (!my_stricmp(Type, "SEND"))
			strcpy(DCC_reject_type, "GET");
		else if (!my_stricmp(Type, "GET"))
			strcpy(DCC_reject_type, "SEND");
		else
			strcpy(DCC_reject_type, Type);

		Client->flags |= DCC_DELETE;
		if (*user == '=')
			user++;
		add_ison_to_whois (user, output_reject_ctcp);
	}
	else
		say("No DCC %s:%s to %s found", Type,
			description ? description : "<any>", user);
}

/*
 * Usage: /DCC GET <nick> [file] [-e passkey]
 */
#ifdef __STDC__
static	void	dcc_getfile (char *args)
#else
static void 	dcc_getfile(args)
	char	*args;
#endif
{
	char		*user;
	char		*filename = NULL;
	DCC_list	*Client;
	char		*fullname = (char *) 0;
	char		*passwd = NULL;
	char		pathname[MAXPATHLEN * 2 + 1];

	if (0 == (user = next_arg(args, &args)))
	{
		say("You must supply a nickname for DCC GET");
		return;
	}
	if (args && *args)
	{
		if (args[0] != '-' || args[1] != 'e')
			filename = next_arg(args, &args);

		if (args && args[0] == '-' && args[1] == 'e')
		{
			next_arg(args, &args);
			passwd = next_arg(args, &args);
		}
	}

	if (0 == (Client = dcc_searchlist(filename, user, DCC_FILEREAD, 0, (char *) 0, 0)))
	{
		if (filename)
			say("No file (%s) offered in SEND mode by %s", filename, user);
		else
			say("No file offered in SEND mode by %s", user);
		return;
	}
	if ((Client->flags & DCC_ACTIVE) || (Client->flags & DCC_WAIT))
	{
		say("A previous DCC GET:%s to %s exists", filename?filename:"<any>", user);
		return;
	}
	if (0 == (Client->flags & DCC_OFFER))
	{
		say("I'm a teapot!");
		Client->flags |= DCC_DELETE;
		return;
	}
	Client->flags |= DCC_TWOCLIENTS;
	if (passwd)
		Client->encrypt = m_strdup(passwd);
	if (!dcc_open(Client))
		return;

	*pathname = 0;
	if (get_string_var(DCC_STORE_PATH_VAR))
	{
		strncpy(pathname, get_string_var(DCC_STORE_PATH_VAR), MAXPATHLEN * 2);
		strncat(pathname, "/", MAXPATHLEN * 2);
	}
	strncat(pathname, Client->description, MAXPATHLEN * 2);
	if (!(fullname = expand_twiddle(pathname)))
		malloc_strcpy(&fullname, pathname);

	if ((Client->file = open(fullname, O_WRONLY | O_TRUNC | O_CREAT, 0644)) == -1)
	{
		say("Unable to open %s: %s", Client->description, errno ? sys_errlist[errno] : "<No Error>");
		FD_CLR(Client->read, &readables);
		close(Client->read);
		Client->read = -1;
		Client->flags |= DCC_DELETE;
	}
	new_free(&fullname);
}

/*
 * Usage: /DCC LIST
 */
#ifdef __STDC__
extern void dcc_list (char *args)
#else
extern void dcc_list(args)
	char	*args;
#endif
{
	DCC_list	*Client;
	static	char	*format =
			"%-7.7s%-3.3s %-9.9s %-9.9s %-20.20s %-8.8s %-8.8s %s";
	unsigned	flags;
	char		*filename;
	struct	tm	*btime;
	char		buf[23];
static	char		*months[] = 
	{
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};


	put_it(format, "Type", " ", "Nick", "Status", "Start time", "Size", "Complete", "Args");

	for (Client = ClientList ; Client != NULL ; Client = Client->next)
	{
		char	completed[9];
		char	size[9];
		char	*time;

		if (Client->filesize)
		{
			sprintf(completed, "%ld%%", (Client->bytes_sent?Client->bytes_sent:Client->bytes_read) * 100 / Client->filesize);
			sprintf(size, "%ld", Client->filesize);
		}
		else
		{
			sprintf(completed, "%ldK", (Client->bytes_sent ? Client->bytes_sent : Client->bytes_read) / 1024);
			strcpy(size, empty_string);
		}

		if (Client->starttime.tv_sec)
		{
			/* XXX - This hack to get around lame lossage. */
			time_t blech = Client->starttime.tv_sec;
			btime = localtime(&blech);
			sprintf(buf, "%-2.2d:%-2.2d:%-2.2d %s %-2.2d %d", btime->tm_hour,
				btime->tm_min, btime->tm_sec, months[btime->tm_mon],
				btime->tm_mday, btime->tm_year + 1900);
			time = buf;
		}
		else
			time = empty_string;

		flags = Client->flags;
		filename = rindex(Client->description, '/');
		if (!filename || get_int_var(DCC_LONG_PATHNAMES_VAR))
			filename = Client->description;
		else if (filename && *filename)
			filename++;
		if (!filename)
			filename = "<unknown>";

		put_it(format, dcc_types[flags&DCC_TYPES],
				Client->encrypt ? "[E]" : empty_string,
				Client->user,
				flags&DCC_DELETE ? "Closed" :
				flags&DCC_ACTIVE ? "Active" : 
				flags&DCC_WAIT ? "Waiting" :
				flags&DCC_OFFER ? "Offered" : "Unknown",
				time, size, completed, filename);
	}
}


/*
 * Usage: /DCC RAW <filedesc> [text]
 */
#ifdef __STDC__
static	void	dcc_send_raw (char *args)
#else
static 	void 	dcc_send_raw(args)
	char	*args;
#endif
{
	char	*name;

	if (!(name = next_arg(args, &args)))
	{
		say("No name specified for DCC RAW");
		return;
	}
	dcc_message_transmit(name, args, DCC_RAW, 1, NULL);
}

/*
 * Usage: /DCC RENAME <nick> [<oldname>] <newname>
 */
#ifdef __STDC__
static	void	dcc_rename (char *args)
#else
static void 	dcc_rename(args)
	char	*args;
#endif
{
	DCC_list	*Client;
	char	*user;
	char	*description;
	char	*newdesc;
	char	*temp;
	
	if (!(user = next_arg(args, &args)) || !(temp = next_arg(args, &args)))
	{
		say("You must specify a nick and new filename for DCC RENAME");
		return;
	}

	if ((newdesc = next_arg(args, &args)) != NULL)
		description = temp;
	else
	{
		newdesc = temp;
		description = NULL;
	}

	if ((Client = dcc_searchlist(description, user, DCC_FILEREAD, 0, (char *) 0, 0)))
	{
		/* Is this needed now? */
		if (!(Client->flags & DCC_OFFER))
		{
			say("Too late to rename that file");
			return;
		}
		new_free(&(Client->description));
		malloc_strcpy(&(Client->description), newdesc);
		say("File %s from %s renamed to %s",
			 description ? description : "<any>", user, newdesc);
	}
	else
		say("No file %s from %s found, or its too late to rename that file",
			description ? description : "<any>", user);
}

/*
 * Usage: /DCC SEND <nick> <filename> [-e passkey]
 */
#ifdef __STDC__
static	void	dcc_filesend (char *args)
#else
static void 	dcc_filesend(args)
	char	*args;
#endif
{
	char	*user;
	char	*filename,
		*fullname;
	DCC_list *Client;
	char	FileBuf[BIG_BUFFER_SIZE+1];
struct	stat	stat_buf;
	char	*passwd = NULL;
	int	portnum = 0;

	if (!(user = next_arg(args, &args)) || !(filename = next_arg(args, &args)))
	{
		say("You must supply a nickname and filename for DCC SEND");
		return;
	}
	if (*filename == '/')
		strcpy(FileBuf, filename);

	else if (*filename == '~')
	{
		if (0 == (fullname = expand_twiddle(filename)))
		{
			yell("Unable to expand %s", filename);
			return;
		}
		strcpy(FileBuf, fullname);
		new_free(&fullname);
	}
	else
	{
		getcwd(FileBuf, sizeof(FileBuf));
		strcat(FileBuf, "/");
		strcat(FileBuf, filename);
	}

	while (args && *args)
	{
		char *argument = next_arg(args, &args);
		if (argument[0] == '-' && argument[1] == 'e')
			passwd = next_arg(args, &args);
		else if (argument[0] == '-' && argument[1] == 'p')
		{
			char *v = next_arg(args, &args);
			portnum = (v) ? atoi(v) : 0;
		}
	}

	if (access(FileBuf, R_OK))
	{
		yell("Cannot access %s", FileBuf);
		return;
	}
	stat_file(FileBuf, &stat_buf);
	if (stat_buf.st_mode & S_IFDIR)
	{
		yell("Cannot send a directory");
		return;
	}
	if (scanstr(FileBuf, "/etc/"))
	{
		yell("Send request rejected");
		return;
	}
	if (strlen(FileBuf) >= 7 &&
	    0 == strcmp(FileBuf + strlen(FileBuf) - 7, "/passwd"))
	{
		yell("Send request rejected");
		return;
	}
	filesize = stat_buf.st_size;
	Client = dcc_searchlist(FileBuf, user, DCC_FILEOFFER, 1, filename, -1);
	filesize = 0;
	if ((Client->flags & DCC_ACTIVE) || (Client->flags & DCC_WAIT))
	{
		say("A previous DCC SEND:%s to %s exists", FileBuf, user);
		return;
	}
	Client->flags |= DCC_TWOCLIENTS;
	if (passwd)
		Client->encrypt = m_strdup(passwd);
	Client->remport = portnum;
	dcc_open(Client);
}


/*
 * Usage: $listen(<port>)
 */
#ifdef __STDC__
extern char	*dcc_raw_listen (int port)
#else
extern char	*dcc_raw_listen(port)
	int	port;
#endif
{
	DCC_list	*Client;
	char		*PortName;
struct	sockaddr_in 	locaddr;
	int		size;
	int		lastlog_level;

	lastlog_level = set_lastlog_msg_level(LOG_DCC);

	if (port && port < 1025)
	{
		say("Cannot bind to a privileged port");
		set_lastlog_msg_level(lastlog_level);
		return NULL;
	}
	PortName = ltoa(port);
	Client = dcc_searchlist("raw_listen", PortName, DCC_RAW_LISTEN, 1, NULL, -1);
	if (Client->flags & DCC_WAIT)
	{
		say("A previous DCC RAW_LISTEN on %s exists", PortName);
		set_lastlog_msg_level(lastlog_level);
		return NULL;
	}
	bzero((char *) &locaddr, sizeof(locaddr));
	locaddr.sin_family = AF_INET;
	locaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	locaddr.sin_port = htons(port);
	if ((Client->read = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		say("socket() failed: %s", sys_errlist[errno]);
		Client->flags |= DCC_DELETE; 
		set_lastlog_msg_level(lastlog_level);
		return NULL;
	}
	FD_SET(Client->read, &readables);
	set_socket_options(Client->read);
	if (bind(Client->read, (struct sockaddr *) &locaddr, sizeof(locaddr)) == -1)
	{
		say("Could not bind port: %s", sys_errlist[errno]);
		Client->flags |= DCC_DELETE;
		set_lastlog_msg_level(lastlog_level);
		return NULL;
	}

	listen(Client->read, 4);
	size = sizeof(locaddr);
	get_time(&Client->starttime);
	getsockname(Client->read, (struct sockaddr *) &locaddr, &size);
	Client->write = ntohs(locaddr.sin_port);
	Client->flags |= DCC_ACTIVE;
	Client->user = m_strdup(ltoa(Client->write));
	set_lastlog_msg_level(lastlog_level);

	return m_strdup(Client->user);
}


/*
 * Usage: $connect(<hostname> <portnum>)
 */
#ifdef __STDC__
extern char	*dcc_raw_connect(char *host, u_short port)
#else
extern char	*dcc_raw_connect(host, port)
	char	*host;
	u_short	port;
#endif
{
	DCC_list	*Client;
	char	*PortName;
	struct	in_addr	address;
	struct	hostent	*hp;
	int	lastlog_level;

	lastlog_level = set_lastlog_msg_level(LOG_DCC);
	if ((address.s_addr = inet_addr(host)) == -1)
	{
		hp = gethostbyname(host);
		if (!hp)
		{
			say("Unknown host: %s", host);
			(void) set_lastlog_msg_level(lastlog_level);
			return m_strdup(empty_string);
		}
		bcopy(hp->h_addr, &address, sizeof(address));
	}
	Client = dcc_searchlist(host, ltoa(port), DCC_RAW, 1, NULL, -1);
	if (Client->flags & DCC_ACTIVE)
	{
		say("A previous DCC RAW to %s on %s exists", host, ltoa(port));
		(void) set_lastlog_msg_level(lastlog_level);
		return m_strdup(empty_string);
	}

	/* Sorry. The missing 'htons' call here broke $connect() */
	Client->remport = htons(port);
	bcopy((char *) &address, (char *) &Client->remote, sizeof(address));
	Client->flags = DCC_OFFER | DCC_RAW;
	if (!dcc_open(Client))
		return m_strdup(empty_string);

	PortName = ltoa(Client->read);
	Client->user = m_strdup(PortName);
	if (do_hook(DCC_RAW_LIST, "%s %s E %d", PortName, host, port))
            if (do_hook(DCC_CONNECT_LIST,"%s RAW %s %d",PortName, host, port))
		say("DCC RAW connection to %s on %s via %d established", host, PortName, port);

	(void) set_lastlog_msg_level(lastlog_level);
	return m_strdup(PortName);
}





/*
 *
 * All the rest of this file is dedicated to automatic replies generated
 * by the client in response to external stimuli from DCC connections.
 *
 */



/*
 * When a user does a CTCP DCC, it comes here for preliminary parsing.
 */
#ifdef __STDC__
extern void	register_dcc_offer (char *user, char *type, char *description, char *address, char *port, char *size, char *extra)
#else
extern void 	register_dcc_offer(user, type, description, address, port, size, extra)
	char	*user;
	char	*type;
	char	*description;
	char	*address;
	char	*port;
	char	*size;
	char	*extra;
#endif
{
	DCC_list *	Client;
	int		CType, jvs_blah;
	char *		c;
	u_long		TempLong;
	unsigned	TempInt;
	int		do_auto = 0;	/* used in dcc chat collisions */

	if (x_debug)
		yell("register_dcc_offer: [%s|%s|%s|%s|%s|%s|%s]", user, type, description, address, port, size, extra);

	if ((c = rindex(description, '/')))
		description = c + 1;
	if ('.' == *description)
		*description = '_';

	if (size && *size)
		filesize = atoi(size);

	     if (!my_stricmp(type, "CHAT"))
		CType = DCC_CHAT;
	else if (!my_stricmp(type, "SEND"))
		CType = DCC_FILEREAD;
#ifdef MIRC_BROKEN_DCC_RESUME
	else if (!my_stricmp(type, "RESUME"))
	{
		/* 
		 * Dont be deceieved by the arguments we're passing it.
		 * The arguments are "out of order" because MIRC doesnt
		 * send them in the traditional order.  Ugh.
		 */
		dcc_getfile_resume_demanded(user, description, address, port);
		return;
	}
	else if (!my_stricmp(type, "ACCEPT"))
	{
		/*
		 * See the comment above.
		 */
		dcc_getfile_resume_start (user, description, address, port);
		return;
	}
#endif
        else
        {
                say("Unknown DCC %s (%s) received from %s", type, description, user);
                return;
        }

	Client = dcc_searchlist(description, user, CType, 1, (char *) 0, -1);
	filesize = 0;

	if (extra && *extra)
		Client->cksum = m_strdup(extra);

	if (Client->flags & DCC_WAIT)
	{
		FD_CLR(Client->read, &readables);
		close(Client->read);
		Client->read = -1;
		Client->flags |= DCC_DELETE;

		if (DCC_CHAT == CType)
		{
			Client = dcc_searchlist(description, user, CType, 1, (char *) 0, -1);
			do_auto = 1;
		}
		else
		{
			say("DCC %s collision for %s:%s", type, user, description);
			send_ctcp(CTCP_NOTICE, user, CTCP_DCC,
				"DCC %s collision occured while connecting to %s (%s)", 
				type, nickname, description);
			return;
		}
	}
	if (Client->flags & DCC_ACTIVE)
	{
		say("Received DCC %s request from %s while previous session still active", type, user);
		return;
	}
	Client->flags |= DCC_OFFER;

	TempLong = strtoul(address, NULL, 10);
	Client->remote.s_addr = htonl(TempLong);
	TempInt = (unsigned)strtoul(port, NULL, 10);
	Client->remport = htons(TempInt);

	if (TempInt < 1024)
	{
		say("DCC %s (%s) request from %s rejected", type, description, user);
		Client->flags |= DCC_DELETE;
		return;
	}

#ifdef HACKED_DCC_WARNING
	/* 
	 * This right here compares the hostname from the userhost stamped
	 * on the incoming privmsg to the address stamped in the handshake.
	 * We do not automatically reject any discrepencies, but warn the
	 * user instead to be cautious.
	 */
	{
		char tmpbuf[128];
		char *fromhost;
		unsigned long compare, compare2;
		struct hostent *hostent_fromhost;

		strcpy(tmpbuf, FromUserHost);
		fromhost = index(tmpbuf, '@') + 1;
		alarm(1);	/* dont block too long... */
		hostent_fromhost = gethostbyname(fromhost);
		alarm(0);
		if (!hostent_fromhost)
		{
			yell("Incoming handshake has an address [%s] that could not be figured out!", fromhost);
			yell("Please use caution in deciding whether to accept it or not");
		}
		else
		{
			compare = *((unsigned long *)hostent_fromhost->h_addr_list[0]);
			compare2 = inet_addr(fromhost);
			if ((compare != Client->remote.s_addr) &&
				(compare2 != Client->remote.s_addr))
			{
				say("WARNING: Fake dcc handshake detected! [%x]",Client->remote.s_addr);
				say("Unless you know where this dcc request is coming from");
				say("It is recommended you ignore it!");
			}
		}
	}
#endif

	if (!TempLong || !Client->remport)
	{
		yell("DCC handshake from %s ignored becuase it had an null port or address", user);
		Client->flags |= DCC_DELETE;
		return;
	}

  	if (do_auto)
  	{
                if (do_hook(DCC_CONNECT_LIST,"%s CHAT",user))
			say("DCC CHAT already requested by %s, connecting...", user);
  		dcc_chat(user);
  	}
        else
        {
               /* DCC SEND and CHAT have different arguments, so they can't
                  very well use the exact same hooked data.  Both now are
                  identical for $0-4, and SEND adds filename/size in $5-6 */
               if ( Client->filesize )
                       jvs_blah = do_hook(DCC_REQUEST_LIST,
                                 "%s %s %s %s %d %s %d",
                                  user, type, description,
                                  inet_ntoa(Client->remote),
                                  ntohs(Client->remport),
                                  Client->description,
                                  Client->filesize);
               else
                       jvs_blah = do_hook(DCC_REQUEST_LIST,
                                 "%s %s %s %s %d",
                                  user, type, description,
                                  inet_ntoa(Client->remote),
                                  ntohs(Client->remport));

            /* indentation broken 22 Nov 96, crowman... eeeeyuck */
            if ( jvs_blah )
	    {
		/* These should be more helpful messages now. */
		/* WC asked to have them moved here */
		if (Client->flags & DCC_FILEREAD)
		{
			struct stat statit;
			if (stat(Client->description, &statit) == 0)
			{
				if (statit.st_size < Client->filesize)
				{
					say("WARNING: File [%s] exists but is smaller then the file offered.", Client->description);
					say("Use /DCC CLOSE GET %s %s        to not get the file.", user, Client->description);
#ifdef MIRC_BROKEN_DCC_RESUME
					say("Use /DCC RESUME %s %s           to continue the copy where it left off.", user, Client->description);
#endif
					say("Use /DCC RENAME %s %s newname   to save it to a different filename", user, Client->description);
					say("Use /DCC GET %s %s              to overwrite the existing file.", user, Client->description);
				}
				else if (statit.st_size == Client->filesize)
				{
					say("WARNING: File [%s] exists, and its the same size.", Client->description);
#ifdef USE_DCC_CHECKSUM
					if (Client->cksum)
					{
						char buffer[10];
						char foo[5];
						file_checksum(foo, Client->description);
						sprintf(buffer, "%x%x%x%x", foo[0], foo[1], foo[2], foo[3]);
						if (!strcmp(Client->cksum, buffer))
							say("WARNING: The two files are confirmed to be identical.");
					}
#endif

					say("Use /DCC CLOSE GET %s %s        to not get the file.", user, Client->description);
					say("Use /DCC RENAME %s %s           to get the file anyways under a different name.", user, Client->description);
					say("Use /DCC GET %s %s              to overwrite the existing file.", user, Client->description);
				}
				else 	/* Bigger than */
				{
					say("WARNING: File [%s] already exists.", Client->description);
					say("Use /DCC CLOSE GET %s %s        to not get the file.", user, Client->description);
					say("Use /DCC RENAME %s %s           to get the file anyways under a different name.", user, Client->description);
					say("Use /DCC GET %s %s              to overwrite the existing file.", user, Client->description);
				}
			}
		}

/* Thanks, Tychy! (lherron@imageek.york.cuny.edu) */
		if (Client->filesize)
			say("DCC %s (%s %d) request received from %s!%s [%s:%d]",
			    type, description, Client->filesize, user, FromUserHost,
			    inet_ntoa(Client->remote), ntohs(Client->remport));
		else
		        say("DCC %s (%s) request received from %s!%s [%s:%d]", 
                             type, description, user, FromUserHost,
                             inet_ntoa(Client->remote), ntohs(Client->remport));
            }
	    return;
	}
}


/*
 * Check all DCCs for data, and if they have any, perform whatever
 * actions are required.
 */
#ifdef __STDC__
extern void	dcc_check (fd_set *Readables)
#else
extern void 	dcc_check(Readables)
	fd_set	*Readables;
#endif
{
	DCC_list	**Client;
	int		previous_server;
	int		lastlog_level;

	previous_server = from_server;
	from_server = (-1);
	message_from((char *) 0, LOG_DCC);
	lastlog_level = set_lastlog_msg_level(LOG_DCC);
	for (Client = &ClientList; *Client != NULL;Client = &(**Client).next)
	{
		if ((*Client)->read != -1 && FD_ISSET((*Client)->read, Readables))
		{
			switch((*Client)->flags&DCC_TYPES)
			{
				case DCC_CHAT:
					process_incoming_chat(*Client);
					break;
				case DCC_RAW_LISTEN:
					process_incoming_listen(*Client);
					break;
				case DCC_RAW:
					process_incoming_raw(*Client);
					break;
				case DCC_FILEOFFER:
					process_outgoing_file(*Client);
					break;
				case DCC_FILEREAD:
					process_incoming_file(*Client);
					break;
			}
		}

		/*
		 * This shouldnt be neccesary any more, but what the hey,
		 * im still paranoid.
		 */
		if (!*Client)
			break;

		if ((*Client)->flags & DCC_DELETE)
			dcc_add_deadclient(*Client);
	}

	message_from((char *) 0, LOG_CRAP);
	set_lastlog_msg_level(lastlog_level);
	dcc_really_erase();
	from_server = previous_server;
}



/*
 * This handles DCC CHAT messages sent to you.
 */
#ifdef __STDC__
static	void	process_incoming_chat (DCC_list *Client)
#else
static void 	process_incoming_chat(Client)
	DCC_list	*Client;
#endif
{
	struct	sockaddr_in	remaddr;
	int	sra;
	char	tmp[DCC_BLOCK_SIZE + 1];
	char	tmp2[DCC_BLOCK_SIZE + 1];
	char	*s, *bufptr;
	long	bytesread;
	int	old_timeout;

	if (Client->flags & DCC_WAIT)
	{
		sra = sizeof(struct sockaddr_in);
		Client->write = accept(Client->read, (struct sockaddr *) &remaddr, &sra);
#ifdef  ESIX
		mark_socket(Client->write);
#endif
		FD_CLR(Client->read, &readables);
		close(Client->read);
		Client->read = -1;
		if ((Client->read = Client->write) > 0)
			FD_SET(Client->read, &readables);
		else
		{
			yell("DCC Error: accept() failed.  Punting.");
			Client->flags |= DCC_DELETE;
			return;
		}

		Client->flags &= ~DCC_WAIT;
		Client->flags |= DCC_ACTIVE;
                if (do_hook(DCC_CONNECT_LIST, "%s CHAT %s %d", Client->user,
                         inet_ntoa(remaddr.sin_addr), ntohs(remaddr.sin_port)))
		say("DCC chat connection to %s[%s,%d] established", Client->user,
			inet_ntoa(remaddr.sin_addr), ntohs(remaddr.sin_port));
		get_time(&Client->starttime);
		return;
	}
	old_timeout = dgets_timeout(1);
        s = Client->buffer;
        bufptr = tmp;
        if (s && *s)
        {
                int     len = strlen(s);
 
                strncpy(tmp, s, len);
		bufptr += len;
        }
	bytesread = dgets(bufptr, DCC_BLOCK_SIZE, Client->read, (char *)0);
	(void) dgets_timeout(old_timeout);
	switch (bytesread)
	{
	case -1:
		malloc_strcat(&Client->buffer, tmp);
                return;
	case 0:
	{
		char *real_tmp = ((dgets_errno == -1) ? "Remote End Closed Connection" : strerror(dgets_errno));
                if (do_hook(DCC_LOST_LIST, "%s CHAT %s", Client->user, real_tmp))
        		say("DCC CHAT connection to %s lost", Client->user, real_tmp);
		FD_CLR(Client->read, &readables);
		close(Client->read);
		Client->read = Client->write = -1;
		Client->flags |= DCC_DELETE;
		return;
	}
	default:
	{
		char userhost[80];
		char equal_nickname[80];

		new_free(&Client->buffer);
		tmp[strlen(tmp) - 1]='\0';
		my_decrypt(tmp, strlen(tmp), Client->encrypt);
		Client->bytes_read += bytesread;
		message_from(Client->user, LOG_DCC);

		if (x_debug) yell("%s", tmp);

#define CTCP_MESSAGE "CTCP_MESSAGE "
#define CTCP_MESSAGE_LEN strlen(CTCP_MESSAGE)
#define CTCP_REPLY "CTCP_REPLY "
#define CTCP_REPLY_LEN strlen(CTCP_REPLY)

		if (!strncmp(tmp, CTCP_MESSAGE, CTCP_MESSAGE_LEN))
		{
			strcpy(userhost, "Unknown@");
			strcat(userhost, inet_ntoa(remaddr.sin_addr));
			FromUserHost = userhost;
			strcpy(equal_nickname, "=");
			strcat(equal_nickname, Client->user);
			/* XXXX */
			strcpy(tmp2, tmp);
			strcpy(tmp, do_ctcp(equal_nickname, get_server_nickname(-1), tmp2 + CTCP_MESSAGE_LEN));
		}
		else if (!strncmp(tmp, "CTCP_REPLY ", strlen("CTCP_REPLY ")))
		{
			strcpy(userhost, "Unknown@");
			strcat(userhost, inet_ntoa(remaddr.sin_addr));
			FromUserHost = userhost;
			strcpy(equal_nickname, "=");
			strcat(equal_nickname, Client->user);
			/* XXXX */
			strcpy(tmp2, tmp);
			strcpy(tmp, do_notice_ctcp(equal_nickname, get_server_nickname(-1), tmp2 + CTCP_REPLY_LEN));
		}

		if (!*tmp)
			break;

		if (do_hook(DCC_CHAT_LIST, "%s %s", Client->user, tmp))
		{
			if (away_set)
			{
				time_t	t = time(NULL);
				char	*msg = (char *) 0;

				msg = (char *) new_malloc(strlen(tmp) + 20);
				sprintf(msg, "%s <%.16s>", tmp, ctime(&t));
				strcpy(tmp, msg);
				new_free(&msg);
			}
			put_it("=%s= %s", Client->user, tmp);
		}
		message_from((char *) 0, LOG_CURRENT);
		return;
	}
	}
}


/*
 * This handles when someone establishes a connection on a $listen()ing
 * socket.  This hooks via /on DCC_RAW.
 */
#ifdef __STDC__
static	void		process_incoming_listen (DCC_list *Client)
#else
static 	void 		process_incoming_listen(Client)
	DCC_list	*Client;
#endif
{
	struct	sockaddr_in	remaddr;
	int	sra;
	char	FdName[10];
	DCC_list	*NewClient;
	int	new_socket;
	struct	hostent	*hp;
#if defined(__linux__) || defined(__sgi)
	const char	*Name;
#else
	char	*Name;
#endif

	sra = sizeof(struct sockaddr_in);
	new_socket = accept(Client->read, (struct sockaddr *) &remaddr, &sra);
	if (new_socket < 0)
	{
		yell("DCC Error: accept() failed.  Punting.");
		return;
	}

	if (0 != (hp = gethostbyaddr((char *)&remaddr.sin_addr,
	    sizeof(remaddr.sin_addr), remaddr.sin_family)))
		Name = hp->h_name;
	else
		Name = inet_ntoa(remaddr.sin_addr);
#ifdef  ESIX
	mark_socket(new_socket);
#endif
	sprintf(FdName, "%d", new_socket);
	NewClient = dcc_searchlist((char *)Name, FdName, DCC_RAW, 1, (char *) 0, 0);
	NewClient->read = NewClient->write = new_socket;
	NewClient->remote = remaddr.sin_addr;
	NewClient->remport = remaddr.sin_port;
	NewClient->flags |= DCC_ACTIVE;
	NewClient->bytes_read = NewClient->bytes_sent = 0L;
	get_time(&NewClient->starttime);
	FD_SET(NewClient->read, &readables);

	if (do_hook(DCC_RAW_LIST, "%s %s N %d", NewClient->user,
						NewClient->description,
						Client->write))
        if (do_hook(DCC_CONNECT_LIST,"%s RAW %s %d", NewClient->user,
                                                     NewClient->description,
                                                     Client->write))
		say ("DCC RAW connection to %s on %s via %d established",
					NewClient->description,
					NewClient->user,
					Client->write);
}



/*
 * This handles when someone sends you a line of info over a DCC RAW
 * connection (that was established with a $listen().
 */
#ifdef __STDC__
static	void		process_incoming_raw (DCC_list *Client)
#else
static 	void 		process_incoming_raw(Client)
	DCC_list	*Client;
#endif
{
	char	tmp[DCC_BLOCK_SIZE+1];
	char 	*s, *bufptr;
	long	bytesread;
	int     old_timeout;

        s = Client->buffer;
        bufptr = tmp;
        if (s && *s)
        {
                int     len = strlen(s);
 
                strncpy(tmp, s, len);
                bufptr += len;
        }
        old_timeout = dgets_timeout(1);
	switch(bytesread = dgets(bufptr, DCC_BLOCK_SIZE, Client->read, (char *)0))
	{
	case -1:
	{
		malloc_strcat(&Client->buffer, tmp);
                return;
	}
	case 0:
	{
		if (do_hook(DCC_RAW_LIST, "%s %s C",
				Client->user, Client->description))
       		if (do_hook(DCC_LOST_LIST,"%s RAW %s", 
				Client->user, Client->description))
			say("DCC RAW connection to %s on %s lost",
				Client->user, Client->description);
		FD_CLR(Client->read, &readables);
		close(Client->read);
		Client->read = Client->write = -1;
		Client->flags |= DCC_DELETE;
		(void) dgets_timeout(old_timeout);
		return;
	}
	default:
	{
		new_free(&Client->buffer);
		tmp[strlen(tmp) - 1] = '\0';
		Client->bytes_read += bytesread;
		if (do_hook(DCC_RAW_LIST, "%s %s D %s",
				Client->user, Client->description, tmp))
			say("Raw data on %s from %s: %s",
				Client->user, Client->description, tmp);
		(void) dgets_timeout(old_timeout);
		return;
	}
	}
}


/*
 * When youre sending a file, and your peer sends an ACK, this handles
 * whether or not to send the next packet.
 */
#ifdef __STDC__
static void		process_outgoing_file (DCC_list *Client)
#else
static void 		process_outgoing_file(Client)
	DCC_list	*Client;
#endif
{
	struct	sockaddr_in	remaddr;
	int			sra;
	char			tmp[DCC_BLOCK_SIZE+1];
	u_32int_t		bytesrecvd;
	int			bytesread;

	if (Client->flags & DCC_WAIT)
	{
		sra = sizeof(struct sockaddr_in);
		Client->write = accept(Client->read, (struct sockaddr *) &remaddr, &sra);
#ifdef  ESIX
		mark_socket(Client->write);
#endif
		FD_CLR(Client->read, &readables);
		close(Client->read);
		if ((Client->read = Client->write) >= 0)
			FD_SET(Client->read, &readables);
		else
		{
			yell("DCC Error: accept() failed.  Punting.");
			Client->flags |= DCC_DELETE;
			return;
		}

		Client->flags &= ~DCC_WAIT;
		Client->flags |= DCC_ACTIVE;
		get_time(&Client->starttime);

                if (do_hook(DCC_CONNECT_LIST, "%s SEND %s %d %s %d",
                        Client->user, inet_ntoa(remaddr.sin_addr),
                        ntohs(remaddr.sin_port), Client->description,
                        Client->filesize))
		say("DCC SEND connection to %s[%s,%d] established", Client->user,
			inet_ntoa(remaddr.sin_addr), ntohs(remaddr.sin_port));

		if ((Client->file = open(Client->description, O_RDONLY)) == -1)
		{
			say("Unable to open %s: %s\n", Client->description,
				errno ? sys_errlist[errno] : "Unknown Host");
			FD_CLR(Client->read, &readables);
			close(Client->read);
			Client->read = Client->write = -1;
			Client->flags |= DCC_DELETE;
			return;
		}
		if (Client->bytes_sent)
		{
			if (x_debug)
				yell("Resuming at address (%d)", Client->bytes_sent);
			lseek(Client->file, Client->bytes_sent, SEEK_SET);
		}
	}
	else 
	{ 
		if (x_debug)
			yell("Reading a packet from [%s:%s(%d)]", Client->user, Client->othername, Client->packets_ack);

		if (read(Client->read, (char *)&bytesrecvd, sizeof(u_32int_t)) < sizeof(u_32int_t))
		{
                        if (do_hook(DCC_LOST_LIST,"%s SEND %s CONNECTION LOST",
                                Client->user, Client->description))
	       		say("DCC SEND:%s connection to %s lost",
				Client->description, Client->user);
			FD_CLR(Client->read, &readables);
			close(Client->read);
			Client->read = -1;
			DCC_done = 1;
			Client->read = Client->write = (-1);
			Client->flags |= DCC_DELETE;
			close(Client->file);
			Client->file = -1;
			status_update(1);
			return;
		}

		/*
		 * Check to see if we need to move the sliding window up
		 */
		if (ntohl(bytesrecvd) >= (Client->packets_ack + 1) * DCC_BLOCK_SIZE)
		{
			if (x_debug)
				yell("Packet #%d ACKed", Client->packets_ack);
			Client->packets_ack++;
			Client->packets_outstanding--;
		}

		/*
		 * If we've sent the whole file already...
		 */
		if (Client->bytes_sent >= Client->filesize)
		{
			/*
			 * If theyve ACKed the last packet, we close 
			 * the connection.
			 */
			if (ntohl(bytesrecvd) >= Client->filesize)
				DCC_close_filesend(Client, "SEND");

			/*
			 * Either way there's nothing more to do.
			 */
			return;
		}
	}

	while (Client->packets_outstanding < get_int_var(DCC_SLIDING_WINDOW_VAR))
	{
		if ((bytesread = read(Client->file, tmp, DCC_BLOCK_SIZE)) > 0)
		{
			my_encrypt(tmp, bytesread, Client->encrypt);
			if (x_debug)
				yell("Sending packet [%s [%s] (packet %d) (%d bytes)]", Client->user, Client->othername, Client->packets_transfer, bytesread);
			write(Client->write, tmp, bytesread);
			Client->packets_outstanding++;
			Client->packets_transfer++;
			Client->bytes_sent += bytesread;
			update_transfer_buffer("(to %10s: %d of %d: %d%%)", Client->user, Client->packets_transfer, Client->packets_total, (Client->bytes_sent * 100 / Client->filesize));
			DCC_done = 0;
			status_update(1);
		}

		/*
		 * If we cant read anything, then just dont worry about it.
		 */
		else
			break;
	}
}

/*
 * When youre recieving a DCC GET file, this is called when the sender
 * sends you a portion of the file. 
 */
#ifdef __STDC__
static	void		process_incoming_file (DCC_list *Client)
#else
static void 		process_incoming_file(Client)
	DCC_list	*Client;
#endif
{
	char		tmp[DCC_BLOCK_SIZE+1];
	u_32int_t	bytestemp;
	int		bytesread;

	if ((bytesread = read(Client->read, tmp, DCC_BLOCK_SIZE)) <= 0)
	{
		if (Client->bytes_read < Client->filesize)
			say("DCC GET to %s lost -- Remote peer closed connection", Client->user);

#ifdef USE_DCC_CHECKSUM
		else if (Client->cksum)
		{
			unsigned char foo[4];
			char tmpbuf[64];

			file_checksum(foo, Client->description);
			sprintf(tmpbuf, "%x%x%x%x", foo[0], foo[1], foo[2], foo[3]);
			if (strcmp(Client->cksum, tmpbuf))
				yell("Incoming file's checksum does not match [%s] [%s]", Client->cksum, tmpbuf);
			else
				yell("Incoming file has been recieved with no errors.");
		}
#endif
		DCC_close_filesend(Client, "GET");
	}
	else
	{
		my_decrypt(tmp, bytesread, Client->encrypt);
		write(Client->file, tmp, bytesread);
		Client->bytes_read += bytesread;
		bytestemp = htonl(Client->bytes_read);
		write(Client->write, (char *)&bytestemp, sizeof(u_32int_t));
		Client->packets_transfer = Client->bytes_read / DCC_BLOCK_SIZE;

/* TAKE THIS OUT IF IT CAUSES PROBLEMS */
		if ((Client->filesize) && (Client->bytes_read > Client->filesize))
		{
			yell("DCC GET WARNING: incoming file is larger then the handshake said");
			yell("DCC GET: Closing connection");
			FD_CLR(Client->read, &readables);
			close(Client->read);
			close(Client->file);
			Client->read = Client->write = Client->file = -1;
			Client->flags |= DCC_DELETE;
			return;
		}
		if (Client->filesize) 
		{
			if (Client->filesize)
				update_transfer_buffer("(%10s: %d of %d: %d%%)", Client->user, Client->packets_transfer, Client->packets_total, Client->bytes_read * 100 / Client->filesize);
			else
				update_transfer_buffer("(%10s %d packets: %dK)", Client->user, Client->packets_transfer, Client->bytes_read / 1024);
		}
	}
}





/*
 * This is a callback.  When we want to do a CTCP DCC REJECT, we do
 * a WHOIS to make sure theyre still on irc, no sense sending it to
 * nobody in particular.  When this gets called back, that means the
 * peer is indeed on irc, so we send them the REJECT.
 */
#ifdef __STDC__
static	void 	output_reject_ctcp (char *notused, char *nicklist)
#else
static void 	output_reject_ctcp (notused, nicklist)
char *notused, *nicklist;
#endif
{
	if (nicklist && *nicklist)
		send_ctcp(CTCP_NOTICE, nicklist, CTCP_DCC,
				"REJECT %s %s", 
				DCC_reject_type, DCC_reject_description);

        strcpy(DCC_reject_description, empty_string);
	strcpy(DCC_reject_type, empty_string);
}



/*
 * This is called when someone sends you a CTCP DCC REJECT.
 */
#ifdef __STDC__
extern void dcc_reject (char *from, char *type, char *args)
#else
extern void dcc_reject (from, type, args)
char *from, *type, *args;
#endif
{
	DCC_list	*Client;
	unsigned	flags;
	char	*description;
	int	CType;

	for (CType = 0; dcc_types[CType] != NULL; CType++)
		if (!my_stricmp(type, dcc_types[CType]))
			break;

	if (!dcc_types[CType])
		return;

	description = next_arg(args, &args);

	if ((Client = dcc_searchlist(NULL, from, CType, 0, description, -1)))
	{
		flags = Client->flags;
		if (flags & DCC_DELETE)
			return;
		if ((flags & DCC_WAIT) || (flags & DCC_ACTIVE))
		{
			FD_CLR(Client->read, &readables);
			close(Client->read);
			if (Client->file != -1)
				close(Client->file);
			Client->read = Client->file = -1;
		}
                if (do_hook(DCC_LOST_LIST,"%s %s %s REJECTED", from, type,
                        description ? description : "<any>"))
		say("DCC %s:%s rejected by %s: closing", type,
			description ? description : "<any>", from);
		Client->flags |= DCC_DELETE; 
	}
#if 0
	else
		yell("DCC %s (%s) rejected/not closed", type, from);
#endif
}


#ifdef __STDC__
static void DCC_close_filesend (DCC_list *Client, char *info)
#else
static void DCC_close_filesend (Client, info)
DCC_list *Client;
char *info;
#endif
{
	char	lame_ultrix[10];	/* should be plenty */
	char	lame_ultrix2[10];
	char	lame_ultrix3[10];
	double xtime, xfer;

	xtime = time_diff(Client->starttime, get_time(NULL));
	xfer = (double)(Client->bytes_sent ? Client->bytes_sent : Client->bytes_read);
	sprintf(lame_ultrix, "%2.4g", (xfer / 1024.0 / xtime));

	/* Cant pass %g to put_it (lame ultrix/dgux), fix suggested by sheik. */
	if (xfer <= 0)
		xfer = 1;
	sprintf(lame_ultrix2, "%2.4g", xfer / 1024.0);

	if (xtime <= 0)
		xtime = 1;
	sprintf(lame_ultrix3, "%2.6g", xtime);

	if(do_hook(DCC_LOST_LIST,"%s %s %s %s TRANSFER COMPLETE",
		Client->user, info, Client->description, lame_ultrix))
	say("DCC %s:%s [%skb] with %s completed in %s sec (%s kb/sec)",
		info, Client->description, lame_ultrix2, Client->user, lame_ultrix3, lame_ultrix);

	DCC_done = 1;
	FD_CLR(Client->read, &readables);
	close(Client->read);
	close(Client->file);
	Client->read = Client->write = Client->file = -1;
	Client->flags |= DCC_DELETE;
	status_update(1);
}



/* 
 * Looks for the dcc transfer that is "current" (last recieved data)
 * and returns information for it
 */
extern char *DCC_get_current_transfer _((void))
{
	if (DCC_done)
		return empty_string;
	else
	{
		DCC_done = 1;
		return DCC_current_transfer_buffer;
	}
}


#if defined(__STDC__) && defined(HAVE_STDARG_H)
static void update_transfer_buffer (char *format, ...)
#else
static void update_transfer_buffer (format, arg0, arg1, arg2, arg3)
char *format, *arg0, *arg1, *arg2, *arg3;
#endif
{
#if defined(__STDC__) && defined(HAVE_STDARG_H)
	va_list args;
	va_start(args, format);
	vsprintf(DCC_current_transfer_buffer, format, args);
#else
	sprintf(DCC_current_transfer_buffer, format, arg0, arg1, arg2, arg3);
#endif
	DCC_done = 0;
	status_update(1);
}


#ifdef __STDC__
static void file_checksum (unsigned char cksum[], char *filename)
#else
static void file_checksum (cksum, filename)
unsigned char cksum[];
char *filename;
#endif
{
	FILE *open_file;
	cksum[0] = cksum[1] = cksum[2] = cksum[3] = 0;

	open_file = fopen(filename, "r");
	if (!open_file)
	{
		yell("Couldnt open %s: %s\n",filename,sys_errlist[errno]);
		return;
	}
	while (!feof(open_file))
	{
		unsigned char values[4];

		/* dont whine at me that i did it this way.
		 * *) you cannot assume every implementation of stdio
		 *    will read zero's past EOF.  So i explicitly have
		 *    them set to zero. this is important.
		 */
		switch (fread(values, sizeof(unsigned char), 4, open_file))
		{
			case 0: values[0] = 0;
			case 1: values[1] = 0;
			case 2: values[2] = 0;
			case 3: values[3] = 0;
			case 4: break;
			default: panic("file_checksum");
		}

		cksum[0] ^= values[0];
		cksum[1] ^= values[1];
		cksum[2] ^= values[2];
		cksum[3] ^= values[3];
	}
	fclose (open_file);
}





/*
 * This stuff doesnt conform to the protocol.
 * Thanks mirc for disregarding the protocol.
 */
#ifdef MIRC_BROKEN_DCC_RESUME

/*
 * Usage: /DCC RESUME <nick> [file] [-e passkey]
 */
#ifdef __STDC__
static	void	dcc_getfile_resume (char *args)
#else
static void 	dcc_getfile_resume (args)
	char	*args;
#endif
{
	char		*user;
	char		*filename = NULL;
	DCC_list	*Client;
	char		*passwd = NULL;
	struct stat	sb;
	char		buf[10];

	if (!(user = next_arg(args, &args)))
	{
		say("You must supply a nickname for DCC RESUME");
		return;
	}

	if (args && *args)
	{
		/* Leeme lone, Yoshi. :P */
		if (args[0] != '-' || args[1] != 'e')
			filename = next_arg(args, &args);

		if (args && args[0] == '-' && args[1] == 'e')
		{
			next_arg(args, &args);
			passwd = next_arg(args, &args);
		}
	}

	/*
	 * This has to be done by hand, we cant use send_ctcp,
	 * because this violates the protocol, and send_ctcp checks
	 * for that.  Ugh.
	 */
	if (stat(filename, &sb) == -1)
	{
		/* File doesnt exist.  Sheesh. */
		say("DCC RESUME: Cannot use DCC RESUME if the file doesnt exist. [%s|%s]", filename, strerror(errno));
		return;
	}


	if (!(Client = dcc_searchlist(filename, user, DCC_FILEREAD, 0, (char *) 0, 0)))
	{
		if (filename)
			say("No file (%s) offered in SEND mode by %s", filename, user);
		else
			say("No file offered in SEND mode by %s", user);
		return;
	}

	if ((Client->flags & DCC_ACTIVE) || (Client->flags & DCC_WAIT))
	{
		say("A previous DCC GET:%s to %s exists", filename?filename:"<any>", user);
		return;
	}

	if (passwd)
		Client->encrypt = m_strdup(passwd);
	Client->bytes_sent = 0L;
	Client->bytes_read = sb.st_size;

	sprintf(buf, "%hd", ntohs(Client->remport));
	malloc_strcpy(&Client->othername, buf);

	malloc_strcpy(&Client->othername, ltoa((long)ntohs(Client->remport)));

	if (x_debug)
		yell("SENDING DCC RESUME to [%s] [%s|%d|%d]", user, filename, ntohs(Client->remport), sb.st_size);

	/* Just in case we have to fool the protocol enforcement. */
	doing_privmsg = doing_notice = in_ctcp_flag = 0;
	send_ctcp(CTCP_PRIVMSG, user, CTCP_DCC, "RESUME %s %d %d", 
		filename, ntohs(Client->remport), sb.st_size);

	/* Then we just sit back and wait for the reply. */
}

/*
 * When the peer demands DCC RESUME
 * We send out a DCC ACCEPT
 */
#ifdef __STDC__
static void dcc_getfile_resume_demanded (char *user, char *filename, char *port, char *offset)
#else
static void dcc_getfile_resume_demanded (user, filename, port, offset)
char *user, *filename, *port, *offset;
#endif
{
	DCC_list	*Client;

	if (x_debug)
		yell("GOT DCC RESUME REQUEST from [%s] [%s|%s|%s]", user, filename, port, offset);

	if (!(Client = dcc_searchlist(filename, user, DCC_FILEOFFER, 0, port, 0)))
	{
		if (x_debug)
			yell("Resume request that doesnt exist.  Hmmm.");
		return;		/* Its a fake. */
	}

	if (!offset)
		return;		/* Its a fake */

	Client->bytes_sent = atoi(offset);
	Client->bytes_read = 0L;

	doing_privmsg = doing_notice = in_ctcp_flag = 0;
	send_ctcp(CTCP_PRIVMSG, user, CTCP_DCC, "ACCEPT %s %s %s",
		filename, port, offset);

	/* Wait for them to open the connection */
}


/*
 * When we get the DCC ACCEPT
 * We start the connection
 */
#ifdef __STDC__
static	void	dcc_getfile_resume_start (char *nick, char *filename, char *port, char *offset)
#else
static void 	dcc_getfile_resume_start (nick, filename, port, offset)
	char 	*nick, *filename, *port, *offset;
#endif
{
	DCC_list	*Client;
	char		*fullname;

	if (x_debug)
		yell("GOT CONFIRMATION FOR DCC RESUME from [%s] [%s]", nick, filename);

	if (!(Client = dcc_searchlist(filename, nick, DCC_FILEREAD, 0, port, 0)))
		return;		/* Its fake. */

	Client->flags |= DCC_TWOCLIENTS;
	if (!dcc_open(Client))
		return;

	if (!(fullname = expand_twiddle(Client->description)))
		malloc_strcpy(&fullname, Client->description);

	if (!(Client->file = open(fullname, O_WRONLY | O_APPEND, 0644)))
	{
		say("Unable to open %s: %s", Client->description, errno ? sys_errlist[errno] : "<No Error>");
		FD_CLR(Client->read, &readables);
		close(Client->read);
		Client->read = -1;
		Client->flags |= DCC_DELETE;
	}

	new_free(&fullname);
}

#endif
