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

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <errno.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 <sys/time.h>
#include "ftp.h"
#include "text.h"
#include "rc.h"
#include "quftp.h"

int ftp_get_file(struct ftpconnection *connection, struct qitem *item) {
	int fd, response, n, counter;
	unsigned long resume = 0;
	char line[MAX_LINE_SIZE], *buffer;
	struct stat statbuf;
	struct timeval tv;
	int updateinterval = 1;
	int updatetime = 1, bytesthissecond = 0;
	double starttime = 0.0, timelength, speed = 0.0;
	if (!item) return 0;
	log(LOG_INFO, "Retrieving \"%s/%s\" from %s\n", connection->remotedir, item->remotefilename,
			connection->hostname);
	if (!ftp_check_ready(connection, 1)) return -1;
	ftp_sendline(connection, "TYPE I\r\n");
	response = ftp_getrc(connection, NULL, 0, 0);
	switch (response) {
		case 200 : log(LOG_INFO, "Binary mode\r");
			   break;
		case 421 : log(LOG_ALERT, "Service unavailable\n");
			   ftp_disconnect(connection);
			   return -response;
			   break;
		case 500 : log(LOG_ALERT, "Server doesn't understand TYPE\r");
			   break;
		case 501 : log(LOG_ALERT, "Server doesn't understand TYPE parameters\r");
			   break;
		case 502 : log(LOG_ALERT, "Server doesn't understand TYPE\r");
			   break;
		case 504 : log(LOG_ALERT, "Server does not support IMAGE type\r");
			   break;
		case 530 : log(LOG_ALERT, "Not logged in\n");
			   ftp_disconnect(connection);
			   return -response;
			   break;
		default  : log(LOG_ALERT, "Unknown response to TYPE: %d\r", response);
			   break;
	}
	/* How often should the progress bar be updated (if at all)? */
	if (config(connection->variables, "progress_update"))
		updateinterval = atoi(config(connection->variables, "progress_update"));
	if (updateinterval < 1) updateinterval = 0;

	/* Passive mode or normal? */
	buffer = config(connection->variables, "passive");
	if (buffer && strcasecmp(buffer, "true") == 0)
		ftp_open_data_pasv(connection, NULL, 0);
	else
		ftp_open_data(connection);

	if (item->size >= 0) {
		if (!stat(item->localfilename, &statbuf)) {
			response = 0;
			if (statbuf.st_size < item->size) {
				/* Restart from statbuf.st_size bytes */
				log(LOG_INFO, ", restarting from %lu bytes", statbuf.st_size);
				sprintf(line, "REST %lu\r\n", statbuf.st_size);
				ftp_sendline(connection, line);
				response = ftp_getrc(connection, NULL, 0, 0);
				if (response >= 500) {
					response = -1;
				} else if (response == 421) {
					log(LOG_ALERT, "Can't restart.. fatal error!\n");
					return -421;
				}
				resume = statbuf.st_size;
			}
			if (statbuf.st_size >= item->size || response == -1) {
				/* Make a backup copy and start again */
				log(LOG_WARNING, "Backing up old file to %s.quftp", item->localfilename);
				sprintf(line, "%s.quftp", item->localfilename);
				rename(item->localfilename, line);
			}
		}
	} else {
		log(LOG_WARNING, "Can't get filesize.. not displaying progress bar\n");
	}

	/* Sent RETR command and attempt to handle response */
	sprintf(line, "RETR %s\r\n", item->remotefilename);
	ftp_sendline(connection, line);
	response = ftp_getrc(connection, line, 0, 0);
	switch (response) {
		case 250 :
		case 350 :
		case 125 : break;
		case 150 : if (!ftp_wait_data(connection)) {	// Transfer ok
					   ftp_close_data(connection);
					   return -response;
				   }
				   break;
		case 110 : log(LOG_ALERT, "Server wants to restart.. don't know how yet!\n");
				   return -response;
				   break;
		case 226 : log(LOG_ALERT, "Got a closing data connection message... now?\n");
				   return -response;
				   break;
		case 421 : log(LOG_ALERT, "Service unavailable\n");
				   ftp_disconnect(connection);
				   ftp_close_data(connection);
				   return -response;
				   break;
		case 425 : log(LOG_ALERT, "Server can't open data connection\n");
				   return -response;
				   break;
		case 426 :
		case 451 : log(LOG_ALERT, "Server aborted transfer\n");
				   return -response;
				   break;
		case 500 : log(LOG_ALERT, "Server doesn't understand RETR\n");
				   ftp_close_data(connection);
				   return -response;
				   break;
		case 501 : log(LOG_ALERT, "Server doesn't understand RETR parameters\n");
				   ftp_close_data(connection);
				   return -response;
				   break;
		case 502 : log(LOG_ALERT, "Server doesn't understand RETR\n");
				   ftp_close_data(connection);
				   return -response;
				   break;
		case 530 : log(LOG_ALERT, "Not logged in\n");
				   ftp_close_data(connection);
				   return -response;
				   break;
		case 550 : { char *temp = &line[0];
				   while (temp && *temp != ':') temp++;
				   temp += 2;
				   log(LOG_WARNING, "Couldn't retrieve %s: %s\n", item->remotefilename, temp);
				   ftp_close_data(connection);
				   return -response; }
		default  : log(LOG_ALERT, "Unknown response to RETR: %d\n", response);
				   return -response;
				   break;
	}
	counter = 0;
	if (!(fd = open(item->localfilename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR))) {
		log(LOG_ALERT, "Can't create local file %s\n", item->localfilename);
		return -1;
	}
	if (resume) {
		lseek(fd, 0, SEEK_END);
		counter = resume;
	}
	setbuf(stdout, NULL);
	buffer = (char *)malloc(4096);

	connection->status = STATUS_TRANSFER;

	/* Start the progress bar display */
	if (item->size > 0 && updateinterval > 0) progress_bar(0, item->size, speed);

	/* Transfer the file */
	while (1) {
		if ((item->size >= 0) && updatetime) {
			gettimeofday(&tv, NULL);
			starttime = ((tv.tv_sec * 1.0) + (tv.tv_usec / 1000000.0));
			updatetime = 0;
			bytesthissecond = 0;
		}

		n = ftp_read_data(connection, buffer, 4096);
		if (n == 0) break;
		write(fd, buffer, n);
		counter += n;
		bytesthissecond += n;

		if (item->size >= 0) {
			gettimeofday(&tv, NULL);
			timelength = ((tv.tv_sec * 1.0) + (tv.tv_usec / 1000000.0)) - starttime;
			if (timelength >= updateinterval) {
				updatetime = 1;
				speed = (bytesthissecond / 1000.0) / (timelength * 1.0);
				if (item->size > 0 && updateinterval > 0) progress_bar(counter, item->size, speed);
			}
		}
	}

	/* Finish off the progress bar */
	if (item->size > 0 && updateinterval > 0) progress_bar(item->size, item->size, speed);

	connection->status = STATUS_IDLE;

	close(fd);
	ftp_close_data(connection);
	ftp_getrc(connection, NULL, 0, 0);
	free(buffer);
	return counter;
}

