/* 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"

void file_becomeuser(FTPSTATE *peer)
{
	if (config->rootmode)
	{
		if (setegid(peer->gidt_asgid) == -1)
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("setegid failed: %s", strerror(errno)));
		if (seteuid(peer->uidt_asuid) == -1)
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("seteuid failed: %s", strerror(errno)));
	}
}

void file_becomeroot(FTPSTATE *peer)
{
	if (config->rootmode)
	{
		if (seteuid((uid_t)0) == -1)
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("seteuid failed: %s", strerror(errno)));
		if (setegid((gid_t)0) == -1)
			log_giveentry(MYLOG_INFO, NULL, safe_snprintf("setegid failed: %s", strerror(errno)));
	}
}

int checkdir(char *dir)
{
	struct stat check;
	int result = stat(dir, &check);

	if (result == 0)
		if (S_ISDIR(check.st_mode))
			return(TRUE);

	return(FALSE);
}

int checkfile(char *file)
{
	struct stat check;
	int result = stat(file, &check);

	if (result == 0)
		if (!S_ISDIR(check.st_mode))
			return(TRUE);
	return(FALSE);
}

int checkchdir(FTPSTATE *peer, char *dir)
{
	int result;
	char *dir2 = safe_snprintf("%s/", dir);
	
	result = (check_acl(peer, dir2, ACL_CHDIR) == 0);

	if (result)
		result = (chdir(dir) == 0);
	freewrapper(dir2);
	return(result);
}

/* this needs explaining, send this function a pointer to nine '-' in a string ie "---------"
   and the file mode of the file, and it will send back the permissions for that file in that
   string, eg "-rw-r--r--" */

/* this version was inspired by how GNU fileutils does it */
   
void convertperms(char *permstr, int mode)
{
	int mcpy = mode;
	int cnt;
	
	/* work out the 3 sets of rwx's */
	
	for(cnt = 6; cnt > -1; cnt -= 3)
	{
		permstr[cnt+1] = (mcpy & S_IROTH) ? 'r' : '-';
		permstr[cnt+2] = (mcpy & S_IWOTH) ? 'w' : '-';
		permstr[cnt+3] = (mcpy & S_IXOTH) ? 'x' : '-';
		mcpy = (mcpy >> 3);
	}

	/* work out special bits. Setuid the setgid the sticky bit */
		
	if (mode & S_ISUID) permstr[3] = (mode & S_IXUSR) ? 's' : 'S';
	if (mode & S_ISGID) permstr[6] = (mode & S_IXGRP) ? 's' : 'S';
	if (mode & S_ISVTX) permstr[9] = (mode & S_IXOTH) ? 't' : 'T';
	
	/* check for regular files first, better performace! */
	if (S_ISREG(mode))
		permstr[0] = '-'; 
	else if (S_ISLNK(mode))
		permstr[0] = 'l';
	else if (S_ISDIR(mode))
		permstr[0] = 'd';
	else if (S_ISBLK(mode))
		permstr[0] = 'b';
	else if (S_ISCHR(mode))
		permstr[0] = 'c';
	else if (S_ISFIFO(mode))
		permstr[0] = 'p';
#ifdef S_ISSOCK
	else if (S_ISSOCK(mode))
		permstr[0] = 's';
#endif
	else
		permstr[0] = '?';
}

char *file_expand(FTPSTATE *peer, char *filename)
{
	char *newfile = strdupwrapper(peer->pwd);
	dir_combine(peer, &newfile, filename);
	return(newfile);
}

int file_isfdregularfile(int fd)
{
	struct stat buf;
	
	if (fstat(fd, &buf) == 0)
		return(S_ISREG(buf.st_mode));
	else
		return(TRUE);		/* assume yes! */
}

int file_isfdadir(int fd)
{
	struct stat buf;
	
	if (fstat(fd, &buf) == 0)
		return(S_ISDIR(buf.st_mode));
	else
		return(TRUE);		/* assume yes, but there is no good reason fstat
					   won't work! */
}

/* opens a unique filename, in peers virtual space, returns the real name in
   rname for logging */

int file_ustoreopen(FTPSTATE *peer, char *infile, int *nounique, char **rname)
{
	char *filename = file_expand(peer, infile);
	int checknext = TRUE;
	int pos = 0;
	int filefd = -1;
        int epos = strlen(filename);

	*nounique = FALSE;
	reallocwrapper(epos + 5, (void **)&filename);

	if (check_acl(peer, filename, ACL_ADD) == 0)
	{
		filefd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666 & ~peer->umask);
		checknext = ((filefd == -1) && (errno == EEXIST));
	}
	
        while((checknext) && (pos < 1000))
	{
		filename[epos] = '.';
		filename[epos+1] = (pos / 100) + '0';
		filename[epos+2] = (pos % 100) / 10 + '0';
		filename[epos+3] = (pos % 10) + '0';
		filename[epos+4] = 0;
		pos++;
		
		if (check_acl(peer, filename, ACL_ADD) == 0)
		{
			filefd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666 & ~peer->umask);
			checknext = ((filefd == -1) && (errno == EEXIST));
		}
	}

	if (pos >= 1000)
		*nounique = TRUE;

	*rname = filename;
	return(filefd);
}

/* opens a file for storing in peers virtual space, returns the real name for logging in rname */

