/* 
 * D C C  */

/* dcc.c        -functions dealing with DCC's see ctcp.c for more.
 * distributed as part of sula - an irc client for X
 * 
 * *  * Author: Tano Fotang, 1997 *  * This program is free software; you can 
 * redistribute it and/or modify it * under the terms of the GNU General
 * Public License as published by the Free  * Software Foundation; either
 * version 2 of the License , or (at your option)  * any later version. * see 
 * the file COPYING for further information. *  */

#include <forms.h>
#include <errno.h>
#include <sys/param.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <glob.h>
#include <fnmatch.h>
#include "checksum.h"
#include "aux.h"
#include "sula.h"
#include "chanwin.h"
#include "script.h"
#include "dcc_gui.h"
#include "global.h"
#include "parser.h"
#include "etc.h"
#include "dcc.h"
#include "setting.h"
#include "ignore.h"
#include "cmd.h"

int iActiveDCC_count;
ActiveDCC *aActiveDCC;

void    resume_cb(FL_OBJECT * ob, long data)
{
#if 0
	FD_dcc *form = (FD_dcc *) ob->form->fdui;

#endif
}

void    resume_request(const char *nick, const char *email,
		       u_short port, off_t offset, CheckSum cksum)
/* we've received a request to resume */
/* first check to see whether what they think they have is actually what
 * we're trying to send. If that's the case, send A DCC ACCEPT. otherwise
 * send a DCC RENAME so they can rename the file instead of overwriting what
 * tehy have. */
{
	DCCFileS *p = filesendStart;

#if DEBUG
	fprintf(stderr, "Resume req from  %s!%s for %hu (offs:%lu, chksum:%lu)\n", nick, email, port, offset, cksum);
#endif
	while (p != filesendEnd && p->port != port)
		p = p->next;
	if (p != filesendEnd) {
		u_long  cks;
		char    buf[128];

		checksum(p->filename, offset, &cks);
		if (cks == cksum) {	/* they have a portion of the file */
#if 0
			/*redundant..they wouldnt send a dcc resume if offset
				== p->size */
				   if (offset == p->size) {	/* they have the same file.let em ignore */
					fprintf(stderr, "%s has %s.(cksum & fileseize match).removing..",
						p->nick, p->filename);
					sprintf(buf, "PRIVMSG %s :%cDCC REJECT %hu%c\n",
						p->nick, 0x01, port, 0x01);
					remove_file_offer(p->id);
				}
				else
#endif
					sprintf(buf, "PRIVMSG %s :%cDCC ACCEPT %lu %hu %lu%c\n",
						p->nick, 0x01,
						cksum,	/* redundant */
						port, offset, 0x01);
			p->offset = offset;
		}
		else
			/* they have a different file */
			sprintf(buf, "PRIVMSG %s :%cDCC RENAME %lu %hu %lu%c\n",
				p->nick, 0x01,
				cks,	/* redundant */
				port,
				offset,		/* redundant */
				0x01);
		sendto_server(Win, buf);
	}

}
static void start_receiving(DCCFileR *, Winstruct *);
extern void accept_request(const char *nick, const char *email,
			   u_short port, off_t offset)
/* 
 * we get here when we get a DCC ACCEPT.  We have to assume that the sender
 * has made sure that we really  have a portion of what they're sending.
 * also, giving an offset is redundant: file will be opened with O_APPEND */
{
	DCCFileR *q = filegetStart;

	while (q != filegetEnd && q->port != port)
		q = q->next;
	if (q == filegetEnd	/* either we didnt send A DCC RESUME or it's been removed */
#ifdef PARANOID
	    || strcasecmp(q->email, email)	/* original file request didnt
						 * come from this address */
#endif
	   ) {
#if DEBUG
		fprintf(stderr, "resume: nick/host %s(%s) or bad offset or req gone\n",
			nick, email);
#endif
		return;
	}
	if (q->resume < 0)
		fprintf(stderr, "Resuming %s from %s, offset %lu (size: %lu)\n",
			q->filename, q->nick, offset, q->size);
	else
		fprintf(stderr, "Resume; rename %s offered by %s; offset was %lu (size: %lu)\n",
			q->filename, q->nick, offset, q->size);
	q->size -= offset;
	start_receiving(q, Win);
}
#define GET_CONTINUE	0
#define GET_IGNORE	1
#define	GET_RESUME	2
static int check_resume(DCCFileR * f, Winstruct * win)
{
/* we have received a DCC SEND. check to see whether we have a file with a
 * similar name.If that's the case, we compare file lengths. If ours is
 * shorter, we send it's length together with its checksum using DCC RESUME.
 * If ours is longer, we compare the checksums. If they#re both the same, we
 * ignore the DCC SEND request. If they differ, we just rename what we have
 * and aceept the file. */

	if (access(f->filename, F_OK) > -1) {
		/* a file exists with that name */
		struct stat sbuf;
		char    buf[512];

		if (stat(f->filename, &sbuf) > -1) {
			int     status;
			u_long  cks;

			if (!S_ISREG(sbuf.st_mode))	/* what we have isnt a regular file */
				return GET_CONTINUE;
			if (sbuf.st_size >= f->size) {
				/* either ours is bigger or both are of same size. 
				 * are the first f->size bytes the same? */
				char    ignore = 0;

				status = checksum(f->filename, f->size, &cks);
				if (status) {
					fprintf(stderr, "%s exists;"
					" Unable to compute checksum: %s\n",
					     f->filename, strerror(status));
					ignore = 1;

				}
				else if (cks == f->checksum)
					/* we have the file (the first f->size bytes are the same) */
				{
					ignore = 1;
					if (gflags & VERBOSE_CTCP)
						fprintf(stderr, "%s exists and is the same:\n"
							"\tchecksum to offs %lu:\E[4m%lu\E[m, size: \E[4m%lu\E[m\n",
							f->filename, f->size, cks, sbuf.st_size);
				}
				if (ignore) {
					if (gflags & VERBOSE_CTCP)
						fprintf(stderr, "DCC SEND from %s ignored.\n",
							f->nick);
					sprintf(buf, "PRIVMSG %s :%cDCC REJECT %hu%c\n",
						f->nick, 0x01, f->port, 0x01);
					sendto_server(win, buf);
					return GET_IGNORE;
				}
				else {
					if (gflags & VERBOSE_CTCP)
						fprintf(stderr, "%s exists but is %s:\n"
							"\tchecksum of %lu bytes:\E[4m%lu\E[m, file size: \E[4m%lu\E[m.\n",
							f->filename,
							sbuf.st_size == f->size ? "different" : "bigger",
						f->size, cks, sbuf.st_size);
					f->resume = 0;
					/* ours is longer and the first bytes are different */
					return GET_CONTINUE;
				}
			}
			else {
				/* ours is shorter.  send checksum and request for resume */
				status = checksum(f->filename, -1, &cks);
				f->resume = -1;		/* our's is shorter */
				if (gflags & VERBOSE_CTCP)
					fprintf(stderr, "(%s %d) %s exists and is shorter:\n"
						"\tchecksum:\E[4m%lu\E[m, size: \E[4m%lu\E[m\n ",
						__FILE__, __LINE__, f->filename, cks, sbuf.st_size);
			}
			f->starttime = time(NULL);
			if (gflags & VERBOSE_CTCP)
				err_msg("Sending checksum and offset to %s for %s\n",
					f->nick, f->filename);
			sprintf(buf, "PRIVMSG %s :%cDCC RESUME %lu %hu %lu%c\n",
				f->nick, 0x01, status ? 0 : cks, f->port, sbuf.st_size, 0x01);
			sendto_server(win, buf);
			return GET_RESUME;
		}		/* if stat() */
	}			/* if(access(f->filename, F_OK..) */
	return GET_CONTINUE;
}
static void remove_from_file_requests_recvd(int id)
{
/* ermove a filerequest from the list of recvd requests */
	DCCFileR *p = filegetStart;
	DCCFileR *q;

	while (p != filegetEnd && p->id != id)
		p = p->next;
	if (p == filegetEnd)	/* either a bug or caller made an errur */
		return;
	free(p->filename);
	if (p->email)
		free(p->email);
	free(p->nick);
	q = p->next;
	if (q == filegetEnd)
		filegetEnd = p;
	else
		*p = *q;
	free(q);
}
extern void rename_request(const char *nick, const char *email,
			   u_short port)
{
	DCCFileR *q = filegetStart;

	while (q != filegetEnd && q->port != port)
		q = q->next;
	if (q == filegetEnd	/* either we didnt send A DCC RESUME or it's been removed */
#ifdef PARANOID
	    || strcasecmp(q->email, email)	/* original file request didnt
						 * come from this address */
#endif
	   ) {
#if DEBUG
		fprintf(stderr, "DCC rename: nick/host %s(%s) or bad offset or req gone\n",
			nick, email);
#endif
		return;
	}
#if DEBUG
	fprintf(stderr, "DCC rename from %s(%s): %s %hu\n", nick, email, q->filename, port);
#endif
	q->resume = 1;
	start_receiving(q, Win);
}
extern void reject_request(const char *nick, const char *email,
			   u_short port)
