/* sec_posixacl.cc: POSIX ACL functions based on Solaris ACLs. This file is part of Cygwin. This software is a copyrighted work licensed under the terms of the Cygwin license. Please consult the file "CYGWIN_LICENSE" for details. */ #include "winsup.h" #include #include "cygerrno.h" #include "path.h" #include "fhandler.h" #include "dtable.h" #include "cygheap.h" #include "tls_pbuf.h" #include "sec_posixacl.h" #include #define _ENTRY_SIZE(_cnt) ((_cnt) * sizeof (aclent_t)) #define _ACL_SIZE(_cnt) (sizeof (__acl_ext_t) + _ENTRY_SIZE (_cnt)) #define ACL_SIZE(_acl) ({ acl_t __acl = _acl; \ _ACL_SIZE((__acl)->count - (__acl)->deleted); \ }) #define ACL_PERM_MASK (ACL_READ | ACL_WRITE | ACL_EXECUTE) extern "C" acl_t acl_init (int count) { acl_t acl; if (count < 0 || count > UINT16_MAX) { set_errno (EINVAL); return NULL; } acl = (acl_t) calloc (1, sizeof (__acl_t)); if (!acl) return NULL; acl->entry = (aclent_t *) calloc (count, sizeof (aclent_t)); if (!acl->entry) { free (acl); return NULL; } acl->magic = ACL_MAGIC; acl->max_count = count; return acl; } extern "C" acl_t acl_dup (acl_t acl) { __try { acl_t new_acl = acl_init (acl->max_count); if (new_acl) { uint16_t new_idx = 0; for (uint16_t idx = 0; idx < acl->count; ++idx) if (acl->entry[idx].a_type != ACL_DELETED_TAG) new_acl->entry[new_idx++] = acl->entry[idx]; new_acl->magic = ACL_MAGIC; new_acl->count = new_idx; return new_acl; } } __except (EINVAL) {} __endtry return NULL; } extern "C" int acl_free (void *obj_p) { __try { acl_t acl; if (obj_p) { if (malloc_usable_size (obj_p) >= sizeof (__acl_t)) { acl = (acl_t) obj_p; if (acl->magic == ACL_MAGIC) free (acl->entry); } free (obj_p); return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_valid (acl_t acl) { __try { if (!(__aclcheck (acl->entry, acl->count, NULL, true))) return 0; set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_copy_entry (acl_entry_t dest_d, acl_entry_t src_d) { __try { uint16_t d_idx, s_idx; acl_t d_acl, s_acl; d_acl = __from_entry (dest_d, d_idx); s_acl = __from_entry (src_d, s_idx); if (d_acl && s_acl) { d_acl->entry[d_idx] = s_acl->entry[s_idx]; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_create_entry (acl_t *acl_p, acl_entry_t *entry_p) { __try { acl_t acl = *acl_p; uint16_t idx; if (acl->deleted > 0) { for (idx = 0; idx < acl->count; ++idx) if (acl->entry[idx].a_type == ACL_DELETED_TAG) { *entry_p = __to_entry (acl, idx); --acl->deleted; goto fill_entry; } } if (acl->count >= acl->max_count) { aclent_t *new_e; new_e = (aclent_t *) realloc (acl->entry, _ENTRY_SIZE (acl->max_count + 1)); if (!new_e) __leave; acl->entry = new_e; ++acl->max_count; } idx = acl->count++; *entry_p = __to_entry (acl, idx); fill_entry: acl->entry[idx].a_type = ACL_UNDEFINED_TAG; acl->entry[idx].a_id = ACL_UNDEFINED_ID; acl->entry[idx].a_perm = 0; return 0; } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_delete_entry (acl_t acl, acl_entry_t entry_d) { __try { acl_t acl_p; uint16_t idx; acl_p = __from_entry (entry_d, idx); if (acl_p == acl) { acl_p->entry[idx].a_type = ACL_DELETED_TAG; acl_p->entry[idx].a_id = ACL_UNDEFINED_ID; acl_p->entry[idx].a_perm = 0; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_get_entry (acl_t acl, int entry_id, acl_entry_t *entry_p) { __try { uint16_t idx; if (entry_id == ACL_FIRST_ENTRY) acl->next = 0; else if (entry_id != ACL_NEXT_ENTRY) { set_errno (EINVAL); __leave; } do { if (acl->next >= acl->count) return 0; idx = acl->next++; } while (acl->entry[idx].a_type == ACL_DELETED_TAG); *entry_p = __to_entry (acl, idx); return 1; } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_calc_mask (acl_t *acl_p) { __try { acl_t acl = *acl_p; mode_t mask = 0; mask = __aclcalcmask (acl->entry, acl->count); /* If __aclcalcmask returns -1 we're done. Otherwise create a mask entry here. */ if (mask != (acl_perm_t) -1) { acl_entry_t entry_d; uint16_t mask_idx; if (acl_create_entry (&acl, &entry_d) < 0) __leave; if (!__from_entry (entry_d, mask_idx)) { set_errno (EINVAL); __leave; } acl->entry[mask_idx].a_type = ACL_MASK; acl->entry[mask_idx].a_id = ACL_UNDEFINED_ID; acl->entry[mask_idx].a_perm = mask; *acl_p = acl; } return 0; } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_clear_perms (acl_permset_t permset_d) { __try { acl_t acl; uint16_t idx; acl = __from_permset (permset_d, idx); if (acl) { acl->entry[idx].a_perm = 0; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_add_perm (acl_permset_t permset_d, acl_perm_t perm) { __try { acl_t acl; uint16_t idx; acl = __from_permset (permset_d, idx); if (acl && !(perm & ~ACL_PERM_MASK)) { acl->entry[idx].a_perm |= perm; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_delete_perm (acl_permset_t permset_d, acl_perm_t perm) { __try { acl_t acl; uint16_t idx; acl = __from_permset (permset_d, idx); if (acl && !(perm & ~ACL_PERM_MASK)) { acl->entry[idx].a_perm &= ~perm; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_get_permset (acl_entry_t entry_d, acl_permset_t *permset_p) { __try { acl_t acl; uint16_t idx; acl = __from_entry (entry_d, idx); if (acl) { *permset_p = (acl_permset_t) entry_d; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_set_permset (acl_entry_t entry_d, acl_permset_t permset_d) { __try { acl_t acl_e, acl_p; uint16_t idx_e, idx_p; acl_e = __from_entry (entry_d, idx_e); acl_p = __from_permset (permset_d, idx_p); if (acl_e && acl_p && !(acl_p->entry[idx_p].a_perm & ~ACL_PERM_MASK)) { acl_e->entry[idx_e].a_perm = acl_p->entry[idx_p].a_perm; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" void * acl_get_qualifier (acl_entry_t entry_d) { __try { acl_t acl; uint16_t idx; acl = __from_entry (entry_d, idx); if (acl && (acl->entry[idx].a_type & (ACL_USER | ACL_GROUP))) { id_t *id = (id_t *) malloc (sizeof (id_t)); if (id) { *id = acl->entry[idx].a_id; return (void *) id; } } else set_errno (EINVAL); } __except (EINVAL) {} __endtry return NULL; } extern "C" int acl_set_qualifier (acl_entry_t entry_d, const void *qualifier_p) { __try { acl_t acl; uint16_t idx; acl = __from_entry (entry_d, idx); if (acl && (acl->entry[idx].a_type & (ACL_USER | ACL_GROUP))) { acl->entry[idx].a_id = *(id_t *) qualifier_p; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_get_tag_type (acl_entry_t entry_d, acl_tag_t *tag_type_p) { __try { acl_t acl; uint16_t idx; acl = __from_entry (entry_d, idx); if (acl) { *tag_type_p = acl->entry[idx].a_type; return 0; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_set_tag_type (acl_entry_t entry_d, acl_tag_t tag_type) { __try { acl_t acl; uint16_t idx; acl = __from_entry (entry_d, idx); if (acl) switch (tag_type) { case ACL_USER_OBJ: case ACL_GROUP_OBJ: case ACL_MASK: case ACL_OTHER: acl->entry[idx].a_id = ACL_UNDEFINED_ID; /*FALLTHRU*/ case ACL_USER: case ACL_GROUP: acl->entry[idx].a_type = tag_type; return 0; default: break; } set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" ssize_t acl_size (acl_t acl) { __try { return (ssize_t) ACL_SIZE (acl); } __except (EINVAL) {} __endtry return -1; } extern "C" ssize_t acl_copy_ext (void *buf_p, acl_t acl, ssize_t size) { __try { ssize_t ext_size = (ssize_t) ACL_SIZE (acl); if (size <= 0) set_errno (EINVAL); else if (ext_size > size) set_errno (ERANGE); else { uint16_t ext_idx = 0; __acl_ext_t *acl_ext = (__acl_ext_t *) buf_p; acl_ext->count = acl->count - acl->deleted; for (uint16_t idx = 0; idx < acl->count; ++idx) if (acl->entry[idx].a_type != ACL_DELETED_TAG) acl_ext->entry[ext_idx++] = acl->entry[idx]; return ext_size; } } __except (EINVAL) {} __endtry return -1; } extern "C" acl_t acl_copy_int (const void *buf_p) { __try { acl_t acl; __acl_ext_t *acl_ext = (__acl_ext_t *) buf_p; acl = acl_init (acl_ext->count); if (acl) { memcpy (acl->entry, acl_ext->entry, _ENTRY_SIZE (acl_ext->count)); acl->count = acl_ext->count; return acl; } } __except (EINVAL) {} __endtry return NULL; } extern "C" acl_t acl_from_text (const char *buf_p) { __try { return (acl_t) __aclfromtext (buf_p, NULL, true); } __except (EINVAL) {} __endtry return NULL; } extern "C" char * acl_to_text (acl_t acl, ssize_t *len_p) { __try { char *ret = __acltotext (acl->entry, acl->count, NULL, '\n', TEXT_IS_POSIX | TEXT_SOME_EFFECTIVE | TEXT_END_SEPARATOR); if (ret && len_p) *len_p = strlen (ret); return ret; } __except (EINVAL) {} __endtry return NULL; } acl_t __reg2 fhandler_base::acl_get (acl_type_t type) { set_errno (ENOTSUP); return NULL; } acl_t __reg2 fhandler_disk_file::acl_get (acl_type_t type) { acl_t acl = NULL; int oret = 0; __try { tmp_pathbuf tp; aclent_t *aclbufp; uint16_t cnt, access_cnt; if (!pc.has_acls ()) { set_errno (ENOTSUP); __leave; } if (type == ACL_TYPE_DEFAULT && !pc.isdir ()) { set_errno (ENOTDIR); __leave; } aclbufp = (aclent_t *) tp.c_get (); if (!get_handle ()) { query_open (query_read_control); if (!(oret = open (O_BINARY, 0))) __leave; } cnt = facl (GETACL, MAX_ACL_ENTRIES, aclbufp); if (cnt < 0) __leave; /* Set access_cnt to number of non-default entries from file ACL. */ if (!pc.isdir ()) access_cnt = cnt; else for (access_cnt = 0; access_cnt < cnt; ++access_cnt) if (aclbufp[access_cnt].a_type & ACL_DEFAULT) break; if (type == ACL_TYPE_ACCESS) { acl = acl_init (access_cnt); if (!acl) __leave; memcpy (acl->entry, aclbufp, _ENTRY_SIZE (access_cnt)); acl->count = access_cnt; } else { cnt -= access_cnt; acl = acl_init (cnt); if (acl && cnt) { memcpy (acl->entry, aclbufp + access_cnt, _ENTRY_SIZE (cnt)); acl->count = cnt; for (cnt = 0; cnt < acl->count; ++cnt) acl->entry[cnt].a_type &= ~ACL_DEFAULT; } } } __except (EINVAL) {} __endtry if (oret) close_fs (); return acl; } extern "C" acl_t acl_get_fd (int fd) { cygheap_fdget cfd (fd); if (cfd < 0) return NULL; return cfd->acl_get (ACL_TYPE_ACCESS); } extern "C" acl_t acl_get_file (const char *path_p, acl_type_t type) { if (type != ACL_TYPE_ACCESS && type != ACL_TYPE_DEFAULT) { set_errno (EINVAL); return NULL; } fhandler_base *fh; if (!(fh = build_fh_name (path_p, PC_SYM_FOLLOW, stat_suffixes))) return NULL; if (fh->error ()) { set_errno (fh->error ()); return NULL; } acl_t acl = fh->acl_get (type); delete fh; return acl; } int __reg3 fhandler_base::acl_set (acl_t acl, acl_type_t type) { set_errno (ENOTSUP); return -1; } int __reg3 fhandler_disk_file::acl_set (acl_t acl, acl_type_t type) { int ret = -1; int oret = 0; __try { tmp_pathbuf tp; aclent_t *aclbufp, *aclbuf_from_file; uint16_t cnt, cnt_from_file, access_cnt; if (!pc.has_acls ()) { set_errno (ENOTSUP); __leave; } if (type == ACL_TYPE_DEFAULT && !pc.isdir ()) { set_errno (ENOTDIR); __leave; } if (acl->count > MAX_ACL_ENTRIES) { set_errno (EINVAL); __leave; } aclbuf_from_file = (aclent_t *) tp.c_get (); if (!get_handle ()) { query_open (query_write_dac); if (!(oret = open (O_BINARY, 0))) __leave; } cnt_from_file = facl (GETACL, MAX_ACL_ENTRIES, aclbuf_from_file); if (cnt_from_file < 0) __leave; aclbufp = (aclent_t *) tp.c_get (); /* Set access_cnt to number of non-default entries from file ACL. */ if (!pc.isdir ()) access_cnt = cnt_from_file; else for (access_cnt = 0; access_cnt < cnt_from_file; ++access_cnt) if (aclbuf_from_file[access_cnt].a_type & ACL_DEFAULT) break; if (type == ACL_TYPE_ACCESS) { /* Check if the number of ACEs fits into the buffer. */ if (acl->count - acl->deleted + cnt_from_file - access_cnt > MAX_ACL_ENTRIES) { set_errno (EINVAL); __leave; } /* Copy the new ACL entries. */ cnt = 0; for (uint16_t idx = 0; idx < acl->count; ++idx) if (acl->entry[idx].a_type != ACL_DELETED_TAG) aclbufp[cnt++] = acl->entry[idx]; /* Append default ACL from file, if any. */ if (access_cnt < cnt_from_file) { memcpy (aclbufp + cnt, aclbuf_from_file + access_cnt, _ENTRY_SIZE (cnt_from_file - access_cnt)); cnt += cnt_from_file - access_cnt; } } else { /* Check if the number of ACEs fits into the buffer. */ if (acl->count - acl->deleted + access_cnt > MAX_ACL_ENTRIES) { set_errno (EINVAL); __leave; } /* Copy non-default entries from file. */ memcpy (aclbufp, aclbuf_from_file, _ENTRY_SIZE (access_cnt)); cnt = access_cnt; /* Append new default ACL entries (and add ACL_DEFAULT flag). */ for (uint16_t idx = 0; idx < acl->count; ++idx) if (acl->entry[idx].a_type != ACL_DELETED_TAG) { aclbufp[cnt] = acl->entry[idx]; aclbufp[cnt++].a_type |= ACL_DEFAULT; } } ret = facl (SETACL, cnt, aclbufp); } __except (EINVAL) {} __endtry if (oret) close_fs (); return ret; } extern "C" int acl_set_fd (int fd, acl_t acl) { cygheap_fdget cfd (fd); if (cfd < 0) return -1; return cfd->acl_set (acl, ACL_TYPE_ACCESS); } extern "C" int acl_set_file(const char *path_p, acl_type_t type, acl_t acl) { if (type != ACL_TYPE_ACCESS && type != ACL_TYPE_DEFAULT) { set_errno (EINVAL); return -1; } fhandler_base *fh; if (!(fh = build_fh_name (path_p, PC_SYM_FOLLOW, stat_suffixes))) return -1; if (fh->error ()) { set_errno (fh->error ()); return -1; } int ret = fh->acl_set (acl, type); delete fh; return ret; } extern "C" int acl_delete_def_file (const char *path_p) { acl_t acl = (acl_t) alloca (sizeof (struct __acl_t)); acl->count = acl->max_count = acl->next = 0; if (!acl) return -1; return acl_set_file(path_p, ACL_TYPE_DEFAULT, acl); } /* libacl extensions */ extern "C" int acl_check (acl_t acl, int *last) { __try { int ret = 0; if (acl->count != 0) { ret = __aclcheck (acl->entry, acl->count, last, true); switch (ret) { case GRP_ERROR: case USER_ERROR: case CLASS_ERROR: case OTHER_ERROR: ret = ACL_MULTI_ERROR; break; default: break; } } return ret; } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_cmp (acl_t acl1, acl_t acl2) { int ret = -1; __try { tmp_pathbuf tp; __acl_ext_t *acl1d = (__acl_ext_t *) tp.c_get (); __acl_ext_t *acl2d = (__acl_ext_t *) tp.c_get (); if (acl_copy_ext (acl1d, acl1, NT_MAX_PATH) < 0) __leave; if (acl_copy_ext (acl2d, acl2, NT_MAX_PATH) < 0) __leave; if (acl1d->count != acl2d->count) return 1; if (__aclsort (acl1d->count, acl1d->entry)) __leave; if (__aclsort (acl2d->count, acl2d->entry)) __leave; for (int idx = 0; idx < acl1d->count; ++idx) { if (acl1d->entry[idx].a_type != acl2d->entry[idx].a_type) { ret = 1; __leave; } if ((acl1d->entry[idx].a_perm & ACL_PERM_MASK) != (acl2d->entry[idx].a_perm & ACL_PERM_MASK)) { ret = 1; __leave; } if ((acl1d->entry[idx].a_type & (ACL_USER | ACL_GROUP)) && acl1d->entry[idx].a_id != acl2d->entry[idx].a_id) { ret = 1; __leave; } } ret = 0; } __except (EINVAL) {} __endtry return ret; } extern "C" int acl_entries (acl_t acl) { __try { return acl->count - acl->deleted; } __except (EINVAL) {} __endtry return -1; } extern "C" int acl_equiv_mode (acl_t acl, mode_t *mode_p) { __try { if (acl->count != 3) { set_errno (EINVAL); __leave; } int u_idx = -1, g_idx = -1, o_idx = -1; for (int idx = 0; idx < 3; ++idx) switch (acl->entry[idx].a_type) { case ACL_USER_OBJ: u_idx = idx; break; case ACL_GROUP_OBJ: g_idx = idx; break; case ACL_OTHER: o_idx = idx; break; } if (u_idx == -1 || g_idx == -1 || o_idx == -1) { set_errno (EINVAL); __leave; } if (mode_p) *mode_p = ((acl->entry[u_idx].a_perm & ACL_PERM_MASK) << 6) | ((acl->entry[g_idx].a_perm & ACL_PERM_MASK) << 3) | (acl->entry[o_idx].a_perm & ACL_PERM_MASK); return 0; } __except (EINVAL) {} __endtry return -1; } static const char *acl_err_txt[] = { "Multiple entries", "Duplicate entries", "Invalid entry type", "Missing or wrong entry" }; extern "C" const char * acl_error (int code) { if (code < ACL_MULTI_ERROR || code > ACL_MISS_ERROR) return NULL; return acl_err_txt[code - ACL_MULTI_ERROR]; } static int __acl_extended_fh (fhandler_base *fh) { int ret = -1; if (!fh->pc.has_acls ()) set_errno (ENOTSUP); else { ret = fh->facl (GETACLCNT, 0, NULL); if (ret >= 0) ret = (ret > MIN_ACL_ENTRIES) ? 1 : 0; } return ret; } extern "C" int acl_extended_fd (int fd) { __try { cygheap_fdget cfd (fd); if (cfd < 0) __leave; return __acl_extended_fh (cfd); } __except (EBADF) {} __endtry return -1; } static int __acl_extended_file (path_conv &pc) { int ret = -1; __try { if (pc.error) set_errno (pc.error); else if (!pc.exists ()) set_errno (ENOENT); else { fhandler_base *fh; if (!(fh = build_fh_pc (pc))) __leave; ret = __acl_extended_fh (fh); delete fh; } } __except (EFAULT) {} __endtry return ret; } extern "C" int acl_extended_file (const char *path_p) { path_conv pc (path_p, PC_SYM_FOLLOW | PC_POSIX | PC_KEEP_HANDLE, stat_suffixes); return __acl_extended_file (pc); } extern "C" int acl_extended_file_nofollow (const char *path_p) { path_conv pc (path_p, PC_SYM_NOFOLLOW | PC_POSIX | PC_KEEP_HANDLE, stat_suffixes); return __acl_extended_file (pc); } extern "C" acl_t acl_from_mode (mode_t mode) { acl_t acl = acl_init (MIN_ACL_ENTRIES); if (!acl) return NULL; acl->count = 3; acl->entry[0].a_type = USER_OBJ; acl->entry[0].a_id = ACL_UNDEFINED_ID; acl->entry[0].a_perm = (mode >> 6) & ACL_PERM_MASK; acl->entry[1].a_type = GROUP_OBJ; acl->entry[1].a_id = ACL_UNDEFINED_ID; acl->entry[1].a_perm = (mode >> 3) & ACL_PERM_MASK; acl->entry[2].a_type = OTHER_OBJ; acl->entry[2].a_id = ACL_UNDEFINED_ID; acl->entry[2].a_perm = mode & ACL_PERM_MASK; return acl; } extern "C" int acl_get_perm (acl_permset_t permset_d, acl_perm_t perm) { __try { acl_t acl; uint16_t idx; acl = __from_permset (permset_d, idx); if (acl && !(perm & ~ACL_PERM_MASK)) return (~acl->entry[idx].a_perm & perm) ? 0 : 1; set_errno (EINVAL); } __except (EINVAL) {} __endtry return -1; } extern "C" char * acl_to_any_text (acl_t acl, const char *prefix, char separator, int options) { __try { return __acltotext (acl->entry, acl->count, prefix, separator, TEXT_IS_POSIX | options); } __except (EINVAL) {} __endtry return NULL; }