/*
 * Copyright (c) 2004-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * FTP gateway
 *
 * (C) Copyright 2004 ArkanoiD
 * (C) Copyright 2004 Alexei Kravchuk
 * (C) Copyright 2001 Matthew Kirkwood
 * based on ftp proxy from "fk" toolkit
 *
 * $Id: ftp.c,v 1.31 2007/09/24 00:04:44 xsaper Exp $
 */

/*
 * Does this impose far too simplistic a state machine?
 *
 * Only time will tell...
 */
/* XXX - search for 999 to find missing response numbers */

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/telnet.h>
#include <arpa/inet.h>

#include <netdb.h>
#include <errno.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>

#include <stdarg.h>

#include <time.h>

#include <syslog.h>

#include "line.h"

#include "telnet.h"
#include "smftp.h"

#include <magic.h>

#include "firewall2.h"
#include "fwfunc.h"
#include "auth.h"
#include "milters.h"
#include "c-type.h"

#include "transaction.h"

#define TEMPFILE_TEMPL "/tmp/ftp-gw.XXXXXX"


#define FAILED  -1
#define REFUSED -2


static	int	authenticated = 0;
static	int	authopened = 0;
static  int	established = 0;

extern magic_t proxy_magic;

static	Cfg	*confp;
static	char	authuser[AUTH_USIZ+1];
static  char	separator[2] = "@";
static	int	user_limit = AUTH_USIZ - 1;
static	int	pass_limit = AUTH_PWSIZ - 1;
static	int	ccdscp = 0;
static	int	cddscp = IPTOS_THROUGHPUT;
static	int	scdscp = 0;
static	int	sddscp = IPTOS_THROUGHPUT;
static	int	transparent = 0;
static	int	extendperm = 0;
static	char	**validests = (char **)0;
static	char	**validusers = (char **)0;
static	int	authallflg = 0;
static	char	username[AUTH_USIZ+1];
static	int	nocheck_data = 0;

static	char	helpfile    [MAX_STR] = "";
static	char	denydestfile[MAX_STR] = "";
static	char	denyfile    [MAX_STR] = "";
static	char	welcomefile [MAX_STR] = "";

unsigned int	buf_limit = 256;


	int	server_fd = -1;

	time_t	last_keepalive_notification_time = 0;
	int	keepalive_timeout_val = 10;

	char	*tmpfilename = NULL;

typedef	struct	{
	int	reversed_streams;
	int	*ftpstate;
	char	ftp_msg[MAX_STR];
	int	*need_noop_resp;
	int	client_cntl_fd;
	int	server_cntl_fd;
} keepalive_args;

typedef	void (*progress_callback)(void *);

void keepalive_progress_callback(keepalive_args *keepalive_args);

static fwparm	options[] = {
 	{	FWPARM_BOOL,	"-transparent",		(char*)	&transparent		},
	{	FWPARM_BOOL,	"-authall",		(char*)	&authallflg		},
	{	FWPARM_BOOL,	"-extnd",		(char*)	&extendperm		},
	{	FWPARM_CHAR,	"-separator",			separator		},
	{	FWPARM_LIST,	"-dest",		(char*)	&validests		},
	{	FWPARM_LIST,	"-user",		(char*) &validusers		},
	{	FWPARM_STRING,	"-plug-to",			proxy_stats.dst		},
	{	FWPARM_PORT,	"-port",		(char *)&proxy_stats.port	},
	{	FWPARM_STRING,	"-authuser",			proxy_stats.authuser	},
	{	FWPARM_OPLOG,	"-log",			(char*)	&transaction_descr	},
	{	FWPARM_OPDENY,	"-deny",		(char*)	&transaction_descr	},
	{       FWPARM_DSCP,    "-clientcmd-dscp",	(char*)	&ccdscp			},
	{       FWPARM_DSCP,    "-clientdata-dscp",	(char*)	&cddscp			},
	{       FWPARM_DSCP,    "-servercmd-dscp",	(char*)	&scdscp			},
	{       FWPARM_DSCP,    "-serverdata-dscp",	(char*)	&sddscp			},
	{	FWPARM_INT,	"-keepalive-timeout",	(char*)	&keepalive_timeout_val	},
	{	FWPARM_BOOL,	"-nocheck-data",	(char*)	&nocheck_data		},
	{	0, 		0,		0 					} };


/* globals */
static	struct	sockaddr_in clientaddr, servaddr;
/* data connection handles */
static	struct	connstate	{
	/* client/NEEDCONN => PORT connections
	 * client/LISTEN   => PASV
	 * server/NEEDCONN => PASV
	 * server/LISTEN   => PORT
	 * (but we don't have to worry about these details
	 * hugely)
	 */
	enum { OFF, NEEDCONN, LISTEN, CONNECTED, READ, WRITE } state;
	/* -1 => no connection/listening socket made */
	int fd;
	/* Address to connect to (only interesting for NEEDCONN) */
	struct sockaddr_in addr;
} clientconn = { OFF, -1, }, serverconn = { OFF, -1, };


/* Deal with data connections */
static	int	make_data_conn(struct connstate *conn);
static	void	cleanup_conn(struct connstate *conn);
static	int	rdwr(struct connstate *c1, struct connstate *c2, int client_cntl_fd, int server_cntl_fd,
			int *alldone, int *ftpstate, char **ftp_msg);

static	int	authsrv_auth(char *resp);
static	int	authsrv_resp(char *resp);
	int	parse_user_cmd(char *buffer,char *user,char *server, unsigned int *port);
static	int	negotiate_server_connection(int fd, int *res, struct connstate *conn);
static	void	port_snprintf(char *buf, size_t len, const char *fmt, struct sockaddr_in *sin);
static	int	parse_port_string(const char *ln, struct sockaddr_in *sin);
	int	strwritev(int ofd, int count, ...);
	void	puts_fd(int fd,char *line);
	int	send_file_msg(int fd,char *fn,int code);
	void	relay_ftp_response(int ifd, int ofd);
	int	getline(int fd,unsigned char *buf,int siz);
static	int	net_send(int s, char *buf, int len, int flags);
	int	send_file_chunk(int o_fd, int tmp_fd);
	void	start_keepalive_timeout();
	int	keepalive_timeout();
	void	end_keepalive_timeout();

enum	direction	{ DOWNLOAD, UPLOAD };
enum	state	{ USER, PASS, ACCT, ESTABLISHED, TRANSFER, TXFRDONE, QUIT, QUITONLY };
enum	state	state = USER;

	int	keepalive_notification(int server_fd, int client_fd, int reversed_streams, int *need_noop_resp);

/* general gunk */
static	ssize_t	tn_recvline(int i, char *line, int maxsize, int fl);
static	int	__read_ftp_response(int ifd, char *ln, int maxsize);
static	int	read_ftp_response(int ifd);

/* option parsing */
	char	*hostname = NULL, *servname = NULL, *bindaddr = NULL;
	int	idletimeout = 0, linger = -1, transp = 0;
	char	*file = NULL;
	int	readonly = 0;
static	int	prefer_server_port = 0;
static	int	refuse_server_port = 0, refuse_server_pasv = 0;
static	struct	portrange {
	int min, max;
} server_port_range = { IPPORT_RESERVED, (2<<16)-1 },
  client_pasv_range = { IPPORT_RESERVED, (2<<16)-1 };

static	int	allocate_listening_socket(int ifd, struct sockaddr_in *psin,  struct portrange *ran);

	int	tmp_fd = -1;

	char	*fname2stor;


/* General FTP things */
static ssize_t tn_recvline(int i, char *line, int maxsize, int fl)
{
	int	res;

	res = recvline(i, line, maxsize, fl);

	if(res > 0)
		unparse_telnet_string(line, i, fl);
	return res;
}


static int __read_ftp_response(int ifd, char *ln, int maxsize)
{
	return __read_smftp_response(ifd, ln, maxsize, &tn_recvline);
}

static int read_ftp_response(int ifd)
{
	return read_smftp_response(ifd, &tn_recvline);
}



/* Specific FTP commands */
/*static int __handle_noarg(int server_fd, const char *cmd)
{ return ____handle_noarg(server_fd, cmd, read_ftp_response); }*/
static int __handle_noarg_resp(int server_fd, const char *cmd, char *ftp_resp, int max_len)
{ return ____handle_noarg_resp(server_fd, cmd, __read_ftp_response, ftp_resp, max_len); }
static int __handle_1str(int server_fd, const char *cmd, const char *arg)
{ return ____handle_1str(server_fd, cmd, arg, read_ftp_response); }
static int __handle_1str_resp(int server_fd, const char *cmd, const char *arg, char *ftp_resp, int max_len)
{ return ____handle_1str_resp(server_fd, cmd, arg, __read_ftp_response, ftp_resp, max_len); }


