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 /* Command-line options */
23 static bool no_exec_mask
= false;
26 mode_t
get_mode(const char* path
) {
28 * Get the mode bits from path.
36 int result
= stat(path
, &s
);
42 /* errno will be set already by stat() */
48 bool is_regular_file(const char* path
) {
50 * Returns true if path is a regular file, false otherwise.
57 int result
= stat(path
, &s
);
59 return S_ISREG(s
.st_mode
);
66 bool is_directory(const char* path
) {
68 * Returns true if path is a directory, false otherwise.
75 int result
= stat(path
, &s
);
77 return S_ISDIR(s
.st_mode
);
86 int acl_set_entry(acl_t
* aclp
,
89 * Update or create the given entry.
93 int gt_result
= acl_get_tag_type(entry
, &entry_tag
);
94 if (gt_result
== -1) {
95 perror("acl_set_entry (acl_get_tag_type)");
99 acl_permset_t entry_permset
;
100 int ps_result
= acl_get_permset(entry
, &entry_permset
);
101 if (ps_result
== -1) {
102 perror("acl_set_entry (acl_get_permset)");
106 acl_entry_t existing_entry
;
107 /* Loop through the given ACL looking for matching entries. */
108 int result
= acl_get_entry(*aclp
, ACL_FIRST_ENTRY
, &existing_entry
);
110 while (result
== 1) {
111 acl_tag_t existing_tag
= ACL_UNDEFINED_TAG
;
112 int tag_result
= acl_get_tag_type(existing_entry
, &existing_tag
);
114 if (tag_result
== -1) {
115 perror("set_acl_tag_permset (acl_get_tag_type)");
119 if (existing_tag
== entry_tag
) {
120 if (entry_tag
== ACL_USER_OBJ
||
121 entry_tag
== ACL_GROUP_OBJ
||
122 entry_tag
== ACL_OTHER
) {
123 /* Only update for these three since all other tags will have
125 acl_permset_t existing_permset
;
126 int gep_result
= acl_get_permset(existing_entry
, &existing_permset
);
127 if (gep_result
== -1) {
128 perror("acl_set_entry (acl_get_permset)");
132 int s_result
= acl_set_permset(existing_entry
, entry_permset
);
133 if (s_result
== -1) {
134 perror("acl_set_entry (acl_set_permset)");
143 result
= acl_get_entry(*aclp
, ACL_NEXT_ENTRY
, &existing_entry
);
146 /* This catches both the initial acl_get_entry and the ones at the
149 perror("acl_set_entry (acl_get_entry)");
153 /* If we've made it this far, we need to add a new entry to the
155 acl_entry_t new_entry
;
157 /* We allocate memory here that we should release! */
158 int c_result
= acl_create_entry(aclp
, &new_entry
);
159 if (c_result
== -1) {
160 perror("acl_set_entry (acl_create_entry)");
164 int st_result
= acl_set_tag_type(new_entry
, entry_tag
);
165 if (st_result
== -1) {
166 perror("acl_set_entry (acl_set_tag_type)");
170 int s_result
= acl_set_permset(new_entry
, entry_permset
);
171 if (s_result
== -1) {
172 perror("acl_set_entry (acl_set_permset)");
176 if (entry_tag
== ACL_USER
|| entry_tag
== ACL_GROUP
) {
177 /* We need to set the qualifier too. */
178 void* entry_qual
= acl_get_qualifier(entry
);
179 if (entry_qual
== (void*)NULL
) {
180 perror("acl_set_entry (acl_get_qualifier)");
184 int sq_result
= acl_set_qualifier(new_entry
, entry_qual
);
185 if (sq_result
== -1) {
186 perror("acl_set_entry (acl_set_qualifier)");
196 int acl_entry_count(acl_t
* acl
) {
198 * Return the number of entries in acl, or -1 on error.
202 int result
= acl_get_entry(*acl
, ACL_FIRST_ENTRY
, &entry
);
204 while (result
== 1) {
206 result
= acl_get_entry(*acl
, ACL_NEXT_ENTRY
, &entry
);
210 perror("acl_is_minimal (acl_get_entry)");
218 int acl_is_minimal(acl_t
* acl
) {
219 /* An ACL is minimal if it has fewer than four entries. Return 0 for
220 * false, 1 for true, and -1 on error.
223 int ec
= acl_entry_count(acl
);
225 perror("acl_is_minimal (acl_entry_count)");
238 int acl_execute_masked(const char* path
) {
239 /* Returns 1 i the given path has an ACL mask which denies
240 execute. Returns 0 if it does not (or if it has no ACL/mask at
241 all), and -1 on error. */
243 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
245 if (acl
== (acl_t
)NULL
) {
249 /* Our return value. */
253 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
255 while (ge_result
== 1) {
256 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
257 int tag_result
= acl_get_tag_type(entry
, &tag
);
259 if (tag_result
== -1) {
260 perror("acl_execute_masked (acl_get_tag_type)");
265 if (tag
== ACL_MASK
) {
266 /* This is the mask entry, get its permissions, and see if
267 execute is specified. */
268 acl_permset_t permset
;
270 int ps_result
= acl_get_permset(entry
, &permset
);
271 if (ps_result
== -1) {
272 perror("acl_execute_masked (acl_get_permset)");
277 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
278 if (gp_result
== -1) {
279 perror("acl_execute_masked (acl_get_perm)");
284 if (gp_result
== 0) {
285 /* No execute bit set in the mask; execute not allowed. */
290 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
299 int any_can_execute_or_dir(const char* path
) {
300 /* If the given path is a directory, returns 1. Otherwise, returns 1
301 * if any ACL entry has EFFECTIVE execute access, 0 if none do, and
304 * This is meant to somewhat mimic setfacl's handling of the capital
305 * 'X' perm, which allows execute access if the target is a
306 * directory or someone can already execute it. We differ in that we
307 * check the effective execute rather than just the execute bits.
310 if (is_directory(path
)) {
311 /* That was easy... */
315 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
317 if (acl
== (acl_t
)NULL
) {
321 /* Our return value. */
324 if (acl_is_minimal(&acl
)) {
325 mode_t mode
= get_mode(path
);
326 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
337 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
339 while (ge_result
== 1) {
340 acl_permset_t permset
;
342 int ps_result
= acl_get_permset(entry
, &permset
);
343 if (ps_result
== -1) {
344 perror("any_can_execute_or_dir (acl_get_permset)");
349 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
350 if (gp_result
== -1) {
351 perror("any_can_execute_or_dir (acl_get_perm)");
356 if (gp_result
== 1) {
357 /* Only return one if this execute bit is not masked. */
358 if (acl_execute_masked(path
) != 1) {
364 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
367 if (ge_result
== -1) {
368 perror("any_can_execute_or_dir (acl_get_entry)");
379 int inherit_default_acl(const char* path
, const char* parent
) {
380 /* Inherit the default ACL from parent to path. This overwrites any
381 * existing default ACL. Returns 1 for success, 0 for failure, and
385 /* Our return value. */
393 if (!is_directory(path
) || !is_directory(parent
)) {
397 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
398 if (parent_acl
== (acl_t
)NULL
) {
402 acl_t path_acl
= acl_dup(parent_acl
);
404 if (path_acl
== (acl_t
)NULL
) {
405 perror("inherit_default_acl (acl_dup)");
406 acl_free(parent_acl
);
410 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
411 if (sf_result
== -1) {
412 perror("inherit_default_acl (acl_set_file)");
423 int wipe_acls(const char* path
) {
424 /* Remove ACL_USER, ACL_GROUP, and ACL_MASK entries from
425 path. Returns 1 for success, 0 for failure, and -1 on error. */
432 /* Finally, remove individual named/mask entries. */
433 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
434 if (acl
== (acl_t
)NULL
) {
435 perror("wipe_acls (acl_get_file)");
439 /* Our return value. */
443 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
445 while (ge_result
== 1) {
446 int d_result
= acl_delete_entry(acl
, entry
);
447 if (d_result
== -1) {
448 perror("wipe_acls (acl_delete_entry)");
453 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
456 /* Catches the first acl_get_entry as well as the ones at the end of
458 if (ge_result
== -1) {
459 perror("wipe_acls (acl_get_entry)");
464 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
465 if (sf_result
== -1) {
466 perror("wipe_acls (acl_set_file)");
477 int apply_default_acl(const char* path
) {
478 /* Really apply the default ACL by looping through it. Returns one
479 * for success, zero for failure (i.e. no ACL), and -1 on unexpected
487 if (!is_regular_file(path
) && !is_directory(path
)) {
491 /* dirname mangles its argument */
492 char path_copy
[PATH_MAX
];
493 strncpy(path_copy
, path
, PATH_MAX
-1);
494 path_copy
[PATH_MAX
-1] = 0;
496 char* parent
= dirname(path_copy
);
497 if (!is_directory(parent
)) {
498 /* Make sure dirname() did what we think it did. */
502 /* Default to not masking the exec bit; i.e. applying the default
503 ACL literally. If --no-exec-mask was not specified, then we try
504 to "guess" whether or not to mask the exec bit. */
505 bool allow_exec
= true;
508 int ace_result
= any_can_execute_or_dir(path
);
510 if (ace_result
== -1) {
511 perror("apply_default_acl (any_can_execute_or_dir)");
514 allow_exec
= (bool)ace_result
;
517 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
519 if (defacl
== (acl_t
)NULL
) {
520 perror("apply_default_acl (acl_get_file)");
524 /* Our return value. */
527 int wipe_result
= wipe_acls(path
);
528 if (wipe_result
== -1) {
529 perror("apply_default_acl (wipe_acls)");
534 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
535 ACL with this one. */
536 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
537 if (acl
== (acl_t
)NULL
) {
538 perror("apply_default_acl (acl_get_file)");
542 /* If it's a directory, inherit the parent's default. */
543 int inherit_result
= inherit_default_acl(path
, parent
);
544 if (inherit_result
== -1) {
545 perror("apply_default_acl (inherit_acls)");
551 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
553 while (ge_result
== 1) {
554 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
555 int tag_result
= acl_get_tag_type(entry
, &tag
);
557 if (tag_result
== -1) {
558 perror("apply_default_acl (acl_get_tag_type)");
564 /* We've got an entry/tag from the default ACL. Get its permset. */
565 acl_permset_t permset
;
566 int ps_result
= acl_get_permset(entry
, &permset
);
567 if (ps_result
== -1) {
568 perror("apply_default_acl (acl_get_permset)");
573 /* If this is a default mask, fix it up. */
574 if (tag
== ACL_MASK
||
575 tag
== ACL_USER_OBJ
||
576 tag
== ACL_GROUP_OBJ
||
580 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
581 minimal ACLs) or acl_other entries, so if execute should be
582 masked, we have to do it manually. */
583 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
584 if (d_result
== -1) {
585 perror("apply_default_acl (acl_delete_perm)");
590 int sp_result
= acl_set_permset(entry
, permset
);
591 if (sp_result
== -1) {
592 perror("apply_default_acl (acl_set_permset)");
599 /* Finally, add the permset to the access ACL. */
600 int set_result
= acl_set_entry(&acl
, entry
);
601 if (set_result
== -1) {
602 perror("apply_default_acl (acl_set_entry)");
607 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
610 /* Catches the first acl_get_entry as well as the ones at the end of
612 if (ge_result
== -1) {
613 perror("apply_default_acl (acl_get_entry)");
618 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
619 if (sf_result
== -1) {
620 perror("apply_default_acl (acl_set_file)");
631 void usage(char* program_name
) {
633 * Print usage information.
635 printf("Apply any applicable default ACLs to the given files or "
637 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
640 printf(" -h, --help Print this help message\n");
641 printf(" -r, --recursive Act on any given directories recursively\n");
642 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
648 int apply_default_acl_nftw(const char *target
,
649 const struct stat
*s
,
652 /* A wrapper around the apply_default_acl() function for use with
653 * nftw(). We need to adjust the return value so that nftw() doesn't
654 * think we've failed.
656 bool reapp_result
= apply_default_acl(target
);
666 bool apply_default_acl_recursive(const char *target
) {
667 /* Attempt to apply default ACLs recursively. If target is a
668 * directory, we recurse through its entries. If not, we just
669 * apply the default ACL to target.
671 * We ignore symlinks for consistency with chmod -r.
674 if (!is_directory(target
)) {
675 return apply_default_acl(target
);
678 int max_levels
= 256;
679 int flags
= FTW_PHYS
; /* Don't follow links. */
681 int nftw_result
= nftw(target
,
682 apply_default_acl_nftw
,
686 if (nftw_result
== 0) {
691 /* nftw will return -1 on error, or if the supplied function
692 * (apply_default_acl_nftw) returns a non-zero result, nftw will
695 if (nftw_result
== -1) {
696 perror("apply_default_acl_recursive (nftw)");
703 int main(int argc
, char* argv
[]) {
705 * Call apply_default_acl on each command-line argument.
712 bool recursive
= false;
713 /* bool no_exec_mask is declared static/global */
715 struct option long_options
[] = {
716 /* These options set a flag. */
717 {"help", no_argument
, NULL
, 'h'},
718 {"recursive", no_argument
, NULL
, 'r'},
719 {"no-exec-mask", no_argument
, NULL
, 'x'},
725 while ((opt
= getopt_long(argc
, argv
, "hrx", long_options
, NULL
)) != -1) {
742 int result
= EXIT_SUCCESS
;
745 for (arg_index
= optind
; arg_index
< argc
; arg_index
++) {
746 const char* target
= argv
[arg_index
];
747 bool reapp_result
= false;
750 reapp_result
= apply_default_acl_recursive(target
);
753 /* It's either normal file, or we're not operating recursively. */
754 reapp_result
= apply_default_acl(target
);
758 result
= EXIT_FAILURE
;