/* Copyright (C) 1999 Beau Kuiper

   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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "ftpd.h"
#include "ftpcmd.h"
#include "reply.h"

extern FTPCMD mainftpcmd[];

/* This will print a NICE! error to the user */

void reporterror(FTPSTATE *peer, char *filename, int errorno)
{
	ftp_write(peer, FALSE, 550, "'%s': %s", filename, strerror(errorno));
}

int readipstr(char *data, int *port, unsigned int *ip)
{
	int a1,a2,a3,a4,a5,a6, result;
	
	result = sscanf(data, "%d,%d,%d,%d,%d,%d",&a1, &a2, &a3, &a4, &a5, &a6);
	if (result != 6)
		return(FALSE);
	
	*ip = (a1 << 24) | (a2 << 16) | (a3 << 8) | (a4);
	*port = (a5 * 256) + a6;
	return(TRUE);
}

int readeipstr(char *ip, unsigned int *outip)
{
	int a1, a2, a3, a4, result;

	result = sscanf(ip, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
	if (result != 4)
		return(FALSE);
	
	*outip = (a1 << 24) | (a2 << 16) | (a3 << 8) | (a4);
	return(TRUE);
}

int ftp_dele(FTPSTATE *peer, char *filename)
{
	if (file_unlink(peer, filename) == 0)
		ftp_write(peer, FALSE, 250, REPLY_DELETE(filename));
	else
		reporterror(peer, filename, errno); 

	return(FALSE);
}

int ftp_chmod(FTPSTATE *peer, char *filename, int mode)
{
	if (file_chmod(peer, filename, mode) == 0)
		ftp_write(peer, FALSE, 250, REPLY_CHMOD(filename, mode)); 
	else
		reporterror(peer, filename, errno); 

	return(FALSE);
}

int ftp_size(FTPSTATE *peer, char *filename)
{
	int filefd;
	off_t size = 0;
	int asize;
	struct stat statdata;
	
	if (peer->binary)
	{
		filefd = file_stat(peer, filename, &statdata);
	 	if (filefd == 0)
	 		size = statdata.st_size;
	}
	else
	{
		char *rname;
		size = 0;
		filefd = file_readopen(peer, filename, &rname);
		freewrapper(rname);
		
		if (filefd > 0)
		{
			char *chin, buffer[BUFFERSIZE];
				
			asize = read(filefd, buffer, BUFFERSIZE-1);
			while (asize > 0)
			{
				buffer[asize] = 0;
				size += asize;
				chin = buffer;
				while ((chin = strchr(chin, 10)) != NULL)
				{
					chin++;
					size++;
				}
				asize = read(filefd, buffer, BUFFERSIZE-1);
			}
			close(filefd);
		}
	}

	if (filefd >= 0)
		ftp_write(peer, FALSE, 213, "%s", offt_tostr(size)); 
	else
		reporterror(peer, filename, errno);

 	return(FALSE);
}

int ftp_mdtm(FTPSTATE *peer, char *filename)
{
	struct stat statdata;
	
	if (file_stat(peer, filename, &statdata) == 0)
	{
		char timestr[20];
		struct tm *resulttm = gmtime(&statdata.st_mtime);
		
		strftime(timestr, 20, "%Y%m%d%H%M%S", resulttm);

		ftp_write(peer, FALSE, 213, timestr); 
	}
	else
		reporterror(peer, filename, errno); 

	return(FALSE);
}

void setdumptokens(FTPSTATE *peer, TOKENSET *ts)
{
	char *outstr;
	time_t mytime = time(NULL);
		
	outstr = ctime(&mytime);
	outstr[strlen(outstr) - 1] = 0;
	tokenset_settoken(ts, 'T', strdupwrapper(outstr));
	tokenset_settoken(ts, 'U', strdupwrapper(peer->username));
	if (peer->pwd)
		tokenset_settoken(ts, 'C', strdupwrapper(dir_getvirtual(peer, peer->pwd)));
	tokenset_settoken(ts, 'E', strdupwrapper(peer->vserver->email));
	tokenset_settoken(ts, 'M', safe_snprintf("%d", peer->maxusers));
	if (peer->loggedin)
		tokenset_settoken(ts, 'N', safe_snprintf("%d", peer->usercount));
	tokenset_settoken(ts, 'R', strdupwrapper(peer->hostname));
	tokenset_settoken(ts, 'L', strdupwrapper(peer->vserver->vhostname));
	tokenset_settoken(ts, 'f', safe_snprintf("%d", peer->downloadedfiles));
	tokenset_settoken(ts, 'F', safe_snprintf("%d", peer->uploadedfiles));
	tokenset_settoken(ts, 'b', safe_snprintf("%s", offt_tostr(peer->downloadedfilebytes)));
	tokenset_settoken(ts, 'B', safe_snprintf("%s", offt_tostr(peer->uploadedfilebytes)));
	tokenset_settoken(ts, 'I', safe_snprintf("%s", offt_tostr(peer->listdownloadedbytes)));
	tokenset_settoken(ts, 'i', safe_snprintf("%d", peer->listconns));
	tokenset_settoken(ts, 'D', safe_snprintf("%s", offt_tostr(peer->listdownloadedbytes + peer->downloadedfilebytes)));
	tokenset_settoken(ts, 't', safe_snprintf("%s", offt_tostr(peer->listdownloadedbytes + peer->downloadedfilebytes + peer->uploadedfilebytes)));
	tokenset_settoken(ts, 'c', safe_snprintf("%d", peer->listconns + peer->downloadedfiles + peer->uploadedfiles));
	tokenset_settoken(ts, 'd', safe_snprintf("%d", peer->listconns + peer->downloadedfiles));
	if (peer->ratioinfo)
		ratio_settokens(peer->ratioinfo, ts);
	tokenset_settoken(ts, 'v', strdupwrapper(peer->vserver->sectionname));
}

int ftp_dumper(FTPSTATE *peer, NEWFILE *infile, int number, char *fmessage, int dotokens, int endtokens)
{
	TOKENSET *ts = NULL;
	char *inp, *tfmessage;
	int first;

	if (dotokens)
		if ((infile != NULL) || (endtokens))
		{
			ts = tokenset_new();
			setdumptokens(peer, ts);
		}
		
	if (infile != NULL)
	{
		first = TRUE;		
		while ((inp = nfgetcs(infile, '\n')) != NULL)
		{
			if (inp[strlen(inp)-1] == '\n')
				inp[strlen(inp)-1] = 0;
			
			if (dotokens)
				inp = tokenset_apply(ts, inp, FALSE);
				
			if (first || config->altlongreplies)
			{
				ftp_write(peer, TRUE, 0, "%d-%s", number, inp);
				first = FALSE;
			}
			else
				ftp_write(peer, TRUE, 0, "    %s", inp);

			freewrapper(inp);
		}
 
		nfclose(infile);
	}
	
	if (endtokens)
	{
		tfmessage = tokenset_apply(ts, strdupwrapper(fmessage), FALSE);
		ftp_write(peer, FALSE, number, "%s", tfmessage);
		freewrapper(tfmessage);
	}
	else
		ftp_write(peer, FALSE, number, "%s", fmessage);
	
	if (ts)
		tokenset_finish(ts);

	return(FALSE);
}

int ftp_dumpstr(FTPSTATE *peer, char *dumpstr, int number, char *fmessage, int endtokens)
{
	TOKENSET *ts = NULL;
	char *oldinp = dumpstr;
	char *inp = dumpstr;
	char *datline, *tfmessage;
	int first;

	ts = tokenset_new();
	setdumptokens(peer, ts);
	
	first = TRUE;
	while (oldinp != NULL)
	{
		inp = strchr(inp, '\n');
		if (inp != NULL)
			*inp = 0;
		
		datline = strdupwrapper(oldinp);
		if (inp != NULL)
		{
			*inp = '\n';
			inp++;
		}
		oldinp = inp;
		datline = tokenset_apply(ts, datline, FALSE);
				
		if (first || config->altlongreplies)
		{
			ftp_write(peer, TRUE, 0, "%d-%s", number, datline);
			first = FALSE;
		}
		else
			ftp_write(peer, TRUE, 0, "    %s", datline);
		freewrapper(datline);

	}
 
	if (endtokens)
	{
		tfmessage = tokenset_apply(ts, strdupwrapper(fmessage), FALSE);
		ftp_write(peer, FALSE, number, "%s", tfmessage);
		freewrapper(tfmessage);
	}
	else
		ftp_write(peer, FALSE, number, "%s", fmessage);

 	tokenset_finish(ts);
	return(FALSE);
}

VSERVER *find_vserver_byname(char *name)
{
	VSERVER *v = config->vservers;
	
	while((v != NULL) && (strcasecmp(v->vhostname, name) != 0))
		v = v->next;
	
	return(v);
}

int host_isip(char *name)
{
	while(((*name >= '0') && (*name <= '9')) || (*name == '.'))
		name++;
	return(*name == 0);
}

int ftp_host(FTPSTATE *peer, char *params)
{
	VSERVER *v;
	/* if there are no vservers, or hostname vservers are not defined,
	   return with not implemented */
	if ((!config->vservers) || (!config->hostvservers))
		return(ftp_write(peer, FALSE, 502, REPLY_NOHOSTS));

	/* if a vserver has already been selected, return 530 */
	if (peer->vserver != config->defaults)
		return(ftp_write(peer, FALSE, 530, REPLY_HOSTSELECTED));
	
	if (config->defaulthost && (host_isip(params)))
		v = config->defaulthost;
	else
		v = find_vserver_byname(params);
	
	if (!v)
		return(ftp_write(peer, FALSE, 533, REPLY_HOSTNOTFOUND(params))); 

	switch(vserver_select(peer, v))
	{
		case 1:
			return(ftp_write(peer, FALSE, 533, REPLY_HOSTMISCONF(params)));
		case 2:
			return(ftp_write(peer, FALSE, 533, REPLY_HOSTNOTFOUND(params)));
		case 3:
			ftp_write(peer, FALSE, 421, REPLY_HOSTTOOBUSY(params));
			return(TRUE);
	}
	
	ftp_write(peer, FALSE, 202, REPLY_HOSTSEL(v->vhostname));

	return(3);
}

int ftp_rnto(FTPSTATE *peer, char *filename)
{ 
	char *badfile;
	char *oldname;
	
	if (!peer->renameoldname)
		return(ftp_write(peer, FALSE, 503, REPLY_RENAMENOSOURCE)); 
	
	oldname = dir_getvirtual(peer, peer->renameoldname);

	if ((badfile = file_rename(peer, oldname, filename)) == NULL)
		ftp_write(peer, FALSE, 250, REPLY_RENAME(oldname,  filename));
	else
		reporterror(peer, badfile, errno);

	freewrapper(peer->renameoldname);
	peer->renameoldname = NULL;

	return(FALSE);
}

int ftp_mkd(FTPSTATE *peer, char *filename)
{
	if (file_mkdir(peer, filename) == 0)
	{
		char *newfile = file_expand(peer, filename);
		ftp_write(peer, FALSE, 257, REPLY_MKDIR(dir_getvirtual(peer, newfile)));
		freewrapper(newfile);
	}
	else
		reporterror(peer, filename, errno); 

	return(FALSE);
}

int ftp_rmd(FTPSTATE *peer, char *filename)
{
	if (file_rmdir(peer, filename) == 0)
		ftp_write(peer, FALSE, 250, REPLY_RMDIR(filename));
	else
		reporterror(peer, filename, errno); 

	return(FALSE);
}

int ftp_cwddo(FTPSTATE *peer, char *newdir, int dump)
{
	char *tmp = strdupwrapper(peer->pwd);
	
	dir_combine(peer, &tmp, newdir);
	
	if (!checkchdir(peer, tmp))
	{
		freewrapper(tmp);
		reporterror(peer, newdir, errno);
		return(FALSE);
	}
	chdir(tmp);
	
	if (peer->realdir)
	{
		char *nt;
		/* this relies on the side effect of checkchdir where
		   the server changes dir to check */
		nt = (char *)dir_getreal(peer);
		if (nt != NULL)
		{
			freewrapper(tmp);
			tmp = nt;
		}
	}
		
	
	{
		NEWFILE *nfile = NULL;
		char *outstr;
		int ret = 257;
		/* If we need to print a file, then open it */ 
		/* Oh bugger, crap, and other things, I left a big
		   security hole here! :-(, now fixed */
		if (dump)
			ret = 250;
		freewrapper(peer->pwd);
		peer->pwd = tmp;
		if ((dump) && (peer->cwddump) && (!peer->cwddumpdata))
		{
			if (peer->cwddump[0] == '/')
				/* if it is an absolute file, don't worry
				   about permissions accessing file */
				nfile = nfopen(peer->cwddump);
			else
				/* worry like hell and pass request via
				   both ACL's and file permissions! */
				nfile = file_nfopen(peer, peer->cwddump);
		}
		outstr = safe_snprintf(REPLY_PWD(dir_getvirtual(peer, peer->pwd)));
		
		if ((dump) && (peer->cwddumpdata))
			ftp_dumpstr(peer, peer->cwddumpdata, ret, outstr, FALSE);
		else
			ftp_dumper(peer, nfile, ret, outstr, TRUE, FALSE);
		freewrapper(outstr);
	}
	
	return(FALSE);
}

int ftp_cwd(FTPSTATE *peer, char *newdir)
{
	return(ftp_cwddo(peer, newdir, TRUE));
}

int ftp_run(FTPSTATE *peer, INPUTLINE *cmd, char *token)
{
	if ((cmd->command->ftpfunc) == NULL)
		return(ftp_write(peer, FALSE, 500, REPLY_CMDNOTKNOWN(token)));
	
	if (cmd->command->paramnum) 
	{
		if (cmd->parameters == NULL)
			return(ftp_write(peer, FALSE, 500, REPLY_NOPARAM(token)));
		else if (cmd->parameters[0] == 0)
			return(ftp_write(peer, FALSE, 500, REPLY_NOPARAM(token)));
	}
	
	if ((cmd->command->needslogin) && (peer->loggedin != TRUE))
		return(ftp_write(peer, FALSE, 530, REPLY_NOLOGIN));
	
	if ((!cmd->command->dataportok) && (peer->dport))
		return(ftp_write(peer, FALSE, 520, REPLY_DATACONNINVALID(token)));
	else
		return(cmd->command->ftpfunc(peer, cmd->parameters));
}

int ftp_quit(FTPSTATE *peer, char *param)
{
	if (peer->quitdump)
	{
		NEWFILE *nfile = nfopen(peer->quitdump);
		ftp_dumper(peer, nfile, 221, REPLY_QUIT, TRUE, FALSE);
	}
	else if (peer->quitdumpdata)
		ftp_dumpstr(peer, peer->quitdumpdata, 221, REPLY_QUIT, FALSE);
	else
		ftp_write(peer, FALSE, 221, REPLY_QUIT);
	return(TRUE);
}

int ftp_user(FTPSTATE *peer, char *param)
{
	if (peer->jailenabled)
		return(ftp_write(peer, FALSE, 530, REPLY_JAILUSER));
	if (peer->loginsleft == 0)
		return(ftp_write(peer, FALSE, 530, REPLY_NOCREDITS));
	if (peer->loggedin)
		shinfo_delusergroup(peer->groupname);

	shinfo_changeuser(param);
	peer->loggedin = 0;
	
	freewrapper(peer->username);
	peer->username = strdupwrapper(param);
	
	return(ftp_write(peer, FALSE, 331, REPLY_USER(peer->username)));
}

int ftp_host_compat(FTPSTATE *peer)
{
	VSERVER *v;
	char *name;
	/* if there are no vservers, or hostname vservers are not defined,
	   return with not implemented */
	if ((!config->vservers) || (!config->hostvservers))
		return(1);

	/* if a vserver has already been selected, return 530 */
	if (peer->vserver != config->defaults)
		return(1);

	name = strchr(peer->username, '%');
	
	if (!name)
		v = config->defaulthost;
	else
	{
		*name = 0;
		name++;
		
		if (config->defaulthost && (host_isip(name)))
			v = config->defaulthost;
		else
			v = find_vserver_byname(name);
	}
	
	if (!v)
		return(0);

	switch(vserver_select(peer, v))
	{
		case 1:
		case 2:
			return(0);
		case 3:
			return(2);
	}
	
	return(1);
}

int ftp_pass(FTPSTATE *peer, char *param)
{
	int result = FALSE;
	int loginok, oldnice = peer->nicevalue;
	int toomany = FALSE;
	char *errmsg = NULL;
	
	switch(ftp_host_compat(peer))
	{
		case 0:
			return(ftp_write(peer, FALSE, 530, REPLY_LOGINFAIL(peer->username, "Bad password")));
		case 2:
			ftp_write(peer, FALSE, 421, REPLY_TOOMANYUSERS);
			return(TRUE);
	}

	if (peer->loggedin)
		return(ftp_write(peer, FALSE, 530, REPLY_ALREADYLOGGEDIN));

	if (strcmp(peer->username, "<unknown>") == 0)
		return(ftp_write(peer, FALSE, 503, REPLY_USEUSERFIRST));
	
	loginok = (param != NULL);
	
	if (loginok)
	{
		file_becomeroot(peer);
		setgroups(0, NULL);
		errmsg = setuseropts(peer, param);
		loginok = (errmsg == NULL);
	}

	/* check some stuff, set basedir to real pathname */
	if (loginok)
	{
		file_becomeuser(peer);
		if (chdir(peer->basedir) == -1)
		{
			loginok = FALSE;
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("rootdir(%s) for user '%s' is not accessable. Check rootdir and rootdir permissions", peer->basedir, peer->username));
		}
		else
		{
			char *curdir = getcwd2();
			if (strcmp(curdir, peer->basedir) != 0)
			{
				log_giveentry(MYLOG_INFO, NULL, safe_snprintf("rootdir changed from '%s' to '%s', symbolic links resolved for user '%s'", peer->basedir, curdir, peer->username));
				freewrapper(peer->basedir);
				peer->basedir = curdir;
			}
			else
				freewrapper(curdir);
		}
		file_becomeroot(peer);
	}

	if (loginok)
	{
		peer->usercount = toomany = shinfo_addusergroup(peer->groupname, peer->maxusers);
		toomany = (toomany == -1);
		if (toomany)
			loginok = FALSE;
	}

	if (loginok)
	{
		char *outstr;
		NEWFILE *nfile;
		
		log_addentry(MYLOG_LOGIN, peer, "Login successful."); 
		outstr = safe_snprintf(REPLY_PASSOK(peer->username));
		
		/* perform chroot if needed */
		if (peer->chroot)
			dochroot(peer);

		if (nice(peer->nicevalue + (-oldnice)) == -1)
		{
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("nice(%d) gave error %s", peer->nicevalue + (-oldnice), strerror(errno)));
			peer->nicevalue = oldnice;
		}
		
		if (config->rootmode)
			setgroups(peer->supgids[0], peer->supgids + 1);
		
		/* see if rootdir == "/", change to "" */
		if (peer->basedir[1] == 0)
			peer->basedir[0] = 0;
		
		if ((peer->droproot) && (config->rootmode))
		{
			if (giveuproot(peer->uidt_asuid, peer->gidt_asgid))
				log_addentry(MYLOG_INFO, peer, "Failed to set capabilities!");
			peer->jailenabled = TRUE;
			/* we are not root anymore and can never be root
			   again! */
			config->rootmode = FALSE;
		}
							
		file_becomeuser(peer);
		
		nfile = nfopen(peer->logindump);
		peer->pwd = strdupwrapper(peer->basedir);
		dir_combine(peer, &(peer->pwd), peer->homedir);
		
		if (peer->logindumpdata)
			result = ftp_dumpstr(peer, peer->logindumpdata, 230, outstr, FALSE);
		else
			result = ftp_dumper(peer, nfile, 230, outstr, TRUE, FALSE);
		freewrapper(outstr);
	}
	else
	{
		peer->loggedin = FALSE;
		log_addentry(MYLOG_LOGIN, peer, "Login failed");
		peer->timeout = peer->vserver->timeout;
		if (toomany)
		{
			NEWFILE *nfile = nfopen(peer->busydump);
			logfullmessage(GROUPFULL, peer->remoteip);
			if (peer->busydumpdata)
				ftp_dumpstr(peer, peer->busydumpdata, 421, REPLY_TOOMANYUSERS, FALSE);
			else
				ftp_dumper(peer, nfile, 421, REPLY_TOOMANYUSERS, TRUE, FALSE);
		}
		else
		{
			usleep(peer->vserver->authwait);
			if (errmsg == NULL)
				errmsg = strdupwrapper("Bad password");
			result = ftp_write(peer, FALSE, 530, REPLY_LOGINFAIL(peer->username, errmsg));
			if (peer->loginsleft > 0)
				peer->loginsleft--;
			freewrapper(errmsg);
		}
		freewrapper(peer->username);
		peer->username = strdupwrapper("<unknown>");
	}
	if (toomany)
		return(TRUE);	/* quit */
	return(3);      	/* make sure timeout gets updated */
}

