/***************************************

    This is part of frox: A simple transparent FTP proxy
    Copyright (C) 2000 James Hollingshead

    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-1307  USA

  ftp-cmds.c Parsing code for individual commands.
  
  ***************************************/

#include <fcntl.h>
#include "ftp-cmds.h"
#include "control.h"
#include "data.h"
#include "transdata.h"
#include "vscan.h"
#include "cache.h"
#include "os.h"

void save_pass(sstr * cmd, sstr * arg);
void pasv_parse(sstr * cmd, sstr * arg);
void port_parse(sstr * cmd, sstr * arg);
void abor_parse(sstr * cmd, sstr * arg);
void xfer_command(sstr * cmd, sstr * arg);
void cwd_command(sstr * cmd, sstr * arg);
void pasv_reply(sstr * msg);

void ftpcmds_init()
{
	static struct cmd_struct list[] = {	/*Pinched in part SUSE */
		{"PORT", port_parse},	/*proxy suite! */
		{"PASV", pasv_parse},
		{"ABOR", abor_parse},
		{"USER", user_munge},
		{"PASS", save_pass},
		{"ACCT", send_command},
		{"CWD", cwd_command},
		{"CDUP", cwd_command},
		{"SMNT", send_command},
		{"QUIT", send_command},
		{"REIN", send_command},
		{"TYPE", send_command},
		{"STRU", send_command},
		{"MODE", send_command},
		{"RETR", xfer_command},
		{"STOR", xfer_command},
		{"STOU", xfer_command},
		{"APPE", xfer_command},
		{"ALLO", send_command},
		{"REST", send_command},
		{"RNFR", send_command},
		{"RNTO", send_command},
		{"DELE", send_command},
		{"RMD", send_command},
		{"MKD", send_command},
		{"PWD", send_command},
		{"LIST", xfer_command},
		{"NLST", xfer_command},
		{"SITE", send_command},
		{"SYST", send_command},
		{"STAT", send_command},
		{"HELP", send_command},
		{"NOOP", send_command},
		{"SIZE", send_command},	/* Not found in RFC 959 */
		{"MDTM", send_command},
		{"MLFL", send_command},
		{"MAIL", send_command},
		{"MSND", send_command},
		{"MSOM", send_command},
		{"MSAM", send_command},
		{"MRSQ", send_command},
		{"MRCP", send_command},
		{"XCWD", send_command},
		{"XMKD", send_command},
		{"XRMD", send_command},
		{"XPWD", send_command},
		{"XCUP", send_command},
#if 0
		{"APSV", send_command},	/* As per RFC 1579      */
#endif
		{"", 0}
	};

	ftp_cmds = list;
}

/* NB this function can be called from other code which has already copied the
 * username into info->username and set info->anonymous. In this case arg will
 * be NULL */
void user_munge(sstr * cmd, sstr * arg)
{
	sstr *tmp;
	if(arg) {
		sstr_cpy(info->username, arg);
		if(sstr_casecmp2(info->username, "ftp")
		   && sstr_casecmp2(info->username, "anonymous"))
			info->anonymous = 0;
		else
			info->anonymous = 1;
	}

	cache_init();
	tmp = sstr_dup(info->username);
	if(config.ftpproxy.sin_addr.s_addr) {

		sstr_apprintf(tmp, "@%s",
			      inet_ntoa(info->final_server_address.sin_addr));
		if(!config.ftpproxynp ||
		   ntohs(info->final_server_address.sin_port) != 21)
			sstr_apprintf(tmp, ":%d",
				      ntohs(info->final_server_address.
					    sin_port));
	}
	send_command(cmd, tmp);
	sstr_free(tmp);
}

void save_pass(sstr * cmd, sstr * arg)
{
	sstr_cpy(info->passwd, arg);
	send_command(cmd, arg);
}

