/* 
 * Copyright (C) 1999-2004 Joachim Wieland <joe@mcknight.de>
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place - Suite 330, Boston, MA 02111, USA.
 */

#include "jftpgw.h"
#include "fw_auth_cmds.h"
#include <sys/mman.h>

#define ANON_USERS " anonymous ftp "

extern int timeout;
extern struct log_cmd_st lcs;
extern struct hostent_list* hostcache;
extern struct conn_info_st conn_info;
extern struct serverinfo srvinfo;

static int login_setforward_user(struct clientinfo*);
static int login_setforward_pass(struct clientinfo*);
static int login_connect(struct clientinfo*);
static int login_mayconnect(struct clientinfo*);
static int login_init_connection(struct clientinfo*);
static int login_connected_setup(struct clientinfo*);

static int login_auth(struct clientinfo*);
static int login_readwelcome(struct clientinfo*);
static int login_sendauth_user(struct clientinfo*);
static int login_sendauth_pass(struct clientinfo*);
static int login_finish_login(struct clientinfo*);
static int login_loggedin_setup(struct clientinfo*);

static int login_failed(struct clientinfo*);


int handle_login(struct clientinfo* clntinfo) {
	struct cmdhandlerstruct *cmdhandler;
	char *buffer = 0;
	int ss, cs;
	int i, expected;
	int protoviolations = 0;

	conn_info.lcs = &lcs;
	conn_info.clntinfo = clntinfo;
	ss = clntinfo->serversocket;
	cs = clntinfo->clientsocket;

	if (clntinfo->transparent == TRANSPARENT_YES
		/* we are connected */
		&&
	    config_compare_option("logintime", "connect")) {

		if (config_get_ioption("loginstyle", 0) != 0) {
			jlog(5, "A login at the connection time only works with loginstyle == 0, setting loginstyle = 0");

			config_option_list_delete("loginstyle");
			config_option_list_add("loginstyle", "0");
		}
	}

	cmdhandler = &login_auth_funcs
				[ config_get_ioption("loginstyle", 0) ][0];

	expected = QUITFUNC + 1;    /* skip reset and quit function */
	while (1) {
		if (timeout) {
			jlog(2, "Timeout in %s line %d\n", __FILE__ ,__LINE__);
			return -1;
		}
		if (buffer) {
			free(buffer);
			buffer = 0;
		}

		buffer = readline(cs);
		lcs.cmd = (char*) 0;

		if (buffer) {
			lcs.cmd = buffer;
			free(lcs.method);
			i = 0;
			lcs.method = quotstrtok(lcs.cmd, WHITESPACES, &i);

			if (buffer[0] == '\0') {
				/* empty line. Prevent logging of the
				 * command */
				free(buffer);
				buffer = 0;
				continue;
			}
			/* log the command */
			log_cmd(&lcs);
			free(lcs.method); lcs.method = (char*) 0;
			lcs.respcode = 0;
			lcs.transferred = 0;
		}

		if (!buffer) {
			if (timeout) {
				jlog(2, "Timeout in %s line %d\n", __FILE__
						,__LINE__);
				err_time_readline(cs);
			} else {
				err_readline(cs);
			}
			return -1;
		}

		jlog(8, "Expecting %s", cmdhandler[expected].cmd);
		if (my_strcasestr(buffer, "PASS") == (char*) 0) {
			jlog(8, "Got: %s", buffer);
		} else {
			jlog(8, "Got the password");
		}

		/* check for QUIT */
		if (checkbegin(buffer, "QUIT")) {
			int ret = (cmdhandler[QUITFUNC].func)
						(buffer, &conn_info);
			ret = (cmdhandler[RESETFUNC].func)
						(buffer, &conn_info);
			free(buffer);
			/* return 1 to prevent the proxy from entering
			 * handle_cmds */
			return 1;
		}
		if (cmdhandler[expected].cmd 
			&& checkbegin(buffer, cmdhandler[expected].cmd)) {

			int ret = (cmdhandler[expected].func)
						(buffer, &conn_info);
			memset(buffer, 0, strlen(buffer));
			protoviolations = 0;
			switch (ret) {
				case CMD_DONE:
					/* we're done - logged in */
					/* CMD_DONE is returned if the USER
					 * command got a 230 response back
					 * */
					break;
				case CMD_HANDLED:
					/* expecting the next */
					expected++;
					break;
				case CMD_ERROR:
					/* reset counter, skip reset and
					 * quit function */
					ret = (cmdhandler[RESETFUNC].func)
							(buffer, &conn_info);
					expected = QUITFUNC + 1;
					/* errors are handled by the
					 * command handler functions
					 * */
					break;
				case CMD_ABORT:
					return -1;
					break;
			}
			/* found and called proper function */
		} else {
			protoviolations++;
			if (protoviolations >=
			    config_get_ioption("loginprotocolviolations", 10)){
				/* like ABORT */
				sayf(clntinfo->clientsocket,
					"500 Too many consequent protocol "
					"violations - Closing connection\r\n");
				return -1;
			}
			sayf(clntinfo->clientsocket,
				"530 Login incorrect. Expected %scommand\r\n",
				cmdhandler[expected].cmd);
			/* reset counter, skip reset and quit function */
			(cmdhandler[RESETFUNC].func)
						(buffer, &conn_info);
			expected = QUITFUNC + 1;
		}
		if (clntinfo->login.stage == LOGIN_ST_FULL) {
			/* we are done */
			free(buffer); buffer = (char*) 0;
			return 0;
		}
	}
	/* lcs.host and lcs.user  are freed at the termination of the
	 * programm */
}

