/*
**  Copyright 2000-2004 University of Illinois Board of Trustees
**  Copyright 2000-2004 Mark D. Roth
**  All rights reserved.
**
**  ftpdata.c - FTP data connection code
**
**  Mark D. Roth <roth@feep.net>
*/

#include <internal.h>

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>

#ifdef STDC_HEADERS
# include <string.h>
# include <stdarg.h>
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif


/* initialize an ftp-data connection */
int
_ftp_data_init(FTP *ftp)
{
	struct sockaddr_in addr;
	socklen_t addrlen;
	int code;
	char buf[FTPBUFSIZE];
	char *cp;
	short ta[6];
	unsigned long tmpaddr;
	unsigned short port;

	/*
	** if the data connection handle is already initialized,
	** that means there must already be an established data
	** connection, so return EAGAIN
	*/
	if (ftp->ftp_data != NULL
	    || ftp->ftp_data_listen != NULL)
	{
		errno = EAGAIN;
		return -1;
	}

	if (BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_PASV))
	{
		/* PASV mode */

		if (_ftp_send_command(ftp, "PASV") == -1
		    || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
			return -1;
		if (code != 227)
		{
			if (code == 421)
				errno = ECONNRESET;
			else
				errno = EINVAL;
			return -1;
		}

		cp = strpbrk(buf, "0123456789");
		if (cp == NULL)
		{
			errno = EINVAL;
			return -1;
		}
		if (sscanf(cp, "%hd,%hd,%hd,%hd,%hd,%hd",
			   &ta[0], &ta[1], &ta[2], &ta[3],
			   &ta[4], &ta[5]) != 6)
		{
			errno = EINVAL;
			return -1;
		}

		snprintf(buf, sizeof(buf), "%d.%d.%d.%d:%d",
			 ta[0], ta[1], ta[2], ta[3],
			 ((ta[4] * 256) + ta[5]));

		if (fget_netio_new(&(ftp->ftp_data), FTPBUFSIZE, buf,
				   ftp->ftp_io_timeout, 0, 0) == -1)
			return -1;
	}
	else
	{
		/* PORT mode */

		/*
		** get local IP address from control connection,
		** since data connection should listen on the same
		** network interface
		*/
		addrlen = sizeof(addr);
		if (fget_netio_local_addr(ftp->ftp_control,
					  (struct sockaddr *)&addr,
					  &addrlen,
					  NULL, 0) == -1)
			return -1;

		/*
		** bind to port and start listening for connection
		** note: we don't specify a port, since we're
		** letting the kernel assign us a random local port
		*/
		if (fget_netio_listen(&(ftp->ftp_data_listen),
				      inet_ntoa(addr.sin_addr), 1) == -1)
			return -1;

		/*
		** find out what port the kernel assigned us
		*/
		addrlen = sizeof(addr);
		if (fget_netio_local_addr(ftp->ftp_data_listen,
					  (struct sockaddr *)&addr,
					  &addrlen,
					  NULL, 0) == -1)
			goto data_init_error;

		/*
		** send PORT command
		*/
		tmpaddr = addr.sin_addr.s_addr;
		port = addr.sin_port;
		if (_ftp_send_command(ftp, "PORT %d,%d,%d,%d,%d,%d",
				      ((unsigned char *)&tmpaddr)[0],
				      ((unsigned char *)&tmpaddr)[1],
				      ((unsigned char *)&tmpaddr)[2],
				      ((unsigned char *)&tmpaddr)[3],
				      ((unsigned char *)&port)[0],
				      ((unsigned char *)&port)[1]) == -1
		    || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
			goto data_init_error;
		if (code != 200)
		{
			if (code == 421)
				errno = ECONNRESET;
			else
				errno = EINVAL;
			goto data_init_error;
		}
	}

	return 0;

  data_init_error:
	if (ftp->ftp_data_listen != NULL)
	{
		fget_netio_free(ftp->ftp_data_listen);
		ftp->ftp_data_listen = NULL;
	}
	return -1;
}


/* establish an ftp-data connection */
int
_vftp_data_start(FTP *ftp, char *fmt, va_list args)
{
	char buf[FTPBUFSIZE];
	int code;

	/*
	** Note: We use fget_netio_vwrite_line() directly here, since
	** the data connection is already open if we're in PASV mode,
	** and _vftp_send_command() won't work while the data connection
	** is open.
	*/
	if (fget_netio_vwrite_line(ftp->ftp_control, ftp->ftp_io_timeout,
				   fmt, args) == -1
	    || _ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
		goto data_start_error;

	if (code != 150
	    && code != 125)
	{
		if (code == 421)
			errno = ECONNRESET;
		else if (code == 450)
			errno = ETXTBSY;
		else if (code == 451)
			errno = EAGAIN;
		else if (code == 452)
			errno = ENOSPC;
		else if (code == 550 || code == 530)
			errno = EACCES;
		else
			errno = EINVAL;
		goto data_start_error;
	}

	if (! BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_PASV))
	{
		if (fget_netio_accept(ftp->ftp_data_listen,
				      FTPBUFSIZE,
				      ftp->ftp_io_timeout,
				      &(ftp->ftp_data)) == -1)
			goto data_start_error;

		fget_netio_free(ftp->ftp_data_listen);
		ftp->ftp_data_listen = NULL;
	}

	return 0;

  data_start_error:
	if (ftp->ftp_data_listen != NULL)
	{
		fget_netio_free(ftp->ftp_data_listen);
		ftp->ftp_data_listen = NULL;
	}
	if (ftp->ftp_data != NULL)
	{
		fget_netio_free(ftp->ftp_data);
		ftp->ftp_data = NULL;
	}
	return -1;
}


/* stdarg front-end for _vftp_data_start() */
int
_ftp_data_start(FTP *ftp, char *fmt, ...)
{
	va_list args;
	int retval;

	va_start(args, fmt);
	retval = _vftp_data_start(ftp, fmt, args);
	va_end(args);

	return retval;
}


/* establish an ftp-data connection */
int
_vftp_data_connect(FTP *ftp, char *fmt, va_list args)
{
	if (_ftp_data_init(ftp) == -1)
		return -1;

	if (_vftp_data_start(ftp, fmt, args) == -1)
		return -1;

	return 0;
}


/* stdarg front-end for _vftp_data_connect() */
int
_ftp_data_connect(FTP *ftp, char *fmt, ...)
{
	va_list args;
	int retval;

	va_start(args, fmt);
	retval = _vftp_data_connect(ftp, fmt, args);
	va_end(args);

	return retval;
}


/* TELNET protocol escape sequences - used by _ftp_data_abort() */
static const unsigned char telnet_ip[] = { IAC, IP };
static const unsigned char telnet_synch[] = { IAC, SYNCH };

/* close data connection */
static int
_ftp_data_abort(FTP *ftp)
{
	int code;
	char buf[FTPBUFSIZE];

#ifdef DEBUG
	printf("==> _ftp_data_abort()\n");
#endif

	/*
	** send ABOR command
	*/
	if (fget_netio_write(ftp->ftp_control, ftp->ftp_io_timeout,
			     (char *)telnet_ip, sizeof(telnet_ip), 0) == -1
	    || fget_netio_write(ftp->ftp_control, ftp->ftp_io_timeout,
	    			(char *)telnet_synch, sizeof(telnet_synch),
				NETIO_WRITE_OOB) == -1
	    || fget_netio_write_line(ftp->ftp_control, ftp->ftp_io_timeout,
	    			     "ABOR") == -1)
		return -1;

	/* close ftp-data socket */
	if (ftp->ftp_data_listen != NULL)
	{
		fget_netio_free(ftp->ftp_data_listen);
		ftp->ftp_data_listen = NULL;
	}
	if (ftp->ftp_data != NULL)
	{
		fget_netio_free(ftp->ftp_data);
		ftp->ftp_data = NULL;
	}

	/*
	** CASE 1:
	** if we were in the middle of a service request,
	** the server will give the following responses:
	**
	** 426	service request terminated abnormally
	** 226	ABOR request was successful
	**
	** CASE 2:
	** if the server thinks that the service request already
	** finished (e.g., it already sent us all the data, but we
	** haven't read it all yet), then it will give the following
	** responses:
	**
	** 226	normal termination of the service request
	** 225	ABOR request successful
	*/

	if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
		return -1;
	if (code != 426 && code != 226)
	{
		if (code == 421)
			errno = ECONNRESET;
		else
			errno = EINVAL;
		return -1;
	}

	if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
		return -1;
	if (code != 226 && code != 225)
	{
		if (code == 421)
			errno = ECONNRESET;
		else
			errno = EINVAL;
		return -1;
	}

	return 0;
}


/* close data connection */
int
_ftp_data_close(FTP *ftp)
{
	int code, eof, retval = 0;
	char buf[FTPBUFSIZE];

#ifdef DEBUG
	printf("==> _ftp_data_close()\n");
#endif

	eof = fget_netio_eof(ftp->ftp_data);

	if (! eof
	    && BIT_ISSET(ftp->ftp_flags, FTP_FLAG_USE_ABOR))
		return _ftp_data_abort(ftp);

	/*
	** if FTP_OPT_USE_ABOR is not set, we'll try to abort by
	** simply closing the data connection and waiting for the
	** server to send an error response.
	**
	** NOTE: there is some dissention about whether this will work
	** properly with all servers.  Brandon Allbery notes:
	**
	** 20:47 <allbery_b> coll:  426?  always?  I've seen them
	**       (a) return random shit (b) hang (c) appear to recover
	**       but fail on subsequent commands
	*/
	if (ftp->ftp_data_listen != NULL)
	{
		fget_netio_free(ftp->ftp_data_listen);
		ftp->ftp_data_listen = NULL;
	}
	if (ftp->ftp_data != NULL)
	{
		retval = fget_netio_free(ftp->ftp_data);
		ftp->ftp_data = NULL;
	}

	/*
	** CASE 1:
	** the server thinks that the service request already finished
	** (whether because it really did, or just because it already sent
	** us all the data, even if we haven't actually read it all yet),
	** so it will respond with:
	**
	** 226	normal termination of the service request
	**
	** CASE 2:
	** we were in the middle of a service request, so the server
	** responds with:
	**
	** 426	service request terminated abnormally
	*/
	if (_ftp_get_response(ftp, &code, buf, sizeof(buf)) == -1)
		return -1;

	if (code == 226)
		return retval;

	if (! eof && code == 426)
		return retval;

	if (code == 421)
		errno = ECONNRESET;
	else
		errno = EINVAL;
	return -1;
}


/* read from data connection */
ssize_t
_ftp_data_read(FTP *ftp, char *buf, size_t bufsize)
{
	if (ftp->ftp_data == NULL)
	{
		errno = EINVAL;
		return -1;
	}

	return fget_netio_read(ftp->ftp_data,
			       ftp->ftp_io_timeout,
			       buf, bufsize);
}


/* write to data connection */
ssize_t
_ftp_data_write(FTP *ftp, char *buf, size_t bufsize)
{
	if (ftp->ftp_data == NULL)
	{
		errno = EINVAL;
		return -1;
	}

	return fget_netio_write(ftp->ftp_data,
				ftp->ftp_io_timeout,
				buf, bufsize, 0);
}




syntax highlighted by Code2HTML, v. 0.9.1