1 /* On Linux, ftw.h needs this special voodoo to work. */
2 #define _XOPEN_SOURCE 500
5 #include <ftw.h> /* nftw() et al. */
7 #include <libgen.h> /* dirname() */
8 #include <limits.h> /* PATH_MAX */
17 #include <acl/libacl.h> /* acl_get_perm, not portable */
18 #include <sys/types.h>
22 mode_t
get_mode(const char* path
) {
24 * Get the mode bits from path.
32 int result
= stat(path
, &s
);
38 /* errno will be set already by stat() */
44 bool is_regular_file(const char* path
) {
46 * Returns true if path is a regular file, false otherwise.
53 int result
= stat(path
, &s
);
55 return S_ISREG(s
.st_mode
);
62 bool is_directory(const char* path
) {
64 * Returns true if path is a directory, false otherwise.
71 int result
= stat(path
, &s
);
73 return S_ISDIR(s
.st_mode
);
82 int acl_set_entry(acl_t
* aclp
,
85 * Update or create the given entry.
89 int gt_result
= acl_get_tag_type(entry
, &entry_tag
);
90 if (gt_result
== -1) {
91 perror("acl_set_entry (acl_get_tag_type)");
95 acl_permset_t entry_permset
;
96 int ps_result
= acl_get_permset(entry
, &entry_permset
);
97 if (ps_result
== -1) {
98 perror("acl_set_entry (acl_get_permset)");
102 acl_entry_t existing_entry
;
103 /* Loop through the given ACL looking for matching entries. */
104 int result
= acl_get_entry(*aclp
, ACL_FIRST_ENTRY
, &existing_entry
);
106 while (result
== 1) {
107 acl_tag_t existing_tag
= ACL_UNDEFINED_TAG
;
108 int tag_result
= acl_get_tag_type(existing_entry
, &existing_tag
);
110 if (tag_result
== -1) {
111 perror("set_acl_tag_permset (acl_get_tag_type)");
115 if (existing_tag
== entry_tag
) {
116 if (entry_tag
== ACL_USER_OBJ
||
117 entry_tag
== ACL_GROUP_OBJ
||
118 entry_tag
== ACL_OTHER
) {
119 /* Only update for these three since all other tags will have
121 acl_permset_t existing_permset
;
122 int gep_result
= acl_get_permset(existing_entry
, &existing_permset
);
123 if (gep_result
== -1) {
124 perror("acl_set_entry (acl_get_permset)");
128 int s_result
= acl_set_permset(existing_entry
, entry_permset
);
129 if (s_result
== -1) {
130 perror("acl_set_entry (acl_set_permset)");
139 result
= acl_get_entry(*aclp
, ACL_NEXT_ENTRY
, &existing_entry
);
142 /* This catches both the initial acl_get_entry and the ones at the
145 perror("acl_set_entry (acl_get_entry)");
149 /* If we've made it this far, we need to add a new entry to the
151 acl_entry_t new_entry
;
153 /* We allocate memory here that we should release! */
154 int c_result
= acl_create_entry(aclp
, &new_entry
);
155 if (c_result
== -1) {
156 perror("acl_set_entry (acl_create_entry)");
160 int st_result
= acl_set_tag_type(new_entry
, entry_tag
);
161 if (st_result
== -1) {
162 perror("acl_set_entry (acl_set_tag_type)");
166 int s_result
= acl_set_permset(new_entry
, entry_permset
);
167 if (s_result
== -1) {
168 perror("acl_set_entry (acl_set_permset)");
172 if (entry_tag
== ACL_USER
|| entry_tag
== ACL_GROUP
) {
173 /* We need to set the qualifier too. */
174 void* entry_qual
= acl_get_qualifier(entry
);
175 if (entry_qual
== (void*)NULL
) {
176 perror("acl_set_entry (acl_get_qualifier)");
180 int sq_result
= acl_set_qualifier(new_entry
, entry_qual
);
181 if (sq_result
== -1) {
182 perror("acl_set_entry (acl_set_qualifier)");
192 int acl_entry_count(acl_t
* acl
) {
194 * Return the number of entries in acl, or -1 on error.
198 int result
= acl_get_entry(*acl
, ACL_FIRST_ENTRY
, &entry
);
200 while (result
== 1) {
202 result
= acl_get_entry(*acl
, ACL_NEXT_ENTRY
, &entry
);
206 perror("acl_is_minimal (acl_get_entry)");
214 int acl_is_minimal(acl_t
* acl
) {
215 /* An ACL is minimal if it has fewer than four entries. Return 0 for
216 * false, 1 for true, and -1 on error.
219 int ec
= acl_entry_count(acl
);
221 perror("acl_is_minimal (acl_entry_count)");
234 int acl_execute_masked(const char* path
) {
235 /* Returns 1 i the given path has an ACL mask which denies
236 execute. Returns 0 if it does not (or if it has no ACL/mask at
237 all), and -1 on error. */
239 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
241 if (acl
== (acl_t
)NULL
) {
245 /* Our return value. */
249 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
251 while (ge_result
== 1) {
252 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
253 int tag_result
= acl_get_tag_type(entry
, &tag
);
255 if (tag_result
== -1) {
256 perror("acl_execute_masked (acl_get_tag_type)");
261 if (tag
== ACL_MASK
) {
262 /* This is the mask entry, get its permissions, and see if
263 execute is specified. */
264 acl_permset_t permset
;
266 int ps_result
= acl_get_permset(entry
, &permset
);
267 if (ps_result
== -1) {
268 perror("acl_execute_masked (acl_get_permset)");
273 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
274 if (gp_result
== -1) {
275 perror("acl_execute_masked (acl_get_perm)");
280 if (gp_result
== 0) {
281 /* No execute bit set in the mask; execute not allowed. */
286 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
295 int any_can_execute_or_dir(const char* path
) {
296 /* If the given path is a directory, returns 1. Otherwise, returns 1
297 * if any ACL entry has EFFECTIVE execute access, 0 if none do, and
300 * This is meant to somewhat mimic setfacl's handling of the capital
301 * 'X' perm, which allows execute access if the target is a
302 * directory or someone can already execute it. We differ in that we
303 * check the effective execute rather than just the execute bits.
306 if (is_directory(path
)) {
307 /* That was easy... */
311 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
313 if (acl
== (acl_t
)NULL
) {
317 /* Our return value. */
320 if (acl_is_minimal(&acl
)) {
321 mode_t mode
= get_mode(path
);
322 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
333 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
335 while (ge_result
== 1) {
336 acl_permset_t permset
;
338 int ps_result
= acl_get_permset(entry
, &permset
);
339 if (ps_result
== -1) {
340 perror("any_can_execute_or_dir (acl_get_permset)");
345 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
346 if (gp_result
== -1) {
347 perror("any_can_execute_or_dir (acl_get_perm)");
352 if (gp_result
== 1) {
353 /* Only return one if this execute bit is not masked. */
354 if (acl_execute_masked(path
) != 1) {
360 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
363 if (ge_result
== -1) {
364 perror("any_can_execute_or_dir (acl_get_entry)");
375 int inherit_default_acl(const char* path
, const char* parent
) {
376 /* Inherit the default ACL from parent to path. This overwrites any
377 * existing default ACL. Returns 1 for success, 0 for failure, and
381 /* Our return value. */
389 if (!is_directory(path
) || !is_directory(parent
)) {
393 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
394 if (parent_acl
== (acl_t
)NULL
) {
398 acl_t path_acl
= acl_dup(parent_acl
);
400 if (path_acl
== (acl_t
)NULL
) {
401 perror("inherit_default_acl (acl_dup)");
402 acl_free(parent_acl
);
406 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
407 if (sf_result
== -1) {
408 perror("inherit_default_acl (acl_set_file)");
419 int wipe_acls(const char* path
) {
420 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
421 path. Returns 1 for success, 0 for failure, and -1 on error. */
428 /* Finally, remove individual named/mask entries. */
429 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
430 if (acl
== (acl_t
)NULL
) {
431 perror("wipe_acls (acl_get_file)");
435 /* Our return value. */
439 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
441 while (ge_result
== 1) {
442 int d_result
= acl_delete_entry(acl
, entry
);
443 if (d_result
== -1) {
444 perror("wipe_acls (acl_delete_entry)");
449 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
452 /* Catches the first acl_get_entry as well as the ones at the end of
454 if (ge_result
== -1) {
455 perror("wipe_acls (acl_get_entry)");
460 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
461 if (sf_result
== -1) {
462 perror("wipe_acls (acl_set_file)");
473 int apply_default_acl(const char* path
) {
474 /* Really apply the default ACL by looping through it. Returns one
475 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
482 if (!is_regular_file(path
) && !is_directory(path
)) {
486 /* dirname mangles its argument */
487 char path_copy
[PATH_MAX
];
488 strncpy(path_copy
, path
, PATH_MAX
-1);
489 path_copy
[PATH_MAX
-1] = 0;
491 char* parent
= dirname(path_copy
);
492 if (!is_directory(parent
)) {
493 /* Make sure dirname() did what we think it did. */
497 int ace_result
= any_can_execute_or_dir(path
);
498 if (ace_result
== -1) {
499 perror("apply_default_acl (any_can_execute_or_dir)");
503 bool allow_exec
= (bool)ace_result
;
505 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
507 if (defacl
== (acl_t
)NULL
) {
508 perror("apply_default_acl (acl_get_file)");
512 /* Our return value. */
515 int wipe_result
= wipe_acls(path
);
516 if (wipe_result
== -1) {
517 perror("apply_default_acl (wipe_acls)");
522 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
523 ACL with this one. */
524 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
525 if (acl
== (acl_t
)NULL
) {
526 perror("apply_default_acl (acl_get_file)");
530 /* If it's a directory, inherit the parent's default. */
531 int inherit_result
= inherit_default_acl(path
, parent
);
532 if (inherit_result
== -1) {
533 perror("apply_default_acl (inherit_acls)");
539 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
541 while (ge_result
== 1) {
542 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
543 int tag_result
= acl_get_tag_type(entry
, &tag
);
545 if (tag_result
== -1) {
546 perror("apply_default_acl (acl_get_tag_type)");
552 /* We've got an entry/tag from the default ACL. Get its permset. */
553 acl_permset_t permset
;
554 int ps_result
= acl_get_permset(entry
, &permset
);
555 if (ps_result
== -1) {
556 perror("apply_default_acl (acl_get_permset)");
561 /* If this is a default mask, fix it up. */
562 if (tag
== ACL_MASK
||
563 tag
== ACL_USER_OBJ
||
564 tag
== ACL_GROUP_OBJ
||
567 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
568 minimal ACLs) or acl_other entries, so if execute should be
569 masked, we have to do it manually. */
570 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
571 if (d_result
== -1) {
572 perror("apply_default_acl (acl_delete_perm)");
577 int sp_result
= acl_set_permset(entry
, permset
);
578 if (sp_result
== -1) {
579 perror("apply_default_acl (acl_set_permset)");
586 /* Finally, add the permset to the access ACL. */
587 int set_result
= acl_set_entry(&acl
, entry
);
588 if (set_result
== -1) {
589 perror("apply_default_acl (acl_set_entry)");
594 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
597 /* Catches the first acl_get_entry as well as the ones at the end of
599 if (ge_result
== -1) {
600 perror("apply_default_acl (acl_get_entry)");
605 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
606 if (sf_result
== -1) {
607 perror("apply_default_acl (acl_set_file)");
618 void usage(char* program_name
) {
620 * Print usage information.
622 printf("Apply any applicable default ACLs to the given files or "
624 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
627 printf(" -h, --help Print this help message\n");
628 printf(" -r, --recursive Act on any given directories recursively\n");
632 int apply_default_acl_nftw(const char *target
,
633 const struct stat
*s
,
636 /* A wrapper around the apply_default_acl() function for use with
637 * nftw(). We need to adjust the return value so that nftw() doesn't
638 * think we've failed.
640 bool reapp_result
= apply_default_acl(target
);
650 bool apply_default_acl_recursive(const char *target
) {
651 /* Attempt to apply default ACLs recursively. If target is a
652 * directory, we recurse through its entries. If not, we just
653 * apply the default ACL to target.
655 * We ignore symlinks for consistency with chmod -r.
658 if (!is_directory(target
)) {
659 return apply_default_acl(target
);
662 int max_levels
= 256;
663 int flags
= FTW_PHYS
; /* Don't follow links. */
665 int nftw_result
= nftw(target
,
666 apply_default_acl_nftw
,
670 if (nftw_result
== 0) {
675 /* nftw will return -1 on error, or if the supplied function
676 * (apply_default_acl_nftw) returns a non-zero result, nftw will
679 if (nftw_result
== -1) {
680 perror("apply_default_acl_recursive (nftw)");
687 int main(int argc
, char* argv
[]) {
689 * Call apply_default_acl on each command-line argument.
697 bool recursive
= false;
699 struct option long_options
[] = {
700 /* These options set a flag. */
701 {"help", no_argument
, NULL
, 'h'},
702 {"recursive", no_argument
, NULL
, 'r'},
708 while ((opt
= getopt_long(argc
, argv
, "hr", long_options
, NULL
)) != -1) {
722 int result
= EXIT_SUCCESS
;
725 for (arg_index
= optind
; arg_index
< argc
; arg_index
++) {
726 const char* target
= argv
[arg_index
];
727 bool reapp_result
= false;
730 reapp_result
= apply_default_acl_recursive(target
);
733 /* It's either normal file, or we're not operating recursively. */
734 reapp_result
= apply_default_acl(target
);
738 result
= EXIT_FAILURE
;