int ftp_rest(FTPSTATE *peer, char *param)
{
	off_t newpos;
	int res;

	res = strto_offt(param, &newpos);
	
	if ((res != 0) || (newpos < 0))
		return(ftp_write(peer, FALSE, 501, REPLY_INVALIDREST));
	else
	{
		peer->restartpos = newpos;
		return(ftp_write(peer, FALSE, 350, REPLY_RESTOK(peer->restartpos)));
	}
}

int ftp_syst(FTPSTATE *peer, char *param)
{
	return(ftp_write(peer, FALSE, 215, "UNIX Type: L8"));
}
	
int ftp_port(FTPSTATE *peer, char *param)
{
	if (peer->epsv_forced)
		return(ftp_write(peer, FALSE, 500, REPLY_EPSVSET));

	/* reset remoteport */
	peer->remoteport = 0;
	if (peer->passiveport)
	{
		select_delfd(peer->sel, peer->passiveport);
		peer->passiveport = 0;
	}

	if (readipstr(param, &(peer->remoteport), &(peer->dataip)))
	{
		if ((peer->remoteport <= 1024) || (peer->remoteport >= 65536))
		{
			peer->remoteport = 0;
			return(ftp_write(peer, FALSE, 500, REPLY_PORTBADPORT));
		}
		if ((peer->dataip != peer->remoteip) && (!peer->fxpallow))
		{
			peer->remoteport = 0;
			peer->dataip = peer->remoteip;
			return(ftp_write(peer, FALSE, 500, REPLY_PORTBADFXP));
		}
		return(ftp_write(peer, FALSE, 200, REPLY_PORTOK(
				 peer->dataip >> 24, peer->dataip >> 16 & 255, peer->dataip >> 8 & 255,
				 peer->dataip & 255, peer->remoteport)));
	}
	else
		return(ftp_write(peer, FALSE, 501, REPLY_PORTBADPARM));
}

