/*
 * Copyright (c) 1992, Brian Berliner and Jeff Polk
 * Copyright (c) 1989-1992, Brian Berliner
 * 
 * You may distribute under the terms of the GNU General Public License as
 * specified in the README file that comes with the CVS 1.4 kit.
 * 
 * chacl
 * 
 * Sets the permission for the specified user for the directory
 */

#include "cvs.h"
#include "fileattr.h"

static const char *const chacl_usage[] =
{
	"Usage: %s %s [-R] [-r branch] [-u user] [-j branch] [-n] [-p priority] [-m message] [-a [no]{read|write|create|tag|control|all|none}[,...]] [-d] [file or directory...]\n",
	"\t-a access\tSet access\n",
	"\t-d\t\tDelete ACL\n",
	"\t-j branch\tApply when merging from branch\n",
	"\t-m message\tCustom error message\n",
    "\t-n\t\tDo not inherit ACL\n",
	"\t-p priority\tOverride ACL priority\n",
    "\t-r branch\tApply to single branch\n",
	"\t-R\t\tRecursively change subdirectories\n",
    "\t-u user\t\tApply to single user\n",
    "(Specify the --help global option for a list of other help options)\n",
    NULL
};

static const char *const rchacl_usage[] =
{
	"Usage: %s %s [-R] [-r branch] [-u user] [-j branch] [-n] [-p priority] [-m message] [-a [no]{read|write|create|tag|control|all|none}[,...]] [-d] [module...]\n",
	"\t-a access\tSet access\n",
	"\t-d\t\tDelete ACL\n",
	"\t-j branch\tApply when merging from branch\n",
	"\t-m message\tCustom error message\n",
    "\t-n\t\tDo not inherit ACL\n",
	"\t-p priority\tOverride ACL priority\n",
    "\t-r branch\tApply to single branch\n",
	"\t-R\t\tRecursively change subdirectories\n",
    "\t-u user\t\tApply to single user\n",
    "(Specify the --help global option for a list of other help options)\n",
    NULL
};

static const char *acl_directory_set;
static char *current_date;

static struct
{
	const char *branch;
	const char *user;
	const char *message;
	const char *merge;
	const char *priority;
	int del;
	int noinherit;
	char *access;
} parms;

static void set_attrs(CXmlNode *acl, const char *type, int deny, int noinherit)
{
	CXmlNode *n = acl->NewNode(type,NULL);
	if(deny)
		n->NewAttribute("deny","1");
	if(noinherit)
		n->NewAttribute("inherit","0");
}

static void set_acl(CXmlNode *base)
{
	CXmlNode *acl, *acl_to_set = NULL;
	acl = fileattr_find(base,"acl");

	while(acl)
	{
		const char *user = fileattr_getvalue(acl,"@user");
		const char *branch = fileattr_getvalue(acl,"@branch");
		const char *merge = fileattr_getvalue(acl,"@merge");
		if(((!user && !parms.user) || (user && parms.user && !usercmp(user,parms.user))) &&
		   ((!branch && !parms.branch) || (branch && parms.branch && !strcmp(branch,parms.branch))) &&
		   ((!merge && !parms.merge) || (merge && parms.merge && !strcmp(merge,parms.merge))))
		{
			acl_to_set = acl;
			break;
		}
		acl = fileattr_next(acl);
	}
	if(acl_to_set)
		fileattr_batch_delete(acl_to_set);
		
	if(!parms.del)
	{
		char *parm = xstrdup(parms.access);
		char *acc = parm?strtok(parm,","):NULL;

		acl = base->NewNode("acl",NULL);
		fileattr_modified();
		if(parms.user)
			acl->NewAttribute("user",parms.user);
		if(parms.branch)
			acl->NewAttribute("branch",parms.branch);
		if(parms.merge)
			acl->NewAttribute("merge",parms.merge);
		if(parms.priority && atoi(parms.priority))
			acl->NewAttribute("priority",parms.priority);
		if(parms.message)
			acl->NewNode("message",parms.message);
		acl->NewNode("modified_by",getcaller());
		acl->NewNode("modified_date",current_date);
		while(acc)
		{
			int deny=0;
			if(!strncmp(acc,"no",2) && strcmp(acc,"none"))
			{
				deny=1;
				acc+=2;
			}
			if(!strcmp(acc,"all"))
				set_attrs(acl,"all",deny,parms.noinherit);
			else if(!strcmp(acc,"none"))
				set_attrs(acl,"all",!deny,parms.noinherit);
			else if(!strcmp(acc,"read"))
				set_attrs(acl,"read",deny,parms.noinherit);
			else if(!strcmp(acc,"write"))
				set_attrs(acl,"write",deny,parms.noinherit);
			else if(!strcmp(acc,"create"))
				set_attrs(acl,"create",deny,parms.noinherit);
			else if(!strcmp(acc,"tag"))
				set_attrs(acl,"tag",deny,parms.noinherit);
			else if(!strcmp(acc,"control"))
				set_attrs(acl,"control",deny,parms.noinherit);
			else
				error(1,0,"Invalid access control attribute '%s'",acc);
			acc = strtok(NULL,",");
		}
		fileattr_prune(acl);
		xfree(parm);
	}
	else
	{
		if(acl_to_set)
			fileattr_prune(acl_to_set);
	}
}

