2 * @file apply-default-acl.c
4 * @brief The entire implementation.
8 /* On Linux, ftw.h needs this special voodoo to work. */
9 #define _XOPEN_SOURCE 500
13 #include <ftw.h> /* nftw() et al. */
15 #include <libgen.h> /* dirname() */
16 #include <limits.h> /* PATH_MAX */
25 #include <acl/libacl.h> /* acl_get_perm, not portable */
26 #include <sys/types.h>
29 /* Most of the libacl functions return 1 for success, 0 for failure,
38 * @brief Get the mode bits from the given path.
41 * The path (file or directory) whose mode we want.
43 * @return A mode_t (st_mode) structure containing the mode bits.
44 * See sys/stat.h for details.
46 mode_t
get_mode(const char* path
) {
53 int result
= stat(path
, &s
);
59 /* errno will be set already by stat() */
67 * @brief Determine whether or not the given path is a regular file.
72 * @return true if @c path is a regular file, false otherwise.
74 bool is_regular_file(const char* path
) {
80 int result
= stat(path
, &s
);
82 return S_ISREG(s
.st_mode
);
92 * @brief Determine whether or not the given path is a directory.
97 * @return true if @c path is a directory, false otherwise.
99 bool is_directory(const char* path
) {
105 int result
= stat(path
, &s
);
107 return S_ISDIR(s
.st_mode
);
117 * @brief Update (or create) an entry in an @b minimal ACL.
119 * This function will not work if @c aclp contains extended
120 * entries. This is fine for our purposes, since we call @c wipe_acls
121 * on each path before applying the default to it.
123 * The assumption that there are no extended entries makes things much
124 * simpler. For example, we only have to update the @c ACL_USER_OBJ,
125 * @c ACL_GROUP_OBJ, and @c ACL_OTHER entries -- all others can simply
126 * be created anew. This means we don't have to fool around comparing
127 * named-user/group entries.
130 * A pointer to the acl_t structure whose entry we want to modify.
133 * The new entry. If @c entry contains a user/group/other entry, we
134 * update the existing one. Otherwise we create a new entry.
136 * @return If there is an unexpected library error, @c ACL_ERROR is
137 * returned. Otherwise, @c ACL_SUCCESS.
140 int acl_set_entry(acl_t
* aclp
,
144 int gt_result
= acl_get_tag_type(entry
, &entry_tag
);
145 if (gt_result
== ACL_ERROR
) {
146 perror("acl_set_entry (acl_get_tag_type)");
150 acl_permset_t entry_permset
;
151 int ps_result
= acl_get_permset(entry
, &entry_permset
);
152 if (ps_result
== ACL_ERROR
) {
153 perror("acl_set_entry (acl_get_permset)");
157 acl_entry_t existing_entry
;
158 /* Loop through the given ACL looking for matching entries. */
159 int result
= acl_get_entry(*aclp
, ACL_FIRST_ENTRY
, &existing_entry
);
161 while (result
== ACL_SUCCESS
) {
162 acl_tag_t existing_tag
= ACL_UNDEFINED_TAG
;
163 int tag_result
= acl_get_tag_type(existing_entry
, &existing_tag
);
165 if (tag_result
== ACL_ERROR
) {
166 perror("set_acl_tag_permset (acl_get_tag_type)");
170 if (existing_tag
== entry_tag
) {
171 if (entry_tag
== ACL_USER_OBJ
||
172 entry_tag
== ACL_GROUP_OBJ
||
173 entry_tag
== ACL_OTHER
) {
174 /* Only update for these three since all other tags will have
175 been wiped. These three are guaranteed to exist, so if we
176 match one of them, we're allowed to return ACL_SUCCESS
177 below and bypass the rest of the function. */
178 acl_permset_t existing_permset
;
179 int gep_result
= acl_get_permset(existing_entry
, &existing_permset
);
180 if (gep_result
== ACL_ERROR
) {
181 perror("acl_set_entry (acl_get_permset)");
185 int s_result
= acl_set_permset(existing_entry
, entry_permset
);
186 if (s_result
== ACL_ERROR
) {
187 perror("acl_set_entry (acl_set_permset)");
196 result
= acl_get_entry(*aclp
, ACL_NEXT_ENTRY
, &existing_entry
);
199 /* This catches both the initial acl_get_entry and the ones at the
201 if (result
== ACL_ERROR
) {
202 perror("acl_set_entry (acl_get_entry)");
206 /* If we've made it this far, we need to add a new entry to the
208 acl_entry_t new_entry
;
210 /* We allocate memory here that we should release! */
211 int c_result
= acl_create_entry(aclp
, &new_entry
);
212 if (c_result
== ACL_ERROR
) {
213 perror("acl_set_entry (acl_create_entry)");
217 int st_result
= acl_set_tag_type(new_entry
, entry_tag
);
218 if (st_result
== ACL_ERROR
) {
219 perror("acl_set_entry (acl_set_tag_type)");
223 int s_result
= acl_set_permset(new_entry
, entry_permset
);
224 if (s_result
== ACL_ERROR
) {
225 perror("acl_set_entry (acl_set_permset)");
229 if (entry_tag
== ACL_USER
|| entry_tag
== ACL_GROUP
) {
230 /* We need to set the qualifier too. */
231 void* entry_qual
= acl_get_qualifier(entry
);
232 if (entry_qual
== (void*)NULL
) {
233 perror("acl_set_entry (acl_get_qualifier)");
237 int sq_result
= acl_set_qualifier(new_entry
, entry_qual
);
238 if (sq_result
== ACL_ERROR
) {
239 perror("acl_set_entry (acl_set_qualifier)");
250 * @brief Determine the number of entries in the given ACL.
253 * A pointer to an @c acl_t structure.
255 * @return Either the non-negative number of entries in @c acl, or
256 * @c ACL_ERROR on error.
258 int acl_entry_count(acl_t
* acl
) {
262 int result
= acl_get_entry(*acl
, ACL_FIRST_ENTRY
, &entry
);
264 while (result
== ACL_SUCCESS
) {
266 result
= acl_get_entry(*acl
, ACL_NEXT_ENTRY
, &entry
);
269 if (result
== ACL_ERROR
) {
270 perror("acl_entry_count (acl_get_entry)");
280 * @brief Determine whether or not the given ACL is minimal.
282 * An ACL is minimal if it has fewer than four entries.
285 * A pointer to an acl_t structure.
288 * - @c ACL_SUCCESS - @c acl is minimal
289 * - @c ACL_FAILURE - @c acl is not minimal
290 * - @c ACL_ERROR - Unexpected library error
292 int acl_is_minimal(acl_t
* acl
) {
294 int ec
= acl_entry_count(acl
);
296 if (ec
== ACL_ERROR
) {
297 perror("acl_is_minimal (acl_entry_count)");
312 * @brief Determine whether the given path has an ACL whose mask
319 * - @c ACL_SUCCESS - @c path has a mask which denies execute.
320 * - @c ACL_FAILURE - The ACL for @c path does not deny execute,
321 * or @c path has no extended ACL at all.
322 * - @c ACL_ERROR - Unexpected library error.
324 int acl_execute_masked(const char* path
) {
326 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
328 if (acl
== (acl_t
)NULL
) {
329 perror("acl_execute_masked (acl_get_file)");
333 /* Our return value. */
334 int result
= ACL_FAILURE
;
337 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
339 while (ge_result
== ACL_SUCCESS
) {
340 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
341 int tag_result
= acl_get_tag_type(entry
, &tag
);
343 if (tag_result
== ACL_ERROR
) {
344 perror("acl_execute_masked (acl_get_tag_type)");
349 if (tag
== ACL_MASK
) {
350 /* This is the mask entry, get its permissions, and see if
351 execute is specified. */
352 acl_permset_t permset
;
354 int ps_result
= acl_get_permset(entry
, &permset
);
355 if (ps_result
== ACL_ERROR
) {
356 perror("acl_execute_masked (acl_get_permset)");
361 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
362 if (gp_result
== ACL_ERROR
) {
363 perror("acl_execute_masked (acl_get_perm)");
368 if (gp_result
== ACL_FAILURE
) {
369 /* No execute bit set in the mask; execute not allowed. */
374 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
384 * @brief Determine whether @c path is executable (by anyone) or a
387 * This is used as part of the heuristic to determine whether or not
388 * we should mask the execute bit when inheriting an ACL. If @c path
389 * is a directory, the answer is a clear-cut yes. This behavior is
390 * modeled after the capital 'X' perms of setfacl.
392 * If @c path is a file, we check the @a effective permissions,
393 * contrary to what setfacl does.
399 * - @c ACL_SUCCESS - @c path is a directory, or someone has effective
401 * - @c ACL_FAILURE - @c path is a regular file and nobody can execute
403 * - @c ACL_ERROR - Unexpected library error.
405 int any_can_execute_or_dir(const char* path
) {
407 if (is_directory(path
)) {
408 /* That was easy... */
412 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
414 if (acl
== (acl_t
)NULL
) {
415 perror("any_can_execute_or_dir (acl_get_file)");
419 /* Our return value. */
420 int result
= ACL_FAILURE
;
422 if (acl_is_minimal(&acl
)) {
423 mode_t mode
= get_mode(path
);
424 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
425 result
= ACL_SUCCESS
;
429 result
= ACL_FAILURE
;
435 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
437 while (ge_result
== ACL_SUCCESS
) {
438 acl_permset_t permset
;
440 int ps_result
= acl_get_permset(entry
, &permset
);
441 if (ps_result
== ACL_ERROR
) {
442 perror("any_can_execute_or_dir (acl_get_permset)");
447 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
448 if (gp_result
== ACL_ERROR
) {
449 perror("any_can_execute_or_dir (acl_get_perm)");
454 if (gp_result
== ACL_SUCCESS
) {
455 /* Only return one if this execute bit is not masked. */
456 if (acl_execute_masked(path
) != ACL_SUCCESS
) {
457 result
= ACL_SUCCESS
;
462 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
465 if (ge_result
== ACL_ERROR
) {
466 perror("any_can_execute_or_dir (acl_get_entry)");
479 * @brief Inherit the default ACL from @c parent to @c path.
481 * The @c parent parameter does not necessarily need to be the parent
482 * of @c path, although that will usually be the case. This overwrites
483 * any existing default ACL on @c path.
486 * The parent directory whose ACL we want to inherit.
489 * The target directory whose ACL we wish to overwrite (or create).
492 * - @c ACL_SUCCESS - The default ACL was inherited successfully.
493 * - @c ACL_FAILURE - Either @c parent or @c path is not a directory.
494 * - @c ACL_ERROR - Unexpected library error.
496 int inherit_default_acl(const char* path
, const char* parent
) {
498 /* Our return value. */
499 int result
= ACL_SUCCESS
;
506 if (!is_directory(path
) || !is_directory(parent
)) {
510 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
511 if (parent_acl
== (acl_t
)NULL
) {
512 perror("inherit_default_acl (acl_get_file)");
516 acl_t path_acl
= acl_dup(parent_acl
);
518 if (path_acl
== (acl_t
)NULL
) {
519 perror("inherit_default_acl (acl_dup)");
520 acl_free(parent_acl
);
524 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
525 if (sf_result
== -1) {
526 perror("inherit_default_acl (acl_set_file)");
539 * @brief Remove @c ACL_USER, @c ACL_GROUP, and @c ACL_MASK entries
540 * from the given path.
543 * The path whose ACLs we want to wipe.
546 * - @c ACL_SUCCESS - The ACLs were wiped successfully, or none
547 * existed in the first place.
548 * - @c ACL_ERROR - Unexpected library error.
550 int wipe_acls(const char* path
) {
557 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
558 if (acl
== (acl_t
)NULL
) {
559 perror("wipe_acls (acl_get_file)");
563 /* Our return value. */
564 int result
= ACL_SUCCESS
;
567 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
569 while (ge_result
== ACL_SUCCESS
) {
570 int d_result
= acl_delete_entry(acl
, entry
);
571 if (d_result
== ACL_ERROR
) {
572 perror("wipe_acls (acl_delete_entry)");
577 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
580 /* Catches the first acl_get_entry as well as the ones at the end of
582 if (ge_result
== ACL_ERROR
) {
583 perror("wipe_acls (acl_get_entry)");
588 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
589 if (sf_result
== ACL_ERROR
) {
590 perror("wipe_acls (acl_set_file)");
603 * @brief Apply parent default ACL to a path.
605 * This overwrites any existing ACLs on @c path.
608 * The path whose ACL we would like to reset to its default.
610 * @param no_exec_mask
611 * The value (either true or false) of the --no-exec-mask flag.
614 * - @c ACL_SUCCESS - The parent default ACL was inherited successfully.
615 * - @c ACL_FAILURE - The target path is not a regular file/directory,
616 * or the parent of @c path is not a directory.
617 * - @c ACL_ERROR - Unexpected library error.
619 int apply_default_acl(const char* path
, bool no_exec_mask
) {
626 if (!is_regular_file(path
) && !is_directory(path
)) {
630 /* dirname mangles its argument */
631 char path_copy
[PATH_MAX
];
632 strncpy(path_copy
, path
, PATH_MAX
-1);
633 path_copy
[PATH_MAX
-1] = 0;
635 char* parent
= dirname(path_copy
);
636 if (!is_directory(parent
)) {
637 /* Make sure dirname() did what we think it did. */
641 /* Default to not masking the exec bit; i.e. applying the default
642 ACL literally. If --no-exec-mask was not specified, then we try
643 to "guess" whether or not to mask the exec bit. */
644 bool allow_exec
= true;
647 int ace_result
= any_can_execute_or_dir(path
);
649 if (ace_result
== ACL_ERROR
) {
650 perror("apply_default_acl (any_can_execute_or_dir)");
654 allow_exec
= (bool)ace_result
;
657 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
659 if (defacl
== (acl_t
)NULL
) {
660 perror("apply_default_acl (acl_get_file)");
664 /* Our return value. */
665 int result
= ACL_SUCCESS
;
667 int wipe_result
= wipe_acls(path
);
668 if (wipe_result
== ACL_ERROR
) {
669 perror("apply_default_acl (wipe_acls)");
674 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
675 ACL with this one. */
676 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
677 if (acl
== (acl_t
)NULL
) {
678 perror("apply_default_acl (acl_get_file)");
682 /* If it's a directory, inherit the parent's default. */
683 int inherit_result
= inherit_default_acl(path
, parent
);
684 if (inherit_result
== ACL_ERROR
) {
685 perror("apply_default_acl (inherit_acls)");
691 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
693 while (ge_result
== ACL_SUCCESS
) {
694 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
695 int tag_result
= acl_get_tag_type(entry
, &tag
);
697 if (tag_result
== ACL_ERROR
) {
698 perror("apply_default_acl (acl_get_tag_type)");
704 /* We've got an entry/tag from the default ACL. Get its permset. */
705 acl_permset_t permset
;
706 int ps_result
= acl_get_permset(entry
, &permset
);
707 if (ps_result
== ACL_ERROR
) {
708 perror("apply_default_acl (acl_get_permset)");
713 /* If this is a default mask, fix it up. */
714 if (tag
== ACL_MASK
||
715 tag
== ACL_USER_OBJ
||
716 tag
== ACL_GROUP_OBJ
||
720 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
721 minimal ACLs) or acl_other entries, so if execute should be
722 masked, we have to do it manually. */
723 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
724 if (d_result
== ACL_ERROR
) {
725 perror("apply_default_acl (acl_delete_perm)");
730 int sp_result
= acl_set_permset(entry
, permset
);
731 if (sp_result
== ACL_ERROR
) {
732 perror("apply_default_acl (acl_set_permset)");
739 /* Finally, add the permset to the access ACL. */
740 int set_result
= acl_set_entry(&acl
, entry
);
741 if (set_result
== ACL_ERROR
) {
742 perror("apply_default_acl (acl_set_entry)");
747 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
750 /* Catches the first acl_get_entry as well as the ones at the end of
752 if (ge_result
== ACL_ERROR
) {
753 perror("apply_default_acl (acl_get_entry)");
758 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
759 if (sf_result
== ACL_ERROR
) {
760 perror("apply_default_acl (acl_set_file)");
773 * @brief Display program usage information.
775 * @param program_name
776 * The program name to use in the output.
779 void usage(char* program_name
) {
780 printf("Apply any applicable default ACLs to the given files or "
782 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
785 printf(" -h, --help Print this help message\n");
786 printf(" -r, --recursive Act on any given directories recursively\n");
787 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
794 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
796 * For parameter information, see the @c nftw man page.
798 * @return If the ACL was applied to @c target successfully, we return
799 * @c FTW_CONTINUE to signal to @ nftw() that we should proceed onto
800 * the next file or directory. Otherwise, we return @c FTW_STOP to
804 int apply_default_acl_nftw(const char *target
,
805 const struct stat
*s
,
809 bool app_result
= apply_default_acl(target
, false);
821 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
823 * This is identical to @c apply_default_acl_nftw(), except it passes
824 * @c true to @c apply_default_acl() as its no_exec_mask argument.
827 int apply_default_acl_nftw_x(const char *target
,
828 const struct stat
*s
,
832 bool app_result
= apply_default_acl(target
, true);
844 * @brief Recursive version of @c apply_default_acl().
846 * If @c target is a directory, we use @c nftw() to call @c
847 * apply_default_acl() recursively on all of its children. Otherwise,
848 * we just delegate to @c apply_default_acl().
850 * We ignore symlinks for consistency with chmod -r.
853 * The root (path) of the recursive application.
855 * @param no_exec_mask
856 * The value (either true or false) of the --no-exec-mask flag.
859 * If @c target is not a directory, we return the result of
860 * calling @c apply_default_acl() on @c target. Otherwise, we convert
861 * the return value of @c nftw(). If @c nftw() succeeds (returns 0),
862 * then we return @c true. Otherwise, we return @c false.
864 * If there is an error, it will be reported via @c perror, but
865 * we still return @c false.
867 bool apply_default_acl_recursive(const char *target
, bool no_exec_mask
) {
869 if (!is_directory(target
)) {
870 return apply_default_acl(target
, no_exec_mask
);
873 int max_levels
= 256;
874 int flags
= FTW_PHYS
; /* Don't follow links. */
876 /* There are two separate functions that could be passed to
877 nftw(). One passes no_exec_mask = true to apply_default_acl(),
878 and the other passes no_exec_mask = false. Since the function we
879 pass to nftw() cannot have parameters, we have to create separate
880 options and make the decision here. */
881 int (*fn
)(const char *, const struct stat
*, int, struct FTW
*) = NULL
;
882 fn
= no_exec_mask
? apply_default_acl_nftw_x
: apply_default_acl_nftw
;
884 int nftw_result
= nftw(target
, fn
, max_levels
, flags
);
886 if (nftw_result
== 0) {
891 /* nftw will return -1 on error, or if the supplied function
892 * (apply_default_acl_nftw) returns a non-zero result, nftw will
895 if (nftw_result
== -1) {
896 perror("apply_default_acl_recursive (nftw)");
905 * @brief Call apply_default_acl (possibly recursively) on each
906 * command-line argument.
908 * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything
909 * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return
912 int main(int argc
, char* argv
[]) {
919 bool recursive
= false;
920 bool no_exec_mask
= false;
922 struct option long_options
[] = {
923 /* These options set a flag. */
924 {"help", no_argument
, NULL
, 'h'},
925 {"recursive", no_argument
, NULL
, 'r'},
926 {"no-exec-mask", no_argument
, NULL
, 'x'},
932 while ((opt
= getopt_long(argc
, argv
, "hrx", long_options
, NULL
)) != -1) {
949 int result
= EXIT_SUCCESS
;
952 for (arg_index
= optind
; arg_index
< argc
; arg_index
++) {
953 const char* target
= argv
[arg_index
];
954 bool reapp_result
= false;
957 reapp_result
= apply_default_acl_recursive(target
, no_exec_mask
);
960 /* It's either normal file, or we're not operating recursively. */
961 reapp_result
= apply_default_acl(target
, no_exec_mask
);
965 result
= EXIT_FAILURE
;