int ftp_put_file(struct ftpconnection *connection, struct qitem *item) {
	int fd, response, n, counter;
	int updatetime = 1, bytesthissecond = 0, updateinterval = 1;
	char line[MAX_LINE_SIZE], *buffer;
	struct stat statbuf;
	struct timeval tv;
	double starttime = 0.0, timelength, speed = 0.0;
	if (!ftp_check_ready(connection, 1)) return -1;
	if (!item) return -1;
	log(LOG_INFO, "Begin transferring %s\r", item->remotefilename);
	ftp_sendline(connection, "TYPE I\r\n");
	ftp_getrc(connection, NULL, 0, 0);
	if (!(fd = open(item->localfilename, O_RDONLY))) {
		log(LOG_ALERT, "%s doesn't exist!\n", item->localfilename);
		return -1;
	}
	fstat(fd, &statbuf);
	sprintf(line, "ALLO %lu\r\n", statbuf.st_size);
	ftp_sendline(connection, line);
	response = ftp_getrc(connection, NULL, 0, 0);
	switch (response) {
		case 200 :
		case 202 : break;
		case 504 : log(LOG_ALERT, "ALLO command does not implement parameter\r");
			   break;
		case 500 : log(LOG_ALERT, "Server doesn't understand ALLO command\r");
			   break;
		case 501 : log(LOG_ALERT, "Syntax error in ALLO command\n");
			   break;
		case 421 : log(LOG_ALERT, "Server shutting down\n");
			   ftp_close_data(connection);
			   ftp_disconnect(connection);
			   return -1;
		case 530 : log(LOG_ALERT, "Server says not logged in!\n");
			   ftp_close_data(connection);
			   ftp_disconnect(connection);
			   return -1;
		default  : log(LOG_ALERT, "ALLO: Unrecognised error %d\n", response);
			   ftp_close_data(connection);
			   ftp_disconnect(connection);
			   break;
	}
	if (config(connection->variables, "progress_update"))
		updateinterval = atoi(config(connection->variables, "progress_update"));
	if (updateinterval < 1) updateinterval = 1;

	buffer = config(connection->variables, "passive");
	if (buffer && strcasecmp(buffer, "true") == 0)
		ftp_open_data_pasv(connection, NULL, 0);
	else ftp_open_data(connection);

	sprintf(line, "STOR %s\r\n", item->remotefilename);
	ftp_sendline(connection, line);
	response = ftp_getrc(connection, NULL, 0, 0);
	switch (response) {
		case 125 : 
		case 150 : if (connection->status == STATUS_WAITING)
				   if (!ftp_wait_data(connection)) {
					   ftp_close_data(connection);
					   return -response;
				   }
				   break;
		case 532 : log(LOG_ALERT, "Server requires an account.. not supported yet\n");
			   ftp_close_data(connection);
			   return -response;
			   break;
		case 450 : log(LOG_ALERT, "Remote file is busy\n");
			   ftp_close_data(connection);
			   return -response;
			   break;
		case 452 : log(LOG_ALERT, "Insufficient storage space\n");
			   ftp_close_data(connection);
			   return -response;
			   break;
		case 553 : log(LOG_ALERT, "Permission denied to upload \"%s\"\n", item->remotefilename);
			   ftp_close_data(connection);
			   return -response;
			   break;
		case 500 : log(LOG_ALERT, "Server doesn't understand STOR command\n");
			   ftp_close_data(connection);
			   return -response;
			   break;
		case 501 : log(LOG_ALERT, "Syntax error in STOR command\n");
			   ftp_close_data(connection);
			   return -response;
			   break;
		case 421 : log(LOG_ALERT, "Server shutting down\n");
			   ftp_close_data(connection);
			   ftp_disconnect(connection);
			   return -response;
			   break;
		case 530 : log(LOG_ALERT, "Server says not logged in!\n");
			   ftp_close_data(connection);
			   ftp_disconnect(connection);
			   return -response;
			   break;
		default  : log(LOG_ALERT, "Unrecognised error %d\n", response);
			   ftp_close_data(connection);
			   ftp_disconnect(connection);
			   break;
	}
	counter = 0;
	buffer = (char *)malloc(4096);

	progress_bar(0, item->size, 0);
	while (1) {
		if (updatetime) {
			gettimeofday(&tv, NULL);
			starttime = ((tv.tv_sec * 1.0) + (tv.tv_usec / 1000000.0));
			updatetime = 0;
			bytesthissecond = 0;
		}

		n = read(fd, buffer, 4096);
		if (n == 0) break;
		if (!ftp_write_data(connection, buffer, n)) break;
		counter += n;
		bytesthissecond += n;

		gettimeofday(&tv, NULL);
		timelength = ((tv.tv_sec * 1.0) + (tv.tv_usec / 1000000.0)) - starttime;
		if (timelength >= updateinterval) {
			updatetime = 1;
			speed = (bytesthissecond / 1000.0) / (timelength * 1.0);
			progress_bar(counter, item->size, speed);
		}
	}
	progress_bar(item->size, item->size, speed);
	free(buffer);
	close(fd);
	ftp_close_data(connection);
	ftp_getrc(connection, NULL, 0, 0);
	return counter;
}