/* they've rejected our file offer */
{
	DCCFileS *q = filesendStart;

	while (q != filesendEnd && q->port != port)
		q = q->next;
	if (q == filesendEnd	/* either we didnt send A DCC RESUME or it's been removed */
#ifdef PARANOID
	    || strcasecmp(q->email, email)	/* original file request didnt
						 * come from this address */
#endif
	   ) {
#if DEBUG
		fprintf(stderr, "DCC reject: nick/host %s(%s) or bad offset or req gone\n",
			nick, email);
#endif
		return;
	}
#if DEBUG
	fprintf(stderr, "DCC REJECTed for %s by %s!%s\n", q->filename, nick, email);
#endif
	remove_file_offer(q->id);
}

static void start_receiving(DCCFileR * q, Winstruct * win)
{
	int     fd;
	char   *buf;

	fd = tcp_open(NULL, q->inetaddr, NULL, q->port, connect_timeout);
	if (fd < 0) {
	      failed:
		buf = (char *) malloc(sizeof(char) * (strlen(q->nick) + 64 + strlen(PROMPT)));

		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		sprintf(buf, "@i" PROMPT "DCC connection to %s (%lu, %d) failed",
			q->nick, q->inetaddr, q->port);
		fl_addto_browser(win->chanwin->brow2, buf);
		free(buf);
		if (tcp_err_msg)
			fl_addto_browser(win->chanwin->brow2, tcp_err_msg);
		remove_from_file_requests_recvd(q->id);
		return;
	}
	if (dcc_getfile(fd, q, win)) {
		/*       close(fd); */
		goto failed;
	}
	/*remove_from_file_requests_recvd(q->id);*/
}

extern void get(int argc, char **argv, Winstruct * win)
{
/* accept a file (DCC send) req. /get nick or /get id */
	DCCFileR *q = filegetStart;
	int     id;

	if (argc == 1) {
		fl_addto_browser(win->chanwin->brow2, "get what?");
		return;
	}
	if (!strncasecmp(argv[1], "-help", 2)) {
		show_std_help(uppercase(argv[0]), NULL, win->chanwin);
		return;
	}
	id = atoi(*++argv);
	if (id)
		while (q != filegetEnd && q->id != id)
			q = q->next;
	else {
		size_t  ll = strlen(*argv);

		while (q != filegetEnd)
			if (!strncasecmp(*argv, q->nick, ll)) {
				id = q->id;
				break;
			}
			else
				q = q->next;
	}
	if (q == filegetEnd) {
		fl_addto_browser(win->chanwin->brow2,
		      PROMPT "No file offer with that id/from that person");
		return;
	}
	if((sflags&EXTENDED_DCC))
	switch (check_resume(q, win)) {
	    case GET_CONTINUE:
		    start_receiving(q, win);
		    break;
	    case GET_IGNORE:
		    remove_from_file_requests_recvd(q->id);
		    break;
	    case GET_RESUME:
		    break;
	}
	else start_receiving(q, win);
}
extern void receive_send_req(const char *nick, const char *email, char *fname,
			     u_long inaddr, u_short port, off_t fsize,
			     CheckSum cksum)
{
	static int id = 0;
	char    buf[512];
	DCCFileR *p;
	char   *tmp;

	if (!fname)
		return;
#if DEBUG
	fprintf(stderr, "From %s!%s(%lu %hu):%s; checksum:%lu size:%lu\n",
		nick, email, inaddr, port, fname, cksum, fsize);
#endif
	tmp = strrchr(fname, '/');
	if (tmp && !(*++tmp)) {
		if (!(gflags & VERBOSE_CLIENT))
			return;
		sprintf(buf, PROMPT "%s (%s) is sending a directory %s..ignored.",
			nick, email, fname);
		fl_addto_browser(Win->chanwin->brow2, buf);
		return;		/* sending a directory.. just ignore */
	}
	if (!tmp)
		tmp = fname;
	if (*tmp == '~')
		tmp++;		/* ~yyydsds */
	if (!(*tmp))
		return;		/* sending a '~' ..another windowz user */
	p = filegetStart;
	filegetStart = (DCCFileR *) malloc(sizeof(DCCFileR));
	if (filegetStart == NULL) {
		err_ret("Error");
		return;
	}
	filegetStart->id = ++id;
	filegetStart->filename = strdup(tmp);
	filegetStart->size = fsize;
	filegetStart->checksum = cksum;
	filegetStart->resume = 0;
	filegetStart->nick = strdup(nick);
	filegetStart->email = strdup(email);
	filegetStart->starttime = time(NULL);
	filegetStart->port = port;
	filegetStart->inetaddr = inaddr;
	filegetStart->next = p;
	if (gflags & BEEP_DCC)
		fl_ringbell(0);
	if (gflags & AUTO_GETFILE) {
		sprintf(buf, "get %d", filegetStart->id);
		process_cmd(buf, Win);
	}
	else {
		sprintf(buf, PROMPT "File offer from %s (%s, %lu bytes). Type \"/get %d\" to get it",
		     nick, filegetStart->filename, fsize, filegetStart->id);
		fl_addto_browser(Win->chanwin->brow2, buf);
	}
	return;

}

extern void receive_chat_req(const char *nick, const char *email,
			     u_long inaddr, u_short port)
{
	static int id = 0;
	char    buf[512];
	static int chat_req_exists(const char *email);

	ChatReqRecvd *p;

	/*fprintf(stderr, "%s %s %u %d\n", nick, email, inaddr, port); */
	if (chat_req_exists(email)) {
		fprintf(stderr, "new chat request from %s a chat request\n"
		" from %s already exists. accept or remove it first", email,
			email);
		return;		/* dont accept chat request if one already
				 * exists for that user@domain that u havent yet accepted
				 * this prevents flooding, but isnt good if two
				 * people are using same address
				 */
	}
	p = chatreqStart;
	chatreqStart = (ChatReqRecvd *) malloc(sizeof(ChatReqRecvd));
	if (chatreqStart == NULL) {
		err_ret("Error");
		return;
	}
	chatreqStart->id = ++id;
	chatreqStart->nick = strdup2(nick);
	chatreqStart->email = strdup2(email);
	chatreqStart->port = port;
	chatreqStart->inetaddr = inaddr;
	chatreqStart->starttime = time(NULL);
	chatreqStart->next = p;
	if (gflags & BEEP_DCC)
		fl_ringbell(0);
	if (gflags & AUTO_CHAT) {
		sprintf(buf, "achat %d", chatreqStart->id);
		process_cmd(buf, Win);
	}
	else {
		sprintf(buf, PROMPT
			"DCC chat request from %s (%s). Type \"/achat %d\" to accept it.",
			nick, email, chatreqStart->id);
		fl_addto_browser(Win->chanwin->brow2, buf);
	}
	return;
}
static int chat_req_exists(const char *email)
{
	ChatReqRecvd *p = chatreqStart;

	while (p != chatreqEnd) {
		if (!strcasecmp(email, p->email))
			return 1;
		else
			p = p->next;
	}
	return 0;
}

