file_cmds-264.1.1.tar.gz
[apple/file_cmds.git] / chmod / chmod_acl.c
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
46 extern void usage(void);
47
48 #ifdef __APPLE__
49 static 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
76 static 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
101 uuid_t *
102 name_to_uuid(char *tok, int nametype) {
103 uuid_t *entryg = NULL;
104 size_t len = strlen(tok);
105
106 if ((entryg = (uuid_t *) calloc(1, sizeof(uuid_t))) == NULL) {
107 errx(1, "Unable to allocate a uuid");
108 }
109
110 if ((nametype & NAME_USER) && mbr_identifier_to_uuid(ID_TYPE_USERNAME, tok, len, *entryg) == 0) {
111 return entryg;
112 }
113
114 if ((nametype & NAME_GROUP) && mbr_identifier_to_uuid(ID_TYPE_GROUPNAME, tok, len, *entryg) == 0) {
115 return entryg;
116 }
117
118 errx(1, "Unable to translate '%s' to a UUID", tok);
119 }
120
121 /* Convert an acl entry in string form to an acl_entry_t */
122 int
123 parse_entry(char *entrybuf, acl_entry_t newent) {
124 char *tok;
125 char *pebuf;
126 uuid_t *entryg;
127
128 acl_tag_t tag;
129 acl_permset_t perms;
130 acl_flagset_t flags;
131 unsigned permcount = 0;
132 unsigned pindex = 0;
133 char *delimiter = " ";
134 int nametype = NAME_EITHER;
135
136 acl_get_permset(newent, &perms);
137 acl_get_flagset_np(newent, &flags);
138
139 pebuf = entrybuf;
140
141 if (0 == strncmp(entrybuf, "user:", 5)) {
142 nametype = NAME_USER;
143 pebuf += 5;
144 } else if (0 == strncmp(entrybuf, "group:", 6)) {
145 nametype = NAME_GROUP;
146 pebuf += 6;
147 }
148
149 if (strchr(pebuf, ':')) /* User/Group names can have spaces */
150 delimiter = ":";
151 tok = strsep(&pebuf, delimiter);
152
153 if ((tok == NULL) || *tok == '\0') {
154 errx(1, "Invalid entry format -- expected user or group name");
155 }
156
157 /* parse the name into a qualifier */
158 entryg = name_to_uuid(tok, nametype);
159
160 tok = strsep(&pebuf, ": "); /* Stick with delimiter? */
161 if ((tok == NULL) || *tok == '\0') {
162 errx(1, "Invalid entry format -- expected allow or deny");
163 }
164
165 /* is the verb 'allow' or 'deny'? */
166 if (!strcmp(tok, "allow")) {
167 tag = ACL_EXTENDED_ALLOW;
168 } else if (!strcmp(tok, "deny")) {
169 tag = ACL_EXTENDED_DENY;
170 } else {
171 errx(1, "Unknown tag type '%s'", tok);
172 }
173
174 /* parse permissions */
175 for (; (tok = strsep(&pebuf, ",")) != NULL;) {
176 if (*tok != '\0') {
177 /* is it a permission? */
178 for (pindex = 0; acl_perms[pindex].name != NULL; pindex++) {
179 if (!strcmp(acl_perms[pindex].name, tok)) {
180 /* got one */
181 acl_add_perm(perms, acl_perms[pindex].perm);
182 permcount++;
183 goto found;
184 }
185 }
186 /* is it a flag? */
187 for (pindex = 0; acl_flags[pindex].name != NULL; pindex++) {
188 if (!strcmp(acl_flags[pindex].name, tok)) {
189 /* got one */
190 acl_add_flag_np(flags, acl_flags[pindex].flag);
191 permcount++;
192 goto found;
193 }
194 }
195 errx(1,"Invalid permission type '%s'", tok);
196 found:
197 continue;
198 }
199 }
200 if (0 == permcount) {
201 errx(1, "No permissions specified");
202 }
203 acl_set_tag_type(newent, tag);
204 acl_set_qualifier(newent, entryg);
205 acl_set_permset(newent, perms);
206 acl_set_flagset_np(newent, flags);
207 free(entryg);
208
209 return(0);
210 }
211
212 /* Convert one or more acl entries in string form to an acl_t */
213 acl_t
214 parse_acl_entries(const char *input) {
215 acl_t acl_input;
216 acl_entry_t newent;
217 char *inbuf;
218 char *oinbuf;
219
220 char **bufp, *entryv[ACL_MAX_ENTRIES];
221 #if 0
222 /* XXX acl_from_text(), when implemented, will presumably use the canonical
223 * text representation format, which is what chmod should be using
224 * We may need to add an entry number to the input
225 */
226 /* Translate the user supplied ACL entry */
227 /* acl_input = acl_from_text(input); */
228 #else
229 inbuf = malloc(MAX_ACL_TEXT_SIZE);
230
231 if (inbuf == NULL)
232 err(1, "malloc() failed");
233 strncpy(inbuf, input, MAX_ACL_TEXT_SIZE);
234 inbuf[MAX_ACL_TEXT_SIZE - 1] = '\0';
235
236 if ((acl_input = acl_init(1)) == NULL)
237 err(1, "acl_init() failed");
238
239 oinbuf = inbuf;
240
241 for (bufp = entryv; (*bufp = strsep(&oinbuf, "\n")) != NULL;)
242 if (**bufp != '\0') {
243 if (0 != acl_create_entry(&acl_input, &newent))
244 err(1, "acl_create_entry() failed");
245 if (0 != parse_entry(*bufp, newent)) {
246 errx(1, "Failed parsing entry '%s'", *bufp);
247 }
248 if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) {
249 errx(1, "Too many entries");
250 }
251 }
252
253 free(inbuf);
254 return acl_input;
255 #endif /* #if 0 */
256 }
257
258 /* XXX No Libc support for inherited entries and generation determination yet */
259 unsigned
260 get_inheritance_level(acl_entry_t entry) {
261 /* XXX to be implemented */
262 return 1;
263 }
264
265 /* Determine a "score" for an acl entry. The entry scores higher if it's
266 * tagged ACL_EXTENDED_DENY, and non-inherited entries are ranked higher
267 * than inherited entries.
268 */
269
270 int
271 score_acl_entry(acl_entry_t entry) {
272
273 acl_tag_t tag;
274 acl_flagset_t flags;
275 acl_permset_t perms;
276
277 int score = 0;
278
279 if (entry == NULL)
280 return (MINIMUM_TIER);
281
282 if (acl_get_tag_type(entry, &tag) != 0) {
283 err(1, "Malformed ACL entry, no tag present");
284 }
285 if (acl_get_flagset_np(entry, &flags) != 0){
286 err(1, "Unable to obtain flagset");
287 }
288 if (acl_get_permset(entry, &perms) != 0)
289 err(1, "Malformed ACL entry, no permset present");
290
291 switch(tag) {
292 case ACL_EXTENDED_ALLOW:
293 break;
294 case ACL_EXTENDED_DENY:
295 score++;
296 break;
297 default:
298 errx(1, "Unknown tag type %d present in ACL entry", tag);
299 /* NOTREACHED */
300 }
301
302 if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
303 score += get_inheritance_level(entry) * INHERITANCE_TIER;
304
305 return score;
306 }
307
308 int
309 compare_acl_qualifiers(uuid_t *qa, uuid_t *qb) {
310 return bcmp(qa, qb, sizeof(uuid_t));
311 }
312
313 /* Compare two ACL permsets.
314 * Returns :
315 * MATCH_SUBSET if bperms is a subset of aperms
316 * MATCH_SUPERSET if bperms is a superset of aperms
317 * MATCH_PARTIAL if the two permsets have a common subset
318 * MATCH_EXACT if the two permsets are identical
319 * MATCH_NONE if they are disjoint
320 */
321
322 int
323 compare_acl_permsets(acl_permset_t aperms, acl_permset_t bperms)
324 {
325 int i;
326 /* TBD Implement other match levels as needed */
327 for (i = 0; acl_perms[i].name != NULL; i++) {
328 if (acl_get_perm_np(aperms, acl_perms[i].perm) !=
329 acl_get_perm_np(bperms, acl_perms[i].perm))
330 return MATCH_NONE;
331 }
332 return MATCH_EXACT;
333 }
334
335 static int
336 compare_acl_flagsets(acl_flagset_t aflags, acl_flagset_t bflags)
337 {
338 int i;
339 /* TBD Implement other match levels as needed */
340 for (i = 0; acl_flags[i].name != NULL; i++) {
341 if (acl_get_flag_np(aflags, acl_flags[i].flag) !=
342 acl_get_flag_np(bflags, acl_flags[i].flag))
343 return MATCH_NONE;
344 }
345 return MATCH_EXACT;
346 }
347
348 /* Compares two ACL entries for equality */
349 int
350 compare_acl_entries(acl_entry_t a, acl_entry_t b)
351 {
352 acl_tag_t atag, btag;
353 acl_permset_t aperms, bperms;
354 acl_flagset_t aflags, bflags;
355 int pcmp = 0, fcmp = 0;
356 void *aqual, *bqual;
357
358 aqual = acl_get_qualifier(a);
359 bqual = acl_get_qualifier(b);
360
361 int compare = compare_acl_qualifiers(aqual, bqual);
362 acl_free(aqual);
363 acl_free(bqual);
364
365 if (compare != 0)
366 return MATCH_NONE;
367
368 if (0 != acl_get_tag_type(a, &atag))
369 err(1, "No tag type present in entry");
370 if (0!= acl_get_tag_type(b, &btag))
371 err(1, "No tag type present in entry");
372
373 if (atag != btag)
374 return MATCH_NONE;
375
376 if ((acl_get_permset(a, &aperms) != 0) ||
377 (acl_get_flagset_np(a, &aflags) != 0) ||
378 (acl_get_permset(b, &bperms) != 0) ||
379 (acl_get_flagset_np(b, &bflags) != 0))
380 err(1, "error fetching permissions");
381
382 pcmp = compare_acl_permsets(aperms, bperms);
383 fcmp = compare_acl_flagsets(aflags, bflags);
384
385 if ((pcmp == MATCH_NONE) || (fcmp == MATCH_NONE))
386 return(MATCH_PARTIAL);
387 else
388 return(MATCH_EXACT);
389 }
390
391 /* Verify that an ACL is in canonical order. Currently, the canonical
392 * form is:
393 * local deny
394 * local allow
395 * inherited deny (parent)
396 * inherited allow (parent)
397 * inherited deny (grandparent)
398 * inherited allow (grandparent)
399 * ...
400 */
401 unsigned int
402 is_canonical(acl_t acl) {
403
404 unsigned aindex;
405 acl_entry_t entry;
406 int score = 0, next_score = 0;
407
408 /* XXX - is a zero entry ACL in canonical form? */
409 if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
410 return 1;
411
412 score = score_acl_entry(entry);
413
414 for (aindex = 0; acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) == 0;
415 aindex++) {
416 if (score < (next_score = score_acl_entry(entry)))
417 return 0;
418 score = next_score;
419 }
420 return 1;
421 }
422
423
424 /* Iterate through an ACL, and find the canonical position for the
425 * specified entry
426 */
427 unsigned int
428 find_canonical_position(acl_t acl, acl_entry_t modifier) {
429
430 acl_entry_t entry;
431 int mscore = 0;
432 unsigned mpos = 0;
433
434 /* Check if there's an entry with the same qualifier
435 * and tag type; if not, find the appropriate slot
436 * for the score.
437 */
438
439 if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
440 return 0;
441
442 mscore = score_acl_entry(modifier);
443
444 while (mscore < score_acl_entry(entry)) {
445
446 mpos++;
447
448 if (0 != acl_get_entry(acl, ACL_NEXT_ENTRY, &entry))
449 break;
450
451 }
452 return mpos;
453 }
454
455 int canonicalize_acl_entries(acl_t acl);
456
457 /* For a given acl_entry_t "modifier", find the first exact or
458 * partially matching entry from the specified acl_t acl
459 */
460
461 int
462 find_matching_entry (acl_t acl, acl_entry_t modifier, acl_entry_t *rentryp,
463 unsigned match_inherited) {
464
465 acl_entry_t entry = NULL;
466
467 unsigned aindex;
468 int cmp, fcmp = MATCH_NONE;
469
470 for (aindex = 0;
471 acl_get_entry(acl, entry == NULL ? ACL_FIRST_ENTRY :
472 ACL_NEXT_ENTRY, &entry) == 0;
473 aindex++) {
474 cmp = compare_acl_entries(entry, modifier);
475 if ((cmp == MATCH_EXACT) || (cmp == MATCH_PARTIAL)) {
476 if (match_inherited) {
477 acl_flagset_t eflags, mflags;
478
479 if (0 != acl_get_flagset_np(modifier, &mflags))
480 err(1, "Unable to get flagset");
481
482 if (0 != acl_get_flagset_np(entry, &eflags))
483 err(1, "Unable to get flagset");
484
485 if (compare_acl_flagsets(mflags, eflags) == MATCH_EXACT) {
486 *rentryp = entry;
487 fcmp = cmp;
488 }
489 }
490 else {
491 *rentryp = entry;
492 fcmp = cmp;
493 }
494 }
495 if (fcmp == MATCH_EXACT)
496 break;
497 }
498 return fcmp;
499 }
500
501 /* Remove all perms specified in modifier from rentry*/
502 int
503 subtract_from_entry(acl_entry_t rentry, acl_entry_t modifier, int* valid_perms)
504 {
505 acl_permset_t rperms, mperms;
506 acl_flagset_t rflags, mflags;
507 if (valid_perms)
508 *valid_perms = 0;
509 int i;
510
511 if ((acl_get_permset(rentry, &rperms) != 0) ||
512 (acl_get_flagset_np(rentry, &rflags) != 0) ||
513 (acl_get_permset(modifier, &mperms) != 0) ||
514 (acl_get_flagset_np(modifier, &mflags) != 0))
515 err(1, "error computing ACL modification");
516
517 for (i = 0; acl_perms[i].name != NULL; i++) {
518 if (acl_get_perm_np(mperms, acl_perms[i].perm))
519 acl_delete_perm(rperms, acl_perms[i].perm);
520 else if (valid_perms && acl_get_perm_np(rperms, acl_perms[i].perm))
521 (*valid_perms)++;
522 }
523 for (i = 0; acl_flags[i].name != NULL; i++) {
524 if (acl_get_flag_np(mflags, acl_flags[i].flag))
525 acl_delete_flag_np(rflags, acl_flags[i].flag);
526 }
527 acl_set_permset(rentry, rperms);
528 acl_set_flagset_np(rentry, rflags);
529 return 0;
530 }
531 /* Add the perms specified in modifier to rentry */
532 static int
533 merge_entry_perms(acl_entry_t rentry, acl_entry_t modifier)
534 {
535 acl_permset_t rperms, mperms;
536 acl_flagset_t rflags, mflags;
537 int i;
538
539 if ((acl_get_permset(rentry, &rperms) != 0) ||
540 (acl_get_flagset_np(rentry, &rflags) != 0) ||
541 (acl_get_permset(modifier, &mperms) != 0) ||
542 (acl_get_flagset_np(modifier, &mflags) != 0))
543 err(1, "error computing ACL modification");
544
545 for (i = 0; acl_perms[i].name != NULL; i++) {
546 if (acl_get_perm_np(mperms, acl_perms[i].perm))
547 acl_add_perm(rperms, acl_perms[i].perm);
548 }
549 for (i = 0; acl_flags[i].name != NULL; i++) {
550 if (acl_get_flag_np(mflags, acl_flags[i].flag))
551 acl_add_flag_np(rflags, acl_flags[i].flag);
552 }
553 acl_set_permset(rentry, rperms);
554 acl_set_flagset_np(rentry, rflags);
555 return 0;
556 }
557
558 int
559 modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags,
560 int position, int inheritance_level,
561 unsigned flag_new_acl, const char* path) {
562
563 unsigned cpos = 0;
564 acl_entry_t newent = NULL;
565 int dmatch = 0;
566 acl_entry_t rentry = NULL;
567 unsigned retval = 0;
568 acl_t oacl = *oaclp;
569
570 /* Add the inherited flag if requested by the user*/
571 if (modifier && (optflags & ACL_INHERIT_FLAG)) {
572 acl_flagset_t mflags;
573
574 acl_get_flagset_np(modifier, &mflags);
575 acl_add_flag_np(mflags, ACL_ENTRY_INHERITED);
576 acl_set_flagset_np(modifier, mflags);
577 }
578
579 if (optflags & ACL_SET_FLAG) {
580 if (position != -1) {
581 if (0 != acl_create_entry_np(&oacl, &newent, position))
582 err(1, "acl_create_entry() failed");
583 acl_copy_entry(newent, modifier);
584 } else {
585 /* If an entry exists, add the new permissions to it, else add an
586 * entry in the canonical position.
587 */
588
589 /* First, check for a matching entry - if one exists, merge flags */
590 dmatch = find_matching_entry(oacl, modifier, &rentry, 1);
591
592 if (dmatch != MATCH_NONE) {
593 if (dmatch == MATCH_EXACT)
594 /* Nothing to be done */
595 goto ma_exit;
596
597 if (dmatch == MATCH_PARTIAL) {
598 merge_entry_perms(rentry, modifier);
599 goto ma_exit;
600 }
601 }
602 /* Insert the entry in canonical order */
603 cpos = find_canonical_position(oacl, modifier);
604 if (0!= acl_create_entry_np(&oacl, &newent, cpos))
605 err(1, "acl_create_entry() failed");
606 acl_copy_entry(newent, modifier);
607 }
608 } else if (optflags & ACL_DELETE_FLAG) {
609 if (flag_new_acl) {
610 warnx("No ACL present '%s'", path);
611 retval = 1;
612 } else if (position != -1 ) {
613 if (0 != acl_get_entry(oacl, position, &rentry)) {
614 warnx("Invalid entry number '%s'", path);
615 retval = 1;
616 } else {
617 acl_delete_entry(oacl, rentry);
618 }
619 } else {
620 unsigned match_found = 0, aindex;
621 for (aindex = 0;
622 acl_get_entry(oacl, rentry == NULL ?
623 ACL_FIRST_ENTRY :
624 ACL_NEXT_ENTRY, &rentry) == 0;
625 aindex++) {
626 unsigned cmp;
627 cmp = compare_acl_entries(rentry, modifier);
628 if ((cmp == MATCH_EXACT) ||
629 (cmp == MATCH_PARTIAL)) {
630 match_found++;
631 if (cmp == MATCH_EXACT)
632 acl_delete_entry(oacl, rentry);
633 else {
634 int valid_perms;
635 /* In the event of a partial match, remove the specified perms from the
636 * entry */
637 subtract_from_entry(rentry, modifier, &valid_perms);
638 /* if no perms survived then delete the entry */
639 if (valid_perms == 0)
640 acl_delete_entry(oacl, rentry);
641 }
642 }
643 }
644 if (0 == match_found) {
645 warnx("Entry not found when attempting delete '%s'",path);
646 retval = 1;
647 }
648 }
649 } else if (optflags & ACL_REWRITE_FLAG) {
650 acl_entry_t rentry;
651
652 if (-1 == position) {
653 usage();
654 }
655 if (0 == flag_new_acl) {
656 if (0 != acl_get_entry(oacl, position,
657 &rentry))
658 err(1, "Invalid entry number '%s'", path);
659
660 if (0 != acl_delete_entry(oacl, rentry))
661 err(1, "Unable to delete entry '%s'", path);
662 }
663 if (0!= acl_create_entry_np(&oacl, &newent, position))
664 err(1, "acl_create_entry() failed");
665 acl_copy_entry(newent, modifier);
666 }
667 ma_exit:
668 *oaclp = oacl;
669 return retval;
670 }
671
672 int
673 modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow) {
674
675 acl_t oacl = NULL;
676 unsigned aindex = 0, flag_new_acl = 0;
677 acl_entry_t newent = NULL;
678 acl_entry_t entry = NULL;
679 unsigned retval = 0;
680
681 extern int fflag;
682
683 /* XXX acl_get_file() returns a zero entry ACL if an ACL was previously
684 * associated with the file, and has had its entries removed.
685 * However, POSIX 1003.1e states that a zero entry ACL should be
686 * returned if the caller asks for ACL_TYPE_DEFAULT, and no ACL is
687 * associated with the path; it
688 * does not specifically state that a request for ACL_TYPE_EXTENDED
689 * should not return a zero entry ACL, however.
690 */
691
692 /* Determine if we've been given a zero entry ACL, or create an ACL if
693 * none exists. There are some issues to consider here: Should we create
694 * a zero-entry ACL for a delete or check canonicity operation?
695 */
696
697 if (path == NULL)
698 usage();
699
700 if (optflags & ACL_CLEAR_FLAG) {
701 filesec_t fsec = filesec_init();
702 if (fsec == NULL) {
703 err(1, "filesec_init() failed");
704 }
705 if (filesec_set_property(fsec, FILESEC_ACL, _FILESEC_REMOVE_ACL) != 0) {
706 err(1, "filesec_set_property() failed");
707 }
708 if (follow) {
709 if (chmodx_np(path, fsec) != 0) {
710 if (!fflag) {
711 warn("Failed to clear ACL on file %s", path);
712 }
713 retval = 1;
714 }
715 } else {
716 int fd = open(path, O_SYMLINK);
717 if (fd != -1) {
718 if (fchmodx_np(fd, fsec) != 0) {
719 if (!fflag) {
720 warn("Failed to clear ACL on file %s", path);
721 }
722 retval = 1;
723 }
724 close(fd);
725 } else {
726 if (!fflag) {
727 warn("Failed to open file %s", path);
728 }
729 retval = 1;
730 }
731 }
732 filesec_free(fsec);
733 return (retval);
734 }
735
736 if (optflags & ACL_FROM_STDIN) {
737 oacl = acl_dup(modifier);
738 } else {
739 if (follow) {
740 oacl = acl_get_file(path, ACL_TYPE_EXTENDED);
741 } else {
742 int fd = open(path, O_SYMLINK);
743 if (fd != -1) {
744 oacl = acl_get_fd_np(fd, ACL_TYPE_EXTENDED);
745 close(fd);
746 }
747 }
748 if ((oacl == NULL) ||
749 (acl_get_entry(oacl,ACL_FIRST_ENTRY, &newent) != 0)) {
750 if ((oacl = acl_init(1)) == NULL)
751 err(1, "acl_init() failed");
752 flag_new_acl = 1;
753 position = 0;
754 }
755
756 if ((0 == flag_new_acl) && (optflags & (ACL_REMOVE_INHERIT_FLAG |
757 ACL_REMOVE_INHERITED_ENTRIES))) {
758 acl_t facl = NULL;
759 if ((facl = acl_init(1)) == NULL)
760 err(1, "acl_init() failed");
761 for (aindex = 0;
762 acl_get_entry(oacl,
763 (entry == NULL ? ACL_FIRST_ENTRY :
764 ACL_NEXT_ENTRY), &entry) == 0;
765 aindex++) {
766 acl_flagset_t eflags;
767 acl_entry_t fent = NULL;
768 if (acl_get_flagset_np(entry, &eflags) != 0) {
769 err(1, "Unable to obtain flagset");
770 }
771
772 if (acl_get_flag_np(eflags, ACL_ENTRY_INHERITED)) {
773 if (optflags & ACL_REMOVE_INHERIT_FLAG) {
774 acl_delete_flag_np(eflags, ACL_ENTRY_INHERITED);
775 acl_set_flagset_np(entry, eflags);
776 acl_create_entry(&facl, &fent);
777 acl_copy_entry(fent, entry);
778 }
779 }
780 else {
781 acl_create_entry(&facl, &fent);
782 acl_copy_entry(fent, entry);
783 }
784 }
785 if (oacl)
786 acl_free(oacl);
787 oacl = facl;
788 } else if (optflags & ACL_TO_STDOUT) {
789 ssize_t len; /* need to get printacl() from ls(1) */
790 char *text = acl_to_text(oacl, &len);
791 puts(text);
792 acl_free(text);
793 } else if (optflags & ACL_CHECK_CANONICITY) {
794 if (flag_new_acl) {
795 warnx("No ACL currently associated with file '%s'", path);
796 }
797 retval = is_canonical(oacl);
798 } else if ((optflags & ACL_SET_FLAG) && (position == -1) &&
799 (!is_canonical(oacl))) {
800 warnx("The specified file '%s' does not have an ACL in canonical order, please specify a position with +a# ", path);
801 retval = 1;
802 } else if (((optflags & ACL_DELETE_FLAG) && (position != -1))
803 || (optflags & ACL_CHECK_CANONICITY)) {
804 retval = modify_acl(&oacl, NULL, optflags, position,
805 inheritance_level, flag_new_acl, path);
806 } else if ((optflags & (ACL_REMOVE_INHERIT_FLAG|ACL_REMOVE_INHERITED_ENTRIES)) && flag_new_acl) {
807 warnx("No ACL currently associated with file '%s'", path);
808 retval = 1;
809 } else {
810 if (!modifier) { /* avoid bus error in acl_get_entry */
811 errx(1, "Internal error: modifier should not be NULL");
812 }
813 for (aindex = 0;
814 acl_get_entry(modifier,
815 (entry == NULL ? ACL_FIRST_ENTRY :
816 ACL_NEXT_ENTRY), &entry) == 0;
817 aindex++) {
818
819 retval += modify_acl(&oacl, entry, optflags,
820 position, inheritance_level,
821 flag_new_acl, path);
822 }
823 }
824 }
825
826 /* XXX Potential race here, since someone else could've modified or
827 * read the ACL on this file (with the intention of modifying it) in
828 * the interval from acl_get_file() to acl_set_file(); we can
829 * minimize one aspect of this window by comparing the original acl
830 * to a fresh one from acl_get_file() but we could consider a
831 * "changeset" mechanism, common locking strategy, or kernel
832 * supplied reservation mechanism to prevent this race.
833 */
834 if (!(optflags & (ACL_TO_STDOUT|ACL_CHECK_CANONICITY))) {
835 int status = -1;
836 if (follow) {
837 status = acl_set_file(path, ACL_TYPE_EXTENDED, oacl);
838 } else {
839 int fd = open(path, O_SYMLINK);
840 if (fd != -1) {
841 status = acl_set_fd_np(fd, oacl,
842 ACL_TYPE_EXTENDED);
843 close(fd);
844 }
845 }
846 if (status != 0) {
847 if (!fflag)
848 warn("Failed to set ACL on file '%s'", path);
849 retval = 1;
850 }
851 }
852
853 if (oacl)
854 acl_free(oacl);
855
856 return retval;
857 }
858
859 #endif /*__APPLE__*/