/* FTP Data Functions
   $Revision: 1.3 $
   $Date: 2002/06/24 04:04:32 $
*/

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include "ftp.h"
#include "quftp.h"

int ftp_open_data_pasv(struct ftpconnection *connection, char *address, int port) {
	int response;
	struct sockaddr_in dataaddr;
	char *buffer = NULL, *line, *temp, *temp2;
	int length;
	if (!ftp_check_ready(connection, 1)) return 0;
	if (!address) {
		ftp_sendline(connection, "PASV\r\n");
		line = (char *)malloc(MAX_LINE_SIZE);
		response = ftp_getrc(connection, line, MAX_LINE_SIZE, 0);
		switch (response) {
			case 227 : temp = strstr(line, "(");
				   temp++;
				   temp2 = strstr(temp, ")");
				   length = temp2-temp;
				   buffer = (char *)malloc(length + 1);
				   memcpy(buffer, temp, length);
				   buffer[length] = 0;
				   address = (char *)malloc(20);
				   port = ftp_parse_pasv(buffer, address);
				   free(line);
				   free(buffer);
				   break;
			case 421 : log(LOG_ALERT, "Service unavailable\n");
				   free(line);
				   ftp_disconnect(connection);
				   return 0;
				   break;
			case 500 : log(LOG_ALERT, "Server doesn't understand PASV\n");
				   free(line);
				   return 0;
				   break;
			case 501 : log(LOG_ALERT, "Server doesn't understand PASV parameters\n");
				   free(line);
				   return 0;
				   break;
			case 502 : log(LOG_ALERT, "Server doesn't understand PASV\n");
				   free(line);
				   return 0;
				   break;
			case 530 : log(LOG_ALERT, "Not logged in\n");
				   free(line);
				   return 0;
				   break;
			default  : log(LOG_ALERT, "Unknown response to PASV: %d\n", response);
				   free(line);
				   return 0;
				   break;
		}
	}
	if ((connection->datafd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(0);
	}
	log(LOG_MESSAGE, "Opening a data connection to %s:%d",
			address, port);
	memset(&dataaddr, 0, sizeof(dataaddr));
	dataaddr.sin_family = AF_INET;
	dataaddr.sin_port = htons(port);
	if (inet_aton(address, &dataaddr.sin_addr) <= 0) {
		perror("inet_aton");
		exit(0);
	}
	if (connect(connection->datafd,(SOCKADDR *)&dataaddr, sizeof(dataaddr)) < 0){
		log(LOG_ALERT, "Can't open a data connection to %s\n", address);
		return -1;
	}
	connection->status = STATUS_TRANSFER;
	return 0;
}

int ftp_close_data(struct ftpconnection *connection) {
	if (connection->dataconfd) close(connection->dataconfd);
	if (connection->datafd) close(connection->datafd);
	connection->datafd = 0;
	connection->dataconfd = 0;
	connection->status = STATUS_IDLE;
	log(LOG_MESSAGE, "Data connection closed\n");
	return 0;
}

int ftp_read_data(struct ftpconnection *connection, char *buffer, int max_size) {
	int n;
	if (connection->status != STATUS_TRANSFER) return -1;
	memset(buffer, 0, max_size);
	n = read(connection->datafd, buffer, max_size);
	if (n < 0) perror("read");
	return n;
}

int ftp_read_data_line(struct ftpconnection *connection, char *buffer, int max_size) {
	int n;
	char *temp;
	if (connection->status != STATUS_TRANSFER) return -1;
	memset(buffer, 0, max_size);
	temp = buffer;
	while ((n = read(connection->datafd, temp, 1)) > 0) {
		if (temp[0] == '\r') continue;
		if (temp[0] == '\n') break;
		if (temp == (buffer + max_size)) break;
		temp++;
	}
	temp[0] = 0;
	if (n < 0) {
		perror("read");
		exit(0);
	}
	if (buffer[0] == 0) return 0;
	return n;
}

int ftp_write_data(struct ftpconnection *connection, char *buffer, int size) {
	int n;
	if (connection->status != STATUS_TRANSFER) return -1;
	if ((n = write(connection->datafd, buffer, size)) < 0) {
		perror("write");
		exit(0);
	}
	return n;
}

int ftp_open_data(struct ftpconnection *connection) {
	struct sockaddr_in servaddr;
	struct in_addr sin;
	char *line;
	int h1, h2, h3, h4, p1, p2, port;
	int response;
	SOCKLEN len;
	if ((connection->dataconfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		exit(0);
	}
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = 0;
	if (bind(connection->dataconfd, (SOCKADDR *)&servaddr, sizeof(servaddr)) < 0) {
		perror("bind");
		exit(0);
	}
	if (listen(connection->dataconfd, 1) < 0) {
		perror("listen");
		exit(0);
	}
	len = 128;
	if (getsockname(connection->dataconfd, (SOCKADDR *)&servaddr, &len) < 0) {
		perror("getsockname");
		exit(0);
	}
	line = (char *)malloc(MAX_LINE_SIZE);
	port = servaddr.sin_port;
	sin.s_addr = connection->localip;
	sprintf(line, "%s.%d\r\n", inet_ntoa(sin), port);
	sscanf(line, "%d.%d.%d.%d.%d", &h1, &h2, &h3, &h4, &port);
	p1 = (port / 256);
	p2 = port - (256 * p1);
	sprintf(line, "PORT %d,%d,%d,%d,%d,%d\r\n", h1, h2, h3, h4, p2, p1);
	connection->status = STATUS_WAITING;
	ftp_sendline(connection, line);
	free(line);
	response = ftp_getrc(connection, NULL, 0, 0);
	switch (response) {
		case 200 : return 0;
		case 421 : log(LOG_ALERT, "Service unavailable\n");
			   ftp_disconnect(connection);
			   return response;
			   break;
		case 500 : log(LOG_ALERT, "Server doesn't understand PORT\n");
			   return response;
			   break;
		case 501 : log(LOG_ALERT, "Server doesn't understand PORT parameters\n");
			   return response;
			   break;
		case 502 : log(LOG_ALERT, "Server doesn't understand PORT\n");
			   return response;
			   break;
		case 530 : log(LOG_ALERT, "Not logged in\n");
			   return response;
			   break;
		default  : log(LOG_ALERT, "Unknown response to PORT: %d\n", response);
			   return response;
			   break;
	}
}

int ftp_wait_data(struct ftpconnection *connection) {
	struct sockaddr_in servaddr;
	int len = 128;
	if (connection->status != STATUS_WAITING) {
		return 0;
	}
	connection->datafd = accept(connection->dataconfd, (SOCKADDR *)&servaddr, &len);
	log(LOG_MESSAGE, "Data connection opened to %s",
		inet_ntoa(servaddr.sin_addr));
	connection->status = STATUS_TRANSFER;
	return 1;
}



syntax highlighted by Code2HTML, v. 0.9.1