/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2011 Nexenta Systems, Inc. All rights reserved. * * Copyright 2022 RackTop Systems, Inc. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ACL_PATH 0 #define ACL_FD 1 typedef union { const char *file; int fd; } acl_inp; /* * Determine whether a file has a trivial ACL * returns: 0 = trivial * 1 = nontrivial * <0 some other system failure, such as ENOENT or EPERM */ int acl_trivial(const char *filename) { int acl_flavor; int aclcnt; int cntcmd; int val = 0; ace_t *acep; acl_flavor = pathconf(filename, _PC_ACL_ENABLED); if (acl_flavor == _ACL_ACE_ENABLED) cntcmd = ACE_GETACLCNT; else cntcmd = GETACLCNT; aclcnt = acl(filename, cntcmd, 0, NULL); if (aclcnt > 0) { if (acl_flavor == _ACL_ACE_ENABLED) { acep = malloc(sizeof (ace_t) * aclcnt); if (acep == NULL) return (-1); if (acl(filename, ACE_GETACL, aclcnt, acep) < 0) { free(acep); return (-1); } val = ace_trivial(acep, aclcnt); free(acep); } else if (aclcnt > MIN_ACL_ENTRIES) val = 1; } return (val); } static int cacl_get(acl_inp inp, int get_flag, int type, acl_t **aclp) { const char *fname; int fd; int ace_acl = 0; int error; int getcmd, cntcmd; acl_t *acl_info; int save_errno; int stat_error; struct stat64 statbuf; *aclp = NULL; if (type == ACL_PATH) { fname = inp.file; ace_acl = pathconf(fname, _PC_ACL_ENABLED); } else { fd = inp.fd; ace_acl = fpathconf(fd, _PC_ACL_ENABLED); } /* * if acl's aren't supported then * send it through the old GETACL interface */ if (ace_acl == 0 || ace_acl == -1) { ace_acl = _ACL_ACLENT_ENABLED; } if (ace_acl & _ACL_ACE_ENABLED) { cntcmd = ACE_GETACLCNT; getcmd = ACE_GETACL; acl_info = acl_alloc(ACE_T); } else { cntcmd = GETACLCNT; getcmd = GETACL; acl_info = acl_alloc(ACLENT_T); } if (acl_info == NULL) return (-1); if (type == ACL_PATH) { acl_info->acl_cnt = acl(fname, cntcmd, 0, NULL); } else { acl_info->acl_cnt = facl(fd, cntcmd, 0, NULL); } save_errno = errno; if (acl_info->acl_cnt < 0) { acl_free(acl_info); errno = save_errno; return (-1); } if (acl_info->acl_cnt == 0) { acl_free(acl_info); errno = save_errno; return (0); } acl_info->acl_aclp = malloc(acl_info->acl_cnt * acl_info->acl_entry_size); save_errno = errno; if (acl_info->acl_aclp == NULL) { acl_free(acl_info); errno = save_errno; return (-1); } if (type == ACL_PATH) { stat_error = stat64(fname, &statbuf); error = acl(fname, getcmd, acl_info->acl_cnt, acl_info->acl_aclp); } else { stat_error = fstat64(fd, &statbuf); error = facl(fd, getcmd, acl_info->acl_cnt, acl_info->acl_aclp); } save_errno = errno; if (error == -1) { acl_free(acl_info); errno = save_errno; return (-1); } if (stat_error == 0) { acl_info->acl_flags = (S_ISDIR(statbuf.st_mode) ? ACL_IS_DIR : 0); } else acl_info->acl_flags = 0; switch (acl_info->acl_type) { case ACLENT_T: if (acl_info->acl_cnt <= MIN_ACL_ENTRIES) acl_info->acl_flags |= ACL_IS_TRIVIAL; break; case ACE_T: if (ace_trivial(acl_info->acl_aclp, acl_info->acl_cnt) == 0) acl_info->acl_flags |= ACL_IS_TRIVIAL; break; default: errno = EINVAL; acl_free(acl_info); return (-1); } if ((acl_info->acl_flags & ACL_IS_TRIVIAL) && (get_flag & ACL_NO_TRIVIAL)) { acl_free(acl_info); errno = 0; return (0); } *aclp = acl_info; return (0); } /* * return -1 on failure, otherwise the number of acl * entries is returned */ int acl_get(const char *path, int get_flag, acl_t **aclp) { acl_inp acl_inp; acl_inp.file = path; return (cacl_get(acl_inp, get_flag, ACL_PATH, aclp)); } int facl_get(int fd, int get_flag, acl_t **aclp) { acl_inp acl_inp; acl_inp.fd = fd; return (cacl_get(acl_inp, get_flag, ACL_FD, aclp)); } /* * Set an ACL, translates acl to ace_t when appropriate. */ static int cacl_set(acl_inp *acl_inp, acl_t *aclp, int type) { int error = 0; int acl_flavor_target; struct stat64 statbuf; int stat_error; int isdir; if (type == ACL_PATH) { stat_error = stat64(acl_inp->file, &statbuf); if (stat_error) return (-1); acl_flavor_target = pathconf(acl_inp->file, _PC_ACL_ENABLED); } else { stat_error = fstat64(acl_inp->fd, &statbuf); if (stat_error) return (-1); acl_flavor_target = fpathconf(acl_inp->fd, _PC_ACL_ENABLED); } /* * If target returns an error or 0 from pathconf call then * fall back to UFS/POSIX Draft interface. * In the case of 0 we will then fail in either acl(2) or * acl_translate(). We could erroneously get 0 back from * a file system that is using fs_pathconf() and not answering * the _PC_ACL_ENABLED question itself. */ if (acl_flavor_target == 0 || acl_flavor_target == -1) acl_flavor_target = _ACL_ACLENT_ENABLED; isdir = S_ISDIR(statbuf.st_mode); if ((error = acl_translate(aclp, acl_flavor_target, isdir, statbuf.st_uid, statbuf.st_gid)) != 0) { return (error); } if (type == ACL_PATH) { error = acl(acl_inp->file, (aclp->acl_type == ACE_T) ? ACE_SETACL : SETACL, aclp->acl_cnt, aclp->acl_aclp); } else { error = facl(acl_inp->fd, (aclp->acl_type == ACE_T) ? ACE_SETACL : SETACL, aclp->acl_cnt, aclp->acl_aclp); } return (error); } int acl_set(const char *path, acl_t *aclp) { acl_inp acl_inp; acl_inp.file = path; return (cacl_set(&acl_inp, aclp, ACL_PATH)); } int facl_set(int fd, acl_t *aclp) { acl_inp acl_inp; acl_inp.fd = fd; return (cacl_set(&acl_inp, aclp, ACL_FD)); } int acl_cnt(acl_t *aclp) { return (aclp->acl_cnt); } int acl_type(acl_t *aclp) { return (aclp->acl_type); } acl_t * acl_dup(acl_t *aclp) { acl_t *newaclp; newaclp = acl_alloc(aclp->acl_type); if (newaclp == NULL) return (NULL); newaclp->acl_aclp = malloc(aclp->acl_entry_size * aclp->acl_cnt); if (newaclp->acl_aclp == NULL) { acl_free(newaclp); return (NULL); } (void) memcpy(newaclp->acl_aclp, aclp->acl_aclp, aclp->acl_entry_size * aclp->acl_cnt); newaclp->acl_cnt = aclp->acl_cnt; return (newaclp); } int acl_flags(acl_t *aclp) { return (aclp->acl_flags); } void * acl_data(acl_t *aclp) { return (aclp->acl_aclp); } /* * Take an acl array and build an acl_t. */ acl_t * acl_to_aclp(enum acl_type type, void *acl, int count) { acl_t *aclp; aclp = acl_alloc(type); if (aclp == NULL) return (aclp); aclp->acl_aclp = acl; aclp->acl_cnt = count; return (aclp); } /* * Remove an ACL from a file and create a trivial ACL based * off of the mode argument. After acl has been set owner/group * are updated to match owner,group arguments */ int acl_strip(const char *file, uid_t owner, gid_t group, mode_t mode) { int error = 0; aclent_t min_acl[MIN_ACL_ENTRIES]; ace_t *min_ace_acl; int acl_flavor; int aclcnt; struct stat64 statbuf; acl_flavor = pathconf(file, _PC_ACL_ENABLED); if (stat64(file, &statbuf) != 0) { error = 1; return (error); } /* * force it through aclent flavor when file system doesn't * understand question */ if (acl_flavor == 0 || acl_flavor == -1) acl_flavor = _ACL_ACLENT_ENABLED; if (acl_flavor & _ACL_ACLENT_ENABLED) { min_acl[0].a_type = USER_OBJ; min_acl[0].a_id = owner; min_acl[0].a_perm = ((mode & 0700) >> 6); min_acl[1].a_type = GROUP_OBJ; min_acl[1].a_id = group; min_acl[1].a_perm = ((mode & 0070) >> 3); min_acl[2].a_type = CLASS_OBJ; min_acl[2].a_id = (uid_t)-1; min_acl[2].a_perm = ((mode & 0070) >> 3); min_acl[3].a_type = OTHER_OBJ; min_acl[3].a_id = (uid_t)-1; min_acl[3].a_perm = (mode & 0007); aclcnt = 4; error = acl(file, SETACL, aclcnt, min_acl); } else if (acl_flavor & _ACL_ACE_ENABLED) { if ((error = acl_trivial_create(mode, S_ISDIR(statbuf.st_mode), &min_ace_acl, &aclcnt)) != 0) return (error); error = acl(file, ACE_SETACL, aclcnt, min_ace_acl); free(min_ace_acl); } else { errno = EINVAL; error = 1; } if (error == 0) error = chown(file, owner, group); return (error); } static int ace_match(void *entry1, void *entry2) { ace_t *p1 = (ace_t *)entry1; ace_t *p2 = (ace_t *)entry2; ace_t ace1, ace2; ace1 = *p1; ace2 = *p2; /* * Need to fixup who field for abstrations for * accurate comparison, since field is undefined. */ if (ace1.a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE)) ace1.a_who = (uid_t)-1; if (ace2.a_flags & (ACE_OWNER|ACE_GROUP|ACE_EVERYONE)) ace2.a_who = (uid_t)-1; return (memcmp(&ace1, &ace2, sizeof (ace_t))); } static int aclent_match(void *entry1, void *entry2) { aclent_t *aclent1 = (aclent_t *)entry1; aclent_t *aclent2 = (aclent_t *)entry2; return (memcmp(aclent1, aclent2, sizeof (aclent_t))); } /* * Find acl entries in acl that correspond to removeacl. Search * is started from slot. The flag argument indicates whether to * remove all matches or just the first match. */ int acl_removeentries(acl_t *acl, acl_t *removeacl, int start_slot, int flag) { int i, j; int match; int (*acl_match)(void *acl1, void *acl2); void *acl_entry, *remove_entry; void *start; int found = 0; if (flag != ACL_REMOVE_ALL && flag != ACL_REMOVE_FIRST) flag = ACL_REMOVE_FIRST; if (acl == NULL || removeacl == NULL) return (EACL_NO_ACL_ENTRY); if (acl->acl_type != removeacl->acl_type) return (EACL_DIFF_TYPE); if (acl->acl_type == ACLENT_T) acl_match = aclent_match; else acl_match = ace_match; for (i = 0, remove_entry = removeacl->acl_aclp; i != removeacl->acl_cnt; i++) { j = 0; acl_entry = (char *)acl->acl_aclp + (acl->acl_entry_size * start_slot); for (;;) { match = acl_match(acl_entry, remove_entry); if (match == 0) { found++; /* avoid memmove if last entry */ if (acl->acl_cnt == (j + 1)) { acl->acl_cnt--; break; } start = (char *)acl_entry + acl->acl_entry_size; (void) memmove(acl_entry, start, acl->acl_entry_size * (acl->acl_cnt-- - (j + 1))); if (flag == ACL_REMOVE_FIRST) break; /* * List has changed, just continue so this * slot gets checked with it's new contents. */ continue; } acl_entry = ((char *)acl_entry + acl->acl_entry_size); if (++j >= acl->acl_cnt) { break; } } remove_entry = (char *)remove_entry + removeacl->acl_entry_size; } return ((found == 0) ? EACL_NO_ACL_ENTRY : 0); } /* * Replace entires entries in acl1 with the corresponding entries * in newentries. The where argument specifies where to begin * the replacement. If the where argument is 1 greater than the * number of acl entries in acl1 then they are appended. If the * where argument is 2+ greater than the number of acl entries then * EACL_INVALID_SLOT is returned. */ int acl_modifyentries(acl_t *acl1, acl_t *newentries, int where) { int slot; int slots_needed; int slots_left; int newsize; if (acl1 == NULL || newentries == NULL) return (EACL_NO_ACL_ENTRY); if (where < 0 || where >= acl1->acl_cnt) return (EACL_INVALID_SLOT); if (acl1->acl_type != newentries->acl_type) return (EACL_DIFF_TYPE); slot = where; slots_left = acl1->acl_cnt - slot + 1; if (slots_left < newentries->acl_cnt) { slots_needed = newentries->acl_cnt - slots_left; newsize = (acl1->acl_entry_size * acl1->acl_cnt) + (acl1->acl_entry_size * slots_needed); acl1->acl_aclp = realloc(acl1->acl_aclp, newsize); if (acl1->acl_aclp == NULL) return (-1); } (void) memcpy((char *)acl1->acl_aclp + (acl1->acl_entry_size * slot), newentries->acl_aclp, newentries->acl_entry_size * newentries->acl_cnt); /* * Did ACL grow? */ if ((slot + newentries->acl_cnt) > acl1->acl_cnt) { acl1->acl_cnt = slot + newentries->acl_cnt; } return (0); } /* * Add acl2 entries into acl1. The where argument specifies where * to add the entries. */ int acl_addentries(acl_t *acl1, acl_t *acl2, int where) { int newsize; int len; void *start; void *to; if (acl1 == NULL || acl2 == NULL) return (EACL_NO_ACL_ENTRY); if (acl1->acl_type != acl2->acl_type) return (EACL_DIFF_TYPE); /* * allow where to specify 1 past last slot for an append operation * but anything greater is an error. */ if (where < 0 || where > acl1->acl_cnt) return (EACL_INVALID_SLOT); newsize = (acl2->acl_entry_size * acl2->acl_cnt) + (acl1->acl_entry_size * acl1->acl_cnt); acl1->acl_aclp = realloc(acl1->acl_aclp, newsize); if (acl1->acl_aclp == NULL) return (-1); /* * first push down entries where new ones will be inserted */ to = (void *)((char *)acl1->acl_aclp + ((where + acl2->acl_cnt) * acl1->acl_entry_size)); start = (void *)((char *)acl1->acl_aclp + where * acl1->acl_entry_size); if (where < acl1->acl_cnt) { len = (acl1->acl_cnt - where) * acl1->acl_entry_size; (void) memmove(to, start, len); } /* * now stick in new entries. */ (void) memmove(start, acl2->acl_aclp, acl2->acl_cnt * acl2->acl_entry_size); acl1->acl_cnt += acl2->acl_cnt; return (0); } /* * return text for an ACL error. */ char * acl_strerror(int errnum) { switch (errnum) { case EACL_GRP_ERROR: return (dgettext(TEXT_DOMAIN, "There is more than one group or default group entry")); case EACL_USER_ERROR: return (dgettext(TEXT_DOMAIN, "There is more than one user or default user entry")); case EACL_OTHER_ERROR: return (dgettext(TEXT_DOMAIN, "There is more than one other entry")); case EACL_CLASS_ERROR: return (dgettext(TEXT_DOMAIN, "There is more than one mask entry")); case EACL_DUPLICATE_ERROR: return (dgettext(TEXT_DOMAIN, "Duplicate user or group entries")); case EACL_MISS_ERROR: return (dgettext(TEXT_DOMAIN, "Missing user/group owner, other, mask entry")); case EACL_MEM_ERROR: return (dgettext(TEXT_DOMAIN, "Memory error")); case EACL_ENTRY_ERROR: return (dgettext(TEXT_DOMAIN, "Unrecognized entry type")); case EACL_INHERIT_ERROR: return (dgettext(TEXT_DOMAIN, "Invalid inheritance flags")); case EACL_FLAGS_ERROR: return (dgettext(TEXT_DOMAIN, "Unrecognized entry flags")); case EACL_PERM_MASK_ERROR: return (dgettext(TEXT_DOMAIN, "Invalid ACL permissions")); case EACL_COUNT_ERROR: return (dgettext(TEXT_DOMAIN, "Invalid ACL count")); case EACL_INVALID_SLOT: return (dgettext(TEXT_DOMAIN, "Invalid ACL entry number specified")); case EACL_NO_ACL_ENTRY: return (dgettext(TEXT_DOMAIN, "ACL entry doesn't exist")); case EACL_DIFF_TYPE: return (dgettext(TEXT_DOMAIN, "Different file system ACL types cannot be merged")); case EACL_INVALID_USER_GROUP: return (dgettext(TEXT_DOMAIN, "Invalid user or group")); case EACL_INVALID_STR: return (dgettext(TEXT_DOMAIN, "ACL string is invalid")); case EACL_FIELD_NOT_BLANK: return (dgettext(TEXT_DOMAIN, "Field expected to be blank")); case EACL_INVALID_ACCESS_TYPE: return (dgettext(TEXT_DOMAIN, "Invalid access type")); case EACL_UNKNOWN_DATA: return (dgettext(TEXT_DOMAIN, "Unrecognized entry")); case EACL_MISSING_FIELDS: return (dgettext(TEXT_DOMAIN, "ACL specification missing required fields")); case EACL_INHERIT_NOTDIR: return (dgettext(TEXT_DOMAIN, "Inheritance flags are only allowed on directories")); case -1: return (strerror(errno)); default: errno = EINVAL; return (dgettext(TEXT_DOMAIN, "Unknown error")); } } extern int yyinteractive; /* PRINTFLIKE1 */ void acl_error(const char *fmt, ...) { va_list va; if (yyinteractive == 0) return; va_start(va, fmt); (void) vfprintf(stderr, fmt, va); va_end(va); } typedef enum id_type { UID_TYPE, GID_TYPE, PID_TYPE } id_type_t; static int sid_to_id_impl(char *sid, id_type_t type, int *is_user, uid_t *id) { idmap_get_handle_t *get_hdl; char *rid_start; idmap_stat rv; idmap_rid_t rid; const char *errstr; idmap_stat status; int error = 1; rid_start = strrchr(sid, '-'); if (rid_start == NULL) return (error); rid = strtonum(rid_start + 1, 0, UINT32_MAX, &errstr); if (errstr != NULL) return (error); if (idmap_get_create(&get_hdl) != IDMAP_SUCCESS) return (error); /* * When these functions return success, the &status output is * indeterminate. We only care about rv==success in this caller, * so just ignore &status. */ /* We need sid prefix. Insert NUL on '-', restore it later. */ *rid_start = '\0'; switch (type) { case UID_TYPE: rv = idmap_get_uidbysid(get_hdl, sid, rid, IDMAP_REQ_FLG_USE_CACHE, id, &status); break; case GID_TYPE: rv = idmap_get_gidbysid(get_hdl, sid, rid, IDMAP_REQ_FLG_USE_CACHE, id, &status); break; case PID_TYPE: rv = idmap_get_pidbysid(get_hdl, sid, rid, IDMAP_REQ_FLG_USE_CACHE, id, is_user, &status); break; } *rid_start = '-'; /* putback character removed earlier */ if (rv == IDMAP_SUCCESS && idmap_get_mappings(get_hdl) == IDMAP_SUCCESS) { error = 0; } idmap_get_destroy(get_hdl); return (error); } int sid_to_id(char *sid, boolean_t user, uid_t *id) { char *domain_start; int error = 1; if ((domain_start = strchr(sid, '@')) == NULL) { error = sid_to_id_impl(sid, user ? UID_TYPE : GID_TYPE, NULL, id); } else { char *name = sid; idmap_stat rv; *domain_start++ = '\0'; if (user) rv = idmap_getuidbywinname(name, domain_start, IDMAP_REQ_FLG_USE_CACHE, id); else rv = idmap_getgidbywinname(name, domain_start, IDMAP_REQ_FLG_USE_CACHE, id); *--domain_start = '@'; if (rv == IDMAP_SUCCESS) error = 0; } return (error); } /* * Variant of sid_to_id() called when we don't know whether the SID * is a user or group. 2nd arg gets the type (0:group, 1:user) * Returns zero for success, 1 for errors. */ int sid_to_xid(char *sid, int *is_user, uid_t *id) { if ((strchr(sid, '@')) != NULL) return (1); return (sid_to_id_impl(sid, PID_TYPE, is_user, id)); }