]>
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 A |
103 | struct passwd *tpass = NULL; |
104 | struct group *tgrp = NULL; | |
105 | uuid_t *entryg = NULL; | |
c59d3020 A |
106 | |
107 | if ((entryg = (uuid_t *) calloc(1,sizeof(uuid_t))) == NULL) | |
108 | err(1, "Unable to allocate a uuid"); | |
c59d3020 | 109 | |
864a4b6e A |
110 | if (nametype & NAME_USER) |
111 | tpass = getpwnam(tok); | |
112 | ||
113 | if (NULL == tpass && (nametype & NAME_GROUP)) | |
c59d3020 | 114 | tgrp = getgrnam(tok); |
864a4b6e | 115 | |
c59d3020 | 116 | if (tpass) { |
864a4b6e A |
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); | |
c59d3020 | 119 | } |
864a4b6e A |
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); | |
c59d3020 | 123 | } |
864a4b6e A |
124 | } else { |
125 | errx(1, "Unable to translate '%s' to a UID/GID", tok); | |
c59d3020 A |
126 | } |
127 | return entryg; | |
128 | } | |
129 | ||
130 | /* Convert an acl entry in string form to an acl_entry_t */ | |
131 | int | |
132 | parse_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; | |
864a4b6e A |
142 | char *delimiter = " "; |
143 | int nametype = NAME_EITHER; | |
c59d3020 A |
144 | |
145 | acl_get_permset(newent, &perms); | |
146 | acl_get_flagset_np(newent, &flags); | |
147 | ||
148 | pebuf = entrybuf; | |
149 | ||
864a4b6e A |
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); | |
c59d3020 A |
161 | |
162 | if ((tok == NULL) || *tok == '\0') { | |
864a4b6e | 163 | errx(1, "Invalid entry format -- expected user or group name"); |
c59d3020 A |
164 | } |
165 | ||
166 | /* parse the name into a qualifier */ | |
864a4b6e | 167 | entryg = name_to_uuid(tok, nametype); |
c59d3020 | 168 | |
864a4b6e | 169 | tok = strsep(&pebuf, ": "); /* Stick with delimiter? */ |
c59d3020 | 170 | if ((tok == NULL) || *tok == '\0') { |
864a4b6e | 171 | errx(1, "Invalid entry format -- expected allow or deny"); |
c59d3020 A |
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 { | |
864a4b6e | 180 | errx(1, "Unknown tag type '%s'", tok); |
c59d3020 A |
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 | } | |
864a4b6e | 204 | errx(1,"Invalid permission type '%s'", tok); |
c59d3020 A |
205 | found: |
206 | continue; | |
207 | } | |
208 | } | |
209 | if (0 == permcount) { | |
864a4b6e | 210 | errx(1, "No permissions specified"); |
c59d3020 A |
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 */ | |
222 | acl_t | |
223 | parse_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)) { | |
864a4b6e | 255 | errx(1, "Failed parsing entry '%s'", *bufp); |
c59d3020 A |
256 | } |
257 | if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) { | |
864a4b6e | 258 | errx(1, "Too many entries"); |
c59d3020 A |
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 */ | |
268 | unsigned | |
269 | get_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 | ||
279 | int | |
280 | score_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: | |
864a4b6e | 307 | errx(1, "Unknown tag type %d present in ACL entry", tag); |
c59d3020 A |
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 | ||
317 | int | |
318 | compare_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 | ||
331 | int | |
332 | compare_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 | ||
4d0bb651 | 344 | static int |
c59d3020 A |
345 | compare_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 */ | |
358 | int | |
359 | compare_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; | |
686e1a44 | 365 | void *aqual, *bqual; |
c59d3020 | 366 | |
686e1a44 A |
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) | |
c59d3020 A |
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 | */ | |
410 | unsigned int | |
411 | is_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 | */ | |
436 | unsigned int | |
437 | find_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 | ||
464 | int 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 | ||
470 | int | |
471 | find_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; | |
864a4b6e | 487 | |
c59d3020 A |
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 | ||
864a4b6e | 494 | if (compare_acl_flagsets(mflags, eflags) == MATCH_EXACT) { |
c59d3020 A |
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*/ | |
511 | int | |
40bf83fe | 512 | subtract_from_entry(acl_entry_t rentry, acl_entry_t modifier, int* valid_perms) |
c59d3020 A |
513 | { |
514 | acl_permset_t rperms, mperms; | |
515 | acl_flagset_t rflags, mflags; | |
40bf83fe A |
516 | if (valid_perms) |
517 | *valid_perms = 0; | |
c59d3020 A |
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); | |
40bf83fe A |
529 | else if (valid_perms && acl_get_perm_np(rperms, acl_perms[i].perm)) |
530 | (*valid_perms)++; | |
c59d3020 A |
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 */ | |
4d0bb651 | 541 | static int |
c59d3020 A |
542 | merge_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 | ||
567 | int | |
568 | modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags, | |
569 | int position, int inheritance_level, | |
40bf83fe | 570 | unsigned flag_new_acl, const char* path) { |
c59d3020 A |
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); | |
40bf83fe | 593 | } else { |
c59d3020 A |
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 | } | |
40bf83fe A |
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 { | |
c59d3020 A |
626 | acl_delete_entry(oacl, rentry); |
627 | } | |
40bf83fe A |
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; | |
c59d3020 A |
644 | /* In the event of a partial match, remove the specified perms from the |
645 | * entry */ | |
40bf83fe A |
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); | |
c59d3020 A |
650 | } |
651 | } | |
c59d3020 | 652 | } |
40bf83fe A |
653 | if (0 == match_found) { |
654 | warnx("Entry not found when attempting delete '%s'",path); | |
655 | retval = 1; | |
c59d3020 | 656 | } |
40bf83fe A |
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 | } | |
c59d3020 A |
676 | ma_exit: |
677 | *oaclp = oacl; | |
678 | return retval; | |
679 | } | |
680 | ||
681 | int | |
4d0bb651 | 682 | modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow) { |
c59d3020 A |
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 | ||
40558d9b A |
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 | ||
4d0bb651 | 726 | if (optflags & ACL_FROM_STDIN) { |
c59d3020 | 727 | oacl = acl_dup(modifier); |
4d0bb651 A |
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 | } | |
c59d3020 A |
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; | |
864a4b6e A |
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) { | |
c59d3020 | 784 | if (flag_new_acl) { |
864a4b6e | 785 | warnx("No ACL currently associated with file '%s'", path); |
c59d3020 | 786 | } |
864a4b6e A |
787 | retval = is_canonical(oacl); |
788 | } else if ((optflags & ACL_SET_FLAG) && (position == -1) && | |
c59d3020 | 789 | (!is_canonical(oacl))) { |
864a4b6e | 790 | warnx("The specified file '%s' does not have an ACL in canonical order, please specify a position with +a# ", path); |
c59d3020 | 791 | retval = 1; |
864a4b6e | 792 | } else if (((optflags & ACL_DELETE_FLAG) && (position != -1)) |
c59d3020 A |
793 | || (optflags & ACL_CHECK_CANONICITY)) { |
794 | retval = modify_acl(&oacl, NULL, optflags, position, | |
40bf83fe | 795 | inheritance_level, flag_new_acl, path); |
864a4b6e A |
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 | } | |
c59d3020 A |
803 | for (aindex = 0; |
804 | acl_get_entry(modifier, | |
805 | (entry == NULL ? ACL_FIRST_ENTRY : | |
806 | ACL_NEXT_ENTRY), &entry) == 0; | |
807 | aindex++) { | |
808 | ||
864a4b6e A |
809 | retval += modify_acl(&oacl, entry, optflags, |
810 | position, inheritance_level, | |
40bf83fe | 811 | flag_new_acl, path); |
c59d3020 | 812 | } |
864a4b6e A |
813 | } |
814 | } | |
c59d3020 A |
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 | */ | |
4d0bb651 A |
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 | } | |
c59d3020 A |
841 | } |
842 | ||
843 | if (oacl) | |
844 | acl_free(oacl); | |
845 | ||
846 | return retval; | |
847 | } | |
848 | ||
849 | #endif /*__APPLE__*/ |