/* 
 * Copyright (C) 1999-2004 Joachim Wieland <joe@mcknight.de>
 * 
 * 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, USA.
 */

#include "jftpgw.h"
#include "cmds.h"

extern int timeout;

/* these two functions reside in cmds.c */
int getuserdest(const char*, struct clientinfo*);
int getpasswd(const char*, struct clientinfo*);

int login(struct clientinfo*, int);

int transfer_initiate(struct conn_info_st* conn_info, int retrieve_from_cache){
	int ret;
	char *t;
	time_t transfer_start;

	ret = transfer_negotiate(conn_info->clntinfo);
	if (ret == -1) {
		/* dramatic error */
		return 1;
	}

	if (ret == 0) {
		/* if there was no error, transfer the file or
		 * listing */
		transfer_start = time(NULL);
		ret = transfer_transmit(conn_info->clntinfo);
		conn_info->lcs->transfer_duration = time(NULL) - transfer_start;
	} else {
		/* there was an error in transfer_negotiate but it
		 * was not dramatic
		 */
		ret = 0;
	}
	if (ret == TRNSMT_SUCCESS) {
		/* the transfer was okay */
		if (retrieve_from_cache) {
			say(conn_info->clntinfo->clientsocket,
					"226 Transfer complete\r\n");
			conn_info->lcs->respcode = 226;
			conn_info->lcs->complete = 1;
		} else {
			t = passall(conn_info->clntinfo->serversocket,
				    conn_info->clntinfo->clientsocket);
			conn_info->lcs->respcode = respcode(t);
			conn_info->lcs->complete = conn_info->lcs->respcode == 226;
			free(t);
		}
	} else {
		if (ret == TRNSMT_ABORTED) {
			/* an aborted transfer, everything's fine */
		}
		if (ret == TRNSMT_NOERRORMSG) {
			/* do not generate an error message */
		}
		if (ret == TRNSMT_ERROR) {
			/* generate error message */
			if (timeout) {
				jlog(2, "Timeout in %s line %d\n", __FILE__
						,__LINE__);
				err_time_readline(
					conn_info->clntinfo->clientsocket);
			} else {
				err_readline(conn_info->clntinfo->serversocket);
			}
			return 1;
		}
	}
	return ret;
}


int std_quit(const char* args, struct conn_info_st* conn_info) {

	int ss = conn_info->clntinfo->serversocket;
	int cs = conn_info->clntinfo->clientsocket;

	/* Are we already connected to a server? */
	if (conn_info->clntinfo->login.stage >= LOGIN_ST_CONNECTED) {
		char* response;
		say(ss, "QUIT\r\n");
		response = passall(ss, cs);
		conn_info->lcs->respcode = respcode(response);
		free(response);
	} else {
		/* Generate an own goodbye message if we are
		 * not yet connected */
		say(cs, "221 Goodbye...\r\n");
	}
	return CMD_QUIT;
}


int std_pasv(const char* args, struct conn_info_st* conn_info) {
	char* answer = 0;
	int ret;

	if (conn_info->clntinfo->servermode == PASSIVE || 
	    conn_info->clntinfo->servermode == ASCLIENT) {
		ret = pasvserver(conn_info->clntinfo);
		if (ret == 0) {
			conn_info->lcs->respcode = 227;
		}
	} else {
		ret = activeserver(&answer, conn_info->clntinfo);
		conn_info->lcs->respcode = respcode(answer);
		free(answer);
	}

	if ( ! ret ) {
		ret |= pasvclient(conn_info->clntinfo);
	}
	if (ret) {
		if (errno == EPIPE) {
			/* The remote server has closed the connection */
			return CMD_ABORT;
		}
		return CMD_ERROR;
	}

	return CMD_HANDLED;
}