int login(struct clientinfo* clntinfo, int stage) {
	int ret = 0;

	if (! clntinfo->user && stage >= LOGIN_ST_USER) {
		say(clntinfo->clientsocket, "500 Error logging in\r\n");
		jlog(4, "No user was set - Cannot proceed");
		return CMD_ERROR;
	}

	if ((ret = login_setforward_user(clntinfo)) < 0) {
		/* the error is logged and say()ed */
		return CMD_ERROR;
	}

	if (! clntinfo->destination) {
		say(clntinfo->clientsocket, "500 Error logging in\r\n");
		jlog(4, "No destination was set - Cannot proceed");
		if (config_compare_option("logintime", "connect")) {
			jlog(4, "This may be because of your logintime --> connect setting");
		}
		return CMD_ERROR;
	}

	if (stage >= LOGIN_ST_CONNECTED) {
		ret = login_connect(clntinfo);
		if (ret) { return ret; }
	}
	if (stage >= LOGIN_ST_USER) {
		ret = login_sendauth_user(clntinfo);
		if (ret) { return ret; }
	}
	if (stage >= LOGIN_ST_FULL) {
		ret = login_connect(clntinfo);
		if (ret) { return ret; }
		ret = login_setforward_pass(clntinfo);
		if (ret) { return ret; }
		ret = login_auth(clntinfo);
		if (ret) {
			int ret2;
			if ((ret2 = login_failed(clntinfo)) < 0) {
				return ret2;
			}
			return ret;
		} else {
			config_destroy_sectionconfig();
		}
		ret = login_loggedin_setup(clntinfo);
		if (ret) { return ret; }
	}
	return CMD_HANDLED;
}


static
int login_connect(struct clientinfo* clntinfo) {
	int ret;

	if (clntinfo->login.stage >= LOGIN_ST_CONNECTED) {
		return CMD_HANDLED;
	}

	if ((ret = login_init_connection(clntinfo)) < 0) {
		/* the error is logged and say()ed */
		return ret;
	}

	if ((ret = login_connected_setup(clntinfo)) < 0) {
		/* the error is logged and say()ed */
		return ret;
	}

	clntinfo->login.stage = LOGIN_ST_CONNECTED;
	return CMD_HANDLED;
}


static
int login_auth(struct clientinfo* clntinfo) {
	int ret;

	if (clntinfo->login.stage >= LOGIN_ST_LOGGEDIN) {
		return CMD_HANDLED;
	}

	if (clntinfo->login.stage < LOGIN_ST_USER) {
		if ((ret = login_sendauth_user(clntinfo)) < 0) {
			clntinfo->login.stage = LOGIN_ST_CONNECTED;
			return ret;
		}
		clntinfo->login.stage = LOGIN_ST_USER;
	}

	if ((ret = login_sendauth_pass(clntinfo)) < 0) {
		clntinfo->login.stage = LOGIN_ST_CONNECTED;
		return ret;
	}

	if ((ret = login_finish_login(clntinfo)) < 0) {
		clntinfo->login.stage = LOGIN_ST_CONNECTED;
		return ret;
	}

	return CMD_HANDLED;
}


