/* ARISA - Common DCC Functionality
 * Copyright (C) 2003, 2004 Carl Ritson
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include "arisa.h"
#include "dcc.h"
#include "user.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>

/** dcc_safe_fn
 * 	Returns 1 if the filename contains no spaces, 0 if it does. 
 * 	It also modifies the file name passed in place to replacing harmful 
 * 	characters with '_'.
 */
int dcc_safe_fn(char *fn) {
	int i,has_space;
	for(i = 0, has_space = 0; fn[i] != '\0'; ++i) {
		if(isspace(fn[i]))
			has_space = 1;
		else {
			switch (fn[i]) {
				case '"':
				case '/':
				case '\\':
					fn[i] = '_';
					break;
				default:
					break;
			}
		}
	}
	return has_space;
}

/** dcc_send
 * 	Handles incomming DCC SEND commands from IRC, attempts to locate
 * 	a RECV interface and upload directory to use.  Rejects sends from
 * 	unauthorised hosts.
 * 	
 * 	If a send is accepted then the ip addr and port from the DCC SEND
 * 	message are embedded in the label of the pack_t structure in the
 * 	new created send type structure.  So pack->label is a pointer to
 * 	sizeof(struct sockaddr_in)+1, the first byte is '\0' so it acts
 * 	as a valid string.  The original file name is placed in pack->file.
 */
send_t *dcc_send(network_t *net, const char *host, const char *hostmask,
		const char *nick, const char *cmd) {
	interface_t *interface = NULL, **interfaces = NULL;
	dcc_aux_t *aux;
	access_t *access;
	send_t *send;
	char *error = NULL,**opt,buffer[512],netname[64];
	off_t size;
	int i,no_opt,no_found,no_interfaces;

	if(net == NULL || nick == NULL || host == NULL || cmd == NULL)
		return NULL;
	
	LOCK(net);
	xstrncpy(netname,net->name,sizeof(netname));
	UNLOCK(net);		
	
	xstrncpy(buffer,cmd,sizeof(buffer));
	no_opt = 6;
	opt = split_opt(buffer,buffer,&no_opt);
	if(opt == NULL)
		return NULL;
	if(no_opt != 6) {
		xfree(opt);
		return NULL;
	}
	size = (off_t) strtoull(opt[5],NULL,10);
	if(size <= 0) {
		xfree(opt);
		return NULL;
	}

	LOCK(global);
	interfaces = PTRARR_DUP(global->interfaces,
			global->no_interfaces,&no_interfaces);
	for(i = 0, no_found = 0; i < no_interfaces; ++i) {
		LOCK(interfaces[i]);
		if(interfaces[i]->deleted != 0 ||
				interfaces[i]->type != INTERFACE_RECV) {
			UNLOCK(interfaces[i]);
			interfaces[i] = NULL;
		} else {
			no_found++;
			UNLOCK(interfaces[i]);
		}
	}
	UNLOCK(global);
	if(no_found == 0)
		error = "No suitable RECV interface found";
	
	/* Final all those with access */
	for(i = 0; i < no_interfaces && no_found > 0; ++i) {
		if(interfaces[i] == NULL)
			continue;
		LOCK(interfaces[i]);
		if(interfaces[i]->deleted == 0) {
			access = interfaces[i]->recv.access;
		} else
			access = NULL;
		UNLOCK(interfaces[i]);
		
		if(access != NULL) {
			if(!access_check(access,net,host,hostmask,nick)) {
				interfaces[i] = NULL;
			} 
		} else
			interfaces[i] = NULL;
		
		if(interfaces[i] == NULL)
			no_found--;
	}
	if(no_found == 0)
		error = "Your host is not permitted to upload";
	
	for(i = 0; i < no_interfaces && no_found > 0; ++i) {
		if(interfaces[i] == NULL)
			continue;
		
		error = NULL;
		
		LOCK(interfaces[i]);
		if(interfaces[i]->recv.no_uploads >=
				interfaces[i]->recv.max_no_uploads) {
			error = "No upload slots available";
		} else if(interfaces[i]->recv.max_size != 0 &&
				size > interfaces[i]->recv.max_size) {
			error = "File exceeds maximum allowed size";
		} else {
			interface = interfaces[i];
		}
		
		if(error != NULL) {
			UNLOCK(interfaces[i]);
			//interfaces[i] = NULL;
			no_found--;
		} else {
			/* interface is still locked */
			break;
		}
	}
	
	if(interfaces != NULL)
		xfree(interfaces);
	
	if(interface == NULL) {
		xfree(opt);
		
		snprintf(buffer,sizeof(buffer),
			"Upload denied. Error: %s. Host: %s. File size: %lld.",
			error,host,size);
		
		irc_send_notice(net,nick,buffer);
		LOGP(L_XDC,"DCC SEND from [%s:%s] (%s), Rejected: %s.",
				netname,nick,host,error);
		return NULL;
	} 
	
	send 			= alloc_send();
	send->state		= STATE_QUEUED;
	send->started		= xtime();
	send->pack 		= alloc_pack();
	send->network		= net;
	send->nick		= xstrdup(nick);
	send->host 		= xstrdup(host);
	send->pack->file 	= xstrdup(opt[2]);
	send->pack->size 	= size;
	send->aux		= xalloc(sizeof(dcc_aux_t));
	aux			= (dcc_aux_t *)send->aux;
	aux->reverse		= 0; // FIXME
	aux->addr.s_addr	= htonl(strtoul(opt[3],NULL,10));
	aux->port		= (unsigned short)strtoul(opt[4],NULL,10);
	
	xfree(opt);
	
	PTRARR_ADD(&interface->recv.uploads,&interface->recv.no_uploads,send);
	LOGP(L_XDC,
		"DCC SEND from [%s:%s] (%s) Accepted on Interface \"%s\".",
		netname,nick,host,interface->name);
	UNLOCK(interface);
	
	return send;
}

