1/*
2 * Copyright (c) 1989, 1993, 1994
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33#include <err.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <string.h>
37#include <sys/types.h>
38#include <unistd.h>
39#include <errno.h>
40#include <fcntl.h>
41#include <sys/stat.h>
42
43#include <membership.h>
44#include "chmod_acl.h"
45
46extern void usage(void);
47
48#ifdef __APPLE__
49static struct {
50	acl_perm_t	perm;
51	char		*name;
52	int		flags;
53#define ACL_PERM_DIR	(1<<0)
54#define ACL_PERM_FILE	(1<<1)
55} acl_perms[] = {
56	{ACL_READ_DATA,		"read",		ACL_PERM_FILE},
57	{ACL_LIST_DIRECTORY,	"list",		ACL_PERM_DIR},
58	{ACL_WRITE_DATA,	"write",	ACL_PERM_FILE},
59	{ACL_ADD_FILE,		"add_file",	ACL_PERM_DIR},
60	{ACL_EXECUTE,		"execute",	ACL_PERM_FILE},
61	{ACL_SEARCH,		"search",	ACL_PERM_DIR},
62	{ACL_DELETE,		"delete",	ACL_PERM_FILE | ACL_PERM_DIR},
63	{ACL_APPEND_DATA,	"append",	ACL_PERM_FILE},
64	{ACL_ADD_SUBDIRECTORY,	"add_subdirectory", ACL_PERM_DIR},
65	{ACL_DELETE_CHILD,	"delete_child",	ACL_PERM_DIR},
66	{ACL_READ_ATTRIBUTES,	"readattr",	ACL_PERM_FILE | ACL_PERM_DIR},
67	{ACL_WRITE_ATTRIBUTES,	"writeattr",	ACL_PERM_FILE | ACL_PERM_DIR},
68	{ACL_READ_EXTATTRIBUTES, "readextattr",	ACL_PERM_FILE | ACL_PERM_DIR},
69	{ACL_WRITE_EXTATTRIBUTES, "writeextattr", ACL_PERM_FILE | ACL_PERM_DIR},
70	{ACL_READ_SECURITY,	"readsecurity",	ACL_PERM_FILE | ACL_PERM_DIR},
71	{ACL_WRITE_SECURITY,	"writesecurity", ACL_PERM_FILE | ACL_PERM_DIR},
72	{ACL_CHANGE_OWNER,	"chown",	ACL_PERM_FILE | ACL_PERM_DIR},
73	{0, NULL, 0}
74};
75
76static struct {
77	acl_flag_t	flag;
78	char		*name;
79	int		flags;
80} acl_flags[] = {
81	{ACL_ENTRY_INHERITED,		"inherited",		ACL_PERM_FILE | ACL_PERM_DIR},
82	{ACL_ENTRY_FILE_INHERIT, 	"file_inherit",		ACL_PERM_DIR},
83	{ACL_ENTRY_DIRECTORY_INHERIT,	"directory_inherit",	ACL_PERM_DIR},
84	{ACL_ENTRY_LIMIT_INHERIT,	"limit_inherit",	ACL_PERM_FILE | ACL_PERM_DIR},
85	{ACL_ENTRY_ONLY_INHERIT,	"only_inherit",		ACL_PERM_DIR},
86	{0, NULL, 0}
87};
88
89/* TBD - Many of these routines could potentially be considered for
90 * inclusion in a library. If that is done, either avoid use of "err"
91 * and implement a better fall-through strategy in case of errors,
92 * or use err_set_exit() and make various structures globals.
93 */
94
95#define NAME_USER   (1)
96#define NAME_GROUP  (2)
97#define NAME_EITHER (NAME_USER | NAME_GROUP)
98
99/* Perform a name to uuid mapping - calls through to memberd */
100
101uuid_t *
102name_to_uuid(char *tok, int nametype) {
103	struct passwd *tpass = NULL;
104	struct group *tgrp = NULL;
105	uuid_t *entryg = NULL;
106
107	if ((entryg = (uuid_t *) calloc(1,sizeof(uuid_t))) == NULL)
108		err(1, "Unable to allocate a uuid");
109
110	if (nametype & NAME_USER)
111		tpass = getpwnam(tok);
112
113	if (NULL == tpass && (nametype & NAME_GROUP))
114		tgrp = getgrnam(tok);
115
116	if (tpass) {
117		if (0 != mbr_uid_to_uuid(tpass->pw_uid, *entryg)) {
118			errx(1, "mbr_uid_to_uuid(): Unable to translate uid %d", tpass->pw_uid);
119		}
120	} else if (tgrp) {
121		if (0 != mbr_gid_to_uuid(tgrp->gr_gid, *entryg)) {
122			errx(1, "mbr_gid_to_uuid(): Unable to translate gid %d", tgrp->gr_gid);
123		}
124	} else {
125		errx(1, "Unable to translate '%s' to a UID/GID", tok);
126	}
127	return entryg;
128}
129
130/* Convert an acl entry in string form to an acl_entry_t */
131int
132parse_entry(char *entrybuf, acl_entry_t newent) {
133	char *tok;
134	char *pebuf;
135	uuid_t *entryg;
136
137	acl_tag_t	tag;
138	acl_permset_t	perms;
139	acl_flagset_t	flags;
140	unsigned permcount = 0;
141	unsigned pindex = 0;
142	char *delimiter = " ";
143	int nametype = NAME_EITHER;
144
145	acl_get_permset(newent, &perms);
146	acl_get_flagset_np(newent, &flags);
147
148	pebuf = entrybuf;
149
150	if (0 == strncmp(entrybuf, "user:", 5)) {
151		nametype = NAME_USER;
152		pebuf += 5;
153	} else if (0 == strncmp(entrybuf, "group:", 6)) {
154		nametype = NAME_GROUP;
155		pebuf += 6;
156	}
157
158	if (strchr(pebuf, ':')) /* User/Group names can have spaces */
159		delimiter = ":";
160	tok = strsep(&pebuf, delimiter);
161
162	if ((tok == NULL) || *tok == '\0') {
163		errx(1, "Invalid entry format -- expected user or group name");
164	}
165
166	/* parse the name into a qualifier */
167	entryg = name_to_uuid(tok, nametype);
168
169	tok = strsep(&pebuf, ": "); /* Stick with delimiter? */
170	if ((tok == NULL) || *tok == '\0') {
171		errx(1, "Invalid entry format -- expected allow or deny");
172	}
173
174	/* is the verb 'allow' or 'deny'? */
175	if (!strcmp(tok, "allow")) {
176		tag = ACL_EXTENDED_ALLOW;
177	} else if (!strcmp(tok, "deny")) {
178		tag = ACL_EXTENDED_DENY;
179	} else {
180		errx(1, "Unknown tag type '%s'", tok);
181	}
182
183	/* parse permissions */
184	for (; (tok = strsep(&pebuf, ",")) != NULL;) {
185		if (*tok != '\0') {
186			/* is it a permission? */
187			for (pindex = 0; acl_perms[pindex].name != NULL; pindex++) {
188				if (!strcmp(acl_perms[pindex].name, tok)) {
189					/* got one */
190					acl_add_perm(perms, acl_perms[pindex].perm);
191					permcount++;
192					goto found;
193				}
194			}
195			/* is it a flag? */
196			for (pindex = 0; acl_flags[pindex].name != NULL; pindex++) {
197				if (!strcmp(acl_flags[pindex].name, tok)) {
198					/* got one */
199					acl_add_flag_np(flags, acl_flags[pindex].flag);
200					permcount++;
201					goto found;
202				}
203			}
204			errx(1,"Invalid permission type '%s'", tok);
205		found:
206			continue;
207		}
208	}
209	if (0 == permcount) {
210		errx(1, "No permissions specified");
211	}
212	acl_set_tag_type(newent, tag);
213	acl_set_qualifier(newent, entryg);
214	acl_set_permset(newent, perms);
215	acl_set_flagset_np(newent, flags);
216	free(entryg);
217
218	return(0);
219}
220
221/* Convert one or more acl entries in string form to an acl_t */
222acl_t
223parse_acl_entries(const char *input) {
224	acl_t acl_input;
225	acl_entry_t newent;
226	char *inbuf;
227	char *oinbuf;
228
229	char **bufp, *entryv[ACL_MAX_ENTRIES];
230#if 0
231/* XXX acl_from_text(), when implemented, will presumably use the canonical
232 * text representation format, which is what chmod should be using
233 * We may need to add an entry number to the input
234 */
235	/* Translate the user supplied ACL entry */
236	/* acl_input = acl_from_text(input); */
237#else
238	inbuf = malloc(MAX_ACL_TEXT_SIZE);
239
240	if (inbuf == NULL)
241		err(1, "malloc() failed");
242	strncpy(inbuf, input, MAX_ACL_TEXT_SIZE);
243	inbuf[MAX_ACL_TEXT_SIZE - 1] = '\0';
244
245	if ((acl_input = acl_init(1)) == NULL)
246		err(1, "acl_init() failed");
247
248	oinbuf = inbuf;
249
250	for (bufp = entryv; (*bufp = strsep(&oinbuf, "\n")) != NULL;)
251		if (**bufp != '\0') {
252			if (0 != acl_create_entry(&acl_input, &newent))
253				err(1, "acl_create_entry() failed");
254			if (0 != parse_entry(*bufp, newent)) {
255				errx(1, "Failed parsing entry '%s'", *bufp);
256			}
257			if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) {
258				errx(1, "Too many entries");
259			}
260		}
261
262	free(inbuf);
263	return acl_input;
264#endif	/* #if 0 */
265}
266
267/* XXX No Libc support for inherited entries and generation determination yet */
268unsigned
269get_inheritance_level(acl_entry_t entry) {
270/* XXX to be implemented */
271	return 1;
272}
273
274/* Determine a "score" for an acl entry. The entry scores higher if it's
275 * tagged ACL_EXTENDED_DENY, and non-inherited entries are ranked higher
276 * than inherited entries.
277 */
278
279int
280score_acl_entry(acl_entry_t entry) {
281
282	acl_tag_t	tag;
283	acl_flagset_t	flags;
284	acl_permset_t	perms;
285
286	int score = 0;
287
288	if (entry == NULL)
289		return (MINIMUM_TIER);
290
291	if (acl_get_tag_type(entry, &tag) != 0) {
292		err(1, "Malformed ACL entry, no tag present");
293	}
294	if (acl_get_flagset_np(entry, &flags) != 0){
295		err(1, "Unable to obtain flagset");
296	}
297	if (acl_get_permset(entry, &perms) != 0)
298		err(1, "Malformed ACL entry, no permset present");
299
300	switch(tag) {
301	case ACL_EXTENDED_ALLOW:
302		break;
303	case ACL_EXTENDED_DENY:
304		score++;
305		break;
306	default:
307		errx(1, "Unknown tag type %d present in ACL entry", tag);
308	        /* NOTREACHED */
309	}
310
311	if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
312		score += get_inheritance_level(entry) * INHERITANCE_TIER;
313
314	return score;
315}
316
317int
318compare_acl_qualifiers(uuid_t *qa, uuid_t *qb) {
319	return bcmp(qa, qb, sizeof(uuid_t));
320}
321
322/* Compare two ACL permsets.
323 *  Returns :
324 *  MATCH_SUBSET if bperms is a subset of aperms
325 *  MATCH_SUPERSET if bperms is a superset of aperms
326 *  MATCH_PARTIAL if the two permsets have a common subset
327 *  MATCH_EXACT if the two permsets are identical
328 *  MATCH_NONE if they are disjoint
329 */
330
331int
332compare_acl_permsets(acl_permset_t aperms, acl_permset_t bperms)
333{
334	int i;
335/* TBD Implement other match levels as needed */
336	for (i = 0; acl_perms[i].name != NULL; i++) {
337		if (acl_get_perm_np(aperms, acl_perms[i].perm) !=
338		    acl_get_perm_np(bperms, acl_perms[i].perm))
339			return MATCH_NONE;
340	}
341	return MATCH_EXACT;
342}
343
344static int
345compare_acl_flagsets(acl_flagset_t aflags, acl_flagset_t bflags)
346{
347	int i;
348/* TBD Implement other match levels as needed */
349	for (i = 0; acl_flags[i].name != NULL; i++) {
350		if (acl_get_flag_np(aflags, acl_flags[i].flag) !=
351		    acl_get_flag_np(bflags, acl_flags[i].flag))
352			return MATCH_NONE;
353	}
354	return MATCH_EXACT;
355}
356
357/* Compares two ACL entries for equality */
358int
359compare_acl_entries(acl_entry_t a, acl_entry_t b)
360{
361	acl_tag_t atag, btag;
362	acl_permset_t aperms, bperms;
363	acl_flagset_t aflags, bflags;
364	int pcmp = 0, fcmp = 0;
365	void *aqual, *bqual;
366
367	aqual = acl_get_qualifier(a);
368	bqual = acl_get_qualifier(b);
369
370	int compare = compare_acl_qualifiers(aqual, bqual);
371	acl_free(aqual);
372	acl_free(bqual);
373
374	if (compare != 0)
375		return MATCH_NONE;
376
377	if (0 != acl_get_tag_type(a, &atag))
378		err(1, "No tag type present in entry");
379	if (0!= acl_get_tag_type(b, &btag))
380		err(1, "No tag type present in entry");
381
382	if (atag != btag)
383		return MATCH_NONE;
384
385	if ((acl_get_permset(a, &aperms) != 0) ||
386	    (acl_get_flagset_np(a, &aflags) != 0) ||
387	    (acl_get_permset(b, &bperms) != 0) ||
388	    (acl_get_flagset_np(b, &bflags) != 0))
389		err(1, "error fetching permissions");
390
391	pcmp = compare_acl_permsets(aperms, bperms);
392	fcmp = compare_acl_flagsets(aflags, bflags);
393
394	if ((pcmp == MATCH_NONE) || (fcmp == MATCH_NONE))
395		return(MATCH_PARTIAL);
396	else
397		return(MATCH_EXACT);
398}
399
400/* Verify that an ACL is in canonical order. Currently, the canonical
401 * form is:
402 * local deny
403 * local allow
404 * inherited deny (parent)
405 * inherited allow (parent)
406 * inherited deny (grandparent)
407 * inherited allow (grandparent)
408 * ...
409 */
410unsigned int
411is_canonical(acl_t acl) {
412
413	unsigned aindex;
414	acl_entry_t entry;
415	int score = 0, next_score = 0;
416
417/* XXX - is a zero entry ACL in canonical form? */
418	if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
419		return 1;
420
421	score = score_acl_entry(entry);
422
423	for (aindex = 0; acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) == 0;
424	     aindex++)	{
425		if (score < (next_score = score_acl_entry(entry)))
426			return 0;
427		score = next_score;
428	}
429	return 1;
430}
431
432
433/* Iterate through an ACL, and find the canonical position for the
434 * specified entry
435 */
436unsigned int
437find_canonical_position(acl_t acl, acl_entry_t modifier) {
438
439	acl_entry_t entry;
440	int mscore = 0;
441	unsigned mpos = 0;
442
443	/* Check if there's an entry with the same qualifier
444	 * and tag type; if not, find the appropriate slot
445	 * for the score.
446	 */
447
448	if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
449		return 0;
450
451	mscore = score_acl_entry(modifier);
452
453	while (mscore < score_acl_entry(entry)) {
454
455		mpos++;
456
457	       if (0 != acl_get_entry(acl, ACL_NEXT_ENTRY, &entry))
458		       break;
459
460	       }
461	return mpos;
462}
463
464int canonicalize_acl_entries(acl_t acl);
465
466/* For a given acl_entry_t "modifier", find the first exact or
467 * partially matching entry from the specified acl_t acl
468 */
469
470int
471find_matching_entry (acl_t acl, acl_entry_t modifier, acl_entry_t *rentryp,
472		     unsigned match_inherited) {
473
474	acl_entry_t entry = NULL;
475
476	unsigned aindex;
477	int cmp, fcmp = MATCH_NONE;
478
479	for (aindex = 0;
480	     acl_get_entry(acl, entry == NULL ? ACL_FIRST_ENTRY :
481			   ACL_NEXT_ENTRY, &entry) == 0;
482	     aindex++)	{
483		cmp = compare_acl_entries(entry, modifier);
484		if ((cmp == MATCH_EXACT) || (cmp == MATCH_PARTIAL)) {
485			if (match_inherited) {
486				acl_flagset_t eflags, mflags;
487
488				if (0 != acl_get_flagset_np(modifier, &mflags))
489					err(1, "Unable to get flagset");
490
491				if (0 != acl_get_flagset_np(entry, &eflags))
492					err(1, "Unable to get flagset");
493
494				if (compare_acl_flagsets(mflags, eflags) == MATCH_EXACT) {
495					*rentryp = entry;
496					fcmp = cmp;
497				}
498			}
499			else {
500				*rentryp = entry;
501				fcmp = cmp;
502			}
503		}
504		if (fcmp == MATCH_EXACT)
505			break;
506	}
507	return fcmp;
508}
509
510/* Remove all perms specified in modifier from rentry*/
511int
512subtract_from_entry(acl_entry_t rentry, acl_entry_t  modifier, int* valid_perms)
513{
514	acl_permset_t rperms, mperms;
515	acl_flagset_t rflags, mflags;
516	if (valid_perms)
517		*valid_perms = 0;
518	int i;
519
520	if ((acl_get_permset(rentry, &rperms) != 0) ||
521	    (acl_get_flagset_np(rentry, &rflags) != 0) ||
522	    (acl_get_permset(modifier, &mperms) != 0) ||
523	    (acl_get_flagset_np(modifier, &mflags) != 0))
524		err(1, "error computing ACL modification");
525
526	for (i = 0; acl_perms[i].name != NULL; i++) {
527		if (acl_get_perm_np(mperms, acl_perms[i].perm))
528			acl_delete_perm(rperms, acl_perms[i].perm);
529		else if (valid_perms && acl_get_perm_np(rperms, acl_perms[i].perm))
530			(*valid_perms)++;
531	}
532	for (i = 0; acl_flags[i].name != NULL; i++) {
533		if (acl_get_flag_np(mflags, acl_flags[i].flag))
534			acl_delete_flag_np(rflags, acl_flags[i].flag);
535	}
536	acl_set_permset(rentry, rperms);
537	acl_set_flagset_np(rentry, rflags);
538	return 0;
539}
540/* Add the perms specified in modifier to rentry */
541static int
542merge_entry_perms(acl_entry_t rentry, acl_entry_t  modifier)
543{
544	acl_permset_t rperms, mperms;
545	acl_flagset_t rflags, mflags;
546	int i;
547
548	if ((acl_get_permset(rentry, &rperms) != 0) ||
549	    (acl_get_flagset_np(rentry, &rflags) != 0) ||
550	    (acl_get_permset(modifier, &mperms) != 0) ||
551	    (acl_get_flagset_np(modifier, &mflags) != 0))
552		err(1, "error computing ACL modification");
553
554	for (i = 0; acl_perms[i].name != NULL; i++) {
555		if (acl_get_perm_np(mperms, acl_perms[i].perm))
556			acl_add_perm(rperms, acl_perms[i].perm);
557	}
558	for (i = 0; acl_flags[i].name != NULL; i++) {
559		if (acl_get_flag_np(mflags, acl_flags[i].flag))
560			acl_add_flag_np(rflags, acl_flags[i].flag);
561	}
562	acl_set_permset(rentry, rperms);
563	acl_set_flagset_np(rentry, rflags);
564	return 0;
565}
566
567int
568modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags,
569	   int position, int inheritance_level,
570	   unsigned flag_new_acl, const char* path) {
571
572	unsigned cpos = 0;
573	acl_entry_t newent = NULL;
574	int dmatch = 0;
575	acl_entry_t rentry = NULL;
576	unsigned retval = 0;
577	acl_t oacl = *oaclp;
578
579/* Add the inherited flag if requested by the user*/
580	if (modifier && (optflags & ACL_INHERIT_FLAG)) {
581		acl_flagset_t mflags;
582
583		acl_get_flagset_np(modifier, &mflags);
584		acl_add_flag_np(mflags, ACL_ENTRY_INHERITED);
585		acl_set_flagset_np(modifier, mflags);
586	}
587
588	if (optflags & ACL_SET_FLAG) {
589		if (position != -1) {
590			if (0 != acl_create_entry_np(&oacl, &newent, position))
591				err(1, "acl_create_entry() failed");
592			acl_copy_entry(newent, modifier);
593		} else {
594/* If an entry exists, add the new permissions to it, else add an
595 * entry in the canonical position.
596 */
597
598/* First, check for a matching entry - if one exists, merge flags */
599			dmatch = find_matching_entry(oacl, modifier, &rentry, 1);
600
601			if (dmatch != MATCH_NONE) {
602				if (dmatch == MATCH_EXACT)
603/* Nothing to be done */
604					goto ma_exit;
605
606				if (dmatch == MATCH_PARTIAL) {
607					merge_entry_perms(rentry, modifier);
608					goto ma_exit;
609				}
610			}
611/* Insert the entry in canonical order */
612			cpos = find_canonical_position(oacl, modifier);
613			if (0!= acl_create_entry_np(&oacl, &newent, cpos))
614				err(1, "acl_create_entry() failed");
615			acl_copy_entry(newent, modifier);
616		}
617	} else if (optflags & ACL_DELETE_FLAG) {
618		if (flag_new_acl) {
619			warnx("No ACL present '%s'", path);
620			retval = 1;
621		} else if (position != -1 ) {
622			if (0 != acl_get_entry(oacl, position, &rentry)) {
623				warnx("Invalid entry number '%s'", path);
624				retval = 1;
625			} else {
626				acl_delete_entry(oacl, rentry);
627			}
628		} else {
629			unsigned match_found = 0, aindex;
630			for (aindex = 0;
631			     acl_get_entry(oacl, rentry == NULL ?
632					   ACL_FIRST_ENTRY :
633					   ACL_NEXT_ENTRY, &rentry) == 0;
634			     aindex++)	{
635				unsigned cmp;
636				cmp = compare_acl_entries(rentry, modifier);
637				if ((cmp == MATCH_EXACT) ||
638				    (cmp == MATCH_PARTIAL)) {
639					match_found++;
640					if (cmp == MATCH_EXACT)
641						acl_delete_entry(oacl, rentry);
642					else {
643						int valid_perms;
644/* In the event of a partial match, remove the specified perms from the
645 * entry */
646						subtract_from_entry(rentry, modifier, &valid_perms);
647						/* if no perms survived then delete the entry */
648						if (valid_perms == 0)
649							acl_delete_entry(oacl, rentry);
650					}
651				}
652			}
653			if (0 == match_found) {
654				warnx("Entry not found when attempting delete '%s'",path);
655				retval = 1;
656			}
657		}
658	} else if (optflags & ACL_REWRITE_FLAG) {
659		acl_entry_t rentry;
660
661		if (-1 == position) {
662			usage();
663		}
664		if (0 == flag_new_acl) {
665			if (0 != acl_get_entry(oacl, position,
666					       &rentry))
667				err(1, "Invalid entry number '%s'", path);
668
669			if (0 != acl_delete_entry(oacl, rentry))
670				err(1, "Unable to delete entry '%s'", path);
671		}
672		if (0!= acl_create_entry_np(&oacl, &newent, position))
673			err(1, "acl_create_entry() failed");
674		acl_copy_entry(newent, modifier);
675	}
676ma_exit:
677	*oaclp = oacl;
678	return retval;
679}
680
681int
682modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow) {
683
684	acl_t oacl = NULL;
685	unsigned aindex  = 0, flag_new_acl = 0;
686	acl_entry_t newent = NULL;
687	acl_entry_t entry = NULL;
688	unsigned retval = 0 ;
689
690	extern int fflag;
691
692/* XXX acl_get_file() returns a zero entry ACL if an ACL was previously
693 * associated with the file, and has had its entries removed.
694 * However, POSIX 1003.1e states that a zero entry ACL should be
695 * returned if the caller asks for ACL_TYPE_DEFAULT, and no ACL is
696 * associated with the path; it
697 * does not specifically state that a request for ACL_TYPE_EXTENDED
698 * should not return a zero entry ACL, however.
699 */
700
701/* Determine if we've been given a zero entry ACL, or create an ACL if
702 * none exists. There are some issues to consider here: Should we create
703 * a zero-entry ACL for a delete or check canonicity operation?
704 */
705
706	if (path == NULL)
707		usage();
708
709	if (optflags & ACL_CLEAR_FLAG) {
710		filesec_t fsec = filesec_init();
711		if (fsec == NULL)
712			err(1, "filesec_init() failed");
713		if (filesec_set_property(fsec, FILESEC_ACL,
714					 _FILESEC_REMOVE_ACL) != 0)
715			err(1, "filesec_set_property() failed");
716		if (chmodx_np(path, fsec) != 0) {
717			if (!fflag)
718				warn("Failed to clear ACL on file %s", path);
719			retval = 1;
720		} else
721			retval = 0;
722		filesec_free(fsec);
723		return (retval);
724	}
725
726	if (optflags & ACL_FROM_STDIN) {
727		oacl = acl_dup(modifier);
728	} else {
729		if (follow) {
730			oacl = acl_get_file(path, ACL_TYPE_EXTENDED);
731		} else {
732			int fd = open(path, O_SYMLINK);
733			if (fd != -1) {
734				oacl = acl_get_fd_np(fd, ACL_TYPE_EXTENDED);
735				close(fd);
736			}
737		}
738		if ((oacl == NULL) ||
739		    (acl_get_entry(oacl,ACL_FIRST_ENTRY, &newent) != 0)) {
740			if ((oacl = acl_init(1)) == NULL)
741				err(1, "acl_init() failed");
742			flag_new_acl = 1;
743			position = 0;
744		}
745
746		if ((0 == flag_new_acl) && (optflags & (ACL_REMOVE_INHERIT_FLAG |
747							ACL_REMOVE_INHERITED_ENTRIES))) {
748			acl_t facl = NULL;
749			if ((facl = acl_init(1)) == NULL)
750				err(1, "acl_init() failed");
751			for (aindex = 0;
752			     acl_get_entry(oacl,
753					   (entry == NULL ? ACL_FIRST_ENTRY :
754					    ACL_NEXT_ENTRY), &entry) == 0;
755			     aindex++) {
756				acl_flagset_t eflags;
757				acl_entry_t fent = NULL;
758				if (acl_get_flagset_np(entry, &eflags) != 0) {
759					err(1, "Unable to obtain flagset");
760				}
761
762				if (acl_get_flag_np(eflags, ACL_ENTRY_INHERITED)) {
763					if (optflags & ACL_REMOVE_INHERIT_FLAG) {
764						acl_delete_flag_np(eflags, ACL_ENTRY_INHERITED);
765						acl_set_flagset_np(entry, eflags);
766						acl_create_entry(&facl, &fent);
767						acl_copy_entry(fent, entry);
768					}
769				}
770				else {
771					acl_create_entry(&facl, &fent);
772					acl_copy_entry(fent, entry);
773				}
774			}
775			if (oacl)
776				acl_free(oacl);
777			oacl = facl;
778		} else if (optflags & ACL_TO_STDOUT) {
779			ssize_t len; /* need to get printacl() from ls(1) */
780			char *text = acl_to_text(oacl, &len);
781			puts(text);
782			acl_free(text);
783		} else if (optflags & ACL_CHECK_CANONICITY) {
784			if (flag_new_acl) {
785				warnx("No ACL currently associated with file '%s'", path);
786			}
787			retval = is_canonical(oacl);
788		} else if ((optflags & ACL_SET_FLAG) && (position == -1) &&
789		    (!is_canonical(oacl))) {
790			warnx("The specified file '%s' does not have an ACL in canonical order, please specify a position with +a# ", path);
791			retval = 1;
792		} else if (((optflags & ACL_DELETE_FLAG) && (position != -1))
793		    || (optflags & ACL_CHECK_CANONICITY)) {
794			retval = modify_acl(&oacl, NULL, optflags, position,
795					    inheritance_level, flag_new_acl, path);
796		} else if ((optflags & (ACL_REMOVE_INHERIT_FLAG|ACL_REMOVE_INHERITED_ENTRIES)) && flag_new_acl) {
797			warnx("No ACL currently associated with file '%s'", path);
798			retval = 1;
799		} else {
800			if (!modifier) { /* avoid bus error in acl_get_entry */
801				errx(1, "Internal error: modifier should not be NULL");
802			}
803			for (aindex = 0;
804			     acl_get_entry(modifier,
805					   (entry == NULL ? ACL_FIRST_ENTRY :
806					    ACL_NEXT_ENTRY), &entry) == 0;
807			     aindex++) {
808
809				retval += modify_acl(&oacl, entry, optflags,
810						     position, inheritance_level,
811						     flag_new_acl, path);
812			}
813		}
814	}
815
816/* XXX Potential race here, since someone else could've modified or
817 * read the ACL on this file (with the intention of modifying it) in
818 * the interval from acl_get_file() to acl_set_file(); we can
819 * minimize one aspect of this  window by comparing the original acl
820 * to a fresh one from acl_get_file() but we could consider a
821 * "changeset" mechanism, common locking  strategy, or kernel
822 * supplied reservation mechanism to prevent this race.
823 */
824	if (!(optflags & (ACL_TO_STDOUT|ACL_CHECK_CANONICITY))) {
825		int status = -1;
826		if (follow) {
827	    		status = acl_set_file(path, ACL_TYPE_EXTENDED, oacl);
828		} else {
829			int fd = open(path, O_SYMLINK);
830			if (fd != -1) {
831				status = acl_set_fd_np(fd, oacl,
832							ACL_TYPE_EXTENDED);
833				close(fd);
834			}
835		}
836		if (status != 0) {
837			if (!fflag)
838				warn("Failed to set ACL on file '%s'", path);
839			retval = 1;
840		}
841	}
842
843	if (oacl)
844		acl_free(oacl);
845
846	return retval;
847}
848
849#endif /*__APPLE__*/
850