int ftp_fxp(struct ftpconnection *left, struct ftpconnection *right, char *source, char *dest) {
	char *line = NULL, *temp = NULL, *command = NULL;
	int response;
	int error = 0;

	if (!left || !right) return 1;
	if (!source || !*source) return 1;
	if (!dest || !*dest) return 1;
	if (!ftp_check_ready(left, 1)) return 1;
	if (!ftp_check_ready(right, 1)) return 1;

	/* Set Data mode to IMAGE */
	log(LOG_INFO, "Binary mode");
	ftp_sendline(left, "TYPE I\r\n");
	ftp_getrc(left, NULL, 0, 0);
	ftp_sendline(right, "TYPE I\r\n");
	ftp_getrc(right, NULL, 0, 0);

	/* Initiate data connection */
	log(LOG_INFO, "Create data connection\r");
	ftp_sendline(right, "PASV\r\n");
	line = (char *)malloc(MAX_LINE_SIZE);
	response = ftp_getrc(right, line, MAX_LINE_SIZE, 0);
	if (response != 227) {
		log(LOG_ALERT, "Receiving server does not support PASV transfers: %d\n", response);
		return response;
	}
	right->status = STATUS_WAITING;
	temp = strstr(line, "(");
	temp++;
	temp[(strstr(temp, ")") - temp)] = 0;
	command = (char *)malloc(MAX_LINE_SIZE);
	sprintf(command, "PORT %s\r\n", temp);
	free(line);
	ftp_sendline(left, command);
	response = ftp_getrc(left, NULL, 0, 0);
	if (response != 200) {
		log(LOG_ALERT, "Can't establish PORT connection on sending server: %d\n", response);
		ftp_close_data(right);
		free(command);
		return response;
	}
	left->status = STATUS_WAITING;

	/* Begin transfer */
	log(LOG_WARNING, "Begin transferring %s to %s\n", source, dest);
	sprintf(command, "STOR %s\r\n", dest);
	ftp_sendline(right, command);
	right->status = STATUS_TRANSFER;

	sprintf(command, "RETR %s\r\n", source);
	ftp_sendline(left, command);
	left->status = STATUS_TRANSFER;

	/* Finish transfer */
	ftp_close_data(left);
	ftp_close_data(right);
	left->status = right->status = STATUS_IDLE;
	free(command);

	response = ftp_getrc(left, NULL, 0, 0);
	switch (response) {
		case 550 : log(LOG_ALERT, "Error: %s does not exist\n", source); break;
		case 150 :
		case 125 : response = ftp_getrc(left, NULL, 0, 0);
			   switch (response) {
				   case 226 :
				   case 250 : break; /* Transfer OK */
				   case 425 : log(LOG_ALERT, "Error opening data connection\n"); error = 425; break;
				   case 426 : log(LOG_ALERT, "FxP Aborted\n"); error = 426; break;
				   case 451 : log(LOG_ALERT, "FxP Aborted\n"); error = 451; break;
				   case 551 : log(LOG_ALERT, "FxP Aborted\n"); error = 551; break;
				   case 552 : log(LOG_ALERT, "FxP Aborted.. not enough space\n"); error = 552; break;
			   }
			   break;
		default  : break;
	}

	response = ftp_getrc(right, NULL, 0, 0);
	switch (response) {
		case 553 : log(LOG_ALERT, "Error: Permission denied uploading %s\n", dest);
			   error = 553;
			   break;
		case 150 :
		case 125 : response = ftp_getrc(right, NULL, 0, 0);
			   switch (response) {
				   case 226 :
				   case 250 : break; /* Transfer OK */
				   case 425 : log(LOG_ALERT, "Error opening data connection\n");
					      error = 425;
					      break;
				   case 426 : log(LOG_ALERT, "FxP Aborted\n"); error = 426; break;
				   case 451 : log(LOG_ALERT, "FxP Aborted\n"); error = 451; break;
				   case 551 : log(LOG_ALERT, "FxP Aborted\n"); error = 551; break;
				   case 552 : log(LOG_ALERT, "FxP Aborted.. not enough space\n");
					      error = 552;
					      break;
			   }
			   break;
		default  : break;
	}
	return error;
}