static
int login_mayconnect(struct clientinfo* clntinfo) {
	int allowed = 0;
	unsigned long int target;
	unsigned long int host_ip;
	unsigned long int client_ip, server_ip;
	int tags;

	allowed = 0;

	/* we don't know yet if clntinfo->destination is a hostname like
	 * "somehost.foo.com" or if it is an IP in a char* like
	 * "212.117.232.20"
	 *
	 * So call inet_addr(), it should make the decision  :-)
	 *
	 * */
	target = inet_addr(clntinfo->destination);

	/* get the client_ip by asking information about the socket the
	 * client is connected */
	client_ip = get_uint_ip(GET_IP_CLIENT, clntinfo);
	server_ip = (unsigned long int) UINT_MAX;

	if (target == (unsigned long int) UINT_MAX) {
		/* clntinfo->destination may be a hostname, but it needn't.
		 * It may also be an invalid IP: "393.39.239.500" or
		 * anything else. Look it up.
		 * */
		host_ip = hostent_get_ip(&hostcache, clntinfo->destination);
		if (host_ip != (unsigned long int) UINT_MAX) {
			/* successful lookup */
			server_ip = host_ip;
		} else {
			/* it's not a valid IP and we could not look it up,
			 * so we could not get valid information about the
			 * destination host */
			jlog(7, "Nonsense destination (no IP and could not look up hostname): %s",
					clntinfo->destination);
			jlog(8, "Please check your nameserver configuration. This may also happen if your chroot-environment does not contain the necessary files which the libc needs for a lookup");
			allowed = 0;
		}
	} else {
		/* okay, it's an IP */
		server_ip = inet_addr(clntinfo->destination);
	}

	if (server_ip != (unsigned long) UINT_MAX) {
		/* we could get a valid IP, now we can evaluate if client
		 * is allowed to connect to the destination host */
		jlog(9, "Checking all tags");
		tags = TAG_ALL_NOT_FORWARDED;
		if (clntinfo->before_forward.user) {
			/* if we are treating a forward, add this value, it
			 * won't come up in the option list otherwise */
			tags |= TAG_FORWARDED;
		}
		/* checking of the configuration works such:
		 *
		 * no forward:   do not evaluate <forwarded>
		 *               always check the normal values
		 *
		 * forward:      evaluate <forwarded>
		 *               if (!in_forwarded_tag) {
		 *     (1)             check before_forward.* values
		 *               } else {
		 *     (2)             check normal values
		 *               }
		 */
		if (clntinfo->before_forward.user) {
			/* Yes, there was a forward applied */
			config_shrink_config(client_ip,
				/* this are the original values */
				/* see above (1) */
				clntinfo->before_forward.dest_ip,
				clntinfo->before_forward.destination,
				clntinfo->before_forward.destinationport,
				clntinfo->before_forward.user,
				/* these are the values set by the forward */
				/* pass them as well, if we are in the
				 * forwarded_tag, config_match_section()
				 * will overwrite the previous with the
				 * following values */
				server_ip,
				clntinfo->destination,
				clntinfo->destinationport,
				clntinfo->user,
				0,		/* set no specific time */
				clntinfo->proxy_ip,
				clntinfo->proxy_port,
				srvinfo.servertype,
				&hostcache,
				tags);
		} else {
			/* not a forward */
			config_shrink_config(client_ip,
				server_ip,
				clntinfo->destination,
				clntinfo->destinationport,
				clntinfo->user,
				clntinfo->before_forward.dest_ip,
				clntinfo->before_forward.destination,
				clntinfo->before_forward.destinationport,
				clntinfo->before_forward.user,
				0,		/* set no specific time */
				clntinfo->proxy_ip,
				clntinfo->proxy_port,
				srvinfo.servertype,
				&hostcache,
				tags);
		}

		allowed = strcmp(config_get_option("access"), "allow") == 0;
	}

	if (!allowed) {
		say(clntinfo->clientsocket, "531 You are not allowed to connect to that host.\r\n");
		jlog(8, "Not allowed to connect to %s", clntinfo->destination);
		lcs.respcode = 531;
		return CMD_ERROR;
	}
	/* if the client was allowed, save the clients original IP */
	if (!clntinfo->before_forward.user) {
		clntinfo->before_forward.dest_ip = server_ip;
	}
	return allowed;  /* a positive int */
}