static Dtype chacl_dirproc (void *callerdat, char *dir, char *repos, char *update_dir,  List *entries, const char *virtual_repository, Dtype hint)
{
	CXmlNode *curdir;

	if(hint!=R_PROCESS)
		return hint;

	if(!quiet)
		printf("%sing ACL for directory %s\n",parms.del?"delet":"sett",update_dir);

	curdir = fileattr_create(NULL,"directory");
	set_acl(curdir);

	xfree(acl_directory_set);
	acl_directory_set = xstrdup(repos);

	return R_PROCESS;
}

static int chacl_dirleaveproc(void *callerdat, char *dir, int err, char *update_dir, List *entries)
{
	xfree(acl_directory_set);
	return 0;
}

int chacl_fileproc(void *callerdat, struct file_info *finfo)
{
	XmlHandle_t acl;
	CXmlNode *curfile;

	/* If someone has specified 'chacl foo' and foo is a directory, you'll
       get a dirent plus every file in the directory.  We only want to set
	   the directory in this case */
	if(acl_directory_set && !strcmp(finfo->repository, acl_directory_set))
		return 0;

	Vers_TS *vers = Version_TS (finfo, NULL, NULL, NULL, 0, 0, 0);
	if(!vers->vn_user && !vers->vn_rcs)
	{
	    if (!really_quiet)
			error (0, 0, "nothing known about %s", fn_root(finfo->fullname));
		freevers_ts(&vers);
		return 0;
	}
	freevers_ts(&vers);

	if(!quiet)
		printf("%sing ACL for file %s\n",parms.del?"delet":"sett",finfo->file);

	acl = fileattr_find(NULL,"file[@name=F'%s']/acl",finfo->file);

	curfile = fileattr_create(NULL,"file[@name=F'%s']",finfo->file);
	set_acl(curfile);
	return 0;
}

static int rchacl_proc(int argc, char **argv, const char *xwhere,
			    const char *mwhere, const char *mfile, int shorten,
			    int local_specified, const char *mname, const char *msg)
{
    /* Begin section which is identical to patch_proc--should this
       be abstracted out somehow?  */
    char *myargv[2];
    int err = 0;
    char *repository, *mapped_repository;
    char *where;

	repository = (char*)xmalloc (strlen (current_parsed_root->directory) + strlen (argv[0])
			      + (mfile == NULL ? 0 : strlen (mfile) + 1) + 2);
	(void) sprintf (repository, "%s/%s", current_parsed_root->directory, argv[0]);
	where = (char*)xmalloc (strlen (argv[0]) + (mfile == NULL ? 0 : strlen (mfile) + 1)
			 + 1);
	(void) strcpy (where, argv[0]);

	/* if mfile isn't null, we need to set up to do only part of the module */
	if (mfile != NULL)
	{
	    char *cp;
	    char *path;

	    /* if the portion of the module is a path, put the dir part on repos */
	    if ((cp = strrchr (mfile, '/')) != NULL)
	    {
		*cp = '\0';
		(void) strcat (repository, "/");
		(void) strcat (repository, mfile);
		(void) strcat (where, "/");
		(void) strcat (where, mfile);
		mfile = cp + 1;
	    }

	    /* take care of the rest */
	    path = (char*)xmalloc (strlen (repository) + strlen (mfile) + 5);
	    (void) sprintf (path, "%s/%s", repository, mfile);
	    if (isdir (path))
	    {
		/* directory means repository gets the dir tacked on */
		(void) strcpy (repository, path);
		(void) strcat (where, "/");
		(void) strcat (where, mfile);
	    }
	    else
	    {
		myargv[0] = argv[0];
		myargv[1] = (char*)mfile;
		argc = 2;
		argv = myargv;
	    }
	    xfree (path);
	}

	mapped_repository = map_repository(repository);

	/* cd to the starting repository */
	if ( CVS_CHDIR (mapped_repository) < 0)
	{
	    error (0, errno, "cannot chdir to %s", fn_root(repository));
	    xfree (repository);
	    xfree (mapped_repository);
	    return (1);
	}
	xfree (repository);
	/* End section which is identical to patch_proc.  */

	current_date = date_from_time_t(global_session_time_t);
	err = start_recursion (chacl_fileproc, (FILESDONEPROC) NULL, (PREDIRENTPROC) NULL, chacl_dirproc,
			   (DIRLEAVEPROC) NULL, NULL,
			   argc - 1, argv + 1, local_specified, W_REPOS, 0, 1,
			   where, mapped_repository, 1, verify_control);

	xfree(current_date);
	xfree (mapped_repository);
    return err;
}


