/*
 * $Id: ftp-cmds.c,v 1.10.2.2 2004/03/10 16:00:49 mt Exp $
 *
 * FTP Proxy command handling
 *
 * Author(s): Jens-Gero Boehm <jens-gero.boehm@suse.de>
 *            Pieter Hollants <pieter.hollants@suse.de>
 *            Marius Tomaschewski <mt@suse.de>
 *            Volker Wiegand <volker.wiegand@suse.de>
 *
 * This file is part of the SuSE Proxy Suite
 *            See also  http://proxy-suite.suse.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-1307, USA.
 *
 * A history log can be found at the end of this file.
 */

#ifndef lint
static char rcsid[] = "$Id: ftp-cmds.c,v 1.10.2.2 2004/03/10 16:00:49 mt Exp $";
#endif

#include <config.h>

#if defined(STDC_HEADERS)
#  include <stdio.h>
#  include <string.h>
#  include <stdlib.h>
#  include <stdarg.h>
#  include <errno.h>
#  include <ctype.h>
#endif

#if defined(HAVE_UNISTD_H)
#  include <unistd.h>
#endif

#if defined(TIME_WITH_SYS_TIME)
#  include <sys/time.h>
#  include <time.h>
#else
#  if defined(HAVE_SYS_TIME_H)
#    include <sys/time.h>
#  else
#    include <time.h>
#  endif
#endif

#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#if defined(HAVE_REGEX)
#  include <sys/types.h>
#  include <regex.h>
#endif

#include "com-config.h"
#include "com-debug.h"
#include "com-misc.h"
#include "com-socket.h"
#include "com-syslog.h"
#include "ftp-client.h"
#include "ftp-cmds.h"


/* ------------------------------------------------------------ */

static void cmds_pthr(CONTEXT *ctx, char *arg);
static void cmds_user(CONTEXT *ctx, char *arg);
static void cmds_pass(CONTEXT *ctx, char *arg);
static void cmds_quit(CONTEXT *ctx, char *arg);
static void cmds_rein(CONTEXT *ctx, char *arg);
static void cmds_port(CONTEXT *ctx, char *arg);
static void cmds_pasv(CONTEXT *ctx, char *arg);
static void cmds_xfer(CONTEXT *ctx, char *arg);
static void cmds_abor(CONTEXT *ctx, char *arg);
#if defined(ENABLE_SSL) /* <!-- SSL --> */
static void cmds_auth(CONTEXT *ctx, char *arg);
#endif /* <!-- /SSL --> */
#if defined(ENABLE_RFC1579)
static void cmds_apsv(CONTEXT *ctx, char *arg);
#endif
#if defined(ENABLE_RFC2428)
static void cmds_eprt(CONTEXT *ctx, char *arg);
static void cmds_epsv(CONTEXT *ctx, char *arg);
#endif


/* ------------------------------------------------------------ */

static int  parse_magic_dest(CONTEXT *ctx, char *dest);
static int  parse_magic_user(CONTEXT *ctx, char *uarg,
                             char a_sep, int a_first,
                             char u_sep, int u_force);

/* ------------------------------------------------------------ */

#if defined(HAVE_REGEX)
#  define REST		NULL, 0, 0
#else
#  define REST		0, 0
#endif

static CMD cmdlist[] = {
	{ "USER", cmds_user, REST },	/* Access control	*/
	{ "PASS", cmds_pass, REST },
	{ "ACCT", cmds_pthr, REST },
	{ "CWD",  cmds_pthr, REST },
	{ "CDUP", cmds_pthr, REST },
	{ "SMNT", cmds_pthr, REST },
	{ "QUIT", cmds_quit, REST },
	{ "REIN", cmds_rein, REST },
	{ "PORT", cmds_port, REST },	/* Transfer parameter	*/
	{ "PASV", cmds_pasv, REST },
	{ "TYPE", cmds_pthr, REST },
	{ "STRU", cmds_pthr, REST },
	{ "MODE", cmds_pthr, REST },
	{ "RETR", cmds_xfer, REST },	/* FTP service		*/
	{ "STOR", cmds_xfer, REST },
	{ "STOU", cmds_xfer, REST },
	{ "APPE", cmds_xfer, REST },
	{ "ALLO", cmds_pthr, REST },
	{ "REST", cmds_pthr, REST },
	{ "RNFR", cmds_pthr, REST },
	{ "RNTO", cmds_pthr, REST },
	{ "ABOR", cmds_abor, REST },
	{ "DELE", cmds_pthr, REST },
	{ "RMD",  cmds_pthr, REST },
	{ "MKD",  cmds_pthr, REST },
	{ "PWD",  cmds_pthr, REST },
	{ "LIST", cmds_xfer, REST },
	{ "NLST", cmds_xfer, REST },
	{ "SITE", cmds_pthr, REST },
	{ "SYST", cmds_pthr, REST },
	{ "STAT", cmds_pthr, REST },
	{ "HELP", cmds_pthr, REST },
	{ "NOOP", cmds_pthr, REST },
	{ "SIZE", cmds_pthr, REST },	/* Not found in RFC 959	*/
	{ "MDTM", cmds_pthr, REST },
	{ "MLFL", cmds_pthr, REST },
	{ "MAIL", cmds_pthr, REST },
	{ "MSND", cmds_pthr, REST },
	{ "MSOM", cmds_pthr, REST },
	{ "MSAM", cmds_pthr, REST },
	{ "MRSQ", cmds_pthr, REST },
	{ "MRCP", cmds_pthr, REST },
	{ "XCWD", cmds_pthr, REST },
	{ "XMKD", cmds_pthr, REST },
	{ "XRMD", cmds_pthr, REST },
	{ "XPWD", cmds_pthr, REST },
	{ "XCUP", cmds_pthr, REST },
	{ "RCMD", cmds_pthr, REST },
#if defined(ENABLE_SSL) /* <!-- SSL --> */
	{ "AUTH", cmds_auth, REST },	/* Only needed for SSL	*/
#endif /* <!-- /SSL --> */
#if defined(ENABLE_RFC1579)
	{ "APSV", cmds_apsv, REST },	/* As per RFC 1579	*/
#endif
#if defined(ENABLE_RFC2428)
	{ "EPRT", cmds_eprt, REST },	/* As per RFC 2428	*/
	{ "EPSV", cmds_epsv, REST },
#endif
	{ NULL,   NULL,      REST }
};


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_get_list
**
**	Parameters....:	(none)
**
**	Return........:	Pointer to command list
**
**	Purpose.......: Make command list known to others.
**
** ------------------------------------------------------------ */