static
int login_setforward_user(struct clientinfo* clntinfo) {

	const char* forward;
	struct slist_t* forward_list, *forward_list_cur;
	char* tmp;
	int newsize;
	int config_state = TAG_GLOBAL | TAG_FROM | TAG_PORT
			 | TAG_TIME | TAG_SERVERTYPE
			 | TAG_PROXYIP | TAG_PROXYPORT;

	/* this is already a forward */
	if ( clntinfo->before_forward.user ) {
		return CMD_HANDLED;
	}

	if ( clntinfo->destination ) {
		config_state |= TAG_TO;
	}

	if ( clntinfo->user ) {
		config_state |= TAG_USER;
	}

	config_shrink_config(get_uint_ip(GET_IP_CLIENT, clntinfo),
			get_uint_ip(GET_IP_SERVER, clntinfo),
			clntinfo->destination,
			clntinfo->destinationport,
			clntinfo->user,
			-1,          /* before_forward.dest_ip */
			(char*) 0,   /* before_forward.destination */
			0,           /* before_forward.destinationport */
			(char*) 0,   /* before_forward.user */
			0,		/* set no specific time */
			clntinfo->proxy_ip,
			clntinfo->proxy_port,
			srvinfo.servertype,
			&hostcache,
			config_state);

	if (config_compare_option("access", "allow") == 0) {
		say(clntinfo->clientsocket, "531 You are not allowed to "
			"connect to that host. Goodbye.\r\n");
		jlog(5, "%s was not allowed to connect.",
					conv_ip_to_char(clntinfo->client_ip));
		return CMD_ERROR;
	}

	/* see if there is an option for the forward */
	forward = config_get_option("forward");

	if (! forward) {
		/* no forward */
		lcs.userlogin = strnulldup(clntinfo->user);
		lcs.usereffective = strnulldup(clntinfo->user);
		if (lcs.userforwarded) { free(lcs.userforwarded); }
		lcs.userforwarded = strdup("<no forward>");
		enough_mem(lcs.userforwarded);
		return CMD_HANDLED;
	}

	/*
	 * <user ftp>
	 *    forward johnfred@fooserver.com
	 *    forward johnfred@fooserver.com,3949,p
	 *    forward johnfred@fooserver.com     *     johnspass
	 *    forward johnfred@fooserver.com   JiKe94  johnspass
	 * </user>
	 *    forward *@fooserver.com:2378
	 *    forward %@fooserver.com:2378
	 */

	/* split the list into tokens */
	if ( ! (forward_list = config_split_line( forward, WHITESPACES )) ) {
		return CMD_HANDLED;
	}

	/* if we have a defaultforward setting, the variable user has to be
	 * defined so that we can decide if we can use defaultforward at
	 * all. If this is not the case, but there is a defaultforward
	 * setting, skip this whole part. This can happen by setting
	 * logintime to "connect" */
	if (!clntinfo->user &&
	    forward_list->value &&
	    forward_list->value[0] == '%' &&
	    forward_list->value[1] == '@') {
		slist_destroy(forward_list);
		return CMD_HANDLED;
	}

	/* if there is a defaultforward but we already have a destination
	 * set, we can quit here as well */
	if (clntinfo->destination &&
	    forward_list->value &&
	    forward_list->value[0] == '%' &&
	    forward_list->value[1] == '@') {
		/* no forward is being used */
		lcs.userlogin = strnulldup(clntinfo->user);
		lcs.usereffective = strnulldup(clntinfo->user);
		if (lcs.userforwarded) { free(lcs.userforwarded); }
		lcs.userforwarded = strdup("<no forward>");
		enough_mem(lcs.userforwarded);
		slist_destroy(forward_list);
		return CMD_HANDLED;
	}


	forward_list_cur = forward_list;

	/* read and save the values */
	clntinfo->forward.login = strdup(forward_list_cur->value);
	enough_mem(clntinfo->forward.login);

	forward_list_cur = forward_list_cur->next;

	if (forward_list_cur && forward_list_cur->value) {
		clntinfo->forward.accept_pw = strdup(forward_list_cur->value);
		enough_mem(clntinfo->forward.accept_pw);
	} else {
		clntinfo->forward.accept_pw = (char*) 0;
	}

	if (forward_list_cur) {
		forward_list_cur = forward_list_cur->next;
	}
	if (forward_list_cur && forward_list_cur->value) {
		clntinfo->forward.send_pw = strdup(forward_list_cur->value);
		enough_mem(clntinfo->forward.send_pw);
	} else {
		clntinfo->forward.send_pw = (char*) 0;
	}
	/* destroy the list again, we have saved the values into
	 * clntinfo->forward.<field>    */
	slist_destroy(forward_list);

	/* delete those configuration values that are evaluated by the
	 * parsing routine  -  they are not necessary anymore */
	config_option_list_delete("transparent-forward");
	config_option_list_delete("forward");

	/* back up */
	clntinfo->before_forward.user = strnulldup(clntinfo->user);
	clntinfo->before_forward.destination
			= strnulldup(clntinfo->destination);
	clntinfo->before_forward.destinationport = clntinfo->destinationport;
	clntinfo->before_forward.dest_ip = get_uint_ip(GET_IP_SERVER,clntinfo);

	/* special case: We don't have set a username. The client could have
	 * used logintime == connect and has a forward that already matches */
	if ( ! clntinfo->before_forward.user ) {
		clntinfo->before_forward.user = malloc(2);
		enough_mem(clntinfo->before_forward.user);
		clntinfo->before_forward.user[0] = '*';
		clntinfo->before_forward.user[1] = '\0';
	}

	/* if there was only a new destination but no new username given,
	 * prepend the old one */
	if ( ! strchr(clntinfo->forward.login, '@')
			&& clntinfo->before_forward.user) {
		newsize = strlen(clntinfo->before_forward.user)
			  + 1 /* @ */
			  + strlen(clntinfo->forward.login)
			  + 1 /* term */;

		tmp = (char*) malloc(newsize);
		enough_mem(tmp);
		snprintf( tmp, newsize, "%s@%s", clntinfo->before_forward.user,
						 clntinfo->forward.login);
		free(clntinfo->forward.login);
		clntinfo->forward.login = tmp;
	}

	if (clntinfo->forward.login[0] == '*' &&
	    clntinfo->forward.login[1] == '@') {
		/* passauth */
		clntinfo->forward.passauth = 1;
	}

	/* If there is still no destination, see if we have a defaultforward
	 * setting */
	if (clntinfo->destination == (char*) 0 &&
	    clntinfo->user &&
	    clntinfo->forward.login[0] == '%' &&
	    clntinfo->forward.login[1] == '@') {
		/* defaultforward */
		int newsize = strlen(&(clntinfo->forward.login[1])) +
				strlen(clntinfo->user) + 1;
		char* tmp = (char*) malloc(newsize);
		enough_mem(tmp);
		snprintf(tmp, newsize, "%s%s", clntinfo->user,
					&(clntinfo->forward.login[1]));
		free(clntinfo->forward.login);
		clntinfo->forward.login = tmp;
		jlog(8, "No destination was set. Using %s because of defaultforward setting", clntinfo->forward.login);
	}

	/* call the parsing routine and let it set the values */
	if (set_userdest(clntinfo->forward.login, 0, clntinfo, "@,: \t") < 0) {
		return CMD_ERROR;
	}

	/* set values for log info struct */
	lcs.userlogin = strnulldup(clntinfo->before_forward.user);
	lcs.usereffective = strnulldup(clntinfo->user);
	lcs.userforwarded = strnulldup(clntinfo->forward.login);

	/* shrink the configuration again - with TAG_FORWARDED this time */

	config_shrink_config(get_uint_ip(GET_IP_CLIENT, clntinfo),
			/* these are the original values */
			/* see above (1) */
			clntinfo->before_forward.dest_ip,
			clntinfo->before_forward.destination,
			clntinfo->before_forward.destinationport,
			clntinfo->before_forward.user,
			/* these are the values set by the forward */
			/* pass them as well, if we are in the
			 * forwarded_tag, config_match_section()
			 * will overwrite the previous with the
			 * following values */
			get_uint_ip(GET_IP_SERVER, clntinfo),
			clntinfo->destination,
			clntinfo->destinationport,
			clntinfo->user,

			0,		/* set no specific time */

			clntinfo->proxy_ip,
			clntinfo->proxy_port,

			srvinfo.servertype, &hostcache,
			TAG_ALL);

	return CMD_HANDLED;
}


