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
);
385 * @brief Determine whether @c path is executable (by anyone) or a
388 * This is used as part of the heuristic to determine whether or not
389 * we should mask the execute bit when inheriting an ACL. If @c path
390 * is a directory, the answer is a clear-cut yes. This behavior is
391 * modeled after the capital 'X' perms of setfacl.
393 * If @c path is a file, we check the @a effective permissions,
394 * contrary to what setfacl does.
400 * - @c ACL_SUCCESS - @c path is a directory, or someone has effective
402 * - @c ACL_FAILURE - @c path is a regular file and nobody can execute
404 * - @c ACL_ERROR - Unexpected library error.
406 int any_can_execute_or_dir(const char* path
) {
408 if (is_directory(path
)) {
409 /* That was easy... */
413 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
415 if (acl
== (acl_t
)NULL
) {
416 perror("any_can_execute_or_dir (acl_get_file)");
420 /* Our return value. */
421 int result
= ACL_FAILURE
;
423 if (acl_is_minimal(&acl
)) {
424 mode_t mode
= get_mode(path
);
425 if (mode
& (S_IXUSR
| S_IXOTH
| S_IXGRP
)) {
426 result
= ACL_SUCCESS
;
430 result
= ACL_FAILURE
;
436 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
438 while (ge_result
== ACL_SUCCESS
) {
439 /* The first thing we do is check to see if this is a mask
440 entry. If it is, we skip it entirely. */
441 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
442 int tag_result
= acl_get_tag_type(entry
, &tag
);
444 if (tag_result
== ACL_ERROR
) {
445 perror("any_can_execute_or_dir (acl_get_tag_type)");
450 if (tag
== ACL_MASK
) {
451 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
455 /* Ok, so it's not a mask entry. Check the execute perms. */
456 acl_permset_t permset
;
458 int ps_result
= acl_get_permset(entry
, &permset
);
459 if (ps_result
== ACL_ERROR
) {
460 perror("any_can_execute_or_dir (acl_get_permset)");
465 int gp_result
= acl_get_perm(permset
, ACL_EXECUTE
);
466 if (gp_result
== ACL_ERROR
) {
467 perror("any_can_execute_or_dir (acl_get_perm)");
472 if (gp_result
== ACL_SUCCESS
) {
473 /* Only return ACL_SUCCESS if this execute bit is not masked. */
474 if (acl_execute_masked(path
) != ACL_SUCCESS
) {
475 result
= ACL_SUCCESS
;
480 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
483 if (ge_result
== ACL_ERROR
) {
484 perror("any_can_execute_or_dir (acl_get_entry)");
497 * @brief Inherit the default ACL from @c parent to @c path.
499 * The @c parent parameter does not necessarily need to be the parent
500 * of @c path, although that will usually be the case. This overwrites
501 * any existing default ACL on @c path.
504 * The parent directory whose ACL we want to inherit.
507 * The target directory whose ACL we wish to overwrite (or create).
510 * - @c ACL_SUCCESS - The default ACL was inherited successfully.
511 * - @c ACL_FAILURE - Either @c parent or @c path is not a directory.
512 * - @c ACL_ERROR - Unexpected library error.
514 int inherit_default_acl(const char* path
, const char* parent
) {
516 /* Our return value. */
517 int result
= ACL_SUCCESS
;
524 if (!is_directory(path
) || !is_directory(parent
)) {
528 acl_t parent_acl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
529 if (parent_acl
== (acl_t
)NULL
) {
530 perror("inherit_default_acl (acl_get_file)");
534 acl_t path_acl
= acl_dup(parent_acl
);
536 if (path_acl
== (acl_t
)NULL
) {
537 perror("inherit_default_acl (acl_dup)");
538 acl_free(parent_acl
);
542 int sf_result
= acl_set_file(path
, ACL_TYPE_DEFAULT
, path_acl
);
543 if (sf_result
== -1) {
544 perror("inherit_default_acl (acl_set_file)");
557 * @brief Remove @c ACL_USER, @c ACL_GROUP, and @c ACL_MASK entries
558 * from the given path.
561 * The path whose ACLs we want to wipe.
564 * - @c ACL_SUCCESS - The ACLs were wiped successfully, or none
565 * existed in the first place.
566 * - @c ACL_ERROR - Unexpected library error.
568 int wipe_acls(const char* path
) {
575 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
576 if (acl
== (acl_t
)NULL
) {
577 perror("wipe_acls (acl_get_file)");
581 /* Our return value. */
582 int result
= ACL_SUCCESS
;
585 int ge_result
= acl_get_entry(acl
, ACL_FIRST_ENTRY
, &entry
);
587 while (ge_result
== ACL_SUCCESS
) {
588 int d_result
= acl_delete_entry(acl
, entry
);
589 if (d_result
== ACL_ERROR
) {
590 perror("wipe_acls (acl_delete_entry)");
595 ge_result
= acl_get_entry(acl
, ACL_NEXT_ENTRY
, &entry
);
598 /* Catches the first acl_get_entry as well as the ones at the end of
600 if (ge_result
== ACL_ERROR
) {
601 perror("wipe_acls (acl_get_entry)");
606 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
607 if (sf_result
== ACL_ERROR
) {
608 perror("wipe_acls (acl_set_file)");
621 * @brief Apply parent default ACL to a path.
623 * This overwrites any existing ACLs on @c path.
626 * The path whose ACL we would like to reset to its default.
628 * @param no_exec_mask
629 * The value (either true or false) of the --no-exec-mask flag.
632 * - @c ACL_SUCCESS - The parent default ACL was inherited successfully.
633 * - @c ACL_FAILURE - The target path is not a regular file/directory,
634 * or the parent of @c path is not a directory.
635 * - @c ACL_ERROR - Unexpected library error.
637 int apply_default_acl(const char* path
, bool no_exec_mask
) {
644 if (!is_regular_file(path
) && !is_directory(path
)) {
648 /* dirname mangles its argument */
649 char path_copy
[PATH_MAX
];
650 strncpy(path_copy
, path
, PATH_MAX
-1);
651 path_copy
[PATH_MAX
-1] = 0;
653 char* parent
= dirname(path_copy
);
654 if (!is_directory(parent
)) {
655 /* Make sure dirname() did what we think it did. */
659 /* Default to not masking the exec bit; i.e. applying the default
660 ACL literally. If --no-exec-mask was not specified, then we try
661 to "guess" whether or not to mask the exec bit. */
662 bool allow_exec
= true;
665 int ace_result
= any_can_execute_or_dir(path
);
667 if (ace_result
== ACL_ERROR
) {
668 perror("apply_default_acl (any_can_execute_or_dir)");
672 allow_exec
= (bool)ace_result
;
675 acl_t defacl
= acl_get_file(parent
, ACL_TYPE_DEFAULT
);
677 if (defacl
== (acl_t
)NULL
) {
678 perror("apply_default_acl (acl_get_file)");
682 /* Our return value. */
683 int result
= ACL_SUCCESS
;
685 int wipe_result
= wipe_acls(path
);
686 if (wipe_result
== ACL_ERROR
) {
687 perror("apply_default_acl (wipe_acls)");
692 /* Do this after wipe_acls(), otherwise we'll overwrite the wiped
693 ACL with this one. */
694 acl_t acl
= acl_get_file(path
, ACL_TYPE_ACCESS
);
695 if (acl
== (acl_t
)NULL
) {
696 perror("apply_default_acl (acl_get_file)");
700 /* If it's a directory, inherit the parent's default. */
701 int inherit_result
= inherit_default_acl(path
, parent
);
702 if (inherit_result
== ACL_ERROR
) {
703 perror("apply_default_acl (inherit_acls)");
709 int ge_result
= acl_get_entry(defacl
, ACL_FIRST_ENTRY
, &entry
);
711 while (ge_result
== ACL_SUCCESS
) {
712 acl_tag_t tag
= ACL_UNDEFINED_TAG
;
713 int tag_result
= acl_get_tag_type(entry
, &tag
);
715 if (tag_result
== ACL_ERROR
) {
716 perror("apply_default_acl (acl_get_tag_type)");
722 /* We've got an entry/tag from the default ACL. Get its permset. */
723 acl_permset_t permset
;
724 int ps_result
= acl_get_permset(entry
, &permset
);
725 if (ps_result
== ACL_ERROR
) {
726 perror("apply_default_acl (acl_get_permset)");
731 /* If this is a default mask, fix it up. */
732 if (tag
== ACL_MASK
||
733 tag
== ACL_USER_OBJ
||
734 tag
== ACL_GROUP_OBJ
||
738 /* The mask doesn't affect acl_user_obj, acl_group_obj (in
739 minimal ACLs) or acl_other entries, so if execute should be
740 masked, we have to do it manually. */
741 int d_result
= acl_delete_perm(permset
, ACL_EXECUTE
);
742 if (d_result
== ACL_ERROR
) {
743 perror("apply_default_acl (acl_delete_perm)");
748 int sp_result
= acl_set_permset(entry
, permset
);
749 if (sp_result
== ACL_ERROR
) {
750 perror("apply_default_acl (acl_set_permset)");
757 /* Finally, add the permset to the access ACL. */
758 int set_result
= acl_set_entry(&acl
, entry
);
759 if (set_result
== ACL_ERROR
) {
760 perror("apply_default_acl (acl_set_entry)");
765 ge_result
= acl_get_entry(defacl
, ACL_NEXT_ENTRY
, &entry
);
768 /* Catches the first acl_get_entry as well as the ones at the end of
770 if (ge_result
== ACL_ERROR
) {
771 perror("apply_default_acl (acl_get_entry)");
776 int sf_result
= acl_set_file(path
, ACL_TYPE_ACCESS
, acl
);
777 if (sf_result
== ACL_ERROR
) {
778 perror("apply_default_acl (acl_set_file)");
791 * @brief Display program usage information.
793 * @param program_name
794 * The program name to use in the output.
797 void usage(char* program_name
) {
798 printf("Apply any applicable default ACLs to the given files or "
800 printf("Usage: %s [flags] <target1> [<target2> [ <target3>...]]\n\n",
803 printf(" -h, --help Print this help message\n");
804 printf(" -r, --recursive Act on any given directories recursively\n");
805 printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
812 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
814 * For parameter information, see the @c nftw man page.
816 * @return If the ACL was applied to @c target successfully, we return
817 * @c FTW_CONTINUE to signal to @ nftw() that we should proceed onto
818 * the next file or directory. Otherwise, we return @c FTW_STOP to
822 int apply_default_acl_nftw(const char *target
,
823 const struct stat
*s
,
827 bool app_result
= apply_default_acl(target
, false);
839 * @brief Wrapper around @c apply_default_acl() for use with @c nftw().
841 * This is identical to @c apply_default_acl_nftw(), except it passes
842 * @c true to @c apply_default_acl() as its no_exec_mask argument.
845 int apply_default_acl_nftw_x(const char *target
,
846 const struct stat
*s
,
850 bool app_result
= apply_default_acl(target
, true);
862 * @brief Recursive version of @c apply_default_acl().
864 * If @c target is a directory, we use @c nftw() to call @c
865 * apply_default_acl() recursively on all of its children. Otherwise,
866 * we just delegate to @c apply_default_acl().
868 * We ignore symlinks for consistency with chmod -r.
871 * The root (path) of the recursive application.
873 * @param no_exec_mask
874 * The value (either true or false) of the --no-exec-mask flag.
877 * If @c target is not a directory, we return the result of
878 * calling @c apply_default_acl() on @c target. Otherwise, we convert
879 * the return value of @c nftw(). If @c nftw() succeeds (returns 0),
880 * then we return @c true. Otherwise, we return @c false.
882 * If there is an error, it will be reported via @c perror, but
883 * we still return @c false.
885 bool apply_default_acl_recursive(const char *target
, bool no_exec_mask
) {
887 if (!is_directory(target
)) {
888 return apply_default_acl(target
, no_exec_mask
);
891 int max_levels
= 256;
892 int flags
= FTW_PHYS
; /* Don't follow links. */
894 /* There are two separate functions that could be passed to
895 nftw(). One passes no_exec_mask = true to apply_default_acl(),
896 and the other passes no_exec_mask = false. Since the function we
897 pass to nftw() cannot have parameters, we have to create separate
898 options and make the decision here. */
899 int (*fn
)(const char *, const struct stat
*, int, struct FTW
*) = NULL
;
900 fn
= no_exec_mask
? apply_default_acl_nftw_x
: apply_default_acl_nftw
;
902 int nftw_result
= nftw(target
, fn
, max_levels
, flags
);
904 if (nftw_result
== 0) {
909 /* nftw will return -1 on error, or if the supplied function
910 * (apply_default_acl_nftw) returns a non-zero result, nftw will
913 if (nftw_result
== -1) {
914 perror("apply_default_acl_recursive (nftw)");
923 * @brief Call apply_default_acl (possibly recursively) on each
924 * command-line argument.
926 * @return Either @c EXIT_FAILURE or @c EXIT_SUCCESS. If everything
927 * goes as expected, we return @c EXIT_SUCCESS. Otherwise, we return
930 int main(int argc
, char* argv
[]) {
937 bool recursive
= false;
938 bool no_exec_mask
= false;
940 struct option long_options
[] = {
941 /* These options set a flag. */
942 {"help", no_argument
, NULL
, 'h'},
943 {"recursive", no_argument
, NULL
, 'r'},
944 {"no-exec-mask", no_argument
, NULL
, 'x'},
950 while ((opt
= getopt_long(argc
, argv
, "hrx", long_options
, NULL
)) != -1) {
967 int result
= EXIT_SUCCESS
;
970 for (arg_index
= optind
; arg_index
< argc
; arg_index
++) {
971 const char* target
= argv
[arg_index
];
972 bool reapp_result
= false;
975 reapp_result
= apply_default_acl_recursive(target
, no_exec_mask
);
978 /* It's either normal file, or we're not operating recursively. */
979 reapp_result
= apply_default_acl(target
, no_exec_mask
);
983 result
= EXIT_FAILURE
;