/* ------------------------------------------------------------- **
** Parse the PORT command in arg and store the client's data listening
** port. Either send out a PASV instead, or open a port of our own
** and send this to the server in a rewritten PORT command.
** ------------------------------------------------------------- */
void port_parse(sstr * cmd, sstr * arg)
{
	int code;
	sstr *msg;

	info->client_data.address = extract_address(arg);

	if(!config_portok(&info->client_data.address)) {
		send_cmessage(500, "Bad PORT command");
		return;
	}

	if(info->mode == PASSIVE && info->client_listen != -1)
		il_free();
	rclose(&info->server_listen);
	rclose(&info->client_listen);

	if(config.apconv) {
		info->mode = APCONV;
		write_log(VERBOSE, "Rewriting PORT command to PASV");

		send_ccommand("PASV", "");
		get_message(&code, &msg);

		info->server_data.address = extract_address(msg);
		if(!config_pasvok(&info->server_data.address)) {
			send_cmessage(500,
				      "Remote server error. PORT failed");
			return;
		} else {
			write_log(VERBOSE, "Rewriting 227 reply.");
			send_cmessage(200, "PORT command OK.");
			return;
		}
	} else {
		sstr *newbuf;
		int a1, a2, a3, a4, p1, p2;
		struct sockaddr_in listenaddr;
		socklen_t len;

		info->mode = ACTIVE;

		len = sizeof(listenaddr);
		getsockname(info->server_control.fd,
			    (struct sockaddr *) &listenaddr, &len);
		listenaddr.sin_family = AF_INET;
		info->server_listen =
			listen_on_socket(&listenaddr, config.actvports);

		if(info->server_listen == -1) {
			send_cmessage(451, "Proxy unable to comply.");
			return;
		}

		n2com(listenaddr, &a1, &a2, &a3, &a4, &p1, &p2);

		newbuf = sstr_init(40);
		sstr_apprintf(newbuf, "%d,%d,%d,%d,%d,%d", a1, a2, a3, a4,
			      p1, p2);

		write_log(VERBOSE, "  Rewritten PORT command:");

		send_command(cmd, newbuf);
		sstr_free(newbuf);
	}
}

/* ------------------------------------------------------------- **
** Intercepted a PASV command. 
**
** Parse the 227 reply message. Either: a) We are transparently
** proxying the data connection - send the 227 through unchanged, and
** do a intercept_listen() for when the client tries to connect. b) We
** aren't - listen on a port of our own and rewrite the 227 with that.
** c) For PAConv open a port for the client, open a port for the server,
** and send the server a PORT command.
** ------------------------------------------------------------- */
void pasv_parse(sstr * cmd, sstr * arg)
{
	int a1, a2, a3, a4, p1, p2;
	struct sockaddr_in tmp;
	int code;
	sstr *msg, *newbuf;

	write_log(VERBOSE, "  Intercepted a PASV command");

	info->mode = PASSIVE;
	rclose(&info->client_listen);
	rclose(&info->server_listen);
	rclose(&info->server_data.fd);
	rclose(&info->client_data.fd);

	if(config.paconv) {
		socklen_t len;
		newbuf = sstr_init(60);

		info->mode = PACONV;
		write_log(VERBOSE, "Rewriting PASV command to PORT");

		write_log(VERBOSE, "Start listening server-side socket");

		len = sizeof(tmp);
		getsockname(info->server_control.fd,
			    (struct sockaddr *) &tmp, &len);
		tmp.sin_family = AF_INET;
		info->server_listen =
			listen_on_socket(&tmp, config.actvports);

		n2com(tmp, &a1, &a2, &a3, &a4, &p1, &p2);
		sstr_apprintf(newbuf, "%d,%d,%d,%d,%d,%d",
			      a1, a2, a3, a4, p1, p2);
		send_ccommand("PORT", sstr_buf(newbuf));
		get_message(&code, NULL);
		if(code < 300) {
			write_log(VERBOSE,
				  "Start listening client-side socket");
			get_local_address(info->client_control.fd, &tmp);
			info->client_listen =
				listen_on_socket(&tmp, config.pasvports);

			n2com(tmp, &a1, &a2, &a3, &a4, &p1, &p2);
			sstr_cpy2(newbuf, "");
			sstr_apprintf(newbuf, "Entering Passive Mode"
				      "(%d,%d,%d,%d,%d,%d)",
				      a1, a2, a3, a4, p1, p2);
			send_message(227, newbuf);
		} else {
			send_cmessage(500, "Error in processing PASV");
		}

		sstr_free(newbuf);
		return;
	}

	send_command(cmd, arg);
	get_message(&code, &msg);

	info->server_data.address = extract_address(msg);
	if(!config_pasvok(&info->server_data.address)) {
		send_cmessage(500, "Bad passive command from server");
		return;
	}

	if(config.transdata) {
		get_local_address(info->client_control.fd, &tmp);
		info->client_listen =
			intercept_listen(info->server_data.address, tmp,
					 config.pasvports);
		if(info->client_listen != -1) {
			send_message(227, msg);
			info->mode = PASSIVE;
			return;
		}
		write_log(VERBOSE,
			  "Intercept_listen failed. Rewriting 227 reply instead");
	}

	get_local_address(info->client_control.fd, &tmp);
	info->client_listen = listen_on_socket(&tmp, config.pasvports);

	if(info->client_listen == -1) {
		send_cmessage(451, "Screwed up pasv command.");
		return;
	}

	n2com(tmp, &a1, &a2, &a3, &a4, &p1, &p2);

	newbuf = sstr_init(60);
	sstr_apprintf(newbuf, "Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
		      a1, a2, a3, a4, p1, p2);

	write_log(VERBOSE, "  Rewritten 227 reply:");

	send_message(227, newbuf);
	info->mode = PASSIVE;
	sstr_free(newbuf);
}

