#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; } } int has_default_tag_acl(const char* path, acl_tag_t tag_type) { /* Returns one if the given path has a default ACL for the supplied tag, zero if it doesn't, and -1 on error. */ acl_t defacl = acl_get_file(path, ACL_TYPE_DEFAULT); if (defacl == (acl_t)NULL) { return 0; } 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 -1; } else { if (tag == tag_type) { return 1; } } result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry); } if (result == -1) { perror("has_default_tag_acl (acl_get_entry)"); return -1; } return 0; } int has_minimal_default_acl(const char* path) { /* An ACL is minimal if and only if it has no mask. Return 1 if it's minimal, zero if it isn't, and -1 on errors. */ int has_dm = has_default_tag_acl(path, ACL_MASK); if (has_dm == 0) { return 1; } else if (has_dm == 1) { return 0; } else { perror("has_minimal_default_acl"); return -1; } } int has_default_user_obj_acl(const char* path) { return has_default_tag_acl(path, ACL_USER_OBJ); } int has_default_group_obj_acl(const char* path) { return has_default_tag_acl(path, ACL_GROUP_OBJ); } int has_default_other_acl(const char* path) { return has_default_tag_acl(path, ACL_OTHER); } int get_default_tag_permset(const char* path, acl_tag_t tag_type, acl_permset_t* output_perms) { /* Returns one if successful, zero when the ACL doesn't exist, and -1 on unexpected errors. */ 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 0; } 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 (acl_get_tag_type)"); return -1; } 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 == -1) { perror("get_default_tag_permset (acl_get_permset)"); } if (ps_result == 0) { return 1; } else { return 0; } } } result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry); } /* This catches both the initial acl_get_entry and the ones at the end of the loop. */ if (result == -1) { perror("get_default_tag_permset (acl_get_entry)"); return -1; } return 0; } int has_default_tag_perm(const char* path, acl_tag_t tag, acl_perm_t perm) { /* Check path to see if tag has the given perm. Returns one if it does, zero if it doesn't (or there's no ACL), and -1 on unexpected errors. */ if (!has_default_tag_acl(path, tag)) { return 0; } acl_permset_t permset; bool ps_result = get_default_tag_permset(path, tag, &permset); if (ps_result != 1) { /* Failure or error. */ return ps_result; } int p_result = acl_get_perm(permset, perm); if (p_result == -1) { perror("has_default_tag_perm (acl_get_perm)"); } return p_result; } int has_default_user_obj_read(const char* path) { return has_default_tag_perm(path, ACL_USER_OBJ, ACL_READ); } int has_default_user_obj_write(const char* path) { return has_default_tag_perm(path, ACL_USER_OBJ, ACL_WRITE); } int has_default_user_obj_execute(const char* path) { return has_default_tag_perm(path, ACL_USER_OBJ, ACL_EXECUTE); } int has_default_group_obj_read(const char* path) { return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_READ); } int has_default_group_obj_write(const char* path) { return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_WRITE); } int has_default_group_obj_execute(const char* path) { return has_default_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE); } int has_default_other_read(const char* path) { return has_default_tag_perm(path, ACL_OTHER, ACL_READ); } int has_default_other_write(const char* path) { return has_default_tag_perm(path, ACL_OTHER, ACL_WRITE); } int has_default_other_execute(const char* path) { return has_default_tag_perm(path, ACL_OTHER, ACL_EXECUTE); } int has_default_mask_read(const char* path) { return has_default_tag_perm(path, ACL_MASK, ACL_READ); } int has_default_mask_write(const char* path) { return has_default_tag_perm(path, ACL_MASK, ACL_WRITE); } int has_default_mask_execute(const char* path) { return has_default_tag_perm(path, ACL_MASK, ACL_EXECUTE); } int 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. Returns one for success, zero for failure (i.e. no ACL), and -1 on unexpected errors. */ if (path == NULL) { return 0; } if (!is_regular_file(path) && !is_directory(path)) { return 0; } /* 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 0; } /* 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; } } if (has_minimal_default_acl(parent)) { /* With a minimal ACL, we'll repeat for the group bits what we already did for the owner/other bits. */ if (has_default_group_obj_read(parent)) { path_mode |= S_IRGRP; } if (has_default_group_obj_write(parent)) { path_mode |= S_IWGRP; } if (has_default_group_obj_acl(parent)) { if (!has_default_group_obj_execute(parent)) { /* It has a group ACL, but no group execute is granted. */ path_mode &= ~S_IXGRP; } } } else { /* The parent has an extended ACL. Extended ACLs use the mask entry. */ /* 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)) { /* This just adds the group execute bit, and doesn't actually grant group execute permissions. */ path_mode |= S_IXGRP; } if (!mode_has_perm(path_mode, S_IXGRP)) { /* The group ACL entry should already have been inherited from the default ACL. If the source was not group executable, we want to modify the destination so that it is not group executable either. In the presence of ACLs, the group permissions come not from the mode bits, but from the group:: ACL entry. So, to do this, we remove the group::x entry. */ if (has_default_group_obj_execute(path)) { /* remove_default_group_obj_execute(path);*/ } } } int chmod_result = chmod(path, path_mode); if (chmod_result == 0) { return 1; } else { return 0; } } 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; } }