int std_port(const char* args, struct conn_info_st* conn_info) {
	int ret = 0;
	size_t sendbufsize;
	int cs = conn_info->clntinfo->clientsocket;
	struct sockaddr_in sin;
	char *answer = 0;
	char *sendbuf = 0;

	if (portcommandcheck(args, &sin, conn_info->clntinfo) < 0) {
		/* an error was already reported */
		return -1;
	}
	conn_info->clntinfo->portcmd = strdup(args);
	enough_mem(conn_info->clntinfo->portcmd);

	if (conn_info->clntinfo->servermode == ACTIVE ||
	    conn_info->clntinfo->servermode == ASCLIENT) {
		ret = activeserver(&answer, conn_info->clntinfo);
		if (ret < 0) {
			free(answer);
			return CMD_ERROR;
		}
		/* Repeat the answer of the server */
		sendbufsize = strlen(answer) + 3;
		sendbuf = (char*) malloc(sendbufsize);
		enough_mem(sendbuf);
		snprintf(sendbuf, sendbufsize, "%s\r\n", answer);
		say(cs, sendbuf);
		conn_info->lcs->respcode = respcode(sendbuf);
		free(sendbuf);
		free(answer);
	} else {
		ret |= pasvserver(conn_info->clntinfo);
		if (!ret) {
			conn_info->lcs->respcode = 200;
			say(cs, "200 PORT command successful.\r\n");
		} else {
			return CMD_ERROR;
		}
	}
	return CMD_HANDLED;
}


int std_stor(const char* args, struct conn_info_st* conn_info) {
	/* chop of the "STOR "/"STOU "/"APPE " prefix */
	char* space = strchr(args, ' ');
	if (space) {
		conn_info->lcs->filename = space + 1;
	} else {
		conn_info->lcs->filename = args;
	}

	conn_info->lcs->direction = 'i';
	conn_info->clntinfo->mode = STOR;

	/* check the transfer mode - if they differ, change the mode to the
	 * server such that it matches the one of the client */

	if (conn_info->clntinfo->transfermode_client !=
			conn_info->clntinfo->transfermode_server) {
		/* switch the transfer mode to TRANSFER_BINARY */
		char type;
		struct message answer;
		if (conn_info->clntinfo->transfermode_client
						== TRANSFER_ASCII) {
			type = 'A';
			conn_info->clntinfo->transfermode_server
							= TRANSFER_ASCII;
		} else {
			type = 'I';
			conn_info->clntinfo->transfermode_server
							= TRANSFER_BINARY;
		}
		sayf(conn_info->clntinfo->serversocket, "TYPE %c\r\n", type);
		answer = readall(conn_info->clntinfo->serversocket);
		jlog(9, "switched type to %c for storing a file: %s", type, answer.lastmsg);
		free(answer.fullmsg);
	}

	if (passcmd(args, conn_info->clntinfo) < 0) {
		return CMD_ERROR;
	}
	if (conn_info->lcs->respcode != 125 && conn_info->lcs->respcode != 150) {
		return CMD_ERROR;
	}
	if (transfer_initiate(conn_info, 0)) {
		return CMD_ERROR;
	}

	return CMD_HANDLED;
}