int ftp_eprt(FTPSTATE *peer, char *param)
{
	char *ipstr;
	int proto, tcpport;
	unsigned int remote_ip;
	
	if (peer->epsv_forced)
		return(ftp_write(peer, FALSE, 500, REPLY_EPSVSET));

	ipstr = mallocwrapper(strlen(param) + 1);

	/* clear off old ports */
	peer->remoteport = 0;
	if (peer->passiveport)
	{
		select_delfd(peer->sel, peer->passiveport);
		peer->passiveport = 0;
	}

	if (sscanf(param, "|%d|%[0-9.]|%d|", &proto, ipstr, &tcpport) != 3)
		ftp_write(peer, FALSE, 500, REPLY_EPRTBADPARM);
	else if (proto != FTP_IPV4)
		ftp_write(peer, FALSE, 522, REPLY_BADPROTO);
	else if (!readeipstr(ipstr, &(remote_ip)))
		ftp_write(peer, FALSE, 500, REPLY_EPRTBADPARM);
	else if (tcpport <= 1024)
		ftp_write(peer, FALSE, 500, REPLY_EPRTBADPORT);
	else if ((remote_ip != peer->remoteip) && (!peer->fxpallow))
		ftp_write(peer, FALSE, 500, REPLY_EPRTBADFXP);
	else
	{
		/* everything seems to be ok */
		peer->remoteport = tcpport;
		peer->dataip = remote_ip;
		ftp_write(peer, FALSE, 200, REPLY_EPRTOK(
				 peer->dataip >> 24, peer->dataip >> 16 & 255, peer->dataip >> 8 & 255,
				 peer->dataip & 255, peer->remoteport));
	}
	freewrapper(ipstr);
	return(FALSE);
}