int ftp_read_file(struct ftpconnection *connection, char *data, int max_size, long status) {
	int response, n;
	char line[MAX_LINE_SIZE];
	if (status == TRANS_TRANS) {
		// Transfer data
		if (data == NULL) return -1;
		if (connection->status != STATUS_TRANSFER) return -1;
		n = ftp_read_data(connection, data, max_size);
		return n;
	} else if (status == TRANS_CLOSE) {
		// Close data connection
		if (connection->status != STATUS_TRANSFER) return -1;
		ftp_close_data(connection);
		ftp_getrc(connection, NULL, 0, 0);
		connection->status = STATUS_IDLE;
		return 0;
	} else {
		char *buffer;
		// Open a new file connection starting from 'status' bytes
		if (data == NULL) return -1;
		if (!ftp_check_ready(connection, 1)) return -1;
		log(LOG_WARNING, "Binary mode\r");
		ftp_sendline(connection, "TYPE I\r\n");
		ftp_getrc(connection, NULL, 0, 0);

		buffer = config(connection->variables, "passive");
		if (buffer && strcasecmp(buffer, "true") == 0)
			ftp_open_data_pasv(connection, NULL, 0);
		else ftp_open_data(connection);

		if (status > 0) {
			sprintf(line, "REST %lu\r\n", status);
			ftp_sendline(connection, line);
			response = ftp_getrc(connection, NULL, 0, 0);
			if (response >= 500) {
				response = -1;
			} else if (response == 421) {
				log(LOG_ALERT, "Can't restart.. fatal error!\n");
				return -421;
			} else {
				log(LOG_ALERT, "Unknown error %d\n", response);
				return -response;
			}
		}
		log(LOG_ALERT, "Read file %s\r", data);
		sprintf(line, "RETR %s\r\n", data);
		ftp_sendline(connection, line);
		response = ftp_getrc(connection, line, 0, 0);
		if (response == 150) {
			ftp_wait_data(connection);
		} else if (response == 125) {
		} else if (response == 550) {
			char *temp = &line[0];
			while (temp && *temp != ':') temp++;
			temp += 2;
			log(LOG_ALERT, "Couldn't retrieve %s: %s\n", data, temp);
			ftp_close_data(connection);
			return -response;
		} else {
			log(LOG_ALERT, "Unknown server response %d\n", -response);
			return -response;
		}
		connection->status = STATUS_TRANSFER;
		return 0;
	}
	return 0;
}



syntax highlighted by Code2HTML, v. 0.9.1