static
int login_setforward_pass(struct clientinfo* clntinfo) {
	/* check if the password matches */
	if (clntinfo->forward.accept_pw) {
		if (strcmp(clntinfo->forward.accept_pw, "*") == 0
			/* allow any pw but use destpw on dest */
			|| cryptcmp(clntinfo->forward.accept_pw, clntinfo->pass)==0) {
			/* check for the exact password */

			clntinfo->pass = realloc(clntinfo->pass,
					strlen(clntinfo->forward.send_pw) + 1);
			enough_mem(clntinfo->pass);
			strncpy(clntinfo->pass, clntinfo->forward.send_pw,
				strlen(clntinfo->forward.send_pw) + 1
					/* always true */);
		} else if (cryptcmp(clntinfo->forward.accept_pw,
						clntinfo->pass) != 0){
			say(clntinfo->clientsocket, "530 Login incorrect\r\n");
			return CMD_ERROR;
		}
	}
	return CMD_HANDLED;
}


#define ERR_STR_P2 "Error connecting to %s port %d: %s\r\n"

static
int login_init_connection(struct clientinfo* clntinfo) {
	int ss, cs = clntinfo->clientsocket;
	int ret, err;

	if ((ret = login_mayconnect(clntinfo)) < 0) {
		/* the error is logged and say()ed */
		return ret;
	}

	/* connect */
	ss = openportname(clntinfo->destination,     /* dest name */
			  clntinfo->destinationport, /* dest port */
			                             /* source address */
			  config_get_addroption("controlserveraddress",
								INADDR_ANY),
			  (struct portrangestruct*) 0); /* source port */
	if (ss < 0) {
		err = errno;
		sayf(cs, "500 "ERR_STR_P2,
				clntinfo->destination,
				clntinfo->destinationport,
				strerror(err));
		jlog(5, ERR_STR_P2, clntinfo->destination,
				   clntinfo->destinationport,
				   strerror(err));
		return CMD_ERROR;
	}

	/* connected */
	clntinfo->serversocket = ss;

	return CMD_HANDLED;
}
#undef ERRSTR