int std_retr(const char* args, struct conn_info_st* conn_info) {
	struct cache_filestruct cfs;
	struct message answer;
	int retrieve_from_cache = 0;
	int ret;
	char* last = (char*) 0;

	/* chop off the "RETR " prefix */
	char* space = strchr(args, ' ');
	if (space) {
		conn_info->lcs->filename = space + 1;
	} else {
		conn_info->lcs->filename = args;
	}
	conn_info->lcs->direction = 'o';

	/* check the transfer mode */
	/* we always want to have a binary connection to the server if we're
	 * retrieving a file and the cache is used */

	if (conn_info->clntinfo->transfermode_server == TRANSFER_ASCII
		&& config_get_bool("cache") == 1) {
		/* switch the transfer mode to TRANSFER_BINARY */
		say(conn_info->clntinfo->serversocket, "TYPE I\r\n");
		conn_info->clntinfo->transfermode_server = TRANSFER_BINARY;
		answer = readall(conn_info->clntinfo->serversocket);
		jlog(9, "switched type to binary for retrieving a file (cache is set to on): %s", answer.lastmsg);
		free(answer.fullmsg);
	}

	if (conn_info->clntinfo->transfermode_server == TRANSFER_BINARY
		&& config_get_bool("cache") == 0
		&& conn_info->clntinfo->transfermode_client == TRANSFER_ASCII) {
		/* switch the transfer mode to TRANSFER_ASCII on the
		 * server side as well */
		say(conn_info->clntinfo->serversocket, "TYPE A\r\n");
		conn_info->clntinfo->transfermode_server
							= TRANSFER_ASCII;
		answer = readall(conn_info->clntinfo->serversocket);
		jlog(9, "switched type to ascii for retrieving a file (cache is set to off): %s", answer.lastmsg);
		free(answer.fullmsg);
	}

	if (conn_info->clntinfo->transfermode_client == TRANSFER_ASCII
		&& conn_info->clntinfo->transfermode_server == TRANSFER_BINARY) {
		/* we'll have to convert to ascii */
		conn_info->clntinfo->transfermode_havetoconvert
							= CONV_TOASCII;
	} else {
		conn_info->clntinfo->transfermode_havetoconvert
							= CONV_NOTCONVERT;
	}

	if (config_get_bool("cache")) {
		cfs = cache_gather_info(conn_info->lcs->filename,
					conn_info->clntinfo);
		/* try to read the file from the cache */
		if ((conn_info->clntinfo->cachefd = cache_readfd(cfs)) < 0) {
			/* okay, it is not in, so try to create it */
			jlog(9, "File %s not in cache",
						conn_info->lcs->filename);
			conn_info->clntinfo->fromcache = 0;
			if ((conn_info->clntinfo->cachefd
						= cache_writefd(cfs)) < 0) {;
				conn_info->clntinfo->tocache = 0;
			} else {
				conn_info->clntinfo->tocache = 1;
			}
		} else {
			jlog(9, "File %s was in cache",
						conn_info->lcs->filename);
			conn_info->clntinfo->fromcache = 1;
			conn_info->clntinfo->tocache = 0;
		}
		free(cfs.filepath);
		free(cfs.filename);
	} else {
		/* no cache active */
		jlog(9, "caching not active");
		conn_info->clntinfo->fromcache = 0;
		conn_info->clntinfo->tocache = 0;
	}
	if (conn_info->clntinfo->fromcache) {
		/* do not send the RETR command to the server */
	}

	/* pass the request to the server if we do not have the file in the
	 * cache */
	if ( ! conn_info->clntinfo->fromcache ) {
		sayf(conn_info->clntinfo->serversocket, "RETR %s\r\n",
						conn_info->lcs->filename);

		last = passall(conn_info->clntinfo->serversocket,
					conn_info->clntinfo->clientsocket);
		if (last) {
			jlog(9, "Send (client - %d): %s",
				conn_info->clntinfo->clientsocket, last);
		}
		if (!last) {
			if (timeout) {
				jlog(2, "Timeout in %s line %d\n", __FILE__
						,__LINE__);
				err_time_readline(conn_info->clntinfo->clientsocket);
			} else {
				err_readline(conn_info->clntinfo->clientsocket);
			}
			return CMD_ERROR;
		}
		if (!checkdigits(last, 150) && !checkdigits(last, 125)) {
			jlog(4, "Server returned invalid response: %s", last);
			if (conn_info->clntinfo->fromcache) {
				close(conn_info->clntinfo->cachefd);
				conn_info->clntinfo->cachefd = -1;
			} else if (conn_info->clntinfo->tocache) {
				close(conn_info->clntinfo->cachefd);
				conn_info->clntinfo->cachefd = -1;
				cfs = cache_gather_info(
					conn_info->lcs->filename,
					conn_info->clntinfo);
				cache_delete(cfs, 1);
			}
			/* say(conn_info->clntinfo->clientsocket, last); */
			return CMD_ERROR;
		}
	} else {
		/* we pretend to be the server */
		sayf(conn_info->clntinfo->clientsocket,
				"150 Opening data connection for %s\r\n",
					conn_info->lcs->filename);
		retrieve_from_cache = 1;
	}

	/* Okay, everything is fine, establish a connection */

	ret = transfer_initiate(conn_info, retrieve_from_cache);
	if (ret != TRNSMT_SUCCESS && ret != TRNSMT_ABORTED) {
		return CMD_ERROR;
	}

	if (ret == TRNSMT_SUCCESS && conn_info->lcs->respcode == 226) {
		/* add to cache */
		if (conn_info->clntinfo->tocache) {
			struct cache_filestruct cfs;
			cfs = cache_gather_info(conn_info->lcs->filename,
							conn_info->clntinfo);
			cache_add(cfs);
			free(cfs.filepath);
			free(cfs.filename);
		}
	} else {
		if (conn_info->clntinfo->tocache) {
			/* delete again from cache - should not
			 * happen */
			struct cache_filestruct cfs;
			cfs = cache_gather_info(conn_info->lcs->filename,
							conn_info->clntinfo);
			cache_delete(cfs, 1);
			free(cfs.filepath);
			free(cfs.filename);
		}
	}
	conn_info->clntinfo->fromcache  = 0;
	conn_info->clntinfo->tocache    = 0;
	return CMD_HANDLED;
}