/** dcc_resume
 * 	Handles DCC RESUME requests from IRC networks, it attempts to find
 * 	a matching send for the request.  If a matching send is found then
 * 	it is marked STATE_RESUME and the pos field set to the position.
 * 	The interface thread will then send a DCC ACCEPT when it has validated
 * 	the resume.
 *
 * 	Note:	Interfaces initialise the bytes_sent field to the listening 
 * 		port number to allow matching against it.  This is a documented
 * 		field reuse.
 */
void dcc_resume(network_t *net, const char *host,
		const char *nick, const char *cmd) {
	dcc_aux_t *aux;
	char buffer[512],file[512];
	char **opt;
	pool_t *p;
	off_t pos;
	unsigned short port;
	int i,j,k,found,no_opt;
	
	//D("%s,%s,%d",host,nick,cmd);
	
	xstrncpy(buffer,cmd,sizeof(buffer));
	no_opt = 5;
	opt = split_opt(buffer,buffer,&no_opt);
	if(opt == NULL)
		return;
	if(no_opt != 5) {
		//D("opt err: %d",no_opt);
		xfree(opt);
		return;
	}
	pos = strtoull(opt[4],NULL,10);
	if(pos < 0) {
		//D("pos err");
		xfree(opt);
		return;
	}
	port = (unsigned short)strtoul(opt[3],NULL,10);
	
	//D("search");
	LOCK(global);
	for(k = 0, found = 0; k < global->no_interfaces && !found; ++k) {
		LOCK(global->interfaces[k]);
		if(global->interfaces[k]->type != INTERFACE_SEND ||
			global->interfaces[k]->deleted != 0) {
			UNLOCK(global->interfaces[k]);
			continue;
		}
		for(i = 0; i < global->interfaces[k]->send.no_pools && !found; ++i) {
			p = global->interfaces[k]->send.pools[i]; 
			LOCK(p);
			if(p->deleted == 0) {
				for(j = 0; j < p->no_csends && !found; ++j) {
					LOCK(p->csends[j]);
					aux = (dcc_aux_t *)p->csends[j]->aux;
					if(p->csends[j]->state == STATE_LISTENING && aux != NULL) {
						if(p->csends[j]->network == net &&
							irc_strcasecmp(p->csends[j]->nick,nick) == 0 &&
							aux->port == port) {
							RLOCK(p->csends[j]->pack);
							pathtofn(p->csends[j]->pack->file,file,sizeof(file));
							dcc_safe_fn(file);
							/* don't bother checking the file because mIRC doesn't use it */
							if(pos < p->csends[j]->pack->size) {
								p->csends[j]->state = STATE_RESUME;
								p->csends[j]->pos = pos;
								found = 1;
							}
							RUNLOCK(p->csends[j]->pack);
						}
					}
					UNLOCK(p->csends[j]);
				}
			}
			UNLOCK(p);
		}
		UNLOCK(global->interfaces[k]);
	}
	UNLOCK(global);
	if(found) {
		char msg[512];
		snprintf(msg,sizeof(msg),
			strchr(file,' ') != NULL ?
			"DCC ACCEPT \"%s\" %u %lld" :
			"DCC ACCEPT %s %u %lld",
			file,port,pos);
		irc_send_ctcp_msg(net,nick,msg);
	}
	LOCK(net);
	LOGP(L_XDC,"%sDCC RESUME from [%s:%s] for [\"%s\",%lld]",
			found == 0 ? "Unknown " : "",
			net->name,nick,
			found == 0 ? opt[2] : file,pos);
	UNLOCK(net);
	xfree(opt);
}

