]>
Commit | Line | Data |
---|---|---|
c59d3020 A |
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> | |
40558d9b A |
40 | #include <fcntl.h> |
41 | #include <sys/stat.h> | |
c59d3020 A |
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 | ||
864a4b6e A |
95 | #define NAME_USER (1) |
96 | #define NAME_GROUP (2) | |
97 | #define NAME_EITHER (NAME_USER | NAME_GROUP) | |
98 | ||
c59d3020 A |
99 | /* Perform a name to uuid mapping - calls through to memberd */ |
100 | ||
101 | uuid_t * | |
864a4b6e | 102 | name_to_uuid(char *tok, int nametype) { |
c59d3020 | 103 | uuid_t *entryg = NULL; |
29341915 | 104 | size_t len = strlen(tok); |
c59d3020 | 105 | |
29341915 A |
106 | if ((entryg = (uuid_t *) calloc(1, sizeof(uuid_t))) == NULL) { |
107 | errx(1, "Unable to allocate a uuid"); | |
c59d3020 | 108 | } |
29341915 A |
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); | |
c59d3020 A |
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; | |
864a4b6e A |
133 | char *delimiter = " "; |
134 | int nametype = NAME_EITHER; | |
c59d3020 A |
135 | |
136 | acl_get_permset(newent, &perms); | |
137 | acl_get_flagset_np(newent, &flags); | |
138 | ||
139 | pebuf = entrybuf; | |
140 | ||
864a4b6e A |
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); | |
c59d3020 A |
152 | |
153 | if ((tok == NULL) || *tok == '\0') { | |
864a4b6e | 154 | errx(1, "Invalid entry format -- expected user or group name"); |
c59d3020 A |
155 | } |
156 | ||
157 | /* parse the name into a qualifier */ | |
864a4b6e | 158 | entryg = name_to_uuid(tok, nametype); |
c59d3020 | 159 | |
864a4b6e | 160 | tok = strsep(&pebuf, ": "); /* Stick with delimiter? */ |
c59d3020 | 161 | if ((tok == NULL) || *tok == '\0') { |
864a4b6e | 162 | errx(1, "Invalid entry format -- expected allow or deny"); |
c59d3020 A |
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 { | |
864a4b6e | 171 | errx(1, "Unknown tag type '%s'", tok); |
c59d3020 A |
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 | } | |
864a4b6e | 195 | errx(1,"Invalid permission type '%s'", tok); |
c59d3020 A |
196 | found: |
197 | continue; | |
198 | } | |
199 | } | |
200 | if (0 == permcount) { | |
864a4b6e | 201 | errx(1, "No permissions specified"); |
c59d3020 A |
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)) { | |
864a4b6e | 246 | errx(1, "Failed parsing entry '%s'", *bufp); |
c59d3020 A |
247 | } |
248 | if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) { | |
864a4b6e | 249 | errx(1, "Too many entries"); |
c59d3020 A |
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: | |
864a4b6e | 298 | errx(1, "Unknown tag type %d present in ACL entry", tag); |
c59d3020 A |
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 | ||
4d0bb651 | 335 | static int |
c59d3020 A |
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; | |
686e1a44 | 356 | void *aqual, *bqual; |
c59d3020 | 357 | |
686e1a44 A |
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) | |
c59d3020 A |
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; | |
864a4b6e | 478 | |
c59d3020 A |
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 | ||
864a4b6e | 485 | if (compare_acl_flagsets(mflags, eflags) == MATCH_EXACT) { |
c59d3020 A |
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 | |
40bf83fe | 503 | subtract_from_entry(acl_entry_t rentry, acl_entry_t modifier, int* valid_perms) |
c59d3020 A |
504 | { |
505 | acl_permset_t rperms, mperms; | |
506 | acl_flagset_t rflags, mflags; | |
40bf83fe A |
507 | if (valid_perms) |
508 | *valid_perms = 0; | |
c59d3020 A |
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); | |
40bf83fe A |
520 | else if (valid_perms && acl_get_perm_np(rperms, acl_perms[i].perm)) |
521 | (*valid_perms)++; | |
c59d3020 A |
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 */ | |
4d0bb651 | 532 | static int |
c59d3020 A |
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, | |
40bf83fe | 561 | unsigned flag_new_acl, const char* path) { |
c59d3020 A |
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); | |
40bf83fe | 584 | } else { |
c59d3020 A |
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 | } | |
40bf83fe A |
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 { | |
c59d3020 A |
617 | acl_delete_entry(oacl, rentry); |
618 | } | |
40bf83fe A |
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; | |
c59d3020 A |
635 | /* In the event of a partial match, remove the specified perms from the |
636 | * entry */ | |
40bf83fe A |
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); | |
c59d3020 A |
641 | } |
642 | } | |
c59d3020 | 643 | } |
40bf83fe A |
644 | if (0 == match_found) { |
645 | warnx("Entry not found when attempting delete '%s'",path); | |
646 | retval = 1; | |
c59d3020 | 647 | } |
40bf83fe A |
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 | } | |
c59d3020 A |
667 | ma_exit: |
668 | *oaclp = oacl; | |
669 | return retval; | |
670 | } | |
671 | ||
672 | int | |
4d0bb651 | 673 | modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow) { |
c59d3020 A |
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; | |
e0055cbe | 679 | unsigned retval = 0; |
c59d3020 A |
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 | ||
40558d9b A |
700 | if (optflags & ACL_CLEAR_FLAG) { |
701 | filesec_t fsec = filesec_init(); | |
e0055cbe | 702 | if (fsec == NULL) { |
40558d9b | 703 | err(1, "filesec_init() failed"); |
e0055cbe A |
704 | } |
705 | if (filesec_set_property(fsec, FILESEC_ACL, _FILESEC_REMOVE_ACL) != 0) { | |
40558d9b | 706 | err(1, "filesec_set_property() failed"); |
e0055cbe A |
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 | } | |
40558d9b A |
732 | filesec_free(fsec); |
733 | return (retval); | |
734 | } | |
735 | ||
4d0bb651 | 736 | if (optflags & ACL_FROM_STDIN) { |
c59d3020 | 737 | oacl = acl_dup(modifier); |
4d0bb651 A |
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 | } | |
c59d3020 A |
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; | |
864a4b6e A |
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) { | |
c59d3020 | 794 | if (flag_new_acl) { |
864a4b6e | 795 | warnx("No ACL currently associated with file '%s'", path); |
c59d3020 | 796 | } |
864a4b6e A |
797 | retval = is_canonical(oacl); |
798 | } else if ((optflags & ACL_SET_FLAG) && (position == -1) && | |
c59d3020 | 799 | (!is_canonical(oacl))) { |
864a4b6e | 800 | warnx("The specified file '%s' does not have an ACL in canonical order, please specify a position with +a# ", path); |
c59d3020 | 801 | retval = 1; |
864a4b6e | 802 | } else if (((optflags & ACL_DELETE_FLAG) && (position != -1)) |
c59d3020 A |
803 | || (optflags & ACL_CHECK_CANONICITY)) { |
804 | retval = modify_acl(&oacl, NULL, optflags, position, | |
40bf83fe | 805 | inheritance_level, flag_new_acl, path); |
864a4b6e A |
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 | } | |
c59d3020 A |
813 | for (aindex = 0; |
814 | acl_get_entry(modifier, | |
815 | (entry == NULL ? ACL_FIRST_ENTRY : | |
816 | ACL_NEXT_ENTRY), &entry) == 0; | |
817 | aindex++) { | |
818 | ||
864a4b6e A |
819 | retval += modify_acl(&oacl, entry, optflags, |
820 | position, inheritance_level, | |
40bf83fe | 821 | flag_new_acl, path); |
c59d3020 | 822 | } |
864a4b6e A |
823 | } |
824 | } | |
c59d3020 A |
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 | */ | |
4d0bb651 A |
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 | } | |
c59d3020 A |
851 | } |
852 | ||
853 | if (oacl) | |
854 | acl_free(oacl); | |
855 | ||
856 | return retval; | |
857 | } | |
858 | ||
859 | #endif /*__APPLE__*/ |