/* Copyright (C) 1999 Beau Kuiper

   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "ftpd.h"
#include "reply.h"

extern int list_write(SELECTER *sel, int fd, void *peerv);
extern int download_write(SELECTER *sel, int fd, void *peerv);
extern int upload_read(SELECTER *sel, int fd, void *peerv);

int datasock_connect(SELECTER *sel, int fd, void *peerv)
{
	FTPSTATE *peer = (FTPSTATE *)peerv;
	DATAPORT *d = peer->dport;
	int error = 0;
	int on, ret = FALSE;
	char *modstr, transtr[100];
	
	if (d->passive)
	{
		unsigned int ip;
		int temp = get_conn(fd, &ip);
		
		if (temp == -1)
		{
			reporterror(peer, "data sock", errno);
			closedatasocket(peer);
			return(2);
		}
		if ((ip != peer->remoteip) && (!peer->fxpallow))
		{
			ftp_write(peer, FALSE, 550, REPLY_PASSIVEBADHOST);
			close(temp);
			closedatasocket(peer);
			return(2);
		}
		else
		{
			d->socketfd = temp;
			select_addfd(sel, temp);
		}
		ret = 2; /* delete the listening socket */
	}
	else
	{
		int intsize = sizeof(int);
		getsockopt(d->socketfd, SOL_SOCKET, SO_ERROR, &error, &intsize);
		select_takewrite(sel, d->socketfd);
		if (error != 0)
		{
			reporterror(peer, "data socket", error);
			closedatasocket(peer);
			return(2);
		}
	}
	
	/* Now attempt to throttle data port to maximum throughput
	   and NOPUSH */
	/* I disabled logging of setsockopt failure becuase it isn't fatal
	   and seemed to annoy people too much */
#ifdef SO_SNDLOWAT		/* If we can set it */
	on = LOWATERMARK;
	setsockopt(d->socketfd, SOL_SOCKET, SO_SNDLOWAT, &on, sizeof(int));
#endif
#ifdef SO_RCVLOWAT
	on = LOWATERMARK;	
	setsockopt(d->socketfd, SOL_SOCKET, SO_RCVLOWAT, &on, sizeof(int));
#endif
	/* set the socket non-blocking */
	fcntl(d->socketfd, F_SETFL, O_NONBLOCK);
	
	/* this is here because we have to make sure the data port
	   is connected before we give credits */
	if ((d->trans_type == TRANS_SUPLOAD) && (peer->ratioinfo))
		ratio_uploadfile(peer->ratioinfo);
	
	if (d->binary)
		modstr = "BINARY";
	else
		modstr = "ASCII";

	transtr[0] = 0;
	if (d->tsize != -1)
		snprintf(transtr, 99, " (%s bytes)", offt_tostr(d->tsize));
			
	if (d->trans_type == TRANS_LIST)
		ftp_write(peer, FALSE, 150, REPLY_TRANSLISTSTART);
	else if ((d->pos == 0) && (d->startpos == 0))
		ftp_write(peer, FALSE, 150, REPLY_TRANSSTART(modstr, transtr));
	else 
		ftp_write(peer, FALSE, 150, REPLY_TRANSRESUME(modstr, d->pos | d->startpos, transtr));
		
	if (d->filefd != -1)
		select_addfd(sel, d->filefd);
	
	if (d->trans_type == TRANS_LIST)
		select_addwrite(sel, d->socketfd, list_write, peer);
	else if (d->trans_type == TRANS_DOWNLOAD)
		select_addwrite(sel, d->socketfd, download_write, peer);
	else
		select_addread(sel, d->socketfd, upload_read, peer);

	/* Set up a bandwith limiter if it is needed */
	if (peer->maxtranspd_up)
		d->upload_limiter = limiter_new(peer->maxtranspd_up);

	if (peer->maxtranspd_down)
		d->download_limiter = limiter_new(peer->maxtranspd_down);
	
	if (peer->maxtranspd && (!peer->maxtranspd_up || 
				 !peer->maxtranspd_down ))
	{
		LIMITER *a = limiter_new(peer->maxtranspd);
		
		if (d->download_limiter == NULL)
			d->download_limiter = a;
		if (d->upload_limiter == NULL)
			d->upload_limiter = a;
	}
		
	d->trans_type = TRANS_RUNNING;	
       	return(ret);
} 