static int __handle_txfr(int ifd, int server_fd, int cmdres, char *server_resp, enum state *state,
			 enum direction dir)
{
	struct connstate	*up, *down;

	/* XXX -- check that we have an OK client data setup */
	if(cmdres != 150)
		goto err_out;

	server_resp = NULL;

	if(dir == DOWNLOAD) {
		up = &serverconn;
		down = &clientconn;
	} else {
		down = &serverconn;
		up = &clientconn;
	}

	if(make_data_conn(up))
		goto err_out;

	/* To confuse you further, these are from the client's PoV */
	up->state = WRITE;

	shutdown(up->fd, SHUT_WR);

	*state = TRANSFER;
	send_msg(0, 150, dir == DOWNLOAD, dir == DOWNLOAD ? "Data connection for reading setup OK" : "Data connection setup OK");
	return 0;
err_out:
	puts_fd(0, server_resp ? server_resp : "426 Connection setup failed");
	return -1;
}



int
do_quit(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	proxy_exit();
	return(0);
}


int
do_n_args(command,flags,argc,argv, nargs)
char*	command;
int	flags;
int	argc;
char	**argv;
int nargs;
{
	if (argc != nargs + 1) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		send_msg(0, 500, 0, "syntax error");
		return(0);
	} else {
		if(!established) {
			send_msg(0, 530, 0, "Login first with USER and PASS");
			return 1;
		}

		puts_fd(server_fd, command);
		relay_ftp_response(server_fd, 0);
	}
	return(0);
}

int
do_noargs(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	return do_n_args(command,flags,argc,argv, 0);
}


int
do_1arg(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	return do_n_args(command,flags,argc,argv, 1);
}

int
do_1_noargs(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	if (argc > 2) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		send_msg(0, 500, 0, "syntax error");
		return(0);
	} else
		return do_n_args(command,flags,argc,argv, argc - 1);
}

int
do_type(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	if (argc < 2 || argc > 3) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		send_msg(0, 500, 0, "syntax error");
		return(0);
	} else
		return do_n_args(command,flags,argc,argv, argc - 1);
}

int
do_mode(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	if (argc != 2) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		send_msg(0, 500, 0, "syntax error");
		return(0);
	} else if(strcasecmp(argv[1], "s")) {
		syslog(LLEV,"command MODE: Unimplemeted MODE type");
		send_msg(0, 502, 0, "Unimplemeted MODE type");
		return(0);
	} else {
		return do_1arg(command,flags,argc,argv);
	}
}


int
do_help(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	if(server_fd == -1) {
		if(*helpfile != '\0') {
			if(send_file_msg(0,helpfile,220)) {
				syslog(LLEV,"fwtksyserr: cannot display help %.512s: %s",helpfile,strerror(errno));
				send_msg(0, 500, 0, "cannot display help");
				return(0);
			}
			return(0);
		} else {
			send_msg(0,214, 0, "no help available");
			return 0;
		}
	} else {
		return do_1_noargs(command, flags, argc, argv);
	}
}



int
do_rein(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	res;
	char	server_resp[MAX_STR];

	if (argc != 1) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		send_msg(0, 500, 0, "syntax error");
		return(0);
	} else {
		if(!established) {
			send_msg(0, 530, 0, "Login first with USER and PASS");
			return 1;
		}

		if((res = __read_ftp_response(server_fd, server_resp, buf_limit)) == 120) {

			res = __read_ftp_response(server_fd, server_resp, buf_limit);
		}

		if(res == 220) {
			send_msg(0, 230, 0, "REIN OK");
			state = USER;
			established = 0;

		} else {
			puts_fd(0, server_resp);
		}
 	}

	return 0;
}