int ftp_pwd(FTPSTATE *peer, char *param)
{
	return(ftp_cwddo(peer, ".", FALSE));
}

int ftp_type(FTPSTATE *peer, char *param)
{
	if ((param[0] & (255-32)) == 'A')
	{
		peer->binary = FALSE;
		return(ftp_write(peer, FALSE, 200, REPLY_TYPEASCII));
	}
	else if ((param[0] & (255-32)) == 'I')
	{
		peer->binary = TRUE;
		return(ftp_write(peer, FALSE, 200, REPLY_TYPEBINARY));
	}
	else
		return(ftp_write(peer, FALSE, 504, REPLY_TYPEUNIMP));
}

int ftp_abor(FTPSTATE *peer, char *param)
{
	/* abort any active or pending data port */
	abortdatasocket(peer);
	return(ftp_write(peer, FALSE, 226, REPLY_ABORT));
}

int ftp_pasv(FTPSTATE *peer, char *param)
{
	unsigned int a1, a2, a3, a4, a5, a6;
	
	if (peer->epsv_forced)
		return(ftp_write(peer, FALSE, 500, REPLY_EPSVSET));
	
	if (peer->passiveport != 0)
		select_delfd(peer->sel, peer->passiveport);

	peer->remoteport = 0;
	
	peer->passiveport = listenparrelelport(peer->remotefd, &(peer->remoteport), &a1, 5);
	
	if (peer->passiveport == -1)
	{
		peer->passiveport = 0;
		reporterror(peer, "passive port", errno);
		return(FALSE);
	}
	select_addfd(peer->sel, peer->passiveport);
	a2 = ((a1 >> 8) & 255);
	a3 = ((a1 >> 16) & 255);
	a4 = ((a1 >> 24) & 255);
	a1 = a1 & 255;
	a5 = (peer->remoteport >> 8);
	a6 = (peer->remoteport & 255);
	return(ftp_write(peer, FALSE, 227, REPLY_PASV(a4, a3, a2, a1, a5, a6)));
}