int chacl (int argc, char **argv)
{
	int c;
	int local = 1;
	int err = 0;
	int is_rchacl = !strcmp(command_name,"rchacl");

	if (argc == 1 || argc == -1)
		usage (is_rchacl?rchacl_usage:chacl_usage);

	memset(&parms,0,sizeof(parms));
	optind = 0;
	while ((c = getopt (argc, argv, "+a:dnRr:u:m:j:p:")) != -1)
	{
		switch (c)
		{
		case 'a':
			if(parms.del)
				error(1,0,"Cannot combine -a and -d");
			parms.access = xstrdup(optarg);
			break;
		case 'd':
			if(parms.access)
				error(1,0,"Cannot combine -a and -d");
			parms.del = 1;
			break;
		case 'j':
			parms.merge=xstrdup(optarg);
			break;
		case 'm':
			parms.message=xstrdup(optarg);
			break;
		case 'n':
			parms.noinherit=1;
			break;
		case 'p':
			parms.priority=xstrdup(optarg);
			break;
		case 'r':
			if(parms.branch)
				error(1,0,"Cannot have multiple -r options");
			parms.branch = xstrdup(optarg);
			break;
		case 'R':
			local = 0;
			break;
		case 'u':
			if(parms.user)
				error(1,0,"Cannot have multiple -u options");
			parms.user = xstrdup(optarg);
			break;
		case '?':
		default:
			usage (chacl_usage);
			break;
		}
	}
	argc -= optind;
	argv += optind;

	if (argc < 0)
		usage (is_rchacl?rchacl_usage:chacl_usage);

	if (current_parsed_root->isremote)
	{
		if(is_rchacl)
		{
			if (!supported_request ("rchacl2"))
				error (1, 0, "server does not support rchacl");
		}
		else
		{
			if (!supported_request ("chacl2"))
				error (1, 0, "server does not support v2 chacl");
		}

		if(parms.branch)
		{
			send_arg("-r");
			send_arg(parms.branch);
		}
		if(parms.user)
		{
			send_arg("-u");
			send_arg(parms.user);
		}
		if(parms.del)
			send_arg("-d");
		if(parms.noinherit)
			send_arg("-n");
		if(parms.access)
		{
			send_arg("-a");
			send_arg(parms.access);
		}
		if(parms.message)
		{
			send_arg("-m");
			send_arg(parms.message);
		}
		if(parms.merge)
		{
			send_arg("-j");
			send_arg(parms.merge);
		}
		if(parms.priority)
		{
			send_arg("-p");
			send_arg(parms.priority);
		}
		if(!local)
		{
			send_arg("-R");
		}
		send_arg("--");
		if (is_rchacl)
		{
			int i;
			for (i = 0; i < argc; i++)
			send_arg (argv[i]);
			send_to_server ("rchacl2\n", 0);
		}
		else
		{
			send_file_names (argc, argv, SEND_EXPAND_WILD);
			send_files (argc, argv, local, 0, SEND_NO_CONTENTS);
			send_to_server ("chacl2\n", 0);
		}
		return get_responses_and_close ();
	}

	if(!acl_mode)
		error(1,0,"Access control is disabled on this repository.");

	if (is_rchacl)
	{
		DBM *db;
		int i;
		db = open_module ();
		for (i = 0; i < argc; i++)
		{
			err += do_module (db, argv[i], MISC, "Changing", rchacl_proc,
					(char *) NULL, 0, local, 0, 0, (char *) NULL);
		}
		close_module (db);
	}
	else
	{
		current_date = date_from_time_t(global_session_time_t);
		err = start_recursion(chacl_fileproc, NULL, (PREDIRENTPROC) NULL, chacl_dirproc, chacl_dirleaveproc, (void*)NULL,
			argc, argv, local, W_LOCAL, 0, 0, (char*)NULL, NULL, 1, verify_control);
		xfree(current_date);
	}

	return (err);
}


syntax highlighted by Code2HTML, v. 0.9.1