/* 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 "reply.h"

#define 	LISTALL		1
#define 	LISTDIR		2
#define		LISTRECURSE	4

int ftplist_parseflags(char *params)
{
	int count, output = 0;
	
	for (count = 0; count < strlen(params); count++)
	{
		if (params[count] == 'a')
			output = output | LISTALL;
		if (params[count] == 'd')
			output = output | LISTDIR;
		if (params[count] == 'R')
			output = output | LISTRECURSE;
		if (params[count] == ' ')
			return(output);
	}
	
	return(output);
}

char *listmakeline(FTPSTATE *peer, char *longname, char *filename, struct stat *fileinfo, STRCACHE *uidcache, STRCACHE *gidcache, int year)
{
	char *output = NULL;
	char *datastr;
	char permissions[11]; 
	char date[20];
	char size[40];
	char *username;
	char *group;
	int links;
	
	/* make sure the stat did work */
	if (fileinfo->st_nlink > 0)
	{
		char numuserstr[20];
		char numgroupstr[20];
		
		/* insert the permissions/ filetype */
		if (peer->fakemode != -1)
		{
			int dirmode = (peer->fakemode & 0777) | S_IFDIR;
			dirmode |= (dirmode & 0444) >> 2;
			
			if (S_ISDIR(fileinfo->st_mode))
				convertperms(permissions, dirmode);
			else
				convertperms(permissions, peer->fakemode | S_IFREG);
		}
		else
			convertperms(permissions, fileinfo->st_mode);
		permissions[10] = 0;
		
		/* Insert links */
		links = MINIMUM(fileinfo->st_nlink, 999);

		/* Insert size */
		if (S_ISCHR(fileinfo->st_mode) || S_ISBLK(fileinfo->st_mode))
		{
			snprintf(size, 39, "%3d, %3d", (int)(fileinfo->st_rdev) >> 8,
							(int)(fileinfo->st_rdev) & 255);
		}
		else
		 	snprintf(size, 39, "%8s", offt_tostr(fileinfo->st_size));

		/* Insert date */
		datastr = ctime(&(fileinfo->st_mtime));
		memcpy(date, datastr + 4, 12);
		if (memcmp(&year, datastr + 20, 4) != 0)
		{
			date[7] = ' ';
			memcpy(date + 8, datastr + 20, 4);
		}
		date[12] = 0;
		
		/* do owner */
		if (peer->fakename)
			username = peer->fakename;
		else if ((username = strcache_check(uidcache, (int)fileinfo->st_uid)) == NULL)
		{
			username = get_passwdname(fileinfo->st_uid, peer->chroot);
			if (username == NULL)
			{
				sprintf(numuserstr, "%d", (int)fileinfo->st_uid);
				username = numuserstr;
			}
		
			strcache_add(uidcache, (int)fileinfo->st_uid, username);	
		}
		username[8] = 0;
			
		/* do group */		
		if (peer->fakegroup)
			group = peer->fakegroup;
		else if ((group = strcache_check(gidcache, fileinfo->st_gid)) == NULL)
		{	
			group = get_groupname(fileinfo->st_gid, peer->chroot);
			if (group == NULL)
			{
				sprintf(numgroupstr, "%d", (int)fileinfo->st_gid);
				group = numgroupstr;
			}
			
			strcache_add(gidcache, (int)fileinfo->st_gid, group);
		}
		group[8] = 0;

		/* Do symbolic links */
		if (permissions[0] == 'l') 
		{
			char linkname[BUFFERSIZE];
			int linklen = readlink(filename, linkname, BUFFERSIZE - 1);
			if (linklen != -1)
			{
				linkname[linklen] = 0;
				output = safe_snprintf("%s %3d %-8s %-8s %s %12s %s -> %s\r\n",
					 permissions, links, username, group, size, date, filename, linkname);
			}
		}
		
		if (!output)
			output = safe_snprintf("%s %3d %-8s %-8s %s %12s %s\r\n",
				permissions, links, username, group, size, date, filename);
		
	}
	else
		output = safe_snprintf("----------   1 unknown   unknown          0 Jan  1 00:00 %s\r\n", filename);

	return(output);
}

void list_decodesubdirs(LISTHANDLE *lh)
{
	int count;
	
	/* no need to do this if no files in this dir */
	if (lh->dirdata->namecount == 0)
		return;
	
	reallocwrapper(sizeof(char *) * (lh->dirdata->namecount + lh->numsubdirs), (void *)&(lh->subdirs));

	for (count = lh->dirdata->namecount - 1; count >= 0; count--)
	{
		if (S_ISDIR(lh->dirdata->stat_buf[count].st_mode))
		{
			if ((strcmp(lh->dirdata->name[count], "..") != 0)
			    && (strcmp(lh->dirdata->name[count], ".") != 0))
			{
				lh->subdirs[lh->numsubdirs] = safe_snprintf("%s/%s", lh->dir, lh->dirdata->name[count]);
				lh->numsubdirs++;
			}
		}				
	}
}