int file_storeopen(FTPSTATE *peer, char *infile, char **rname)
{
	char *filename = file_expand(peer, infile);
	int newfd = -1;
	int openflags = FALSE;
	int canoverwrite = FALSE;
	int canadd;
	
	canoverwrite = (check_acl(peer, filename, ACL_REPLACE) == 0);
	canadd = (check_acl(peer, filename, ACL_ADD) == 0);
	
	/* if the user can add files, then allow creation */
	if (canadd)
	{
		openflags |= O_CREAT;
		/* if the user cannot overwrite files */ 
		if (!canoverwrite)
			openflags |= O_EXCL;
	}

	/* try opening the file, or no-op if no acl perms available */
	
	if (canoverwrite || canadd)
		newfd = open(filename, openflags | O_WRONLY, 0666 & ~peer->umask);
	else
		errno = EACCES;
	
	/* now test it against device file access settings */
	if ((errno == 0) && (!peer->accessdevices))
		if (!file_isfdregularfile(newfd))
		{
			close(newfd);
			errno = EACCES;
			newfd = -1;
		}

	*rname = filename;
	return(newfd);
}				


int file_readopen(FTPSTATE *peer, char *infile, char **rname)
{
	int filefd = -1;
	char *filename = file_expand(peer, infile);
	
	check_acl(peer, filename, ACL_READ);
	
	if (errno == 0)
		filefd = open(filename, O_RDONLY);
	
	if (errno == 0)
		if (file_isfdadir(filefd))
		{
			close(filefd);
			errno = EISDIR;
			filefd = -1;
		}
		
	if ((errno == 0) && (!peer->accessdevices))
		if (!file_isfdregularfile(filefd))
		{
			close(filefd);
			errno = EACCES;
			filefd = -1;
		}
	
	*rname = filename;
	return(filefd);
}

NEWFILE *file_nfopen(FTPSTATE *peer, char *filename)
{
	char *rname;
	int fd = file_readopen(peer, filename, &rname);
	NEWFILE *result = NULL;
	
	if (fd > 0)
		result = nfdopen(fd);
	
	freewrapper(rname);
	return(result);
} 

MYGLOBDATA *file_glob(FTPSTATE *peer, char *dir, char *pattern, int allfiles)
{
	int dirlen = strlen(dir);
	MYGLOBDATA *mg = NULL;
	char *pdir = mallocwrapper(dirlen + 2);
	
	memcpy(pdir, dir, dirlen);
	if (pdir[dirlen-1] == '/')
		dirlen--;
		
	pdir[dirlen] = '/';
	pdir[dirlen+1] = 0;
	
	check_acl(peer, pdir, ACL_LIST);
	
	if (errno == 0)
		mg = myglob(pdir, pattern, allfiles, TRUE);
	
	freewrapper(pdir);
	return(mg);
}

int file_unlink(FTPSTATE *peer, char *infile)
{
	int result = -1;
	char *filename = file_expand(peer, infile);

	check_acl(peer, filename, ACL_DELETE);
	
	if (errno == 0)
		result = unlink(filename);
	
	freewrapper(filename);
	return(result);
}

/* there is a race here, where the user can get to overwrite a file if it created between
   checkfile and rename. It appears to be very difficult to prevent */

char *file_rename(FTPSTATE *peer, char *infile, char *infile2)
{
	char *filename = file_expand(peer, infile);
	char *filename2 = file_expand(peer, infile2);
	char *badfile = NULL;
	
	if (checkfile(filename2))
	{
		if (check_acl(peer, filename2, ACL_DELETE) == -1)
			badfile = infile2;
 	}
 	else
 		errno = 0;
 		
 	if (errno == 0)
		if (check_acl(peer, filename2, ACL_ADD) == -1)
			badfile = infile2; 
	if (errno == 0)
		if (check_acl(peer, filename, ACL_DELETE) == -1)
			badfile = infile;
		
	if (errno == 0)
	{
		if (rename(filename, filename2) == -1)
			badfile = infile;
	}
	
	freewrapper(filename);
	freewrapper(filename2);
	return(badfile);
}

int file_stat(FTPSTATE *peer, char *infile, struct stat *output)
{
	int result = -1;
	char *filename = file_expand(peer, infile);
	
	check_acl(peer, filename, ACL_READ);
	
	if (errno == 0)
		result = stat(filename, output);
	
	freewrapper(filename);
	return(result);
}

int file_mkdir(FTPSTATE *peer, char *infile)
{
	int result = -1;
	char *filename = file_expand(peer, infile);

	check_acl(peer, filename, ACL_MKDIR);
	
	if (errno == 0)
		result = mkdir(filename, 0777 & ~peer->umask);
	
	freewrapper(filename);
	return(result);
}

int file_rmdir(FTPSTATE *peer, char *infile)
{
	int result = -1;
	char *filename = file_expand(peer, infile);
	
	check_acl(peer, filename, ACL_RMDIR);
	
	if (errno == 0)
		result = rmdir(filename);
	
	freewrapper(filename);
	return(result);
}

int file_chmod(FTPSTATE *peer, char *infile, int mode)
{
	int result = -1;
	char *filename = file_expand(peer, infile);
	
	check_acl(peer, filename, ACL_CHMOD);
	
	if (errno == 0)
		/* never allow sticky bit or setgid or setuid to be set */
		result = chmod(filename, mode & 0777);
	
	freewrapper(filename);
	return(result);
}
