#include #include /* dirname() */ #include /* PATH_MAX */ #include #include #include #include #include #include /* ACLs */ #include /* acl_get_perm, not portable */ #include #include mode_t get_mode(const char* path) { if (path == NULL) { errno = ENOENT; return -1; } struct stat s; int result = stat(path, &s); if (result == 0) { return s.st_mode; } else { /* errno will be set already by stat() */ return result; } } bool mode_has_perm(mode_t mode, int perm) { if (mode & perm) { return true; } else { return false; } } bool is_regular_file(const char* path) { if (path == NULL) { return false; } struct stat s; int result = stat(path, &s); if (result == 0) { return S_ISREG(s.st_mode); } else { return false; } } bool is_directory(const char* path) { if (path == NULL) { return false; } struct stat s; int result = stat(path, &s); if (result == 0) { return S_ISDIR(s.st_mode); } else { return false; } } bool has_default_acl(const char* path) { /* Return true if the given path has a default ACL, false otherwise. */ acl_t defacl = acl_get_file(path, ACL_TYPE_DEFAULT); if (defacl == (acl_t)NULL) { return false; } /* Used to store the entry if it exists, even though we don't care what it is. */ acl_entry_t dummy; int result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &dummy); if (result == 1) { /* There's a first entry in the default ACL. */ return true; } else if (result == 0) { return false; } else { perror("has_default_acl"); return false; } } bool has_default_tag_acl(const char* path, acl_tag_t tag_type) { /* Return true if the given path has a default ACL for the supplied tag, false otherwise. */ acl_t defacl = acl_get_file(path, ACL_TYPE_DEFAULT); if (defacl == (acl_t)NULL) { return false; } acl_entry_t entry; int result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry); while (result == 1) { acl_tag_t tag = ACL_UNDEFINED_TAG; int tag_result = acl_get_tag_type(entry, &tag); if (tag_result == -1) { perror("has_default_tag_acl - acl_get_tag_type"); return false; } else { if (tag == tag_type) { return true; } } result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry); } return false; } bool has_default_user_obj_acl(const char* path) { return has_default_tag_acl(path, ACL_USER_OBJ); } bool has_default_group_obj_acl(const char* path) { return has_default_tag_acl(path, ACL_GROUP_OBJ); } bool has_default_other_acl(const char* path) { return has_default_tag_acl(path, ACL_OTHER); } bool get_default_tag_permset(const char* path, acl_tag_t tag_type, acl_permset_t* output_perms) { /* Returns true if successful or false on error */ acl_t defacl = acl_get_file(path, ACL_TYPE_DEFAULT); if (defacl == (acl_t)NULL) { /* Follow the acl_foo convention of -1 == error. */ errno = EINVAL; return false; } acl_entry_t entry; int result = acl_get_entry(defacl, ACL_FIRST_ENTRY, &entry); while (result == 1) { acl_tag_t tag = ACL_UNDEFINED_TAG; int tag_result = acl_get_tag_type(entry, &tag); if (tag_result == -1) { perror("get_default_tag_permset"); return false; } else { if (tag == tag_type) { /* We found the right tag, now get the permset. */ int ps_result = acl_get_permset(entry, output_perms); if (ps_result == 0) { return true; } else { return false; } } } result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry); } errno = EINVAL; return false; } bool get_default_user_obj_permset(const char* path, acl_permset_t* output_perms) { return get_default_tag_permset(path, ACL_USER_OBJ, output_perms); } bool get_default_group_obj_permset(const char* path, acl_permset_t* output_perms) { return get_default_tag_permset(path, ACL_GROUP_OBJ, output_perms); } bool get_default_other_permset(const char* path, acl_permset_t* output_perms) { return get_default_tag_permset(path, ACL_OTHER, output_perms); } bool has_default_tag_perm(const char* path, acl_tag_t tag, acl_perm_t perm) { /* Check path to see if tag has perm. */ if (!has_default_tag_acl(path, tag)) { return false; } acl_permset_t permset; bool ps_result = get_default_tag_permset(path, tag, &permset); if (!ps_result) { return false; } int p_result = acl_get_perm(permset, perm); if (p_result == 1) { return true; } else { return false; } } bool has_default_user_obj_read(const char* path) { return has_default_tag_perm(path, ACL_USER_OBJ, ACL_READ); } bool has_default_user_obj_write(const char* path) { return has_default_tag_perm(path, ACL_USER_OBJ, ACL_WRITE); } bool has_default_user_obj_execute(const char* path) { return has_default_tag_perm(path, ACL_USER_OBJ, ACL_EXECUTE); } bool has_default_group_obj_read(const char* path) { return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_READ); } bool has_default_group_obj_write(const char* path) { return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_WRITE); } bool has_default_group_obj_execute(const char* path) { return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE); } bool has_default_other_read(const char* path) { return has_default_tag_perm(path, ACL_OTHER, ACL_READ); } bool has_default_other_write(const char* path) { return has_default_tag_perm(path, ACL_OTHER, ACL_WRITE); } bool has_default_other_execute(const char* path) { return has_default_tag_perm(path, ACL_OTHER, ACL_EXECUTE); } bool has_default_mask_read(const char* path) { return has_default_tag_perm(path, ACL_MASK, ACL_READ); } bool has_default_mask_write(const char* path) { return has_default_tag_perm(path, ACL_MASK, ACL_WRITE); } bool has_default_mask_execute(const char* path) { return has_default_tag_perm(path, ACL_MASK, ACL_EXECUTE); } bool reapply_default_acl(const char* path) { /* If this is a normal file or directory (i.e. that has just been created), we proceed to find its parent directory which will have a default ACL. */ if (path == NULL) { return false; } if (!is_regular_file(path) && !is_directory(path)) { return false; } /* dirname mangles its argument */ char path_copy[PATH_MAX]; strncpy(path_copy, path, PATH_MAX-1); path_copy[PATH_MAX-1] = 0; char* parent = dirname(path_copy); if (!is_directory(parent)) { /* Make sure dirname() did what we think it did. */ return false; } /* This is the original mode of path. We will simply add permissions to it, and then later reapply the result via chmod. */ mode_t path_mode = get_mode(path); /* If parent has a default user ACL, apply it to path via chmod. */ if (has_default_user_obj_read(parent)) { path_mode |= S_IRUSR; } if (has_default_user_obj_write(parent)) { path_mode |= S_IWUSR; } /* However, we don't want to set the execute bit on via the ACL unless it was on originally. Furthermore, if the ACL denies execute, we want to honor that. */ if (has_default_user_obj_acl(parent)) { if (!has_default_user_obj_execute(parent)) { /* It has a user ACL, but no user execute is granted. */ path_mode &= ~S_IXUSR; } } /* Do the same thing with the other perms/ACL. */ if (has_default_other_read(parent)) { path_mode |= S_IROTH; } if (has_default_other_write(parent)) { path_mode |= S_IWOTH; } if (has_default_other_acl(parent)) { if (!has_default_other_execute(parent)) { /* It has an other ACL, but no other execute is granted. */ path_mode &= ~S_IXOTH; } } /* For the group bits, we'll use the ACL's mask instead of the group object bits. If the default ACL had a group entry, it should already have propagated (but might be masked). */ if (has_default_mask_read(parent)) { path_mode |= S_IRGRP; } if (has_default_mask_write(parent)) { path_mode |= S_IWGRP; } /* Honor the mask execute unconditionally. */ if (has_default_mask_execute(parent)) { if (mode_has_perm(path_mode, S_IXGRP)) { path_mode |= S_IXGRP; } } int result = chmod(path, path_mode); if (result == 0) { return true; } else { return false; } } int main(int argc, char* argv[]) { const char* target = argv[1]; bool result = reapply_default_acl(target); if (result) { printf("Success.\n"); return EXIT_SUCCESS; } else { return EXIT_FAILURE; } }