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

ACLLIST *acllist_create(void)
{
	ACLLIST *newlist = mallocwrapper(sizeof(ACLLIST));
	
	newlist->aclcount = 0;
	newlist->acls = NULL;
	
	return(newlist);
}

int getaclattr(char *instr)
{	
	int result = 0;
	
	if (strcasecmp(instr, "ALL") == 0)
		return(ACL_ADD | ACL_READ | ACL_LIST | ACL_DELETE
			       | ACL_MKDIR | ACL_RMDIR | ACL_WRITE
			       | ACL_CHMOD | ACL_CHDIR | ACL_REPLACE );
	else if (strcasecmp(instr, "NONE") == 0)
		return(0);
	
	while(*instr != 0)
	{
		/* make sure character is upper case! */
		char aclchar = *instr & (255 - 32);
		
		switch (aclchar)
		{
			case 'A': { result |= ACL_ADD ; break ; }
			case 'R': { result |= ACL_READ ; break ; }
			case 'L': { result |= ACL_LIST ; break ; }
			case 'X': { result |= ACL_REPLACE ; break ; }
			case 'D': { result |= ACL_DELETE ; break ; }
			case 'M': { result |= ACL_MKDIR ; break ; }
			case 'I': { result |= ACL_RMDIR ; break ; }
			case 'W': { result |= ACL_WRITE ; break ; }
			case 'H': { result |= ACL_CHMOD ; break ; }
			case 'C': { result |= ACL_CHDIR ; break ; }
			default : { log_giveentry(MYLOG_INFO, 0, safe_snprintf("Unknown ACL char %c", aclchar)); }
		}
		instr++;
	}
	return(result);
}

/* this assumes ret is at least 10 chars long */

char *acllist_makepermstr(ACLLIST *acl, int cnt, char *ret)
{
	char refchars[10] = { "RALXDMIWHC" };
	int count, xcount = 1;
	strcpy(ret, "");
	
	for (count = 0; count < strlen(refchars); count++)
	{
		if (acl->acls[cnt].attr & xcount)
			strncat(ret, refchars + count, 1);
		xcount *= 2;  
	}
	return(ret);
}

void acllist_add(ACLLIST *acl, char *dir, char *attribs, int is_regexp)
{
	int count;
	int attr = getaclattr(attribs);
	
	/* see if this directory already has an ACL. If it does,
	   combine the ACL attributes if allowed to */

	debuglog("adding acl: %s %s", dir, attribs);
	for (count = 0; count < acl->aclcount; count++)
	{
		if (strcmp(acl->acls[count].dir, dir) == 0)
		{	
			if (!(acl->acls[count].attr & ACL_REPLACE))
				acl->acls[count].attr |= attr;
			return;
		}
	}
	
	/* if there isn't an ACL yet for this directory, create it here */
	
	reallocwrapper(sizeof(ACL) * (acl->aclcount+1), (void **)&(acl->acls));
	
	acl->acls[acl->aclcount].dir = strdupwrapper(dir);
	acl->acls[acl->aclcount].regexp = is_regexp;
	acl->acls[acl->aclcount].attr = attr;

	acl->aclcount++;
} 
/*
void acllist_tokendo(ACLLIST *acl, TOKENSET *tset)
{
	int count;
	for (count = 0; count < acl->aclcount; count++)
		acl->acls[count].dir = tokenset_apply(tset, acl->acls[count].dir);
}
*/
int acllist_check(ACLLIST *acl, char *dir, int attr)
{
	int result = 0;
	char *mydir = strdupwrapper(dir);
	int len = strlen(dir);
	int count;
	
	debuglog("inacl : %s", dir);
	
	/* go through the list and find and test any regexp acls */
	
	for (count = 0; count < acl->aclcount; count++)
	{
		if ( acl->acls[count].regexp )
		{
			debuglog( "regexp acl %s, %s\n", mydir, acl->acls[count].dir);
			if (my_fnmatch( acl->acls[count].dir, mydir, 
				     acl->acls[count].regexp == 2 ? FNM_PATHNAME : 0 ) 
				     == 0)
			{
				result = acl->acls[count].attr;	
				goto gotmatch;
			}
		}
	}
	
	/* now do regular dir matches */
	
	while( mydir[0] != 0 )
	{
		char *tmp;
		debuglog("acl breakdown : %s", mydir);		
		for (count = 0; count < acl->aclcount; count++)
		{
			debuglog( "%s, %s\n", mydir, acl->acls[count].dir);
			if (strcmp( mydir, acl->acls[count].dir ) == 0)
			{
				result = acl->acls[count].attr;	
				goto gotmatch;
			}
		}
		
		if (mydir[len - 1] == '/')
			len--;
		mydir[len] = 0;
		tmp = strrchr(mydir, '/');
		if (tmp != NULL)
		{
			tmp++;
			*tmp = 0;
			len = tmp - mydir;
		}
	}

	result = 0;
gotmatch:
	freewrapper(mydir);
	if ((result & attr) == attr)
		return(TRUE);
	
	return(FALSE);
}

void acllist_dest(ACLLIST *acl)
{
	int count;
	
	if (acl->aclcount > 0)
	{
		for (count = 0; count < acl->aclcount; count++)
			freewrapper(acl->acls[count].dir);
		
		freewrapper(acl->acls);
	}
	
	freewrapper(acl);
}

int check_acl(FTPSTATE *peer, char *path, int perm)
{
	int ok;
	 
	ok = acllist_check(peer->acldata, dir_getvirtual(peer, path), perm);
	
	errno = 0;
	if (ok)
		return(0);
	else
		errno = EACCES;
	return(-1);
}