/* ------------------------------------------------------------- **
** Intercepted an ABOR -- we need to send telnet IPs etc.
** ------------------------------------------------------------- */
void abor_parse(sstr * cmd, sstr * arg)
{
	int code;
	rclose(&info->server_data.fd);
	rclose(&info->client_data.fd);
	info->state = NEITHER;
	vscan_abort();

	get_message(&code, NULL);
	send_cmessage(426, "Transfer aborted. Data connection closed.");
	send_cmessage(226, "Abort successful");
	return;
}

/* ------------------------------------------------------------- **
** Keep track of directory for logging (and ?caching) purposes.
** ------------------------------------------------------------- */
void cwd_command(sstr * cmd, sstr * arg)
{
	int code;
	sstr *msg;

	send_command(cmd, arg);

	get_message(&code, &msg);
	send_message(code, msg);

	if(code > 299)
		return;

	if(sstr_getchar(cmd, 1) == 'D')
		/*CDUP*/ sstr_ncat2(info->strictpath, "..", 2);
	else {
		 /*CWD*/ urlescape(arg, "%/ ;");
		sstr_cat(info->strictpath, arg);
	}
	sstr_ncat2(info->strictpath, "/", 1);
	write_log(VERBOSE, "Strictpath = \"%s\"", sstr_buf(info->strictpath));
}

/* ------------------------------------------------------------- **
** Commands that require a data stream.
** ------------------------------------------------------------- */
void xfer_command(sstr * cmd, sstr * arg)
{
	if(info->mode == APCONV) {
		write_log(VERBOSE,
			  "Connecting to both data streams for %s command",
			  sstr_buf(cmd));
		if(connect_client_data() == -1) {
			send_cmessage(425, "Can't open data connection");
			return;
		}

		if(connect_server_data() == -1) {
			send_cmessage(425, "Can't open data connection");
			return;
		}
	}

	if(sstr_casecmp2(cmd, "LIST") && sstr_casecmp2(cmd, "NLST")) {
		info->needs_logging = TRUE;
		info->virus = -1;
		info->cached = 0;
		sstr_cpy(info->filename, arg);
		urlescape(info->filename, "% ;/");
	}

	if(!sstr_casecmp2(cmd, "RETR") ||
	   !sstr_casecmp2(cmd, "LIST") || !sstr_casecmp2(cmd, "NLST"))
		info->state = DOWNLOAD;
	else
		info->state = UPLOAD;
	info->upload = info->state == UPLOAD;
	send_command(cmd, arg);

	if(!sstr_casecmp2(cmd, "RETR"))
		vscan_new(0);
}


syntax highlighted by Code2HTML, v. 0.9.1