int buildnextdir(FTPSTATE *peer, LISTHANDLE *lh)
{
	MYGLOBDATA *dirlisting = NULL;

	myglobfree(lh->dirdata);
	lh->dirdata = NULL;

	/* loop while there are still subdirectories to look at until an accessable one
	   is found */	
	while ((!dirlisting) && (lh->numsubdirs > 0)) 
	{
		freewrapper(lh->dir);
		lh->numsubdirs--;
		lh->dir = lh->subdirs[lh->numsubdirs];
		dirlisting = file_glob(peer, lh->dir, "*", lh->all);
		lh->subdirs[lh->numsubdirs] = NULL;
	}
	
	if (dirlisting)
	{
		lh->dirdata = dirlisting;
		lh->pos = -1;
		list_decodesubdirs(lh);
		return(TRUE);
	}
	
	return(FALSE);
}

LISTHANDLE *getlisthandle(FTPSTATE *peer, char *dirpattern, int nlist, char *reldir, int params)
{
	char *mypat = strdupwrapper(dirpattern);
	char *dir, *pattern;
	char *t;
	time_t ct;
	LISTHANDLE *lh;
		
	/* manufacture original listing dir and pattern */
	dir = mypat;
	*(strrchr(mypat, '/')) = 0;
	pattern = strrchr(dirpattern, '/') + 1;

	lh = mallocwrapper(sizeof(LISTHANDLE));

	lh->dirdata = file_glob(peer, dir, pattern, params & LISTALL);
	
	if (!lh->dirdata)
	{
		freewrapper(lh);
		freewrapper(mypat);
		return(NULL);
	}
	
	ct = time(NULL);
	t = ctime(&ct);
	memcpy(&(lh->year), t + 20, 4);
	lh->uidcache = strcache_new();
	lh->gidcache = strcache_new();
	lh->pos = 0;
	lh->nlist = nlist;
	lh->rdir = reldir;
	lh->odirlen = strlen(dir);
	lh->dir = dir;
	lh->recursive = params & LISTRECURSE;
	lh->all = params & LISTALL;
	lh->numsubdirs = 0;
	lh->subdirs = NULL;

	if (lh->recursive)
	{
		list_decodesubdirs(lh);
		lh->pos = -1;
	}
		
	return(lh);
}

char *getlisthandleline(FTPSTATE *peer, LISTHANDLE *lh)
{
	char *longname;
	char *ret;
	char *relativename;
	
	if (lh == NULL)
		return(NULL);
	
	if (lh->pos == -1)
	{
		relativename = lh->dir + lh->odirlen;
		
		if (relativename[0] == 0)
		{
			if (lh->rdir[0] == 0)
				ret = strdupwrapper(".:\r\n");
			else
				ret = safe_snprintf("%s:\r\n", lh->rdir);
		}
		else
		{
			if (lh->rdir[0] == 0)
				ret = safe_snprintf("%s:\r\n", relativename + 1);
			else if ((lh->rdir[0] == '/') && (lh->rdir[1] == 0))
				ret = safe_snprintf("%s:\r\n", relativename);
			else
				ret = safe_snprintf("%s%s:\r\n", lh->rdir, relativename);
		}

		lh->pos++;
		return(ret);
	}
	
	if (lh->pos == 0)
	{
		lh->pos++;
		if (!(lh->nlist))
			return(safe_snprintf("total %d\r\n", lh->dirdata->blocks));
	}
	
	if (lh->pos > lh->dirdata->namecount)
	{
		if (lh->recursive)
			if (buildnextdir(peer, lh))
				return(strdupwrapper("\r\n"));
		return(NULL);
	}
		
	if (lh->nlist)	
	{
		ret = safe_snprintf("%s\r\n", lh->dirdata->name[lh->pos-1]);
		lh->pos++;
		return(ret);
	}
	
	longname = safe_snprintf("%s/%s", lh->dir, lh->dirdata->name[lh->pos-1]);
	ret = listmakeline(peer, longname, lh->dirdata->name[lh->pos-1], 
		&(lh->dirdata->stat_buf[lh->pos-1]), lh->uidcache, lh->gidcache, lh->year);
	lh->pos++;
	freewrapper(longname);
	return(ret);
}