static
int login_connected_setup(struct clientinfo* clntinfo) {
	int ret;
	char* buffer;

	if ((ret = login_readwelcome(clntinfo)) < 0) {
		return ret;
	}

	if (/*clntinfo->transparent == TRANSPARENT_YES*/
		/* we are connected */
		/*&&*/
	    config_compare_option("logintime", "connect")) {
		say(clntinfo->clientsocket,clntinfo->login.welcomemsg.fullmsg);

		clntinfo->login.welcomemsg.fullmsg = (char*) 0;
		clntinfo->login.welcomemsg.lastmsg = (char*) 0;
	}

	if (config_get_bool("initialsyst") == 1) {
		say(clntinfo->serversocket, "SYST\r\n");
		buffer = ftp_readline(clntinfo->serversocket);
		if (!buffer) {
			if (timeout) {
				jlog(1, "Timeout in %s line %d\n", __FILE__ ,__LINE__);
				err_time_readline(clntinfo->clientsocket);
				return CMD_ABORT;
			} else {
				err_readline(clntinfo->clientsocket);
				return CMD_ABORT;
			}
		}
		if (!strlen(buffer)) {
			jlog(2, "The server did not respond to the initial SYST command");
			say(clntinfo->clientsocket,
					"The server did not respond correctly\r\n");
			free(buffer);
			return CMD_ERROR;
		}
		free(buffer);
	} else {
		jlog(9, "Suppressing the initial SYST command");
	}
	buffer = (char*) 0;

	return CMD_HANDLED;
}


static
int login_readwelcome(struct clientinfo *clntinfo) {
	/* Read the welcome line */
	clntinfo->login.welcomemsg = readall(clntinfo->serversocket);
	if (!clntinfo->login.welcomemsg.fullmsg) {
		/* an error occurred */
		if (timeout) {
			jlog(2, "Timeout in %s line %d\n", __FILE__ ,__LINE__);
			err_time_readline(clntinfo->clientsocket);
		} else {
			set_errstr("Server closed the connection");
			err_readline(clntinfo->clientsocket);
		}
		return CMD_ABORT;
	}

	jlog(9, "Connected to %s, got \"%s\" as welcome message",
		clntinfo->destination, clntinfo->login.welcomemsg.fullmsg);

	if (!checkbegin(clntinfo->login.welcomemsg.lastmsg, "220 ")) {
		jlog(2, "Not a valid FTP server response (%s)",
				clntinfo->login.welcomemsg.fullmsg);
		say(clntinfo->clientsocket,clntinfo->login.welcomemsg.fullmsg);

		free(clntinfo->login.welcomemsg.fullmsg);
		clntinfo->login.welcomemsg.fullmsg = (char*) 0;
		clntinfo->login.welcomemsg.lastmsg = (char*) 0;
		return CMD_ERROR;
	}
	return CMD_HANDLED;
}


static
int login_sendauth_user(struct clientinfo* clntinfo) {
	size_t sendbufsize, ret;
	char* sendbuf;

	if (clntinfo->login.stage >= LOGIN_ST_USER) {
		return CMD_HANDLED;
	}

	sendbufsize = strlen("USER \r\n") + strlen(clntinfo->user) + 1;
	sendbuf = (char*) malloc(sendbufsize);
	enough_mem(sendbuf);

	snprintf(sendbuf, sendbufsize, "USER %s\r\n", clntinfo->user);
	ret = say(clntinfo->serversocket, sendbuf);
	free(sendbuf);
	sendbuf = 0;
	if (ret < 0) {
		jlog(2, "Error writing the user name to the server: %s",
			strerror(errno));
		return CMD_ABORT;
	}

	clntinfo->login.authresp = readall(clntinfo->serversocket);

	if (!clntinfo->login.authresp.fullmsg) {
		if (timeout) {
			jlog(2, "Timeout in %s line %d\n", __FILE__ ,__LINE__);
			err_time_readline(clntinfo->clientsocket);
		} else {
			err_readline(clntinfo->clientsocket);
		}
		free(clntinfo->login.welcomemsg.fullmsg);
		clntinfo->login.welcomemsg.fullmsg = (char*) 0;
		clntinfo->login.welcomemsg.lastmsg = (char*) 0;
		return CMD_ABORT;
	}

	clntinfo->login.stage = LOGIN_ST_USER;
	return CMD_HANDLED;
}


