#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_type_tag_acl(const char* path, acl_type_t type, acl_tag_t desired_tag) { /* 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, type); 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 == desired_tag) { 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_default_tag_acl(const char* path, acl_tag_t desired_tag) { return has_type_tag_acl(path, ACL_TYPE_DEFAULT, desired_tag); } int has_access_tag_acl(const char* path, acl_tag_t desired_tag) { return has_type_tag_acl(path, ACL_TYPE_ACCESS, desired_tag); } 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 has_default_mask_acl(const char* path) { return has_default_tag_acl(path, ACL_MASK); } int get_type_tag_entry(const char* path, acl_type_t type, acl_tag_t desired_tag, acl_entry_t* entry) { /* Returns one if successful, zero when the ACL doesn't exist, and -1 on unexpected errors. */ acl_t defacl = acl_get_file(path, type); if (defacl == (acl_t)NULL) { /* Follow the acl_foo convention of -1 == error. */ return 0; } 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_entry (acl_get_tag_type)"); return -1; } if (tag == desired_tag) { /* We found the right tag, so return successfully. */ return 1; } 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_entry (acl_get_entry)"); return -1; } return 0; } int get_default_tag_entry(const char* path, acl_tag_t desired_tag, acl_entry_t* entry) { return get_type_tag_entry(path, ACL_TYPE_DEFAULT, desired_tag, entry); } int get_access_tag_entry(const char* path, acl_tag_t desired_tag, acl_entry_t* entry) { return get_type_tag_entry(path, ACL_TYPE_ACCESS, desired_tag, entry); } int get_type_tag_permset(const char* path, acl_type_t type, acl_tag_t desired_tag, 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, type); if (defacl == (acl_t)NULL) { /* Follow the acl_foo convention of -1 == error. */ return 0; } acl_entry_t entry; int result = get_default_tag_entry(path, desired_tag, &entry); if (result == 1) { /* 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; } } else { return result; } } int get_default_tag_permset(const char* path, acl_tag_t desired_tag, acl_permset_t* output_perms) { return get_type_tag_permset(path, ACL_TYPE_DEFAULT, desired_tag, output_perms); } int get_access_tag_permset(const char* path, acl_tag_t desired_tag, acl_permset_t* output_perms) { return get_type_tag_permset(path, ACL_TYPE_ACCESS, desired_tag, output_perms); } 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 remove_access_tag_perm(const char* path, acl_tag_t tag, acl_perm_t perm) { /* Attempt to remove perm from tag. Returns one if successful, zero if there was nothing to do, and -1 on errors. */ int hata = has_access_tag_acl(path, tag); if (hata != 1) { /* Failure or error. */ return hata; } acl_permset_t permset; bool ps_result = get_access_tag_permset(path, tag, &permset); if (ps_result != 1) { /* Failure or error. */ return ps_result; } int d_result = acl_delete_perm(permset, perm); if (d_result == -1) { perror("remove_access_tag_perm (acl_delete_perm)"); return -1; } /* We've only removed perm from the permset; now we have to replace the permset. */ acl_entry_t entry; int entry_result = get_access_tag_entry(path, tag, &entry); if (entry_result == -1) { perror("remove_access_tag_perm (get_access_tag_entry)"); return -1; } if (entry_result == 1) { /* Success. */ int s_result = acl_set_permset(entry, permset); if (s_result == -1) { perror("remove_access_tag_perm (acl_set_permset)"); return -1; } return 1; } else { return 0; } } int remove_access_group_obj_execute(const char* path) { return remove_access_tag_perm(path, ACL_GROUP_OBJ, ACL_EXECUTE); } 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 (has_default_mask_acl(parent)) { /* 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; } else { path_mode &= ~S_IRGRP; } if (has_default_mask_write(parent)) { path_mode |= S_IWGRP; } else { path_mode &= ~S_IWGRP; } 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. */ remove_access_group_obj_execute(path); } /* We need to determine whether or not to mask the execute bit. This applies not only to the user/group/other entries, but also to all other named entries. If the original file wasn't executable, then the result probably should not be. To determine whether or not "it was executable", we rely on the user execute bits. Obviously this should be done before we twiddle that bit. */ if (has_default_mask_execute(parent)) { if (mode_has_perm(path_mode, S_IXUSR)) { /* This just adds the group execute bit, and doesn't actually grant group execute permissions. */ path_mode |= S_IXGRP; } } else { path_mode &= ~S_IXGRP; } } else { /* It's a minimal ACL. We'll repeat for the group bits what we already did for the owner/other bits. */ if (has_default_group_obj_acl(parent)) { if (has_default_group_obj_read(parent)) { path_mode |= S_IRGRP; } else { path_mode &= ~S_IRGRP; } if (has_default_group_obj_write(parent)) { path_mode |= S_IWGRP; } else { path_mode &= ~S_IWGRP; } /* We don't want to set the execute bit on via the ACL unless it was on originally. */ if (!has_default_group_obj_execute(parent)) { path_mode &= ~S_IXGRP; } } } /* If parent has a default user ACL, apply it. */ if (has_default_user_obj_acl(parent)) { if (has_default_user_obj_read(parent)) { /* Add user read. */ path_mode |= S_IRUSR; } else { /* Remove user read. */ path_mode &= ~S_IRUSR; } if (has_default_user_obj_write(parent)) { /* Add user write. */ path_mode |= S_IWUSR; } else { /* Remove user write. */ path_mode &= ~S_IWUSR; } /* We don't want to set the execute bit on via the ACL unless it was on originally. */ if (!has_default_user_obj_execute(parent)) { /* Remove user execute. */ path_mode &= ~S_IXUSR; } } /* Do the same thing with the other perms/ACL. */ if (has_default_other_acl(parent)) { if (has_default_other_read(parent)) { path_mode |= S_IROTH; } else { path_mode &= ~S_IROTH; } if (has_default_other_write(parent)) { path_mode |= S_IWOTH; } else { path_mode &= ~S_IWOTH; } /* We don't want to set the execute bit on via the ACL unless it was on originally. */ if (!has_default_other_execute(parent)) { path_mode &= ~S_IXOTH; } } 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) { return EXIT_SUCCESS; } else { return EXIT_FAILURE; } }