/*
 * $Id: ftp-client.c,v 1.5 1999/09/24 06:38:52 wiegand Exp $
 *
 * Functions for the FTP Proxy client 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-client.c,v 1.5 1999/09/24 06:38:52 wiegand Exp $";
#endif

#include <config.h>

#if defined(STDC_HEADERS)
#  include <stdio.h>
#  include <string.h>
#  include <stdlib.h>
#  include <stdarg.h>
#  include <errno.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 <signal.h>

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

#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"
#include "ftp-ldap.h"


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

static void client_cli_ctrl_read(char *str);
static void client_srv_ctrl_read(char *str);
static void client_srv_passive  (char *arg);
static void client_xfer_fireup  (void);


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

static int close_flag  = 0;	/* Program termination request	*/

static CONTEXT ctx;		/* The general client context	*/


/* ------------------------------------------------------------ **
**
**	Function......:	client_signal
**
**	Parameters....:	signo		Signal to be handled
**
**	Return........:	(most probably, none)
**
**	Purpose.......: Handler for signals, mainly killing.
**
** ------------------------------------------------------------ */

static RETSIGTYPE client_signal(int signo)
{
#if defined(COMPILE_DEBUG)
	debug(2, "client signal %d", signo);
#endif

	close_flag  = 1;

	signal(signo, client_signal);
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_run
**
**	Parameters....:	(stdin/stdout is the User-PI)
**
**	Return........:	(none)
**
**	Purpose.......: Main function for client operation.
**
** ------------------------------------------------------------ */

void client_run(void)
{
	int sock, need;
	char str[1024], *p, *q;
	FILE *fp;
	BUF *buf;
	time_t diff;

	/*
	** Setup client signal handling (mostly graceful exit)
	*/
	signal(SIGINT,  client_signal);
	signal(SIGTERM, client_signal);
	signal(SIGQUIT, client_signal);
	signal(SIGHUP,  client_signal);
	signal(SIGCHLD, SIG_IGN);
	signal(SIGUSR1, SIG_IGN);

	/*
	** Prepare our general client context
	*/
	memset(&ctx, 0, sizeof(ctx));
	ctx.cli_mode = MOD_ACT_FTP;
	ctx.expect   = EXP_IDLE;
	ctx.timeout  = config_int(NULL, "TimeOut", 900);

	sock = fileno(stdin);		/* "recover" our socket */

	/*
	** Check whether a DenyMessage file exists. This
	** indicates that we are currently not willing
	** to serve any clients.
	*/
	p = config_str(NULL, "DenyMessage", NULL);
	if (p != NULL && (fp = fopen(p, "r")) != NULL) {
		while (fgets(str, sizeof(str) - 4, fp) != NULL) {
			p = socket_msgline(str);
			if ((q = strchr(p, '\n')) != NULL)
				strcpy(q, "\r\n");
			else
				strcat(p, "\r\n");
			send(sock, "421-", 4, 0);
			send(sock, p, strlen(p), 0);
		}
		fclose(fp);
		if ((p = config_str(NULL, "DenyString", NULL)) != NULL)
			p = socket_msgline(p);
		else
			p = "Service not available";
		send(sock, "421 ", 4, 0);
		send(sock, p, strlen(p), 0);
		send(sock, ".\r\n", 3, 0);
		p = socket_addr2str(socket_sck2addr(sock, REM_END, NULL));
		close(sock);
		syslog_write(U_ERR, "reject: '%s' (DenyMessage)", p);
		exit(EXIT_SUCCESS);
	}

	/*
	** Create a High Level Socket for the client's User-PI
	*/
	if ((ctx.cli_ctrl = socket_init(sock)) == NULL)
		misc_die(FL, "client_run: ?cli_ctrl?");
	ctx.cli_ctrl->ctyp = "Cli-Ctrl";

	/*
	** Announce the connection request
	*/
	syslog_write(U_INF, "connect from %s", ctx.cli_ctrl->peer);

	/*
	** Display the welcome message (invite the user to login)
	*/
	if ((p = config_str(NULL, "WelcomeString", NULL)) == NULL)
		p = "%h FTP server (Version %v - %b) ready";
	strcpy(str, socket_msgline(p));
	client_respond(220,
		config_str(NULL, "WelcomeMessage", NULL), str);

	/*
	** See if we need to change our root directory
	*/
	chdir("/");
	if ((p = config_str(NULL, "ServerRoot", NULL)) != NULL) {
		if (chroot(p)) {
			syslog_error("can't chroot to '%.1024s'", p);
			exit(EXIT_FAILURE);
		}
		chdir("/");
	}

	/*
	** Enter the client mainloop
	*/
	while (close_flag == 0) {
		/*
		** We need to go into select() only
		** if all input has been processed.
		** (Data buffers are never split.)
		*/
		need = 1;
		if (ctx.cli_ctrl && ctx.cli_ctrl->rbuf)
			need = 0;
		if (ctx.srv_ctrl && ctx.srv_ctrl->rbuf)
			need = 0;
		if (need != 0) {
			if (socket_exec(ctx.timeout, &close_flag) <= 0)
				break;	/* Timed out or worse */
		}
#if defined(COMPILE_DEBUG)
		debug(4, "client-loop ...");
#endif

		/*
		** Check if any zombie sockets can be removed
		*/
		if (ctx.cli_ctrl != NULL && ctx.cli_ctrl->sock == -1)
			close_flag = 1;		/* Oops, forget it ... */

		if (ctx.srv_ctrl != NULL && ctx.srv_ctrl->sock == -1) {
#if defined(COMPILE_DEBUG)
			debug(3, "about to destroy Srv-Ctrl");
#endif
			/*
			** If we have any open data connections,
			** make really sure they don't survive.
			*/
			if (ctx.cli_data != NULL)
				ctx.cli_data->kill = 1;
			if (ctx.srv_data != NULL)
				ctx.srv_data->kill = 1;

			/*
			** Our client should be informed
			*/
			if (ctx.cli_ctrl->kill == 0) {
				client_respond(421, NULL,
					"Service not available, "
					"closing control connection");
			}

			/*
			** Don't forget to remove the dead socket
			*/
			socket_kill(ctx.srv_ctrl);
			ctx.srv_ctrl = NULL;
		}

		if (ctx.cli_data != NULL && ctx.cli_data->sock == -1) {
#if defined(COMPILE_DEBUG)
			debug(3, "about to destroy Cli-Data");
#endif
			/*
			** If we have an outstanding server reply
			** (e.g. 226 Transfer complete), send it.
			** If we have not, it will come sooner or
			** later. Then just send it to the client
			** at the time it arrives.
			*/
			if (ctx.xfer_rep[0] != '\0') {
				socket_printf(ctx.cli_ctrl,
					"%s\r\n", ctx.xfer_rep);
				memset(ctx.xfer_rep, 0,
					sizeof(ctx.xfer_rep));
			} else if (ctx.expect == EXP_XFER)
				ctx.expect = EXP_PTHR;

			/*
			** Good time for statistics and data reset
			*/
			if ((diff = time(NULL) - ctx.xfer_beg) < 1)
				diff = 1;
			syslog_write(U_INF,
				"Transfer for %s: %s '%s' %d sec",
				ctx.cli_ctrl->peer, ctx.xfer_cmd,
				ctx.xfer_arg, (int) diff);
			client_data_reset();

			/*
			** Doom the corresponding server socket
			*/
			if (ctx.srv_data != NULL)
				ctx.srv_data->kill = 1;

			/*
			** Don't forget to remove the dead socket
			*/
			socket_kill(ctx.cli_data);
			ctx.cli_data = NULL;
		}

		if (ctx.srv_data != NULL && ctx.srv_data->sock == -1) {
#if defined(COMPILE_DEBUG)
			debug(3, "about to destroy Srv-Data");
#endif
			/*
			** Doom the corresponding client socket
			*/
			if (ctx.cli_data != NULL)
				ctx.cli_data->kill = 1;

			/*
			** Don't forget to remove the dead socket
			*/
			socket_kill(ctx.srv_data);
			ctx.srv_data = NULL;
		}

		/*
		** Serve the control connections
		*/
		if (ctx.cli_ctrl != NULL && ctx.cli_ctrl->rbuf != NULL) {
			if (socket_gets(ctx.cli_ctrl,
					str, sizeof(str)) != NULL)
				client_cli_ctrl_read(str);
		}
		if (ctx.srv_ctrl != NULL && ctx.srv_ctrl->rbuf != NULL) {
			if (socket_gets(ctx.srv_ctrl,
					str, sizeof(str)) != NULL)
				client_srv_ctrl_read(str);
		}

		/*
		** Serve the data connections. This is a bit tricky,
		** since all we do is move the buffer pointers.
		*/
		if (ctx.cli_data != NULL && ctx.srv_data != NULL) {
			if (ctx.cli_data->rbuf != NULL) {
#if defined(COMPILE_DEBUG)
				debug(2, "Cli-Data -> Srv-Data");
#endif
				if (ctx.srv_data->wbuf == NULL) {
					ctx.srv_data->wbuf =
						ctx.cli_data->rbuf;
				} else {
					for (buf = ctx.srv_data->wbuf;
							buf && buf->next;
							buf = buf->next)
						;
					buf->next = ctx.cli_data->rbuf;
				}
				ctx.cli_data->rbuf = NULL;
			}
			if (ctx.srv_data->rbuf != NULL) {
#if defined(COMPILE_DEBUG)
				debug(2, "Srv-Data -> Cli-Data");
#endif
				if (ctx.cli_data->wbuf == NULL) {
					ctx.cli_data->wbuf =
						ctx.srv_data->rbuf;
				} else {
					for (buf = ctx.cli_data->wbuf;
							buf && buf->next;
							buf = buf->next)
						;
					buf->next = ctx.srv_data->rbuf;
				}
				ctx.srv_data->rbuf = NULL;
			}
		}
		/* at this point the main loop resumes ... */
	}

	/*
	** Free allocated memory
	*/
	if (ctx.username != NULL)
		misc_free(FL, ctx.username);

#if defined(COMPILE_DEBUG)
	debug(1, "}}}}} %s client-exit", misc_getprog());
#endif
	exit(EXIT_SUCCESS);
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_cli_ctrl_read
**
**	Parameters....:	str		Buffer with data
**
**	Return........:	(none)
**
**	Purpose.......: Decode and execute client PI commands.
**
** ------------------------------------------------------------ */

static void client_cli_ctrl_read(char *str)
{
	char *arg;
	CMD *cmd;
	int c;

	if (str == NULL) {		/* Basic sanity check	*/
#if defined(COMPILE_DEBUG)
		debug(2, "null User-PI msg: nothing to do");
#endif
		return;
	}

	/*
	** Handle a minimum amount of Telnet line control
	*/
	while ((arg = strchr(str, IAC)) != NULL) {
		c = (arg[1] & 255);
		switch (c) {
			case WILL:
			case WONT:
				/*
				** RFC 1123, 4.1.2.12
				*/
				syslog_write(U_WRN,
					"WILL/WONT refused for %s",
					ctx.cli_ctrl->peer);
				socket_printf(ctx.cli_ctrl,
					"%c%c%c", IAC, DONT, arg[2]);
				memmove(arg, arg + 3, strlen(arg) - 2);
				break;

			case DO:
			case DONT:
				/*
				** RFC 1123, 4.1.2.12
				*/
				syslog_write(U_WRN,
					"DO/DONT refused for %s",
					ctx.cli_ctrl->peer);
				socket_printf(ctx.cli_ctrl,
					"%c%c%c", IAC, WONT, arg[2]);
				memmove(arg, arg + 3, strlen(arg) - 2);
				break;

			case IAC:
				memmove(arg, arg + 1, strlen(arg));
				break;

			case IP:
			case DM:
				syslog_write(U_INF, "IAC-%s from %s",
						(c == IP) ? "IP" : "DM",
						ctx.cli_ctrl->peer);
				memmove(str, arg + 2, strlen(arg) + 1);
				break;

			default:
				memmove(arg, arg + 2, strlen(arg) + 1);
		}
	}

	/*
	** If there is nothing left to process, please call again
	*/
	if (str[0] == '\0') {
#if defined(COMPILE_DEBUG)
		debug(2, "empty User-PI msg: nothing to do");
#endif
		return;
	}

	/*
	** Separate arguments if given
	*/
	if ((arg = strchr(str, ' ')) == NULL)
		arg = strchr(str, '\t');
	if (arg == NULL)
		arg = "";
	else {
		while (*arg == ' ' || *arg == '\t')
			*arg++ = '\0';
	}

#if defined(COMPILE_DEBUG)
	debug(1, "from User-PI (%d): cmd='%.32s' arg='%.512s'",
				ctx.cli_ctrl->sock, str, NIL(arg));
#endif

	/*
	** Try to execute the given command. The "USER" command
	**   must be enabled in any case, since it's the one to
	**   setup allow/deny (let's call it bootstrapping) ...
	*/
	for (cmd = cmds_get_list(); cmd->name != NULL; cmd++) {
		if (strcasecmp("USER", cmd->name) == 0)
			cmd->legal = 1;		/* Need this one! */
		if (strcasecmp(str, cmd->name) != 0)
			continue;
		if (cmd->legal == 0) {
			client_respond(502, NULL, "'%.32s': "
				"command not implemented", str);
			syslog_write(U_WRN,
				"'%.32s' from %s not allowed",
				str, ctx.cli_ctrl->peer);
			return;
		}
#if defined(HAVE_REGEX)
		if (cmd->regex != NULL) {
			char *p;
			p = cmds_reg_exec(cmd->regex, arg);
			if (p != NULL) {
				client_respond(501, NULL,
					"'%.32s': syntax error "
					"in arguments", str);
				syslog_write(U_WRN,
					"bad arg '%.128s'%s for "
					"'%s' from %s: %s", arg,
					(strlen(arg) > 128) ?
					"..." : "", cmd->name,
					ctx.cli_ctrl->peer, p);
				return;
			}
		}
#endif
		ctx.curr_cmd = str;
		(*cmd->func)(&ctx, arg);
		return;
	}

	/*
	** Arriving here means the command was not found...
	*/
	client_respond(500, NULL, "'%.32s': command unrecognized", str);
	syslog_write(U_WRN, "unknown '%.32s' from %s",
					str, ctx.cli_ctrl->peer);
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_srv_ctrl_read
**
**	Parameters....:	str		Buffer with data
**
**	Return........:	(none)
**
**	Purpose.......: Decode and execute server responses.
**
** ------------------------------------------------------------ */

static void client_srv_ctrl_read(char *str)
{
	int code, c1, c2, c3;
	char *arg;

	if (str == NULL)		/* Basic sanity check	*/
		return;

#if defined(COMPILE_DEBUG)
	debug(1, "from Server-PI (%d): '%.512s'",
					ctx.srv_ctrl->sock, str);
#endif

	/*
	** Intermediate responses can usually be forwarded
	*/
	if (*str < '2' || *str > '5' || str[3] != ' ') {
		/*
		** If this is the destination host's
		** welcome message let's discard it.
		*/
		if (ctx.expect == EXP_CONN)
			return;

#if defined(COMPILE_DEBUG)
		debug(2, "'%.4s'... forwarded to %s %d=%s", str,
			ctx.cli_ctrl->ctyp, ctx.cli_ctrl->sock,
			ctx.cli_ctrl->peer);
#endif
		socket_printf(ctx.cli_ctrl, "%s\r\n", str);
		return;
	}

	/*
	** Consider only valid final response codes
	*/
	if ((code = atoi(str)) < 200 || code > 599) {
		syslog_error("bad response %d from server for %s",
					code, ctx.srv_ctrl->peer);
		return;
	}
	c1 =  code / 100;
	c2 = (code % 100) / 10;
	c3 =  code % 10;
	for (arg = str + 3; *arg == ' '; arg++)
		;

	/*
	** We have a response code, go see what we expected
	*/
	switch (ctx.expect) {
		case EXP_CONN:
			/*
			** Waiting for a 220 Welcome
			*/
			if (c1 == 2) {
				socket_printf(ctx.srv_ctrl,
					"USER %s\r\n", ctx.username);
				ctx.expect = EXP_USER;
			} else {
				socket_printf(ctx.cli_ctrl,
						"%s\r\n", str);
				ctx.expect = EXP_IDLE;
				ctx.cli_ctrl->kill = 1;
			}
			break;

		case EXP_USER:
			/*
			** Only the following are useful:
			** 230=logged in, 331=need password
			*/
			socket_printf(ctx.cli_ctrl, "%s\r\n", str);
			if (c1 != 2 && c1 != 3)
				ctx.cli_ctrl->kill = 1;
			ctx.expect = EXP_IDLE;
			break;

		case EXP_PASV:
			if (code == 227 && *arg != '\0')
				client_srv_passive(arg);
			else {
				socket_printf(ctx.cli_ctrl,
						"%s\r\n", str);
				client_data_reset();
				ctx.expect = EXP_IDLE;
			}
			break;

		case EXP_PORT:
			if (code == 200)
				client_xfer_fireup();
			else {
				socket_printf(ctx.cli_ctrl,
						"%s\r\n", str);
				client_data_reset();
				ctx.expect = EXP_IDLE;
			}
			break;

		case EXP_XFER:
			/*
			** Distinguish between success and failure
			*/
			if (c1 == 2) {
				misc_strncpy(ctx.xfer_rep, str,
					sizeof(ctx.xfer_rep));
			} else {
				socket_printf(ctx.cli_ctrl,
						"%s\r\n", str);
				client_data_reset();
			}
			ctx.expect = EXP_IDLE;
			break;

		case EXP_ABOR:
			if (c1 == 2) {
				client_data_reset();
				ctx.expect = EXP_IDLE;
			}
			break;

		case EXP_PTHR:
			socket_printf(ctx.cli_ctrl, "%s\r\n", str);
			ctx.expect = EXP_IDLE;
			break;

		case EXP_IDLE:
			socket_printf(ctx.cli_ctrl, "%s\r\n", str);
			if (code == 421) {
				syslog_write(T_WRN,
					"server closed connection "
					"for %s", ctx.cli_ctrl->peer);
				ctx.cli_ctrl->kill = 1;
			}else {
				syslog_write(T_WRN,
					"bogus '%.512s' from "
					"Server-PI for %s",
					ctx.cli_ctrl->peer, str);
			}
			break;
	}
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_srv_passive
**
**	Parameters....:	arg		Command argument(s)
**
**	Return........:	(none)
**
**	Purpose.......: Evaluate the 227 response from the server.
**
** ------------------------------------------------------------ */

static void client_srv_passive(char *arg)
{
	int h1, h2, h3, h4, p1, p2;
	u_int32_t addr, ladr;
	u_int16_t port;

	if (arg == NULL)		/* Basic sanity check	*/
		return;

	/*
	** Read the port. According to RFC 1123, 4.1.2.6,
	** we have to scan the string for the first digit.
	*/
	while (*arg < '0' || *arg > '9')
		arg++;
	if (sscanf(arg, "%d,%d,%d,%d,%d,%d",
			&h1, &h2, &h3, &h4, &p1, &p2) != 6) {
		syslog_error("bad 227 from server for %s",
						ctx.cli_ctrl->peer);
		client_respond(425, NULL, "Can't open data connection");
		client_data_reset();
		ctx.expect = EXP_IDLE;
		return;
	}
	addr = (u_int32_t) ((h1 << 24) + (h2 << 16) + (h3 << 8) + h4);
	port = (u_int16_t) ((p1 <<  8) +  p2);

	/*
	** Open a connection to the server at the given port
	*/
	ladr = socket_sck2addr(ctx.srv_ctrl->sock, LOC_END, NULL);
	if (socket_d_connect(addr, port, ladr, ctx.srv_lrng,
			ctx.srv_urng, &(ctx.srv_data), "Srv-Data") == 0) {
		syslog_error("can't connect Srv-Data for %s",
						ctx.cli_ctrl->peer);
		client_respond(425, NULL, "Can't open data connection");
		client_data_reset();
		ctx.expect = EXP_IDLE;
		return;
	}

	/*
	** Finally send the original command from the client
	*/
	client_xfer_fireup();
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_xfer_fireup
**
**	Parameters....:	(none)
**
**	Return........:	(none)
**
**	Purpose.......: Send a deferred transfer command to the
**			server if applicable. If the transfer
**			to the client is ACTIVE, we also need to
**			connect() to the client.
**
** ------------------------------------------------------------ */

static void client_xfer_fireup(void)
{
	u_int32_t ladr;

	/*
	** If appropriate, connect to the client's data port
	*/
	if (ctx.cli_mode == MOD_ACT_FTP) {
		ladr = socket_sck2addr(ctx.cli_ctrl->sock,
						LOC_END, NULL);
		if (socket_d_connect(ctx.cli_addr, ctx.cli_port,
				ladr, ctx.act_lrng, ctx.act_urng,
				&(ctx.cli_data), "Cli-Data") == 0) {
			syslog_error("can't connect Cli-Data for %s",
						ctx.cli_ctrl->peer);
			client_respond(425, NULL,
					"Can't open data connection");
			client_data_reset();
			ctx.expect = EXP_IDLE;
			return;
		}
	}

	/*
	** Send the original command from the client
	*/
	if (ctx.xfer_arg[0] != '\0') {
		socket_printf(ctx.srv_ctrl, "%s %s\r\n",
				ctx.xfer_cmd, ctx.xfer_arg);
		syslog_write(T_INF, "'%s %s' sent for %s",
			ctx.xfer_cmd, ctx.xfer_arg, ctx.cli_ctrl->peer);
	} else {
		socket_printf(ctx.srv_ctrl, "%s\r\n", ctx.xfer_cmd);
		syslog_write(T_INF, "'%s' sent for %s",
			ctx.xfer_cmd, ctx.cli_ctrl->peer);
	}

	/*
	** Prepare the handling and statistics buffers
	*/
	memset(ctx.xfer_rep, 0, sizeof(ctx.xfer_rep));
	ctx.xfer_beg = time(NULL);
	ctx.xfer_cnt = 0;

	ctx.expect = EXP_XFER;		/* Expect 226 complete	*/
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_respond
**
**	Parameters....:	code		Return code
**			file		Optional file with info
**			fmt		Format string for output
**
**	Return........:	(none)
**
**	Purpose.......: Decode and execute server responses.
**
** ------------------------------------------------------------ */

void client_respond(int code, char *file, char *fmt, ...)
{
	va_list aptr;
	char str[1024], *p, *q;
	FILE *fp;

	/*
	** Display additional info from file if found
	*/
	if (file != NULL && (fp = fopen(file, "r")) != NULL) {
		while (fgets(str, sizeof(str), fp) != NULL) {
			p = socket_msgline(str);
			if ((q = strchr(p, '\n')) != NULL)
				*q = '\0';
			socket_printf(ctx.cli_ctrl,
					"%03d-%s\r\n", code, p);
		}
		fclose(fp);
	}

	/*
	** The last line carries the ultimate reponse code
	*/
	va_start(aptr, fmt);
	vsprintf(str, fmt, aptr);
	va_end(aptr);
	socket_printf(ctx.cli_ctrl, "%03d %s.\r\n", code, str);
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_reinit
**
**	Parameters....:	(none)
**
**	Return........:	(none)
**
**	Purpose.......: Hard-Reset data and server connections.
**
** ------------------------------------------------------------ */

void client_reinit(void)
{
	/*
	** Remove any server or data 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_kill(ctx.srv_ctrl);
		ctx.srv_ctrl = NULL;
	}
	client_data_reset();

	/*
	** Remove the current user and status
	*/
	if (ctx.username != NULL) {
		misc_free(FL, ctx.username);
		ctx.username = NULL;
	}
	ctx.expect = EXP_IDLE;
}


/* ------------------------------------------------------------ **
**
**	Function......:	client_data_reset
**
**	Parameters....:	(none)
**
**	Return........:	(none)
**
**	Purpose.......: Reset variables for data transfer.
**
** ------------------------------------------------------------ */

void client_data_reset(void)
{
	memset(ctx.xfer_cmd, 0, sizeof(ctx.xfer_cmd));
	memset(ctx.xfer_arg, 0, sizeof(ctx.xfer_arg));
	ctx.xfer_beg = 0;
	ctx.xfer_cnt = 0;

	ctx.cli_mode = MOD_ACT_FTP;

	if (ctx.cli_ctrl != NULL) {
		ctx.cli_addr = ctx.cli_ctrl->addr;
		ctx.cli_port = ctx.cli_ctrl->port;
	}
}


/* ------------------------------------------------------------
 * $Log: ftp-client.c,v $
 * 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:07  wiegand
 * syslog / abort cleanup and review
 *
 * 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
 *
 * ------------------------------------------------------------ */