extern void achat(int argc, char **argv, Winstruct * win)
{
/* accept a chat request; win==NULL -> use a new chat window */
	ChatReqRecvd *q = chatreqStart;
	int     fd, id;
	char   *buf = NULL;

	if (argc == 1) {
		fl_addto_browser(win->chanwin->brow2, PROMPT "accept what?");
		return;
	}
	if (!strncasecmp(argv[1], "-help", 2)) {
		show_std_help(uppercase(argv[0]), NULL, win->chanwin);
		return;
	}
	id = atoi(*++argv);
	if (id)
		while (q != chatreqEnd)
			if (q->id == id)
				break;
			else
				q = q->next;
	else {
		size_t  ll = strlen(*argv);

		while (q != chatreqEnd)
			if (!strncasecmp(*argv, q->nick, ll)) {
				id = q->id;
				break;
			}
			else
				q = q->next;
	}
	if (q == chatreqEnd) {
		fl_addto_browser(win->chanwin->brow2,
				 PROMPT "ACHAT: No matching requests");
		return;
	}
#if DEBUG
	fprintf(stderr, "opening connection to DCC chat... #%d", q->id);
#endif
	fd = tcp_open(NULL, q->inetaddr, NULL, q->port, connect_timeout);
#if DEBUG
	fprintf(stderr, "success?\n");
#endif
	if (fd < 0) {
	      failed:
		buf = (char *) malloc(sizeof(char) * 256);

		sprintf(buf, PROMPT "DCC chat connection to %s (%lu, %d) failed",
			q->nick, q->inetaddr, q->port);
#if DEBUG
		if (tcp_err_msg)
			fprintf(stderr, "failed: %s\n", tcp_err_msg);
#endif
		if (win) {
			fl_addto_browser(win->chanwin->brow2, buf);
			if (tcp_err_msg)
				fl_addto_browser(win->chanwin->brow2, tcp_err_msg);
		}
		else {
			berr_msg(buf);
			if (tcp_err_msg)
				fl_addto_browser(win->chanwin->brow2, tcp_err_msg);
		}
		free(buf);
		remove_from_chat_requests_recvd(q->id);
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		return;
	}
	/* do chat on current win */
	{
		struct in_addr in;

		in.s_addr = htonl(q->inetaddr);
#warning (19/02/98) TODO does peername match?
		if (dcc_create_chatwin(fd, q->nick, q->email,
				       inet_ntoa(in), win, q->id)) {
			tcp_err_msg = NULL;
			close(fd);
			goto failed;
		}
	}

}

/********************************************************/
/* d c c   c h a t                                      */
/********************************************************/
extern DCCChatWin *search_dccwinsbyfd(int fd)
{
/* find the window for an  fd when u read from fd */
	DCCChatWin *p = dccchatwinStart;

	while (p != dccchatwinEnd)
		if (*p->fd == fd)
			return p;
		else
			p = p->next;
	return NULL;
}

extern DCCChatWin *search_dccwinsbyid(int id)
{
/* find the window for an  fd when u read from fd */
	DCCChatWin *p = dccchatwinStart;

	while (p != dccchatwinEnd)
		if (p->id == id)
			return p;
		else
			p = p->next;
	return NULL;
}

extern DCCChat *get_windcc_byid(const Winstruct * win, int id)
{
/* search the dcc chats on this window for  id */
	DCCChat *current;

	if (!win)
		return NULL;
	current = win->dccStart->right;
	while (current != win->dccStart) {
		if (current->id == id)
			return current;
		else
			current = current->right;
	}
	return NULL;
}

static void dcc_chat_cb(int fd, void *data)
{
	static void parse_chat_output(DCCChatWin * dccwin, int read, char *msg);
	char    line[MAXLINE + 1];
	int     n;
	DCCChatWin *dccwin;
	char   *buf;

	if ((n = read(fd, line, MAXLINE)) < 0) {
		if (errno == EAGAIN) {
			errno = 0;
			return;
		}
		fl_remove_io_callback(fd, FL_READ, dcc_chat_cb);
		close(fd);
		dccwin = search_dccwinsbyfd(fd);
		/* todo: dont quit; just close all dcc chats */
		assert(dccwin);
		*dccwin->fd = -1;
		buf = (char *) malloc(sizeof(char) * (64 + strlen(dccwin->nick)
						      + strlen(PROMPT)));

		if (buf != NULL) {
			sprintf(buf, PROMPT "Read error from %s", dccwin->nick);
			fl_addto_browser(dccwin->win->chanwin->brow2, buf);
			free(buf);
		}
		n = remove_dccchat(dccwin->id, NULL);
		assert(n);	/*if (!remove_dccchat(dccwin->id, NULL))
				 * err_quit("Bug: DCC chat window list is screwed up"); */
		if (fl_get_button(dccwin->win->chanwin->r_dcc_chat))
			fl_set_button(dccwin->win->chanwin->r_quote, 1);
		UpdateStatus(dccwin->win);
		return;
	}
	if (n == 0) {
		fl_remove_io_callback(fd, FL_READ, dcc_chat_cb);
		close(fd);
		dccwin = search_dccwinsbyfd(fd);
		assert(dccwin != NULL);
		*dccwin->fd = -1;
		buf = (char *) malloc(sizeof(char) * (64 + strlen(dccwin->nick) + strlen(PROMPT)));

		sprintf(buf, PROMPT "DCC Connection closed from %s.", dccwin->nick);
		if (fl_get_button(dccwin->win->chanwin->r_dcc_chat))
			fl_set_button(dccwin->win->chanwin->r_quote, 1);
		fit_object_label(dccwin->win->chanwin->WinStatusLine, buf);
		fl_addto_browser(dccwin->win->chanwin->brow2, buf);
		free(buf);
		assert(remove_dccchat(dccwin->id, NULL));
		/*err_quit("Bug: DCC chat window list contains junk"); */
		UpdateStatus(dccwin->win);
		return;
	}
	line[n] = '\0';
	dccwin = search_dccwinsbyfd(fd);	/* get window to display data on */
	assert(dccwin != NULL);
	dccwin->read += n;
	if (line[--n] == '\n')
		line[n] = '\0';
	parse_chat_output(dccwin, n, line);

}
static int check_DCC_msg(MsgNr i, Winstruct * win, const char *nick,
			 const char *msg)
{
	/* 
	 * <w> <id> <nick> <msg> 
	 * msg: <email> <filename> <size> <read/written>
	 * or msg: chat message
	 */

	int     j, w = -1;
	On     *p;
	char   *buf, *m, *tmp, stop = 0, do_external = 1;

	if (want[i].start == want[i].end)
		return 0;
	if (win)
		for (j = 0; j < win_count; j++)
			if (&winstruct[j] == win) {
				w = j;
				break;
			}
	p = want[i].start;
	m = (char *) malloc(sizeof(char) * (strlen(msg) + strlen(nick) + 3));

	if (m == NULL)
		return 0;
	sprintf(m, "%s %s", nick, msg);
	buf = (char *) malloc(sizeof(char) * (strlen(m) + 32));

	if (!buf)
		return 0;

	tmp = (gflags & MATCH_CASELESS) ? lowercase(m) : strdup(m);
	while (p != want[i].end) {
		if (!fnmatch(
				    gflags & MATCH_CASELESS ? lowercase(p->pattern) : p->pattern, tmp,
		  FNM_PATHNAME | FNM_PERIOD) && !(p->flag & HK_SUSPENDED)) {
			if (p->type == CMD_FD) {
				if (do_external) {
					sprintf(buf, "%d %d %s\n", w, p->ser_number, m);
					if (!write_to_sp(p->cmd.sp, buf) &&
					    (p->flag & HK_EXCLUSIVE))
						do_external = 0;
				}
			}
			else if (p->type == CMD_IN /*&& !stop <-- wrong */ ) {
				do_internal_hook(p, m, w);
				if (p->ser_number == 0)
					stop = 1;
			}

		}
		p = p->next;
	}
	free(buf);
	free(m);
	free(tmp);
	return (stop || !do_external);
}

static void parse_chat_output(DCCChatWin * dccwin, int read, char *msg)
{
	char   *bigbuf;

	if (check_DCC_msg(CHATMSG, dccwin->win, dccwin->nick, msg))
		return;
	if ((gflags & BEEP_MSG))
		/* cant beep away since win->server->away wont work for dcc */
		fl_ringbell(0);
	if (*msg == 0x02)
		switch (*++msg) {
		    case 0x02:{
				    msg++;
				    unquote_str(&msg);
				    fmt_string = "%n%p%z%Z$";
				    bigbuf = format_msg(
							       fmt_other_dccchat, dccwin->nick, msg, read, dccwin->read);
				    if (bigbuf)
					    fl_addto_browser(dccwin->win->chanwin->browser, bigbuf);
				    return;
			    }
		    default:
			    break;
		}		/* swicth */
	fmt_string = "%n%p%z%Z$";
	bigbuf = format_msg(fmt_other_dccchat, dccwin->nick, msg, read, dccwin->read);
	if (bigbuf)
		fl_addto_browser(dccwin->win->chanwin->browser, bigbuf);
}

extern int dcc_create_chatwin(int fd, char *nick,
		       char *email, char *rem_host, Winstruct * win, int id)