int startdatasocket(FTPSTATE *peer, int filefd, int trans_type, off_t tsize)
{
	DATAPORT *d = mallocwrapper(sizeof(DATAPORT));

	/* if peer->remoteport is not set, use default port, RFC 959 */
	if (peer->remoteport == 0)
		peer->remoteport = peer->connport - 1;
	
	d->download_limiter = NULL;
	d->upload_limiter = NULL;
	d->filefd = filefd;
	d->socketfd = -1;
	d->transbytes = 0;
	if ((peer->binary) && (trans_type != TRANS_LIST))
	{
		d->pos = peer->restartpos;
		d->startpos = 0;
	}
	else
	{
		d->pos = 0;
		d->startpos = peer->restartpos;
	}
	d->binary = peer->binary;

	if ((trans_type == TRANS_UPLOAD) && (peer->restartpos == 0))
		d->trans_type = TRANS_SUPLOAD;
	else
		d->trans_type = trans_type;

	d->tsize = tsize;
	d->lhandle = NULL;
	d->passive = (peer->passiveport != 0);
	d->buffer = NULL;
	
	if (!d->passive)
	{
		if (peer->epsv_forced)
		{
			ftp_write(peer, FALSE, 500, REPLY_EPSVSET);
			if (filefd != -1)
				close(filefd);
			freewrapper(d);
			return(-1);
		}	
		file_becomeroot(peer);
		d->socketfd = conn_server_nonblocking(peer->dataip, peer->remoteport, peer->connport-1, peer->remotefd);
		file_becomeuser(peer);
	}
	else
		d->socketfd = peer->passiveport;

	d->buffer = string_new();	 
	peer->remoteport = 0;
	peer->dataip = peer->remoteip;
	peer->passiveport = FALSE;
	peer->dport = d;
	peer->restartpos = 0;

	/* can't use default port in epsv all mode */

	if (d->socketfd == -1)
	{	
		reporterror(peer, "data socket", errno);
		if (filefd != -1)
			close(filefd);
		closedatasocket(peer);
		return(-1);
	}
	else 
	{
		if (d->passive)
			select_addread(peer->sel, d->socketfd, datasock_connect, peer);
		else
		{
			select_addfd(peer->sel, d->socketfd);
			select_addwrite(peer->sel, d->socketfd, datasock_connect, peer);
		}
	}
	
	return(0);

}

void closedatasocket(FTPSTATE *peer)
{
	if (peer->dport->lhandle)
		freelisthandle(peer->dport->lhandle);
	freeifnotnull(peer->dport->buffer);
	if (peer->dport->download_limiter == peer->dport->upload_limiter)
	{
		freeifnotnull(peer->dport->download_limiter);
		peer->dport->upload_limiter = NULL;
	}
	else
	{
		freeifnotnull(peer->dport->download_limiter);
		freeifnotnull(peer->dport->upload_limiter);
	}
	freewrapper(peer->dport);
	peer->dport = NULL;
	shinfo_changeop("idle");
}

void abortdatasocket(FTPSTATE *peer)
{
	if (peer->dport)
	{
		if (peer->dport->trans_type == TRANS_RUNNING)
		{
			select_delfd(peer->sel, peer->dport->socketfd);
			if (peer->dport->filefd != -1)
				select_delfd(peer->sel, peer->dport->filefd);
			ftp_write(peer, FALSE, 426, REPLY_TRANSABORT(peer->dport->transbytes));
		}
		else
		{
			select_delfd(peer->sel, peer->dport->socketfd);
			ftp_write(peer, FALSE, 530, REPLY_TRANSSTILLBORN);
		}	
		closedatasocket(peer);
	}
	/* also disband any data-ports open, or prepared, since the
	   rfc says so */   
	
	peer->remoteport = 0;
	peer->dataip = peer->remoteip;
	if (peer->passiveport)
	{
		select_delfd(peer->sel, peer->passiveport);
		peer->passiveport = FALSE;
	}
}			