int
do_generic(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{

	if(!established) {
		send_msg(0, 530, 0, "Login first with USER and PASS");
		return 1;
	}

	puts_fd(server_fd, command);
	relay_ftp_response(server_fd, 0);
	return(0);
}

int
do_stat_abor(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	unsigned char	kbuf[4];

	kbuf[0] = IAC;
	kbuf[1] = IP;
	kbuf[2] = IAC;
	if(net_send(server_fd,(char *)kbuf,3,MSG_OOB) != 3)
		return(0);
	kbuf[0] = DM;
	if(net_send(server_fd,(char *)kbuf,1,0) != 1)
		return(0);

	puts_fd(server_fd, command);     /* handle STAT or ABOR */
	relay_ftp_response(server_fd, 0);
	return(0);
}


int do_user(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	res;
	socklen_t slen;
	int	ftpstate;
	char	msg[MAX_STR];
	char	server_resp[MAX_STR];

	if(!authenticated && authallflg) {
		authsrv_auth(argv[1]);
		state = PASS;
		return 0;
	}
	else
		authenticated = 1;

	if (parse_user_cmd(argv[1],username,proxy_stats.dst, &proxy_stats.port) != 0) {
		send_msg(0,501, 0, "Use USER user@site to connect via proxy");
		return 1;
	}


	if(server_fd <= 0) {
		char err_msg[MAX_STR];

		if (proxy_check_dest(validests,extendperm) || searchlist(username,validusers)) {
			char 		mbuf[MAX_STR];

			if(*denydestfile != '\0') {
				if(send_file_msg(0,denydestfile,500)) {
					syslog(LLEV,"fwtksyserr: cannot display denydest-msg %.512s: %s",denydestfile,strerror(errno));
					send_msg(0,500, 0, "Can't display denydest-msg file.");
					proxy_exit();
				} else {
					return(1);
				}
			}
			snprintf(mbuf,sizeof(mbuf),"Permission denied for user %.100s to connect to %.512s",proxy_stats.authuser,proxy_stats.dst);
			send_msg(0, 500, 0, mbuf);
			return(1);
		}

		proxy_update_operation("CONNECTING");

		if((server_fd = conn_server(proxy_stats.dst,proxy_stats.port,0,err_msg)) < 0) {
			syslog(LLEV,"cannot connect to server %s/%d: %s", proxy_stats.dst,proxy_stats.port, err_msg);
			send_msg(0,500, 0, "FTP gateway destination connect failed");
			proxy_exit();
		}

		/* Remember who we're talking to.. */
		if(getpeername(0, (struct sockaddr*)&clientaddr, (slen=sizeof(clientaddr),&slen)) < 0) {
			syslog(LLEV, "fwtksyserr: Can't get client address: %s", strerror(errno));
			send_msg(0, 500, 0, "Can't get client address.");
			proxy_exit();
		}
		if((server_fd>=0) && getpeername(server_fd, (struct sockaddr*)&servaddr, (slen=sizeof(servaddr),&slen)) < 0) {
			syslog(LLEV, "fwtksyserr: Can't get server address: %s", strerror(errno));
			send_msg(0,500, 0, "Can't get server address.");
			proxy_exit();
		}


		/* on connection, the server must say hello */
		ftpstate = (server_fd < 0) ? 500 : __read_ftp_response(server_fd, server_resp, buf_limit);
		if(DIG1(ftpstate) != 2) {
			state = QUITONLY;
			switch(server_fd) {
			case FAILED:
				send_msg(0, 500, 0, "Connection to server failed");
				syslog(LLEV, "Connection to server failed");
				break;
			case REFUSED:
				send_msg(0, 500, 0, "Use of proxy refused");
				syslog(LLEV, "Use of proxy refused");
				break;
			default:
				send_msg(0, ftpstate, 0, "Server denied access");
				syslog(LLEV, "Server denied access");
			}
			return 1;
		} else {
			proxy_update_operation("AUTHENTICATION");
		}
	}

	snprintf(msg, sizeof(msg), "Connected to ftp server (%s)", server_resp);

	res = __handle_1str(server_fd, "USER", argv[1]);
	if((res == 331) || (res == 332)) {
		send_msg(0, 331, 1, msg);
		send_msg(0, 331, 0, "Username OK, send password");
		state = PASS;
	} else if(res == 230) {
		send_msg(0, 230, 1, msg);
		send_msg(0, 230, 0, "Logged in OK, no password needed");
		state = ESTABLISHED;
		established = 1;
	if (scdscp)
		proxy_set_dscp(server_fd,scdscp);

	} else {
		send_msg(0, res, 0, "Apparently not"); /* XXX */
		return 1;
	}

	return 0;
}



int do_pass(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	res;

	if(state != PASS) {
		send_msg(0, 530, 0, "Login first with USER");
		return 1;
	}

	if(authenticated || !authallflg) {

		if(argv[1] && strlen(argv[1]) > pass_limit)
			buffer_overrun("do_pass", NULL, pass_limit);

		res = __handle_1str(server_fd, "PASS", argv[1]);
		if(res == 230) {
			send_msg(0, 230, 0, "Logged in OK");
			state = ESTABLISHED;
			established = 1;
			if (scdscp)
				proxy_set_dscp(server_fd,scdscp);

		} else if(res == 202) {
			send_msg(0, 202, 0, "Not neccessary, never mind");
			state = ESTABLISHED;
			established = 1;
			if (scdscp)
				proxy_set_dscp(server_fd,scdscp);

		} else
			send_msg(0, res, 0, "Apparently not"); /* XXX */
		return 0;
	} else if (strlen(command) < 6) {
		syslog(LLEV,"%.100s command syntax error %d",command,argc);
		send_msg(0, 500, 0, "syntax error");
		return(0);
	} else {
		authsrv_resp(&command[5]);
		state = USER;
		return 0;
	}
}

int do_pasv(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	sock = -1;
#define	PASVLEN	1000		/* XXX - ick */
	char	pasvbuf[PASVLEN+1];

	pasvbuf[PASVLEN] = 0;

	if((sock = allocate_listening_socket(server_fd, &clientconn.addr, &client_pasv_range)) < 0) {
		send_msg(0, 999, 0, "System error");
		if(sock != -1)
			close(sock);
		return 1;
	}

	clientconn.state = LISTEN;
	if(clientconn.fd != -1)
		close(clientconn.fd);
	clientconn.fd = sock;

	/* XXX - ick */
	port_snprintf(pasvbuf, PASVLEN, "Passive OK (%d,%d,%d,%d,%d,%d)", &clientconn.addr);

	send_msg(0, 227, 0, pasvbuf);
	return 0;
}


int
do_list(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	res;
	char	server_resp[MAX_STR];

	if(!established) {
		send_msg(0, 530, 0, "Login first with USER and PASS");
		return 1;
	}


	if(negotiate_server_connection(server_fd, &res, &serverconn)) {
		send_msg(0, res, 0, "Server connection failed");
		return -1;
	}

	res = argc >= 2  ? __handle_1str_resp(server_fd,argv[0] , argv[1], server_resp, buf_limit) :
					  	__handle_noarg_resp(server_fd, argv[0], server_resp, buf_limit);

	return __handle_txfr(0, server_fd, res, server_resp, &state, DOWNLOAD);

}

int
do_retr(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	res;

	char	server_resp[MAX_STR];

	if(!established) {
		send_msg(0, 530, 0, "Login first with USER and PASS");
		return 1;
	}

	if(negotiate_server_connection(server_fd, &res, &serverconn)) {
		send_msg(0, res, 0, "Server connection failed");
		return -1;
	}

	start_keepalive_timeout();

	return __handle_txfr(0, server_fd, __handle_1str_resp(server_fd, "RETR", argv[1], server_resp, buf_limit), server_resp, &state, DOWNLOAD);
}

int
do_stor(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	int	res;

	if(!established) {
		send_msg(0, 530, 0, "Login first with USER and PASS");
		return 1;
	}

	if(negotiate_server_connection(server_fd, &res, &serverconn)) {
		send_msg(0, res, 0, "Server connection failed");
		return -1;
	}

	start_keepalive_timeout();


	/* Postpone STOR till after (possible) milter data check 
	 * because the server can close the data connection due to timeout expiration */
	fname2stor = xstrdup(argv[1]);
	return __handle_txfr(0, server_fd, 150, NULL, &state, UPLOAD);
}

int
do_port(command,flags,argc,argv)
char*	command;
int	flags;
int	argc;
char	**argv;
{
	struct sockaddr_in	sin;

	if(!established) {
		send_msg(0, 530, 0, "Login first with USER and PASS");
		return 1;
	}

	if(parse_port_string(argv[1], &sin))
		return -1;

	if(sin.sin_addr.s_addr != clientaddr.sin_addr.s_addr) {
		send_msg(0, 999, 0, "Connection to wrong address refused");
		return -1;
	}

	/* cool, got address */
	cleanup_conn(&clientconn);
	clientconn.state = NEEDCONN;
	clientconn.addr = sin;

	send_msg(0, 200, 0, "OK");

	return 0;

}


int main(int argc, char **argv)
{
	size_t	bytes	= 0;
	char	*line	= NULL;
	int	ftpstate;
	char	*tmp;

	char	buffer[MAX_STR];

	char	pass[AUTH_PWSIZ];
	char	tokbuf[MAX_STR];
	char	*tokav[MAX_ARG];
	int	tokac;

	proxy_init(argc,argv);
	
	proxy_chroot_setugid();
	if (ccdscp)
		proxy_set_dscp(0,ccdscp);

	if((tmp = proxy_conf_string(proxy_confp, "help-msg"))) {
		strncpy(helpfile, tmp, sizeof(helpfile) - 1);
		helpfile[sizeof(helpfile) - 1] = '\0';
	}

	if((tmp = proxy_conf_string(proxy_confp, "denial-msg"))) {
		strncpy(denyfile, tmp, sizeof(denyfile) - 1);
		denyfile[sizeof(denyfile) - 1] = '\0';
	}

	if((tmp = proxy_conf_string(proxy_confp, "denydest-msg"))) {
		strncpy(denydestfile, tmp, sizeof(denydestfile) - 1);
		denydestfile[sizeof(denydestfile) - 1] = '\0';
	}

	if((tmp = proxy_conf_string(proxy_confp, "welcome-msg"))) {
		strncpy(welcomefile, tmp, sizeof(welcomefile) - 1);
		welcomefile[sizeof(welcomefile) - 1] = '\0';
	}

	if ((confp=proxy_conf_hosts(proxy_confp,proxy_stats.rladdr, proxy_stats.riaddr)))
		proxy_parse_options(confp,options);
	else {
		if(*denyfile != '\0') {
			if(send_file_msg(0,denyfile,500)) {
				syslog(LLEV,"fwtksyserr: cannot display denial-msg %.512s: %s",denyfile,strerror(errno));
			}
		} else {
			snprintf(buffer,sizeof(buffer),"500 %s/%s not authorized to use FTP proxy",proxy_stats.rladdr,proxy_stats.riaddr);
			send_msg(0, 500, 0, buffer);
		}
		proxy_exit();
	}


	user_limit = proxy_conf_int(proxy_confp,"user-limit",1,sizeof(username)-1,user_limit);
	pass_limit = proxy_conf_int(proxy_confp,"pass-limit",1,sizeof(pass)-1,pass_limit);
	buf_limit  = proxy_conf_int(proxy_confp,"buf-limit",1,sizeof(buffer)-1,buf_limit);


	sotimeout(proxy_timeout);
	memset(buffer, 0, sizeof(buffer));
	memset(username, 0, sizeof(username));
	memset(pass, 0, sizeof(pass));

	if (transparent) {
/*
 *	We do not check if port number is permitted because it comes
 *	from redirection rule we defined manually anyways.
 */
		proxy_get_transparent_dst(proxy_stats.dst,&proxy_stats.port);

		if (proxy_check_dest(validests,extendperm)) {
			char 		mbuf[MAX_STR];

			if(*denydestfile != '\0') {
				if(send_file_msg(0,denydestfile,500)) {
					syslog(LLEV,"fwtksyserr: cannot display denydest-msg %.512s: %s",denydestfile,strerror(errno));
				} else {
					return(1);
				}
			}
			snprintf(mbuf,sizeof(mbuf),"Permission denied to connect to %.512s",proxy_stats.dst);
			send_msg(0, 500, 0, mbuf);

			proxy_exit();
		}
	}

	if(authallflg)
		send_msg(0, 220, 1, "Proxy first requires authentication");


	if(*welcomefile != '\0') {
		if(send_file_msg(0,welcomefile,220)) {
			syslog(LLEV,"fwtksyserr: cannot display welcome %.512s: %s",welcomefile,strerror(errno));
			proxy_exit();
		}
	} else {
	    send_msg(0, 220, 0, "FTP Proxy ready");
	}

	while(1) {
		int				msg_int;
		const	struct	hash_entry	*he;
		int				(*handler)(char*,int,int,char**);
		char				*ftp_msg = NULL;
		int txfrdone = 0;

		if(state == TRANSFER) {
			if(idletimeout > 0) alarm(0);
			bytes += rdwr(&clientconn, &serverconn, 0, server_fd, &txfrdone, &ftpstate, &ftp_msg);
			if(idletimeout > 0) alarm(idletimeout);
			if(txfrdone)
				state = TXFRDONE;
		}
		if(state == TXFRDONE) {
			int ftp_resp;
			char server_resp[MAX_STR];
			ftp_resp = __read_ftp_response(server_fd, server_resp, buf_limit);
			if(ftpstate == -1)	puts_fd(0, server_resp);
			else			send_msg(0, ftpstate, 0, ftp_msg ? ftp_msg :
											DIG1(ftpstate) == 2 && txfrdone == 1 ?
													"OK, done, I think" :
														"Error occured");
			cleanup_conn(&clientconn);
			cleanup_conn(&serverconn);

			if(tmpfilename != NULL) {
				unlink(tmpfilename);
				free(tmpfilename);
				tmpfilename = NULL;
			}

			state = ESTABLISHED;
			if(ftp_msg) {
				free(ftp_msg);
				ftp_msg = NULL;
			}
		}

		if(established) proxy_update_operation("IDLE");
		/* read command */

		if(getline(0, (unsigned char*) buffer, buf_limit) < 0) {
			syslog(LLEV,"error reading client data: %s",strerror(errno));
			proxy_exit();
		}

		tokac = enargv(buffer,tokav,56,tokbuf,sizeof(tokbuf));
		if(!tokav[0]) {
			send_msg(0, 502, 0, "unimplemented");
			if(state != QUITONLY)
				syslog(LLEV, "read non-command from client; line = %s", line);
			continue;
		}

		if((state == USER || state == PASS)
		 && strcasecmp(tokav[0], "USER") && strcasecmp(tokav[0], "PASS") && strcasecmp(tokav[0], "QUIT") && strcasecmp(tokav[0], "HELP")) {
				send_msg(0, 530, 0, "Login first with USER and PASS");
				continue;
		}

		if (!(he = find_he2(&transaction_descr,tokav[0],strlen(tokav[0])))) {
			syslog(LLEV,"unknown ftp command %.100s",buffer);
			send_msg(0, 502, 0, "command unknown or out of state");
			continue;
		} else {
			if(established) proxy_update_operation(buffer);
			if (he->flags & OP_LOG)
				syslog(LLEV,"%.512s/%.20s: %.512s",proxy_stats.rladdr,proxy_stats.riaddr,buffer);
			if (he->flags & OP_DENY) {
				syslog(LLEV,"deny host=%.512s/%.20s operation %.100s",proxy_stats.rladdr,proxy_stats.riaddr,buffer);
				send_msg(0, 530, 0, "operation denied");
				continue;
			}
			if(extendperm && (he->flags & OP_XTND)) {
				msg_int = auth_perm(proxy_confp,proxy_stats.authuser,"ftp-gw",proxy_stats.riaddr,tokav);
				if(msg_int == 1 || msg_int == -1) {
					syslog(LLEV,"deny host=%.512s/%.20s operation %.100s - extended permissions",proxy_stats.rladdr,proxy_stats.riaddr,buffer);
					send_msg(0, 530, 0, "operation denied");
					continue;
				}
			}
			handler = he->handler;
			assert (TRANSACTION_HANDLER (handler));
			handler(buffer,he->flags,tokac,tokav);
			continue;
		}
	}
/*
 *	Unreached
 */
	proxy_exit();


	return 0;
}


/* Misc stuff */
static int rdwr(struct connstate *c1, struct connstate *c2, int client_cntl_fd, int server_cntl_fd,
		int *done, int *ftpstate, char **ftp_msg)
{
	int		c, mfd = 0;
	int		i, o;
	fd_set		ifds, ofds, exfds;

#define	BUFLEN	4096	/* Is this adequate? */

	char		buf[BUFLEN];
	ssize_t		r, w;
	int		reversed_streams = 0;
	int		need_noop_resp = 0;

	static int	sent_chunk_bytes = 0, sent_bytes = 0, rcvd_bytes = 0;

	int		w_total, w_chunk;

	int		ftp_resp;
	char**		milters = NULL;
	char* magic_ctype;

	keepalive_args *keepalive_args;

	*ftpstate = -1;	/* before reading NOOP responses (server keepalive notification) a final RETR command response should
			 * be read. It is stored in this variable */

	*ftp_msg = NULL; /* final RETR command response is stored in this variable */

	if(c2->state != WRITE) {
		struct connstate *t;
		t = c1; c1 = c2; c2 = t;

		reversed_streams = 1;
	}

	if((c1->state != READ) && (c2->state != WRITE)) {
		syslog(LOG_ERR,"securityalert: wrong state of peers in rdwr: (%d, %d)", c1->state, c2->state);
		send_msg(0,500, 0, "wrong state of peers");
		proxy_exit();
	}

	o = c1->fd; i = c2->fd;

	*done = 0;

	c = 0;
	for(;;) {
		FD_ZERO(&ifds);
		FD_SET(client_cntl_fd, &ifds);

		if(need_noop_resp) FD_SET(server_cntl_fd, &ifds);	/* check for input from the ftp server only if NOOP has been sent
									 * as a keepalive notification, so in this case a response should be read out */

		FD_ZERO(&ofds);
		if(c2->state != WRITE && tmp_fd != -1) {	/* check output data connection status only if input	*/
			FD_SET(o, &ofds);			/* is closed and there is open tmp file			*/
			mfd = o;
		} else if(c2->state == WRITE) {			/* check input data connection if it has an appropriate state */
			FD_SET(i, &ifds);
			mfd = i;
		} else {
			syslog(LLEV, "fwtkerr: Server data connection is incorrect");
			proxy_exit();
		}

		/* quit if either of intfds becomes exceptional */
		FD_ZERO(&exfds);
		FD_SET(client_cntl_fd, &exfds);
		FD_SET(server_cntl_fd, &exfds);

		if(client_cntl_fd > mfd) mfd = client_cntl_fd;
		if(server_cntl_fd > mfd) mfd = server_cntl_fd;
		if(select(mfd+1, &ifds, &ofds, &exfds, NULL) < 0) {
			syslog(LLEV, "fwtkerrsys: select() failed: %s", strerror(errno));
			proxy_exit();
		}

		if(need_noop_resp && FD_ISSET(server_cntl_fd, &ifds)) {	/* read out a response to the NOOP command sent to the ftp server
									 * as a keepalive notification  */

			if(*ftpstate == -1 && !reversed_streams) {	/* first response read is a final RETR response */
				char server_resp[MAX_STR];
				ftp_resp = __read_ftp_response(server_cntl_fd, server_resp, buf_limit);
				*ftp_msg = xstrdup(server_resp + 4); 
				*ftpstate = ftp_resp;
			} else {
				ftp_resp = read_ftp_response(server_cntl_fd);
			}
			need_noop_resp = 0;
			continue;
		}

		/* Control data awaits -- we can go */
		if(FD_ISSET(client_cntl_fd, &ifds))
/*		&& !FD_ISSET(i, &ifds)) */
			break;

		if(FD_ISSET(client_cntl_fd, &exfds)) {
/*			telnet_response(00, MSG_OOB); */
			break;
		}
		if(FD_ISSET(server_cntl_fd, &exfds)) {
/*			telnet_response(01, MSG_OOB); */
			break;
		}

		if(tmp_fd != -1 && c1->state == READ && FD_ISSET(o, &ofds)) {	/* data from the previously stored tmp file
										 *  could be  sent to the client */

			if((sent_chunk_bytes = send_file_chunk(o, tmp_fd)) > 0) { /* chunk sent ok */

				if(reversed_streams) {
					proxy_stats.outbytes += sent_chunk_bytes;
				}
				else  {
					proxy_stats.inbytes += sent_chunk_bytes;
				}
				proxy_update_status();
				sent_bytes += sent_chunk_bytes;

				if(keepalive_notification(reversed_streams ? 
					-1 : server_cntl_fd, reversed_streams ? 
						client_cntl_fd : -1 , reversed_streams, &need_noop_resp) 
					&& reversed_streams)
						*ftpstate = 226;	
/* HACK!!! Client keepalive notification has been sent while uploading file.
* It can only be sent as a multiline final response
* to the STOR command having initiated data connection.
* It is assumed here that everithing will be OK and a response
* code will be 226. Since multiline response has been started here
* with code 226, an actual final response code should be coerced to be 226 too.
* Don't see any good way to handle error conditions in such a case!
									*/

				continue;

			} else {  
				/* tmp file sending is complete or error has occured */

				*done = 1;

				syslog(LOG_DEBUG, "SEND DONE");

				cleanup_conn(c1);
				cleanup_conn(c2);

				if(sent_chunk_bytes < 0) {
					syslog(LLEV, "can't send data from tmp file: %s", strerror(errno));
				} else if(sent_bytes != rcvd_bytes) {
					syslog(LLEV, "truncated file sent (rcvd: %d; sent: %d)", rcvd_bytes, sent_bytes);
					if(*ftpstate == -1 || !reversed_streams){
						*ftpstate = 426;
					}
					*ftp_msg = xstrdup("Error occured");					
					send_msg(0, *ftpstate, 1, "Truncated file sent");
				}

				rcvd_bytes = sent_bytes = 0;

				if(tmp_fd != -1) close(tmp_fd);
				tmp_fd = -1;

				break;
			}
		}

		if(!FD_ISSET(i, &ifds)) continue;

		/* Read data from sender */
		r = recv(i, buf, BUFLEN, 0);

		if(r < 0)
			syslog(LLEV, "fwtkerrsys: recv(%d): %s", i, strerror(errno));
		if(!r || (r < 0)) {
			/* zero-length read => connection closed */

			cleanup_conn(c2);  /* cleanup input data connection */

			if(tmp_fd != -1) {
				/* if data have been stored in the tmp file, it's time now for creating
				 * output data connection to send data from the tmp file
				 */

				 syslog(LOG_DEBUG, "RECV DONE");

				if(r < 0) {
					if(reversed_streams) {
						puts_fd(server_cntl_fd, "NOOP");	/* After data transfer has beeen completed
											 * a final ftp response should be read.
											 * It is necessary to send this stub command
											 * so that there is something to read afterward.
											 */
					} else {
						send_msg(client_cntl_fd, 150, 0, "Data reading failed.");
						*ftpstate = 426;
					}

					if(*ftpstate == -1) {
						*ftpstate = 426;
					}
					*ftp_msg = xstrdup("Error occured");					
					send_msg(client_cntl_fd, *ftpstate, 1, "Data reading failed");
					syslog(LLEV, "Data reading failed: %s", strerror(errno));

					*done = -1;
					close(tmp_fd);
					tmp_fd = -1;
					rcvd_bytes = sent_bytes = 0;
					break;
				}

				start_keepalive_timeout();  /* start keepalive timeout to notify ftp server that we are up by sending NOOP */

				keepalive_args = xmalloc(sizeof(*keepalive_args));

				keepalive_args->reversed_streams	= reversed_streams;
				keepalive_args->ftpstate		= ftpstate;
				keepalive_args->ftp_msg[0]		= '\0';
				keepalive_args->need_noop_resp		= &need_noop_resp;
				keepalive_args->client_cntl_fd		= client_cntl_fd;
				keepalive_args->server_cntl_fd		= server_cntl_fd;

				/* set keepalive_progress_callback and keepalive_args as args container for it */
/*
				for(i = 0; i < 150; i++) {
					keepalive_progress_callback(keepalive_args);
					sleep(10);
				}
*/

				fsync(tmp_fd);

				/* process data */
				
				if(milters) {
					char* err_msg = NULL;
				    
					if(inspect_with_milters(tmpfilename, magic_ctype, milters, &err_msg) == -1) {
							    /* Content is BAD! */
					
						if(!err_msg) err_msg = "Error occurred";
					
						if(reversed_streams) {
							puts_fd(server_cntl_fd, "NOOP");	/* After data transfer has beeen completed
												 * a final ftp response should be read.
												 * It is necessary to send this stub command
												 * so that there is something to read afterward.
												 */
						} else {
							send_msg(client_cntl_fd, 150, 0, err_msg);
							*ftpstate = 426;
						}

						if(*ftpstate == -1) {
							*ftpstate = 426;
						}
						*ftp_msg = xstrdup("Error occurred");					
						send_msg(client_cntl_fd, *ftpstate, 1, err_msg);

						*done = -1;
						close(tmp_fd);
						tmp_fd = -1;
						rcvd_bytes = sent_bytes = 0;
						break;
					}
				}

				if(strlen(keepalive_args->ftp_msg) > 4) {
					*ftp_msg = xstrdup(keepalive_args->ftp_msg + 4);
				}

				if(keepalive_args) {
					free(keepalive_args);
					keepalive_args = NULL;
				}

				if(reversed_streams) {
					int	ftp_resp = 0;
					char	server_resp[MAX_STR];

					if(need_noop_resp)  {
						ftp_resp = read_ftp_response(server_cntl_fd);
						need_noop_resp = 0;
					}

					ftp_resp = __handle_1str_resp(server_cntl_fd, "STOR", fname2stor, server_resp, buf_limit);
					free(fname2stor);

					if(ftp_resp != 150 || make_data_conn(c1)) {
						if(*ftpstate == -1) {
							*ftpstate = 426;
						}
						if(ftp_resp != 150)
							*ftp_msg = xstrdup(server_resp + 4);
						else
							*ftp_msg = xstrdup("Error occured");
						send_msg(client_cntl_fd, *ftpstate, 1, "Connection for writing setup failed");
						syslog(LLEV, "fwtkerr: Connection for writing setup failed");

						puts_fd(server_cntl_fd, "NOOP");	/* After data transfer has beeen completed
											 * a final ftp response should be read.
											 * It is necessary to send this stub command
											 * so that there is something to read afterward.
											 */

						*done = -1;
						close(tmp_fd);
						tmp_fd = -1;
						rcvd_bytes = sent_bytes = 0;
						break;
					}
				} else {
					if(make_data_conn(c1)) {
						send_msg(client_cntl_fd, 150, 0, "File processing complete.");

						*ftpstate = 426;
						*ftp_msg = xstrdup("Error occured");
						send_msg(client_cntl_fd, *ftpstate, 1, "Connection for writing setup failed");
						syslog(LLEV, "fwtkerr: Connection for writing setup failed");

						*done = -1;
						close(tmp_fd);
						tmp_fd = -1;
						rcvd_bytes = sent_bytes = 0;
						break;
					}

					send_msg(client_cntl_fd, 150, 1, "File processing complete.");
					send_msg(client_cntl_fd, 150, 0, "Data connection for writing setup OK");
				}

				/* output data connection created correctly */

				c1->state = READ;

				o = c1->fd;

				shutdown(o, SHUT_RD);


				if(lseek(tmp_fd, 0, SEEK_SET) < 0) {  /* rewind the tmp file to the beginning */

					syslog(LLEV, "fwtksyserr: can't lseek tmp file: %s", strerror(errno));
					proxy_exit();
				}
				continue;
			} else {                 /* data are processed without tmp file using, so just cleanup both connections here */
				*done = r ? 1 : -1;
				rcvd_bytes = sent_bytes = 0;
				if(r < 0 && *ftpstate == -1) {
					*ftpstate = 426;
				}
				*ftp_msg = xstrdup("Error occured");				

				cleanup_conn(c1);
				break;
			}
		}
		
		if(rcvd_bytes == 0) {
		/* Look for magic. If there is no magic in config, don't even bother */
			
			if (config_havemagic())
				magic_ctype = xstrdup(magic_buffer(proxy_magic, buf, r));
			else
			        magic_ctype = "unknown";
				
			if(config_ctype(magic_ctype, &milters)) {   /* MIME type is denied */
					
				if(reversed_streams) {
					puts_fd(server_cntl_fd, "NOOP");	/* After data transfer has beeen completed
										 * a final ftp response should be read.
										 * It is necessary to send this stub command
										 * so that there is something to read afterward.
										 */
				} else {
					send_msg(client_cntl_fd, 150, 0, "Data MIME type is denied.");
					*ftpstate = 426;
				}

				if(*ftpstate == -1) {
					*ftpstate = 426;
				}
				*ftp_msg = xstrdup("prohibited");
				send_msg(client_cntl_fd, *ftpstate, 1, "Data MIME type is denied");

				*done = -1;
				rcvd_bytes = sent_bytes = 0;
				break;

			} else if(milters) {   /* MIME type will be inspected with the 'milters' list */
				char	tmpfile_templ[] = TEMPFILE_TEMPL;			
				
				if(( tmp_fd = mkstemp(tmpfile_templ)) == -1) {
					syslog(LLEV, "fwtksyserr: mkstemp: can't open tmp file for writing data stream: %s", strerror(errno));
					proxy_exit();
					
				} else
					tmpfilename = xstrdup(tmpfile_templ);
				
			} else {      /* MIME type is permitted to be relayed unchecked */
				
				if(make_data_conn(c1)) {
					syslog(LLEV, "fwtksyserr: Can't create data connection for writing: %s", strerror(errno));
					cleanup_conn(c2);
					
					if(reversed_streams) {
						puts_fd(server_cntl_fd, "NOOP");	/* After data transfer has beeen completed
											 * a final ftp response should be read.
											 * It is necessary to send this stub command
											 * so that there is something to read afterward.
											 */
					} else {
						send_msg(client_cntl_fd, 150, 0, "Can't create data connection for writing.");
						*ftpstate = 426;
					}

					if(*ftpstate == -1) {
						*ftpstate = 426;
					}
					*ftp_msg = xstrdup("Error occurred");
					send_msg(client_cntl_fd, *ftpstate, 1, "Can't create data connection for writing");


					*done = -1;
					rcvd_bytes = sent_bytes = 0;
					break;
				}
				c1->state = READ;
				shutdown(c1->fd, SHUT_RD);
				
				o = c1->fd;
				
				send_msg(client_cntl_fd, 150, 0, "Data connection setup OK");
				*ftpstate = -1;
			}
		}
						    

		rcvd_bytes += r;

		if(tmp_fd == -1) { /* send data right through, without intermediate storing/processing if there is no open tmp file */

			w = sowrite(o, buf, r);
			if(w >= 0) {
				c += w;
				sent_bytes += w;
			}

			if(w < r) {
				syslog(LLEV, "error: short send(%d): %s", o, strerror(errno));
				proxy_exit();
			}

			if(reversed_streams)
				proxy_stats.outbytes += w;
			else
				proxy_stats.inbytes += w;
			proxy_update_status();

		} else { /* there is an open tmp file, store data in this file first */

			keepalive_notification(reversed_streams ? server_cntl_fd: -1, reversed_streams ? -1 : client_cntl_fd
							, reversed_streams, &need_noop_resp);

			for(w_total = 0; w_total < r; w_total += w_chunk) {
				if((w_chunk = write(tmp_fd, buf + w_total, r - w_total)) <=0) {
					syslog(LLEV, "fwtksyserr: error while writing data stream into tmp file: %s", strerror(errno));
					proxy_exit();
				}
			}
		}
	}

	if(need_noop_resp)  {
		if(*ftpstate == -1 && !reversed_streams) {	/* first response read is a final RETR response */
			char server_resp[MAX_STR];
			ftp_resp = __read_ftp_response(server_cntl_fd, server_resp, buf_limit);
			*ftp_msg = xstrdup(server_resp + 4); 
			*ftpstate = ftp_resp;
		} else {
			ftp_resp = read_ftp_response(server_cntl_fd);
		}


	}
	
	if(milters)
		freelist(&milters);

	return c;
}


static int make_data_conn(struct connstate *conn)
{
	int	as;

	switch(conn->state) {
	case CONNECTED:
		return 0;

	case LISTEN:
		if((as=accept(conn->fd, NULL, NULL)) < 0) {
			syslog(LLEV, "fwtksyserr: accept(%d) failed: %s", conn->fd, strerror(errno));
			return -1;
		}
		close(conn->fd);
		proxy_set_dscp(as,sddscp);
		conn->fd = as;
		break;

	case NEEDCONN:
		if((as = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
			syslog(LLEV, "fwtksyserr: Can't make socket for connection: %s", strerror(errno));
			return -1;
		}

		conn->addr.sin_family = AF_INET;	/* XXX? */
		if(connect(as, (struct sockaddr *) &conn->addr, sizeof(conn->addr)) < 0) {
			syslog(LLEV, "Can't connect socket: %s", strerror(errno));
			close(as);
			return -1;
		}
		proxy_set_dscp(as,cddscp);
		conn->fd = as;
		break;

	default:
		syslog(LOG_ERR,"securityalert: wrong state trying to make a data connection: %d", conn->state);
		send_msg(0,500, 0, "wrong state trying to make a data connection");
		proxy_exit();
	}
	conn->state = CONNECTED;
	return 0;
}


static void cleanup_conn(struct connstate *conn)
{
	conn->state = OFF;
	if(conn->fd != -1) {
		close(conn->fd);
		conn->fd = -1;
	}
}


static int parse_port_string(const char *ln, struct sockaddr_in *sin)
{
	int		res = -1;
#define NARGS	6
	unsigned	i, argt[NARGS];
	char		*tmp_str;
	char		*ptok, *pendtok;

	tmp_str = xstrdup(ln);
	ptok = tmp_str;
	for(i=0; i<NARGS; i++)
		if(!(ptok = strtok(ptok, ","))
		|| (argt[i] = strtol(ptok, &pendtok, 10), ptok == pendtok)
		|| (ptok = NULL, argt[i] > 255))
			goto done;

	sin->sin_addr.s_addr = htonl((argt[0]<<24) + (argt[1]<<16) + (argt[2]<<8) + argt[3]);
	sin->sin_port = htons((argt[4]<<8) + argt[5]);

	res = 0;

done:
	free(tmp_str);
	return res;
}



/* Listen on a local socket (address taken from fd's local address) */
static int allocate_listening_socket(int ifd, struct sockaddr_in *psin,
				     struct portrange *ran)
{
	struct sockaddr_in	sin;
	socklen_t		slen;
	int			sock, res;

	if(!psin)
		psin = &sin;

	if((res = sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog(LLEV, "fwtksyserr: Can't make listening socket for client: %s", strerror(errno));
		goto error;
	}

	/* global */
	if((res = getsockname(0, (struct sockaddr *) psin, (slen=sizeof(*psin),&slen))) < 0) {
		syslog(LLEV, "fwtksyserr: Can't getsockname(%d): %s", ifd, strerror(errno));
		goto error;
	}

	/* XXX - fuck me this is foul */
#define	PASV_TRIES	100
	for(res=0; res<PASV_TRIES; res++) {
		psin->sin_port = htons((rand() % (ran->max - ran->min)) + ran->min);
		if(!bind(sock, (struct sockaddr *) psin, sizeof(sin)))
			break;
	}
	if(res == PASV_TRIES) {
		syslog(LLEV, "fwtksyserr: Can't find a port for client PASV: %s", strerror(errno));
		res = -1;
		goto error;
	}

	if((res = listen(sock, 1)) < 0) {
		syslog(LLEV, "fwtksyserr: Can't listen on client PASV socket: %s", strerror(errno));
		goto error;
	}

error:
	if(res < 0) {
		close(sock);
		return res;
	}

 	proxy_set_dscp(sock,cddscp);
	return sock;
}



static int negotiate_pasv_connection(int fd, int *cmdres, struct connstate *conn)
{
	int			res = -1;
	char			*cp, *cp2;
	struct sockaddr_in	sin;
	int			sock;
	char			server_resp[MAX_STR];

	*cmdres = 999;

	if(strwritev(fd, 1, "PASV\r\n") < 0) {
		syslog(LLEV, "strwritev(%d) failed: %s", fd, strerror(errno));
		return -1;
	}
	*cmdres = __read_ftp_response(fd, server_resp, buf_limit);
	if(DIG1(*cmdres) != 2) {
		/* PASV failed -- at least we don't have to parse the response :) */
		goto done;
	}

	/* bollocks, must now parse the response */
	for(cp = server_resp+3; *cp; cp++) {
		if(isdigit(*cp))
			break;
	}
	if(!*cp) {
		syslog(LLEV, "Address not found in server PASV response");
		goto done;
	}

	for(cp2 = cp; *cp2; cp2++) {
		if((',' != *cp2) && !isdigit(*cp2))
			break;
	}
	if(!*cp2) {
		syslog(LLEV, "PASV response broken");
		return -1;
	}
	*cp2 = 0;

	*cmdres = 504;	/* 999 */
	if(parse_port_string(cp, &sin))
		goto done;

	if(sin.sin_addr.s_addr != servaddr.sin_addr.s_addr)
		goto done;


	/* so all is now probably well... */
	if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		syslog(LLEV, "fwtksyserr: Can't make socket for PASV to server: %s", strerror(errno));
		goto done;
	}

	sin.sin_family = AF_INET;
	if(connect(sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) {
		syslog(LLEV, "Can't connect PASV socket to server: %s", strerror(errno));
		close(sock);
		goto done;
	}

	proxy_set_dscp(sock,sddscp);

	conn->addr = sin;
	conn->state = CONNECTED;
	conn->fd = sock;

	*cmdres = 200;
	res = 0;

done:
	return res;
}


static void port_snprintf(char *buf, size_t len, const char *fmt, struct sockaddr_in *sin)
{
#define	SA(n)	((ntohl(sin->sin_addr.s_addr)>>(n)) & 0xff)
#define	SP(n)	((ntohs(sin->sin_port)>>(n)) & 0xff)
	snprintf(buf, len, fmt, SA(24), SA(16), SA(8), SA(0),
				SP(8), SP(0));
}


static int negotiate_port_connection(int fd, int *cmdres, struct connstate *conn)
{
	int	sock, res;
	char	pstr[MAX_STR+1];

	pstr[MAX_STR] = 0;

	*cmdres = 999;

	if((res = sock = allocate_listening_socket(fd, &serverconn.addr,
						   &server_port_range)) < 0)
		return res;

	port_snprintf(pstr, MAX_STR, "%d,%d,%d,%d,%d,%d", &serverconn.addr);

	if(strwritev(fd, 3, "PORT ", pstr, "\r\n") < 0) {
		syslog(LLEV, "strwritev(%d) failed: %s", fd, strerror(errno));
		close(sock);
		return -1;
	}
	*cmdres = read_ftp_response(fd);
	if(DIG1(*cmdres) != 2) {
		/* PASV failed -- at least we don't have to parse the response :) */
		close(sock);
		conn->state = OFF;
	} else {
		conn->state = LISTEN;
		conn->fd = sock;
		*cmdres = 200;

		res = 0;
	}

	return res;
}


static int negotiate_server_connection(int fd, int *res, struct connstate *conn)
{
	if(prefer_server_port && !refuse_server_port
	   && !negotiate_port_connection(fd, res, conn))
		/* PORT OK -- we have a listening socket */
		return 0;

	/* Try passive mode first */
	if(!refuse_server_pasv && !negotiate_pasv_connection(fd, res, conn))
		/* PASV accepted, data sock is setup */
		return 0;

	if(!prefer_server_port && !refuse_server_port
	   && !negotiate_port_connection(fd, res, conn))
		/* PORT OK -- we have a listening socket */
		return 0;

	return 1;
}


static	int
authsrv_resp(char *resp)
{
	char	buf[512];

	if(!authopened) {
		send_msg(0, 501, 0, "Use \"authorize\" command first");
		return 1;
	}

	if(strlen(resp) > 512) {
		send_msg(0, 501, 0, "Response too long");
		return 1;
	}

	/* send response */
	if(index(resp,'\'') == (char *)0 && index(resp,'\"') == (char *)0)
		snprintf(buf,sizeof(buf),"response '%s'",resp);
	else
		snprintf(buf,sizeof(buf),"response %s",resp);
	if(auth_send(buf))
		goto lostconn;
	if(auth_recv(buf,sizeof(buf)))
		goto lostconn;
	if(!strncmp(buf,"challenge ",10) ||
	!strncmp(buf,"chalnecho ",10)) {
		send_msg(0, 331, 0, &buf[10]);
		return 0;
	} else
	if(!strncmp(buf,"display ",8)) {
		send_msg(0, 331, 1, &buf[8]);
		return 0;
	}
	else
	if(strncmp(buf,"ok",2)) {
		auth_close();
		authopened = 0;
		send_msg(0, 501, 0, buf);
		return 1;
	}
	authenticated = 1;
	syslog(LLEV,"authenticate user=%.100s",authuser);
	strlcpy(proxy_stats.authuser,authuser,sizeof(proxy_stats.authuser));
	proxy_update_status();
	auth_close();
	authopened = 0;

	if(buf[2] != '\0') {
		char	ebuf[MAX_STR];

		snprintf(ebuf, sizeof(ebuf), "User authenticated to proxy: %s",&buf[2]);
		send_msg(0, 230, 0, ebuf);
		return 0;
	}
	send_msg(0, 230, 0, "User authenticated to proxy");
	return 0;

lostconn:
	auth_close();
	authopened = 0;
	send_msg(0, 501 , 0, "Cannot connect to authentication server");
	return 1;
}


static	int
authsrv_auth(char *username)
{
	char		lbuf[512];
	char		rbuf[512];
	int		mline = 0;

	authenticated = 0;

	if(strlen(username) > 24) {
		send_msg(0, 501, 0, "User name too large");
		return 1;
	}
	if(strlen(username) == 0) {
		send_msg(0, 501, 0, "Missing or extra username");
		return 1;
	}
	if(!authopened)
		if(auth_open(confp)) {
			send_msg(0, 501, 0, "Cannot connect to authentication server");
			return 1;
		}

	/* get welcome message from auth server */
	if(!authopened) {
		if(auth_recv(rbuf,sizeof(rbuf)))
			goto lostconn;
		if(strncmp(rbuf,"Authsrv ready",13)) {
			send_msg(0, 551, 0, rbuf);
			auth_close();
			return 1;
		}
	}
	authopened++;
	/* send username */
	if(strlen(proxy_stats.rladdr) + strlen(proxy_stats.riaddr) + strlen(username) + 100 > MAX_STR) {
		syslog(LLEV,"securityalert: buffer overflow on ftp authentication");
		send_msg(0, 501, 0, "client data exceed buffer size");
		return 1;
	} else
		snprintf(lbuf, sizeof(lbuf), "authorize %s 'ftp-gw %s/%s'",username,proxy_stats.rladdr,proxy_stats.riaddr);
	if(auth_send(lbuf))
		goto lostconn;
	if(auth_recv(rbuf,sizeof(rbuf)))
		goto lostconn;

	if(!strncmp(rbuf,"challenge ",10) ||
	!strncmp(rbuf,"chalnecho ",10))
		snprintf(lbuf, sizeof(lbuf), "%s",&rbuf[10]);
	else
	if(!strncmp(rbuf,"display ",8)) {
		snprintf(lbuf, sizeof(lbuf), "%s",&rbuf[8]);
		mline = 1;
	}
	else
		if(!strncmp(rbuf,"password",8))
			snprintf(lbuf, sizeof(lbuf), "Enter authentication password for %s", username);
		else {
			send_msg(0, 551, 0, rbuf);
			return 1;
		}
	strlcpy(authuser, username, sizeof(authuser));
	send_msg(0, 331, mline, lbuf);
	return 0;

lostconn:
	auth_close();
	authopened = 0;
	send_msg(0, 501, 0, "Cannot connect to authentication server");
	return 1;
}

int
parse_user_cmd(buffer,user,server, port)
char	*buffer;
char	*user;
char	*server;
unsigned int *port;
{
	char	*p;

	if ((p = strtok(buffer,separator)) != NULL) {
		if(strlen(p) > ((user_limit < AUTH_USIZ) ? user_limit : AUTH_USIZ)) {
			buffer_overrun("user parse", p, (user_limit < AUTH_USIZ) ? user_limit : 31); /* This function never returns */
		}
		strlcpy(user,p,AUTH_USIZ);
	}
	if ((p = strtok(NULL,":")) != NULL && *server == '\0')
		if(strlen(p) > 63) {
			buffer_overrun("user parse", p, 63); /* This function never returns */
		}
		strncpy(server,p,64);
	if ((p = strtok(NULL,"\r\n")) != NULL && *port <= 0) {
		while(isspace(*p)) p++;
	    	*port = atoi(p);
	}
	if (!*port)
		*port = str_to_port("ftp");
	if ((strlen(user)==0) || (strlen(server)==0) ||
	    (strlen(user) > user_limit))
		return(1);
	return(0);
}

static int
net_send(s, buf, len, flags)
int s;
char *buf;
int len;
int flags;
{
	int	nbytes = 0;
	int	j;

	while (nbytes < len) {
		j = send(s, buf, len-nbytes, flags);
		if (j <= 0)
			return (nbytes ? nbytes : j);
		buf += j;
		nbytes += j;
	}
	return nbytes;
}


int strwritev(int ofd, int count, ...)
{
	va_list	ap;
	int	i;
	int	copied_len = 0;
	char	line[MAX_STR] = "";

	va_start(ap, count);


	for(i =  0; i < count; i++) {
		char *t;

		if(!(t = va_arg(ap, char*))) continue;
		if(!*t) continue;

		strncat(line, t, buf_limit - copied_len - 1);
		copied_len += strlen(t);

		if(copied_len > buf_limit) {
			buffer_overrun("strwritev", NULL, buf_limit); /* This function never returns */
		}
	}

	va_end(ap);

	if (ofd)
		proxy_stats.outbytes += copied_len;
	else
		proxy_stats.inbytes += copied_len;


	if(sowrite(ofd, line, copied_len) != copied_len) return -1;

	return count;
}


void
puts_fd(fd,line)
int	fd;
char	*line;
{
	int	x;
	x = strlen(line);
	if (strwritev(fd, 2, line, "\r\n") < 0) {
		close(fd);
		if (fd) {
			syslog(LLEV,"error writing server data, %s", strerror(errno));
			send_msg(0, 500, 0, "error writing server data");
		} else
			syslog(LLEV,"error writing client data, %s", strerror(errno));
		proxy_exit();
	}
}

int send_file_msg(fd,fn,code)
int	fd;
char	*fn;
int	code;
{
	FILE	*f;
	char	buf[MAX_STR];
	char	*c;
	int	x;
	int	sentsomething = 0;

	if((f = fopen(fn,"r")) == (FILE *)0)
		return(1);
	while(fgets(buf,buf_limit,f) != (char *)0) {
		if((c = index(buf,'\n')) != (char *)0)
			*c = '\0';
		x = fgetc(f);

		send_msg(fd, code, x != EOF, buf);
		ungetc(x,f);

		sentsomething++;
	}
	fclose(f);
	if (!sentsomething) {
		syslog(LLEV,"fwtkcfgerr: send_file_msg for %d is empty",code);
		send_msg(fd, code, 0, "The file to display is empty");
		return(1);
	}
	return(0);
}



void read_raw_ftp_response(int ifd, int *cont, char *ln, int maxsize, read_actor *reader)
{
	int	len;

	if((len = reader(ifd, ln, maxsize,0))) {
		if(    len > 3 			 && isdigit((ln)[0])
			&& isdigit((ln)[1]) && isdigit((ln)[2]))
		{
				*cont = ((ln)[3] != ' ');
		}
		trim_eol(ln);
	}
	else {
		/* error checking */
	}
}


void relay_ftp_response(int ifd, int ofd)
{
	int	cont = 0;
	char	line[MAX_STR];

	do {
		read_raw_ftp_response(ifd, &cont, line, buf_limit, &tn_recvline);
		puts_fd(ofd, line);
	} while(cont);
}


int
getline(fd,buf,siz)
int		fd;
unsigned char	*buf;
int		siz;
{
	int	x = 0;

	while(1) {
		if(soread(fd,(char *)&buf[x],1) != 1)
			return(-1);

		/* get \r\n - read a char and throw it away */
		if(buf[x] == '\r') {
			if(soread(fd,(char *)&buf[x],1) != 1)
				return(-1);
			buf[x] = '\0';
			return(x);
		}

		if(buf[x] == '\n') {
			buf[x] = '\0';
			return(x);
		}

		if(buf[x] == IAC) {
			unsigned char	j;
			unsigned char	jbuf[4];

			if(soread(fd,(char*)&j,1) != 1)
				return(-1);

			switch(j) {
			case WILL:
			case WONT:
				if(soread(fd,(char *)&j,1) != 1)
					return(-1);
				jbuf[0] = IAC;
				jbuf[1] = DONT;
				jbuf[2] = j;
				if(net_send(fd,(char *)jbuf,3,0) != 3)
					return(-1);
				continue;
			case DO:
			case DONT:
				if(soread(fd,(char *)&j,1) != 1)
					return(-1);
				jbuf[0] = IAC;
				jbuf[1] = WONT;
				jbuf[2] = j;
				if(net_send(fd,(char *)jbuf,3, 0) != 3)
					return(-1);
				continue;
			case IAC:
				break;
			case IP:
				if(soread(fd,(char *)&j,1) != 1)
					return(-1);
				if(j == DM || j == IAC)
					x = 0;
				continue;
			default:
				continue;
			}
		}

		if(++x >= siz) {
			buffer_overrun("getline", NULL, siz); /* This function never returns */
		}
	}
}


int send_file_chunk(int o_fd, int tmp_fd)
{
	int	r, w;
	char	buf[MAX_STR];


	if((r = read(tmp_fd, buf, sizeof(buf))) > 0) {
		w = sowrite(o_fd, buf, r);

		if(w < r) {
			syslog(LLEV, "short send(%d): %s", o_fd, strerror(errno));
			return -1;
		}
	}

	if(r < 0) {
		syslog(LLEV, "fwtksyserr: can't read tmp file: %s", strerror(errno));
		return -1;
	}

	return r;
}


void start_keepalive_timeout()
{
	last_keepalive_notification_time = time(NULL);
}

int keepalive_timeout()
{
	int	ret_val = (last_keepalive_notification_time && keepalive_timeout_val && difftime(time(NULL), last_keepalive_notification_time) > keepalive_timeout_val) ? 1 : 0;

	return ret_val;
}

void end_keepalive_timeout()
{
	last_keepalive_notification_time = 0;
}


int keepalive_notification(int server_fd, int client_fd, int reversed_streams, int *need_noop_resp)
{
	int	notify_fd_fl;
	int	ret_val = 0;

	if(tmp_fd == -1) return 0;

	if(server_fd != -1) {   /* send keepalives to the server */

		if(!*need_noop_resp && keepalive_timeout()) {	/* keepalive timeout has expired and there is no
								 * previous keepalive notification (NOOP command)
								 * without response recieved */

			/* set fd into the nonblocking mode so that sending keepalive notification could not block processing */
			if((notify_fd_fl = fcntl(server_fd, F_GETFL)) != -1 &&
				fcntl(server_fd, F_SETFL, notify_fd_fl | O_NONBLOCK) != -1) {

				if(strwritev(server_fd, 1, "PWD\r\n") != -1) {	/* send NOOP command to notify ftp server that we still are up */
					*need_noop_resp = 1;			/* set flag that response to the NOOP command is waited for */

					ret_val = 1;

					syslog(LLEV, "keepalive_notification: send noop");
				}
				fcntl(server_fd, F_SETFL, notify_fd_fl); /* restore fd mode */
			}
		}
	}

	if(client_fd != -1) {  /* send keepalives to the client */

		/* a message that a file downloading/processing is still in progress should be displayed periodically
		 * in order for a user not to disconnect
		 */
		if(keepalive_timeout()) {

			/* set fd into the nonblocking mode, so that sending keepalive notification to the user could not block processing */
			if((notify_fd_fl = fcntl(client_fd, F_GETFL)) != -1 &&
				fcntl(client_fd, F_SETFL, notify_fd_fl | O_NONBLOCK) != -1) {

				send_msg(client_fd, reversed_streams 	? 226	/* attach notification to the "2xx OK ..." message while uploading */
									: 150,	/* attach notification to the "1xx Data connection ... " while downloading */
							1, "File is being processed. Please wait.");
				ret_val = 1;

				fcntl(client_fd, F_SETFL, notify_fd_fl);  /* restore fd mode */
			}
		}
	}

	start_keepalive_timeout();

	return ret_val;
}


void keepalive_progress_callback(keepalive_args *keepalive_args)
{
	int	reversed_streams	= keepalive_args->reversed_streams;
	int	*need_noop_resp		= keepalive_args->need_noop_resp;
	int	ftp_resp;
	int	*ftpstate		= keepalive_args->ftpstate;
	char	*ftp_msg		= keepalive_args->ftp_msg;
	int	server_cntl_fd		= keepalive_args->server_cntl_fd;

	if(*need_noop_resp) {
		fd_set ifds;
		struct timeval tv = {0,0};
		
		FD_ZERO(&ifds);
		FD_SET(server_cntl_fd, &ifds);

		syslog(LLEV, "keepalive: need noop resp");

		if(select(server_cntl_fd+1, &ifds, NULL, NULL, &tv) < 0) {
			syslog(LLEV, "fwtksyserr: select() failed: %s", strerror(errno));
			proxy_exit();
		}


		if(FD_ISSET(server_cntl_fd, &ifds)) {	/* read out a response to the NOOP command sent to the ftp server
								* as a keepalive notification  */

			if(*ftpstate == -1 && !reversed_streams) {	/* first response read is a final RETR response */
				ftp_resp = __read_ftp_response(server_cntl_fd, ftp_msg, buf_limit);
				*ftpstate = ftp_resp;
			} else {
				ftp_resp = read_ftp_response(server_cntl_fd);
			}


			*need_noop_resp = 0;
		}
	}

	if(  keepalive_notification(keepalive_args->server_cntl_fd, keepalive_args->client_cntl_fd, reversed_streams, need_noop_resp)
	  && reversed_streams)
		*ftpstate = 226;	/* HACK!!! Client keepalive notification has been sent while uploading file.
					* It can only be sent as a multiline final response
					* to the STOR command having initiated data connection.
					* It is assumed here that everithing will be OK and a response
					* code will be 226. Since multiline response has been started here
					* with code 226, an actual final response code should be coerced to be 226 too.
					* Don't see any good way to handle error conditions in such a case!
					*/
}