/* create a chat connection; create new window for chat if win==NULL  return
 * 0 on success */
{
	DCCChatWin *p;
	DCCChat *d;

	struct linger l;
	int     keepalive = 1;
	char    new_win = 1;	/* creating a new window? */

#if DEBUG
	fprintf(stderr, "creating window for DCC chat\n");
#endif
	d = (DCCChat *) malloc(sizeof(DCCChat));
	if (!d) {
		err_ret("dcc_chat_connect error");
		return -1;
	}
	d->fd = fd;
	d->nick = strdup(nick);
	d->email = strdup2(email);
	l.l_onoff = 0;
	l.l_linger = 0;
	if (setsockopt(d->fd, SOL_SOCKET, SO_LINGER, (void *) &l, sizeof(l)) < 0)
		if (errno == EBADF || errno == ENOTSOCK) {
			berr_ret("setsockopt error");
			close(d->fd);
			return -1;
		}
		else
			err_ret("setsockopt SOL_SOCKET error.. ignored");
	if (setsockopt(d->fd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepalive,
		       sizeof(keepalive)) < 0)
		err_ret("setsockopt SO_KEEPALIVE failed.. ignored");
	if (fcntl(d->fd, F_SETFL, O_NONBLOCK) == -1)
		err_ret("fcntl error.. ignored");
	p = dccchatwinStart;
	dccchatwinStart = (DCCChatWin *) malloc(sizeof(DCCChatWin));
	if (!dccchatwinStart)
		err_sys(" DCCChatWin Error");
	d->id = ++chat_id;
	dccchatwinStart->next = p;
	dccchatwinStart->id = d->id;
	/*       *dccchatwinStart->fd=d->fd  below */
	dccchatwinStart->nick = d->nick;
	dccchatwinStart->email = d->email;
	dccchatwinStart->read = 0;
	if (win) {
		int     i;

		for (i = 0; i < win_count; i++)
			if (winstruct[i].chanwin && win == &winstruct[i]) {
				dccchatwinStart->win = win;
				new_win = 0;
				break;
			}
	}
	if (new_win) {
		/*  create a channel window for the dcc chat    */
		win = new_chan_win();
		win->server = NULL;
		set_object_attribs(win);
		dccchatwinStart->win = win;
	}
	d->left = win->dccCur;
	d->right = win->dccCur->right;
	win->dccCur->right->left = d;
	win->dccCur->right = d;
	win->dccCur = d;
	dccchatwinStart->fd = &win->dccCur->fd;
	if (new_win) {		/* just created the win */
		char   *label = (char *) malloc(sizeof(char) *
					  (strlen(win->dccCur->nick) + 100 +
		       (win->dccCur->email ? strlen(win->dccCur->email) : 0)
						));

		sprintf(label, "[%d] " sula_NAME sula_VERSION ": DCC chat with %s",
			(win-winstruct)+1,win->dccCur->nick /*, dccchatwinStart->email */ );
		fl_show_form(win->chanwin->chanwin, FL_PLACE_FREE, FL_FULLBORDER,
			     label);
		free(label);
	}
	fl_add_io_callback(win->dccCur->fd, FL_READ, dcc_chat_cb, (void *) 0);
	{
		char    buf[512];

		sprintf(buf, "DCC CHAT connection to %s (%s) established (%d)",
		   win->dccCur->nick, email ? email : "-", win->dccCur->fd);
		fit_object_label(win->chanwin->WinStatusLine, buf);
		if (win->server == NULL) {
			sprintf(buf, sula_NAME sula_VERSION ": DCC chat with %s", win->dccCur->nick);
			fl_set_form_title(win->chanwin->chanwin, buf);
		}
		if (gflags & BEEP_DCC)
			fl_ringbell(0);
#ifdef PARANOID
		sprintf(buf, "You are talking to %s (%s). Their connection is from %s.",
			win->dccCur->nick, dccchatwinStart->email ?
			dccchatwinStart->email : "-", rem_host);
#if DEBUG
		fprintf(stderr, "%s\n", buf);
#endif
		fl_addto_browser(win->chanwin->brow2, buf);
#endif
	}

	if (id)
		remove_from_chat_requests_recvd(id);
	fl_set_button(win->chanwin->r_dcc_chat, 1);
	UpdateStatus(win);
	if (new_win)
		fl_do_forms();
	return 0;
}

extern int sendto_dcc_chat(Winstruct * win, DCCChat * dcc_chat, const char *buf)
{
	int     n, w;

	if (!buf || !win || dcc_chat == win->dccStart
	    || win->dccStart->left == win->dccStart)
		return 0;
	if (dcc_chat->fd < 0) {
		fl_ringbell(50);
		fl_set_object_label(win->chanwin->WinStatusLine,
				 "No DCC chat connection with that id (?)");
		return 0;
	}
	n = strlen(buf);
	w = writen(dcc_chat->fd, buf, n);
	if (w != n) {
		if (errno == EAGAIN /*|| errno == EINTR*/) {
			/* let's forget the rest of the msg.
			 * let caller figure out how to send the remainin (n-w) bytes
			 */
			errno = 0;
			return w;
		}
		else {
			char    buf[128];

			sprintf(buf, "Dcc chat: write error to %s.", dcc_chat->nick);
			fl_addto_browser(win->chanwin->brow2, buf);
			remove_dccchat(dcc_chat->id, NULL);
			return w;
		}
	}
	return w;
}

extern int remove_dccchat(int id, char *reason)
{
/* close a chat connecion */
	DCCChat *p;
	DCCChatWin *q, *qq;
	Winstruct *win;

	q = search_dccwinsbyid(id);	/* find the correspondin global chat; */
	if (!q)
		return 0;	/*err_quit("DCC chat: global list errur"); */
	win = q->win;
	p = get_windcc_byid(q->win, q->id);	/* find the chat connection on this window */
	if (!p)
		return 0;	/* no chats */
	assert(p != win->dccStart);	/* superflous 'cause not possible.. see get_windcc_byid() */
	qq = q->next;
	if (qq == dccchatwinEnd)
		dccchatwinEnd = q;
	else
		*q = *qq;
	free(qq);
	if (p->fd > 0) {
		char   *buf = NULL;

		fl_remove_io_callback(p->fd, FL_READ, dcc_chat_cb);
		if (reason) {
			buf = (char *) malloc(sizeof(char) * (strlen(reason) + 2));

			if (!buf)
				goto go;
			else
				sprintf(buf, "%s\n", reason);
			writen(p->fd, buf, strlen(buf));
		}
		else if (chat_quit_text) {
			buf = (char *) malloc(sizeof(char) * (strlen(chat_quit_text) + 2));

			if (!buf)
				goto go;
			else
				sprintf(buf, "%s\n", chat_quit_text);
			writen(p->fd, buf, strlen(buf));
		}
		free(buf);
	      go:
		close(p->fd);
	}
	free(p->nick);
	free(p->email);
	p->left->right = p->right;
	p->right->left = p->left;
	if (p == win->dccCur)
		win->dccCur = p->left;
	if (win->dccCur == win->dccStart)
		win->dccCur = win->dccStart->right;
	/* caller should update window status line */
	return 1;
}

static void dcc_sentchats_cb(int fd, void *data)
{
/* accept reply to a chat request that u sent out */
	ChatReqSent *p = chatsentStart;
	int     newfd, ll;
	struct sockaddr_in they;

	while (p != chatsentEnd  && p->fd !=fd)
		p = p->next;
	assert(p != chatsentEnd);
	if ((newfd = accept(p->fd, (struct sockaddr *) &they, &ll)) < 0) {
		{
			err_ret("dcc_sentchats_cb accept error");
			remove_sentchatreq(p->id);
			return;
		}
	}
	if (dcc_create_chatwin(newfd, p->nick, NULL,
			    (char *) inet_ntoa(they.sin_addr), p->win, 0)) {
		if (p->win) {
			char    buf[256];

			sprintf(buf, PROMPT "error creating win for DCC CHAT to %s (%s)",
				p->nick, inet_ntoa(they.sin_addr));
			fl_addto_browser(p->win->chanwin->brow2, buf);
		}
		else
			berr_ret(PROMPT "error creating win for DCC CHAT to %s (%s)",
				 p->nick, inet_ntoa(they.sin_addr));
		close(newfd);
	}
	remove_sentchatreq(p->id);

}

extern void remove_from_chat_requests_recvd(int id)
{
/* ermove a chat request from the list of recvd requests used by chat()  */
	ChatReqRecvd *p = chatreqStart;
	ChatReqRecvd *q;

	while (p != chatreqEnd && p->id != id)
		p = p->next;
	if (p == chatreqEnd) {	/* either a bug or caller made an error */
#if DEBUG
		fprintf(stderr, "(%s[%d]) #%d not found. please report.\n",
			__FILE__, __LINE__, id);
#endif
		return;
	}
	free(p->nick);
	free(p->email);
	q = p->next;
	if (q == chatreqEnd)
		chatreqEnd = p;
	else
		*p = *q;
	free(q);
	return;
}