int ftp_epsv(FTPSTATE *peer, char *param)
{
	int proto, a1;

	/* the all parameter means that only epsv can be used from now on */

	if (peer->passiveport != 0)
		select_delfd(peer->sel, peer->passiveport);

	peer->remoteport = 0;
	peer->passiveport = 0;
	
	if (param)
	{
		if (strcasecmp(param, "ALL") == 0)
		{
			peer->epsv_forced = 1;
			return(ftp_write(peer, FALSE, 200, REPLY_EPSVON));
		} 
		else if (sscanf(param, "%d", &proto) == 1)
		{
			if (proto != FTP_IPV4)
				return(ftp_write(peer, FALSE, 522, REPLY_BADPROTO));
		}
		else
			return(ftp_write(peer, FALSE, 500, REPLY_EPSVERR));
	}
		
	peer->passiveport = listenparrelelport(peer->remotefd, &(peer->remoteport), &a1, 5);
	
	if (peer->passiveport == -1)
	{
		peer->passiveport = 0;
		reporterror(peer, "passive port", errno);
		return(FALSE);
	}
	select_addfd(peer->sel, peer->passiveport);

	return(ftp_write(peer, FALSE, 229, REPLY_EPSV(peer->remoteport)));
}

int ftp_list(FTPSTATE *peer, char *param)
{
	char *p = param;
	int done = FALSE;
	int parm = 0;
	while (!done)
	{
		if (p == NULL)
			done = TRUE;
		else if ((p[0] != '-') && (p[0] != ' ') && (p[0] != 0))
			done = TRUE;
		else
		{
			parm = parm | ftplist_parseflags(p + 1);
			p = strchr(p, ' ');
			if (p != NULL)
				p++;
		}
	}

	return(ftp_lister(peer, p, FALSE, parm));
}