/** dcc_accept
 * 	Handles DCC ACCEPT messages from IRC, it locates the approriate upload
 * 	in a RECV interface and marks it as STATE_RESUMED.
 */
void dcc_accept(network_t *net, const char *host,
		const char *nick, const char *cmd) {
	dcc_aux_t *aux;
	long port;
	char buffer[512];
	char **opt;
	send_t *u;
	off_t pos;
	int i,j,found,no_opt;
	
	xstrncpy(buffer,cmd,sizeof(buffer));
	no_opt = 5;
	opt = split_opt(buffer,buffer,&no_opt);
	if(opt == NULL)
		return;
	if(no_opt != 5) {
		xfree(opt);
		return;
	}
	
	port = strtol(opt[3],NULL,10);
	pos = strtoull(opt[4],NULL,10);

	LOCK(global);
	for(i = 0, found = 0; i < global->no_interfaces && !found; ++i) {
		LOCK(global->interfaces[i]);
		if(global->interfaces[i]->type != INTERFACE_RECV || 
			global->interfaces[i]->deleted != 0) {
			UNLOCK(global->interfaces[i]);
			continue;
		}
		for(j = 0; j < global->interfaces[i]->recv.no_uploads 
							&& !found; ++j) {
			u = global->interfaces[i]->recv.uploads[j];
			LOCK(u);
			aux = (dcc_aux_t *)u->aux;
			if(u->state == STATE_RESUME && aux != NULL) {
				if(net == u->network && pos == u->pos &&
						aux->port == port &&
						irc_strcasecmp(u->nick,nick) == 0) {
					RLOCK(u->pack);
					if(strcmp(u->pack->file,opt[2]) == 0) {
						u->state = STATE_RESUMED;
						found = 1;
					}
					RUNLOCK(u->pack);
				}
			}
			UNLOCK(u);
		}
		UNLOCK(global->interfaces[i]);
	}
	UNLOCK(global);

	xfree(opt);
	
	LOCK(net);
	LOGP(L_XDC,"%sDCC ACCEPT from [%s:%s]",
			(found == 0 ? "" : "Unknown "),net->name,nick);
	UNLOCK(net);
}

void dcc_recv(network_t *net, const char *host,
		const char *nick, const char *cmd) {
	dcc_aux_t *aux;
	char buffer[512];
	
}

/** dcc_cmd
 * 	Multiplexs DCC commands to the dcc_send, dcc_resume and dcc_accept
 * 	functions.
 */
void dcc_cmd(network_t *net, const char *host, const char *hostmask,
		const char *nick, const char *cmd) {
	//D("%s,%s,%s",host,nick,cmd);
	if(strncasecmp(cmd,"DCC SEND",8) == 0)
		dcc_send(net,host,hostmask,nick,cmd);
	else if(strncasecmp(cmd,"DCC RESUME",10) == 0)
		dcc_resume(net,host,nick,cmd);
	else if(strncasecmp(cmd,"DCC ACCEPT",10) == 0)
		dcc_accept(net,host,nick,cmd);
	else if(strncasecmp(cmd,"DCC RECV",8) == 0)
		dcc_recv(net,host,nick,cmd);
}

void dcc_send_msg(struct in_addr *adv, 
		send_t *send, unsigned short port) {
	char buffer[512], file[512], nick[64], ip[32] = "$IIP";
	dcc_aux_t *aux = xalloc(sizeof(dcc_aux_t));
	network_t *net;
	
	if(adv != NULL)
		snprintf(ip,sizeof(ip),"%lu",
			(unsigned long) ntohl(adv->s_addr));

	aux->reverse = 0;
	aux->port = port;
	
	LOCK(send);
	send->state = STATE_LISTENING;
	send->aux = aux;
	RLOCK(send->pack);
	net = send->network;
	xstrncpy(nick,send->nick,sizeof(nick));
	pathtofn(send->pack->file,file,sizeof(file));
	
	snprintf(buffer,sizeof(buffer),
		dcc_safe_fn(file) != 0 ?
		"DCC SEND \"%s\" %s %u %lld" : "DCC SEND %s %s %u %lld",
		file,ip,port,send->pack->size);
	
	RUNLOCK(send->pack);
	UNLOCK(send);
	
	LOCK(net);
	if(adv != NULL)
		inet_ntop(AF_INET,adv,ip,sizeof(ip));
	LOGTP(L_INF,"Port (%s:%u) offered to [%s:%s] for send of {\"%s\"}.",
			adv != NULL ? ip : "AUTO",port,
			net->name,nick,file);
	UNLOCK(net);
	
	D("%s",buffer);
	
	irc_send_ctcp_msg(net,nick,buffer);
}