extern void remove_sentchatreq(int id)
{
/* remove a sent request from the list of sent requests */
	ChatReqSent *p = chatsentStart;
	ChatReqSent *q;

	while (p != chatsentEnd && p->id != id )
		p = p->next;
	if (p == chatsentEnd)	/* either a bug or caller made an errur */
		return;
	fl_remove_io_callback(p->fd, FL_READ, dcc_sentchats_cb);
	close(p->fd);
	free(p->nick);
	free(p->email);
	q = p->next;
	if (q == chatsentEnd)
		chatsentEnd = p;
	else
		*p = *q;
	free(q);
	return;
}

extern int dcc_sendchatreq(Winstruct * win, Winstruct * w, const char *nick)
{
/* send a chat req from within win; w wil be used when reply arrives(can be * 
 * NULL) */
	ChatReqSent *p;
	int     fd, ll;
	struct sockaddr_in here;
	static int id = 0;	/* # of chat reqs sent */
	char    first_run = 1, dynamic_slip = 1, buf[512];

	if (first_run) {
		bzero((char *) &me, sizeof(me));
		if (gethostname(host, sizeof(host)) < 0) {
			err_ret("gethostname errur");
			return -1;
		}
		if ((me.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
			struct hostent *hp;

			hp = gethostbyname(host);
			if (hp == NULL) {
				herror(" gethostbyname error");
				return -1;
			}
			bcopy(hp->h_addr, (char *) &me.sin_addr, hp->h_length);
		}
		if(dynamic_slip) first_run = 0;
	}
	if ((fd = tcp_create_server()) < 0)
		return -1;
	ll = sizeof(here);
	if (getsockname(fd, (struct sockaddr *) &here, &ll) < 0) {
		err_ret("dcc_sendchatreq: getsockname errur");
		close(fd);
		return -1;
	}
	p = chatsentStart;
	chatsentStart = (ChatReqSent *) malloc(sizeof(ChatReqSent));
	if (chatsentStart == NULL) {
		err_ret("dcc_sendchatreq error");
		close(fd);
		return -1;
	}
	chatsentStart->id = ++id;
	chatsentStart->fd = fd;
	chatsentStart->nick = strdup(nick);
	chatsentStart->email = NULL;	/* we dont know it */
	chatsentStart->next = p;
	chatsentStart->starttime = time(NULL);	/* close request if it isnt accept after some time */
	chatsentStart->server = win && win->server ? win->server->servname : NULL;
	chatsentStart->win = w;
	fl_add_io_callback(chatsentStart->fd, FL_READ, dcc_sentchats_cb, (void *) 0);
	sprintf(buf, "PRIVMSG %s :%cDCC CHAT chat %lu %u%c\n", chatsentStart->nick,
		0x01, ntohl(me.sin_addr.s_addr),
		ntohs(here.sin_port), 0x01);
	sendto_server(win, buf);
	return 0;

}

static void remove_filetransfer(int id)
{
/* remove  from the list called by dcc_getfile:cb and offeredfilesr_cb  send
 * != 0: we were sending; */
	char    buf[256];
	DCCFile *p = DCCfileStart, *q;

	while (p != DCCfileEnd && p->id != id)
		p = p->next;
	if (p == DCCfileEnd)
		return;
	if(p->w>-1){
		aActiveDCC[p->w].empty=1;
		fl_hide_form(aActiveDCC[p->w].form->active_dcc_get);
		fl_free_form(aActiveDCC[p->w].form->active_dcc_get);
	}
	sprintf(buf, "%s %s %lu %lu", p->email, p->filename, p->size, p->read);
	if (p->flag) {
		sprintf(buf, "%s %s %lu %lu", p->host, p->filename, p->size, p->read);
		check_DCC_msg(DCC_SEND_DONE, NULL, p->nick, buf);	/*done sending */
		free(p->host);
	}
	else {
		sprintf(buf, "%s %s %s %lu %lu", p->host, p->email, p->filename, p->size,
			p->read);
		check_DCC_msg(DCC_GET_DONE, NULL, p->nick, buf);	/*done getting */
	}
	free(p->filename);
	free(p->email);
	free(p->nick);
	q = p->next;
	if (q == DCCfileEnd)
		DCCfileEnd = p;
	else
		*p = *q;
	free(q);
	return;
}
static void dcc_getfile_cb(int fd, void *data)
/* to be called only by the io_callback! */
{
	char    line[MAXLINE + 1];
	int     n;
	DCCFile *p;

	p = DCCfileStart;
	while (p != DCCfileEnd && p->fd != fd)
		p = p->next;
	assert(p != DCCfileEnd);	/* how in heaven's name did u get in here then?? */
	if ((n = read(fd, line, MAXLINE)) < 0) {
		if (errno != EAGAIN) {
			fl_remove_io_callback(fd, FL_READ | FL_WRITE, dcc_getfile_cb);
			close(fd);
			close(p->fp);
			berr_msg(PROMPT
				 "DCC get: Read error from %s for %s (read %lu of %lu bytes)",
				 p->email, p->filename, p->read, p->size);
			if (gflags & BEEP_ERR)
				fl_ringbell(0);
			remove_filetransfer(p->id);
		}
		return;
	}
	if (n == 0) {
		float   diff;
		float   read;
		struct timeval t;

		gettimeofday(&t, NULL);
		fl_remove_io_callback(fd, FL_READ | FL_WRITE, dcc_getfile_cb);
		close(fd);
		close(p->fp);
		read = p->size > 0 ? (p->read * 100.0) / p->size : 100;
		diff = t.tv_sec - p->starttime.tv_sec +
		   (float) ((t.tv_usec - p->starttime.tv_usec) / 1000000.0);
		if (p->read >= p->size) {
			berr_msg(PROMPT "File %s received from %s. Read %lu bytes (%.0f%%) in %f seconds"
				 " (%0.3f kb/sec)",
				 p->filename, p->nick, p->read, read,
				 diff, 0.1 * p->read / ((0.1 * 1024) * (diff == 0 ? 1 : diff)));
		}
		else {
			berr_msg(PROMPT "File transfer %s from %s terminated. Read %lu bytes (%.0f%%) in %f seconds"
				 " (%0.3f kb/sec)",
				 p->filename, p->nick, p->read, read, diff,
				 (0.1 * p->read) / ((0.1 * 1024) * (diff == 0 ? 1 : diff)));

		}
		remove_filetransfer(p->id);
		if (gflags & BEEP_DCC)
			fl_ringbell(0);
		return;
	}
	gettimeofday(&p->lasttime, NULL);
	p->read += n;
      re_write:
	if (write(p->fp, line, n) != n) {
		if (errno == EAGAIN)
			goto re_write;
		else{
		/* no use checking errno. */
		berr_ret(PROMPT "DCC GET: Write error to local file ", p->filename);
	      out:
		fl_remove_io_callback(fd, FL_READ | FL_WRITE, dcc_getfile_cb);
		close(fd);
		close(p->fp);
		remove_filetransfer(p->id);
		}
		return;
	}
	else {
		long     read = htonl(p->read);

	      again:
		if (writen(fd, (char *) &read, sizeof(read)) != sizeof(read)) {
			if (errno == EAGAIN)
				goto again;
			err_ret("Write error to %s after receiving %lu bytes", p->nick, p->read);
			if (gflags & BEEP_ERR)
				fl_ringbell(0);
			goto out;
		}
	}
}
#define ACTIVE_ALLOC 2
static int ActiveForm_alloc(){
	int i;
	if(aActiveDCC==NULL)
		aActiveDCC=malloc(sizeof(ActiveDCC)*ACTIVE_ALLOC);
	else
		aActiveDCC=realloc(aActiveDCC,
		 (iActiveDCC_count+ACTIVE_ALLOC)*sizeof(ActiveDCC));
	if(aActiveDCC==NULL) return (errno);
	for (i=iActiveDCC_count;i< iActiveDCC_count+ACTIVE_ALLOC; i++){
		aActiveDCC[i].empty=1;
	}
	iActiveDCC_count+=ACTIVE_ALLOC ;
	return (0);
}
static int  ignore_user(FL_OBJECT * ob, int event,
		   FL_Coord mx, FL_Coord my, int key, void *xev)
{
	
	if(event== FL_ENTER || event== FL_LEAVE || event==FL_PUSH || 
	event== FL_RELEASE )
		    return FL_PREEMPT;
       	return !FL_PREEMPT;
}
static ActiveDCC *transfer_win(DCCFile * f,
off_t size,
u_long checksum,
char to)
 {
	int i;
	if(aActiveDCC==NULL && ActiveForm_alloc()){
		return NULL;
	}

retry:
	for(i=0;i<iActiveDCC_count;i++)
	  if(aActiveDCC[i].empty){
		char buf[255];
		ActiveDCC *p=&aActiveDCC[i];
		p->form= create_form_active_dcc_get();
		fl_set_object_label(p->form->file,f->filename);
		sprintf(buf,"%lu",size);
		fl_set_object_label(p->form->size, buf);
		sprintf(buf,"%lu",checksum);
		fl_set_object_label(p->form->checksum, buf);
		if(to){
			strcpy(buf,f->nick);
			fl_set_object_label(p->form->dest, "To");
		}else
			sprintf(buf,"%s!%s",f->nick,f->email);
		fl_set_object_label(p->form->from, buf);
		fl_set_object_label(p->form->speed,"0 kb/s");
		fl_set_object_prehandler(p->form->slider, ignore_user);
		fl_set_slider_bounds(p->form->slider,0,100);
		fl_set_object_bw(p->form->slider,-1);
		p->form->stop->u_ldata=f->id;
		f->w=i;
		aActiveDCC[i].empty=0;
		return (&aActiveDCC[i]);
	}

	if(ActiveForm_alloc()) return NULL;
	goto retry;
}
void    file_get_cb(FL_OBJECT * ob, long data)
{
	close_getfile(ob->u_ldata,NULL);
}


extern int dcc_getfile(int fd, DCCFileR * f, Winstruct * win)
/* start receiving a file */
{
	DCCFile *p;
	int     keepalive = 1;
	char    buf[MAXLINE + 1];
	ActiveDCC *adcc;

	p = DCCfileStart;
	DCCfileStart = (DCCFile *) malloc(sizeof(DCCFile));
	if (!DCCfileStart) {
		err_ret("DCC GET error");
		return -1;
	}
	DCCfileStart->fd = fd;
	DCCfileStart->nick = strdup(f->nick);
	DCCfileStart->filename = strdup(f->filename);
	DCCfileStart->size = f->size;
	DCCfileStart->offset = 0;
	DCCfileStart->flag = 0;
	DCCfileStart->read = 0;
	DCCfileStart->w=-1;

	if (setsockopt(DCCfileStart->fd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepalive,
		       sizeof(keepalive)) < 0)
		err_ret("setsockopt SO_KEEPALIVE failed.. ignored");
	if (fcntl(DCCfileStart->fd, F_SETFL, O_NONBLOCK) == -1)
		err_ret("O_NONBLOCK:fcntl error..ignored.");
	DCCfileStart->id = ++dcc_file_id;

	if (f->resume == 1	/* a client which understands DCC RESUME but not
				 * DCC RENAME will send u an empty file now */
	    ||
	    (f->resume == 0 && access(DCCfileStart->filename, F_OK) > -1)
	   ) {
		/*char    *buff=(char *)malloc(sizeof(char)*(
		 * strlen(DCCfileStart->filename)+64)); */

		srandom(time(NULL));
		sprintf(buf, "%s.%d", DCCfileStart->filename, (int) random() % 10000000);
		free(DCCfileStart->filename);
		DCCfileStart->filename = strdup(buf);
		sprintf(buf, "File exists. Renamed to %s", DCCfileStart->filename);
		fl_addto_browser(win->chanwin->brow2, buf);
	}
	if ((DCCfileStart->fp = open(DCCfileStart->filename,
				     f->resume < 0 ?	/* ours is shorter */
				     O_WRONLY | O_APPEND | O_CREAT :
	/* renaming */
				     O_WRONLY | O_CREAT | O_TRUNC,
				     S_IRUSR | S_IWUSR | S_IRGRP))
	    < 0) {
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		sprintf(buf, "%s %s", strerror(errno), DCCfileStart->filename);
		fl_addto_browser(win->chanwin->brow2, buf);
		remove_filetransfer(DCCfileStart->id);
		return -1;
	}
	gettimeofday(&DCCfileStart->starttime, NULL);
	DCCfileStart->email = strdup(f->email);
	{
		struct in_addr in;

		in.s_addr = htonl(f->inetaddr);
		DCCfileStart->host = strdup2(inet_ntoa(in));
	}
	DCCfileStart->next = p;
	sprintf(buf, PROMPT "%s transfer for %s (%lu bytes left) from %s (%s)"

#ifdef PARANOID
		"\n\t%s is on host %s"
#endif
		,f->resume < 0 ? "Resuming" : "Starting",
	     DCCfileStart->filename, DCCfileStart->size, DCCfileStart->nick,
		DCCfileStart->email
#ifdef PARANOID
		,DCCfileStart->nick, DCCfileStart->host
#endif
	   );
	fl_addto_browser(win->chanwin->brow2, buf);
	fl_add_io_callback(DCCfileStart->fd, FL_READ, dcc_getfile_cb, (void *) 0);
	
	adcc=transfer_win(DCCfileStart, f->size,f->checksum,0);
	if(adcc){
	    adcc->form->size->u_ldata=DCCfileStart->read;/* read*/
	    fl_set_form_atclose(adcc->form->active_dcc_get,dont_close_win, 0);
	    fl_show_form(adcc->form->active_dcc_get,FL_PLACE_CENTER,
		FL_FULLBORDER, sula_NAME": DCC File Transfer");
	    remove_from_file_requests_recvd(f->id);
	    fl_do_forms();
	} else perror("Error--close some windows"); 
	return 0;
}

/* 
 *    FILE
 
 offer
 */

static void dcc_sendfile_cb(int fd, void *data)
/* send file. to be called only by the io_callback! */
{
	static void done_sending(DCCFile * p);
	char   *line;
	int     n;
	int     w;		/* bytes written */
	DCCFile *p;

	p = DCCfileStart;
	while (p != DCCfileEnd && p->fd !=fd)
		p = p->next;
	assert(p != DCCfileEnd);	/* how did you get in here then?? */
	line = malloc(sizeof(char) * (max_sendbuf + 2));

	if (!line) {
		fl_show_alert(sula_NAME sula_VERSION ": Memory failure",
			      "Try shutting down some applications",
			      strerror(errno), 1);
		fl_ringbell(10);
		free(line);
		done_sending(p);
		return;
	}
	if ((n = read(fd, line, sizeof(int))) < 0) {

		if (errno == EAGAIN) {
			free(line);
			return;
		}		/* EWOULDBLOCK; lets try again */
		free(line);
		berr_ret(PROMPT "DCC SEND: Read error from %s (sent %lu of %lu bytes)",
			 p->email, p->read, p->size);
		fl_remove_io_callback(fd, FL_READ | FL_WRITE, dcc_sendfile_cb);
		close(fd);
		close(p->fp);
		remove_filetransfer(p->id);
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		return;
	}
	if (n == 0) {
		free(line);
		done_sending(p);
		return;
	}
	n = read(p->fp, line, max_sendbuf);
	if (n == 0) {
		free(line);
		if (gflags & VERBOSE_CLIENT)
			if (p->read < p->size - p->offset)
				berr_ret("unexpected EOF %s (local file)", p->filename);
		done_sending(p);
		if (gflags & BEEP_DCC)
			fl_ringbell(0);
		return;
	}
	if (n < 0) {
		free(line);
		if (errno == EAGAIN /*|| errno==EINTR -->SA_RESTART */ ){ 
			errno = 0;
			return;
		}
		berr_ret(PROMPT "DCC SEND: Read error on local file %s", p->filename);
		fl_remove_io_callback(fd, FL_READ, dcc_sendfile_cb);
		close(fd);
		remove_filetransfer(p->id);
		close(p->fp);
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		return;
	}
	w = writen(fd, line, n);
	if (w != n) {
		if ((errno == EAGAIN /* EINTR wont occur: SA_RESTART */ ) &&
		    lseek(p->fp, w - n, SEEK_CUR) != -1) {	/* wrote w bytes instead of n */
			goto weiter;
		}		/* let'S try again */
		free(line);
		berr_ret(PROMPT "DCC SEND(%d): Write error to %s (%s) ", p->nick, p->email);
		fl_remove_io_callback(fd, FL_READ, dcc_getfile_cb);
		close(fd);
		close(p->fp);
		remove_filetransfer(p->id);
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		return;
	}
      weiter:
	free(line);
	gettimeofday(&p->lasttime, NULL);
	p->read += w;
	return;
}

static void done_sending(DCCFile * p)
{
	float   diff;
	struct timeval t;
	float   read;

	gettimeofday(&t, NULL);
	fl_remove_io_callback(p->fd, FL_READ, dcc_sendfile_cb);
	close(p->fd);
	close(p->fp);
	read = p->size > 0 ? ((p->read * 100.0) / (p->size - p->offset)) : 100;
	diff = t.tv_sec - p->starttime.tv_sec +
	   (float) ((t.tv_usec - p->starttime.tv_usec) / 1000000.0);

	if (p->read >= p->size) {
		berr_msg(PROMPT "File sent to %s. Wrote %lu bytes (%.0f%%) in %f seconds"
			 " (%0.3f kb/sec)", p->nick, p->read, read, diff,
		 (0.1 * p->read) / ((0.1 * 1024) * (diff == 0 ? 1 : diff)));
	}
	remove_filetransfer(p->id);
	if (gflags & BEEP_DCC)
		fl_ringbell(0);
	return;
}
extern void close_getfile(int id, Winstruct * win)
{
/* close a DCC-Get/send connection: stop reeivin a file,or stop sending one */
	char    buf[512];
	float   read;
	DCCFile *p = DCCfileStart;

	while (p != DCCfileEnd && p->id!= id)
		p = p->next;
	if (p == DCCfileEnd)
		return;
	if (p->flag)
		fl_remove_io_callback(p->fd, FL_READ, dcc_sendfile_cb);
	else
		fl_remove_io_callback(p->fd, FL_READ, dcc_getfile_cb);
	close(p->fd);
	close(p->fp);
	
	read = p->size > 0 ? ((p->read * 100.0) / (p->size - p->offset)) : 100;
	sprintf(buf, PROMPT "File transfer with %s disrupted. %s %lu bytes (%.0f%%).",
		p->nick, p->flag ? "Wrote" : "Read", p->read, read);
	if (win)
		fl_addto_browser(win->chanwin->brow2, buf);
	else
		berr_msg(buf);
	remove_filetransfer(p->id);
	return;
}
static void offered_files_cb(int fd, void *data)
{
/* accept reply to an offer that u sent */
	#define FIRST_RD 1
	DCCFileS *p = filesendStart;
	int     newfd, ll;
	struct sockaddr_in they;
	int     keepalive = 1;
	struct linger l;
	char   *buf;
	DCCFile *q = DCCfileStart;
	int     fp;
	int     n;
	#if FIRST_RD
	int w;
	#endif
	ActiveDCC *adcc;

	while (p != filesendEnd && p->fd != fd)
		p = p->next;
	assert(p != filesendEnd);	/* fatal error: u cant be in here if there was
					 * * * no fd to read from! */
	/*err_quit("%s %d Bug: file list contains trash", __FILE__, __LINE__); */
	if ((newfd = accept(p->fd, (struct sockaddr *) &they, &ll)) < 0) {
		err_ret("offered_files_cb accept error");
		remove_file_offer(p->id);
		return;
	}
	if ((fp = open(p->filename, O_RDONLY | O_NONBLOCK)) < 0) {
		berr_ret("%s (%d) %s", __FILE__, __LINE__, p->filename);
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		close(newfd);
		remove_file_offer(p->id);
		return;
	}
	DCCfileStart = (DCCFile *) malloc(sizeof(DCCFile));
	if (!DCCfileStart) {
		berr_ret("offered_files_cb");
		close(newfd);
		close(fp);
		remove_file_offer(p->id);
		return;
	}
	DCCfileStart->next = q;
	DCCfileStart->fd = newfd;
	DCCfileStart->fp = fp;
	DCCfileStart->nick = strdup(p->nick);
	DCCfileStart->filename = strdup(p->filename);
	DCCfileStart->size = p->size;
	DCCfileStart->flag = 1;
	DCCfileStart->read = 0;
	DCCfileStart->w=-1;
	DCCfileStart->offset = p->offset;
	lseek(DCCfileStart->fp, DCCfileStart->offset, SEEK_SET);
	l.l_onoff = 0;
	l.l_linger = 0;
	if (setsockopt(DCCfileStart->fd, SOL_SOCKET, SO_LINGER, (void *) &l, sizeof(l)) < 0)
		err_ret("%s (%d) setsockopt SO_LINGER failed", __FILE__, __LINE__);
	if (setsockopt(DCCfileStart->fd, SOL_SOCKET, SO_KEEPALIVE, (void *) &keepalive,
		       sizeof(keepalive)) < 0)
		err_ret("%s (%d) setsockopt SO_KEEPALIVE failed", __FILE__, __LINE__);
	if (fcntl(DCCfileStart->fd, F_SETFL, O_NONBLOCK) == -1)
		err_ret("fcntl error. May block!!");
	DCCfileStart->id = ++dcc_file_id;
	gettimeofday(&DCCfileStart->starttime, NULL);
	DCCfileStart->email = NULL;	/* we dont know it */
	DCCfileStart->host = strdup2(inet_ntoa(they.sin_addr));
	if (gflags & VERBOSE_CLIENT) {
		char   *buf = (char *) malloc(sizeof(char) * (
		 strlen(DCCfileStart->filename) + strlen(DCCfileStart->host)
					+ strlen(DCCfileStart->nick) + 64));

		sprintf(buf, PROMPT "%s transfer for %s (%ld bytes left) to %s (%s)[%d]",
			DCCfileStart->offset ? "Resuming" : "Starting",
			DCCfileStart->filename,
			DCCfileStart->size - DCCfileStart->offset,
			DCCfileStart->nick,
			DCCfileStart->host, DCCfileStart->fd);
		berr_msg(buf);
		free(buf);
	}
	
	buf = (char *) malloc(sizeof(char) * (max_sendbuf + 2));

	if (!buf) {
		remove_file_offer(p->id);
		fl_show_alert(sula_NAME sula_VERSION ": Error",
		       "Try closing down some windows", strerror(errno), 1);
		close(DCCfileStart->fp);
		close(DCCfileStart->fd);
		remove_filetransfer(DCCfileStart->id);
		return;
	}
	#if FIRST_RD
	n = read(DCCfileStart->fp, buf, max_sendbuf);
	if (n <= 0) {
		remove_file_offer(p->id);
		if (n == 0 && n == DCCfileStart->size) {
			sprintf(buf, PROMPT "File transfer to %s (%s) completed. Empty file.",
				DCCfileStart->nick, DCCfileStart->host);
		}
		else
			sprintf(buf, "%s %s", DCCfileStart->filename, strerror(errno));
		berr_msg(buf);
		free(buf);
		close(DCCfileStart->fp);
		close(DCCfileStart->fd);
		remove_filetransfer(DCCfileStart->id);
		return;
	}
	w = writen(DCCfileStart->fd, buf, n);
	if (w != n) {
		if ((errno == EAGAIN/* || errno == EINTR*/) 
			&& lseek(DCCfileStart->fp,
			w - n, SEEK_CUR) != -1) {
			errno = 0;
			goto weiter;
		}
		remove_file_offer(p->id);
		sprintf(buf, "Write error to %s (%s): %s",
		   DCCfileStart->nick, DCCfileStart->host, strerror(errno));
		if (tmp_win)
			fl_addto_browser(tmp_win->chanwin->brow2, buf);
		else
			berr_msg(buf);
		free(buf);
		close(DCCfileStart->fd);
		close(DCCfileStart->fp);
		close(DCCfileStart->fd);
		remove_filetransfer(DCCfileStart->id);
		return;
	}
      weiter:
      #endif
	free(buf);
	gettimeofday(&DCCfileStart->lasttime, NULL);
	#if FIRST_RD
	DCCfileStart->read = w;
	#endif
	fl_add_io_callback(DCCfileStart->fd, FL_READ, dcc_sendfile_cb, (void *) 0);
	#if 1
	adcc=transfer_win(DCCfileStart, p->size,p->checksum,1);
	remove_file_offer(p->id);
	if(adcc){
	    adcc->form->size->u_ldata=DCCfileStart->read;/* read*/
	    fl_set_form_atclose(adcc->form->active_dcc_get,dont_close_win, 0);
	    fl_show_form(adcc->form->active_dcc_get,FL_PLACE_CENTER,
		FL_FULLBORDER, sula_NAME": DCC File Transfer");
	    fl_do_forms();
	}
	 else
	  perror("Error--close some windows");
	#else
	remove_file_offer(p->id);
	#endif
}

extern void remove_file_offer(int id)
{
/* remove a file offer */
	DCCFileS *p = filesendStart;
	DCCFileS *q;

	while (p != filesendEnd && p->id !=id)
		p = p->next;
	if (p == filesendEnd)
		return;
	fl_remove_io_callback(p->fd, FL_READ, offered_files_cb);
	close(p->fd);
	free(p->nick);
	free(p->filename);
	free(p->email);
	q = p->next;
	if (q == filesendEnd)
		filesendEnd = p;
	else
		*p = *q;
	free(q);
	return;
}
extern void offer(int argc, char **argv, Winstruct * win)
{
/* offer files to people. 
send nick file1 file2 ... or: send file1 file2 to nick
or: send -nick nick f1 f2 .. (if one filename=to) 
TODO:send -nicks  nick1 nick2 ..  -files file1 file2 file3
 */

	if (!win->server || win->server->fd < 0) {
		if (gflags & BEEP_ERR)
			fl_ringbell(0);
		fl_addto_browser(win->chanwin->brow2,
		     PROMPT "Cannot send file offer: no server connection");
		return;
	}
	if (!strncasecmp(argv[1], "-help", 2)) {
		show_std_help(uppercase(argv[0]), NULL, win->chanwin);
		return;
	}
	if (argc < 3) {
		if (gflags & VERBOSE_CLIENT)
			fl_addto_browser(win->chanwin->brow2,
					 PROMPT "Send what where?");
		return;
	}
	else {
		/* note: there's now no need to use a queue here. will be changed */
		typedef struct TFiles {
			char   *name;
			struct TFiles *link;
		} Files;
		char   *nick = NULL;
		Files  *front = NULL, *rear = NULL;
		glob_t  gl;
		char    start = 1;
		char    buffer[512];

		argv++;
		while (*argv) {
			Files  *p;

			if (!strncasecmp("-nick", *argv, 2) && *(argv + 1)) {
				nick = strdup(*++argv);
				argv++;
				continue;
			}
			if (!nick && !strcasecmp("to", *argv)) {
				if (*++argv)
					nick = strdup(*argv++);
				else {
					if (gflags & VERBOSE_CLIENT)
						fl_addto_browser(win->chanwin->brow2, PROMPT "Send files to ??");
					return;
				}
				continue;
			}
			p = (Files *) malloc(sizeof(Files));
			if (!p) {
				err_ret("%s (%d)", __FILE__, __LINE__);
				return;
			}
			if (!rear)
				front = p;
			else
				rear->link = p;
			p->name = strdup(*argv);
			rear = p;
			argv++;
		}
		if (!nick && rear) {	/* the first parameter shld be the nick */
			Files  *p = front;

			nick = strdup(front->name);
			if (front == rear)
				rear = NULL;
			else
				front = p->link;
			free(p->name);
			free(p);
		}
		if (!rear) {
			if (gflags & VERBOSE_CLIENT)
				fl_addto_browser(win->chanwin->brow2,
						 PROMPT "No files to send?");
			return;
		}
		gl.gl_pathc = 0;
		while (rear) {
			Files  *p = front;
			int     result;
			char   *file = front->name;

			if (*file == '~')
				file = (expand_tilde(file));
			if (!file) {
				sprintf(buffer, "%sCant expand %s (%s)", PROMPT,
					p->name, strerror(errno));
				fl_addto_browser(win->chanwin->brow2, buffer);
				goto remove;
			}
			if (start) {
				result = glob(file, GLOB_NOSORT, NULL, &gl);
				start = 0;

			}
			else
				result = glob(file, GLOB_APPEND | GLOB_NOSORT, NULL, &gl);
			if (result && errno) {
				char   *err = strerror(errno);

				/*buffer = (char *) malloc(sizeof(char) * (strlen(err) 
				 * + strlen(file) + strlen(PROMPT) + 4));
				 */
				sprintf(buffer, "%s%s: %s", PROMPT, file, err);
				fl_addto_browser(win->chanwin->brow2, buffer);
				if (result == GLOB_NOSPACE) {
					globfree(&gl);
					return;
				}
			}
			/*globfree(&gl); */
		      remove:
			if (front == rear)
				rear = NULL;
			else
				front = p->link;
			free(p->name);
			free(p);
		}
		if (gl.gl_pathc == 0) {
			fl_addto_browser(win->chanwin->brow2,
					 PROMPT "No matching files found.");
			return;
		}
		/*fprintf(stderr,"%s Send files to %s:\n",__FILE__, nick); */
		argv = gl.gl_pathv;

		while (*argv) {
			/*
			 * shldnt have put the filenames in a queue in the first place..
			 * now it is LIFO $&%§-? i'm not rewriting this function again.
			 */
			struct stat buf;

			if (stat(*argv, &buf) < 0) {	/*not lstat. the links! */
				sprintf(buffer, PROMPT "(%s %d) %s: stat error: %s", __FILE__, __LINE__,
					*argv, strerror(errno));
				fl_addto_browser(win->chanwin->brow2, buffer);

				argv++;
				continue;
			}
			if (!S_ISREG(buf.st_mode)) {
				sprintf(buffer, "%sCant send %s: Not a regular file.",
					PROMPT, *argv);
				fl_addto_browser(win->chanwin->brow2, buffer);
				argv++;
				continue;
			}
			if (access(*argv, R_OK) < 0) {
				sprintf(buffer, "%s No read access for %s: %s",
					PROMPT, *argv, strerror(errno));
				fl_addto_browser(win->chanwin->brow2, buffer);
				argv++;
				continue;
			}
			if (addto_sentfileoffer(nick, *argv, buf.st_size, win))
				break;
			argv++;
		}
		globfree(&gl);
	}
}

extern int addto_sentfileoffer(const char *nick, char *file, off_t size,
			       Winstruct * win)
{
	int     fd, ll;
	struct sockaddr_in here;
	static char first_run = 1, dynamic_slip = 1;
	DCCFileS *p;
	static int id = 0;	/* # of file reqs sent */
	char    buf[MAXLINE];
	char   *tmpfile;	/* the name that receipient will see */
	/*CheckSum cks;	*/	/*u_long */

	if (!nick || !file || !win)
		return -1;
	tmpfile = strrchr(file, '/');
	if (tmpfile)
		tmpfile++;
	else {
		tmpfile = strrchr(file, '~');
		if (!tmpfile)
			tmpfile = file;
	}
	if (!tmpfile)
		return 0;

	/*
	 * we want to determine our internet soocket addess only once. 
	 * on failure try later 
	 */
	if (first_run) {
		bzero((char *) &me, sizeof(me));
		if (gethostname(host, sizeof(host)) < 0) {
			err_ret("gethostname errur");
			return -1;
		}
		if ((me.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
			struct hostent *hp;

			hp = gethostbyname(host);
			if (hp == NULL) {
				herror(" gethostbyname error");
				return -1;
			}
			bcopy(hp->h_addr, (char *) &me.sin_addr, hp->h_length);
		}
		if(!dynamic_slip) first_run = !first_run;
	}
	if ((fd = tcp_create_server()) < 0)
		return errno;
	ll = sizeof(here);
	if (getsockname(fd, (struct sockaddr *) &here, &ll) < 0) {
		err_ret("%s %d getsockname errur", __FILE__, __LINE__);
		close(fd);
		return -1;
	}
	p = filesendStart;
	filesendStart = (DCCFileS *) malloc(sizeof(DCCFileS));
	if (filesendStart == NULL) {
		close(fd);
		err_ret("errror");
		return errno;
	}
	filesendStart->nick = strdup(nick);
	filesendStart->id = ++id;
	filesendStart->offset = 0;	/* for resume */
	filesendStart->filename = strdup(file);
	filesendStart->size = size;
	filesendStart->offset = 0;
	filesendStart->email = NULL;
	filesendStart->starttime = time(NULL);
	filesendStart->next = p;
	filesendStart->fd = fd;
	filesendStart->port = ntohs(here.sin_port);
	if (!checksum(filesendStart->filename, -1, &filesendStart->checksum))
		sprintf(buf, "PRIVMSG %s :%cDCC SEND %s %lu %hu %lu%c%c%lu %lu%c\n",
			filesendStart->nick,
			0x01, tmpfile, ntohl(me.sin_addr.s_addr),
		 ntohs(here.sin_port), filesendStart->size, 0x01,0x02,
			0L /* extension flag */ , filesendStart->checksum,0x02);
	else {
		perror(filesendStart->filename);
		sprintf(buf, "PRIVMSG %s :%cDCC SEND %s %lu %hu %lu%c\n", filesendStart->nick,
			0x01, tmpfile, ntohl(me.sin_addr.s_addr),
			filesendStart->port, filesendStart->size, 0x01);
	}
	fl_add_io_callback(filesendStart->fd, FL_READ, offered_files_cb, (void *) 0);
	sendto_server(win, buf);
	return 0;

}