static
int login_sendauth_pass(struct clientinfo* clntinfo) {
	size_t sendbufsize;
	int ret;
	char* sendbuf, *buffer;

	buffer = clntinfo->login.authresp.lastmsg;
	if ( !checkdigits(buffer, 331) && !checkdigits(buffer, 230)) {
		/* prepend the response from the server with an error code
		 * and return it to the client */
		sendbufsize = strlen(buffer) + 4 + 1;
		sendbuf = (char*) malloc(sendbufsize);
		enough_mem(sendbuf);
		snprintf(sendbuf, sendbufsize, "500 %s", buffer);
		say(clntinfo->clientsocket, sendbuf);
		free(sendbuf);

		jlog(7, "Got \"%s\" after sending the username.", buffer);
		free(clntinfo->login.authresp.fullmsg);
		free(clntinfo->login.welcomemsg.fullmsg);
		clntinfo->login.welcomemsg.fullmsg = (char*) 0;
		clntinfo->login.welcomemsg.lastmsg = (char*) 0;
		clntinfo->login.authresp.fullmsg   = (char*) 0;
		clntinfo->login.authresp.lastmsg   = (char*) 0;
		return CMD_ERROR;
	}

	if (!checkdigits(buffer, 230)) {
		char* userdup;
		size_t size;

		sendbufsize = strlen("PASS \r\n") + strlen(clntinfo->pass) + 1;
		sendbuf = (char*) malloc(sendbufsize);
		enough_mem(sendbuf);
		snprintf(sendbuf, sendbufsize, "PASS %s\r\n", clntinfo->pass);
		ret = say(clntinfo->serversocket, sendbuf);

		size = strlen(clntinfo->user) + 3;
		userdup = (char*) malloc( size );
		snprintf(userdup, size, " %s ", clntinfo->user);

		if (strstr(ANON_USERS, userdup)) {
			clntinfo->anon_user = clntinfo->pass;
			clntinfo->pass = (char*) 0;
		} else {
			memset(sendbuf, (char) 0, sendbufsize);
			clntinfo->anon_user = (char*) 0;
		}
		free(sendbuf);
		free(userdup);
		sendbuf = userdup = (char*) 0;
		free(clntinfo->login.authresp.fullmsg);
		clntinfo->login.authresp.fullmsg   = (char*) 0;
		clntinfo->login.authresp.lastmsg   = (char*) 0;

		if (ret < 0) {
			jlog(2, "Error writing the password to the server: %s",
				strerror(errno));
			free(clntinfo->login.welcomemsg.fullmsg);
			clntinfo->login.welcomemsg.fullmsg = (char*) 0;
			clntinfo->login.welcomemsg.lastmsg = (char*) 0;
			return CMD_ABORT;
		}
	}
	return CMD_HANDLED;
}


static
int login_loggedin_setup(struct clientinfo* clntinfo) {

	/* we seem to have a successful login */
	jlog(7, "Logged in to %s as %s!",
		clntinfo->destination, clntinfo->user);

	/* initialize the log_cmd_st structure with the values that have to
	 * be set only once after a successful login */
	lcs.anon_user = clntinfo->anon_user;
	lcs.svrip = strdup(get_char_ip(GET_IP_SERVER, clntinfo));
	lcs.svrname = hostent_get_name(&hostcache, inet_addr(lcs.svrip));
	lcs.svrlogin = clntinfo->destination;

	/* we don't need the configuration sections and the backup anymore */

	config_delete_master();
	config_delete_backup();

	/* get the throughput rate */
	clntinfo->throughput = config_get_foption("throughput", -1.0);

	clntinfo->addr_to_server =
			socketinfo_get_local_ip(clntinfo->serversocket);
	lcs.ifipsvr = strdup(conv_ip_to_char(clntinfo->addr_to_server));
	enough_mem(lcs.ifipsvr);

	clntinfo->data_addr_to_client
				= config_get_addroption("dataclientaddress",
						clntinfo->addr_to_client);
	jlog(8, "Got %s as data client address",
	       inet_ntoa(*((struct in_addr*) &clntinfo->data_addr_to_client)));

	clntinfo->data_addr_to_server =
				config_get_addroption("dataserveraddress",
						clntinfo->addr_to_server);
	jlog(8, "Got %s as data server address",
	       inet_ntoa(*((struct in_addr*) &clntinfo->data_addr_to_server)));

	if (clntinfo->servermode == UNSPEC) {
		clntinfo->servermode = getservermode();
	}

	clntinfo->login.stage = LOGIN_ST_FULL;

	return CMD_HANDLED;
}