CMD *cmds_get_list(void)
{
	return cmdlist;
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_set_allow
**
**	Parameters....:	allow		List of allowd commands
**					(comma/space delimited)
**
**	Return........:	(none)
**
**	Purpose.......: Setup allowed/forbidden command flags
**			according to a "ValidCommands" config
**			string (from file or LDAP Server).
**
** ------------------------------------------------------------ */

void cmds_set_allow(char *allow)
{
	CMD *cmd;
	char *p, *q;
	int i;

	/*
	** Base line: if no option is given, then anything
	**   is allowed. But if there is one, everything
	**   is forbidden except those items on the list.
	*/
	if (allow == NULL) {
		for (cmd = cmdlist; cmd->name != NULL; cmd++) {
#if defined(HAVE_REGEX)
			if (cmd->regex != NULL) {
				regfree((regex_t *) cmd->regex);
				misc_free(FL, cmd->regex);
				cmd->regex = NULL;
			}
#endif
			cmd->legal = 1;
			cmd->len   = strlen(cmd->name);
		}
#if defined(COMPILE_DEBUG)
		debug(2, "allowed: '(all)'");
#endif
		return;
	}

	/*
	** Initially deny everything
	*/
	for (cmd = cmdlist; cmd->name != NULL; cmd++) {
#if defined(HAVE_REGEX)
		if (cmd->regex != NULL) {
			regfree((regex_t *) cmd->regex);
			misc_free(FL, cmd->regex);
			cmd->regex = NULL;
		}
#endif
		cmd->legal = 0;
		cmd->len   = strlen(cmd->name);
	}

	/*
	** Scan the allow list and enable accordingly
	*/
	for (p = allow; *p != '\0'; ) {
		while (*p != '\0' && isalpha((int)*p) == 0)
			p++;
		if (*p == '\0')
			break;
		for (q = p, i = 0; isalpha((int)*q); q++, i++)
			;
		for (cmd = cmdlist; cmd->name; cmd++) {
			if (cmd->len != i)
				continue;
			if (strncasecmp(cmd->name, p, i) != 0)
				continue;
			cmd->legal = 1;
#if defined(HAVE_REGEX)
			if (*q == '=') {	/* RegEx to follow? */
				char *r;
				r = cmds_reg_comp(&(cmd->regex), ++q);
#if defined(COMPILE_DEBUG)
				debug(2, "allowed: '%s' -> '%s'",
						cmd->name, NIL(r));
#endif
				while (*q && *q != ' ' && *q != '\t')
					q++;
				break;
			}
#endif
#if defined(COMPILE_DEBUG)
			debug(2, "allowed: '%s'", cmd->name);
#endif
			break;
		}
		p = q;
	}
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_pthr
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon pass-through FTP commands.
**
** ------------------------------------------------------------ */

static void cmds_pthr(CONTEXT *ctx, char *arg)
{
	char *cmd;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_pthr: ?ctx?");
	if ((cmd = ctx->curr_cmd) == NULL)
		misc_die(FL, "cmds_pthr: ?curr_cmd?");

	if (ctx->srv_ctrl == NULL) {
		client_respond(530, NULL, "Not logged in");
		syslog_write(U_WRN, "'%s' without login from %s",
					cmd, ctx->cli_ctrl->peer);
		return;
	}

	if (arg == NULL || *arg == '\0') {
		socket_printf(ctx->srv_ctrl, "%s\r\n", cmd);
		syslog_write(U_INF, "'%s' from %s",
					cmd, ctx->cli_ctrl->peer);
	} else {
		socket_printf(ctx->srv_ctrl, "%s %.1024s\r\n", cmd, arg);
		syslog_write(U_INF, "'%s %.1024s' from %s",
					cmd, arg, ctx->cli_ctrl->peer);
	}

	ctx->expect = EXP_PTHR;		/* Expect Response	*/
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_user
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'USER' command.
**
** ------------------------------------------------------------ */

static void cmds_user(CONTEXT *ctx, char *arg)
{
	CMD  *cmd;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_user: ?ctx?");

	/*
	** Check for the user name
	*/
	if (arg == NULL || *arg == '\0') {
		client_respond(501, NULL, "Missing user name");
		syslog_write(U_WRN, "'USER' without name from %s",
					ctx->cli_ctrl->peer);
		return;
	}

	/*
	** Abort any previous service
	*/
	client_reinit();

#if defined(HAVE_REGEX)
	/*
	** Check for a RegEx constraint on the USER command
	*/
	cmds_set_allow(config_str(NULL, "ValidCommands", NULL));
	for (cmd = cmdlist; cmd->name != NULL; cmd++) {
		char *p;
		if (strcasecmp("USER", cmd->name) != 0)
			continue;
		if (cmd->regex == NULL)
			break;
		if ((p = cmds_reg_exec(cmd->regex, arg)) != NULL) {
			client_respond(501, NULL,
				"'USER': syntax error in arguments");
			syslog_write(U_WRN,
				"bad arg '%.128s'%s for "
				"'USER' from %s: %s", arg,
				(strlen(arg) > 128) ?  "..." : "",
				ctx->cli_ctrl->peer, p);
			return;
		}
		break;
	}
#endif


	/*
	** Check for permission and existence of "transparent proxy"
	** magic destination address and port from the client socket
	**
	** "fall through" on error and proceed to check magic user
	** or use DestinationAddress from config...
	*/
	ctx->magic_addr = INADDR_ANY;
	ctx->magic_port = INPORT_ANY;

	if(config_bool(NULL, "AllowTransProxy", 0)) {
		u_int32_t addr = INADDR_ANY;
		u_int16_t port = INPORT_ANY;

		if(!socket_orgdst(ctx->cli_ctrl, &addr, &port) &&
		   INADDR_ANY != addr && INADDR_NONE != addr &&
		   INPORT_ANY != port)
		{
			/*
			** check if destination is a local IP;
			** ignore to loop-protection...
			*/
			int rc = socket_chkladdr(addr);

			switch( rc) {
			case -1:
				syslog_write(T_ERR,
				"check of transparent destination failed");
			break;
			case 0:
				ctx->magic_addr = ntohl(addr);
				ctx->magic_port = ntohs(port);
				syslog_write(T_INF,
				"transparent proxy request to %s:%d from %s",
				socket_addr2str(ctx->magic_addr),
				ctx->magic_port, ctx->cli_ctrl->peer);
			break;
			default:
			syslog_write(T_DBG,
				"requested transparent destination %s is local",
				socket_addr2str(ntohl(addr)));
			break;
			}
		} else {
			syslog_write(T_DBG,
				"no transparent proxy destination found");
		}
	}

	/*
	** Check if we need auth and should use the auth-user mode
	*/
	if(NULL != config_str(NULL, "UserAuthType", NULL)) {
		ctx->auth_mode = UAUTH_FTP;
		ctx->magic_auth = config_str(NULL, "UserAuthMagic", NULL);
		if(NULL != ctx->magic_auth) {
			if( sizeof("auth") != strlen(ctx->magic_auth)) {
				syslog_write(T_ERR, "invalid UserAuthMagic");
				client_respond(530, NULL, "Not logged in");
				client_reinit();
				return;
			}
			if(strncasecmp(ctx->magic_auth, "auth", sizeof("auth")-1))
				ctx->auth_mode = UAUTH_MUA; /* user%auth */
			else
				ctx->auth_mode = UAUTH_MAU; /* auth%user */
		}
	}

	/*
	** Check if we have to parse 'auth' user name...
	*/
	if(NULL != ctx->magic_auth) {
		int   is_ok = 1;
		char  a_sep = ctx->auth_mode == UAUTH_MAU
		            ? ctx->magic_auth[sizeof("auth")-1]
		            : ctx->magic_auth[0];
#if defined(COMPILE_DEBUG)
		debug(2, "parsing '%s' using auth-magic '%.512s'",
			arg, ctx->magic_auth);
#endif

		if(config_bool(NULL, "ForceMagicUser", 0) != 0) {
			char *u_sep = config_str(NULL, "UserMagicChar",
			              config_str(NULL, "UseMagicChar", "@"));
			is_ok = parse_magic_user(ctx, arg,   a_sep,
			                         ctx->auth_mode == UAUTH_MAU,
			                         u_sep[0], 1);
		} else
		if(config_bool(NULL, "AllowMagicUser", 0) != 0) {
			char *u_sep = config_str(NULL, "UserMagicChar",
			              config_str(NULL, "UseMagicChar", "@"));
			is_ok = parse_magic_user(ctx, arg,   a_sep,
			                         ctx->auth_mode == UAUTH_MAU,
			                         u_sep[0], 0);
		} else {
			is_ok = parse_magic_user(ctx, arg,   a_sep,
			                         ctx->auth_mode == UAUTH_MAU,
			                         0,        0);
		}

		if(is_ok || NULL == ctx->userauth || NULL == ctx->username ||
		            '\0' == ctx->userauth || '\0' == ctx->username) {
			if(1 == is_ok) {
				syslog_write(U_ERR,
				             "missed magic dest in 'USER' from %s",
				             ctx->cli_ctrl->peer);
			} else {
				syslog_write(U_ERR,
				             "invalid magic in 'USER' from %s",
				             ctx->cli_ctrl->peer);
			}
			client_respond(530, NULL, "Not logged in");
			client_reinit();
			return;
		}
	} else {
		/*
		** USER="user[<u_sep>host[:port]]"
		*/
		if(config_bool(NULL, "ForceMagicUser", 0) != 0) {
			char *p, *u_sep = config_str(NULL, "UserMagicChar",
			                  config_str(NULL, "UseMagicChar", "@"));
			if( (p = strrchr(arg, u_sep[0]))) {
				*p++ = '\0';
				if(-1 == parse_magic_dest(ctx, p)) {
					syslog_write(U_ERR,
					             "invalid magic in 'USER' from %s",
					             ctx->cli_ctrl->peer);
					client_respond(530, NULL,
					               "Not logged in");
					client_reinit();
					return;
				}
			} else {
				syslog_write(U_ERR,
					"magic dest missed in 'USER' from %s",
					ctx->cli_ctrl->peer);
				client_respond(530, NULL, "Not logged in");
				client_reinit();
				return;
			}
		} else
		if(config_bool(NULL, "AllowMagicUser", 0) != 0) {
			char *p, *u_sep = config_str(NULL, "UserMagicChar",
			                  config_str(NULL, "UseMagicChar", "@"));
			if( (p = strrchr(arg, u_sep[0]))) {
				*p++ = '\0';
				if(-1 == parse_magic_dest(ctx, p)) {
					syslog_write(U_ERR,
					             "invalid magic in 'USER' from %s",
					             ctx->cli_ctrl->peer);
					client_respond(530, NULL,
					               "Not logged in");
					client_reinit();
					return;
				}
			}
		}
		ctx->username = misc_strdup(FL, arg);
		if(NULL == ctx->username || '\0' == ctx->username) {
			client_respond(501, NULL, "Missing user name");
			syslog_write(U_WRN, "'USER' without name from %s",
			                    ctx->cli_ctrl->peer);
			return;
		}
	}

	/*
	** Retrieve the relevant user information
	*/
	if (ctx->magic_addr != INADDR_ANY &&
	    ctx->magic_addr != INADDR_NONE)
	{
		syslog_write(U_INF, "'USER %s' dest %s:%d from %s",
				arg, socket_addr2str(ctx->magic_addr),
				(int)ctx->magic_port, ctx->cli_ctrl->peer);
	} else
	if(config_str(NULL, "DestinationAddress", NULL) == NULL) {
		syslog_write(U_ERR, "unknown destination address");
		client_respond(501, NULL,"Unknown destination address");
		client_reinit();
		return;
	} else {
		syslog_write(U_INF, "'USER %s' from %s",
				arg, ctx->cli_ctrl->peer);
	}

	if(UAUTH_NONE != ctx->auth_mode) {
		/*
		** anable PASS command only...
		*/
		cmds_set_allow("PASS");

		/*
		** hmm... a USER name is there, but we need
		** PASS+auth as well, because it may be needed
		** for auth itself and for profile reading...
		*/
		client_respond(331, NULL, "User name okay, need password.");
	} else {
		/*
		** read user's profile, connect the server
		*/
		if(0 == client_setup(NULL)) {
			client_srv_open();
		} else {
			/*
			** FIXME: client_respond required? checkit!!
			*/
			client_respond(530, NULL, "Not logged in");
			client_reinit();
		}
	}
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_pass
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'PASS' command.
**
** ------------------------------------------------------------ */

static void cmds_pass(CONTEXT *ctx, char *arg)
{
	char *pass = NULL, *q = NULL;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_pass: ?ctx?");

	/*
	** inform auditor...
	*/
	syslog_write(U_INF, "'PASS XXXX' from %s", ctx->cli_ctrl->peer);

	/*
	** should never be NULL, but ...
	** if no password supplied, send none either
	*/
	if(NULL == arg)
		pass = "";
	else
		pass = arg;

	/*
	** Check if we are in auth mode...
	*/
	if(UAUTH_NONE != ctx->auth_mode) {
		/*
		** Check if we should do auth using
		** the normal FTP PASS comand.
		*/
		if(UAUTH_FTP == ctx->auth_mode) {
			ctx->userpass = misc_strdup(FL, pass);
		} else
		/*
		** Check if have to parse for magic
		** auth pass in the FTP PASS command.
		*/
		if(NULL != ctx->magic_auth && pass[0] != '\0') {
			if(ctx->auth_mode == UAUTH_MAU) {
				q = strchr(pass, ctx->magic_auth[sizeof("auth")-1]);
				if(NULL != q) {
					*q++ = '\0';
					ctx->userpass = misc_strdup(FL, q);
				}
			} else {
				q = strrchr(pass, ctx->magic_auth[0]);
				if(NULL != q) {
					*q++ = '\0';
					ctx->userpass = misc_strdup(FL, pass);
					pass          = q;
				}
			}
			if(NULL == q) {
				syslog_write(U_ERR,
				             "invalid magic in 'PASS' from %s",
				             ctx->cli_ctrl->peer);
				client_respond(530, NULL, "Not logged in");
				client_reinit();
				return;
			}
		}

		/*
		** OK, we have all data to auth user, read his
		** proxy-profile (if any) and connect to server
		*/
		if(0 == client_setup(pass)) {
			client_srv_open();
		} else {
			client_respond(530, NULL, "Not logged in");
			client_reinit();
		}
	} else {
		/*
		** paranoia check...
		*/
		if (ctx->srv_ctrl == NULL) {
			client_respond(530, NULL, "Not logged in");
			syslog_write(U_WRN, "'PASS' without login from %s",
			             ctx->cli_ctrl->peer);
			return;
		}

		/*
		** Send to server, but do not display
		*/
		socket_printf(ctx->srv_ctrl, "PASS %.1024s\r\n", pass);
		syslog_write(U_INF, "'PASS XXXX' from %s",
		             ctx->cli_ctrl->peer);

		/* Expect Response */
		ctx->expect = EXP_PTHR;
	}
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_rein
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'REIN' command.
**
** ------------------------------------------------------------ */

static void cmds_rein(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_rein: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	syslog_write(U_INF, "'REIN' from %s", ctx->cli_ctrl->peer);

	/*
	** Abort any running service
	*/
	client_reinit();
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_quit
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'QUIT' command.
**
** ------------------------------------------------------------ */

static void cmds_quit(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_quit: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	/*
	** Close all dependent connections
	*/
	if (ctx->srv_data != NULL) {
		socket_kill(ctx->srv_data);
		ctx->srv_data = NULL;
	}
	if (ctx->cli_data != NULL) {
		socket_kill(ctx->cli_data);
		ctx->cli_data = NULL;
	}
	if (ctx->srv_ctrl != NULL) {
		socket_printf(ctx->srv_ctrl, "QUIT\r\n");
		ctx->srv_ctrl->kill = 1;
	}

	/*
	** Say good-bye
	*/
	client_respond(221, NULL, "Goodbye");
	syslog_write(U_INF, "'QUIT' from %s", ctx->cli_ctrl->peer);
	ctx->expect = EXP_IDLE;
	ctx->cli_ctrl->kill = 1;
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_port
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'PORT' command.
**
** ------------------------------------------------------------ */

static void cmds_port(CONTEXT *ctx, char *arg)
{
	int h1, h2, h3, h4, p1, p2;
	u_int32_t addr;
	u_int16_t port;
	char *peer;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_port: ?ctx?");

	/*
	** Evaluate the arguments
	*/
	if (arg == NULL || sscanf(arg, "%d,%d,%d,%d,%d,%d",
			&h1, &h2, &h3, &h4, &p1, &p2) != 6 ||
			h1 < 0 || h1 > 255 || h2 < 0 || h2 > 255 ||
			h3 < 0 || h3 > 255 || h4 < 0 || h4 > 255 ||
			p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255) {
		client_respond(501, NULL, "Syntax error in arguments");
		syslog_write(U_WRN,
			"syntax error in 'PORT' from %s",
			ctx->cli_ctrl->peer);
		client_data_reset(MOD_RESET);
		return;
	}
	addr = (h1 << 24) + (h2 << 16) + (h3 << 8) + h4;
	port = (p1 <<  8) +  p2;
	peer = socket_addr2str(addr);

	/*
	** If requested, validate the IP address
	*/
	if (ctx->same_adr != 0 && addr != ctx->cli_ctrl->addr) {
		client_respond(501, NULL,
			"PORT address does not match originator");
		syslog_write(U_WRN,
			"different address in 'PORT' from %s",
			ctx->cli_ctrl->peer);
		client_data_reset(MOD_RESET);
		return;
	}

	/*
	** The common behaviour seems to be that PORT cancels
	** a previous PASV. Hmmm, we do it only on "request".
	*/
	if (config_bool(NULL, "PortResetsPasv", 1)) {
		if (ctx->cli_data != NULL) {
			syslog_write(U_WRN,
				"killing old PASV socket for %s",
				ctx->cli_ctrl->peer);
			socket_kill(ctx->cli_data);
			ctx->cli_data = NULL;
		}
		ctx->cli_mode = MOD_ACT_FTP;
	}

	/*
	** All is well, memorize and respond.
	*/
	ctx->cli_addr = addr;
	ctx->cli_port = port;

	client_respond(200, NULL, "PORT command successful");
	syslog_write(U_INF, "'PORT %s:%d' from %s",
			peer, (int) port, ctx->cli_ctrl->peer);
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_pasv
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'PASV' command. The BSD
**			ftpd.c source says that the 425 code
**			has been "blessed by Jon Postel".
**
** ------------------------------------------------------------ */

static void cmds_pasv(CONTEXT *ctx, char *arg)
{
	u_int32_t addr;
	u_int16_t port;
	char str[1024], *p, *q;
	FILE *fp;
	int  incr;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_pasv: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	/*
	** If we already have a listening socket, kill it
	*/
	if (ctx->cli_data != NULL) {
		syslog_write(U_WRN, "killing old PASV socket for %s",
						ctx->cli_ctrl->peer);
		socket_kill(ctx->cli_data);
		ctx->cli_data = NULL;
	}

	/*
	** should we bind a rand(port-range) or increment?
	*/
	incr = !config_bool(NULL,"SockBindRand", 0);

	/*
	** Open a socket that is good for listening
	**
	** TransProxy mode: check if we can use our real
	** ip instead of the server's one as our local ip,
	** we bind the socket/ports to.
	*/
	addr = INADDR_ANY;
	if(config_bool(NULL, "AllowTransProxy", 0)) {
		addr = config_addr(NULL, "Listen", (u_int32_t)INADDR_ANY);
	}
	if(INADDR_ANY == addr) {
		addr = socket_sck2addr(ctx->cli_ctrl->sock, LOC_END, NULL);
	}
	if ((port = socket_d_listen(addr, ctx->pas_lrng, ctx->pas_urng,
			&(ctx->cli_data), "Cli-Data", incr)) == 0)
	{
		syslog_error("Cli-Data: can't bind to %s:%d-%d for %s",
			socket_addr2str(addr), (int) ctx->pas_lrng,
			(int) ctx->pas_urng, ctx->cli_ctrl->peer);
		client_respond(425, NULL, "Can't open data connection");
		return;
	}

	/*
	** Consider address "masquerading" (e.g. within a
	** Cisco LocalDirector environment). In this case
	** we have to present a different logical address
	** to the client. The router will re-translate.
	*/
	p = config_str(NULL, "TranslatedAddress", NULL);
	if (p != NULL) {
		if (*p == '/') {
			if ((fp = fopen(p, "r")) != NULL) {
				while (fgets(str, sizeof(str),
							fp) != NULL) {
					q = misc_strtrim(str);
					if (q == NULL || *q == '#' ||
							 *q == '\0')
						continue;
					addr = socket_str2addr(q, addr);
					break;
				}
				fclose(fp);
			} else {
				syslog_write(U_WRN,
					"can't open NAT file '%*s'",
					MAX_PATH_SIZE, p);
			}
		} else
			addr = socket_str2addr(p, addr);
	}

	/*
	** Tell the user where we are listening
	*/
	client_respond(227, NULL,
			"Entering Passive Mode (%d,%d,%d,%d,%d,%d)",
			(int) ((addr >> 24) & 0xff),
			(int) ((addr >> 16) & 0xff),
			(int) ((addr >>  8) & 0xff),
			(int) ( addr        & 0xff),
			(int) ((port >>  8) & 0xff),
			(int) ( port        & 0xff));
	syslog_write(U_INF, "PASV set to %s:%d for %s",
			socket_addr2str(addr), (int) port,
			ctx->cli_ctrl->peer);

	ctx->cli_mode = MOD_PAS_FTP;
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_xfer
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the data transfer commands.
**
** ------------------------------------------------------------ */

static void cmds_xfer(CONTEXT *ctx, char *arg)
{
	int mode = MOD_ACT_FTP;
	char *cmd;
	u_int32_t addr;
	u_int16_t port;

	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_xfer: ?ctx?");
	if ((cmd = ctx->curr_cmd) == NULL)
		misc_die(FL, "cmds_xfer: ?curr_cmd?");
	if (arg == NULL)		/* Protect the strncpy	*/
		arg = "";

	/*
	** Remember command and arguments for the time when
	** we have established the data connection with the
	** server.
	*/
	if (*arg == '\0') {
		syslog_write(U_INF, "'%s' from %s",
				cmd, ctx->cli_ctrl->peer);
	} else {
		syslog_write(U_INF, "'%s %.*s' from %s", cmd,
			MAX_PATH_SIZE, arg, ctx->cli_ctrl->peer);
	}
	misc_strncpy(ctx->xfer_cmd, cmd, sizeof(ctx->xfer_cmd));
	misc_strncpy(ctx->xfer_arg, arg, sizeof(ctx->xfer_arg));

	/*
	** Check if we want to follow the client mode
	*/
	if ((mode = ctx->srv_mode) == MOD_CLI_FTP)
		mode = ctx->cli_mode;

	/*
	** In passive mode we wait for the server to listen
	*/
	if (mode == MOD_PAS_FTP) {
		socket_printf(ctx->srv_ctrl, "PASV\r\n");
		ctx->expect = EXP_PASV;		/* Expect 227	*/
		return;
	}

	/*
	** In active mode we listen and the server connects
	*/
	if (mode == MOD_ACT_FTP) {
		/*
		** should we bind a rand(port-range) or increment?
		*/
		int incr = !config_bool(NULL,"SockBindRand", 0);

		addr = socket_sck2addr(ctx->srv_ctrl->sock,
						LOC_END, NULL);

		if ((port = socket_d_listen(addr, ctx->srv_lrng,
				ctx->srv_urng, &(ctx->srv_data),
				"Srv-Data", incr)) == 0) {
			syslog_error("Srv-Data: can't bind to "
					"%s:%d-%d for %s",
					socket_addr2str(addr),
					(int) ctx->srv_lrng,
					(int) ctx->srv_urng,
					ctx->cli_ctrl->peer);
			client_respond(425, NULL,
					"Can't open data connection");
			client_data_reset(MOD_RESET);
			return;
		}

		/*
		** Tell the server where we are listening
		*/
		socket_printf(ctx->srv_ctrl,
				"PORT %d,%d,%d,%d,%d,%d\r\n",
				(int) ((addr >> 24) & 0xff),
				(int) ((addr >> 16) & 0xff),
				(int) ((addr >>  8) & 0xff),
				(int) ( addr        & 0xff),
				(int) ((port >>  8) & 0xff),
				(int) ( port        & 0xff));
		syslog_write(T_INF, "'PORT %s:%d' for %s",
				socket_addr2str(addr), (int) port,
				ctx->cli_ctrl->peer);
		ctx->expect = EXP_PORT;		/* Expect 200	*/
		return;
	}

	/*
	** Oops, this should not happen ...
	*/
	misc_die(FL, "cmds_xfer: ?mode %d?", mode);
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_abor
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'ABOR' command.
**
** ------------------------------------------------------------ */

static void cmds_abor(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_abor: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	/*
	** Tell the auditor (with slightly more attention)
	*/
	syslog_write(U_WRN, "'ABOR' from %s", ctx->cli_ctrl->peer);

	/*
	** Reset data connection variables (esp. PASV)
	*/
	client_data_reset(MOD_RESET);

	/*
	** If no transfer is in progress, don't worry
	*/
	if (ctx->cli_data == NULL && ctx->srv_data == NULL) {
		client_respond(225, NULL, "ABOR command successful");
		return;
	}

	/*
	** If we have a data connection to the client, kill it
	*/
	if (ctx->cli_data != NULL) {
		socket_kill(ctx->cli_data);
		ctx->cli_data = NULL;
		client_respond(426, NULL,
			"Connection closed; transfer aborted");
		client_respond(226, NULL, "ABOR command successful");
	}

	/*
	** Finally propagate the ABOR to the server
	** (We follow the crowd and send only IAC-IP-IAC as OOB)
	*/
	if (ctx->srv_ctrl != NULL) {
#if defined(MSG_OOB)
		char str[4];
		socket_flag(ctx->srv_ctrl, MSG_OOB);
		str[0] = IAC;
		str[1] = IP;
		str[2] = IAC;
		socket_write(ctx->srv_ctrl, str, 3);
		socket_flag(ctx->srv_ctrl, 0);
		str[0] = DM;
		socket_write(ctx->srv_ctrl, str, 1);
#endif
		socket_printf(ctx->srv_ctrl, "ABOR\r\n");
		ctx->expect = EXP_ABOR;
	}
}


#if defined(ENABLE_RFC1579)
/* ------------------------------------------------------------ **
**
**	Function......:	cmds_apsv
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'APSV' command.
**
** ------------------------------------------------------------ */

static void cmds_apsv(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_apsv: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	/* TODO */
}
#endif


#if defined(ENABLE_RFC2428)
/* ------------------------------------------------------------ **
**
**	Function......:	cmds_eprt
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'EPRT' command.
**
** ------------------------------------------------------------ */

static void cmds_eprt(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_eprt: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	/* TODO */
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_epsv
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'EPSV' command.
**
** ------------------------------------------------------------ */
static void cmds_epsv(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_epsv: ?ctx?");
	arg = arg;		/* Calm down picky compilers	*/

	/* TODO */
}
#endif


#if defined(ENABLE_SSL) /* <!-- SSL --> */
/* ------------------------------------------------------------ **
**
**	Function......:	cmds_auth
**
**	Parameters....:	ctx		Pointer to user context
**			arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Act upon the 'AUTH' command.
**			This is for SSL authentication.
**
** ------------------------------------------------------------ */

static void cmds_auth(CONTEXT *ctx, char *arg)
{
	if (ctx == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_auth: ?ctx?");

	if (arg == NULL || strcasecmp(arg, "SSL") != 0) {
		client_respond(501, NULL,
				"Missing or bad auth method");
		return;
	}

	/* TODO */
}


#endif /* <!-- /SSL --> */
#if defined(HAVE_REGEX)
/* ------------------------------------------------------------ **
**
**	Function......:	cmds_reg_comp
**
**	Parameters....:	ppre		Pointer to pattern pointer
**			ptr		String to be compiled
**
**	Return........:	Pointer to De-HTML-ized string
**
**	Purpose.......: Compiles string as regular expression.
**
** ------------------------------------------------------------ */

char *cmds_reg_comp(void **ppre, char *ptr)
{
	static char str[1024];
	char tmp[1024];
	int c;
	size_t i;
	regex_t *re;

	if (ppre == NULL)		/* Basic sanity check	*/
		misc_die(FL, "cmds_reg_comp: ?ppre?");

	/*
	** Remove any previous pattern buffer
	*/
	if (*ppre != NULL) {
		regfree((regex_t *) *ppre);
		misc_free(FL, *ppre);
		*ppre = NULL;
	}

	/*
	** If no pattern is given, disable the feature
	*/
	if (ptr == NULL)
		return NULL;

	/*
	** Preprocess the pattern, i.e. "De-HTML-ize" it
	*/
	memset(str, 0, sizeof(str));
	for (i = 0; *ptr != '\0' && i < (sizeof(str) - 64); ptr++) {
		if (*ptr == ' ' || *ptr == '\t')
			break;
		if (*ptr != '%') {
			str[i++] = *ptr;
			continue;
		}
		if (isxdigit((int)ptr[1]) && isxdigit((int)ptr[2])) {
#if defined(HAVE_SNPRINTF)
			snprintf(tmp, sizeof(tmp), "%.2s", ptr + 1);
#else
			sprintf(tmp, "%.2s", ptr + 1);
#endif
			sscanf(tmp, "%x", &c);
			str[i++] = (char) c;
			ptr += 2;
			continue;
		}
		str[i++] = '%';		/* no special meaning	*/
	}

	/*
	** Time to do the actual compilation
	*/
	re = (regex_t *) misc_alloc(FL, sizeof(regex_t));
	if ((i = regcomp(re, str, REG_EXTENDED | REG_NEWLINE |
						REG_NOSUB)) != 0) {
		regerror(i, re, tmp, sizeof(tmp));
		syslog_error("can't eval RegEx '%s': %s", str, tmp);
		regfree(re);
		misc_free(FL, (void *) re);
		return NULL;
	}

	/*
	** all is well
	*/
	*ppre = (void *) re;
	return str;
}


/* ------------------------------------------------------------ **
**
**	Function......:	cmds_reg_exec
**
**	Parameters....:	regex		Pointer to RegEx pattern
**			str		String to check
**
**	Return........:	NULL=success, else pointer to error msg
**
**	Purpose.......: Check if a given string (argument) is legal.
**
** ------------------------------------------------------------ */

char *cmds_reg_exec(void *regex, char *str)
{
	static char err[1024];
	int i;

	if (regex == NULL || str == NULL)	/* Sanity check	*/
		misc_die(FL, "cmds_reg_exec: ?regex? ?str?");

#if defined(COMPILE_DEBUG)
	debug(2, "trying RegEx for '%.*s'", MAX_PATH_SIZE, str);
#endif
	if ((i = regexec((regex_t *) regex, str, 0, NULL, 0)) != 0) {
		regerror(i, (regex_t *) regex, err, sizeof(err));
		return err;
	}

	/*
	** All is well
	*/
	return NULL;
}
#endif /* !HAVE_REGEX */

static int parse_magic_user(CONTEXT *ctx, char *uarg,
                            char a_sep, int a_first,
                            char u_sep, int u_force)
{
	char *p, *q;

	if(NULL == uarg || '\0' == uarg || '\0' == a_sep) {
		misc_die(FL, "parse_magic_user: ?uarg? ?a_sep?");
	}

#if defined(COMPILE_DEBUG)
	debug(2, "parse_magic_user: uarg='%.512s' as=%c af=%d us=%c uf=%d",
	         uarg, a_sep, a_first, 0 == u_sep? '0' : u_sep, u_force);
#endif

	if('\0' == u_sep) {
		if(a_first) {
			/*
			** USER="auth<a_sep>user"
			*/
			p = strchr(uarg, a_sep);
			if(NULL == p) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: a_sep => NULL");
#endif
				return -1;
			}
			*p++ = '\0';
			if('\0' == p[0] || '\0' == uarg) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: a_sep => ''");
#endif
				return -1;
			}
			ctx->userauth = misc_strdup(FL, uarg);
			ctx->username = misc_strdup(FL, p);
		} else {
			/*
			** USER="user<a_sep>auth"
			*/
			p = strrchr(uarg, a_sep);
			if(NULL == p) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: a_sep => NULL");
#endif
				return -1;
			}
			*p++ = '\0';
			if('\0' == p[0] || '\0' == uarg) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: a_sep => ''");
#endif
				return -1;
			}
			ctx->username = misc_strdup(FL, uarg);
			ctx->userauth = misc_strdup(FL, p);
		}
#if defined(COMPILE_DEBUG)
		debug(2, "magic user='%.256s' auth='%.256s'",
		         NIL(ctx->username), NIL(ctx->userauth));
#endif
		return 0;
	}

	if(a_first) {
		/*
		** USER="auth<a_sep>user[<u_sep>host[:port]]"
		*/
		p = strchr(uarg, a_sep);
		if(NULL == p) {
#if defined(COMPILE_DEBUG)
			debug(3, "parse_magic_user: a_sep => NULL");
#endif
			return -1;
		}
		*p++ = '\0';
		if('\0' == p[0] || '\0' == uarg) {
#if defined(COMPILE_DEBUG)
			debug(3, "parse_magic_user: a_sep => ''");
#endif
			return -1;
		}
		q = strrchr(p, u_sep);
		if(NULL == q) {
#if defined(COMPILE_DEBUG)
			debug(3, "parse_magic_user: u_sep => NULL, user => '%.512s'", p);
#endif
			if(u_force)
				return  1;
		} else {
			*q++ = '\0';
			if('\0' == p[0] || '\0' == q[0]) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: u_sep => '', user => '%.512s'", p);
#endif
				return -1;
			}
			if(-1 == parse_magic_dest(ctx, q))
				return -1;
		}
		ctx->userauth = misc_strdup(FL, uarg);
		ctx->username = misc_strdup(FL, p);
#if defined(COMPILE_DEBUG)
		debug(2, "magic user='%.256s' auth='%.256s'",
		         NIL(ctx->username), NIL(ctx->userauth));
#endif
		return 0;
	}

	/*
	** USER="user<a_sep>auth[<u_sep>host[:port]]"
	*/
	p = strrchr(uarg, a_sep);
	if(NULL == p) {
#if defined(COMPILE_DEBUG)
		debug(3, "parse_magic_user: a_sep => NULL");
#endif
		return -1;
	}
	*p++ = '\0';
	if('\0' == p[0] || '\0' == uarg) {
#if defined(COMPILE_DEBUG)
		debug(3, "parse_magic_user: a_sep => ''");
#endif
		return -1;
	}
	if(a_sep == u_sep) {
		q = strrchr(uarg, u_sep);
		if(NULL == q) {
#if defined(COMPILE_DEBUG)
			debug(3, "parse_magic_user: u_sep => NULL, user => '%.512s', dest => '%.512s'", uarg, p);
#endif
			if(u_force)
				return 1;
			ctx->username = misc_strdup(FL, uarg);
			ctx->userauth = misc_strdup(FL, p);
		} else {
			*q++ = '\0';
			if('\0' == uarg[0] || '\0' == q[0]) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: u_sep => '', user => '%.512s', auth => '%.512s', dest => '%.512s'", uarg, q, p);
#endif
				return -1;
			}
			if(-1 == parse_magic_dest(ctx, p))
				return -1;
			ctx->username = misc_strdup(FL, uarg);
			ctx->userauth = misc_strdup(FL, q);
		}
	} else {
		q = strchr(p, u_sep);
		if(NULL == q) {
#if defined(COMPILE_DEBUG)
			debug(3, "parse_magic_user: u_sep => NULL, user => '%.512s', auth => '%.512s'", uarg, p);
#endif
			if(u_force)
				return 1;
		} else {
			*q++ = '\0';
			if('\0' == p[0] || '\0' == q[0]) {
#if defined(COMPILE_DEBUG)
				debug(3, "parse_magic_user: u_sep => '', user => '%.512s', auth => '%.512s', dest => '%.512s'", uarg, p, q);
#endif
				return -1;
			}
			if(-1 == parse_magic_dest(ctx, q))
				return -1;
		}
		ctx->username = misc_strdup(FL, uarg);
		ctx->userauth = misc_strdup(FL, p);
	}
#if defined(COMPILE_DEBUG)
	debug(2, "magic user='%.256s' auth='%.256s'",
	         NIL(ctx->username), NIL(ctx->userauth));
#endif
	return 0;
}

static int parse_magic_dest(CONTEXT *ctx, char *dest)
{
	char *ptr;

	if(dest && dest[0]) {
		if( (ptr = strrchr(dest, ':'))) {
			*ptr++ = '\0';
			ctx->magic_port = socket_str2port(ptr, IPPORT_FTP);
		} else {
			ctx->magic_port = IPPORT_FTP;
		}
		ctx->magic_addr = socket_str2addr(dest, INADDR_ANY);
#if defined(COMPILE_DEBUG)
		debug(2, "parse magic host='%.256s' port='%d'",
		         socket_addr2str(ctx->magic_addr),
		                         ctx->magic_port);
#endif
		if(ctx->magic_addr != INADDR_ANY &&
		   ctx->magic_addr != INADDR_NONE) {
			return 0;
		}
#if defined(COMPILE_DEBUG)
	} else {
		debug(2, "parse magic dest => NONE");
#endif
	}
	return -1;
}


/* ------------------------------------------------------------
 * $Log: ftp-cmds.c,v $
 * Revision 1.10.2.2  2004/03/10 16:00:49  mt
 * added support for RCMD command (pass-through)
 *
 * Revision 1.10.2.1  2003/05/07 11:10:45  mt
 * - fixed user magic parsing to allow emal-address in
 *   username while @ is used as user magic separator
 * - added ForceUserMagic config variable to enforce
 *   host[:port] presence in FTP USER command
 *
 * Revision 1.10  2002/05/02 13:16:35  mt
 * implemented simple (ldap based) user auth
 *
 * Revision 1.9.2.1  2002/04/04 14:23:32  mt
 * improved transparent proxy log messages
 *
 * Revision 1.9  2002/01/14 19:39:30  mt
 * implemented workarround for Netscape (4.x) directory symlink handling
 * changed to socket_orgdst usage to get ransparent proxy destination
 *
 * Revision 1.8  2001/11/06 23:04:44  mt
 * applied / merged with transparent proxy patches v8
 * see ftp-proxy/NEWS for more detailed release news
 *
 * Revision 1.7  1999/10/19 10:19:15  wiegand
 * use port range also for control connection to server
 *
 * Revision 1.6  1999/09/30 09:48:36  wiegand
 * added global RegEx check for USER command
 * added dynamic TranslatedAddress via file
 *
 * Revision 1.5  1999/09/24 06:38:52  wiegand
 * added regular expressions for all commands
 * removed character map and length of paths
 * added flag to reset PASV on every PORT
 * added "magic" user with built-in destination
 * added some argument pointer fortification
 *
 * Revision 1.4  1999/09/21 07:13:34  wiegand
 * syslog / abort cleanup and review
 * remove previous PASV socket
 *
 * Revision 1.3  1999/09/17 16:32:29  wiegand
 * changes from source code review
 * added POSIX regular expressions
 *
 * Revision 1.2  1999/09/16 16:29:57  wiegand
 * minor updates improving code quality
 *
 * Revision 1.1  1999/09/15 14:06:22  wiegand
 * initial checkin
 *
 * ------------------------------------------------------------ */