int ftp_nlst(FTPSTATE *peer, char *param)
{
	char *p = param;
	int done = FALSE;
	int parm = 0;
	while (!done)
	{
		if (p == NULL)
			done = TRUE;
		else if ((p[0] != '-') && (p[0] != ' ') && (p[0] != 0))
			done = TRUE;
		else
		{
			parm = parm | ftplist_parseflags(p + 1);
			p = strchr(p, ' ');
			if (p != NULL)
				p++;
		}
	}

	return(ftp_lister(peer, p, TRUE, parm));
}

int ftp_cdup(FTPSTATE *peer, char *param)
{
	return(ftp_cwd(peer, ".."));
}

int ftp_rnfr(FTPSTATE *peer, char *param)
{
	freeifnotnull(peer->renameoldname);
	peer->renameoldname = file_expand(peer, param);
	return(ftp_write(peer, FALSE, 350, REPLY_RENAMESOURCE));
}

int ftp_noop(FTPSTATE *peer, char *param)
{
	return(ftp_write(peer, FALSE, 200, REPLY_NOOP));
}

int ftp_rein(FTPSTATE *peer, char *param)
{
	if (peer->jailenabled)
		return(ftp_write(peer, FALSE, 530, REPLY_JAILUSER));
	if (peer->loggedin)
		shinfo_delusergroup(peer->groupname);

	peer->loggedin = FALSE;
	abortdatasocket(peer);
	return(ftp_write(peer, FALSE, 220, "Reinitialize successful, enter username."));
} 