int std_list(const char* args, struct conn_info_st* conn_info) {
	if (passcmd(args, conn_info->clntinfo) < 0) {
		return CMD_ERROR;
	}
	if (conn_info->lcs->respcode != 125 && conn_info->lcs->respcode != 150) {
		/* the sockets are closed by transfer_cleanup */
		return CMD_ERROR;
	}
	/* This is a listing of the server that is treated like a transfer
	 * but is not converted from binary to ascii */
	conn_info->clntinfo->serverlisting = 1;
	if (transfer_initiate(conn_info, 0)) {
		return CMD_ERROR;
	}
	return CMD_HANDLED;
}


/* a simple function that determines the transfer mode (ascii or image) */

int std_type(const char* args, struct conn_info_st* conn_info) {
	char* space = strrchr(args, ' ');
	struct message answer;

	/* just register the desired type. If we're downloading we always
	 * keep a binary connection to the server and convert to ascii if
	 * necessary, if we're uploading however we pass the type through
	 * to the server. The reason for this is the cache. All files should
	 * reside in the binary format in the cache */

	if (space) {
		if (*(space + 1) == 'A' || *(space + 1) == 'a') {
			conn_info->clntinfo->transfermode_client
							= TRANSFER_ASCII;
			conn_info->lcs->type = 'a';
		} else {
			conn_info->clntinfo->transfermode_client
							= TRANSFER_BINARY;
			conn_info->lcs->type = 'b';
		}
	}

	if (conn_info->clntinfo->transfermode_client == TRANSFER_ASCII
		&& config_get_bool("cache") == 0) {
		/* switch the server to ASCII as well */
		say(conn_info->clntinfo->serversocket, "TYPE A\r\n");
		conn_info->clntinfo->transfermode_server = TRANSFER_ASCII;
	} else if (conn_info->clntinfo->transfermode_client == TRANSFER_ASCII
		&& config_get_bool("cache") == 1) {
		/* keep a binary connection to the server */
		say(conn_info->clntinfo->serversocket, "TYPE I\r\n");
		conn_info->clntinfo->transfermode_server = TRANSFER_BINARY;
	} else {
		/* default to a binary connection */
		say(conn_info->clntinfo->serversocket, "TYPE I\r\n");
		conn_info->clntinfo->transfermode_server = TRANSFER_BINARY;
	}

	answer = readall(conn_info->clntinfo->serversocket);
	free(answer.fullmsg);
	/* tell the client about the new status */
	sayf(conn_info->clntinfo->clientsocket, "200 Type set to %c\r\n",
			conn_info->lcs->type == 'a' ? 'A' : 'I');
	conn_info->lcs->respcode = 200;
	return CMD_HANDLED;
}

int std_loggedin(const char* args, struct conn_info_st* conn_info) {
	say(conn_info->clntinfo->clientsocket,
			"503 You are already logged in!\r\n");
	return CMD_HANDLED;
}


/* disable EPSV to avoid timeout.
 *        Problem Report by Ken'ichi Fukamachi <fukachan@fml.org>
 *
 * In the following case, ftp tries to run in EPSV mode but jftpgw 
 * cannot understand this, so timeout occurs.
 * 
 *    NetBSD ftpd (lukemftpd) --- jftpgw --- NetBSD ftp (lukemftp)
 * 
 * To avoid EPSV mode, disable this mode by jftpgw.
 *
 */
int std_epsv(const char* args, struct conn_info_st* conn_info) {
  int cs = conn_info->clntinfo->clientsocket;

  conn_info->lcs->respcode = 500;
  say(cs, "500 'EPSV': command not understood.\r\n");

  return CMD_HANDLED;
}


syntax highlighted by Code2HTML, v. 0.9.1