#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 acl = acl_get_file(path, type); if (acl == (acl_t)NULL) { /* Follow the acl_foo convention of -1 == error. */ return 0; } int result = acl_get_entry(acl, 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_type_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(acl, 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_type_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_type_tag_entry(path, type, 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_type_tag_permset (acl_get_permset)"); return -1; } 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 desired_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. */ acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS); if (acl == (acl_t)NULL) { /* Error. */ return -1; } acl_permset_t permset; bool ps_result = get_access_tag_permset(path, desired_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 result = acl_get_entry(acl, 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("remove_access_tag_perm (acl_get_tag_type)"); return -1; } if (tag == desired_tag) { /* We found the right tag. Update the permset. */ int s_result = acl_set_permset(entry, permset); if (s_result == -1) { perror("remove_access_tag_perm (acl_set_permset)"); return -1; } int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl); if (sf_result == -1) { perror("remove_access_tag_perm (acl_set_file)"); return -1; } return 1; } result = acl_get_entry(acl, 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("remove_access_tag_perm (acl_get_entry)"); return -1; } 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 set_acl_entry(acl_t* aclp, acl_entry_t entry) { /* Update or create the given entry. */ acl_tag_t entry_tag; int gt_result = acl_get_tag_type(entry, &entry_tag); if (gt_result == -1) { perror("set_acl_entry (acl_get_tag_type)"); return -1; } acl_permset_t entry_permset; int ps_result = acl_get_permset(entry, &entry_permset); if (ps_result == -1) { perror("set_acl_entry (acl_get_permset)"); return -1; } acl_entry_t existing_entry; /* Loop through the given ACL looking for matching entries. */ int result = acl_get_entry(*aclp, ACL_FIRST_ENTRY, &existing_entry); while (result == 1) { acl_tag_t existing_tag = ACL_UNDEFINED_TAG; int tag_result = acl_get_tag_type(existing_entry, &existing_tag); if (tag_result == -1) { perror("set_acl_tag_permset (acl_get_tag_type)"); return -1; } if (existing_tag == entry_tag) { bool update_it = true; if (entry_tag == ACL_USER || entry_tag == ACL_GROUP) { void* entry_qual = acl_get_qualifier(entry); if (entry_qual == (void*)NULL) { perror("set_acl_entry (acl_get_qualifier - entry_qual)"); return -1; } void* existing_qual = acl_get_qualifier(existing_entry); if (existing_qual == (void*)NULL) { perror("set_acl_entry (acl_get_qualifier - existing_qual)"); return -1; } if (entry_tag == ACL_USER) { uid_t* u1p = (uid_t *)entry_qual; uid_t* u2p = (uid_t *)existing_qual; if (*u1p != *u2p) { update_it = false; } } else { gid_t* g1p = (gid_t *)entry_qual; gid_t* g2p = (gid_t *)existing_qual; if (*g1p != *g2p) { update_it = false; } } acl_free(entry_qual); acl_free(existing_qual); } if (update_it) { acl_permset_t existing_permset; int gep_result = acl_get_permset(existing_entry, &existing_permset); if (gep_result == -1) { perror("set_acl_entry (acl_get_permset)"); return -1; } /* If an existing entry doesn't have its execute bits set, we don't want to set them from a default ACL. Unless it's the mask entry! */ int gp_result = acl_get_perm(existing_permset, ACL_EXECUTE); if (gp_result == -1) { perror("set_acl_entry (acl_get_perm)"); return -1; } if (gp_result == 0 && existing_tag != ACL_MASK) { /* It doesn't already have execute perms */ int d_result = acl_delete_perm(entry_permset, ACL_EXECUTE); if (d_result == -1) { perror("set_acl_entry (acl_delete_perm)"); return -1; } } int s_result = acl_set_permset(existing_entry, entry_permset); if (s_result == -1) { perror("set_acl_entry (acl_set_permset)"); return -1; } return 1; } } result = acl_get_entry(*aclp, ACL_NEXT_ENTRY, &existing_entry); } /* This catches both the initial acl_get_entry and the ones at the end of the loop. */ if (result == -1) { perror("set_acl_entry (acl_get_entry)"); return -1; } /* If we've made it this far, we need to add a new entry to the ACL. */ acl_entry_t new_entry; int c_result = acl_create_entry(aclp, &new_entry); if (c_result == -1) { perror("set_acl_entry (acl_create_entry)"); return -1; } int st_result = acl_set_tag_type(new_entry, entry_tag); if (st_result == -1) { perror("set_acl_entry (acl_set_tag_type)"); return -1; } int s_result = acl_set_permset(new_entry, entry_permset); if (s_result == -1) { perror("set_acl_entry (acl_set_permset)"); return -1; } if (entry_tag == ACL_USER || entry_tag == ACL_GROUP) { /* We need to set the qualifier too. */ void* entry_qual = acl_get_qualifier(entry); if (entry_qual == (void*)NULL) { perror("set_acl_entry (acl_get_qualifier)"); return -1; } int sq_result = acl_set_qualifier(new_entry, entry_qual); if (sq_result == -1) { perror("set_acl_entry (acl_set_qualifier)"); return -1; } } return 1; } int acl_is_minimal(acl_t* acl) { /* An ACL is minimal if it has fewer than four entries. */ acl_entry_t entry; int entry_count = 0; int result = acl_get_entry(*acl, ACL_FIRST_ENTRY, &entry); while (result == 1) { entry_count++; result = acl_get_entry(*acl, ACL_NEXT_ENTRY, &entry); } if (result == -1) { perror("acl_is_minimal (acl_get_entry)"); return -1; } if (entry_count > 3) { return 1; } else { return 0; } } int any_can_execute(const char* path) { acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS); if (acl_is_minimal(&acl)) { mode_t mode = get_mode(path); if (mode & (S_IXUSR | S_IXOTH | S_IXGRP)) { return 1; } else { return 0; } } if (acl == (acl_t)NULL) { return 0; } acl_entry_t entry; int result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); while (result == 1) { acl_permset_t permset; int ps_result = acl_get_permset(entry, &permset); if (ps_result == -1) { perror("any_can_execute (acl_get_permset)"); return -1; } int gp_result = acl_get_perm(permset, ACL_EXECUTE); if (gp_result == -1) { perror("any_can_execute (acl_get_perm)"); return -1; } if (gp_result == 1) { return 1; } result = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); } if (result == -1) { perror("any_can_execute (acl_get_entry)"); return -1; } return 0; } int reapply_default_acl_ng(const char* path) { /* Really reapply 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; } 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; } int ace_result = any_can_execute(path); if (ace_result == -1) { perror("reapply_default_acl_ng (any_can_execute)"); return -1; } bool allow_exec = (bool)ace_result; acl_t defacl = acl_get_file(parent, ACL_TYPE_DEFAULT); acl_t acl = acl_get_file(path, ACL_TYPE_ACCESS); if (defacl == (acl_t)NULL || acl == (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; } /* We've got an entry/tag from the default ACL. Get its permset. */ acl_permset_t permset; int ps_result = acl_get_permset(entry, &permset); if (ps_result == -1) { perror("reapply_default_acl_ng (acl_get_permset)"); return -1; } /* If this is a default mask, fix it up. */ if (tag == ACL_MASK || 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 masked, we have to do it manually. */ int d_result = acl_delete_perm(permset, ACL_EXECUTE); if (d_result == -1) { perror("reapply_default_acl_ng (acl_delete_perm)"); return -1; } int sp_result = acl_set_permset(entry, permset); if (sp_result == -1) { perror("reapply_default_acl_ng (acl_set_permset)"); return -1; } } } /* Finally, add the permset to the access ACL. */ int set_result = set_acl_entry(&acl, entry); if (set_result == -1) { perror("reapply_default_acl_ng (set_acl_entry)"); return -1; } result = acl_get_entry(defacl, ACL_NEXT_ENTRY, &entry); } /* Catches the first acl_get_entry as well as the ones at the end of the loop. */ if (result == -1) { perror("reapply_default_acl_ng (acl_get_entry)"); return -1; } int sf_result = acl_set_file(path, ACL_TYPE_ACCESS, acl); if (sf_result == -1) { perror("reapply_default_acl_ng (acl_set_file)"); return -1; } return 1; } int main(int argc, char* argv[]) { const char* target = argv[1]; bool result = reapply_default_acl_ng(target); if (result) { return EXIT_SUCCESS; } else { return EXIT_FAILURE; } }