static
int login_finish_login(struct clientinfo* clntinfo) {
	char* buffer;

	if (stage_action("loggedin") < 0) {
		say(clntinfo->clientsocket, "421 Error setting up (see logfile)\r\n");
		return CMD_ABORT;
	}

	clntinfo->login.authresp = readall(clntinfo->serversocket);
	if (!clntinfo->login.authresp.fullmsg) {
		if (timeout) {
			jlog(2, "Timeout in %s line %d\n", __FILE__,
					__LINE__);
			err_time_readline(clntinfo->clientsocket);
		}
		else {
			err_readline(clntinfo->clientsocket);
		}
		if (clntinfo->login.welcomemsg.fullmsg) {
			free(clntinfo->login.welcomemsg.fullmsg);
		}
		return CMD_ABORT;
	}

	if (clntinfo->login.welcomemsg.fullmsg) {
		/* we have not yet sent the welcome message to the client */
		buffer = merge_responses(clntinfo->login.welcomemsg.fullmsg,
					clntinfo->login.authresp.fullmsg);
		/* free the welcome message */
		free(clntinfo->login.welcomemsg.fullmsg);
		clntinfo->login.welcomemsg.fullmsg
			= clntinfo->login.welcomemsg.lastmsg = (char*) 0;
	} else {
		/* if the welcome message is sent, just copy the
		 * authentication response */
		buffer = strdup(clntinfo->login.authresp.fullmsg);
		enough_mem(buffer);
	}

	lcs.respcode = getcode(clntinfo->login.authresp.lastmsg);

	if (!checkdigits(clntinfo->login.authresp.lastmsg, 230)) {
		say(clntinfo->clientsocket,
				clntinfo->login.authresp.lastmsg);
		jlog(8, "Got \"%s\" after sending the password",
				clntinfo->login.authresp.fullmsg);
		free(clntinfo->login.authresp.fullmsg);
		free(buffer);
		clntinfo->login.authresp.fullmsg   = (char*) 0;
		clntinfo->login.authresp.lastmsg   = (char*) 0;
		return CMD_ERROR;
	}
	free(clntinfo->login.authresp.fullmsg);
	clntinfo->login.authresp.fullmsg   = (char*) 0;
	clntinfo->login.authresp.lastmsg   = (char*) 0;

	/* Now the client receives the welcome message as well as the
	 * response of the server from the authentication with the same code
	 * at the beginning */
	say(clntinfo->clientsocket, buffer);
	free(buffer);
	buffer =0;

	return CMD_HANDLED;
}


static
int login_failed(struct clientinfo* clntinfo) {

	static int failed_logins;

	/* if there was a forward, restore the clntinfo fields from the
	 * before_forward values */

	if (clntinfo->before_forward.user) {
		free(clntinfo->user);
		clntinfo->user = clntinfo->before_forward.user;
		clntinfo->before_forward.user = (char*) 0;
	}
	if (clntinfo->before_forward.destination) {
		free(clntinfo->destination);
		clntinfo->destination = clntinfo->before_forward.destination;
		clntinfo->before_forward.destination = (char*) 0;

		clntinfo->destinationport
				= clntinfo->before_forward.destinationport;
	}

	failed_logins ++;

	if (failed_logins >= config_get_ioption("failedlogins", 3)) {
		return CMD_ABORT;
	}

	/* switch configuration - remove the shrinked one,
	 * set the backup into place. If the login failed,
	 * the backup is used */

	if (config_activate_backup() < 0) {
		jlog(6, "Backup could not be activated - can't continue with login procedure");
		return CMD_ABORT;
	}

	/* shrink the configuration again - this will update the option list */

	config_shrink_config(get_uint_ip(GET_IP_CLIENT, clntinfo),
			(unsigned long int) UINT_MAX,
			(char*) 0,   /* destination */
			0,           /* destinationport */
			(char*) 0,   /* user */
			(unsigned long int) UINT_MAX,
			(char*) 0,   /* before_forward.destination */
			0,           /* before_forward.destinationport */
			(char*) 0,   /* before_forward.user */
			0,           /* set no specific time */
			clntinfo->proxy_ip,
			clntinfo->proxy_port,
			srvinfo.servertype,
			&hostcache,
			TAG_CONNECTED);

	return CMD_HANDLED;
}





/* login function flow

shrinks bindport.c: childsetup()
	handle_login {login.c 51}
	login {login.c 168}
shrinks		login_setforward_user {login.c 334}
		login_connect {login.c 214}
			login_init_connection {login.c 499}
shrinks				login_mayconnect {login.c 263}
			login_connected_setup {login.c 528}
				login_readwelcome {login.c 585}
		login_sendauth_user {login.c 618}
		login_setforward_pass {login.c 472}
		login_auth {login.c 237}
			login_sendauth_user ... {70}
			login_sendauth_pass {login.c 661}
			login_loggedin_setup {login.c 797}
				login_successfulp {login.c 731}
shrinks		login_failed {login.c 813}


We send USER, server sends 230:
	cmds_after_user 
		sets LOGIN_ST_LOGGEDIN and calls
		login(LOGIN_ST_FULL)
		sets CMD_DONE to tell the handler that the login
		procedure is done


*/


syntax highlighted by Code2HTML, v. 0.9.1