int ftp_none(FTPSTATE *peer, char *param)
{
	return(FALSE);
}

int ftp_help(FTPSTATE *peer, char *param)
{
	return(ftp_dohelp(peer, mainftpcmd, param, peer->cmddisableset));
}

int ftp_dohelp(FTPSTATE *peer, FTPCMD *ftpcmds, char *command, int *disableset)
{
	char outstr[128];
	char cmd[10];
	int result = FALSE;
	int count2,count = 0;
	
	if (command != NULL)
	{
		while(((strcasecmp(ftpcmds[count].command, command) != 0) &&
		     (ftpcmds[count].ftpfunc != NULL)) ||
		     (peer->loggedin ? disableset[count] : ftpcmds[count].needslogin))
			count++;
		
		if (ftpcmds[count].ftpfunc == NULL)
			ftp_write(peer, FALSE, 502, REPLY_HELPUNKNOWN(command));
		else
			ftp_write(peer, FALSE, 214, "%-8s : %s", ftpcmds[count].command, ftpcmds[count].helpdesc);
		return(FALSE);
	}
	
	ftp_write(peer, TRUE, 0, REPLY_HELPSTART);
	while (ftpcmds[count].ftpfunc != NULL)
	{
		count2 = 0;
		if (config->altlongreplies)
			strcpy(outstr, "214-");
		else
			strcpy(outstr, "    ");
		/* now increment past disabled commands */
		while((peer->loggedin ? disableset[count] : ftpcmds[count].needslogin))
			count++;

		while ((count2 < 7) && (ftpcmds[count].ftpfunc != NULL))
		{
			snprintf(cmd, 10, "%-9s", ftpcmds[count].command);
			strcat(outstr, cmd);
			count2++;
			count++;
			/* now increment past disabled commands */
			while((peer->loggedin ? disableset[count] : ftpcmds[count].needslogin))
				count++;
		}
		ftp_write(peer, TRUE, 0, outstr);
	}
	
	result = ftp_write(peer, FALSE, 214, REPLY_HELPEND(peer->vserver->email)); 

	return(result);
}

int ftp_allo(FTPSTATE *peer, char *param)
{
	return(ftp_write(peer, FALSE, 202, REPLY_ALLO));
}

int ftp_acct(FTPSTATE *peer, char *param)
{
	return(ftp_write(peer, FALSE, 200, REPLY_ACCT));
}

int ftp_stru(FTPSTATE *peer, char *param)
{
	if ((param[0] & (255-32)) == 'F')
		return(ftp_write(peer, FALSE, 200, REPLY_STRUFILE));
	else
		return(ftp_write(peer, FALSE, 500, REPLY_STRUUNKNOWN));
}
