+/**
+ * @file apply-default-acl.c
+ *
+ * @brief The entire implementation.
+ *
+ */
+
/* On Linux, ftw.h needs this special voodoo to work. */
#define _XOPEN_SOURCE 500
#include <sys/acl.h>
+/* Command-line options */
+static bool no_exec_mask = false;
+
+
+/**
+ * @brief Get the mode bits from the given path.
+ *
+ * @param path
+ * The path (file or directory) whose mode we want.
+ *
+ * @return A mode_t (st_mode) structure containing the mode bits.
+ * See sys/stat.h for details.
+ */
mode_t get_mode(const char* path) {
- /*
- * Get the mode bits from path.
- */
if (path == NULL) {
errno = ENOENT;
return -1;
}
+/**
+ * @brief Determine whether or not the given path is a regular file.
+ *
+ * @param path
+ * The path to test.
+ *
+ * @return true if @c path is a regular file, false otherwise.
+ */
bool is_regular_file(const char* path) {
- /*
- * Returns true if path is a regular file, false otherwise.
- */
if (path == NULL) {
return false;
}
}
}
+
+/**
+ * @brief Determine whether or not the given path is a directory.
+ *
+ * @param path
+ * The path to test.
+ *
+ * @return true if @c path is a directory, false otherwise.
+ */
bool is_directory(const char* path) {
- /*
- * Returns true if path is a directory, false otherwise.
- */
if (path == NULL) {
return false;
}
}
-int any_can_execute(const char* path) {
- /* Returns 1 if any ACL entry has execute access, 0 if none do, and
+int acl_execute_masked(const char* path) {
+ /* Returns 1 i the given path has an ACL mask which denies
+ execute. Returns 0 if it does not (or if it has no ACL/mask at
+ all), and -1 on error. */
+
+ acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
+
+ if (acl == (acl_t)NULL) {
+ return 0;
+ }
+
+ /* Our return value. */
+ int result = 0;
+
+ acl_entry_t entry;
+ int ge_result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
+
+ while (ge_result == 1) {
+ acl_tag_t tag = ACL_UNDEFINED_TAG;
+ int tag_result = acl_get_tag_type(entry, &tag);
+
+ if (tag_result == -1) {
+ perror("acl_execute_masked (acl_get_tag_type)");
+ result = -1;
+ goto cleanup;
+ }
+
+ if (tag == ACL_MASK) {
+ /* This is the mask entry, get its permissions, and see if
+ execute is specified. */
+ acl_permset_t permset;
+
+ int ps_result = acl_get_permset(entry, &permset);
+ if (ps_result == -1) {
+ perror("acl_execute_masked (acl_get_permset)");
+ result = -1;
+ goto cleanup;
+ }
+
+ int gp_result = acl_get_perm(permset, ACL_EXECUTE);
+ if (gp_result == -1) {
+ perror("acl_execute_masked (acl_get_perm)");
+ result = -1;
+ goto cleanup;
+ }
+
+ if (gp_result == 0) {
+ /* No execute bit set in the mask; execute not allowed. */
+ return 1;
+ }
+ }
+
+ ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
+ }
+
+ cleanup:
+ acl_free(acl);
+ return result;
+}
+
+
+int any_can_execute_or_dir(const char* path) {
+ /* If the given path is a directory, returns 1. Otherwise, returns 1
+ * if any ACL entry has EFFECTIVE execute access, 0 if none do, and
* -1 on error.
+ *
+ * This is meant to somewhat mimic setfacl's handling of the capital
+ * 'X' perm, which allows execute access if the target is a
+ * directory or someone can already execute it. We differ in that we
+ * check the effective execute rather than just the execute bits.
*/
+
+ if (is_directory(path)) {
+ /* That was easy... */
+ return 1;
+ }
+
acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS);
if (acl == (acl_t)NULL) {
int ps_result = acl_get_permset(entry, &permset);
if (ps_result == -1) {
- perror("any_can_execute (acl_get_permset)");
+ perror("any_can_execute_or_dir (acl_get_permset)");
result = -1;
goto cleanup;
}
int gp_result = acl_get_perm(permset, ACL_EXECUTE);
if (gp_result == -1) {
- perror("any_can_execute (acl_get_perm)");
+ perror("any_can_execute_or_dir (acl_get_perm)");
result = -1;
goto cleanup;
}
if (gp_result == 1) {
- result = 1;
- goto cleanup;
+ /* Only return one if this execute bit is not masked. */
+ if (acl_execute_masked(path) != 1) {
+ result = 1;
+ goto cleanup;
+ }
}
ge_result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
}
if (ge_result == -1) {
- perror("any_can_execute (acl_get_entry)");
+ perror("any_can_execute_or_dir (acl_get_entry)");
result = -1;
goto cleanup;
}
/* Really apply the default ACL by looping through it. Returns one
* for success, zero for failure (i.e. no ACL), and -1 on unexpected
* errors.
+
*/
if (path == NULL) {
return 0;
return 0;
}
- int ace_result = any_can_execute(path);
- if (ace_result == -1) {
- perror("apply_default_acl (any_can_execute)");
+ /* Default to not masking the exec bit; i.e. applying the default
+ ACL literally. If --no-exec-mask was not specified, then we try
+ to "guess" whether or not to mask the exec bit. */
+ bool allow_exec = true;
+
+ if (!no_exec_mask) {
+ int ace_result = any_can_execute_or_dir(path);
+
+ if (ace_result == -1) {
+ perror("apply_default_acl (any_can_execute_or_dir)");
return -1;
}
-
- bool allow_exec = (bool)ace_result;
+ allow_exec = (bool)ace_result;
+ }
acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT);
int tag_result = acl_get_tag_type(entry, &tag);
if (tag_result == -1) {
- perror("has_default_tag_acl (acl_get_tag_type)");
+ perror("apply_default_acl (acl_get_tag_type)");
result = -1;
goto cleanup;
}
tag == ACL_USER_OBJ ||
tag == ACL_GROUP_OBJ ||
tag == ACL_OTHER) {
+
if (!allow_exec) {
/* The mask doesn't affect acl_user_obj, acl_group_obj (in
minimal ACLs) or acl_other entries, so if execute should be
printf("Flags:\n");
printf(" -h, --help Print this help message\n");
printf(" -r, --recursive Act on any given directories recursively\n");
+ printf(" -x, --no-exec-mask Apply execute permissions unconditionally\n");
+
+ return;
}
int apply_default_acl_nftw(const char *target,
- const struct stat *s,
- int info,
- struct FTW *ftw) {
+ const struct stat *s,
+ int info,
+ struct FTW *ftw) {
/* A wrapper around the apply_default_acl() function for use with
* nftw(). We need to adjust the return value so that nftw() doesn't
* think we've failed.
return EXIT_FAILURE;
}
-
bool recursive = false;
+ /* bool no_exec_mask is declared static/global */
struct option long_options[] = {
/* These options set a flag. */
{"help", no_argument, NULL, 'h'},
{"recursive", no_argument, NULL, 'r'},
+ {"no-exec-mask", no_argument, NULL, 'x'},
{NULL, 0, NULL, 0}
};
int opt = 0;
- while ((opt = getopt_long(argc, argv, "hr", long_options, NULL)) != -1) {
+ while ((opt = getopt_long(argc, argv, "hrx", long_options, NULL)) != -1) {
switch (opt) {
case 'h':
usage(argv[0]);
case 'r':
recursive = true;
break;
+ case 'x':
+ no_exec_mask = true;
+ break;
default:
usage(argv[0]);
return EXIT_FAILURE;