void freelisthandle(LISTHANDLE *lh)
{
	if (!lh)
		return;

	while(lh->numsubdirs > 0)
	{
		lh->numsubdirs--;
		freewrapper(lh->subdirs[lh->numsubdirs]);
	}
	freeifnotnull(lh->subdirs);
		
	if (lh->dirdata)
		myglobfree(lh->dirdata);	
	freewrapper(lh->dir);
	freewrapper(lh->rdir);
	strcache_free(lh->uidcache);
	strcache_free(lh->gidcache);
	freewrapper(lh);
	return;
}	

int list_write(SELECTER *sel, int fd, void *peerv)
{
	FTPSTATE *peer = (FTPSTATE *)peerv;
	DATAPORT *d = peer->dport;
	int finish = FALSE;
	int size = 0;
	int maxsize;
	
	if (STRLENGTH(d->buffer) == 0)
	{
		int ret = FALSE;
		
		while (!ret)
		{
			char *line = getlisthandleline(peer, d->lhandle);
			if (line)
			{
				string_cat(&(d->buffer), line, -1);
				freewrapper(line);
				if (STRLENGTH(d->buffer) > BUFFERSIZE)
					ret = TRUE;
			}
			else
			{
				ret = TRUE;
				finish = TRUE;
			}
		}
	}
	
	if (STRLENGTH(d->buffer) > 0)
	{
		if (peer->maxtranspd_down)
			maxsize = MINIMUM(peer->maxtranspd_down, STRLENGTH(d->buffer));
		else if (peer->maxtranspd)
			maxsize = MINIMUM(peer->maxtranspd, STRLENGTH(d->buffer));
		else
			maxsize = STRLENGTH(d->buffer);
		size = write(d->socketfd, STRTOCHAR(d->buffer), maxsize);
	}
	
	if (size > 0)
	{
		peer->listdownloadedbytes += size;
		d->transbytes += size;
		string_dropfront(&(d->buffer), size);
	}

	if ((size <= 0) || (finish && (STRLENGTH(d->buffer) == 0)))
	{
		if ((peer->maxtranspd) || (peer->maxtranspd_down))
			limiter_add(d->download_limiter, 0, TRUE);
		
		socket_flush_wait(d->socketfd, peer->timeout);
		close(d->socketfd);
		
		if (finish)
			ftp_write(peer, FALSE, 226, REPLY_TRANSDONE(d->transbytes));
		else
			ftp_write(peer, FALSE, 426, REPLY_TRANSABORT(d->transbytes));
		closedatasocket(peer);
		return(2);
	}
	
	if ((peer->maxtranspd) || (peer->maxtranspd_down))
		limiter_add(d->download_limiter, size, FALSE);
	
	return(FALSE);
}

char *listmakepattern(FTPSTATE *peer, char *parm, char **rdir, int flags)
{
	char *dir;
	char *parm2;
 	int l;
 		
	if (parm == NULL)
		parm2 = strdupwrapper("*");
	else
		parm2 = strdupwrapper(parm);

	dir = file_expand(peer, parm2);
	freewrapper(parm2);
	
	if (parm)
		*rdir = strdupwrapper(parm);
	else
		*rdir = strdupwrapper("");
	
	/* This will check for directory names and changes them to the files
	   in the directory instead unless user specifiess -d */
	if (checkdir(dir) && !(flags & LISTDIR))
	{	
		int a = strlen(dir);
		reallocwrapper(a + 6, (void *)&dir);
		dir[a] = '/'; dir[a+1] = '*'; dir[a+2] = 0;
	}
	else
	{	
		char *pos = strrchr(*rdir, '/');
		if (pos)
			*(strrchr(*rdir, '/')) = 0;
		else
			**rdir = 0;
	}
	
	l = strlen(*rdir);

	if (l > 1)
		while((*rdir)[l - 1] == '/')
		{
			(*rdir)[l - 1] = 0;
			l--;
		}
		
	return(dir);
}

int ftp_lister(FTPSTATE *peer, char *parm, int nlist, int params)
{
	char *fpattern;
	char *reldir;

	/* if -d used, recursion gets turned off */
	if ((params & LISTDIR) && (params & LISTRECURSE))
 		params = params & ~LISTRECURSE;
 		
 	fpattern = listmakepattern(peer, parm, &reldir, params);
	
	if (startdatasocket(peer, -1, TRANS_LIST, -1) == 0)
	{
		peer->dport->lhandle = getlisthandle(peer, fpattern, nlist, reldir, params);
		peer->listconns++;
	}
	
	freewrapper(fpattern);
	return(FALSE);
}