void dcc_rsend_accept_msg(struct in_addr *adv,
		send_t *send, unsigned short port) {
	char buffer[512], file[512], nick[64], ip[32] = "$IIP";
	network_t *net;
	
	if(adv != NULL)
		snprintf(ip,sizeof(ip),"%lu",
			(unsigned long) ntohl(adv->s_addr));

	LOCK(send);
	send->state = STATE_LISTENING;
	RLOCK(send->pack);
	net = send->network;
	xstrncpy(nick,send->nick,sizeof(nick));
	xstrncpy(file,send->pack->file,sizeof(file));

	snprintf(buffer,sizeof(buffer),
			strchr(send->pack->file,' ') != NULL ?
			"DCC RECV \"%s\" %s %u %lld" :
			"DCC RECV %s %s %u %lld",
			send->pack->file,ip,port,send->pos);
	
	RUNLOCK(send->pack);
	UNLOCK(send);

	LOCK(net);
	if(adv != NULL)
		inet_ntop(AF_INET,adv,ip,sizeof(ip));
	LOGTP(L_INF,"Port (%s:%u) offered to [%s:%s] for received of {\"%s\"}.",
			adv != NULL ? ip : "AUTO",port,
			net->name,nick,file);

	irc_send_ctcp_msg(net,nick,buffer);
}

void dcc_chat_msg(struct in_addr *adv,
		chat_t *chat, unsigned short port) {
	char buffer[512], nick[64], ip[32] = "$IIP";
	network_t *net;

	if(adv != NULL)
		snprintf(ip,sizeof(ip),"%lu",
			(unsigned long) ntohl(adv->s_addr));
	
	LOCK(chat);
	chat->state = STATE_LISTENING;
	net = chat->network;
	xstrncpy(nick,chat->nick,sizeof(nick));
	snprintf(buffer,sizeof(buffer),"DCC CHAT CHAT %s %u",ip,port);
	UNLOCK(chat);
	
	LOCK(net);
	if(adv != NULL)
		inet_ntop(AF_INET,adv,ip,sizeof(ip));
	LOGTP(L_INF,"Port (%s:%u) offered to [%s:%s] for chat.",
			adv != NULL ? ip : "AUTO",
			port,net->name,nick);
	UNLOCK(net);
	
	irc_send_ctcp_msg(net,nick,buffer);
}

void dcc_rsend_msg(send_t *send) {
	char buffer[512], file[512], nick[64];
	network_t *net;

	LOCK(send);
	RLOCK(send->pack);
	net = send->network;
	xstrncpy(nick,send->nick,sizeof(nick));
	pathtofn(send->pack->file,file,sizeof(file));
	
	snprintf(buffer,sizeof(buffer),
			dcc_safe_fn(file) != 0 ?
			"DCC RSEND \"%s\" %lld" : 
			"DCC RSEND %s %lld",
			file,send->pack->size);
	
	RUNLOCK(send->pack);
	UNLOCK(send);

	LOCK(net);
	LOGTP(L_INF,"Port requested from [%s:%s] for send of {\"%s\"}.",
			net->name,nick,file);
	UNLOCK(net);
	
	irc_send_ctcp_msg(net,nick,buffer);
}

void dcc_rchat_msg(chat_t *chat) {
	char buffer[512], nick[64];
	network_t *net;

	LOCK(chat);
	net = chat->network;
	xstrncpy(nick,chat->nick,sizeof(nick));
	snprintf(buffer,sizeof(buffer),"DCC RCHAT CHAT");
	UNLOCK(chat);

	LOCK(net);
	LOGTP(L_INF,"Port requested from [%s:%s] for chat.",
			net->name,nick);
	UNLOCK(net);

	irc_send_ctcp_msg(net,nick,buffer);
}

void dcc_recv_msg(send_t *send) {
	char buffer[512], file[512], nick[64];
	network_t *net;
	dcc_aux_t *aux;
	off_t pos;

	LOCK(send);
	if(send->pos == 0) {
		UNLOCK(send);
		return;
	}
	send->state = STATE_RESUME;
	RLOCK(send->pack);
	aux = (dcc_aux_t *)send->aux;
	net = send->network;
	xstrncpy(nick,send->nick,sizeof(nick));
	xstrncpy(file,send->pack->file,sizeof(file));
	pos = send->pos;
	
	snprintf(buffer,sizeof(buffer),
			strchr(send->pack->file,' ') != NULL ?
			"DCC RESUME \"%s\" %u %lld" :
			"DCC RESUME %s %u %lld",
			send->pack->file,aux->port,send->pos);
	
	RUNLOCK(send->pack);
	UNLOCK(send);

	LOCK(net);
	LOGTP(L_INF,"Resume requested from [%s:%s] for {\"%s\",%lld}.",
			net->name,nick,file,pos);
	UNLOCK(net);
	
	irc_send_ctcp_msg(net,nick,buffer);
}
